2 * DOS file system functions
4 * Copyright 1993 Erik Bos
5 * Copyright 1996 Alexandre Julliard
16 #if defined(__svr4__) || defined(_SCO_DS)
17 #include <sys/statfs.h>
30 /* Chars we don't want to see in DOS file names */
31 #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345"
33 static const char *DOSFS_Devices[][2] =
37 { "NUL", "/dev/null" },
49 #define GET_DRIVE(path) \
50 (((path)[1] == ':') ? toupper((path)[0]) - 'A' : DOSFS_CurDrive)
52 /* DOS extended error status */
53 WORD DOS_ExtendedError;
59 /***********************************************************************
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 '/'.
66 static int DOSFS_ValidDOSName( const char *name, int ignore_case )
68 static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
70 const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
75 /* Check for "." and ".." */
78 /* All other names beginning with '.' are invalid */
79 return (IS_END_OF_NAME(*p));
81 while (!IS_END_OF_NAME(*p))
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 */
88 if (*p != '.') return 1; /* End of name */
90 if (IS_END_OF_NAME(*p)) return 0; /* Empty extension not allowed */
92 while (!IS_END_OF_NAME(*p))
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 */
103 /***********************************************************************
106 * Remove all '.' and '..' at the beginning of 'name'.
108 static const char * DOSFS_CheckDotDot( const char *name, char *buffer,
109 char sep , int *len )
111 char *p = buffer + strlen(buffer);
115 if (IS_END_OF_NAME(name[1]))
118 while ((*name == '\\') || (*name == '/')) name++;
120 else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
123 while ((*name == '\\') || (*name == '/')) name++;
124 while ((p > buffer) && (*p != sep)) { p--; (*len)++; }
125 *p = '\0'; /* Remove trailing separator */
133 /***********************************************************************
134 * DOSFS_ToDosFCBFormat
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.
141 const char *DOSFS_ToDosFCBFormat( const char *name )
143 static const char invalid_chars[] = INVALID_DOS_CHARS;
144 static char buffer[12];
145 const char *p = name;
148 /* Check for "." and ".." */
152 strcpy( buffer, ". " );
154 return (!*p || (*p == '/') || (*p == '\\')) ? buffer : NULL;
157 for (i = 0; i < 8; i++)
174 if (strchr( invalid_chars, *p )) return NULL;
175 buffer[i] = toupper(*p);
183 /* Skip all chars after wildcard up to first dot */
184 while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
188 /* Check if name too long */
189 if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return NULL;
191 if (*p == '.') p++; /* Skip dot */
193 for (i = 8; i < 11; i++)
203 return NULL; /* Second extension not allowed */
211 if (strchr( invalid_chars, *p )) return NULL;
212 buffer[i] = toupper(*p);
222 /***********************************************************************
223 * DOSFS_ToDosDTAFormat
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.
230 const char *DOSFS_ToDosDTAFormat( const char *name )
232 static char buffer[13];
235 memcpy( buffer, name, 8 );
236 for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--);
238 memcpy( p, name + 8, 3 );
239 for (p += 3; p[-1] == ' '; p--);
240 if (p[-1] == '.') p--;
246 /***********************************************************************
249 * Check a DOS file name against a mask (both in FCB format).
251 static int DOSFS_MatchShort( const char *mask, const char *name )
254 for (i = 11; i > 0; i--, mask++, name++)
255 if ((*mask != '?') && (*mask != *name)) return 0;
260 /***********************************************************************
263 * Check a long file name against a mask.
265 static int DOSFS_MatchLong( const char *mask, const char *name,
268 while (*name && *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;
279 else if (*mask != '?')
283 if (*mask != *name) return 0;
285 else if (toupper(*mask) != toupper(*name)) return 0;
290 return (!*name && !*mask);
294 /***********************************************************************
295 * DOSFS_ToDosDateTime
297 * Convert a Unix time in the DOS date/time format.
299 void DOSFS_ToDosDateTime( time_t unixtime, WORD *pDate, WORD *pTime )
301 struct tm *tm = localtime( &unixtime );
303 *pTime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
305 *pDate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
310 /***********************************************************************
311 * DOSFS_UnixTimeToFileTime
313 * Convert a Unix time to FILETIME format.
315 void DOSFS_UnixTimeToFileTime( time_t unixtime, FILETIME *filetime )
318 filetime->dwLowDateTime = unixtime;
319 filetime->dwHighDateTime = 0;
323 /***********************************************************************
324 * DOSFS_FileTimeToUnixTime
326 * Convert a FILETIME format to Unix time.
328 time_t DOSFS_FileTimeToUnixTime( FILETIME *filetime )
331 return filetime->dwLowDateTime;
335 /***********************************************************************
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 '/'.
343 const char *DOSFS_Hash( const char *name, int dir_format, int ignore_case )
345 static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
346 static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
348 static char buffer[13];
354 if (dir_format) strcpy( buffer, " " );
356 if (DOSFS_ValidDOSName( name, ignore_case ))
358 /* Check for '.' and '..' */
362 if (!dir_format) buffer[1] = buffer[2] = '\0';
363 if (name[1] == '.') buffer[1] = '.';
367 /* Simply copy the name, converting to uppercase */
369 for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
370 *dst++ = toupper(*name);
373 if (dir_format) dst = buffer + 8;
375 for (name++; !IS_END_OF_NAME(*name); name++)
376 *dst++ = toupper(*name);
378 if (!dir_format) *dst = '\0';
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... */
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*/
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 */
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 */
404 /* Copy first 4 chars, replacing invalid chars with '_' */
405 for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
407 if (IS_END_OF_NAME(*p) || (p == ext)) break;
408 *dst++ = strchr( invalid_chars, *p ) ? '_' : toupper(*p);
410 /* Pad to 5 chars with '~' */
411 while (i-- >= 0) *dst++ = '~';
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];
418 /* Copy the first 3 chars of the extension (if any) */
421 if (!dir_format) *dst++ = '.';
422 for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
423 *dst++ = toupper(*ext);
425 if (!dir_format) *dst = '\0';
431 /***********************************************************************
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.
439 static int DOSFS_FindUnixName( const char *path, const char *name,
440 char *buffer, int maxlen, UINT32 drive_flags )
443 struct dirent *dirent;
445 const char *dos_name = DOSFS_ToDosFCBFormat( name );
446 const char *p = strchr( name, '/' );
447 int len = p ? (int)(p - name) : strlen(name);
449 dprintf_dosfs( stddeb, "DOSFS_FindUnixName: %s %s\n", path, name );
451 if ((p = strchr( name, '\\' ))) len = MIN( (int)(p - name), len );
453 if (!(dir = opendir( path )))
455 dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s): can't open dir\n",
459 while ((dirent = readdir( dir )) != NULL)
461 /* Check against Unix name */
462 if (len == strlen(dirent->d_name))
464 if (drive_flags & DRIVE_CASE_SENSITIVE)
466 if (!lstrncmp32A( dirent->d_name, name, len )) break;
470 if (!lstrncmpi32A( dirent->d_name, name, len )) break;
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;
481 if (dirent) lstrcpyn32A( buffer, dirent->d_name, maxlen );
483 dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s) -> %s\n",
484 path, name, dirent ? buffer : "** Not found **" );
485 return (dirent != NULL);
489 /***********************************************************************
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.
495 const char *DOSFS_IsDevice( const char *name )
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++)
505 const char *dev = DOSFS_Devices[i][0];
506 if (!lstrncmpi32A( dev, name, strlen(dev) ))
508 p = name + strlen( dev );
509 if (!*p || (*p == '.')) return DOSFS_Devices[i][1];
516 /***********************************************************************
517 * DOSFS_GetUnixFileName
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.
523 const char * DOSFS_GetUnixFileName( const char * name, int check_last )
525 static char buffer[MAX_PATHNAME_LEN];
526 int drive, len, found;
530 dprintf_dosfs( stddeb, "DOSFS_GetUnixFileName: %s\n", name );
531 if (name[0] && (name[1] == ':'))
533 drive = toupper(name[0]) - 'A';
536 else if (name[0] == '/') /* Absolute Unix path? */
538 if ((drive = DRIVE_FindDriveRoot( &name )) == -1)
540 fprintf( stderr, "Warning: %s not accessible from a DOS drive\n",
542 /* Assume it really was a DOS name */
543 drive = DRIVE_GetCurrentDrive();
546 else drive = DRIVE_GetCurrentDrive();
548 if (!DRIVE_IsValid(drive))
550 DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk );
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 */
558 if ((*name == '\\') || (*name == '/'))
560 while ((*name == '\\') || (*name == '/')) name++;
564 lstrcpyn32A( root + 1, DRIVE_GetUnixCwd(drive),
565 MAX_PATHNAME_LEN - (int)(root - buffer) - 1 );
566 if (root[1]) *root = '/';
569 p = buffer[1] ? buffer + strlen(buffer) : buffer;
570 len = MAX_PATHNAME_LEN - strlen(buffer);
572 while (*name && found)
574 const char *newname = DOSFS_CheckDotDot( name, root, '/', &len );
577 p = root + strlen(root);
583 DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
586 if ((found = DOSFS_FindUnixName( buffer, name, p+1, len-1, flags )))
591 while (!IS_END_OF_NAME(*name)) name++;
593 else if (!check_last)
596 for (len--; !IS_END_OF_NAME(*name) && (len > 1); name++, len--)
597 *p++ = tolower(*name);
600 while ((*name == '\\') || (*name == '/')) name++;
606 DOS_ERROR( ER_FileNotFound, EC_NotFound, SA_Abort, EL_Disk );
609 if (*name) /* Not last */
611 DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
615 if (!buffer[0]) strcpy( buffer, "/" );
616 dprintf_dosfs( stddeb, "DOSFS_GetUnixFileName: returning %s\n", buffer );
621 /***********************************************************************
622 * DOSFS_GetDosTrueName
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.
628 const char * DOSFS_GetDosTrueName( const char *name, int unix_format )
630 static char buffer[MAX_PATHNAME_LEN];
635 dprintf_dosfs( stddeb, "DOSFS_GetDosTrueName(%s,%d)\n", name, unix_format);
636 if (name[0] && (name[1] == ':'))
638 drive = toupper(name[0]) - 'A';
641 else if (name[0] == '/') /* Absolute Unix path? */
643 if ((drive = DRIVE_FindDriveRoot( &name )) == -1)
645 fprintf( stderr, "Warning: %s not accessible from a DOS drive\n",
647 /* Assume it really was a DOS name */
648 drive = DRIVE_GetCurrentDrive();
651 else drive = DRIVE_GetCurrentDrive();
653 if (!DRIVE_IsValid(drive))
655 DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk );
662 if (IS_END_OF_NAME(*name))
664 while ((*name == '\\') || (*name == '/')) name++;
669 lstrcpyn32A( p, DRIVE_GetDosCwd(drive), sizeof(buffer) - 3 );
670 if (*p) p += strlen(p); else p--;
673 len = MAX_PATHNAME_LEN - (int)(p - buffer);
674 flags = DRIVE_GetFlags(drive);
678 const char *newname = DOSFS_CheckDotDot( name, buffer+2, '\\', &len );
681 p = buffer + strlen(buffer);
687 DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
691 if (unix_format) /* Hash it into a DOS name */
693 lstrcpyn32A( p, DOSFS_Hash( name, FALSE,
694 !(flags & DRIVE_CASE_SENSITIVE) ),
698 while (!IS_END_OF_NAME(*name)) name++;
700 else /* Already DOS format, simply upper-case it */
702 while (!IS_END_OF_NAME(*name) && (len > 1))
704 *p++ = toupper(*name);
710 while ((*name == '\\') || (*name == '/')) name++;
717 dprintf_dosfs( stddeb, "DOSFS_GetDosTrueName: returning %s\n", buffer );
722 /***********************************************************************
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.
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 )
734 static DIR *dir = NULL;
735 struct dirent *dirent;
737 static char buffer[MAX_PATHNAME_LEN];
738 static int cur_pos = 0;
739 static int drive_root = 0;
741 const char *hash_name;
744 if ((attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
747 strcpy( entry->name, DRIVE_GetLabel( drive ) );
748 entry->attr = FA_LABEL;
750 DOSFS_ToDosDateTime( time(NULL), &entry->date, &entry->time );
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 */
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 );
762 if (dir) closedir(dir);
763 if (!(dir = opendir( path ))) return 0;
766 if (DRIVE_FindDriveRoot( &drive_path ) != -1)
768 while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
769 if (!*drive_path) drive_root = 1;
771 dprintf_dosfs(stddeb, "DOSFS_FindNext: drive_root = %d\n", drive_root);
772 lstrcpyn32A( buffer, path, sizeof(buffer) - 1 );
775 strcat( buffer, "/" );
776 p = buffer + strlen(buffer);
777 attr |= FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
778 flags = DRIVE_GetFlags( drive );
781 while ((dirent = readdir( dir )) != NULL)
783 if (skip-- > 0) continue;
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;
791 /* Check the long mask */
795 if (!DOSFS_MatchLong( long_mask, dirent->d_name,
796 flags & DRIVE_CASE_SENSITIVE )) continue;
799 /* Check the short mask */
803 hash_name = DOSFS_Hash( dirent->d_name, TRUE,
804 !(flags & DRIVE_CASE_SENSITIVE) );
805 if (!DOSFS_MatchShort( short_mask, hash_name )) continue;
808 /* Check the file attributes */
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 ))
814 fprintf( stderr, "DOSFS_FindNext: can't stat %s\n", buffer );
817 if (entry->attr & ~attr) continue;
819 /* We now have a matching entry; fill the result and return */
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 );
830 p[-1] = '\0'; /* Remove trailing slash in buffer */
835 return 0; /* End of directory */
839 /***********************************************************************
840 * GetShortPathNameA (KERNEL32.271)
842 DWORD GetShortPathName32A( LPCSTR longpath, LPSTR shortpath, DWORD shortlen )
846 dprintf_dosfs( stddeb, "GetShortPathName32A(%s,%p,%ld)\n",
847 longpath, shortpath, shortlen );
849 dostruename = DOSFS_GetDosTrueName( longpath, TRUE );
850 lstrcpyn32A( shortpath, dostruename, shortlen );
851 return strlen(dostruename);
855 /***********************************************************************
856 * GetShortPathNameW (KERNEL32.272)
858 DWORD GetShortPathName32W( LPCWSTR longpath, LPWSTR shortpath, DWORD shortlen )
860 LPSTR longpatha = STRING32_DupUniToAnsi( longpath );
861 LPCSTR dostruename = DOSFS_GetDosTrueName( longpatha, TRUE );
863 lstrcpynAtoW( shortpath, dostruename, shortlen );
864 return strlen(dostruename);
868 /***********************************************************************
869 * GetFullPathNameA (KERNEL32.272)
871 DWORD GetFullPathName32A( LPCSTR fn, DWORD buflen, LPSTR buf, LPSTR *lastpart) {
872 dprintf_file(stddeb,"GetFullPathNameA(%s)\n",fn);
874 lstrcpyn32A(buf,fn,buflen);
876 *lastpart=strrchr(buf,'\\');