INT21_GetFreeDiskSpace(): The drive parameter is found in the DL
[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 <stdarg.h>
29 #include <stdio.h>
30 #ifdef HAVE_UNISTD_H
31 # include <unistd.h>
32 #endif
33
34 #include "windef.h"
35 #include "winbase.h"
36 #include "winreg.h"
37 #include "winternl.h"
38 #include "wine/winbase16.h"
39 #include "dosexe.h"
40 #include "file.h"
41 #include "winerror.h"
42 #include "winuser.h"
43 #include "wine/unicode.h"
44 #include "wine/debug.h"
45 #include "wine/exception.h"
46
47 /*
48  * Note:
49  * - Most of the file related functions are wrong. NT's kernel32
50  *   doesn't maintain a per drive current directory, while DOS does. 
51  *   We should in fact keep track in here of those per driver
52  *   directories, and use this infro while dealing with partial paths
53  *   (drive defined, but only relative paths). This could even be
54  *   created as an array of CDS (there should be an entry for that in
55  *   the LOL)
56  */
57
58 /*
59  * Forward declarations.
60  */
61 static BOOL INT21_RenameFile( CONTEXT86 *context );
62
63 WINE_DEFAULT_DEBUG_CHANNEL(int21);
64
65
66 #include "pshpack1.h"
67
68 /*
69  * Extended Drive Parameter Block.
70  * This structure is compatible with standard DOS4+ DPB and 
71  * extended DOS7 DPB.
72  */
73 typedef struct _INT21_DPB {
74     BYTE   drive;                /* 00 drive number (0=A, ...) */
75     BYTE   unit;                 /* 01 unit number within device driver */
76     WORD   sector_bytes;         /* 02 bytes per sector */
77     BYTE   cluster_sectors;      /* 04 highest sector number within a cluster */
78     BYTE   shift;                /* 05 shift count to convert clusters into sectors */
79     WORD   num_reserved;         /* 06 reserved sectors at beginning of drive */
80     BYTE   num_FAT;              /* 08 number of FATs */
81     WORD   num_root_entries;     /* 09 number of root directory entries */
82     WORD   first_data_sector;    /* 0b number of first sector containing user data */
83     WORD   num_clusters1;        /* 0d highest cluster number (number of data clusters + 1) */
84     WORD   sectors_per_FAT;      /* 0f number of sectors per FAT */
85     WORD   first_dir_sector;     /* 11 sector number of first directory sector */
86     SEGPTR driver_header;        /* 13 address of device driver header */
87     BYTE   media_ID;             /* 17 media ID byte */
88     BYTE   access_flag;          /* 18 0x00 if disk accessed, 0xff if not */
89     SEGPTR next;                 /* 19 pointer to next DPB */
90     WORD   search_cluster1;      /* 1d cluster at which to start search for free space */
91     WORD   free_clusters_lo;     /* 1f number of free clusters on drive or 0xffff if unknown */
92     WORD   free_clusters_hi;     /* 21 hiword of clusters_free */
93     WORD   mirroring_flags;      /* 23 active FAT/mirroring flags */
94     WORD   info_sector;          /* 25 sector number of file system info sector or 0xffff for none */
95     WORD   spare_boot_sector;    /* 27 sector number of backup boot sector or 0xffff for none */
96     DWORD  first_cluster_sector; /* 29 sector number of the first cluster */
97     DWORD  num_clusters2;        /* 2d maximum cluster number */
98     DWORD  fat_clusters;         /* 31 number of clusters occupied by FAT */
99     DWORD  root_cluster;         /* 35 cluster number of start of root directory */
100     DWORD  search_cluster2;      /* 39 cluster at which to start searching for free space */
101 } INT21_DPB;
102
103
104 /*
105  * Structure for DOS data that can be accessed directly from applications.
106  * Real and protected mode pointers will be returned to this structure so
107  * the structure must be correctly packed.
108  */
109 typedef struct _INT21_HEAP {
110     WORD uppercase_size;             /* Size of the following table in bytes */
111     BYTE uppercase_table[128];       /* Uppercase equivalents of chars from 0x80 to 0xff. */
112
113     WORD lowercase_size;             /* Size of the following table in bytes */
114     BYTE lowercase_table[256];       /* Lowercase equivalents of chars from 0x00 to 0xff. */
115
116     WORD collating_size;             /* Size of the following table in bytes */
117     BYTE collating_table[256];       /* Values used to sort characters from 0x00 to 0xff. */
118
119     WORD filename_size;              /* Size of the following filename data in bytes */
120     BYTE filename_reserved1;         /* 0x01 for MS-DOS 3.30-6.00 */
121     BYTE filename_lowest;            /* Lowest permissible character value for filename */
122     BYTE filename_highest;           /* Highest permissible character value for filename */
123     BYTE filename_reserved2;         /* 0x00 for MS-DOS 3.30-6.00 */
124     BYTE filename_exclude_first;     /* First illegal character in permissible range */
125     BYTE filename_exclude_last;      /* Last illegal character in permissible range */
126     BYTE filename_reserved3;         /* 0x02 for MS-DOS 3.30-6.00 */
127     BYTE filename_illegal_size;      /* Number of terminators in the following table */
128     BYTE filename_illegal_table[16]; /* Characters which terminate a filename */
129
130     WORD dbcs_size;                  /* Number of valid ranges in the following table */
131     BYTE dbcs_table[16];             /* Start/end bytes for N ranges and 00/00 as terminator */
132
133     BYTE      misc_indos;                    /* Interrupt 21 nesting flag */
134     WORD      misc_segment;                  /* Real mode segment for INT21_HEAP */
135     WORD      misc_selector;                 /* Protected mode selector for INT21_HEAP */
136     INT21_DPB misc_dpb_list[MAX_DOS_DRIVES]; /* Drive parameter blocks for all drives */
137
138 } INT21_HEAP;
139
140
141 struct FCB {
142     BYTE  drive_number;
143     CHAR  file_name[8];
144     CHAR  file_extension[3];
145     WORD  current_block_number;
146     WORD  logical_record_size;
147     DWORD file_size;
148     WORD  date_of_last_write;
149     WORD  time_of_last_write;
150     BYTE  file_number;
151     BYTE  attributes;
152     WORD  starting_cluster;
153     WORD  sequence_number;
154     BYTE  file_attributes;
155     BYTE  unused;
156     BYTE  record_within_current_block;
157     BYTE  random_access_record_number[4];
158 };
159
160
161 struct XFCB {
162     BYTE  xfcb_signature;
163     BYTE  reserved[5];
164     BYTE  xfcb_file_attribute;
165     BYTE  fcb[37];
166 };
167
168 /* DTA layout for FindFirst/FindNext */
169 typedef struct
170 {
171     BYTE   drive;        /* 00 drive letter */
172     char   mask[11];     /* 01 search template */
173     BYTE   search_attr;  /* 0c search attributes */
174     WORD   count;        /* 0d entry count within directory */
175     WORD   cluster;      /* 0f cluster of parent directory */
176     WCHAR *fullPath;     /* 11 full path (was: reserved) */
177     BYTE   fileattr;     /* 15 file attributes */
178     WORD   filetime;     /* 16 file time */
179     WORD   filedate;     /* 18 file date */
180     DWORD  filesize;     /* 1a file size */
181     char   filename[13]; /* 1e file name + extension */
182 } FINDFILE_DTA;
183
184 /* FCB layout for FindFirstFCB/FindNextFCB */
185 typedef struct
186 {
187     BYTE   drive;                /* 00 drive letter */
188     char   filename[11];         /* 01 filename 8+3 format */
189     int    count;                /* 0c entry count (was: reserved) */
190     WCHAR *fullPath;             /* 10 full path (was: reserved) */
191 } FINDFILE_FCB;
192
193 /* DOS directory entry for FindFirstFCB/FindNextFCB */
194 typedef struct
195 {
196     char   filename[11];         /* 00 filename 8+3 format */
197     BYTE   fileattr;             /* 0b file attributes */
198     BYTE   reserved[10];         /* 0c reserved */
199     WORD   filetime;             /* 16 file time */
200     WORD   filedate;             /* 18 file date */
201     WORD   cluster;              /* 1a file first cluster */
202     DWORD  filesize;             /* 1c file size */
203 } DOS_DIRENTRY_LAYOUT;
204
205 #include "poppack.h"
206
207 /* dos file attributes */
208 #define FA_NORMAL    0x00        /* Normal file, no attributes */
209 #define FA_RDONLY    0x01        /* Read only attribute */
210 #define FA_HIDDEN    0x02        /* Hidden file */
211 #define FA_SYSTEM    0x04        /* System file */
212 #define FA_LABEL     0x08        /* Volume label */
213 #define FA_DIRECTORY 0x10        /* Directory */
214 #define FA_ARCHIVE   0x20        /* Archive */
215 #define FA_UNUSED    0x40        /* Unused */
216
217 /* Error codes */
218 #define ER_NoNetwork         0x49
219
220 /* Error classes */
221 #define EC_OutOfResource     0x01
222 #define EC_Temporary         0x02
223 #define EC_AccessDenied      0x03
224 #define EC_InternalError     0x04
225 #define EC_HardwareFailure   0x05
226 #define EC_SystemFailure     0x06
227 #define EC_ProgramError      0x07
228 #define EC_NotFound          0x08
229 #define EC_MediaError        0x0b
230 #define EC_Exists            0x0c
231 #define EC_Unknown           0x0d
232
233 /* Suggested actions */
234 #define SA_Retry             0x01
235 #define SA_DelayedRetry      0x02
236 #define SA_Abort             0x04
237 #define SA_Ignore            0x06
238 #define SA_Ask4Retry         0x07
239
240 /* Error locus */
241 #define EL_Unknown           0x01
242 #define EL_Disk              0x02
243 #define EL_Network           0x03
244 #define EL_Serial            0x04
245 #define EL_Memory            0x05
246
247
248 /* Many calls translate a drive argument like this:
249    drive number (00h = default, 01h = A:, etc)
250    */
251 /******************************************************************
252  *              INT21_DriveName
253  *
254  * Many calls translate a drive argument like this:
255  * drive number (00h = default, 01h = A:, etc)
256  */
257 static const char *INT21_DriveName(int drive)
258 {
259     if (drive > 0) 
260     {
261         if (drive <= 26) return wine_dbg_sprintf("%c:", 'A' + drive - 1);
262         else return wine_dbg_sprintf( "<Bad drive: %d>", drive);
263     }
264     return "default";
265 }
266
267 /***********************************************************************
268  *           INT21_GetCurrentDrive
269  *
270  * Return current drive using scheme (0=A:, 1=B:, 2=C:, ...) or
271  * MAX_DOS_DRIVES on error.
272  */
273 static BYTE INT21_GetCurrentDrive(void)
274 {
275     WCHAR current_directory[MAX_PATH];
276
277     if (!GetCurrentDirectoryW( MAX_PATH, current_directory ) ||
278         current_directory[1] != ':')
279     {
280         TRACE( "Failed to get current drive.\n" );
281         return MAX_DOS_DRIVES;
282     }
283
284     return toupperW( current_directory[0] ) - 'A';
285 }
286
287
288 /***********************************************************************
289  *           INT21_MapDrive
290  *
291  * Convert drive number from scheme (0=default, 1=A:, 2=B:, ...) into
292  * scheme (0=A:, 1=B:, 2=C:, ...) or MAX_DOS_DRIVES on error.
293  */
294 static BYTE INT21_MapDrive( BYTE drive )
295 {
296     if (drive)
297     {
298         WCHAR drivespec[3] = {'A', ':', 0};
299         UINT  drivetype;
300
301         drivespec[0] += drive - 1;
302         drivetype = GetDriveTypeW( drivespec );
303
304         if (drivetype == DRIVE_UNKNOWN || drivetype == DRIVE_NO_ROOT_DIR)
305             return MAX_DOS_DRIVES;
306
307         return drive - 1;
308     }
309
310     return INT21_GetCurrentDrive();
311 }
312
313
314 /***********************************************************************
315  *           INT21_SetCurrentDrive
316  *
317  * Set current drive. Uses scheme (0=A:, 1=B:, 2=C:, ...).
318  */
319 static void INT21_SetCurrentDrive( BYTE drive )
320 {
321     WCHAR drivespec[3] = {'A', ':', 0};
322
323     drivespec[0] += drive;
324
325     if (!SetCurrentDirectoryW( drivespec ))
326         TRACE( "Failed to set current drive.\n" );
327 }
328
329
330 /***********************************************************************
331  *           INT21_ReadChar
332  *
333  * Reads a character from the standard input.
334  * Extended keycodes will be returned as two separate characters.
335  */
336 static BOOL INT21_ReadChar( BYTE *input, CONTEXT86 *waitctx )
337 {
338     static BYTE pending_scan = 0;
339
340     if (pending_scan)
341     {
342         if (input)
343             *input = pending_scan;
344         if (waitctx)
345             pending_scan = 0;
346         return TRUE;
347     }
348     else
349     {
350         BYTE ascii;
351         BYTE scan;
352         if (!DOSVM_Int16ReadChar( &ascii, &scan, waitctx ))
353             return FALSE;
354
355         if (input)
356             *input = ascii;
357         if (waitctx && !ascii)
358             pending_scan = scan;
359         return TRUE;
360     }
361 }
362
363
364 /***********************************************************************
365  *           INT21_GetSystemCountryCode
366  *
367  * Return DOS country code for default system locale.
368  */
369 static WORD INT21_GetSystemCountryCode( void )
370 {
371     /*
372      * FIXME: Determine country code. We should probably use
373      *        DOSCONF structure for that.
374      */
375     return GetSystemDefaultLangID();
376 }
377
378
379 /***********************************************************************
380  *           INT21_FillCountryInformation
381  *
382  * Fill 34-byte buffer with country information data using
383  * default system locale.
384  */
385 static void INT21_FillCountryInformation( BYTE *buffer )
386 {
387     /* 00 - WORD: date format
388      *          00 = mm/dd/yy
389      *          01 = dd/mm/yy
390      *          02 = yy/mm/dd
391      */
392     *(WORD*)(buffer + 0) = 0; /* FIXME: Get from locale */
393
394     /* 02 - BYTE[5]: ASCIIZ currency symbol string */
395     buffer[2] = '$'; /* FIXME: Get from locale */
396     buffer[3] = 0;
397
398     /* 07 - BYTE[2]: ASCIIZ thousands separator */
399     buffer[7] = 0; /* FIXME: Get from locale */
400     buffer[8] = 0;
401
402     /* 09 - BYTE[2]: ASCIIZ decimal separator */
403     buffer[9]  = '.'; /* FIXME: Get from locale */
404     buffer[10] = 0;
405
406     /* 11 - BYTE[2]: ASCIIZ date separator */
407     buffer[11] = '/'; /* FIXME: Get from locale */
408     buffer[12] = 0;
409
410     /* 13 - BYTE[2]: ASCIIZ time separator */
411     buffer[13] = ':'; /* FIXME: Get from locale */
412     buffer[14] = 0;
413
414     /* 15 - BYTE: Currency format
415      *          bit 2 = set if currency symbol replaces decimal point
416      *          bit 1 = number of spaces between value and currency symbol
417      *          bit 0 = 0 if currency symbol precedes value
418      *                  1 if currency symbol follows value
419      */
420     buffer[15] = 0; /* FIXME: Get from locale */
421
422     /* 16 - BYTE: Number of digits after decimal in currency */
423     buffer[16] = 0; /* FIXME: Get from locale */
424
425     /* 17 - BYTE: Time format
426      *          bit 0 = 0 if 12-hour clock
427      *                  1 if 24-hour clock
428      */
429     buffer[17] = 1; /* FIXME: Get from locale */
430
431     /* 18 - DWORD: Address of case map routine */
432     *(DWORD*)(buffer + 18) = 0; /* FIXME: ptr to case map routine */
433
434     /* 22 - BYTE[2]: ASCIIZ data-list separator */
435     buffer[22] = ','; /* FIXME: Get from locale */
436     buffer[23] = 0;
437
438     /* 24 - BYTE[10]: Reserved */
439     memset( buffer + 24, 0, 10 );
440 }
441
442
443 /***********************************************************************
444  *           INT21_FillHeap
445  *
446  * Initialize DOS heap.
447  */
448 static void INT21_FillHeap( INT21_HEAP *heap )
449 {
450     static const char terminators[] = "\"\\./[]:|<>+=;,";
451     int i;
452
453     /*
454      * Uppercase table.
455      */
456     heap->uppercase_size = 128;
457     for (i = 0; i < 128; i++) 
458         heap->uppercase_table[i] = toupper( 128 + i );
459
460     /*
461      * Lowercase table.
462      */
463     heap->lowercase_size = 256;
464     for (i = 0; i < 256; i++) 
465         heap->lowercase_table[i] = tolower( i );
466     
467     /*
468      * Collating table.
469      */
470     heap->collating_size = 256;
471     for (i = 0; i < 256; i++) 
472         heap->collating_table[i] = i;
473
474     /*
475      * Filename table.
476      */
477     heap->filename_size = 8 + strlen(terminators);
478     heap->filename_illegal_size = strlen(terminators);
479     strcpy( heap->filename_illegal_table, terminators );
480
481     heap->filename_reserved1 = 0x01;
482     heap->filename_lowest = 0;           /* FIXME: correct value? */
483     heap->filename_highest = 0xff;       /* FIXME: correct value? */
484     heap->filename_reserved2 = 0x00;    
485     heap->filename_exclude_first = 0x00; /* FIXME: correct value? */
486     heap->filename_exclude_last = 0x00;  /* FIXME: correct value? */
487     heap->filename_reserved3 = 0x02;
488
489     /*
490      * DBCS lead byte table. This table is empty.
491      */
492     heap->dbcs_size = 0;
493     memset( heap->dbcs_table, 0, sizeof(heap->dbcs_table) );
494
495     /*
496      * Initialize InDos flag.
497      */
498     heap->misc_indos = 0;
499
500     /*
501      * FIXME: Should drive parameter blocks (DPB) be
502      *        initialized here and linked to DOS LOL?
503      */
504 }
505
506
507 /***********************************************************************
508  *           INT21_GetHeapPointer
509  *
510  * Get pointer for DOS heap (INT21_HEAP).
511  * Creates and initializes heap on first call.
512  */
513 static INT21_HEAP *INT21_GetHeapPointer( void )
514 {
515     static INT21_HEAP *heap_pointer = NULL;
516
517     if (!heap_pointer)
518     {
519         WORD heap_segment;
520         WORD heap_selector;
521
522         heap_pointer = DOSVM_AllocDataUMB( sizeof(INT21_HEAP), 
523                                            &heap_segment,
524                                            &heap_selector );
525
526         heap_pointer->misc_segment  = heap_segment;
527         heap_pointer->misc_selector = heap_selector;
528         INT21_FillHeap( heap_pointer );
529     }
530
531     return heap_pointer;
532 }
533
534
535 /***********************************************************************
536  *           INT21_GetHeapSelector
537  *
538  * Get segment/selector for DOS heap (INT21_HEAP).
539  * Creates and initializes heap on first call.
540  */
541 static WORD INT21_GetHeapSelector( CONTEXT86 *context )
542 {
543     INT21_HEAP *heap = INT21_GetHeapPointer();
544
545     if (!ISV86(context) && DOSVM_IsWin16())
546         return heap->misc_selector;
547     else
548         return heap->misc_segment;
549 }
550
551
552 /***********************************************************************
553  *           INT21_FillDrivePB
554  *
555  * Fill DOS heap drive parameter block for the specified drive.
556  * Return TRUE if drive was valid and there were
557  * no errors while reading drive information.
558  */
559 static BOOL INT21_FillDrivePB( BYTE drive )
560 {
561     WCHAR       drivespec[3] = {'A', ':', 0};
562     INT21_HEAP *heap = INT21_GetHeapPointer();
563     INT21_DPB  *dpb;
564     UINT        drivetype;
565     DWORD       cluster_sectors;
566     DWORD       sector_bytes;
567     DWORD       free_clusters;
568     DWORD       total_clusters;
569
570     if (drive >= MAX_DOS_DRIVES)
571         return FALSE;
572
573     dpb = &heap->misc_dpb_list[drive];
574     drivespec[0] += drive;
575     drivetype = GetDriveTypeW( drivespec );
576
577     /*
578      * FIXME: Does this check work correctly with floppy/cdrom drives?
579      */
580     if (drivetype == DRIVE_NO_ROOT_DIR || drivetype == DRIVE_UNKNOWN)
581         return FALSE;
582
583     /*
584      * FIXME: Does this check work correctly with floppy/cdrom drives?
585      */
586     if (!GetDiskFreeSpaceW( drivespec, &cluster_sectors, &sector_bytes,
587                             &free_clusters, &total_clusters ))
588         return FALSE;
589
590     /*
591      * FIXME: Most of the values listed below are incorrect.
592      *        All values should be validated.
593      */
594  
595     dpb->drive           = drive;
596     dpb->unit            = 0;
597     dpb->sector_bytes    = sector_bytes;
598     dpb->cluster_sectors = cluster_sectors - 1;
599
600     dpb->shift = 0;
601     while (cluster_sectors > 1)
602     {
603         cluster_sectors /= 2;
604         dpb->shift++;
605     }
606
607     dpb->num_reserved         = 0;
608     dpb->num_FAT              = 1;
609     dpb->num_root_entries     = 2;
610     dpb->first_data_sector    = 2;
611     dpb->num_clusters1        = total_clusters;
612     dpb->sectors_per_FAT      = 1;
613     dpb->first_dir_sector     = 1;
614     dpb->driver_header        = 0;
615     dpb->media_ID             = (drivetype == DRIVE_FIXED) ? 0xF8 : 0xF0;
616     dpb->access_flag          = 0;
617     dpb->next                 = 0;
618     dpb->search_cluster1      = 0;
619     dpb->free_clusters_lo     = LOWORD(free_clusters);
620     dpb->free_clusters_hi     = HIWORD(free_clusters);
621     dpb->mirroring_flags      = 0;
622     dpb->info_sector          = 0xffff;
623     dpb->spare_boot_sector    = 0xffff;
624     dpb->first_cluster_sector = 0;
625     dpb->num_clusters2        = total_clusters;
626     dpb->fat_clusters         = 32;
627     dpb->root_cluster         = 0;
628     dpb->search_cluster2      = 0;
629
630     return TRUE;
631 }
632
633
634 /***********************************************************************
635  *           INT21_GetCurrentDirectory
636  *
637  * Handler for:
638  * - function 0x47
639  * - subfunction 0x47 of function 0x71
640  */
641 static BOOL INT21_GetCurrentDirectory( CONTEXT86 *context, BOOL islong )
642 {
643     char  *buffer = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi);
644     BYTE   new_drive = INT21_MapDrive( DL_reg(context) );
645     BYTE   old_drive = INT21_GetCurrentDrive();
646     WCHAR  pathW[MAX_PATH];
647     char   pathA[MAX_PATH];
648     WCHAR *ptr = pathW;
649
650     TRACE( "drive %d\n", DL_reg(context) );
651
652     if (new_drive == MAX_DOS_DRIVES)
653     {        
654         SetLastError(ERROR_INVALID_DRIVE);
655         return FALSE;
656     }
657     
658     /*
659      * Grab current directory.
660      */
661
662     INT21_SetCurrentDrive( new_drive );
663     if (!GetCurrentDirectoryW( MAX_PATH, pathW ))
664     {        
665         INT21_SetCurrentDrive( old_drive );
666         return FALSE;
667     }
668     INT21_SetCurrentDrive( old_drive );
669
670     /*
671      * Convert into short format.
672      */
673
674     if (!islong)
675     {
676         DWORD result = GetShortPathNameW( pathW, pathW, MAX_PATH );
677         if (!result)
678             return FALSE;
679         if (result > MAX_PATH)
680         {
681             WARN( "Short path too long!\n" );
682             SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
683             return FALSE;
684         }
685     }
686
687     /*
688      * The returned pathname does not include 
689      * the drive letter, colon or leading backslash.
690      */
691
692     if (ptr[0] == '\\')
693     {
694         /*
695          * FIXME: We should probably just strip host part from name...
696          */
697         FIXME( "UNC names are not supported.\n" );
698         SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
699         return FALSE;
700     }
701     else if (!ptr[0] || ptr[1] != ':' || ptr[2] != '\\')
702     {
703         WARN( "Path is neither UNC nor DOS path: %s\n",
704               wine_dbgstr_w(ptr) );
705         SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
706         return FALSE;
707     }
708     else
709     {
710         /* Remove drive letter, colon and leading backslash. */
711         ptr += 3;
712     }
713
714     /*
715      * Convert into OEM string.
716      */
717     
718     if (!WideCharToMultiByte(CP_OEMCP, 0, ptr, -1, pathA, 
719                              MAX_PATH, NULL, NULL))
720     {
721         WARN( "Cannot convert path!\n" );
722         SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
723         return FALSE;
724     }
725
726     /*
727      * Success.
728      */
729
730     if (!islong)
731     {
732         /* Undocumented success code. */
733         SET_AX( context, 0x0100 );
734         
735         /* Truncate buffer to 64 bytes. */
736         pathA[63] = 0;
737     }
738
739     TRACE( "%c:=%s\n", 'A' + new_drive, pathA );
740
741     strcpy( buffer, pathA );
742     return TRUE;
743 }
744
745
746 /***********************************************************************
747  *           INT21_SetCurrentDirectory
748  *
749  * Handler for:
750  * - function 0x3b
751  * - subfunction 0x3b of function 0x71
752  */
753 static BOOL INT21_SetCurrentDirectory( CONTEXT86 *context )
754 {
755     WCHAR dirW[MAX_PATH];
756     char *dirA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
757     BYTE  drive = INT21_GetCurrentDrive();
758     BOOL  result;
759
760     TRACE( "SET CURRENT DIRECTORY %s\n", dirA );
761
762     MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
763     result = SetCurrentDirectoryW( dirW );
764
765     /* This function must not change current drive. */
766     INT21_SetCurrentDrive( drive );
767
768     return result;
769 }
770
771
772 /***********************************************************************
773  *           INT21_CreateFile
774  *
775  * Handler for:
776  * - function 0x3c
777  * - function 0x3d
778  * - function 0x5b
779  * - function 0x6c
780  * - subfunction 0x6c of function 0x71
781  */
782 static BOOL INT21_CreateFile( CONTEXT86 *context, 
783                               DWORD      pathSegOff,
784                               BOOL       returnStatus,
785                               WORD       dosAccessShare,
786                               BYTE       dosAction )
787 {
788     WORD   dosStatus;
789     char  *pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, pathSegOff);
790     WCHAR  pathW[MAX_PATH];   
791     DWORD  winAccess;
792     DWORD  winAttributes;
793     HANDLE winHandle;
794     DWORD  winMode;
795     DWORD  winSharing;
796
797     TRACE( "CreateFile called: function=%02x, action=%02x, access/share=%04x, "
798            "create flags=%04x, file=%s.\n",
799            AH_reg(context), dosAction, dosAccessShare, CX_reg(context), pathA );
800
801     /*
802      * Application tried to create/open a file whose name 
803      * ends with a backslash. This is not allowed.
804      *
805      * FIXME: This needs to be validated, especially the return value.
806      */
807     if (pathA[strlen(pathA) - 1] == '/')
808     {
809         SetLastError( ERROR_FILE_NOT_FOUND );
810         return FALSE;
811     }
812
813     /*
814      * Convert DOS action flags into Win32 creation disposition parameter.
815      */ 
816     switch(dosAction)
817     {
818     case 0x01:
819         winMode = OPEN_EXISTING;
820         break;
821     case 0x02:
822         winMode = TRUNCATE_EXISTING;
823         break;
824     case 0x10:
825         winMode = CREATE_NEW;
826         break;
827     case 0x11:
828         winMode = OPEN_ALWAYS;
829         break;
830     case 0x12:
831         winMode = CREATE_ALWAYS;
832         break;
833     default:
834         SetLastError( ERROR_INVALID_PARAMETER );
835         return FALSE;
836     }
837
838     /*
839      * Convert DOS access/share flags into Win32 desired access parameter.
840      */ 
841     switch(dosAccessShare & 0x07)
842     {
843     case OF_READ:
844         winAccess = GENERIC_READ;
845         break;
846     case OF_WRITE:
847         winAccess = GENERIC_WRITE;
848         break;
849     case OF_READWRITE:
850         winAccess = GENERIC_READ | GENERIC_WRITE;
851         break;
852     case 0x04:
853         /*
854          * Read-only, do not modify file's last-access time (DOS7).
855          *
856          * FIXME: How to prevent modification of last-access time?
857          */
858         winAccess = GENERIC_READ;
859         break;
860     default:
861         winAccess = 0;
862     }
863
864     /*
865      * Convert DOS access/share flags into Win32 share mode parameter.
866      */ 
867     switch(dosAccessShare & 0x70)
868     {
869     case OF_SHARE_EXCLUSIVE:  
870         winSharing = 0; 
871         break;
872     case OF_SHARE_DENY_WRITE: 
873         winSharing = FILE_SHARE_READ; 
874         break;
875     case OF_SHARE_DENY_READ:  
876         winSharing = FILE_SHARE_WRITE; 
877         break;
878     case OF_SHARE_DENY_NONE:
879     case OF_SHARE_COMPAT:
880     default:
881         winSharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
882     }
883
884     /*
885      * FIXME: Bit (dosAccessShare & 0x80) represents inheritance.
886      *        What to do with this bit?
887      * FIXME: Bits in the high byte of dosAccessShare are not supported.
888      *        See both function 0x6c and subfunction 0x6c of function 0x71 for
889      *        definition of these bits.
890      */
891
892     /*
893      * Convert DOS create attributes into Win32 flags and attributes parameter.
894      */
895     if (winMode == OPEN_EXISTING || winMode == TRUNCATE_EXISTING)
896     {
897         winAttributes = 0;
898     }
899     else
900     {        
901         WORD dosAttributes = CX_reg(context);
902
903         if (dosAttributes & FILE_ATTRIBUTE_LABEL)
904         {
905             /*
906              * Application tried to create volume label entry.
907              * This is difficult to support so we do not allow it.
908              *
909              * FIXME: If volume does not already have a label, 
910              *        this function is supposed to succeed.
911              */
912             SetLastError( ERROR_ACCESS_DENIED );
913             return TRUE;
914         }
915
916         winAttributes = dosAttributes & 
917             (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | 
918              FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE);
919     }
920
921     /*
922      * Open the file.
923      */
924     MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
925
926     winHandle = CreateFileW( pathW, winAccess, winSharing, NULL, 
927                              winMode, winAttributes, 0 );
928
929     if (winHandle == INVALID_HANDLE_VALUE)
930         return FALSE;
931
932     /*
933      * Determine DOS file status.
934      *
935      * 1 = file opened
936      * 2 = file created
937      * 3 = file replaced
938      */
939     switch(winMode)
940     {
941     case OPEN_EXISTING:
942         dosStatus = 1;
943         break;
944     case TRUNCATE_EXISTING:
945         dosStatus = 3; 
946         break;
947     case CREATE_NEW:
948         dosStatus = 2;
949         break;
950     case OPEN_ALWAYS:
951         dosStatus = (GetLastError() == ERROR_ALREADY_EXISTS) ? 1 : 2;
952         break;
953     case CREATE_ALWAYS:
954         dosStatus = (GetLastError() == ERROR_ALREADY_EXISTS) ? 3 : 2;
955         break;
956     default:
957         dosStatus = 0;
958     }
959
960     /*
961      * Return DOS file handle and DOS status.
962      */
963     SET_AX( context, Win32HandleToDosFileHandle(winHandle) );
964
965     if (returnStatus)
966         SET_CX( context, dosStatus );
967
968     TRACE( "CreateFile finished: handle=%d, status=%d.\n", 
969            AX_reg(context), dosStatus );
970
971     return TRUE;
972 }
973
974
975 /***********************************************************************
976  *           INT21_BufferedInput
977  *
978  * Handler for function 0x0a and reading from console using
979  * function 0x3f.
980  *
981  * Reads a string of characters from standard input until
982  * enter key is pressed. Returns either number of characters 
983  * read from console including terminating CR or 
984  * zero if capacity was zero.
985  */
986 static WORD INT21_BufferedInput( CONTEXT86 *context, BYTE *ptr, WORD capacity )
987 {
988     BYTE length = 0;
989
990     /*
991      * Return immediately if capacity is zero.
992      */
993     if (capacity == 0)
994         return 0;
995
996     while(TRUE)
997     {
998         BYTE ascii;
999         BYTE scan;
1000
1001         DOSVM_Int16ReadChar( &ascii, &scan, context );
1002
1003         if (ascii == '\r' || ascii == '\n')
1004         {
1005             /*
1006              * FIXME: What should be echoed here?
1007              */
1008             DOSVM_PutChar( '\r' );
1009             DOSVM_PutChar( '\n' );
1010             ptr[length] = '\r';
1011             return length + 1;
1012         }
1013
1014         /*
1015          * FIXME: This function is supposed to support
1016          *        DOS editing keys...
1017          */
1018
1019         /*
1020          * If the buffer becomes filled to within one byte of
1021          * capacity, DOS rejects all further characters up to,
1022          * but not including, the terminating carriage return.
1023          */
1024         if (ascii != 0 && length < capacity-1)
1025         {
1026             DOSVM_PutChar( ascii );
1027             ptr[length] = ascii;
1028             length++;
1029         }
1030     }
1031 }
1032
1033
1034 /***********************************************************************
1035  *           INT21_GetCurrentDTA
1036  */
1037 static BYTE *INT21_GetCurrentDTA( CONTEXT86 *context )
1038 {
1039     TDB *pTask = GlobalLock16(GetCurrentTask());
1040
1041     /* FIXME: This assumes DTA was set correctly! */
1042     return (BYTE *)CTX_SEG_OFF_TO_LIN( context, SELECTOROF(pTask->dta),
1043                                                 (DWORD)OFFSETOF(pTask->dta) );
1044 }
1045
1046
1047 /***********************************************************************
1048  *           INT21_OpenFileUsingFCB
1049  *
1050  * Handler for function 0x0f.
1051  *
1052  * PARAMS
1053  *  DX:DX [I/O] File control block (FCB or XFCB) of unopened file
1054  *
1055  * RETURNS (in AL)
1056  *  0x00: successful
1057  *  0xff: failed
1058  *
1059  * NOTES
1060  *  Opens a FCB file for read/write in compatibility mode. Upon calling
1061  *  the FCB must have the drive_number, file_name, and file_extension
1062  *  fields filled and all other bytes cleared.
1063  */
1064 static void INT21_OpenFileUsingFCB( CONTEXT86 *context )
1065 {
1066     struct FCB *fcb;
1067     struct XFCB *xfcb;
1068     char file_path[16];
1069     char *pos;
1070     HANDLE handle;
1071     HFILE16 hfile16;
1072     BY_HANDLE_FILE_INFORMATION info;
1073     BYTE AL_result;
1074
1075     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1076     if (fcb->drive_number == 0xff) {
1077         xfcb = (struct XFCB *) fcb;
1078         fcb = (struct FCB *) xfcb->fcb;
1079     } /* if */
1080
1081     AL_result = 0;
1082     file_path[0] = 'A' + INT21_MapDrive( fcb->drive_number );
1083
1084     if (AL_result == 0) {
1085         file_path[1] = ':';
1086         pos = &file_path[2];
1087         memcpy(pos, fcb->file_name, 8);
1088         pos[8] = ' ';
1089         pos[9] = '\0';
1090         pos = strchr(pos, ' ');
1091         *pos = '.';
1092         pos++;
1093         memcpy(pos, fcb->file_extension, 3);
1094         pos[3] = ' ';
1095         pos[4] = '\0';
1096         pos = strchr(pos, ' ');
1097         *pos = '\0';
1098
1099         handle = (HANDLE) _lopen(file_path, OF_READWRITE);
1100         if (handle == INVALID_HANDLE_VALUE) {
1101             TRACE("_lopen(\"%s\") failed: INVALID_HANDLE_VALUE\n", file_path);
1102             AL_result = 0xff; /* failed */
1103         } else {
1104             hfile16 = Win32HandleToDosFileHandle(handle);
1105             if (hfile16 == HFILE_ERROR16) {
1106                 TRACE("Win32HandleToDosFileHandle(%p) failed: HFILE_ERROR\n", handle);
1107                 CloseHandle(handle);
1108                 AL_result = 0xff; /* failed */
1109             } else if (hfile16 > 255) {
1110                 TRACE("hfile16 (=%d) larger than 255 for \"%s\"\n", hfile16, file_path);
1111                 _lclose16(hfile16);
1112                 AL_result = 0xff; /* failed */
1113             } else {
1114                 if (!GetFileInformationByHandle(handle, &info)) {
1115                     TRACE("GetFileInformationByHandle(%d, %p) for \"%s\" failed\n",
1116                           hfile16, handle, file_path);
1117                     _lclose16(hfile16);
1118                     AL_result = 0xff; /* failed */
1119                 } else {
1120                     fcb->drive_number = file_path[0] - 'A' + 1;
1121                     fcb->current_block_number = 0;
1122                     fcb->logical_record_size = 128;
1123                     fcb->file_size = info.nFileSizeLow;
1124                     FileTimeToDosDateTime(&info.ftLastWriteTime,
1125                         &fcb->date_of_last_write, &fcb->time_of_last_write);
1126                     fcb->file_number = hfile16;
1127                     fcb->attributes = 0xc2;
1128                     fcb->starting_cluster = 0; /* don't know correct init value */
1129                     fcb->sequence_number = 0; /* don't know correct init value */
1130                     fcb->file_attributes = info.dwFileAttributes;
1131                     /* The following fields are not initialized */
1132                     /* by the native function: */
1133                     /* unused */
1134                     /* record_within_current_block */
1135                     /* random_access_record_number */
1136
1137                     TRACE("successful opened file \"%s\" as %d (handle %p)\n",
1138                           file_path, hfile16, handle);
1139                     AL_result = 0x00; /* successful */
1140                 } /* if */
1141             } /* if */
1142         } /* if */
1143     } /* if */
1144     SET_AL(context, AL_result);
1145 }
1146
1147
1148 /***********************************************************************
1149  *           INT21_CloseFileUsingFCB
1150  *
1151  * Handler for function 0x10.
1152  *
1153  * PARAMS
1154  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1155  *
1156  * RETURNS (in AL)
1157  *  0x00: successful
1158  *  0xff: failed
1159  *
1160  * NOTES
1161  *  Closes a FCB file.
1162  */
1163 static void INT21_CloseFileUsingFCB( CONTEXT86 *context )
1164 {
1165     struct FCB *fcb;
1166     struct XFCB *xfcb;
1167     BYTE AL_result;
1168
1169     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1170     if (fcb->drive_number == 0xff) {
1171         xfcb = (struct XFCB *) fcb;
1172         fcb = (struct FCB *) xfcb->fcb;
1173     } /* if */
1174
1175     if (_lclose16((HFILE16) fcb->file_number) != 0) {
1176         TRACE("_lclose16(%d) failed\n", fcb->file_number);
1177         AL_result = 0xff; /* failed */
1178     } else {
1179         TRACE("successful closed file %d\n", fcb->file_number);
1180         AL_result = 0x00; /* successful */
1181     } /* if */
1182     SET_AL(context, AL_result);
1183 }
1184
1185
1186 /***********************************************************************
1187  *           INT21_SequentialReadFromFCB
1188  *
1189  * Handler for function 0x14.
1190  *
1191  * PARAMS
1192  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1193  *
1194  * RETURNS (in AL)
1195  *  0: successful
1196  *  1: end of file, no data read
1197  *  2: segment wrap in DTA, no data read (not returned now)
1198  *  3: end of file, partial record read
1199  *
1200  * NOTES
1201  *  Reads a record with the size FCB->logical_record_size from the FCB
1202  *  to the disk transfer area. The position of the record is specified
1203  *  with FCB->current_block_number and FCB->record_within_current_block.
1204  *  Then FCB->current_block_number and FCB->record_within_current_block
1205  *  are updated to point to the next record. If a partial record is
1206  *  read, it is filled with zeros up to the FCB->logical_record_size.
1207  */
1208 static void INT21_SequentialReadFromFCB( CONTEXT86 *context )
1209 {
1210     struct FCB *fcb;
1211     struct XFCB *xfcb;
1212     HANDLE handle;
1213     DWORD record_number;
1214     long position;
1215     BYTE *disk_transfer_area;
1216     UINT bytes_read;
1217     BYTE AL_result;
1218
1219     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1220     if (fcb->drive_number == 0xff) {
1221         xfcb = (struct XFCB *) fcb;
1222         fcb = (struct FCB *) xfcb->fcb;
1223     } /* if */
1224
1225     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1226     if (handle == INVALID_HANDLE_VALUE) {
1227         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1228             fcb->file_number);
1229         AL_result = 0x01; /* end of file, no data read */
1230     } else {
1231         record_number = 128 * fcb->current_block_number + fcb->record_within_current_block;
1232         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1233         if (position != record_number * fcb->logical_record_size) {
1234             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1235                   fcb->file_number, record_number * fcb->logical_record_size, position);
1236             AL_result = 0x01; /* end of file, no data read */
1237         } else {
1238             disk_transfer_area = INT21_GetCurrentDTA(context);
1239             bytes_read = _lread((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
1240             if (bytes_read != fcb->logical_record_size) {
1241                 TRACE("_lread(%d, %p, %d) failed with %d\n",
1242                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_read);
1243                 if (bytes_read == 0) {
1244                     AL_result = 0x01; /* end of file, no data read */
1245                 } else {
1246                     memset(&disk_transfer_area[bytes_read], 0, fcb->logical_record_size - bytes_read);
1247                     AL_result = 0x03; /* end of file, partial record read */
1248                 } /* if */
1249             } else {
1250                 TRACE("successful read %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1251                     bytes_read, record_number, position, fcb->file_number, handle);
1252                 AL_result = 0x00; /* successful */
1253             } /* if */
1254         } /* if */
1255     } /* if */
1256     if (AL_result == 0x00 || AL_result == 0x03) {
1257         if (fcb->record_within_current_block == 127) {
1258             fcb->record_within_current_block = 0;
1259             fcb->current_block_number++;
1260         } else {
1261             fcb->record_within_current_block++;
1262         } /* if */
1263     } /* if */
1264     SET_AL(context, AL_result);
1265 }
1266
1267
1268 /***********************************************************************
1269  *           INT21_SequentialWriteToFCB
1270  *
1271  * Handler for function 0x15.
1272  *
1273  * PARAMS
1274  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1275  *
1276  * RETURNS (in AL)
1277  *  0: successful
1278  *  1: disk full
1279  *  2: segment wrap in DTA (not returned now)
1280  *
1281  * NOTES
1282  *  Writes a record with the size FCB->logical_record_size from the disk
1283  *  transfer area to the FCB. The position of the record is specified
1284  *  with FCB->current_block_number and FCB->record_within_current_block.
1285  *  Then FCB->current_block_number and FCB->record_within_current_block
1286  *  are updated to point to the next record. 
1287  */
1288 static void INT21_SequentialWriteToFCB( CONTEXT86 *context )
1289 {
1290     struct FCB *fcb;
1291     struct XFCB *xfcb;
1292     HANDLE handle;
1293     DWORD record_number;
1294     long position;
1295     BYTE *disk_transfer_area;
1296     UINT bytes_written;
1297     BYTE AL_result;
1298
1299     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1300     if (fcb->drive_number == 0xff) {
1301         xfcb = (struct XFCB *) fcb;
1302         fcb = (struct FCB *) xfcb->fcb;
1303     } /* if */
1304
1305     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1306     if (handle == INVALID_HANDLE_VALUE) {
1307         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1308             fcb->file_number);
1309         AL_result = 0x01; /* disk full */
1310     } else {
1311         record_number = 128 * fcb->current_block_number + fcb->record_within_current_block;
1312         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1313         if (position != record_number * fcb->logical_record_size) {
1314             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1315                   fcb->file_number, record_number * fcb->logical_record_size, position);
1316             AL_result = 0x01; /* disk full */
1317         } else {
1318             disk_transfer_area = INT21_GetCurrentDTA(context);
1319             bytes_written = _lwrite((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
1320             if (bytes_written != fcb->logical_record_size) {
1321                 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
1322                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_written);
1323                 AL_result = 0x01; /* disk full */
1324             } else {
1325                 TRACE("successful written %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1326                     bytes_written, record_number, position, fcb->file_number, handle);
1327                 AL_result = 0x00; /* successful */
1328             } /* if */
1329         } /* if */
1330     } /* if */
1331     if (AL_result == 0x00) {
1332         if (fcb->record_within_current_block == 127) {
1333             fcb->record_within_current_block = 0;
1334             fcb->current_block_number++;
1335         } else {
1336             fcb->record_within_current_block++;
1337         } /* if */
1338     } /* if */
1339     SET_AL(context, AL_result);
1340 }
1341
1342
1343 /***********************************************************************
1344  *           INT21_ReadRandomRecordFromFCB
1345  *
1346  * Handler for function 0x21.
1347  *
1348  * PARAMS
1349  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1350  *
1351  * RETURNS (in AL)
1352  *  0: successful
1353  *  1: end of file, no data read
1354  *  2: segment wrap in DTA, no data read (not returned now)
1355  *  3: end of file, partial record read
1356  *
1357  * NOTES
1358  *  Reads a record with the size FCB->logical_record_size from
1359  *  the FCB to the disk transfer area. The position of the record
1360  *  is specified with FCB->random_access_record_number. The
1361  *  FCB->random_access_record_number is not updated. If a partial record
1362  *  is read, it is filled with zeros up to the FCB->logical_record_size.
1363  */
1364 static void INT21_ReadRandomRecordFromFCB( CONTEXT86 *context )
1365 {
1366     struct FCB *fcb;
1367     struct XFCB *xfcb;
1368     HANDLE handle;
1369     DWORD record_number;
1370     long position;
1371     BYTE *disk_transfer_area;
1372     UINT bytes_read;
1373     BYTE AL_result;
1374
1375     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1376     if (fcb->drive_number == 0xff) {
1377         xfcb = (struct XFCB *) fcb;
1378         fcb = (struct FCB *) xfcb->fcb;
1379     } /* if */
1380
1381     memcpy(&record_number, fcb->random_access_record_number, 4);
1382     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1383     if (handle == INVALID_HANDLE_VALUE) {
1384         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1385             fcb->file_number);
1386         AL_result = 0x01; /* end of file, no data read */
1387     } else {
1388         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1389         if (position != record_number * fcb->logical_record_size) {
1390             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1391                   fcb->file_number, record_number * fcb->logical_record_size, position);
1392             AL_result = 0x01; /* end of file, no data read */
1393         } else {
1394             disk_transfer_area = INT21_GetCurrentDTA(context);
1395             bytes_read = _lread((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
1396             if (bytes_read != fcb->logical_record_size) {
1397                 TRACE("_lread(%d, %p, %d) failed with %d\n",
1398                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_read);
1399                 if (bytes_read == 0) {
1400                     AL_result = 0x01; /* end of file, no data read */
1401                 } else {
1402                     memset(&disk_transfer_area[bytes_read], 0, fcb->logical_record_size - bytes_read);
1403                     AL_result = 0x03; /* end of file, partial record read */
1404                 } /* if */
1405             } else {
1406                 TRACE("successful read %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1407                     bytes_read, record_number, position, fcb->file_number, handle);
1408                 AL_result = 0x00; /* successful */
1409             } /* if */
1410         } /* if */
1411     } /* if */
1412     fcb->current_block_number = record_number / 128;
1413     fcb->record_within_current_block = record_number % 128;
1414     SET_AL(context, AL_result);
1415 }
1416
1417
1418 /***********************************************************************
1419  *           INT21_WriteRandomRecordToFCB
1420  *
1421  * Handler for function 0x22.
1422  *
1423  * PARAMS
1424  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1425  *
1426  * RETURNS (in AL)
1427  *  0: successful
1428  *  1: disk full
1429  *  2: segment wrap in DTA (not returned now)
1430  *
1431  * NOTES
1432  *  Writes a record with the size FCB->logical_record_size from
1433  *  the disk transfer area to the FCB. The position of the record
1434  *  is specified with FCB->random_access_record_number. The
1435  *  FCB->random_access_record_number is not updated.
1436  */
1437 static void INT21_WriteRandomRecordToFCB( CONTEXT86 *context )
1438 {
1439     struct FCB *fcb;
1440     struct XFCB *xfcb;
1441     HANDLE handle;
1442     DWORD record_number;
1443     long position;
1444     BYTE *disk_transfer_area;
1445     UINT bytes_written;
1446     BYTE AL_result;
1447
1448     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1449     if (fcb->drive_number == 0xff) {
1450         xfcb = (struct XFCB *) fcb;
1451         fcb = (struct FCB *) xfcb->fcb;
1452     } /* if */
1453
1454     memcpy(&record_number, fcb->random_access_record_number, 4);
1455     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1456     if (handle == INVALID_HANDLE_VALUE) {
1457         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1458             fcb->file_number);
1459         AL_result = 0x01; /* disk full */
1460     } else {
1461         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1462         if (position != record_number * fcb->logical_record_size) {
1463             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1464                   fcb->file_number, record_number * fcb->logical_record_size, position);
1465             AL_result = 0x01; /* disk full */
1466         } else {
1467             disk_transfer_area = INT21_GetCurrentDTA(context);
1468             bytes_written = _lwrite((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
1469             if (bytes_written != fcb->logical_record_size) {
1470                 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
1471                       fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_written);
1472                 AL_result = 0x01; /* disk full */
1473             } else {
1474                 TRACE("successful written %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1475                     bytes_written, record_number, position, fcb->file_number, handle);
1476                 AL_result = 0x00; /* successful */
1477             } /* if */
1478         } /* if */
1479     } /* if */
1480     fcb->current_block_number = record_number / 128;
1481     fcb->record_within_current_block = record_number % 128;
1482     SET_AL(context, AL_result);
1483 }
1484
1485
1486 /***********************************************************************
1487  *           INT21_RandomBlockReadFromFCB
1488  *
1489  * Handler for function 0x27.
1490  *
1491  * PARAMS
1492  *  CX    [I/O] Number of records to read
1493  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1494  *
1495  * RETURNS (in AL)
1496  *  0: successful
1497  *  1: end of file, no data read
1498  *  2: segment wrap in DTA, no data read (not returned now)
1499  *  3: end of file, partial record read
1500  *
1501  * NOTES
1502  *  Reads several records with the size FCB->logical_record_size from
1503  *  the FCB to the disk transfer area. The number of records to be
1504  *  read is specified in the CX register. The position of the first
1505  *  record is specified with FCB->random_access_record_number. The
1506  *  FCB->random_access_record_number, the FCB->current_block_number
1507  *  and FCB->record_within_current_block are updated to point to the
1508  *  next record after the records read. If a partial record is read,
1509  *  it is filled with zeros up to the FCB->logical_record_size. The
1510  *  CX register is set to the number of successfully read records.
1511  */
1512 static void INT21_RandomBlockReadFromFCB( CONTEXT86 *context )
1513 {
1514     struct FCB *fcb;
1515     struct XFCB *xfcb;
1516     HANDLE handle;
1517     DWORD record_number;
1518     long position;
1519     BYTE *disk_transfer_area;
1520     UINT records_requested;
1521     UINT bytes_requested;
1522     UINT bytes_read;
1523     UINT records_read;
1524     BYTE AL_result;
1525
1526     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1527     if (fcb->drive_number == 0xff) {
1528         xfcb = (struct XFCB *) fcb;
1529         fcb = (struct FCB *) xfcb->fcb;
1530     } /* if */
1531
1532     memcpy(&record_number, fcb->random_access_record_number, 4);
1533     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1534     if (handle == INVALID_HANDLE_VALUE) {
1535         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1536             fcb->file_number);
1537         records_read = 0;
1538         AL_result = 0x01; /* end of file, no data read */
1539     } else {
1540         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1541         if (position != record_number * fcb->logical_record_size) {
1542             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1543                   fcb->file_number, record_number * fcb->logical_record_size, position);
1544             records_read = 0;
1545             AL_result = 0x01; /* end of file, no data read */
1546         } else {
1547             disk_transfer_area = INT21_GetCurrentDTA(context);
1548             records_requested = CX_reg(context);
1549             bytes_requested = (UINT) records_requested * fcb->logical_record_size;
1550             bytes_read = _lread((HFILE) handle, disk_transfer_area, bytes_requested);
1551             if (bytes_read != bytes_requested) {
1552                 TRACE("_lread(%d, %p, %d) failed with %d\n",
1553                       fcb->file_number, disk_transfer_area, bytes_requested, bytes_read);
1554                 records_read = bytes_read / fcb->logical_record_size;
1555                 if (bytes_read % fcb->logical_record_size == 0) {
1556                     AL_result = 0x01; /* end of file, no data read */
1557                 } else {
1558                     records_read++;
1559                     memset(&disk_transfer_area[bytes_read], 0, records_read * fcb->logical_record_size - bytes_read);
1560                     AL_result = 0x03; /* end of file, partial record read */
1561                 } /* if */
1562             } else {
1563                 TRACE("successful read %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1564                     bytes_read, record_number, position, fcb->file_number, handle);
1565                 records_read = records_requested;
1566                 AL_result = 0x00; /* successful */
1567             } /* if */
1568         } /* if */
1569     } /* if */
1570     record_number += records_read;
1571     memcpy(fcb->random_access_record_number, &record_number, 4);
1572     fcb->current_block_number = record_number / 128;
1573     fcb->record_within_current_block = record_number % 128;
1574     SET_CX(context, records_read);
1575     SET_AL(context, AL_result);
1576 }
1577
1578
1579 /***********************************************************************
1580  *           INT21_RandomBlockWriteToFCB
1581  *
1582  * Handler for function 0x28.
1583  *
1584  * PARAMS
1585  *  CX    [I/O] Number of records to write
1586  *  DX:DX [I/O] File control block (FCB or XFCB) of open file
1587  *
1588  * RETURNS (in AL)
1589  *  0: successful
1590  *  1: disk full
1591  *  2: segment wrap in DTA (not returned now)
1592  *
1593  * NOTES
1594  *  Writes several records with the size FCB->logical_record_size from
1595  *  the disk transfer area to the FCB. The number of records to be
1596  *  written is specified in the CX register. The position of the first
1597  *  record is specified with FCB->random_access_record_number. The
1598  *  FCB->random_access_record_number, the FCB->current_block_number
1599  *  and FCB->record_within_current_block are updated to point to the
1600  *  next record after the records written. The CX register is set to
1601  *  the number of successfully written records.
1602  */
1603 static void INT21_RandomBlockWriteToFCB( CONTEXT86 *context )
1604 {
1605     struct FCB *fcb;
1606     struct XFCB *xfcb;
1607     HANDLE handle;
1608     DWORD record_number;
1609     long position;
1610     BYTE *disk_transfer_area;
1611     UINT records_requested;
1612     UINT bytes_requested;
1613     UINT bytes_written;
1614     UINT records_written;
1615     BYTE AL_result;
1616
1617     fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1618     if (fcb->drive_number == 0xff) {
1619         xfcb = (struct XFCB *) fcb;
1620         fcb = (struct FCB *) xfcb->fcb;
1621     } /* if */
1622
1623     memcpy(&record_number, fcb->random_access_record_number, 4);
1624     handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1625     if (handle == INVALID_HANDLE_VALUE) {
1626         TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1627             fcb->file_number);
1628         records_written = 0;
1629         AL_result = 0x01; /* disk full */
1630     } else {
1631         position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1632         if (position != record_number * fcb->logical_record_size) {
1633             TRACE("seek(%d, %ld, 0) failed with %ld\n",
1634                   fcb->file_number, record_number * fcb->logical_record_size, position);
1635             records_written = 0;
1636             AL_result = 0x01; /* disk full */
1637         } else {
1638             disk_transfer_area = INT21_GetCurrentDTA(context);
1639             records_requested = CX_reg(context);
1640             bytes_requested = (UINT) records_requested * fcb->logical_record_size;
1641             bytes_written = _lwrite((HFILE) handle, disk_transfer_area, bytes_requested);
1642             if (bytes_written != bytes_requested) {
1643                 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
1644                       fcb->file_number, disk_transfer_area, bytes_requested, bytes_written);
1645                 records_written = bytes_written / fcb->logical_record_size;
1646                 AL_result = 0x01; /* disk full */
1647             } else {
1648                 TRACE("successful write %d bytes from record %ld (position %ld) of file %d (handle %p)\n",
1649                     bytes_written, record_number, position, fcb->file_number, handle);
1650                 records_written = records_requested;
1651                 AL_result = 0x00; /* successful */
1652             } /* if */
1653         } /* if */
1654     } /* if */
1655     record_number += records_written;
1656     memcpy(fcb->random_access_record_number, &record_number, 4);
1657     fcb->current_block_number = record_number / 128;
1658     fcb->record_within_current_block = record_number % 128;
1659     SET_CX(context, records_written);
1660     SET_AL(context, AL_result);
1661 }
1662
1663
1664 /***********************************************************************
1665  *           INT21_CreateDirectory
1666  *
1667  * Handler for:
1668  * - function 0x39
1669  * - subfunction 0x39 of function 0x71
1670  * - subfunction 0xff of function 0x43 (CL == 0x39)
1671  */
1672 static BOOL INT21_CreateDirectory( CONTEXT86 *context )
1673 {
1674     WCHAR dirW[MAX_PATH];
1675     char *dirA = CTX_SEG_OFF_TO_LIN(context,
1676                                     context->SegDs, 
1677                                     context->Edx);
1678
1679     TRACE( "CREATE DIRECTORY %s\n", dirA );
1680
1681     MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
1682
1683     if (CreateDirectoryW(dirW, NULL))
1684         return TRUE;
1685
1686     /*
1687      * FIXME: CreateDirectory's LastErrors will clash with the ones
1688      *        used by DOS. AH=39 only returns 3 (path not found) and 
1689      *        5 (access denied), while CreateDirectory return several
1690      *        ones. Remap some of them. -Marcus
1691      */
1692     switch (GetLastError()) 
1693     {
1694     case ERROR_ALREADY_EXISTS:
1695     case ERROR_FILENAME_EXCED_RANGE:
1696     case ERROR_DISK_FULL:
1697         SetLastError(ERROR_ACCESS_DENIED);
1698         break;
1699     default: 
1700         break;
1701     }
1702
1703     return FALSE;
1704 }
1705
1706
1707 /***********************************************************************
1708  *           INT21_ExtendedCountryInformation
1709  *
1710  * Handler for function 0x65.
1711  */
1712 static void INT21_ExtendedCountryInformation( CONTEXT86 *context )
1713 {
1714     BYTE *dataptr = CTX_SEG_OFF_TO_LIN( context, context->SegEs, context->Edi );
1715
1716     TRACE( "GET EXTENDED COUNTRY INFORMATION, subfunction %02x\n",
1717            AL_reg(context) );
1718
1719     /*
1720      * Check subfunctions that are passed country and code page.
1721      */
1722     if (AL_reg(context) >= 0x01 && AL_reg(context) <= 0x07)
1723     {
1724         WORD country = DX_reg(context);
1725         WORD codepage = BX_reg(context);
1726
1727         if (country != 0xffff && country != INT21_GetSystemCountryCode())
1728             FIXME( "Requested info on non-default country %04x\n", country );
1729
1730         if (codepage != 0xffff && codepage != GetOEMCP())
1731             FIXME( "Requested info on non-default code page %04x\n", codepage );
1732     }
1733
1734     switch (AL_reg(context)) {
1735     case 0x00: /* SET GENERAL INTERNATIONALIZATION INFO */
1736         INT_BARF( context, 0x21 );
1737         SET_CFLAG( context );
1738         break;
1739
1740     case 0x01: /* GET GENERAL INTERNATIONALIZATION INFO */
1741         TRACE( "Get general internationalization info\n" );
1742         dataptr[0] = 0x01; /* Info ID */
1743         *(WORD*)(dataptr+1) = 38; /* Size of the following info */
1744         *(WORD*)(dataptr+3) = INT21_GetSystemCountryCode(); /* Country ID */
1745         *(WORD*)(dataptr+5) = GetOEMCP(); /* Code page */
1746         INT21_FillCountryInformation( dataptr + 7 );
1747         SET_CX( context, 41 ); /* Size of returned info */
1748         break;
1749         
1750     case 0x02: /* GET POINTER TO UPPERCASE TABLE */
1751     case 0x04: /* GET POINTER TO FILENAME UPPERCASE TABLE */
1752         TRACE( "Get pointer to uppercase table\n" );
1753         dataptr[0] = AL_reg(context); /* Info ID */
1754         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1755                                            offsetof(INT21_HEAP, uppercase_size) );
1756         SET_CX( context, 5 ); /* Size of returned info */
1757         break;
1758
1759     case 0x03: /* GET POINTER TO LOWERCASE TABLE */
1760         TRACE( "Get pointer to lowercase table\n" );
1761         dataptr[0] = 0x03; /* Info ID */
1762         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1763                                            offsetof(INT21_HEAP, lowercase_size) );
1764         SET_CX( context, 5 ); /* Size of returned info */
1765         break;
1766
1767     case 0x05: /* GET POINTER TO FILENAME TERMINATOR TABLE */
1768         TRACE("Get pointer to filename terminator table\n");
1769         dataptr[0] = 0x05; /* Info ID */
1770         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1771                                            offsetof(INT21_HEAP, filename_size) );
1772         SET_CX( context, 5 ); /* Size of returned info */
1773         break;
1774
1775     case 0x06: /* GET POINTER TO COLLATING SEQUENCE TABLE */
1776         TRACE("Get pointer to collating sequence table\n");
1777         dataptr[0] = 0x06; /* Info ID */
1778         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1779                                            offsetof(INT21_HEAP, collating_size) );
1780         SET_CX( context, 5 ); /* Size of returned info */
1781         break;
1782
1783     case 0x07: /* GET POINTER TO DBCS LEAD BYTE TABLE */
1784         TRACE("Get pointer to DBCS lead byte table\n");
1785         dataptr[0] = 0x07; /* Info ID */
1786         *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1787                                            offsetof(INT21_HEAP, dbcs_size) );
1788         SET_CX( context, 5 ); /* Size of returned info */
1789         break;
1790
1791     case 0x20: /* CAPITALIZE CHARACTER */
1792     case 0xa0: /* CAPITALIZE FILENAME CHARACTER */
1793         TRACE("Convert char to uppercase\n");
1794         SET_DL( context, toupper(DL_reg(context)) );
1795         break;
1796
1797     case 0x21: /* CAPITALIZE STRING */
1798     case 0xa1: /* CAPITALIZE COUNTED FILENAME STRING */
1799         TRACE("Convert string to uppercase with length\n");
1800         {
1801             char *ptr = (char *)CTX_SEG_OFF_TO_LIN( context,
1802                                                     context->SegDs,
1803                                                     context->Edx );
1804             WORD len = CX_reg(context);
1805             while (len--) { *ptr = toupper(*ptr); ptr++; }
1806         }
1807         break;
1808
1809     case 0x22: /* CAPITALIZE ASCIIZ STRING */
1810     case 0xa2: /* CAPITALIZE ASCIIZ FILENAME */
1811         TRACE("Convert ASCIIZ string to uppercase\n");
1812         _strupr( (LPSTR)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx) );
1813         break;
1814
1815     case 0x23: /* DETERMINE IF CHARACTER REPRESENTS YES/NO RESPONSE */
1816         INT_BARF( context, 0x21 );
1817         SET_CFLAG( context );
1818         break;
1819
1820     default:
1821         INT_BARF( context, 0x21 );
1822         SET_CFLAG(context);
1823         break;
1824     }
1825 }
1826
1827
1828 /***********************************************************************
1829  *           INT21_FileAttributes
1830  *
1831  * Handler for:
1832  * - function 0x43
1833  * - subfunction 0x43 of function 0x71
1834  */
1835 static BOOL INT21_FileAttributes( CONTEXT86 *context, 
1836                                   BYTE       subfunction,
1837                                   BOOL       islong )
1838 {
1839     WCHAR fileW[MAX_PATH];
1840     char *fileA = CTX_SEG_OFF_TO_LIN(context, 
1841                                      context->SegDs, 
1842                                      context->Edx);
1843     HANDLE   handle;
1844     BOOL     status;
1845     FILETIME filetime;
1846     DWORD    result;
1847     WORD     date, time;
1848
1849     switch (subfunction)
1850     {
1851     case 0x00: /* GET FILE ATTRIBUTES */
1852         TRACE( "GET FILE ATTRIBUTES for %s\n", fileA );
1853         MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1854
1855         result = GetFileAttributesW( fileW );
1856         if (result == INVALID_FILE_ATTRIBUTES)
1857             return FALSE;
1858         else
1859         {
1860             SET_CX( context, (WORD)result );
1861             if (!islong)
1862                 SET_AX( context, (WORD)result ); /* DR DOS */
1863         }
1864         break;
1865
1866     case 0x01: /* SET FILE ATTRIBUTES */
1867         TRACE( "SET FILE ATTRIBUTES 0x%02x for %s\n", 
1868                CX_reg(context), fileA );
1869         MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1870
1871         if (!SetFileAttributesW( fileW, CX_reg(context) ))
1872             return FALSE;
1873         break;
1874
1875     case 0x02: /* GET COMPRESSED FILE SIZE */
1876         TRACE( "GET COMPRESSED FILE SIZE for %s\n", fileA );
1877         MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1878
1879         result = GetCompressedFileSizeW( fileW, NULL );
1880         if (result == INVALID_FILE_SIZE)
1881             return FALSE;
1882         else
1883         {
1884             SET_AX( context, LOWORD(result) );
1885             SET_DX( context, HIWORD(result) );
1886         }
1887         break;
1888
1889     case 0x03: /* SET FILE LAST-WRITTEN DATE AND TIME */
1890         if (!islong)
1891             INT_BARF( context, 0x21 );
1892         else
1893         {
1894             TRACE( "SET FILE LAST-WRITTEN DATE AND TIME, file %s\n", fileA );
1895             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1896
1897             handle = CreateFileW( fileW, GENERIC_WRITE, 
1898                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1899                                   NULL, OPEN_EXISTING, 0, 0 );
1900             if (handle == INVALID_HANDLE_VALUE)
1901                 return FALSE;
1902
1903             DosDateTimeToFileTime( DI_reg(context), 
1904                                    CX_reg(context),
1905                                    &filetime );
1906             status = SetFileTime( handle, NULL, NULL, &filetime );
1907
1908             CloseHandle( handle );
1909             return status;
1910         }
1911         break;
1912
1913     case 0x04: /* GET FILE LAST-WRITTEN DATE AND TIME */
1914         if (!islong)
1915             INT_BARF( context, 0x21 );
1916         else
1917         {
1918             TRACE( "GET FILE LAST-WRITTEN DATE AND TIME, file %s\n", fileA );
1919             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1920
1921             handle = CreateFileW( fileW, GENERIC_READ, 
1922                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1923                                   NULL, OPEN_EXISTING, 0, 0 );
1924             if (handle == INVALID_HANDLE_VALUE)
1925                 return FALSE;
1926
1927             status = GetFileTime( handle, NULL, NULL, &filetime );
1928             if (status)
1929             {
1930                 FileTimeToDosDateTime( &filetime, &date, &time );
1931                 SET_DI( context, date );
1932                 SET_CX( context, time );
1933             }
1934
1935             CloseHandle( handle );
1936             return status;
1937         }
1938         break;
1939
1940     case 0x05: /* SET FILE LAST ACCESS DATE */
1941         if (!islong)
1942             INT_BARF( context, 0x21 );
1943         else
1944         {
1945             TRACE( "SET FILE LAST ACCESS DATE, file %s\n", fileA );
1946             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1947
1948             handle = CreateFileW( fileW, GENERIC_WRITE, 
1949                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1950                                   NULL, OPEN_EXISTING, 0, 0 );
1951             if (handle == INVALID_HANDLE_VALUE)
1952                 return FALSE;
1953
1954             DosDateTimeToFileTime( DI_reg(context), 
1955                                    0,
1956                                    &filetime );
1957             status = SetFileTime( handle, NULL, &filetime, NULL );
1958
1959             CloseHandle( handle );
1960             return status;
1961         }
1962         break;
1963
1964     case 0x06: /* GET FILE LAST ACCESS DATE */
1965         if (!islong)
1966             INT_BARF( context, 0x21 );
1967         else
1968         {
1969             TRACE( "GET FILE LAST ACCESS DATE, file %s\n", fileA );
1970             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
1971
1972             handle = CreateFileW( fileW, GENERIC_READ, 
1973                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1974                                   NULL, OPEN_EXISTING, 0, 0 );
1975             if (handle == INVALID_HANDLE_VALUE)
1976                 return FALSE;
1977
1978             status = GetFileTime( handle, NULL, &filetime, NULL );
1979             if (status)
1980             {
1981                 FileTimeToDosDateTime( &filetime, &date, NULL );
1982                 SET_DI( context, date );
1983             }
1984
1985             CloseHandle( handle );
1986             return status;
1987         }
1988         break;
1989
1990     case 0x07: /* SET FILE CREATION DATE AND TIME */
1991         if (!islong)
1992             INT_BARF( context, 0x21 );
1993         else
1994         {
1995             TRACE( "SET FILE CREATION DATE AND TIME, file %s\n", fileA );
1996
1997             handle = CreateFileW( fileW, GENERIC_WRITE,
1998                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
1999                                   NULL, OPEN_EXISTING, 0, 0 );
2000             if (handle == INVALID_HANDLE_VALUE)
2001                 return FALSE;
2002             
2003             /*
2004              * FIXME: SI has number of 10-millisecond units past time in CX.
2005              */
2006             DosDateTimeToFileTime( DI_reg(context),
2007                                    CX_reg(context),
2008                                    &filetime );
2009             status = SetFileTime( handle, &filetime, NULL, NULL );
2010
2011             CloseHandle( handle );
2012             return status;
2013         }
2014         break;
2015
2016     case 0x08: /* GET FILE CREATION DATE AND TIME */
2017         if (!islong)
2018             INT_BARF( context, 0x21 );
2019         else
2020         {
2021             TRACE( "GET FILE CREATION DATE AND TIME, handle %d\n",
2022                    BX_reg(context) );
2023
2024             handle = CreateFileW( fileW, GENERIC_READ, 
2025                                   FILE_SHARE_READ | FILE_SHARE_WRITE, 
2026                                   NULL, OPEN_EXISTING, 0, 0 );
2027             if (handle == INVALID_HANDLE_VALUE)
2028                 return FALSE;
2029
2030             status = GetFileTime( handle, &filetime, NULL, NULL );
2031             if (status)
2032             {            
2033                 FileTimeToDosDateTime( &filetime, &date, &time );
2034                 SET_DI( context, date );
2035                 SET_CX( context, time );
2036                 /*
2037                  * FIXME: SI has number of 10-millisecond units past 
2038                  *        time in CX.
2039                  */
2040                 SET_SI( context, 0 );
2041             }
2042
2043             CloseHandle(handle);
2044             return status;
2045         }
2046         break;
2047
2048     case 0xff: /* EXTENDED-LENGTH FILENAME OPERATIONS */
2049         if (islong || context->Ebp != 0x5053)
2050             INT_BARF( context, 0x21 );
2051         else
2052         {
2053             switch(CL_reg(context))
2054             {
2055             case 0x39:
2056                 if (!INT21_CreateDirectory( context ))
2057                     return FALSE;
2058                 break;
2059
2060             case 0x56:
2061                 if (!INT21_RenameFile( context ))
2062                     return FALSE;
2063                 break;
2064
2065             default:
2066                 INT_BARF( context, 0x21 );
2067             }
2068         }
2069         break;
2070
2071     default:
2072         INT_BARF( context, 0x21 );
2073     }
2074
2075     return TRUE;
2076 }
2077
2078
2079 /***********************************************************************
2080  *           INT21_FileDateTime
2081  *
2082  * Handler for function 0x57.
2083  */
2084 static BOOL INT21_FileDateTime( CONTEXT86 *context )
2085 {
2086     HANDLE   handle = DosFileHandleToWin32Handle(BX_reg(context));
2087     FILETIME filetime;
2088     WORD     date, time;
2089
2090     switch (AL_reg(context)) {
2091     case 0x00:  /* Get last-written stamp */
2092         TRACE( "GET FILE LAST-WRITTEN DATE AND TIME, handle %d\n",
2093                BX_reg(context) );
2094         {
2095             if (!GetFileTime( handle, NULL, NULL, &filetime ))
2096                 return FALSE;
2097             FileTimeToDosDateTime( &filetime, &date, &time );
2098             SET_DX( context, date );
2099             SET_CX( context, time );
2100             break;
2101         }
2102
2103     case 0x01:  /* Set last-written stamp */
2104         TRACE( "SET FILE LAST-WRITTEN DATE AND TIME, handle %d\n",
2105                BX_reg(context) );
2106         {
2107             DosDateTimeToFileTime( DX_reg(context), 
2108                                    CX_reg(context),
2109                                    &filetime );
2110             if (!SetFileTime( handle, NULL, NULL, &filetime ))
2111                 return FALSE;
2112             break;
2113         }
2114
2115     case 0x04:  /* Get last access stamp, DOS 7 */
2116         TRACE( "GET FILE LAST ACCESS DATE AND TIME, handle %d\n",
2117                BX_reg(context) );
2118         {
2119             if (!GetFileTime( handle, NULL, &filetime, NULL ))
2120                 return FALSE;
2121             FileTimeToDosDateTime( &filetime, &date, &time );
2122             SET_DX( context, date );
2123             SET_CX( context, time );
2124             break;
2125         }
2126
2127     case 0x05:  /* Set last access stamp, DOS 7 */
2128         TRACE( "SET FILE LAST ACCESS DATE AND TIME, handle %d\n",
2129                BX_reg(context) );
2130         {
2131             DosDateTimeToFileTime( DX_reg(context), 
2132                                    CX_reg(context),
2133                                    &filetime );
2134             if (!SetFileTime( handle, NULL, &filetime, NULL ))
2135                 return FALSE;
2136             break;
2137         }
2138
2139     case 0x06:  /* Get creation stamp, DOS 7 */
2140         TRACE( "GET FILE CREATION DATE AND TIME, handle %d\n",
2141                BX_reg(context) );
2142         {
2143             if (!GetFileTime( handle, &filetime, NULL, NULL ))
2144                 return FALSE;
2145             FileTimeToDosDateTime( &filetime, &date, &time );
2146             SET_DX( context, date );
2147             SET_CX( context, time );
2148             /*
2149              * FIXME: SI has number of 10-millisecond units past time in CX.
2150              */
2151             SET_SI( context, 0 );
2152             break;
2153         }
2154
2155     case 0x07:  /* Set creation stamp, DOS 7 */
2156         TRACE( "SET FILE CREATION DATE AND TIME, handle %d\n",
2157                BX_reg(context) );
2158         {
2159             /*
2160              * FIXME: SI has number of 10-millisecond units past time in CX.
2161              */
2162             DosDateTimeToFileTime( DX_reg(context), 
2163                                    CX_reg(context),
2164                                    &filetime );
2165             if (!SetFileTime( handle, &filetime, NULL, NULL ))
2166                 return FALSE;
2167             break;
2168         }
2169
2170     default:
2171         INT_BARF( context, 0x21 );
2172         break;
2173     }
2174
2175     return TRUE;
2176 }
2177
2178
2179 /***********************************************************************
2180  *           INT21_GetPSP
2181  *
2182  * Handler for functions 0x51 and 0x62.
2183  */
2184 static void INT21_GetPSP( CONTEXT86 *context )
2185 {
2186     TRACE( "GET CURRENT PSP ADDRESS (%02x)\n", AH_reg(context) );
2187
2188     /*
2189      * FIXME: should we return the original DOS PSP upon
2190      *        Windows startup ? 
2191      */
2192     if (!ISV86(context) && DOSVM_IsWin16())
2193         SET_BX( context, LOWORD(GetCurrentPDB16()) );
2194     else
2195         SET_BX( context, DOSVM_psp );
2196 }
2197
2198 static inline void setword( BYTE *ptr, WORD w )
2199 {
2200     ptr[0] = (BYTE)w;
2201     ptr[1] = (BYTE)(w >> 8);
2202 }
2203
2204 static void CreateBPB(int drive, BYTE *data, BOOL16 limited)
2205 /* limited == TRUE is used with INT 0x21/0x440d */
2206 {
2207     /* FIXME: we're forcing some values without checking that those are valid */
2208     if (drive > 1) 
2209     {
2210         setword(data, 512);
2211         data[2] = 2;
2212         setword(&data[3], 0);
2213         data[5] = 2;
2214         setword(&data[6], 240);
2215         setword(&data[8], 64000);
2216         data[0x0a] = 0xf8;
2217         setword(&data[0x0b], 40);
2218         setword(&data[0x0d], 56);
2219         setword(&data[0x0f], 2);
2220         setword(&data[0x11], 0);
2221         if (!limited) 
2222         {
2223             setword(&data[0x1f], 800);
2224             data[0x21] = 5;
2225             setword(&data[0x22], 1);
2226         }
2227     }
2228     else
2229     { /* 1.44mb */
2230         setword(data, 512);
2231         data[2] = 2;
2232         setword(&data[3], 0);
2233         data[5] = 2;
2234         setword(&data[6], 240);
2235         setword(&data[8], 2880);
2236         data[0x0a] = 0xf8;
2237         setword(&data[0x0b], 6);
2238         setword(&data[0x0d], 18);
2239         setword(&data[0x0f], 2);
2240         setword(&data[0x11], 0);
2241         if (!limited) 
2242         {
2243             setword(&data[0x1f], 80);
2244             data[0x21] = 7;
2245             setword(&data[0x22], 2);
2246         }
2247     }
2248 }
2249
2250 /***********************************************************************
2251  *           INT21_Ioctl_Block
2252  *
2253  * Handler for block device IOCTLs.
2254  */
2255 static void INT21_Ioctl_Block( CONTEXT86 *context )
2256 {
2257     BYTE *dataptr;
2258     BYTE  drive = INT21_MapDrive( BL_reg(context) );
2259     WCHAR drivespec[4] = {'A', ':', '\\', 0};
2260     UINT  drivetype;
2261
2262     drivespec[0] += drive;
2263     drivetype = GetDriveTypeW( drivespec );
2264
2265     RESET_CFLAG(context);
2266     if (drivetype == DRIVE_UNKNOWN || drivetype == DRIVE_NO_ROOT_DIR)
2267     {
2268         TRACE( "IOCTL - SUBFUNCTION %d - INVALID DRIVE %c:\n", 
2269                AL_reg(context), 'A' + drive );
2270         SetLastError( ERROR_INVALID_DRIVE );
2271         SET_AX( context, ERROR_INVALID_DRIVE );
2272         SET_CFLAG( context );
2273         return;
2274     }
2275
2276     switch (AL_reg(context))
2277     {
2278     case 0x04: /* READ FROM BLOCK DEVICE CONTROL CHANNEL */
2279     case 0x05: /* WRITE TO BLOCK DEVICE CONTROL CHANNEL */
2280         INT_BARF( context, 0x21 );
2281         break;
2282
2283     case 0x08: /* CHECK IF BLOCK DEVICE REMOVABLE */
2284         TRACE( "IOCTL - CHECK IF BLOCK DEVICE REMOVABLE - %c:\n",
2285                'A' + drive );
2286
2287         if (drivetype == DRIVE_REMOVABLE)
2288             SET_AX( context, 0 ); /* removable */
2289         else
2290             SET_AX( context, 1 ); /* not removable */
2291         break;
2292
2293     case 0x09: /* CHECK IF BLOCK DEVICE REMOTE */
2294         TRACE( "IOCTL - CHECK IF BLOCK DEVICE REMOTE - %c:\n",
2295                'A' + drive );
2296
2297         if (drivetype == DRIVE_REMOTE)
2298             SET_DX( context, (1<<9) | (1<<12) ); /* remote + no direct IO */
2299         else
2300             SET_DX( context, 0 ); /* FIXME: use driver attr here */
2301         break;
2302
2303     case 0x0d: /* GENERIC BLOCK DEVICE REQUEST */
2304         /* Get pointer to IOCTL parameter block. */
2305         dataptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
2306
2307         switch (CX_reg(context))
2308         {
2309         case 0x0841: /* write logical device track */
2310             TRACE( "GENERIC IOCTL - Write logical device track - %c:\n",
2311                    'A' + drive);
2312             {
2313                 WORD head   = *(WORD *)dataptr+1;
2314                 WORD cyl    = *(WORD *)dataptr+3;
2315                 WORD sect   = *(WORD *)dataptr+5;
2316                 WORD nrsect = *(WORD *)dataptr+7;
2317                 BYTE *data  =  (BYTE *)dataptr+9; /* FIXME: is this correct? */
2318
2319                 if (!DOSVM_RawWrite(drive, head*cyl*sect, nrsect, data, FALSE))
2320                 {
2321                     SET_AX( context, ERROR_WRITE_FAULT );
2322                     SET_CFLAG(context);
2323                 }
2324             }
2325             break;
2326
2327         case 0x084a: /* lock logical volume */
2328             TRACE( "GENERIC IOCTL - Lock logical volume, level %d mode %d - %c:\n",
2329                    BH_reg(context), DX_reg(context), 'A' + drive );
2330             break;
2331
2332         case 0x0860: /* get device parameters */
2333             /* FIXME: we're faking some values here */
2334             /* used by w4wgrp's winfile */
2335             memset(dataptr, 0, 0x20); /* DOS 6.22 uses 0x20 bytes */
2336             dataptr[0] = 0x04;
2337             dataptr[6] = 0; /* media type */
2338             if (drive > 1)
2339             {
2340                 dataptr[1] = 0x05; /* fixed disk */
2341                 setword(&dataptr[2], 0x01); /* non removable */
2342                 setword(&dataptr[4], 0x300); /* # of cylinders */
2343             }
2344             else
2345             {
2346                 dataptr[1] = 0x07; /* block dev, floppy */
2347                 setword(&dataptr[2], 0x02); /* removable */
2348                 setword(&dataptr[4], 80); /* # of cylinders */
2349             }
2350             CreateBPB(drive, &dataptr[7], TRUE);
2351             RESET_CFLAG(context);
2352             break;
2353
2354         case 0x0861: /* read logical device track */
2355             TRACE( "GENERIC IOCTL - Read logical device track - %c:\n",
2356                    'A' + drive);
2357             {
2358                 WORD head   = *(WORD *)dataptr+1;
2359                 WORD cyl    = *(WORD *)dataptr+3;
2360                 WORD sect   = *(WORD *)dataptr+5;
2361                 WORD nrsect = *(WORD *)dataptr+7;
2362                 BYTE *data  =  (BYTE *)dataptr+9; /* FIXME: is this correct? */
2363
2364                 if (!DOSVM_RawRead(drive, head*cyl*sect, nrsect, data, FALSE))
2365                 {
2366                     SET_AX( context, ERROR_READ_FAULT );
2367                     SET_CFLAG(context);
2368                 }
2369             }
2370             break;
2371
2372         case 0x0866: /* get volume serial number */
2373             {
2374                 WCHAR   label[12],fsname[9];
2375                 DWORD   serial;
2376
2377                 drivespec[0] += drive;
2378                 GetVolumeInformationW(drivespec, label, 12, &serial, NULL, NULL, fsname, 9);
2379                 *(WORD*)dataptr = 0;
2380                 memcpy(dataptr+2,&serial,4);
2381                 WideCharToMultiByte(CP_OEMCP, 0, label, 11, dataptr + 6, 11, NULL, NULL);
2382                 WideCharToMultiByte(CP_OEMCP, 0, fsname, 8, dataptr + 17, 8, NULL, NULL);
2383             }
2384             break;
2385
2386         case 0x086a: /* unlock logical volume */
2387             TRACE( "GENERIC IOCTL - Logical volume unlocked - %c:\n", 
2388                    'A' + drive );
2389             break;
2390
2391         case 0x086f: /* get drive map information */
2392             memset(dataptr+1, '\0', dataptr[0]-1);
2393             dataptr[1] = dataptr[0];
2394             dataptr[2] = 0x07; /* protected mode driver; no eject; no notification */
2395             dataptr[3] = 0xFF; /* no physical drive */
2396             break;
2397
2398         case 0x0872:
2399             /* Trail on error implementation */
2400             SET_AX( context, drivetype == DRIVE_UNKNOWN ? 0x0f : 0x01 );
2401             SET_CFLAG(context); /* Seems to be set all the time */
2402             break;
2403
2404         default:
2405             INT_BARF( context, 0x21 );            
2406         }
2407         break;
2408
2409     case 0x0e: /* GET LOGICAL DRIVE MAP */
2410         TRACE( "IOCTL - GET LOGICAL DRIVE MAP - %c:\n",
2411                'A' + drive );
2412         /* FIXME: this is not correct if drive has mappings */
2413         SET_AL( context, 0 ); /* drive has no mapping */
2414         break;
2415
2416     case 0x0f: /* SET LOGICAL DRIVE MAP */
2417         {
2418             WCHAR dev[3], tgt[4];
2419
2420             TRACE("IOCTL - SET LOGICAL DRIVE MAP for drive %s\n",
2421                   INT21_DriveName( BL_reg(context)));
2422             dev[0] = 'A' + drive; dev[1] = ':'; dev[2] = 0;
2423             tgt[0] = 'A' + drive + 1; dev[1] = ':'; dev[2] = '\\'; dev[3] = 0;
2424             if (!DefineDosDeviceW(DDD_RAW_TARGET_PATH, dev, tgt))
2425             {
2426                 SET_CFLAG(context);
2427                 SET_AX( context, 0x000F );  /* invalid drive */
2428             }
2429         }
2430         break;
2431
2432     case 0x11: /* QUERY GENERIC IOCTL CAPABILITY */
2433     default:
2434         INT_BARF( context, 0x21 );
2435     }
2436 }
2437
2438
2439 /***********************************************************************
2440  *           INT21_Ioctl_Char
2441  *
2442  * Handler for character device IOCTLs.
2443  */
2444 static void INT21_Ioctl_Char( CONTEXT86 *context )
2445 {
2446     static const WCHAR emmxxxx0W[] = {'E','M','M','X','X','X','X','0',0};
2447     static const WCHAR scsimgrW[] = {'S','C','S','I','M','G','R','$',0};
2448
2449     HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
2450     const DOS_DEVICE *dev = DOSFS_GetDeviceByHandle(handle);
2451
2452     if (dev && !strcmpiW( dev->name, emmxxxx0W )) 
2453     {
2454         EMS_Ioctl_Handler(context);
2455         return;
2456     }
2457
2458     if (dev && !strcmpiW( dev->name, scsimgrW ) && AL_reg(context) == 2)
2459     {
2460         DOSVM_ASPIHandler(context);
2461         return;
2462     }
2463
2464     switch (AL_reg(context))
2465     {
2466     case 0x00: /* GET DEVICE INFORMATION */
2467         TRACE( "IOCTL - GET DEVICE INFORMATION - %d\n", BX_reg(context) );
2468         if (dev)
2469         {
2470             /*
2471              * Returns attribute word in DX: 
2472              *   Bit 14 - Device driver can process IOCTL requests.
2473              *   Bit 13 - Output until busy supported.
2474              *   Bit 11 - Driver supports OPEN/CLOSE calls.
2475              *   Bit  8 - Unknown.
2476              *   Bit  7 - Set (indicates device).
2477              *   Bit  6 - EOF on input.
2478              *   Bit  5 - Raw (binary) mode.
2479              *   Bit  4 - Device is special (uses int29).
2480              *   Bit  3 - Clock device.
2481              *   Bit  2 - NUL device.
2482              *   Bit  1 - Standard output.
2483              *   Bit  0 - Standard input.
2484              */
2485             SET_DX( context, dev->flags );
2486         }
2487         else
2488         {
2489             /*
2490              * Returns attribute word in DX: 
2491              *   Bit 15    - File is remote.
2492              *   Bit 14    - Don't set file date/time on closing.
2493              *   Bit 11    - Media not removable.
2494              *   Bit  8    - Generate int24 if no disk space on write 
2495              *               or read past end of file
2496              *   Bit  7    - Clear (indicates file).
2497              *   Bit  6    - File has not been written.
2498              *   Bit  5..0 - Drive number (0=A:,...)
2499              *
2500              * FIXME: Should check if file is on remote or removable drive.
2501              * FIXME: Should use drive file is located on (and not current).
2502              */
2503             SET_DX( context, 0x0140 + INT21_GetCurrentDrive() );
2504         }
2505         break;
2506
2507     case 0x01: /* SET DEVICE INFORMATION */
2508     case 0x02: /* READ FROM CHARACTER DEVICE CONTROL CHANNEL */
2509     case 0x03: /* WRITE TO CHARACTER DEVICE CONTROL CHANNEL */
2510     case 0x06: /* GET INPUT STATUS */
2511     case 0x07: /* GET OUTPUT STATUS */
2512         INT_BARF( context, 0x21 );
2513         break;
2514
2515     case 0x0a: /* CHECK IF HANDLE IS REMOTE */
2516         TRACE( "IOCTL - CHECK IF HANDLE IS REMOTE - %d\n", BX_reg(context) );
2517         /*
2518          * Returns attribute word in DX:
2519          *   Bit 15 - Set if remote.
2520          *   Bit 14 - Set if date/time not set on close.
2521          *
2522          * FIXME: Should check if file is on remote drive.
2523          */
2524         SET_DX( context, 0 );
2525         break;
2526
2527     case 0x0c: /* GENERIC CHARACTER DEVICE REQUEST */
2528     case 0x10: /* QUERY GENERIC IOCTL CAPABILITY */
2529     default:
2530         INT_BARF( context, 0x21 );
2531     }
2532 }
2533
2534
2535 /***********************************************************************
2536  *           INT21_Ioctl
2537  *
2538  * Handler for function 0x44.
2539  */
2540 static void INT21_Ioctl( CONTEXT86 *context )
2541 {
2542     switch (AL_reg(context))
2543     {
2544     case 0x00:
2545     case 0x01:
2546     case 0x02:
2547     case 0x03:
2548         INT21_Ioctl_Char( context );
2549         break;
2550
2551     case 0x04:
2552     case 0x05:
2553         INT21_Ioctl_Block( context );
2554         break;
2555
2556     case 0x06:
2557     case 0x07:
2558         INT21_Ioctl_Char( context );
2559         break;
2560
2561     case 0x08:
2562     case 0x09:
2563         INT21_Ioctl_Block( context );
2564         break;
2565
2566     case 0x0a:
2567         INT21_Ioctl_Char( context );
2568         break;
2569
2570     case 0x0b: /* SET SHARING RETRY COUNT */
2571         TRACE( "SET SHARING RETRY COUNT: Pause %d, retries %d.\n",
2572                CX_reg(context), DX_reg(context) );
2573         if (!CX_reg(context))
2574         {
2575             SET_AX( context, 1 );
2576             SET_CFLAG( context );
2577         }
2578         else
2579         {
2580             DOSDEV_SetSharingRetry( CX_reg(context), DX_reg(context) );
2581             RESET_CFLAG( context );
2582         }
2583         break;
2584
2585     case 0x0c:
2586         INT21_Ioctl_Char( context );
2587         break;
2588
2589     case 0x0d:
2590     case 0x0e:
2591     case 0x0f:
2592         INT21_Ioctl_Block( context );
2593         break;
2594
2595     case 0x10:
2596         INT21_Ioctl_Char( context );
2597         break;
2598
2599     case 0x11:
2600         INT21_Ioctl_Block( context );
2601         break;
2602
2603     case 0x12: /*  DR DOS - DETERMINE DOS TYPE (OBSOLETE FUNCTION) */
2604         TRACE( "DR DOS - DETERMINE DOS TYPE (OBSOLETE FUNCTION)\n" );
2605         SET_CFLAG(context);        /* Error / This is not DR DOS. */
2606         SET_AX( context, 0x0001 ); /* Invalid function */
2607         break;
2608
2609     case 0x52: /* DR DOS - DETERMINE DOS TYPE */
2610         TRACE( "DR DOS - DETERMINE DOS TYPE\n" );
2611         SET_CFLAG(context);        /* Error / This is not DR DOS. */
2612         SET_AX( context, 0x0001 ); /* Invalid function */
2613         break;
2614
2615     case 0xe0:  /* Sun PC-NFS API */
2616         TRACE( "Sun PC-NFS API\n" );
2617         /* not installed */
2618         break;
2619
2620     default:
2621         INT_BARF( context, 0x21 );
2622     }
2623 }
2624
2625
2626 /***********************************************************************
2627  *           INT21_Fat32
2628  *
2629  * Handler for function 0x73.
2630  */
2631 static BOOL INT21_Fat32( CONTEXT86 *context )
2632 {
2633     switch (AL_reg(context))
2634     {
2635     case 0x02: /* FAT32 - GET EXTENDED DPB */
2636         {
2637             BYTE drive = INT21_MapDrive( DL_reg(context) );
2638             WORD *ptr = CTX_SEG_OFF_TO_LIN(context, 
2639                                            context->SegEs, context->Edi);
2640             INT21_DPB *target = (INT21_DPB*)(ptr + 1);
2641             INT21_DPB *source;
2642
2643             TRACE( "FAT32 - GET EXTENDED DPB %d\n", DL_reg(context) );
2644
2645             if ( CX_reg(context) < sizeof(INT21_DPB) + 2 || *ptr < sizeof(INT21_DPB) )
2646             {
2647                 SetLastError( ERROR_BAD_LENGTH );
2648                 return FALSE;
2649             }
2650
2651             if ( !INT21_FillDrivePB( drive ) )
2652             {
2653                 SetLastError( ERROR_INVALID_DRIVE );
2654                 return FALSE;
2655             }
2656
2657             source = &INT21_GetHeapPointer()->misc_dpb_list[drive];
2658
2659             *ptr = sizeof(INT21_DPB);
2660             memcpy( target, source, sizeof(INT21_DPB));
2661
2662             if (LOWORD(context->Esi) != 0xF1A6)
2663             {
2664                 target->driver_header = 0;
2665                 target->next          = 0;
2666             }
2667             else
2668             {
2669                 FIXME( "Caller requested driver and next DPB pointers!\n" );
2670             }
2671         }
2672         break;
2673
2674     case 0x03: /* FAT32 - GET EXTENDED FREE SPACE ON DRIVE */
2675         {
2676             WCHAR dirW[MAX_PATH];
2677             char *dirA = CTX_SEG_OFF_TO_LIN( context,
2678                                              context->SegDs, context->Edx );
2679             BYTE *data = CTX_SEG_OFF_TO_LIN( context, 
2680                                              context->SegEs, context->Edi );
2681             DWORD cluster_sectors;
2682             DWORD sector_bytes;
2683             DWORD free_clusters;
2684             DWORD total_clusters;
2685
2686             TRACE( "FAT32 - GET EXTENDED FREE SPACE ON DRIVE %s\n", dirA );
2687             MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
2688
2689             if (CX_reg(context) < 44)
2690             {
2691                 SetLastError( ERROR_BAD_LENGTH );
2692                 return FALSE;
2693             }
2694
2695             if (!GetDiskFreeSpaceW( dirW, &cluster_sectors, &sector_bytes,
2696                                     &free_clusters, &total_clusters ))
2697                 return FALSE;
2698
2699             *(WORD*) (data +  0) = 44; /* size of structure */
2700             *(WORD*) (data +  2) = 0;  /* version */
2701             *(DWORD*)(data +  4) = cluster_sectors;
2702             *(DWORD*)(data +  8) = sector_bytes;
2703             *(DWORD*)(data + 12) = free_clusters;
2704             *(DWORD*)(data + 16) = total_clusters;
2705
2706             /*
2707              * Below we have free/total sectors and
2708              * free/total allocation units without adjustment
2709              * for compression. We fake both using cluster information.
2710              */
2711             *(DWORD*)(data + 20) = free_clusters * cluster_sectors;
2712             *(DWORD*)(data + 24) = total_clusters * cluster_sectors;
2713             *(DWORD*)(data + 28) = free_clusters;
2714             *(DWORD*)(data + 32) = total_clusters;
2715             
2716             /*
2717              * Between (data + 36) and (data + 43) there
2718              * are eight reserved bytes.
2719              */
2720         }
2721         break;
2722
2723     default:
2724         INT_BARF( context, 0x21 );
2725     }
2726
2727     return TRUE;
2728 }
2729
2730 static void INT21_ConvertFindDataWtoA(WIN32_FIND_DATAA *dataA,
2731                                       const WIN32_FIND_DATAW *dataW)
2732 {
2733     dataA->dwFileAttributes = dataW->dwFileAttributes;
2734     dataA->ftCreationTime   = dataW->ftCreationTime;
2735     dataA->ftLastAccessTime = dataW->ftLastAccessTime;
2736     dataA->ftLastWriteTime  = dataW->ftLastWriteTime;
2737     dataA->nFileSizeHigh    = dataW->nFileSizeHigh;
2738     dataA->nFileSizeLow     = dataW->nFileSizeLow;
2739     WideCharToMultiByte( CP_OEMCP, 0, dataW->cFileName, -1,
2740                          dataA->cFileName, sizeof(dataA->cFileName), NULL, NULL );
2741     WideCharToMultiByte( CP_OEMCP, 0, dataW->cAlternateFileName, -1,
2742                          dataA->cAlternateFileName, sizeof(dataA->cAlternateFileName), NULL, NULL );
2743 }
2744
2745 /***********************************************************************
2746  *           INT21_LongFilename
2747  *
2748  * Handler for function 0x71.
2749  */
2750 static void INT21_LongFilename( CONTEXT86 *context )
2751 {
2752     BOOL bSetDOSExtendedError = FALSE;
2753     WCHAR pathW[MAX_PATH];
2754     char* pathA;
2755
2756     if (HIBYTE(HIWORD(GetVersion16())) < 0x07)
2757     {
2758         TRACE( "LONG FILENAME - functions supported only under DOS7\n" );
2759         SET_CFLAG( context );
2760         SET_AL( context, 0 );
2761         return;
2762     }
2763
2764     switch (AL_reg(context))
2765     {
2766     case 0x0d: /* RESET DRIVE */
2767         INT_BARF( context, 0x21 );
2768         break;
2769
2770     case 0x39: /* LONG FILENAME - MAKE DIRECTORY */
2771         if (!INT21_CreateDirectory( context ))
2772             bSetDOSExtendedError = TRUE;
2773         break;
2774
2775     case 0x3a: /* LONG FILENAME - REMOVE DIRECTORY */
2776         pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
2777
2778         TRACE( "LONG FILENAME - REMOVE DIRECTORY %s\n", pathA);
2779         MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
2780         if (!RemoveDirectoryW( pathW )) bSetDOSExtendedError = TRUE;
2781         break;
2782
2783     case 0x3b: /* LONG FILENAME - CHANGE DIRECTORY */
2784         if (!INT21_SetCurrentDirectory( context ))
2785             bSetDOSExtendedError = TRUE;
2786         break;
2787
2788     case 0x41: /* LONG FILENAME - DELETE FILE */
2789         pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
2790
2791         TRACE( "LONG FILENAME - DELETE FILE %s\n", pathA );
2792         MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
2793
2794         if (!DeleteFileW( pathW )) bSetDOSExtendedError = TRUE;
2795         break;
2796
2797     case 0x43: /* LONG FILENAME - EXTENDED GET/SET FILE ATTRIBUTES */
2798         if (!INT21_FileAttributes( context, BL_reg(context), TRUE ))
2799             bSetDOSExtendedError = TRUE;
2800         break;
2801
2802     case 0x47: /* LONG FILENAME - GET CURRENT DIRECTORY */
2803         if (!INT21_GetCurrentDirectory( context, TRUE ))
2804             bSetDOSExtendedError = TRUE;
2805         break;
2806
2807     case 0x4e: /* LONG FILENAME - FIND FIRST MATCHING FILE */
2808         {
2809             HANDLE              handle;
2810             HGLOBAL16           h16;
2811             WIN32_FIND_DATAW    dataW;
2812             WIN32_FIND_DATAA*   dataA;
2813
2814             pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs,context->Edx);
2815             TRACE(" LONG FILENAME - FIND FIRST MATCHING FILE for %s\n", pathA);
2816
2817             MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
2818             handle = FindFirstFileW(pathW, &dataW);
2819             
2820             dataA = (WIN32_FIND_DATAA *)CTX_SEG_OFF_TO_LIN(context, context->SegEs,
2821                                                            context->Edi);
2822             if (handle != INVALID_HANDLE_VALUE && 
2823                 (h16 = GlobalAlloc16(GMEM_MOVEABLE, sizeof(handle))))
2824             {
2825                 HANDLE* ptr = GlobalLock16( h16 );
2826                 *ptr = handle;
2827                 GlobalUnlock16( h16 );
2828                 SET_AX( context, h16 );
2829                 INT21_ConvertFindDataWtoA(dataA, &dataW);
2830             }
2831             else
2832             {           
2833                 if (handle != INVALID_HANDLE_VALUE) FindClose(handle);
2834                 SET_AX( context, INVALID_HANDLE_VALUE16);
2835                 bSetDOSExtendedError = TRUE;
2836             }
2837         }
2838         break;
2839
2840     case 0x4f: /* LONG FILENAME - FIND NEXT MATCHING FILE */
2841         {
2842             HGLOBAL16           h16 = BX_reg(context);
2843             HANDLE*             ptr;
2844             WIN32_FIND_DATAW    dataW;
2845             WIN32_FIND_DATAA*   dataA;
2846
2847             TRACE("LONG FILENAME - FIND NEXT MATCHING FILE for handle %d\n",
2848                   BX_reg(context));
2849
2850             dataA = (WIN32_FIND_DATAA *)CTX_SEG_OFF_TO_LIN(context, context->SegEs,
2851                                                            context->Edi);
2852
2853             if (h16 != INVALID_HANDLE_VALUE16 && (ptr = GlobalLock16( h16 )))
2854             {
2855                 if (!FindNextFileW(*ptr, &dataW)) bSetDOSExtendedError = TRUE;
2856                 else INT21_ConvertFindDataWtoA(dataA, &dataW);
2857                 GlobalUnlock16( h16 );
2858             }
2859             else
2860             {
2861                 SetLastError( ERROR_INVALID_HANDLE );
2862                 bSetDOSExtendedError = TRUE;
2863             }
2864         }
2865         break;
2866
2867     case 0x56: /* LONG FILENAME - RENAME FILE */
2868         if (!INT21_RenameFile(context))
2869             bSetDOSExtendedError = TRUE;
2870         break;
2871
2872     case 0x60: /* LONG FILENAME - CONVERT PATH */
2873         {
2874             WCHAR   res[MAX_PATH];
2875
2876             switch (CL_reg(context))
2877             {
2878             case 0x01:  /* Get short filename or path */
2879                 MultiByteToWideChar(CP_OEMCP, 0, CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi), -1, pathW, MAX_PATH);
2880                 if (!GetShortPathNameW(pathW, res, 67))
2881                     bSetDOSExtendedError = TRUE;
2882                 else
2883                 {
2884                     SET_AX( context, 0 );
2885                     WideCharToMultiByte(CP_OEMCP, 0, res, -1, 
2886                                         CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi), 
2887                                         67, NULL, NULL);
2888                 }
2889                 break;
2890             
2891             case 0x02:  /* Get canonical long filename or path */
2892                 MultiByteToWideChar(CP_OEMCP, 0, CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi), -1, pathW, MAX_PATH);
2893                 if (!GetFullPathNameW(pathW, 128, res, NULL))
2894                     bSetDOSExtendedError = TRUE;
2895                 else
2896                 {
2897                     SET_AX( context, 0 );
2898                     WideCharToMultiByte(CP_OEMCP, 0, res, -1, 
2899                                         CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi), 
2900                                         128, NULL, NULL);
2901                 }
2902                 break;
2903             default:
2904                 FIXME("Unimplemented long file name function:\n");
2905                 INT_BARF( context, 0x21 );
2906                 SET_CFLAG(context);
2907                 SET_AL( context, 0 );
2908                 break;
2909             }
2910         }
2911         break;
2912
2913     case 0x6c: /* LONG FILENAME - CREATE OR OPEN FILE */
2914         if (!INT21_CreateFile( context, context->Esi, TRUE,
2915                                BX_reg(context), DL_reg(context) ))
2916             bSetDOSExtendedError = TRUE;
2917         break;
2918
2919     case 0xa0: /* LONG FILENAME - GET VOLUME INFORMATION */
2920         {
2921             DWORD filename_len, flags;
2922             WCHAR dstW[8];
2923
2924             pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs,context->Edx);
2925
2926             TRACE("LONG FILENAME - GET VOLUME INFORMATION for drive having root dir '%s'.\n", pathA);
2927             SET_AX( context, 0 );
2928             MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
2929             if (!GetVolumeInformationW( pathW, NULL, 0, NULL, &filename_len,
2930                                         &flags, dstW, 8 ))
2931             {
2932                 INT_BARF( context, 0x21 );
2933                 SET_CFLAG(context);
2934                 break;
2935             }
2936             SET_BX( context, flags | 0x4000 ); /* support for LFN functions */
2937             SET_CX( context, filename_len );
2938             SET_DX( context, MAX_PATH ); /* FIXME: which len if DRIVE_SHORT_NAMES ? */
2939             WideCharToMultiByte(CP_OEMCP, 0, dstW, -1, 
2940                                 CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi), 
2941                                 8, NULL, NULL);
2942         }
2943         break;
2944
2945     case 0xa1: /* LONG FILENAME - "FindClose" - TERMINATE DIRECTORY SEARCH */
2946         {
2947             HGLOBAL16 h16 = BX_reg(context);
2948             HANDLE* ptr;
2949
2950             TRACE("LONG FILENAME - FINDCLOSE for handle %d\n",
2951                   BX_reg(context));
2952             if (h16 != INVALID_HANDLE_VALUE16 && (ptr = GlobalLock16( h16 )))
2953             {
2954                 if (!FindClose( *ptr )) bSetDOSExtendedError = TRUE;
2955                 GlobalUnlock16( h16 );
2956                 GlobalFree16( h16 );
2957             }
2958             else
2959             {
2960                 SetLastError( ERROR_INVALID_HANDLE );
2961                 bSetDOSExtendedError = TRUE;
2962             }
2963         }
2964         break;
2965           
2966     case 0xa6: /* LONG FILENAME - GET FILE INFO BY HANDLE */
2967         {
2968             HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
2969             BY_HANDLE_FILE_INFORMATION *info =
2970                 CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
2971             
2972             TRACE( "LONG FILENAME - GET FILE INFO BY HANDLE\n" );
2973             
2974             if (!GetFileInformationByHandle(handle, info))
2975                 bSetDOSExtendedError = TRUE;
2976         }
2977         break;
2978
2979     case 0xa7: /* LONG FILENAME - CONVERT TIME */
2980         switch (BL_reg(context))
2981         {
2982         case 0x00: /* FILE TIME TO DOS TIME */
2983             {
2984                 WORD      date, time;
2985                 FILETIME *filetime = CTX_SEG_OFF_TO_LIN(context,
2986                                                         context->SegDs,
2987                                                         context->Esi);
2988
2989                 TRACE( "LONG FILENAME - FILE TIME TO DOS TIME\n" );
2990
2991                 FileTimeToDosDateTime( filetime, &date, &time );
2992
2993                 SET_DX( context, date );
2994                 SET_CX( context, time );
2995
2996                 /*
2997                  * FIXME: BH has number of 10-millisecond units 
2998                  * past time in CX.
2999                  */
3000                 SET_BH( context, 0 );
3001             }
3002             break;
3003
3004         case 0x01: /* DOS TIME TO FILE TIME */
3005             {
3006                 FILETIME *filetime = CTX_SEG_OFF_TO_LIN(context,
3007                                                         context->SegEs,
3008                                                         context->Edi);
3009
3010                 TRACE( "LONG FILENAME - DOS TIME TO FILE TIME\n" );
3011
3012                 /*
3013                  * FIXME: BH has number of 10-millisecond units 
3014                  * past time in CX.
3015                  */
3016                 DosDateTimeToFileTime( DX_reg(context), CX_reg(context),
3017                                        filetime );
3018             }
3019             break;
3020
3021         default:
3022             INT_BARF( context, 0x21 );
3023             break;
3024         }
3025         break;
3026
3027     case 0xa8: /* LONG FILENAME - GENERATE SHORT FILENAME */
3028     case 0xa9: /* LONG FILENAME - SERVER CREATE OR OPEN FILE */
3029     case 0xaa: /* LONG FILENAME - SUBST */
3030     default:
3031         FIXME("Unimplemented long file name function:\n");
3032         INT_BARF( context, 0x21 );
3033         SET_CFLAG(context);
3034         SET_AL( context, 0 );
3035         break;
3036     }
3037
3038     if (bSetDOSExtendedError)
3039     {
3040         SET_AX( context, GetLastError() );
3041         SET_CFLAG( context );
3042     }
3043 }
3044
3045
3046 /***********************************************************************
3047  *           INT21_RenameFile
3048  *
3049  * Handler for:
3050  * - function 0x56
3051  * - subfunction 0x56 of function 0x71
3052  * - subfunction 0xff of function 0x43 (CL == 0x56)
3053  */
3054 static BOOL INT21_RenameFile( CONTEXT86 *context )
3055 {
3056     WCHAR fromW[MAX_PATH];
3057     WCHAR toW[MAX_PATH];
3058     char *fromA = CTX_SEG_OFF_TO_LIN(context, 
3059                                      context->SegDs,context->Edx);
3060     char *toA = CTX_SEG_OFF_TO_LIN(context, 
3061                                    context->SegEs,context->Edi);
3062
3063     TRACE( "RENAME FILE %s to %s\n", fromA, toA );
3064     MultiByteToWideChar(CP_OEMCP, 0, fromA, -1, fromW, MAX_PATH);
3065     MultiByteToWideChar(CP_OEMCP, 0, toA, -1, toW, MAX_PATH);
3066
3067     return MoveFileW( fromW, toW );
3068 }
3069
3070
3071 /***********************************************************************
3072  *           INT21_NetworkFunc
3073  *
3074  * Handler for:
3075  * - function 0x5e
3076  */
3077 static BOOL INT21_NetworkFunc (CONTEXT86 *context)
3078 {
3079     switch (AL_reg(context)) 
3080     {
3081     case 0x00: /* Get machine name. */
3082         {
3083             WCHAR dstW[MAX_COMPUTERNAME_LENGTH + 1];
3084             DWORD s = sizeof(dstW) / sizeof(WCHAR);
3085             int len;
3086
3087             char *dst = CTX_SEG_OFF_TO_LIN (context,context->SegDs,context->Edx);
3088             TRACE("getting machine name to %p\n", dst);
3089             if (!GetComputerNameW(dstW, &s) ||
3090                 !WideCharToMultiByte(CP_OEMCP, 0, dstW, -1, dst, 16, NULL, NULL))
3091             {
3092                 WARN("failed!\n");
3093                 SetLastError( ER_NoNetwork );
3094                 return TRUE;
3095             }
3096             for (len = strlen(dst); len < 15; len++) dst[len] = ' ';
3097             dst[15] = 0;
3098             SET_CH( context, 1 ); /* Valid */
3099             SET_CL( context, 1 ); /* NETbios number??? */
3100             TRACE("returning %s\n", debugstr_an(dst, 16));
3101             return FALSE;
3102         }
3103
3104     default:
3105         SetLastError( ER_NoNetwork );
3106         return TRUE;
3107     }
3108 }
3109
3110 /******************************************************************
3111  *              INT21_GetDiskSerialNumber
3112  *
3113  */
3114 static int INT21_GetDiskSerialNumber( CONTEXT86 *context )
3115 {
3116     BYTE *dataptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3117     WCHAR path[] = {'A',':',0}, label[11];
3118     DWORD serial;
3119
3120     path[0] += INT21_MapDrive(BL_reg(context));
3121     if (!GetVolumeInformationW( path, label, 11, &serial, NULL, NULL, NULL, 0))
3122     {
3123         SetLastError( ERROR_INVALID_DRIVE );
3124         return 0;
3125     }
3126
3127     *(WORD *)dataptr = 0;
3128     memcpy(dataptr + 2, &serial, sizeof(DWORD));
3129     WideCharToMultiByte(CP_OEMCP, 0, label, 11, dataptr + 6, 11, NULL, NULL);
3130     strncpy(dataptr + 17, "FAT16   ", 8);
3131     return 1;
3132 }
3133
3134
3135 /******************************************************************
3136  *              INT21_SetDiskSerialNumber
3137  *
3138  */
3139 static int INT21_SetDiskSerialNumber( CONTEXT86 *context )
3140 {
3141 #if 0
3142     BYTE *dataptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3143     int drive = INT21_MapDrive(BL_reg(context));
3144
3145     if (!is_valid_drive(drive))
3146     {
3147         SetLastError( ERROR_INVALID_DRIVE );
3148         return 0;
3149     }
3150
3151     DRIVE_SetSerialNumber( drive, *(DWORD *)(dataptr + 2) );
3152     return 1;
3153 #else
3154     FIXME("Setting drive serial number is no longer supported\n");
3155     SetLastError( ERROR_NOT_SUPPORTED );
3156     return 0;
3157 #endif
3158 }
3159
3160
3161 /******************************************************************
3162  *              INT21_GetFreeDiskSpace
3163  *
3164  */
3165 static int INT21_GetFreeDiskSpace( CONTEXT86 *context )
3166 {
3167     DWORD cluster_sectors, sector_bytes, free_clusters, total_clusters;
3168     WCHAR root[] = {'A',':','\\',0};
3169
3170     root[0] += INT21_MapDrive(DL_reg(context));
3171     if (!GetDiskFreeSpaceW( root, &cluster_sectors, &sector_bytes,
3172                             &free_clusters, &total_clusters )) return 0;
3173     SET_AX( context, cluster_sectors );
3174     SET_BX( context, free_clusters );
3175     SET_CX( context, sector_bytes );
3176     SET_DX( context, total_clusters );
3177     return 1;
3178 }
3179
3180 /******************************************************************
3181  *              INT21_GetDriveAllocInfo
3182  *
3183  */
3184 static int INT21_GetDriveAllocInfo( CONTEXT86 *context, int drive )
3185 {
3186     INT21_DPB  *dpb;
3187
3188     if (!INT21_FillDrivePB( drive )) return 0;
3189     dpb = &(INT21_GetHeapPointer()->misc_dpb_list[drive]);
3190     SET_AL( context, dpb->cluster_sectors + 1 );
3191     SET_CX( context, dpb->sector_bytes );
3192     SET_DX( context, dpb->num_clusters1 );
3193
3194     context->SegDs = INT21_GetHeapSelector( context );
3195     SET_BX( context, offsetof( INT21_HEAP, misc_dpb_list[drive].media_ID ) );
3196     return 1;
3197 }
3198
3199 /***********************************************************************
3200  *           INT21_GetExtendedError
3201  */
3202 static void INT21_GetExtendedError( CONTEXT86 *context )
3203 {
3204     BYTE class, action, locus;
3205     WORD error = GetLastError();
3206
3207     switch(error)
3208     {
3209     case ERROR_SUCCESS:
3210         class = action = locus = 0;
3211         break;
3212     case ERROR_DIR_NOT_EMPTY:
3213         class  = EC_Exists;
3214         action = SA_Ignore;
3215         locus  = EL_Disk;
3216         break;
3217     case ERROR_ACCESS_DENIED:
3218         class  = EC_AccessDenied;
3219         action = SA_Abort;
3220         locus  = EL_Disk;
3221         break;
3222     case ERROR_CANNOT_MAKE:
3223         class  = EC_AccessDenied;
3224         action = SA_Abort;
3225         locus  = EL_Unknown;
3226         break;
3227     case ERROR_DISK_FULL:
3228     case ERROR_HANDLE_DISK_FULL:
3229         class  = EC_MediaError;
3230         action = SA_Abort;
3231         locus  = EL_Disk;
3232         break;
3233     case ERROR_FILE_EXISTS:
3234     case ERROR_ALREADY_EXISTS:
3235         class  = EC_Exists;
3236         action = SA_Abort;
3237         locus  = EL_Disk;
3238         break;
3239     case ERROR_FILE_NOT_FOUND:
3240         class  = EC_NotFound;
3241         action = SA_Abort;
3242         locus  = EL_Disk;
3243         break;
3244     case ERROR_GEN_FAILURE:
3245         class  = EC_SystemFailure;
3246         action = SA_Abort;
3247         locus  = EL_Unknown;
3248         break;
3249     case ERROR_INVALID_DRIVE:
3250         class  = EC_MediaError;
3251         action = SA_Abort;
3252         locus  = EL_Disk;
3253         break;
3254     case ERROR_INVALID_HANDLE:
3255         class  = EC_ProgramError;
3256         action = SA_Abort;
3257         locus  = EL_Disk;
3258         break;
3259     case ERROR_LOCK_VIOLATION:
3260         class  = EC_AccessDenied;
3261         action = SA_Abort;
3262         locus  = EL_Disk;
3263         break;
3264     case ERROR_NO_MORE_FILES:
3265         class  = EC_MediaError;
3266         action = SA_Abort;
3267         locus  = EL_Disk;
3268         break;
3269     case ER_NoNetwork:
3270         class  = EC_NotFound;
3271         action = SA_Abort;
3272         locus  = EL_Network;
3273         break;
3274     case ERROR_NOT_ENOUGH_MEMORY:
3275         class  = EC_OutOfResource;
3276         action = SA_Abort;
3277         locus  = EL_Memory;
3278         break;
3279     case ERROR_PATH_NOT_FOUND:
3280         class  = EC_NotFound;
3281         action = SA_Abort;
3282         locus  = EL_Disk;
3283         break;
3284     case ERROR_SEEK:
3285         class  = EC_NotFound;
3286         action = SA_Ignore;
3287         locus  = EL_Disk;
3288         break;
3289     case ERROR_SHARING_VIOLATION:
3290         class  = EC_Temporary;
3291         action = SA_Retry;
3292         locus  = EL_Disk;
3293         break;
3294     case ERROR_TOO_MANY_OPEN_FILES:
3295         class  = EC_ProgramError;
3296         action = SA_Abort;
3297         locus  = EL_Disk;
3298         break;
3299     default:
3300         FIXME("Unknown error %d\n", error );
3301         class  = EC_SystemFailure;
3302         action = SA_Abort;
3303         locus  = EL_Unknown;
3304         break;
3305     }
3306     TRACE("GET EXTENDED ERROR code 0x%02x class 0x%02x action 0x%02x locus %02x\n",
3307            error, class, action, locus );
3308     SET_AX( context, error );
3309     SET_BH( context, class );
3310     SET_BL( context, action );
3311     SET_CH( context, locus );
3312 }
3313
3314 static BOOL INT21_CreateTempFile( CONTEXT86 *context )
3315 {
3316     static int counter = 0;
3317     char *name = CTX_SEG_OFF_TO_LIN(context,  context->SegDs, context->Edx );
3318     char *p = name + strlen(name);
3319
3320     /* despite what Ralf Brown says, some programs seem to call without
3321      * ending backslash (DOS accepts that, so we accept it too) */
3322     if ((p == name) || (p[-1] != '\\')) *p++ = '\\';
3323
3324     for (;;)
3325     {
3326         sprintf( p, "wine%04x.%03d", (int)getpid(), counter );
3327         counter = (counter + 1) % 1000;
3328
3329         SET_AX( context, 
3330                 Win32HandleToDosFileHandle( 
3331                     CreateFileA( name, GENERIC_READ | GENERIC_WRITE,
3332                                  FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
3333                                  CREATE_NEW, 0, 0 ) ) );
3334         if (AX_reg(context) != HFILE_ERROR16)
3335         {
3336             TRACE("created %s\n", name );
3337             return TRUE;
3338         }
3339         if (GetLastError() != ERROR_FILE_EXISTS) return FALSE;
3340     }
3341 }
3342
3343 /***********************************************************************
3344  *           DOSFS_ToDosFCBFormat
3345  *
3346  * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
3347  * expanding wild cards and converting to upper-case in the process.
3348  * File name can be terminated by '\0', '\\' or '/'.
3349  * Return FALSE if the name is not a valid DOS name.
3350  * 'buffer' must be at least 12 characters long.
3351  */
3352 /* Chars we don't want to see in DOS file names */
3353 #define INVALID_DOS_CHARS  "*?<>|\"+=,;[] \345"
3354 static BOOL INT21_ToDosFCBFormat( LPCWSTR name, LPWSTR buffer )
3355 {
3356     static const char invalid_chars[] = INVALID_DOS_CHARS;
3357     LPCWSTR p = name;
3358     int i;
3359
3360     /* Check for "." and ".." */
3361     if (*p == '.')
3362     {
3363         p++;
3364         buffer[0] = '.';
3365         for(i = 1; i < 11; i++) buffer[i] = ' ';
3366         buffer[11] = 0;
3367         if (*p == '.')
3368         {
3369             buffer[1] = '.';
3370             p++;
3371         }
3372         return (!*p || (*p == '/') || (*p == '\\'));
3373     }
3374
3375     for (i = 0; i < 8; i++)
3376     {
3377         switch(*p)
3378         {
3379         case '\0':
3380         case '\\':
3381         case '/':
3382         case '.':
3383             buffer[i] = ' ';
3384             break;
3385         case '?':
3386             p++;
3387             /* fall through */
3388         case '*':
3389             buffer[i] = '?';
3390             break;
3391         default:
3392             if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
3393             buffer[i] = toupperW(*p);
3394             p++;
3395             break;
3396         }
3397     }
3398
3399     if (*p == '*')
3400     {
3401         /* Skip all chars after wildcard up to first dot */
3402         while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
3403     }
3404     else
3405     {
3406         /* Check if name too long */
3407         if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
3408     }
3409     if (*p == '.') p++;  /* Skip dot */
3410
3411     for (i = 8; i < 11; i++)
3412     {
3413         switch(*p)
3414         {
3415         case '\0':
3416         case '\\':
3417         case '/':
3418             buffer[i] = ' ';
3419             break;
3420         case '.':
3421             return FALSE;  /* Second extension not allowed */
3422         case '?':
3423             p++;
3424             /* fall through */
3425         case '*':
3426             buffer[i] = '?';
3427             break;
3428         default:
3429             if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
3430             buffer[i] = toupperW(*p);
3431             p++;
3432             break;
3433         }
3434     }
3435     buffer[11] = '\0';
3436
3437     /* at most 3 character of the extension are processed
3438      * is something behind this ?
3439      */
3440     while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */
3441     return IS_END_OF_NAME(*p);
3442 }
3443
3444 static HANDLE       INT21_FindHandle;
3445 static const WCHAR *INT21_FindPath; /* will point to current dta->fullPath search */
3446
3447 /******************************************************************
3448  *              INT21_FindFirst
3449  */
3450 static int INT21_FindFirst( CONTEXT86 *context )
3451 {
3452     WCHAR *p;
3453     const char *path;
3454     FINDFILE_DTA *dta = (FINDFILE_DTA *)INT21_GetCurrentDTA(context);
3455     WCHAR maskW[12], pathW[MAX_PATH];
3456     static const WCHAR wildcardW[] = {'*','.','*',0};
3457
3458     path = (const char *)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3459     MultiByteToWideChar(CP_OEMCP, 0, path, -1, pathW, MAX_PATH);
3460
3461     p = strrchrW( pathW, '\\');
3462     if (!p)
3463     {
3464         if (pathW[0] && pathW[1] == ':') p = pathW + 2;
3465         else p = pathW;
3466     }
3467     else p++;
3468
3469     /* Note: terminating NULL in dta->mask overwrites dta->search_attr
3470      *       (doesn't matter as it is set below anyway)
3471      */
3472     if (!INT21_ToDosFCBFormat( p, maskW ))
3473     {
3474         SetLastError( ERROR_FILE_NOT_FOUND );
3475         SET_AX( context, ERROR_FILE_NOT_FOUND );
3476         SET_CFLAG(context);
3477         return 0;
3478     }
3479     WideCharToMultiByte(CP_OEMCP, 0, maskW, 12, dta->mask, sizeof(dta->mask), NULL, NULL);
3480
3481     dta->fullPath = HeapAlloc( GetProcessHeap(), 0, sizeof(wildcardW) + (p - pathW)*sizeof(WCHAR) );
3482     memcpy( dta->fullPath, pathW, (p - pathW) * sizeof(WCHAR) );
3483     memcpy( dta->fullPath + (p - pathW), wildcardW, sizeof(wildcardW) );
3484     /* we must have a fully qualified file name in dta->fullPath
3485      * (we could have a UNC path, but this would lead to some errors later on)
3486      */
3487     dta->drive = toupperW(dta->fullPath[0]) - 'A';
3488     dta->count = 0;
3489     dta->search_attr = CL_reg(context);
3490     return 1;
3491 }
3492
3493 /******************************************************************
3494  *              match_short
3495  *
3496  * Check is a short path name (DTA unicode) matches a mask (FCB ansi)
3497  */
3498 static BOOL match_short(LPCWSTR shortW, LPCSTR maskA)
3499 {
3500     WCHAR mask[11], file[12];
3501     int i;
3502
3503     if (!INT21_ToDosFCBFormat( shortW, file )) return FALSE;
3504     MultiByteToWideChar(CP_OEMCP, 0, maskA, 11, mask, 11);
3505     for (i = 0; i < 11; i++)
3506         if (mask[i] != '?' && mask[i] != file[i]) return FALSE;
3507     return TRUE;
3508 }
3509
3510 static unsigned INT21_FindHelper(LPCWSTR fullPath, unsigned drive, unsigned count, 
3511                                  LPCSTR mask, unsigned search_attr, 
3512                                  WIN32_FIND_DATAW* entry)
3513 {
3514     unsigned ncalls;
3515
3516     if ((search_attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
3517     {
3518         WCHAR path[] = {' ',':',0};
3519
3520         if (count) return 0;
3521         path[0] = drive + 'A';
3522         if (!GetVolumeInformationW(path, entry->cAlternateFileName, 13, NULL, NULL, NULL, NULL, 0)) return 0;
3523         RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftCreationTime );
3524         RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftLastAccessTime );
3525         RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftLastWriteTime );
3526         entry->dwFileAttributes = FILE_ATTRIBUTE_LABEL;
3527         entry->nFileSizeHigh = entry->nFileSizeLow = 0;
3528         TRACE("returning %s as label\n", debugstr_w(entry->cAlternateFileName));
3529         return 1;
3530     }
3531
3532     if (!INT21_FindHandle || INT21_FindPath != fullPath || count == 0)
3533     {
3534         if (INT21_FindHandle) FindClose(INT21_FindHandle);
3535         INT21_FindHandle = FindFirstFileW(fullPath, entry);
3536         if (INT21_FindHandle == INVALID_HANDLE_VALUE)
3537         {
3538             INT21_FindHandle = 0;
3539             return 0;
3540         }
3541         INT21_FindPath = fullPath;
3542         /* we need to resync search */
3543         ncalls = count;
3544     }
3545     else ncalls = 1;
3546
3547     while (ncalls-- != 0)
3548     {
3549         if (!FindNextFileW(INT21_FindHandle, entry))
3550         {
3551             FindClose(INT21_FindHandle); INT21_FindHandle = 0;
3552             return 0;
3553         }
3554     }
3555     while (count < 0xffff)
3556     {
3557         count++;
3558         /* Check the file attributes, and path */
3559         if (!(entry->dwFileAttributes & ~search_attr) &&
3560             match_short(entry->cAlternateFileName, mask))
3561         {
3562             return count;
3563         }
3564         if (!FindNextFileW(INT21_FindHandle, entry))
3565         {
3566             FindClose(INT21_FindHandle); INT21_FindHandle = 0;
3567             return 0;
3568         }
3569     }
3570     WARN("Too many directory entries in %s\n", debugstr_w(fullPath) );
3571     return 0;
3572 }
3573
3574 /******************************************************************
3575  *              INT21_FindNext
3576  */
3577 static int INT21_FindNext( CONTEXT86 *context )
3578 {
3579     FINDFILE_DTA *dta = (FINDFILE_DTA *)INT21_GetCurrentDTA(context);
3580     DWORD attr = dta->search_attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
3581     WIN32_FIND_DATAW entry;
3582     int n;
3583
3584     if (!dta->fullPath) return 0;
3585
3586     n = INT21_FindHelper(dta->fullPath, dta->drive, dta->count, 
3587                          dta->mask, attr, &entry);
3588     if (n)
3589     {
3590         dta->fileattr = entry.dwFileAttributes;
3591         dta->filesize = entry.nFileSizeLow;
3592         FileTimeToDosDateTime( &entry.ftLastWriteTime, &dta->filedate, &dta->filetime );
3593         WideCharToMultiByte(CP_OEMCP, 0, entry.cAlternateFileName, -1, 
3594                             dta->filename, 13, NULL, NULL);
3595         if (!memchr(dta->mask,'?',11))
3596         {
3597             /* wildcardless search, release resources in case no findnext will
3598              * be issued, and as a workaround in case file creation messes up
3599              * findnext, as sometimes happens with pkunzip
3600              */
3601             HeapFree( GetProcessHeap(), 0, dta->fullPath );
3602             INT21_FindPath = dta->fullPath = NULL;
3603         }
3604         dta->count = n;
3605         return 1;
3606     }
3607     HeapFree( GetProcessHeap(), 0, dta->fullPath );
3608     INT21_FindPath = dta->fullPath = NULL;
3609     return 0;
3610 }
3611
3612 /* microsoft's programmers should be shot for using CP/M style int21
3613    calls in Windows for Workgroup's winfile.exe */
3614
3615 /******************************************************************
3616  *              INT21_FindFirstFCB
3617  *
3618  */
3619 static int INT21_FindFirstFCB( CONTEXT86 *context )
3620 {
3621     BYTE *fcb = (BYTE *)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3622     FINDFILE_FCB *pFCB;
3623     int drive;
3624     WCHAR p[] = {' ',':',};
3625
3626     if (*fcb == 0xff) pFCB = (FINDFILE_FCB *)(fcb + 7);
3627     else pFCB = (FINDFILE_FCB *)fcb;
3628     drive = INT21_MapDrive( pFCB->drive );
3629     if (drive == MAX_DOS_DRIVES) return 0;
3630
3631     p[0] = 'A' + drive;
3632     pFCB->fullPath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
3633     if (!pFCB->fullPath) return 0;
3634     GetLongPathNameW(p, pFCB->fullPath, MAX_PATH);
3635     pFCB->count = 0;
3636     return 1;
3637 }
3638
3639 /******************************************************************
3640  *              INT21_FindNextFCB
3641  *
3642  */
3643 static int INT21_FindNextFCB( CONTEXT86 *context )
3644 {
3645     BYTE *fcb = (BYTE *)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3646     FINDFILE_FCB *pFCB;
3647     DOS_DIRENTRY_LAYOUT *pResult = (DOS_DIRENTRY_LAYOUT *)INT21_GetCurrentDTA(context);
3648     WIN32_FIND_DATAW entry;
3649     BYTE attr;
3650     int n;
3651     WCHAR nameW[12];
3652
3653     if (*fcb == 0xff) /* extended FCB ? */
3654     {
3655         attr = fcb[6];
3656         pFCB = (FINDFILE_FCB *)(fcb + 7);
3657     }
3658     else
3659     {
3660         attr = 0;
3661         pFCB = (FINDFILE_FCB *)fcb;
3662     }
3663
3664     if (!pFCB->fullPath) return 0;
3665     n = INT21_FindHelper(pFCB->fullPath, INT21_MapDrive( pFCB->drive ),
3666                          pFCB->count, pFCB->filename, attr, &entry);
3667     if (!n)
3668     {
3669         HeapFree( GetProcessHeap(), 0, pFCB->fullPath );
3670         INT21_FindPath = pFCB->fullPath = NULL;
3671         return 0;
3672     }
3673     pFCB->count += n;
3674
3675     if (*fcb == 0xff)
3676     {
3677         /* place extended FCB header before pResult if called with extended FCB */
3678         *(BYTE *)pResult = 0xff;
3679         (BYTE *)pResult +=6; /* leave reserved field behind */
3680         *(BYTE *)pResult = entry.dwFileAttributes;
3681         ((BYTE *)pResult)++;
3682     }
3683     *(BYTE *)pResult = INT21_MapDrive( pFCB->drive ); /* DOS_DIRENTRY_LAYOUT after current drive number */
3684     ((BYTE *)pResult)++;
3685     pResult->fileattr = entry.dwFileAttributes;
3686     pResult->cluster  = 0;  /* what else? */
3687     pResult->filesize = entry.nFileSizeLow;
3688     memset( pResult->reserved, 0, sizeof(pResult->reserved) );
3689     FileTimeToDosDateTime( &entry.ftLastWriteTime,
3690                            &pResult->filedate, &pResult->filetime );
3691
3692     /* Convert file name to FCB format */
3693     INT21_ToDosFCBFormat( entry.cAlternateFileName, nameW );
3694     WideCharToMultiByte(CP_OEMCP, 0, nameW, 11, pResult->filename, 11, NULL, NULL);
3695     return 1;
3696 }
3697
3698
3699 /******************************************************************
3700  *              INT21_ParseFileNameIntoFCB
3701  *
3702  */
3703 static void INT21_ParseFileNameIntoFCB( CONTEXT86 *context )
3704 {
3705     char *filename =
3706         CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi );
3707     char *fcb =
3708         CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi );
3709     char *s;
3710     WCHAR *buffer;
3711     WCHAR fcbW[12];
3712     INT buffer_len, len;
3713
3714     SET_AL( context, 0xff ); /* failed */
3715
3716     TRACE("filename: '%s'\n", filename);
3717
3718     s = filename;
3719     while (*s && (*s != ' ') && (*s != '\r') && (*s != '\n'))
3720         s++;
3721     len = filename - s;
3722
3723     buffer_len = MultiByteToWideChar(CP_OEMCP, 0, filename, len, NULL, 0);
3724     buffer = HeapAlloc( GetProcessHeap(), 0, (buffer_len + 1) * sizeof(WCHAR));
3725     len = MultiByteToWideChar(CP_OEMCP, 0, filename, len, buffer, buffer_len);
3726     buffer[len] = 0;
3727     INT21_ToDosFCBFormat(buffer, fcbW);
3728     HeapFree(GetProcessHeap(), 0, buffer);
3729     WideCharToMultiByte(CP_OEMCP, 0, fcbW, 12, fcb + 1, 12, NULL, NULL);
3730     *fcb = 0;
3731     TRACE("FCB: '%s'\n", fcb + 1);
3732
3733     SET_AL( context, ((strchr(filename, '*')) || (strchr(filename, '$'))) != 0 );
3734
3735     /* point DS:SI to first unparsed character */
3736     SET_SI( context, context->Esi + (int)s - (int)filename );
3737 }
3738
3739 /***********************************************************************
3740  *           DOSVM_Int21Handler
3741  *
3742  * Interrupt 0x21 handler.
3743  */
3744 void WINAPI DOSVM_Int21Handler( CONTEXT86 *context )
3745 {
3746     BOOL bSetDOSExtendedError = FALSE;
3747
3748     TRACE( "AX=%04x BX=%04x CX=%04x DX=%04x "
3749            "SI=%04x DI=%04x DS=%04x ES=%04x EFL=%08lx\n",
3750            AX_reg(context), BX_reg(context), 
3751            CX_reg(context), DX_reg(context),
3752            SI_reg(context), DI_reg(context),
3753            (WORD)context->SegDs, (WORD)context->SegEs,
3754            context->EFlags );
3755
3756    /*
3757     * Extended error is used by (at least) functions 0x2f to 0x62.
3758     * Function 0x59 returns extended error and error should not
3759     * be cleared before handling the function.
3760     */
3761     if (AH_reg(context) >= 0x2f && AH_reg(context) != 0x59) 
3762         SetLastError(0);
3763
3764     RESET_CFLAG(context); /* Not sure if this is a good idea. */
3765
3766     switch(AH_reg(context))
3767     {
3768     case 0x00: /* TERMINATE PROGRAM */
3769         TRACE("TERMINATE PROGRAM\n");
3770         if (DOSVM_IsWin16())
3771             ExitThread( 0 );
3772         else if(ISV86(context))
3773             MZ_Exit( context, FALSE, 0 );
3774         else
3775             ERR( "Called from DOS protected mode\n" );
3776         break;
3777
3778     case 0x01: /* READ CHARACTER FROM STANDARD INPUT, WITH ECHO */
3779         {
3780             BYTE ascii;
3781             TRACE("DIRECT CHARACTER INPUT WITH ECHO\n");
3782             INT21_ReadChar( &ascii, context );
3783             SET_AL( context, ascii );
3784             /*
3785              * FIXME: What to echo when extended keycodes are read?
3786              */
3787             DOSVM_PutChar(AL_reg(context));
3788         }
3789         break;
3790
3791     case 0x02: /* WRITE CHARACTER TO STANDARD OUTPUT */
3792         TRACE("Write Character to Standard Output\n");
3793         DOSVM_PutChar(DL_reg(context));
3794         break;
3795
3796     case 0x03: /* READ CHARACTER FROM STDAUX  */
3797     case 0x04: /* WRITE CHARACTER TO STDAUX */
3798     case 0x05: /* WRITE CHARACTER TO PRINTER */
3799         INT_BARF( context, 0x21 );
3800         break;
3801
3802     case 0x06: /* DIRECT CONSOLE IN/OUTPUT */
3803         if (DL_reg(context) == 0xff) 
3804         {
3805             TRACE("Direct Console Input\n");
3806
3807             if (INT21_ReadChar( NULL, NULL ))
3808             {
3809                 BYTE ascii;
3810                 INT21_ReadChar( &ascii, context );
3811                 SET_AL( context, ascii );
3812                 RESET_ZFLAG( context );
3813             }
3814             else
3815             {
3816                 /* no character available */
3817                 SET_AL( context, 0 );
3818                 SET_ZFLAG( context );
3819             }
3820         } 
3821         else 
3822         {
3823             TRACE("Direct Console Output\n");
3824             DOSVM_PutChar(DL_reg(context));
3825             /*
3826              * At least DOS versions 2.1-7.0 return character 
3827              * that was written in AL register.
3828              */
3829             SET_AL( context, DL_reg(context) );
3830         }
3831         break;
3832
3833     case 0x07: /* DIRECT CHARACTER INPUT WITHOUT ECHO */
3834         {
3835             BYTE ascii;
3836             TRACE("DIRECT CHARACTER INPUT WITHOUT ECHO\n");
3837             INT21_ReadChar( &ascii, context );
3838             SET_AL( context, ascii );
3839         }
3840         break;
3841
3842     case 0x08: /* CHARACTER INPUT WITHOUT ECHO */
3843         {
3844             BYTE ascii;
3845             TRACE("CHARACTER INPUT WITHOUT ECHO\n");
3846             INT21_ReadChar( &ascii, context );
3847             SET_AL( context, ascii );
3848         }
3849         break;
3850
3851     case 0x09: /* WRITE STRING TO STANDARD OUTPUT */
3852         TRACE("WRITE '$'-terminated string from %04lX:%04X to stdout\n",
3853               context->SegDs, DX_reg(context) );
3854         {
3855             LPSTR data = CTX_SEG_OFF_TO_LIN( context, 
3856                                              context->SegDs, context->Edx );
3857             LPSTR p = data;
3858
3859             /*
3860              * Do NOT use strchr() to calculate the string length,
3861              * as '\0' is valid string content, too!
3862              * Maybe we should check for non-'$' strings, but DOS doesn't.
3863              */
3864             while (*p != '$') p++;
3865
3866             if (DOSVM_IsWin16())
3867                 WriteFile( DosFileHandleToWin32Handle(1), 
3868                            data, p - data, 0, 0 );
3869             else
3870                 for(; data != p; data++)
3871                     DOSVM_PutChar( *data );
3872
3873             SET_AL( context, '$' ); /* yes, '$' (0x24) gets returned in AL */
3874         }
3875         break;
3876
3877     case 0x0a: /* BUFFERED INPUT */
3878         {
3879             BYTE *ptr = CTX_SEG_OFF_TO_LIN(context,
3880                                            context->SegDs,
3881                                            context->Edx);
3882             WORD result;
3883
3884             TRACE( "BUFFERED INPUT (size=%d)\n", ptr[0] );
3885
3886             /*
3887              * FIXME: Some documents state that
3888              *        ptr[1] holds number of chars from last input which 
3889              *        may be recalled on entry, other documents do not mention
3890              *        this at all.
3891              */
3892             if (ptr[1])
3893                 TRACE( "Handle old chars in buffer!\n" );
3894
3895             /*
3896              * ptr[0] - capacity (includes terminating CR)
3897              * ptr[1] - characters read (excludes terminating CR)
3898              */
3899             result = INT21_BufferedInput( context, ptr + 2, ptr[0] );
3900             if (result > 0)
3901                 ptr[1] = (BYTE)result - 1;
3902             else
3903                 ptr[1] = 0;
3904         }
3905         break;
3906
3907     case 0x0b: /* GET STDIN STATUS */
3908         TRACE( "GET STDIN STATUS\n" );
3909         {
3910             if (INT21_ReadChar( NULL, NULL ))
3911                 SET_AL( context, 0xff ); /* character available */
3912             else
3913                 SET_AL( context, 0 ); /* no character available */
3914         }
3915         break;
3916
3917     case 0x0c: /* FLUSH BUFFER AND READ STANDARD INPUT */
3918         {
3919             BYTE al = AL_reg(context); /* Input function to execute after flush. */
3920
3921             TRACE( "FLUSH BUFFER AND READ STANDARD INPUT - 0x%02x\n", al );
3922
3923             /* FIXME: buffers are not flushed */
3924
3925             /*
3926              * If AL is one of 0x01, 0x06, 0x07, 0x08, or 0x0a,
3927              * int21 function identified by AL will be called.
3928              */
3929             if(al == 0x01 || al == 0x06 || al == 0x07 || al == 0x08 || al == 0x0a)
3930             {
3931                 SET_AH( context, al );
3932                 DOSVM_Int21Handler( context );
3933             }
3934         }
3935         break;
3936
3937     case 0x0d: /* DISK BUFFER FLUSH */
3938         TRACE("DISK BUFFER FLUSH ignored\n");
3939         break;
3940
3941     case 0x0e: /* SELECT DEFAULT DRIVE */
3942         TRACE( "SELECT DEFAULT DRIVE - %c:\n", 'A' + DL_reg(context) );
3943         INT21_SetCurrentDrive( DL_reg(context) );
3944         SET_AL( context, MAX_DOS_DRIVES );
3945         break;
3946
3947     case 0x0f: /* OPEN FILE USING FCB */
3948         INT21_OpenFileUsingFCB( context );
3949         break;
3950
3951     case 0x10: /* CLOSE FILE USING FCB */
3952         INT21_CloseFileUsingFCB( context );
3953         break;
3954
3955     case 0x11: /* FIND FIRST MATCHING FILE USING FCB */
3956         TRACE("FIND FIRST MATCHING FILE USING FCB %p\n",
3957               CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx));
3958         if (!INT21_FindFirstFCB(context))
3959         {
3960             SET_AL( context, 0xff );
3961             break;
3962         }
3963         /* else fall through */
3964
3965     case 0x12: /* FIND NEXT MATCHING FILE USING FCB */
3966         SET_AL( context, INT21_FindNextFCB(context) ? 0x00 : 0xff );
3967         break;
3968
3969      case 0x13: /* DELETE FILE USING FCB */
3970         INT_BARF( context, 0x21 );
3971         break;
3972
3973     case 0x14: /* SEQUENTIAL READ FROM FCB FILE */
3974         INT21_SequentialReadFromFCB( context );
3975         break;
3976
3977     case 0x15: /* SEQUENTIAL WRITE TO FCB FILE */
3978         INT21_SequentialWriteToFCB( context );
3979         break;
3980
3981     case 0x16: /* CREATE OR TRUNCATE FILE USING FCB */
3982     case 0x17: /* RENAME FILE USING FCB */
3983         INT_BARF( context, 0x21 );
3984         break;
3985
3986     case 0x18: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
3987         SET_AL( context, 0 );
3988         break;
3989
3990     case 0x19: /* GET CURRENT DEFAULT DRIVE */
3991         SET_AL( context, INT21_GetCurrentDrive() );
3992         TRACE( "GET CURRENT DRIVE -> %c:\n", 'A' + AL_reg( context ) );
3993         break;
3994
3995     case 0x1a: /* SET DISK TRANSFER AREA ADDRESS */
3996         TRACE( "SET DISK TRANSFER AREA ADDRESS %04lX:%04X\n",
3997                context->SegDs, DX_reg(context) );
3998         {
3999             TDB *task = GlobalLock16( GetCurrentTask() );
4000             task->dta = MAKESEGPTR( context->SegDs, DX_reg(context) );
4001         }
4002         break;
4003
4004     case 0x1b: /* GET ALLOCATION INFORMATION FOR DEFAULT DRIVE */
4005         if (!INT21_GetDriveAllocInfo(context, 0))
4006             SET_AX( context, 0xffff );
4007         break;
4008
4009     case 0x1c: /* GET ALLOCATION INFORMATION FOR SPECIFIC DRIVE */
4010         if (!INT21_GetDriveAllocInfo(context, DL_reg(context)))
4011             SET_AX( context, 0xffff );
4012         break;
4013
4014     case 0x1d: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
4015     case 0x1e: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
4016         SET_AL( context, 0 );
4017         break;
4018
4019     case 0x1f: /* GET DRIVE PARAMETER BLOCK FOR DEFAULT DRIVE */
4020         {
4021             BYTE drive = INT21_MapDrive( 0 ); 
4022             TRACE( "GET DPB FOR DEFAULT DRIVE\n" );
4023
4024             if (INT21_FillDrivePB( drive ))
4025             {
4026                 SET_AL( context, 0x00 ); /* success */
4027                 SET_BX( context, offsetof( INT21_HEAP, misc_dpb_list[drive] ) );
4028                 context->SegDs = INT21_GetHeapSelector( context );
4029             }
4030             else
4031             {
4032                 SET_AL( context, 0xff ); /* invalid or network drive */
4033             }
4034         }
4035         break;
4036
4037     case 0x20: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
4038         SET_AL( context, 0 );
4039         break;
4040
4041     case 0x21: /* READ RANDOM RECORD FROM FCB FILE */
4042         INT21_ReadRandomRecordFromFCB( context );
4043         break;
4044
4045     case 0x22: /* WRITE RANDOM RECORD TO FCB FILE */
4046         INT21_WriteRandomRecordToFCB( context );
4047         break;
4048
4049     case 0x23: /* GET FILE SIZE FOR FCB */
4050     case 0x24: /* SET RANDOM RECORD NUMBER FOR FCB */
4051         INT_BARF( context, 0x21 );
4052         break;
4053
4054     case 0x25: /* SET INTERRUPT VECTOR */
4055         TRACE("SET INTERRUPT VECTOR 0x%02x\n",AL_reg(context));
4056         {
4057             FARPROC16 ptr = (FARPROC16)MAKESEGPTR( context->SegDs, DX_reg(context) );
4058             if (!ISV86(context) && DOSVM_IsWin16())
4059                 DOSVM_SetPMHandler16(  AL_reg(context), ptr );
4060             else
4061                 DOSVM_SetRMHandler( AL_reg(context), ptr );
4062         }
4063         break;
4064
4065     case 0x26: /* CREATE NEW PROGRAM SEGMENT PREFIX */
4066         INT_BARF( context, 0x21 );
4067         break;
4068
4069     case 0x27: /* RANDOM BLOCK READ FROM FCB FILE */
4070         INT21_RandomBlockReadFromFCB( context );
4071         break;
4072
4073     case 0x28: /* RANDOM BLOCK WRITE TO FCB FILE */
4074         INT21_RandomBlockWriteToFCB( context );
4075         break;
4076
4077     case 0x29: /* PARSE FILENAME INTO FCB */
4078         INT21_ParseFileNameIntoFCB(context);
4079         break;
4080
4081     case 0x2a: /* GET SYSTEM DATE */
4082         TRACE( "GET SYSTEM DATE\n" );
4083         {
4084             SYSTEMTIME systime;
4085             GetLocalTime( &systime );
4086             SET_CX( context, systime.wYear );
4087             SET_DH( context, systime.wMonth );
4088             SET_DL( context, systime.wDay );
4089             SET_AL( context, systime.wDayOfWeek );
4090         }
4091         break;
4092
4093     case 0x2b: /* SET SYSTEM DATE */
4094         TRACE( "SET SYSTEM DATE\n" );
4095         {
4096             WORD year  = CX_reg(context);
4097             BYTE month = DH_reg(context);
4098             BYTE day   = DL_reg(context);
4099
4100             if (year  >= 1980 && year  <= 2099 &&
4101                 month >= 1    && month <= 12   &&
4102                 day   >= 1    && day   <= 31)
4103             {
4104                 FIXME( "SetSystemDate(%02d/%02d/%04d): not allowed\n",
4105                        day, month, year );
4106                 SET_AL( context, 0 );  /* Let's pretend we succeeded */
4107             }
4108             else
4109             {
4110                 SET_AL( context, 0xff ); /* invalid date */
4111                 TRACE( "SetSystemDate(%02d/%02d/%04d): invalid date\n",
4112                        day, month, year );
4113             }
4114         }
4115         break;
4116
4117     case 0x2c: /* GET SYSTEM TIME */
4118         TRACE( "GET SYSTEM TIME\n" );
4119         {
4120             SYSTEMTIME systime;
4121             GetLocalTime( &systime );
4122             SET_CH( context, systime.wHour );
4123             SET_CL( context, systime.wMinute );
4124             SET_DH( context, systime.wSecond );
4125             SET_DL( context, systime.wMilliseconds / 10 );
4126         }
4127         break;
4128
4129     case 0x2d: /* SET SYSTEM TIME */
4130         if( CH_reg(context) >= 24 || CL_reg(context) >= 60 || DH_reg(context) >= 60 || DL_reg(context) >= 100 ) {
4131             TRACE("SetSystemTime(%02d:%02d:%02d.%02d): wrong time\n",
4132               CH_reg(context), CL_reg(context),
4133               DH_reg(context), DL_reg(context) );
4134             SET_AL( context, 0xFF );
4135         }
4136         else
4137         {
4138             FIXME("SetSystemTime(%02d:%02d:%02d.%02d): not allowed\n",
4139                   CH_reg(context), CL_reg(context),
4140                   DH_reg(context), DL_reg(context) );
4141             SET_AL( context, 0 );  /* Let's pretend we succeeded */
4142         }
4143         break;
4144
4145     case 0x2e: /* SET VERIFY FLAG */
4146         TRACE("SET VERIFY FLAG ignored\n");
4147         /* we cannot change the behaviour anyway, so just ignore it */
4148         break;
4149
4150     case 0x2f: /* GET DISK TRANSFER AREA ADDRESS */
4151         TRACE( "GET DISK TRANSFER AREA ADDRESS\n" );
4152         {
4153             TDB *task = GlobalLock16( GetCurrentTask() );
4154             context->SegEs = SELECTOROF( task->dta );
4155             SET_BX( context, OFFSETOF( task->dta ) );
4156         }
4157         break;
4158
4159     case 0x30: /* GET DOS VERSION */
4160         TRACE( "GET DOS VERSION - %s requested\n",
4161                (AL_reg(context) == 0x00) ? "OEM number" : "version flag" );
4162
4163         if (AL_reg(context) == 0x00)
4164             SET_BH( context, 0xff ); /* OEM number => undefined */
4165         else
4166             SET_BH( context, 0x08 ); /* version flag => DOS is in ROM */
4167
4168         SET_AL( context, HIBYTE(HIWORD(GetVersion16())) ); /* major version */
4169         SET_AH( context, LOBYTE(HIWORD(GetVersion16())) ); /* minor version */
4170
4171         SET_BL( context, 0x12 );     /* 0x123456 is 24-bit Wine's serial # */
4172         SET_CX( context, 0x3456 );
4173         break;
4174
4175     case 0x31: /* TERMINATE AND STAY RESIDENT */
4176         FIXME("TERMINATE AND STAY RESIDENT stub\n");
4177         break;
4178
4179     case 0x32: /* GET DOS DRIVE PARAMETER BLOCK FOR SPECIFIC DRIVE */
4180         {
4181             BYTE drive = INT21_MapDrive( DL_reg(context) );           
4182             TRACE( "GET DPB FOR SPECIFIC DRIVE %d\n", DL_reg(context) );
4183
4184             if (INT21_FillDrivePB( drive ))
4185             {
4186                 SET_AL( context, 0x00 ); /* success */
4187                 SET_DX( context, offsetof( INT21_HEAP, misc_dpb_list[drive] ) );
4188                 context->SegDs = INT21_GetHeapSelector( context );
4189             }
4190             else
4191             {
4192                 SET_AL( context, 0xff ); /* invalid or network drive */
4193             }
4194         }
4195         break;
4196
4197     case 0x33: /* MULTIPLEXED */
4198         switch (AL_reg(context))
4199         {
4200         case 0x00: /* GET CURRENT EXTENDED BREAK STATE */
4201             TRACE("GET CURRENT EXTENDED BREAK STATE\n");
4202             SET_DL( context, DOSCONF_GetConfig()->brk_flag );
4203             break;
4204
4205         case 0x01: /* SET EXTENDED BREAK STATE */
4206             TRACE("SET CURRENT EXTENDED BREAK STATE\n");
4207             DOSCONF_GetConfig()->brk_flag = (DL_reg(context) > 0) ? 1 : 0;
4208             break;
4209
4210         case 0x02: /* GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE*/
4211             TRACE("GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE\n");
4212             /* ugly coding in order to stay reentrant */
4213             if (DL_reg(context))
4214             {
4215                 SET_DL( context, DOSCONF_GetConfig()->brk_flag );
4216                 DOSCONF_GetConfig()->brk_flag = 1;
4217             }
4218             else
4219             {
4220                 SET_DL( context, DOSCONF_GetConfig()->brk_flag );
4221                 DOSCONF_GetConfig()->brk_flag = 0;
4222             }
4223             break;
4224
4225         case 0x05: /* GET BOOT DRIVE */
4226             TRACE("GET BOOT DRIVE\n");
4227             SET_DL( context, 3 );
4228             /* c: is Wine's bootdrive (a: is 1)*/
4229             break;
4230
4231         case 0x06: /* GET TRUE VERSION NUMBER */
4232             TRACE("GET TRUE VERSION NUMBER\n");
4233             SET_BL( context, HIBYTE(HIWORD(GetVersion16())) ); /* major */
4234             SET_BH( context, LOBYTE(HIWORD(GetVersion16())) ); /* minor */
4235             SET_DL( context, 0x00 ); /* revision */
4236             SET_DH( context, 0x08 ); /* DOS is in ROM */
4237             break;
4238
4239         default:
4240             INT_BARF( context, 0x21 );
4241             break;
4242         }
4243         break;
4244
4245     case 0x34: /* GET ADDRESS OF INDOS FLAG */
4246         TRACE( "GET ADDRESS OF INDOS FLAG\n" );
4247         context->SegEs = INT21_GetHeapSelector( context );
4248         SET_BX( context, offsetof(INT21_HEAP, misc_indos) );
4249         break;
4250
4251     case 0x35: /* GET INTERRUPT VECTOR */
4252         TRACE("GET INTERRUPT VECTOR 0x%02x\n",AL_reg(context));
4253         {
4254             FARPROC16 addr;
4255             if (!ISV86(context) && DOSVM_IsWin16())
4256                 addr = DOSVM_GetPMHandler16( AL_reg(context) );
4257             else
4258                 addr = DOSVM_GetRMHandler( AL_reg(context) );
4259             context->SegEs = SELECTOROF(addr);
4260             SET_BX( context, OFFSETOF(addr) );
4261         }
4262         break;
4263
4264     case 0x36: /* GET FREE DISK SPACE */
4265         TRACE("GET FREE DISK SPACE FOR DRIVE %s\n",
4266               INT21_DriveName( DL_reg(context) ));
4267         if (!INT21_GetFreeDiskSpace(context)) SET_AX( context, 0xffff );
4268         break;
4269
4270     case 0x37: /* SWITCHAR */
4271         {
4272             switch (AL_reg(context))
4273             {
4274             case 0x00: /* "SWITCHAR" - GET SWITCH CHARACTER */
4275                 TRACE( "SWITCHAR - GET SWITCH CHARACTER\n" );
4276                 SET_AL( context, 0x00 ); /* success*/
4277                 SET_DL( context, '/' );
4278                 break;
4279             case 0x01: /*"SWITCHAR" - SET SWITCH CHARACTER*/
4280                 FIXME( "SWITCHAR - SET SWITCH CHARACTER: %c\n",
4281                        DL_reg( context ));
4282                 SET_AL( context, 0x00 ); /* success*/
4283                 break;
4284             default:
4285                 INT_BARF( context, 0x21 );
4286                 break;
4287             }
4288         }
4289         break;
4290
4291     case 0x38: /* GET COUNTRY-SPECIFIC INFORMATION */
4292         TRACE( "GET COUNTRY-SPECIFIC INFORMATION\n" );
4293         if (AL_reg(context))
4294         {
4295             WORD country = AL_reg(context);
4296             if (country == 0xff)
4297                 country = BX_reg(context);
4298             if (country != INT21_GetSystemCountryCode()) {
4299                 FIXME( "Requested info on non-default country %04x\n", country );
4300                 SET_AX(context, 2);
4301                 SET_CFLAG(context);
4302             }
4303         }
4304         if(AX_reg(context) != 2 )
4305         {
4306             INT21_FillCountryInformation( CTX_SEG_OFF_TO_LIN(context,
4307                                                              context->SegDs,
4308                                                              context->Edx) );
4309             SET_AX( context, INT21_GetSystemCountryCode() );
4310             SET_BX( context, INT21_GetSystemCountryCode() );
4311             RESET_CFLAG(context);
4312         }
4313         break;
4314
4315     case 0x39: /* "MKDIR" - CREATE SUBDIRECTORY */
4316         if (!INT21_CreateDirectory( context ))
4317             bSetDOSExtendedError = TRUE;
4318         else
4319             RESET_CFLAG(context);
4320         break;
4321
4322     case 0x3a: /* "RMDIR" - REMOVE DIRECTORY */
4323         {
4324             WCHAR dirW[MAX_PATH];
4325             char *dirA = CTX_SEG_OFF_TO_LIN(context,
4326                                             context->SegDs, context->Edx);
4327
4328             TRACE( "REMOVE DIRECTORY %s\n", dirA );
4329
4330             MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
4331
4332             if (!RemoveDirectoryW( dirW ))
4333                 bSetDOSExtendedError = TRUE;
4334             else
4335                 RESET_CFLAG(context);
4336         }
4337         break;
4338
4339     case 0x3b: /* "CHDIR" - SET CURRENT DIRECTORY */
4340         if (!INT21_SetCurrentDirectory( context ))
4341             bSetDOSExtendedError = TRUE;
4342         else
4343             RESET_CFLAG(context);
4344         break;
4345
4346     case 0x3c: /* "CREAT" - CREATE OR TRUNCATE FILE */
4347         if (!INT21_CreateFile( context, context->Edx, FALSE, 
4348                                OF_READWRITE | OF_SHARE_COMPAT, 0x12 ))
4349             bSetDOSExtendedError = TRUE;
4350         else
4351             RESET_CFLAG(context);
4352         break;
4353
4354     case 0x3d: /* "OPEN" - OPEN EXISTING FILE */
4355         if (!INT21_CreateFile( context, context->Edx, FALSE, 
4356                                AL_reg(context), 0x01 ))
4357             bSetDOSExtendedError = TRUE;
4358         else
4359             RESET_CFLAG(context);
4360         break;
4361
4362     case 0x3e: /* "CLOSE" - CLOSE FILE */
4363         TRACE( "CLOSE handle %d\n", BX_reg(context) );
4364         if (_lclose16( BX_reg(context) ) == HFILE_ERROR16)
4365             bSetDOSExtendedError = TRUE;
4366         else
4367             RESET_CFLAG(context);
4368         break;
4369
4370     case 0x3f: /* "READ" - READ FROM FILE OR DEVICE */
4371         TRACE( "READ from %d to %04lX:%04X for %d bytes\n",
4372                BX_reg(context),
4373                context->SegDs,
4374                DX_reg(context),
4375                CX_reg(context) );
4376         {
4377             DWORD result;
4378             WORD  count  = CX_reg(context);
4379             BYTE *buffer = CTX_SEG_OFF_TO_LIN( context, 
4380                                                context->SegDs,
4381                                                context->Edx );
4382
4383             /* Some programs pass a count larger than the allocated buffer */
4384             if (DOSVM_IsWin16())
4385             {
4386                 WORD maxcount = GetSelectorLimit16( context->SegDs )
4387                     - DX_reg(context) + 1;
4388                 if (count > maxcount)
4389                     count = maxcount;
4390             }
4391
4392             /*
4393              * FIXME: Reading from console (BX=1) in DOS mode
4394              *        does not work as it is supposed to work.
4395              */
4396
4397             RESET_CFLAG(context); /* set if error */
4398             if (!DOSVM_IsWin16() && BX_reg(context) == 0)
4399             {
4400                 result = INT21_BufferedInput( context, buffer, count );
4401                 SET_AX( context, (WORD)result );
4402             }
4403             else if (ReadFile( DosFileHandleToWin32Handle(BX_reg(context)),
4404                                buffer, count, &result, NULL ))
4405                 SET_AX( context, (WORD)result );
4406             else
4407                 bSetDOSExtendedError = TRUE;
4408         }
4409         break;
4410
4411     case 0x40:  /* "WRITE" - WRITE TO FILE OR DEVICE */
4412         TRACE( "WRITE from %04lX:%04X to handle %d for %d byte\n",
4413                context->SegDs, DX_reg(context),
4414                BX_reg(context), CX_reg(context) );
4415         {
4416             BYTE *ptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
4417
4418             if (!DOSVM_IsWin16() && 
4419                 (BX_reg(context) == 1 || BX_reg(context) == 2))
4420             {
4421                 int i;
4422                 for(i=0; i<CX_reg(context); i++)
4423                     DOSVM_PutChar(ptr[i]);
4424                 SET_AX(context, CX_reg(context));
4425                 RESET_CFLAG(context);
4426             }
4427             else
4428             {
4429                 HFILE handle = (HFILE)DosFileHandleToWin32Handle(BX_reg(context));
4430                 LONG result = _hwrite( handle, ptr, CX_reg(context) );
4431                 if (result == HFILE_ERROR)
4432                     bSetDOSExtendedError = TRUE;
4433                 else
4434                 {
4435                     SET_AX( context, (WORD)result );
4436                     RESET_CFLAG(context);
4437                 }
4438             }
4439         }
4440         break;
4441
4442     case 0x41: /* "UNLINK" - DELETE FILE */
4443         {
4444             WCHAR fileW[MAX_PATH];
4445             char *fileA = CTX_SEG_OFF_TO_LIN(context, 
4446                                              context->SegDs, 
4447                                              context->Edx);
4448
4449             TRACE( "UNLINK %s\n", fileA );
4450             MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
4451
4452             if (!DeleteFileW( fileW ))
4453                 bSetDOSExtendedError = TRUE;
4454             else
4455                 RESET_CFLAG(context);
4456         }
4457         break;
4458
4459     case 0x42: /* "LSEEK" - SET CURRENT FILE POSITION */
4460         TRACE( "LSEEK handle %d offset %ld from %s\n",
4461                BX_reg(context), 
4462                MAKELONG( DX_reg(context), CX_reg(context) ),
4463                (AL_reg(context) == 0) ? 
4464                "start of file" : ((AL_reg(context) == 1) ? 
4465                                   "current file position" : "end of file") );
4466         {
4467             HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
4468             LONG   offset = MAKELONG( DX_reg(context), CX_reg(context) );
4469             DWORD  status = SetFilePointer( handle, offset, 
4470                                             NULL, AL_reg(context) );
4471             if (status == INVALID_SET_FILE_POINTER)
4472                 bSetDOSExtendedError = TRUE;
4473             else
4474             {
4475                 SET_AX( context, LOWORD(status) );
4476                 SET_DX( context, HIWORD(status) );
4477                 RESET_CFLAG(context);
4478             }
4479         }
4480         break;
4481
4482     case 0x43: /* FILE ATTRIBUTES */
4483         if (!INT21_FileAttributes( context, AL_reg(context), FALSE ))
4484             bSetDOSExtendedError = TRUE;
4485         else
4486             RESET_CFLAG(context);
4487         break;
4488
4489     case 0x44: /* IOCTL */
4490         INT21_Ioctl( context );
4491         break;
4492
4493     case 0x45: /* "DUP" - DUPLICATE FILE HANDLE */
4494         TRACE( "DUPLICATE FILE HANDLE %d\n", BX_reg(context) );
4495         {
4496             HANDLE handle32;
4497             HFILE  handle16 = HFILE_ERROR;
4498
4499             if (DuplicateHandle( GetCurrentProcess(),
4500                                  DosFileHandleToWin32Handle(BX_reg(context)),
4501                                  GetCurrentProcess(), 
4502                                  &handle32,
4503                                  0, TRUE, DUPLICATE_SAME_ACCESS ))
4504                 handle16 = Win32HandleToDosFileHandle(handle32);
4505
4506             if (handle16 == HFILE_ERROR)
4507                 bSetDOSExtendedError = TRUE;
4508             else
4509             {
4510                 SET_AX( context, handle16 );
4511                 RESET_CFLAG(context);
4512             }
4513         }
4514         break;
4515
4516     case 0x46: /* "DUP2", "FORCEDUP" - FORCE DUPLICATE FILE HANDLE */
4517         TRACE( "FORCEDUP - FORCE DUPLICATE FILE HANDLE %d to %d\n",
4518                BX_reg(context), CX_reg(context) );
4519
4520         if (FILE_Dup2( BX_reg(context), CX_reg(context) ) == HFILE_ERROR16)
4521             bSetDOSExtendedError = TRUE;
4522         else
4523             RESET_CFLAG(context);
4524         break;
4525
4526     case 0x47: /* "CWD" - GET CURRENT DIRECTORY */
4527         if (!INT21_GetCurrentDirectory( context, FALSE ))
4528             bSetDOSExtendedError = TRUE;
4529         else
4530             RESET_CFLAG(context);
4531         break;
4532
4533     case 0x48: /* ALLOCATE MEMORY */
4534         TRACE( "ALLOCATE MEMORY for %d paragraphs\n", BX_reg(context) );
4535         {
4536             WORD  selector = 0;
4537             DWORD bytes = (DWORD)BX_reg(context) << 4;
4538
4539             if (!ISV86(context) && DOSVM_IsWin16())
4540             {
4541                 DWORD rv = GlobalDOSAlloc16( bytes );
4542                 selector = LOWORD( rv );
4543             }
4544             else
4545                 DOSMEM_GetBlock( bytes, &selector );
4546
4547             if (selector)
4548             {
4549                 SET_AX( context, selector );
4550                 RESET_CFLAG(context);
4551             }
4552             else
4553             {
4554                 SET_CFLAG(context);
4555                 SET_AX( context, 0x0008 ); /* insufficient memory */
4556                 SET_BX( context, DOSMEM_Available() >> 4 );
4557             }
4558         }
4559         break;
4560
4561     case 0x49: /* FREE MEMORY */
4562         TRACE( "FREE MEMORY segment %04lX\n", context->SegEs );
4563         {
4564             BOOL ok;
4565             
4566             if (!ISV86(context) && DOSVM_IsWin16())
4567             {
4568                 ok = !GlobalDOSFree16( context->SegEs );
4569
4570                 /* If we don't reset ES_reg, we will fail in the relay code */
4571                 if (ok)
4572                     context->SegEs = 0;
4573             }
4574             else
4575                 ok = DOSMEM_FreeBlock( (void*)((DWORD)context->SegEs << 4) );
4576
4577             if (!ok)
4578             {
4579                 TRACE("FREE MEMORY failed\n");
4580                 SET_CFLAG(context);
4581                 SET_AX( context, 0x0009 ); /* memory block address invalid */
4582             }
4583         }
4584         break;
4585
4586     case 0x4a: /* RESIZE MEMORY BLOCK */
4587         TRACE( "RESIZE MEMORY segment %04lX to %d paragraphs\n", 
4588                context->SegEs, BX_reg(context) );
4589         {
4590             DWORD newsize = (DWORD)BX_reg(context) << 4;
4591             
4592             if (!ISV86(context) && DOSVM_IsWin16())
4593             {
4594                 FIXME( "Resize memory block - unsupported under Win16\n" );
4595                 SET_CFLAG(context);
4596             }
4597             else
4598             {
4599                 LPVOID address = (void*)((DWORD)context->SegEs << 4);
4600                 UINT blocksize = DOSMEM_ResizeBlock( address, newsize, FALSE );
4601
4602                 RESET_CFLAG(context);
4603                 if (blocksize == (UINT)-1)
4604                 {
4605                     SET_CFLAG( context );
4606                     SET_AX( context, 0x0009 ); /* illegal address */
4607                 }
4608                 else if(blocksize != newsize)
4609                 {
4610                     SET_CFLAG( context );
4611                     SET_AX( context, 0x0008 );    /* insufficient memory */
4612                     SET_BX( context, blocksize >> 4 ); /* new block size */
4613                 }
4614             }
4615         }
4616         break;
4617
4618     case 0x4b: /* "EXEC" - LOAD AND/OR EXECUTE PROGRAM */
4619         {
4620             BYTE *program = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
4621             BYTE *paramblk = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Ebx);
4622
4623             TRACE( "EXEC %s\n", program );
4624
4625             RESET_CFLAG(context);
4626             if (DOSVM_IsWin16())
4627             {
4628                 HINSTANCE16 instance = WinExec16( program, SW_NORMAL );
4629                 if (instance < 32)
4630                 {
4631                     SET_CFLAG( context );
4632                     SET_AX( context, instance );
4633                 }
4634             }
4635             else
4636             {
4637                 if (!MZ_Exec( context, program, AL_reg(context), paramblk))
4638                     bSetDOSExtendedError = TRUE;
4639             }
4640         }
4641         break;
4642
4643     case 0x4c: /* "EXIT" - TERMINATE WITH RETURN CODE */
4644         TRACE( "EXIT with return code %d\n", AL_reg(context) );
4645         if (DOSVM_IsWin16())
4646             ExitThread( AL_reg(context) );
4647         else if(ISV86(context))
4648             MZ_Exit( context, FALSE, AL_reg(context) );
4649         else
4650         {
4651             /*
4652              * Exit from DPMI.
4653              */            
4654             DWORD rv = AL_reg(context);
4655             RaiseException( EXCEPTION_VM86_INTx, 0, 1, &rv );
4656         }
4657         break;
4658
4659     case 0x4d: /* GET RETURN CODE */
4660         TRACE("GET RETURN CODE (ERRORLEVEL)\n");
4661         SET_AX( context, DOSVM_retval );
4662         DOSVM_retval = 0;
4663         break;
4664
4665     case 0x4e: /* "FINDFIRST" - FIND FIRST MATCHING FILE */
4666         TRACE("FINDFIRST mask 0x%04x spec %s\n",CX_reg(context),
4667               (LPCSTR)CTX_SEG_OFF_TO_LIN(context,  context->SegDs, context->Edx));
4668         if (!INT21_FindFirst(context)) break;
4669         /* fall through */
4670
4671     case 0x4f: /* "FINDNEXT" - FIND NEXT MATCHING FILE */
4672         TRACE("FINDNEXT\n");
4673         if (!INT21_FindNext(context))
4674         {
4675             SetLastError( ERROR_NO_MORE_FILES );
4676             SET_AX( context, ERROR_NO_MORE_FILES );
4677             SET_CFLAG(context);
4678         }
4679         else SET_AX( context, 0 );  /* OK */
4680         break;
4681
4682     case 0x50: /* SET CURRENT PROCESS ID (SET PSP ADDRESS) */
4683         TRACE("SET CURRENT PROCESS ID (SET PSP ADDRESS)\n");
4684         DOSVM_psp = BX_reg(context);
4685         break;
4686
4687     case 0x51: /* GET PSP ADDRESS */
4688         INT21_GetPSP( context );
4689         break;
4690
4691     case 0x52: /* "SYSVARS" - GET LIST OF LISTS */
4692         {
4693             SEGPTR ptr = DOSDEV_GetLOL( ISV86(context) || !DOSVM_IsWin16() );
4694             context->SegEs = SELECTOROF(ptr);
4695             SET_BX( context, OFFSETOF(ptr) );
4696         }
4697         break;
4698
4699     case 0x54: /* Get Verify Flag */
4700         TRACE("Get Verify Flag - Not Supported\n");
4701         SET_AL( context, 0x00 );  /* pretend we can tell. 00h = off 01h = on */
4702         break;
4703
4704     case 0x56: /* "RENAME" - RENAME FILE */
4705         if (!INT21_RenameFile( context ))
4706             bSetDOSExtendedError = TRUE;
4707         else
4708             RESET_CFLAG(context);
4709         break;
4710
4711     case 0x57: /* FILE DATE AND TIME */
4712         if (!INT21_FileDateTime( context ))
4713             bSetDOSExtendedError = TRUE;
4714         else
4715             RESET_CFLAG(context);
4716         break;
4717
4718     case 0x58: /* GET OR SET MEMORY ALLOCATION STRATEGY */
4719         switch (AL_reg(context))
4720         {
4721         case 0x00: /* GET MEMORY ALLOCATION STRATEGY */
4722             TRACE( "GET MEMORY ALLOCATION STRATEGY\n" );
4723             SET_AX( context, 0 ); /* low memory first fit */
4724             break;
4725
4726         case 0x01: /* SET ALLOCATION STRATEGY */
4727             TRACE( "SET MEMORY ALLOCATION STRATEGY to %d - ignored\n",
4728                    BL_reg(context) );
4729             break;
4730
4731         case 0x02: /* GET UMB LINK STATE */
4732             TRACE( "GET UMB LINK STATE\n" );
4733             SET_AL( context, 0 ); /* UMBs not part of DOS memory chain */
4734             break;
4735
4736         case 0x03: /* SET UMB LINK STATE */
4737             TRACE( "SET UMB LINK STATE to %d - ignored\n",
4738                    BX_reg(context) );
4739             break;
4740
4741         default:
4742             INT_BARF( context, 0x21 );
4743         }
4744         break;
4745
4746     case 0x59: /* GET EXTENDED ERROR INFO */
4747         INT21_GetExtendedError( context );
4748         break;
4749
4750     case 0x5a: /* CREATE TEMPORARY FILE */
4751         TRACE("CREATE TEMPORARY FILE\n");
4752         bSetDOSExtendedError = !INT21_CreateTempFile(context);
4753         break;
4754
4755     case 0x5b: /* CREATE NEW FILE */ 
4756         if (!INT21_CreateFile( context, context->Edx, FALSE,
4757                                OF_READWRITE | OF_SHARE_COMPAT, 0x10 ))
4758             bSetDOSExtendedError = TRUE;
4759         else
4760             RESET_CFLAG(context);
4761         break;
4762
4763     case 0x5c: /* "FLOCK" - RECORD LOCKING */
4764         {
4765             DWORD  offset = MAKELONG(DX_reg(context), CX_reg(context));
4766             DWORD  length = MAKELONG(DI_reg(context), SI_reg(context));
4767             HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
4768
4769             RESET_CFLAG(context);
4770             switch (AL_reg(context))
4771             {
4772             case 0x00: /* LOCK */
4773                 TRACE( "lock handle %d offset %ld length %ld\n",
4774                        BX_reg(context), offset, length );
4775                 if (!LockFile( handle, offset, 0, length, 0 ))
4776                     bSetDOSExtendedError = TRUE;
4777                 break;
4778
4779             case 0x01: /* UNLOCK */
4780                 TRACE( "unlock handle %d offset %ld length %ld\n",
4781                        BX_reg(context), offset, length );
4782                 if (!UnlockFile( handle, offset, 0, length, 0 ))
4783                     bSetDOSExtendedError = TRUE;
4784                 break;
4785
4786             default:
4787                 INT_BARF( context, 0x21 );
4788             }
4789         }
4790         break;
4791
4792     case 0x5d: /* NETWORK 5D */
4793         FIXME( "Network function 5D not implemented.\n" );
4794         SetLastError( ER_NoNetwork );
4795         bSetDOSExtendedError = TRUE;
4796         break;
4797
4798     case 0x5e: /* NETWORK 5E */
4799         bSetDOSExtendedError = INT21_NetworkFunc( context);
4800         break;
4801
4802     case 0x5f: /* NETWORK 5F */
4803         /* FIXME: supporting this would need to 1:
4804          * - implement per drive current directory (as kernel32 doesn't)
4805          * - assign enabled/disabled flag on a per drive basis
4806          */
4807         /* network software not installed */
4808         TRACE("NETWORK function AX=%04x not implemented\n",AX_reg(context));
4809         SetLastError( ER_NoNetwork );
4810         bSetDOSExtendedError = TRUE;
4811         break;
4812
4813     case 0x60: /* "TRUENAME" - CANONICALIZE FILENAME OR PATH */
4814         {
4815             WCHAR       pathW[MAX_PATH], res[MAX_PATH];
4816             /* FIXME: likely to be broken */
4817
4818             TRACE("TRUENAME %s\n",
4819                   (LPCSTR)CTX_SEG_OFF_TO_LIN(context, context->SegDs,context->Esi));
4820             MultiByteToWideChar(CP_OEMCP, 0, CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi), -1, pathW, MAX_PATH);
4821             if (!GetFullPathNameW( pathW, 128, res, NULL ))
4822                 bSetDOSExtendedError = TRUE;
4823             else
4824             {
4825                 SET_AX( context, 0 );
4826                 WideCharToMultiByte(CP_OEMCP, 0, res, -1, 
4827                                     CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi), 
4828                                     128, NULL, NULL);
4829             }
4830         }
4831         break;
4832
4833     case 0x61: /* UNUSED */
4834         SET_AL( context, 0 );
4835         break;
4836
4837     case 0x62: /* GET PSP ADDRESS */
4838         INT21_GetPSP( context );
4839         break;
4840
4841     case 0x63: /* MISC. LANGUAGE SUPPORT */
4842         switch (AL_reg(context)) {
4843         case 0x00: /* GET DOUBLE BYTE CHARACTER SET LEAD-BYTE TABLE */
4844             TRACE( "GET DOUBLE BYTE CHARACTER SET LEAD-BYTE TABLE\n" );
4845             context->SegDs = INT21_GetHeapSelector( context );
4846             SET_SI( context, offsetof(INT21_HEAP, dbcs_table) );
4847             SET_AL( context, 0 ); /* success */
4848             break;
4849         }
4850         break;
4851
4852     case 0x64: /* OS/2 DOS BOX */
4853         INT_BARF( context, 0x21 );
4854         SET_CFLAG(context);
4855         break;
4856
4857     case 0x65: /* EXTENDED COUNTRY INFORMATION */
4858         INT21_ExtendedCountryInformation( context );
4859         break;
4860
4861     case 0x66: /* GLOBAL CODE PAGE TABLE */
4862         switch (AL_reg(context))
4863         {
4864         case 0x01:
4865             TRACE( "GET GLOBAL CODE PAGE TABLE\n" );
4866             SET_BX( context, GetOEMCP() );
4867             SET_DX( context, GetOEMCP() );
4868             break;
4869         case 0x02:
4870             FIXME( "SET GLOBAL CODE PAGE TABLE, active %d, system %d - ignored\n",
4871                    BX_reg(context), DX_reg(context) );
4872             break;
4873         }
4874         break;
4875
4876     case 0x67: /* SET HANDLE COUNT */
4877         TRACE( "SET HANDLE COUNT to %d\n", BX_reg(context) );
4878         if (SetHandleCount( BX_reg(context) ) < BX_reg(context) )
4879             bSetDOSExtendedError = TRUE;
4880         break;
4881
4882     case 0x68: /* "FFLUSH" - COMMIT FILE */
4883         TRACE( "FFLUSH - handle %d\n", BX_reg(context) );
4884         if (!FlushFileBuffers( DosFileHandleToWin32Handle(BX_reg(context)) ))
4885             bSetDOSExtendedError = TRUE;
4886         break;
4887
4888     case 0x69: /* DISK SERIAL NUMBER */
4889         switch (AL_reg(context))
4890         {
4891         case 0x00:
4892             TRACE("GET DISK SERIAL NUMBER for drive %s\n",
4893                   INT21_DriveName(BL_reg(context)));
4894             if (!INT21_GetDiskSerialNumber(context)) bSetDOSExtendedError = TRUE;
4895             else SET_AX( context, 0 );
4896             break;
4897
4898         case 0x01:
4899             TRACE("SET DISK SERIAL NUMBER for drive %s\n",
4900                   INT21_DriveName(BL_reg(context)));
4901             if (!INT21_SetDiskSerialNumber(context)) bSetDOSExtendedError = TRUE;
4902             else SET_AX( context, 1 );
4903             break;
4904         }
4905         break;
4906
4907     case 0x6a: /* COMMIT FILE */
4908         TRACE( "COMMIT FILE - handle %d\n", BX_reg(context) );
4909         if (!FlushFileBuffers( DosFileHandleToWin32Handle(BX_reg(context)) ))
4910             bSetDOSExtendedError = TRUE;
4911         break;
4912
4913     case 0x6b: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
4914         SET_AL( context, 0 );
4915         break;
4916
4917     case 0x6c: /* EXTENDED OPEN/CREATE */
4918         if (!INT21_CreateFile( context, context->Esi, TRUE,
4919                                BX_reg(context), DL_reg(context) ))
4920             bSetDOSExtendedError = TRUE;
4921         break;
4922
4923     case 0x70: /* MSDOS 7 - GET/SET INTERNATIONALIZATION INFORMATION */
4924         FIXME( "MS-DOS 7 - GET/SET INTERNATIONALIZATION INFORMATION\n" );
4925         SET_CFLAG( context );
4926         SET_AL( context, 0 );
4927         break;
4928
4929     case 0x71: /* MSDOS 7 - LONG FILENAME FUNCTIONS */
4930         INT21_LongFilename( context );
4931         break;
4932
4933     case 0x73: /* MSDOS7 - FAT32 */
4934         RESET_CFLAG( context );
4935         if (!INT21_Fat32( context ))
4936             bSetDOSExtendedError = TRUE;
4937         break;
4938
4939     case 0xdc: /* CONNECTION SERVICES - GET CONNECTION NUMBER */
4940         TRACE( "CONNECTION SERVICES - GET CONNECTION NUMBER - ignored\n" );
4941         break;
4942
4943     case 0xea: /* NOVELL NETWARE - RETURN SHELL VERSION */
4944         TRACE( "NOVELL NETWARE - RETURN SHELL VERSION - ignored\n" );
4945         break;
4946
4947     case 0xff: /* DOS32 EXTENDER (DOS/4GW) - API */
4948         /* we don't implement a DOS32 extender */
4949         TRACE( "DOS32 EXTENDER API - ignored\n" );
4950         break;
4951
4952     default:
4953         INT_BARF( context, 0x21 );
4954         break;
4955
4956     } /* END OF SWITCH */
4957
4958     /* Set general error condition. */
4959     if (bSetDOSExtendedError)
4960     {
4961         SET_AX( context, GetLastError() );
4962         SET_CFLAG( context );
4963     }
4964
4965     /* Print error code if carry flag is set. */
4966     if (context->EFlags & 0x0001)
4967         TRACE("failed, error %ld\n", GetLastError() );
4968
4969     TRACE( "returning: AX=%04x BX=%04x CX=%04x DX=%04x "
4970            "SI=%04x DI=%04x DS=%04x ES=%04x EFL=%08lx\n",
4971            AX_reg(context), BX_reg(context), 
4972            CX_reg(context), DX_reg(context), 
4973            SI_reg(context), DI_reg(context),
4974            (WORD)context->SegDs, (WORD)context->SegEs,
4975            context->EFlags );
4976 }