GetLongPathName rewrite.
[wine.git] / files / drive.c
blobbbe68dab4a7f6b66060d96f753af4063b4d79d25
1 /*
2 * DOS drives handling functions
4 * Copyright 1993 Erik Bos
5 * Copyright 1996 Alexandre Julliard
7 * Label & serial number read support.
8 * (c) 1999 Petr Tomasek <tomasek@etf.cuni.cz>
9 * (c) 2000 Andreas Mohr (changes)
13 #include "config.h"
15 #include <assert.h>
16 #include <ctype.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <unistd.h>
25 #ifdef HAVE_SYS_PARAM_H
26 # include <sys/param.h>
27 #endif
28 #ifdef STATFS_DEFINED_BY_SYS_VFS
29 # include <sys/vfs.h>
30 #else
31 # ifdef STATFS_DEFINED_BY_SYS_MOUNT
32 # include <sys/mount.h>
33 # else
34 # ifdef STATFS_DEFINED_BY_SYS_STATFS
35 # include <sys/statfs.h>
36 # endif
37 # endif
38 #endif
40 #include "winbase.h"
41 #include "wine/winbase16.h" /* for GetCurrentTask */
42 #include "wine/winestring.h" /* for lstrcpyAtoW */
43 #include "winerror.h"
44 #include "drive.h"
45 #include "cdrom.h"
46 #include "file.h"
47 #include "heap.h"
48 #include "msdos.h"
49 #include "options.h"
50 #include "wine/port.h"
51 #include "task.h"
52 #include "debugtools.h"
54 DEFAULT_DEBUG_CHANNEL(dosfs)
55 DECLARE_DEBUG_CHANNEL(file)
57 typedef struct
59 char *root; /* root dir in Unix format without trailing / */
60 char *dos_cwd; /* cwd in DOS format without leading or trailing \ */
61 char *unix_cwd; /* cwd in Unix format without leading or trailing / */
62 char *device; /* raw device path */
63 BOOL read_volinfo; /* read the volume info from the device ? */
64 char label_conf[12]; /* drive label as cfg'd in wine.conf */
65 char label_read[12]; /* drive label as read from device */
66 DWORD serial_conf; /* drive serial number as cfg'd in wine.conf */
67 DRIVETYPE type; /* drive type */
68 UINT flags; /* drive flags */
69 dev_t dev; /* unix device number */
70 ino_t ino; /* unix inode number */
71 } DOSDRIVE;
74 static const char * const DRIVE_Types[] =
76 "floppy", /* TYPE_FLOPPY */
77 "hd", /* TYPE_HD */
78 "cdrom", /* TYPE_CDROM */
79 "network" /* TYPE_NETWORK */
83 /* Known filesystem types */
85 typedef struct
87 const char *name;
88 UINT flags;
89 } FS_DESCR;
91 static const FS_DESCR DRIVE_Filesystems[] =
93 { "unix", DRIVE_CASE_SENSITIVE | DRIVE_CASE_PRESERVING },
94 { "msdos", DRIVE_SHORT_NAMES },
95 { "dos", DRIVE_SHORT_NAMES },
96 { "fat", DRIVE_SHORT_NAMES },
97 { "vfat", DRIVE_CASE_PRESERVING },
98 { "win95", DRIVE_CASE_PRESERVING },
99 { NULL, 0 }
103 static DOSDRIVE DOSDrives[MAX_DOS_DRIVES];
104 static int DRIVE_CurDrive = -1;
106 static HTASK16 DRIVE_LastTask = 0;
109 /***********************************************************************
110 * DRIVE_GetDriveType
112 static DRIVETYPE DRIVE_GetDriveType( const char *name )
114 char buffer[20];
115 int i;
117 PROFILE_GetWineIniString( name, "Type", "hd", buffer, sizeof(buffer) );
118 for (i = 0; i < sizeof(DRIVE_Types)/sizeof(DRIVE_Types[0]); i++)
120 if (!strcasecmp( buffer, DRIVE_Types[i] )) return (DRIVETYPE)i;
122 MESSAGE("%s: unknown drive type '%s', defaulting to 'hd'.\n",
123 name, buffer );
124 return TYPE_HD;
128 /***********************************************************************
129 * DRIVE_GetFSFlags
131 static UINT DRIVE_GetFSFlags( const char *name, const char *value )
133 const FS_DESCR *descr;
135 for (descr = DRIVE_Filesystems; descr->name; descr++)
136 if (!strcasecmp( value, descr->name )) return descr->flags;
137 MESSAGE("%s: unknown filesystem type '%s', defaulting to 'win95'.\n",
138 name, value );
139 return DRIVE_CASE_PRESERVING;
143 /***********************************************************************
144 * DRIVE_Init
146 int DRIVE_Init(void)
148 int i, len, count = 0;
149 char name[] = "Drive A";
150 char path[MAX_PATHNAME_LEN];
151 char buffer[80];
152 struct stat drive_stat_buffer;
153 char *p;
154 DOSDRIVE *drive;
156 for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, name[6]++, drive++)
158 PROFILE_GetWineIniString( name, "Path", "", path, sizeof(path)-1 );
159 if (path[0])
161 p = path + strlen(path) - 1;
162 while ((p > path) && ((*p == '/') || (*p == '\\'))) *p-- = '\0';
163 if (!path[0]) strcpy( path, "/" );
165 if (stat( path, &drive_stat_buffer ))
167 MESSAGE("Could not stat %s, ignoring drive %c:\n", path, 'A' + i );
168 continue;
170 if (!S_ISDIR(drive_stat_buffer.st_mode))
172 MESSAGE("%s is not a directory, ignoring drive %c:\n",
173 path, 'A' + i );
174 continue;
177 drive->root = HEAP_strdupA( GetProcessHeap(), 0, path );
178 drive->dos_cwd = HEAP_strdupA( GetProcessHeap(), 0, "" );
179 drive->unix_cwd = HEAP_strdupA( GetProcessHeap(), 0, "" );
180 drive->type = DRIVE_GetDriveType( name );
181 drive->device = NULL;
182 drive->flags = 0;
183 drive->dev = drive_stat_buffer.st_dev;
184 drive->ino = drive_stat_buffer.st_ino;
186 /* Get the drive label */
187 PROFILE_GetWineIniString( name, "Label", name, drive->label_conf, 12 );
188 if ((len = strlen(drive->label_conf)) < 11)
190 /* Pad label with spaces */
191 memset( drive->label_conf + len, ' ', 11 - len );
192 drive->label_conf[11] = '\0';
195 /* Get the serial number */
196 PROFILE_GetWineIniString( name, "Serial", "12345678",
197 buffer, sizeof(buffer) );
198 drive->serial_conf = strtoul( buffer, NULL, 16 );
200 /* Get the filesystem type */
201 PROFILE_GetWineIniString( name, "Filesystem", "win95",
202 buffer, sizeof(buffer) );
203 drive->flags = DRIVE_GetFSFlags( name, buffer );
205 /* Get the device */
206 PROFILE_GetWineIniString( name, "Device", "",
207 buffer, sizeof(buffer) );
208 if (buffer[0])
210 drive->device = HEAP_strdupA( GetProcessHeap(), 0, buffer );
211 drive->read_volinfo =
212 (BOOL)PROFILE_GetWineIniInt( name, "ReadVolInfo", 1);
214 else
215 drive->read_volinfo = FALSE;
217 /* Make the first hard disk the current drive */
218 if ((DRIVE_CurDrive == -1) && (drive->type == TYPE_HD))
219 DRIVE_CurDrive = i;
221 count++;
222 TRACE("%s: path=%s type=%s label='%s' serial=%08lx "
223 "flags=%08x dev=%x ino=%x\n",
224 name, path, DRIVE_Types[drive->type],
225 drive->label_conf, drive->serial_conf, drive->flags,
226 (int)drive->dev, (int)drive->ino );
228 else WARN("%s: not defined\n", name );
231 if (!count)
233 MESSAGE("Warning: no valid DOS drive found, check your configuration file.\n" );
234 /* Create a C drive pointing to Unix root dir */
235 DOSDrives[2].root = HEAP_strdupA( GetProcessHeap(), 0, "/" );
236 DOSDrives[2].dos_cwd = HEAP_strdupA( GetProcessHeap(), 0, "" );
237 DOSDrives[2].unix_cwd = HEAP_strdupA( GetProcessHeap(), 0, "" );
238 strcpy( DOSDrives[2].label_conf, "Drive C " );
239 DOSDrives[2].serial_conf = 12345678;
240 DOSDrives[2].type = TYPE_HD;
241 DOSDrives[2].flags = 0;
242 DRIVE_CurDrive = 2;
245 /* Make sure the current drive is valid */
246 if (DRIVE_CurDrive == -1)
248 for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
250 if (drive->root && !(drive->flags & DRIVE_DISABLED))
252 DRIVE_CurDrive = i;
253 break;
258 return 1;
262 /***********************************************************************
263 * DRIVE_IsValid
265 int DRIVE_IsValid( int drive )
267 if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
268 return (DOSDrives[drive].root &&
269 !(DOSDrives[drive].flags & DRIVE_DISABLED));
273 /***********************************************************************
274 * DRIVE_GetCurrentDrive
276 int DRIVE_GetCurrentDrive(void)
278 TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
279 if (pTask && (pTask->curdrive & 0x80)) return pTask->curdrive & ~0x80;
280 return DRIVE_CurDrive;
284 /***********************************************************************
285 * DRIVE_SetCurrentDrive
287 int DRIVE_SetCurrentDrive( int drive )
289 TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
290 if (!DRIVE_IsValid( drive ))
292 SetLastError( ERROR_INVALID_DRIVE );
293 return 0;
295 TRACE("%c:\n", 'A' + drive );
296 DRIVE_CurDrive = drive;
297 if (pTask) pTask->curdrive = drive | 0x80;
298 return 1;
302 /***********************************************************************
303 * DRIVE_FindDriveRoot
305 * Find a drive for which the root matches the beginning of the given path.
306 * This can be used to translate a Unix path into a drive + DOS path.
307 * Return value is the drive, or -1 on error. On success, path is modified
308 * to point to the beginning of the DOS path.
310 int DRIVE_FindDriveRoot( const char **path )
312 /* idea: check at all '/' positions.
313 * If the device and inode of that path is identical with the
314 * device and inode of the current drive then we found a solution.
315 * If there is another drive pointing to a deeper position in
316 * the file tree, we want to find that one, not the earlier solution.
318 int drive, rootdrive = -1;
319 char buffer[MAX_PATHNAME_LEN];
320 char *next = buffer;
321 const char *p = *path;
322 struct stat st;
324 strcpy( buffer, "/" );
325 for (;;)
327 if (stat( buffer, &st ) || !S_ISDIR( st.st_mode )) break;
329 /* Find the drive */
331 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
333 if (!DOSDrives[drive].root ||
334 (DOSDrives[drive].flags & DRIVE_DISABLED)) continue;
336 if ((DOSDrives[drive].dev == st.st_dev) &&
337 (DOSDrives[drive].ino == st.st_ino))
339 rootdrive = drive;
340 *path = p;
341 break;
345 /* Get the next path component */
347 *next++ = '/';
348 while ((*p == '/') || (*p == '\\')) p++;
349 if (!*p) break;
350 while (!IS_END_OF_NAME(*p)) *next++ = *p++;
351 *next = 0;
353 *next = 0;
355 if (rootdrive != -1)
356 TRACE("%s -> drive %c:, root='%s', name='%s'\n",
357 buffer, 'A' + rootdrive, DOSDrives[rootdrive].root, *path );
358 return rootdrive;
362 /***********************************************************************
363 * DRIVE_GetRoot
365 const char * DRIVE_GetRoot( int drive )
367 if (!DRIVE_IsValid( drive )) return NULL;
368 return DOSDrives[drive].root;
372 /***********************************************************************
373 * DRIVE_GetDosCwd
375 const char * DRIVE_GetDosCwd( int drive )
377 TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
378 if (!DRIVE_IsValid( drive )) return NULL;
380 /* Check if we need to change the directory to the new task. */
381 if (pTask && (pTask->curdrive & 0x80) && /* The task drive is valid */
382 ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
383 (DRIVE_LastTask != GetCurrentTask())) /* and the task changed */
385 /* Perform the task-switch */
386 if (!DRIVE_Chdir( drive, pTask->curdir )) DRIVE_Chdir( drive, "\\" );
387 DRIVE_LastTask = GetCurrentTask();
389 return DOSDrives[drive].dos_cwd;
393 /***********************************************************************
394 * DRIVE_GetUnixCwd
396 const char * DRIVE_GetUnixCwd( int drive )
398 TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
399 if (!DRIVE_IsValid( drive )) return NULL;
401 /* Check if we need to change the directory to the new task. */
402 if (pTask && (pTask->curdrive & 0x80) && /* The task drive is valid */
403 ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
404 (DRIVE_LastTask != GetCurrentTask())) /* and the task changed */
406 /* Perform the task-switch */
407 if (!DRIVE_Chdir( drive, pTask->curdir )) DRIVE_Chdir( drive, "\\" );
408 DRIVE_LastTask = GetCurrentTask();
410 return DOSDrives[drive].unix_cwd;
414 /***********************************************************************
415 * DRIVE_GetDevice
417 const char * DRIVE_GetDevice( int drive )
419 return (DRIVE_IsValid( drive )) ? DOSDrives[drive].device : NULL;
423 /***********************************************************************
424 * DRIVE_ReadSuperblock
426 * Used in DRIVE_GetLabel
428 int DRIVE_ReadSuperblock (int drive, char * buff)
430 #define DRIVE_SUPER 96
431 int fd;
432 off_t offs;
434 if (memset(buff,0,DRIVE_SUPER)!=buff) return -1;
435 if ((fd=open(DOSDrives[drive].device,O_RDONLY)) == -1)
437 struct stat st;
438 if (!DOSDrives[drive].device)
439 ERR("No device configured for drive %c: !\n", 'A'+drive);
440 else
441 ERR("Couldn't open device '%s' for drive %c: ! (%s)\n", DOSDrives[drive].device, 'A'+drive,
442 (stat(DOSDrives[drive].device, &st)) ?
443 "not available or symlink not valid ?" : "no permission");
444 ERR("Can't read drive volume info ! Either pre-set it or make sure the device to read it from is accessible !\n");
445 PROFILE_UsageWineIni();
446 return -1;
449 switch(DOSDrives[drive].type)
451 case TYPE_FLOPPY:
452 case TYPE_HD:
453 offs = 0;
454 break;
455 case TYPE_CDROM:
456 /* FIXME: Maybe we should search for the first data track on the CD,
457 not just assume that it is the first track */
458 offs = (off_t)2048*(16+0);
459 break;
460 default:
461 offs = 0;
462 break;
465 if ((offs) && (lseek(fd,offs,SEEK_SET)!=offs)) return -4;
466 if (read(fd,buff,DRIVE_SUPER)!=DRIVE_SUPER) return -2;
468 switch(DOSDrives[drive].type)
470 case TYPE_FLOPPY:
471 case TYPE_HD:
472 if (buff[0x26]!=0x29) /* Check for FAT present */
473 return -3;
474 break;
475 case TYPE_CDROM:
476 if (strncmp(&buff[1],"CD001",5)) /* Check for iso9660 present */
477 return -3;
478 /* FIXME: do we need to check for "CDROM", too ? (high sierra) */
479 break;
480 default:
481 return -3;
482 break;
485 return close(fd);
489 /***********************************************************************
490 * DRIVE_GetLabel
492 const char * DRIVE_GetLabel( int drive )
494 int read = 0;
495 char buff[DRIVE_SUPER];
496 int offs = -1;
498 if (!DRIVE_IsValid( drive )) return NULL;
499 if (DRIVE_GetType(drive) == TYPE_CDROM)
501 WINE_CDAUDIO wcda;
503 if (!(CDROM_Open(&wcda, drive)))
505 int media = CDROM_GetMediaType(&wcda);
507 if (media == CDS_AUDIO)
509 strcpy(DOSDrives[drive].label_read, "Audio CD ");
510 read = 1;
512 else
513 if (media == CDS_NO_INFO)
515 strcpy(DOSDrives[drive].label_read, " ");
516 read = 1;
519 CDROM_Close(&wcda);
522 if ((!read) && (DOSDrives[drive].read_volinfo))
524 if (DRIVE_ReadSuperblock(drive,(char *) buff))
525 ERR("Invalid or unreadable superblock on %s (%c:).\n",
526 DOSDrives[drive].device, (char)(drive+'A'));
527 else {
528 if (DOSDrives[drive].type == TYPE_CDROM)
529 offs = 40;
530 else
531 if (DOSDrives[drive].type == TYPE_FLOPPY ||
532 DOSDrives[drive].type == TYPE_HD)
533 offs = 0x2b;
535 /* FIXME: ISO9660 uses 32-bytes long label. Should we do also? */
536 if (offs != -1) memcpy(DOSDrives[drive].label_read,buff+offs,11);
537 DOSDrives[drive].label_read[11]='\0';
538 read = 1;
542 return (read) ?
543 DOSDrives[drive].label_read : DOSDrives[drive].label_conf;
547 /***********************************************************************
548 * DRIVE_GetSerialNumber
550 DWORD DRIVE_GetSerialNumber( int drive )
552 DWORD serial = 0;
553 char buff[DRIVE_SUPER];
555 if (!DRIVE_IsValid( drive )) return 0;
557 if (DOSDrives[drive].read_volinfo)
559 switch(DOSDrives[drive].type)
561 case TYPE_FLOPPY:
562 case TYPE_HD:
563 if (DRIVE_ReadSuperblock(drive,(char *) buff))
564 MESSAGE("Invalid or unreadable superblock on %s (%c:)."
565 " Maybe not FAT?\n" ,
566 DOSDrives[drive].device, 'A'+drive);
567 else
568 serial = *((DWORD*)(buff+0x27));
569 break;
570 case TYPE_CDROM:
571 serial = CDROM_GetSerial(drive);
572 break;
573 default:
574 FIXME("Serial number reading from file system on drive %c: not supported yet.\n", drive+'A');
578 return (serial) ? serial : DOSDrives[drive].serial_conf;
582 /***********************************************************************
583 * DRIVE_SetSerialNumber
585 int DRIVE_SetSerialNumber( int drive, DWORD serial )
587 if (!DRIVE_IsValid( drive )) return 0;
588 if ((DOSDrives[drive].read_volinfo) &&
589 (DOSDrives[drive].type != TYPE_CDROM))
590 FIXME("Setting the serial number is useless for drive %c: until writing it is properly implemented, as this drive reads it from the device.\n", 'A'+drive);
591 DOSDrives[drive].serial_conf = serial;
592 return 1;
596 /***********************************************************************
597 * DRIVE_GetType
599 DRIVETYPE DRIVE_GetType( int drive )
601 if (!DRIVE_IsValid( drive )) return TYPE_INVALID;
602 return DOSDrives[drive].type;
606 /***********************************************************************
607 * DRIVE_GetFlags
609 UINT DRIVE_GetFlags( int drive )
611 if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
612 return DOSDrives[drive].flags;
616 /***********************************************************************
617 * DRIVE_Chdir
619 int DRIVE_Chdir( int drive, const char *path )
621 DOS_FULL_NAME full_name;
622 char buffer[MAX_PATHNAME_LEN];
623 LPSTR unix_cwd;
624 BY_HANDLE_FILE_INFORMATION info;
625 TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
627 strcpy( buffer, "A:" );
628 buffer[0] += drive;
629 TRACE("(%c:,%s)\n", buffer[0], path );
630 lstrcpynA( buffer + 2, path, sizeof(buffer) - 2 );
632 if (!DOSFS_GetFullName( buffer, TRUE, &full_name )) return 0;
633 if (!FILE_Stat( full_name.long_name, &info )) return 0;
634 if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
636 SetLastError( ERROR_FILE_NOT_FOUND );
637 return 0;
639 unix_cwd = full_name.long_name + strlen( DOSDrives[drive].root );
640 while (*unix_cwd == '/') unix_cwd++;
642 TRACE("(%c:): unix_cwd=%s dos_cwd=%s\n",
643 'A' + drive, unix_cwd, full_name.short_name + 3 );
645 HeapFree( GetProcessHeap(), 0, DOSDrives[drive].dos_cwd );
646 HeapFree( GetProcessHeap(), 0, DOSDrives[drive].unix_cwd );
647 DOSDrives[drive].dos_cwd = HEAP_strdupA( GetProcessHeap(), 0,
648 full_name.short_name + 3 );
649 DOSDrives[drive].unix_cwd = HEAP_strdupA( GetProcessHeap(), 0, unix_cwd );
651 if (pTask && (pTask->curdrive & 0x80) &&
652 ((pTask->curdrive & ~0x80) == drive))
654 lstrcpynA( pTask->curdir, full_name.short_name + 2,
655 sizeof(pTask->curdir) );
656 DRIVE_LastTask = GetCurrentTask();
658 return 1;
662 /***********************************************************************
663 * DRIVE_Disable
665 int DRIVE_Disable( int drive )
667 if ((drive < 0) || (drive >= MAX_DOS_DRIVES) || !DOSDrives[drive].root)
669 SetLastError( ERROR_INVALID_DRIVE );
670 return 0;
672 DOSDrives[drive].flags |= DRIVE_DISABLED;
673 return 1;
677 /***********************************************************************
678 * DRIVE_Enable
680 int DRIVE_Enable( int drive )
682 if ((drive < 0) || (drive >= MAX_DOS_DRIVES) || !DOSDrives[drive].root)
684 SetLastError( ERROR_INVALID_DRIVE );
685 return 0;
687 DOSDrives[drive].flags &= ~DRIVE_DISABLED;
688 return 1;
692 /***********************************************************************
693 * DRIVE_SetLogicalMapping
695 int DRIVE_SetLogicalMapping ( int existing_drive, int new_drive )
697 /* If new_drive is already valid, do nothing and return 0
698 otherwise, copy DOSDrives[existing_drive] to DOSDrives[new_drive] */
700 DOSDRIVE *old, *new;
702 old = DOSDrives + existing_drive;
703 new = DOSDrives + new_drive;
705 if ((existing_drive < 0) || (existing_drive >= MAX_DOS_DRIVES) ||
706 !old->root ||
707 (new_drive < 0) || (new_drive >= MAX_DOS_DRIVES))
709 SetLastError( ERROR_INVALID_DRIVE );
710 return 0;
713 if ( new->root )
715 TRACE("Can\'t map drive %c to drive %c - drive %c already exists\n",
716 'A' + existing_drive, 'A' + new_drive, 'A' + new_drive );
717 /* it is already mapped there, so return success */
718 if (!strcmp(old->root,new->root))
719 return 1;
720 return 0;
723 new->root = HEAP_strdupA( GetProcessHeap(), 0, old->root );
724 new->dos_cwd = HEAP_strdupA( GetProcessHeap(), 0, old->dos_cwd );
725 new->unix_cwd = HEAP_strdupA( GetProcessHeap(), 0, old->unix_cwd );
726 memcpy ( new->label_conf, old->label_conf, 12 );
727 new->serial_conf = old->serial_conf;
728 new->type = old->type;
729 new->flags = old->flags;
730 new->dev = old->dev;
731 new->ino = old->ino;
733 TRACE("Drive %c is now equal to drive %c\n",
734 'A' + new_drive, 'A' + existing_drive );
736 return 1;
740 /***********************************************************************
741 * DRIVE_OpenDevice
743 * Open the drive raw device and return a Unix fd (or -1 on error).
745 int DRIVE_OpenDevice( int drive, int flags )
747 if (!DRIVE_IsValid( drive )) return -1;
748 return open( DOSDrives[drive].device, flags );
752 /***********************************************************************
753 * DRIVE_RawRead
755 * Read raw sectors from a device
757 int DRIVE_RawRead(BYTE drive, DWORD begin, DWORD nr_sect, BYTE *dataptr, BOOL fake_success)
759 int fd;
761 if ((fd = DRIVE_OpenDevice( drive, O_RDONLY )) != -1)
763 lseek( fd, begin * 512, SEEK_SET );
764 /* FIXME: check errors */
765 read( fd, dataptr, nr_sect * 512 );
766 close( fd );
768 else
770 memset(dataptr, 0, nr_sect * 512);
771 if (fake_success)
773 if (begin == 0 && nr_sect > 1) *(dataptr + 512) = 0xf8;
774 if (begin == 1) *dataptr = 0xf8;
776 else
777 return 0;
779 return 1;
783 /***********************************************************************
784 * DRIVE_RawWrite
786 * Write raw sectors to a device
788 int DRIVE_RawWrite(BYTE drive, DWORD begin, DWORD nr_sect, BYTE *dataptr, BOOL fake_success)
790 int fd;
792 if ((fd = DRIVE_OpenDevice( drive, O_RDONLY )) != -1)
794 lseek( fd, begin * 512, SEEK_SET );
795 /* FIXME: check errors */
796 write( fd, dataptr, nr_sect * 512 );
797 close( fd );
799 else
800 if (!(fake_success))
801 return 0;
803 return 1;
807 /***********************************************************************
808 * DRIVE_GetFreeSpace
810 static int DRIVE_GetFreeSpace( int drive, PULARGE_INTEGER size,
811 PULARGE_INTEGER available )
813 struct statfs info;
814 unsigned long long bigsize,bigavail=0;
816 if (!DRIVE_IsValid(drive))
818 SetLastError( ERROR_INVALID_DRIVE );
819 return 0;
822 /* FIXME: add autoconf check for this */
823 #if defined(__svr4__) || defined(_SCO_DS) || defined(__sun)
824 if (statfs( DOSDrives[drive].root, &info, 0, 0) < 0)
825 #else
826 if (statfs( DOSDrives[drive].root, &info) < 0)
827 #endif
829 FILE_SetDosError();
830 WARN("cannot do statfs(%s)\n", DOSDrives[drive].root);
831 return 0;
834 bigsize = (unsigned long long)info.f_bsize
835 * (unsigned long long)info.f_blocks;
836 #ifdef STATFS_HAS_BAVAIL
837 bigavail = (unsigned long long)info.f_bavail
838 * (unsigned long long)info.f_bsize;
839 #else
840 # ifdef STATFS_HAS_BFREE
841 bigavail = (unsigned long long)info.f_bfree
842 * (unsigned long long)info.f_bsize;
843 # else
844 # error "statfs has no bfree/bavail member!"
845 # endif
846 #endif
847 size->s.LowPart = (DWORD)bigsize;
848 size->s.HighPart = (DWORD)(bigsize>>32);
849 available->s.LowPart = (DWORD)bigavail;
850 available->s.HighPart = (DWORD)(bigavail>>32);
851 return 1;
854 /***********************************************************************
855 * DRIVE_GetCurrentDirectory
856 * Returns "X:\\path\\etc\\".
858 * Despite the API description, return required length including the
859 * terminating null when buffer too small. This is the real behaviour.
862 static UINT DRIVE_GetCurrentDirectory( UINT buflen, LPSTR buf )
864 UINT ret;
865 const char *s = DRIVE_GetDosCwd( DRIVE_GetCurrentDrive() );
867 assert(s);
868 ret = strlen(s) + 3; /* length of WHOLE current directory */
869 if (ret >= buflen) return ret + 1;
870 lstrcpynA( buf, "A:\\", MIN( 4, buflen ) );
871 if (buflen) buf[0] += DRIVE_GetCurrentDrive();
872 if (buflen > 3) lstrcpynA( buf + 3, s, buflen - 3 );
873 return ret;
876 /***********************************************************************
877 * GetDiskFreeSpace16 (KERNEL.422)
879 BOOL16 WINAPI GetDiskFreeSpace16( LPCSTR root, LPDWORD cluster_sectors,
880 LPDWORD sector_bytes, LPDWORD free_clusters,
881 LPDWORD total_clusters )
883 return GetDiskFreeSpaceA( root, cluster_sectors, sector_bytes,
884 free_clusters, total_clusters );
888 /***********************************************************************
889 * GetDiskFreeSpace32A (KERNEL32.206)
891 * Fails if expression resulting from current drive's dir and "root"
892 * is not a root dir of the target drive.
894 * UNDOC: setting some LPDWORDs to NULL is perfectly possible
895 * if the corresponding info is unneeded.
897 * FIXME: needs to support UNC names from Win95 OSR2 on.
899 * Behaviour under Win95a:
900 * CurrDir root result
901 * "E:\\TEST" "E:" FALSE
902 * "E:\\" "E:" TRUE
903 * "E:\\" "E" FALSE
904 * "E:\\" "\\" TRUE
905 * "E:\\TEST" "\\" TRUE
906 * "E:\\TEST" ":\\" FALSE
907 * "E:\\TEST" "E:\\" TRUE
908 * "E:\\TEST" "" FALSE
909 * "E:\\" "" FALSE (!)
910 * "E:\\" 0x0 TRUE
911 * "E:\\TEST" 0x0 TRUE (!)
912 * "E:\\TEST" "C:" TRUE (when CurrDir of "C:" set to "\\")
913 * "E:\\TEST" "C:" FALSE (when CurrDir of "C:" set to "\\TEST")
915 BOOL WINAPI GetDiskFreeSpaceA( LPCSTR root, LPDWORD cluster_sectors,
916 LPDWORD sector_bytes, LPDWORD free_clusters,
917 LPDWORD total_clusters )
919 int drive;
920 ULARGE_INTEGER size,available;
921 LPCSTR path;
922 DWORD cluster_sec;
924 if ((!root) || (strcmp(root,"\\") == 0))
925 drive = DRIVE_GetCurrentDrive();
926 else
927 if ( (strlen(root) >= 2) && (root[1] == ':')) /* root contains drive tag */
929 drive = toupper(root[0]) - 'A';
930 path = &root[2];
931 if (path[0] == '\0')
932 path = DRIVE_GetDosCwd(drive);
933 else
934 if (path[0] == '\\')
935 path++;
936 if (strlen(path)) /* oops, we are in a subdir */
937 return FALSE;
939 else
940 return FALSE;
942 if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
944 /* Cap the size and available at 2GB as per specs. */
945 if ((size.s.HighPart) ||(size.s.LowPart > 0x7fffffff))
947 size.s.HighPart = 0;
948 size.s.LowPart = 0x7fffffff;
950 if ((available.s.HighPart) ||(available.s.LowPart > 0x7fffffff))
952 available.s.HighPart =0;
953 available.s.LowPart = 0x7fffffff;
955 if (DRIVE_GetType(drive)==TYPE_CDROM) {
956 if (sector_bytes)
957 *sector_bytes = 2048;
958 size.s.LowPart /= 2048;
959 available.s.LowPart /= 2048;
960 } else {
961 if (sector_bytes)
962 *sector_bytes = 512;
963 size.s.LowPart /= 512;
964 available.s.LowPart /= 512;
966 /* fixme: probably have to adjust those variables too for CDFS */
967 cluster_sec = 1;
968 while (cluster_sec * 65536 < size.s.LowPart) cluster_sec *= 2;
970 if (cluster_sectors)
971 *cluster_sectors = cluster_sec;
972 if (free_clusters)
973 *free_clusters = available.s.LowPart / cluster_sec;
974 if (total_clusters)
975 *total_clusters = size.s.LowPart / cluster_sec;
976 return TRUE;
980 /***********************************************************************
981 * GetDiskFreeSpace32W (KERNEL32.207)
983 BOOL WINAPI GetDiskFreeSpaceW( LPCWSTR root, LPDWORD cluster_sectors,
984 LPDWORD sector_bytes, LPDWORD free_clusters,
985 LPDWORD total_clusters )
987 LPSTR xroot;
988 BOOL ret;
990 xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, root);
991 ret = GetDiskFreeSpaceA( xroot,cluster_sectors, sector_bytes,
992 free_clusters, total_clusters );
993 HeapFree( GetProcessHeap(), 0, xroot );
994 return ret;
998 /***********************************************************************
999 * GetDiskFreeSpaceEx32A (KERNEL32.871)
1001 * This function is used to aquire the size of the available and
1002 * total space on a logical volume.
1004 * RETURNS
1006 * Zero on failure, nonzero upon success. Use GetLastError to obtain
1007 * detailed error information.
1010 BOOL WINAPI GetDiskFreeSpaceExA( LPCSTR root,
1011 PULARGE_INTEGER avail,
1012 PULARGE_INTEGER total,
1013 PULARGE_INTEGER totalfree)
1015 int drive;
1016 ULARGE_INTEGER size,available;
1018 if (!root) drive = DRIVE_GetCurrentDrive();
1019 else
1021 if ((root[1]) && ((root[1] != ':') || (root[2] != '\\')))
1023 FIXME("there are valid root names which are not supported yet\n");
1024 /* ..like UNC names, for instance. */
1026 WARN("invalid root '%s'\n", root );
1027 return FALSE;
1029 drive = toupper(root[0]) - 'A';
1032 if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
1034 if (total)
1036 total->s.HighPart = size.s.HighPart;
1037 total->s.LowPart = size.s.LowPart ;
1040 if (totalfree)
1042 totalfree->s.HighPart = available.s.HighPart;
1043 totalfree->s.LowPart = available.s.LowPart ;
1046 if (avail)
1048 if (FIXME_ON(dosfs))
1050 /* On Windows2000, we need to check the disk quota
1051 allocated for the user owning the calling process. We
1052 don't want to be more obtrusive than necessary with the
1053 FIXME messages, so don't print the FIXME unless Wine is
1054 actually masquerading as Windows2000. */
1056 OSVERSIONINFOA ovi;
1057 ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
1058 if (GetVersionExA(&ovi))
1060 if (ovi.dwPlatformId == VER_PLATFORM_WIN32_NT && ovi.dwMajorVersion > 4)
1061 FIXME("no per-user quota support yet\n");
1065 /* Quick hack, should eventually be fixed to work 100% with
1066 Windows2000 (see comment above). */
1067 avail->s.HighPart = available.s.HighPart;
1068 avail->s.LowPart = available.s.LowPart ;
1071 return TRUE;
1074 /***********************************************************************
1075 * GetDiskFreeSpaceEx32W (KERNEL32.873)
1077 BOOL WINAPI GetDiskFreeSpaceExW( LPCWSTR root, PULARGE_INTEGER avail,
1078 PULARGE_INTEGER total,
1079 PULARGE_INTEGER totalfree)
1081 LPSTR xroot;
1082 BOOL ret;
1084 xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, root);
1085 ret = GetDiskFreeSpaceExA( xroot, avail, total, totalfree);
1086 HeapFree( GetProcessHeap(), 0, xroot );
1087 return ret;
1090 /***********************************************************************
1091 * GetDriveType16 (KERNEL.136)
1092 * This function returns the type of a drive in Win16.
1093 * Note that it returns DRIVE_REMOTE for CD-ROMs, since MSCDEX uses the
1094 * remote drive API. The return value DRIVE_REMOTE for CD-ROMs has been
1095 * verified on Win 3.11 and Windows 95. Some programs rely on it, so don't
1096 * do any pseudo-clever changes.
1098 * RETURNS
1099 * drivetype DRIVE_xxx
1101 UINT16 WINAPI GetDriveType16(
1102 UINT16 drive /* [in] number (NOT letter) of drive */
1104 TRACE("(%c:)\n", 'A' + drive );
1105 switch(DRIVE_GetType(drive))
1107 case TYPE_FLOPPY: return DRIVE_REMOVABLE;
1108 case TYPE_HD: return DRIVE_FIXED;
1109 case TYPE_CDROM: return DRIVE_REMOTE;
1110 case TYPE_NETWORK: return DRIVE_REMOTE;
1111 case TYPE_INVALID:
1112 default: return DRIVE_CANNOTDETERMINE;
1117 /***********************************************************************
1118 * GetDriveType32A (KERNEL32.208)
1120 * Returns the type of the disk drive specified. If root is NULL the
1121 * root of the current directory is used.
1123 * RETURNS
1125 * Type of drive (from Win32 SDK):
1127 * DRIVE_UNKNOWN unable to find out anything about the drive
1128 * DRIVE_NO_ROOT_DIR nonexistand root dir
1129 * DRIVE_REMOVABLE the disk can be removed from the machine
1130 * DRIVE_FIXED the disk can not be removed from the machine
1131 * DRIVE_REMOTE network disk
1132 * DRIVE_CDROM CDROM drive
1133 * DRIVE_RAMDISK virtual disk in ram
1135 * DRIVE_DOESNOTEXIST XXX Not valid return value
1136 * DRIVE_CANNOTDETERMINE XXX Not valid return value
1138 * BUGS
1140 * Currently returns DRIVE_DOESNOTEXIST and DRIVE_CANNOTDETERMINE
1141 * when it really should return DRIVE_NO_ROOT_DIR and DRIVE_UNKNOWN.
1142 * Why were the former defines used?
1144 * DRIVE_RAMDISK is unsupported.
1146 UINT WINAPI GetDriveTypeA(LPCSTR root /* String describing drive */)
1148 int drive;
1149 TRACE("(%s)\n", debugstr_a(root));
1151 if (NULL == root) drive = DRIVE_GetCurrentDrive();
1152 else
1154 if ((root[1]) && (root[1] != ':'))
1156 WARN("invalid root '%s'\n", debugstr_a(root));
1157 return DRIVE_DOESNOTEXIST;
1159 drive = toupper(root[0]) - 'A';
1161 switch(DRIVE_GetType(drive))
1163 case TYPE_FLOPPY: return DRIVE_REMOVABLE;
1164 case TYPE_HD: return DRIVE_FIXED;
1165 case TYPE_CDROM: return DRIVE_CDROM;
1166 case TYPE_NETWORK: return DRIVE_REMOTE;
1167 case TYPE_INVALID: return DRIVE_DOESNOTEXIST;
1168 default: return DRIVE_CANNOTDETERMINE;
1173 /***********************************************************************
1174 * GetDriveType32W (KERNEL32.209)
1176 UINT WINAPI GetDriveTypeW( LPCWSTR root )
1178 LPSTR xpath = HEAP_strdupWtoA( GetProcessHeap(), 0, root );
1179 UINT ret = GetDriveTypeA( xpath );
1180 HeapFree( GetProcessHeap(), 0, xpath );
1181 return ret;
1185 /***********************************************************************
1186 * GetCurrentDirectory16 (KERNEL.411)
1188 UINT16 WINAPI GetCurrentDirectory16( UINT16 buflen, LPSTR buf )
1190 return (UINT16)DRIVE_GetCurrentDirectory(buflen, buf);
1194 /***********************************************************************
1195 * GetCurrentDirectory32A (KERNEL32.196)
1197 UINT WINAPI GetCurrentDirectoryA( UINT buflen, LPSTR buf )
1199 UINT ret;
1200 char longname[MAX_PATHNAME_LEN];
1201 char shortname[MAX_PATHNAME_LEN];
1202 ret = DRIVE_GetCurrentDirectory(MAX_PATHNAME_LEN, shortname);
1203 if ( ret > MAX_PATHNAME_LEN ) {
1204 ERR_(file)("pathnamelength (%d) > MAX_PATHNAME_LEN!\n", ret );
1205 return ret;
1207 GetLongPathNameA(shortname, longname, MAX_PATHNAME_LEN);
1208 ret = lstrlenA( longname ) + 1;
1209 if (ret > buflen) return ret;
1210 lstrcpyA(buf, longname);
1211 return ret - 1;
1214 /***********************************************************************
1215 * GetCurrentDirectory32W (KERNEL32.197)
1217 UINT WINAPI GetCurrentDirectoryW( UINT buflen, LPWSTR buf )
1219 LPSTR xpath = HeapAlloc( GetProcessHeap(), 0, buflen+1 );
1220 UINT ret = GetCurrentDirectoryA( buflen, xpath );
1221 if (ret < buflen) lstrcpyAtoW ( buf, xpath );
1222 HeapFree( GetProcessHeap(), 0, xpath );
1223 return ret;
1227 /***********************************************************************
1228 * SetCurrentDirectory (KERNEL.412)
1230 BOOL16 WINAPI SetCurrentDirectory16( LPCSTR dir )
1232 return SetCurrentDirectoryA( dir );
1236 /***********************************************************************
1237 * SetCurrentDirectory32A (KERNEL32.479)
1239 BOOL WINAPI SetCurrentDirectoryA( LPCSTR dir )
1241 int olddrive, drive = DRIVE_GetCurrentDrive();
1243 if (!dir) {
1244 ERR_(file)("(NULL)!\n");
1245 return FALSE;
1247 if (dir[0] && (dir[1]==':'))
1249 drive = tolower( *dir ) - 'a';
1250 dir += 2;
1253 /* WARNING: we need to set the drive before the dir, as DRIVE_Chdir
1254 sets pTask->curdir only if pTask->curdrive is drive */
1255 olddrive = drive; /* in case DRIVE_Chdir fails */
1256 if (!(DRIVE_SetCurrentDrive( drive )))
1257 return FALSE;
1258 /* FIXME: what about empty strings? Add a \\ ? */
1259 if (!DRIVE_Chdir( drive, dir )) {
1260 DRIVE_SetCurrentDrive(olddrive);
1261 return FALSE;
1263 return TRUE;
1267 /***********************************************************************
1268 * SetCurrentDirectory32W (KERNEL32.480)
1270 BOOL WINAPI SetCurrentDirectoryW( LPCWSTR dirW )
1272 LPSTR dir = HEAP_strdupWtoA( GetProcessHeap(), 0, dirW );
1273 BOOL res = SetCurrentDirectoryA( dir );
1274 HeapFree( GetProcessHeap(), 0, dir );
1275 return res;
1279 /***********************************************************************
1280 * GetLogicalDriveStrings32A (KERNEL32.231)
1282 UINT WINAPI GetLogicalDriveStringsA( UINT len, LPSTR buffer )
1284 int drive, count;
1286 for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1287 if (DRIVE_IsValid(drive)) count++;
1288 if ((count * 4) + 1 <= len)
1290 LPSTR p = buffer;
1291 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1292 if (DRIVE_IsValid(drive))
1294 *p++ = 'a' + drive;
1295 *p++ = ':';
1296 *p++ = '\\';
1297 *p++ = '\0';
1299 *p = '\0';
1300 return count * 4;
1302 else
1303 return (count * 4) + 1;/* account for terminating null */
1304 /* The API tells about these different return values */
1308 /***********************************************************************
1309 * GetLogicalDriveStrings32W (KERNEL32.232)
1311 UINT WINAPI GetLogicalDriveStringsW( UINT len, LPWSTR buffer )
1313 int drive, count;
1315 for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1316 if (DRIVE_IsValid(drive)) count++;
1317 if (count * 4 * sizeof(WCHAR) <= len)
1319 LPWSTR p = buffer;
1320 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1321 if (DRIVE_IsValid(drive))
1323 *p++ = (WCHAR)('a' + drive);
1324 *p++ = (WCHAR)':';
1325 *p++ = (WCHAR)'\\';
1326 *p++ = (WCHAR)'\0';
1328 *p = (WCHAR)'\0';
1330 return count * 4 * sizeof(WCHAR);
1334 /***********************************************************************
1335 * GetLogicalDrives (KERNEL32.233)
1337 DWORD WINAPI GetLogicalDrives(void)
1339 DWORD ret = 0;
1340 int drive;
1342 for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1344 if ( (DRIVE_IsValid(drive)) ||
1345 (DOSDrives[drive].type == TYPE_CDROM)) /* audio CD is also valid */
1346 ret |= (1 << drive);
1348 return ret;
1352 /***********************************************************************
1353 * GetVolumeInformation32A (KERNEL32.309)
1355 BOOL WINAPI GetVolumeInformationA( LPCSTR root, LPSTR label,
1356 DWORD label_len, DWORD *serial,
1357 DWORD *filename_len, DWORD *flags,
1358 LPSTR fsname, DWORD fsname_len )
1360 int drive;
1361 char *cp;
1363 /* FIXME, SetLastError()s missing */
1365 if (!root) drive = DRIVE_GetCurrentDrive();
1366 else
1368 if ((root[1]) && (root[1] != ':'))
1370 WARN("invalid root '%s'\n",root);
1371 return FALSE;
1373 drive = toupper(root[0]) - 'A';
1375 if (!DRIVE_IsValid( drive )) return FALSE;
1376 if (label)
1378 lstrcpynA( label, DRIVE_GetLabel(drive), label_len );
1379 for (cp = label; *cp; cp++);
1380 while (cp != label && *(cp-1) == ' ') cp--;
1381 *cp = '\0';
1383 if (serial) *serial = DRIVE_GetSerialNumber(drive);
1385 /* Set the filesystem information */
1386 /* Note: we only emulate a FAT fs at present */
1388 if (filename_len) {
1389 if (DOSDrives[drive].flags & DRIVE_SHORT_NAMES)
1390 *filename_len = 12;
1391 else
1392 *filename_len = 255;
1394 if (flags)
1396 *flags=0;
1397 if (DOSDrives[drive].flags & DRIVE_CASE_SENSITIVE)
1398 *flags|=FS_CASE_SENSITIVE;
1399 if (DOSDrives[drive].flags & DRIVE_CASE_PRESERVING)
1400 *flags|=FS_CASE_IS_PRESERVED;
1402 if (fsname) {
1403 /* Diablo checks that return code ... */
1404 if (DRIVE_GetType(drive)==TYPE_CDROM)
1405 lstrcpynA( fsname, "CDFS", fsname_len );
1406 else
1407 lstrcpynA( fsname, "FAT", fsname_len );
1409 return TRUE;
1413 /***********************************************************************
1414 * GetVolumeInformation32W (KERNEL32.310)
1416 BOOL WINAPI GetVolumeInformationW( LPCWSTR root, LPWSTR label,
1417 DWORD label_len, DWORD *serial,
1418 DWORD *filename_len, DWORD *flags,
1419 LPWSTR fsname, DWORD fsname_len )
1421 LPSTR xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, root );
1422 LPSTR xvolname = label ? HeapAlloc(GetProcessHeap(),0,label_len) : NULL;
1423 LPSTR xfsname = fsname ? HeapAlloc(GetProcessHeap(),0,fsname_len) : NULL;
1424 BOOL ret = GetVolumeInformationA( xroot, xvolname, label_len, serial,
1425 filename_len, flags, xfsname,
1426 fsname_len );
1427 if (ret)
1429 if (label) lstrcpyAtoW( label, xvolname );
1430 if (fsname) lstrcpyAtoW( fsname, xfsname );
1432 HeapFree( GetProcessHeap(), 0, xroot );
1433 HeapFree( GetProcessHeap(), 0, xvolname );
1434 HeapFree( GetProcessHeap(), 0, xfsname );
1435 return ret;
1438 /***********************************************************************
1439 * SetVolumeLabelA (KERNEL32.675)
1441 BOOL WINAPI SetVolumeLabelA( LPCSTR root, LPCSTR volname )
1443 int drive;
1445 /* FIXME, SetLastErrors missing */
1447 if (!root) drive = DRIVE_GetCurrentDrive();
1448 else
1450 if ((root[1]) && (root[1] != ':'))
1452 WARN("invalid root '%s'\n",root);
1453 return FALSE;
1455 drive = toupper(root[0]) - 'A';
1457 if (!DRIVE_IsValid( drive )) return FALSE;
1459 /* some copy protection stuff check this */
1460 if (DRIVE_GetType( drive ) == TYPE_CDROM) return FALSE;
1462 FIXME("(%s,%s),stub!\n", root, volname);
1463 return TRUE;
1466 /***********************************************************************
1467 * SetVolumeLabelW (KERNEL32.676)
1469 BOOL WINAPI SetVolumeLabelW(LPCWSTR rootpath,LPCWSTR volname)
1471 LPSTR xroot, xvol;
1472 BOOL ret;
1474 xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, rootpath);
1475 xvol = HEAP_strdupWtoA( GetProcessHeap(), 0, volname);
1476 ret = SetVolumeLabelA( xroot, xvol );
1477 HeapFree( GetProcessHeap(), 0, xroot );
1478 HeapFree( GetProcessHeap(), 0, xvol );
1479 return ret;