Added some missing functions.
[wine] / dlls / winedos / int21.c
1 /*
2  * DOS interrupt 21h handler
3  *
4  * Copyright 1993, 1994 Erik Bos
5  * Copyright 1996 Alexandre Julliard
6  * Copyright 1997 Andreas Mohr
7  * Copyright 1998 Uwe Bonnes
8  * Copyright 1998, 1999 Ove Kaaven
9  * Copyright 2003 Thomas Mertes
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  */
25
26 #include "config.h"
27
28 #include "windef.h"
29 #include "winbase.h"
30 #include "winternl.h"
31 #include "wine/winbase16.h"
32 #include "dosexe.h"
33 #include "miscemu.h"
34 #include "msdos.h"
35 #include "file.h"
36 #include "task.h"
37 #include "winerror.h"
38 #include "winuser.h"
39 #include "wine/unicode.h"
40 #include "wine/debug.h"
41
42 /*
43  * FIXME: Delete this reference when all int21 code has been moved to winedos.
44  */
45 extern void WINAPI INT_Int21Handler( CONTEXT86 *context );
46
47 /*
48  * Forward declarations.
49  */
50 static BOOL INT21_RenameFile( CONTEXT86 *context );
51
52 WINE_DEFAULT_DEBUG_CHANNEL(int21);
53
54
55 #include "pshpack1.h"
56
57 /*
58  * Structure for DOS data that can be accessed directly from applications.
59  * Real and protected mode pointers will be returned to this structure so
60  * the structure must be correctly packed.
61  */
62 typedef struct _INT21_HEAP {
63     WORD uppercase_size;             /* Size of the following table in bytes */
64     BYTE uppercase_table[128];       /* Uppercase equivalents of chars from 0x80 to 0xff. */
65
66     WORD lowercase_size;             /* Size of the following table in bytes */
67     BYTE lowercase_table[256];       /* Lowercase equivalents of chars from 0x00 to 0xff. */
68
69     WORD collating_size;             /* Size of the following table in bytes */
70     BYTE collating_table[256];       /* Values used to sort characters from 0x00 to 0xff. */
71
72     WORD filename_size;              /* Size of the following filename data in bytes */
73     BYTE filename_reserved1;         /* 0x01 for MS-DOS 3.30-6.00 */
74     BYTE filename_lowest;            /* Lowest permissible character value for filename */
75     BYTE filename_highest;           /* Highest permissible character value for filename */
76     BYTE filename_reserved2;         /* 0x00 for MS-DOS 3.30-6.00 */
77     BYTE filename_exclude_first;     /* First illegal character in permissible range */
78     BYTE filename_exclude_last;      /* Last illegal character in permissible range */
79     BYTE filename_reserved3;         /* 0x02 for MS-DOS 3.30-6.00 */
80     BYTE filename_illegal_size;      /* Number of terminators in the following table */
81     BYTE filename_illegal_table[16]; /* Characters which terminate a filename */
82
83     WORD dbcs_size;                  /* Number of valid ranges in the following table */
84     BYTE dbcs_table[16];             /* Start/end bytes for N ranges and 00/00 as terminator */
85
86     BYTE misc_indos;                 /* Interrupt 21 nesting flag */
87 } INT21_HEAP;
88
89
90 struct FCB {
91     BYTE  drive_number;
92     CHAR  file_name[8];
93     CHAR  file_extension[3];
94     WORD  current_block_number;
95     WORD  logical_record_size;
96     DWORD file_size;
97     WORD  date_of_last_write;
98     WORD  time_of_last_write;
99     BYTE  file_number;
100     BYTE  attributes;
101     WORD  starting_cluster;
102     WORD  sequence_number;
103     BYTE  file_attributes;
104     BYTE  unused;
105     BYTE  record_within_current_block;
106     BYTE  random_access_record_number[4];
107 };
108
109
110 struct XFCB {
111     BYTE  xfcb_signature;
112     BYTE  reserved[5];
113     BYTE  xfcb_file_attribute;
114     BYTE  fcb[37];
115 };
116
117 #include "poppack.h"
118
119
120 /***********************************************************************
121  *           INT21_GetCurrentDrive
122  *
123  * Return current drive using scheme (0=A:, 1=B:, 2=C:, ...) or
124  * MAX_DOS_DRIVES on error.
125  */
126 static BYTE INT21_GetCurrentDrive()
127 {
128     WCHAR current_directory[MAX_PATH];
129
130     if (!GetCurrentDirectoryW( MAX_PATH, current_directory ) ||
131         current_directory[1] != ':')
132     {
133         TRACE( "Failed to get current drive.\n" );
134         return MAX_DOS_DRIVES;
135     }
136
137     return toupperW( current_directory[0] ) - 'A';
138 }
139
140
141 /***********************************************************************
142  *           INT21_MapDrive
143  *
144  * Convert drive number from scheme (0=default, 1=A:, 2=B:, ...) into
145  * scheme (0=A:, 1=B:, 2=C:, ...) or MAX_DOS_DRIVES on error.
146  */
147 static BYTE INT21_MapDrive( BYTE drive )
148 {
149     if (drive)
150     {
151         WCHAR drivespec[3] = {'A', ':', 0};
152         UINT  drivetype;
153
154         drivespec[0] += drive - 1;
155         drivetype = GetDriveTypeW( drivespec );
156
157         if (drivetype == DRIVE_UNKNOWN || drivetype == DRIVE_NO_ROOT_DIR)
158             return MAX_DOS_DRIVES;
159
160         return drive - 1;
161     }
162
163     return INT21_GetCurrentDrive();
164 }
165
166
167 /***********************************************************************
168  *           INT21_SetCurrentDrive
169  *
170  * Set current drive. Uses scheme (0=A:, 1=B:, 2=C:, ...).
171  */
172 static void INT21_SetCurrentDrive( BYTE drive )
173 {
174     WCHAR drivespec[3] = {'A', ':', 0};
175
176     drivespec[0] += drive;
177
178     if (!SetCurrentDirectoryW( drivespec ))
179         TRACE( "Failed to set current drive.\n" );
180 }
181
182
183 /***********************************************************************
184  *           INT21_ReadChar
185  *
186  * Reads a character from the standard input.
187  * Extended keycodes will be returned as two separate characters.
188  */
189 static BOOL INT21_ReadChar( BYTE *input, CONTEXT86 *waitctx )
190 {
191     static BYTE pending_scan = 0;
192
193     if (pending_scan)
194     {
195         if (input)
196             *input = pending_scan;
197         if (waitctx)
198             pending_scan = 0;
199         return TRUE;
200     }
201     else
202     {
203         BYTE ascii;
204         BYTE scan;
205         if (!DOSVM_Int16ReadChar( &ascii, &scan, waitctx ))
206             return FALSE;
207
208         if (input)
209             *input = ascii;
210         if (waitctx && !ascii)
211             pending_scan = scan;
212         return TRUE;
213     }
214 }
215
216
217 /***********************************************************************
218  *           INT21_GetSystemCountryCode
219  *
220  * Return DOS country code for default system locale.
221  */
222 static WORD INT21_GetSystemCountryCode( void )
223 {
224     /*
225      * FIXME: Determine country code. We should probably use
226      *        DOSCONF structure for that.
227      */
228     return GetSystemDefaultLangID();
229 }
230
231
232 /***********************************************************************
233  *           INT21_FillCountryInformation
234  *
235  * Fill 34-byte buffer with country information data using
236  * default system locale.
237  */
238 static void INT21_FillCountryInformation( BYTE *buffer )
239 {
240     /* 00 - WORD: date format
241      *          00 = mm/dd/yy
242      *          01 = dd/mm/yy
243      *          02 = yy/mm/dd
244      */
245     *(WORD*)(buffer + 0) = 0; /* FIXME: Get from locale */
246
247     /* 02 - BYTE[5]: ASCIIZ currency symbol string */
248     buffer[2] = '$'; /* FIXME: Get from locale */
249     buffer[3] = 0;
250
251     /* 07 - BYTE[2]: ASCIIZ thousands separator */
252     buffer[7] = 0; /* FIXME: Get from locale */
253     buffer[8] = 0;
254
255     /* 09 - BYTE[2]: ASCIIZ decimal separator */
256     buffer[9]  = '.'; /* FIXME: Get from locale */
257     buffer[10] = 0;
258
259     /* 11 - BYTE[2]: ASCIIZ date separator */
260     buffer[11] = '/'; /* FIXME: Get from locale */
261     buffer[12] = 0;
262
263     /* 13 - BYTE[2]: ASCIIZ time separator */
264     buffer[13] = ':'; /* FIXME: Get from locale */
265     buffer[14] = 0;
266
267     /* 15 - BYTE: Currency format
268      *          bit 2 = set if currency symbol replaces decimal point
269      *          bit 1 = number of spaces between value and currency symbol
270      *          bit 0 = 0 if currency symbol precedes value
271      *                  1 if currency symbol follows value
272      */
273     buffer[15] = 0; /* FIXME: Get from locale */
274
275     /* 16 - BYTE: Number of digits after decimal in currency */
276     buffer[16] = 0; /* FIXME: Get from locale */
277
278     /* 17 - BYTE: Time format
279      *          bit 0 = 0 if 12-hour clock
280      *                  1 if 24-hour clock
281      */
282     buffer[17] = 1; /* FIXME: Get from locale */
283
284     /* 18 - DWORD: Address of case map routine */
285     *(DWORD*)(buffer + 18) = 0; /* FIXME: ptr to case map routine */
286
287     /* 22 - BYTE[2]: ASCIIZ data-list separator */
288     buffer[22] = ','; /* FIXME: Get from locale */
289     buffer[23] = 0;
290
291     /* 24 - BYTE[10]: Reserved */
292     memset( buffer + 24, 0, 10 );
293 }
294
295
296 /***********************************************************************
297  *           INT21_FillHeap
298  *
299  * Initialize DOS heap.
300  */
301 static void INT21_FillHeap( INT21_HEAP *heap )
302 {
303     static const char terminators[] = "\"\\./[]:|<>+=;,";
304     int i;
305
306     /*
307      * Uppercase table.
308      */
309     heap->uppercase_size = 128;
310     for (i = 0; i < 128; i++) 
311         heap->uppercase_table[i] = toupper( 128 + i );
312
313     /*
314      * Lowercase table.
315      */
316     heap->lowercase_size = 256;
317     for (i = 0; i < 256; i++) 
318         heap->lowercase_table[i] = tolower( i );
319     
320     /*
321      * Collating table.
322      */
323     heap->collating_size = 256;
324     for (i = 0; i < 256; i++) 
325         heap->collating_table[i] = i;
326
327     /*
328      * Filename table.
329      */
330     heap->filename_size = 8 + strlen(terminators);
331     heap->filename_illegal_size = strlen(terminators);
332     strcpy( heap->filename_illegal_table, terminators );
333
334     heap->filename_reserved1 = 0x01;
335     heap->filename_lowest = 0;           /* FIXME: correct value? */
336     heap->filename_highest = 0xff;       /* FIXME: correct value? */
337     heap->filename_reserved2 = 0x00;    
338     heap->filename_exclude_first = 0x00; /* FIXME: correct value? */
339     heap->filename_exclude_last = 0x00;  /* FIXME: correct value? */
340     heap->filename_reserved3 = 0x02;
341
342     /*
343      * DBCS lead byte table. This table is empty.
344      */
345     heap->dbcs_size = 0;
346     memset( heap->dbcs_table, 0, sizeof(heap->dbcs_table) );
347
348     /*
349      * Initialize InDos flag.
350      */
351     heap->misc_indos = 0;
352 }
353
354
355 /***********************************************************************
356  *           INT21_GetHeapSelector
357  *
358  * Get segment/selector for DOS heap (INT21_HEAP).
359  * Creates and initializes heap on first call.
360  */
361 static WORD INT21_GetHeapSelector( CONTEXT86 *context )
362 {
363     static WORD heap_segment = 0;
364     static WORD heap_selector = 0;
365     static BOOL heap_initialized = FALSE;
366
367     if (!heap_initialized)
368     {
369         INT21_HEAP *ptr = DOSVM_AllocDataUMB( sizeof(INT21_HEAP), 
370                                               &heap_segment,
371                                               &heap_selector );
372         INT21_FillHeap( ptr );
373         heap_initialized = TRUE;
374     }
375
376     if (!ISV86(context) && DOSVM_IsWin16())
377         return heap_selector;
378     else
379         return heap_segment;
380 }
381
382
383 /***********************************************************************
384  *           INT21_GetCurrentDirectory
385  *
386  * Handler for:
387  * - function 0x47
388  * - subfunction 0x47 of function 0x71
389  */
390 static BOOL INT21_GetCurrentDirectory( CONTEXT86 *context, BOOL islong )
391 {
392     char  *buffer = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi);
393     BYTE   new_drive = INT21_MapDrive( DL_reg(context) );
394     BYTE   old_drive = INT21_GetCurrentDrive();
395     WCHAR  pathW[MAX_PATH];
396     char   pathA[MAX_PATH];
397     WCHAR *ptr = pathW;
398
399     TRACE( "drive %d\n", DL_reg(context) );
400
401     if (new_drive == MAX_DOS_DRIVES)
402     {        
403         SetLastError(ERROR_INVALID_DRIVE);
404         return FALSE;
405     }
406     
407     /*
408      * Grab current directory.
409      */
410
411     INT21_SetCurrentDrive( new_drive );
412     if (!GetCurrentDirectoryW( MAX_PATH, pathW ))
413     {        
414         INT21_SetCurrentDrive( old_drive );
415         return FALSE;
416     }
417     INT21_SetCurrentDrive( old_drive );
418
419     /*
420      * Convert into short format.
421      */
422
423     if (!islong)
424     {
425         DWORD result = GetShortPathNameW( pathW, pathW, MAX_PATH );
426         if (!result)
427             return FALSE;
428         if (result > MAX_PATH)
429         {
430             WARN( "Short path too long!\n" );
431             SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
432             return FALSE;
433         }
434     }
435
436     /*
437      * The returned pathname does not include 
438      * the drive letter, colon or leading backslash.
439      */
440
441     if (ptr[0] == '\\')
442     {
443         /*
444          * FIXME: We should probably just strip host part from name...
445          */
446         FIXME( "UNC names are not supported.\n" );
447         SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
448         return FALSE;
449     }
450     else if (!ptr[0] || ptr[1] != ':' || ptr[2] != '\\')
451     {
452         WARN( "Path is neither UNC nor DOS path: %s\n",
453               wine_dbgstr_w(ptr) );
454         SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
455         return FALSE;
456     }
457     else
458     {
459         /* Remove drive letter, colon and leading backslash. */
460         ptr += 3;
461     }
462
463     /*
464      * Convert into OEM string.
465      */
466     
467     if (!WideCharToMultiByte(CP_OEMCP, 0, ptr, -1, pathA, 
468                              MAX_PATH, NULL, NULL))
469     {
470         WARN( "Cannot convert path!\n" );
471         SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
472         return FALSE;
473     }
474
475     /*
476      * Success.
477      */
478
479     if (!islong)
480     {
481         /* Undocumented success code. */
482         SET_AX( context, 0x0100 );
483         
484         /* Truncate buffer to 64 bytes. */
485         pathA[63] = 0;
486     }
487
488     TRACE( "%c:=%s\n", 'A' + new_drive, pathA );
489
490     strcpy( buffer, pathA );
491     return TRUE;
492 }
493
494
495 /***********************************************************************
496  *           INT21_SetCurrentDirectory
497  *
498  * Handler for:
499  * - function 0x3b
500  * - subfunction 0x3b of function 0x71
501  */
502 static BOOL INT21_SetCurrentDirectory( CONTEXT86 *context )
503 {
504     WCHAR dirW[MAX_PATH];
505     char *dirA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
506     BYTE  drive = INT21_GetCurrentDrive();
507     BOOL  result;
508
509     TRACE( "SET CURRENT DIRECTORY %s\n", dirA );
510
511     MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
512     result = SetCurrentDirectoryW( dirW );
513
514     /* This function must not change current drive. */
515     INT21_SetCurrentDrive( drive );
516
517     return result;
518 }
519
520
521 /***********************************************************************
522  *           INT21_CreateFile
523  *
524  * Handler for:
525  * - function 0x3c
526  * - function 0x3d
527  * - function 0x5b
528  * - function 0x6c
529  * - subfunction 0x6c of function 0x71
530  */
531 static BOOL INT21_CreateFile( CONTEXT86 *context, 
532                               DWORD      pathSegOff,
533                               BOOL       returnStatus,
534                               WORD       dosAccessShare,
535                               BYTE       dosAction )
536 {
537     WORD   dosStatus;
538     char  *pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, pathSegOff);
539     WCHAR  pathW[MAX_PATH];   
540     DWORD  winAccess;
541     DWORD  winAttributes;
542     HANDLE winHandle;
543     DWORD  winMode;
544     DWORD  winSharing;
545
546     TRACE( "CreateFile called: function=%02x, action=%02x, access/share=%04x, "
547            "create flags=%04x, file=%s.\n",
548            AH_reg(context), dosAction, dosAccessShare, CX_reg(context), pathA );
549
550     /*
551      * Application tried to create/open a file whose name 
552      * ends with a backslash. This is not allowed.
553      *
554      * FIXME: This needs to be validated, especially the return value.
555      */
556     if (pathA[strlen(pathA) - 1] == '/')
557     {
558         SetLastError( ERROR_FILE_NOT_FOUND );
559         return FALSE;
560     }
561
562     /*
563      * Convert DOS action flags into Win32 creation disposition parameter.
564      */ 
565     switch(dosAction)
566     {
567     case 0x01:
568         winMode = OPEN_EXISTING;
569         break;
570     case 0x02:
571         winMode = TRUNCATE_EXISTING;
572         break;
573     case 0x10:
574         winMode = CREATE_NEW;
575         break;
576     case 0x11:
577         winMode = OPEN_ALWAYS;
578         break;
579     case 0x12:
580         winMode = CREATE_ALWAYS;
581         break;
582     default:
583         SetLastError( ERROR_INVALID_PARAMETER );
584         return FALSE;
585     }
586
587     /*
588      * Convert DOS access/share flags into Win32 desired access parameter.
589      */ 
590     switch(dosAccessShare & 0x07)
591     {
592     case OF_READ:
593         winAccess = GENERIC_READ;
594         break;
595     case OF_WRITE:
596         winAccess = GENERIC_WRITE;
597         break;
598     case OF_READWRITE:
599         winAccess = GENERIC_READ | GENERIC_WRITE;
600         break;
601     case 0x04:
602         /*
603          * Read-only, do not modify file's last-access time (DOS7).
604          *
605          * FIXME: How to prevent modification of last-access time?
606          */
607         winAccess = GENERIC_READ;
608         break;
609     default:
610         winAccess = 0;
611     }
612
613     /*
614      * Convert DOS access/share flags into Win32 share mode parameter.
615      */ 
616     switch(dosAccessShare & 0x70)
617     {
618     case OF_SHARE_EXCLUSIVE:  
619         winSharing = 0; 
620         break;
621     case OF_SHARE_DENY_WRITE: 
622         winSharing = FILE_SHARE_READ; 
623         break;
624     case OF_SHARE_DENY_READ:  
625         winSharing = FILE_SHARE_WRITE; 
626         break;
627     case OF_SHARE_DENY_NONE:
628     case OF_SHARE_COMPAT:
629     default:
630         winSharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
631     }
632
633     /*
634      * FIXME: Bit (dosAccessShare & 0x80) represents inheritance.
635      *        What to do with this bit?
636      * FIXME: Bits in the high byte of dosAccessShare are not supported.
637      *        See both function 0x6c and subfunction 0x6c of function 0x71 for
638      *        definition of these bits.
639      */
640
641     /*
642      * Convert DOS create attributes into Win32 flags and attributes parameter.
643      */
644     if (winMode == OPEN_EXISTING || winMode == TRUNCATE_EXISTING)
645     {
646         winAttributes = 0;
647     }
648     else
649     {        
650         WORD dosAttributes = CX_reg(context);
651
652         if (dosAttributes & FILE_ATTRIBUTE_LABEL)
653         {
654             /*
655              * Application tried to create volume label entry.
656              * This is difficult to support so we do not allow it.
657              *
658              * FIXME: If volume does not already have a label, 
659              *        this function is supposed to succeed.
660              */
661             SetLastError( ERROR_ACCESS_DENIED );
662             return TRUE;
663         }
664
665         winAttributes = dosAttributes & 
666             (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | 
667              FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE);
668     }
669
670     /*
671      * Open the file.
672      */
673     MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
674
675     winHandle = CreateFileW( pathW, winAccess, winSharing, NULL, 
676                              winMode, winAttributes, 0 );
677
678     if (winHandle == INVALID_HANDLE_VALUE)
679         return FALSE;
680
681     /*
682      * Determine DOS file status.
683      *
684      * 1 = file opened
685      * 2 = file created
686      * 3 = file replaced
687      */
688     switch(winMode)
689     {
690     case OPEN_EXISTING:
691         dosStatus = 1;
692         break;
693     case TRUNCATE_EXISTING:
694         dosStatus = 3; 
695         break;
696     case CREATE_NEW:
697         dosStatus = 2;
698         break;
699     case OPEN_ALWAYS:
700         dosStatus = (GetLastError() == ERROR_ALREADY_EXISTS) ? 1 : 2;
701         break;
702     case CREATE_ALWAYS:
703         dosStatus = (GetLastError() == ERROR_ALREADY_EXISTS) ? 3 : 2;
704         break;
705     default:
706         dosStatus = 0;
707     }
708
709     /*
710      * Return DOS file handle and DOS status.
711      */
712     SET_AX( context, Win32HandleToDosFileHandle(winHandle) );
713
714     if (returnStatus)
715         SET_CX( context, dosStatus );
716
717     TRACE( "CreateFile finished: handle=%d, status=%d.\n", 
718            AX_reg(context), dosStatus );
719
720     return TRUE;
721 }
722
723
724 /***********************************************************************
725  *           INT21_BufferedInput
726  *
727  * Handler for function 0x0a and reading from console using
728  * function 0x3f.
729  *
730  * Reads a string of characters from standard input until
731  * enter key is pressed. Returns either number of characters 
732  * read from console including terminating CR or 
733  * zero if capacity was zero.
734  */
735 static WORD INT21_BufferedInput( CONTEXT86 *context, BYTE *ptr, WORD capacity )
736 {
737     BYTE length = 0;
738
739     /*
740      * Return immediately if capacity is zero.
741      */
742     if (capacity == 0)
743         return 0;
744
745     while(TRUE)
746     {
747         BYTE ascii;
748         BYTE scan;
749
750         DOSVM_Int16ReadChar( &ascii, &scan, context );
751
752         if (ascii == '\r' || ascii == '\n')
753         {
754             /*
755              * FIXME: What should be echoed here?
756              */
757             DOSVM_PutChar( '\r' );
758             DOSVM_PutChar( '\n' );
759             ptr[length] = '\r';
760             return length + 1;
761         }
762
763         /*
764          * FIXME: This function is supposed to support
765          *        DOS editing keys...
766          */
767
768         /*
769          * If the buffer becomes filled to within one byte of
770          * capacity, DOS rejects all further characters up to,
771          * but not including, the terminating carriage return.
772          */
773         if (ascii != 0 && length < capacity-1)
774         {
775             DOSVM_PutChar( ascii );
776             ptr[length] = ascii;
777             length++;
778         }
779     }
780 }
781
782
783 /***********************************************************************
784  *           INT21_GetCurrentDTA
785  */
786 static BYTE *INT21_GetCurrentDTA( CONTEXT86 *context )
787 {
788     TDB *pTask = GlobalLock16(GetCurrentTask());
789
790     /* FIXME: This assumes DTA was set correctly! */
791     return (BYTE *)CTX_SEG_OFF_TO_LIN( context, SELECTOROF(pTask->dta),
792                                                 (DWORD)OFFSETOF(pTask->dta) );
793 }
794
795
796 /***********************************************************************
797  *           INT21_OpenFileUsingFCB
798  *
799  * Handler for function 0x0f.
800  *
801  * PARAMS
802  *  DX:DX [I/O] File control block (FCB or XFCB) of unopened file
803  *
804  * RETURNS (in AL)
805  *  0x00: successful
806  *  0xff: failed
807  *
808  * NOTES
809  *  Opens a FCB file for read/write in compatibility mode. Upon calling
810  *  the FCB must have the drive_number, file_name, and file_extension
811  *  fields filled and all other bytes cleared.
812  */
813 static void INT21_OpenFileUsingFCB( CONTEXT86 *context )
814 {
815     struct FCB *fcb;
816     struct XFCB *xfcb;
817     char file_path[16];
818     char *pos;
819     HANDLE handle;
820     HFILE16 hfile16;
821     BY_HANDLE_FILE_INFORMATION info;
822     BYTE AL_result;
823
824     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
825     if (fcb->drive_number == 0xff) {
826         xfcb = (struct XFCB *) fcb;
827         fcb = (struct FCB *) xfcb->fcb;
828     } /* if */
829
830     AL_result = 0;
831     file_path[0] = 'A' + INT21_MapDrive( fcb->drive_number );
832
833     if (AL_result == 0) {
834         file_path[1] = ':';
835         pos = &file_path[2];
836         memcpy(pos, fcb->file_name, 8);
837         pos[8] = ' ';
838         pos[9] = '\0';
839         pos = strchr(pos, ' ');
840         *pos = '.';
841         pos++;
842         memcpy(pos, fcb->file_extension, 3);
843         pos[3] = ' ';
844         pos[4] = '\0';
845         pos = strchr(pos, ' ');
846         *pos = '\0';
847
848         handle = (HANDLE) _lopen(file_path, OF_READWRITE);
849         if (handle == INVALID_HANDLE_VALUE) {
850             TRACE("_lopen(\"%s\") failed: INVALID_HANDLE_VALUE\n", file_path);
851             AL_result = 0xff; /* failed */
852         } else {
853             hfile16 = Win32HandleToDosFileHandle(handle);
854             if (hfile16 == HFILE_ERROR16) {
855                 TRACE("Win32HandleToDosFileHandle(%p) failed: HFILE_ERROR\n", handle);
856                 CloseHandle(handle);
857                 AL_result = 0xff; /* failed */
858             } else if (hfile16 > 255) {
859                 TRACE("hfile16 (=%d) larger than 255 for \"%s\"\n", hfile16, file_path);
860                 _lclose16(hfile16);
861                 AL_result = 0xff; /* failed */
862             } else {
863                 if (!GetFileInformationByHandle(handle, &info)) {
864                     TRACE("GetFileInformationByHandle(%d, %p) for \"%s\" failed\n",
865                           hfile16, handle, file_path);
866                     _lclose16(hfile16);
867                     AL_result = 0xff; /* failed */
868                 } else {
869                     fcb->drive_number = file_path[0] - 'A' + 1;
870                     fcb->current_block_number = 0;
871                     fcb->logical_record_size = 128;
872                     fcb->file_size = info.nFileSizeLow;
873                     FileTimeToDosDateTime(&info.ftLastWriteTime,
874                         &fcb->date_of_last_write, &fcb->time_of_last_write);
875                     fcb->file_number = hfile16;
876                     fcb->attributes = 0xc2;
877                     fcb->starting_cluster = 0; /* don't know correct init value */
878                     fcb->sequence_number = 0; /* don't know correct init value */
879                     fcb->file_attributes = info.dwFileAttributes;
880                     /* The following fields are not initialized */
881                     /* by the native function: */
882                     /* unused */
883                     /* record_within_current_block */
884                     /* random_access_record_number */
885
886                     TRACE("successful opened file \"%s\" as %d (handle %p)\n",
887                           file_path, hfile16, handle);
888                     AL_result = 0x00; /* successful */
889                 } /* if */
890             } /* if */
891         } /* if */
892     } /* if */
893     SET_AL(context, AL_result);
894 }
895
896
897 /***********************************************************************
898  *           INT21_CloseFileUsingFCB
899  *
900  * Handler for function 0x10.
901  *
902  * PARAMS
903  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
904  *
905  * RETURNS (in AL)
906  *  0x00: successful
907  *  0xff: failed
908  *
909  * NOTES
910  *  Closes a FCB file.
911  */
912 static void INT21_CloseFileUsingFCB( CONTEXT86 *context )
913 {
914     struct FCB *fcb;
915     struct XFCB *xfcb;
916     BYTE AL_result;
917
918     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
919     if (fcb->drive_number == 0xff) {
920         xfcb = (struct XFCB *) fcb;
921         fcb = (struct FCB *) xfcb->fcb;
922     } /* if */
923
924     if (_lclose16((HFILE16) fcb->file_number) != 0) {
925         TRACE("_lclose16(%d) failed\n", fcb->file_number);
926         AL_result = 0xff; /* failed */
927     } else {
928         TRACE("successful closed file %d\n", fcb->file_number);
929         AL_result = 0x00; /* successful */
930     } /* if */
931     SET_AL(context, AL_result);
932 }
933
934
935 /***********************************************************************
936  *           INT21_SequentialReadFromFCB
937  *
938  * Handler for function 0x14.
939  *
940  * PARAMS
941  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
942  *
943  * RETURNS (in AL)
944  *  0: successful
945  *  1: end of file, no data read
946  *  2: segment wrap in DTA, no data read (not returned now)
947  *  3: end of file, partial record read
948  *
949  * NOTES
950  *  Reads a record with the size FCB->logical_record_size from the FCB
951  *  to the disk transfer area. The position of the record is specified
952  *  with FCB->current_block_number and FCB->record_within_current_block.
953  *  Then FCB->current_block_number and FCB->record_within_current_block
954  *  are updated to point to the next record. If a partial record is
955  *  read, it is filled with zeros up to the FCB->logical_record_size.
956  */
957 static void INT21_SequentialReadFromFCB( CONTEXT86 *context )
958 {
959     struct FCB *fcb;
960     struct XFCB *xfcb;
961     HANDLE handle;
962     DWORD record_number;
963     long position;
964     BYTE *disk_transfer_area;
965     UINT bytes_read;
966     BYTE AL_result;
967
968     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
969     if (fcb->drive_number == 0xff) {
970         xfcb = (struct XFCB *) fcb;
971         fcb = (struct FCB *) xfcb->fcb;
972     } /* if */
973
974     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
975     if (handle == INVALID_HANDLE_VALUE) {
976         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
977             fcb->file_number);
978         AL_result = 0x01; /* end of file, no data read */
979     } else {
980         record_number = 128 * fcb->current_block_number + fcb->record_within_current_block;
981         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
982         if (position != record_number * fcb->logical_record_size) {
983             TRACE("seek(%d, %ld, 0) failed with %ld\n",
984                   fcb->file_number, record_number * fcb->logical_record_size, position);
985             AL_result = 0x01; /* end of file, no data read */
986         } else {
987             disk_transfer_area = INT21_GetCurrentDTA(context);
988             bytes_read = _lread((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
989             if (bytes_read != fcb->logical_record_size) {
990                 TRACE("_lread(%d, %p, %d) failed with %d\n",
991                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_read);
992                 if (bytes_read == 0) {
993                     AL_result = 0x01; /* end of file, no data read */
994                 } else {
995                     memset(&disk_transfer_area[bytes_read], 0, fcb->logical_record_size - bytes_read);
996                     AL_result = 0x03; /* end of file, partial record read */
997                 } /* if */
998             } else {
999                 TRACE("successful read %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1000                     bytes_read, record_number, position, fcb->file_number, handle);
1001                 AL_result = 0x00; /* successful */
1002             } /* if */
1003         } /* if */
1004     } /* if */
1005     if (AL_result == 0x00 || AL_result == 0x03) {
1006         if (fcb->record_within_current_block == 127) {
1007             fcb->record_within_current_block = 0;
1008             fcb->current_block_number++;
1009         } else {
1010             fcb->record_within_current_block++;
1011         } /* if */
1012     } /* if */
1013     SET_AL(context, AL_result);
1014 }
1015
1016
1017 /***********************************************************************
1018  *           INT21_SequentialWriteToFCB
1019  *
1020  * Handler for function 0x15.
1021  *
1022  * PARAMS
1023  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1024  *
1025  * RETURNS (in AL)
1026  *  0: successful
1027  *  1: disk full
1028  *  2: segment wrap in DTA (not returned now)
1029  *
1030  * NOTES
1031  *  Writes a record with the size FCB->logical_record_size from the disk
1032  *  transfer area to the FCB. The position of the record is specified
1033  *  with FCB->current_block_number and FCB->record_within_current_block.
1034  *  Then FCB->current_block_number and FCB->record_within_current_block
1035  *  are updated to point to the next record. 
1036  */
1037 static void INT21_SequentialWriteToFCB( CONTEXT86 *context )
1038 {
1039     struct FCB *fcb;
1040     struct XFCB *xfcb;
1041     HANDLE handle;
1042     DWORD record_number;
1043     long position;
1044     BYTE *disk_transfer_area;
1045     UINT bytes_written;
1046     BYTE AL_result;
1047
1048     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1049     if (fcb->drive_number == 0xff) {
1050         xfcb = (struct XFCB *) fcb;
1051         fcb = (struct FCB *) xfcb->fcb;
1052     } /* if */
1053
1054     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1055     if (handle == INVALID_HANDLE_VALUE) {
1056         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1057             fcb->file_number);
1058         AL_result = 0x01; /* disk full */
1059     } else {
1060         record_number = 128 * fcb->current_block_number + fcb->record_within_current_block;
1061         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1062         if (position != record_number * fcb->logical_record_size) {
1063             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1064                   fcb->file_number, record_number * fcb->logical_record_size, position);
1065             AL_result = 0x01; /* disk full */
1066         } else {
1067             disk_transfer_area = INT21_GetCurrentDTA(context);
1068             bytes_written = _lwrite((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
1069             if (bytes_written != fcb->logical_record_size) {
1070                 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
1071                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_written);
1072                 AL_result = 0x01; /* disk full */
1073             } else {
1074                 TRACE("successful written %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1075                     bytes_written, record_number, position, fcb->file_number, handle);
1076                 AL_result = 0x00; /* successful */
1077             } /* if */
1078         } /* if */
1079     } /* if */
1080     if (AL_result == 0x00) {
1081         if (fcb->record_within_current_block == 127) {
1082             fcb->record_within_current_block = 0;
1083             fcb->current_block_number++;
1084         } else {
1085             fcb->record_within_current_block++;
1086         } /* if */
1087     } /* if */
1088     SET_AL(context, AL_result);
1089 }
1090
1091
1092 /***********************************************************************
1093  *           INT21_ReadRandomRecordFromFCB
1094  *
1095  * Handler for function 0x21.
1096  *
1097  * PARAMS
1098  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1099  *
1100  * RETURNS (in AL)
1101  *  0: successful
1102  *  1: end of file, no data read
1103  *  2: segment wrap in DTA, no data read (not returned now)
1104  *  3: end of file, partial record read
1105  *
1106  * NOTES
1107  *  Reads a record with the size FCB->logical_record_size from
1108  *  the FCB to the disk transfer area. The position of the record
1109  *  is specified with FCB->random_access_record_number. The
1110  *  FCB->random_access_record_number is not updated. If a partial record
1111  *  is read, it is filled with zeros up to the FCB->logical_record_size.
1112  */
1113 static void INT21_ReadRandomRecordFromFCB( CONTEXT86 *context )
1114 {
1115     struct FCB *fcb;
1116     struct XFCB *xfcb;
1117     HANDLE handle;
1118     DWORD record_number;
1119     long position;
1120     BYTE *disk_transfer_area;
1121     UINT bytes_read;
1122     BYTE AL_result;
1123
1124     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1125     if (fcb->drive_number == 0xff) {
1126         xfcb = (struct XFCB *) fcb;
1127         fcb = (struct FCB *) xfcb->fcb;
1128     } /* if */
1129
1130     memcpy(&record_number, fcb->random_access_record_number, 4);
1131     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1132     if (handle == INVALID_HANDLE_VALUE) {
1133         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1134             fcb->file_number);
1135         AL_result = 0x01; /* end of file, no data read */
1136     } else {
1137         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1138         if (position != record_number * fcb->logical_record_size) {
1139             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1140                   fcb->file_number, record_number * fcb->logical_record_size, position);
1141             AL_result = 0x01; /* end of file, no data read */
1142         } else {
1143             disk_transfer_area = INT21_GetCurrentDTA(context);
1144             bytes_read = _lread((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
1145             if (bytes_read != fcb->logical_record_size) {
1146                 TRACE("_lread(%d, %p, %d) failed with %d\n",
1147                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_read);
1148                 if (bytes_read == 0) {
1149                     AL_result = 0x01; /* end of file, no data read */
1150                 } else {
1151                     memset(&disk_transfer_area[bytes_read], 0, fcb->logical_record_size - bytes_read);
1152                     AL_result = 0x03; /* end of file, partial record read */
1153                 } /* if */
1154             } else {
1155                 TRACE("successful read %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1156                     bytes_read, record_number, position, fcb->file_number, handle);
1157                 AL_result = 0x00; /* successful */
1158             } /* if */
1159         } /* if */
1160     } /* if */
1161     fcb->current_block_number = record_number / 128;
1162     fcb->record_within_current_block = record_number % 128;
1163     SET_AL(context, AL_result);
1164 }
1165
1166
1167 /***********************************************************************
1168  *           INT21_WriteRandomRecordToFCB
1169  *
1170  * Handler for function 0x22.
1171  *
1172  * PARAMS
1173  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1174  *
1175  * RETURNS (in AL)
1176  *  0: successful
1177  *  1: disk full
1178  *  2: segment wrap in DTA (not returned now)
1179  *
1180  * NOTES
1181  *  Writes a record with the size FCB->logical_record_size from
1182  *  the disk transfer area to the FCB. The position of the record
1183  *  is specified with FCB->random_access_record_number. The
1184  *  FCB->random_access_record_number is not updated.
1185  */
1186 static void INT21_WriteRandomRecordToFCB( CONTEXT86 *context )
1187 {
1188     struct FCB *fcb;
1189     struct XFCB *xfcb;
1190     HANDLE handle;
1191     DWORD record_number;
1192     long position;
1193     BYTE *disk_transfer_area;
1194     UINT bytes_written;
1195     BYTE AL_result;
1196
1197     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1198     if (fcb->drive_number == 0xff) {
1199         xfcb = (struct XFCB *) fcb;
1200         fcb = (struct FCB *) xfcb->fcb;
1201     } /* if */
1202
1203     memcpy(&record_number, fcb->random_access_record_number, 4);
1204     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1205     if (handle == INVALID_HANDLE_VALUE) {
1206         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1207             fcb->file_number);
1208         AL_result = 0x01; /* disk full */
1209     } else {
1210         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1211         if (position != record_number * fcb->logical_record_size) {
1212             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1213                   fcb->file_number, record_number * fcb->logical_record_size, position);
1214             AL_result = 0x01; /* disk full */
1215         } else {
1216             disk_transfer_area = INT21_GetCurrentDTA(context);
1217             bytes_written = _lwrite((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
1218             if (bytes_written != fcb->logical_record_size) {
1219                 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
1220                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_written);
1221                 AL_result = 0x01; /* disk full */
1222             } else {
1223                 TRACE("successful written %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1224                     bytes_written, record_number, position, fcb->file_number, handle);
1225                 AL_result = 0x00; /* successful */
1226             } /* if */
1227         } /* if */
1228     } /* if */
1229     fcb->current_block_number = record_number / 128;
1230     fcb->record_within_current_block = record_number % 128;
1231     SET_AL(context, AL_result);
1232 }
1233
1234
1235 /***********************************************************************
1236  *           INT21_RandomBlockReadFromFCB
1237  *
1238  * Handler for function 0x27.
1239  *
1240  * PARAMS
1241  *  CX    [I/O] Number of records to read
1242  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1243  *
1244  * RETURNS (in AL)
1245  *  0: successful
1246  *  1: end of file, no data read
1247  *  2: segment wrap in DTA, no data read (not returned now)
1248  *  3: end of file, partial record read
1249  *
1250  * NOTES
1251  *  Reads several records with the size FCB->logical_record_size from
1252  *  the FCB to the disk transfer area. The number of records to be
1253  *  read is specified in the CX register. The position of the first
1254  *  record is specified with FCB->random_access_record_number. The
1255  *  FCB->random_access_record_number, the FCB->current_block_number
1256  *  and FCB->record_within_current_block are updated to point to the
1257  *  next record after the records read. If a partial record is read,
1258  *  it is filled with zeros up to the FCB->logical_record_size. The
1259  *  CX register is set to the number of successfully read records.
1260  */
1261 static void INT21_RandomBlockReadFromFCB( CONTEXT86 *context )
1262 {
1263     struct FCB *fcb;
1264     struct XFCB *xfcb;
1265     HANDLE handle;
1266     DWORD record_number;
1267     long position;
1268     BYTE *disk_transfer_area;
1269     UINT records_requested;
1270     UINT bytes_requested;
1271     UINT bytes_read;
1272     UINT records_read;
1273     BYTE AL_result;
1274
1275     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1276     if (fcb->drive_number == 0xff) {
1277         xfcb = (struct XFCB *) fcb;
1278         fcb = (struct FCB *) xfcb->fcb;
1279     } /* if */
1280
1281     memcpy(&record_number, fcb->random_access_record_number, 4);
1282     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1283     if (handle == INVALID_HANDLE_VALUE) {
1284         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1285             fcb->file_number);
1286         records_read = 0;
1287         AL_result = 0x01; /* end of file, no data read */
1288     } else {
1289         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1290         if (position != record_number * fcb->logical_record_size) {
1291             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1292                   fcb->file_number, record_number * fcb->logical_record_size, position);
1293             records_read = 0;
1294             AL_result = 0x01; /* end of file, no data read */
1295         } else {
1296             disk_transfer_area = INT21_GetCurrentDTA(context);
1297             records_requested = CX_reg(context);
1298             bytes_requested = (UINT) records_requested * fcb->logical_record_size;
1299             bytes_read = _lread((HFILE) handle, disk_transfer_area, bytes_requested);
1300             if (bytes_read != bytes_requested) {
1301                 TRACE("_lread(%d, %p, %d) failed with %d\n",
1302                       fcb->file_number, disk_transfer_area, bytes_requested, bytes_read);
1303                 records_read = bytes_read / fcb->logical_record_size;
1304                 if (bytes_read % fcb->logical_record_size == 0) {
1305                     AL_result = 0x01; /* end of file, no data read */
1306                 } else {
1307                     records_read++;
1308                     memset(&disk_transfer_area[bytes_read], 0, records_read * fcb->logical_record_size - bytes_read);
1309                     AL_result = 0x03; /* end of file, partial record read */
1310                 } /* if */
1311             } else {
1312                 TRACE("successful read %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1313                     bytes_read, record_number, position, fcb->file_number, handle);
1314                 records_read = records_requested;
1315                 AL_result = 0x00; /* successful */
1316             } /* if */
1317         } /* if */
1318     } /* if */
1319     record_number += records_read;
1320     memcpy(fcb->random_access_record_number, &record_number, 4);
1321     fcb->current_block_number = record_number / 128;
1322     fcb->record_within_current_block = record_number % 128;
1323     SET_CX(context, records_read);
1324     SET_AL(context, AL_result);
1325 }
1326
1327
1328 /***********************************************************************
1329  *           INT21_RandomBlockWriteToFCB
1330  *
1331  * Handler for function 0x28.
1332  *
1333  * PARAMS
1334  *  CX    [I/O] Number of records to write
1335  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1336  *
1337  * RETURNS (in AL)
1338  *  0: successful
1339  *  1: disk full
1340  *  2: segment wrap in DTA (not returned now)
1341  *
1342  * NOTES
1343  *  Writes several records with the size FCB->logical_record_size from
1344  *  the disk transfer area to the FCB. The number of records to be
1345  *  written is specified in the CX register. The position of the first
1346  *  record is specified with FCB->random_access_record_number. The
1347  *  FCB->random_access_record_number, the FCB->current_block_number
1348  *  and FCB->record_within_current_block are updated to point to the
1349  *  next record after the records written. The CX register is set to
1350  *  the number of successfully written records.
1351  */
1352 static void INT21_RandomBlockWriteToFCB( CONTEXT86 *context )
1353 {
1354     struct FCB *fcb;
1355     struct XFCB *xfcb;
1356     HANDLE handle;
1357     DWORD record_number;
1358     long position;
1359     BYTE *disk_transfer_area;
1360     UINT records_requested;
1361     UINT bytes_requested;
1362     UINT bytes_written;
1363     UINT records_written;
1364     BYTE AL_result;
1365
1366     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1367     if (fcb->drive_number == 0xff) {
1368         xfcb = (struct XFCB *) fcb;
1369         fcb = (struct FCB *) xfcb->fcb;
1370     } /* if */
1371
1372     memcpy(&record_number, fcb->random_access_record_number, 4);
1373     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1374     if (handle == INVALID_HANDLE_VALUE) {
1375         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1376             fcb->file_number);
1377         records_written = 0;
1378         AL_result = 0x01; /* disk full */
1379     } else {
1380         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1381         if (position != record_number * fcb->logical_record_size) {
1382             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1383                   fcb->file_number, record_number * fcb->logical_record_size, position);
1384             records_written = 0;
1385             AL_result = 0x01; /* disk full */
1386         } else {
1387             disk_transfer_area = INT21_GetCurrentDTA(context);
1388             records_requested = CX_reg(context);
1389             bytes_requested = (UINT) records_requested * fcb->logical_record_size;
1390             bytes_written = _lwrite((HFILE) handle, disk_transfer_area, bytes_requested);
1391             if (bytes_written != bytes_requested) {
1392                 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
1393                       fcb->file_number, disk_transfer_area, bytes_requested, bytes_written);
1394                 records_written = bytes_written / fcb->logical_record_size;
1395                 AL_result = 0x01; /* disk full */
1396             } else {
1397                 TRACE("successful write %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1398                     bytes_written, record_number, position, fcb->file_number, handle);
1399                 records_written = records_requested;
1400                 AL_result = 0x00; /* successful */
1401             } /* if */
1402         } /* if */
1403     } /* if */
1404     record_number += records_written;
1405     memcpy(fcb->random_access_record_number, &record_number, 4);
1406     fcb->current_block_number = record_number / 128;
1407     fcb->record_within_current_block = record_number % 128;
1408     SET_CX(context, records_written);
1409     SET_AL(context, AL_result);
1410 }
1411
1412
1413 /***********************************************************************
1414  *           INT21_CreateDirectory
1415  *
1416  * Handler for:
1417  * - function 0x39
1418  * - subfunction 0x39 of function 0x71
1419  * - subfunction 0xff of function 0x43 (CL == 0x39)
1420  */
1421 static BOOL INT21_CreateDirectory( CONTEXT86 *context )
1422 {
1423     WCHAR dirW[MAX_PATH];
1424     char *dirA = CTX_SEG_OFF_TO_LIN(context,
1425                                     context->SegDs, 
1426                                     context->Edx);
1427
1428     TRACE( "CREATE DIRECTORY %s\n", dirA );
1429
1430     MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
1431
1432     if (CreateDirectoryW(dirW, NULL))
1433         return TRUE;
1434
1435     /*
1436      * FIXME: CreateDirectory's LastErrors will clash with the ones
1437      *        used by DOS. AH=39 only returns 3 (path not found) and 
1438      *        5 (access denied), while CreateDirectory return several
1439      *        ones. Remap some of them. -Marcus
1440      */
1441     switch (GetLastError()) 
1442     {
1443     case ERROR_ALREADY_EXISTS:
1444     case ERROR_FILENAME_EXCED_RANGE:
1445     case ERROR_DISK_FULL:
1446         SetLastError(ERROR_ACCESS_DENIED);
1447         break;
1448     default: 
1449         break;
1450     }
1451
1452     return FALSE;
1453 }
1454
1455
1456 /***********************************************************************
1457  *           INT21_ExtendedCountryInformation
1458  *
1459  * Handler for function 0x65.
1460  */
1461 static void INT21_ExtendedCountryInformation( CONTEXT86 *context )
1462 {
1463     BYTE *dataptr = CTX_SEG_OFF_TO_LIN( context, context->SegEs, context->Edi );
1464
1465     TRACE( "GET EXTENDED COUNTRY INFORMATION, subfunction %02x\n",
1466            AL_reg(context) );
1467
1468     /*
1469      * Check subfunctions that are passed country and code page.
1470      */
1471     if (AL_reg(context) >= 0x01 && AL_reg(context) <= 0x07)
1472     {
1473         WORD country = DX_reg(context);
1474         WORD codepage = BX_reg(context);
1475
1476         if (country != 0xffff && country != INT21_GetSystemCountryCode())
1477             FIXME( "Requested info on non-default country %04x\n", country );
1478
1479         if (codepage != 0xffff && codepage != GetOEMCP())
1480             FIXME( "Requested info on non-default code page %04x\n", codepage );
1481     }
1482
1483     switch (AL_reg(context)) {
1484     case 0x00: /* SET GENERAL INTERNATIONALIZATION INFO */
1485         INT_BARF( context, 0x21 );
1486         SET_CFLAG( context );
1487         break;
1488
1489     case 0x01: /* GET GENERAL INTERNATIONALIZATION INFO */
1490         TRACE( "Get general internationalization info\n" );
1491         dataptr[0] = 0x01; /* Info ID */
1492         *(WORD*)(dataptr+1) = 38; /* Size of the following info */
1493         *(WORD*)(dataptr+3) = INT21_GetSystemCountryCode(); /* Country ID */
1494         *(WORD*)(dataptr+5) = GetOEMCP(); /* Code page */
1495         INT21_FillCountryInformation( dataptr + 7 );
1496         SET_CX( context, 41 ); /* Size of returned info */
1497         break;
1498         
1499     case 0x02: /* GET POINTER TO UPPERCASE TABLE */
1500     case 0x04: /* GET POINTER TO FILENAME UPPERCASE TABLE */
1501         TRACE( "Get pointer to uppercase table\n" );
1502         dataptr[0] = AL_reg(context); /* Info ID */
1503         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1504                                            offsetof(INT21_HEAP, uppercase_size) );
1505         SET_CX( context, 5 ); /* Size of returned info */
1506         break;
1507
1508     case 0x03: /* GET POINTER TO LOWERCASE TABLE */
1509         TRACE( "Get pointer to lowercase table\n" );
1510         dataptr[0] = 0x03; /* Info ID */
1511         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1512                                            offsetof(INT21_HEAP, lowercase_size) );
1513         SET_CX( context, 5 ); /* Size of returned info */
1514         break;
1515
1516     case 0x05: /* GET POINTER TO FILENAME TERMINATOR TABLE */
1517         TRACE("Get pointer to filename terminator table\n");
1518         dataptr[0] = 0x05; /* Info ID */
1519         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1520                                            offsetof(INT21_HEAP, filename_size) );
1521         SET_CX( context, 5 ); /* Size of returned info */
1522         break;
1523
1524     case 0x06: /* GET POINTER TO COLLATING SEQUENCE TABLE */
1525         TRACE("Get pointer to collating sequence table\n");
1526         dataptr[0] = 0x06; /* Info ID */
1527         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1528                                            offsetof(INT21_HEAP, collating_size) );
1529         SET_CX( context, 5 ); /* Size of returned info */
1530         break;
1531
1532     case 0x07: /* GET POINTER TO DBCS LEAD BYTE TABLE */
1533         TRACE("Get pointer to DBCS lead byte table\n");
1534         dataptr[0] = 0x07; /* Info ID */
1535         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1536                                            offsetof(INT21_HEAP, dbcs_size) );
1537         SET_CX( context, 5 ); /* Size of returned info */
1538         break;
1539
1540     case 0x20: /* CAPITALIZE CHARACTER */
1541     case 0xa0: /* CAPITALIZE FILENAME CHARACTER */
1542         TRACE("Convert char to uppercase\n");
1543         SET_DL( context, toupper(DL_reg(context)) );
1544         break;
1545
1546     case 0x21: /* CAPITALIZE STRING */
1547     case 0xa1: /* CAPITALIZE COUNTED FILENAME STRING */
1548         TRACE("Convert string to uppercase with length\n");
1549         {
1550             char *ptr = (char *)CTX_SEG_OFF_TO_LIN( context,
1551                                                     context->SegDs,
1552                                                     context->Edx );
1553             WORD len = CX_reg(context);
1554             while (len--) { *ptr = toupper(*ptr); ptr++; }
1555         }
1556         break;
1557
1558     case 0x22: /* CAPITALIZE ASCIIZ STRING */
1559     case 0xa2: /* CAPITALIZE ASCIIZ FILENAME */
1560         TRACE("Convert ASCIIZ string to uppercase\n");
1561         _strupr( (LPSTR)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx) );
1562         break;
1563
1564     case 0x23: /* DETERMINE IF CHARACTER REPRESENTS YES/NO RESPONSE */
1565         INT_BARF( context, 0x21 );
1566         SET_CFLAG( context );
1567         break;
1568
1569     default:
1570         INT_BARF( context, 0x21 );
1571         SET_CFLAG(context);
1572         break;
1573     }
1574 }
1575
1576
1577 /***********************************************************************
1578  *           INT21_FileAttributes
1579  *
1580  * Handler for:
1581  * - function 0x43
1582  * - subfunction 0x43 of function 0x71
1583  */
1584 static BOOL INT21_FileAttributes( CONTEXT86 *context, 
1585                                   BYTE       subfunction,
1586                                   BOOL       islong )
1587 {
1588     WCHAR fileW[MAX_PATH];
1589     char *fileA = CTX_SEG_OFF_TO_LIN(context, 
1590                                      context->SegDs, 
1591                                      context->Edx);
1592     HANDLE   handle;
1593     BOOL     status;
1594     FILETIME filetime;
1595     DWORD    result;
1596     WORD     date, time;
1597
1598     switch (subfunction)
1599     {
1600     case 0x00: /* GET FILE ATTRIBUTES */
1601         TRACE( "GET FILE ATTRIBUTES for %s\n", fileA );
1602         MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1603
1604         result = GetFileAttributesW( fileW );
1605         if (result == -1)
1606             return FALSE;
1607         else
1608         {
1609             SET_CX( context, (WORD)result );
1610             if (!islong)
1611                 SET_AX( context, (WORD)result ); /* DR DOS */
1612         }
1613         break;
1614
1615     case 0x01: /* SET FILE ATTRIBUTES */
1616         TRACE( "SET FILE ATTRIBUTES 0x%02x for %s\n", 
1617                CX_reg(context), fileA );
1618         MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1619
1620         if (!SetFileAttributesW( fileW, CX_reg(context) ))
1621             return FALSE;
1622         break;
1623
1624     case 0x02: /* GET COMPRESSED FILE SIZE */
1625         TRACE( "GET COMPRESSED FILE SIZE for %s\n", fileA );
1626         MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1627
1628         result = GetCompressedFileSizeW( fileW, NULL );
1629         if (result == INVALID_FILE_SIZE)
1630             return FALSE;
1631         else
1632         {
1633             SET_AX( context, LOWORD(result) );
1634             SET_DX( context, HIWORD(result) );
1635         }
1636         break;
1637
1638     case 0x03: /* SET FILE LAST-WRITTEN DATE AND TIME */
1639         if (!islong)
1640             INT_BARF( context, 0x21 );
1641         else
1642         {
1643             TRACE( "SET FILE LAST-WRITTEN DATE AND TIME, file %s\n", fileA );
1644             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1645
1646             handle = CreateFileW( fileW, GENERIC_WRITE, 
1647                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1648                                   NULL, OPEN_EXISTING, 0, 0 );
1649             if (handle == INVALID_HANDLE_VALUE)
1650                 return FALSE;
1651
1652             DosDateTimeToFileTime( DI_reg(context), 
1653                                    CX_reg(context),
1654                                    &filetime );
1655             status = SetFileTime( handle, NULL, NULL, &filetime );
1656
1657             CloseHandle( handle );
1658             return status;
1659         }
1660         break;
1661
1662     case 0x04: /* GET FILE LAST-WRITTEN DATE AND TIME */
1663         if (!islong)
1664             INT_BARF( context, 0x21 );
1665         else
1666         {
1667             TRACE( "GET FILE LAST-WRITTEN DATE AND TIME, file %s\n", fileA );
1668             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1669
1670             handle = CreateFileW( fileW, GENERIC_READ, 
1671                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1672                                   NULL, OPEN_EXISTING, 0, 0 );
1673             if (handle == INVALID_HANDLE_VALUE)
1674                 return FALSE;
1675
1676             status = GetFileTime( handle, NULL, NULL, &filetime );
1677             if (status)
1678             {
1679                 FileTimeToDosDateTime( &filetime, &date, &time );
1680                 SET_DI( context, date );
1681                 SET_CX( context, time );
1682             }
1683
1684             CloseHandle( handle );
1685             return status;
1686         }
1687         break;
1688
1689     case 0x05: /* SET FILE LAST ACCESS DATE */
1690         if (!islong)
1691             INT_BARF( context, 0x21 );
1692         else
1693         {
1694             TRACE( "SET FILE LAST ACCESS DATE, file %s\n", fileA );
1695             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1696
1697             handle = CreateFileW( fileW, GENERIC_WRITE, 
1698                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1699                                   NULL, OPEN_EXISTING, 0, 0 );
1700             if (handle == INVALID_HANDLE_VALUE)
1701                 return FALSE;
1702
1703             DosDateTimeToFileTime( DI_reg(context), 
1704                                    0,
1705                                    &filetime );
1706             status = SetFileTime( handle, NULL, &filetime, NULL );
1707
1708             CloseHandle( handle );
1709             return status;
1710         }
1711         break;
1712
1713     case 0x06: /* GET FILE LAST ACCESS DATE */
1714         if (!islong)
1715             INT_BARF( context, 0x21 );
1716         else
1717         {
1718             TRACE( "GET FILE LAST ACCESS DATE, file %s\n", fileA );
1719             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1720
1721             handle = CreateFileW( fileW, GENERIC_READ, 
1722                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1723                                   NULL, OPEN_EXISTING, 0, 0 );
1724             if (handle == INVALID_HANDLE_VALUE)
1725                 return FALSE;
1726
1727             status = GetFileTime( handle, NULL, &filetime, NULL );
1728             if (status)
1729             {
1730                 FileTimeToDosDateTime( &filetime, &date, NULL );
1731                 SET_DI( context, date );
1732             }
1733
1734             CloseHandle( handle );
1735             return status;
1736         }
1737         break;
1738
1739     case 0x07: /* SET FILE CREATION DATE AND TIME */
1740         if (!islong)
1741             INT_BARF( context, 0x21 );
1742         else
1743         {
1744             TRACE( "SET FILE CREATION DATE AND TIME, file %s\n", fileA );
1745
1746             handle = CreateFileW( fileW, GENERIC_WRITE,
1747                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1748                                   NULL, OPEN_EXISTING, 0, 0 );
1749             if (handle == INVALID_HANDLE_VALUE)
1750                 return FALSE;
1751             
1752             /*
1753              * FIXME: SI has number of 10-millisecond units past time in CX.
1754              */
1755             DosDateTimeToFileTime( DI_reg(context),
1756                                    CX_reg(context),
1757                                    &filetime );
1758             status = SetFileTime( handle, &filetime, NULL, NULL );
1759
1760             CloseHandle( handle );
1761             return status;
1762         }
1763         break;
1764
1765     case 0x08: /* GET FILE CREATION DATE AND TIME */
1766         if (!islong)
1767             INT_BARF( context, 0x21 );
1768         else
1769         {
1770             TRACE( "GET FILE CREATION DATE AND TIME, handle %d\n",
1771                    BX_reg(context) );
1772
1773             handle = CreateFileW( fileW, GENERIC_READ, 
1774                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1775                                   NULL, OPEN_EXISTING, 0, 0 );
1776             if (handle == INVALID_HANDLE_VALUE)
1777                 return FALSE;
1778
1779             status = GetFileTime( handle, &filetime, NULL, NULL );
1780             if (status)
1781             {            
1782                 FileTimeToDosDateTime( &filetime, &date, &time );
1783                 SET_DI( context, date );
1784                 SET_CX( context, time );
1785                 /*
1786                  * FIXME: SI has number of 10-millisecond units past 
1787                  *        time in CX.
1788                  */
1789                 SET_SI( context, 0 );
1790             }
1791
1792             CloseHandle(handle);
1793             return status;
1794         }
1795         break;
1796
1797     case 0xff: /* EXTENDED-LENGTH FILENAME OPERATIONS */
1798         if (islong || context->Ebp != 0x5053)
1799             INT_BARF( context, 0x21 );
1800         else
1801         {
1802             switch(CL_reg(context))
1803             {
1804             case 0x39:
1805                 if (!INT21_CreateDirectory( context ))
1806                     return FALSE;
1807                 break;
1808
1809             case 0x56:
1810                 if (!INT21_RenameFile( context ))
1811                     return FALSE;
1812                 break;
1813
1814             default:
1815                 INT_BARF( context, 0x21 );
1816             }
1817         }
1818         break;
1819
1820     default:
1821         INT_BARF( context, 0x21 );
1822     }
1823
1824     return TRUE;
1825 }
1826
1827
1828 /***********************************************************************
1829  *           INT21_FileDateTime
1830  *
1831  * Handler for function 0x57.
1832  */
1833 static BOOL INT21_FileDateTime( CONTEXT86 *context )
1834 {
1835     HANDLE   handle = DosFileHandleToWin32Handle(BX_reg(context));
1836     FILETIME filetime;
1837     WORD     date, time;
1838
1839     switch (AL_reg(context)) {
1840     case 0x00:  /* Get last-written stamp */
1841         TRACE( "GET FILE LAST-WRITTEN DATE AND TIME, handle %d\n",
1842                BX_reg(context) );
1843         {
1844             if (!GetFileTime( handle, NULL, NULL, &filetime ))
1845                 return FALSE;
1846             FileTimeToDosDateTime( &filetime, &date, &time );
1847             SET_DX( context, date );
1848             SET_CX( context, time );
1849             break;
1850         }
1851
1852     case 0x01:  /* Set last-written stamp */
1853         TRACE( "SET FILE LAST-WRITTEN DATE AND TIME, handle %d\n",
1854                BX_reg(context) );
1855         {
1856             DosDateTimeToFileTime( DX_reg(context), 
1857                                    CX_reg(context),
1858                                    &filetime );
1859             if (!SetFileTime( handle, NULL, NULL, &filetime ))
1860                 return FALSE;
1861             break;
1862         }
1863
1864     case 0x04:  /* Get last access stamp, DOS 7 */
1865         TRACE( "GET FILE LAST ACCESS DATE AND TIME, handle %d\n",
1866                BX_reg(context) );
1867         {
1868             if (!GetFileTime( handle, NULL, &filetime, NULL ))
1869                 return FALSE;
1870             FileTimeToDosDateTime( &filetime, &date, &time );
1871             SET_DX( context, date );
1872             SET_CX( context, time );
1873             break;
1874         }
1875
1876     case 0x05:  /* Set last access stamp, DOS 7 */
1877         TRACE( "SET FILE LAST ACCESS DATE AND TIME, handle %d\n",
1878                BX_reg(context) );
1879         {
1880             DosDateTimeToFileTime( DX_reg(context), 
1881                                    CX_reg(context),
1882                                    &filetime );
1883             if (!SetFileTime( handle, NULL, &filetime, NULL ))
1884                 return FALSE;
1885             break;
1886         }
1887
1888     case 0x06:  /* Get creation stamp, DOS 7 */
1889         TRACE( "GET FILE CREATION DATE AND TIME, handle %d\n",
1890                BX_reg(context) );
1891         {
1892             if (!GetFileTime( handle, &filetime, NULL, NULL ))
1893                 return FALSE;
1894             FileTimeToDosDateTime( &filetime, &date, &time );
1895             SET_DX( context, date );
1896             SET_CX( context, time );
1897             /*
1898              * FIXME: SI has number of 10-millisecond units past time in CX.
1899              */
1900             SET_SI( context, 0 );
1901             break;
1902         }
1903
1904     case 0x07:  /* Set creation stamp, DOS 7 */
1905         TRACE( "SET FILE CREATION DATE AND TIME, handle %d\n",
1906                BX_reg(context) );
1907         {
1908             /*
1909              * FIXME: SI has number of 10-millisecond units past time in CX.
1910              */
1911             DosDateTimeToFileTime( DX_reg(context), 
1912                                    CX_reg(context),
1913                                    &filetime );
1914             if (!SetFileTime( handle, &filetime, NULL, NULL ))
1915                 return FALSE;
1916             break;
1917         }
1918
1919     default:
1920         INT_BARF( context, 0x21 );
1921         break;
1922     }
1923
1924     return TRUE;
1925 }
1926
1927
1928 /***********************************************************************
1929  *           INT21_GetPSP
1930  *
1931  * Handler for functions 0x51 and 0x62.
1932  */
1933 static void INT21_GetPSP( CONTEXT86 *context )
1934 {
1935     TRACE( "GET CURRENT PSP ADDRESS (%02x)\n", AH_reg(context) );
1936
1937     /*
1938      * FIXME: should we return the original DOS PSP upon
1939      *        Windows startup ? 
1940      */
1941     if (!ISV86(context) && DOSVM_IsWin16())
1942         SET_BX( context, LOWORD(GetCurrentPDB16()) );
1943     else
1944         SET_BX( context, DOSVM_psp );
1945 }
1946
1947
1948 /***********************************************************************
1949  *           INT21_Ioctl_Block
1950  *
1951  * Handler for block device IOCTLs.
1952  */
1953 static void INT21_Ioctl_Block( CONTEXT86 *context )
1954 {
1955     BYTE *dataptr;
1956     BYTE  drive = INT21_MapDrive( BL_reg(context) );
1957     WCHAR drivespec[4] = {'A', ':', '\\', 0};
1958     UINT  drivetype;
1959
1960     drivespec[0] += drive;
1961     drivetype = GetDriveTypeW( drivespec );
1962
1963     if (drivetype == DRIVE_UNKNOWN || drivetype == DRIVE_NO_ROOT_DIR)
1964     {
1965         TRACE( "IOCTL - SUBFUNCTION %d - INVALID DRIVE %c:\n", 
1966                AL_reg(context), 'A' + drive );
1967         SetLastError( ERROR_INVALID_DRIVE );
1968         SET_AX( context, ERROR_INVALID_DRIVE );
1969         SET_CFLAG( context );
1970         return;
1971     }
1972
1973     switch (AL_reg(context))
1974     {
1975     case 0x04: /* READ FROM BLOCK DEVICE CONTROL CHANNEL */
1976     case 0x05: /* WRITE TO BLOCK DEVICE CONTROL CHANNEL */
1977         INT_BARF( context, 0x21 );
1978         break;
1979
1980     case 0x08: /* CHECK IF BLOCK DEVICE REMOVABLE */
1981         TRACE( "IOCTL - CHECK IF BLOCK DEVICE REMOVABLE - %c:\n",
1982                'A' + drive );
1983
1984         if (drivetype == DRIVE_REMOVABLE)
1985             SET_AX( context, 0 ); /* removable */
1986         else
1987             SET_AX( context, 1 ); /* not removable */
1988         break;
1989
1990     case 0x09: /* CHECK IF BLOCK DEVICE REMOTE */
1991         TRACE( "IOCTL - CHECK IF BLOCK DEVICE REMOTE - %c:\n",
1992                'A' + drive );
1993
1994         if (drivetype == DRIVE_REMOTE)
1995             SET_DX( context, (1<<9) | (1<<12) ); /* remote + no direct IO */
1996         else
1997             SET_DX( context, 0 ); /* FIXME: use driver attr here */
1998         break;
1999
2000     case 0x0d: /* GENERIC BLOCK DEVICE REQUEST */
2001         /* Get pointer to IOCTL parameter block. */
2002         dataptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
2003
2004         switch (CX_reg(context))
2005         {
2006         case 0x0841: /* write logical device track */
2007             TRACE( "GENERIC IOCTL - Write logical device track - %c:\n",
2008                    'A' + drive);
2009             {
2010                 WORD head   = *(WORD *)dataptr+1;
2011                 WORD cyl    = *(WORD *)dataptr+3;
2012                 WORD sect   = *(WORD *)dataptr+5;
2013                 WORD nrsect = *(WORD *)dataptr+7;
2014                 BYTE *data  =  (BYTE *)dataptr+9; /* FIXME: is this correct? */
2015
2016                 if (!DOSVM_RawWrite(drive, head*cyl*sect, nrsect, data, FALSE))
2017                 {
2018                     SET_AX( context, ERROR_WRITE_FAULT );
2019                     SET_CFLAG(context);
2020                 }
2021             }
2022             break;
2023
2024         case 0x084a: /* lock logical volume */
2025             TRACE( "GENERIC IOCTL - Lock logical volume, level %d mode %d - %c:\n",
2026                    BH_reg(context), DX_reg(context), 'A' + drive );
2027             break;
2028
2029         case 0x0860: /* get device parameters */
2030             INT_Int21Handler( context );
2031             break;
2032
2033         case 0x0861: /* read logical device track */
2034             TRACE( "GENERIC IOCTL - Read logical device track - %c:\n",
2035                    'A' + drive);
2036             {
2037                 WORD head   = *(WORD *)dataptr+1;
2038                 WORD cyl    = *(WORD *)dataptr+3;
2039                 WORD sect   = *(WORD *)dataptr+5;
2040                 WORD nrsect = *(WORD *)dataptr+7;
2041                 BYTE *data  =  (BYTE *)dataptr+9; /* FIXME: is this correct? */
2042
2043                 if (!DOSVM_RawRead(drive, head*cyl*sect, nrsect, data, FALSE))
2044                 {
2045                     SET_AX( context, ERROR_READ_FAULT );
2046                     SET_CFLAG(context);
2047                 }
2048             }
2049             break;
2050
2051         case 0x0866: /* get volume serial number */
2052             INT_Int21Handler( context );
2053             break;
2054
2055         case 0x086a: /* unlock logical volume */
2056             TRACE( "GENERIC IOCTL - Logical volume unlocked - %c:\n", 
2057                    'A' + drive );
2058             break;
2059
2060         case 0x086f: /* get drive map information */
2061             INT_Int21Handler( context );
2062             break;
2063
2064         case 0x0872:
2065             INT_Int21Handler( context );
2066             break;
2067
2068         default:
2069             INT_BARF( context, 0x21 );            
2070         }
2071         break;
2072
2073     case 0x0e: /* GET LOGICAL DRIVE MAP */
2074         TRACE( "IOCTL - GET LOGICAL DRIVE MAP - %c:\n",
2075                'A' + drive );
2076         /* FIXME: this is not correct if drive has mappings */
2077         SET_AL( context, 0 ); /* drive has no mapping */
2078         break;
2079
2080     case 0x0f: /* SET LOGICAL DRIVE MAP */
2081         INT_Int21Handler( context );
2082         break;
2083
2084     case 0x11: /* QUERY GENERIC IOCTL CAPABILITY */
2085     default:
2086         INT_BARF( context, 0x21 );
2087     }
2088 }
2089
2090
2091 /***********************************************************************
2092  *           INT21_Ioctl_Char
2093  *
2094  * Handler for character device IOCTLs.
2095  */
2096 static void INT21_Ioctl_Char( CONTEXT86 *context )
2097 {
2098     static const WCHAR emmxxxx0W[] = {'E','M','M','X','X','X','X','0',0};
2099     static const WCHAR scsimgrW[] = {'S','C','S','I','M','G','R','$',0};
2100
2101     HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
2102     const DOS_DEVICE *dev = DOSFS_GetDeviceByHandle(handle);
2103
2104     if (dev && !strcmpiW( dev->name, emmxxxx0W )) 
2105     {
2106         EMS_Ioctl_Handler(context);
2107         return;
2108     }
2109
2110     if (dev && !strcmpiW( dev->name, scsimgrW ) && AL_reg(context) == 2)
2111     {
2112         DOSVM_ASPIHandler(context);
2113         return;
2114     }
2115
2116     switch (AL_reg(context))
2117     {
2118     case 0x00: /* GET DEVICE INFORMATION */
2119         TRACE( "IOCTL - GET DEVICE INFORMATION - %d\n", BX_reg(context) );
2120         if (dev)
2121         {
2122             /*
2123              * Returns attribute word in DX: 
2124              *   Bit 14 - Device driver can process IOCTL requests.
2125              *   Bit 13 - Output until busy supported.
2126              *   Bit 11 - Driver supports OPEN/CLOSE calls.
2127              *   Bit  8 - Unknown.
2128              *   Bit  7 - Set (indicates device).
2129              *   Bit  6 - EOF on input.
2130              *   Bit  5 - Raw (binary) mode.
2131              *   Bit  4 - Device is special (uses int29).
2132              *   Bit  3 - Clock device.
2133              *   Bit  2 - NUL device.
2134              *   Bit  1 - Standard output.
2135              *   Bit  0 - Standard input.
2136              */
2137             SET_DX( context, dev->flags );
2138         }
2139         else
2140         {
2141             /*
2142              * Returns attribute word in DX: 
2143              *   Bit 15    - File is remote.
2144              *   Bit 14    - Don't set file date/time on closing.
2145              *   Bit 11    - Media not removable.
2146              *   Bit  8    - Generate int24 if no disk space on write 
2147              *               or read past end of file
2148              *   Bit  7    - Clear (indicates file).
2149              *   Bit  6    - File has not been written.
2150              *   Bit  5..0 - Drive number (0=A:,...)
2151              *
2152              * FIXME: Should check if file is on remote or removable drive.
2153              * FIXME: Should use drive file is located on (and not current).
2154              */
2155             SET_DX( context, 0x0140 + INT21_GetCurrentDrive() );
2156         }
2157         break;
2158
2159     case 0x01: /* SET DEVICE INFORMATION */
2160     case 0x02: /* READ FROM CHARACTER DEVICE CONTROL CHANNEL */
2161     case 0x03: /* WRITE TO CHARACTER DEVICE CONTROL CHANNEL */
2162     case 0x06: /* GET INPUT STATUS */
2163     case 0x07: /* GET OUTPUT STATUS */
2164         INT_BARF( context, 0x21 );
2165         break;
2166
2167     case 0x0a: /* CHECK IF HANDLE IS REMOTE */
2168         TRACE( "IOCTL - CHECK IF HANDLE IS REMOTE - %d\n", BX_reg(context) );
2169         /*
2170          * Returns attribute word in DX:
2171          *   Bit 15 - Set if remote.
2172          *   Bit 14 - Set if date/time not set on close.
2173          *
2174          * FIXME: Should check if file is on remote drive.
2175          */
2176         SET_DX( context, 0 );
2177         break;
2178
2179     case 0x0c: /* GENERIC CHARACTER DEVICE REQUEST */
2180     case 0x10: /* QUERY GENERIC IOCTL CAPABILITY */
2181     default:
2182         INT_BARF( context, 0x21 );
2183     }
2184 }
2185
2186
2187 /***********************************************************************
2188  *           INT21_Ioctl
2189  *
2190  * Handler for function 0x44.
2191  */
2192 static void INT21_Ioctl( CONTEXT86 *context )
2193 {
2194     switch (AL_reg(context))
2195     {
2196     case 0x00:
2197     case 0x01:
2198     case 0x02:
2199     case 0x03:
2200         INT21_Ioctl_Char( context );
2201         break;
2202
2203     case 0x04:
2204     case 0x05:
2205         INT21_Ioctl_Block( context );
2206         break;
2207
2208     case 0x06:
2209     case 0x07:
2210         INT21_Ioctl_Char( context );
2211         break;
2212
2213     case 0x08:
2214     case 0x09:
2215         INT21_Ioctl_Block( context );
2216         break;
2217
2218     case 0x0a:
2219         INT21_Ioctl_Char( context );
2220         break;
2221
2222     case 0x0b: /* SET SHARING RETRY COUNT */
2223         TRACE( "SET SHARING RETRY COUNT: Pause %d, retries %d.\n",
2224                CX_reg(context), DX_reg(context) );
2225         if (!CX_reg(context))
2226         {
2227             SET_AX( context, 1 );
2228             SET_CFLAG( context );
2229         }
2230         else
2231         {
2232             DOSMEM_LOL()->sharing_retry_delay = CX_reg(context);
2233             if (DX_reg(context))
2234                 DOSMEM_LOL()->sharing_retry_count = DX_reg(context);
2235             RESET_CFLAG( context );
2236         }
2237         break;
2238
2239     case 0x0c:
2240         INT21_Ioctl_Char( context );
2241         break;
2242
2243     case 0x0d:
2244     case 0x0e:
2245     case 0x0f:
2246         INT21_Ioctl_Block( context );
2247         break;
2248
2249     case 0x10:
2250         INT21_Ioctl_Char( context );
2251         break;
2252
2253     case 0x11:
2254         INT21_Ioctl_Block( context );
2255         break;
2256
2257     case 0x12: /*  DR DOS - DETERMINE DOS TYPE (OBSOLETE FUNCTION) */
2258         TRACE( "DR DOS - DETERMINE DOS TYPE (OBSOLETE FUNCTION)\n" );
2259         SET_CFLAG(context);        /* Error / This is not DR DOS. */
2260         SET_AX( context, 0x0001 ); /* Invalid function */
2261         break;
2262
2263     case 0x52: /* DR DOS - DETERMINE DOS TYPE */
2264         TRACE( "DR DOS - DETERMINE DOS TYPE\n" );
2265         SET_CFLAG(context);        /* Error / This is not DR DOS. */
2266         SET_AX( context, 0x0001 ); /* Invalid function */
2267         break;
2268
2269     case 0xe0:  /* Sun PC-NFS API */
2270         TRACE( "Sun PC-NFS API\n" );
2271         /* not installed */
2272         break;
2273
2274     default:
2275         INT_BARF( context, 0x21 );
2276     }
2277 }
2278
2279
2280 /***********************************************************************
2281  *           INT21_LongFilename
2282  *
2283  * Handler for function 0x71.
2284  */
2285 static void INT21_LongFilename( CONTEXT86 *context )
2286 {
2287     BOOL bSetDOSExtendedError = FALSE;
2288
2289     if (HIBYTE(HIWORD(GetVersion16())) < 0x07)
2290     {
2291         TRACE( "LONG FILENAME - functions supported only under DOS7\n" );
2292         SET_CFLAG( context );
2293         SET_AL( context, 0 );
2294         return;
2295     }
2296
2297     switch (AL_reg(context))
2298     {
2299     case 0x0d: /* RESET DRIVE */
2300         INT_BARF( context, 0x21 );
2301         break;
2302
2303     case 0x39: /* LONG FILENAME - MAKE DIRECTORY */
2304         if (!INT21_CreateDirectory( context ))
2305             bSetDOSExtendedError = TRUE;
2306         break;
2307
2308     case 0x3a: /* LONG FILENAME - REMOVE DIRECTORY */
2309         {
2310             WCHAR dirW[MAX_PATH];
2311             char *dirA = CTX_SEG_OFF_TO_LIN(context,
2312                                             context->SegDs, context->Edx);
2313
2314             TRACE( "LONG FILENAME - REMOVE DIRECTORY %s\n", dirA );
2315             MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
2316
2317             if (!RemoveDirectoryW( dirW ))
2318                 bSetDOSExtendedError = TRUE;
2319         }
2320         break;
2321
2322     case 0x3b: /* LONG FILENAME - CHANGE DIRECTORY */
2323         if (!INT21_SetCurrentDirectory( context ))
2324             bSetDOSExtendedError = TRUE;
2325         break;
2326
2327     case 0x41: /* LONG FILENAME - DELETE FILE */
2328         {
2329             WCHAR fileW[MAX_PATH];
2330             char *fileA = CTX_SEG_OFF_TO_LIN(context, 
2331                                              context->SegDs, context->Edx);
2332
2333             TRACE( "LONG FILENAME - DELETE FILE %s\n", fileA );
2334             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
2335
2336             if (!DeleteFileW( fileW ))
2337                 bSetDOSExtendedError = TRUE;
2338         }
2339         break;
2340
2341     case 0x43: /* LONG FILENAME - EXTENDED GET/SET FILE ATTRIBUTES */
2342         if (!INT21_FileAttributes( context, BL_reg(context), TRUE ))
2343             bSetDOSExtendedError = TRUE;
2344         break;
2345
2346     case 0x47: /* LONG FILENAME - GET CURRENT DIRECTORY */
2347         if (!INT21_GetCurrentDirectory( context, TRUE ))
2348             bSetDOSExtendedError = TRUE;
2349         break;
2350
2351     case 0x4e: /* LONG FILENAME - FIND FIRST MATCHING FILE */
2352     case 0x4f: /* LONG FILENAME - FIND NEXT MATCHING FILE */
2353         INT_Int21Handler( context );
2354         break;
2355
2356     case 0x56: /* LONG FILENAME - RENAME FILE */
2357         if (!INT21_RenameFile(context))
2358             bSetDOSExtendedError = TRUE;
2359         break;
2360
2361     case 0x60: /* LONG FILENAME - CONVERT PATH */
2362         INT_Int21Handler( context );
2363         break;
2364
2365     case 0x6c: /* LONG FILENAME - CREATE OR OPEN FILE */
2366         if (!INT21_CreateFile( context, context->Esi, TRUE,
2367                                BX_reg(context), DL_reg(context) ))
2368             bSetDOSExtendedError = TRUE;
2369         break;
2370
2371     case 0xa0: /* LONG FILENAME - GET VOLUME INFORMATION */
2372     case 0xa1: /* LONG FILENAME - "FindClose" - TERMINATE DIRECTORY SEARCH */
2373         INT_Int21Handler( context );
2374         break;
2375
2376     case 0xa6: /* LONG FILENAME - GET FILE INFO BY HANDLE */
2377         {
2378             HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
2379             BY_HANDLE_FILE_INFORMATION *info =
2380                 CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
2381             
2382             TRACE( "LONG FILENAME - GET FILE INFO BY HANDLE\n" );
2383             
2384             if (!GetFileInformationByHandle(handle, info))
2385                 bSetDOSExtendedError = TRUE;
2386         }
2387         break;
2388
2389     case 0xa7: /* LONG FILENAME - CONVERT TIME */
2390         switch (BL_reg(context))
2391         {
2392         case 0x00: /* FILE TIME TO DOS TIME */
2393             {
2394                 WORD      date, time;
2395                 FILETIME *filetime = CTX_SEG_OFF_TO_LIN(context,
2396                                                         context->SegDs,
2397                                                         context->Esi);
2398
2399                 TRACE( "LONG FILENAME - FILE TIME TO DOS TIME\n" );
2400
2401                 FileTimeToDosDateTime( filetime, &date, &time );
2402
2403                 SET_DX( context, date );
2404                 SET_CX( context, time );
2405
2406                 /*
2407                  * FIXME: BH has number of 10-millisecond units 
2408                  * past time in CX.
2409                  */
2410                 SET_BH( context, 0 );
2411             }
2412             break;
2413
2414         case 0x01: /* DOS TIME TO FILE TIME */
2415             {
2416                 FILETIME *filetime = CTX_SEG_OFF_TO_LIN(context,
2417                                                         context->SegEs,
2418                                                         context->Edi);
2419
2420                 TRACE( "LONG FILENAME - DOS TIME TO FILE TIME\n" );
2421
2422                 /*
2423                  * FIXME: BH has number of 10-millisecond units 
2424                  * past time in CX.
2425                  */
2426                 DosDateTimeToFileTime( DX_reg(context), CX_reg(context),
2427                                        filetime );
2428             }
2429             break;
2430
2431         default:
2432             INT_BARF( context, 0x21 );
2433             break;
2434         }
2435         break;
2436
2437     case 0xa8: /* LONG FILENAME - GENERATE SHORT FILENAME */
2438     case 0xa9: /* LONG FILENAME - SERVER CREATE OR OPEN FILE */
2439     case 0xaa: /* LONG FILENAME - SUBST */
2440     default:
2441         INT_BARF( context, 0x21 );
2442     }
2443
2444     if (bSetDOSExtendedError)
2445     {
2446         SET_AX( context, GetLastError() );
2447         SET_CFLAG( context );
2448     }
2449 }
2450
2451
2452 /***********************************************************************
2453  *           INT21_RenameFile
2454  *
2455  * Handler for:
2456  * - function 0x56
2457  * - subfunction 0x56 of function 0x71
2458  * - subfunction 0xff of function 0x43 (CL == 0x56)
2459  */
2460 static BOOL INT21_RenameFile( CONTEXT86 *context )
2461 {
2462     WCHAR fromW[MAX_PATH];
2463     WCHAR toW[MAX_PATH];
2464     char *fromA = CTX_SEG_OFF_TO_LIN(context, 
2465                                      context->SegDs,context->Edx);
2466     char *toA = CTX_SEG_OFF_TO_LIN(context, 
2467                                    context->SegEs,context->Edi);
2468
2469     TRACE( "RENAME FILE %s to %s\n", fromA, toA );
2470     MultiByteToWideChar(CP_OEMCP, 0, fromA, -1, fromW, MAX_PATH);
2471     MultiByteToWideChar(CP_OEMCP, 0, toA, -1, toW, MAX_PATH);
2472
2473     return MoveFileW( fromW, toW );
2474 }
2475
2476
2477 /***********************************************************************
2478  *           INT21_GetExtendedError
2479  */
2480 static void INT21_GetExtendedError( CONTEXT86 *context )
2481 {
2482     BYTE class, action, locus;
2483     WORD error = GetLastError();
2484
2485     switch(error)
2486     {
2487     case ERROR_SUCCESS:
2488         class = action = locus = 0;
2489         break;
2490     case ERROR_DIR_NOT_EMPTY:
2491         class  = EC_Exists;
2492         action = SA_Ignore;
2493         locus  = EL_Disk;
2494         break;
2495     case ERROR_ACCESS_DENIED:
2496         class  = EC_AccessDenied;
2497         action = SA_Abort;
2498         locus  = EL_Disk;
2499         break;
2500     case ERROR_CANNOT_MAKE:
2501         class  = EC_AccessDenied;
2502         action = SA_Abort;
2503         locus  = EL_Unknown;
2504         break;
2505     case ERROR_DISK_FULL:
2506     case ERROR_HANDLE_DISK_FULL:
2507         class  = EC_MediaError;
2508         action = SA_Abort;
2509         locus  = EL_Disk;
2510         break;
2511     case ERROR_FILE_EXISTS:
2512     case ERROR_ALREADY_EXISTS:
2513         class  = EC_Exists;
2514         action = SA_Abort;
2515         locus  = EL_Disk;
2516         break;
2517     case ERROR_FILE_NOT_FOUND:
2518         class  = EC_NotFound;
2519         action = SA_Abort;
2520         locus  = EL_Disk;
2521         break;
2522     case ER_GeneralFailure:
2523         class  = EC_SystemFailure;
2524         action = SA_Abort;
2525         locus  = EL_Unknown;
2526         break;
2527     case ERROR_INVALID_DRIVE:
2528         class  = EC_MediaError;
2529         action = SA_Abort;
2530         locus  = EL_Disk;
2531         break;
2532     case ERROR_INVALID_HANDLE:
2533         class  = EC_ProgramError;
2534         action = SA_Abort;
2535         locus  = EL_Disk;
2536         break;
2537     case ERROR_LOCK_VIOLATION:
2538         class  = EC_AccessDenied;
2539         action = SA_Abort;
2540         locus  = EL_Disk;
2541         break;
2542     case ERROR_NO_MORE_FILES:
2543         class  = EC_MediaError;
2544         action = SA_Abort;
2545         locus  = EL_Disk;
2546         break;
2547     case ER_NoNetwork:
2548         class  = EC_NotFound;
2549         action = SA_Abort;
2550         locus  = EL_Network;
2551         break;
2552     case ERROR_NOT_ENOUGH_MEMORY:
2553         class  = EC_OutOfResource;
2554         action = SA_Abort;
2555         locus  = EL_Memory;
2556         break;
2557     case ERROR_PATH_NOT_FOUND:
2558         class  = EC_NotFound;
2559         action = SA_Abort;
2560         locus  = EL_Disk;
2561         break;
2562     case ERROR_SEEK:
2563         class  = EC_NotFound;
2564         action = SA_Ignore;
2565         locus  = EL_Disk;
2566         break;
2567     case ERROR_SHARING_VIOLATION:
2568         class  = EC_Temporary;
2569         action = SA_Retry;
2570         locus  = EL_Disk;
2571         break;
2572     case ERROR_TOO_MANY_OPEN_FILES:
2573         class  = EC_ProgramError;
2574         action = SA_Abort;
2575         locus  = EL_Disk;
2576         break;
2577     default:
2578         FIXME("Unknown error %d\n", error );
2579         class  = EC_SystemFailure;
2580         action = SA_Abort;
2581         locus  = EL_Unknown;
2582         break;
2583     }
2584     TRACE("GET EXTENDED ERROR code 0x%02x class 0x%02x action 0x%02x locus %02x\n",
2585            error, class, action, locus );
2586     SET_AX( context, error );
2587     SET_BH( context, class );
2588     SET_BL( context, action );
2589     SET_CH( context, locus );
2590 }
2591
2592
2593 /***********************************************************************
2594  *           DOSVM_Int21Handler
2595  *
2596  * Interrupt 0x21 handler.
2597  */
2598 void WINAPI DOSVM_Int21Handler( CONTEXT86 *context )
2599 {
2600     BOOL bSetDOSExtendedError = FALSE;
2601
2602     TRACE( "AX=%04x BX=%04x CX=%04x DX=%04x "
2603            "SI=%04x DI=%04x DS=%04x ES=%04x EFL=%08lx\n",
2604            AX_reg(context), BX_reg(context), 
2605            CX_reg(context), DX_reg(context),
2606            SI_reg(context), DI_reg(context),
2607            (WORD)context->SegDs, (WORD)context->SegEs,
2608            context->EFlags );
2609
2610    /*
2611     * Extended error is used by (at least) functions 0x2f to 0x62.
2612     * Function 0x59 returns extended error and error should not
2613     * be cleared before handling the function.
2614     */
2615     if (AH_reg(context) >= 0x2f && AH_reg(context) != 0x59) 
2616         SetLastError(0);
2617
2618     RESET_CFLAG(context); /* Not sure if this is a good idea. */
2619
2620     switch(AH_reg(context))
2621     {
2622     case 0x00: /* TERMINATE PROGRAM */
2623         TRACE("TERMINATE PROGRAM\n");
2624         if (DOSVM_IsWin16())
2625             ExitThread( 0 );
2626         else
2627             MZ_Exit( context, FALSE, 0 );
2628         break;
2629
2630     case 0x01: /* READ CHARACTER FROM STANDARD INPUT, WITH ECHO */
2631         {
2632             BYTE ascii;
2633             TRACE("DIRECT CHARACTER INPUT WITH ECHO\n");
2634             INT21_ReadChar( &ascii, context );
2635             SET_AL( context, ascii );
2636             /*
2637              * FIXME: What to echo when extended keycodes are read?
2638              */
2639             DOSVM_PutChar(AL_reg(context));
2640         }
2641         break;
2642
2643     case 0x02: /* WRITE CHARACTER TO STANDARD OUTPUT */
2644         TRACE("Write Character to Standard Output\n");
2645         DOSVM_PutChar(DL_reg(context));
2646         break;
2647
2648     case 0x03: /* READ CHARACTER FROM STDAUX  */
2649     case 0x04: /* WRITE CHARACTER TO STDAUX */
2650     case 0x05: /* WRITE CHARACTER TO PRINTER */
2651         INT_BARF( context, 0x21 );
2652         break;
2653
2654     case 0x06: /* DIRECT CONSOLE IN/OUTPUT */
2655         if (DL_reg(context) == 0xff) 
2656         {
2657             TRACE("Direct Console Input\n");
2658
2659             if (INT21_ReadChar( NULL, NULL ))
2660             {
2661                 BYTE ascii;
2662                 INT21_ReadChar( &ascii, context );
2663                 SET_AL( context, ascii );
2664                 RESET_ZFLAG( context );
2665             }
2666             else
2667             {
2668                 /* no character available */
2669                 SET_AL( context, 0 );
2670                 SET_ZFLAG( context );
2671             }
2672         } 
2673         else 
2674         {
2675             TRACE("Direct Console Output\n");
2676             DOSVM_PutChar(DL_reg(context));
2677             /*
2678              * At least DOS versions 2.1-7.0 return character 
2679              * that was written in AL register.
2680              */
2681             SET_AL( context, DL_reg(context) );
2682         }
2683         break;
2684
2685     case 0x07: /* DIRECT CHARACTER INPUT WITHOUT ECHO */
2686         {
2687             BYTE ascii;
2688             TRACE("DIRECT CHARACTER INPUT WITHOUT ECHO\n");
2689             INT21_ReadChar( &ascii, context );
2690             SET_AL( context, ascii );
2691         }
2692         break;
2693
2694     case 0x08: /* CHARACTER INPUT WITHOUT ECHO */
2695         {
2696             BYTE ascii;
2697             TRACE("CHARACTER INPUT WITHOUT ECHO\n");
2698             INT21_ReadChar( &ascii, context );
2699             SET_AL( context, ascii );
2700         }
2701         break;
2702
2703     case 0x09: /* WRITE STRING TO STANDARD OUTPUT */
2704         TRACE("WRITE '$'-terminated string from %04lX:%04X to stdout\n",
2705               context->SegDs, DX_reg(context) );
2706         {
2707             LPSTR data = CTX_SEG_OFF_TO_LIN( context, 
2708                                              context->SegDs, context->Edx );
2709             LPSTR p = data;
2710
2711             /*
2712              * Do NOT use strchr() to calculate the string length,
2713              * as '\0' is valid string content, too!
2714              * Maybe we should check for non-'$' strings, but DOS doesn't.
2715              */
2716             while (*p != '$') p++;
2717
2718             if (DOSVM_IsWin16())
2719                 WriteFile( DosFileHandleToWin32Handle(1), 
2720                            data, p - data, 0, 0 );
2721             else
2722                 for(; data != p; data++)
2723                     DOSVM_PutChar( *data );
2724
2725             SET_AL( context, '$' ); /* yes, '$' (0x24) gets returned in AL */
2726         }
2727         break;
2728
2729     case 0x0a: /* BUFFERED INPUT */
2730         {
2731             BYTE *ptr = CTX_SEG_OFF_TO_LIN(context,
2732                                            context->SegDs,
2733                                            context->Edx);
2734             WORD result;
2735
2736             TRACE( "BUFFERED INPUT (size=%d)\n", ptr[0] );
2737
2738             /*
2739              * FIXME: Some documents state that
2740              *        ptr[1] holds number of chars from last input which 
2741              *        may be recalled on entry, other documents do not mention
2742              *        this at all.
2743              */
2744             if (ptr[1])
2745                 TRACE( "Handle old chars in buffer!\n" );
2746
2747             /*
2748              * ptr[0] - capacity (includes terminating CR)
2749              * ptr[1] - characters read (excludes terminating CR)
2750              */
2751             result = INT21_BufferedInput( context, ptr + 2, ptr[0] );
2752             if (result > 0)
2753                 ptr[1] = (BYTE)result - 1;
2754             else
2755                 ptr[1] = 0;
2756         }
2757         break;
2758
2759     case 0x0b: /* GET STDIN STATUS */
2760         TRACE( "GET STDIN STATUS\n" );
2761         {
2762             if (INT21_ReadChar( NULL, NULL ))
2763                 SET_AL( context, 0xff ); /* character available */
2764             else
2765                 SET_AL( context, 0 ); /* no character available */
2766         }
2767         break;
2768
2769     case 0x0c: /* FLUSH BUFFER AND READ STANDARD INPUT */
2770         {
2771             BYTE al = AL_reg(context); /* Input function to execute after flush. */
2772
2773             TRACE( "FLUSH BUFFER AND READ STANDARD INPUT - 0x%02x\n", al );
2774
2775             /* FIXME: buffers are not flushed */
2776
2777             /*
2778              * If AL is one of 0x01, 0x06, 0x07, 0x08, or 0x0a,
2779              * int21 function identified by AL will be called.
2780              */
2781             if(al == 0x01 || al == 0x06 || al == 0x07 || al == 0x08 || al == 0x0a)
2782             {
2783                 SET_AH( context, al );
2784                 DOSVM_Int21Handler( context );
2785             }
2786         }
2787         break;
2788
2789     case 0x0d: /* DISK BUFFER FLUSH */
2790         TRACE("DISK BUFFER FLUSH ignored\n");
2791         break;
2792
2793     case 0x0e: /* SELECT DEFAULT DRIVE */
2794         TRACE( "SELECT DEFAULT DRIVE - %c:\n", 'A' + DL_reg(context) );
2795         INT21_SetCurrentDrive( DL_reg(context) );
2796         SET_AL( context, MAX_DOS_DRIVES );
2797         break;
2798
2799     case 0x0f: /* OPEN FILE USING FCB */
2800         INT21_OpenFileUsingFCB( context );
2801         break;
2802
2803     case 0x10: /* CLOSE FILE USING FCB */
2804         INT21_CloseFileUsingFCB( context );
2805         break;
2806
2807     case 0x11: /* FIND FIRST MATCHING FILE USING FCB */
2808     case 0x12: /* FIND NEXT MATCHING FILE USING FCB */
2809         INT_Int21Handler( context );
2810         break;
2811
2812     case 0x13: /* DELETE FILE USING FCB */
2813         INT_BARF( context, 0x21 );
2814         break;
2815
2816     case 0x14: /* SEQUENTIAL READ FROM FCB FILE */
2817         INT21_SequentialReadFromFCB( context );
2818         break;
2819
2820     case 0x15: /* SEQUENTIAL WRITE TO FCB FILE */
2821         INT21_SequentialWriteToFCB( context );
2822         break;
2823
2824     case 0x16: /* CREATE OR TRUNCATE FILE USING FCB */
2825     case 0x17: /* RENAME FILE USING FCB */
2826         INT_BARF( context, 0x21 );
2827         break;
2828
2829     case 0x18: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
2830         SET_AL( context, 0 );
2831         break;
2832
2833     case 0x19: /* GET CURRENT DEFAULT DRIVE */
2834         SET_AL( context, INT21_GetCurrentDrive() );
2835         TRACE( "GET CURRENT DRIVE -> %c:\n", 'A' + AL_reg( context ) );
2836         break;
2837
2838     case 0x1a: /* SET DISK TRANSFER AREA ADDRESS */
2839         TRACE( "SET DISK TRANSFER AREA ADDRESS %04lX:%04X\n",
2840                context->SegDs, DX_reg(context) );
2841         {
2842             TDB *task = GlobalLock16( GetCurrentTask() );
2843             task->dta = MAKESEGPTR( context->SegDs, DX_reg(context) );
2844         }
2845         break;
2846
2847     case 0x1b: /* GET ALLOCATION INFORMATION FOR DEFAULT DRIVE */
2848     case 0x1c: /* GET ALLOCATION INFORMATION FOR SPECIFIC DRIVE */
2849         INT_Int21Handler( context );
2850         break;
2851
2852     case 0x1d: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
2853     case 0x1e: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
2854         SET_AL( context, 0 );
2855         break;
2856
2857     case 0x1f: /* GET DRIVE PARAMETER BLOCK FOR DEFAULT DRIVE */
2858         INT_Int21Handler( context );
2859         break;
2860
2861     case 0x20: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
2862         SET_AL( context, 0 );
2863         break;
2864
2865     case 0x21: /* READ RANDOM RECORD FROM FCB FILE */
2866         INT21_ReadRandomRecordFromFCB( context );
2867         break;
2868
2869     case 0x22: /* WRITE RANDOM RECORD TO FCB FILE */
2870         INT21_WriteRandomRecordToFCB( context );
2871         break;
2872
2873     case 0x23: /* GET FILE SIZE FOR FCB */
2874     case 0x24: /* SET RANDOM RECORD NUMBER FOR FCB */
2875         INT_BARF( context, 0x21 );
2876         break;
2877
2878     case 0x25: /* SET INTERRUPT VECTOR */
2879         TRACE("SET INTERRUPT VECTOR 0x%02x\n",AL_reg(context));
2880         {
2881             FARPROC16 ptr = (FARPROC16)MAKESEGPTR( context->SegDs, DX_reg(context) );
2882             if (!ISV86(context) && DOSVM_IsWin16())
2883                 DOSVM_SetPMHandler16(  AL_reg(context), ptr );
2884             else
2885                 DOSVM_SetRMHandler( AL_reg(context), ptr );
2886         }
2887         break;
2888
2889     case 0x26: /* CREATE NEW PROGRAM SEGMENT PREFIX */
2890         INT_BARF( context, 0x21 );
2891         break;
2892
2893     case 0x27: /* RANDOM BLOCK READ FROM FCB FILE */
2894         INT21_RandomBlockReadFromFCB( context );
2895         break;
2896
2897     case 0x28: /* RANDOM BLOCK WRITE TO FCB FILE */
2898         INT21_RandomBlockWriteToFCB( context );
2899         break;
2900
2901     case 0x29: /* PARSE FILENAME INTO FCB */
2902         INT_Int21Handler( context );
2903         break;
2904
2905     case 0x2a: /* GET SYSTEM DATE */
2906         TRACE( "GET SYSTEM DATE\n" );
2907         {
2908             SYSTEMTIME systime;
2909             GetLocalTime( &systime );
2910             SET_CX( context, systime.wYear );
2911             SET_DH( context, systime.wMonth );
2912             SET_DL( context, systime.wDay );
2913             SET_AL( context, systime.wDayOfWeek );
2914         }
2915         break;
2916
2917     case 0x2b: /* SET SYSTEM DATE */
2918         TRACE( "SET SYSTEM DATE\n" );
2919         {
2920             WORD year  = CX_reg(context);
2921             BYTE month = DH_reg(context);
2922             BYTE day   = DL_reg(context);
2923
2924             if (year  >= 1980 && year  <= 2099 &&
2925                 month >= 1    && month <= 12   &&
2926                 day   >= 1    && day   <= 31)
2927             {
2928                 FIXME( "SetSystemDate(%02d/%02d/%04d): not allowed\n",
2929                        day, month, year );
2930                 SET_AL( context, 0 );  /* Let's pretend we succeeded */
2931             }
2932             else
2933             {
2934                 SET_AL( context, 0xff ); /* invalid date */
2935             }
2936         }
2937         break;
2938
2939     case 0x2c: /* GET SYSTEM TIME */
2940         TRACE( "GET SYSTEM TIME\n" );
2941         {
2942             SYSTEMTIME systime;
2943             GetLocalTime( &systime );
2944             SET_CH( context, systime.wHour );
2945             SET_CL( context, systime.wMinute );
2946             SET_DH( context, systime.wSecond );
2947             SET_DL( context, systime.wMilliseconds / 10 );
2948         }
2949         break;
2950
2951     case 0x2d: /* SET SYSTEM TIME */
2952         FIXME("SetSystemTime(%02d:%02d:%02d.%02d): not allowed\n",
2953               CH_reg(context), CL_reg(context),
2954               DH_reg(context), DL_reg(context) );
2955         SET_AL( context, 0 );  /* Let's pretend we succeeded */
2956         break;
2957
2958     case 0x2e: /* SET VERIFY FLAG */
2959         TRACE("SET VERIFY FLAG ignored\n");
2960         /* we cannot change the behaviour anyway, so just ignore it */
2961         break;
2962
2963     case 0x2f: /* GET DISK TRANSFER AREA ADDRESS */
2964         TRACE( "GET DISK TRANSFER AREA ADDRESS\n" );
2965         {
2966             TDB *task = GlobalLock16( GetCurrentTask() );
2967             context->SegEs = SELECTOROF( task->dta );
2968             SET_BX( context, OFFSETOF( task->dta ) );
2969         }
2970         break;
2971
2972     case 0x30: /* GET DOS VERSION */
2973         TRACE( "GET DOS VERSION - %s requested\n",
2974                (AL_reg(context) == 0x00) ? "OEM number" : "version flag" );
2975
2976         if (AL_reg(context) == 0x00)
2977             SET_BH( context, 0xff ); /* OEM number => undefined */
2978         else
2979             SET_BH( context, 0x08 ); /* version flag => DOS is in ROM */
2980
2981         SET_AL( context, HIBYTE(HIWORD(GetVersion16())) ); /* major version */
2982         SET_AH( context, LOBYTE(HIWORD(GetVersion16())) ); /* minor version */
2983
2984         SET_BL( context, 0x12 );     /* 0x123456 is 24-bit Wine's serial # */
2985         SET_CX( context, 0x3456 );
2986         break;
2987
2988     case 0x31: /* TERMINATE AND STAY RESIDENT */
2989         FIXME("TERMINATE AND STAY RESIDENT stub\n");
2990         break;
2991
2992     case 0x32: /* GET DOS DRIVE PARAMETER BLOCK FOR SPECIFIC DRIVE */
2993         INT_Int21Handler( context );
2994         break;
2995
2996     case 0x33: /* MULTIPLEXED */
2997         switch (AL_reg(context))
2998         {
2999         case 0x00: /* GET CURRENT EXTENDED BREAK STATE */
3000             TRACE("GET CURRENT EXTENDED BREAK STATE\n");
3001             SET_DL( context, DOSCONF_GetConfig()->brk_flag );
3002             break;
3003
3004         case 0x01: /* SET EXTENDED BREAK STATE */
3005             TRACE("SET CURRENT EXTENDED BREAK STATE\n");
3006             DOSCONF_GetConfig()->brk_flag = (DL_reg(context) > 0) ? 1 : 0;
3007             break;
3008
3009         case 0x02: /* GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE*/
3010             TRACE("GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE\n");
3011             /* ugly coding in order to stay reentrant */
3012             if (DL_reg(context))
3013             {
3014                 SET_DL( context, DOSCONF_GetConfig()->brk_flag );
3015                 DOSCONF_GetConfig()->brk_flag = 1;
3016             }
3017             else
3018             {
3019                 SET_DL( context, DOSCONF_GetConfig()->brk_flag );
3020                 DOSCONF_GetConfig()->brk_flag = 0;
3021             }
3022             break;
3023
3024         case 0x05: /* GET BOOT DRIVE */
3025             TRACE("GET BOOT DRIVE\n");
3026             SET_DL( context, 3 );
3027             /* c: is Wine's bootdrive (a: is 1)*/
3028             break;
3029
3030         case 0x06: /* GET TRUE VERSION NUMBER */
3031             TRACE("GET TRUE VERSION NUMBER\n");
3032             SET_BL( context, HIBYTE(HIWORD(GetVersion16())) ); /* major */
3033             SET_BH( context, LOBYTE(HIWORD(GetVersion16())) ); /* minor */
3034             SET_DL( context, 0x00 ); /* revision */
3035             SET_DH( context, 0x08 ); /* DOS is in ROM */
3036             break;
3037
3038         default:
3039             INT_BARF( context, 0x21 );
3040             break;
3041         }
3042         break;
3043
3044     case 0x34: /* GET ADDRESS OF INDOS FLAG */
3045         TRACE( "GET ADDRESS OF INDOS FLAG\n" );
3046         context->SegEs = INT21_GetHeapSelector( context );
3047         SET_BX( context, offsetof(INT21_HEAP, misc_indos) );
3048         break;
3049
3050     case 0x35: /* GET INTERRUPT VECTOR */
3051         TRACE("GET INTERRUPT VECTOR 0x%02x\n",AL_reg(context));
3052         {
3053             FARPROC16 addr;
3054             if (!ISV86(context) && DOSVM_IsWin16())
3055                 addr = DOSVM_GetPMHandler16( AL_reg(context) );
3056             else
3057                 addr = DOSVM_GetRMHandler( AL_reg(context) );
3058             context->SegEs = SELECTOROF(addr);
3059             SET_BX( context, OFFSETOF(addr) );
3060         }
3061         break;
3062
3063     case 0x36: /* GET FREE DISK SPACE */
3064         INT_Int21Handler( context );
3065         break;
3066
3067     case 0x37: /* SWITCHAR */
3068         {
3069             switch (AL_reg(context))
3070             {
3071             case 0x00: /* "SWITCHAR" - GET SWITCH CHARACTER */
3072                 TRACE( "SWITCHAR - GET SWITCH CHARACTER\n" );
3073                 SET_AL( context, 0x00 ); /* success*/
3074                 SET_DL( context, '/' );
3075                 break;
3076             case 0x01: /*"SWITCHAR" - SET SWITCH CHARACTER*/
3077                 FIXME( "SWITCHAR - SET SWITCH CHARACTER: %c\n",
3078                        DL_reg( context ));
3079                 SET_AL( context, 0x00 ); /* success*/
3080                 break;
3081             default:
3082                 INT_BARF( context, 0x21 );
3083                 break;
3084             }
3085         }
3086         break;
3087
3088     case 0x38: /* GET COUNTRY-SPECIFIC INFORMATION */
3089         TRACE( "GET COUNTRY-SPECIFIC INFORMATION\n" );
3090         if (AL_reg(context)) 
3091         {
3092             WORD country = AL_reg(context);
3093             if (country == 0xff)
3094                 country = BX_reg(context);
3095             if (country != INT21_GetSystemCountryCode())
3096                 FIXME( "Requested info on non-default country %04x\n", country );
3097         }
3098         INT21_FillCountryInformation( CTX_SEG_OFF_TO_LIN(context, 
3099                                                          context->SegDs, 
3100                                                          context->Edx) );
3101         SET_AX( context, INT21_GetSystemCountryCode() );
3102         SET_BX( context, INT21_GetSystemCountryCode() );
3103         break;
3104
3105     case 0x39: /* "MKDIR" - CREATE SUBDIRECTORY */
3106         if (!INT21_CreateDirectory( context ))
3107             bSetDOSExtendedError = TRUE;
3108         break;
3109
3110     case 0x3a: /* "RMDIR" - REMOVE DIRECTORY */
3111         {
3112             WCHAR dirW[MAX_PATH];
3113             char *dirA = CTX_SEG_OFF_TO_LIN(context,
3114                                             context->SegDs, context->Edx);
3115
3116             TRACE( "REMOVE DIRECTORY %s\n", dirA );
3117
3118             MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
3119
3120             if (!RemoveDirectoryW( dirW ))
3121                 bSetDOSExtendedError = TRUE;
3122         }
3123         break;
3124
3125     case 0x3b: /* "CHDIR" - SET CURRENT DIRECTORY */
3126         if (!INT21_SetCurrentDirectory( context ))
3127             bSetDOSExtendedError = TRUE;
3128         break;
3129
3130     case 0x3c: /* "CREAT" - CREATE OR TRUNCATE FILE */
3131         if (!INT21_CreateFile( context, context->Edx, FALSE, 
3132                                OF_READWRITE | OF_SHARE_COMPAT, 0x12 ))
3133             bSetDOSExtendedError = TRUE;
3134         break;
3135
3136     case 0x3d: /* "OPEN" - OPEN EXISTING FILE */
3137         if (!INT21_CreateFile( context, context->Edx, FALSE, 
3138                                AL_reg(context), 0x01 ))
3139             bSetDOSExtendedError = TRUE;
3140         break;
3141
3142     case 0x3e: /* "CLOSE" - CLOSE FILE */
3143         TRACE( "CLOSE handle %d\n", BX_reg(context) );
3144         if (_lclose16( BX_reg(context) ) == HFILE_ERROR16)
3145             bSetDOSExtendedError = TRUE;
3146         break;
3147
3148     case 0x3f: /* "READ" - READ FROM FILE OR DEVICE */
3149         TRACE( "READ from %d to %04lX:%04X for %d bytes\n",
3150                BX_reg(context),
3151                context->SegDs,
3152                DX_reg(context),
3153                CX_reg(context) );
3154         {
3155             DWORD result;
3156             WORD  count  = CX_reg(context);
3157             BYTE *buffer = CTX_SEG_OFF_TO_LIN( context, 
3158                                                context->SegDs,
3159                                                context->Edx );
3160
3161             /* Some programs pass a count larger than the allocated buffer */
3162             if (DOSVM_IsWin16())
3163             {
3164                 WORD maxcount = GetSelectorLimit16( context->SegDs )
3165                     - DX_reg(context) + 1;
3166                 if (count > maxcount)
3167                     count = maxcount;
3168             }
3169
3170             /*
3171              * FIXME: Reading from console (BX=1) in DOS mode
3172              *        does not work as it is supposed to work.
3173              */
3174
3175             if (!DOSVM_IsWin16() && BX_reg(context) == 0)
3176             {
3177                 result = INT21_BufferedInput( context, buffer, count );
3178                 SET_AX( context, (WORD)result );
3179             }
3180             else if (ReadFile( DosFileHandleToWin32Handle(BX_reg(context)),
3181                                buffer, count, &result, NULL ))
3182                 SET_AX( context, (WORD)result );
3183             else
3184                 bSetDOSExtendedError = TRUE;
3185         }
3186         break;
3187
3188     case 0x40:  /* "WRITE" - WRITE TO FILE OR DEVICE */
3189         TRACE( "WRITE from %04lX:%04X to handle %d for %d byte\n",
3190                context->SegDs, DX_reg(context),
3191                BX_reg(context), CX_reg(context) );
3192         {
3193             BYTE *ptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3194
3195             if (!DOSVM_IsWin16() && 
3196                 (BX_reg(context) == 1 || BX_reg(context) == 2))
3197             {
3198                 int i;
3199                 for(i=0; i<CX_reg(context); i++)
3200                     DOSVM_PutChar(ptr[i]);
3201                 SET_AX(context, CX_reg(context));
3202             }
3203             else
3204             {
3205                 HFILE handle = (HFILE)DosFileHandleToWin32Handle(BX_reg(context));
3206                 LONG result = _hwrite( handle, ptr, CX_reg(context) );
3207                 if (result == HFILE_ERROR)
3208                     bSetDOSExtendedError = TRUE;
3209                 else
3210                     SET_AX( context, (WORD)result );
3211             }
3212         }
3213         break;
3214
3215     case 0x41: /* "UNLINK" - DELETE FILE */
3216         {
3217             WCHAR fileW[MAX_PATH];
3218             char *fileA = CTX_SEG_OFF_TO_LIN(context, 
3219                                              context->SegDs, 
3220                                              context->Edx);
3221
3222             TRACE( "UNLINK %s\n", fileA );
3223             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
3224
3225             if (!DeleteFileW( fileW ))
3226                 bSetDOSExtendedError = TRUE;
3227         }
3228         break;
3229
3230     case 0x42: /* "LSEEK" - SET CURRENT FILE POSITION */
3231         TRACE( "LSEEK handle %d offset %ld from %s\n",
3232                BX_reg(context), 
3233                MAKELONG( DX_reg(context), CX_reg(context) ),
3234                (AL_reg(context) == 0) ? 
3235                "start of file" : ((AL_reg(context) == 1) ? 
3236                                   "current file position" : "end of file") );
3237         {
3238             HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
3239             LONG   offset = MAKELONG( DX_reg(context), CX_reg(context) );
3240             DWORD  status = SetFilePointer( handle, offset, 
3241                                             NULL, AL_reg(context) );
3242             if (status == INVALID_SET_FILE_POINTER)
3243                 bSetDOSExtendedError = TRUE;
3244             else
3245             {
3246                 SET_AX( context, LOWORD(status) );
3247                 SET_DX( context, HIWORD(status) );
3248             }
3249         }
3250         break;
3251
3252     case 0x43: /* FILE ATTRIBUTES */
3253         if (!INT21_FileAttributes( context, AL_reg(context), FALSE ))
3254             bSetDOSExtendedError = TRUE;
3255         break;
3256
3257     case 0x44: /* IOCTL */
3258         INT21_Ioctl( context );
3259         break;
3260
3261     case 0x45: /* "DUP" - DUPLICATE FILE HANDLE */
3262         TRACE( "DUPLICATE FILE HANDLE %d\n", BX_reg(context) );
3263         {
3264             HANDLE handle32;
3265             HFILE  handle16 = HFILE_ERROR;
3266
3267             if (DuplicateHandle( GetCurrentProcess(),
3268                                  DosFileHandleToWin32Handle(BX_reg(context)),
3269                                  GetCurrentProcess(), 
3270                                  &handle32,
3271                                  0, TRUE, DUPLICATE_SAME_ACCESS ))
3272                 handle16 = Win32HandleToDosFileHandle(handle32);
3273
3274             if (handle16 == HFILE_ERROR)
3275                 bSetDOSExtendedError = TRUE;
3276             else
3277                 SET_AX( context, handle16 );
3278         }
3279         break;
3280
3281     case 0x46: /* "DUP2", "FORCEDUP" - FORCE DUPLICATE FILE HANDLE */
3282         TRACE( "FORCEDUP - FORCE DUPLICATE FILE HANDLE %d to %d\n",
3283                BX_reg(context), CX_reg(context) );
3284         if (FILE_Dup2( BX_reg(context), CX_reg(context) ) == HFILE_ERROR16)
3285             bSetDOSExtendedError = TRUE;
3286         break;
3287
3288     case 0x47: /* "CWD" - GET CURRENT DIRECTORY */
3289         if (!INT21_GetCurrentDirectory( context, FALSE ))
3290             bSetDOSExtendedError = TRUE;
3291         break;
3292
3293     case 0x48: /* ALLOCATE MEMORY */
3294         TRACE( "ALLOCATE MEMORY for %d paragraphs\n", BX_reg(context) );
3295         {
3296             WORD  selector = 0;
3297             DWORD bytes = (DWORD)BX_reg(context) << 4;
3298
3299             if (!ISV86(context) && DOSVM_IsWin16())
3300             {
3301                 DWORD rv = GlobalDOSAlloc16( bytes );
3302                 selector = LOWORD( rv );
3303             }
3304             else
3305                 DOSMEM_GetBlock( bytes, &selector );
3306                
3307             if (selector)
3308                 SET_AX( context, selector );
3309             else
3310             {
3311                 SET_CFLAG(context);
3312                 SET_AX( context, 0x0008 ); /* insufficient memory */
3313                 SET_BX( context, DOSMEM_Available() >> 4 );
3314             }
3315         }
3316         break;
3317
3318     case 0x49: /* FREE MEMORY */
3319         TRACE( "FREE MEMORY segment %04lX\n", context->SegEs );
3320         {
3321             BOOL ok;
3322             
3323             if (!ISV86(context) && DOSVM_IsWin16())
3324             {
3325                 ok = !GlobalDOSFree16( context->SegEs );
3326
3327                 /* If we don't reset ES_reg, we will fail in the relay code */
3328                 if (ok)
3329                     context->SegEs = 0;
3330             }
3331             else
3332                 ok = DOSMEM_FreeBlock( (void*)((DWORD)context->SegEs << 4) );
3333
3334             if (!ok)
3335             {
3336                 TRACE("FREE MEMORY failed\n");
3337                 SET_CFLAG(context);
3338                 SET_AX( context, 0x0009 ); /* memory block address invalid */
3339             }
3340         }
3341         break;
3342
3343     case 0x4a: /* RESIZE MEMORY BLOCK */
3344         TRACE( "RESIZE MEMORY segment %04lX to %d paragraphs\n", 
3345                context->SegEs, BX_reg(context) );
3346         {
3347             DWORD newsize = (DWORD)BX_reg(context) << 4;
3348             
3349             if (!ISV86(context) && DOSVM_IsWin16())
3350             {
3351                 FIXME( "Resize memory block - unsupported under Win16\n" );
3352             }
3353             else
3354             {
3355                 LPVOID address = (void*)((DWORD)context->SegEs << 4);
3356                 UINT blocksize = DOSMEM_ResizeBlock( address, newsize, FALSE );
3357
3358                 if (blocksize == (UINT)-1)
3359                 {
3360                     SET_CFLAG( context );
3361                     SET_AX( context, 0x0009 ); /* illegal address */
3362                 }
3363                 else if(blocksize != newsize)
3364                 {
3365                     SET_CFLAG( context );
3366                     SET_AX( context, 0x0008 );    /* insufficient memory */
3367                     SET_BX( context, blocksize >> 4 ); /* new block size */
3368                 }
3369             }
3370         }
3371         break;
3372
3373     case 0x4b: /* "EXEC" - LOAD AND/OR EXECUTE PROGRAM */
3374         {
3375             BYTE *program = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3376             BYTE *paramblk = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Ebx);
3377
3378             TRACE( "EXEC %s\n", program );
3379
3380             if (DOSVM_IsWin16())
3381             {
3382                 HINSTANCE16 instance = WinExec16( program, SW_NORMAL );
3383                 if (instance < 32)
3384                 {
3385                     SET_CFLAG( context );
3386                     SET_AX( context, instance );
3387                 }
3388             }
3389             else
3390             {
3391                 if (!MZ_Exec( context, program, AL_reg(context), paramblk))
3392                     bSetDOSExtendedError = TRUE;
3393             }
3394         }
3395         break;
3396
3397     case 0x4c: /* "EXIT" - TERMINATE WITH RETURN CODE */
3398         TRACE( "EXIT with return code %d\n", AL_reg(context) );
3399         if (DOSVM_IsWin16())
3400             ExitThread( AL_reg(context) );
3401         else
3402             MZ_Exit( context, FALSE, AL_reg(context) );
3403         break;
3404
3405     case 0x4d: /* GET RETURN CODE */
3406         TRACE("GET RETURN CODE (ERRORLEVEL)\n");
3407         SET_AX( context, DOSVM_retval );
3408         DOSVM_retval = 0;
3409         break;
3410
3411     case 0x4e: /* "FINDFIRST" - FIND FIRST MATCHING FILE */
3412     case 0x4f: /* "FINDNEXT" - FIND NEXT MATCHING FILE */
3413         INT_Int21Handler( context );
3414         break;
3415
3416     case 0x50: /* SET CURRENT PROCESS ID (SET PSP ADDRESS) */
3417         TRACE("SET CURRENT PROCESS ID (SET PSP ADDRESS)\n");
3418         DOSVM_psp = BX_reg(context);
3419         break;
3420
3421     case 0x51: /* GET PSP ADDRESS */
3422         INT21_GetPSP( context );
3423         break;
3424
3425     case 0x52: /* "SYSVARS" - GET LIST OF LISTS */
3426         if (!ISV86(context) && DOSVM_IsWin16())
3427         {
3428             SEGPTR ptr = DOSMEM_LOL()->wine_pm_lol;
3429             context->SegEs = SELECTOROF(ptr);
3430             SET_BX( context, OFFSETOF(ptr) );
3431         }
3432         else
3433         {
3434             SEGPTR ptr = DOSMEM_LOL()->wine_rm_lol;
3435             context->SegEs = SELECTOROF(ptr);
3436             SET_BX( context, OFFSETOF(ptr) );
3437         }
3438         break;
3439
3440     case 0x54: /* Get Verify Flag */
3441         TRACE("Get Verify Flag - Not Supported\n");
3442         SET_AL( context, 0x00 );  /* pretend we can tell. 00h = off 01h = on */
3443         break;
3444
3445     case 0x56: /* "RENAME" - RENAME FILE */
3446         if (!INT21_RenameFile( context ))
3447             bSetDOSExtendedError = TRUE;
3448         break;
3449
3450     case 0x57: /* FILE DATE AND TIME */
3451         if (!INT21_FileDateTime( context ))
3452             bSetDOSExtendedError = TRUE;
3453         break;
3454
3455     case 0x58: /* GET OR SET MEMORY ALLOCATION STRATEGY */
3456         switch (AL_reg(context))
3457         {
3458         case 0x00: /* GET MEMORY ALLOCATION STRATEGY */
3459             TRACE( "GET MEMORY ALLOCATION STRATEGY\n" );
3460             SET_AX( context, 0 ); /* low memory first fit */
3461             break;
3462
3463         case 0x01: /* SET ALLOCATION STRATEGY */
3464             TRACE( "SET MEMORY ALLOCATION STRATEGY to %d - ignored\n",
3465                    BL_reg(context) );
3466             break;
3467
3468         case 0x02: /* GET UMB LINK STATE */
3469             TRACE( "GET UMB LINK STATE\n" );
3470             SET_AL( context, 0 ); /* UMBs not part of DOS memory chain */
3471             break;
3472
3473         case 0x03: /* SET UMB LINK STATE */
3474             TRACE( "SET UMB LINK STATE to %d - ignored\n",
3475                    BX_reg(context) );
3476             break;
3477
3478         default:
3479             INT_BARF( context, 0x21 );
3480         }
3481         break;
3482
3483     case 0x59: /* GET EXTENDED ERROR INFO */
3484         INT21_GetExtendedError( context );
3485         break;
3486
3487     case 0x5a: /* CREATE TEMPORARY FILE */
3488         INT_Int21Handler( context );
3489         break;
3490
3491     case 0x5b: /* CREATE NEW FILE */ 
3492         if (!INT21_CreateFile( context, context->Edx, FALSE,
3493                                OF_READWRITE | OF_SHARE_COMPAT, 0x10 ))
3494             bSetDOSExtendedError = TRUE;
3495         break;
3496
3497     case 0x5c: /* "FLOCK" - RECORD LOCKING */
3498         {
3499             DWORD  offset = MAKELONG(DX_reg(context), CX_reg(context));
3500             DWORD  length = MAKELONG(DI_reg(context), SI_reg(context));
3501             HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
3502
3503             switch (AL_reg(context))
3504             {
3505             case 0x00: /* LOCK */
3506                 TRACE( "lock handle %d offset %ld length %ld\n",
3507                        BX_reg(context), offset, length );
3508                 if (!LockFile( handle, offset, 0, length, 0 ))
3509                     bSetDOSExtendedError = TRUE;
3510                 break;
3511
3512             case 0x01: /* UNLOCK */
3513                 TRACE( "unlock handle %d offset %ld length %ld\n",
3514                        BX_reg(context), offset, length );
3515                 if (!UnlockFile( handle, offset, 0, length, 0 ))
3516                     bSetDOSExtendedError = TRUE;
3517                 break;
3518
3519             default:
3520                 INT_BARF( context, 0x21 );
3521             }
3522         }
3523         break;
3524
3525     case 0x5d: /* NETWORK 5D */
3526         FIXME( "Network function 5D not implemented.\n" );
3527         SetLastError( ER_NoNetwork );
3528         bSetDOSExtendedError = TRUE;
3529         break;
3530
3531     case 0x5e: /* NETWORK 5E */
3532     case 0x5f: /* NETWORK 5F */
3533     case 0x60: /* "TRUENAME" - CANONICALIZE FILENAME OR PATH */
3534         INT_Int21Handler( context );
3535         break;
3536
3537     case 0x61: /* UNUSED */
3538         SET_AL( context, 0 );
3539         break;
3540
3541     case 0x62: /* GET PSP ADDRESS */
3542         INT21_GetPSP( context );
3543         break;
3544
3545     case 0x63: /* MISC. LANGUAGE SUPPORT */
3546         switch (AL_reg(context)) {
3547         case 0x00: /* GET DOUBLE BYTE CHARACTER SET LEAD-BYTE TABLE */
3548             TRACE( "GET DOUBLE BYTE CHARACTER SET LEAD-BYTE TABLE\n" );
3549             context->SegDs = INT21_GetHeapSelector( context );
3550             SET_SI( context, offsetof(INT21_HEAP, dbcs_table) );
3551             SET_AL( context, 0 ); /* success */
3552             break;
3553         }
3554         break;
3555
3556     case 0x64: /* OS/2 DOS BOX */
3557         INT_BARF( context, 0x21 );
3558         SET_CFLAG(context);
3559         break;
3560
3561     case 0x65: /* EXTENDED COUNTRY INFORMATION */
3562         INT21_ExtendedCountryInformation( context );
3563         break;
3564
3565     case 0x66: /* GLOBAL CODE PAGE TABLE */
3566         switch (AL_reg(context))
3567         {
3568         case 0x01:
3569             TRACE( "GET GLOBAL CODE PAGE TABLE\n" );
3570             SET_BX( context, GetOEMCP() );
3571             SET_DX( context, GetOEMCP() );
3572             break;
3573         case 0x02:
3574             FIXME( "SET GLOBAL CODE PAGE TABLE, active %d, system %d - ignored\n",
3575                    BX_reg(context), DX_reg(context) );
3576             break;
3577         }
3578         break;
3579
3580     case 0x67: /* SET HANDLE COUNT */
3581         TRACE( "SET HANDLE COUNT to %d\n", BX_reg(context) );
3582         if (SetHandleCount( BX_reg(context) ) < BX_reg(context) )
3583             bSetDOSExtendedError = TRUE;
3584         break;
3585
3586     case 0x68: /* "FFLUSH" - COMMIT FILE */
3587         TRACE( "FFLUSH - handle %d\n", BX_reg(context) );
3588         if (!FlushFileBuffers( DosFileHandleToWin32Handle(BX_reg(context)) ))
3589             bSetDOSExtendedError = TRUE;
3590         break;
3591
3592     case 0x69: /* DISK SERIAL NUMBER */
3593         INT_Int21Handler( context );
3594         break;
3595
3596     case 0x6a: /* COMMIT FILE */
3597         TRACE( "COMMIT FILE - handle %d\n", BX_reg(context) );
3598         if (!FlushFileBuffers( DosFileHandleToWin32Handle(BX_reg(context)) ))
3599             bSetDOSExtendedError = TRUE;
3600         break;
3601
3602     case 0x6b: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
3603         SET_AL( context, 0 );
3604         break;
3605
3606     case 0x6c: /* EXTENDED OPEN/CREATE */
3607         if (!INT21_CreateFile( context, context->Esi, TRUE,
3608                                BX_reg(context), DL_reg(context) ))
3609             bSetDOSExtendedError = TRUE;
3610         break;
3611
3612     case 0x70: /* MSDOS 7 - GET/SET INTERNATIONALIZATION INFORMATION */
3613         FIXME( "MS-DOS 7 - GET/SET INTERNATIONALIZATION INFORMATION\n" );
3614         SET_CFLAG( context );
3615         SET_AL( context, 0 );
3616         break;
3617
3618     case 0x71: /* MSDOS 7 - LONG FILENAME FUNCTIONS */
3619         INT21_LongFilename( context );
3620         break;
3621
3622     case 0x73: /* MSDOS7 - FAT32 */
3623         INT_Int21Handler( context );
3624         break;
3625
3626     case 0xdc: /* CONNECTION SERVICES - GET CONNECTION NUMBER */
3627         TRACE( "CONNECTION SERVICES - GET CONNECTION NUMBER - ignored\n" );
3628         break;
3629
3630     case 0xea: /* NOVELL NETWARE - RETURN SHELL VERSION */
3631         TRACE( "NOVELL NETWARE - RETURN SHELL VERSION - ignored\n" );
3632         break;
3633
3634     default:
3635         INT_BARF( context, 0x21 );
3636         break;
3637
3638     } /* END OF SWITCH */
3639
3640     /* Set general error condition. */
3641     if (bSetDOSExtendedError)
3642     {
3643         SET_AX( context, GetLastError() );
3644         SET_CFLAG( context );
3645     }
3646
3647     /* Print error code if carry flag is set. */
3648     if (context->EFlags & 0x0001)
3649         TRACE("failed, error %ld\n", GetLastError() );
3650
3651     TRACE( "returning: AX=%04x BX=%04x CX=%04x DX=%04x "
3652            "SI=%04x DI=%04x DS=%04x ES=%04x EFL=%08lx\n",
3653            AX_reg(context), BX_reg(context), 
3654            CX_reg(context), DX_reg(context), 
3655            SI_reg(context), DI_reg(context),
3656            (WORD)context->SegDs, (WORD)context->SegEs,
3657            context->EFlags );
3658 }