GetTimeFormat() should return LocalTime not SystemTime.
[wine] / files / drive.c
1 /*
2  * DOS drives handling functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 1996 Alexandre Julliard
6  */
7
8 #include "config.h"
9
10 #include <assert.h>
11 #include <ctype.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 #include <unistd.h>
19
20 #ifdef HAVE_SYS_PARAM_H
21 # include <sys/param.h>
22 #endif
23 #ifdef STATFS_DEFINED_BY_SYS_VFS
24 # include <sys/vfs.h>
25 #else
26 # ifdef STATFS_DEFINED_BY_SYS_MOUNT
27 #  include <sys/mount.h>
28 # else
29 #  ifdef STATFS_DEFINED_BY_SYS_STATFS
30 #   include <sys/statfs.h>
31 #  endif
32 # endif
33 #endif
34
35 #include "winbase.h"
36 #include "wine/winbase16.h"   /* for GetCurrentTask */
37 #include "wine/winestring.h"  /* for lstrcpyAtoW */
38 #include "winerror.h"
39 #include "drive.h"
40 #include "file.h"
41 #include "heap.h"
42 #include "msdos.h"
43 #include "options.h"
44 #include "task.h"
45 #include "debugtools.h"
46
47 DECLARE_DEBUG_CHANNEL(dosfs)
48 DECLARE_DEBUG_CHANNEL(file)
49
50 typedef struct
51 {
52     char     *root;      /* root dir in Unix format without trailing / */
53     char     *dos_cwd;   /* cwd in DOS format without leading or trailing \ */
54     char     *unix_cwd;  /* cwd in Unix format without leading or trailing / */
55     char     *device;    /* raw device path */
56     char      label[12]; /* drive label */
57     DWORD     serial;    /* drive serial number */
58     DRIVETYPE type;      /* drive type */
59     UINT    flags;     /* drive flags */
60     dev_t     dev;       /* unix device number */
61     ino_t     ino;       /* unix inode number */
62 } DOSDRIVE;
63
64
65 static const char * const DRIVE_Types[] =
66 {
67     "floppy",   /* TYPE_FLOPPY */
68     "hd",       /* TYPE_HD */
69     "cdrom",    /* TYPE_CDROM */
70     "network"   /* TYPE_NETWORK */
71 };
72
73
74 /* Known filesystem types */
75
76 typedef struct
77 {
78     const char *name;
79     UINT      flags;
80 } FS_DESCR;
81
82 static const FS_DESCR DRIVE_Filesystems[] =
83 {
84     { "unix",   DRIVE_CASE_SENSITIVE | DRIVE_CASE_PRESERVING },
85     { "msdos",  DRIVE_SHORT_NAMES },
86     { "dos",    DRIVE_SHORT_NAMES },
87     { "fat",    DRIVE_SHORT_NAMES },
88     { "vfat",   DRIVE_CASE_PRESERVING },
89     { "win95",  DRIVE_CASE_PRESERVING },
90     { NULL, 0 }
91 };
92
93
94 static DOSDRIVE DOSDrives[MAX_DOS_DRIVES];
95 static int DRIVE_CurDrive = -1;
96
97 static HTASK16 DRIVE_LastTask = 0;
98
99
100 /***********************************************************************
101  *           DRIVE_GetDriveType
102  */
103 static DRIVETYPE DRIVE_GetDriveType( const char *name )
104 {
105     char buffer[20];
106     int i;
107
108     PROFILE_GetWineIniString( name, "Type", "hd", buffer, sizeof(buffer) );
109     for (i = 0; i < sizeof(DRIVE_Types)/sizeof(DRIVE_Types[0]); i++)
110     {
111         if (!strcasecmp( buffer, DRIVE_Types[i] )) return (DRIVETYPE)i;
112     }
113     MESSAGE("%s: unknown type '%s', defaulting to 'hd'.\n", name, buffer );
114     return TYPE_HD;
115 }
116
117
118 /***********************************************************************
119  *           DRIVE_GetFSFlags
120  */
121 static UINT DRIVE_GetFSFlags( const char *name, const char *value )
122 {
123     const FS_DESCR *descr;
124
125     for (descr = DRIVE_Filesystems; descr->name; descr++)
126         if (!strcasecmp( value, descr->name )) return descr->flags;
127     MESSAGE("%s: unknown filesystem type '%s', defaulting to 'win95'.\n",
128         name, value );
129     return DRIVE_CASE_PRESERVING;
130 }
131
132
133 /***********************************************************************
134  *           DRIVE_Init
135  */
136 int DRIVE_Init(void)
137 {
138     int i, len, count = 0;
139     char name[] = "Drive A";
140     char path[MAX_PATHNAME_LEN];
141     char buffer[80];
142     struct stat drive_stat_buffer;
143     char *p;
144     DOSDRIVE *drive;
145
146     for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, name[6]++, drive++)
147     {
148         PROFILE_GetWineIniString( name, "Path", "", path, sizeof(path)-1 );
149         if (path[0])
150         {
151             p = path + strlen(path) - 1;
152             while ((p > path) && ((*p == '/') || (*p == '\\'))) *p-- = '\0';
153             if (!path[0]) strcpy( path, "/" );
154
155             if (stat( path, &drive_stat_buffer ))
156             {
157                 MESSAGE("Could not stat %s, ignoring drive %c:\n", path, 'A' + i );
158                 continue;
159             }
160             if (!S_ISDIR(drive_stat_buffer.st_mode))
161             {
162                 MESSAGE("%s is not a directory, ignoring drive %c:\n",
163                     path, 'A' + i );
164                 continue;
165             }
166
167             drive->root = HEAP_strdupA( SystemHeap, 0, path );
168             drive->dos_cwd  = HEAP_strdupA( SystemHeap, 0, "" );
169             drive->unix_cwd = HEAP_strdupA( SystemHeap, 0, "" );
170             drive->type     = DRIVE_GetDriveType( name );
171             drive->device   = NULL;
172             drive->flags    = 0;
173             drive->dev      = drive_stat_buffer.st_dev;
174             drive->ino      = drive_stat_buffer.st_ino;
175
176             /* Get the drive label */
177             PROFILE_GetWineIniString( name, "Label", name, drive->label, 12 );
178             if ((len = strlen(drive->label)) < 11)
179             {
180                 /* Pad label with spaces */
181                 memset( drive->label + len, ' ', 11 - len );
182                 drive->label[12] = '\0';
183             }
184
185             /* Get the serial number */
186             PROFILE_GetWineIniString( name, "Serial", "12345678",
187                                       buffer, sizeof(buffer) );
188             drive->serial = strtoul( buffer, NULL, 16 );
189
190             /* Get the filesystem type */
191             PROFILE_GetWineIniString( name, "Filesystem", "win95",
192                                       buffer, sizeof(buffer) );
193             drive->flags = DRIVE_GetFSFlags( name, buffer );
194
195             /* Get the device */
196             PROFILE_GetWineIniString( name, "Device", "",
197                                       buffer, sizeof(buffer) );
198             if (buffer[0])
199                 drive->device = HEAP_strdupA( SystemHeap, 0, buffer );
200
201             /* Make the first hard disk the current drive */
202             if ((DRIVE_CurDrive == -1) && (drive->type == TYPE_HD))
203                 DRIVE_CurDrive = i;
204
205             count++;
206             TRACE_(dosfs)("%s: path=%s type=%s label='%s' serial=%08lx flags=%08x dev=%x ino=%x\n",
207                            name, path, DRIVE_Types[drive->type],
208                            drive->label, drive->serial, drive->flags,
209                            (int)drive->dev, (int)drive->ino );
210         }
211         else WARN_(dosfs)("%s: not defined\n", name );
212     }
213
214     if (!count) 
215     {
216         MESSAGE("Warning: no valid DOS drive found, check your configuration file.\n" );
217         /* Create a C drive pointing to Unix root dir */
218         DOSDrives[2].root     = HEAP_strdupA( SystemHeap, 0, "/" );
219         DOSDrives[2].dos_cwd  = HEAP_strdupA( SystemHeap, 0, "" );
220         DOSDrives[2].unix_cwd = HEAP_strdupA( SystemHeap, 0, "" );
221         strcpy( DOSDrives[2].label, "Drive C    " );
222         DOSDrives[2].serial   = 0x12345678;
223         DOSDrives[2].type     = TYPE_HD;
224         DOSDrives[2].flags    = 0;
225         DRIVE_CurDrive = 2;
226     }
227
228     /* Make sure the current drive is valid */
229     if (DRIVE_CurDrive == -1)
230     {
231         for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
232         {
233             if (drive->root && !(drive->flags & DRIVE_DISABLED))
234             {
235                 DRIVE_CurDrive = i;
236                 break;
237             }
238         }
239     }
240
241     return 1;
242 }
243
244
245 /***********************************************************************
246  *           DRIVE_IsValid
247  */
248 int DRIVE_IsValid( int drive )
249 {
250     if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
251     return (DOSDrives[drive].root &&
252             !(DOSDrives[drive].flags & DRIVE_DISABLED));
253 }
254
255
256 /***********************************************************************
257  *           DRIVE_GetCurrentDrive
258  */
259 int DRIVE_GetCurrentDrive(void)
260 {
261     TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
262     if (pTask && (pTask->curdrive & 0x80)) return pTask->curdrive & ~0x80;
263     return DRIVE_CurDrive;
264 }
265
266
267 /***********************************************************************
268  *           DRIVE_SetCurrentDrive
269  */
270 int DRIVE_SetCurrentDrive( int drive )
271 {
272     TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
273     if (!DRIVE_IsValid( drive ))
274     {
275         SetLastError( ERROR_INVALID_DRIVE );
276         return 0;
277     }
278     TRACE_(dosfs)("%c:\n", 'A' + drive );
279     DRIVE_CurDrive = drive;
280     if (pTask) pTask->curdrive = drive | 0x80;
281     return 1;
282 }
283
284
285 /***********************************************************************
286  *           DRIVE_FindDriveRoot
287  *
288  * Find a drive for which the root matches the begginning of the given path.
289  * This can be used to translate a Unix path into a drive + DOS path.
290  * Return value is the drive, or -1 on error. On success, path is modified
291  * to point to the beginning of the DOS path.
292  */
293 int DRIVE_FindDriveRoot( const char **path )
294 {
295     /* idea: check at all '/' positions.
296      * If the device and inode of that path is identical with the
297      * device and inode of the current drive then we found a solution.
298      * If there is another drive pointing to a deeper position in
299      * the file tree, we want to find that one, not the earlier solution.
300      */
301     int drive, rootdrive = -1;
302     char buffer[MAX_PATHNAME_LEN];
303     char *next = buffer;
304     const char *p = *path;
305     struct stat st;
306
307     strcpy( buffer, "/" );
308     for (;;)
309     {
310         if (stat( buffer, &st ) || !S_ISDIR( st.st_mode )) break;
311
312         /* Find the drive */
313
314         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
315         {
316            if (!DOSDrives[drive].root ||
317                (DOSDrives[drive].flags & DRIVE_DISABLED)) continue;
318
319            if ((DOSDrives[drive].dev == st.st_dev) &&
320                (DOSDrives[drive].ino == st.st_ino))
321            {
322                rootdrive = drive;
323                *path = p;
324            }
325         }
326
327         /* Get the next path component */
328
329         *next++ = '/';
330         while ((*p == '/') || (*p == '\\')) p++;
331         if (!*p) break;
332         while (!IS_END_OF_NAME(*p)) *next++ = *p++;
333         *next = 0;
334     }
335     *next = 0;
336
337     if (rootdrive != -1)
338         TRACE_(dosfs)("%s -> drive %c:, root='%s', name='%s'\n",
339                        buffer, 'A' + rootdrive,
340                        DOSDrives[rootdrive].root, *path );
341     return rootdrive;
342 }
343
344
345 /***********************************************************************
346  *           DRIVE_GetRoot
347  */
348 const char * DRIVE_GetRoot( int drive )
349 {
350     if (!DRIVE_IsValid( drive )) return NULL;
351     return DOSDrives[drive].root;
352 }
353
354
355 /***********************************************************************
356  *           DRIVE_GetDosCwd
357  */
358 const char * DRIVE_GetDosCwd( int drive )
359 {
360     TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
361     if (!DRIVE_IsValid( drive )) return NULL;
362
363     /* Check if we need to change the directory to the new task. */
364     if (pTask && (pTask->curdrive & 0x80) &&    /* The task drive is valid */
365         ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
366         (DRIVE_LastTask != GetCurrentTask()))   /* and the task changed */
367     {
368         /* Perform the task-switch */
369         if (!DRIVE_Chdir( drive, pTask->curdir )) DRIVE_Chdir( drive, "\\" );
370         DRIVE_LastTask = GetCurrentTask();
371     }
372     return DOSDrives[drive].dos_cwd;
373 }
374
375
376 /***********************************************************************
377  *           DRIVE_GetUnixCwd
378  */
379 const char * DRIVE_GetUnixCwd( int drive )
380 {
381     TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
382     if (!DRIVE_IsValid( drive )) return NULL;
383
384     /* Check if we need to change the directory to the new task. */
385     if (pTask && (pTask->curdrive & 0x80) &&    /* The task drive is valid */
386         ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
387         (DRIVE_LastTask != GetCurrentTask()))   /* and the task changed */
388     {
389         /* Perform the task-switch */
390         if (!DRIVE_Chdir( drive, pTask->curdir )) DRIVE_Chdir( drive, "\\" );
391         DRIVE_LastTask = GetCurrentTask();
392     }
393     return DOSDrives[drive].unix_cwd;
394 }
395
396
397 /***********************************************************************
398  *           DRIVE_GetLabel
399  */
400 const char * DRIVE_GetLabel( int drive )
401 {
402     if (!DRIVE_IsValid( drive )) return NULL;
403     return DOSDrives[drive].label;
404 }
405
406
407 /***********************************************************************
408  *           DRIVE_GetSerialNumber
409  */
410 DWORD DRIVE_GetSerialNumber( int drive )
411 {
412     if (!DRIVE_IsValid( drive )) return 0;
413     return DOSDrives[drive].serial;
414 }
415
416
417 /***********************************************************************
418  *           DRIVE_SetSerialNumber
419  */
420 int DRIVE_SetSerialNumber( int drive, DWORD serial )
421 {
422     if (!DRIVE_IsValid( drive )) return 0;
423     DOSDrives[drive].serial = serial;
424     return 1;
425 }
426
427
428 /***********************************************************************
429  *           DRIVE_GetType
430  */
431 DRIVETYPE DRIVE_GetType( int drive )
432 {
433     if (!DRIVE_IsValid( drive )) return TYPE_INVALID;
434     return DOSDrives[drive].type;
435 }
436
437
438 /***********************************************************************
439  *           DRIVE_GetFlags
440  */
441 UINT DRIVE_GetFlags( int drive )
442 {
443     if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
444     return DOSDrives[drive].flags;
445 }
446
447
448 /***********************************************************************
449  *           DRIVE_Chdir
450  */
451 int DRIVE_Chdir( int drive, const char *path )
452 {
453     DOS_FULL_NAME full_name;
454     char buffer[MAX_PATHNAME_LEN];
455     LPSTR unix_cwd;
456     BY_HANDLE_FILE_INFORMATION info;
457     TDB *pTask = (TDB *)GlobalLock16( GetCurrentTask() );
458
459     strcpy( buffer, "A:" );
460     buffer[0] += drive;
461     TRACE_(dosfs)("(%c:,%s)\n", buffer[0], path );
462     lstrcpynA( buffer + 2, path, sizeof(buffer) - 2 );
463
464     if (!DOSFS_GetFullName( buffer, TRUE, &full_name )) return 0;
465     if (!FILE_Stat( full_name.long_name, &info )) return 0;
466     if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
467     {
468         SetLastError( ERROR_FILE_NOT_FOUND );
469         return 0;
470     }
471     unix_cwd = full_name.long_name + strlen( DOSDrives[drive].root );
472     while (*unix_cwd == '/') unix_cwd++;
473
474     TRACE_(dosfs)("(%c:): unix_cwd=%s dos_cwd=%s\n",
475                    'A' + drive, unix_cwd, full_name.short_name + 3 );
476
477     HeapFree( SystemHeap, 0, DOSDrives[drive].dos_cwd );
478     HeapFree( SystemHeap, 0, DOSDrives[drive].unix_cwd );
479     DOSDrives[drive].dos_cwd  = HEAP_strdupA( SystemHeap, 0,
480                                               full_name.short_name + 3 );
481     DOSDrives[drive].unix_cwd = HEAP_strdupA( SystemHeap, 0, unix_cwd );
482
483     if (pTask && (pTask->curdrive & 0x80) && 
484         ((pTask->curdrive & ~0x80) == drive))
485     {
486         lstrcpynA( pTask->curdir, full_name.short_name + 2,
487                      sizeof(pTask->curdir) );
488         DRIVE_LastTask = GetCurrentTask();
489     }
490     return 1;
491 }
492
493
494 /***********************************************************************
495  *           DRIVE_Disable
496  */
497 int DRIVE_Disable( int drive  )
498 {
499     if ((drive < 0) || (drive >= MAX_DOS_DRIVES) || !DOSDrives[drive].root)
500     {
501         SetLastError( ERROR_INVALID_DRIVE );
502         return 0;
503     }
504     DOSDrives[drive].flags |= DRIVE_DISABLED;
505     return 1;
506 }
507
508
509 /***********************************************************************
510  *           DRIVE_Enable
511  */
512 int DRIVE_Enable( int drive  )
513 {
514     if ((drive < 0) || (drive >= MAX_DOS_DRIVES) || !DOSDrives[drive].root)
515     {
516         SetLastError( ERROR_INVALID_DRIVE );
517         return 0;
518     }
519     DOSDrives[drive].flags &= ~DRIVE_DISABLED;
520     return 1;
521 }
522
523
524 /***********************************************************************
525  *           DRIVE_SetLogicalMapping
526  */
527 int DRIVE_SetLogicalMapping ( int existing_drive, int new_drive )
528 {
529  /* If new_drive is already valid, do nothing and return 0
530     otherwise, copy DOSDrives[existing_drive] to DOSDrives[new_drive] */
531   
532     DOSDRIVE *old, *new;
533     
534     old = DOSDrives + existing_drive;
535     new = DOSDrives + new_drive;
536
537     if ((existing_drive < 0) || (existing_drive >= MAX_DOS_DRIVES) ||
538         !old->root ||
539         (new_drive < 0) || (new_drive >= MAX_DOS_DRIVES))
540     {
541         SetLastError( ERROR_INVALID_DRIVE );
542         return 0;
543     }
544
545     if ( new->root )
546     {
547         TRACE_(dosfs)("Can\'t map drive %c to drive %c - "
548                                 "drive %c already exists\n",
549                         'A' + existing_drive, 'A' + new_drive,
550                         'A' + new_drive );
551         /* it is already mapped there, so return success */
552         if (!strcmp(old->root,new->root))
553             return 1;
554         return 0;
555     }
556
557     new->root = HEAP_strdupA( SystemHeap, 0, old->root );
558     new->dos_cwd = HEAP_strdupA( SystemHeap, 0, old->dos_cwd );
559     new->unix_cwd = HEAP_strdupA( SystemHeap, 0, old->unix_cwd );
560     memcpy ( new->label, old->label, 12 );
561     new->serial = old->serial;
562     new->type = old->type;
563     new->flags = old->flags;
564     new->dev = old->dev;
565     new->ino = old->ino;
566
567     TRACE_(dosfs)("Drive %c is now equal to drive %c\n",
568                     'A' + new_drive, 'A' + existing_drive );
569
570     return 1;
571 }
572
573
574 /***********************************************************************
575  *           DRIVE_OpenDevice
576  *
577  * Open the drive raw device and return a Unix fd (or -1 on error).
578  */
579 int DRIVE_OpenDevice( int drive, int flags )
580 {
581     if (!DRIVE_IsValid( drive )) return -1;
582     return open( DOSDrives[drive].device, flags );
583 }
584
585
586 /***********************************************************************
587  *           DRIVE_RawRead
588  *
589  * Read raw sectors from a device
590  */
591 int DRIVE_RawRead(BYTE drive, DWORD begin, DWORD nr_sect, BYTE *dataptr, BOOL fake_success)
592 {
593     int fd;
594
595     if ((fd = DRIVE_OpenDevice( drive, O_RDONLY )) != -1)
596     {
597         lseek( fd, begin * 512, SEEK_SET );
598         /* FIXME: check errors */
599         read( fd, dataptr, nr_sect * 512 );
600         close( fd );
601     }
602     else
603     {
604         memset(dataptr, 0, nr_sect * 512);
605                 if (fake_success)
606         {
607                         if (begin == 0 && nr_sect > 1) *(dataptr + 512) = 0xf8;
608                         if (begin == 1) *dataptr = 0xf8;
609                 }
610                 else
611                 return 0;
612     }
613         return 1;
614 }
615
616
617 /***********************************************************************
618  *           DRIVE_RawWrite
619  *
620  * Write raw sectors to a device
621  */
622 int DRIVE_RawWrite(BYTE drive, DWORD begin, DWORD nr_sect, BYTE *dataptr, BOOL fake_success)
623 {
624         int fd;
625
626     if ((fd = DRIVE_OpenDevice( drive, O_RDONLY )) != -1)
627     {
628         lseek( fd, begin * 512, SEEK_SET );
629         /* FIXME: check errors */
630         write( fd, dataptr, nr_sect * 512 );
631         close( fd );
632     }
633     else
634         if (!(fake_success))
635                 return 0;
636
637         return 1;
638 }
639
640
641 /***********************************************************************
642  *           DRIVE_GetFreeSpace
643  */
644 static int DRIVE_GetFreeSpace( int drive, PULARGE_INTEGER size, 
645                                PULARGE_INTEGER available )
646 {
647     struct statfs info;
648     unsigned long long  bigsize,bigavail=0;
649
650     if (!DRIVE_IsValid(drive))
651     {
652         SetLastError( ERROR_INVALID_DRIVE );
653         return 0;
654     }
655
656 /* FIXME: add autoconf check for this */
657 #if defined(__svr4__) || defined(_SCO_DS) || defined(__sun)
658     if (statfs( DOSDrives[drive].root, &info, 0, 0) < 0)
659 #else
660     if (statfs( DOSDrives[drive].root, &info) < 0)
661 #endif
662     {
663         FILE_SetDosError();
664         WARN_(dosfs)("cannot do statfs(%s)\n", DOSDrives[drive].root);
665         return 0;
666     }
667
668     bigsize = (unsigned long long)info.f_bsize 
669       * (unsigned long long)info.f_blocks;
670 #ifdef STATFS_HAS_BAVAIL
671     bigavail = (unsigned long long)info.f_bavail 
672       * (unsigned long long)info.f_bsize;
673 #else
674 # ifdef STATFS_HAS_BFREE
675     bigavail = (unsigned long long)info.f_bfree 
676       * (unsigned long long)info.f_bsize;
677 # else
678 #  error "statfs has no bfree/bavail member!"
679 # endif
680 #endif
681     size->LowPart = (DWORD)bigsize;
682     size->HighPart = (DWORD)(bigsize>>32);
683     available->LowPart = (DWORD)bigavail;
684     available->HighPart = (DWORD)(bigavail>>32);
685     return 1;
686 }
687
688
689 /***********************************************************************
690  *           GetDiskFreeSpace16   (KERNEL.422)
691  */
692 BOOL16 WINAPI GetDiskFreeSpace16( LPCSTR root, LPDWORD cluster_sectors,
693                                   LPDWORD sector_bytes, LPDWORD free_clusters,
694                                   LPDWORD total_clusters )
695 {
696     return GetDiskFreeSpaceA( root, cluster_sectors, sector_bytes,
697                                 free_clusters, total_clusters );
698 }
699
700
701 /***********************************************************************
702  *           GetDiskFreeSpace32A   (KERNEL32.206)
703  *
704  * Fails if expression resulting from current drive's dir and "root"
705  * is not a root dir of the target drive.
706  *
707  * UNDOC: setting some LPDWORDs to NULL is perfectly possible 
708  * if the corresponding info is unneeded.
709  *
710  * FIXME: needs to support UNC names from Win95 OSR2 on.
711  *
712  * Behaviour under Win95a:
713  * CurrDir     root   result
714  * "E:\\TEST"  "E:"   FALSE
715  * "E:\\"      "E:"   TRUE
716  * "E:\\"      "E"    FALSE
717  * "E:\\"      "\\"   TRUE
718  * "E:\\TEST"  "\\"   TRUE
719  * "E:\\TEST"  ":\\"  FALSE
720  * "E:\\TEST"  "E:\\" TRUE
721  * "E:\\TEST"  ""     FALSE
722  * "E:\\"      ""     FALSE (!)
723  * "E:\\"      0x0    TRUE
724  * "E:\\TEST"  0x0    TRUE  (!)
725  * "E:\\TEST"  "C:"   TRUE  (when CurrDir of "C:" set to "\\")
726  * "E:\\TEST"  "C:"   FALSE (when CurrDir of "C:" set to "\\TEST")
727  */
728 BOOL WINAPI GetDiskFreeSpaceA( LPCSTR root, LPDWORD cluster_sectors,
729                                    LPDWORD sector_bytes, LPDWORD free_clusters,
730                                    LPDWORD total_clusters )
731 {
732     int drive;
733     ULARGE_INTEGER size,available;
734     LPCSTR path;
735     DWORD cluster_sec;
736
737     if ((!root) || (root == "\\"))
738         drive = DRIVE_GetCurrentDrive();
739     else
740     if ( (strlen(root) >= 2) && (root[1] == ':')) /* root contains drive tag */
741     {
742         drive = toupper(root[0]) - 'A';
743         path = &root[2];
744         if (path[0] == '\0')
745             path = DRIVE_GetDosCwd(drive);
746         else
747         if (path[0] == '\\')
748             path++;
749         if (strlen(path)) /* oops, we are in a subdir */
750             return FALSE;
751     }
752     else
753         return FALSE;
754
755     if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
756
757     /* Cap the size and available at 2GB as per specs.  */
758     if ((size.HighPart) ||(size.LowPart > 0x7fffffff))
759         {
760           size.HighPart = 0;
761           size.LowPart = 0x7fffffff;
762         }
763     if ((available.HighPart) ||(available.LowPart > 0x7fffffff))
764       {
765         available.HighPart =0;
766         available.LowPart = 0x7fffffff;
767       }
768     if (DRIVE_GetType(drive)==TYPE_CDROM) {
769         if (sector_bytes)
770         *sector_bytes    = 2048;
771         size.LowPart            /= 2048;
772         available.LowPart       /= 2048;
773     } else {
774         if (sector_bytes)
775         *sector_bytes    = 512;
776         size.LowPart            /= 512;
777         available.LowPart       /= 512;
778     }
779     /* fixme: probably have to adjust those variables too for CDFS */
780     cluster_sec = 1;
781     while (cluster_sec * 65536 < size.LowPart) cluster_sec *= 2;
782
783     if (cluster_sectors)
784         *cluster_sectors = cluster_sec;
785     if (free_clusters)
786         *free_clusters   = available.LowPart / cluster_sec;
787     if (total_clusters)
788         *total_clusters  = size.LowPart / cluster_sec;
789     return TRUE;
790 }
791
792
793 /***********************************************************************
794  *           GetDiskFreeSpace32W   (KERNEL32.207)
795  */
796 BOOL WINAPI GetDiskFreeSpaceW( LPCWSTR root, LPDWORD cluster_sectors,
797                                    LPDWORD sector_bytes, LPDWORD free_clusters,
798                                    LPDWORD total_clusters )
799 {
800     LPSTR xroot;
801     BOOL ret;
802
803     xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, root);
804     ret = GetDiskFreeSpaceA( xroot,cluster_sectors, sector_bytes,
805                                free_clusters, total_clusters );
806     HeapFree( GetProcessHeap(), 0, xroot );
807     return ret;
808 }
809
810
811 /***********************************************************************
812  *           GetDiskFreeSpaceEx32A   (KERNEL32.871)
813  */
814 BOOL WINAPI GetDiskFreeSpaceExA( LPCSTR root,
815                                      PULARGE_INTEGER avail,
816                                      PULARGE_INTEGER total,
817                                      PULARGE_INTEGER totalfree)
818 {
819     int drive;
820     ULARGE_INTEGER size,available;
821
822     if (!root) drive = DRIVE_GetCurrentDrive();
823     else
824     {
825         if ((root[1]) && ((root[1] != ':') || (root[2] != '\\')))
826         {
827             WARN_(dosfs)("invalid root '%s'\n", root );
828             return FALSE;
829         }
830         drive = toupper(root[0]) - 'A';
831     }
832     if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
833     /*FIXME: Do we have the number of bytes available to the user? */
834     if (totalfree) {
835         totalfree->HighPart = size.HighPart;
836         totalfree->LowPart = size.LowPart ;
837     }
838     if (avail) {
839         avail->HighPart = available.HighPart;
840         avail->LowPart = available.LowPart ;
841     }
842     return TRUE;
843 }
844
845 /***********************************************************************
846  *           GetDiskFreeSpaceEx32W   (KERNEL32.873)
847  */
848 BOOL WINAPI GetDiskFreeSpaceExW( LPCWSTR root, PULARGE_INTEGER avail,
849                                      PULARGE_INTEGER total,
850                                      PULARGE_INTEGER  totalfree)
851 {
852     LPSTR xroot;
853     BOOL ret;
854
855     xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, root);
856     ret = GetDiskFreeSpaceExA( xroot, avail, total, totalfree);
857     HeapFree( GetProcessHeap(), 0, xroot );
858     return ret;
859 }
860
861 /***********************************************************************
862  *           GetDriveType16   (KERNEL.136)
863  * This functions returns the drivetype of a drive in Win16. 
864  * Note that it returns DRIVE_REMOTE for CD-ROMs, since MSCDEX uses the
865  * remote drive API. The returnvalue DRIVE_REMOTE for CD-ROMs has been
866  * verified on Win3.11 and Windows 95. Some programs rely on it, so don't
867  * do any pseudo-clever changes.
868  *
869  * RETURNS
870  *      drivetype DRIVE_xxx
871  */
872 UINT16 WINAPI GetDriveType16(
873         UINT16 drive    /* [in] number (NOT letter) of drive */
874 ) {
875     TRACE_(dosfs)("(%c:)\n", 'A' + drive );
876     switch(DRIVE_GetType(drive))
877     {
878     case TYPE_FLOPPY:  return DRIVE_REMOVABLE;
879     case TYPE_HD:      return DRIVE_FIXED;
880     case TYPE_CDROM:   return DRIVE_REMOTE;
881     case TYPE_NETWORK: return DRIVE_REMOTE;
882     case TYPE_INVALID:
883     default:           return DRIVE_CANNOTDETERMINE;
884     }
885 }
886
887
888 /***********************************************************************
889  *           GetDriveType32A   (KERNEL32.208)
890  *
891  * Returns the type of the disk drive specified.  If root is NULL the
892  * root of the current directory is used.
893  *
894  * RETURNS
895  *
896  *  Type of drive (from Win32 SDK):
897  *
898  *   DRIVE_UNKNOWN     unable to find out anything about the drive
899  *   DRIVE_NO_ROOT_DIR nonexistand root dir
900  *   DRIVE_REMOVABLE   the disk can be removed from the machine
901  *   DRIVE_FIXED       the disk can not be removed from the machine
902  *   DRIVE_REMOTE      network disk
903  *   DRIVE_CDROM       CDROM drive
904  *   DRIVE_RAMDISK     virtual disk in ram
905  *
906  *   DRIVE_DOESNOTEXIST    XXX Not valid return value
907  *   DRIVE_CANNOTDETERMINE XXX Not valid return value
908  *   
909  * BUGS
910  *
911  *  Currently returns DRIVE_DOESNOTEXIST and DRIVE_CANNOTDETERMINE
912  *  when it really should return DRIVE_NO_ROOT_DIR and DRIVE_UNKNOWN.
913  *  Why where the former defines used?
914  *
915  *  DRIVE_RAMDISK is unsupported.
916  */
917 UINT WINAPI GetDriveTypeA(LPCSTR root /* String describing drive */)
918 {
919     int drive;
920     TRACE_(dosfs)("(%s)\n", debugstr_a(root));
921
922     if (NULL == root) drive = DRIVE_GetCurrentDrive();
923     else
924     {
925         if ((root[1]) && (root[1] != ':'))
926         {
927             WARN_(dosfs)("invalid root '%s'\n", debugstr_a(root));
928             return DRIVE_DOESNOTEXIST;
929         }
930         drive = toupper(root[0]) - 'A';
931     }
932     switch(DRIVE_GetType(drive))
933     {
934     case TYPE_FLOPPY:  return DRIVE_REMOVABLE;
935     case TYPE_HD:      return DRIVE_FIXED;
936     case TYPE_CDROM:   return DRIVE_CDROM;
937     case TYPE_NETWORK: return DRIVE_REMOTE;
938     case TYPE_INVALID: return DRIVE_DOESNOTEXIST;
939     default:           return DRIVE_CANNOTDETERMINE;
940     }
941 }
942
943
944 /***********************************************************************
945  *           GetDriveType32W   (KERNEL32.209)
946  */
947 UINT WINAPI GetDriveTypeW( LPCWSTR root )
948 {
949     LPSTR xpath = HEAP_strdupWtoA( GetProcessHeap(), 0, root );
950     UINT ret = GetDriveTypeA( xpath );
951     HeapFree( GetProcessHeap(), 0, xpath );
952     return ret;
953 }
954
955
956 /***********************************************************************
957  *           GetCurrentDirectory16   (KERNEL.411)
958  */
959 UINT16 WINAPI GetCurrentDirectory16( UINT16 buflen, LPSTR buf )
960 {
961     return (UINT16)GetCurrentDirectoryA( buflen, buf );
962 }
963
964
965 /***********************************************************************
966  *           GetCurrentDirectory32A   (KERNEL32.196)
967  *
968  * Returns "X:\\path\\etc\\".
969  *
970  * Despite the API description, return required length including the 
971  * terminating null when buffer too small. This is the real behaviour.
972  */
973 UINT WINAPI GetCurrentDirectoryA( UINT buflen, LPSTR buf )
974 {
975     UINT ret;
976     const char *s = DRIVE_GetDosCwd( DRIVE_GetCurrentDrive() );
977
978     assert(s);
979     ret = strlen(s) + 3; /* length of WHOLE current directory */
980     if (ret >= buflen) return ret + 1;
981     lstrcpynA( buf, "A:\\", MIN( 4, buflen ) );
982     if (buflen) buf[0] += DRIVE_GetCurrentDrive();
983     if (buflen > 3) lstrcpynA( buf + 3, s, buflen - 3 );
984     return ret;
985 }
986
987
988 /***********************************************************************
989  *           GetCurrentDirectory32W   (KERNEL32.197)
990  */
991 UINT WINAPI GetCurrentDirectoryW( UINT buflen, LPWSTR buf )
992 {
993     LPSTR xpath = HeapAlloc( GetProcessHeap(), 0, buflen+1 );
994     UINT ret = GetCurrentDirectoryA( buflen, xpath );
995     lstrcpyAtoW( buf, xpath );
996     HeapFree( GetProcessHeap(), 0, xpath );
997     return ret;
998 }
999
1000
1001 /***********************************************************************
1002  *           SetCurrentDirectory   (KERNEL.412)
1003  */
1004 BOOL16 WINAPI SetCurrentDirectory16( LPCSTR dir )
1005 {
1006     return SetCurrentDirectoryA( dir );
1007 }
1008
1009
1010 /***********************************************************************
1011  *           SetCurrentDirectory32A   (KERNEL32.479)
1012  */
1013 BOOL WINAPI SetCurrentDirectoryA( LPCSTR dir )
1014 {
1015     int olddrive, drive = DRIVE_GetCurrentDrive();
1016
1017     if (!dir) {
1018         ERR_(file)("(NULL)!\n");
1019         return FALSE;
1020     }
1021     if (dir[0] && (dir[1]==':'))
1022     {
1023         drive = tolower( *dir ) - 'a';
1024         dir += 2;
1025     }
1026
1027     /* WARNING: we need to set the drive before the dir, as DRIVE_Chdir
1028        sets pTask->curdir only if pTask->curdrive is drive */
1029     olddrive = drive; /* in case DRIVE_Chdir fails */
1030     if (!(DRIVE_SetCurrentDrive( drive )))
1031         return FALSE;
1032     /* FIXME: what about empty strings? Add a \\ ? */
1033     if (!DRIVE_Chdir( drive, dir )) {
1034         DRIVE_SetCurrentDrive(olddrive);
1035         return FALSE;
1036     }
1037     return TRUE;
1038 }
1039
1040
1041 /***********************************************************************
1042  *           SetCurrentDirectory32W   (KERNEL32.480)
1043  */
1044 BOOL WINAPI SetCurrentDirectoryW( LPCWSTR dirW )
1045 {
1046     LPSTR dir = HEAP_strdupWtoA( GetProcessHeap(), 0, dirW );
1047     BOOL res = SetCurrentDirectoryA( dir );
1048     HeapFree( GetProcessHeap(), 0, dir );
1049     return res;
1050 }
1051
1052
1053 /***********************************************************************
1054  *           GetLogicalDriveStrings32A   (KERNEL32.231)
1055  */
1056 UINT WINAPI GetLogicalDriveStringsA( UINT len, LPSTR buffer )
1057 {
1058     int drive, count;
1059
1060     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1061         if (DRIVE_IsValid(drive)) count++;
1062     if (count * 4 * sizeof(char) <= len)
1063     {
1064         LPSTR p = buffer;
1065         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1066             if (DRIVE_IsValid(drive))
1067             {
1068                 *p++ = 'a' + drive;
1069                 *p++ = ':';
1070                 *p++ = '\\';
1071                 *p++ = '\0';
1072             }
1073         *p = '\0';
1074     }
1075     return count * 4 * sizeof(char);
1076 }
1077
1078
1079 /***********************************************************************
1080  *           GetLogicalDriveStrings32W   (KERNEL32.232)
1081  */
1082 UINT WINAPI GetLogicalDriveStringsW( UINT len, LPWSTR buffer )
1083 {
1084     int drive, count;
1085
1086     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1087         if (DRIVE_IsValid(drive)) count++;
1088     if (count * 4 * sizeof(WCHAR) <= len)
1089     {
1090         LPWSTR p = buffer;
1091         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1092             if (DRIVE_IsValid(drive))
1093             {
1094                 *p++ = (WCHAR)('a' + drive);
1095                 *p++ = (WCHAR)':';
1096                 *p++ = (WCHAR)'\\';
1097                 *p++ = (WCHAR)'\0';
1098             }
1099         *p = (WCHAR)'\0';
1100     }
1101     return count * 4 * sizeof(WCHAR);
1102 }
1103
1104
1105 /***********************************************************************
1106  *           GetLogicalDrives   (KERNEL32.233)
1107  */
1108 DWORD WINAPI GetLogicalDrives(void)
1109 {
1110     DWORD ret = 0;
1111     int drive;
1112
1113     for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1114         if (DRIVE_IsValid(drive)) ret |= (1 << drive);
1115     return ret;
1116 }
1117
1118
1119 /***********************************************************************
1120  *           GetVolumeInformation32A   (KERNEL32.309)
1121  */
1122 BOOL WINAPI GetVolumeInformationA( LPCSTR root, LPSTR label,
1123                                        DWORD label_len, DWORD *serial,
1124                                        DWORD *filename_len, DWORD *flags,
1125                                        LPSTR fsname, DWORD fsname_len )
1126 {
1127     int drive;
1128     char *cp;
1129
1130     /* FIXME, SetLastErrors missing */
1131
1132     if (!root) drive = DRIVE_GetCurrentDrive();
1133     else
1134     {
1135         if ((root[1]) && (root[1] != ':'))
1136         {
1137             WARN_(dosfs)("invalid root '%s'\n",root);
1138             return FALSE;
1139         }
1140         drive = toupper(root[0]) - 'A';
1141     }
1142     if (!DRIVE_IsValid( drive )) return FALSE;
1143     if (label)
1144     {
1145        lstrcpynA( label, DRIVE_GetLabel(drive), label_len );
1146        for (cp = label; *cp; cp++);
1147        while (cp != label && *(cp-1) == ' ') cp--;
1148        *cp = '\0';
1149     }
1150     if (serial) *serial = DRIVE_GetSerialNumber(drive);
1151
1152     /* Set the filesystem information */
1153     /* Note: we only emulate a FAT fs at the present */
1154
1155     if (filename_len) {
1156         if (DOSDrives[drive].flags & DRIVE_SHORT_NAMES)
1157             *filename_len = 12;
1158         else
1159             *filename_len = 255;
1160     }
1161     if (flags)
1162       {
1163        *flags=0;
1164        if (DOSDrives[drive].flags & DRIVE_CASE_SENSITIVE)
1165          *flags|=FS_CASE_SENSITIVE;
1166        if (DOSDrives[drive].flags & DRIVE_CASE_PRESERVING)
1167          *flags|=FS_CASE_IS_PRESERVED ;
1168       }
1169     if (fsname) {
1170         /* Diablo checks that return code ... */
1171         if (DRIVE_GetType(drive)==TYPE_CDROM)
1172             lstrcpynA( fsname, "CDFS", fsname_len );
1173         else
1174             lstrcpynA( fsname, "FAT", fsname_len );
1175     }
1176     return TRUE;
1177 }
1178
1179
1180 /***********************************************************************
1181  *           GetVolumeInformation32W   (KERNEL32.310)
1182  */
1183 BOOL WINAPI GetVolumeInformationW( LPCWSTR root, LPWSTR label,
1184                                        DWORD label_len, DWORD *serial,
1185                                        DWORD *filename_len, DWORD *flags,
1186                                        LPWSTR fsname, DWORD fsname_len )
1187 {
1188     LPSTR xroot    = HEAP_strdupWtoA( GetProcessHeap(), 0, root );
1189     LPSTR xvolname = label ? HeapAlloc(GetProcessHeap(),0,label_len) : NULL;
1190     LPSTR xfsname  = fsname ? HeapAlloc(GetProcessHeap(),0,fsname_len) : NULL;
1191     BOOL ret = GetVolumeInformationA( xroot, xvolname, label_len, serial,
1192                                           filename_len, flags, xfsname,
1193                                           fsname_len );
1194     if (ret)
1195     {
1196         if (label) lstrcpyAtoW( label, xvolname );
1197         if (fsname) lstrcpyAtoW( fsname, xfsname );
1198     }
1199     HeapFree( GetProcessHeap(), 0, xroot );
1200     HeapFree( GetProcessHeap(), 0, xvolname );
1201     HeapFree( GetProcessHeap(), 0, xfsname );
1202     return ret;
1203 }
1204
1205 /***********************************************************************
1206  *           SetVolumeLabelA   (KERNEL32.675)
1207  */
1208 BOOL WINAPI SetVolumeLabelA(LPCSTR rootpath,LPCSTR volname)
1209 {
1210         FIXME_(dosfs)("(%s,%s),stub!\n",rootpath,volname);
1211         return TRUE;
1212 }
1213
1214 /***********************************************************************
1215  *           SetVolumeLabelW   (KERNEL32.676)
1216  */
1217 BOOL WINAPI SetVolumeLabelW(LPCWSTR rootpath,LPCWSTR volname)
1218 {
1219     LPSTR xroot, xvol;
1220     BOOL ret;
1221
1222     xroot = HEAP_strdupWtoA( GetProcessHeap(), 0, rootpath);
1223     xvol = HEAP_strdupWtoA( GetProcessHeap(), 0, volname);
1224     ret = SetVolumeLabelA( xroot, xvol );
1225     HeapFree( GetProcessHeap(), 0, xroot );
1226     HeapFree( GetProcessHeap(), 0, xvol );
1227     return ret;
1228 }