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