/* * DOS file system functions * * Copyright 1993 Erik Bos * Copyright 1996 Alexandre Julliard */ #include #include #include #include #include #include #include #include #if defined(__svr4__) || defined(_SCO_DS) #include #endif #include "windows.h" #include "dos_fs.h" #include "drive.h" #include "file.h" #include "msdos.h" #include "stddebug.h" #include "debug.h" #include "xmalloc.h" #include "string32.h" /* Chars we don't want to see in DOS file names */ #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345" static const char *DOSFS_Devices[][2] = { { "CON", "" }, { "PRN", "" }, { "NUL", "/dev/null" }, { "AUX", "" }, { "LPT1", "" }, { "LPT2", "" }, { "LPT3", "" }, { "LPT4", "" }, { "COM1", "" }, { "COM2", "" }, { "COM3", "" }, { "COM4", "" } }; #define GET_DRIVE(path) \ (((path)[1] == ':') ? toupper((path)[0]) - 'A' : DOSFS_CurDrive) /* DOS extended error status */ WORD DOS_ExtendedError; BYTE DOS_ErrorClass; BYTE DOS_ErrorAction; BYTE DOS_ErrorLocus; /*********************************************************************** * DOSFS_ValidDOSName * * Return 1 if Unix file 'name' is also a valid MS-DOS name * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format). * File name can be terminated by '\0', '\\' or '/'. */ static int DOSFS_ValidDOSName( const char *name, int ignore_case ) { static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS; const char *p = name; const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars; int len = 0; if (*p == '.') { /* Check for "." and ".." */ p++; if (*p == '.') p++; /* All other names beginning with '.' are invalid */ return (IS_END_OF_NAME(*p)); } while (!IS_END_OF_NAME(*p)) { if (strchr( invalid, *p )) return 0; /* Invalid char */ if (*p == '.') break; /* Start of the extension */ if (++len > 8) return 0; /* Name too long */ p++; } if (*p != '.') return 1; /* End of name */ p++; if (IS_END_OF_NAME(*p)) return 0; /* Empty extension not allowed */ len = 0; while (!IS_END_OF_NAME(*p)) { if (strchr( invalid, *p )) return 0; /* Invalid char */ if (*p == '.') return 0; /* Second extension not allowed */ if (++len > 3) return 0; /* Extension too long */ p++; } return 1; } /*********************************************************************** * DOSFS_CheckDotDot * * Remove all '.' and '..' at the beginning of 'name'. */ static const char * DOSFS_CheckDotDot( const char *name, char *buffer, char sep , int *len ) { char *p = buffer + strlen(buffer); while (*name == '.') { if (IS_END_OF_NAME(name[1])) { name++; while ((*name == '\\') || (*name == '/')) name++; } else if ((name[1] == '.') && IS_END_OF_NAME(name[2])) { name += 2; while ((*name == '\\') || (*name == '/')) name++; while ((p > buffer) && (*p != sep)) { p--; (*len)++; } *p = '\0'; /* Remove trailing separator */ } else break; } return name; } /*********************************************************************** * DOSFS_ToDosFCBFormat * * Convert a file name to DOS FCB format (8+3 chars, padded with blanks), * expanding wild cards and converting to upper-case in the process. * File name can be terminated by '\0', '\\' or '/'. * Return NULL if the name is not a valid DOS name. */ const char *DOSFS_ToDosFCBFormat( const char *name ) { static const char invalid_chars[] = INVALID_DOS_CHARS; static char buffer[12]; const char *p = name; int i; /* Check for "." and ".." */ if (*p == '.') { p++; strcpy( buffer, ". " ); if (*p == '.') p++; return (!*p || (*p == '/') || (*p == '\\')) ? buffer : NULL; } for (i = 0; i < 8; i++) { switch(*p) { case '\0': case '\\': case '/': case '.': buffer[i] = ' '; break; case '?': p++; /* fall through */ case '*': buffer[i] = '?'; break; default: if (strchr( invalid_chars, *p )) return NULL; buffer[i] = toupper(*p); p++; break; } } if (*p == '*') { /* Skip all chars after wildcard up to first dot */ while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++; } else { /* Check if name too long */ if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return NULL; } if (*p == '.') p++; /* Skip dot */ for (i = 8; i < 11; i++) { switch(*p) { case '\0': case '\\': case '/': buffer[i] = ' '; break; case '.': return NULL; /* Second extension not allowed */ case '?': p++; /* fall through */ case '*': buffer[i] = '?'; break; default: if (strchr( invalid_chars, *p )) return NULL; buffer[i] = toupper(*p); p++; break; } } buffer[11] = '\0'; return buffer; } /*********************************************************************** * DOSFS_ToDosDTAFormat * * Convert a file name from FCB to DTA format (name.ext, null-terminated) * converting to upper-case in the process. * File name can be terminated by '\0', '\\' or '/'. * Return NULL if the name is not a valid DOS name. */ const char *DOSFS_ToDosDTAFormat( const char *name ) { static char buffer[13]; char *p; memcpy( buffer, name, 8 ); for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--); *p++ = '.'; memcpy( p, name + 8, 3 ); for (p += 3; p[-1] == ' '; p--); if (p[-1] == '.') p--; *p = '\0'; return buffer; } /*********************************************************************** * DOSFS_MatchShort * * Check a DOS file name against a mask (both in FCB format). */ static int DOSFS_MatchShort( const char *mask, const char *name ) { int i; for (i = 11; i > 0; i--, mask++, name++) if ((*mask != '?') && (*mask != *name)) return 0; return 1; } /*********************************************************************** * DOSFS_MatchLong * * Check a long file name against a mask. */ static int DOSFS_MatchLong( const char *mask, const char *name, int case_sensitive ) { while (*name && *mask) { if (*mask == '*') { mask++; while (*mask == '*') mask++; /* Skip consecutive '*' */ if (!*mask) return 1; if (case_sensitive) while (*name && (*name != *mask)) name++; else while (*name && (toupper(*name) != toupper(*mask))) name++; if (!*name) return 0; } else if (*mask != '?') { if (case_sensitive) { if (*mask != *name) return 0; } else if (toupper(*mask) != toupper(*name)) return 0; } mask++; name++; } return (!*name && !*mask); } /*********************************************************************** * DOSFS_ToDosDateTime * * Convert a Unix time in the DOS date/time format. */ void DOSFS_ToDosDateTime( time_t unixtime, WORD *pDate, WORD *pTime ) { struct tm *tm = localtime( &unixtime ); if (pTime) *pTime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2); if (pDate) *pDate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday; } /*********************************************************************** * DOSFS_UnixTimeToFileTime * * Convert a Unix time to FILETIME format. */ void DOSFS_UnixTimeToFileTime( time_t unixtime, FILETIME *filetime ) { /* FIXME :-) */ filetime->dwLowDateTime = unixtime; filetime->dwHighDateTime = 0; } /*********************************************************************** * DOSFS_FileTimeToUnixTime * * Convert a FILETIME format to Unix time. */ time_t DOSFS_FileTimeToUnixTime( FILETIME *filetime ) { /* FIXME :-) */ return filetime->dwLowDateTime; } /*********************************************************************** * DOSFS_Hash * * Transform a Unix file name into a hashed DOS name. If the name is a valid * DOS name, it is converted to upper-case; otherwise it is replaced by a * hashed version that fits in 8.3 format. * File name can be terminated by '\0', '\\' or '/'. */ const char *DOSFS_Hash( const char *name, int dir_format, int ignore_case ) { static const char invalid_chars[] = INVALID_DOS_CHARS "~."; static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"; static char buffer[13]; const char *p, *ext; char *dst; unsigned short hash; int i; if (dir_format) strcpy( buffer, " " ); if (DOSFS_ValidDOSName( name, ignore_case )) { /* Check for '.' and '..' */ if (*name == '.') { buffer[0] = '.'; if (!dir_format) buffer[1] = buffer[2] = '\0'; if (name[1] == '.') buffer[1] = '.'; return buffer; } /* Simply copy the name, converting to uppercase */ for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++) *dst++ = toupper(*name); if (*name == '.') { if (dir_format) dst = buffer + 8; else *dst++ = '.'; for (name++; !IS_END_OF_NAME(*name); name++) *dst++ = toupper(*name); } if (!dir_format) *dst = '\0'; } else { /* Compute the hash code of the file name */ /* If you know something about hash functions, feel free to */ /* insert a better algorithm here... */ if (ignore_case) { for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++) hash = (hash << 3) ^ (hash >> 5) ^ tolower(*p) ^ (tolower(p[1]) << 8); hash = (hash << 3) ^ (hash >> 5) ^ tolower(*p); /* Last character*/ } else { for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++) hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8); hash = (hash << 3) ^ (hash >> 5) ^ *p; /* Last character */ } /* Find last dot for start of the extension */ for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++) if (*p == '.') ext = p; if (ext && IS_END_OF_NAME(ext[1])) ext = NULL; /* Empty extension ignored */ /* Copy first 4 chars, replacing invalid chars with '_' */ for (i = 4, p = name, dst = buffer; i > 0; i--, p++) { if (IS_END_OF_NAME(*p) || (p == ext)) break; *dst++ = strchr( invalid_chars, *p ) ? '_' : toupper(*p); } /* Pad to 5 chars with '~' */ while (i-- >= 0) *dst++ = '~'; /* Insert hash code converted to 3 ASCII chars */ *dst++ = hash_chars[(hash >> 10) & 0x1f]; *dst++ = hash_chars[(hash >> 5) & 0x1f]; *dst++ = hash_chars[hash & 0x1f]; /* Copy the first 3 chars of the extension (if any) */ if (ext) { if (!dir_format) *dst++ = '.'; for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++) *dst++ = toupper(*ext); } if (!dir_format) *dst = '\0'; } return buffer; } /*********************************************************************** * DOSFS_FindUnixName * * Find the Unix file name in a given directory that corresponds to * a file name (either in Unix or DOS format). * File name can be terminated by '\0', '\\' or '/'. * Return 1 if OK, 0 if no file name matches. */ static int DOSFS_FindUnixName( const char *path, const char *name, char *buffer, int maxlen, UINT32 drive_flags ) { DIR *dir; struct dirent *dirent; const char *dos_name = DOSFS_ToDosFCBFormat( name ); const char *p = strchr( name, '/' ); int len = p ? (int)(p - name) : strlen(name); dprintf_dosfs( stddeb, "DOSFS_FindUnixName: %s %s\n", path, name ); if ((p = strchr( name, '\\' ))) len = MIN( (int)(p - name), len ); if (!(dir = opendir( path ))) { dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s): can't open dir\n", path, name ); return 0; } while ((dirent = readdir( dir )) != NULL) { /* Check against Unix name */ if (len == strlen(dirent->d_name)) { if (drive_flags & DRIVE_CASE_SENSITIVE) { if (!lstrncmp32A( dirent->d_name, name, len )) break; } else { if (!lstrncmpi32A( dirent->d_name, name, len )) break; } } if (dos_name) { /* Check against hashed DOS name */ const char *hash_name = DOSFS_Hash( dirent->d_name, TRUE, !(drive_flags & DRIVE_CASE_SENSITIVE) ); if (!strcmp( dos_name, hash_name )) break; } } if (dirent) lstrcpyn32A( buffer, dirent->d_name, maxlen ); closedir( dir ); dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s) -> %s\n", path, name, dirent ? buffer : "** Not found **" ); return (dirent != NULL); } /*********************************************************************** * DOSFS_IsDevice * * Check if a DOS file name represents a DOS device. Returns the name * of the associated Unix device, or NULL if not found. */ const char *DOSFS_IsDevice( const char *name ) { int i; const char *p; if (name[0] && (name[1] == ':')) name += 2; if ((p = strrchr( name, '/' ))) name = p + 1; if ((p = strrchr( name, '\\' ))) name = p + 1; for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++) { const char *dev = DOSFS_Devices[i][0]; if (!lstrncmpi32A( dev, name, strlen(dev) )) { p = name + strlen( dev ); if (!*p || (*p == '.')) return DOSFS_Devices[i][1]; } } return NULL; } /*********************************************************************** * DOSFS_GetUnixFileName * * Convert a file name (DOS or mixed DOS/Unix format) to a valid Unix name. * Return NULL if one of the path components does not exist. The last path * component is only checked if 'check_last' is non-zero. */ const char * DOSFS_GetUnixFileName( const char * name, int check_last ) { static char buffer[MAX_PATHNAME_LEN]; int drive, len, found; UINT32 flags; char *p, *root; dprintf_dosfs( stddeb, "DOSFS_GetUnixFileName: %s\n", name ); if (name[0] && (name[1] == ':')) { drive = toupper(name[0]) - 'A'; name += 2; } else if (name[0] == '/') /* Absolute Unix path? */ { if ((drive = DRIVE_FindDriveRoot( &name )) == -1) { fprintf( stderr, "Warning: %s not accessible from a DOS drive\n", name ); /* Assume it really was a DOS name */ drive = DRIVE_GetCurrentDrive(); } } else drive = DRIVE_GetCurrentDrive(); if (!DRIVE_IsValid(drive)) { DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk ); return NULL; } flags = DRIVE_GetFlags(drive); lstrcpyn32A( buffer, DRIVE_GetRoot(drive), MAX_PATHNAME_LEN ); if (buffer[1]) root = buffer + strlen(buffer); else root = buffer; /* root directory */ if ((*name == '\\') || (*name == '/')) { while ((*name == '\\') || (*name == '/')) name++; } else { lstrcpyn32A( root + 1, DRIVE_GetUnixCwd(drive), MAX_PATHNAME_LEN - (int)(root - buffer) - 1 ); if (root[1]) *root = '/'; } p = buffer[1] ? buffer + strlen(buffer) : buffer; len = MAX_PATHNAME_LEN - strlen(buffer); found = 1; while (*name && found) { const char *newname = DOSFS_CheckDotDot( name, root, '/', &len ); if (newname != name) { p = root + strlen(root); name = newname; continue; } if (len <= 1) { DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk ); return NULL; } if ((found = DOSFS_FindUnixName( buffer, name, p+1, len-1, flags ))) { *p = '/'; len -= strlen(p); p += strlen(p); while (!IS_END_OF_NAME(*name)) name++; } else if (!check_last) { *p++ = '/'; for (len--; !IS_END_OF_NAME(*name) && (len > 1); name++, len--) *p++ = tolower(*name); *p = '\0'; } while ((*name == '\\') || (*name == '/')) name++; } if (!found) { if (check_last) { DOS_ERROR( ER_FileNotFound, EC_NotFound, SA_Abort, EL_Disk ); return NULL; } if (*name) /* Not last */ { DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk ); return NULL; } } if (!buffer[0]) strcpy( buffer, "/" ); dprintf_dosfs( stddeb, "DOSFS_GetUnixFileName: returning %s\n", buffer ); return buffer; } /*********************************************************************** * DOSFS_GetDosTrueName * * Convert a file name (DOS or Unix format) to a complete DOS name. * Return NULL if the path name is invalid or too long. * The unix_format flag is a hint that the file name is in Unix format. */ const char * DOSFS_GetDosTrueName( const char *name, int unix_format ) { static char buffer[MAX_PATHNAME_LEN]; int drive, len; UINT32 flags; char *p; dprintf_dosfs( stddeb, "DOSFS_GetDosTrueName(%s,%d)\n", name, unix_format); if (name[0] && (name[1] == ':')) { drive = toupper(name[0]) - 'A'; name += 2; } else if (name[0] == '/') /* Absolute Unix path? */ { if ((drive = DRIVE_FindDriveRoot( &name )) == -1) { fprintf( stderr, "Warning: %s not accessible from a DOS drive\n", name ); /* Assume it really was a DOS name */ drive = DRIVE_GetCurrentDrive(); } } else drive = DRIVE_GetCurrentDrive(); if (!DRIVE_IsValid(drive)) { DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk ); return NULL; } p = buffer; *p++ = 'A' + drive; *p++ = ':'; if (IS_END_OF_NAME(*name)) { while ((*name == '\\') || (*name == '/')) name++; } else { *p++ = '\\'; lstrcpyn32A( p, DRIVE_GetDosCwd(drive), sizeof(buffer) - 3 ); if (*p) p += strlen(p); else p--; } *p = '\0'; len = MAX_PATHNAME_LEN - (int)(p - buffer); flags = DRIVE_GetFlags(drive); while (*name) { const char *newname = DOSFS_CheckDotDot( name, buffer+2, '\\', &len ); if (newname != name) { p = buffer + strlen(buffer); name = newname; continue; } if (len <= 1) { DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk ); return NULL; } *p++ = '\\'; if (unix_format) /* Hash it into a DOS name */ { lstrcpyn32A( p, DOSFS_Hash( name, FALSE, !(flags & DRIVE_CASE_SENSITIVE) ), len ); len -= strlen(p); p += strlen(p); while (!IS_END_OF_NAME(*name)) name++; } else /* Already DOS format, simply upper-case it */ { while (!IS_END_OF_NAME(*name) && (len > 1)) { *p++ = toupper(*name); name++; len--; } *p = '\0'; } while ((*name == '\\') || (*name == '/')) name++; } if (!buffer[2]) { buffer[2] = '\\'; buffer[3] = '\0'; } dprintf_dosfs( stddeb, "DOSFS_GetDosTrueName: returning %s\n", buffer ); return buffer; } /*********************************************************************** * DOSFS_FindNext * * Find the next matching file. Return the number of entries read to find * the matching one, or 0 if no more entries. * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long * file name mask. Either or both can be NULL. */ int DOSFS_FindNext( const char *path, const char *short_mask, const char *long_mask, int drive, BYTE attr, int skip, DOS_DIRENT *entry ) { static DIR *dir = NULL; struct dirent *dirent; int count = 0; static char buffer[MAX_PATHNAME_LEN]; static int cur_pos = 0; static int drive_root = 0; char *p; const char *hash_name; UINT32 flags; if ((attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL) { if (skip) return 0; strcpy( entry->name, DRIVE_GetLabel( drive ) ); entry->attr = FA_LABEL; entry->size = 0; DOSFS_ToDosDateTime( time(NULL), &entry->date, &entry->time ); return 1; } /* Check the cached directory */ if (dir && !strcmp( buffer, path ) && (cur_pos <= skip)) skip -= cur_pos; else /* Not in the cache, open it anew */ { const char *drive_path; dprintf_dosfs( stddeb, "DOSFS_FindNext: cache miss, path=%s skip=%d buf=%s cur=%d\n", path, skip, buffer, cur_pos ); cur_pos = skip; if (dir) closedir(dir); if (!(dir = opendir( path ))) return 0; drive_path = path; drive_root = 0; if (DRIVE_FindDriveRoot( &drive_path ) != -1) { while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++; if (!*drive_path) drive_root = 1; } dprintf_dosfs(stddeb, "DOSFS_FindNext: drive_root = %d\n", drive_root); lstrcpyn32A( buffer, path, sizeof(buffer) - 1 ); } strcat( buffer, "/" ); p = buffer + strlen(buffer); attr |= FA_UNUSED | FA_ARCHIVE | FA_RDONLY; flags = DRIVE_GetFlags( drive ); hash_name = NULL; while ((dirent = readdir( dir )) != NULL) { if (skip-- > 0) continue; count++; /* Don't return '.' and '..' in the root of the drive */ if (drive_root && (dirent->d_name[0] == '.') && (!dirent->d_name[1] || ((dirent->d_name[1] == '.') && !dirent->d_name[2]))) continue; /* Check the long mask */ if (long_mask) { if (!DOSFS_MatchLong( long_mask, dirent->d_name, flags & DRIVE_CASE_SENSITIVE )) continue; } /* Check the short mask */ if (short_mask) { hash_name = DOSFS_Hash( dirent->d_name, TRUE, !(flags & DRIVE_CASE_SENSITIVE) ); if (!DOSFS_MatchShort( short_mask, hash_name )) continue; } /* Check the file attributes */ lstrcpyn32A( p, dirent->d_name, sizeof(buffer) - (int)(p - buffer) ); if (!FILE_Stat( buffer, &entry->attr, &entry->size, &entry->date, &entry->time )) { fprintf( stderr, "DOSFS_FindNext: can't stat %s\n", buffer ); continue; } if (entry->attr & ~attr) continue; /* We now have a matching entry; fill the result and return */ if (!hash_name) hash_name = DOSFS_Hash( dirent->d_name, TRUE, !(flags & DRIVE_CASE_SENSITIVE) ); strcpy( entry->name, hash_name ); lstrcpyn32A( entry->unixname, dirent->d_name, sizeof(entry->unixname)); if (!(flags & DRIVE_CASE_PRESERVING)) AnsiLower( entry->unixname ); dprintf_dosfs( stddeb, "DOSFS_FindNext: returning %s %02x %ld\n", entry->name, entry->attr, entry->size ); cur_pos += count; p[-1] = '\0'; /* Remove trailing slash in buffer */ return count; } closedir( dir ); dir = NULL; return 0; /* End of directory */ } /*********************************************************************** * GetShortPathNameA (KERNEL32.271) */ DWORD GetShortPathName32A( LPCSTR longpath, LPSTR shortpath, DWORD shortlen ) { LPCSTR dostruename; dprintf_dosfs( stddeb, "GetShortPathName32A(%s,%p,%ld)\n", longpath, shortpath, shortlen ); dostruename = DOSFS_GetDosTrueName( longpath, TRUE ); lstrcpyn32A( shortpath, dostruename, shortlen ); return strlen(dostruename); } /*********************************************************************** * GetShortPathNameW (KERNEL32.272) */ DWORD GetShortPathName32W( LPCWSTR longpath, LPWSTR shortpath, DWORD shortlen ) { LPSTR longpatha = STRING32_DupUniToAnsi( longpath ); LPCSTR dostruename = DOSFS_GetDosTrueName( longpatha, TRUE ); free( longpatha ); lstrcpynAtoW( shortpath, dostruename, shortlen ); return strlen(dostruename); } /*********************************************************************** * GetFullPathNameA (KERNEL32.272) */ DWORD GetFullPathName32A( LPCSTR fn, DWORD buflen, LPSTR buf, LPSTR *lastpart) { dprintf_file(stddeb,"GetFullPathNameA(%s)\n",fn); /* FIXME */ lstrcpyn32A(buf,fn,buflen); if (lastpart) *lastpart=strrchr(buf,'\\'); return strlen(fn); }