Now using the NT CD interface.
[wine] / dlls / winedos / devices.c
1 /*
2  * DOS devices
3  *
4  * Copyright 1999 Ove Kåven
5  */
6
7 #include <stdlib.h>
8 #include <string.h>
9 #include "wine/winbase16.h"
10 #include "msdos.h"
11 #include "miscemu.h"
12 #include "dosexe.h"
13 #include "debugtools.h"
14
15 #include "pshpack1.h"
16
17 typedef struct {
18   BYTE ljmp1;
19   RMCBPROC strategy;
20   BYTE ljmp2;
21   RMCBPROC interrupt;
22 } WINEDEV_THUNK;
23
24 typedef struct {
25   BYTE size; /* length of header + data */
26   BYTE unit; /* unit (block devices only) */
27   BYTE command;
28   WORD status;
29   BYTE reserved[8];
30 } REQUEST_HEADER;
31
32 typedef struct {
33   REQUEST_HEADER hdr;
34   BYTE media; /* media descriptor from BPB */
35   SEGPTR buffer;
36   WORD count; /* byte/sector count */
37   WORD sector; /* starting sector (block devices) */
38   DWORD volume; /* volume ID (block devices) */
39 } REQ_IO;
40
41 typedef struct {
42   REQUEST_HEADER hdr;
43   BYTE data;
44 } REQ_SAFEINPUT;
45
46 #include "poppack.h"
47
48 #define CON_BUFFER 128
49
50 enum strategy { SYSTEM_STRATEGY_NUL, SYSTEM_STRATEGY_CON, NB_SYSTEM_STRATEGIES };
51
52 static void *strategy_data[NB_SYSTEM_STRATEGIES];
53
54 #define NONEXT ((DWORD)-1)
55
56 #define ATTR_STDIN     0x0001
57 #define ATTR_STDOUT    0x0002
58 #define ATTR_NUL       0x0004
59 #define ATTR_CLOCK     0x0008
60 #define ATTR_FASTCON   0x0010
61 #define ATTR_RAW       0x0020
62 #define ATTR_NOTEOF    0x0040
63 #define ATTR_DEVICE    0x0080
64 #define ATTR_REMOVABLE 0x0800
65 #define ATTR_NONIBM    0x2000 /* block devices */
66 #define ATTR_UNTILBUSY 0x2000 /* char devices */
67 #define ATTR_IOCTL     0x4000
68 #define ATTR_CHAR      0x8000
69
70 #define CMD_INIT       0
71 #define CMD_MEDIACHECK 1 /* block devices */
72 #define CMD_BUILDBPB   2 /* block devices */
73 #define CMD_INIOCTL    3
74 #define CMD_INPUT      4 /* read data */
75 #define CMD_SAFEINPUT  5 /* "non-destructive input no wait", char devices */
76 #define CMD_INSTATUS   6 /* char devices */
77 #define CMD_INFLUSH    7 /* char devices */
78 #define CMD_OUTPUT     8 /* write data */
79 #define CMD_SAFEOUTPUT 9 /* write data with verify */
80 #define CMD_OUTSTATUS 10 /* char devices */
81 #define CMD_OUTFLUSH  11 /* char devices */
82 #define CMD_OUTIOCTL  12
83 #define CMD_DEVOPEN   13
84 #define CMD_DEVCLOSE  14
85 #define CMD_REMOVABLE 15 /* block devices */
86 #define CMD_UNTILBUSY 16 /* output until busy */
87
88 #define STAT_MASK  0x00FF
89 #define STAT_DONE  0x0100
90 #define STAT_BUSY  0x0200
91 #define STAT_ERROR 0x8000
92
93 #define LJMP 0xea
94
95
96 /* prototypes */
97 static void WINAPI nul_strategy(CONTEXT86*ctx);
98 static void WINAPI nul_interrupt(CONTEXT86*ctx);
99 static void WINAPI con_strategy(CONTEXT86*ctx);
100 static void WINAPI con_interrupt(CONTEXT86*ctx);
101
102 /* devices */
103 typedef struct 
104 {
105     char name[8];
106     WORD attr;
107     RMCBPROC strategy;
108     RMCBPROC interrupt;
109 } WINEDEV;
110
111 static WINEDEV devs[] = 
112 {
113   { "NUL     ",
114     ATTR_CHAR|ATTR_NUL|ATTR_DEVICE,
115     nul_strategy, nul_interrupt },
116
117   { "CON     ",
118     ATTR_CHAR|ATTR_STDIN|ATTR_STDOUT|ATTR_FASTCON|ATTR_NOTEOF|ATTR_DEVICE,
119     con_strategy, con_interrupt }
120 };
121
122 #define NR_DEVS (sizeof(devs)/sizeof(WINEDEV))
123
124 /* DOS data segment */
125 typedef struct
126 {
127     DOS_LISTOFLISTS    lol;
128     DOS_DEVICE_HEADER  dev[NR_DEVS-1];
129     WINEDEV_THUNK      thunk[NR_DEVS];
130     REQ_IO             req;
131     BYTE               buffer[CON_BUFFER];
132
133 } DOS_DATASEG;
134
135 #define DOS_DATASEG_OFF(xxx) FIELD_OFFSET(DOS_DATASEG, xxx)
136
137 struct _DOS_LISTOFLISTS * DOSMEM_LOL()
138 {
139     return (struct _DOS_LISTOFLISTS *)DOSMEM_MapRealToLinear
140       (MAKESEGPTR(HIWORD(DOS_LOLSeg),0));
141 }
142
143
144 /* the device implementations */
145 static void do_lret(CONTEXT86*ctx)
146 {
147   WORD *stack = CTX_SEG_OFF_TO_LIN(ctx, ctx->SegSs, ctx->Esp);
148
149   ctx->Eip   = *(stack++);
150   ctx->SegCs = *(stack++);
151   ctx->Esp  += 2*sizeof(WORD);
152 }
153
154 static void do_strategy(CONTEXT86*ctx, int id, int extra)
155 {
156   REQUEST_HEADER *hdr = CTX_SEG_OFF_TO_LIN(ctx, ctx->SegEs, ctx->Ebx);
157   void **hdr_ptr = strategy_data[id];
158
159   if (!hdr_ptr) {
160     hdr_ptr = calloc(1,sizeof(void *)+extra);
161     strategy_data[id] = hdr_ptr;
162   }
163   *hdr_ptr = hdr;
164   do_lret(ctx);
165 }
166
167 static REQUEST_HEADER * get_hdr(int id, void**extra)
168 {
169   void **hdr_ptr = strategy_data[id];
170   if (extra)
171     *extra = hdr_ptr ? (void*)(hdr_ptr+1) : (void *)NULL;
172   return hdr_ptr ? *hdr_ptr : (void *)NULL;
173 }
174
175 static void WINAPI nul_strategy(CONTEXT86*ctx)
176 {
177   do_strategy(ctx, SYSTEM_STRATEGY_NUL, 0);
178 }
179
180 static void WINAPI nul_interrupt(CONTEXT86*ctx)
181 {
182   REQUEST_HEADER *hdr = get_hdr(SYSTEM_STRATEGY_NUL, NULL);
183   /* eat everything and recycle nothing */
184   switch (hdr->command) {
185   case CMD_INPUT:
186     ((REQ_IO*)hdr)->count = 0;
187     hdr->status = STAT_DONE;
188     break;
189   case CMD_SAFEINPUT:
190     hdr->status = STAT_DONE|STAT_BUSY;
191     break;
192   default:
193     hdr->status = STAT_DONE;
194   }
195   do_lret(ctx);
196 }
197
198 static void WINAPI con_strategy(CONTEXT86*ctx)
199 {
200   do_strategy(ctx, SYSTEM_STRATEGY_CON, sizeof(int));
201 }
202
203 static void WINAPI con_interrupt(CONTEXT86*ctx)
204 {
205   int *scan;
206   REQUEST_HEADER *hdr = get_hdr(SYSTEM_STRATEGY_CON,(void **)&scan);
207   BIOSDATA *bios = DOSMEM_BiosData();
208   WORD CurOfs = bios->NextKbdCharPtr;
209   DOS_LISTOFLISTS *lol = DOSMEM_LOL();
210   DOS_DATASEG *dataseg = (DOS_DATASEG *)lol;
211   BYTE *linebuffer = dataseg->buffer;
212   BYTE *curbuffer = (lol->offs_unread_CON) ?
213     (((BYTE*)dataseg) + lol->offs_unread_CON) : (BYTE*)NULL;
214   DOS_DEVICE_HEADER *con = dataseg->dev;
215
216   switch (hdr->command) {
217   case CMD_INPUT:
218     {
219       REQ_IO *io = (REQ_IO *)hdr;
220       WORD count = io->count, len = 0;
221       BYTE *buffer = CTX_SEG_OFF_TO_LIN(ctx,
222                                         SELECTOROF(io->buffer),
223                                         (DWORD)OFFSETOF(io->buffer));
224
225       hdr->status = STAT_BUSY;
226       /* first, check whether we already have data in line buffer */
227       if (curbuffer) {
228         /* yep, copy as much as we can */
229         BYTE data = 0;
230         while ((len<count) && (data != '\r')) {
231           data = *curbuffer++;
232           buffer[len++] = data;
233         }
234         if (data == '\r') {
235           /* line buffer emptied */
236           lol->offs_unread_CON = 0;
237           curbuffer = NULL;
238           /* if we're not in raw mode, call it a day*/
239           if (!(con->attr & ATTR_RAW)) {
240             hdr->status = STAT_DONE;
241             io->count = len;
242             break;
243           }
244         } else {
245           /* still some data left */
246           lol->offs_unread_CON = curbuffer - (BYTE*)lol;
247           /* but buffer was filled, we're done */
248           hdr->status = STAT_DONE;
249           io->count = len;
250           break;
251         }
252       }
253
254       /* if we're in raw mode, we just need to fill the buffer */
255       if (con->attr & ATTR_RAW) {
256         while (len<count) {
257           WORD data;
258
259           /* do we have a waiting scancode? */
260           if (*scan) {
261             /* yes, store scancode in buffer */
262             buffer[len++] = *scan;
263             *scan = 0;
264             if (len==count) break;
265           }
266
267           /* check for new keyboard input */
268           while (CurOfs == bios->FirstKbdCharPtr) {
269             /* no input available yet, so wait... */
270             DOSVM_Wait( -1, 0 );
271           }
272           /* read from keyboard queue (call int16?) */
273           data = ((WORD*)bios)[CurOfs];
274           CurOfs += 2;
275           if (CurOfs >= bios->KbdBufferEnd) CurOfs = bios->KbdBufferStart;
276           bios->NextKbdCharPtr = CurOfs;
277           /* if it's an extended key, save scancode */
278           if (LOBYTE(data) == 0) *scan = HIBYTE(data);
279           /* store ASCII char in buffer */
280           buffer[len++] = LOBYTE(data);
281         }
282       } else {
283         /* we're not in raw mode, so we need to do line input... */
284         while (TRUE) {
285           WORD data;
286           /* check for new keyboard input */
287           while (CurOfs == bios->FirstKbdCharPtr) {
288             /* no input available yet, so wait... */
289             DOSVM_Wait( -1, 0 );
290           }
291           /* read from keyboard queue (call int16?) */
292           data = ((WORD*)bios)[CurOfs];
293           CurOfs += 2;
294           if (CurOfs >= bios->KbdBufferEnd) CurOfs = bios->KbdBufferStart;
295           bios->NextKbdCharPtr = CurOfs;
296
297           if (LOBYTE(data) == '\r') {
298             /* it's the return key, we're done */
299             linebuffer[len++] = LOBYTE(data);
300             break;
301           }
302           else if (LOBYTE(data) >= ' ') {
303             /* a character */
304             if ((len+1)<CON_BUFFER) {
305               linebuffer[len] = LOBYTE(data);
306               WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), &linebuffer[len++], 1, NULL, NULL);
307             }
308             /* else beep, but I don't like noise */
309           }
310           else switch (LOBYTE(data)) {
311           case '\b':
312             if (len>0) {
313               len--;
314               WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "\b \b", 3, NULL, NULL);
315             }
316             break;
317           }
318         }
319         if (len > count) {
320           /* save rest of line for later */
321           lol->offs_unread_CON = linebuffer - (BYTE*)lol + count;
322           len = count;
323         }
324         memcpy(buffer, linebuffer, len);
325       }
326       hdr->status = STAT_DONE;
327       io->count = len;
328     }
329     break;
330   case CMD_SAFEINPUT:
331     if (curbuffer) {
332       /* some line input waiting */
333       hdr->status = STAT_DONE;
334       ((REQ_SAFEINPUT*)hdr)->data = *curbuffer;
335     }
336     else if (con->attr & ATTR_RAW) {
337       if (CurOfs == bios->FirstKbdCharPtr) {
338         /* no input */
339         hdr->status = STAT_DONE|STAT_BUSY;
340       } else {
341         /* some keyboard input waiting */
342         hdr->status = STAT_DONE;
343         ((REQ_SAFEINPUT*)hdr)->data = ((BYTE*)bios)[CurOfs];
344       }
345     } else {
346       /* no line input */
347       hdr->status = STAT_DONE|STAT_BUSY;
348     }
349     break;
350   case CMD_INSTATUS:
351     if (curbuffer) {
352       /* we have data */
353       hdr->status = STAT_DONE;
354     }
355     else if (con->attr & ATTR_RAW) {
356       if (CurOfs == bios->FirstKbdCharPtr) {
357         /* no input */
358         hdr->status = STAT_DONE|STAT_BUSY;
359       } else {
360         /* some keyboard input waiting */
361         hdr->status = STAT_DONE;
362       }
363     } else {
364       /* no line input */
365       hdr->status = STAT_DONE|STAT_BUSY;
366     }
367
368     break;
369   case CMD_INFLUSH:
370     /* flush line and keyboard queue */
371     lol->offs_unread_CON = 0;
372     bios->NextKbdCharPtr = bios->FirstKbdCharPtr;
373     break;
374   case CMD_OUTPUT:
375   case CMD_SAFEOUTPUT:
376     {
377       REQ_IO *io = (REQ_IO *)hdr;
378       BYTE *buffer = CTX_SEG_OFF_TO_LIN(ctx,
379                                         SELECTOROF(io->buffer),
380                                         (DWORD)OFFSETOF(io->buffer));
381       DWORD result = 0;
382       WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buffer, io->count, &result, NULL);
383       io->count = result;
384       hdr->status = STAT_DONE;
385     }
386     break;
387   default:
388     hdr->status = STAT_DONE;
389   }
390   do_lret(ctx);
391 }
392
393 static void InitListOfLists(DOS_LISTOFLISTS *DOS_LOL)
394 {
395 /*
396 Output of DOS 6.22:
397
398 0133:0020                    6A 13-33 01 CC 00 33 01 59 00         j.3...3.Y.
399 0133:0030  70 00 00 00 72 02 00 02-6D 00 33 01 00 00 2E 05   p...r...m.3.....
400 0133:0040  00 00 FC 04 00 00 03 08-92 21 11 E0 04 80 C6 0D   .........!......
401 0133:0050  CC 0D 4E 55 4C 20 20 20-20 20 00 00 00 00 00 00   ..NUL     ......
402 0133:0060  00 4B BA C1 06 14 00 00-00 03 01 00 04 70 CE FF   .K...........p..
403 0133:0070  FF 00 00 00 00 00 00 00-00 01 00 00 0D 05 00 00   ................
404 0133:0080  00 FF FF 00 00 00 00 FE-00 00 F8 03 FF 9F 70 02   ..............p.
405 0133:0090  D0 44 C8 FD D4 44 C8 FD-D4 44 C8 FD D0 44 C8 FD   .D...D...D...D..
406 0133:00A0  D0 44 C8 FD D0 44                                 .D...D
407 */
408   DOS_LOL->CX_Int21_5e01                = 0x0;
409   DOS_LOL->LRU_count_FCB_cache  = 0x0;
410   DOS_LOL->LRU_count_FCB_open           = 0x0;
411   DOS_LOL->OEM_func_handler             = -1; /* not available */
412   DOS_LOL->INT21_offset         = 0x0;
413   DOS_LOL->sharing_retry_count  = 3;
414   DOS_LOL->sharing_retry_delay  = 1;
415   DOS_LOL->ptr_disk_buf         = 0x0;
416   DOS_LOL->offs_unread_CON              = 0x0;
417   DOS_LOL->seg_first_MCB                = 0x0;
418   DOS_LOL->ptr_first_DPB                = 0x0;
419   DOS_LOL->ptr_first_SysFileTable       = 0x0;
420   DOS_LOL->ptr_clock_dev_hdr            = 0x0;
421   DOS_LOL->ptr_CON_dev_hdr              = 0x0;
422   DOS_LOL->max_byte_per_sec             = 512;
423   DOS_LOL->ptr_disk_buf_info            = 0x0;
424   DOS_LOL->ptr_array_CDS                = 0x0;
425   DOS_LOL->ptr_sys_FCB          = 0x0;
426   DOS_LOL->nr_protect_FCB               = 0x0;
427   DOS_LOL->nr_block_dev         = 0x0;
428   DOS_LOL->nr_avail_drive_letters       = 26; /* A - Z */
429   DOS_LOL->nr_drives_JOINed             = 0x0;
430   DOS_LOL->ptr_spec_prg_names           = 0x0;
431   DOS_LOL->ptr_SETVER_prg_list  = 0x0; /* no SETVER list */
432   DOS_LOL->DOS_HIGH_A20_func_offs       = 0x0;
433   DOS_LOL->PSP_last_exec                = 0x0;
434   DOS_LOL->BUFFERS_val          = 99; /* maximum: 99 */
435   DOS_LOL->BUFFERS_nr_lookahead = 8; /* maximum: 8 */
436   DOS_LOL->boot_drive                   = 3; /* C: */
437   DOS_LOL->flag_DWORD_moves             = 0x01; /* i386+ */
438   DOS_LOL->size_extended_mem            = 0xf000; /* very high value */
439 }
440
441 void DOSDEV_InstallDOSDevices(void)
442 {
443   DOS_DATASEG *dataseg;
444   UINT16 seg;
445   unsigned int n;
446
447   /* allocate DOS data segment or something */
448   DOS_LOLSeg = GlobalDOSAlloc16(sizeof(DOS_DATASEG));
449   seg = HIWORD(DOS_LOLSeg);
450   dataseg = MapSL( MAKESEGPTR(LOWORD(DOS_LOLSeg), 0) );
451
452   /* initialize the magnificent List Of Lists */
453   InitListOfLists(&dataseg->lol);
454
455   /* Set up first device (NUL) */
456   dataseg->lol.NUL_dev.next_dev  = MAKESEGPTR(seg, DOS_DATASEG_OFF(dev[0]));
457   dataseg->lol.NUL_dev.attr      = devs[0].attr;
458   dataseg->lol.NUL_dev.strategy  = DOS_DATASEG_OFF(thunk[0].ljmp1);
459   dataseg->lol.NUL_dev.interrupt = DOS_DATASEG_OFF(thunk[0].ljmp2);
460   memcpy(dataseg->lol.NUL_dev.name, devs[0].name, 8);
461
462   /* Set up the remaining devices */
463   for (n = 1; n < NR_DEVS; n++)
464   {
465     dataseg->dev[n-1].next_dev  = (n+1) == NR_DEVS ? NONEXT :
466                                   MAKESEGPTR(seg, DOS_DATASEG_OFF(dev[n]));
467     dataseg->dev[n-1].attr      = devs[n].attr;
468     dataseg->dev[n-1].strategy  = DOS_DATASEG_OFF(thunk[n].ljmp1);
469     dataseg->dev[n-1].interrupt = DOS_DATASEG_OFF(thunk[n].ljmp2);
470     memcpy(dataseg->dev[n-1].name, devs[n].name, 8);
471   }
472
473   /* Set up thunks */
474   for (n = 0; n < NR_DEVS; n++)
475   {
476     dataseg->thunk[n].ljmp1     = LJMP;
477     dataseg->thunk[n].strategy  = (RMCBPROC)DPMI_AllocInternalRMCB(devs[n].strategy);
478     dataseg->thunk[n].ljmp2     = LJMP;
479     dataseg->thunk[n].interrupt = (RMCBPROC)DPMI_AllocInternalRMCB(devs[n].interrupt);
480   }
481
482   /* CON is device 1 */
483   dataseg->lol.ptr_CON_dev_hdr = MAKESEGPTR(seg, DOS_DATASEG_OFF(dev[0]));
484 }
485
486 DWORD DOSDEV_Console(void)
487 {
488   return DOSMEM_LOL()->ptr_CON_dev_hdr;
489 }
490
491 DWORD DOSDEV_FindCharDevice(char*name)
492 {
493   SEGPTR cur_ptr = MAKESEGPTR(HIWORD(DOS_LOLSeg), FIELD_OFFSET(DOS_LISTOFLISTS,NUL_dev));
494   DOS_DEVICE_HEADER *cur = DOSMEM_MapRealToLinear(cur_ptr);
495   char dname[8];
496   int cnt;
497
498   /* get first 8 characters */
499   strncpy(dname,name,8);
500   /* if less than 8 characters, pad with spaces */
501   for (cnt=0; cnt<8; cnt++)
502     if (!dname[cnt]) dname[cnt]=' ';
503
504   /* search for char devices with the right name */
505   while (cur &&
506          ((!(cur->attr & ATTR_CHAR)) ||
507           memcmp(cur->name,dname,8))) {
508     cur_ptr = cur->next_dev;
509     if (cur_ptr == NONEXT) cur=NULL;
510     else cur = DOSMEM_MapRealToLinear(cur_ptr);
511   }
512   return cur_ptr;
513 }
514
515 static void DOSDEV_DoReq(void*req, DWORD dev)
516 {
517   REQUEST_HEADER *hdr = (REQUEST_HEADER *)req;
518   DOS_DEVICE_HEADER *dhdr;
519   CONTEXT86 ctx;
520   char *phdr;
521
522   dhdr = DOSMEM_MapRealToLinear(dev);
523   phdr = ((char*)DOSMEM_LOL()) + DOS_DATASEG_OFF(req);
524
525   /* copy request to request scratch area */
526   memcpy(phdr, req, hdr->size);
527
528   /* prepare to call device driver */
529   memset(&ctx, 0, sizeof(ctx));
530
531   /* ES:BX points to request for strategy routine */
532   ctx.SegEs = HIWORD(DOS_LOLSeg);
533   ctx.Ebx   = DOS_DATASEG_OFF(req);
534
535   /* call strategy routine */
536   ctx.SegCs = SELECTOROF(dev);
537   ctx.Eip   = dhdr->strategy;
538   DPMI_CallRMProc(&ctx, 0, 0, 0);
539
540   /* call interrupt routine */
541   ctx.SegCs = SELECTOROF(dev);
542   ctx.Eip   = dhdr->interrupt;
543   DPMI_CallRMProc(&ctx, 0, 0, 0);
544
545   /* completed, copy request back */
546   memcpy(req, phdr, hdr->size);
547
548   if (hdr->status & STAT_ERROR) {
549     switch (hdr->status & STAT_MASK) {
550     case 0x0F: /* invalid disk change */
551       /* this error seems to fit the bill */
552       SetLastError(ER_NotSameDevice);
553       break;
554     default:
555       SetLastError((hdr->status & STAT_MASK) + 0x13);
556       break;
557     }
558   }
559 }
560
561 static int DOSDEV_IO(unsigned cmd, DWORD dev, DWORD buf, int buflen)
562 {
563   REQ_IO req;
564
565   req.hdr.size=sizeof(req);
566   req.hdr.unit=0; /* not dealing with block devices yet */
567   req.hdr.command=cmd;
568   req.hdr.status=STAT_BUSY;
569   req.media=0; /* not dealing with block devices yet */
570   req.buffer=buf;
571   req.count=buflen;
572   req.sector=0; /* block devices */
573   req.volume=0; /* block devices */
574
575   DOSDEV_DoReq(&req, dev);
576
577   return req.count;
578 }
579
580 int DOSDEV_Peek(DWORD dev, BYTE*data)
581 {
582   REQ_SAFEINPUT req;
583
584   req.hdr.size=sizeof(req);
585   req.hdr.unit=0; /* not dealing with block devices yet */
586   req.hdr.command=CMD_SAFEINPUT;
587   req.hdr.status=STAT_BUSY;
588   req.data=0;
589
590   DOSDEV_DoReq(&req, dev);
591
592   if (req.hdr.status & STAT_BUSY) return 0;
593
594   *data = req.data;
595   return 1;
596 }
597
598 int DOSDEV_Read(DWORD dev, DWORD buf, int buflen)
599 {
600   return DOSDEV_IO(CMD_INPUT, dev, buf, buflen);
601 }
602
603 int DOSDEV_Write(DWORD dev, DWORD buf, int buflen, int verify)
604 {
605   return DOSDEV_IO(verify?CMD_SAFEOUTPUT:CMD_OUTPUT, dev, buf, buflen);
606 }
607
608 int DOSDEV_IoctlRead(DWORD dev, DWORD buf, int buflen)
609 {
610   return DOSDEV_IO(CMD_INIOCTL, dev, buf, buflen);
611 }
612
613 int DOSDEV_IoctlWrite(DWORD dev, DWORD buf, int buflen)
614 {
615   return DOSDEV_IO(CMD_OUTIOCTL, dev, buf, buflen);
616 }