Release 961102
[wine] / files / dos_fs.c
1 /*
2  * DOS file system functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 1996 Alexandre Julliard
6  */
7
8 #include <sys/types.h>
9 #include <ctype.h>
10 #include <dirent.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <sys/stat.h>
15 #include <time.h>
16 #if defined(__svr4__) || defined(_SCO_DS)
17 #include <sys/statfs.h>
18 #endif
19
20 #include "windows.h"
21 #include "dos_fs.h"
22 #include "drive.h"
23 #include "file.h"
24 #include "msdos.h"
25 #include "stddebug.h"
26 #include "debug.h"
27 #include "xmalloc.h"
28 #include "string32.h"
29
30 /* Chars we don't want to see in DOS file names */
31 #define INVALID_DOS_CHARS  "*?<>|\"+=,;[] \345"
32
33 static const char *DOSFS_Devices[][2] =
34 {
35     { "CON",  "" },
36     { "PRN",  "" },
37     { "NUL",  "/dev/null" },
38     { "AUX",  "" },
39     { "LPT1", "" },
40     { "LPT2", "" },
41     { "LPT3", "" },
42     { "LPT4", "" },
43     { "COM1", "" },
44     { "COM2", "" },
45     { "COM3", "" },
46     { "COM4", "" }
47 };
48
49 #define GET_DRIVE(path) \
50     (((path)[1] == ':') ? toupper((path)[0]) - 'A' : DOSFS_CurDrive)
51
52     /* DOS extended error status */
53 WORD DOS_ExtendedError;
54 BYTE DOS_ErrorClass;
55 BYTE DOS_ErrorAction;
56 BYTE DOS_ErrorLocus;
57
58
59 /***********************************************************************
60  *           DOSFS_ValidDOSName
61  *
62  * Return 1 if Unix file 'name' is also a valid MS-DOS name
63  * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
64  * File name can be terminated by '\0', '\\' or '/'.
65  */
66 static int DOSFS_ValidDOSName( const char *name, int ignore_case )
67 {
68     static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
69     const char *p = name;
70     const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
71     int len = 0;
72
73     if (*p == '.')
74     {
75         /* Check for "." and ".." */
76         p++;
77         if (*p == '.') p++;
78         /* All other names beginning with '.' are invalid */
79         return (IS_END_OF_NAME(*p));
80     }
81     while (!IS_END_OF_NAME(*p))
82     {
83         if (strchr( invalid, *p )) return 0;  /* Invalid char */
84         if (*p == '.') break;  /* Start of the extension */
85         if (++len > 8) return 0;  /* Name too long */
86         p++;
87     }
88     if (*p != '.') return 1;  /* End of name */
89     p++;
90     if (IS_END_OF_NAME(*p)) return 0;  /* Empty extension not allowed */
91     len = 0;
92     while (!IS_END_OF_NAME(*p))
93     {
94         if (strchr( invalid, *p )) return 0;  /* Invalid char */
95         if (*p == '.') return 0;  /* Second extension not allowed */
96         if (++len > 3) return 0;  /* Extension too long */
97         p++;
98     }
99     return 1;
100 }
101
102
103 /***********************************************************************
104  *           DOSFS_CheckDotDot
105  *
106  * Remove all '.' and '..' at the beginning of 'name'.
107  */
108 static const char * DOSFS_CheckDotDot( const char *name, char *buffer,
109                                        char sep , int *len )
110 {
111     char *p = buffer + strlen(buffer);
112
113     while (*name == '.')
114     {
115         if (IS_END_OF_NAME(name[1]))
116         {
117             name++;
118             while ((*name == '\\') || (*name == '/')) name++;
119         }
120         else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
121         {
122             name += 2;
123             while ((*name == '\\') || (*name == '/')) name++;
124             while ((p > buffer) && (*p != sep)) { p--; (*len)++; }
125             *p = '\0';  /* Remove trailing separator */
126         }
127         else break;
128     }
129     return name;
130 }
131
132
133 /***********************************************************************
134  *           DOSFS_ToDosFCBFormat
135  *
136  * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
137  * expanding wild cards and converting to upper-case in the process.
138  * File name can be terminated by '\0', '\\' or '/'.
139  * Return NULL if the name is not a valid DOS name.
140  */
141 const char *DOSFS_ToDosFCBFormat( const char *name )
142 {
143     static const char invalid_chars[] = INVALID_DOS_CHARS;
144     static char buffer[12];
145     const char *p = name;
146     int i;
147
148     /* Check for "." and ".." */
149     if (*p == '.')
150     {
151         p++;
152         strcpy( buffer, ".          " );
153         if (*p == '.') p++;
154         return (!*p || (*p == '/') || (*p == '\\')) ? buffer : NULL;
155     }
156
157     for (i = 0; i < 8; i++)
158     {
159         switch(*p)
160         {
161         case '\0':
162         case '\\':
163         case '/':
164         case '.':
165             buffer[i] = ' ';
166             break;
167         case '?':
168             p++;
169             /* fall through */
170         case '*':
171             buffer[i] = '?';
172             break;
173         default:
174             if (strchr( invalid_chars, *p )) return NULL;
175             buffer[i] = toupper(*p);
176             p++;
177             break;
178         }
179     }
180
181     if (*p == '*')
182     {
183         /* Skip all chars after wildcard up to first dot */
184         while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
185     }
186     else
187     {
188         /* Check if name too long */
189         if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return NULL;
190     }
191     if (*p == '.') p++;  /* Skip dot */
192
193     for (i = 8; i < 11; i++)
194     {
195         switch(*p)
196         {
197         case '\0':
198         case '\\':
199         case '/':
200             buffer[i] = ' ';
201             break;
202         case '.':
203             return NULL;  /* Second extension not allowed */
204         case '?':
205             p++;
206             /* fall through */
207         case '*':
208             buffer[i] = '?';
209             break;
210         default:
211             if (strchr( invalid_chars, *p )) return NULL;
212             buffer[i] = toupper(*p);
213             p++;
214             break;
215         }
216     }
217     buffer[11] = '\0';
218     return buffer;
219 }
220
221
222 /***********************************************************************
223  *           DOSFS_ToDosDTAFormat
224  *
225  * Convert a file name from FCB to DTA format (name.ext, null-terminated)
226  * converting to upper-case in the process.
227  * File name can be terminated by '\0', '\\' or '/'.
228  * Return NULL if the name is not a valid DOS name.
229  */
230 const char *DOSFS_ToDosDTAFormat( const char *name )
231 {
232     static char buffer[13];
233     char *p;
234
235     memcpy( buffer, name, 8 );
236     for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--);
237     *p++ = '.';
238     memcpy( p, name + 8, 3 );
239     for (p += 3; p[-1] == ' '; p--);
240     if (p[-1] == '.') p--;
241     *p = '\0';
242     return buffer;
243 }
244
245
246 /***********************************************************************
247  *           DOSFS_MatchShort
248  *
249  * Check a DOS file name against a mask (both in FCB format).
250  */
251 static int DOSFS_MatchShort( const char *mask, const char *name )
252 {
253     int i;
254     for (i = 11; i > 0; i--, mask++, name++)
255         if ((*mask != '?') && (*mask != *name)) return 0;
256     return 1;
257 }
258
259
260 /***********************************************************************
261  *           DOSFS_MatchLong
262  *
263  * Check a long file name against a mask.
264  */
265 static int DOSFS_MatchLong( const char *mask, const char *name,
266                             int case_sensitive )
267 {
268     while (*name && *mask)
269     {
270         if (*mask == '*')
271         {
272             mask++;
273             while (*mask == '*') mask++;  /* Skip consecutive '*' */
274             if (!*mask) return 1;
275             if (case_sensitive) while (*name && (*name != *mask)) name++;
276             else while (*name && (toupper(*name) != toupper(*mask))) name++;
277             if (!*name) return 0;
278         }
279         else if (*mask != '?')
280         {
281             if (case_sensitive)
282             {
283                 if (*mask != *name) return 0;
284             }
285             else if (toupper(*mask) != toupper(*name)) return 0;
286         }
287         mask++;
288         name++;
289     }
290     return (!*name && !*mask);
291 }
292
293
294 /***********************************************************************
295  *           DOSFS_ToDosDateTime
296  *
297  * Convert a Unix time in the DOS date/time format.
298  */
299 void DOSFS_ToDosDateTime( time_t unixtime, WORD *pDate, WORD *pTime )
300 {
301     struct tm *tm = localtime( &unixtime );
302     if (pTime)
303         *pTime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
304     if (pDate)
305         *pDate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
306                  + tm->tm_mday;
307 }
308
309
310 /***********************************************************************
311  *           DOSFS_UnixTimeToFileTime
312  *
313  * Convert a Unix time to FILETIME format.
314  */
315 void DOSFS_UnixTimeToFileTime( time_t unixtime, FILETIME *filetime )
316 {
317     /* FIXME :-) */
318     filetime->dwLowDateTime  = unixtime;
319     filetime->dwHighDateTime = 0;
320 }
321
322
323 /***********************************************************************
324  *           DOSFS_FileTimeToUnixTime
325  *
326  * Convert a FILETIME format to Unix time.
327  */
328 time_t DOSFS_FileTimeToUnixTime( FILETIME *filetime )
329 {
330     /* FIXME :-) */
331     return filetime->dwLowDateTime;
332 }
333
334
335 /***********************************************************************
336  *           DOSFS_Hash
337  *
338  * Transform a Unix file name into a hashed DOS name. If the name is a valid
339  * DOS name, it is converted to upper-case; otherwise it is replaced by a
340  * hashed version that fits in 8.3 format.
341  * File name can be terminated by '\0', '\\' or '/'.
342  */
343 const char *DOSFS_Hash( const char *name, int dir_format, int ignore_case )
344 {
345     static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
346     static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
347
348     static char buffer[13];
349     const char *p, *ext;
350     char *dst;
351     unsigned short hash;
352     int i;
353
354     if (dir_format) strcpy( buffer, "           " );
355
356     if (DOSFS_ValidDOSName( name, ignore_case ))
357     {
358         /* Check for '.' and '..' */
359         if (*name == '.')
360         {
361             buffer[0] = '.';
362             if (!dir_format) buffer[1] = buffer[2] = '\0';
363             if (name[1] == '.') buffer[1] = '.';
364             return buffer;
365         }
366
367         /* Simply copy the name, converting to uppercase */
368
369         for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
370             *dst++ = toupper(*name);
371         if (*name == '.')
372         {
373             if (dir_format) dst = buffer + 8;
374             else *dst++ = '.';
375             for (name++; !IS_END_OF_NAME(*name); name++)
376                 *dst++ = toupper(*name);
377         }
378         if (!dir_format) *dst = '\0';
379     }
380     else
381     {
382         /* Compute the hash code of the file name */
383         /* If you know something about hash functions, feel free to */
384         /* insert a better algorithm here... */
385         if (ignore_case)
386         {
387             for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
388                 hash = (hash << 3) ^ (hash >> 5) ^ tolower(*p) ^ (tolower(p[1]) << 8);
389             hash = (hash << 3) ^ (hash >> 5) ^ tolower(*p); /* Last character*/
390         }
391         else
392         {
393             for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
394                 hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
395             hash = (hash << 3) ^ (hash >> 5) ^ *p;  /* Last character */
396         }
397
398         /* Find last dot for start of the extension */
399         for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
400             if (*p == '.') ext = p;
401         if (ext && IS_END_OF_NAME(ext[1]))
402             ext = NULL;  /* Empty extension ignored */
403
404         /* Copy first 4 chars, replacing invalid chars with '_' */
405         for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
406         {
407             if (IS_END_OF_NAME(*p) || (p == ext)) break;
408             *dst++ = strchr( invalid_chars, *p ) ? '_' : toupper(*p);
409         }
410         /* Pad to 5 chars with '~' */
411         while (i-- >= 0) *dst++ = '~';
412
413         /* Insert hash code converted to 3 ASCII chars */
414         *dst++ = hash_chars[(hash >> 10) & 0x1f];
415         *dst++ = hash_chars[(hash >> 5) & 0x1f];
416         *dst++ = hash_chars[hash & 0x1f];
417
418         /* Copy the first 3 chars of the extension (if any) */
419         if (ext)
420         {
421             if (!dir_format) *dst++ = '.';
422             for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
423                 *dst++ = toupper(*ext);
424         }
425         if (!dir_format) *dst = '\0';
426     }
427     return buffer;
428 }
429
430
431 /***********************************************************************
432  *           DOSFS_FindUnixName
433  *
434  * Find the Unix file name in a given directory that corresponds to
435  * a file name (either in Unix or DOS format).
436  * File name can be terminated by '\0', '\\' or '/'.
437  * Return 1 if OK, 0 if no file name matches.
438  */
439 static int DOSFS_FindUnixName( const char *path, const char *name,
440                                char *buffer, int maxlen, UINT32 drive_flags )
441 {
442     DIR *dir;
443     struct dirent *dirent;
444
445     const char *dos_name = DOSFS_ToDosFCBFormat( name );
446     const char *p = strchr( name, '/' );
447     int len = p ? (int)(p - name) : strlen(name);
448
449     dprintf_dosfs( stddeb, "DOSFS_FindUnixName: %s %s\n", path, name );
450
451     if ((p = strchr( name, '\\' ))) len = MIN( (int)(p - name), len );
452
453     if (!(dir = opendir( path )))
454     {
455         dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s): can't open dir\n",
456                        path, name );
457         return 0;
458     }
459     while ((dirent = readdir( dir )) != NULL)
460     {
461         /* Check against Unix name */
462         if (len == strlen(dirent->d_name))
463         {
464             if (drive_flags & DRIVE_CASE_SENSITIVE)
465             {
466                 if (!lstrncmp32A( dirent->d_name, name, len )) break;
467             }
468             else
469             {
470                 if (!lstrncmpi32A( dirent->d_name, name, len )) break;
471             }
472         }
473         if (dos_name)
474         {
475             /* Check against hashed DOS name */
476             const char *hash_name = DOSFS_Hash( dirent->d_name, TRUE,
477                                        !(drive_flags & DRIVE_CASE_SENSITIVE) );
478             if (!strcmp( dos_name, hash_name )) break;
479         }
480     }
481     if (dirent) lstrcpyn32A( buffer, dirent->d_name, maxlen );
482     closedir( dir );
483     dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s) -> %s\n",
484                    path, name, dirent ? buffer : "** Not found **" );
485     return (dirent != NULL);
486 }
487
488
489 /***********************************************************************
490  *           DOSFS_IsDevice
491  *
492  * Check if a DOS file name represents a DOS device. Returns the name
493  * of the associated Unix device, or NULL if not found.
494  */
495 const char *DOSFS_IsDevice( const char *name )
496 {
497     int i;
498     const char *p;
499
500     if (name[0] && (name[1] == ':')) name += 2;
501     if ((p = strrchr( name, '/' ))) name = p + 1;
502     if ((p = strrchr( name, '\\' ))) name = p + 1;
503     for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
504     {
505         const char *dev = DOSFS_Devices[i][0];
506         if (!lstrncmpi32A( dev, name, strlen(dev) ))
507         {
508             p = name + strlen( dev );
509             if (!*p || (*p == '.')) return DOSFS_Devices[i][1];
510         }
511     }
512     return NULL;
513 }
514
515
516 /***********************************************************************
517  *           DOSFS_GetUnixFileName
518  *
519  * Convert a file name (DOS or mixed DOS/Unix format) to a valid Unix name.
520  * Return NULL if one of the path components does not exist. The last path
521  * component is only checked if 'check_last' is non-zero.
522  */
523 const char * DOSFS_GetUnixFileName( const char * name, int check_last )
524 {
525     static char buffer[MAX_PATHNAME_LEN];
526     int drive, len, found;
527     UINT32 flags;
528     char *p, *root;
529
530     dprintf_dosfs( stddeb, "DOSFS_GetUnixFileName: %s\n", name );
531     if (name[0] && (name[1] == ':'))
532     {
533         drive = toupper(name[0]) - 'A';
534         name += 2;
535     }
536     else if (name[0] == '/') /* Absolute Unix path? */
537     {
538         if ((drive = DRIVE_FindDriveRoot( &name )) == -1)
539         {
540             fprintf( stderr, "Warning: %s not accessible from a DOS drive\n",
541                      name );
542             /* Assume it really was a DOS name */
543             drive = DRIVE_GetCurrentDrive();            
544         }
545     }
546     else drive = DRIVE_GetCurrentDrive();
547
548     if (!DRIVE_IsValid(drive))
549     {
550         DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk );
551         return NULL;
552     }
553     flags = DRIVE_GetFlags(drive);
554     lstrcpyn32A( buffer, DRIVE_GetRoot(drive), MAX_PATHNAME_LEN );
555     if (buffer[1]) root = buffer + strlen(buffer);
556     else root = buffer;  /* root directory */
557
558     if ((*name == '\\') || (*name == '/'))
559     {
560         while ((*name == '\\') || (*name == '/')) name++;
561     }
562     else
563     {
564         lstrcpyn32A( root + 1, DRIVE_GetUnixCwd(drive),
565                      MAX_PATHNAME_LEN - (int)(root - buffer) - 1 );
566         if (root[1]) *root = '/';
567     }
568
569     p = buffer[1] ? buffer + strlen(buffer) : buffer;
570     len = MAX_PATHNAME_LEN - strlen(buffer);
571     found = 1;
572     while (*name && found)
573     {
574         const char *newname = DOSFS_CheckDotDot( name, root, '/', &len );
575         if (newname != name)
576         {
577             p = root + strlen(root);
578             name = newname;
579             continue;
580         }
581         if (len <= 1)
582         {
583             DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
584             return NULL;
585         }
586         if ((found = DOSFS_FindUnixName( buffer, name, p+1, len-1, flags )))
587         {
588             *p = '/';
589             len -= strlen(p);
590             p += strlen(p);
591             while (!IS_END_OF_NAME(*name)) name++;
592         }
593         else if (!check_last)
594         {
595             *p++ = '/';
596             for (len--; !IS_END_OF_NAME(*name) && (len > 1); name++, len--)
597                 *p++ = tolower(*name);
598             *p = '\0';
599         }
600         while ((*name == '\\') || (*name == '/')) name++;
601     }
602     if (!found)
603     {
604         if (check_last)
605         {
606             DOS_ERROR( ER_FileNotFound, EC_NotFound, SA_Abort, EL_Disk );
607             return NULL;
608         }
609         if (*name)  /* Not last */
610         {
611             DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
612             return NULL;
613         }
614     }
615     if (!buffer[0]) strcpy( buffer, "/" );
616     dprintf_dosfs( stddeb, "DOSFS_GetUnixFileName: returning %s\n", buffer );
617     return buffer;
618 }
619
620
621 /***********************************************************************
622  *           DOSFS_GetDosTrueName
623  *
624  * Convert a file name (DOS or Unix format) to a complete DOS name.
625  * Return NULL if the path name is invalid or too long.
626  * The unix_format flag is a hint that the file name is in Unix format.
627  */
628 const char * DOSFS_GetDosTrueName( const char *name, int unix_format )
629 {
630     static char buffer[MAX_PATHNAME_LEN];
631     int drive, len;
632     UINT32 flags;
633     char *p;
634
635     dprintf_dosfs( stddeb, "DOSFS_GetDosTrueName(%s,%d)\n", name, unix_format);
636     if (name[0] && (name[1] == ':'))
637     {
638         drive = toupper(name[0]) - 'A';
639         name += 2;
640     }
641     else if (name[0] == '/') /* Absolute Unix path? */
642     {
643         if ((drive = DRIVE_FindDriveRoot( &name )) == -1)
644         {
645             fprintf( stderr, "Warning: %s not accessible from a DOS drive\n",
646                      name );
647             /* Assume it really was a DOS name */
648             drive = DRIVE_GetCurrentDrive();            
649         }
650     }
651     else drive = DRIVE_GetCurrentDrive();
652
653     if (!DRIVE_IsValid(drive))
654     {
655         DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk );
656         return NULL;
657     }
658
659     p = buffer;
660     *p++ = 'A' + drive;
661     *p++ = ':';
662     if (IS_END_OF_NAME(*name))
663     {
664         while ((*name == '\\') || (*name == '/')) name++;
665     }
666     else
667     {
668         *p++ = '\\';
669         lstrcpyn32A( p, DRIVE_GetDosCwd(drive), sizeof(buffer) - 3 );
670         if (*p) p += strlen(p); else p--;
671     }
672     *p = '\0';
673     len = MAX_PATHNAME_LEN - (int)(p - buffer);
674     flags = DRIVE_GetFlags(drive);
675
676     while (*name)
677     {
678         const char *newname = DOSFS_CheckDotDot( name, buffer+2, '\\', &len );
679         if (newname != name)
680         {
681             p = buffer + strlen(buffer);
682             name = newname;
683             continue;
684         }
685         if (len <= 1)
686         {
687             DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
688             return NULL;
689         }
690         *p++ = '\\';
691         if (unix_format)  /* Hash it into a DOS name */
692         {
693             lstrcpyn32A( p, DOSFS_Hash( name, FALSE,
694                                      !(flags & DRIVE_CASE_SENSITIVE) ),
695                          len );
696             len -= strlen(p);
697             p += strlen(p);
698             while (!IS_END_OF_NAME(*name)) name++;
699         }
700         else  /* Already DOS format, simply upper-case it */
701         {
702             while (!IS_END_OF_NAME(*name) && (len > 1))
703             {
704                 *p++ = toupper(*name);
705                 name++;
706                 len--;
707             }
708             *p = '\0';
709         }
710         while ((*name == '\\') || (*name == '/')) name++;
711     }
712     if (!buffer[2])
713     {
714         buffer[2] = '\\';
715         buffer[3] = '\0';
716     }
717     dprintf_dosfs( stddeb, "DOSFS_GetDosTrueName: returning %s\n", buffer );
718     return buffer;
719 }
720
721
722 /***********************************************************************
723  *           DOSFS_FindNext
724  *
725  * Find the next matching file. Return the number of entries read to find
726  * the matching one, or 0 if no more entries.
727  * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
728  * file name mask. Either or both can be NULL.
729  */
730 int DOSFS_FindNext( const char *path, const char *short_mask,
731                     const char *long_mask, int drive, BYTE attr,
732                     int skip, DOS_DIRENT *entry )
733 {
734     static DIR *dir = NULL;
735     struct dirent *dirent;
736     int count = 0;
737     static char buffer[MAX_PATHNAME_LEN];
738     static int cur_pos = 0;
739     static int drive_root = 0;
740     char *p;
741     const char *hash_name;
742     UINT32 flags;
743
744     if ((attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
745     {
746         if (skip) return 0;
747         strcpy( entry->name, DRIVE_GetLabel( drive ) );
748         entry->attr = FA_LABEL;
749         entry->size = 0;
750         DOSFS_ToDosDateTime( time(NULL), &entry->date, &entry->time );
751         return 1;
752     }
753
754     /* Check the cached directory */
755     if (dir && !strcmp( buffer, path ) && (cur_pos <= skip)) skip -= cur_pos;
756     else  /* Not in the cache, open it anew */
757     {
758         const char *drive_path;
759         dprintf_dosfs( stddeb, "DOSFS_FindNext: cache miss, path=%s skip=%d buf=%s cur=%d\n",
760                        path, skip, buffer, cur_pos );
761         cur_pos = skip;
762         if (dir) closedir(dir);
763         if (!(dir = opendir( path ))) return 0;
764         drive_path = path;
765         drive_root = 0;
766         if (DRIVE_FindDriveRoot( &drive_path ) != -1)
767         {
768             while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
769             if (!*drive_path) drive_root = 1;
770         }
771         dprintf_dosfs(stddeb, "DOSFS_FindNext: drive_root = %d\n", drive_root);
772         lstrcpyn32A( buffer, path, sizeof(buffer) - 1 );
773         
774     }
775     strcat( buffer, "/" );
776     p = buffer + strlen(buffer);
777     attr |= FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
778     flags = DRIVE_GetFlags( drive );
779     hash_name = NULL;
780
781     while ((dirent = readdir( dir )) != NULL)
782     {
783         if (skip-- > 0) continue;
784         count++;
785
786         /* Don't return '.' and '..' in the root of the drive */
787         if (drive_root && (dirent->d_name[0] == '.') &&
788             (!dirent->d_name[1] ||
789              ((dirent->d_name[1] == '.') && !dirent->d_name[2]))) continue;
790
791         /* Check the long mask */
792
793         if (long_mask)
794         {
795             if (!DOSFS_MatchLong( long_mask, dirent->d_name,
796                                   flags & DRIVE_CASE_SENSITIVE )) continue;
797         }
798
799         /* Check the short mask */
800
801         if (short_mask)
802         {
803             hash_name = DOSFS_Hash( dirent->d_name, TRUE,
804                                     !(flags & DRIVE_CASE_SENSITIVE) );
805             if (!DOSFS_MatchShort( short_mask, hash_name )) continue;
806         }
807
808         /* Check the file attributes */
809
810         lstrcpyn32A( p, dirent->d_name, sizeof(buffer) - (int)(p - buffer) );
811         if (!FILE_Stat( buffer, &entry->attr, &entry->size,
812                         &entry->date, &entry->time ))
813         {
814             fprintf( stderr, "DOSFS_FindNext: can't stat %s\n", buffer );
815             continue;
816         }
817         if (entry->attr & ~attr) continue;
818
819         /* We now have a matching entry; fill the result and return */
820
821         if (!hash_name)
822             hash_name = DOSFS_Hash( dirent->d_name, TRUE,
823                                     !(flags & DRIVE_CASE_SENSITIVE) );
824         strcpy( entry->name, hash_name );
825         lstrcpyn32A( entry->unixname, dirent->d_name, sizeof(entry->unixname));
826         if (!(flags & DRIVE_CASE_PRESERVING)) AnsiLower( entry->unixname );
827         dprintf_dosfs( stddeb, "DOSFS_FindNext: returning %s %02x %ld\n",
828                        entry->name, entry->attr, entry->size );
829         cur_pos += count;
830         p[-1] = '\0';  /* Remove trailing slash in buffer */
831         return count;
832     }
833     closedir( dir );
834     dir = NULL;
835     return 0;  /* End of directory */
836 }
837
838
839 /***********************************************************************
840  *           GetShortPathNameA   (KERNEL32.271)
841  */
842 DWORD GetShortPathName32A( LPCSTR longpath, LPSTR shortpath, DWORD shortlen )
843 {
844     LPCSTR dostruename;
845
846     dprintf_dosfs( stddeb, "GetShortPathName32A(%s,%p,%ld)\n",
847                    longpath, shortpath, shortlen );
848
849     dostruename = DOSFS_GetDosTrueName( longpath, TRUE );
850     lstrcpyn32A( shortpath, dostruename, shortlen );
851     return strlen(dostruename);
852 }
853
854
855 /***********************************************************************
856  *           GetShortPathNameW   (KERNEL32.272)
857  */
858 DWORD GetShortPathName32W( LPCWSTR longpath, LPWSTR shortpath, DWORD shortlen )
859 {
860     LPSTR longpatha = STRING32_DupUniToAnsi( longpath );
861     LPCSTR dostruename = DOSFS_GetDosTrueName( longpatha, TRUE );
862     free( longpatha );
863     lstrcpynAtoW( shortpath, dostruename, shortlen );
864     return strlen(dostruename);
865 }
866
867
868 /***********************************************************************
869  *           GetFullPathNameA   (KERNEL32.272)
870  */
871 DWORD GetFullPathName32A( LPCSTR fn, DWORD buflen, LPSTR buf, LPSTR *lastpart) {
872         dprintf_file(stddeb,"GetFullPathNameA(%s)\n",fn);
873         /* FIXME */
874         lstrcpyn32A(buf,fn,buflen);
875         if (lastpart)
876                 *lastpart=strrchr(buf,'\\');
877         return strlen(fn);
878 }