wined3d: Move wined3drs_fogdensity to the state table.
[wine.git] / dlls / winedos / int21.c
blob86224aed9b8ce9e080ac7f7e5785e81436765f26
1 /*
2 * DOS interrupt 21h handler
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
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.
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.
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 #include "config.h"
27 #include "wine/port.h"
29 #include <stdarg.h>
30 #include <stdio.h>
31 #ifdef HAVE_SYS_STAT_H
32 # include <sys/stat.h>
33 #endif
34 #ifdef HAVE_UNISTD_H
35 # include <unistd.h>
36 #endif
38 #include "windef.h"
39 #include "winbase.h"
40 #include "winreg.h"
41 #include "winternl.h"
42 #include "wine/winbase16.h"
43 #include "dosexe.h"
44 #include "winerror.h"
45 #include "winuser.h"
46 #include "wine/unicode.h"
47 #include "wine/server.h"
48 #include "wine/debug.h"
49 #include "wine/exception.h"
51 BOOL WINAPI VerifyConsoleIoHandle(HANDLE);
53 * Note:
54 * - Most of the file related functions are wrong. NT's kernel32
55 * doesn't maintain a per drive current directory, while DOS does.
56 * We should in fact keep track in here of those per drive
57 * directories, and use this info while dealing with partial paths
58 * (drive defined, but only relative paths). This could even be
59 * created as an array of CDS (there should be an entry for that in
60 * the LOL)
64 * Forward declarations.
66 static BOOL INT21_RenameFile( CONTEXT86 *context );
68 WINE_DEFAULT_DEBUG_CHANNEL(int21);
71 #include "pshpack1.h"
74 * Extended Drive Parameter Block.
75 * This structure is compatible with standard DOS4+ DPB and
76 * extended DOS7 DPB.
78 typedef struct _INT21_DPB {
79 BYTE drive; /* 00 drive number (0=A, ...) */
80 BYTE unit; /* 01 unit number within device driver */
81 WORD sector_bytes; /* 02 bytes per sector */
82 BYTE cluster_sectors; /* 04 highest sector number within a cluster */
83 BYTE shift; /* 05 shift count to convert clusters into sectors */
84 WORD num_reserved; /* 06 reserved sectors at beginning of drive */
85 BYTE num_FAT; /* 08 number of FATs */
86 WORD num_root_entries; /* 09 number of root directory entries */
87 WORD first_data_sector; /* 0b number of first sector containing user data */
88 WORD num_clusters1; /* 0d highest cluster number (number of data clusters + 1) */
89 WORD sectors_per_FAT; /* 0f number of sectors per FAT */
90 WORD first_dir_sector; /* 11 sector number of first directory sector */
91 SEGPTR driver_header; /* 13 address of device driver header */
92 BYTE media_ID; /* 17 media ID byte */
93 BYTE access_flag; /* 18 0x00 if disk accessed, 0xff if not */
94 SEGPTR next; /* 19 pointer to next DPB */
95 WORD search_cluster1; /* 1d cluster at which to start search for free space */
96 WORD free_clusters_lo; /* 1f number of free clusters on drive or 0xffff if unknown */
97 WORD free_clusters_hi; /* 21 hiword of clusters_free */
98 WORD mirroring_flags; /* 23 active FAT/mirroring flags */
99 WORD info_sector; /* 25 sector number of file system info sector or 0xffff for none */
100 WORD spare_boot_sector; /* 27 sector number of backup boot sector or 0xffff for none */
101 DWORD first_cluster_sector; /* 29 sector number of the first cluster */
102 DWORD num_clusters2; /* 2d maximum cluster number */
103 DWORD fat_clusters; /* 31 number of clusters occupied by FAT */
104 DWORD root_cluster; /* 35 cluster number of start of root directory */
105 DWORD search_cluster2; /* 39 cluster at which to start searching for free space */
106 } INT21_DPB;
110 * Structure for DOS data that can be accessed directly from applications.
111 * Real and protected mode pointers will be returned to this structure so
112 * the structure must be correctly packed.
114 typedef struct _INT21_HEAP {
115 WORD uppercase_size; /* Size of the following table in bytes */
116 BYTE uppercase_table[128]; /* Uppercase equivalents of chars from 0x80 to 0xff. */
118 WORD lowercase_size; /* Size of the following table in bytes */
119 BYTE lowercase_table[256]; /* Lowercase equivalents of chars from 0x00 to 0xff. */
121 WORD collating_size; /* Size of the following table in bytes */
122 BYTE collating_table[256]; /* Values used to sort characters from 0x00 to 0xff. */
124 WORD filename_size; /* Size of the following filename data in bytes */
125 BYTE filename_reserved1; /* 0x01 for MS-DOS 3.30-6.00 */
126 BYTE filename_lowest; /* Lowest permissible character value for filename */
127 BYTE filename_highest; /* Highest permissible character value for filename */
128 BYTE filename_reserved2; /* 0x00 for MS-DOS 3.30-6.00 */
129 BYTE filename_exclude_first; /* First illegal character in permissible range */
130 BYTE filename_exclude_last; /* Last illegal character in permissible range */
131 BYTE filename_reserved3; /* 0x02 for MS-DOS 3.30-6.00 */
132 BYTE filename_illegal_size; /* Number of terminators in the following table */
133 BYTE filename_illegal_table[16]; /* Characters which terminate a filename */
135 WORD dbcs_size; /* Number of valid ranges in the following table */
136 BYTE dbcs_table[16]; /* Start/end bytes for N ranges and 00/00 as terminator */
138 BYTE misc_indos; /* Interrupt 21 nesting flag */
139 WORD misc_segment; /* Real mode segment for INT21_HEAP */
140 WORD misc_selector; /* Protected mode selector for INT21_HEAP */
141 INT21_DPB misc_dpb_list[MAX_DOS_DRIVES]; /* Drive parameter blocks for all drives */
143 } INT21_HEAP;
146 struct FCB {
147 BYTE drive_number;
148 CHAR file_name[8];
149 CHAR file_extension[3];
150 WORD current_block_number;
151 WORD logical_record_size;
152 DWORD file_size;
153 WORD date_of_last_write;
154 WORD time_of_last_write;
155 BYTE file_number;
156 BYTE attributes;
157 WORD starting_cluster;
158 WORD sequence_number;
159 BYTE file_attributes;
160 BYTE unused;
161 BYTE record_within_current_block;
162 BYTE random_access_record_number[4];
166 struct XFCB {
167 BYTE xfcb_signature;
168 BYTE reserved[5];
169 BYTE xfcb_file_attribute;
170 BYTE fcb[37];
173 /* DTA layout for FindFirst/FindNext */
174 typedef struct
176 BYTE drive; /* 00 drive letter */
177 char mask[11]; /* 01 search template */
178 BYTE search_attr; /* 0c search attributes */
179 WORD count; /* 0d entry count within directory */
180 WORD cluster; /* 0f cluster of parent directory */
181 WCHAR *fullPath; /* 11 full path (was: reserved) */
182 BYTE fileattr; /* 15 file attributes */
183 WORD filetime; /* 16 file time */
184 WORD filedate; /* 18 file date */
185 DWORD filesize; /* 1a file size */
186 char filename[13]; /* 1e file name + extension */
187 } FINDFILE_DTA;
189 /* FCB layout for FindFirstFCB/FindNextFCB */
190 typedef struct
192 BYTE drive; /* 00 drive letter */
193 char filename[11]; /* 01 filename 8+3 format */
194 int count; /* 0c entry count (was: reserved) */
195 WCHAR *fullPath; /* 10 full path (was: reserved) */
196 } FINDFILE_FCB;
198 /* DOS directory entry for FindFirstFCB/FindNextFCB */
199 typedef struct
201 char filename[11]; /* 00 filename 8+3 format */
202 BYTE fileattr; /* 0b file attributes */
203 BYTE reserved[10]; /* 0c reserved */
204 WORD filetime; /* 16 file time */
205 WORD filedate; /* 18 file date */
206 WORD cluster; /* 1a file first cluster */
207 DWORD filesize; /* 1c file size */
208 } DOS_DIRENTRY_LAYOUT;
210 #include "poppack.h"
212 /* dos file attributes */
213 #define FA_NORMAL 0x00 /* Normal file, no attributes */
214 #define FA_RDONLY 0x01 /* Read only attribute */
215 #define FA_HIDDEN 0x02 /* Hidden file */
216 #define FA_SYSTEM 0x04 /* System file */
217 #define FA_LABEL 0x08 /* Volume label */
218 #define FA_DIRECTORY 0x10 /* Directory */
219 #define FA_ARCHIVE 0x20 /* Archive */
220 #define FA_UNUSED 0x40 /* Unused */
222 /* Error codes */
223 #define ER_NoNetwork 0x49
225 /* Error classes */
226 #define EC_OutOfResource 0x01
227 #define EC_Temporary 0x02
228 #define EC_AccessDenied 0x03
229 #define EC_InternalError 0x04
230 #define EC_HardwareFailure 0x05
231 #define EC_SystemFailure 0x06
232 #define EC_ProgramError 0x07
233 #define EC_NotFound 0x08
234 #define EC_MediaError 0x0b
235 #define EC_Exists 0x0c
236 #define EC_Unknown 0x0d
238 /* Suggested actions */
239 #define SA_Retry 0x01
240 #define SA_DelayedRetry 0x02
241 #define SA_Abort 0x04
242 #define SA_Ignore 0x06
243 #define SA_Ask4Retry 0x07
245 /* Error locus */
246 #define EL_Unknown 0x01
247 #define EL_Disk 0x02
248 #define EL_Network 0x03
249 #define EL_Serial 0x04
250 #define EL_Memory 0x05
252 /* BIOS Keyboard Scancodes */
253 #define KEY_LEFT 0x4B
254 #define KEY_RIGHT 0x4D
255 #define KEY_UP 0x48
256 #define KEY_DOWN 0x50
257 #define KEY_IC 0x52 /* insert char */
258 #define KEY_DC 0x53 /* delete char */
259 #define KEY_BACKSPACE 0x0E
260 #define KEY_HOME 0x47
261 #define KEY_END 0x4F
262 #define KEY_NPAGE 0x49
263 #define KEY_PPAGE 0x51
266 struct magic_device
268 WCHAR name[10];
269 HANDLE handle;
270 LARGE_INTEGER index;
271 void (*ioctl_handler)(CONTEXT86 *);
274 static void INT21_IoctlScsiMgrHandler( CONTEXT86 * );
275 static void INT21_IoctlEMSHandler( CONTEXT86 * );
276 static void INT21_IoctlHPScanHandler( CONTEXT86 * );
278 static struct magic_device magic_devices[] =
280 { {'s','c','s','i','m','g','r','$',0}, NULL, { { 0, 0 } }, INT21_IoctlScsiMgrHandler },
281 { {'e','m','m','x','x','x','x','0',0}, NULL, { { 0, 0 } }, INT21_IoctlEMSHandler },
282 { {'h','p','s','c','a','n',0}, NULL, { { 0, 0 } }, INT21_IoctlHPScanHandler },
285 #define NB_MAGIC_DEVICES (sizeof(magic_devices)/sizeof(magic_devices[0]))
288 /* Many calls translate a drive argument like this:
289 drive number (00h = default, 01h = A:, etc)
291 /******************************************************************
292 * INT21_DriveName
294 * Many calls translate a drive argument like this:
295 * drive number (00h = default, 01h = A:, etc)
297 static const char *INT21_DriveName(int drive)
299 if (drive > 0)
301 if (drive <= 26) return wine_dbg_sprintf("%c:", 'A' + drive - 1);
302 else return wine_dbg_sprintf( "<Bad drive: %d>", drive);
304 return "default";
307 /***********************************************************************
308 * INT21_GetCurrentDrive
310 * Return current drive using scheme (0=A:, 1=B:, 2=C:, ...) or
311 * MAX_DOS_DRIVES on error.
313 static BYTE INT21_GetCurrentDrive(void)
315 WCHAR current_directory[MAX_PATH];
317 if (!GetCurrentDirectoryW( MAX_PATH, current_directory ) ||
318 current_directory[1] != ':')
320 TRACE( "Failed to get current drive.\n" );
321 return MAX_DOS_DRIVES;
324 return toupperW( current_directory[0] ) - 'A';
328 /***********************************************************************
329 * INT21_MapDrive
331 * Convert drive number from scheme (0=default, 1=A:, 2=B:, ...) into
332 * scheme (0=A:, 1=B:, 2=C:, ...) or MAX_DOS_DRIVES on error.
334 static BYTE INT21_MapDrive( BYTE drive )
336 if (drive)
338 WCHAR drivespec[3] = {'A', ':', 0};
339 UINT drivetype;
341 drivespec[0] += drive - 1;
342 drivetype = GetDriveTypeW( drivespec );
344 if (drivetype == DRIVE_UNKNOWN || drivetype == DRIVE_NO_ROOT_DIR)
345 return MAX_DOS_DRIVES;
347 return drive - 1;
350 return INT21_GetCurrentDrive();
354 /***********************************************************************
355 * INT21_SetCurrentDrive
357 * Set current drive. Uses scheme (0=A:, 1=B:, 2=C:, ...).
359 static void INT21_SetCurrentDrive( BYTE drive )
361 WCHAR drivespec[3] = {'A', ':', 0};
363 drivespec[0] += drive;
365 if (!SetCurrentDirectoryW( drivespec ))
366 TRACE( "Failed to set current drive.\n" );
370 /***********************************************************************
371 * INT21_ReadChar
373 * Reads a character from the standard input.
374 * Extended keycodes will be returned as two separate characters.
376 static BOOL INT21_ReadChar( BYTE *input, CONTEXT86 *waitctx )
378 static BYTE pending_scan = 0;
380 if (pending_scan)
382 if (input)
383 *input = pending_scan;
384 if (waitctx)
385 pending_scan = 0;
386 return TRUE;
388 else
390 BYTE ascii;
391 BYTE scan;
392 if (!DOSVM_Int16ReadChar( &ascii, &scan, waitctx ))
393 return FALSE;
395 if (input)
396 *input = ascii;
397 if (waitctx && !ascii)
398 pending_scan = scan;
399 return TRUE;
404 /***********************************************************************
405 * INT21_GetSystemCountryCode
407 * Return DOS country code for default system locale.
409 static WORD INT21_GetSystemCountryCode( void )
412 * FIXME: Determine country code. We should probably use
413 * DOSCONF structure for that.
415 return GetSystemDefaultLangID();
419 /***********************************************************************
420 * INT21_FillCountryInformation
422 * Fill 34-byte buffer with country information data using
423 * default system locale.
425 static void INT21_FillCountryInformation( BYTE *buffer )
427 /* 00 - WORD: date format
428 * 00 = mm/dd/yy
429 * 01 = dd/mm/yy
430 * 02 = yy/mm/dd
432 *(WORD*)(buffer + 0) = 0; /* FIXME: Get from locale */
434 /* 02 - BYTE[5]: ASCIIZ currency symbol string */
435 buffer[2] = '$'; /* FIXME: Get from locale */
436 buffer[3] = 0;
438 /* 07 - BYTE[2]: ASCIIZ thousands separator */
439 buffer[7] = 0; /* FIXME: Get from locale */
440 buffer[8] = 0;
442 /* 09 - BYTE[2]: ASCIIZ decimal separator */
443 buffer[9] = '.'; /* FIXME: Get from locale */
444 buffer[10] = 0;
446 /* 11 - BYTE[2]: ASCIIZ date separator */
447 buffer[11] = '/'; /* FIXME: Get from locale */
448 buffer[12] = 0;
450 /* 13 - BYTE[2]: ASCIIZ time separator */
451 buffer[13] = ':'; /* FIXME: Get from locale */
452 buffer[14] = 0;
454 /* 15 - BYTE: Currency format
455 * bit 2 = set if currency symbol replaces decimal point
456 * bit 1 = number of spaces between value and currency symbol
457 * bit 0 = 0 if currency symbol precedes value
458 * 1 if currency symbol follows value
460 buffer[15] = 0; /* FIXME: Get from locale */
462 /* 16 - BYTE: Number of digits after decimal in currency */
463 buffer[16] = 0; /* FIXME: Get from locale */
465 /* 17 - BYTE: Time format
466 * bit 0 = 0 if 12-hour clock
467 * 1 if 24-hour clock
469 buffer[17] = 1; /* FIXME: Get from locale */
471 /* 18 - DWORD: Address of case map routine */
472 *(DWORD*)(buffer + 18) = 0; /* FIXME: ptr to case map routine */
474 /* 22 - BYTE[2]: ASCIIZ data-list separator */
475 buffer[22] = ','; /* FIXME: Get from locale */
476 buffer[23] = 0;
478 /* 24 - BYTE[10]: Reserved */
479 memset( buffer + 24, 0, 10 );
483 /***********************************************************************
484 * INT21_FillHeap
486 * Initialize DOS heap.
488 * Filename Terminator Table of w2k DE NTVDM:
489 * 16 00 01 00 FF 00 00 20-02 0E 2E 22 2F 5C 5B 5D ....... ..."/\[]
490 * 3A 7C 3C 3E 2B 3D 3B 2C-00 :|<>+=;,
492 static void INT21_FillHeap( INT21_HEAP *heap )
494 static const char terminators[] = ".\"/\\[]:|<>+=;,";
495 int i;
498 * Uppercase table.
500 heap->uppercase_size = 128;
501 for (i = 0; i < 128; i++)
502 heap->uppercase_table[i] = toupper( 128 + i );
505 * Lowercase table.
507 heap->lowercase_size = 256;
508 for (i = 0; i < 256; i++)
509 heap->lowercase_table[i] = tolower( i );
512 * Collating table.
514 heap->collating_size = 256;
515 for (i = 0; i < 256; i++)
516 heap->collating_table[i] = i;
519 * Filename table.
521 heap->filename_size = 8 + strlen(terminators);
522 heap->filename_illegal_size = strlen(terminators);
523 memcpy( heap->filename_illegal_table, terminators, heap->filename_illegal_size );
525 heap->filename_reserved1 = 0x01;
526 heap->filename_lowest = 0x00;
527 heap->filename_highest = 0xff;
528 heap->filename_reserved2 = 0x00;
529 heap->filename_exclude_first = 0x00;
530 heap->filename_exclude_last = 0x20;
531 heap->filename_reserved3 = 0x02;
534 * DBCS lead byte table. This table is empty.
536 heap->dbcs_size = 0;
537 memset( heap->dbcs_table, 0, sizeof(heap->dbcs_table) );
540 * Initialize InDos flag.
542 heap->misc_indos = 0;
545 * FIXME: Should drive parameter blocks (DPB) be
546 * initialized here and linked to DOS LOL?
551 /***********************************************************************
552 * INT21_GetHeapPointer
554 * Get pointer for DOS heap (INT21_HEAP).
555 * Creates and initializes heap on first call.
557 static INT21_HEAP *INT21_GetHeapPointer( void )
559 static INT21_HEAP *heap_pointer = NULL;
561 if (!heap_pointer)
563 WORD heap_segment;
564 WORD heap_selector;
566 heap_pointer = DOSVM_AllocDataUMB( sizeof(INT21_HEAP),
567 &heap_segment,
568 &heap_selector );
570 heap_pointer->misc_segment = heap_segment;
571 heap_pointer->misc_selector = heap_selector;
572 INT21_FillHeap( heap_pointer );
575 return heap_pointer;
579 /***********************************************************************
580 * INT21_GetHeapSelector
582 * Get segment/selector for DOS heap (INT21_HEAP).
583 * Creates and initializes heap on first call.
585 static WORD INT21_GetHeapSelector( CONTEXT86 *context )
587 INT21_HEAP *heap = INT21_GetHeapPointer();
589 if (!ISV86(context) && DOSVM_IsWin16())
590 return heap->misc_selector;
591 else
592 return heap->misc_segment;
596 /***********************************************************************
597 * INT21_FillDrivePB
599 * Fill DOS heap drive parameter block for the specified drive.
600 * Return TRUE if drive was valid and there were
601 * no errors while reading drive information.
603 static BOOL INT21_FillDrivePB( BYTE drive )
605 WCHAR drivespec[3] = {'A', ':', 0};
606 INT21_HEAP *heap = INT21_GetHeapPointer();
607 INT21_DPB *dpb;
608 UINT drivetype;
609 DWORD cluster_sectors;
610 DWORD sector_bytes;
611 DWORD free_clusters;
612 DWORD total_clusters;
614 if (drive >= MAX_DOS_DRIVES)
615 return FALSE;
617 dpb = &heap->misc_dpb_list[drive];
618 drivespec[0] += drive;
619 drivetype = GetDriveTypeW( drivespec );
622 * FIXME: Does this check work correctly with floppy/cdrom drives?
624 if (drivetype == DRIVE_NO_ROOT_DIR || drivetype == DRIVE_UNKNOWN)
625 return FALSE;
628 * FIXME: Does this check work correctly with floppy/cdrom drives?
630 if (!GetDiskFreeSpaceW( drivespec, &cluster_sectors, &sector_bytes,
631 &free_clusters, &total_clusters ))
632 return FALSE;
635 * FIXME: Most of the values listed below are incorrect.
636 * All values should be validated.
639 dpb->drive = drive;
640 dpb->unit = 0;
641 dpb->sector_bytes = sector_bytes;
642 dpb->cluster_sectors = cluster_sectors - 1;
644 dpb->shift = 0;
645 while (cluster_sectors > 1)
647 cluster_sectors /= 2;
648 dpb->shift++;
651 dpb->num_reserved = 0;
652 dpb->num_FAT = 1;
653 dpb->num_root_entries = 2;
654 dpb->first_data_sector = 2;
655 dpb->num_clusters1 = total_clusters;
656 dpb->sectors_per_FAT = 1;
657 dpb->first_dir_sector = 1;
658 dpb->driver_header = 0;
659 dpb->media_ID = (drivetype == DRIVE_FIXED) ? 0xF8 : 0xF0;
660 dpb->access_flag = 0;
661 dpb->next = 0;
662 dpb->search_cluster1 = 0;
663 dpb->free_clusters_lo = LOWORD(free_clusters);
664 dpb->free_clusters_hi = HIWORD(free_clusters);
665 dpb->mirroring_flags = 0;
666 dpb->info_sector = 0xffff;
667 dpb->spare_boot_sector = 0xffff;
668 dpb->first_cluster_sector = 0;
669 dpb->num_clusters2 = total_clusters;
670 dpb->fat_clusters = 32;
671 dpb->root_cluster = 0;
672 dpb->search_cluster2 = 0;
674 return TRUE;
678 /***********************************************************************
679 * INT21_GetCurrentDirectory
681 * Handler for:
682 * - function 0x47
683 * - subfunction 0x47 of function 0x71
685 static BOOL INT21_GetCurrentDirectory( CONTEXT86 *context, BOOL islong )
687 char *buffer = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi);
688 BYTE drive = INT21_MapDrive( DL_reg(context) );
689 WCHAR pathW[MAX_PATH];
690 char pathA[MAX_PATH];
691 WCHAR *ptr = pathW;
693 TRACE( "drive %d\n", DL_reg(context) );
695 if (drive == MAX_DOS_DRIVES)
697 SetLastError(ERROR_INVALID_DRIVE);
698 return FALSE;
702 * Grab current directory.
705 if (!GetCurrentDirectoryW( MAX_PATH, pathW )) return FALSE;
707 if (toupperW(pathW[0]) - 'A' != drive || pathW[1] != ':')
709 /* cwd is not on the requested drive, get the environment string instead */
711 WCHAR env_var[4];
713 env_var[0] = '=';
714 env_var[1] = 'A' + drive;
715 env_var[2] = ':';
716 env_var[3] = 0;
717 if (!GetEnvironmentVariableW( env_var, pathW, MAX_PATH ))
719 /* return empty path */
720 buffer[0] = 0;
721 return TRUE;
726 * Convert into short format.
729 if (!islong)
731 DWORD result = GetShortPathNameW( pathW, pathW, MAX_PATH );
732 if (!result)
733 return FALSE;
734 if (result > MAX_PATH)
736 WARN( "Short path too long!\n" );
737 SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
738 return FALSE;
743 * The returned pathname does not include
744 * the drive letter, colon or leading backslash.
747 if (ptr[0] == '\\')
750 * FIXME: We should probably just strip host part from name...
752 FIXME( "UNC names are not supported.\n" );
753 SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
754 return FALSE;
756 else if (!ptr[0] || ptr[1] != ':' || ptr[2] != '\\')
758 WARN( "Path is neither UNC nor DOS path: %s\n",
759 wine_dbgstr_w(ptr) );
760 SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
761 return FALSE;
763 else
765 /* Remove drive letter, colon and leading backslash. */
766 ptr += 3;
770 * Convert into OEM string.
773 if (!WideCharToMultiByte(CP_OEMCP, 0, ptr, -1, pathA,
774 MAX_PATH, NULL, NULL))
776 WARN( "Cannot convert path!\n" );
777 SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */
778 return FALSE;
782 * Success.
785 if (!islong)
787 /* Undocumented success code. */
788 SET_AX( context, 0x0100 );
790 /* Truncate buffer to 64 bytes. */
791 pathA[63] = 0;
794 TRACE( "%c:=%s\n", 'A' + drive, pathA );
796 strcpy( buffer, pathA );
797 return TRUE;
801 /***********************************************************************
802 * INT21_SetCurrentDirectory
804 * Handler for:
805 * - function 0x3b
806 * - subfunction 0x3b of function 0x71
808 static BOOL INT21_SetCurrentDirectory( CONTEXT86 *context )
810 WCHAR dirW[MAX_PATH];
811 WCHAR env_var[4];
812 DWORD attr;
813 char *dirA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
814 BYTE drive = INT21_GetCurrentDrive();
815 BOOL result;
817 TRACE( "SET CURRENT DIRECTORY %s\n", dirA );
819 MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
820 if (!GetFullPathNameW( dirW, MAX_PATH, dirW, NULL )) return FALSE;
822 attr = GetFileAttributesW( dirW );
823 if (attr == INVALID_FILE_ATTRIBUTES || !(attr & FILE_ATTRIBUTE_DIRECTORY))
825 SetLastError( ERROR_PATH_NOT_FOUND );
826 return FALSE;
829 env_var[0] = '=';
830 env_var[1] = dirW[0];
831 env_var[2] = ':';
832 env_var[3] = 0;
833 result = SetEnvironmentVariableW( env_var, dirW );
835 /* only set current directory if on the current drive */
836 if (result && (toupperW(dirW[0]) - 'A' == drive)) result = SetCurrentDirectoryW( dirW );
838 return result;
841 /***********************************************************************
842 * INT21_CreateMagicDeviceHandle
844 * Create a dummy file handle for a "magic" device.
846 static HANDLE INT21_CreateMagicDeviceHandle( LPCWSTR name )
848 const char *dir = wine_get_server_dir();
849 int len;
850 HANDLE ret;
851 NTSTATUS status;
852 OBJECT_ATTRIBUTES attr;
853 UNICODE_STRING nameW;
854 IO_STATUS_BLOCK io;
856 len = MultiByteToWideChar( CP_UNIXCP, 0, dir, -1, NULL, 0 );
857 nameW.Length = (len + 1 + strlenW( name )) * sizeof(WCHAR);
858 nameW.MaximumLength = nameW.Length + sizeof(WCHAR);
859 if (!(nameW.Buffer = HeapAlloc( GetProcessHeap(), 0, nameW.Length )))
861 SetLastError( ERROR_NOT_ENOUGH_MEMORY );
862 return 0;
864 MultiByteToWideChar( CP_UNIXCP, 0, dir, -1, nameW.Buffer, len );
865 nameW.Buffer[len-1] = '/';
866 strcpyW( nameW.Buffer + len, name );
868 attr.Length = sizeof(attr);
869 attr.RootDirectory = 0;
870 attr.Attributes = 0;
871 attr.ObjectName = &nameW;
872 attr.SecurityDescriptor = NULL;
873 attr.SecurityQualityOfService = NULL;
875 status = NtCreateFile( &ret, GENERIC_READ|GENERIC_WRITE, &attr, &io, NULL, 0,
876 FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN_IF,
877 FILE_SYNCHRONOUS_IO_ALERT, NULL, 0 );
878 if (status)
880 ret = 0;
881 SetLastError( RtlNtStatusToDosError(status) );
883 RtlFreeUnicodeString( &nameW );
884 return ret;
888 /***********************************************************************
889 * INT21_OpenMagicDevice
891 * Open a file handle for "magic" devices like EMMXXXX0.
893 static HANDLE INT21_OpenMagicDevice( LPCWSTR name, DWORD access )
895 unsigned int i;
896 const WCHAR *p;
897 HANDLE handle;
899 if (name[0] && (name[1] == ':')) name += 2;
900 if ((p = strrchrW( name, '/' ))) name = p + 1;
901 if ((p = strrchrW( name, '\\' ))) name = p + 1;
903 for (i = 0; i < NB_MAGIC_DEVICES; i++)
905 int len = strlenW( magic_devices[i].name );
906 if (!strncmpiW( magic_devices[i].name, name, len ) &&
907 (!name[len] || name[len] == '.' || name[len] == ':')) break;
909 if (i == NB_MAGIC_DEVICES) return 0;
911 if (!magic_devices[i].handle) /* need to open it */
913 IO_STATUS_BLOCK io;
914 FILE_INTERNAL_INFORMATION info;
916 if (!(handle = INT21_CreateMagicDeviceHandle( magic_devices[i].name ))) return 0;
918 NtQueryInformationFile( handle, &io, &info, sizeof(info), FileInternalInformation );
919 magic_devices[i].index = info.IndexNumber;
920 magic_devices[i].handle = handle;
922 if (!DuplicateHandle( GetCurrentProcess(), magic_devices[i].handle,
923 GetCurrentProcess(), &handle, access, FALSE, 0 )) handle = 0;
924 return handle;
928 /***********************************************************************
929 * INT21_CreateFile
931 * Handler for:
932 * - function 0x3c
933 * - function 0x3d
934 * - function 0x5b
935 * - function 0x6c
936 * - subfunction 0x6c of function 0x71
938 static BOOL INT21_CreateFile( CONTEXT86 *context,
939 DWORD pathSegOff,
940 BOOL returnStatus,
941 WORD dosAccessShare,
942 BYTE dosAction )
944 WORD dosStatus;
945 char *pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, pathSegOff);
946 WCHAR pathW[MAX_PATH];
947 DWORD winAccess;
948 DWORD winAttributes;
949 HANDLE winHandle;
950 DWORD winMode;
951 DWORD winSharing;
953 TRACE( "CreateFile called: function=%02x, action=%02x, access/share=%04x, "
954 "create flags=%04x, file=%s.\n",
955 AH_reg(context), dosAction, dosAccessShare, CX_reg(context), pathA );
958 * Application tried to create/open a file whose name
959 * ends with a backslash. This is not allowed.
961 * FIXME: This needs to be validated, especially the return value.
963 if (pathA[strlen(pathA) - 1] == '/')
965 SetLastError( ERROR_FILE_NOT_FOUND );
966 return FALSE;
970 * Convert DOS action flags into Win32 creation disposition parameter.
972 switch(dosAction)
974 case 0x01:
975 winMode = OPEN_EXISTING;
976 break;
977 case 0x02:
978 winMode = TRUNCATE_EXISTING;
979 break;
980 case 0x10:
981 winMode = CREATE_NEW;
982 break;
983 case 0x11:
984 winMode = OPEN_ALWAYS;
985 break;
986 case 0x12:
987 winMode = CREATE_ALWAYS;
988 break;
989 default:
990 SetLastError( ERROR_INVALID_PARAMETER );
991 return FALSE;
995 * Convert DOS access/share flags into Win32 desired access parameter.
997 switch(dosAccessShare & 0x07)
999 case OF_READ:
1000 winAccess = GENERIC_READ;
1001 break;
1002 case OF_WRITE:
1003 winAccess = GENERIC_WRITE;
1004 break;
1005 case OF_READWRITE:
1006 winAccess = GENERIC_READ | GENERIC_WRITE;
1007 break;
1008 case 0x04:
1010 * Read-only, do not modify file's last-access time (DOS7).
1012 * FIXME: How to prevent modification of last-access time?
1014 winAccess = GENERIC_READ;
1015 break;
1016 default:
1017 winAccess = 0;
1021 * Convert DOS access/share flags into Win32 share mode parameter.
1023 switch(dosAccessShare & 0x70)
1025 case OF_SHARE_EXCLUSIVE:
1026 winSharing = 0;
1027 break;
1028 case OF_SHARE_DENY_WRITE:
1029 winSharing = FILE_SHARE_READ;
1030 break;
1031 case OF_SHARE_DENY_READ:
1032 winSharing = FILE_SHARE_WRITE;
1033 break;
1034 case OF_SHARE_DENY_NONE:
1035 case OF_SHARE_COMPAT:
1036 default:
1037 winSharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
1041 * FIXME: Bit (dosAccessShare & 0x80) represents inheritance.
1042 * What to do with this bit?
1043 * FIXME: Bits in the high byte of dosAccessShare are not supported.
1044 * See both function 0x6c and subfunction 0x6c of function 0x71 for
1045 * definition of these bits.
1049 * Convert DOS create attributes into Win32 flags and attributes parameter.
1051 if (winMode == OPEN_EXISTING || winMode == TRUNCATE_EXISTING)
1053 winAttributes = 0;
1055 else
1057 WORD dosAttributes = CX_reg(context);
1059 if (dosAttributes & FA_LABEL)
1062 * Application tried to create volume label entry.
1063 * This is difficult to support so we do not allow it.
1065 * FIXME: If volume does not already have a label,
1066 * this function is supposed to succeed.
1068 SetLastError( ERROR_ACCESS_DENIED );
1069 return TRUE;
1072 winAttributes = dosAttributes &
1073 (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN |
1074 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE);
1078 * Open the file.
1080 MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
1082 if ((winHandle = INT21_OpenMagicDevice( pathW, winAccess )))
1084 dosStatus = 1;
1086 else
1088 winHandle = CreateFileW( pathW, winAccess, winSharing, NULL,
1089 winMode, winAttributes, 0 );
1090 /* DOS allows to open files on a CDROM R/W */
1091 if( winHandle == INVALID_HANDLE_VALUE &&
1092 GetLastError()== ERROR_WRITE_PROTECT) {
1093 winHandle = CreateFileW( pathW, winAccess & ~GENERIC_WRITE,
1094 winSharing, NULL, winMode, winAttributes, 0 );
1097 if (winHandle == INVALID_HANDLE_VALUE)
1098 return FALSE;
1101 * Determine DOS file status.
1103 * 1 = file opened
1104 * 2 = file created
1105 * 3 = file replaced
1107 switch(winMode)
1109 case OPEN_EXISTING:
1110 dosStatus = 1;
1111 break;
1112 case TRUNCATE_EXISTING:
1113 dosStatus = 3;
1114 break;
1115 case CREATE_NEW:
1116 dosStatus = 2;
1117 break;
1118 case OPEN_ALWAYS:
1119 dosStatus = (GetLastError() == ERROR_ALREADY_EXISTS) ? 1 : 2;
1120 break;
1121 case CREATE_ALWAYS:
1122 dosStatus = (GetLastError() == ERROR_ALREADY_EXISTS) ? 3 : 2;
1123 break;
1124 default:
1125 dosStatus = 0;
1130 * Return DOS file handle and DOS status.
1132 SET_AX( context, Win32HandleToDosFileHandle(winHandle) );
1134 if (returnStatus)
1135 SET_CX( context, dosStatus );
1137 TRACE( "CreateFile finished: handle=%d, status=%d.\n",
1138 AX_reg(context), dosStatus );
1140 return TRUE;
1144 /***********************************************************************
1145 * INT21_BufferedInput
1147 * Handler for function 0x0a and reading from console using
1148 * function 0x3f.
1150 * Reads a string of characters from standard input until
1151 * enter key is pressed. Returns either number of characters
1152 * read from console including terminating CR or
1153 * zero if capacity was zero.
1155 static WORD INT21_BufferedInput( CONTEXT86 *context, BYTE *ptr, WORD capacity )
1157 BYTE length = 0;
1160 * Return immediately if capacity is zero.
1162 if (capacity == 0)
1163 return 0;
1165 while(TRUE)
1167 BYTE ascii;
1168 BYTE scan;
1170 DOSVM_Int16ReadChar( &ascii, &scan, context );
1172 if (ascii == '\r' || ascii == '\n')
1174 ptr[length] = '\r';
1175 return length + 1;
1179 * DOS handles only backspace and KEY_LEFT
1180 * perhaps we should do more
1182 if (ascii == '\b' || scan == KEY_LEFT)
1184 if (length==0) continue;
1185 DOSVM_PutChar( '\b' );
1186 length--;
1187 continue;
1191 * If the buffer becomes filled to within one byte of
1192 * capacity, DOS rejects all further characters up to,
1193 * but not including, the terminating carriage return.
1195 if (ascii != 0 && length < capacity-1)
1197 DOSVM_PutChar( ascii );
1198 ptr[length] = ascii;
1199 length++;
1205 /***********************************************************************
1206 * INT21_GetCurrentDTA
1208 static BYTE *INT21_GetCurrentDTA( CONTEXT86 *context )
1210 TDB *pTask = GlobalLock16(GetCurrentTask());
1212 /* FIXME: This assumes DTA was set correctly! */
1213 return (BYTE *)CTX_SEG_OFF_TO_LIN( context, SELECTOROF(pTask->dta),
1214 (DWORD)OFFSETOF(pTask->dta) );
1218 /***********************************************************************
1219 * INT21_OpenFileUsingFCB
1221 * Handler for function 0x0f.
1223 * PARAMS
1224 * DX:DX [I/O] File control block (FCB or XFCB) of unopened file
1226 * RETURNS (in AL)
1227 * 0x00: successful
1228 * 0xff: failed
1230 * NOTES
1231 * Opens a FCB file for read/write in compatibility mode. Upon calling
1232 * the FCB must have the drive_number, file_name, and file_extension
1233 * fields filled and all other bytes cleared.
1235 static void INT21_OpenFileUsingFCB( CONTEXT86 *context )
1237 struct FCB *fcb;
1238 struct XFCB *xfcb;
1239 char file_path[16];
1240 char *pos;
1241 HANDLE handle;
1242 HFILE16 hfile16;
1243 BY_HANDLE_FILE_INFORMATION info;
1244 BYTE AL_result;
1246 fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1247 if (fcb->drive_number == 0xff) {
1248 xfcb = (struct XFCB *) fcb;
1249 fcb = (struct FCB *) xfcb->fcb;
1250 } /* if */
1252 AL_result = 0;
1253 file_path[0] = 'A' + INT21_MapDrive( fcb->drive_number );
1255 if (AL_result == 0) {
1256 file_path[1] = ':';
1257 pos = &file_path[2];
1258 memcpy(pos, fcb->file_name, 8);
1259 pos[8] = ' ';
1260 pos[9] = '\0';
1261 pos = strchr(pos, ' ');
1262 *pos = '.';
1263 pos++;
1264 memcpy(pos, fcb->file_extension, 3);
1265 pos[3] = ' ';
1266 pos[4] = '\0';
1267 pos = strchr(pos, ' ');
1268 *pos = '\0';
1270 handle = (HANDLE) _lopen(file_path, OF_READWRITE);
1271 if (handle == INVALID_HANDLE_VALUE) {
1272 TRACE("_lopen(\"%s\") failed: INVALID_HANDLE_VALUE\n", file_path);
1273 AL_result = 0xff; /* failed */
1274 } else {
1275 hfile16 = Win32HandleToDosFileHandle(handle);
1276 if (hfile16 == HFILE_ERROR16) {
1277 TRACE("Win32HandleToDosFileHandle(%p) failed: HFILE_ERROR\n", handle);
1278 CloseHandle(handle);
1279 AL_result = 0xff; /* failed */
1280 } else if (hfile16 > 255) {
1281 TRACE("hfile16 (=%d) larger than 255 for \"%s\"\n", hfile16, file_path);
1282 _lclose16(hfile16);
1283 AL_result = 0xff; /* failed */
1284 } else {
1285 if (!GetFileInformationByHandle(handle, &info)) {
1286 TRACE("GetFileInformationByHandle(%d, %p) for \"%s\" failed\n",
1287 hfile16, handle, file_path);
1288 _lclose16(hfile16);
1289 AL_result = 0xff; /* failed */
1290 } else {
1291 fcb->drive_number = file_path[0] - 'A' + 1;
1292 fcb->current_block_number = 0;
1293 fcb->logical_record_size = 128;
1294 fcb->file_size = info.nFileSizeLow;
1295 FileTimeToDosDateTime(&info.ftLastWriteTime,
1296 &fcb->date_of_last_write, &fcb->time_of_last_write);
1297 fcb->file_number = hfile16;
1298 fcb->attributes = 0xc2;
1299 fcb->starting_cluster = 0; /* don't know correct init value */
1300 fcb->sequence_number = 0; /* don't know correct init value */
1301 fcb->file_attributes = info.dwFileAttributes;
1302 /* The following fields are not initialized */
1303 /* by the native function: */
1304 /* unused */
1305 /* record_within_current_block */
1306 /* random_access_record_number */
1308 TRACE("successful opened file \"%s\" as %d (handle %p)\n",
1309 file_path, hfile16, handle);
1310 AL_result = 0x00; /* successful */
1311 } /* if */
1312 } /* if */
1313 } /* if */
1314 } /* if */
1315 SET_AL(context, AL_result);
1319 /***********************************************************************
1320 * INT21_CloseFileUsingFCB
1322 * Handler for function 0x10.
1324 * PARAMS
1325 * DX:DX [I/O] File control block (FCB or XFCB) of open file
1327 * RETURNS (in AL)
1328 * 0x00: successful
1329 * 0xff: failed
1331 * NOTES
1332 * Closes a FCB file.
1334 static void INT21_CloseFileUsingFCB( CONTEXT86 *context )
1336 struct FCB *fcb;
1337 struct XFCB *xfcb;
1338 BYTE AL_result;
1340 fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1341 if (fcb->drive_number == 0xff) {
1342 xfcb = (struct XFCB *) fcb;
1343 fcb = (struct FCB *) xfcb->fcb;
1344 } /* if */
1346 if (_lclose16((HFILE16) fcb->file_number) != 0) {
1347 TRACE("_lclose16(%d) failed\n", fcb->file_number);
1348 AL_result = 0xff; /* failed */
1349 } else {
1350 TRACE("successful closed file %d\n", fcb->file_number);
1351 AL_result = 0x00; /* successful */
1352 } /* if */
1353 SET_AL(context, AL_result);
1357 /***********************************************************************
1358 * INT21_SequentialReadFromFCB
1360 * Handler for function 0x14.
1362 * PARAMS
1363 * DX:DX [I/O] File control block (FCB or XFCB) of open file
1365 * RETURNS (in AL)
1366 * 0: successful
1367 * 1: end of file, no data read
1368 * 2: segment wrap in DTA, no data read (not returned now)
1369 * 3: end of file, partial record read
1371 * NOTES
1372 * Reads a record with the size FCB->logical_record_size from the FCB
1373 * to the disk transfer area. The position of the record is specified
1374 * with FCB->current_block_number and FCB->record_within_current_block.
1375 * Then FCB->current_block_number and FCB->record_within_current_block
1376 * are updated to point to the next record. If a partial record is
1377 * read, it is filled with zeros up to the FCB->logical_record_size.
1379 static void INT21_SequentialReadFromFCB( CONTEXT86 *context )
1381 struct FCB *fcb;
1382 struct XFCB *xfcb;
1383 HANDLE handle;
1384 DWORD record_number;
1385 long position;
1386 BYTE *disk_transfer_area;
1387 UINT bytes_read;
1388 BYTE AL_result;
1390 fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1391 if (fcb->drive_number == 0xff) {
1392 xfcb = (struct XFCB *) fcb;
1393 fcb = (struct FCB *) xfcb->fcb;
1394 } /* if */
1396 handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1397 if (handle == INVALID_HANDLE_VALUE) {
1398 TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1399 fcb->file_number);
1400 AL_result = 0x01; /* end of file, no data read */
1401 } else {
1402 record_number = 128 * fcb->current_block_number + fcb->record_within_current_block;
1403 position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1404 if (position != record_number * fcb->logical_record_size) {
1405 TRACE("seek(%d, %d, 0) failed with %ld\n",
1406 fcb->file_number, record_number * fcb->logical_record_size, position);
1407 AL_result = 0x01; /* end of file, no data read */
1408 } else {
1409 disk_transfer_area = INT21_GetCurrentDTA(context);
1410 bytes_read = _lread((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
1411 if (bytes_read != fcb->logical_record_size) {
1412 TRACE("_lread(%d, %p, %d) failed with %d\n",
1413 fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_read);
1414 if (bytes_read == 0) {
1415 AL_result = 0x01; /* end of file, no data read */
1416 } else {
1417 memset(&disk_transfer_area[bytes_read], 0, fcb->logical_record_size - bytes_read);
1418 AL_result = 0x03; /* end of file, partial record read */
1419 } /* if */
1420 } else {
1421 TRACE("successful read %d bytes from record %d (position %ld) of file %d (handle %p)\n",
1422 bytes_read, record_number, position, fcb->file_number, handle);
1423 AL_result = 0x00; /* successful */
1424 } /* if */
1425 } /* if */
1426 } /* if */
1427 if (AL_result == 0x00 || AL_result == 0x03) {
1428 if (fcb->record_within_current_block == 127) {
1429 fcb->record_within_current_block = 0;
1430 fcb->current_block_number++;
1431 } else {
1432 fcb->record_within_current_block++;
1433 } /* if */
1434 } /* if */
1435 SET_AL(context, AL_result);
1439 /***********************************************************************
1440 * INT21_SequentialWriteToFCB
1442 * Handler for function 0x15.
1444 * PARAMS
1445 * DX:DX [I/O] File control block (FCB or XFCB) of open file
1447 * RETURNS (in AL)
1448 * 0: successful
1449 * 1: disk full
1450 * 2: segment wrap in DTA (not returned now)
1452 * NOTES
1453 * Writes a record with the size FCB->logical_record_size from the disk
1454 * transfer area to the FCB. The position of the record is specified
1455 * with FCB->current_block_number and FCB->record_within_current_block.
1456 * Then FCB->current_block_number and FCB->record_within_current_block
1457 * are updated to point to the next record.
1459 static void INT21_SequentialWriteToFCB( CONTEXT86 *context )
1461 struct FCB *fcb;
1462 struct XFCB *xfcb;
1463 HANDLE handle;
1464 DWORD record_number;
1465 long position;
1466 BYTE *disk_transfer_area;
1467 UINT bytes_written;
1468 BYTE AL_result;
1470 fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1471 if (fcb->drive_number == 0xff) {
1472 xfcb = (struct XFCB *) fcb;
1473 fcb = (struct FCB *) xfcb->fcb;
1474 } /* if */
1476 handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1477 if (handle == INVALID_HANDLE_VALUE) {
1478 TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1479 fcb->file_number);
1480 AL_result = 0x01; /* disk full */
1481 } else {
1482 record_number = 128 * fcb->current_block_number + fcb->record_within_current_block;
1483 position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1484 if (position != record_number * fcb->logical_record_size) {
1485 TRACE("seek(%d, %d, 0) failed with %ld\n",
1486 fcb->file_number, record_number * fcb->logical_record_size, position);
1487 AL_result = 0x01; /* disk full */
1488 } else {
1489 disk_transfer_area = INT21_GetCurrentDTA(context);
1490 bytes_written = _lwrite((HFILE) handle, (LPCSTR)disk_transfer_area, fcb->logical_record_size);
1491 if (bytes_written != fcb->logical_record_size) {
1492 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
1493 fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_written);
1494 AL_result = 0x01; /* disk full */
1495 } else {
1496 TRACE("successful written %d bytes from record %d (position %ld) of file %d (handle %p)\n",
1497 bytes_written, record_number, position, fcb->file_number, handle);
1498 AL_result = 0x00; /* successful */
1499 } /* if */
1500 } /* if */
1501 } /* if */
1502 if (AL_result == 0x00) {
1503 if (fcb->record_within_current_block == 127) {
1504 fcb->record_within_current_block = 0;
1505 fcb->current_block_number++;
1506 } else {
1507 fcb->record_within_current_block++;
1508 } /* if */
1509 } /* if */
1510 SET_AL(context, AL_result);
1514 /***********************************************************************
1515 * INT21_ReadRandomRecordFromFCB
1517 * Handler for function 0x21.
1519 * PARAMS
1520 * DX:DX [I/O] File control block (FCB or XFCB) of open file
1522 * RETURNS (in AL)
1523 * 0: successful
1524 * 1: end of file, no data read
1525 * 2: segment wrap in DTA, no data read (not returned now)
1526 * 3: end of file, partial record read
1528 * NOTES
1529 * Reads a record with the size FCB->logical_record_size from
1530 * the FCB to the disk transfer area. The position of the record
1531 * is specified with FCB->random_access_record_number. The
1532 * FCB->random_access_record_number is not updated. If a partial record
1533 * is read, it is filled with zeros up to the FCB->logical_record_size.
1535 static void INT21_ReadRandomRecordFromFCB( CONTEXT86 *context )
1537 struct FCB *fcb;
1538 struct XFCB *xfcb;
1539 HANDLE handle;
1540 DWORD record_number;
1541 long position;
1542 BYTE *disk_transfer_area;
1543 UINT bytes_read;
1544 BYTE AL_result;
1546 fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1547 if (fcb->drive_number == 0xff) {
1548 xfcb = (struct XFCB *) fcb;
1549 fcb = (struct FCB *) xfcb->fcb;
1550 } /* if */
1552 memcpy(&record_number, fcb->random_access_record_number, 4);
1553 handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1554 if (handle == INVALID_HANDLE_VALUE) {
1555 TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1556 fcb->file_number);
1557 AL_result = 0x01; /* end of file, no data read */
1558 } else {
1559 position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1560 if (position != record_number * fcb->logical_record_size) {
1561 TRACE("seek(%d, %d, 0) failed with %ld\n",
1562 fcb->file_number, record_number * fcb->logical_record_size, position);
1563 AL_result = 0x01; /* end of file, no data read */
1564 } else {
1565 disk_transfer_area = INT21_GetCurrentDTA(context);
1566 bytes_read = _lread((HFILE) handle, disk_transfer_area, fcb->logical_record_size);
1567 if (bytes_read != fcb->logical_record_size) {
1568 TRACE("_lread(%d, %p, %d) failed with %d\n",
1569 fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_read);
1570 if (bytes_read == 0) {
1571 AL_result = 0x01; /* end of file, no data read */
1572 } else {
1573 memset(&disk_transfer_area[bytes_read], 0, fcb->logical_record_size - bytes_read);
1574 AL_result = 0x03; /* end of file, partial record read */
1575 } /* if */
1576 } else {
1577 TRACE("successful read %d bytes from record %d (position %ld) of file %d (handle %p)\n",
1578 bytes_read, record_number, position, fcb->file_number, handle);
1579 AL_result = 0x00; /* successful */
1580 } /* if */
1581 } /* if */
1582 } /* if */
1583 fcb->current_block_number = record_number / 128;
1584 fcb->record_within_current_block = record_number % 128;
1585 SET_AL(context, AL_result);
1589 /***********************************************************************
1590 * INT21_WriteRandomRecordToFCB
1592 * Handler for function 0x22.
1594 * PARAMS
1595 * DX:DX [I/O] File control block (FCB or XFCB) of open file
1597 * RETURNS (in AL)
1598 * 0: successful
1599 * 1: disk full
1600 * 2: segment wrap in DTA (not returned now)
1602 * NOTES
1603 * Writes a record with the size FCB->logical_record_size from
1604 * the disk transfer area to the FCB. The position of the record
1605 * is specified with FCB->random_access_record_number. The
1606 * FCB->random_access_record_number is not updated.
1608 static void INT21_WriteRandomRecordToFCB( CONTEXT86 *context )
1610 struct FCB *fcb;
1611 struct XFCB *xfcb;
1612 HANDLE handle;
1613 DWORD record_number;
1614 long position;
1615 BYTE *disk_transfer_area;
1616 UINT bytes_written;
1617 BYTE AL_result;
1619 fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1620 if (fcb->drive_number == 0xff) {
1621 xfcb = (struct XFCB *) fcb;
1622 fcb = (struct FCB *) xfcb->fcb;
1623 } /* if */
1625 memcpy(&record_number, fcb->random_access_record_number, 4);
1626 handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1627 if (handle == INVALID_HANDLE_VALUE) {
1628 TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1629 fcb->file_number);
1630 AL_result = 0x01; /* disk full */
1631 } else {
1632 position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1633 if (position != record_number * fcb->logical_record_size) {
1634 TRACE("seek(%d, %d, 0) failed with %ld\n",
1635 fcb->file_number, record_number * fcb->logical_record_size, position);
1636 AL_result = 0x01; /* disk full */
1637 } else {
1638 disk_transfer_area = INT21_GetCurrentDTA(context);
1639 bytes_written = _lwrite((HFILE) handle, (LPCSTR)disk_transfer_area, fcb->logical_record_size);
1640 if (bytes_written != fcb->logical_record_size) {
1641 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
1642 fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_written);
1643 AL_result = 0x01; /* disk full */
1644 } else {
1645 TRACE("successful written %d bytes from record %d (position %ld) of file %d (handle %p)\n",
1646 bytes_written, record_number, position, fcb->file_number, handle);
1647 AL_result = 0x00; /* successful */
1648 } /* if */
1649 } /* if */
1650 } /* if */
1651 fcb->current_block_number = record_number / 128;
1652 fcb->record_within_current_block = record_number % 128;
1653 SET_AL(context, AL_result);
1657 /***********************************************************************
1658 * INT21_RandomBlockReadFromFCB
1660 * Handler for function 0x27.
1662 * PARAMS
1663 * CX [I/O] Number of records to read
1664 * DX:DX [I/O] File control block (FCB or XFCB) of open file
1666 * RETURNS (in AL)
1667 * 0: successful
1668 * 1: end of file, no data read
1669 * 2: segment wrap in DTA, no data read (not returned now)
1670 * 3: end of file, partial record read
1672 * NOTES
1673 * Reads several records with the size FCB->logical_record_size from
1674 * the FCB to the disk transfer area. The number of records to be
1675 * read is specified in the CX register. The position of the first
1676 * record is specified with FCB->random_access_record_number. The
1677 * FCB->random_access_record_number, the FCB->current_block_number
1678 * and FCB->record_within_current_block are updated to point to the
1679 * next record after the records read. If a partial record is read,
1680 * it is filled with zeros up to the FCB->logical_record_size. The
1681 * CX register is set to the number of successfully read records.
1683 static void INT21_RandomBlockReadFromFCB( CONTEXT86 *context )
1685 struct FCB *fcb;
1686 struct XFCB *xfcb;
1687 HANDLE handle;
1688 DWORD record_number;
1689 long position;
1690 BYTE *disk_transfer_area;
1691 UINT records_requested;
1692 UINT bytes_requested;
1693 UINT bytes_read;
1694 UINT records_read;
1695 BYTE AL_result;
1697 fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1698 if (fcb->drive_number == 0xff) {
1699 xfcb = (struct XFCB *) fcb;
1700 fcb = (struct FCB *) xfcb->fcb;
1701 } /* if */
1703 memcpy(&record_number, fcb->random_access_record_number, 4);
1704 handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1705 if (handle == INVALID_HANDLE_VALUE) {
1706 TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1707 fcb->file_number);
1708 records_read = 0;
1709 AL_result = 0x01; /* end of file, no data read */
1710 } else {
1711 position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1712 if (position != record_number * fcb->logical_record_size) {
1713 TRACE("seek(%d, %d, 0) failed with %ld\n",
1714 fcb->file_number, record_number * fcb->logical_record_size, position);
1715 records_read = 0;
1716 AL_result = 0x01; /* end of file, no data read */
1717 } else {
1718 disk_transfer_area = INT21_GetCurrentDTA(context);
1719 records_requested = CX_reg(context);
1720 bytes_requested = (UINT) records_requested * fcb->logical_record_size;
1721 bytes_read = _lread((HFILE) handle, disk_transfer_area, bytes_requested);
1722 if (bytes_read != bytes_requested) {
1723 TRACE("_lread(%d, %p, %d) failed with %d\n",
1724 fcb->file_number, disk_transfer_area, bytes_requested, bytes_read);
1725 records_read = bytes_read / fcb->logical_record_size;
1726 if (bytes_read % fcb->logical_record_size == 0) {
1727 AL_result = 0x01; /* end of file, no data read */
1728 } else {
1729 records_read++;
1730 memset(&disk_transfer_area[bytes_read], 0, records_read * fcb->logical_record_size - bytes_read);
1731 AL_result = 0x03; /* end of file, partial record read */
1732 } /* if */
1733 } else {
1734 TRACE("successful read %d bytes from record %d (position %ld) of file %d (handle %p)\n",
1735 bytes_read, record_number, position, fcb->file_number, handle);
1736 records_read = records_requested;
1737 AL_result = 0x00; /* successful */
1738 } /* if */
1739 } /* if */
1740 } /* if */
1741 record_number += records_read;
1742 memcpy(fcb->random_access_record_number, &record_number, 4);
1743 fcb->current_block_number = record_number / 128;
1744 fcb->record_within_current_block = record_number % 128;
1745 SET_CX(context, records_read);
1746 SET_AL(context, AL_result);
1750 /***********************************************************************
1751 * INT21_RandomBlockWriteToFCB
1753 * Handler for function 0x28.
1755 * PARAMS
1756 * CX [I/O] Number of records to write
1757 * DX:DX [I/O] File control block (FCB or XFCB) of open file
1759 * RETURNS (in AL)
1760 * 0: successful
1761 * 1: disk full
1762 * 2: segment wrap in DTA (not returned now)
1764 * NOTES
1765 * Writes several records with the size FCB->logical_record_size from
1766 * the disk transfer area to the FCB. The number of records to be
1767 * written is specified in the CX register. The position of the first
1768 * record is specified with FCB->random_access_record_number. The
1769 * FCB->random_access_record_number, the FCB->current_block_number
1770 * and FCB->record_within_current_block are updated to point to the
1771 * next record after the records written. The CX register is set to
1772 * the number of successfully written records.
1774 static void INT21_RandomBlockWriteToFCB( CONTEXT86 *context )
1776 struct FCB *fcb;
1777 struct XFCB *xfcb;
1778 HANDLE handle;
1779 DWORD record_number;
1780 long position;
1781 BYTE *disk_transfer_area;
1782 UINT records_requested;
1783 UINT bytes_requested;
1784 UINT bytes_written;
1785 UINT records_written;
1786 BYTE AL_result;
1788 fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
1789 if (fcb->drive_number == 0xff) {
1790 xfcb = (struct XFCB *) fcb;
1791 fcb = (struct FCB *) xfcb->fcb;
1792 } /* if */
1794 memcpy(&record_number, fcb->random_access_record_number, 4);
1795 handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number);
1796 if (handle == INVALID_HANDLE_VALUE) {
1797 TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n",
1798 fcb->file_number);
1799 records_written = 0;
1800 AL_result = 0x01; /* disk full */
1801 } else {
1802 position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0);
1803 if (position != record_number * fcb->logical_record_size) {
1804 TRACE("seek(%d, %d, 0) failed with %ld\n",
1805 fcb->file_number, record_number * fcb->logical_record_size, position);
1806 records_written = 0;
1807 AL_result = 0x01; /* disk full */
1808 } else {
1809 disk_transfer_area = INT21_GetCurrentDTA(context);
1810 records_requested = CX_reg(context);
1811 bytes_requested = (UINT) records_requested * fcb->logical_record_size;
1812 bytes_written = _lwrite((HFILE) handle, (LPCSTR)disk_transfer_area, bytes_requested);
1813 if (bytes_written != bytes_requested) {
1814 TRACE("_lwrite(%d, %p, %d) failed with %d\n",
1815 fcb->file_number, disk_transfer_area, bytes_requested, bytes_written);
1816 records_written = bytes_written / fcb->logical_record_size;
1817 AL_result = 0x01; /* disk full */
1818 } else {
1819 TRACE("successful write %d bytes from record %d (position %ld) of file %d (handle %p)\n",
1820 bytes_written, record_number, position, fcb->file_number, handle);
1821 records_written = records_requested;
1822 AL_result = 0x00; /* successful */
1823 } /* if */
1824 } /* if */
1825 } /* if */
1826 record_number += records_written;
1827 memcpy(fcb->random_access_record_number, &record_number, 4);
1828 fcb->current_block_number = record_number / 128;
1829 fcb->record_within_current_block = record_number % 128;
1830 SET_CX(context, records_written);
1831 SET_AL(context, AL_result);
1835 /***********************************************************************
1836 * INT21_CreateDirectory
1838 * Handler for:
1839 * - function 0x39
1840 * - subfunction 0x39 of function 0x71
1841 * - subfunction 0xff of function 0x43 (CL == 0x39)
1843 static BOOL INT21_CreateDirectory( CONTEXT86 *context )
1845 WCHAR dirW[MAX_PATH];
1846 char *dirA = CTX_SEG_OFF_TO_LIN(context,
1847 context->SegDs,
1848 context->Edx);
1850 TRACE( "CREATE DIRECTORY %s\n", dirA );
1852 MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
1854 if (CreateDirectoryW(dirW, NULL))
1855 return TRUE;
1858 * FIXME: CreateDirectory's LastErrors will clash with the ones
1859 * used by DOS. AH=39 only returns 3 (path not found) and
1860 * 5 (access denied), while CreateDirectory return several
1861 * ones. Remap some of them. -Marcus
1863 switch (GetLastError())
1865 case ERROR_ALREADY_EXISTS:
1866 case ERROR_FILENAME_EXCED_RANGE:
1867 case ERROR_DISK_FULL:
1868 SetLastError(ERROR_ACCESS_DENIED);
1869 break;
1870 default:
1871 break;
1874 return FALSE;
1878 /***********************************************************************
1879 * INT21_ExtendedCountryInformation
1881 * Handler for function 0x65.
1883 static void INT21_ExtendedCountryInformation( CONTEXT86 *context )
1885 BYTE *dataptr = CTX_SEG_OFF_TO_LIN( context, context->SegEs, context->Edi );
1886 BYTE buffsize = CX_reg (context);
1888 TRACE( "GET EXTENDED COUNTRY INFORMATION, subfunction %02x\n",
1889 AL_reg(context) );
1892 * Check subfunctions that are passed country and code page.
1894 if (AL_reg(context) >= 0x01 && AL_reg(context) <= 0x07)
1896 WORD country = DX_reg(context);
1897 WORD codepage = BX_reg(context);
1899 if (country != 0xffff && country != INT21_GetSystemCountryCode())
1900 FIXME( "Requested info on non-default country %04x\n", country );
1902 if (codepage != 0xffff && codepage != GetOEMCP())
1903 FIXME( "Requested info on non-default code page %04x\n", codepage );
1906 switch (AL_reg(context)) {
1907 case 0x00: /* SET GENERAL INTERNATIONALIZATION INFO */
1908 INT_BARF( context, 0x21 );
1909 SET_CFLAG( context );
1910 break;
1912 case 0x01: /* GET GENERAL INTERNATIONALIZATION INFO */
1913 TRACE( "Get general internationalization info\n" );
1914 dataptr[0] = 0x01; /* Info ID */
1915 *(WORD*)(dataptr+1) = 38; /* Size of the following info */
1916 *(WORD*)(dataptr+3) = INT21_GetSystemCountryCode(); /* Country ID */
1917 *(WORD*)(dataptr+5) = GetOEMCP(); /* Code page */
1918 /* FIXME: fill buffer partially up to buffsize bytes*/
1919 if (buffsize >= 0x29){
1920 INT21_FillCountryInformation( dataptr + 7 );
1921 SET_CX( context, 0x29 ); /* Size of returned info */
1922 }else{
1923 SET_CX( context, 0x07 ); /* Size of returned info */
1925 break;
1927 case 0x02: /* GET POINTER TO UPPERCASE TABLE */
1928 case 0x04: /* GET POINTER TO FILENAME UPPERCASE TABLE */
1929 TRACE( "Get pointer to uppercase table\n" );
1930 dataptr[0] = AL_reg(context); /* Info ID */
1931 *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1932 offsetof(INT21_HEAP, uppercase_size) );
1933 SET_CX( context, 5 ); /* Size of returned info */
1934 break;
1936 case 0x03: /* GET POINTER TO LOWERCASE TABLE */
1937 TRACE( "Get pointer to lowercase table\n" );
1938 dataptr[0] = 0x03; /* Info ID */
1939 *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1940 offsetof(INT21_HEAP, lowercase_size) );
1941 SET_CX( context, 5 ); /* Size of returned info */
1942 break;
1944 case 0x05: /* GET POINTER TO FILENAME TERMINATOR TABLE */
1945 TRACE("Get pointer to filename terminator table\n");
1946 dataptr[0] = 0x05; /* Info ID */
1947 *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1948 offsetof(INT21_HEAP, filename_size) );
1949 SET_CX( context, 5 ); /* Size of returned info */
1950 break;
1952 case 0x06: /* GET POINTER TO COLLATING SEQUENCE TABLE */
1953 TRACE("Get pointer to collating sequence table\n");
1954 dataptr[0] = 0x06; /* Info ID */
1955 *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1956 offsetof(INT21_HEAP, collating_size) );
1957 SET_CX( context, 5 ); /* Size of returned info */
1958 break;
1960 case 0x07: /* GET POINTER TO DBCS LEAD BYTE TABLE */
1961 TRACE("Get pointer to DBCS lead byte table\n");
1962 dataptr[0] = 0x07; /* Info ID */
1963 *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ),
1964 offsetof(INT21_HEAP, dbcs_size) );
1965 SET_CX( context, 5 ); /* Size of returned info */
1966 break;
1968 case 0x20: /* CAPITALIZE CHARACTER */
1969 case 0xa0: /* CAPITALIZE FILENAME CHARACTER */
1970 TRACE("Convert char to uppercase\n");
1971 SET_DL( context, toupper(DL_reg(context)) );
1972 break;
1974 case 0x21: /* CAPITALIZE STRING */
1975 case 0xa1: /* CAPITALIZE COUNTED FILENAME STRING */
1976 TRACE("Convert string to uppercase with length\n");
1978 char *ptr = (char *)CTX_SEG_OFF_TO_LIN( context,
1979 context->SegDs,
1980 context->Edx );
1981 WORD len = CX_reg(context);
1982 while (len--) { *ptr = toupper(*ptr); ptr++; }
1984 break;
1986 case 0x22: /* CAPITALIZE ASCIIZ STRING */
1987 case 0xa2: /* CAPITALIZE ASCIIZ FILENAME */
1988 TRACE("Convert ASCIIZ string to uppercase\n");
1990 char *p = CTX_SEG_OFF_TO_LIN( context, context->SegDs, context->Edx );
1991 for ( ; *p; p++) *p = toupper(*p);
1993 break;
1995 case 0x23: /* DETERMINE IF CHARACTER REPRESENTS YES/NO RESPONSE */
1996 INT_BARF( context, 0x21 );
1997 SET_CFLAG( context );
1998 break;
2000 default:
2001 INT_BARF( context, 0x21 );
2002 SET_CFLAG(context);
2003 break;
2008 /***********************************************************************
2009 * INT21_FileAttributes
2011 * Handler for:
2012 * - function 0x43
2013 * - subfunction 0x43 of function 0x71
2015 static BOOL INT21_FileAttributes( CONTEXT86 *context,
2016 BYTE subfunction,
2017 BOOL islong )
2019 WCHAR fileW[MAX_PATH];
2020 char *fileA = CTX_SEG_OFF_TO_LIN(context,
2021 context->SegDs,
2022 context->Edx);
2023 HANDLE handle;
2024 BOOL status;
2025 FILETIME filetime;
2026 DWORD result;
2027 WORD date, time;
2029 switch (subfunction)
2031 case 0x00: /* GET FILE ATTRIBUTES */
2032 TRACE( "GET FILE ATTRIBUTES for %s\n", fileA );
2033 MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
2035 result = GetFileAttributesW( fileW );
2036 if (result == INVALID_FILE_ATTRIBUTES)
2037 return FALSE;
2038 else
2040 SET_CX( context, (WORD)result );
2041 if (!islong)
2042 SET_AX( context, (WORD)result ); /* DR DOS */
2044 break;
2046 case 0x01: /* SET FILE ATTRIBUTES */
2047 TRACE( "SET FILE ATTRIBUTES 0x%02x for %s\n",
2048 CX_reg(context), fileA );
2049 MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
2051 if (!SetFileAttributesW( fileW, CX_reg(context) ))
2052 return FALSE;
2053 break;
2055 case 0x02: /* GET COMPRESSED FILE SIZE */
2056 TRACE( "GET COMPRESSED FILE SIZE for %s\n", fileA );
2057 MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
2059 result = GetCompressedFileSizeW( fileW, NULL );
2060 if (result == INVALID_FILE_SIZE)
2061 return FALSE;
2062 else
2064 SET_AX( context, LOWORD(result) );
2065 SET_DX( context, HIWORD(result) );
2067 break;
2069 case 0x03: /* SET FILE LAST-WRITTEN DATE AND TIME */
2070 if (!islong)
2071 INT_BARF( context, 0x21 );
2072 else
2074 TRACE( "SET FILE LAST-WRITTEN DATE AND TIME, file %s\n", fileA );
2075 MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
2077 handle = CreateFileW( fileW, GENERIC_WRITE,
2078 FILE_SHARE_READ | FILE_SHARE_WRITE,
2079 NULL, OPEN_EXISTING, 0, 0 );
2080 if (handle == INVALID_HANDLE_VALUE)
2081 return FALSE;
2083 DosDateTimeToFileTime( DI_reg(context),
2084 CX_reg(context),
2085 &filetime );
2086 status = SetFileTime( handle, NULL, NULL, &filetime );
2088 CloseHandle( handle );
2089 return status;
2091 break;
2093 case 0x04: /* GET FILE LAST-WRITTEN DATE AND TIME */
2094 if (!islong)
2095 INT_BARF( context, 0x21 );
2096 else
2098 TRACE( "GET FILE LAST-WRITTEN DATE AND TIME, file %s\n", fileA );
2099 MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
2101 handle = CreateFileW( fileW, GENERIC_READ,
2102 FILE_SHARE_READ | FILE_SHARE_WRITE,
2103 NULL, OPEN_EXISTING, 0, 0 );
2104 if (handle == INVALID_HANDLE_VALUE)
2105 return FALSE;
2107 status = GetFileTime( handle, NULL, NULL, &filetime );
2108 if (status)
2110 FileTimeToDosDateTime( &filetime, &date, &time );
2111 SET_DI( context, date );
2112 SET_CX( context, time );
2115 CloseHandle( handle );
2116 return status;
2118 break;
2120 case 0x05: /* SET FILE LAST ACCESS DATE */
2121 if (!islong)
2122 INT_BARF( context, 0x21 );
2123 else
2125 TRACE( "SET FILE LAST ACCESS DATE, file %s\n", fileA );
2126 MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
2128 handle = CreateFileW( fileW, GENERIC_WRITE,
2129 FILE_SHARE_READ | FILE_SHARE_WRITE,
2130 NULL, OPEN_EXISTING, 0, 0 );
2131 if (handle == INVALID_HANDLE_VALUE)
2132 return FALSE;
2134 DosDateTimeToFileTime( DI_reg(context),
2136 &filetime );
2137 status = SetFileTime( handle, NULL, &filetime, NULL );
2139 CloseHandle( handle );
2140 return status;
2142 break;
2144 case 0x06: /* GET FILE LAST ACCESS DATE */
2145 if (!islong)
2146 INT_BARF( context, 0x21 );
2147 else
2149 TRACE( "GET FILE LAST ACCESS DATE, file %s\n", fileA );
2150 MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
2152 handle = CreateFileW( fileW, GENERIC_READ,
2153 FILE_SHARE_READ | FILE_SHARE_WRITE,
2154 NULL, OPEN_EXISTING, 0, 0 );
2155 if (handle == INVALID_HANDLE_VALUE)
2156 return FALSE;
2158 status = GetFileTime( handle, NULL, &filetime, NULL );
2159 if (status)
2161 FileTimeToDosDateTime( &filetime, &date, NULL );
2162 SET_DI( context, date );
2165 CloseHandle( handle );
2166 return status;
2168 break;
2170 case 0x07: /* SET FILE CREATION DATE AND TIME */
2171 if (!islong)
2172 INT_BARF( context, 0x21 );
2173 else
2175 TRACE( "SET FILE CREATION DATE AND TIME, file %s\n", fileA );
2177 handle = CreateFileW( fileW, GENERIC_WRITE,
2178 FILE_SHARE_READ | FILE_SHARE_WRITE,
2179 NULL, OPEN_EXISTING, 0, 0 );
2180 if (handle == INVALID_HANDLE_VALUE)
2181 return FALSE;
2184 * FIXME: SI has number of 10-millisecond units past time in CX.
2186 DosDateTimeToFileTime( DI_reg(context),
2187 CX_reg(context),
2188 &filetime );
2189 status = SetFileTime( handle, &filetime, NULL, NULL );
2191 CloseHandle( handle );
2192 return status;
2194 break;
2196 case 0x08: /* GET FILE CREATION DATE AND TIME */
2197 if (!islong)
2198 INT_BARF( context, 0x21 );
2199 else
2201 TRACE( "GET FILE CREATION DATE AND TIME, handle %d\n",
2202 BX_reg(context) );
2204 handle = CreateFileW( fileW, GENERIC_READ,
2205 FILE_SHARE_READ | FILE_SHARE_WRITE,
2206 NULL, OPEN_EXISTING, 0, 0 );
2207 if (handle == INVALID_HANDLE_VALUE)
2208 return FALSE;
2210 status = GetFileTime( handle, &filetime, NULL, NULL );
2211 if (status)
2213 FileTimeToDosDateTime( &filetime, &date, &time );
2214 SET_DI( context, date );
2215 SET_CX( context, time );
2217 * FIXME: SI has number of 10-millisecond units past
2218 * time in CX.
2220 SET_SI( context, 0 );
2223 CloseHandle(handle);
2224 return status;
2226 break;
2228 case 0xff: /* EXTENDED-LENGTH FILENAME OPERATIONS */
2229 if (islong || context->Ebp != 0x5053)
2230 INT_BARF( context, 0x21 );
2231 else
2233 switch(CL_reg(context))
2235 case 0x39:
2236 if (!INT21_CreateDirectory( context ))
2237 return FALSE;
2238 break;
2240 case 0x56:
2241 if (!INT21_RenameFile( context ))
2242 return FALSE;
2243 break;
2245 default:
2246 INT_BARF( context, 0x21 );
2249 break;
2251 default:
2252 INT_BARF( context, 0x21 );
2255 return TRUE;
2259 /***********************************************************************
2260 * INT21_FileDateTime
2262 * Handler for function 0x57.
2264 static BOOL INT21_FileDateTime( CONTEXT86 *context )
2266 HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
2267 FILETIME filetime;
2268 WORD date, time;
2270 switch (AL_reg(context)) {
2271 case 0x00: /* Get last-written stamp */
2272 TRACE( "GET FILE LAST-WRITTEN DATE AND TIME, handle %d\n",
2273 BX_reg(context) );
2275 if (!GetFileTime( handle, NULL, NULL, &filetime ))
2276 return FALSE;
2277 FileTimeToDosDateTime( &filetime, &date, &time );
2278 SET_DX( context, date );
2279 SET_CX( context, time );
2280 break;
2283 case 0x01: /* Set last-written stamp */
2284 TRACE( "SET FILE LAST-WRITTEN DATE AND TIME, handle %d\n",
2285 BX_reg(context) );
2287 DosDateTimeToFileTime( DX_reg(context),
2288 CX_reg(context),
2289 &filetime );
2290 if (!SetFileTime( handle, NULL, NULL, &filetime ))
2291 return FALSE;
2292 break;
2295 case 0x04: /* Get last access stamp, DOS 7 */
2296 TRACE( "GET FILE LAST ACCESS DATE AND TIME, handle %d\n",
2297 BX_reg(context) );
2299 if (!GetFileTime( handle, NULL, &filetime, NULL ))
2300 return FALSE;
2301 FileTimeToDosDateTime( &filetime, &date, &time );
2302 SET_DX( context, date );
2303 SET_CX( context, time );
2304 break;
2307 case 0x05: /* Set last access stamp, DOS 7 */
2308 TRACE( "SET FILE LAST ACCESS DATE AND TIME, handle %d\n",
2309 BX_reg(context) );
2311 DosDateTimeToFileTime( DX_reg(context),
2312 CX_reg(context),
2313 &filetime );
2314 if (!SetFileTime( handle, NULL, &filetime, NULL ))
2315 return FALSE;
2316 break;
2319 case 0x06: /* Get creation stamp, DOS 7 */
2320 TRACE( "GET FILE CREATION DATE AND TIME, handle %d\n",
2321 BX_reg(context) );
2323 if (!GetFileTime( handle, &filetime, NULL, NULL ))
2324 return FALSE;
2325 FileTimeToDosDateTime( &filetime, &date, &time );
2326 SET_DX( context, date );
2327 SET_CX( context, time );
2329 * FIXME: SI has number of 10-millisecond units past time in CX.
2331 SET_SI( context, 0 );
2332 break;
2335 case 0x07: /* Set creation stamp, DOS 7 */
2336 TRACE( "SET FILE CREATION DATE AND TIME, handle %d\n",
2337 BX_reg(context) );
2340 * FIXME: SI has number of 10-millisecond units past time in CX.
2342 DosDateTimeToFileTime( DX_reg(context),
2343 CX_reg(context),
2344 &filetime );
2345 if (!SetFileTime( handle, &filetime, NULL, NULL ))
2346 return FALSE;
2347 break;
2350 default:
2351 INT_BARF( context, 0x21 );
2352 break;
2355 return TRUE;
2359 /***********************************************************************
2360 * INT21_GetPSP
2362 * Handler for functions 0x51 and 0x62.
2364 static void INT21_GetPSP( CONTEXT86 *context )
2366 TRACE( "GET CURRENT PSP ADDRESS (%02x)\n", AH_reg(context) );
2369 * FIXME: should we return the original DOS PSP upon
2370 * Windows startup ?
2372 if (!ISV86(context) && DOSVM_IsWin16())
2373 SET_BX( context, LOWORD(GetCurrentPDB16()) );
2374 else
2375 SET_BX( context, DOSVM_psp );
2378 static inline void setword( BYTE *ptr, WORD w )
2380 ptr[0] = (BYTE)w;
2381 ptr[1] = (BYTE)(w >> 8);
2384 static void CreateBPB(int drive, BYTE *data, BOOL16 limited)
2385 /* limited == TRUE is used with INT 0x21/0x440d */
2387 /* FIXME: we're forcing some values without checking that those are valid */
2388 if (drive > 1)
2390 setword(data, 512);
2391 data[2] = 2;
2392 setword(&data[3], 0);
2393 data[5] = 2;
2394 setword(&data[6], 240);
2395 setword(&data[8], 64000);
2396 data[0x0a] = 0xf8;
2397 setword(&data[0x0b], 40);
2398 setword(&data[0x0d], 56);
2399 setword(&data[0x0f], 2);
2400 setword(&data[0x11], 0);
2401 if (!limited)
2403 setword(&data[0x1f], 800);
2404 data[0x21] = 5;
2405 setword(&data[0x22], 1);
2408 else
2409 { /* 1.44mb */
2410 setword(data, 512);
2411 data[2] = 2;
2412 setword(&data[3], 0);
2413 data[5] = 2;
2414 setword(&data[6], 240);
2415 setword(&data[8], 2880);
2416 data[0x0a] = 0xf8;
2417 setword(&data[0x0b], 6);
2418 setword(&data[0x0d], 18);
2419 setword(&data[0x0f], 2);
2420 setword(&data[0x11], 0);
2421 if (!limited)
2423 setword(&data[0x1f], 80);
2424 data[0x21] = 7;
2425 setword(&data[0x22], 2);
2430 inline DWORD INT21_Ioctl_CylHeadSect2Lin(DWORD cyl, WORD head, WORD sec, WORD cyl_cnt, WORD head_cnt, WORD sec_cnt)
2432 DWORD res = (cyl * head_cnt*sec_cnt + head * sec_cnt + sec);
2433 return res;
2436 /***********************************************************************
2437 * INT21_Ioctl_Block
2439 * Handler for block device IOCTLs.
2441 static void INT21_Ioctl_Block( CONTEXT86 *context )
2443 BYTE *dataptr;
2444 BYTE drive = INT21_MapDrive( BL_reg(context) );
2445 WCHAR drivespec[4] = {'A', ':', '\\', 0};
2446 UINT drivetype;
2448 drivespec[0] += drive;
2449 drivetype = GetDriveTypeW( drivespec );
2451 RESET_CFLAG(context);
2452 if (drivetype == DRIVE_UNKNOWN || drivetype == DRIVE_NO_ROOT_DIR)
2454 TRACE( "IOCTL - SUBFUNCTION %d - INVALID DRIVE %c:\n",
2455 AL_reg(context), 'A' + drive );
2456 SetLastError( ERROR_INVALID_DRIVE );
2457 SET_AX( context, ERROR_INVALID_DRIVE );
2458 SET_CFLAG( context );
2459 return;
2462 switch (AL_reg(context))
2464 case 0x04: /* READ FROM BLOCK DEVICE CONTROL CHANNEL */
2465 case 0x05: /* WRITE TO BLOCK DEVICE CONTROL CHANNEL */
2466 INT_BARF( context, 0x21 );
2467 break;
2469 case 0x08: /* CHECK IF BLOCK DEVICE REMOVABLE */
2470 TRACE( "IOCTL - CHECK IF BLOCK DEVICE REMOVABLE - %c:\n",
2471 'A' + drive );
2473 if (drivetype == DRIVE_REMOVABLE)
2474 SET_AX( context, 0 ); /* removable */
2475 else
2476 SET_AX( context, 1 ); /* not removable */
2477 break;
2479 case 0x09: /* CHECK IF BLOCK DEVICE REMOTE */
2480 TRACE( "IOCTL - CHECK IF BLOCK DEVICE REMOTE - %c:\n",
2481 'A' + drive );
2483 if (drivetype == DRIVE_REMOTE)
2484 SET_DX( context, (1<<9) | (1<<12) ); /* remote + no direct IO */
2485 else
2486 SET_DX( context, 0 ); /* FIXME: use driver attr here */
2487 break;
2489 case 0x0d: /* GENERIC BLOCK DEVICE REQUEST */
2490 /* Get pointer to IOCTL parameter block */
2491 dataptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
2493 switch (CX_reg(context))
2495 case 0x0841: /* write logical device track */
2496 TRACE( "GENERIC IOCTL - Write logical device track - %c:\n",
2497 'A' + drive);
2499 WORD head = *(WORD *)(dataptr+1);
2500 WORD cyl = *(WORD *)(dataptr+3);
2501 WORD sect = *(WORD *)(dataptr+5);
2502 WORD nrsect = *(WORD *)(dataptr+7);
2503 BYTE *data = CTX_SEG_OFF_TO_LIN(context, *(WORD *)(dataptr+11), *(WORD *)(dataptr+9));
2504 WORD cyl_cnt, head_cnt, sec_cnt;
2506 /* FIXME: we're faking some values here */
2507 if (drive > 1)
2509 /* cyl_cnt = 0x300;
2510 head_cnt = 16;
2511 sec_cnt = 255; */
2512 SET_AX( context, ERROR_WRITE_FAULT );
2513 SET_CFLAG(context);
2514 break;
2516 else
2517 { /* floppy */
2518 cyl_cnt = 80;
2519 head_cnt = 2;
2520 sec_cnt = 18;
2523 if (!DOSVM_RawWrite(drive, INT21_Ioctl_CylHeadSect2Lin(cyl, head, sect, cyl_cnt, head_cnt, sec_cnt), nrsect, data, FALSE))
2525 SET_AX( context, ERROR_WRITE_FAULT );
2526 SET_CFLAG(context);
2529 break;
2531 case 0x084a: /* lock logical volume */
2532 TRACE( "GENERIC IOCTL - Lock logical volume, level %d mode %d - %c:\n",
2533 BH_reg(context), DX_reg(context), 'A' + drive );
2534 break;
2536 case 0x0860: /* get device parameters */
2537 /* FIXME: we're faking some values here */
2538 /* used by w4wgrp's winfile */
2539 memset(dataptr, 0, 0x20); /* DOS 6.22 uses 0x20 bytes */
2540 dataptr[0] = 0x04;
2541 dataptr[6] = 0; /* media type */
2542 if (drive > 1)
2544 dataptr[1] = 0x05; /* fixed disk */
2545 setword(&dataptr[2], 0x01); /* non removable */
2546 setword(&dataptr[4], 0x300); /* # of cylinders */
2548 else
2550 dataptr[1] = 0x07; /* block dev, floppy */
2551 setword(&dataptr[2], 0x02); /* removable */
2552 setword(&dataptr[4], 80); /* # of cylinders */
2554 CreateBPB(drive, &dataptr[7], TRUE);
2555 RESET_CFLAG(context);
2556 break;
2558 case 0x0861: /* read logical device track */
2559 TRACE( "GENERIC IOCTL - Read logical device track - %c:\n",
2560 'A' + drive);
2562 WORD head = *(WORD *)(dataptr+1);
2563 WORD cyl = *(WORD *)(dataptr+3);
2564 WORD sect = *(WORD *)(dataptr+5);
2565 WORD nrsect = *(WORD *)(dataptr+7);
2566 BYTE *data = CTX_SEG_OFF_TO_LIN(context, *(WORD *)(dataptr+11), *(WORD *)(dataptr+9));
2567 WORD cyl_cnt, head_cnt, sec_cnt;
2569 /* FIXME: we're faking some values here */
2570 if (drive > 1)
2572 cyl_cnt = 0x300;
2573 head_cnt = 16;
2574 sec_cnt = 255;
2576 else
2577 { /* floppy */
2578 cyl_cnt = 80;
2579 head_cnt = 2;
2580 sec_cnt = 18;
2583 if (!DOSVM_RawRead(drive, INT21_Ioctl_CylHeadSect2Lin(cyl, head, sect, cyl_cnt, head_cnt, sec_cnt), nrsect, data, FALSE))
2585 SET_AX( context, ERROR_READ_FAULT );
2586 SET_CFLAG(context);
2589 break;
2591 case 0x0866: /* get volume serial number */
2593 WCHAR label[12],fsname[9];
2594 DWORD serial;
2596 drivespec[0] += drive;
2597 GetVolumeInformationW(drivespec, label, 12, &serial, NULL, NULL, fsname, 9);
2598 *(WORD*)dataptr = 0;
2599 memcpy(dataptr+2,&serial,4);
2600 WideCharToMultiByte(CP_OEMCP, 0, label, 11, (LPSTR)dataptr + 6, 11, NULL, NULL);
2601 WideCharToMultiByte(CP_OEMCP, 0, fsname, 8, (LPSTR)dataptr + 17, 8, NULL, NULL);
2603 break;
2605 case 0x086a: /* unlock logical volume */
2606 TRACE( "GENERIC IOCTL - Logical volume unlocked - %c:\n",
2607 'A' + drive );
2608 break;
2610 case 0x086f: /* get drive map information */
2611 memset(dataptr+1, '\0', dataptr[0]-1);
2612 dataptr[1] = dataptr[0];
2613 dataptr[2] = 0x07; /* protected mode driver; no eject; no notification */
2614 dataptr[3] = 0xFF; /* no physical drive */
2615 break;
2617 case 0x0872:
2618 /* Trial and error implementation */
2619 SET_AX( context, drivetype == DRIVE_UNKNOWN ? 0x0f : 0x01 );
2620 SET_CFLAG(context); /* Seems to be set all the time */
2621 break;
2623 default:
2624 INT_BARF( context, 0x21 );
2626 break;
2628 case 0x0e: /* GET LOGICAL DRIVE MAP */
2629 TRACE( "IOCTL - GET LOGICAL DRIVE MAP - %c:\n",
2630 'A' + drive );
2631 /* FIXME: this is not correct if drive has mappings */
2632 SET_AL( context, 0 ); /* drive has no mapping */
2633 break;
2635 case 0x0f: /* SET LOGICAL DRIVE MAP */
2636 TRACE("IOCTL - SET LOGICAL DRIVE MAP for drive %s\n",
2637 INT21_DriveName( BL_reg(context)));
2638 /* FIXME: as of today, we don't support logical drive mapping... */
2639 SET_AL( context, 0 );
2640 break;
2642 case 0x11: /* QUERY GENERIC IOCTL CAPABILITY */
2643 default:
2644 INT_BARF( context, 0x21 );
2649 /***********************************************************************
2650 * INT21_IoctlScsiMgrHandler
2652 * IOCTL handler for the SCSIMGR device.
2654 static void INT21_IoctlScsiMgrHandler( CONTEXT86 *context )
2656 switch (AL_reg(context))
2658 case 0x00: /* GET DEVICE INFORMATION */
2659 SET_DX( context, 0xc0c0 );
2660 break;
2662 case 0x02: /* READ FROM CHARACTER DEVICE CONTROL CHANNEL */
2663 DOSVM_ASPIHandler(context);
2664 break;
2666 case 0x0a: /* CHECK IF HANDLE IS REMOTE */
2667 SET_DX( context, 0 );
2668 break;
2670 case 0x01: /* SET DEVICE INFORMATION */
2671 case 0x03: /* WRITE TO CHARACTER DEVICE CONTROL CHANNEL */
2672 case 0x06: /* GET INPUT STATUS */
2673 case 0x07: /* GET OUTPUT STATUS */
2674 case 0x0c: /* GENERIC CHARACTER DEVICE REQUEST */
2675 case 0x10: /* QUERY GENERIC IOCTL CAPABILITY */
2676 default:
2677 INT_BARF( context, 0x21 );
2678 break;
2683 /***********************************************************************
2684 * INT21_IoctlEMSHandler
2686 * IOCTL handler for the EMXXXX0 device.
2688 static void INT21_IoctlEMSHandler( CONTEXT86 *context )
2690 EMS_Ioctl_Handler(context);
2694 /***********************************************************************
2695 * INT21_IoctlHPScanHandler
2697 * IOCTL handler for the HPSCAN device.
2699 static void INT21_IoctlHPScanHandler( CONTEXT86 *context )
2701 switch (AL_reg(context))
2703 case 0x00: /* GET DEVICE INFORMATION */
2704 SET_DX( context, 0xc0c0 );
2705 break;
2707 case 0x0a: /* CHECK IF HANDLE IS REMOTE */
2708 SET_DX( context, 0 );
2709 break;
2711 case 0x01: /* SET DEVICE INFORMATION */
2712 case 0x02: /* READ FROM CHARACTER DEVICE CONTROL CHANNEL */
2713 case 0x03: /* WRITE TO CHARACTER DEVICE CONTROL CHANNEL */
2714 case 0x06: /* GET INPUT STATUS */
2715 case 0x07: /* GET OUTPUT STATUS */
2716 case 0x0c: /* GENERIC CHARACTER DEVICE REQUEST */
2717 case 0x10: /* QUERY GENERIC IOCTL CAPABILITY */
2718 default:
2719 INT_BARF( context, 0x21 );
2720 break;
2725 /***********************************************************************
2726 * INT21_Ioctl_Char
2728 * Handler for character device IOCTLs.
2730 static void INT21_Ioctl_Char( CONTEXT86 *context )
2732 int status, i;
2733 int IsConsoleIOHandle = 0;
2734 IO_STATUS_BLOCK io;
2735 FILE_INTERNAL_INFORMATION info;
2736 HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
2738 status = NtQueryInformationFile( handle, &io, &info, sizeof(info), FileInternalInformation );
2739 if (status)
2741 if( VerifyConsoleIoHandle( handle))
2742 IsConsoleIOHandle = 1;
2743 else {
2744 SET_AX( context, RtlNtStatusToDosError(status) );
2745 SET_CFLAG( context );
2746 return;
2748 } else {
2749 for (i = 0; i < NB_MAGIC_DEVICES; i++)
2751 if (!magic_devices[i].handle) continue;
2752 if (magic_devices[i].index.QuadPart == info.IndexNumber.QuadPart)
2754 /* found it */
2755 magic_devices[i].ioctl_handler( context );
2756 return;
2761 /* no magic device found, do default handling */
2763 switch (AL_reg(context))
2765 case 0x00: /* GET DEVICE INFORMATION */
2766 TRACE( "IOCTL - GET DEVICE INFORMATION - %d\n", BX_reg(context) );
2767 if (IsConsoleIOHandle || GetFileType(handle) == FILE_TYPE_CHAR)
2770 * Returns attribute word in DX:
2771 * Bit 14 - Device driver can process IOCTL requests.
2772 * Bit 13 - Output until busy supported.
2773 * Bit 11 - Driver supports OPEN/CLOSE calls.
2774 * Bit 8 - Unknown.
2775 * Bit 7 - Set (indicates device).
2776 * Bit 6 - EOF on input.
2777 * Bit 5 - Raw (binary) mode.
2778 * Bit 4 - Device is special (uses int29).
2779 * Bit 3 - Clock device.
2780 * Bit 2 - NUL device.
2781 * Bit 1 - Console output device.
2782 * Bit 0 - Console input device.
2784 SET_DX( context, IsConsoleIOHandle ? 0x80c3 : 0x80c0 /* FIXME */ );
2786 else
2789 * Returns attribute word in DX:
2790 * Bit 15 - File is remote.
2791 * Bit 14 - Don't set file date/time on closing.
2792 * Bit 11 - Media not removable.
2793 * Bit 8 - Generate int24 if no disk space on write
2794 * or read past end of file
2795 * Bit 7 - Clear (indicates file).
2796 * Bit 6 - File has not been written.
2797 * Bit 5..0 - Drive number (0=A:,...)
2799 * FIXME: Should check if file is on remote or removable drive.
2800 * FIXME: Should use drive file is located on (and not current).
2802 SET_DX( context, 0x0140 + INT21_GetCurrentDrive() );
2804 break;
2806 case 0x0a: /* CHECK IF HANDLE IS REMOTE */
2807 TRACE( "IOCTL - CHECK IF HANDLE IS REMOTE - %d\n", BX_reg(context) );
2809 * Returns attribute word in DX:
2810 * Bit 15 - Set if remote.
2811 * Bit 14 - Set if date/time not set on close.
2813 * FIXME: Should check if file is on remote drive.
2815 SET_DX( context, 0 );
2816 break;
2818 case 0x01: /* SET DEVICE INFORMATION */
2819 case 0x02: /* READ FROM CHARACTER DEVICE CONTROL CHANNEL */
2820 case 0x03: /* WRITE TO CHARACTER DEVICE CONTROL CHANNEL */
2821 case 0x06: /* GET INPUT STATUS */
2822 case 0x07: /* GET OUTPUT STATUS */
2823 case 0x0c: /* GENERIC CHARACTER DEVICE REQUEST */
2824 case 0x10: /* QUERY GENERIC IOCTL CAPABILITY */
2825 default:
2826 INT_BARF( context, 0x21 );
2827 break;
2832 /***********************************************************************
2833 * INT21_Ioctl
2835 * Handler for function 0x44.
2837 static void INT21_Ioctl( CONTEXT86 *context )
2839 switch (AL_reg(context))
2841 case 0x00:
2842 case 0x01:
2843 case 0x02:
2844 case 0x03:
2845 INT21_Ioctl_Char( context );
2846 break;
2848 case 0x04:
2849 case 0x05:
2850 INT21_Ioctl_Block( context );
2851 break;
2853 case 0x06:
2854 case 0x07:
2855 INT21_Ioctl_Char( context );
2856 break;
2858 case 0x08:
2859 case 0x09:
2860 INT21_Ioctl_Block( context );
2861 break;
2863 case 0x0a:
2864 INT21_Ioctl_Char( context );
2865 break;
2867 case 0x0b: /* SET SHARING RETRY COUNT */
2868 TRACE( "SET SHARING RETRY COUNT: Pause %d, retries %d.\n",
2869 CX_reg(context), DX_reg(context) );
2870 if (!CX_reg(context))
2872 SET_AX( context, 1 );
2873 SET_CFLAG( context );
2875 else
2877 DOSDEV_SetSharingRetry( CX_reg(context), DX_reg(context) );
2878 RESET_CFLAG( context );
2880 break;
2882 case 0x0c:
2883 INT21_Ioctl_Char( context );
2884 break;
2886 case 0x0d:
2887 case 0x0e:
2888 case 0x0f:
2889 INT21_Ioctl_Block( context );
2890 break;
2892 case 0x10:
2893 INT21_Ioctl_Char( context );
2894 break;
2896 case 0x11:
2897 INT21_Ioctl_Block( context );
2898 break;
2900 case 0x12: /* DR DOS - DETERMINE DOS TYPE (OBSOLETE FUNCTION) */
2901 TRACE( "DR DOS - DETERMINE DOS TYPE (OBSOLETE FUNCTION)\n" );
2902 SET_CFLAG(context); /* Error / This is not DR DOS. */
2903 SET_AX( context, 0x0001 ); /* Invalid function */
2904 break;
2906 case 0x52: /* DR DOS - DETERMINE DOS TYPE */
2907 TRACE( "DR DOS - DETERMINE DOS TYPE\n" );
2908 SET_CFLAG(context); /* Error / This is not DR DOS. */
2909 SET_AX( context, 0x0001 ); /* Invalid function */
2910 break;
2912 case 0xe0: /* Sun PC-NFS API */
2913 TRACE( "Sun PC-NFS API\n" );
2914 /* not installed */
2915 break;
2917 default:
2918 INT_BARF( context, 0x21 );
2923 /***********************************************************************
2924 * INT21_Fat32
2926 * Handler for function 0x73.
2928 static BOOL INT21_Fat32( CONTEXT86 *context )
2930 switch (AL_reg(context))
2932 case 0x02: /* FAT32 - GET EXTENDED DPB */
2934 BYTE drive = INT21_MapDrive( DL_reg(context) );
2935 WORD *ptr = CTX_SEG_OFF_TO_LIN(context,
2936 context->SegEs, context->Edi);
2937 INT21_DPB *target = (INT21_DPB*)(ptr + 1);
2938 INT21_DPB *source;
2940 TRACE( "FAT32 - GET EXTENDED DPB %d\n", DL_reg(context) );
2942 if ( CX_reg(context) < sizeof(INT21_DPB) + 2 || *ptr < sizeof(INT21_DPB) )
2944 SetLastError( ERROR_BAD_LENGTH );
2945 return FALSE;
2948 if ( !INT21_FillDrivePB( drive ) )
2950 SetLastError( ERROR_INVALID_DRIVE );
2951 return FALSE;
2954 source = &INT21_GetHeapPointer()->misc_dpb_list[drive];
2956 *ptr = sizeof(INT21_DPB);
2957 memcpy( target, source, sizeof(INT21_DPB));
2959 if (LOWORD(context->Esi) != 0xF1A6)
2961 target->driver_header = 0;
2962 target->next = 0;
2964 else
2966 FIXME( "Caller requested driver and next DPB pointers!\n" );
2969 break;
2971 case 0x03: /* FAT32 - GET EXTENDED FREE SPACE ON DRIVE */
2973 WCHAR dirW[MAX_PATH];
2974 char *dirA = CTX_SEG_OFF_TO_LIN( context,
2975 context->SegDs, context->Edx );
2976 BYTE *data = CTX_SEG_OFF_TO_LIN( context,
2977 context->SegEs, context->Edi );
2978 DWORD cluster_sectors;
2979 DWORD sector_bytes;
2980 DWORD free_clusters;
2981 DWORD total_clusters;
2983 TRACE( "FAT32 - GET EXTENDED FREE SPACE ON DRIVE %s\n", dirA );
2984 MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
2986 if (CX_reg(context) < 44)
2988 SetLastError( ERROR_BAD_LENGTH );
2989 return FALSE;
2992 if (!GetDiskFreeSpaceW( dirW, &cluster_sectors, &sector_bytes,
2993 &free_clusters, &total_clusters ))
2994 return FALSE;
2996 *(WORD*) (data + 0) = 44; /* size of structure */
2997 *(WORD*) (data + 2) = 0; /* version */
2998 *(DWORD*)(data + 4) = cluster_sectors;
2999 *(DWORD*)(data + 8) = sector_bytes;
3000 *(DWORD*)(data + 12) = free_clusters;
3001 *(DWORD*)(data + 16) = total_clusters;
3004 * Below we have free/total sectors and
3005 * free/total allocation units without adjustment
3006 * for compression. We fake both using cluster information.
3008 *(DWORD*)(data + 20) = free_clusters * cluster_sectors;
3009 *(DWORD*)(data + 24) = total_clusters * cluster_sectors;
3010 *(DWORD*)(data + 28) = free_clusters;
3011 *(DWORD*)(data + 32) = total_clusters;
3014 * Between (data + 36) and (data + 43) there
3015 * are eight reserved bytes.
3018 break;
3020 default:
3021 INT_BARF( context, 0x21 );
3024 return TRUE;
3027 static void INT21_ConvertFindDataWtoA(WIN32_FIND_DATAA *dataA,
3028 const WIN32_FIND_DATAW *dataW)
3030 dataA->dwFileAttributes = dataW->dwFileAttributes;
3031 dataA->ftCreationTime = dataW->ftCreationTime;
3032 dataA->ftLastAccessTime = dataW->ftLastAccessTime;
3033 dataA->ftLastWriteTime = dataW->ftLastWriteTime;
3034 dataA->nFileSizeHigh = dataW->nFileSizeHigh;
3035 dataA->nFileSizeLow = dataW->nFileSizeLow;
3036 WideCharToMultiByte( CP_OEMCP, 0, dataW->cFileName, -1,
3037 dataA->cFileName, sizeof(dataA->cFileName), NULL, NULL );
3038 WideCharToMultiByte( CP_OEMCP, 0, dataW->cAlternateFileName, -1,
3039 dataA->cAlternateFileName, sizeof(dataA->cAlternateFileName), NULL, NULL );
3042 /***********************************************************************
3043 * INT21_LongFilename
3045 * Handler for function 0x71.
3047 static void INT21_LongFilename( CONTEXT86 *context )
3049 BOOL bSetDOSExtendedError = FALSE;
3050 WCHAR pathW[MAX_PATH];
3051 char* pathA;
3053 if (HIBYTE(HIWORD(GetVersion16())) < 0x07)
3055 TRACE( "LONG FILENAME - functions supported only under DOS7\n" );
3056 SET_CFLAG( context );
3057 SET_AL( context, 0 );
3058 return;
3061 switch (AL_reg(context))
3063 case 0x0d: /* RESET DRIVE */
3064 INT_BARF( context, 0x21 );
3065 break;
3067 case 0x39: /* LONG FILENAME - MAKE DIRECTORY */
3068 if (!INT21_CreateDirectory( context ))
3069 bSetDOSExtendedError = TRUE;
3070 break;
3072 case 0x3a: /* LONG FILENAME - REMOVE DIRECTORY */
3073 pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3075 TRACE( "LONG FILENAME - REMOVE DIRECTORY %s\n", pathA);
3076 MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
3077 if (!RemoveDirectoryW( pathW )) bSetDOSExtendedError = TRUE;
3078 break;
3080 case 0x3b: /* LONG FILENAME - CHANGE DIRECTORY */
3081 if (!INT21_SetCurrentDirectory( context ))
3082 bSetDOSExtendedError = TRUE;
3083 break;
3085 case 0x41: /* LONG FILENAME - DELETE FILE */
3086 pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3088 TRACE( "LONG FILENAME - DELETE FILE %s\n", pathA );
3089 MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
3091 if (!DeleteFileW( pathW )) bSetDOSExtendedError = TRUE;
3092 break;
3094 case 0x43: /* LONG FILENAME - EXTENDED GET/SET FILE ATTRIBUTES */
3095 if (!INT21_FileAttributes( context, BL_reg(context), TRUE ))
3096 bSetDOSExtendedError = TRUE;
3097 break;
3099 case 0x47: /* LONG FILENAME - GET CURRENT DIRECTORY */
3100 if (!INT21_GetCurrentDirectory( context, TRUE ))
3101 bSetDOSExtendedError = TRUE;
3102 break;
3104 case 0x4e: /* LONG FILENAME - FIND FIRST MATCHING FILE */
3106 HANDLE handle;
3107 HGLOBAL16 h16;
3108 WIN32_FIND_DATAW dataW;
3109 WIN32_FIND_DATAA* dataA;
3111 pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs,context->Edx);
3112 TRACE(" LONG FILENAME - FIND FIRST MATCHING FILE for %s\n", pathA);
3114 MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
3115 handle = FindFirstFileW(pathW, &dataW);
3117 dataA = (WIN32_FIND_DATAA *)CTX_SEG_OFF_TO_LIN(context, context->SegEs,
3118 context->Edi);
3119 if (handle != INVALID_HANDLE_VALUE &&
3120 (h16 = GlobalAlloc16(GMEM_MOVEABLE, sizeof(handle))))
3122 HANDLE* ptr = GlobalLock16( h16 );
3123 *ptr = handle;
3124 GlobalUnlock16( h16 );
3125 SET_AX( context, h16 );
3126 INT21_ConvertFindDataWtoA(dataA, &dataW);
3128 else
3130 if (handle != INVALID_HANDLE_VALUE) FindClose(handle);
3131 SET_AX( context, INVALID_HANDLE_VALUE16);
3132 bSetDOSExtendedError = TRUE;
3135 break;
3137 case 0x4f: /* LONG FILENAME - FIND NEXT MATCHING FILE */
3139 HGLOBAL16 h16 = BX_reg(context);
3140 HANDLE* ptr;
3141 WIN32_FIND_DATAW dataW;
3142 WIN32_FIND_DATAA* dataA;
3144 TRACE("LONG FILENAME - FIND NEXT MATCHING FILE for handle %d\n",
3145 BX_reg(context));
3147 dataA = (WIN32_FIND_DATAA *)CTX_SEG_OFF_TO_LIN(context, context->SegEs,
3148 context->Edi);
3150 if (h16 != INVALID_HANDLE_VALUE16 && (ptr = GlobalLock16( h16 )))
3152 if (!FindNextFileW(*ptr, &dataW)) bSetDOSExtendedError = TRUE;
3153 else INT21_ConvertFindDataWtoA(dataA, &dataW);
3154 GlobalUnlock16( h16 );
3156 else
3158 SetLastError( ERROR_INVALID_HANDLE );
3159 bSetDOSExtendedError = TRUE;
3162 break;
3164 case 0x56: /* LONG FILENAME - RENAME FILE */
3165 if (!INT21_RenameFile(context))
3166 bSetDOSExtendedError = TRUE;
3167 break;
3169 case 0x60: /* LONG FILENAME - CONVERT PATH */
3171 WCHAR res[MAX_PATH];
3173 switch (CL_reg(context))
3175 case 0x00: /* "truename" - Canonicalize path */
3177 * FIXME: This is not 100% equal to 0x01 case,
3178 * if you fix this, fix int21 subfunction 0x60, too.
3181 case 0x01: /* Get short filename or path */
3182 MultiByteToWideChar(CP_OEMCP, 0, CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi), -1, pathW, MAX_PATH);
3183 if (!GetShortPathNameW(pathW, res, 67))
3184 bSetDOSExtendedError = TRUE;
3185 else
3187 SET_AX( context, 0 );
3188 WideCharToMultiByte(CP_OEMCP, 0, res, -1,
3189 CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi),
3190 67, NULL, NULL);
3192 break;
3194 case 0x02: /* Get canonical long filename or path */
3195 MultiByteToWideChar(CP_OEMCP, 0, CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi), -1, pathW, MAX_PATH);
3196 if (!GetFullPathNameW(pathW, 128, res, NULL))
3197 bSetDOSExtendedError = TRUE;
3198 else
3200 SET_AX( context, 0 );
3201 WideCharToMultiByte(CP_OEMCP, 0, res, -1,
3202 CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi),
3203 128, NULL, NULL);
3205 break;
3206 default:
3207 FIXME("Unimplemented long file name function:\n");
3208 INT_BARF( context, 0x21 );
3209 SET_CFLAG(context);
3210 SET_AL( context, 0 );
3211 break;
3214 break;
3216 case 0x6c: /* LONG FILENAME - CREATE OR OPEN FILE */
3217 if (!INT21_CreateFile( context, context->Esi, TRUE,
3218 BX_reg(context), DL_reg(context) ))
3219 bSetDOSExtendedError = TRUE;
3220 break;
3222 case 0xa0: /* LONG FILENAME - GET VOLUME INFORMATION */
3224 DWORD filename_len, flags;
3225 WCHAR dstW[8];
3227 pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs,context->Edx);
3229 TRACE("LONG FILENAME - GET VOLUME INFORMATION for drive having root dir '%s'.\n", pathA);
3230 SET_AX( context, 0 );
3231 MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH);
3232 if (!GetVolumeInformationW( pathW, NULL, 0, NULL, &filename_len,
3233 &flags, dstW, 8 ))
3235 INT_BARF( context, 0x21 );
3236 SET_CFLAG(context);
3237 break;
3239 SET_BX( context, flags | 0x4000 ); /* support for LFN functions */
3240 SET_CX( context, filename_len );
3241 SET_DX( context, MAX_PATH ); /* FIXME: which len if DRIVE_SHORT_NAMES ? */
3242 WideCharToMultiByte(CP_OEMCP, 0, dstW, -1,
3243 CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi),
3244 8, NULL, NULL);
3246 break;
3248 case 0xa1: /* LONG FILENAME - "FindClose" - TERMINATE DIRECTORY SEARCH */
3250 HGLOBAL16 h16 = BX_reg(context);
3251 HANDLE* ptr;
3253 TRACE("LONG FILENAME - FINDCLOSE for handle %d\n",
3254 BX_reg(context));
3255 if (h16 != INVALID_HANDLE_VALUE16 && (ptr = GlobalLock16( h16 )))
3257 if (!FindClose( *ptr )) bSetDOSExtendedError = TRUE;
3258 GlobalUnlock16( h16 );
3259 GlobalFree16( h16 );
3261 else
3263 SetLastError( ERROR_INVALID_HANDLE );
3264 bSetDOSExtendedError = TRUE;
3267 break;
3269 case 0xa6: /* LONG FILENAME - GET FILE INFO BY HANDLE */
3271 HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
3272 BY_HANDLE_FILE_INFORMATION *info =
3273 CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3275 TRACE( "LONG FILENAME - GET FILE INFO BY HANDLE\n" );
3277 if (!GetFileInformationByHandle(handle, info))
3278 bSetDOSExtendedError = TRUE;
3280 break;
3282 case 0xa7: /* LONG FILENAME - CONVERT TIME */
3283 switch (BL_reg(context))
3285 case 0x00: /* FILE TIME TO DOS TIME */
3287 WORD date, time;
3288 FILETIME *filetime = CTX_SEG_OFF_TO_LIN(context,
3289 context->SegDs,
3290 context->Esi);
3292 TRACE( "LONG FILENAME - FILE TIME TO DOS TIME\n" );
3294 FileTimeToDosDateTime( filetime, &date, &time );
3296 SET_DX( context, date );
3297 SET_CX( context, time );
3300 * FIXME: BH has number of 10-millisecond units
3301 * past time in CX.
3303 SET_BH( context, 0 );
3305 break;
3307 case 0x01: /* DOS TIME TO FILE TIME */
3309 FILETIME *filetime = CTX_SEG_OFF_TO_LIN(context,
3310 context->SegEs,
3311 context->Edi);
3313 TRACE( "LONG FILENAME - DOS TIME TO FILE TIME\n" );
3316 * FIXME: BH has number of 10-millisecond units
3317 * past time in CX.
3319 DosDateTimeToFileTime( DX_reg(context), CX_reg(context),
3320 filetime );
3322 break;
3324 default:
3325 INT_BARF( context, 0x21 );
3326 break;
3328 break;
3330 case 0xa8: /* LONG FILENAME - GENERATE SHORT FILENAME */
3331 case 0xa9: /* LONG FILENAME - SERVER CREATE OR OPEN FILE */
3332 case 0xaa: /* LONG FILENAME - SUBST */
3333 default:
3334 FIXME("Unimplemented long file name function:\n");
3335 INT_BARF( context, 0x21 );
3336 SET_CFLAG(context);
3337 SET_AL( context, 0 );
3338 break;
3341 if (bSetDOSExtendedError)
3343 SET_AX( context, GetLastError() );
3344 SET_CFLAG( context );
3349 /***********************************************************************
3350 * INT21_RenameFile
3352 * Handler for:
3353 * - function 0x56
3354 * - subfunction 0x56 of function 0x71
3355 * - subfunction 0xff of function 0x43 (CL == 0x56)
3357 static BOOL INT21_RenameFile( CONTEXT86 *context )
3359 WCHAR fromW[MAX_PATH];
3360 WCHAR toW[MAX_PATH];
3361 char *fromA = CTX_SEG_OFF_TO_LIN(context,
3362 context->SegDs,context->Edx);
3363 char *toA = CTX_SEG_OFF_TO_LIN(context,
3364 context->SegEs,context->Edi);
3366 TRACE( "RENAME FILE %s to %s\n", fromA, toA );
3367 MultiByteToWideChar(CP_OEMCP, 0, fromA, -1, fromW, MAX_PATH);
3368 MultiByteToWideChar(CP_OEMCP, 0, toA, -1, toW, MAX_PATH);
3370 return MoveFileW( fromW, toW );
3374 /***********************************************************************
3375 * INT21_NetworkFunc
3377 * Handler for:
3378 * - function 0x5e
3380 static BOOL INT21_NetworkFunc (CONTEXT86 *context)
3382 switch (AL_reg(context))
3384 case 0x00: /* Get machine name. */
3386 WCHAR dstW[MAX_COMPUTERNAME_LENGTH + 1];
3387 DWORD s = sizeof(dstW) / sizeof(WCHAR);
3388 int len;
3390 char *dst = CTX_SEG_OFF_TO_LIN (context,context->SegDs,context->Edx);
3391 TRACE("getting machine name to %p\n", dst);
3392 if (!GetComputerNameW(dstW, &s) ||
3393 !WideCharToMultiByte(CP_OEMCP, 0, dstW, -1, dst, 16, NULL, NULL))
3395 WARN("failed!\n");
3396 SetLastError( ER_NoNetwork );
3397 return TRUE;
3399 for (len = strlen(dst); len < 15; len++) dst[len] = ' ';
3400 dst[15] = 0;
3401 SET_CH( context, 1 ); /* Valid */
3402 SET_CL( context, 1 ); /* NETbios number??? */
3403 TRACE("returning %s\n", debugstr_an(dst, 16));
3404 return FALSE;
3407 default:
3408 SetLastError( ER_NoNetwork );
3409 return TRUE;
3413 /******************************************************************
3414 * INT21_GetDiskSerialNumber
3417 static int INT21_GetDiskSerialNumber( CONTEXT86 *context )
3419 BYTE *dataptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3420 WCHAR path[] = {'A',':',0}, label[11];
3421 DWORD serial;
3423 path[0] += INT21_MapDrive(BL_reg(context));
3424 if (!GetVolumeInformationW( path, label, 11, &serial, NULL, NULL, NULL, 0))
3426 SetLastError( ERROR_INVALID_DRIVE );
3427 return 0;
3430 *(WORD *)dataptr = 0;
3431 memcpy(dataptr + 2, &serial, sizeof(DWORD));
3432 WideCharToMultiByte(CP_OEMCP, 0, label, 11, (LPSTR)dataptr + 6, 11, NULL, NULL);
3433 memcpy(dataptr + 17, "FAT16 ", 8);
3434 return 1;
3438 /******************************************************************
3439 * INT21_SetDiskSerialNumber
3442 static int INT21_SetDiskSerialNumber( CONTEXT86 *context )
3444 #if 0
3445 BYTE *dataptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3446 int drive = INT21_MapDrive(BL_reg(context));
3448 if (!is_valid_drive(drive))
3450 SetLastError( ERROR_INVALID_DRIVE );
3451 return 0;
3454 DRIVE_SetSerialNumber( drive, *(DWORD *)(dataptr + 2) );
3455 return 1;
3456 #else
3457 FIXME("Setting drive serial number is no longer supported\n");
3458 SetLastError( ERROR_NOT_SUPPORTED );
3459 return 0;
3460 #endif
3464 /******************************************************************
3465 * INT21_GetFreeDiskSpace
3468 static int INT21_GetFreeDiskSpace( CONTEXT86 *context )
3470 DWORD cluster_sectors, sector_bytes, free_clusters, total_clusters;
3471 WCHAR root[] = {'A',':','\\',0};
3472 const DWORD max_clusters = 0x3d83;
3473 const DWORD max_sectors_per_cluster = 0x7f;
3474 const DWORD max_bytes_per_sector = 0x200;
3476 root[0] += INT21_MapDrive(DL_reg(context));
3477 if (!GetDiskFreeSpaceW( root, &cluster_sectors, &sector_bytes,
3478 &free_clusters, &total_clusters )) return 0;
3480 /* Some old win31 apps (Lotus SmartSuite 5.1) crap out if there's too
3481 * much disk space, so Windows XP seems to apply the following limits:
3482 * cluster_sectors <= 0x7f
3483 * sector size <= 0x200
3484 * clusters <= 0x3D83
3485 * This means total reported space is limited to about 1GB.
3488 /* Make sure bytes-per-sector is in [, max] */
3489 while (sector_bytes > max_bytes_per_sector) {
3490 sector_bytes >>= 1;
3491 free_clusters <<= 1;
3492 total_clusters <<= 1;
3494 /* Then make sure sectors-per-cluster is in [max/2, max]. */
3495 while (cluster_sectors <= max_sectors_per_cluster/2) {
3496 cluster_sectors <<= 1;
3497 free_clusters >>= 1;
3498 total_clusters >>= 1;
3500 while (cluster_sectors > max_sectors_per_cluster) {
3501 cluster_sectors >>= 1;
3502 free_clusters <<= 1;
3503 total_clusters <<= 1;
3506 /* scale up sectors_per_cluster to exactly max_sectors_per_cluster.
3507 * We could skip this, but that would impose an artificially low
3508 * limit on reported disk space.
3509 * To avoid overflow, first apply a preliminary cap on sector count;
3510 * this will not affect the correctness of the final result,
3511 * because if the preliminary cap hits, the final one will, too.
3513 if (total_clusters > 4 * max_clusters)
3514 total_clusters = 4 * max_clusters;
3515 if (free_clusters > 4 * max_clusters)
3516 free_clusters = 4 * max_clusters;
3517 if (cluster_sectors < max_sectors_per_cluster) {
3518 free_clusters *= cluster_sectors;
3519 free_clusters /= max_sectors_per_cluster;
3520 total_clusters *= cluster_sectors;
3521 total_clusters /= max_sectors_per_cluster;
3522 cluster_sectors = max_sectors_per_cluster;
3525 /* Finally, apply real cluster count cap. */
3526 if (total_clusters > max_clusters)
3527 total_clusters = max_clusters;
3528 if (free_clusters > max_clusters)
3529 free_clusters = max_clusters;
3531 SET_AX( context, cluster_sectors );
3532 SET_BX( context, free_clusters );
3533 SET_CX( context, sector_bytes );
3534 SET_DX( context, total_clusters );
3535 return 1;
3538 /******************************************************************
3539 * INT21_GetDriveAllocInfo
3542 static int INT21_GetDriveAllocInfo( CONTEXT86 *context, BYTE drive )
3544 INT21_DPB *dpb;
3546 drive = INT21_MapDrive( drive );
3547 if (!INT21_FillDrivePB( drive )) return 0;
3548 dpb = &(INT21_GetHeapPointer()->misc_dpb_list[drive]);
3549 SET_AL( context, dpb->cluster_sectors + 1 );
3550 SET_CX( context, dpb->sector_bytes );
3551 SET_DX( context, dpb->num_clusters1 );
3553 context->SegDs = INT21_GetHeapSelector( context );
3554 SET_BX( context, offsetof( INT21_HEAP, misc_dpb_list[drive].media_ID ) );
3555 return 1;
3558 /***********************************************************************
3559 * INT21_GetExtendedError
3561 static void INT21_GetExtendedError( CONTEXT86 *context )
3563 BYTE class, action, locus;
3564 WORD error = GetLastError();
3566 switch(error)
3568 case ERROR_SUCCESS:
3569 class = action = locus = 0;
3570 break;
3571 case ERROR_DIR_NOT_EMPTY:
3572 class = EC_Exists;
3573 action = SA_Ignore;
3574 locus = EL_Disk;
3575 break;
3576 case ERROR_ACCESS_DENIED:
3577 class = EC_AccessDenied;
3578 action = SA_Abort;
3579 locus = EL_Disk;
3580 break;
3581 case ERROR_CANNOT_MAKE:
3582 class = EC_AccessDenied;
3583 action = SA_Abort;
3584 locus = EL_Unknown;
3585 break;
3586 case ERROR_DISK_FULL:
3587 case ERROR_HANDLE_DISK_FULL:
3588 class = EC_MediaError;
3589 action = SA_Abort;
3590 locus = EL_Disk;
3591 break;
3592 case ERROR_FILE_EXISTS:
3593 case ERROR_ALREADY_EXISTS:
3594 class = EC_Exists;
3595 action = SA_Abort;
3596 locus = EL_Disk;
3597 break;
3598 case ERROR_FILE_NOT_FOUND:
3599 class = EC_NotFound;
3600 action = SA_Abort;
3601 locus = EL_Disk;
3602 break;
3603 case ERROR_GEN_FAILURE:
3604 class = EC_SystemFailure;
3605 action = SA_Abort;
3606 locus = EL_Unknown;
3607 break;
3608 case ERROR_INVALID_DRIVE:
3609 class = EC_MediaError;
3610 action = SA_Abort;
3611 locus = EL_Disk;
3612 break;
3613 case ERROR_INVALID_HANDLE:
3614 class = EC_ProgramError;
3615 action = SA_Abort;
3616 locus = EL_Disk;
3617 break;
3618 case ERROR_LOCK_VIOLATION:
3619 class = EC_AccessDenied;
3620 action = SA_Abort;
3621 locus = EL_Disk;
3622 break;
3623 case ERROR_NO_MORE_FILES:
3624 class = EC_MediaError;
3625 action = SA_Abort;
3626 locus = EL_Disk;
3627 break;
3628 case ER_NoNetwork:
3629 class = EC_NotFound;
3630 action = SA_Abort;
3631 locus = EL_Network;
3632 break;
3633 case ERROR_NOT_ENOUGH_MEMORY:
3634 class = EC_OutOfResource;
3635 action = SA_Abort;
3636 locus = EL_Memory;
3637 break;
3638 case ERROR_PATH_NOT_FOUND:
3639 class = EC_NotFound;
3640 action = SA_Abort;
3641 locus = EL_Disk;
3642 break;
3643 case ERROR_SEEK:
3644 class = EC_NotFound;
3645 action = SA_Ignore;
3646 locus = EL_Disk;
3647 break;
3648 case ERROR_SHARING_VIOLATION:
3649 class = EC_Temporary;
3650 action = SA_Retry;
3651 locus = EL_Disk;
3652 break;
3653 case ERROR_TOO_MANY_OPEN_FILES:
3654 class = EC_ProgramError;
3655 action = SA_Abort;
3656 locus = EL_Disk;
3657 break;
3658 default:
3659 FIXME("Unknown error %d\n", error );
3660 class = EC_SystemFailure;
3661 action = SA_Abort;
3662 locus = EL_Unknown;
3663 break;
3665 TRACE("GET EXTENDED ERROR code 0x%02x class 0x%02x action 0x%02x locus %02x\n",
3666 error, class, action, locus );
3667 SET_AX( context, error );
3668 SET_BH( context, class );
3669 SET_BL( context, action );
3670 SET_CH( context, locus );
3673 static BOOL INT21_CreateTempFile( CONTEXT86 *context )
3675 static int counter = 0;
3676 char *name = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx );
3677 char *p = name + strlen(name);
3679 /* despite what Ralf Brown says, some programs seem to call without
3680 * ending backslash (DOS accepts that, so we accept it too) */
3681 if ((p == name) || (p[-1] != '\\')) *p++ = '\\';
3683 for (;;)
3685 sprintf( p, "wine%04x.%03d", (int)getpid(), counter );
3686 counter = (counter + 1) % 1000;
3688 SET_AX( context,
3689 Win32HandleToDosFileHandle(
3690 CreateFileA( name, GENERIC_READ | GENERIC_WRITE,
3691 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
3692 CREATE_NEW, 0, 0 ) ) );
3693 if (AX_reg(context) != HFILE_ERROR16)
3695 TRACE("created %s\n", name );
3696 return TRUE;
3698 if (GetLastError() != ERROR_FILE_EXISTS) return FALSE;
3702 /***********************************************************************
3703 * DOSFS_ToDosFCBFormat
3705 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
3706 * expanding wild cards and converting to upper-case in the process.
3707 * File name can be terminated by '\0', '\\' or '/'.
3708 * Return FALSE if the name is not a valid DOS name.
3709 * 'buffer' must be at least 12 characters long.
3711 /* Chars we don't want to see in DOS file names */
3712 static BOOL INT21_ToDosFCBFormat( LPCWSTR name, LPWSTR buffer )
3714 static const WCHAR invalid_chars[] = {'*','?','<','>','|','\\','"','+','=',',',';','[',']',' ','\345',0};
3715 LPCWSTR p = name;
3716 int i;
3718 /* Check for "." and ".." */
3719 if (*p == '.')
3721 p++;
3722 buffer[0] = '.';
3723 for(i = 1; i < 11; i++) buffer[i] = ' ';
3724 buffer[11] = 0;
3725 if (*p == '.')
3727 buffer[1] = '.';
3728 p++;
3730 return (!*p || (*p == '/') || (*p == '\\'));
3733 for (i = 0; i < 8; i++)
3735 switch(*p)
3737 case '\0':
3738 case '\\':
3739 case '/':
3740 case '.':
3741 buffer[i] = ' ';
3742 break;
3743 case '?':
3744 p++;
3745 /* fall through */
3746 case '*':
3747 buffer[i] = '?';
3748 break;
3749 default:
3750 if (strchrW( invalid_chars, *p )) return FALSE;
3751 buffer[i] = toupperW(*p);
3752 p++;
3753 break;
3757 if (*p == '*')
3759 /* Skip all chars after wildcard up to first dot */
3760 while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
3762 else
3764 /* Check if name too long */
3765 if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
3767 if (*p == '.') p++; /* Skip dot */
3769 for (i = 8; i < 11; i++)
3771 switch(*p)
3773 case '\0':
3774 case '\\':
3775 case '/':
3776 buffer[i] = ' ';
3777 break;
3778 case '.':
3779 return FALSE; /* Second extension not allowed */
3780 case '?':
3781 p++;
3782 /* fall through */
3783 case '*':
3784 buffer[i] = '?';
3785 break;
3786 default:
3787 if (strchrW( invalid_chars, *p )) return FALSE;
3788 buffer[i] = toupperW(*p);
3789 p++;
3790 break;
3793 buffer[11] = '\0';
3795 /* at most 3 character of the extension are processed
3796 * is something behind this ?
3798 while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */
3799 return (!*p || (*p == '/') || (*p == '\\'));
3802 static HANDLE INT21_FindHandle;
3803 static const WCHAR *INT21_FindPath; /* will point to current dta->fullPath search */
3805 /******************************************************************
3806 * INT21_FindFirst
3808 static int INT21_FindFirst( CONTEXT86 *context )
3810 WCHAR *p, *q;
3811 const char *path;
3812 FINDFILE_DTA *dta = (FINDFILE_DTA *)INT21_GetCurrentDTA(context);
3813 WCHAR maskW[12], pathW[MAX_PATH];
3814 static const WCHAR wildcardW[] = {'*','.','*',0};
3816 path = (const char *)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3817 MultiByteToWideChar(CP_OEMCP, 0, path, -1, pathW, MAX_PATH);
3819 p = strrchrW( pathW, '\\');
3820 q = strrchrW( pathW, '/');
3821 if (q>p) p = q;
3822 if (!p)
3824 if (pathW[0] && pathW[1] == ':') p = pathW + 2;
3825 else p = pathW;
3827 else p++;
3829 /* Note: terminating NULL in dta->mask overwrites dta->search_attr
3830 * (doesn't matter as it is set below anyway)
3832 if (!INT21_ToDosFCBFormat( p, maskW ))
3834 SetLastError( ERROR_FILE_NOT_FOUND );
3835 SET_AX( context, ERROR_FILE_NOT_FOUND );
3836 SET_CFLAG(context);
3837 return 0;
3839 WideCharToMultiByte(CP_OEMCP, 0, maskW, 12, dta->mask, sizeof(dta->mask), NULL, NULL);
3841 dta->fullPath = HeapAlloc( GetProcessHeap(), 0, sizeof(wildcardW) + (p - pathW)*sizeof(WCHAR) );
3842 memcpy( dta->fullPath, pathW, (p - pathW) * sizeof(WCHAR) );
3843 memcpy( dta->fullPath + (p - pathW), wildcardW, sizeof(wildcardW) );
3844 /* we must have a fully qualified file name in dta->fullPath
3845 * (we could have a UNC path, but this would lead to some errors later on)
3847 dta->drive = toupperW(dta->fullPath[0]) - 'A';
3848 dta->count = 0;
3849 dta->search_attr = CL_reg(context);
3850 return 1;
3853 /******************************************************************
3854 * match_short
3856 * Check is a short path name (DTA unicode) matches a mask (FCB ansi)
3858 static BOOL match_short(LPCWSTR shortW, LPCSTR maskA)
3860 WCHAR mask[11], file[12];
3861 int i;
3863 if (!INT21_ToDosFCBFormat( shortW, file )) return FALSE;
3864 MultiByteToWideChar(CP_OEMCP, 0, maskA, 11, mask, 11);
3865 for (i = 0; i < 11; i++)
3866 if (mask[i] != '?' && mask[i] != file[i]) return FALSE;
3867 return TRUE;
3870 static unsigned INT21_FindHelper(LPCWSTR fullPath, unsigned drive, unsigned count,
3871 LPCSTR mask, unsigned search_attr,
3872 WIN32_FIND_DATAW* entry)
3874 unsigned ncalls;
3876 if ((search_attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
3878 WCHAR path[] = {' ',':',0};
3880 if (count) return 0;
3881 path[0] = drive + 'A';
3882 if (!GetVolumeInformationW(path, entry->cAlternateFileName, 13, NULL, NULL, NULL, NULL, 0)) return 0;
3883 RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftCreationTime );
3884 RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftLastAccessTime );
3885 RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftLastWriteTime );
3886 entry->dwFileAttributes = FA_LABEL;
3887 entry->nFileSizeHigh = entry->nFileSizeLow = 0;
3888 TRACE("returning %s as label\n", debugstr_w(entry->cAlternateFileName));
3889 return 1;
3892 if (!INT21_FindHandle || INT21_FindPath != fullPath || count == 0)
3894 if (INT21_FindHandle) FindClose(INT21_FindHandle);
3895 INT21_FindHandle = FindFirstFileW(fullPath, entry);
3896 if (INT21_FindHandle == INVALID_HANDLE_VALUE)
3898 INT21_FindHandle = 0;
3899 return 0;
3901 INT21_FindPath = fullPath;
3902 /* we need to resync search */
3903 ncalls = count;
3905 else ncalls = 1;
3907 while (ncalls-- != 0)
3909 if (!FindNextFileW(INT21_FindHandle, entry))
3911 FindClose(INT21_FindHandle); INT21_FindHandle = 0;
3912 return 0;
3915 while (count < 0xffff)
3917 count++;
3918 /* Check the file attributes, and path */
3919 if (!(entry->dwFileAttributes & ~search_attr) &&
3920 match_short(entry->cAlternateFileName[0] ? entry->cAlternateFileName : entry->cFileName,
3921 mask))
3923 return count;
3925 if (!FindNextFileW(INT21_FindHandle, entry))
3927 FindClose(INT21_FindHandle); INT21_FindHandle = 0;
3928 return 0;
3931 WARN("Too many directory entries in %s\n", debugstr_w(fullPath) );
3932 return 0;
3935 /******************************************************************
3936 * INT21_FindNext
3938 static int INT21_FindNext( CONTEXT86 *context )
3940 FINDFILE_DTA *dta = (FINDFILE_DTA *)INT21_GetCurrentDTA(context);
3941 DWORD attr = dta->search_attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
3942 WIN32_FIND_DATAW entry;
3943 int n;
3945 if (!dta->fullPath) return 0;
3947 n = INT21_FindHelper(dta->fullPath, dta->drive, dta->count,
3948 dta->mask, attr, &entry);
3949 if (n)
3951 dta->fileattr = entry.dwFileAttributes;
3952 dta->filesize = entry.nFileSizeLow;
3953 FileTimeToDosDateTime( &entry.ftLastWriteTime, &dta->filedate, &dta->filetime );
3954 if (entry.cAlternateFileName[0])
3955 WideCharToMultiByte(CP_OEMCP, 0, entry.cAlternateFileName, -1,
3956 dta->filename, 13, NULL, NULL);
3957 else
3958 WideCharToMultiByte(CP_OEMCP, 0, entry.cFileName, -1, dta->filename, 13, NULL, NULL);
3960 if (!memchr(dta->mask,'?',11))
3962 /* wildcardless search, release resources in case no findnext will
3963 * be issued, and as a workaround in case file creation messes up
3964 * findnext, as sometimes happens with pkunzip
3966 HeapFree( GetProcessHeap(), 0, dta->fullPath );
3967 INT21_FindPath = dta->fullPath = NULL;
3969 dta->count = n;
3970 return 1;
3972 HeapFree( GetProcessHeap(), 0, dta->fullPath );
3973 INT21_FindPath = dta->fullPath = NULL;
3974 return 0;
3977 /* microsoft's programmers should be shot for using CP/M style int21
3978 calls in Windows for Workgroup's winfile.exe */
3980 /******************************************************************
3981 * INT21_FindFirstFCB
3984 static int INT21_FindFirstFCB( CONTEXT86 *context )
3986 BYTE *fcb = (BYTE *)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
3987 FINDFILE_FCB *pFCB;
3988 int drive;
3989 WCHAR p[] = {' ',':',};
3991 if (*fcb == 0xff) pFCB = (FINDFILE_FCB *)(fcb + 7);
3992 else pFCB = (FINDFILE_FCB *)fcb;
3993 drive = INT21_MapDrive( pFCB->drive );
3994 if (drive == MAX_DOS_DRIVES) return 0;
3996 p[0] = 'A' + drive;
3997 pFCB->fullPath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
3998 if (!pFCB->fullPath) return 0;
3999 GetLongPathNameW(p, pFCB->fullPath, MAX_PATH);
4000 pFCB->count = 0;
4001 return 1;
4004 /******************************************************************
4005 * INT21_FindNextFCB
4008 static int INT21_FindNextFCB( CONTEXT86 *context )
4010 BYTE *fcb = (BYTE *)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
4011 FINDFILE_FCB *pFCB;
4012 LPBYTE pResult = INT21_GetCurrentDTA(context);
4013 DOS_DIRENTRY_LAYOUT *ddl;
4014 WIN32_FIND_DATAW entry;
4015 BYTE attr;
4016 int n;
4017 WCHAR nameW[12];
4019 if (*fcb == 0xff) /* extended FCB ? */
4021 attr = fcb[6];
4022 pFCB = (FINDFILE_FCB *)(fcb + 7);
4024 else
4026 attr = 0;
4027 pFCB = (FINDFILE_FCB *)fcb;
4030 if (!pFCB->fullPath) return 0;
4031 n = INT21_FindHelper(pFCB->fullPath, INT21_MapDrive( pFCB->drive ),
4032 pFCB->count, pFCB->filename, attr, &entry);
4033 if (!n)
4035 HeapFree( GetProcessHeap(), 0, pFCB->fullPath );
4036 INT21_FindPath = pFCB->fullPath = NULL;
4037 return 0;
4039 pFCB->count += n;
4041 if (*fcb == 0xff)
4043 /* place extended FCB header before pResult if called with extended FCB */
4044 *pResult = 0xff;
4045 pResult += 6; /* leave reserved field behind */
4046 *pResult++ = entry.dwFileAttributes;
4048 *pResult++ = INT21_MapDrive( pFCB->drive ); /* DOS_DIRENTRY_LAYOUT after current drive number */
4049 ddl = (DOS_DIRENTRY_LAYOUT*)pResult;
4050 ddl->fileattr = entry.dwFileAttributes;
4051 ddl->cluster = 0; /* what else? */
4052 ddl->filesize = entry.nFileSizeLow;
4053 memset( ddl->reserved, 0, sizeof(ddl->reserved) );
4054 FileTimeToDosDateTime( &entry.ftLastWriteTime,
4055 &ddl->filedate, &ddl->filetime );
4057 /* Convert file name to FCB format */
4058 if (entry.cAlternateFileName[0])
4059 INT21_ToDosFCBFormat( entry.cAlternateFileName, nameW );
4060 else
4061 INT21_ToDosFCBFormat( entry.cFileName, nameW );
4062 WideCharToMultiByte(CP_OEMCP, 0, nameW, 11, ddl->filename, 11, NULL, NULL);
4063 return 1;
4067 /******************************************************************
4068 * INT21_ParseFileNameIntoFCB
4071 static void INT21_ParseFileNameIntoFCB( CONTEXT86 *context )
4073 char *filename =
4074 CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi );
4075 char *fcb =
4076 CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi );
4077 char *s;
4078 WCHAR *buffer;
4079 WCHAR fcbW[12];
4080 INT buffer_len, len;
4082 SET_AL( context, 0xff ); /* failed */
4084 TRACE("filename: '%s'\n", filename);
4086 s = filename;
4087 while (*s && (*s != ' ') && (*s != '\r') && (*s != '\n'))
4088 s++;
4089 len = filename - s;
4091 buffer_len = MultiByteToWideChar(CP_OEMCP, 0, filename, len, NULL, 0);
4092 buffer = HeapAlloc( GetProcessHeap(), 0, (buffer_len + 1) * sizeof(WCHAR));
4093 len = MultiByteToWideChar(CP_OEMCP, 0, filename, len, buffer, buffer_len);
4094 buffer[len] = 0;
4095 INT21_ToDosFCBFormat(buffer, fcbW);
4096 HeapFree(GetProcessHeap(), 0, buffer);
4097 WideCharToMultiByte(CP_OEMCP, 0, fcbW, 12, fcb + 1, 12, NULL, NULL);
4098 *fcb = 0;
4099 TRACE("FCB: '%s'\n", fcb + 1);
4101 SET_AL( context, ((strchr(filename, '*')) || (strchr(filename, '$'))) != 0 );
4103 /* point DS:SI to first unparsed character */
4104 SET_SI( context, context->Esi + (int)s - (int)filename );
4107 static BOOL INT21_Dup2(HFILE16 hFile1, HFILE16 hFile2)
4109 HFILE16 res = HFILE_ERROR16;
4110 HANDLE handle, new_handle;
4111 #define DOS_TABLE_SIZE 256
4112 DWORD map[DOS_TABLE_SIZE / 32];
4113 int i;
4115 handle = DosFileHandleToWin32Handle(hFile1);
4116 if (handle == INVALID_HANDLE_VALUE)
4117 return FALSE;
4119 _lclose16(hFile2);
4120 /* now loop to allocate the same one... */
4121 memset(map, 0, sizeof(map));
4122 for (i = 0; i < DOS_TABLE_SIZE; i++)
4124 if (!DuplicateHandle(GetCurrentProcess(), handle,
4125 GetCurrentProcess(), &new_handle,
4126 0, FALSE, DUPLICATE_SAME_ACCESS))
4128 res = HFILE_ERROR16;
4129 break;
4131 res = Win32HandleToDosFileHandle(new_handle);
4132 if (res == HFILE_ERROR16 || res == hFile2) break;
4133 map[res / 32] |= 1 << (res % 32);
4135 /* clean up the allocated slots */
4136 for (i = 0; i < DOS_TABLE_SIZE; i++)
4138 if (map[i / 32] & (1 << (i % 32)))
4139 _lclose16((HFILE16)i);
4141 return res == hFile2;
4145 /***********************************************************************
4146 * DOSVM_Int21Handler
4148 * Interrupt 0x21 handler.
4150 void WINAPI DOSVM_Int21Handler( CONTEXT86 *context )
4152 BOOL bSetDOSExtendedError = FALSE;
4154 TRACE( "AX=%04x BX=%04x CX=%04x DX=%04x "
4155 "SI=%04x DI=%04x DS=%04x ES=%04x EFL=%08x\n",
4156 AX_reg(context), BX_reg(context),
4157 CX_reg(context), DX_reg(context),
4158 SI_reg(context), DI_reg(context),
4159 (WORD)context->SegDs, (WORD)context->SegEs,
4160 context->EFlags );
4163 * Extended error is used by (at least) functions 0x2f to 0x62.
4164 * Function 0x59 returns extended error and error should not
4165 * be cleared before handling the function.
4167 if (AH_reg(context) >= 0x2f && AH_reg(context) != 0x59)
4168 SetLastError(0);
4170 RESET_CFLAG(context); /* Not sure if this is a good idea. */
4172 switch(AH_reg(context))
4174 case 0x00: /* TERMINATE PROGRAM */
4175 TRACE("TERMINATE PROGRAM\n");
4176 if (DOSVM_IsWin16())
4177 ExitThread( 0 );
4178 else if(ISV86(context))
4179 MZ_Exit( context, FALSE, 0 );
4180 else
4181 ERR( "Called from DOS protected mode\n" );
4182 break;
4184 case 0x01: /* READ CHARACTER FROM STANDARD INPUT, WITH ECHO */
4186 BYTE ascii;
4187 TRACE("DIRECT CHARACTER INPUT WITH ECHO\n");
4188 INT21_ReadChar( &ascii, context );
4189 SET_AL( context, ascii );
4191 * FIXME: What to echo when extended keycodes are read?
4193 DOSVM_PutChar(AL_reg(context));
4195 break;
4197 case 0x02: /* WRITE CHARACTER TO STANDARD OUTPUT */
4198 TRACE("Write Character to Standard Output\n");
4199 DOSVM_PutChar(DL_reg(context));
4200 break;
4202 case 0x03: /* READ CHARACTER FROM STDAUX */
4203 case 0x04: /* WRITE CHARACTER TO STDAUX */
4204 case 0x05: /* WRITE CHARACTER TO PRINTER */
4205 INT_BARF( context, 0x21 );
4206 break;
4208 case 0x06: /* DIRECT CONSOLE IN/OUTPUT */
4209 if (DL_reg(context) == 0xff)
4211 TRACE("Direct Console Input\n");
4213 if (INT21_ReadChar( NULL, NULL ))
4215 BYTE ascii;
4216 INT21_ReadChar( &ascii, context );
4217 SET_AL( context, ascii );
4218 RESET_ZFLAG( context );
4220 else
4222 /* no character available */
4223 SET_AL( context, 0 );
4224 SET_ZFLAG( context );
4227 else
4229 TRACE("Direct Console Output\n");
4230 DOSVM_PutChar(DL_reg(context));
4232 * At least DOS versions 2.1-7.0 return character
4233 * that was written in AL register.
4235 SET_AL( context, DL_reg(context) );
4237 break;
4239 case 0x07: /* DIRECT CHARACTER INPUT WITHOUT ECHO */
4241 BYTE ascii;
4242 TRACE("DIRECT CHARACTER INPUT WITHOUT ECHO\n");
4243 INT21_ReadChar( &ascii, context );
4244 SET_AL( context, ascii );
4246 break;
4248 case 0x08: /* CHARACTER INPUT WITHOUT ECHO */
4250 BYTE ascii;
4251 TRACE("CHARACTER INPUT WITHOUT ECHO\n");
4252 INT21_ReadChar( &ascii, context );
4253 SET_AL( context, ascii );
4255 break;
4257 case 0x09: /* WRITE STRING TO STANDARD OUTPUT */
4258 TRACE("WRITE '$'-terminated string from %04X:%04X to stdout\n",
4259 context->SegDs, DX_reg(context) );
4261 LPSTR data = CTX_SEG_OFF_TO_LIN( context,
4262 context->SegDs, context->Edx );
4263 LPSTR p = data;
4264 DWORD w;
4266 * Do NOT use strchr() to calculate the string length,
4267 * as '\0' is valid string content, too!
4268 * Maybe we should check for non-'$' strings, but DOS doesn't.
4270 while (*p != '$') p++;
4272 if (DOSVM_IsWin16())
4273 WriteFile( DosFileHandleToWin32Handle(1),
4274 data, p - data, &w, NULL );
4275 else
4276 for(; data != p; data++)
4277 DOSVM_PutChar( *data );
4279 SET_AL( context, '$' ); /* yes, '$' (0x24) gets returned in AL */
4281 break;
4283 case 0x0a: /* BUFFERED INPUT */
4285 BYTE *ptr = CTX_SEG_OFF_TO_LIN(context,
4286 context->SegDs,
4287 context->Edx);
4288 WORD result;
4290 TRACE( "BUFFERED INPUT (size=%d)\n", ptr[0] );
4293 * FIXME: Some documents state that
4294 * ptr[1] holds number of chars from last input which
4295 * may be recalled on entry, other documents do not mention
4296 * this at all.
4298 if (ptr[1])
4299 TRACE( "Handle old chars in buffer!\n" );
4302 * ptr[0] - capacity (includes terminating CR)
4303 * ptr[1] - characters read (excludes terminating CR)
4305 result = INT21_BufferedInput( context, ptr + 2, ptr[0] );
4306 if (result > 0)
4307 ptr[1] = (BYTE)result - 1;
4308 else
4309 ptr[1] = 0;
4311 break;
4313 case 0x0b: /* GET STDIN STATUS */
4314 TRACE( "GET STDIN STATUS\n" );
4316 if (INT21_ReadChar( NULL, NULL ))
4317 SET_AL( context, 0xff ); /* character available */
4318 else
4319 SET_AL( context, 0 ); /* no character available */
4321 break;
4323 case 0x0c: /* FLUSH BUFFER AND READ STANDARD INPUT */
4325 BYTE al = AL_reg(context); /* Input function to execute after flush. */
4327 TRACE( "FLUSH BUFFER AND READ STANDARD INPUT - 0x%02x\n", al );
4329 /* FIXME: buffers are not flushed */
4332 * If AL is one of 0x01, 0x06, 0x07, 0x08, or 0x0a,
4333 * int21 function identified by AL will be called.
4335 if(al == 0x01 || al == 0x06 || al == 0x07 || al == 0x08 || al == 0x0a)
4337 SET_AH( context, al );
4338 DOSVM_Int21Handler( context );
4341 break;
4343 case 0x0d: /* DISK BUFFER FLUSH */
4344 TRACE("DISK BUFFER FLUSH ignored\n");
4345 break;
4347 case 0x0e: /* SELECT DEFAULT DRIVE */
4348 TRACE( "SELECT DEFAULT DRIVE - %c:\n", 'A' + DL_reg(context) );
4349 INT21_SetCurrentDrive( DL_reg(context) );
4350 SET_AL( context, MAX_DOS_DRIVES );
4351 break;
4353 case 0x0f: /* OPEN FILE USING FCB */
4354 INT21_OpenFileUsingFCB( context );
4355 break;
4357 case 0x10: /* CLOSE FILE USING FCB */
4358 INT21_CloseFileUsingFCB( context );
4359 break;
4361 case 0x11: /* FIND FIRST MATCHING FILE USING FCB */
4362 TRACE("FIND FIRST MATCHING FILE USING FCB %p\n",
4363 CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx));
4364 if (!INT21_FindFirstFCB(context))
4366 SET_AL( context, 0xff );
4367 break;
4369 /* else fall through */
4371 case 0x12: /* FIND NEXT MATCHING FILE USING FCB */
4372 SET_AL( context, INT21_FindNextFCB(context) ? 0x00 : 0xff );
4373 break;
4375 case 0x13: /* DELETE FILE USING FCB */
4376 INT_BARF( context, 0x21 );
4377 break;
4379 case 0x14: /* SEQUENTIAL READ FROM FCB FILE */
4380 INT21_SequentialReadFromFCB( context );
4381 break;
4383 case 0x15: /* SEQUENTIAL WRITE TO FCB FILE */
4384 INT21_SequentialWriteToFCB( context );
4385 break;
4387 case 0x16: /* CREATE OR TRUNCATE FILE USING FCB */
4388 case 0x17: /* RENAME FILE USING FCB */
4389 INT_BARF( context, 0x21 );
4390 break;
4392 case 0x18: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
4393 SET_AL( context, 0 );
4394 break;
4396 case 0x19: /* GET CURRENT DEFAULT DRIVE */
4397 SET_AL( context, INT21_GetCurrentDrive() );
4398 TRACE( "GET CURRENT DRIVE -> %c:\n", 'A' + AL_reg( context ) );
4399 break;
4401 case 0x1a: /* SET DISK TRANSFER AREA ADDRESS */
4402 TRACE( "SET DISK TRANSFER AREA ADDRESS %04X:%04X\n",
4403 context->SegDs, DX_reg(context) );
4405 TDB *task = GlobalLock16( GetCurrentTask() );
4406 task->dta = MAKESEGPTR( context->SegDs, DX_reg(context) );
4408 break;
4410 case 0x1b: /* GET ALLOCATION INFORMATION FOR DEFAULT DRIVE */
4411 if (!INT21_GetDriveAllocInfo(context, 0))
4412 SET_AX( context, 0xffff );
4413 break;
4415 case 0x1c: /* GET ALLOCATION INFORMATION FOR SPECIFIC DRIVE */
4416 if (!INT21_GetDriveAllocInfo(context, DL_reg(context)))
4417 SET_AX( context, 0xffff );
4418 break;
4420 case 0x1d: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
4421 case 0x1e: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
4422 SET_AL( context, 0 );
4423 break;
4425 case 0x1f: /* GET DRIVE PARAMETER BLOCK FOR DEFAULT DRIVE */
4427 BYTE drive = INT21_MapDrive( 0 );
4428 TRACE( "GET DPB FOR DEFAULT DRIVE\n" );
4430 if (INT21_FillDrivePB( drive ))
4432 SET_AL( context, 0x00 ); /* success */
4433 SET_BX( context, offsetof( INT21_HEAP, misc_dpb_list[drive] ) );
4434 context->SegDs = INT21_GetHeapSelector( context );
4436 else
4438 SET_AL( context, 0xff ); /* invalid or network drive */
4441 break;
4443 case 0x20: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
4444 SET_AL( context, 0 );
4445 break;
4447 case 0x21: /* READ RANDOM RECORD FROM FCB FILE */
4448 INT21_ReadRandomRecordFromFCB( context );
4449 break;
4451 case 0x22: /* WRITE RANDOM RECORD TO FCB FILE */
4452 INT21_WriteRandomRecordToFCB( context );
4453 break;
4455 case 0x23: /* GET FILE SIZE FOR FCB */
4456 case 0x24: /* SET RANDOM RECORD NUMBER FOR FCB */
4457 INT_BARF( context, 0x21 );
4458 break;
4460 case 0x25: /* SET INTERRUPT VECTOR */
4461 TRACE("SET INTERRUPT VECTOR 0x%02x\n",AL_reg(context));
4463 FARPROC16 ptr = (FARPROC16)MAKESEGPTR( context->SegDs, DX_reg(context) );
4464 if (!ISV86(context) && DOSVM_IsWin16())
4465 DOSVM_SetPMHandler16( AL_reg(context), ptr );
4466 else
4467 DOSVM_SetRMHandler( AL_reg(context), ptr );
4469 break;
4471 case 0x26: /* CREATE NEW PROGRAM SEGMENT PREFIX */
4472 INT_BARF( context, 0x21 );
4473 break;
4475 case 0x27: /* RANDOM BLOCK READ FROM FCB FILE */
4476 INT21_RandomBlockReadFromFCB( context );
4477 break;
4479 case 0x28: /* RANDOM BLOCK WRITE TO FCB FILE */
4480 INT21_RandomBlockWriteToFCB( context );
4481 break;
4483 case 0x29: /* PARSE FILENAME INTO FCB */
4484 INT21_ParseFileNameIntoFCB(context);
4485 break;
4487 case 0x2a: /* GET SYSTEM DATE */
4488 TRACE( "GET SYSTEM DATE\n" );
4490 SYSTEMTIME systime;
4491 GetLocalTime( &systime );
4492 SET_CX( context, systime.wYear );
4493 SET_DH( context, systime.wMonth );
4494 SET_DL( context, systime.wDay );
4495 SET_AL( context, systime.wDayOfWeek );
4497 break;
4499 case 0x2b: /* SET SYSTEM DATE */
4500 TRACE( "SET SYSTEM DATE\n" );
4502 WORD year = CX_reg(context);
4503 BYTE month = DH_reg(context);
4504 BYTE day = DL_reg(context);
4506 if (year >= 1980 && year <= 2099 &&
4507 month >= 1 && month <= 12 &&
4508 day >= 1 && day <= 31)
4510 FIXME( "SetSystemDate(%02d/%02d/%04d): not allowed\n",
4511 day, month, year );
4512 SET_AL( context, 0 ); /* Let's pretend we succeeded */
4514 else
4516 SET_AL( context, 0xff ); /* invalid date */
4517 TRACE( "SetSystemDate(%02d/%02d/%04d): invalid date\n",
4518 day, month, year );
4521 break;
4523 case 0x2c: /* GET SYSTEM TIME */
4524 TRACE( "GET SYSTEM TIME\n" );
4526 SYSTEMTIME systime;
4527 GetLocalTime( &systime );
4528 SET_CH( context, systime.wHour );
4529 SET_CL( context, systime.wMinute );
4530 SET_DH( context, systime.wSecond );
4531 SET_DL( context, systime.wMilliseconds / 10 );
4533 break;
4535 case 0x2d: /* SET SYSTEM TIME */
4536 if( CH_reg(context) >= 24 || CL_reg(context) >= 60 || DH_reg(context) >= 60 || DL_reg(context) >= 100 ) {
4537 TRACE("SetSystemTime(%02d:%02d:%02d.%02d): wrong time\n",
4538 CH_reg(context), CL_reg(context),
4539 DH_reg(context), DL_reg(context) );
4540 SET_AL( context, 0xFF );
4542 else
4544 FIXME("SetSystemTime(%02d:%02d:%02d.%02d): not allowed\n",
4545 CH_reg(context), CL_reg(context),
4546 DH_reg(context), DL_reg(context) );
4547 SET_AL( context, 0 ); /* Let's pretend we succeeded */
4549 break;
4551 case 0x2e: /* SET VERIFY FLAG */
4552 TRACE("SET VERIFY FLAG ignored\n");
4553 /* we cannot change the behaviour anyway, so just ignore it */
4554 break;
4556 case 0x2f: /* GET DISK TRANSFER AREA ADDRESS */
4557 TRACE( "GET DISK TRANSFER AREA ADDRESS\n" );
4559 TDB *task = GlobalLock16( GetCurrentTask() );
4560 context->SegEs = SELECTOROF( task->dta );
4561 SET_BX( context, OFFSETOF( task->dta ) );
4563 break;
4565 case 0x30: /* GET DOS VERSION */
4566 TRACE( "GET DOS VERSION - %s requested\n",
4567 (AL_reg(context) == 0x00) ? "OEM number" : "version flag" );
4569 if (AL_reg(context) == 0x00)
4570 SET_BH( context, 0xff ); /* OEM number => undefined */
4571 else
4572 SET_BH( context, 0x08 ); /* version flag => DOS is in ROM */
4574 SET_AL( context, HIBYTE(HIWORD(GetVersion16())) ); /* major version */
4575 SET_AH( context, LOBYTE(HIWORD(GetVersion16())) ); /* minor version */
4577 SET_BL( context, 0x12 ); /* 0x123456 is 24-bit Wine's serial # */
4578 SET_CX( context, 0x3456 );
4579 break;
4581 case 0x31: /* TERMINATE AND STAY RESIDENT */
4582 FIXME("TERMINATE AND STAY RESIDENT stub\n");
4583 break;
4585 case 0x32: /* GET DOS DRIVE PARAMETER BLOCK FOR SPECIFIC DRIVE */
4587 BYTE drive = INT21_MapDrive( DL_reg(context) );
4588 TRACE( "GET DPB FOR SPECIFIC DRIVE %d\n", DL_reg(context) );
4590 if (INT21_FillDrivePB( drive ))
4592 SET_AL( context, 0x00 ); /* success */
4593 SET_DX( context, offsetof( INT21_HEAP, misc_dpb_list[drive] ) );
4594 context->SegDs = INT21_GetHeapSelector( context );
4596 else
4598 SET_AL( context, 0xff ); /* invalid or network drive */
4601 break;
4603 case 0x33: /* MULTIPLEXED */
4604 switch (AL_reg(context))
4606 case 0x00: /* GET CURRENT EXTENDED BREAK STATE */
4607 TRACE("GET CURRENT EXTENDED BREAK STATE\n");
4608 SET_DL( context, DOSCONF_GetConfig()->brk_flag );
4609 break;
4611 case 0x01: /* SET EXTENDED BREAK STATE */
4612 TRACE("SET CURRENT EXTENDED BREAK STATE\n");
4613 DOSCONF_GetConfig()->brk_flag = (DL_reg(context) > 0) ? 1 : 0;
4614 break;
4616 case 0x02: /* GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE*/
4617 TRACE("GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE\n");
4618 /* ugly coding in order to stay reentrant */
4619 if (DL_reg(context))
4621 SET_DL( context, DOSCONF_GetConfig()->brk_flag );
4622 DOSCONF_GetConfig()->brk_flag = 1;
4624 else
4626 SET_DL( context, DOSCONF_GetConfig()->brk_flag );
4627 DOSCONF_GetConfig()->brk_flag = 0;
4629 break;
4631 case 0x05: /* GET BOOT DRIVE */
4632 TRACE("GET BOOT DRIVE\n");
4633 SET_DL( context, 3 );
4634 /* c: is Wine's bootdrive (a: is 1)*/
4635 break;
4637 case 0x06: /* GET TRUE VERSION NUMBER */
4638 TRACE("GET TRUE VERSION NUMBER\n");
4639 SET_BL( context, HIBYTE(HIWORD(GetVersion16())) ); /* major */
4640 SET_BH( context, LOBYTE(HIWORD(GetVersion16())) ); /* minor */
4641 SET_DL( context, 0x00 ); /* revision */
4642 SET_DH( context, 0x08 ); /* DOS is in ROM */
4643 break;
4645 default:
4646 INT_BARF( context, 0x21 );
4647 break;
4649 break;
4651 case 0x34: /* GET ADDRESS OF INDOS FLAG */
4652 TRACE( "GET ADDRESS OF INDOS FLAG\n" );
4653 context->SegEs = INT21_GetHeapSelector( context );
4654 SET_BX( context, offsetof(INT21_HEAP, misc_indos) );
4655 break;
4657 case 0x35: /* GET INTERRUPT VECTOR */
4658 TRACE("GET INTERRUPT VECTOR 0x%02x\n",AL_reg(context));
4660 FARPROC16 addr;
4661 if (!ISV86(context) && DOSVM_IsWin16())
4662 addr = DOSVM_GetPMHandler16( AL_reg(context) );
4663 else
4664 addr = DOSVM_GetRMHandler( AL_reg(context) );
4665 context->SegEs = SELECTOROF(addr);
4666 SET_BX( context, OFFSETOF(addr) );
4668 break;
4670 case 0x36: /* GET FREE DISK SPACE */
4671 TRACE("GET FREE DISK SPACE FOR DRIVE %s (limited to about 1GB)\n",
4672 INT21_DriveName( DL_reg(context) ));
4673 if (!INT21_GetFreeDiskSpace(context)) SET_AX( context, 0xffff );
4674 break;
4676 case 0x37: /* SWITCHAR */
4678 switch (AL_reg(context))
4680 case 0x00: /* "SWITCHAR" - GET SWITCH CHARACTER */
4681 TRACE( "SWITCHAR - GET SWITCH CHARACTER\n" );
4682 SET_AL( context, 0x00 ); /* success*/
4683 SET_DL( context, '/' );
4684 break;
4685 case 0x01: /*"SWITCHAR" - SET SWITCH CHARACTER*/
4686 FIXME( "SWITCHAR - SET SWITCH CHARACTER: %c\n",
4687 DL_reg( context ));
4688 SET_AL( context, 0x00 ); /* success*/
4689 break;
4690 default:
4691 INT_BARF( context, 0x21 );
4692 break;
4695 break;
4697 case 0x38: /* GET COUNTRY-SPECIFIC INFORMATION */
4698 TRACE( "GET COUNTRY-SPECIFIC INFORMATION\n" );
4699 if (AL_reg(context))
4701 WORD country = AL_reg(context);
4702 if (country == 0xff)
4703 country = BX_reg(context);
4704 if (country != INT21_GetSystemCountryCode()) {
4705 FIXME( "Requested info on non-default country %04x\n", country );
4706 SET_AX(context, 2);
4707 SET_CFLAG(context);
4710 if(AX_reg(context) != 2 )
4712 INT21_FillCountryInformation( CTX_SEG_OFF_TO_LIN(context,
4713 context->SegDs,
4714 context->Edx) );
4715 SET_AX( context, INT21_GetSystemCountryCode() );
4716 SET_BX( context, INT21_GetSystemCountryCode() );
4717 RESET_CFLAG(context);
4719 break;
4721 case 0x39: /* "MKDIR" - CREATE SUBDIRECTORY */
4722 if (!INT21_CreateDirectory( context ))
4723 bSetDOSExtendedError = TRUE;
4724 else
4725 RESET_CFLAG(context);
4726 break;
4728 case 0x3a: /* "RMDIR" - REMOVE DIRECTORY */
4730 WCHAR dirW[MAX_PATH];
4731 char *dirA = CTX_SEG_OFF_TO_LIN(context,
4732 context->SegDs, context->Edx);
4734 TRACE( "REMOVE DIRECTORY %s\n", dirA );
4736 MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH);
4738 if (!RemoveDirectoryW( dirW ))
4739 bSetDOSExtendedError = TRUE;
4740 else
4741 RESET_CFLAG(context);
4743 break;
4745 case 0x3b: /* "CHDIR" - SET CURRENT DIRECTORY */
4746 if (!INT21_SetCurrentDirectory( context ))
4747 bSetDOSExtendedError = TRUE;
4748 else
4749 RESET_CFLAG(context);
4750 break;
4752 case 0x3c: /* "CREAT" - CREATE OR TRUNCATE FILE */
4753 if (!INT21_CreateFile( context, context->Edx, FALSE,
4754 OF_READWRITE | OF_SHARE_COMPAT, 0x12 ))
4755 bSetDOSExtendedError = TRUE;
4756 else
4757 RESET_CFLAG(context);
4758 break;
4760 case 0x3d: /* "OPEN" - OPEN EXISTING FILE */
4761 if (!INT21_CreateFile( context, context->Edx, FALSE,
4762 AL_reg(context), 0x01 ))
4763 bSetDOSExtendedError = TRUE;
4764 else
4765 RESET_CFLAG(context);
4766 break;
4768 case 0x3e: /* "CLOSE" - CLOSE FILE */
4769 TRACE( "CLOSE handle %d\n", BX_reg(context) );
4770 if (_lclose16( BX_reg(context) ) == HFILE_ERROR16)
4771 bSetDOSExtendedError = TRUE;
4772 else
4773 RESET_CFLAG(context);
4774 break;
4776 case 0x3f: /* "READ" - READ FROM FILE OR DEVICE */
4777 TRACE( "READ from %d to %04X:%04X for %d bytes\n",
4778 BX_reg(context),
4779 context->SegDs,
4780 DX_reg(context),
4781 CX_reg(context) );
4783 DWORD result;
4784 WORD count = CX_reg(context);
4785 BYTE *buffer = CTX_SEG_OFF_TO_LIN( context,
4786 context->SegDs,
4787 context->Edx );
4789 /* Some programs pass a count larger than the allocated buffer */
4790 if (DOSVM_IsWin16())
4792 DWORD maxcount = GetSelectorLimit16( context->SegDs )
4793 - DX_reg(context) + 1;
4794 if (count > maxcount)
4795 count = maxcount;
4799 * FIXME: Reading from console (BX=1) in DOS mode
4800 * does not work as it is supposed to work.
4803 RESET_CFLAG(context); /* set if error */
4804 if (!DOSVM_IsWin16() && BX_reg(context) == 0)
4806 result = INT21_BufferedInput( context, buffer, count );
4807 SET_AX( context, (WORD)result );
4809 else if (ReadFile( DosFileHandleToWin32Handle(BX_reg(context)),
4810 buffer, count, &result, NULL ))
4811 SET_AX( context, (WORD)result );
4812 else
4813 bSetDOSExtendedError = TRUE;
4815 break;
4817 case 0x40: /* "WRITE" - WRITE TO FILE OR DEVICE */
4818 TRACE( "WRITE from %04X:%04X to handle %d for %d byte\n",
4819 context->SegDs, DX_reg(context),
4820 BX_reg(context), CX_reg(context) );
4822 char *ptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
4824 if (!DOSVM_IsWin16() &&
4825 (BX_reg(context) == 1 || BX_reg(context) == 2))
4827 int i;
4828 for(i=0; i<CX_reg(context); i++)
4829 DOSVM_PutChar(ptr[i]);
4830 SET_AX(context, CX_reg(context));
4831 RESET_CFLAG(context);
4833 else
4835 HFILE handle = (HFILE)DosFileHandleToWin32Handle(BX_reg(context));
4836 LONG result = _hwrite( handle, ptr, CX_reg(context) );
4837 if (result == HFILE_ERROR)
4838 bSetDOSExtendedError = TRUE;
4839 else
4841 SET_AX( context, (WORD)result );
4842 RESET_CFLAG(context);
4846 break;
4848 case 0x41: /* "UNLINK" - DELETE FILE */
4850 WCHAR fileW[MAX_PATH];
4851 char *fileA = CTX_SEG_OFF_TO_LIN(context,
4852 context->SegDs,
4853 context->Edx);
4855 TRACE( "UNLINK %s\n", fileA );
4856 MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH);
4858 if (!DeleteFileW( fileW ))
4859 bSetDOSExtendedError = TRUE;
4860 else
4861 RESET_CFLAG(context);
4863 break;
4865 case 0x42: /* "LSEEK" - SET CURRENT FILE POSITION */
4866 TRACE( "LSEEK handle %d offset %d from %s\n",
4867 BX_reg(context),
4868 MAKELONG( DX_reg(context), CX_reg(context) ),
4869 (AL_reg(context) == 0) ?
4870 "start of file" : ((AL_reg(context) == 1) ?
4871 "current file position" : "end of file") );
4873 HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
4874 LONG offset = MAKELONG( DX_reg(context), CX_reg(context) );
4875 DWORD status = SetFilePointer( handle, offset,
4876 NULL, AL_reg(context) );
4877 if (status == INVALID_SET_FILE_POINTER)
4878 bSetDOSExtendedError = TRUE;
4879 else
4881 SET_AX( context, LOWORD(status) );
4882 SET_DX( context, HIWORD(status) );
4883 RESET_CFLAG(context);
4886 break;
4888 case 0x43: /* FILE ATTRIBUTES */
4889 if (!INT21_FileAttributes( context, AL_reg(context), FALSE ))
4890 bSetDOSExtendedError = TRUE;
4891 else
4892 RESET_CFLAG(context);
4893 break;
4895 case 0x44: /* IOCTL */
4896 INT21_Ioctl( context );
4897 break;
4899 case 0x45: /* "DUP" - DUPLICATE FILE HANDLE */
4900 TRACE( "DUPLICATE FILE HANDLE %d\n", BX_reg(context) );
4902 HANDLE handle32;
4903 HFILE handle16 = HFILE_ERROR;
4905 if (DuplicateHandle( GetCurrentProcess(),
4906 DosFileHandleToWin32Handle(BX_reg(context)),
4907 GetCurrentProcess(),
4908 &handle32,
4909 0, TRUE, DUPLICATE_SAME_ACCESS ))
4910 handle16 = Win32HandleToDosFileHandle(handle32);
4912 if (handle16 == HFILE_ERROR)
4913 bSetDOSExtendedError = TRUE;
4914 else
4916 SET_AX( context, handle16 );
4917 RESET_CFLAG(context);
4920 break;
4922 case 0x46: /* "DUP2", "FORCEDUP" - FORCE DUPLICATE FILE HANDLE */
4923 TRACE( "FORCEDUP - FORCE DUPLICATE FILE HANDLE %d to %d\n",
4924 BX_reg(context), CX_reg(context) );
4925 if (!INT21_Dup2(BX_reg(context), CX_reg(context)))
4926 bSetDOSExtendedError = TRUE;
4927 else
4928 RESET_CFLAG(context);
4929 break;
4931 case 0x47: /* "CWD" - GET CURRENT DIRECTORY */
4932 if (!INT21_GetCurrentDirectory( context, FALSE ))
4933 bSetDOSExtendedError = TRUE;
4934 else
4935 RESET_CFLAG(context);
4936 break;
4938 case 0x48: /* ALLOCATE MEMORY */
4939 TRACE( "ALLOCATE MEMORY for %d paragraphs\n", BX_reg(context) );
4941 WORD selector = 0;
4942 DWORD bytes = (DWORD)BX_reg(context) << 4;
4944 if (!ISV86(context) && DOSVM_IsWin16())
4946 DWORD rv = GlobalDOSAlloc16( bytes );
4947 selector = LOWORD( rv );
4949 else
4950 DOSMEM_AllocBlock( bytes, &selector );
4952 if (selector)
4954 SET_AX( context, selector );
4955 RESET_CFLAG(context);
4957 else
4959 SET_CFLAG(context);
4960 SET_AX( context, 0x0008 ); /* insufficient memory */
4961 SET_BX( context, DOSMEM_Available() >> 4 );
4964 break;
4966 case 0x49: /* FREE MEMORY */
4967 TRACE( "FREE MEMORY segment %04X\n", context->SegEs );
4969 BOOL ok;
4971 if (!ISV86(context) && DOSVM_IsWin16())
4973 ok = !GlobalDOSFree16( context->SegEs );
4975 /* If we don't reset ES_reg, we will fail in the relay code */
4976 if (ok)
4977 context->SegEs = 0;
4979 else
4980 ok = DOSMEM_FreeBlock( PTR_REAL_TO_LIN(context->SegEs, 0) );
4982 if (!ok)
4984 TRACE("FREE MEMORY failed\n");
4985 SET_CFLAG(context);
4986 SET_AX( context, 0x0009 ); /* memory block address invalid */
4989 break;
4991 case 0x4a: /* RESIZE MEMORY BLOCK */
4992 TRACE( "RESIZE MEMORY segment %04X to %d paragraphs\n",
4993 context->SegEs, BX_reg(context) );
4995 DWORD newsize = (DWORD)BX_reg(context) << 4;
4997 if (!ISV86(context) && DOSVM_IsWin16())
4999 FIXME( "Resize memory block - unsupported under Win16\n" );
5000 SET_CFLAG(context);
5002 else
5004 LPVOID address = (void*)((DWORD)context->SegEs << 4);
5005 UINT blocksize = DOSMEM_ResizeBlock( address, newsize, FALSE );
5007 RESET_CFLAG(context);
5008 if (blocksize == (UINT)-1)
5010 SET_CFLAG( context );
5011 SET_AX( context, 0x0009 ); /* illegal address */
5013 else if(blocksize != newsize)
5015 SET_CFLAG( context );
5016 SET_AX( context, 0x0008 ); /* insufficient memory */
5017 SET_BX( context, blocksize >> 4 ); /* new block size */
5021 break;
5023 case 0x4b: /* "EXEC" - LOAD AND/OR EXECUTE PROGRAM */
5025 char *program = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx);
5026 BYTE *paramblk = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Ebx);
5028 TRACE( "EXEC %s\n", program );
5030 RESET_CFLAG(context);
5031 if (DOSVM_IsWin16())
5033 HINSTANCE16 instance = WinExec16( program, SW_NORMAL );
5034 if (instance < 32)
5036 SET_CFLAG( context );
5037 SET_AX( context, instance );
5040 else
5042 if (!MZ_Exec( context, program, AL_reg(context), paramblk))
5043 bSetDOSExtendedError = TRUE;
5046 break;
5048 case 0x4c: /* "EXIT" - TERMINATE WITH RETURN CODE */
5049 TRACE( "EXIT with return code %d\n", AL_reg(context) );
5050 if (DOSVM_IsWin16())
5051 ExitThread( AL_reg(context) );
5052 else if(ISV86(context))
5053 MZ_Exit( context, FALSE, AL_reg(context) );
5054 else
5057 * Exit from DPMI.
5059 ULONG_PTR rv = AL_reg(context);
5060 RaiseException( EXCEPTION_VM86_INTx, 0, 1, &rv );
5062 break;
5064 case 0x4d: /* GET RETURN CODE */
5065 TRACE("GET RETURN CODE (ERRORLEVEL)\n");
5066 SET_AX( context, DOSVM_retval );
5067 DOSVM_retval = 0;
5068 break;
5070 case 0x4e: /* "FINDFIRST" - FIND FIRST MATCHING FILE */
5071 TRACE("FINDFIRST mask 0x%04x spec %s\n",CX_reg(context),
5072 (LPCSTR)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx));
5073 if (!INT21_FindFirst(context)) break;
5074 /* fall through */
5076 case 0x4f: /* "FINDNEXT" - FIND NEXT MATCHING FILE */
5077 TRACE("FINDNEXT\n");
5078 if (!INT21_FindNext(context))
5080 SetLastError( ERROR_NO_MORE_FILES );
5081 SET_AX( context, ERROR_NO_MORE_FILES );
5082 SET_CFLAG(context);
5084 else SET_AX( context, 0 ); /* OK */
5085 break;
5087 case 0x50: /* SET CURRENT PROCESS ID (SET PSP ADDRESS) */
5088 TRACE("SET CURRENT PROCESS ID (SET PSP ADDRESS)\n");
5089 DOSVM_psp = BX_reg(context);
5090 break;
5092 case 0x51: /* GET PSP ADDRESS */
5093 INT21_GetPSP( context );
5094 break;
5096 case 0x52: /* "SYSVARS" - GET LIST OF LISTS */
5098 SEGPTR ptr = DOSDEV_GetLOL( ISV86(context) || !DOSVM_IsWin16() );
5099 context->SegEs = SELECTOROF(ptr);
5100 SET_BX( context, OFFSETOF(ptr) );
5102 break;
5104 case 0x54: /* Get Verify Flag */
5105 TRACE("Get Verify Flag - Not Supported\n");
5106 SET_AL( context, 0x00 ); /* pretend we can tell. 00h = off 01h = on */
5107 break;
5109 case 0x56: /* "RENAME" - RENAME FILE */
5110 if (!INT21_RenameFile( context ))
5111 bSetDOSExtendedError = TRUE;
5112 else
5113 RESET_CFLAG(context);
5114 break;
5116 case 0x57: /* FILE DATE AND TIME */
5117 if (!INT21_FileDateTime( context ))
5118 bSetDOSExtendedError = TRUE;
5119 else
5120 RESET_CFLAG(context);
5121 break;
5123 case 0x58: /* GET OR SET MEMORY ALLOCATION STRATEGY */
5124 switch (AL_reg(context))
5126 case 0x00: /* GET MEMORY ALLOCATION STRATEGY */
5127 TRACE( "GET MEMORY ALLOCATION STRATEGY\n" );
5128 SET_AX( context, 0 ); /* low memory first fit */
5129 break;
5131 case 0x01: /* SET ALLOCATION STRATEGY */
5132 TRACE( "SET MEMORY ALLOCATION STRATEGY to %d - ignored\n",
5133 BL_reg(context) );
5134 break;
5136 case 0x02: /* GET UMB LINK STATE */
5137 TRACE( "GET UMB LINK STATE\n" );
5138 SET_AL( context, 0 ); /* UMBs not part of DOS memory chain */
5139 break;
5141 case 0x03: /* SET UMB LINK STATE */
5142 TRACE( "SET UMB LINK STATE to %d - ignored\n",
5143 BX_reg(context) );
5144 break;
5146 default:
5147 INT_BARF( context, 0x21 );
5149 break;
5151 case 0x59: /* GET EXTENDED ERROR INFO */
5152 INT21_GetExtendedError( context );
5153 break;
5155 case 0x5a: /* CREATE TEMPORARY FILE */
5156 TRACE("CREATE TEMPORARY FILE\n");
5157 bSetDOSExtendedError = !INT21_CreateTempFile(context);
5158 break;
5160 case 0x5b: /* CREATE NEW FILE */
5161 if (!INT21_CreateFile( context, context->Edx, FALSE,
5162 OF_READWRITE | OF_SHARE_COMPAT, 0x10 ))
5163 bSetDOSExtendedError = TRUE;
5164 else
5165 RESET_CFLAG(context);
5166 break;
5168 case 0x5c: /* "FLOCK" - RECORD LOCKING */
5170 DWORD offset = MAKELONG(DX_reg(context), CX_reg(context));
5171 DWORD length = MAKELONG(DI_reg(context), SI_reg(context));
5172 HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context));
5174 RESET_CFLAG(context);
5175 switch (AL_reg(context))
5177 case 0x00: /* LOCK */
5178 TRACE( "lock handle %d offset %d length %d\n",
5179 BX_reg(context), offset, length );
5180 if (!LockFile( handle, offset, 0, length, 0 ))
5181 bSetDOSExtendedError = TRUE;
5182 break;
5184 case 0x01: /* UNLOCK */
5185 TRACE( "unlock handle %d offset %d length %d\n",
5186 BX_reg(context), offset, length );
5187 if (!UnlockFile( handle, offset, 0, length, 0 ))
5188 bSetDOSExtendedError = TRUE;
5189 break;
5191 default:
5192 INT_BARF( context, 0x21 );
5195 break;
5197 case 0x5d: /* NETWORK 5D */
5198 FIXME( "Network function 5D not implemented.\n" );
5199 SetLastError( ER_NoNetwork );
5200 bSetDOSExtendedError = TRUE;
5201 break;
5203 case 0x5e: /* NETWORK 5E */
5204 bSetDOSExtendedError = INT21_NetworkFunc( context);
5205 break;
5207 case 0x5f: /* NETWORK 5F */
5208 /* FIXME: supporting this would need to 1:
5209 * - implement per drive current directory (as kernel32 doesn't)
5210 * - assign enabled/disabled flag on a per drive basis
5212 /* network software not installed */
5213 TRACE("NETWORK function AX=%04x not implemented\n",AX_reg(context));
5214 SetLastError( ER_NoNetwork );
5215 bSetDOSExtendedError = TRUE;
5216 break;
5218 case 0x60: /* "TRUENAME" - CANONICALIZE FILENAME OR PATH */
5220 WCHAR pathW[MAX_PATH], res[MAX_PATH];
5221 /* FIXME: likely to be broken */
5223 TRACE("TRUENAME %s\n",
5224 (LPCSTR)CTX_SEG_OFF_TO_LIN(context, context->SegDs,context->Esi));
5225 MultiByteToWideChar(CP_OEMCP, 0, CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi), -1, pathW, MAX_PATH);
5226 if (!GetFullPathNameW( pathW, 128, res, NULL ))
5227 bSetDOSExtendedError = TRUE;
5228 else
5230 SET_AX( context, 0 );
5231 WideCharToMultiByte(CP_OEMCP, 0, res, -1,
5232 CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi),
5233 128, NULL, NULL);
5236 break;
5238 case 0x61: /* UNUSED */
5239 SET_AL( context, 0 );
5240 break;
5242 case 0x62: /* GET PSP ADDRESS */
5243 INT21_GetPSP( context );
5244 break;
5246 case 0x63: /* MISC. LANGUAGE SUPPORT */
5247 switch (AL_reg(context)) {
5248 case 0x00: /* GET DOUBLE BYTE CHARACTER SET LEAD-BYTE TABLE */
5249 TRACE( "GET DOUBLE BYTE CHARACTER SET LEAD-BYTE TABLE\n" );
5250 context->SegDs = INT21_GetHeapSelector( context );
5251 SET_SI( context, offsetof(INT21_HEAP, dbcs_table) );
5252 SET_AL( context, 0 ); /* success */
5253 break;
5255 break;
5257 case 0x64: /* OS/2 DOS BOX */
5258 INT_BARF( context, 0x21 );
5259 SET_CFLAG(context);
5260 break;
5262 case 0x65: /* EXTENDED COUNTRY INFORMATION */
5263 INT21_ExtendedCountryInformation( context );
5264 break;
5266 case 0x66: /* GLOBAL CODE PAGE TABLE */
5267 switch (AL_reg(context))
5269 case 0x01:
5270 TRACE( "GET GLOBAL CODE PAGE TABLE\n" );
5271 SET_BX( context, GetOEMCP() );
5272 SET_DX( context, GetOEMCP() );
5273 break;
5274 case 0x02:
5275 FIXME( "SET GLOBAL CODE PAGE TABLE, active %d, system %d - ignored\n",
5276 BX_reg(context), DX_reg(context) );
5277 break;
5279 break;
5281 case 0x67: /* SET HANDLE COUNT */
5282 TRACE( "SET HANDLE COUNT to %d\n", BX_reg(context) );
5283 if (SetHandleCount( BX_reg(context) ) < BX_reg(context) )
5284 bSetDOSExtendedError = TRUE;
5285 break;
5287 case 0x68: /* "FFLUSH" - COMMIT FILE */
5288 TRACE( "FFLUSH - handle %d\n", BX_reg(context) );
5289 if (!FlushFileBuffers( DosFileHandleToWin32Handle(BX_reg(context)) ))
5290 bSetDOSExtendedError = TRUE;
5291 break;
5293 case 0x69: /* DISK SERIAL NUMBER */
5294 switch (AL_reg(context))
5296 case 0x00:
5297 TRACE("GET DISK SERIAL NUMBER for drive %s\n",
5298 INT21_DriveName(BL_reg(context)));
5299 if (!INT21_GetDiskSerialNumber(context)) bSetDOSExtendedError = TRUE;
5300 else SET_AX( context, 0 );
5301 break;
5303 case 0x01:
5304 TRACE("SET DISK SERIAL NUMBER for drive %s\n",
5305 INT21_DriveName(BL_reg(context)));
5306 if (!INT21_SetDiskSerialNumber(context)) bSetDOSExtendedError = TRUE;
5307 else SET_AX( context, 1 );
5308 break;
5310 break;
5312 case 0x6a: /* COMMIT FILE */
5313 TRACE( "COMMIT FILE - handle %d\n", BX_reg(context) );
5314 if (!FlushFileBuffers( DosFileHandleToWin32Handle(BX_reg(context)) ))
5315 bSetDOSExtendedError = TRUE;
5316 break;
5318 case 0x6b: /* NULL FUNCTION FOR CP/M COMPATIBILITY */
5319 SET_AL( context, 0 );
5320 break;
5322 case 0x6c: /* EXTENDED OPEN/CREATE */
5323 if (!INT21_CreateFile( context, context->Esi, TRUE,
5324 BX_reg(context), DL_reg(context) ))
5325 bSetDOSExtendedError = TRUE;
5326 break;
5328 case 0x70: /* MSDOS 7 - GET/SET INTERNATIONALIZATION INFORMATION */
5329 FIXME( "MS-DOS 7 - GET/SET INTERNATIONALIZATION INFORMATION\n" );
5330 SET_CFLAG( context );
5331 SET_AL( context, 0 );
5332 break;
5334 case 0x71: /* MSDOS 7 - LONG FILENAME FUNCTIONS */
5335 INT21_LongFilename( context );
5336 break;
5338 case 0x73: /* MSDOS7 - FAT32 */
5339 RESET_CFLAG( context );
5340 if (!INT21_Fat32( context ))
5341 bSetDOSExtendedError = TRUE;
5342 break;
5344 case 0xdc: /* CONNECTION SERVICES - GET CONNECTION NUMBER */
5345 TRACE( "CONNECTION SERVICES - GET CONNECTION NUMBER - ignored\n" );
5346 break;
5348 case 0xea: /* NOVELL NETWARE - RETURN SHELL VERSION */
5349 TRACE( "NOVELL NETWARE - RETURN SHELL VERSION - ignored\n" );
5350 break;
5352 case 0xff: /* DOS32 EXTENDER (DOS/4GW) - API */
5353 /* we don't implement a DOS32 extender */
5354 TRACE( "DOS32 EXTENDER API - ignored\n" );
5355 break;
5357 default:
5358 INT_BARF( context, 0x21 );
5359 break;
5361 } /* END OF SWITCH */
5363 /* Set general error condition. */
5364 if (bSetDOSExtendedError)
5366 SET_AX( context, GetLastError() );
5367 SET_CFLAG( context );
5370 /* Print error code if carry flag is set. */
5371 if (context->EFlags & 0x0001)
5372 TRACE("failed, error %d\n", GetLastError() );
5374 TRACE( "returning: AX=%04x BX=%04x CX=%04x DX=%04x "
5375 "SI=%04x DI=%04x DS=%04x ES=%04x EFL=%08x\n",
5376 AX_reg(context), BX_reg(context),
5377 CX_reg(context), DX_reg(context),
5378 SI_reg(context), DI_reg(context),
5379 (WORD)context->SegDs, (WORD)context->SegEs,
5380 context->EFlags );