Pipe9x
Pipes with overlapped-style I/O on Windows 9x
pipe9x.c
1/* Pipe9X - Anonymous pipes with overlapped I/O semantics on Windows 9x
2 * Copyright (C) 2024 Daniel Collins <solemnwarning@solemnwarning.net>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * 3. Neither the name of the copyright holder nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29*/
30
31#include <assert.h>
32#include <stdlib.h>
33#include <string.h>
34#include <stdio.h>
35
36#include <windows.h>
37
38#include "pipe9x.h"
39
43struct PipeData
44{
45 HANDLE pipe;
46 unsigned char *rw_buf;
47 size_t rw_buf_size;
48 OVERLAPPED overlapped;
49 BOOL pending;
50
51 BOOL use_thread_fallback;
52 HANDLE io_thread;
53 DWORD bytes_transferred;
54 DWORD io_result;
55};
56
60struct _PipeReadHandle
61{
62 struct PipeData data;
63};
64
68struct _PipeWriteHandle
69{
70 struct PipeData data;
71};
72
74 PipeReadHandle *prh_out,
75 size_t read_size,
76 BOOL read_inherit,
77 PipeWriteHandle *pwh_out,
78 size_t write_size,
79 BOOL write_inherit)
80{
81 *prh_out = NULL;
82 *pwh_out = NULL;
83
84 PipeReadHandle prh = malloc(sizeof(struct _PipeReadHandle));
85 PipeWriteHandle pwh = malloc(sizeof(struct _PipeWriteHandle));
86
87 if(prh == NULL || pwh == NULL)
88 {
89 free(pwh);
90 free(prh);
91
92 return ERROR_OUTOFMEMORY;
93 }
94
95 prh->data.pipe = INVALID_HANDLE_VALUE;
96 prh->data.rw_buf = malloc(read_size);
97 prh->data.rw_buf_size = read_size;
98 prh->data.overlapped.hEvent = NULL;
99 prh->data.pending = FALSE;
100 prh->data.use_thread_fallback = FALSE;
101 prh->data.io_thread = NULL;
102
103 pwh->data.pipe = INVALID_HANDLE_VALUE;
104 pwh->data.rw_buf = malloc(read_size);
105 pwh->data.rw_buf_size = read_size;
106 pwh->data.overlapped.hEvent = NULL;
107 pwh->data.pending = FALSE;
108 pwh->data.use_thread_fallback = FALSE;
109 pwh->data.io_thread = NULL;
110
111 if(prh->data.rw_buf == NULL || pwh->data.rw_buf == NULL)
112 {
115
116 return ERROR_OUTOFMEMORY;
117 }
118
119 /* Create event objects used to signal overlapped I/O completion. */
120
121 prh->data.overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
122 if(prh->data.overlapped.hEvent == NULL)
123 {
124 DWORD error = GetLastError();
125
128
129 return error;
130 }
131
132 pwh->data.overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
133 if(pwh->data.overlapped.hEvent == NULL)
134 {
135 DWORD error = GetLastError();
136
139
140 return error;
141 }
142
143 /* Create named pipe to serve as the read end of the pipe.
144 *
145 * Anonymous pipes cannot be used for overlapped I/O, so we need to
146 * create a named pipe with a random name and open it.
147 */
148
149 char pipename[32];
150
151 while(prh->data.pipe == INVALID_HANDLE_VALUE)
152 {
153 strcpy(pipename, "\\\\.\\pipe\\tmp_");
154 int pnlen = strlen(pipename);
155
156 while(pnlen < (sizeof(pipename) - 1))
157 {
158 char rndchar = 'A' + (rand() % 26);
159 pipename[pnlen++] = rndchar;
160 }
161
162 pipename[pnlen] = '\0';
163
164 SECURITY_ATTRIBUTES r_secattrs = { sizeof(SECURITY_ATTRIBUTES), NULL, read_inherit };
165
166 prh->data.pipe = CreateNamedPipe(
167 pipename, /* lpName */
168 (PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED), /* dwOpenMode */
169 (PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT), /* dwPipeMode */
170 1, /* nMaxInstances */
171 read_size, /* nOutBufferSize */
172 read_size, /* nInBufferSize */
173 0, /* nDefaultTimeOut */
174 &r_secattrs); /* lpSecurityAttributes */
175
176 if(prh->data.pipe == INVALID_HANDLE_VALUE)
177 {
178 DWORD error = GetLastError();
179
180 if(error == ERROR_FILE_EXISTS)
181 {
182 /* Name already in use, loop and try another. */
183 }
184 else if(error == ERROR_CALL_NOT_IMPLEMENTED)
185 {
186 /* Okay... named pipes only exist on Windows NT, so we have to
187 * fall back to using anonymous pipes, which means we can't use
188 * overlapped I/O and have to fall back to performing read/write
189 * operations on a background thread instead.
190 *
191 * Wheeee.
192 */
193
194 if(!CreatePipe(&(prh->data.pipe), &(pwh->data.pipe), &r_secattrs, read_size))
195 {
196 error = GetLastError();
197
200
201 return error;
202 }
203
204 if(read_inherit != write_inherit)
205 {
206 fprintf(stderr, "replacing write handle (%d)\n", (int)(write_inherit));
207 HANDLE new_write_handle;
208
209 if(!DuplicateHandle(
210 GetCurrentProcess(),
211 pwh->data.pipe,
212 GetCurrentProcess(),
213 &new_write_handle,
214 0,
215 write_inherit,
216 DUPLICATE_SAME_ACCESS))
217 {
218 error = GetLastError();
219
222
223 return error;
224 }
225
226 CloseHandle(pwh->data.pipe);
227 pwh->data.pipe = new_write_handle;
228 }
229
230 prh->data.use_thread_fallback = TRUE;
231 pwh->data.use_thread_fallback = TRUE;
232
233 *prh_out = prh;
234 *pwh_out = pwh;
235
236 return ERROR_SUCCESS;
237 }
238 else{
241
242 return error;
243 }
244 }
245 }
246
247 if(!ConnectNamedPipe(prh->data.pipe, &(prh->data.overlapped)))
248 {
249 DWORD error = GetLastError();
250
251 if(error != ERROR_IO_PENDING)
252 {
255
256 return error;
257 }
258 }
259
260 /* Make a connection to the pipe to serve as the write end. */
261
262 SECURITY_ATTRIBUTES w_secattrs = { sizeof(SECURITY_ATTRIBUTES), NULL, write_inherit };
263
264 pwh->data.pipe = CreateFile(
265 pipename, /* lpFileName */
266 GENERIC_WRITE, /* dwDesiredAccess */
267 0, /* dwShareMode */
268 &w_secattrs, /* lpSecurityAttributes */
269 OPEN_EXISTING, /* dwCreationDisposition */
270 FILE_FLAG_OVERLAPPED, /* dwFlagsAndAttributes */
271 NULL); /* hTemplateFile */
272
273 if(pwh->data.pipe == INVALID_HANDLE_VALUE)
274 {
275 DWORD error = GetLastError();
276
279
280 return error;
281 }
282
283 /* Complete the connection on the read end. */
284
285 DWORD transferred_bytes;
286 if(!GetOverlappedResult(prh->data.pipe, &(prh->data.overlapped), &transferred_bytes, TRUE))
287 {
288 DWORD error = GetLastError();
289
292
293 return error;
294 }
295
296 *prh_out = prh;
297 *pwh_out = pwh;
298
299 return ERROR_SUCCESS;
300}
301
302static void _pipe9x_cleanup(struct PipeData *pd)
303{
304 if(pd->pipe != INVALID_HANDLE_VALUE)
305 {
306 CloseHandle(pd->pipe);
307 pd->pipe = NULL;
308 }
309
310 if(pd->pending)
311 {
312 if(pd->use_thread_fallback)
313 {
314 assert(pd->io_thread != NULL);
315
316 WaitForSingleObject(pd->io_thread, INFINITE);
317 CloseHandle(pd->io_thread);
318
319 pd->io_thread = NULL;
320 }
321 else{
322 assert(pd->overlapped.hEvent != NULL);
323 WaitForSingleObject(pd->overlapped.hEvent, INFINITE);
324 }
325 }
326
327 if(pd->overlapped.hEvent != NULL)
328 {
329 CloseHandle(pd->overlapped.hEvent);
330 pd->overlapped.hEvent = NULL;
331 }
332
333 free(pd->rw_buf);
334 pd->rw_buf = NULL;
335}
336
337void pipe9x_read_close(PipeReadHandle prh)
338{
339 if(prh == NULL)
340 {
341 return;
342 }
343
344 _pipe9x_cleanup(&(prh->data));
345 free(prh);
346}
347
348static DWORD WINAPI _pipe9x_read_thread(LPVOID lpParameter)
349{
350 PipeReadHandle prh = (PipeReadHandle)(lpParameter);
351
352 if(ReadFile(
353 prh->data.pipe,
354 prh->data.rw_buf,
355 prh->data.rw_buf_size,
356 &(prh->data.bytes_transferred),
357 NULL))
358 {
359 prh->data.io_result = ERROR_SUCCESS;
360 }
361 else{
362 prh->data.io_result = GetLastError();
363 }
364
365 SetEvent(prh->data.overlapped.hEvent);
366
367 return 0;
368}
369
370DWORD pipe9x_read_initiate(PipeReadHandle prh)
371{
372 assert(prh != NULL);
373
374 if(prh->data.pending)
375 {
376 return ERROR_IO_INCOMPLETE;
377 }
378
379 if(prh->data.use_thread_fallback)
380 {
381 assert(prh->data.io_thread == NULL);
382
383 ResetEvent(prh->data.overlapped.hEvent);
384
385 DWORD io_thread_id;
386 prh->data.io_thread = CreateThread(NULL, 0, &_pipe9x_read_thread, prh, 0, &io_thread_id);
387
388 if(prh->data.io_thread == NULL)
389 {
390 return GetLastError();
391 }
392
393 prh->data.pending = TRUE;
394
395 return ERROR_IO_PENDING;
396 }
397 else{
398 if(ReadFile(
399 prh->data.pipe,
400 prh->data.rw_buf,
401 prh->data.rw_buf_size,
402 &(prh->data.bytes_transferred),
403 &(prh->data.overlapped)))
404 {
405 /* Not sure if this is actually a valid result for overlapped
406 * operations, but lets assume it is...
407 */
408
409 prh->data.pending = TRUE;
410 return ERROR_IO_PENDING;
411 }
412 else{
413 DWORD error = GetLastError();
414
415 if(error == ERROR_IO_PENDING)
416 {
417 prh->data.pending = TRUE;
418 return ERROR_IO_PENDING;
419 }
420 else{
421 return error;
422 }
423 }
424 }
425}
426
427DWORD pipe9x_read_result(PipeReadHandle prh, void **data_out, size_t *data_size_out, BOOL wait)
428{
429 assert(prh != NULL);
430
431 if(!prh->data.pending)
432 {
433 return ERROR_INVALID_PARAMETER;
434 }
435
436 if(prh->data.use_thread_fallback)
437 {
438 assert(prh->data.io_thread != NULL);
439
440 DWORD wait_result = WaitForSingleObject(prh->data.overlapped.hEvent, (wait ? INFINITE : 0));
441 if(wait_result == WAIT_OBJECT_0)
442 {
443 wait_result = WaitForSingleObject(prh->data.io_thread, INFINITE);
444 assert(wait_result == WAIT_OBJECT_0);
445
446 prh->data.pending = FALSE;
447
448 CloseHandle(prh->data.io_thread);
449 prh->data.io_thread = NULL;
450
451 if(prh->data.io_result == ERROR_SUCCESS)
452 {
453 *data_out = prh->data.rw_buf;
454 *data_size_out = prh->data.bytes_transferred;
455 }
456
457 return prh->data.io_result;
458 }
459 else if(wait_result == WAIT_TIMEOUT)
460 {
461 return ERROR_IO_INCOMPLETE;
462 }
463 else{
464 /* WaitForSingleObject() shouldn't fail here... */
465 abort();
466 }
467 }
468
469 DWORD bytes_transferred;
470 if(GetOverlappedResult(prh->data.pipe, &(prh->data.overlapped), &bytes_transferred, wait))
471 {
472 prh->data.pending = FALSE;
473
474 *data_out = prh->data.rw_buf;
475 *data_size_out = bytes_transferred;
476
477 return ERROR_SUCCESS;
478 }
479 else{
480 DWORD error = GetLastError();
481
482 /* Clear the pending flag if the operation has failed. */
483 if(error != ERROR_IO_INCOMPLETE)
484 {
485 prh->data.pending = FALSE;
486 }
487
488 return error;
489 }
490}
491
492BOOL pipe9x_read_pending(PipeReadHandle prh)
493{
494 assert(prh != NULL);
495 return prh->data.pending;
496}
497
498HANDLE pipe9x_read_pipe(PipeReadHandle prh)
499{
500 assert(prh != NULL);
501 return prh->data.pipe;
502}
503
504HANDLE pipe9x_read_event(PipeReadHandle prh)
505{
506 assert(prh != NULL);
507 return prh->data.overlapped.hEvent;
508}
509
510static DWORD WINAPI _pipe9x_write_thread(LPVOID lpParameter)
511{
512 PipeWriteHandle pwh = (PipeWriteHandle)(lpParameter);
513
514 if(WriteFile(
515 pwh->data.pipe,
516 pwh->data.rw_buf,
517 pwh->data.bytes_transferred,
518 &(pwh->data.bytes_transferred),
519 NULL))
520 {
521 pwh->data.io_result = ERROR_SUCCESS;
522 }
523 else{
524 pwh->data.io_result = GetLastError();
525 }
526
527 SetEvent(pwh->data.overlapped.hEvent);
528
529 return 0;
530}
531
532DWORD pipe9x_write_initiate(PipeWriteHandle pwh, const void *data, size_t data_size)
533{
534 assert(pwh != NULL);
535
536 if(pwh->data.pending)
537 {
538 return ERROR_IO_INCOMPLETE;
539 }
540
541 if(data_size > pwh->data.rw_buf_size)
542 {
543 return ERROR_FILE_TOO_LARGE;
544 }
545
546 memcpy(pwh->data.rw_buf, data, data_size);
547
548 if(pwh->data.use_thread_fallback)
549 {
550 assert(pwh->data.io_thread == NULL);
551
552 ResetEvent(pwh->data.overlapped.hEvent);
553
554 pwh->data.bytes_transferred = data_size;
555
556 DWORD io_thread_id;
557 pwh->data.io_thread = CreateThread(NULL, 0, &_pipe9x_write_thread, pwh, 0, &io_thread_id);
558
559 if(pwh->data.io_thread == NULL)
560 {
561 return GetLastError();
562 }
563
564 pwh->data.pending = TRUE;
565
566 return ERROR_IO_PENDING;
567 }
568 else{
569 if(WriteFile(
570 pwh->data.pipe,
571 pwh->data.rw_buf,
572 data_size,
573 &(pwh->data.bytes_transferred),
574 &(pwh->data.overlapped)))
575 {
576 /* Not sure if this is actually a valid result for overlapped
577 * operations, but lets assume it is...
578 */
579
580 pwh->data.pending = TRUE;
581 return ERROR_IO_PENDING;
582 }
583 else{
584 DWORD error = GetLastError();
585
586 if(error == ERROR_IO_PENDING)
587 {
588 pwh->data.pending = TRUE;
589 return ERROR_IO_PENDING;
590 }
591 else{
592 return error;
593 }
594 }
595 }
596}
597
598DWORD pipe9x_write_result(PipeWriteHandle pwh, size_t *data_written_out, BOOL wait)
599{
600 assert(pwh != NULL);
601
602 if(!pwh->data.pending)
603 {
604 return ERROR_INVALID_PARAMETER;
605 }
606
607 if(pwh->data.use_thread_fallback)
608 {
609 assert(pwh->data.io_thread != NULL);
610
611 DWORD wait_result = WaitForSingleObject(pwh->data.overlapped.hEvent, (wait ? INFINITE : 0));
612 if(wait_result == WAIT_OBJECT_0)
613 {
614 wait_result = WaitForSingleObject(pwh->data.io_thread, INFINITE);
615 assert(wait_result == WAIT_OBJECT_0);
616
617 pwh->data.pending = FALSE;
618
619 CloseHandle(pwh->data.io_thread);
620 pwh->data.io_thread = NULL;
621
622 if(pwh->data.io_result == ERROR_SUCCESS)
623 {
624 *data_written_out = pwh->data.bytes_transferred;
625 }
626
627 return pwh->data.io_result;
628 }
629 else if(wait_result == WAIT_TIMEOUT)
630 {
631 return ERROR_IO_INCOMPLETE;
632 }
633 else{
634 /* WaitForSingleObject() shouldn't fail here... */
635 abort();
636 }
637 }
638
639 DWORD bytes_transferred;
640 if(GetOverlappedResult(pwh->data.pipe, &(pwh->data.overlapped), &bytes_transferred, wait))
641 {
642 pwh->data.pending = FALSE;
643
644 *data_written_out = bytes_transferred;
645
646 return ERROR_SUCCESS;
647 }
648 else{
649 DWORD error = GetLastError();
650
651 /* Clear the pending flag if the operation has failed. */
652 if(error != ERROR_IO_INCOMPLETE)
653 {
654 pwh->data.pending = FALSE;
655 }
656
657 return error;
658 }
659}
660
661void pipe9x_write_close(PipeWriteHandle pwh)
662{
663 if(pwh == NULL)
664 {
665 return;
666 }
667
668 _pipe9x_cleanup(&(pwh->data));
669 free(pwh);
670}
671
672BOOL pipe9x_write_pending(PipeWriteHandle pwh)
673{
674 assert(pwh != NULL);
675 return pwh->data.pending;
676}
677
678HANDLE pipe9x_write_pipe(PipeWriteHandle pwh)
679{
680 assert(pwh != NULL);
681 return pwh->data.pipe;
682}
683
684HANDLE pipe9x_write_event(PipeWriteHandle pwh)
685{
686 assert(pwh != NULL);
687 return pwh->data.overlapped.hEvent;
688}
DWORD pipe9x_write_result(PipeWriteHandle pwh, size_t *data_written_out, BOOL wait)
Get the result from a write operation.
Definition: pipe9x.c:598
BOOL pipe9x_write_pending(PipeWriteHandle pwh)
Check if a write operation is pending.
Definition: pipe9x.c:672
void pipe9x_read_close(PipeReadHandle prh)
Closes the read end of a pipe created by pipe9x_create().
Definition: pipe9x.c:337
DWORD pipe9x_read_result(PipeReadHandle prh, void **data_out, size_t *data_size_out, BOOL wait)
Get the result from a read operation.
Definition: pipe9x.c:427
DWORD pipe9x_create(PipeReadHandle *prh_out, size_t read_size, BOOL read_inherit, PipeWriteHandle *pwh_out, size_t write_size, BOOL write_inherit)
Create a pair of connected pipe handles.
Definition: pipe9x.c:73
void pipe9x_write_close(PipeWriteHandle pwh)
Closes the write end of a pipe created by pipe9x_create().
Definition: pipe9x.c:661
HANDLE pipe9x_write_pipe(PipeWriteHandle pwh)
Get the underlying Windows HANDLE of the pipe.
Definition: pipe9x.c:678
DWORD pipe9x_write_initiate(PipeWriteHandle prh, const void *data, size_t data_size)
Start a write in the background.
Definition: pipe9x.c:532
HANDLE pipe9x_read_pipe(PipeReadHandle prh)
Get the underlying Windows HANDLE of the pipe.
Definition: pipe9x.c:498
HANDLE pipe9x_write_event(PipeWriteHandle prh)
Get an event object for detecting I/O completion.
Definition: pipe9x.c:684
HANDLE pipe9x_read_event(PipeReadHandle prh)
Get an event object for detecting I/O completion.
Definition: pipe9x.c:504
DWORD pipe9x_read_initiate(PipeReadHandle prh)
Start a read in the background.
Definition: pipe9x.c:370
BOOL pipe9x_read_pending(PipeReadHandle prh)
Check if a read operation is pending.
Definition: pipe9x.c:492