- Fix positioning of text in buttons.
[wine] / files / drive.c
1 /*
2  * DOS drives handling functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 1996 Alexandre Julliard
6  *
7  * Label & serial number read support.
8  *  (c) 1999 Petr Tomasek <tomasek@etf.cuni.cz>
9  *  (c) 2000 Andreas Mohr (changes)
10  *
11  */
12
13 #include "config.h"
14
15 #include <assert.h>
16 #include <ctype.h>
17 #include <string.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <unistd.h>
25
26 #ifdef HAVE_SYS_PARAM_H
27 # include <sys/param.h>
28 #endif
29 #ifdef STATFS_DEFINED_BY_SYS_VFS
30 # include <sys/vfs.h>
31 #else
32 # ifdef STATFS_DEFINED_BY_SYS_MOUNT
33 #  include <sys/mount.h>
34 # else
35 #  ifdef STATFS_DEFINED_BY_SYS_STATFS
36 #   include <sys/statfs.h>
37 #  endif
38 # endif
39 #endif
40
41 #include "winbase.h"
42 #include "ntddk.h"
43 #include "wine/winbase16.h"   /* for GetCurrentTask */
44 #include "winerror.h"
45 #include "drive.h"
46 #include "cdrom.h"
47 #include "file.h"
48 #include "heap.h"
49 #include "msdos.h"
50 #include "options.h"
51 #include "wine/port.h"
52 #include "task.h"
53 #include "debugtools.h"
54
55 DEFAULT_DEBUG_CHANNEL(dosfs);
56 DECLARE_DEBUG_CHANNEL(file);
57
58 typedef struct
59 {
60     char     *root;      /* root dir in Unix format without trailing / */
61     char     *dos_cwd;   /* cwd in DOS format without leading or trailing \ */
62     char     *unix_cwd;  /* cwd in Unix format without leading or trailing / */
63     char     *device;    /* raw device path */
64     char      label_conf[12]; /* drive label as cfg'd in wine config */
65     char      label_read[12]; /* drive label as read from device */
66     DWORD     serial_conf;    /* drive serial number as cfg'd in wine config */
67     UINT      type;      /* drive type */
68     UINT      flags;     /* drive flags */
69     dev_t     dev;       /* unix device number */
70     ino_t     ino;       /* unix inode number */
71 } DOSDRIVE;
72
73
74 static const char * const DRIVE_Types[] =
75 {
76     "",         /* DRIVE_UNKNOWN */
77     "",         /* DRIVE_NO_ROOT_DIR */
78     "floppy",   /* DRIVE_REMOVABLE */
79     "hd",       /* DRIVE_FIXED */
80     "network",  /* DRIVE_REMOTE */
81     "cdrom",    /* DRIVE_CDROM */
82     "ramdisk"   /* DRIVE_RAMDISK */
83 };
84
85
86 /* Known filesystem types */
87
88 typedef struct
89 {
90     const char *name;
91     UINT      flags;
92 } FS_DESCR;
93
94 static const FS_DESCR DRIVE_Filesystems[] =
95 {
96     { "unix",   DRIVE_CASE_SENSITIVE | DRIVE_CASE_PRESERVING },
97     { "msdos",  DRIVE_SHORT_NAMES },
98     { "dos",    DRIVE_SHORT_NAMES },
99     { "fat",    DRIVE_SHORT_NAMES },
100     { "vfat",   DRIVE_CASE_PRESERVING },
101     { "win95",  DRIVE_CASE_PRESERVING },
102     { NULL, 0 }
103 };
104
105
106 static DOSDRIVE DOSDrives[MAX_DOS_DRIVES];
107 static int DRIVE_CurDrive = -1;
108
109 static HTASK16 DRIVE_LastTask = 0;
110
111 /* strdup on the process heap */
112 inline static char *heap_strdup( const char *str )
113 {
114     INT len = strlen(str) + 1;
115     LPSTR p = HeapAlloc( GetProcessHeap(), 0, len );
116     if (p) memcpy( p, str, len );
117     return p;
118 }
119
120 /***********************************************************************
121  *           DRIVE_GetDriveType
122  */
123 static UINT DRIVE_GetDriveType( const char *name )
124 {
125     char buffer[20];
126     int i;
127
128     PROFILE_GetWineIniString( name, "Type", "hd", buffer, sizeof(buffer) );
129     for (i = 0; i < sizeof(DRIVE_Types)/sizeof(DRIVE_Types[0]); i++)
130     {
131         if (!strcasecmp( buffer, DRIVE_Types[i] )) return i;
132     }
133     MESSAGE("%s: unknown drive type '%s', defaulting to 'hd'.\n",
134         name, buffer );
135     return DRIVE_FIXED;
136 }
137
138
139 /***********************************************************************
140  *           DRIVE_GetFSFlags
141  */
142 static UINT DRIVE_GetFSFlags( const char *name, const char *value )
143 {
144     const FS_DESCR *descr;
145
146     for (descr = DRIVE_Filesystems; descr->name; descr++)
147         if (!strcasecmp( value, descr->name )) return descr->flags;
148     MESSAGE("%s: unknown filesystem type '%s', defaulting to 'win95'.\n",
149         name, value );
150     return DRIVE_CASE_PRESERVING;
151 }
152
153
154 /***********************************************************************
155  *           DRIVE_Init
156  */
157 int DRIVE_Init(void)
158 {
159     int i, len, count = 0;
160     char name[] = "Drive A";
161     char drive_env[] = "=A:";
162     char path[MAX_PATHNAME_LEN];
163     char buffer[80];
164     struct stat drive_stat_buffer;
165     char *p;
166     DOSDRIVE *drive;
167
168     for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, name[6]++, drive++)
169     {
170         PROFILE_GetWineIniString( name, "Path", "", path, sizeof(path)-1 );
171         if (path[0])
172         {
173             p = path + strlen(path) - 1;
174             while ((p > path) && ((*p == '/') || (*p == '\\'))) *p-- = '\0';
175             if (!path[0]) strcpy( path, "/" );
176
177             if (stat( path, &drive_stat_buffer ))
178             {
179                 MESSAGE("Could not stat %s (%s), ignoring drive %c:\n",
180                     path, strerror(errno), 'A' + i);
181                 continue;
182             }
183             if (!S_ISDIR(drive_stat_buffer.st_mode))
184             {
185                 MESSAGE("%s is not a directory, ignoring drive %c:\n",
186                     path, 'A' + i );
187                 continue;
188             }
189
190             drive->root     = heap_strdup( path );
191             drive->dos_cwd  = heap_strdup( "" );
192             drive->unix_cwd = heap_strdup( "" );
193             drive->type     = DRIVE_GetDriveType( name );
194             drive->device   = NULL;
195             drive->flags    = 0;
196             drive->dev      = drive_stat_buffer.st_dev;
197             drive->ino      = drive_stat_buffer.st_ino;
198
199             /* Get the drive label */
200             PROFILE_GetWineIniString( name, "Label", "", drive->label_conf, 12 );
201             if ((len = strlen(drive->label_conf)) < 11)
202             {
203                 /* Pad label with spaces */
204                 memset( drive->label_conf + len, ' ', 11 - len );
205                 drive->label_conf[11] = '\0';
206             }
207
208             /* Get the serial number */
209             PROFILE_GetWineIniString( name, "Serial", "12345678",
210                                       buffer, sizeof(buffer) );
211             drive->serial_conf = strtoul( buffer, NULL, 16 );
212
213             /* Get the filesystem type */
214             PROFILE_GetWineIniString( name, "Filesystem", "win95",
215                                       buffer, sizeof(buffer) );
216             drive->flags = DRIVE_GetFSFlags( name, buffer );
217
218             /* Get the device */
219             PROFILE_GetWineIniString( name, "Device", "",
220                                       buffer, sizeof(buffer) );
221             if (buffer[0])
222             {
223                 drive->device = heap_strdup( buffer );
224                 if (PROFILE_GetWineIniBool( name, "ReadVolInfo", 1))
225                     drive->flags |= DRIVE_READ_VOL_INFO;
226             }
227
228             /* Get the FailReadOnly flag */
229             if (PROFILE_GetWineIniBool( name, "FailReadOnly", 0 ))
230                 drive->flags |= DRIVE_FAIL_READ_ONLY;
231
232             /* Make the first hard disk the current drive */
233             if ((DRIVE_CurDrive == -1) && (drive->type == DRIVE_FIXED))
234                 DRIVE_CurDrive = i;
235
236             count++;
237             TRACE("%s: path=%s type=%s label='%s' serial=%08lx "
238                   "flags=%08x dev=%x ino=%x\n",
239                   name, path, DRIVE_Types[drive->type],
240                   drive->label_conf, drive->serial_conf, drive->flags,
241                   (int)drive->dev, (int)drive->ino );
242         }
243         else WARN("%s: not defined\n", name );
244     }
245
246     if (!count) 
247     {
248         MESSAGE("Warning: no valid DOS drive found, check your configuration file.\n" );
249         /* Create a C drive pointing to Unix root dir */
250         DOSDrives[2].root     = heap_strdup( "/" );
251         DOSDrives[2].dos_cwd  = heap_strdup( "" );
252         DOSDrives[2].unix_cwd = heap_strdup( "" );
253         strcpy( DOSDrives[2].label_conf, "Drive C    " );
254         DOSDrives[2].serial_conf   = 12345678;
255         DOSDrives[2].type     = DRIVE_FIXED;
256         DOSDrives[2].device   = NULL;
257         DOSDrives[2].flags    = 0;
258         DRIVE_CurDrive = 2;
259     }
260
261     /* Make sure the current drive is valid */
262     if (DRIVE_CurDrive == -1)
263     {
264         for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
265         {
266             if (drive->root && !(drive->flags & DRIVE_DISABLED))
267             {
268                 DRIVE_CurDrive = i;
269                 break;
270             }
271         }
272     }
273
274     /* get current working directory info for all drives */
275     for (i = 0; i < MAX_DOS_DRIVES; i++, drive_env[1]++)
276     {
277         if (!GetEnvironmentVariableA(drive_env, path, sizeof(path))) continue;
278         /* sanity check */
279         if (toupper(path[0]) != drive_env[1] || path[1] != ':') continue;
280         DRIVE_Chdir( i, path + 2 );
281     }
282     return 1;
283 }
284
285
286 /***********************************************************************
287  *           DRIVE_IsValid
288  */
289 int DRIVE_IsValid( int drive )
290 {
291     if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
292     return (DOSDrives[drive].root &&
293             !(DOSDrives[drive].flags & DRIVE_DISABLED));
294 }
295
296
297 /***********************************************************************
298  *           DRIVE_GetCurrentDrive
299  */
300 int DRIVE_GetCurrentDrive(void)
301 {
302     TDB *pTask = TASK_GetCurrent();
303     if (pTask && (pTask->curdrive & 0x80)) return pTask->curdrive & ~0x80;
304     return DRIVE_CurDrive;
305 }
306
307
308 /***********************************************************************
309  *           DRIVE_SetCurrentDrive
310  */
311 int DRIVE_SetCurrentDrive( int drive )
312 {
313     TDB *pTask = TASK_GetCurrent();
314     if (!DRIVE_IsValid( drive ))
315     {
316         SetLastError( ERROR_INVALID_DRIVE );
317         return 0;
318     }
319     TRACE("%c:\n", 'A' + drive );
320     DRIVE_CurDrive = drive;
321     if (pTask) pTask->curdrive = drive | 0x80;
322     chdir(DRIVE_GetUnixCwd(drive));
323     return 1;
324 }
325
326
327 /***********************************************************************
328  *           DRIVE_FindDriveRoot
329  *
330  * Find a drive for which the root matches the beginning of the given path.
331  * This can be used to translate a Unix path into a drive + DOS path.
332  * Return value is the drive, or -1 on error. On success, path is modified
333  * to point to the beginning of the DOS path.
334  */
335 int DRIVE_FindDriveRoot( const char **path )
336 {
337     /* idea: check at all '/' positions.
338      * If the device and inode of that path is identical with the
339      * device and inode of the current drive then we found a solution.
340      * If there is another drive pointing to a deeper position in
341      * the file tree, we want to find that one, not the earlier solution.
342      */
343     int drive, rootdrive = -1;
344     char buffer[MAX_PATHNAME_LEN];
345     char *next = buffer;
346     const char *p = *path;
347     struct stat st;
348
349     strcpy( buffer, "/" );
350     for (;;)
351     {
352         if (stat( buffer, &st ) || !S_ISDIR( st.st_mode )) break;
353
354         /* Find the drive */
355
356         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
357         {
358            if (!DOSDrives[drive].root ||
359                (DOSDrives[drive].flags & DRIVE_DISABLED)) continue;
360
361            if ((DOSDrives[drive].dev == st.st_dev) &&
362                (DOSDrives[drive].ino == st.st_ino))
363            {
364                rootdrive = drive;
365                *path = p;
366                break;
367            }
368         }
369
370         /* Get the next path component */
371
372         *next++ = '/';
373         while ((*p == '/') || (*p == '\\')) p++;
374         if (!*p) break;
375         while (!IS_END_OF_NAME(*p)) *next++ = *p++;
376         *next = 0;
377     }
378     *next = 0;
379
380     if (rootdrive != -1)
381         TRACE("%s -> drive %c:, root='%s', name='%s'\n",
382               buffer, 'A' + rootdrive, DOSDrives[rootdrive].root, *path );
383     return rootdrive;
384 }
385
386
387 /***********************************************************************
388  *           DRIVE_GetRoot
389  */
390 const char * DRIVE_GetRoot( int drive )
391 {
392     if (!DRIVE_IsValid( drive )) return NULL;
393     return DOSDrives[drive].root;
394 }
395
396
397 /***********************************************************************
398  *           DRIVE_GetDosCwd
399  */
400 const char * DRIVE_GetDosCwd( int drive )
401 {
402     TDB *pTask = TASK_GetCurrent();
403     if (!DRIVE_IsValid( drive )) return NULL;
404
405     /* Check if we need to change the directory to the new task. */
406     if (pTask && (pTask->curdrive & 0x80) &&    /* The task drive is valid */
407         ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
408         (DRIVE_LastTask != GetCurrentTask()))   /* and the task changed */
409     {
410         /* Perform the task-switch */
411         if (!DRIVE_Chdir( drive, pTask->curdir )) DRIVE_Chdir( drive, "\\" );
412         DRIVE_LastTask = GetCurrentTask();
413     }
414     return DOSDrives[drive].dos_cwd;
415 }
416
417
418 /***********************************************************************
419  *           DRIVE_GetUnixCwd
420  */
421 const char * DRIVE_GetUnixCwd( int drive )
422 {
423     TDB *pTask = TASK_GetCurrent();
424     if (!DRIVE_IsValid( drive )) return NULL;
425
426     /* Check if we need to change the directory to the new task. */
427     if (pTask && (pTask->curdrive & 0x80) &&    /* The task drive is valid */
428         ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
429         (DRIVE_LastTask != GetCurrentTask()))   /* and the task changed */
430     {
431         /* Perform the task-switch */
432         if (!DRIVE_Chdir( drive, pTask->curdir )) DRIVE_Chdir( drive, "\\" );
433         DRIVE_LastTask = GetCurrentTask();
434     }
435     return DOSDrives[drive].unix_cwd;
436 }
437
438
439 /***********************************************************************
440  *           DRIVE_GetDevice
441  */
442 const char * DRIVE_GetDevice( int drive )
443 {
444     return (DRIVE_IsValid( drive )) ? DOSDrives[drive].device : NULL;
445 }
446
447
448 /***********************************************************************
449  *           DRIVE_ReadSuperblock
450  *
451  * NOTE 
452  *      DRIVE_SetLabel and DRIVE_SetSerialNumber use this in order
453  * to check, that they are writing on a FAT filesystem !
454  */
455 int DRIVE_ReadSuperblock (int drive, char * buff)
456 {
457 #define DRIVE_SUPER 96
458     int fd;
459     off_t offs;
460
461     if (memset(buff,0,DRIVE_SUPER)!=buff) return -1;
462     if ((fd=open(DOSDrives[drive].device,O_RDONLY)) == -1)
463     {
464         struct stat st;
465         if (!DOSDrives[drive].device)
466             ERR("No device configured for drive %c: !\n", 'A'+drive);
467         else
468             ERR("Couldn't open device '%s' for drive %c: ! (%s)\n", DOSDrives[drive].device, 'A'+drive,
469                  (stat(DOSDrives[drive].device, &st)) ?
470                         "not available or symlink not valid ?" : "no permission");
471         ERR("Can't read drive volume info ! Either pre-set it or make sure the device to read it from is accessible !\n");
472         PROFILE_UsageWineIni();
473         return -1;
474     }
475
476     switch(DOSDrives[drive].type)
477     {
478         case DRIVE_REMOVABLE:
479         case DRIVE_FIXED:
480             offs = 0;
481             break;
482         case DRIVE_CDROM:
483             offs = CDROM_Data_FindBestVoldesc(fd);
484             break;
485         default:
486             offs = 0;
487             break;
488     }
489
490     if ((offs) && (lseek(fd,offs,SEEK_SET)!=offs)) return -4;
491     if (read(fd,buff,DRIVE_SUPER)!=DRIVE_SUPER) return -2;
492
493     switch(DOSDrives[drive].type)
494     {
495         case DRIVE_REMOVABLE:
496         case DRIVE_FIXED:
497             if ((buff[0x26]!=0x29) ||  /* Check for FAT present */
498                 /* FIXME: do really all FAT have their name beginning with
499                    "FAT" ? (At least FAT12, FAT16 and FAT32 have :) */
500                 memcmp( buff+0x36,"FAT",3))
501             {
502                 ERR("The filesystem is not FAT !! (device=%s)\n",
503                     DOSDrives[drive].device);
504                 return -3;
505             }
506             break;
507         case DRIVE_CDROM:
508             if (strncmp(&buff[1],"CD001",5)) /* Check for iso9660 present */
509                 return -3;
510             /* FIXME: do we need to check for "CDROM", too ? (high sierra) */
511                 break;
512         default:
513                 return -3;
514                 break;
515     }
516
517     return close(fd);
518 }
519
520
521 /***********************************************************************
522  *           DRIVE_WriteSuperblockEntry
523  *
524  * NOTE
525  *      We are writing as little as possible (ie. not the whole SuperBlock)
526  * not to interfere with kernel. The drive can be mounted !
527  */
528 int DRIVE_WriteSuperblockEntry (int drive, off_t ofs, size_t len, char * buff)
529 {
530     int fd;
531
532     if ((fd=open(DOSDrives[drive].device,O_WRONLY))==-1) 
533     {
534         ERR("Cannot open the device %s (for writing)\n",
535             DOSDrives[drive].device); 
536         return -1;
537     }
538     if (lseek(fd,ofs,SEEK_SET)!=ofs)
539     {
540         ERR("lseek failed on device %s !\n",
541             DOSDrives[drive].device); 
542         close(fd);
543         return -2;
544     }
545     if (write(fd,buff,len)!=len) 
546     {
547         close(fd);
548         ERR("Cannot write on %s !\n",
549             DOSDrives[drive].device); 
550         return -3;
551     }
552     return close (fd);
553 }
554
555
556
557 /***********************************************************************
558  *           DRIVE_GetLabel
559  */
560 const char * DRIVE_GetLabel( int drive )
561 {
562     int read = 0;
563     char buff[DRIVE_SUPER];
564     int offs = -1;
565
566     if (!DRIVE_IsValid( drive )) return NULL;
567     if (DOSDrives[drive].type == DRIVE_CDROM)
568     {
569         read = CDROM_GetLabel(drive, DOSDrives[drive].label_read); 
570     }
571     else
572     if (DOSDrives[drive].flags & DRIVE_READ_VOL_INFO)
573     {
574         if (DRIVE_ReadSuperblock(drive,(char *) buff))
575             ERR("Invalid or unreadable superblock on %s (%c:).\n",
576                 DOSDrives[drive].device, (char)(drive+'A'));
577         else {
578             if (DOSDrives[drive].type == DRIVE_REMOVABLE ||
579                 DOSDrives[drive].type == DRIVE_FIXED)
580                 offs = 0x2b;
581
582             /* FIXME: ISO9660 uses a 32 bytes long label. Should we do also? */
583             if (offs != -1) memcpy(DOSDrives[drive].label_read,buff+offs,11);
584             DOSDrives[drive].label_read[11]='\0';
585             read = 1;
586         }
587     }
588
589     return (read) ?
590         DOSDrives[drive].label_read : DOSDrives[drive].label_conf;
591 }
592
593
594 /***********************************************************************
595  *           DRIVE_GetSerialNumber
596  */
597 DWORD DRIVE_GetSerialNumber( int drive )
598 {
599     DWORD serial = 0;
600 char buff[DRIVE_SUPER];
601
602     if (!DRIVE_IsValid( drive )) return 0;
603     
604     if (DOSDrives[drive].flags & DRIVE_READ_VOL_INFO)
605     {
606         switch(DOSDrives[drive].type)
607         {
608         case DRIVE_REMOVABLE:
609         case DRIVE_FIXED:
610             if (DRIVE_ReadSuperblock(drive,(char *) buff))
611                 MESSAGE("Invalid or unreadable superblock on %s (%c:)."
612                         " Maybe not FAT?\n" ,
613                         DOSDrives[drive].device, 'A'+drive);
614             else
615                 serial = *((DWORD*)(buff+0x27));
616             break;
617         case DRIVE_CDROM:
618             serial = CDROM_GetSerial(drive);
619             break;
620         default:
621             FIXME("Serial number reading from file system on drive %c: not supported yet.\n", drive+'A');
622         }
623     }
624                 
625     return (serial) ? serial : DOSDrives[drive].serial_conf;
626 }
627
628
629 /***********************************************************************
630  *           DRIVE_SetSerialNumber
631  */
632 int DRIVE_SetSerialNumber( int drive, DWORD serial )
633 {
634     char buff[DRIVE_SUPER];
635
636     if (!DRIVE_IsValid( drive )) return 0;
637
638     if (DOSDrives[drive].flags & DRIVE_READ_VOL_INFO)
639     {
640         if ((DOSDrives[drive].type != DRIVE_REMOVABLE) &&
641             (DOSDrives[drive].type != DRIVE_FIXED)) return 0;
642         /* check, if the drive has a FAT filesystem */ 
643         if (DRIVE_ReadSuperblock(drive, buff)) return 0;
644         if (DRIVE_WriteSuperblockEntry(drive, 0x27, 4, (char *) &serial)) return 0;
645         return 1;
646     }
647
648     if (DOSDrives[drive].type == DRIVE_CDROM) return 0;
649     DOSDrives[drive].serial_conf = serial;
650     return 1;
651 }
652
653
654 /***********************************************************************
655  *           DRIVE_GetType
656  */
657 static UINT DRIVE_GetType( int drive )
658 {
659     if (!DRIVE_IsValid( drive )) return DRIVE_UNKNOWN;
660     return DOSDrives[drive].type;
661 }
662
663
664 /***********************************************************************
665  *           DRIVE_GetFlags
666  */
667 UINT DRIVE_GetFlags( int drive )
668 {
669     if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
670     return DOSDrives[drive].flags;
671 }
672
673
674 /***********************************************************************
675  *           DRIVE_Chdir
676  */
677 int DRIVE_Chdir( int drive, const char *path )
678 {
679     DOS_FULL_NAME full_name;
680     char buffer[MAX_PATHNAME_LEN];
681     LPSTR unix_cwd;
682     BY_HANDLE_FILE_INFORMATION info;
683     TDB *pTask = TASK_GetCurrent();
684
685     strcpy( buffer, "A:" );
686     buffer[0] += drive;
687     TRACE("(%s,%s)\n", buffer, path );
688     lstrcpynA( buffer + 2, path, sizeof(buffer) - 2 );
689
690     if (!DOSFS_GetFullName( buffer, TRUE, &full_name )) return 0;
691     if (!FILE_Stat( full_name.long_name, &info )) return 0;
692     if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
693     {
694         SetLastError( ERROR_FILE_NOT_FOUND );
695         return 0;
696     }
697     unix_cwd = full_name.long_name + strlen( DOSDrives[drive].root );
698     while (*unix_cwd == '/') unix_cwd++;
699
700     TRACE("(%c:): unix_cwd=%s dos_cwd=%s\n",
701                    'A' + drive, unix_cwd, full_name.short_name + 3 );
702
703     HeapFree( GetProcessHeap(), 0, DOSDrives[drive].dos_cwd );
704     HeapFree( GetProcessHeap(), 0, DOSDrives[drive].unix_cwd );
705     DOSDrives[drive].dos_cwd  = heap_strdup( full_name.short_name + 3 );
706     DOSDrives[drive].unix_cwd = heap_strdup( unix_cwd );
707
708     if (pTask && (pTask->curdrive & 0x80) && 
709         ((pTask->curdrive & ~0x80) == drive))
710     {
711         lstrcpynA( pTask->curdir, full_name.short_name + 2,
712                      sizeof(pTask->curdir) );
713         DRIVE_LastTask = GetCurrentTask();
714         chdir(unix_cwd); /* Only change if on current drive */
715     }
716     return 1;
717 }
718
719
720 /***********************************************************************
721  *           DRIVE_Disable
722  */
723 int DRIVE_Disable( int drive  )
724 {
725     if ((drive < 0) || (drive >= MAX_DOS_DRIVES) || !DOSDrives[drive].root)
726     {
727         SetLastError( ERROR_INVALID_DRIVE );
728         return 0;
729     }
730     DOSDrives[drive].flags |= DRIVE_DISABLED;
731     return 1;
732 }
733
734
735 /***********************************************************************
736  *           DRIVE_Enable
737  */
738 int DRIVE_Enable( int drive  )
739 {
740     if ((drive < 0) || (drive >= MAX_DOS_DRIVES) || !DOSDrives[drive].root)
741     {
742         SetLastError( ERROR_INVALID_DRIVE );
743         return 0;
744     }
745     DOSDrives[drive].flags &= ~DRIVE_DISABLED;
746     return 1;
747 }
748
749
750 /***********************************************************************
751  *           DRIVE_SetLogicalMapping
752  */
753 int DRIVE_SetLogicalMapping ( int existing_drive, int new_drive )
754 {
755  /* If new_drive is already valid, do nothing and return 0
756     otherwise, copy DOSDrives[existing_drive] to DOSDrives[new_drive] */
757   
758     DOSDRIVE *old, *new;
759     
760     old = DOSDrives + existing_drive;
761     new = DOSDrives + new_drive;
762
763     if ((existing_drive < 0) || (existing_drive >= MAX_DOS_DRIVES) ||
764         !old->root ||
765         (new_drive < 0) || (new_drive >= MAX_DOS_DRIVES))
766     {
767         SetLastError( ERROR_INVALID_DRIVE );
768         return 0;
769     }
770
771     if ( new->root )
772     {
773         TRACE("Can't map drive %c: to already existing drive %c:\n",
774               'A' + existing_drive, 'A' + new_drive );
775         /* it is already mapped there, so return success */
776         if (!strcmp(old->root,new->root))
777             return 1;
778         return 0;
779     }
780
781     new->root     = heap_strdup( old->root );
782     new->dos_cwd  = heap_strdup( old->dos_cwd );
783     new->unix_cwd = heap_strdup( old->unix_cwd );
784     new->device   = heap_strdup( old->device );
785     memcpy ( new->label_conf, old->label_conf, 12 );
786     memcpy ( new->label_read, old->label_read, 12 );
787     new->serial_conf = old->serial_conf;
788     new->type = old->type;
789     new->flags = old->flags;
790     new->dev = old->dev;
791     new->ino = old->ino;
792
793     TRACE("Drive %c: is now equal to drive %c:\n",
794           'A' + new_drive, 'A' + existing_drive );
795
796     return 1;
797 }
798
799
800 /***********************************************************************
801  *           DRIVE_OpenDevice
802  *
803  * Open the drive raw device and return a Unix fd (or -1 on error).
804  */
805 int DRIVE_OpenDevice( int drive, int flags )
806 {
807     if (!DRIVE_IsValid( drive )) return -1;
808     return open( DOSDrives[drive].device, flags );
809 }
810
811
812 /***********************************************************************
813  *           DRIVE_RawRead
814  *
815  * Read raw sectors from a device
816  */
817 int DRIVE_RawRead(BYTE drive, DWORD begin, DWORD nr_sect, BYTE *dataptr, BOOL fake_success)
818 {
819     int fd;
820
821     if ((fd = DRIVE_OpenDevice( drive, O_RDONLY )) != -1)
822     {
823         lseek( fd, begin * 512, SEEK_SET );
824         /* FIXME: check errors */
825         read( fd, dataptr, nr_sect * 512 );
826         close( fd );
827     }
828     else
829     {
830         memset(dataptr, 0, nr_sect * 512);
831         if (fake_success)
832         {
833             if (begin == 0 && nr_sect > 1) *(dataptr + 512) = 0xf8;
834             if (begin == 1) *dataptr = 0xf8;
835         }
836         else
837             return 0;
838     }
839     return 1;
840 }
841
842
843 /***********************************************************************
844  *           DRIVE_RawWrite
845  *
846  * Write raw sectors to a device
847  */
848 int DRIVE_RawWrite(BYTE drive, DWORD begin, DWORD nr_sect, BYTE *dataptr, BOOL fake_success)
849 {
850     int fd;
851
852     if ((fd = DRIVE_OpenDevice( drive, O_RDONLY )) != -1)
853     {
854         lseek( fd, begin * 512, SEEK_SET );
855         /* FIXME: check errors */
856         write( fd, dataptr, nr_sect * 512 );
857         close( fd );
858     }
859     else
860     if (!(fake_success))
861         return 0;
862
863     return 1;
864 }
865
866
867 /***********************************************************************
868  *           DRIVE_GetFreeSpace
869  */
870 static int DRIVE_GetFreeSpace( int drive, PULARGE_INTEGER size, 
871                                PULARGE_INTEGER available )
872 {
873     struct statfs info;
874
875     if (!DRIVE_IsValid(drive))
876     {
877         SetLastError( ERROR_INVALID_DRIVE );
878         return 0;
879     }
880
881 /* FIXME: add autoconf check for this */
882 #if defined(__svr4__) || defined(_SCO_DS) || defined(__sun)
883     if (statfs( DOSDrives[drive].root, &info, 0, 0) < 0)
884 #else
885     if (statfs( DOSDrives[drive].root, &info) < 0)
886 #endif
887     {
888         FILE_SetDosError();
889         WARN("cannot do statfs(%s)\n", DOSDrives[drive].root);
890         return 0;
891     }
892
893     size->QuadPart = RtlEnlargedUnsignedMultiply( info.f_bsize, info.f_blocks );
894 #ifdef STATFS_HAS_BAVAIL
895     available->QuadPart = RtlEnlargedUnsignedMultiply( info.f_bavail, info.f_bsize );
896 #else
897 # ifdef STATFS_HAS_BFREE
898     available->QuadPart = RtlEnlargedUnsignedMultiply( info.f_bfree, info.f_bsize );
899 # else
900 #  error "statfs has no bfree/bavail member!"
901 # endif
902 #endif
903     if (DOSDrives[drive].type == DRIVE_CDROM)
904     { /* ALWAYS 0, even if no real CD-ROM mounted there !! */
905         available->QuadPart = 0;
906     }
907     return 1;
908 }
909
910 /***********************************************************************
911  *       DRIVE_GetCurrentDirectory
912  * Returns "X:\\path\\etc\\".
913  *
914  * Despite the API description, return required length including the 
915  * terminating null when buffer too small. This is the real behaviour.
916 */
917
918 static UINT DRIVE_GetCurrentDirectory( UINT buflen, LPSTR buf )
919 {
920     UINT ret;
921     const char *s = DRIVE_GetDosCwd( DRIVE_GetCurrentDrive() );
922
923     assert(s);
924     ret = strlen(s) + 3; /* length of WHOLE current directory */
925     if (ret >= buflen) return ret + 1;
926     lstrcpynA( buf, "A:\\", min( 4, buflen ) );
927     if (buflen) buf[0] += DRIVE_GetCurrentDrive();
928     if (buflen > 3) lstrcpynA( buf + 3, s, buflen - 3 );
929     return ret;
930 }
931
932
933 /***********************************************************************
934  *           DRIVE_BuildEnv
935  *
936  * Build the environment array containing the drives current directories.
937  * Resulting pointer must be freed with HeapFree.
938  */
939 char *DRIVE_BuildEnv(void)
940 {
941     int i, length = 0;
942     const char *cwd[MAX_DOS_DRIVES];
943     char *env, *p;
944
945     for (i = 0; i < MAX_DOS_DRIVES; i++)
946     {
947         if ((cwd[i] = DRIVE_GetDosCwd(i)) && cwd[i][0]) length += strlen(cwd[i]) + 8;
948     }
949     if (!(env = HeapAlloc( GetProcessHeap(), 0, length+1 ))) return NULL;
950     for (i = 0, p = env; i < MAX_DOS_DRIVES; i++)
951     {
952         if (cwd[i] && cwd[i][0])
953             p += sprintf( p, "=%c:=%c:\\%s", 'A'+i, 'A'+i, cwd[i] ) + 1;
954     }
955     *p = 0;
956     return env;
957 }
958
959
960 /***********************************************************************
961  *           GetDiskFreeSpace   (KERNEL.422)
962  */
963 BOOL16 WINAPI GetDiskFreeSpace16( LPCSTR root, LPDWORD cluster_sectors,
964                                   LPDWORD sector_bytes, LPDWORD free_clusters,
965                                   LPDWORD total_clusters )
966 {
967     return GetDiskFreeSpaceA( root, cluster_sectors, sector_bytes,
968                                 free_clusters, total_clusters );
969 }
970
971
972 /***********************************************************************
973  *           GetDiskFreeSpaceA   (KERNEL32.@)
974  *
975  * Fails if expression resulting from current drive's dir and "root"
976  * is not a root dir of the target drive.
977  *
978  * UNDOC: setting some LPDWORDs to NULL is perfectly possible 
979  * if the corresponding info is unneeded.
980  *
981  * FIXME: needs to support UNC names from Win95 OSR2 on.
982  *
983  * Behaviour under Win95a:
984  * CurrDir     root   result
985  * "E:\\TEST"  "E:"   FALSE
986  * "E:\\"      "E:"   TRUE
987  * "E:\\"      "E"    FALSE
988  * "E:\\"      "\\"   TRUE
989  * "E:\\TEST"  "\\"   TRUE
990  * "E:\\TEST"  ":\\"  FALSE
991  * "E:\\TEST"  "E:\\" TRUE
992  * "E:\\TEST"  ""     FALSE
993  * "E:\\"      ""     FALSE (!)
994  * "E:\\"      0x0    TRUE
995  * "E:\\TEST"  0x0    TRUE  (!)
996  * "E:\\TEST"  "C:"   TRUE  (when CurrDir of "C:" set to "\\")
997  * "E:\\TEST"  "C:"   FALSE (when CurrDir of "C:" set to "\\TEST")
998  */
999 BOOL WINAPI GetDiskFreeSpaceA( LPCSTR root, LPDWORD cluster_sectors,
1000                                    LPDWORD sector_bytes, LPDWORD free_clusters,
1001                                    LPDWORD total_clusters )
1002 {
1003     int drive, sec_size;
1004     ULARGE_INTEGER size,available;
1005     LPCSTR path;
1006     DWORD cluster_sec;
1007
1008     if ((!root) || (strcmp(root,"\\") == 0))
1009         drive = DRIVE_GetCurrentDrive();
1010     else
1011     if ( (strlen(root) >= 2) && (root[1] == ':')) /* root contains drive tag */
1012     {
1013         drive = toupper(root[0]) - 'A';
1014         path = &root[2];
1015         if (path[0] == '\0')
1016             path = DRIVE_GetDosCwd(drive);
1017         else
1018         if (path[0] == '\\')
1019             path++;
1020         if (strlen(path)) /* oops, we are in a subdir */
1021             return FALSE;
1022     }
1023     else
1024         return FALSE;
1025
1026     if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
1027
1028     /* Cap the size and available at 2GB as per specs.  */
1029     if ((size.s.HighPart) ||(size.s.LowPart > 0x7fffffff))
1030     {
1031         size.s.HighPart = 0;
1032         size.s.LowPart = 0x7fffffff;
1033     }
1034     if ((available.s.HighPart) ||(available.s.LowPart > 0x7fffffff))
1035     {
1036         available.s.HighPart =0;
1037         available.s.LowPart = 0x7fffffff;
1038     }
1039     sec_size = (DRIVE_GetType(drive)==DRIVE_CDROM) ? 2048 : 512;
1040     size.s.LowPart            /= sec_size;
1041     available.s.LowPart       /= sec_size;
1042     /* fixme: probably have to adjust those variables too for CDFS */
1043     cluster_sec = 1;
1044     while (cluster_sec * 65536 < size.s.LowPart) cluster_sec *= 2;
1045
1046     if (cluster_sectors)
1047         *cluster_sectors = cluster_sec;
1048     if (sector_bytes)
1049         *sector_bytes    = sec_size;
1050     if (free_clusters)
1051         *free_clusters   = available.s.LowPart / cluster_sec;
1052     if (total_clusters)
1053         *total_clusters  = size.s.LowPart / cluster_sec;
1054     return TRUE;
1055 }
1056
1057
1058 /***********************************************************************
1059  *           GetDiskFreeSpaceW   (KERNEL32.@)
1060  */
1061 BOOL WINAPI GetDiskFreeSpaceW( LPCWSTR root, LPDWORD cluster_sectors,
1062                                    LPDWORD sector_bytes, LPDWORD free_clusters,
1063                                    LPDWORD total_clusters )
1064 {
1065     LPSTR xroot;
1066     BOOL ret;
1067
1068     xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, root);
1069     ret = GetDiskFreeSpaceA( xroot,cluster_sectors, sector_bytes,
1070                                free_clusters, total_clusters );
1071     HeapFree( GetProcessHeap(), 0, xroot );
1072     return ret;
1073 }
1074
1075
1076 /***********************************************************************
1077  *           GetDiskFreeSpaceExA   (KERNEL32.@)
1078  *
1079  *  This function is used to acquire the size of the available and
1080  *  total space on a logical volume.
1081  *
1082  * RETURNS
1083  *
1084  *  Zero on failure, nonzero upon success. Use GetLastError to obtain
1085  *  detailed error information.
1086  *
1087  */
1088 BOOL WINAPI GetDiskFreeSpaceExA( LPCSTR root,
1089                                      PULARGE_INTEGER avail,
1090                                      PULARGE_INTEGER total,
1091                                      PULARGE_INTEGER totalfree)
1092 {
1093     int drive;
1094     ULARGE_INTEGER size,available;
1095
1096     if (!root) drive = DRIVE_GetCurrentDrive();
1097     else
1098     {
1099         if ((root[1]) && ((root[1] != ':') || (root[2] != '\\')))
1100         {
1101             FIXME("there are valid root names which are not supported yet\n");
1102             /* ..like UNC names, for instance. */
1103
1104             WARN("invalid root '%s'\n", root );
1105             return FALSE;
1106         }
1107         drive = toupper(root[0]) - 'A';
1108     }
1109
1110     if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
1111
1112     if (total)
1113     {
1114         total->s.HighPart = size.s.HighPart;
1115         total->s.LowPart = size.s.LowPart;
1116     }
1117
1118     if (totalfree)
1119     {
1120         totalfree->s.HighPart = available.s.HighPart;
1121         totalfree->s.LowPart = available.s.LowPart;
1122     }
1123
1124     if (avail)
1125     {
1126         if (FIXME_ON(dosfs))
1127         {
1128             /* On Windows2000, we need to check the disk quota
1129                allocated for the user owning the calling process. We
1130                don't want to be more obtrusive than necessary with the
1131                FIXME messages, so don't print the FIXME unless Wine is
1132                actually masquerading as Windows2000. */
1133
1134             OSVERSIONINFOA ovi;
1135             ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
1136             if (GetVersionExA(&ovi))
1137             {
1138               if (ovi.dwPlatformId == VER_PLATFORM_WIN32_NT && ovi.dwMajorVersion > 4)
1139                   FIXME("no per-user quota support yet\n");
1140             }
1141         }
1142
1143         /* Quick hack, should eventually be fixed to work 100% with
1144            Windows2000 (see comment above). */
1145         avail->s.HighPart = available.s.HighPart;
1146         avail->s.LowPart = available.s.LowPart;
1147     }
1148
1149     return TRUE;
1150 }
1151
1152 /***********************************************************************
1153  *           GetDiskFreeSpaceExW   (KERNEL32.@)
1154  */
1155 BOOL WINAPI GetDiskFreeSpaceExW( LPCWSTR root, PULARGE_INTEGER avail,
1156                                      PULARGE_INTEGER total,
1157                                      PULARGE_INTEGER  totalfree)
1158 {
1159     LPSTR xroot;
1160     BOOL ret;
1161
1162     xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, root);
1163     ret = GetDiskFreeSpaceExA( xroot, avail, total, totalfree);
1164     HeapFree( GetProcessHeap(), 0, xroot );
1165     return ret;
1166 }
1167
1168 /***********************************************************************
1169  *           GetDriveType   (KERNEL.136)
1170  * This function returns the type of a drive in Win16. 
1171  * Note that it returns DRIVE_REMOTE for CD-ROMs, since MSCDEX uses the
1172  * remote drive API. The return value DRIVE_REMOTE for CD-ROMs has been
1173  * verified on Win 3.11 and Windows 95. Some programs rely on it, so don't
1174  * do any pseudo-clever changes.
1175  *
1176  * RETURNS
1177  *      drivetype DRIVE_xxx
1178  */
1179 UINT16 WINAPI GetDriveType16( UINT16 drive ) /* [in] number (NOT letter) of drive */
1180 {
1181     UINT type = DRIVE_GetType(drive);
1182     TRACE("(%c:)\n", 'A' + drive );
1183     if (type == DRIVE_CDROM) type = DRIVE_REMOTE;
1184     return type;
1185 }
1186
1187
1188 /***********************************************************************
1189  *           GetDriveTypeA   (KERNEL32.@)
1190  *
1191  * Returns the type of the disk drive specified. If root is NULL the
1192  * root of the current directory is used.
1193  *
1194  * RETURNS
1195  *
1196  *  Type of drive (from Win32 SDK):
1197  *
1198  *   DRIVE_UNKNOWN     unable to find out anything about the drive
1199  *   DRIVE_NO_ROOT_DIR nonexistent root dir
1200  *   DRIVE_REMOVABLE   the disk can be removed from the machine
1201  *   DRIVE_FIXED       the disk can not be removed from the machine
1202  *   DRIVE_REMOTE      network disk
1203  *   DRIVE_CDROM       CDROM drive
1204  *   DRIVE_RAMDISK     virtual disk in RAM
1205  */
1206 UINT WINAPI GetDriveTypeA(LPCSTR root) /* [in] String describing drive */
1207 {
1208     int drive;
1209     TRACE("(%s)\n", debugstr_a(root));
1210
1211     if (NULL == root) drive = DRIVE_GetCurrentDrive();
1212     else
1213     {
1214         if ((root[1]) && (root[1] != ':'))
1215         {
1216             WARN("invalid root %s\n", debugstr_a(root));
1217             return DRIVE_NO_ROOT_DIR;
1218         }
1219         drive = toupper(root[0]) - 'A';
1220     }
1221     return DRIVE_GetType(drive);
1222 }
1223
1224
1225 /***********************************************************************
1226  *           GetDriveTypeW   (KERNEL32.@)
1227  */
1228 UINT WINAPI GetDriveTypeW( LPCWSTR root )
1229 {
1230     LPSTR xpath = HEAP_strdupWtoA( GetProcessHeap(), 0, root );
1231     UINT ret = GetDriveTypeA( xpath );
1232     HeapFree( GetProcessHeap(), 0, xpath );
1233     return ret;
1234 }
1235
1236
1237 /***********************************************************************
1238  *           GetCurrentDirectory   (KERNEL.411)
1239  */
1240 UINT16 WINAPI GetCurrentDirectory16( UINT16 buflen, LPSTR buf )
1241 {
1242     return (UINT16)DRIVE_GetCurrentDirectory(buflen, buf);
1243 }
1244
1245
1246 /***********************************************************************
1247  *           GetCurrentDirectoryA   (KERNEL32.@)
1248  */
1249 UINT WINAPI GetCurrentDirectoryA( UINT buflen, LPSTR buf )
1250 {
1251     UINT ret;
1252     char longname[MAX_PATHNAME_LEN];
1253     char shortname[MAX_PATHNAME_LEN];
1254     ret = DRIVE_GetCurrentDirectory(MAX_PATHNAME_LEN, shortname);
1255     if ( ret > MAX_PATHNAME_LEN ) {
1256       ERR_(file)("pathnamelength (%d) > MAX_PATHNAME_LEN!\n", ret );
1257       return ret;
1258     }
1259     GetLongPathNameA(shortname, longname, MAX_PATHNAME_LEN);
1260     ret = strlen( longname ) + 1;
1261     if (ret > buflen) return ret;
1262     strcpy(buf, longname);
1263     return ret - 1;
1264 }
1265
1266 /***********************************************************************
1267  *           GetCurrentDirectoryW   (KERNEL32.@)
1268  */
1269 UINT WINAPI GetCurrentDirectoryW( UINT buflen, LPWSTR buf )
1270 {
1271     LPSTR xpath = HeapAlloc( GetProcessHeap(), 0, buflen+1 );
1272     UINT ret = GetCurrentDirectoryA( buflen, xpath );
1273     if (ret < buflen) ret = MultiByteToWideChar( CP_ACP, 0, xpath, -1, buf, buflen ) - 1;
1274     HeapFree( GetProcessHeap(), 0, xpath );
1275     return ret;
1276 }
1277
1278
1279 /***********************************************************************
1280  *           SetCurrentDirectory   (KERNEL.412)
1281  */
1282 BOOL16 WINAPI SetCurrentDirectory16( LPCSTR dir )
1283 {
1284     return SetCurrentDirectoryA( dir );
1285 }
1286
1287
1288 /***********************************************************************
1289  *           SetCurrentDirectoryA   (KERNEL32.@)
1290  */
1291 BOOL WINAPI SetCurrentDirectoryA( LPCSTR dir )
1292 {
1293     int drive, olddrive = DRIVE_GetCurrentDrive();
1294
1295     if (!dir) {
1296         ERR_(file)("(NULL)!\n");
1297         return FALSE;
1298     }
1299     if (dir[0] && (dir[1]==':'))
1300     {
1301         drive = toupper( *dir ) - 'A';
1302         dir += 2;
1303     }
1304     else
1305         drive = olddrive;
1306
1307     /* WARNING: we need to set the drive before the dir, as DRIVE_Chdir
1308        sets pTask->curdir only if pTask->curdrive is drive */
1309     if (!(DRIVE_SetCurrentDrive( drive )))
1310         return FALSE;
1311     /* FIXME: what about empty strings? Add a \\ ? */
1312     if (!DRIVE_Chdir( drive, dir )) {
1313         DRIVE_SetCurrentDrive(olddrive);
1314         return FALSE;
1315     }
1316     return TRUE;
1317 }
1318
1319
1320 /***********************************************************************
1321  *           SetCurrentDirectoryW   (KERNEL32.@)
1322  */
1323 BOOL WINAPI SetCurrentDirectoryW( LPCWSTR dirW )
1324 {
1325     LPSTR dir = HEAP_strdupWtoA( GetProcessHeap(), 0, dirW );
1326     BOOL res = SetCurrentDirectoryA( dir );
1327     HeapFree( GetProcessHeap(), 0, dir );
1328     return res;
1329 }
1330
1331
1332 /***********************************************************************
1333  *           GetLogicalDriveStringsA   (KERNEL32.@)
1334  */
1335 UINT WINAPI GetLogicalDriveStringsA( UINT len, LPSTR buffer )
1336 {
1337     int drive, count;
1338
1339     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1340         if (DRIVE_IsValid(drive)) count++;
1341     if ((count * 4) + 1 <= len)
1342     {
1343         LPSTR p = buffer;
1344         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1345             if (DRIVE_IsValid(drive))
1346             {
1347                 *p++ = 'a' + drive;
1348                 *p++ = ':';
1349                 *p++ = '\\';
1350                 *p++ = '\0';
1351             }
1352         *p = '\0';
1353         return count * 4;
1354     }
1355     else
1356         return (count * 4) + 1; /* account for terminating null */
1357     /* The API tells about these different return values */
1358 }
1359
1360
1361 /***********************************************************************
1362  *           GetLogicalDriveStringsW   (KERNEL32.@)
1363  */
1364 UINT WINAPI GetLogicalDriveStringsW( UINT len, LPWSTR buffer )
1365 {
1366     int drive, count;
1367
1368     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1369         if (DRIVE_IsValid(drive)) count++;
1370     if (count * 4 * sizeof(WCHAR) <= len)
1371     {
1372         LPWSTR p = buffer;
1373         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1374             if (DRIVE_IsValid(drive))
1375             {
1376                 *p++ = (WCHAR)('a' + drive);
1377                 *p++ = (WCHAR)':';
1378                 *p++ = (WCHAR)'\\';
1379                 *p++ = (WCHAR)'\0';
1380             }
1381         *p = (WCHAR)'\0';
1382     }
1383     return count * 4 * sizeof(WCHAR);
1384 }
1385
1386
1387 /***********************************************************************
1388  *           GetLogicalDrives   (KERNEL32.@)
1389  */
1390 DWORD WINAPI GetLogicalDrives(void)
1391 {
1392     DWORD ret = 0;
1393     int drive;
1394
1395     for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1396     {
1397         if ( (DRIVE_IsValid(drive)) ||
1398             (DOSDrives[drive].type == DRIVE_CDROM)) /* audio CD is also valid */
1399             ret |= (1 << drive);
1400     }
1401     return ret;
1402 }
1403
1404
1405 /***********************************************************************
1406  *           GetVolumeInformationA   (KERNEL32.@)
1407  */
1408 BOOL WINAPI GetVolumeInformationA( LPCSTR root, LPSTR label,
1409                                        DWORD label_len, DWORD *serial,
1410                                        DWORD *filename_len, DWORD *flags,
1411                                        LPSTR fsname, DWORD fsname_len )
1412 {
1413     int drive;
1414     char *cp;
1415
1416     /* FIXME, SetLastError()s missing */
1417
1418     if (!root) drive = DRIVE_GetCurrentDrive();
1419     else
1420     {
1421         if ((root[1]) && (root[1] != ':'))
1422         {
1423             WARN("invalid root '%s'\n",root);
1424             return FALSE;
1425         }
1426         drive = toupper(root[0]) - 'A';
1427     }
1428     if (!DRIVE_IsValid( drive )) return FALSE;
1429     if (label)
1430     {
1431        lstrcpynA( label, DRIVE_GetLabel(drive), label_len );
1432        cp = label + strlen(label);
1433        while (cp != label && *(cp-1) == ' ') cp--;
1434        *cp = '\0';
1435     }
1436     if (serial) *serial = DRIVE_GetSerialNumber(drive);
1437
1438     /* Set the filesystem information */
1439     /* Note: we only emulate a FAT fs at present */
1440
1441     if (filename_len) {
1442         if (DOSDrives[drive].flags & DRIVE_SHORT_NAMES)
1443             *filename_len = 12;
1444         else
1445             *filename_len = 255;
1446     }
1447     if (flags)
1448       {
1449        *flags=0;
1450        if (DOSDrives[drive].flags & DRIVE_CASE_SENSITIVE)
1451          *flags|=FS_CASE_SENSITIVE;
1452        if (DOSDrives[drive].flags & DRIVE_CASE_PRESERVING)
1453          *flags|=FS_CASE_IS_PRESERVED;
1454       }
1455     if (fsname) {
1456         /* Diablo checks that return code ... */
1457         if (DOSDrives[drive].type == DRIVE_CDROM)
1458             lstrcpynA( fsname, "CDFS", fsname_len );
1459         else
1460             lstrcpynA( fsname, "FAT", fsname_len );
1461     }
1462     return TRUE;
1463 }
1464
1465
1466 /***********************************************************************
1467  *           GetVolumeInformationW   (KERNEL32.@)
1468  */
1469 BOOL WINAPI GetVolumeInformationW( LPCWSTR root, LPWSTR label,
1470                                        DWORD label_len, DWORD *serial,
1471                                        DWORD *filename_len, DWORD *flags,
1472                                        LPWSTR fsname, DWORD fsname_len )
1473 {
1474     LPSTR xroot    = HEAP_strdupWtoA( GetProcessHeap(), 0, root );
1475     LPSTR xvolname = label ? HeapAlloc(GetProcessHeap(),0,label_len) : NULL;
1476     LPSTR xfsname  = fsname ? HeapAlloc(GetProcessHeap(),0,fsname_len) : NULL;
1477     BOOL ret = GetVolumeInformationA( xroot, xvolname, label_len, serial,
1478                                           filename_len, flags, xfsname,
1479                                           fsname_len );
1480     if (ret)
1481     {
1482         if (label) MultiByteToWideChar( CP_ACP, 0, xvolname, -1, label, label_len );
1483         if (fsname) MultiByteToWideChar( CP_ACP, 0, xfsname, -1, fsname, fsname_len );
1484     }
1485     HeapFree( GetProcessHeap(), 0, xroot );
1486     HeapFree( GetProcessHeap(), 0, xvolname );
1487     HeapFree( GetProcessHeap(), 0, xfsname );
1488     return ret;
1489 }
1490
1491 /***********************************************************************
1492  *           SetVolumeLabelA   (KERNEL32.@)
1493  */
1494 BOOL WINAPI SetVolumeLabelA( LPCSTR root, LPCSTR volname )
1495 {
1496     int drive;
1497     
1498     /* FIXME, SetLastErrors missing */
1499
1500     if (!root) drive = DRIVE_GetCurrentDrive();
1501     else
1502     {
1503         if ((root[1]) && (root[1] != ':'))
1504         {
1505             WARN("invalid root '%s'\n",root);
1506             return FALSE;
1507         }
1508         drive = toupper(root[0]) - 'A';
1509     }
1510     if (!DRIVE_IsValid( drive )) return FALSE;
1511
1512     /* some copy protection stuff check this */
1513     if (DOSDrives[drive].type == DRIVE_CDROM) return FALSE;
1514
1515     FIXME("(%s,%s),stub!\n", root, volname);
1516     return TRUE;
1517 }
1518
1519 /***********************************************************************
1520  *           SetVolumeLabelW   (KERNEL32.@)
1521  */
1522 BOOL WINAPI SetVolumeLabelW(LPCWSTR rootpath,LPCWSTR volname)
1523 {
1524     LPSTR xroot, xvol;
1525     BOOL ret;
1526
1527     xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, rootpath);
1528     xvol = HEAP_strdupWtoA( GetProcessHeap(), 0, volname);
1529     ret = SetVolumeLabelA( xroot, xvol );
1530     HeapFree( GetProcessHeap(), 0, xroot );
1531     HeapFree( GetProcessHeap(), 0, xvol );
1532     return ret;
1533 }