Added some long filename 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         return drive - 1;
151     
152     return INT21_GetCurrentDrive();
153 }
154
155
156 /***********************************************************************
157  *           INT21_ReadChar
158  *
159  * Reads a character from the standard input.
160  * Extended keycodes will be returned as two separate characters.
161  */
162 static BOOL INT21_ReadChar( BYTE *input, CONTEXT86 *waitctx )
163 {
164     static BYTE pending_scan = 0;
165
166     if (pending_scan)
167     {
168         if (input)
169             *input = pending_scan;
170         if (waitctx)
171             pending_scan = 0;
172         return TRUE;
173     }
174     else
175     {
176         BYTE ascii;
177         BYTE scan;
178         if (!DOSVM_Int16ReadChar( &ascii, &scan, waitctx ))
179             return FALSE;
180
181         if (input)
182             *input = ascii;
183         if (waitctx && !ascii)
184             pending_scan = scan;
185         return TRUE;
186     }
187 }
188
189
190 /***********************************************************************
191  *           INT21_GetSystemCountryCode
192  *
193  * Return DOS country code for default system locale.
194  */
195 static WORD INT21_GetSystemCountryCode()
196 {
197     /*
198      * FIXME: Determine country code. We should probably use
199      *        DOSCONF structure for that.
200      */
201     return GetSystemDefaultLangID();
202 }
203
204
205 /***********************************************************************
206  *           INT21_FillCountryInformation
207  *
208  * Fill 34-byte buffer with country information data using
209  * default system locale.
210  */
211 static void INT21_FillCountryInformation( BYTE *buffer )
212 {
213     /* 00 - WORD: date format
214      *          00 = mm/dd/yy
215      *          01 = dd/mm/yy
216      *          02 = yy/mm/dd
217      */
218     *(WORD*)(buffer + 0) = 0; /* FIXME: Get from locale */
219
220     /* 02 - BYTE[5]: ASCIIZ currency symbol string */
221     buffer[2] = '$'; /* FIXME: Get from locale */
222     buffer[3] = 0;
223
224     /* 07 - BYTE[2]: ASCIIZ thousands separator */
225     buffer[7] = 0; /* FIXME: Get from locale */
226     buffer[8] = 0;
227
228     /* 09 - BYTE[2]: ASCIIZ decimal separator */
229     buffer[9]  = '.'; /* FIXME: Get from locale */
230     buffer[10] = 0;
231
232     /* 11 - BYTE[2]: ASCIIZ date separator */
233     buffer[11] = '/'; /* FIXME: Get from locale */
234     buffer[12] = 0;
235
236     /* 13 - BYTE[2]: ASCIIZ time separator */
237     buffer[13] = ':'; /* FIXME: Get from locale */
238     buffer[14] = 0;
239
240     /* 15 - BYTE: Currency format
241      *          bit 2 = set if currency symbol replaces decimal point
242      *          bit 1 = number of spaces between value and currency symbol
243      *          bit 0 = 0 if currency symbol precedes value
244      *                  1 if currency symbol follows value
245      */
246     buffer[15] = 0; /* FIXME: Get from locale */
247
248     /* 16 - BYTE: Number of digits after decimal in currency */
249     buffer[16] = 0; /* FIXME: Get from locale */
250
251     /* 17 - BYTE: Time format
252      *          bit 0 = 0 if 12-hour clock
253      *                  1 if 24-hour clock
254      */
255     buffer[17] = 1; /* FIXME: Get from locale */
256
257     /* 18 - DWORD: Address of case map routine */
258     *(DWORD*)(buffer + 18) = 0; /* FIXME: ptr to case map routine */
259
260     /* 22 - BYTE[2]: ASCIIZ data-list separator */
261     buffer[22] = ','; /* FIXME: Get from locale */
262     buffer[23] = 0;
263
264     /* 24 - BYTE[10]: Reserved */
265     memset( buffer + 24, 0, 10 );
266 }
267
268
269 /***********************************************************************
270  *           INT21_FillHeap
271  *
272  * Initialize DOS heap.
273  */
274 static void INT21_FillHeap( INT21_HEAP *heap )
275 {
276     static const char terminators[] = "\"\\./[]:|<>+=;,";
277     int i;
278
279     /*
280      * Uppercase table.
281      */
282     heap->uppercase_size = 128;
283     for (i = 0; i < 128; i++) 
284         heap->uppercase_table[i] = toupper( 128 + i );
285
286     /*
287      * Lowercase table.
288      */
289     heap->lowercase_size = 256;
290     for (i = 0; i < 256; i++) 
291         heap->lowercase_table[i] = tolower( i );
292     
293     /*
294      * Collating table.
295      */
296     heap->collating_size = 256;
297     for (i = 0; i < 256; i++) 
298         heap->collating_table[i] = i;
299
300     /*
301      * Filename table.
302      */
303     heap->filename_size = 8 + strlen(terminators);
304     heap->filename_illegal_size = strlen(terminators);
305     strcpy( heap->filename_illegal_table, terminators );
306
307     heap->filename_reserved1 = 0x01;
308     heap->filename_lowest = 0;           /* FIXME: correct value? */
309     heap->filename_highest = 0xff;       /* FIXME: correct value? */
310     heap->filename_reserved2 = 0x00;    
311     heap->filename_exclude_first = 0x00; /* FIXME: correct value? */
312     heap->filename_exclude_last = 0x00;  /* FIXME: correct value? */
313     heap->filename_reserved3 = 0x02;
314
315     /*
316      * DBCS lead byte table. This table is empty.
317      */
318     heap->dbcs_size = 0;
319     memset( heap->dbcs_table, 0, sizeof(heap->dbcs_table) );
320
321     /*
322      * Initialize InDos flag.
323      */
324     heap->misc_indos = 0;
325 }
326
327
328 /***********************************************************************
329  *           INT21_GetHeapSelector
330  *
331  * Get segment/selector for DOS heap (INT21_HEAP).
332  * Creates and initializes heap on first call.
333  */
334 static WORD INT21_GetHeapSelector( CONTEXT86 *context )
335 {
336     static WORD heap_segment = 0;
337     static WORD heap_selector = 0;
338     static BOOL heap_initialized = FALSE;
339
340     if (!heap_initialized)
341     {
342         INT21_HEAP *ptr = DOSVM_AllocDataUMB( sizeof(INT21_HEAP), 
343                                               &heap_segment,
344                                               &heap_selector );
345         INT21_FillHeap( ptr );
346         heap_initialized = TRUE;
347     }
348
349     if (!ISV86(context) && DOSVM_IsWin16())
350         return heap_selector;
351     else
352         return heap_segment;
353 }
354
355
356 /***********************************************************************
357  *           INT21_BufferedInput
358  *
359  * Handler for function 0x0a and reading from console using
360  * function 0x3f.
361  *
362  * Reads a string of characters from standard input until
363  * enter key is pressed. Returns either number of characters 
364  * read from console including terminating CR or 
365  * zero if capacity was zero.
366  */
367 static WORD INT21_BufferedInput( CONTEXT86 *context, BYTE *ptr, WORD capacity )
368 {
369     BYTE length = 0;
370
371     /*
372      * Return immediately if capacity is zero.
373      */
374     if (capacity == 0)
375         return 0;
376
377     while(TRUE)
378     {
379         BYTE ascii;
380         BYTE scan;
381
382         DOSVM_Int16ReadChar( &ascii, &scan, context );
383
384         if (ascii == '\r' || ascii == '\n')
385         {
386             /*
387              * FIXME: What should be echoed here?
388              */
389             DOSVM_PutChar( '\r' );
390             DOSVM_PutChar( '\n' );
391             ptr[length] = '\r';
392             return length + 1;
393         }
394
395         /*
396          * FIXME: This function is supposed to support
397          *        DOS editing keys...
398          */
399
400         /*
401          * If the buffer becomes filled to within one byte of
402          * capacity, DOS rejects all further characters up to,
403          * but not including, the terminating carriage return.
404          */
405         if (ascii != 0 && length < capacity-1)
406         {
407             DOSVM_PutChar( ascii );
408             ptr[length] = ascii;
409             length++;
410         }
411     }
412 }
413
414
415 /***********************************************************************
416  *           INT21_GetCurrentDTA
417  */
418 static BYTE *INT21_GetCurrentDTA( CONTEXT86 *context )
419 {
420     TDB *pTask = GlobalLock16(GetCurrentTask());
421
422     /* FIXME: This assumes DTA was set correctly! */
423     return (BYTE *)CTX_SEG_OFF_TO_LIN( context, SELECTOROF(pTask->dta),
424                                                 (DWORD)OFFSETOF(pTask->dta) );
425 }
426
427
428 /***********************************************************************
429  *           INT21_OpenFileUsingFCB
430  *
431  * Handler for function 0x0f.
432  *
433  * PARAMS
434  *  DX:DX [I/O] File control block (FCB or XFCB) of unopened file
435  *
436  * RETURNS (in AL)
437  *  0x00: successful
438  *  0xff: failed
439  *
440  * NOTES
441  *  Opens a FCB file for read/write in compatibility mode. Upon calling
442  *  the FCB must have the drive_number, file_name, and file_extension
443  *  fields filled and all other bytes cleared.
444  */
445 static void INT21_OpenFileUsingFCB( CONTEXT86 *context )
446 {
447     struct FCB *fcb;
448     struct XFCB *xfcb;
449     char file_path[16];
450     char *pos;
451     HANDLE handle;
452     HFILE16 hfile16;
453     BY_HANDLE_FILE_INFORMATION info;
454     BYTE AL_result;
455
456     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
457     if (fcb->drive_number == 0xff) {
458         xfcb = (struct XFCB *) fcb;
459         fcb = (struct FCB *) xfcb->fcb;
460     } /* if */
461
462     AL_result = 0;
463     file_path[0] = 'A' + INT21_MapDrive( fcb->drive_number );
464
465     if (AL_result == 0) {
466         file_path[1] = ':';
467         pos = &file_path[2];
468         memcpy(pos, fcb->file_name, 8);
469         pos[8] = ' ';
470         pos[9] = '\0';
471         pos = strchr(pos, ' ');
472         *pos = '.';
473         pos++;
474         memcpy(pos, fcb->file_extension, 3);
475         pos[3] = ' ';
476         pos[4] = '\0';
477         pos = strchr(pos, ' ');
478         *pos = '\0';
479
480         handle = (HANDLE) _lopen(file_path, OF_READWRITE);
481         if (handle == INVALID_HANDLE_VALUE) {
482             TRACE("_lopen(\"%s\") failed: INVALID_HANDLE_VALUE\n", file_path);
483             AL_result = 0xff; /* failed */
484         } else {
485             hfile16 = Win32HandleToDosFileHandle(handle);
486             if (hfile16 == HFILE_ERROR16) {
487                 TRACE("Win32HandleToDosFileHandle(%p) failed: HFILE_ERROR\n", handle);
488                 CloseHandle(handle);
489                 AL_result = 0xff; /* failed */
490             } else if (hfile16 > 255) {
491                 TRACE("hfile16 (=%d) larger than 255 for \"%s\"\n", hfile16, file_path);
492                 _lclose16(hfile16);
493                 AL_result = 0xff; /* failed */
494             } else {
495                 if (!GetFileInformationByHandle(handle, &info)) {
496                     TRACE("GetFileInformationByHandle(%d, %p) for \"%s\" failed\n",
497                           hfile16, handle, file_path);
498                     _lclose16(hfile16);
499                     AL_result = 0xff; /* failed */
500                 } else {
501                     fcb->drive_number = file_path[0] - 'A' + 1;
502                     fcb->current_block_number = 0;
503                     fcb->logical_record_size = 128;
504                     fcb->file_size = info.nFileSizeLow;
505                     FileTimeToDosDateTime(&info.ftLastWriteTime,
506                         &fcb->date_of_last_write, &fcb->time_of_last_write);
507                     fcb->file_number = hfile16;
508                     fcb->attributes = 0xc2;
509                     fcb->starting_cluster = 0; /* don't know correct init value */
510                     fcb->sequence_number = 0; /* don't know correct init value */
511                     fcb->file_attributes = info.dwFileAttributes;
512                     /* The following fields are not initialized */
513                     /* by the native function: */
514                     /* unused */
515                     /* record_within_current_block */
516                     /* random_access_record_number */
517
518                     TRACE("successful opened file \"%s\" as %d (handle %p)\n",
519                           file_path, hfile16, handle);
520                     AL_result = 0x00; /* successful */
521                 } /* if */
522             } /* if */
523         } /* if */
524     } /* if */
525     SET_AL(context, AL_result);
526 }
527
528
529 /***********************************************************************
530  *           INT21_CloseFileUsingFCB
531  *
532  * Handler for function 0x10.
533  *
534  * PARAMS
535  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
536  *
537  * RETURNS (in AL)
538  *  0x00: successful
539  *  0xff: failed
540  *
541  * NOTES
542  *  Closes a FCB file.
543  */
544 static void INT21_CloseFileUsingFCB( CONTEXT86 *context )
545 {
546     struct FCB *fcb;
547     struct XFCB *xfcb;
548     BYTE AL_result;
549
550     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
551     if (fcb->drive_number == 0xff) {
552         xfcb = (struct XFCB *) fcb;
553         fcb = (struct FCB *) xfcb->fcb;
554     } /* if */
555
556     if (_lclose16((HFILE16) fcb->file_number) != 0) {
557         TRACE("_lclose16(%d) failed\n", fcb->file_number);
558         AL_result = 0xff; /* failed */
559     } else {
560         TRACE("successful closed file %d\n", fcb->file_number);
561         AL_result = 0x00; /* successful */
562     } /* if */
563     SET_AL(context, AL_result);
564 }
565
566
567 /***********************************************************************
568  *           INT21_SequentialReadFromFCB
569  *
570  * Handler for function 0x14.
571  *
572  * PARAMS
573  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
574  *
575  * RETURNS (in AL)
576  *  0: successful
577  *  1: end of file, no data read
578  *  2: segment wrap in DTA, no data read (not returned now)
579  *  3: end of file, partial record read
580  *
581  * NOTES
582  *  Reads a record with the size FCB->logical_record_size from the FCB
583  *  to the disk transfer area. The position of the record is specified
584  *  with FCB->current_block_number and FCB->record_within_current_block.
585  *  Then FCB->current_block_number and FCB->record_within_current_block
586  *  are updated to point to the next record. If a partial record is
587  *  read, it is filled with zeros up to the FCB->logical_record_size.
588  */
589 static void INT21_SequentialReadFromFCB( CONTEXT86 *context )
590 {
591     struct FCB *fcb;
592     struct XFCB *xfcb;
593     HANDLE handle;
594     DWORD record_number;
595     long position;
596     BYTE *disk_transfer_area;
597     UINT bytes_read;
598     BYTE AL_result;
599
600     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
601     if (fcb->drive_number == 0xff) {
602         xfcb = (struct XFCB *) fcb;
603         fcb = (struct FCB *) xfcb->fcb;
604     } /* if */
605
606     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
607     if (handle == INVALID_HANDLE_VALUE) {
608         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
609             fcb->file_number);
610         AL_result = 0x01; /* end of file, no data read */
611     } else {
612         record_number = 128 * fcb->current_block_number + fcb->record_within_current_block;
613         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
614         if (position != record_number * fcb->logical_record_size) {
615             TRACE("seek(%d, %ld, 0) failed with %ld\n",
616                   fcb->file_number, record_number * fcb->logical_record_size, position);
617             AL_result = 0x01; /* end of file, no data read */
618         } else {
619             disk_transfer_area = INT21_GetCurrentDTA(context);
620             bytes_read = _lread((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
621             if (bytes_read != fcb->logical_record_size) {
622                 TRACE("_lread(%d, %p, %d) failed with %d\n",
623                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_read);
624                 if (bytes_read == 0) {
625                     AL_result = 0x01; /* end of file, no data read */
626                 } else {
627                     memset(&disk_transfer_area[bytes_read], 0, fcb->logical_record_size - bytes_read);
628                     AL_result = 0x03; /* end of file, partial record read */
629                 } /* if */
630             } else {
631                 TRACE("successful read %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
632                     bytes_read, record_number, position, fcb->file_number, handle);
633                 AL_result = 0x00; /* successful */
634             } /* if */
635         } /* if */
636     } /* if */
637     if (AL_result == 0x00 || AL_result == 0x03) {
638         if (fcb->record_within_current_block == 127) {
639             fcb->record_within_current_block = 0;
640             fcb->current_block_number++;
641         } else {
642             fcb->record_within_current_block++;
643         } /* if */
644     } /* if */
645     SET_AL(context, AL_result);
646 }
647
648
649 /***********************************************************************
650  *           INT21_SequentialWriteToFCB
651  *
652  * Handler for function 0x15.
653  *
654  * PARAMS
655  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
656  *
657  * RETURNS (in AL)
658  *  0: successful
659  *  1: disk full
660  *  2: segment wrap in DTA (not returned now)
661  *
662  * NOTES
663  *  Writes a record with the size FCB->logical_record_size from the disk
664  *  transfer area to the FCB. The position of the record is specified
665  *  with FCB->current_block_number and FCB->record_within_current_block.
666  *  Then FCB->current_block_number and FCB->record_within_current_block
667  *  are updated to point to the next record. 
668  */
669 static void INT21_SequentialWriteToFCB( CONTEXT86 *context )
670 {
671     struct FCB *fcb;
672     struct XFCB *xfcb;
673     HANDLE handle;
674     DWORD record_number;
675     long position;
676     BYTE *disk_transfer_area;
677     UINT bytes_written;
678     BYTE AL_result;
679
680     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
681     if (fcb->drive_number == 0xff) {
682         xfcb = (struct XFCB *) fcb;
683         fcb = (struct FCB *) xfcb->fcb;
684     } /* if */
685
686     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
687     if (handle == INVALID_HANDLE_VALUE) {
688         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
689             fcb->file_number);
690         AL_result = 0x01; /* disk full */
691     } else {
692         record_number = 128 * fcb->current_block_number + fcb->record_within_current_block;
693         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
694         if (position != record_number * fcb->logical_record_size) {
695             TRACE("seek(%d, %ld, 0) failed with %ld\n",
696                   fcb->file_number, record_number * fcb->logical_record_size, position);
697             AL_result = 0x01; /* disk full */
698         } else {
699             disk_transfer_area = INT21_GetCurrentDTA(context);
700             bytes_written = _lwrite((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
701             if (bytes_written != fcb->logical_record_size) {
702                 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
703                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_written);
704                 AL_result = 0x01; /* disk full */
705             } else {
706                 TRACE("successful written %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
707                     bytes_written, record_number, position, fcb->file_number, handle);
708                 AL_result = 0x00; /* successful */
709             } /* if */
710         } /* if */
711     } /* if */
712     if (AL_result == 0x00) {
713         if (fcb->record_within_current_block == 127) {
714             fcb->record_within_current_block = 0;
715             fcb->current_block_number++;
716         } else {
717             fcb->record_within_current_block++;
718         } /* if */
719     } /* if */
720     SET_AL(context, AL_result);
721 }
722
723
724 /***********************************************************************
725  *           INT21_ReadRandomRecordFromFCB
726  *
727  * Handler for function 0x21.
728  *
729  * PARAMS
730  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
731  *
732  * RETURNS (in AL)
733  *  0: successful
734  *  1: end of file, no data read
735  *  2: segment wrap in DTA, no data read (not returned now)
736  *  3: end of file, partial record read
737  *
738  * NOTES
739  *  Reads a record with the size FCB->logical_record_size from
740  *  the FCB to the disk transfer area. The position of the record
741  *  is specified with FCB->random_access_record_number. The
742  *  FCB->random_access_record_number is not updated. If a partial record
743  *  is read, it is filled with zeros up to the FCB->logical_record_size.
744  */
745 static void INT21_ReadRandomRecordFromFCB( CONTEXT86 *context )
746 {
747     struct FCB *fcb;
748     struct XFCB *xfcb;
749     HANDLE handle;
750     DWORD record_number;
751     long position;
752     BYTE *disk_transfer_area;
753     UINT bytes_read;
754     BYTE AL_result;
755
756     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
757     if (fcb->drive_number == 0xff) {
758         xfcb = (struct XFCB *) fcb;
759         fcb = (struct FCB *) xfcb->fcb;
760     } /* if */
761
762     memcpy(&record_number, fcb->random_access_record_number, 4);
763     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
764     if (handle == INVALID_HANDLE_VALUE) {
765         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
766             fcb->file_number);
767         AL_result = 0x01; /* end of file, no data read */
768     } else {
769         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
770         if (position != record_number * fcb->logical_record_size) {
771             TRACE("seek(%d, %ld, 0) failed with %ld\n",
772                   fcb->file_number, record_number * fcb->logical_record_size, position);
773             AL_result = 0x01; /* end of file, no data read */
774         } else {
775             disk_transfer_area = INT21_GetCurrentDTA(context);
776             bytes_read = _lread((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
777             if (bytes_read != fcb->logical_record_size) {
778                 TRACE("_lread(%d, %p, %d) failed with %d\n",
779                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_read);
780                 if (bytes_read == 0) {
781                     AL_result = 0x01; /* end of file, no data read */
782                 } else {
783                     memset(&disk_transfer_area[bytes_read], 0, fcb->logical_record_size - bytes_read);
784                     AL_result = 0x03; /* end of file, partial record read */
785                 } /* if */
786             } else {
787                 TRACE("successful read %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
788                     bytes_read, record_number, position, fcb->file_number, handle);
789                 AL_result = 0x00; /* successful */
790             } /* if */
791         } /* if */
792     } /* if */
793     fcb->current_block_number = record_number / 128;
794     fcb->record_within_current_block = record_number % 128;
795     SET_AL(context, AL_result);
796 }
797
798
799 /***********************************************************************
800  *           INT21_WriteRandomRecordToFCB
801  *
802  * Handler for function 0x22.
803  *
804  * PARAMS
805  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
806  *
807  * RETURNS (in AL)
808  *  0: successful
809  *  1: disk full
810  *  2: segment wrap in DTA (not returned now)
811  *
812  * NOTES
813  *  Writes a record with the size FCB->logical_record_size from
814  *  the disk transfer area to the FCB. The position of the record
815  *  is specified with FCB->random_access_record_number. The
816  *  FCB->random_access_record_number is not updated.
817  */
818 static void INT21_WriteRandomRecordToFCB( CONTEXT86 *context )
819 {
820     struct FCB *fcb;
821     struct XFCB *xfcb;
822     HANDLE handle;
823     DWORD record_number;
824     long position;
825     BYTE *disk_transfer_area;
826     UINT bytes_written;
827     BYTE AL_result;
828
829     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
830     if (fcb->drive_number == 0xff) {
831         xfcb = (struct XFCB *) fcb;
832         fcb = (struct FCB *) xfcb->fcb;
833     } /* if */
834
835     memcpy(&record_number, fcb->random_access_record_number, 4);
836     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
837     if (handle == INVALID_HANDLE_VALUE) {
838         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
839             fcb->file_number);
840         AL_result = 0x01; /* disk full */
841     } else {
842         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
843         if (position != record_number * fcb->logical_record_size) {
844             TRACE("seek(%d, %ld, 0) failed with %ld\n",
845                   fcb->file_number, record_number * fcb->logical_record_size, position);
846             AL_result = 0x01; /* disk full */
847         } else {
848             disk_transfer_area = INT21_GetCurrentDTA(context);
849             bytes_written = _lwrite((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
850             if (bytes_written != fcb->logical_record_size) {
851                 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
852                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_written);
853                 AL_result = 0x01; /* disk full */
854             } else {
855                 TRACE("successful written %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
856                     bytes_written, record_number, position, fcb->file_number, handle);
857                 AL_result = 0x00; /* successful */
858             } /* if */
859         } /* if */
860     } /* if */
861     fcb->current_block_number = record_number / 128;
862     fcb->record_within_current_block = record_number % 128;
863     SET_AL(context, AL_result);
864 }
865
866
867 /***********************************************************************
868  *           INT21_RandomBlockReadFromFCB
869  *
870  * Handler for function 0x27.
871  *
872  * PARAMS
873  *  CX    [I/O] Number of records to read
874  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
875  *
876  * RETURNS (in AL)
877  *  0: successful
878  *  1: end of file, no data read
879  *  2: segment wrap in DTA, no data read (not returned now)
880  *  3: end of file, partial record read
881  *
882  * NOTES
883  *  Reads several records with the size FCB->logical_record_size from
884  *  the FCB to the disk transfer area. The number of records to be
885  *  read is specified in the CX register. The position of the first
886  *  record is specified with FCB->random_access_record_number. The
887  *  FCB->random_access_record_number, the FCB->current_block_number
888  *  and FCB->record_within_current_block are updated to point to the
889  *  next record after the records read. If a partial record is read,
890  *  it is filled with zeros up to the FCB->logical_record_size. The
891  *  CX register is set to the number of successfully read records.
892  */
893 static void INT21_RandomBlockReadFromFCB( CONTEXT86 *context )
894 {
895     struct FCB *fcb;
896     struct XFCB *xfcb;
897     HANDLE handle;
898     DWORD record_number;
899     long position;
900     BYTE *disk_transfer_area;
901     UINT records_requested;
902     UINT bytes_requested;
903     UINT bytes_read;
904     UINT records_read;
905     BYTE AL_result;
906
907     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
908     if (fcb->drive_number == 0xff) {
909         xfcb = (struct XFCB *) fcb;
910         fcb = (struct FCB *) xfcb->fcb;
911     } /* if */
912
913     memcpy(&record_number, fcb->random_access_record_number, 4);
914     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
915     if (handle == INVALID_HANDLE_VALUE) {
916         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
917             fcb->file_number);
918         records_read = 0;
919         AL_result = 0x01; /* end of file, no data read */
920     } else {
921         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
922         if (position != record_number * fcb->logical_record_size) {
923             TRACE("seek(%d, %ld, 0) failed with %ld\n",
924                   fcb->file_number, record_number * fcb->logical_record_size, position);
925             records_read = 0;
926             AL_result = 0x01; /* end of file, no data read */
927         } else {
928             disk_transfer_area = INT21_GetCurrentDTA(context);
929             records_requested = CX_reg(context);
930             bytes_requested = (UINT) records_requested * fcb->logical_record_size;
931             bytes_read = _lread((HFILE) handle, disk_transfer_area, bytes_requested);
932             if (bytes_read != bytes_requested) {
933                 TRACE("_lread(%d, %p, %d) failed with %d\n",
934                       fcb->file_number, disk_transfer_area, bytes_requested, bytes_read);
935                 records_read = bytes_read / fcb->logical_record_size;
936                 if (bytes_read % fcb->logical_record_size == 0) {
937                     AL_result = 0x01; /* end of file, no data read */
938                 } else {
939                     records_read++;
940                     memset(&disk_transfer_area[bytes_read], 0, records_read * fcb->logical_record_size - bytes_read);
941                     AL_result = 0x03; /* end of file, partial record read */
942                 } /* if */
943             } else {
944                 TRACE("successful read %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
945                     bytes_read, record_number, position, fcb->file_number, handle);
946                 records_read = records_requested;
947                 AL_result = 0x00; /* successful */
948             } /* if */
949         } /* if */
950     } /* if */
951     record_number += records_read;
952     memcpy(fcb->random_access_record_number, &record_number, 4);
953     fcb->current_block_number = record_number / 128;
954     fcb->record_within_current_block = record_number % 128;
955     SET_CX(context, records_read);
956     SET_AL(context, AL_result);
957 }
958
959
960 /***********************************************************************
961  *           INT21_RandomBlockWriteToFCB
962  *
963  * Handler for function 0x28.
964  *
965  * PARAMS
966  *  CX    [I/O] Number of records to write
967  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
968  *
969  * RETURNS (in AL)
970  *  0: successful
971  *  1: disk full
972  *  2: segment wrap in DTA (not returned now)
973  *
974  * NOTES
975  *  Writes several records with the size FCB->logical_record_size from
976  *  the disk transfer area to the FCB. The number of records to be
977  *  written is specified in the CX register. The position of the first
978  *  record is specified with FCB->random_access_record_number. The
979  *  FCB->random_access_record_number, the FCB->current_block_number
980  *  and FCB->record_within_current_block are updated to point to the
981  *  next record after the records written. The CX register is set to
982  *  the number of successfully written records.
983  */
984 static void INT21_RandomBlockWriteToFCB( CONTEXT86 *context )
985 {
986     struct FCB *fcb;
987     struct XFCB *xfcb;
988     HANDLE handle;
989     DWORD record_number;
990     long position;
991     BYTE *disk_transfer_area;
992     UINT records_requested;
993     UINT bytes_requested;
994     UINT bytes_written;
995     UINT records_written;
996     BYTE AL_result;
997
998     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
999     if (fcb->drive_number == 0xff) {
1000         xfcb = (struct XFCB *) fcb;
1001         fcb = (struct FCB *) xfcb->fcb;
1002     } /* if */
1003
1004     memcpy(&record_number, fcb->random_access_record_number, 4);
1005     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1006     if (handle == INVALID_HANDLE_VALUE) {
1007         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1008             fcb->file_number);
1009         records_written = 0;
1010         AL_result = 0x01; /* disk full */
1011     } else {
1012         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1013         if (position != record_number * fcb->logical_record_size) {
1014             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1015                   fcb->file_number, record_number * fcb->logical_record_size, position);
1016             records_written = 0;
1017             AL_result = 0x01; /* disk full */
1018         } else {
1019             disk_transfer_area = INT21_GetCurrentDTA(context);
1020             records_requested = CX_reg(context);
1021             bytes_requested = (UINT) records_requested * fcb->logical_record_size;
1022             bytes_written = _lwrite((HFILE) handle, disk_transfer_area, bytes_requested);
1023             if (bytes_written != bytes_requested) {
1024                 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
1025                       fcb->file_number, disk_transfer_area, bytes_requested, bytes_written);
1026                 records_written = bytes_written / fcb->logical_record_size;
1027                 AL_result = 0x01; /* disk full */
1028             } else {
1029                 TRACE("successful write %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1030                     bytes_written, record_number, position, fcb->file_number, handle);
1031                 records_written = records_requested;
1032                 AL_result = 0x00; /* successful */
1033             } /* if */
1034         } /* if */
1035     } /* if */
1036     record_number += records_written;
1037     memcpy(fcb->random_access_record_number, &record_number, 4);
1038     fcb->current_block_number = record_number / 128;
1039     fcb->record_within_current_block = record_number % 128;
1040     SET_CX(context, records_written);
1041     SET_AL(context, AL_result);
1042 }
1043
1044
1045 /***********************************************************************
1046  *           INT21_CreateDirectory
1047  *
1048  * Handler for:
1049  * - function 0x39
1050  * - subfunction 0x39 of function 0x71
1051  * - subfunction 0xff of function 0x43 (CL == 0x39)
1052  */
1053 static BOOL INT21_CreateDirectory( CONTEXT86 *context )
1054 {
1055     WCHAR dirW[MAX_PATH];
1056     char *dirA = CTX_SEG_OFF_TO_LIN(context,
1057                                     context->SegDs, 
1058                                     context->Edx);
1059
1060     TRACE( "CREATE DIRECTORY %s\n", dirA );
1061
1062     MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
1063
1064     if (CreateDirectoryW(dirW, NULL))
1065         return TRUE;
1066
1067     /*
1068      * FIXME: CreateDirectory's LastErrors will clash with the ones
1069      *        used by DOS. AH=39 only returns 3 (path not found) and 
1070      *        5 (access denied), while CreateDirectory return several
1071      *        ones. Remap some of them. -Marcus
1072      */
1073     switch (GetLastError()) 
1074     {
1075     case ERROR_ALREADY_EXISTS:
1076     case ERROR_FILENAME_EXCED_RANGE:
1077     case ERROR_DISK_FULL:
1078         SetLastError(ERROR_ACCESS_DENIED);
1079         break;
1080     default: 
1081         break;
1082     }
1083
1084     return FALSE;
1085 }
1086
1087
1088 /***********************************************************************
1089  *           INT21_ExtendedCountryInformation
1090  *
1091  * Handler for function 0x65.
1092  */
1093 static void INT21_ExtendedCountryInformation( CONTEXT86 *context )
1094 {
1095     BYTE *dataptr = CTX_SEG_OFF_TO_LIN( context, context->SegEs, context->Edi );
1096
1097     TRACE( "GET EXTENDED COUNTRY INFORMATION, subfunction %02x\n",
1098            AL_reg(context) );
1099
1100     /*
1101      * Check subfunctions that are passed country and code page.
1102      */
1103     if (AL_reg(context) >= 0x01 && AL_reg(context) <= 0x07)
1104     {
1105         WORD country = DX_reg(context);
1106         WORD codepage = BX_reg(context);
1107
1108         if (country != 0xffff && country != INT21_GetSystemCountryCode())
1109             FIXME( "Requested info on non-default country %04x\n", country );
1110
1111         if (codepage != 0xffff && codepage != GetOEMCP())
1112             FIXME( "Requested info on non-default code page %04x\n", codepage );
1113     }
1114
1115     switch (AL_reg(context)) {
1116     case 0x00: /* SET GENERAL INTERNATIONALIZATION INFO */
1117         INT_BARF( context, 0x21 );
1118         SET_CFLAG( context );
1119         break;
1120
1121     case 0x01: /* GET GENERAL INTERNATIONALIZATION INFO */
1122         TRACE( "Get general internationalization info\n" );
1123         dataptr[0] = 0x01; /* Info ID */
1124         *(WORD*)(dataptr+1) = 38; /* Size of the following info */
1125         *(WORD*)(dataptr+3) = INT21_GetSystemCountryCode(); /* Country ID */
1126         *(WORD*)(dataptr+5) = GetOEMCP(); /* Code page */
1127         INT21_FillCountryInformation( dataptr + 7 );
1128         SET_CX( context, 41 ); /* Size of returned info */
1129         break;
1130         
1131     case 0x02: /* GET POINTER TO UPPERCASE TABLE */
1132     case 0x04: /* GET POINTER TO FILENAME UPPERCASE TABLE */
1133         TRACE( "Get pointer to uppercase table\n" );
1134         dataptr[0] = AL_reg(context); /* Info ID */
1135         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1136                                            offsetof(INT21_HEAP, uppercase_size) );
1137         SET_CX( context, 5 ); /* Size of returned info */
1138         break;
1139
1140     case 0x03: /* GET POINTER TO LOWERCASE TABLE */
1141         TRACE( "Get pointer to lowercase table\n" );
1142         dataptr[0] = 0x03; /* Info ID */
1143         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1144                                            offsetof(INT21_HEAP, lowercase_size) );
1145         SET_CX( context, 5 ); /* Size of returned info */
1146         break;
1147
1148     case 0x05: /* GET POINTER TO FILENAME TERMINATOR TABLE */
1149         TRACE("Get pointer to filename terminator table\n");
1150         dataptr[0] = 0x05; /* Info ID */
1151         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1152                                            offsetof(INT21_HEAP, filename_size) );
1153         SET_CX( context, 5 ); /* Size of returned info */
1154         break;
1155
1156     case 0x06: /* GET POINTER TO COLLATING SEQUENCE TABLE */
1157         TRACE("Get pointer to collating sequence table\n");
1158         dataptr[0] = 0x06; /* Info ID */
1159         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1160                                            offsetof(INT21_HEAP, collating_size) );
1161         SET_CX( context, 5 ); /* Size of returned info */
1162         break;
1163
1164     case 0x07: /* GET POINTER TO DBCS LEAD BYTE TABLE */
1165         TRACE("Get pointer to DBCS lead byte table\n");
1166         dataptr[0] = 0x07; /* Info ID */
1167         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1168                                            offsetof(INT21_HEAP, dbcs_size) );
1169         SET_CX( context, 5 ); /* Size of returned info */
1170         break;
1171
1172     case 0x20: /* CAPITALIZE CHARACTER */
1173     case 0xa0: /* CAPITALIZE FILENAME CHARACTER */
1174         TRACE("Convert char to uppercase\n");
1175         SET_DL( context, toupper(DL_reg(context)) );
1176         break;
1177
1178     case 0x21: /* CAPITALIZE STRING */
1179     case 0xa1: /* CAPITALIZE COUNTED FILENAME STRING */
1180         TRACE("Convert string to uppercase with length\n");
1181         {
1182             char *ptr = (char *)CTX_SEG_OFF_TO_LIN( context,
1183                                                     context->SegDs,
1184                                                     context->Edx );
1185             WORD len = CX_reg(context);
1186             while (len--) { *ptr = toupper(*ptr); ptr++; }
1187         }
1188         break;
1189
1190     case 0x22: /* CAPITALIZE ASCIIZ STRING */
1191     case 0xa2: /* CAPITALIZE ASCIIZ FILENAME */
1192         TRACE("Convert ASCIIZ string to uppercase\n");
1193         _strupr( (LPSTR)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx) );
1194         break;
1195
1196     case 0x23: /* DETERMINE IF CHARACTER REPRESENTS YES/NO RESPONSE */
1197         INT_BARF( context, 0x21 );
1198         SET_CFLAG( context );
1199         break;
1200
1201     default:
1202         INT_BARF( context, 0x21 );
1203         SET_CFLAG(context);
1204         break;
1205     }
1206 }
1207
1208
1209 /***********************************************************************
1210  *           INT21_FileAttributes
1211  *
1212  * Handler for:
1213  * - function 0x43
1214  * - subfunction 0x43 of function 0x71
1215  */
1216 static BOOL INT21_FileAttributes( CONTEXT86 *context, 
1217                                   BYTE       subfunction,
1218                                   BOOL       islong )
1219 {
1220     WCHAR fileW[MAX_PATH];
1221     char *fileA = CTX_SEG_OFF_TO_LIN(context, 
1222                                      context->SegDs, 
1223                                      context->Edx);
1224     HANDLE   handle;
1225     BOOL     status;
1226     FILETIME filetime;
1227     DWORD    result;
1228     WORD     date, time;
1229
1230     switch (subfunction)
1231     {
1232     case 0x00: /* GET FILE ATTRIBUTES */
1233         TRACE( "GET FILE ATTRIBUTES for %s\n", fileA );
1234         MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1235
1236         result = GetFileAttributesW( fileW );
1237         if (result == -1)
1238             return FALSE;
1239         else
1240         {
1241             SET_CX( context, (WORD)result );
1242             if (!islong)
1243                 SET_AX( context, (WORD)result ); /* DR DOS */
1244         }
1245         break;
1246
1247     case 0x01: /* SET FILE ATTRIBUTES */
1248         TRACE( "SET FILE ATTRIBUTES 0x%02x for %s\n", 
1249                CX_reg(context), fileA );
1250         MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1251
1252         if (!SetFileAttributesW( fileW, CX_reg(context) ))
1253             return FALSE;
1254         break;
1255
1256     case 0x02: /* GET COMPRESSED FILE SIZE */
1257         TRACE( "GET COMPRESSED FILE SIZE for %s\n", fileA );
1258         MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1259
1260         result = GetCompressedFileSizeW( fileW, NULL );
1261         if (result == INVALID_FILE_SIZE)
1262             return FALSE;
1263         else
1264         {
1265             SET_AX( context, LOWORD(result) );
1266             SET_DX( context, HIWORD(result) );
1267         }
1268         break;
1269
1270     case 0x03: /* SET FILE LAST-WRITTEN DATE AND TIME */
1271         if (!islong)
1272             INT_BARF( context, 0x21 );
1273         else
1274         {
1275             TRACE( "SET FILE LAST-WRITTEN DATE AND TIME, file %s\n", fileA );
1276             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1277
1278             handle = CreateFileW( fileW, GENERIC_WRITE, 
1279                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1280                                   NULL, OPEN_EXISTING, 0, 0 );
1281             if (handle == INVALID_HANDLE_VALUE)
1282                 return FALSE;
1283
1284             DosDateTimeToFileTime( DI_reg(context), 
1285                                    CX_reg(context),
1286                                    &filetime );
1287             status = SetFileTime( handle, NULL, NULL, &filetime );
1288
1289             CloseHandle( handle );
1290             return status;
1291         }
1292         break;
1293
1294     case 0x04: /* GET FILE LAST-WRITTEN DATE AND TIME */
1295         if (!islong)
1296             INT_BARF( context, 0x21 );
1297         else
1298         {
1299             TRACE( "GET FILE LAST-WRITTEN DATE AND TIME, file %s\n", fileA );
1300             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1301
1302             handle = CreateFileW( fileW, GENERIC_READ, 
1303                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1304                                   NULL, OPEN_EXISTING, 0, 0 );
1305             if (handle == INVALID_HANDLE_VALUE)
1306                 return FALSE;
1307
1308             status = GetFileTime( handle, NULL, NULL, &filetime );
1309             if (status)
1310             {
1311                 FileTimeToDosDateTime( &filetime, &date, &time );
1312                 SET_DI( context, date );
1313                 SET_CX( context, time );
1314             }
1315
1316             CloseHandle( handle );
1317             return status;
1318         }
1319         break;
1320
1321     case 0x05: /* SET FILE LAST ACCESS DATE */
1322         if (!islong)
1323             INT_BARF( context, 0x21 );
1324         else
1325         {
1326             TRACE( "SET FILE LAST ACCESS DATE, file %s\n", fileA );
1327             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1328
1329             handle = CreateFileW( fileW, GENERIC_WRITE, 
1330                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1331                                   NULL, OPEN_EXISTING, 0, 0 );
1332             if (handle == INVALID_HANDLE_VALUE)
1333                 return FALSE;
1334
1335             DosDateTimeToFileTime( DI_reg(context), 
1336                                    0,
1337                                    &filetime );
1338             status = SetFileTime( handle, NULL, &filetime, NULL );
1339
1340             CloseHandle( handle );
1341             return status;
1342         }
1343         break;
1344
1345     case 0x06: /* GET FILE LAST ACCESS DATE */
1346         if (!islong)
1347             INT_BARF( context, 0x21 );
1348         else
1349         {
1350             TRACE( "GET FILE LAST ACCESS DATE, file %s\n", fileA );
1351             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1352
1353             handle = CreateFileW( fileW, GENERIC_READ, 
1354                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1355                                   NULL, OPEN_EXISTING, 0, 0 );
1356             if (handle == INVALID_HANDLE_VALUE)
1357                 return FALSE;
1358
1359             status = GetFileTime( handle, NULL, &filetime, NULL );
1360             if (status)
1361             {
1362                 FileTimeToDosDateTime( &filetime, &date, NULL );
1363                 SET_DI( context, date );
1364             }
1365
1366             CloseHandle( handle );
1367             return status;
1368         }
1369         break;
1370
1371     case 0x07: /* SET FILE CREATION DATE AND TIME */
1372         if (!islong)
1373             INT_BARF( context, 0x21 );
1374         else
1375         {
1376             TRACE( "SET FILE CREATION DATE AND TIME, file %s\n", fileA );
1377
1378             handle = CreateFileW( fileW, GENERIC_WRITE,
1379                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1380                                   NULL, OPEN_EXISTING, 0, 0 );
1381             if (handle == INVALID_HANDLE_VALUE)
1382                 return FALSE;
1383             
1384             /*
1385              * FIXME: SI has number of 10-millisecond units past time in CX.
1386              */
1387             DosDateTimeToFileTime( DI_reg(context),
1388                                    CX_reg(context),
1389                                    &filetime );
1390             status = SetFileTime( handle, &filetime, NULL, NULL );
1391
1392             CloseHandle( handle );
1393             return status;
1394         }
1395         break;
1396
1397     case 0x08: /* GET FILE CREATION DATE AND TIME */
1398         if (!islong)
1399             INT_BARF( context, 0x21 );
1400         else
1401         {
1402             TRACE( "GET FILE CREATION DATE AND TIME, handle %d\n",
1403                    BX_reg(context) );
1404
1405             handle = CreateFileW( fileW, GENERIC_READ, 
1406                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1407                                   NULL, OPEN_EXISTING, 0, 0 );
1408             if (handle == INVALID_HANDLE_VALUE)
1409                 return FALSE;
1410
1411             status = GetFileTime( handle, &filetime, NULL, NULL );
1412             if (status)
1413             {            
1414                 FileTimeToDosDateTime( &filetime, &date, &time );
1415                 SET_DI( context, date );
1416                 SET_CX( context, time );
1417                 /*
1418                  * FIXME: SI has number of 10-millisecond units past 
1419                  *        time in CX.
1420                  */
1421                 SET_SI( context, 0 );
1422             }
1423
1424             CloseHandle(handle);
1425             return status;
1426         }
1427         break;
1428
1429     case 0xff: /* EXTENDED-LENGTH FILENAME OPERATIONS */
1430         if (islong || context->Ebp != 0x5053)
1431             INT_BARF( context, 0x21 );
1432         else
1433         {
1434             switch(CL_reg(context))
1435             {
1436             case 0x39:
1437                 if (!INT21_CreateDirectory( context ))
1438                     return FALSE;
1439                 break;
1440
1441             case 0x56:
1442                 if (!INT21_RenameFile( context ))
1443                     return FALSE;
1444                 break;
1445
1446             default:
1447                 INT_BARF( context, 0x21 );
1448             }
1449         }
1450         break;
1451
1452     default:
1453         INT_BARF( context, 0x21 );
1454     }
1455
1456     return TRUE;
1457 }
1458
1459
1460 /***********************************************************************
1461  *           INT21_FileDateTime
1462  *
1463  * Handler for function 0x57.
1464  */
1465 static BOOL INT21_FileDateTime( CONTEXT86 *context )
1466 {
1467     HANDLE   handle = DosFileHandleToWin32Handle(BX_reg(context));
1468     FILETIME filetime;
1469     WORD     date, time;
1470
1471     switch (AL_reg(context)) {
1472     case 0x00:  /* Get last-written stamp */
1473         TRACE( "GET FILE LAST-WRITTEN DATE AND TIME, handle %d\n",
1474                BX_reg(context) );
1475         {
1476             if (!GetFileTime( handle, NULL, NULL, &filetime ))
1477                 return FALSE;
1478             FileTimeToDosDateTime( &filetime, &date, &time );
1479             SET_DX( context, date );
1480             SET_CX( context, time );
1481             break;
1482         }
1483
1484     case 0x01:  /* Set last-written stamp */
1485         TRACE( "SET FILE LAST-WRITTEN DATE AND TIME, handle %d\n",
1486                BX_reg(context) );
1487         {
1488             DosDateTimeToFileTime( DX_reg(context), 
1489                                    CX_reg(context),
1490                                    &filetime );
1491             if (!SetFileTime( handle, NULL, NULL, &filetime ))
1492                 return FALSE;
1493             break;
1494         }
1495
1496     case 0x04:  /* Get last access stamp, DOS 7 */
1497         TRACE( "GET FILE LAST ACCESS DATE AND TIME, handle %d\n",
1498                BX_reg(context) );
1499         {
1500             if (!GetFileTime( handle, NULL, &filetime, NULL ))
1501                 return FALSE;
1502             FileTimeToDosDateTime( &filetime, &date, &time );
1503             SET_DX( context, date );
1504             SET_CX( context, time );
1505             break;
1506         }
1507
1508     case 0x05:  /* Set last access stamp, DOS 7 */
1509         TRACE( "SET FILE LAST ACCESS DATE AND TIME, handle %d\n",
1510                BX_reg(context) );
1511         {
1512             DosDateTimeToFileTime( DX_reg(context), 
1513                                    CX_reg(context),
1514                                    &filetime );
1515             if (!SetFileTime( handle, NULL, &filetime, NULL ))
1516                 return FALSE;
1517             break;
1518         }
1519
1520     case 0x06:  /* Get creation stamp, DOS 7 */
1521         TRACE( "GET FILE CREATION DATE AND TIME, handle %d\n",
1522                BX_reg(context) );
1523         {
1524             if (!GetFileTime( handle, &filetime, NULL, NULL ))
1525                 return FALSE;
1526             FileTimeToDosDateTime( &filetime, &date, &time );
1527             SET_DX( context, date );
1528             SET_CX( context, time );
1529             /*
1530              * FIXME: SI has number of 10-millisecond units past time in CX.
1531              */
1532             SET_SI( context, 0 );
1533             break;
1534         }
1535
1536     case 0x07:  /* Set creation stamp, DOS 7 */
1537         TRACE( "SET FILE CREATION DATE AND TIME, handle %d\n",
1538                BX_reg(context) );
1539         {
1540             /*
1541              * FIXME: SI has number of 10-millisecond units past time in CX.
1542              */
1543             DosDateTimeToFileTime( DX_reg(context), 
1544                                    CX_reg(context),
1545                                    &filetime );
1546             if (!SetFileTime( handle, &filetime, NULL, NULL ))
1547                 return FALSE;
1548             break;
1549         }
1550
1551     default:
1552         INT_BARF( context, 0x21 );
1553         break;
1554     }
1555
1556     return TRUE;
1557 }
1558
1559
1560 /***********************************************************************
1561  *           INT21_GetPSP
1562  *
1563  * Handler for functions 0x51 and 0x62.
1564  */
1565 static void INT21_GetPSP( CONTEXT86 *context )
1566 {
1567     TRACE( "GET CURRENT PSP ADDRESS (%02x)\n", AH_reg(context) );
1568
1569     /*
1570      * FIXME: should we return the original DOS PSP upon
1571      *        Windows startup ? 
1572      */
1573     if (!ISV86(context) && DOSVM_IsWin16())
1574         SET_BX( context, LOWORD(GetCurrentPDB16()) );
1575     else
1576         SET_BX( context, DOSVM_psp );
1577 }
1578
1579
1580 /***********************************************************************
1581  *           INT21_Ioctl_Block
1582  *
1583  * Handler for block device IOCTLs.
1584  */
1585 static void INT21_Ioctl_Block( CONTEXT86 *context )
1586 {
1587     INT_Int21Handler( context );
1588 }
1589
1590
1591 /***********************************************************************
1592  *           INT21_Ioctl_Char
1593  *
1594  * Handler for character device IOCTLs.
1595  */
1596 static void INT21_Ioctl_Char( CONTEXT86 *context )
1597 {
1598     static const WCHAR emmxxxx0W[] = {'E','M','M','X','X','X','X','0',0};
1599     static const WCHAR scsimgrW[] = {'S','C','S','I','M','G','R','$',0};
1600
1601     HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
1602     const DOS_DEVICE *dev = DOSFS_GetDeviceByHandle(handle);
1603
1604     if (dev && !strcmpiW( dev->name, emmxxxx0W )) 
1605     {
1606         EMS_Ioctl_Handler(context);
1607         return;
1608     }
1609
1610     if (dev && !strcmpiW( dev->name, scsimgrW ) && AL_reg(context) == 2)
1611     {
1612         DOSVM_ASPIHandler(context);
1613         return;
1614     }
1615
1616     INT_Int21Handler( context );
1617 }
1618
1619
1620 /***********************************************************************
1621  *           INT21_Ioctl
1622  *
1623  * Handler for function 0x44.
1624  */
1625 static void INT21_Ioctl( CONTEXT86 *context )
1626 {
1627     switch (AL_reg(context))
1628     {
1629     case 0x00:
1630     case 0x01:
1631     case 0x02:
1632     case 0x03:
1633         INT21_Ioctl_Char( context );
1634         break;
1635
1636     case 0x04:
1637     case 0x05:
1638         INT21_Ioctl_Block( context );
1639         break;
1640
1641     case 0x06:
1642     case 0x07:
1643         INT21_Ioctl_Char( context );
1644         break;
1645
1646     case 0x08:
1647     case 0x09:
1648         INT21_Ioctl_Block( context );
1649         break;
1650
1651     case 0x0a:
1652         INT21_Ioctl_Char( context );
1653         break;
1654
1655     case 0x0b: /* SET SHARING RETRY COUNT */
1656         TRACE( "SET SHARING RETRY COUNT: Pause %d, retries %d.\n",
1657                CX_reg(context), DX_reg(context) );
1658         if (!CX_reg(context))
1659         {
1660             SET_AX( context, 1 );
1661             SET_CFLAG( context );
1662         }
1663         else
1664         {
1665             DOSMEM_LOL()->sharing_retry_delay = CX_reg(context);
1666             if (DX_reg(context))
1667                 DOSMEM_LOL()->sharing_retry_count = DX_reg(context);
1668             RESET_CFLAG( context );
1669         }
1670         break;
1671
1672     case 0x0c:
1673         INT21_Ioctl_Char( context );
1674         break;
1675
1676     case 0x0d:
1677     case 0x0e:
1678     case 0x0f:
1679         INT21_Ioctl_Block( context );
1680         break;
1681
1682     case 0x10:
1683         INT21_Ioctl_Char( context );
1684         break;
1685
1686     case 0x11:
1687         INT21_Ioctl_Block( context );
1688         break;
1689
1690     case 0x12: /*  DR DOS - DETERMINE DOS TYPE (OBSOLETE FUNCTION) */
1691         TRACE( "DR DOS - DETERMINE DOS TYPE (OBSOLETE FUNCTION)\n" );
1692         SET_CFLAG(context);        /* Error / This is not DR DOS. */
1693         SET_AX( context, 0x0001 ); /* Invalid function */
1694         break;
1695
1696     case 0x52: /* DR DOS - DETERMINE DOS TYPE */
1697         TRACE( "DR DOS - DETERMINE DOS TYPE\n" );
1698         SET_CFLAG(context);        /* Error / This is not DR DOS. */
1699         SET_AX( context, 0x0001 ); /* Invalid function */
1700         break;
1701
1702     case 0xe0:  /* Sun PC-NFS API */
1703         TRACE( "Sun PC-NFS API\n" );
1704         /* not installed */
1705         break;
1706
1707     default:
1708         INT_BARF( context, 0x21 );
1709     }
1710 }
1711
1712
1713 /***********************************************************************
1714  *           INT21_LongFilename
1715  *
1716  * Handler for function 0x71.
1717  */
1718 static void INT21_LongFilename( CONTEXT86 *context )
1719 {
1720     BOOL bSetDOSExtendedError = FALSE;
1721
1722     if (HIBYTE(HIWORD(GetVersion16())) < 0x07)
1723     {
1724         TRACE( "LONG FILENAME - functions supported only under DOS7\n" );
1725         SET_CFLAG( context );
1726         SET_AL( context, 0 );
1727         return;
1728     }
1729
1730     switch (AL_reg(context))
1731     {
1732     case 0x0d: /* RESET DRIVE */
1733         INT_BARF( context, 0x21 );
1734         break;
1735
1736     case 0x39: /* LONG FILENAME - MAKE DIRECTORY */
1737         if (!INT21_CreateDirectory( context ))
1738             bSetDOSExtendedError = TRUE;
1739         break;
1740
1741     case 0x3a: /* LONG FILENAME - REMOVE DIRECTORY */
1742         {
1743             WCHAR dirW[MAX_PATH];
1744             char *dirA = CTX_SEG_OFF_TO_LIN(context,
1745                                             context->SegDs, context->Edx);
1746
1747             TRACE( "LONG FILENAME - REMOVE DIRECTORY %s\n", dirA );
1748             MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
1749
1750             if (!RemoveDirectoryW( dirW ))
1751                 bSetDOSExtendedError = TRUE;
1752         }
1753         break;
1754
1755     case 0x3b: /* LONG FILENAME - CHANGE DIRECTORY */
1756         INT_Int21Handler( context );
1757         break;
1758
1759     case 0x41: /* LONG FILENAME - DELETE FILE */
1760         {
1761             WCHAR fileW[MAX_PATH];
1762             char *fileA = CTX_SEG_OFF_TO_LIN(context, 
1763                                              context->SegDs, context->Edx);
1764
1765             TRACE( "LONG FILENAME - DELETE FILE %s\n", fileA );
1766             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1767
1768             if (!DeleteFileW( fileW ))
1769                 bSetDOSExtendedError = TRUE;
1770         }
1771         break;
1772
1773     case 0x43: /* LONG FILENAME - EXTENDED GET/SET FILE ATTRIBUTES */
1774         if (!INT21_FileAttributes( context, BL_reg(context), TRUE ))
1775             bSetDOSExtendedError = TRUE;
1776         break;
1777
1778     case 0x47: /* LONG FILENAME - GET CURRENT DIRECTORY */
1779     case 0x4e: /* LONG FILENAME - FIND FIRST MATCHING FILE */
1780     case 0x4f: /* LONG FILENAME - FIND NEXT MATCHING FILE */
1781         INT_Int21Handler( context );
1782         break;
1783
1784     case 0x56: /* LONG FILENAME - RENAME FILE */
1785         if (!INT21_RenameFile(context))
1786             bSetDOSExtendedError = TRUE;
1787         break;
1788
1789     case 0x60: /* LONG FILENAME - CONVERT PATH */
1790     case 0x6c: /* LONG FILENAME - CREATE OR OPEN FILE */
1791     case 0xa0: /* LONG FILENAME - GET VOLUME INFORMATION */
1792     case 0xa1: /* LONG FILENAME - "FindClose" - TERMINATE DIRECTORY SEARCH */
1793         INT_Int21Handler( context );
1794         break;
1795
1796     case 0xa6: /* LONG FILENAME - GET FILE INFO BY HANDLE */
1797         {
1798             HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
1799             BY_HANDLE_FILE_INFORMATION *info =
1800                 CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1801             
1802             TRACE( "LONG FILENAME - GET FILE INFO BY HANDLE\n" );
1803             
1804             if (!GetFileInformationByHandle(handle, info))
1805                 bSetDOSExtendedError = TRUE;
1806         }
1807         break;
1808
1809     case 0xa7: /* LONG FILENAME - CONVERT TIME */
1810         switch (BL_reg(context))
1811         {
1812         case 0x00: /* FILE TIME TO DOS TIME */
1813             {
1814                 WORD      date, time;
1815                 FILETIME *filetime = CTX_SEG_OFF_TO_LIN(context,
1816                                                         context->SegDs,
1817                                                         context->Esi);
1818
1819                 TRACE( "LONG FILENAME - FILE TIME TO DOS TIME\n" );
1820
1821                 FileTimeToDosDateTime( filetime, &date, &time );
1822
1823                 SET_DX( context, date );
1824                 SET_CX( context, time );
1825
1826                 /*
1827                  * FIXME: BH has number of 10-millisecond units 
1828                  * past time in CX.
1829                  */
1830                 SET_BH( context, 0 );
1831             }
1832             break;
1833
1834         case 0x01: /* DOS TIME TO FILE TIME */
1835             {
1836                 FILETIME *filetime = CTX_SEG_OFF_TO_LIN(context,
1837                                                         context->SegEs,
1838                                                         context->Edi);
1839
1840                 TRACE( "LONG FILENAME - DOS TIME TO FILE TIME\n" );
1841
1842                 /*
1843                  * FIXME: BH has number of 10-millisecond units 
1844                  * past time in CX.
1845                  */
1846                 DosDateTimeToFileTime( DX_reg(context), CX_reg(context),
1847                                        filetime );
1848             }
1849             break;
1850
1851         default:
1852             INT_BARF( context, 0x21 );
1853             break;
1854         }
1855         break;
1856
1857     case 0xa8: /* LONG FILENAME - GENERATE SHORT FILENAME */
1858     case 0xa9: /* LONG FILENAME - SERVER CREATE OR OPEN FILE */
1859     case 0xaa: /* LONG FILENAME - SUBST */
1860     default:
1861         INT_BARF( context, 0x21 );
1862     }
1863
1864     if (bSetDOSExtendedError)
1865     {
1866         SET_AX( context, GetLastError() );
1867         SET_CFLAG( context );
1868     }
1869 }
1870
1871
1872 /***********************************************************************
1873  *           INT21_RenameFile
1874  *
1875  * Handler for:
1876  * - function 0x56
1877  * - subfunction 0x56 of function 0x71
1878  * - subfunction 0xff of function 0x43 (CL == 0x56)
1879  */
1880 static BOOL INT21_RenameFile( CONTEXT86 *context )
1881 {
1882     WCHAR fromW[MAX_PATH];
1883     WCHAR toW[MAX_PATH];
1884     char *fromA = CTX_SEG_OFF_TO_LIN(context, 
1885                                      context->SegDs,context->Edx);
1886     char *toA = CTX_SEG_OFF_TO_LIN(context, 
1887                                    context->SegEs,context->Edi);
1888
1889     TRACE( "RENAME FILE %s to %s\n", fromA, toA );
1890     MultiByteToWideChar(CP_OEMCP, 0, fromA, -1, fromW, MAX_PATH);
1891     MultiByteToWideChar(CP_OEMCP, 0, toA, -1, toW, MAX_PATH);
1892
1893     return MoveFileW( fromW, toW );
1894 }
1895
1896
1897 /***********************************************************************
1898  *           INT21_GetExtendedError
1899  */
1900 static void INT21_GetExtendedError( CONTEXT86 *context )
1901 {
1902     BYTE class, action, locus;
1903     WORD error = GetLastError();
1904
1905     switch(error)
1906     {
1907     case ERROR_SUCCESS:
1908         class = action = locus = 0;
1909         break;
1910     case ERROR_DIR_NOT_EMPTY:
1911         class  = EC_Exists;
1912         action = SA_Ignore;
1913         locus  = EL_Disk;
1914         break;
1915     case ERROR_ACCESS_DENIED:
1916         class  = EC_AccessDenied;
1917         action = SA_Abort;
1918         locus  = EL_Disk;
1919         break;
1920     case ERROR_CANNOT_MAKE:
1921         class  = EC_AccessDenied;
1922         action = SA_Abort;
1923         locus  = EL_Unknown;
1924         break;
1925     case ERROR_DISK_FULL:
1926     case ERROR_HANDLE_DISK_FULL:
1927         class  = EC_MediaError;
1928         action = SA_Abort;
1929         locus  = EL_Disk;
1930         break;
1931     case ERROR_FILE_EXISTS:
1932     case ERROR_ALREADY_EXISTS:
1933         class  = EC_Exists;
1934         action = SA_Abort;
1935         locus  = EL_Disk;
1936         break;
1937     case ERROR_FILE_NOT_FOUND:
1938         class  = EC_NotFound;
1939         action = SA_Abort;
1940         locus  = EL_Disk;
1941         break;
1942     case ER_GeneralFailure:
1943         class  = EC_SystemFailure;
1944         action = SA_Abort;
1945         locus  = EL_Unknown;
1946         break;
1947     case ERROR_INVALID_DRIVE:
1948         class  = EC_MediaError;
1949         action = SA_Abort;
1950         locus  = EL_Disk;
1951         break;
1952     case ERROR_INVALID_HANDLE:
1953         class  = EC_ProgramError;
1954         action = SA_Abort;
1955         locus  = EL_Disk;
1956         break;
1957     case ERROR_LOCK_VIOLATION:
1958         class  = EC_AccessDenied;
1959         action = SA_Abort;
1960         locus  = EL_Disk;
1961         break;
1962     case ERROR_NO_MORE_FILES:
1963         class  = EC_MediaError;
1964         action = SA_Abort;
1965         locus  = EL_Disk;
1966         break;
1967     case ER_NoNetwork:
1968         class  = EC_NotFound;
1969         action = SA_Abort;
1970         locus  = EL_Network;
1971         break;
1972     case ERROR_NOT_ENOUGH_MEMORY:
1973         class  = EC_OutOfResource;
1974         action = SA_Abort;
1975         locus  = EL_Memory;
1976         break;
1977     case ERROR_PATH_NOT_FOUND:
1978         class  = EC_NotFound;
1979         action = SA_Abort;
1980         locus  = EL_Disk;
1981         break;
1982     case ERROR_SEEK:
1983         class  = EC_NotFound;
1984         action = SA_Ignore;
1985         locus  = EL_Disk;
1986         break;
1987     case ERROR_SHARING_VIOLATION:
1988         class  = EC_Temporary;
1989         action = SA_Retry;
1990         locus  = EL_Disk;
1991         break;
1992     case ERROR_TOO_MANY_OPEN_FILES:
1993         class  = EC_ProgramError;
1994         action = SA_Abort;
1995         locus  = EL_Disk;
1996         break;
1997     default:
1998         FIXME("Unknown error %d\n", error );
1999         class  = EC_SystemFailure;
2000         action = SA_Abort;
2001         locus  = EL_Unknown;
2002         break;
2003     }
2004     TRACE("GET EXTENDED ERROR code 0x%02x class 0x%02x action 0x%02x locus %02x\n",
2005            error, class, action, locus );
2006     SET_AX( context, error );
2007     SET_BH( context, class );
2008     SET_BL( context, action );
2009     SET_CH( context, locus );
2010 }
2011
2012
2013 /***********************************************************************
2014  *           DOSVM_Int21Handler
2015  *
2016  * Interrupt 0x21 handler.
2017  */
2018 void WINAPI DOSVM_Int21Handler( CONTEXT86 *context )
2019 {
2020     BOOL bSetDOSExtendedError = FALSE;
2021
2022     TRACE( "AX=%04x BX=%04x CX=%04x DX=%04x "
2023            "SI=%04x DI=%04x DS=%04x ES=%04x EFL=%08lx\n",
2024            AX_reg(context), BX_reg(context), 
2025            CX_reg(context), DX_reg(context),
2026            SI_reg(context), DI_reg(context),
2027            (WORD)context->SegDs, (WORD)context->SegEs,
2028            context->EFlags );
2029
2030    /*
2031     * Extended error is used by (at least) functions 0x2f to 0x62.
2032     * Function 0x59 returns extended error and error should not
2033     * be cleared before handling the function.
2034     */
2035     if (AH_reg(context) >= 0x2f && AH_reg(context) != 0x59) 
2036         SetLastError(0);
2037
2038     RESET_CFLAG(context); /* Not sure if this is a good idea. */
2039
2040     switch(AH_reg(context))
2041     {
2042     case 0x00: /* TERMINATE PROGRAM */
2043         TRACE("TERMINATE PROGRAM\n");
2044         if (DOSVM_IsWin16())
2045             ExitThread( 0 );
2046         else
2047             MZ_Exit( context, FALSE, 0 );
2048         break;
2049
2050     case 0x01: /* READ CHARACTER FROM STANDARD INPUT, WITH ECHO */
2051         {
2052             BYTE ascii;
2053             TRACE("DIRECT CHARACTER INPUT WITH ECHO\n");
2054             INT21_ReadChar( &ascii, context );
2055             SET_AL( context, ascii );
2056             /*
2057              * FIXME: What to echo when extended keycodes are read?
2058              */
2059             DOSVM_PutChar(AL_reg(context));
2060         }
2061         break;
2062
2063     case 0x02: /* WRITE CHARACTER TO STANDARD OUTPUT */
2064         TRACE("Write Character to Standard Output\n");
2065         DOSVM_PutChar(DL_reg(context));
2066         break;
2067
2068     case 0x03: /* READ CHARACTER FROM STDAUX  */
2069     case 0x04: /* WRITE CHARACTER TO STDAUX */
2070     case 0x05: /* WRITE CHARACTER TO PRINTER */
2071         INT_BARF( context, 0x21 );
2072         break;
2073
2074     case 0x06: /* DIRECT CONSOLE IN/OUTPUT */
2075         if (DL_reg(context) == 0xff) 
2076         {
2077             TRACE("Direct Console Input\n");
2078
2079             if (INT21_ReadChar( NULL, NULL ))
2080             {
2081                 BYTE ascii;
2082                 INT21_ReadChar( &ascii, context );
2083                 SET_AL( context, ascii );
2084                 RESET_ZFLAG( context );
2085             }
2086             else
2087             {
2088                 /* no character available */
2089                 SET_AL( context, 0 );
2090                 SET_ZFLAG( context );
2091             }
2092         } 
2093         else 
2094         {
2095             TRACE("Direct Console Output\n");
2096             DOSVM_PutChar(DL_reg(context));
2097             /*
2098              * At least DOS versions 2.1-7.0 return character 
2099              * that was written in AL register.
2100              */
2101             SET_AL( context, DL_reg(context) );
2102         }
2103         break;
2104
2105     case 0x07: /* DIRECT CHARACTER INPUT WITHOUT ECHO */
2106         {
2107             BYTE ascii;
2108             TRACE("DIRECT CHARACTER INPUT WITHOUT ECHO\n");
2109             INT21_ReadChar( &ascii, context );
2110             SET_AL( context, ascii );
2111         }
2112         break;
2113
2114     case 0x08: /* CHARACTER INPUT WITHOUT ECHO */
2115         {
2116             BYTE ascii;
2117             TRACE("CHARACTER INPUT WITHOUT ECHO\n");
2118             INT21_ReadChar( &ascii, context );
2119             SET_AL( context, ascii );
2120         }
2121         break;
2122
2123     case 0x09: /* WRITE STRING TO STANDARD OUTPUT */
2124         TRACE("WRITE '$'-terminated string from %04lX:%04X to stdout\n",
2125               context->SegDs, DX_reg(context) );
2126         {
2127             LPSTR data = CTX_SEG_OFF_TO_LIN( context, 
2128                                              context->SegDs, context->Edx );
2129             LPSTR p = data;
2130
2131             /*
2132              * Do NOT use strchr() to calculate the string length,
2133              * as '\0' is valid string content, too!
2134              * Maybe we should check for non-'$' strings, but DOS doesn't.
2135              */
2136             while (*p != '$') p++;
2137
2138             if (DOSVM_IsWin16())
2139                 WriteFile( DosFileHandleToWin32Handle(1), 
2140                            data, p - data, 0, 0 );
2141             else
2142                 for(; data != p; data++)
2143                     DOSVM_PutChar( *data );
2144
2145             SET_AL( context, '$' ); /* yes, '$' (0x24) gets returned in AL */
2146         }
2147         break;
2148
2149     case 0x0a: /* BUFFERED INPUT */
2150         {
2151             BYTE *ptr = CTX_SEG_OFF_TO_LIN(context,
2152                                            context->SegDs,
2153                                            context->Edx);
2154             WORD result;
2155
2156             TRACE( "BUFFERED INPUT (size=%d)\n", ptr[0] );
2157
2158             /*
2159              * FIXME: Some documents state that
2160              *        ptr[1] holds number of chars from last input which 
2161              *        may be recalled on entry, other documents do not mention
2162              *        this at all.
2163              */
2164             if (ptr[1])
2165                 TRACE( "Handle old chars in buffer!\n" );
2166
2167             /*
2168              * ptr[0] - capacity (includes terminating CR)
2169              * ptr[1] - characters read (excludes terminating CR)
2170              */
2171             result = INT21_BufferedInput( context, ptr + 2, ptr[0] );
2172             if (result > 0)
2173                 ptr[1] = (BYTE)result - 1;
2174             else
2175                 ptr[1] = 0;
2176         }
2177         break;
2178
2179     case 0x0b: /* GET STDIN STATUS */
2180         TRACE( "GET STDIN STATUS\n" );
2181         {
2182             if (INT21_ReadChar( NULL, NULL ))
2183                 SET_AL( context, 0xff ); /* character available */
2184             else
2185                 SET_AL( context, 0 ); /* no character available */
2186         }
2187         break;
2188
2189     case 0x0c: /* FLUSH BUFFER AND READ STANDARD INPUT */
2190         {
2191             BYTE al = AL_reg(context); /* Input function to execute after flush. */
2192
2193             TRACE( "FLUSH BUFFER AND READ STANDARD INPUT - 0x%02x\n", al );
2194
2195             /* FIXME: buffers are not flushed */
2196
2197             /*
2198              * If AL is one of 0x01, 0x06, 0x07, 0x08, or 0x0a,
2199              * int21 function identified by AL will be called.
2200              */
2201             if(al == 0x01 || al == 0x06 || al == 0x07 || al == 0x08 || al == 0x0a)
2202             {
2203                 SET_AH( context, al );
2204                 DOSVM_Int21Handler( context );
2205             }
2206         }
2207         break;
2208
2209     case 0x0d: /* DISK BUFFER FLUSH */
2210         TRACE("DISK BUFFER FLUSH ignored\n");
2211         break;
2212
2213     case 0x0e: /* SELECT DEFAULT DRIVE */
2214         INT_Int21Handler( context );
2215         break;
2216
2217     case 0x0f: /* OPEN FILE USING FCB */
2218         INT21_OpenFileUsingFCB( context );
2219         break;
2220
2221     case 0x10: /* CLOSE FILE USING FCB */
2222         INT21_CloseFileUsingFCB( context );
2223         break;
2224
2225     case 0x11: /* FIND FIRST MATCHING FILE USING FCB */
2226     case 0x12: /* FIND NEXT MATCHING FILE USING FCB */
2227         INT_Int21Handler( context );
2228         break;
2229
2230     case 0x13: /* DELETE FILE USING FCB */
2231         INT_BARF( context, 0x21 );
2232         break;
2233
2234     case 0x14: /* SEQUENTIAL READ FROM FCB FILE */
2235         INT21_SequentialReadFromFCB( context );
2236         break;
2237
2238     case 0x15: /* SEQUENTIAL WRITE TO FCB FILE */
2239         INT21_SequentialWriteToFCB( context );
2240         break;
2241
2242     case 0x16: /* CREATE OR TRUNCATE FILE USING FCB */
2243     case 0x17: /* RENAME FILE USING FCB */
2244         INT_BARF( context, 0x21 );
2245         break;
2246
2247     case 0x18: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
2248         SET_AL( context, 0 );
2249         break;
2250
2251     case 0x19: /* GET CURRENT DEFAULT DRIVE */
2252         SET_AL( context, INT21_GetCurrentDrive() );
2253         TRACE( "GET CURRENT DRIVE -> %c:\n", 'A' + AL_reg( context ) );
2254         break;
2255
2256     case 0x1a: /* SET DISK TRANSFER AREA ADDRESS */
2257         TRACE( "SET DISK TRANSFER AREA ADDRESS %04lX:%04X\n",
2258                context->SegDs, DX_reg(context) );
2259         {
2260             TDB *task = GlobalLock16( GetCurrentTask() );
2261             task->dta = MAKESEGPTR( context->SegDs, DX_reg(context) );
2262         }
2263         break;
2264
2265     case 0x1b: /* GET ALLOCATION INFORMATION FOR DEFAULT DRIVE */
2266     case 0x1c: /* GET ALLOCATION INFORMATION FOR SPECIFIC DRIVE */
2267         INT_Int21Handler( context );
2268         break;
2269
2270     case 0x1d: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
2271     case 0x1e: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
2272         SET_AL( context, 0 );
2273         break;
2274
2275     case 0x1f: /* GET DRIVE PARAMETER BLOCK FOR DEFAULT DRIVE */
2276         INT_Int21Handler( context );
2277         break;
2278
2279     case 0x20: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
2280         SET_AL( context, 0 );
2281         break;
2282
2283     case 0x21: /* READ RANDOM RECORD FROM FCB FILE */
2284         INT21_ReadRandomRecordFromFCB( context );
2285         break;
2286
2287     case 0x22: /* WRITE RANDOM RECORD TO FCB FILE */
2288         INT21_WriteRandomRecordToFCB( context );
2289         break;
2290
2291     case 0x23: /* GET FILE SIZE FOR FCB */
2292     case 0x24: /* SET RANDOM RECORD NUMBER FOR FCB */
2293         INT_BARF( context, 0x21 );
2294         break;
2295
2296     case 0x25: /* SET INTERRUPT VECTOR */
2297         TRACE("SET INTERRUPT VECTOR 0x%02x\n",AL_reg(context));
2298         {
2299             FARPROC16 ptr = (FARPROC16)MAKESEGPTR( context->SegDs, DX_reg(context) );
2300             if (!ISV86(context) && DOSVM_IsWin16())
2301                 DOSVM_SetPMHandler16(  AL_reg(context), ptr );
2302             else
2303                 DOSVM_SetRMHandler( AL_reg(context), ptr );
2304         }
2305         break;
2306
2307     case 0x26: /* CREATE NEW PROGRAM SEGMENT PREFIX */
2308         INT_BARF( context, 0x21 );
2309         break;
2310
2311     case 0x27: /* RANDOM BLOCK READ FROM FCB FILE */
2312         INT21_RandomBlockReadFromFCB( context );
2313         break;
2314
2315     case 0x28: /* RANDOM BLOCK WRITE TO FCB FILE */
2316         INT21_RandomBlockWriteToFCB( context );
2317         break;
2318
2319     case 0x29: /* PARSE FILENAME INTO FCB */
2320         INT_Int21Handler( context );
2321         break;
2322
2323     case 0x2a: /* GET SYSTEM DATE */
2324         TRACE( "GET SYSTEM DATE\n" );
2325         {
2326             SYSTEMTIME systime;
2327             GetLocalTime( &systime );
2328             SET_CX( context, systime.wYear );
2329             SET_DH( context, systime.wMonth );
2330             SET_DL( context, systime.wDay );
2331             SET_AL( context, systime.wDayOfWeek );
2332         }
2333         break;
2334
2335     case 0x2b: /* SET SYSTEM DATE */
2336         TRACE( "SET SYSTEM DATE\n" );
2337         {
2338             WORD year  = CX_reg(context);
2339             BYTE month = DH_reg(context);
2340             BYTE day   = DL_reg(context);
2341
2342             if (year  >= 1980 && year  <= 2099 &&
2343                 month >= 1    && month <= 12   &&
2344                 day   >= 1    && day   <= 31)
2345             {
2346                 FIXME( "SetSystemDate(%02d/%02d/%04d): not allowed\n",
2347                        day, month, year );
2348                 SET_AL( context, 0 );  /* Let's pretend we succeeded */
2349             }
2350             else
2351             {
2352                 SET_AL( context, 0xff ); /* invalid date */
2353             }
2354         }
2355         break;
2356
2357     case 0x2c: /* GET SYSTEM TIME */
2358         TRACE( "GET SYSTEM TIME\n" );
2359         {
2360             SYSTEMTIME systime;
2361             GetLocalTime( &systime );
2362             SET_CH( context, systime.wHour );
2363             SET_CL( context, systime.wMinute );
2364             SET_DH( context, systime.wSecond );
2365             SET_DL( context, systime.wMilliseconds / 10 );
2366         }
2367         break;
2368
2369     case 0x2d: /* SET SYSTEM TIME */
2370         FIXME("SetSystemTime(%02d:%02d:%02d.%02d): not allowed\n",
2371               CH_reg(context), CL_reg(context),
2372               DH_reg(context), DL_reg(context) );
2373         SET_AL( context, 0 );  /* Let's pretend we succeeded */
2374         break;
2375
2376     case 0x2e: /* SET VERIFY FLAG */
2377         TRACE("SET VERIFY FLAG ignored\n");
2378         /* we cannot change the behaviour anyway, so just ignore it */
2379         break;
2380
2381     case 0x2f: /* GET DISK TRANSFER AREA ADDRESS */
2382         TRACE( "GET DISK TRANSFER AREA ADDRESS\n" );
2383         {
2384             TDB *task = GlobalLock16( GetCurrentTask() );
2385             context->SegEs = SELECTOROF( task->dta );
2386             SET_BX( context, OFFSETOF( task->dta ) );
2387         }
2388         break;
2389
2390     case 0x30: /* GET DOS VERSION */
2391         TRACE( "GET DOS VERSION - %s requested\n",
2392                (AL_reg(context) == 0x00) ? "OEM number" : "version flag" );
2393
2394         if (AL_reg(context) == 0x00)
2395             SET_BH( context, 0xff ); /* OEM number => undefined */
2396         else
2397             SET_BH( context, 0x08 ); /* version flag => DOS is in ROM */
2398
2399         SET_AL( context, HIBYTE(HIWORD(GetVersion16())) ); /* major version */
2400         SET_AH( context, LOBYTE(HIWORD(GetVersion16())) ); /* minor version */
2401
2402         SET_BL( context, 0x12 );     /* 0x123456 is 24-bit Wine's serial # */
2403         SET_CX( context, 0x3456 );
2404         break;
2405
2406     case 0x31: /* TERMINATE AND STAY RESIDENT */
2407         FIXME("TERMINATE AND STAY RESIDENT stub\n");
2408         break;
2409
2410     case 0x32: /* GET DOS DRIVE PARAMETER BLOCK FOR SPECIFIC DRIVE */
2411         INT_Int21Handler( context );
2412         break;
2413
2414     case 0x33: /* MULTIPLEXED */
2415         switch (AL_reg(context))
2416         {
2417         case 0x00: /* GET CURRENT EXTENDED BREAK STATE */
2418             TRACE("GET CURRENT EXTENDED BREAK STATE\n");
2419             SET_DL( context, DOSCONF_GetConfig()->brk_flag );
2420             break;
2421
2422         case 0x01: /* SET EXTENDED BREAK STATE */
2423             TRACE("SET CURRENT EXTENDED BREAK STATE\n");
2424             DOSCONF_GetConfig()->brk_flag = (DL_reg(context) > 0) ? 1 : 0;
2425             break;
2426
2427         case 0x02: /* GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE*/
2428             TRACE("GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE\n");
2429             /* ugly coding in order to stay reentrant */
2430             if (DL_reg(context))
2431             {
2432                 SET_DL( context, DOSCONF_GetConfig()->brk_flag );
2433                 DOSCONF_GetConfig()->brk_flag = 1;
2434             }
2435             else
2436             {
2437                 SET_DL( context, DOSCONF_GetConfig()->brk_flag );
2438                 DOSCONF_GetConfig()->brk_flag = 0;
2439             }
2440             break;
2441
2442         case 0x05: /* GET BOOT DRIVE */
2443             TRACE("GET BOOT DRIVE\n");
2444             SET_DL( context, 3 );
2445             /* c: is Wine's bootdrive (a: is 1)*/
2446             break;
2447
2448         case 0x06: /* GET TRUE VERSION NUMBER */
2449             TRACE("GET TRUE VERSION NUMBER\n");
2450             SET_BL( context, HIBYTE(HIWORD(GetVersion16())) ); /* major */
2451             SET_BH( context, LOBYTE(HIWORD(GetVersion16())) ); /* minor */
2452             SET_DL( context, 0x00 ); /* revision */
2453             SET_DH( context, 0x08 ); /* DOS is in ROM */
2454             break;
2455
2456         default:
2457             INT_BARF( context, 0x21 );
2458             break;
2459         }
2460         break;
2461
2462     case 0x34: /* GET ADDRESS OF INDOS FLAG */
2463         TRACE( "GET ADDRESS OF INDOS FLAG\n" );
2464         context->SegEs = INT21_GetHeapSelector( context );
2465         SET_BX( context, offsetof(INT21_HEAP, misc_indos) );
2466         break;
2467
2468     case 0x35: /* GET INTERRUPT VECTOR */
2469         TRACE("GET INTERRUPT VECTOR 0x%02x\n",AL_reg(context));
2470         {
2471             FARPROC16 addr;
2472             if (!ISV86(context) && DOSVM_IsWin16())
2473                 addr = DOSVM_GetPMHandler16( AL_reg(context) );
2474             else
2475                 addr = DOSVM_GetRMHandler( AL_reg(context) );
2476             context->SegEs = SELECTOROF(addr);
2477             SET_BX( context, OFFSETOF(addr) );
2478         }
2479         break;
2480
2481     case 0x36: /* GET FREE DISK SPACE */
2482         INT_Int21Handler( context );
2483         break;
2484
2485     case 0x37: /* SWITCHAR */
2486         {
2487             switch (AL_reg(context))
2488             {
2489             case 0x00: /* "SWITCHAR" - GET SWITCH CHARACTER */
2490                 TRACE( "SWITCHAR - GET SWITCH CHARACTER\n" );
2491                 SET_AL( context, 0x00 ); /* success*/
2492                 SET_DL( context, '/' );
2493                 break;
2494             case 0x01: /*"SWITCHAR" - SET SWITCH CHARACTER*/
2495                 FIXME( "SWITCHAR - SET SWITCH CHARACTER: %c\n",
2496                        DL_reg( context ));
2497                 SET_AL( context, 0x00 ); /* success*/
2498                 break;
2499             default:
2500                 INT_BARF( context, 0x21 );
2501                 break;
2502             }
2503         }
2504         break;
2505
2506     case 0x38: /* GET COUNTRY-SPECIFIC INFORMATION */
2507         TRACE( "GET COUNTRY-SPECIFIC INFORMATION\n" );
2508         if (AL_reg(context)) 
2509         {
2510             WORD country = AL_reg(context);
2511             if (country == 0xff)
2512                 country = BX_reg(context);
2513             if (country != INT21_GetSystemCountryCode())
2514                 FIXME( "Requested info on non-default country %04x\n", country );
2515         }
2516         INT21_FillCountryInformation( CTX_SEG_OFF_TO_LIN(context, 
2517                                                          context->SegDs, 
2518                                                          context->Edx) );
2519         SET_AX( context, INT21_GetSystemCountryCode() );
2520         SET_BX( context, INT21_GetSystemCountryCode() );
2521         break;
2522
2523     case 0x39: /* "MKDIR" - CREATE SUBDIRECTORY */
2524         if (!INT21_CreateDirectory( context ))
2525             bSetDOSExtendedError = TRUE;
2526         break;
2527
2528     case 0x3a: /* "RMDIR" - REMOVE DIRECTORY */
2529         {
2530             WCHAR dirW[MAX_PATH];
2531             char *dirA = CTX_SEG_OFF_TO_LIN(context,
2532                                             context->SegDs, context->Edx);
2533
2534             TRACE( "REMOVE DIRECTORY %s\n", dirA );
2535
2536             MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
2537
2538             if (!RemoveDirectoryW( dirW ))
2539                 bSetDOSExtendedError = TRUE;
2540         }
2541         break;
2542
2543     case 0x3b: /* "CHDIR" - SET CURRENT DIRECTORY */
2544     case 0x3c: /* "CREAT" - CREATE OR TRUNCATE FILE */
2545     case 0x3d: /* "OPEN" - OPEN EXISTING FILE */
2546         INT_Int21Handler( context );
2547         break;
2548
2549     case 0x3e: /* "CLOSE" - CLOSE FILE */
2550         TRACE( "CLOSE handle %d\n", BX_reg(context) );
2551         if (_lclose16( BX_reg(context) ) == HFILE_ERROR16)
2552             bSetDOSExtendedError = TRUE;
2553         break;
2554
2555     case 0x3f: /* "READ" - READ FROM FILE OR DEVICE */
2556         TRACE( "READ from %d to %04lX:%04X for %d bytes\n",
2557                BX_reg(context),
2558                context->SegDs,
2559                DX_reg(context),
2560                CX_reg(context) );
2561         {
2562             DWORD result;
2563             WORD  count  = CX_reg(context);
2564             BYTE *buffer = CTX_SEG_OFF_TO_LIN( context, 
2565                                                context->SegDs,
2566                                                context->Edx );
2567
2568             /* Some programs pass a count larger than the allocated buffer */
2569             if (DOSVM_IsWin16())
2570             {
2571                 WORD maxcount = GetSelectorLimit16( context->SegDs )
2572                     - DX_reg(context) + 1;
2573                 if (count > maxcount)
2574                     count = maxcount;
2575             }
2576
2577             /*
2578              * FIXME: Reading from console (BX=1) in DOS mode
2579              *        does not work as it is supposed to work.
2580              */
2581
2582             if (!DOSVM_IsWin16() && BX_reg(context) == 0)
2583             {
2584                 result = INT21_BufferedInput( context, buffer, count );
2585                 SET_AX( context, (WORD)result );
2586             }
2587             else if (ReadFile( DosFileHandleToWin32Handle(BX_reg(context)),
2588                                buffer, count, &result, NULL ))
2589                 SET_AX( context, (WORD)result );
2590             else
2591                 bSetDOSExtendedError = TRUE;
2592         }
2593         break;
2594
2595     case 0x40:  /* "WRITE" - WRITE TO FILE OR DEVICE */
2596         TRACE( "WRITE from %04lX:%04X to handle %d for %d byte\n",
2597                context->SegDs, DX_reg(context),
2598                BX_reg(context), CX_reg(context) );
2599         {
2600             BYTE *ptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
2601
2602             if (!DOSVM_IsWin16() && 
2603                 (BX_reg(context) == 1 || BX_reg(context) == 2))
2604             {
2605                 int i;
2606                 for(i=0; i<CX_reg(context); i++)
2607                     DOSVM_PutChar(ptr[i]);
2608                 SET_AX(context, CX_reg(context));
2609             }
2610             else
2611             {
2612                 HFILE handle = (HFILE)DosFileHandleToWin32Handle(BX_reg(context));
2613                 LONG result = _hwrite( handle, ptr, CX_reg(context) );
2614                 if (result == HFILE_ERROR)
2615                     bSetDOSExtendedError = TRUE;
2616                 else
2617                     SET_AX( context, (WORD)result );
2618             }
2619         }
2620         break;
2621
2622     case 0x41: /* "UNLINK" - DELETE FILE */
2623         {
2624             WCHAR fileW[MAX_PATH];
2625             char *fileA = CTX_SEG_OFF_TO_LIN(context, 
2626                                              context->SegDs, 
2627                                              context->Edx);
2628
2629             TRACE( "UNLINK %s\n", fileA );
2630             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
2631
2632             if (!DeleteFileW( fileW ))
2633                 bSetDOSExtendedError = TRUE;
2634         }
2635         break;
2636
2637     case 0x42: /* "LSEEK" - SET CURRENT FILE POSITION */
2638         TRACE( "LSEEK handle %d offset %ld from %s\n",
2639                BX_reg(context), 
2640                MAKELONG( DX_reg(context), CX_reg(context) ),
2641                (AL_reg(context) == 0) ? 
2642                "start of file" : ((AL_reg(context) == 1) ? 
2643                                   "current file position" : "end of file") );
2644         {
2645             HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
2646             LONG   offset = MAKELONG( DX_reg(context), CX_reg(context) );
2647             DWORD  status = SetFilePointer( handle, offset, 
2648                                             NULL, AL_reg(context) );
2649             if (status == INVALID_SET_FILE_POINTER)
2650                 bSetDOSExtendedError = TRUE;
2651             else
2652             {
2653                 SET_AX( context, LOWORD(status) );
2654                 SET_DX( context, HIWORD(status) );
2655             }
2656         }
2657         break;
2658
2659     case 0x43: /* FILE ATTRIBUTES */
2660         if (!INT21_FileAttributes( context, AL_reg(context), FALSE ))
2661             bSetDOSExtendedError = TRUE;
2662         break;
2663
2664     case 0x44: /* IOCTL */
2665         INT21_Ioctl( context );
2666         break;
2667
2668     case 0x45: /* "DUP" - DUPLICATE FILE HANDLE */
2669         TRACE( "DUPLICATE FILE HANDLE %d\n", BX_reg(context) );
2670         {
2671             HANDLE handle32;
2672             HFILE  handle16 = HFILE_ERROR;
2673
2674             if (DuplicateHandle( GetCurrentProcess(),
2675                                  DosFileHandleToWin32Handle(BX_reg(context)),
2676                                  GetCurrentProcess(), 
2677                                  &handle32,
2678                                  0, TRUE, DUPLICATE_SAME_ACCESS ))
2679                 handle16 = Win32HandleToDosFileHandle(handle32);
2680
2681             if (handle16 == HFILE_ERROR)
2682                 bSetDOSExtendedError = TRUE;
2683             else
2684                 SET_AX( context, handle16 );
2685         }
2686         break;
2687
2688     case 0x46: /* "DUP2", "FORCEDUP" - FORCE DUPLICATE FILE HANDLE */
2689         TRACE( "FORCEDUP - FORCE DUPLICATE FILE HANDLE %d to %d\n",
2690                BX_reg(context), CX_reg(context) );
2691         if (FILE_Dup2( BX_reg(context), CX_reg(context) ) == HFILE_ERROR16)
2692             bSetDOSExtendedError = TRUE;
2693         break;
2694
2695     case 0x47: /* "CWD" - GET CURRENT DIRECTORY */
2696         INT_Int21Handler( context );
2697         break;
2698
2699     case 0x48: /* ALLOCATE MEMORY */
2700         TRACE( "ALLOCATE MEMORY for %d paragraphs\n", BX_reg(context) );
2701         {
2702             WORD  selector = 0;
2703             DWORD bytes = (DWORD)BX_reg(context) << 4;
2704
2705             if (!ISV86(context) && DOSVM_IsWin16())
2706             {
2707                 DWORD rv = GlobalDOSAlloc16( bytes );
2708                 selector = LOWORD( rv );
2709             }
2710             else
2711                 DOSMEM_GetBlock( bytes, &selector );
2712                
2713             if (selector)
2714                 SET_AX( context, selector );
2715             else
2716             {
2717                 SET_CFLAG(context);
2718                 SET_AX( context, 0x0008 ); /* insufficient memory */
2719                 SET_BX( context, DOSMEM_Available() >> 4 );
2720             }
2721         }
2722         break;
2723
2724     case 0x49: /* FREE MEMORY */
2725         TRACE( "FREE MEMORY segment %04lX\n", context->SegEs );
2726         {
2727             BOOL ok;
2728             
2729             if (!ISV86(context) && DOSVM_IsWin16())
2730             {
2731                 ok = !GlobalDOSFree16( context->SegEs );
2732
2733                 /* If we don't reset ES_reg, we will fail in the relay code */
2734                 if (ok)
2735                     context->SegEs = 0;
2736             }
2737             else
2738                 ok = DOSMEM_FreeBlock( (void*)((DWORD)context->SegEs << 4) );
2739
2740             if (!ok)
2741             {
2742                 TRACE("FREE MEMORY failed\n");
2743                 SET_CFLAG(context);
2744                 SET_AX( context, 0x0009 ); /* memory block address invalid */
2745             }
2746         }
2747         break;
2748
2749     case 0x4a: /* RESIZE MEMORY BLOCK */
2750         TRACE( "RESIZE MEMORY segment %04lX to %d paragraphs\n", 
2751                context->SegEs, BX_reg(context) );
2752         {
2753             DWORD newsize = (DWORD)BX_reg(context) << 4;
2754             
2755             if (!ISV86(context) && DOSVM_IsWin16())
2756             {
2757                 FIXME( "Resize memory block - unsupported under Win16\n" );
2758             }
2759             else
2760             {
2761                 LPVOID address = (void*)((DWORD)context->SegEs << 4);
2762                 UINT blocksize = DOSMEM_ResizeBlock( address, newsize, FALSE );
2763
2764                 if (blocksize == (UINT)-1)
2765                 {
2766                     SET_CFLAG( context );
2767                     SET_AX( context, 0x0009 ); /* illegal address */
2768                 }
2769                 else if(blocksize != newsize)
2770                 {
2771                     SET_CFLAG( context );
2772                     SET_AX( context, 0x0008 );    /* insufficient memory */
2773                     SET_BX( context, blocksize >> 4 ); /* new block size */
2774                 }
2775             }
2776         }
2777         break;
2778
2779     case 0x4b: /* "EXEC" - LOAD AND/OR EXECUTE PROGRAM */
2780         {
2781             BYTE *program = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
2782             BYTE *paramblk = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Ebx);
2783
2784             TRACE( "EXEC %s\n", program );
2785
2786             if (DOSVM_IsWin16())
2787             {
2788                 HINSTANCE16 instance = WinExec16( program, SW_NORMAL );
2789                 if (instance < 32)
2790                 {
2791                     SET_CFLAG( context );
2792                     SET_AX( context, instance );
2793                 }
2794             }
2795             else
2796             {
2797                 if (!MZ_Exec( context, program, AL_reg(context), paramblk))
2798                     bSetDOSExtendedError = TRUE;
2799             }
2800         }
2801         break;
2802
2803     case 0x4c: /* "EXIT" - TERMINATE WITH RETURN CODE */
2804         TRACE( "EXIT with return code %d\n", AL_reg(context) );
2805         if (DOSVM_IsWin16())
2806             ExitThread( AL_reg(context) );
2807         else
2808             MZ_Exit( context, FALSE, AL_reg(context) );
2809         break;
2810
2811     case 0x4d: /* GET RETURN CODE */
2812         TRACE("GET RETURN CODE (ERRORLEVEL)\n");
2813         SET_AX( context, DOSVM_retval );
2814         DOSVM_retval = 0;
2815         break;
2816
2817     case 0x4e: /* "FINDFIRST" - FIND FIRST MATCHING FILE */
2818     case 0x4f: /* "FINDNEXT" - FIND NEXT MATCHING FILE */
2819         INT_Int21Handler( context );
2820         break;
2821
2822     case 0x50: /* SET CURRENT PROCESS ID (SET PSP ADDRESS) */
2823         TRACE("SET CURRENT PROCESS ID (SET PSP ADDRESS)\n");
2824         DOSVM_psp = BX_reg(context);
2825         break;
2826
2827     case 0x51: /* GET PSP ADDRESS */
2828         INT21_GetPSP( context );
2829         break;
2830
2831     case 0x52: /* "SYSVARS" - GET LIST OF LISTS */
2832         if (!ISV86(context) && DOSVM_IsWin16())
2833         {
2834             SEGPTR ptr = DOSMEM_LOL()->wine_pm_lol;
2835             context->SegEs = SELECTOROF(ptr);
2836             SET_BX( context, OFFSETOF(ptr) );
2837         }
2838         else
2839         {
2840             SEGPTR ptr = DOSMEM_LOL()->wine_rm_lol;
2841             context->SegEs = SELECTOROF(ptr);
2842             SET_BX( context, OFFSETOF(ptr) );
2843         }
2844         break;
2845
2846     case 0x54: /* Get Verify Flag */
2847         TRACE("Get Verify Flag - Not Supported\n");
2848         SET_AL( context, 0x00 );  /* pretend we can tell. 00h = off 01h = on */
2849         break;
2850
2851     case 0x56: /* "RENAME" - RENAME FILE */
2852         if (!INT21_RenameFile( context ))
2853             bSetDOSExtendedError = TRUE;
2854         break;
2855
2856     case 0x57: /* FILE DATE AND TIME */
2857         if (!INT21_FileDateTime( context ))
2858             bSetDOSExtendedError = TRUE;
2859         break;
2860
2861     case 0x58: /* GET OR SET MEMORY ALLOCATION STRATEGY */
2862         switch (AL_reg(context))
2863         {
2864         case 0x00: /* GET MEMORY ALLOCATION STRATEGY */
2865             TRACE( "GET MEMORY ALLOCATION STRATEGY\n" );
2866             SET_AX( context, 0 ); /* low memory first fit */
2867             break;
2868
2869         case 0x01: /* SET ALLOCATION STRATEGY */
2870             TRACE( "SET MEMORY ALLOCATION STRATEGY to %d - ignored\n",
2871                    BL_reg(context) );
2872             break;
2873
2874         case 0x02: /* GET UMB LINK STATE */
2875             TRACE( "GET UMB LINK STATE\n" );
2876             SET_AL( context, 0 ); /* UMBs not part of DOS memory chain */
2877             break;
2878
2879         case 0x03: /* SET UMB LINK STATE */
2880             TRACE( "SET UMB LINK STATE to %d - ignored\n",
2881                    BX_reg(context) );
2882             break;
2883
2884         default:
2885             INT_BARF( context, 0x21 );
2886         }
2887         break;
2888
2889     case 0x59: /* GET EXTENDED ERROR INFO */
2890         INT21_GetExtendedError( context );
2891         break;
2892
2893     case 0x5a: /* CREATE TEMPORARY FILE */
2894     case 0x5b: /* CREATE NEW FILE */ 
2895         INT_Int21Handler( context );
2896         break;
2897
2898     case 0x5c: /* "FLOCK" - RECORD LOCKING */
2899         {
2900             DWORD  offset = MAKELONG(DX_reg(context), CX_reg(context));
2901             DWORD  length = MAKELONG(DI_reg(context), SI_reg(context));
2902             HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
2903
2904             switch (AL_reg(context))
2905             {
2906             case 0x00: /* LOCK */
2907                 TRACE( "lock handle %d offset %ld length %ld\n",
2908                        BX_reg(context), offset, length );
2909                 if (!LockFile( handle, offset, 0, length, 0 ))
2910                     bSetDOSExtendedError = TRUE;
2911                 break;
2912
2913             case 0x01: /* UNLOCK */
2914                 TRACE( "unlock handle %d offset %ld length %ld\n",
2915                        BX_reg(context), offset, length );
2916                 if (!UnlockFile( handle, offset, 0, length, 0 ))
2917                     bSetDOSExtendedError = TRUE;
2918                 break;
2919
2920             default:
2921                 INT_BARF( context, 0x21 );
2922             }
2923         }
2924         break;
2925
2926     case 0x5d: /* NETWORK 5D */
2927         FIXME( "Network function 5D not implemented.\n" );
2928         SetLastError( ER_NoNetwork );
2929         bSetDOSExtendedError = TRUE;
2930         break;
2931
2932     case 0x5e: /* NETWORK 5E */
2933     case 0x5f: /* NETWORK 5F */
2934     case 0x60: /* "TRUENAME" - CANONICALIZE FILENAME OR PATH */
2935         INT_Int21Handler( context );
2936         break;
2937
2938     case 0x61: /* UNUSED */
2939         SET_AL( context, 0 );
2940         break;
2941
2942     case 0x62: /* GET PSP ADDRESS */
2943         INT21_GetPSP( context );
2944         break;
2945
2946     case 0x63: /* MISC. LANGUAGE SUPPORT */
2947         switch (AL_reg(context)) {
2948         case 0x00: /* GET DOUBLE BYTE CHARACTER SET LEAD-BYTE TABLE */
2949             TRACE( "GET DOUBLE BYTE CHARACTER SET LEAD-BYTE TABLE\n" );
2950             context->SegDs = INT21_GetHeapSelector( context );
2951             SET_SI( context, offsetof(INT21_HEAP, dbcs_table) );
2952             SET_AL( context, 0 ); /* success */
2953             break;
2954         }
2955         break;
2956
2957     case 0x64: /* OS/2 DOS BOX */
2958         INT_BARF( context, 0x21 );
2959         SET_CFLAG(context);
2960         break;
2961
2962     case 0x65: /* EXTENDED COUNTRY INFORMATION */
2963         INT21_ExtendedCountryInformation( context );
2964         break;
2965
2966     case 0x66: /* GLOBAL CODE PAGE TABLE */
2967         switch (AL_reg(context))
2968         {
2969         case 0x01:
2970             TRACE( "GET GLOBAL CODE PAGE TABLE\n" );
2971             SET_BX( context, GetOEMCP() );
2972             SET_DX( context, GetOEMCP() );
2973             break;
2974         case 0x02:
2975             FIXME( "SET GLOBAL CODE PAGE TABLE, active %d, system %d - ignored\n",
2976                    BX_reg(context), DX_reg(context) );
2977             break;
2978         }
2979         break;
2980
2981     case 0x67: /* SET HANDLE COUNT */
2982         TRACE( "SET HANDLE COUNT to %d\n", BX_reg(context) );
2983         if (SetHandleCount( BX_reg(context) ) < BX_reg(context) )
2984             bSetDOSExtendedError = TRUE;
2985         break;
2986
2987     case 0x68: /* "FFLUSH" - COMMIT FILE */
2988         TRACE( "FFLUSH - handle %d\n", BX_reg(context) );
2989         if (!FlushFileBuffers( DosFileHandleToWin32Handle(BX_reg(context)) ))
2990             bSetDOSExtendedError = TRUE;
2991         break;
2992
2993     case 0x69: /* DISK SERIAL NUMBER */
2994         INT_Int21Handler( context );
2995         break;
2996
2997     case 0x6a: /* COMMIT FILE */
2998         TRACE( "COMMIT FILE - handle %d\n", BX_reg(context) );
2999         if (!FlushFileBuffers( DosFileHandleToWin32Handle(BX_reg(context)) ))
3000             bSetDOSExtendedError = TRUE;
3001         break;
3002
3003     case 0x6b: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
3004         SET_AL( context, 0 );
3005         break;
3006
3007     case 0x6c: /* EXTENDED OPEN/CREATE */
3008         INT_Int21Handler( context );
3009         break;
3010
3011     case 0x70: /* MSDOS 7 - GET/SET INTERNATIONALIZATION INFORMATION */
3012         FIXME( "MS-DOS 7 - GET/SET INTERNATIONALIZATION INFORMATION\n" );
3013         SET_CFLAG( context );
3014         SET_AL( context, 0 );
3015         break;
3016
3017     case 0x71: /* MSDOS 7 - LONG FILENAME FUNCTIONS */
3018         INT21_LongFilename( context );
3019         break;
3020
3021     case 0x73: /* MSDOS7 - FAT32 */
3022         INT_Int21Handler( context );
3023         break;
3024
3025     case 0xdc: /* CONNECTION SERVICES - GET CONNECTION NUMBER */
3026         TRACE( "CONNECTION SERVICES - GET CONNECTION NUMBER - ignored\n" );
3027         break;
3028
3029     case 0xea: /* NOVELL NETWARE - RETURN SHELL VERSION */
3030         TRACE( "NOVELL NETWARE - RETURN SHELL VERSION - ignored\n" );
3031         break;
3032
3033     default:
3034         INT_BARF( context, 0x21 );
3035         break;
3036
3037     } /* END OF SWITCH */
3038
3039     /* Set general error condition. */
3040     if (bSetDOSExtendedError)
3041     {
3042         SET_AX( context, GetLastError() );
3043         SET_CFLAG( context );
3044     }
3045
3046     /* Print error code if carry flag is set. */
3047     if (context->EFlags & 0x0001)
3048         TRACE("failed, error %ld\n", GetLastError() );
3049
3050     TRACE( "returning: AX=%04x BX=%04x CX=%04x DX=%04x "
3051            "SI=%04x DI=%04x DS=%04x ES=%04x EFL=%08lx\n",
3052            AX_reg(context), BX_reg(context), 
3053            CX_reg(context), DX_reg(context), 
3054            SI_reg(context), DI_reg(context),
3055            (WORD)context->SegDs, (WORD)context->SegEs,
3056            context->EFlags );
3057 }