/* * DOS file system functions * * Copyright 1993 Erik Bos * Copyright 1996 Alexandre Julliard */ #include #include #include #include #include #include #include #include #include #include #include #if defined(__svr4__) || defined(_SCO_DS) #include #endif #include "windows.h" #include "winerror.h" #include "drive.h" #include "file.h" #include "heap.h" #include "msdos.h" #include "stddebug.h" #include "debug.h" /* Define the VFAT ioctl to get both short and long file names */ /* FIXME: is it possible to get this to work on other systems? */ #ifdef linux #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, long) /* We want the real kernel dirent structure, not the libc one */ typedef struct { long d_ino; long d_off; unsigned short d_reclen; char d_name[256]; } KERNEL_DIRENT; #else /* linux */ #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */ #endif /* linux */ /* 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; /* Info structure for FindFirstFile handle */ typedef struct { LPSTR path; LPSTR mask; int drive; int skip; } FIND_FIRST_INFO; /* Directory info for DOSFS_ReadDir */ typedef struct { DIR *dir; #ifdef VFAT_IOCTL_READDIR_BOTH int fd; char short_name[12]; KERNEL_DIRENT dirent[2]; #endif } DOS_DIR; /*********************************************************************** * 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_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 FALSE if the name is not a valid DOS name. * 'buffer' must be at least 12 characters long. */ BOOL32 DOSFS_ToDosFCBFormat( LPCSTR name, LPSTR buffer ) { static const char invalid_chars[] = INVALID_DOS_CHARS; const char *p = name; int i; /* Check for "." and ".." */ if (*p == '.') { p++; strcpy( buffer, ". " ); if (*p == '.') { buffer[1] = '.'; p++; } return (!*p || (*p == '/') || (*p == '\\')); } 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 FALSE; 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 FALSE; } if (*p == '.') p++; /* Skip dot */ for (i = 8; i < 11; i++) { switch(*p) { case '\0': case '\\': case '/': buffer[i] = ' '; break; case '.': return FALSE; /* Second extension not allowed */ case '?': p++; /* fall through */ case '*': buffer[i] = '?'; break; default: if (strchr( invalid_chars, *p )) return FALSE; buffer[i] = toupper(*p); p++; break; } } buffer[11] = '\0'; return TRUE; } /*********************************************************************** * 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 '/'. * 'buffer' must be at least 13 characters long. */ static void DOSFS_ToDosDTAFormat( LPCSTR name, LPSTR buffer ) { 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'; } /*********************************************************************** * 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 ) { if (!strcmp( mask, "*.*" )) return 1; 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_OpenDir */ static DOS_DIR *DOSFS_OpenDir( LPCSTR path ) { DOS_DIR *dir = HeapAlloc( SystemHeap, 0, sizeof(*dir) ); if (!dir) { DOS_ERROR( ER_OutOfMemory, EC_OutOfResource, SA_Abort, EL_Memory ); return NULL; } #ifdef VFAT_IOCTL_READDIR_BOTH /* Check if the VFAT ioctl is supported on this directory */ if ((dir->fd = open( path, O_RDONLY )) != -1) { if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1) { close( dir->fd ); dir->fd = -1; } else { /* Set the file pointer back at the start of the directory */ lseek( dir->fd, 0, SEEK_SET ); dir->dir = NULL; return dir; } } #endif /* VFAT_IOCTL_READDIR_BOTH */ /* Now use the standard opendir/readdir interface */ if (!(dir->dir = opendir( path ))) { HeapFree( SystemHeap, 0, dir ); return NULL; } return dir; } /*********************************************************************** * DOSFS_CloseDir */ static void DOSFS_CloseDir( DOS_DIR *dir ) { #ifdef VFAT_IOCTL_READDIR_BOTH if (dir->fd != -1) close( dir->fd ); #endif /* VFAT_IOCTL_READDIR_BOTH */ if (dir->dir) closedir( dir->dir ); HeapFree( SystemHeap, 0, dir ); } /*********************************************************************** * DOSFS_ReadDir */ static BOOL32 DOSFS_ReadDir( DOS_DIR *dir, LPCSTR *long_name, LPCSTR *short_name ) { struct dirent *dirent; #ifdef VFAT_IOCTL_READDIR_BOTH if (dir->fd != -1) { if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1) return FALSE; if (!dir->dirent[0].d_reclen) return FALSE; if (!DOSFS_ToDosFCBFormat( dir->dirent[0].d_name, dir->short_name )) dir->short_name[0] = '\0'; *short_name = dir->short_name; if (dir->dirent[1].d_name[0]) *long_name = dir->dirent[1].d_name; else *long_name = dir->dirent[0].d_name; return TRUE; } #endif /* VFAT_IOCTL_READDIR_BOTH */ if (!(dirent = readdir( dir->dir ))) return FALSE; *long_name = dirent->d_name; *short_name = NULL; return TRUE; } /*********************************************************************** * 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 '/'. * 'buffer' must be at least 13 characters long. */ static void DOSFS_Hash( LPCSTR name, LPSTR buffer, BOOL32 dir_format, BOOL32 ignore_case ) { static const char invalid_chars[] = INVALID_DOS_CHARS "~."; static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"; 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; } /* 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'; return; } /* 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++ = strchr( invalid_chars, *ext ) ? '_' : toupper(*ext); } if (!dir_format) *dst = '\0'; } /*********************************************************************** * 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 TRUE if OK, FALSE if no file name matches. * * 'long_buf' must be at least 'long_len' characters long. If the long name * turns out to be larger than that, the function returns FALSE. * 'short_buf' must be at least 13 characters long. */ BOOL32 DOSFS_FindUnixName( LPCSTR path, LPCSTR name, LPSTR long_buf, INT32 long_len, LPSTR short_buf, BOOL32 ignore_case) { DOS_DIR *dir; LPCSTR long_name, short_name; char dos_name[12], tmp_buf[13]; BOOL32 ret; const char *p = strchr( name, '/' ); int len = p ? (int)(p - name) : strlen(name); if ((p = strchr( name, '\\' ))) len = MIN( (int)(p - name), len ); if (long_len < len + 1) return FALSE; dprintf_dosfs( stddeb, "DOSFS_FindUnixName: %s,%s\n", path, name ); if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0'; if (!(dir = DOSFS_OpenDir( path ))) { dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s): can't open dir\n", path, name ); return FALSE; } while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name ))) { /* Check against Unix name */ if (len == strlen(long_name)) { if (!ignore_case) { if (!lstrncmp32A( long_name, name, len )) break; } else { if (!lstrncmpi32A( long_name, name, len )) break; } } if (dos_name[0]) { /* Check against hashed DOS name */ if (!short_name) { DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case ); short_name = tmp_buf; } if (!strcmp( dos_name, short_name )) break; } } if (ret) { if (long_buf) strcpy( long_buf, long_name ); if (short_buf) { if (short_name) DOSFS_ToDosDTAFormat( short_name, short_buf ); else DOSFS_Hash( long_name, short_buf, FALSE, ignore_case ); } dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s) -> %s (%s)\n", path, name, long_name, short_buf ? short_buf : "***"); } else dprintf_dosfs(stddeb,"DOSFS_FindUnixName(%s,%s) -> ** Not found **\n", path, name ); DOSFS_CloseDir( dir ); return ret; } /*********************************************************************** * 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_GetPathDrive * * Get the drive specified by a given path name (DOS or Unix format). */ static int DOSFS_GetPathDrive( const char **name ) { int drive; const char *p = *name; if (*p && (p[1] == ':')) { drive = toupper(*p) - 'A'; *name += 2; } else if (*p == '/') /* 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 -1; } return drive; } /*********************************************************************** * DOSFS_GetFullName * * Convert a file name (DOS or mixed DOS/Unix format) to a valid * Unix name / short DOS name pair. * Return FALSE if one of the path components does not exist. The last path * component is only checked if 'check_last' is non-zero. * The buffers pointed to by 'long_buf' and 'short_buf' must be * at least MAX_PATHNAME_LEN long. */ BOOL32 DOSFS_GetFullName( LPCSTR name, BOOL32 check_last, DOS_FULL_NAME *full ) { BOOL32 found; UINT32 flags; char *p_l, *p_s, *root; dprintf_dosfs( stddeb, "DOSFS_GetFullName: %s (last=%d)\n", name, check_last ); if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE; flags = DRIVE_GetFlags( full->drive ); lstrcpyn32A( full->long_name, DRIVE_GetRoot( full->drive ), sizeof(full->long_name) ); if (full->long_name[1]) root = full->long_name + strlen(full->long_name); else root = full->long_name; /* root directory */ strcpy( full->short_name, "A:\\" ); full->short_name[0] += full->drive; if ((*name == '\\') || (*name == '/')) /* Absolute path */ { while ((*name == '\\') || (*name == '/')) name++; } else /* Relative path */ { lstrcpyn32A( root + 1, DRIVE_GetUnixCwd( full->drive ), sizeof(full->long_name) - (root - full->long_name) - 1 ); if (root[1]) *root = '/'; lstrcpyn32A( full->short_name + 3, DRIVE_GetDosCwd( full->drive ), sizeof(full->short_name) - 3 ); } p_l = full->long_name[1] ? full->long_name + strlen(full->long_name) : full->long_name; p_s = full->short_name[3] ? full->short_name + strlen(full->short_name) : full->short_name + 2; found = TRUE; while (*name && found) { /* Check for '.' and '..' */ if (*name == '.') { if (IS_END_OF_NAME(name[1])) { name++; while ((*name == '\\') || (*name == '/')) name++; continue; } else if ((name[1] == '.') && IS_END_OF_NAME(name[2])) { name += 2; while ((*name == '\\') || (*name == '/')) name++; while ((p_l > root) && (*p_l != '/')) p_l--; while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--; *p_l = *p_s = '\0'; /* Remove trailing separator */ continue; } } /* Make sure buffers are large enough */ if ((p_s >= full->short_name + sizeof(full->short_name) - 14) || (p_l >= full->long_name + sizeof(full->long_name) - 1)) { DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk ); return FALSE; } /* Get the long and short name matching the file name */ if ((found = DOSFS_FindUnixName( full->long_name, name, p_l + 1, sizeof(full->long_name) - (p_l - full->long_name) - 1, p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) ))) { *p_l++ = '/'; p_l += strlen(p_l); *p_s++ = '\\'; p_s += strlen(p_s); while (!IS_END_OF_NAME(*name)) name++; } else if (!check_last) { *p_l++ = '/'; *p_s++ = '\\'; while (!IS_END_OF_NAME(*name) && (p_s < full->short_name + sizeof(full->short_name) - 1) && (p_l < full->long_name + sizeof(full->long_name) - 1)) { *p_l++ = *p_s++ = tolower(*name); name++; } *p_l = *p_s = '\0'; } while ((*name == '\\') || (*name == '/')) name++; } if (!found) { if (check_last) { DOS_ERROR( ER_FileNotFound, EC_NotFound, SA_Abort, EL_Disk ); return FALSE; } if (*name) /* Not last */ { DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk ); return FALSE; } } if (!full->long_name[0]) strcpy( full->long_name, "/" ); if (!full->short_name[2]) strcpy( full->short_name + 2, "\\" ); dprintf_dosfs( stddeb, "DOSFS_GetFullName: returning %s = %s\n", full->long_name, full->short_name ); return TRUE; } /*********************************************************************** * GetShortPathName32A (KERNEL32.271) */ DWORD GetShortPathName32A( LPCSTR longpath, LPSTR shortpath, DWORD shortlen ) { DOS_FULL_NAME full_name; /* FIXME: is it correct to always return a fully qualified short path? */ if (!DOSFS_GetFullName( longpath, TRUE, &full_name )) return 0; lstrcpyn32A( shortpath, full_name.short_name, shortlen ); return strlen( full_name.short_name ); } /*********************************************************************** * GetShortPathName32W (KERNEL32.272) */ DWORD GetShortPathName32W( LPCWSTR longpath, LPWSTR shortpath, DWORD shortlen ) { DOS_FULL_NAME full_name; DWORD ret = 0; LPSTR longpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, longpath ); /* FIXME: is it correct to always return a fully qualified short path? */ if (DOSFS_GetFullName( longpathA, TRUE, &full_name )) { ret = strlen( full_name.short_name ); lstrcpynAtoW( shortpath, full_name.short_name, shortlen ); } HeapFree( GetProcessHeap(), 0, longpathA ); return ret; } /*********************************************************************** * DOSFS_DoGetFullPathName * * Implementation of GetFullPathName32A/W. */ static DWORD DOSFS_DoGetFullPathName( LPCSTR name, DWORD len, LPSTR result, BOOL32 unicode ) { char buffer[MAX_PATHNAME_LEN]; int drive; char *p; dprintf_dosfs( stddeb, "GetFullPathName: converting %s\n", name ); if (!name || !result) return 0; if ((drive = DOSFS_GetPathDrive( &name )) == -1) return 0; p = buffer; *p++ = 'A' + drive; *p++ = ':'; if (IS_END_OF_NAME(*name)) /* Absolute path */ { while ((*name == '\\') || (*name == '/')) name++; } else /* Relative path */ { *p++ = '\\'; lstrcpyn32A( p, DRIVE_GetDosCwd(drive), sizeof(buffer) - 3 ); if (*p) p += strlen(p); else p--; } *p = '\0'; while (*name) { if (*name == '.') { if (IS_END_OF_NAME(name[1])) { name++; while ((*name == '\\') || (*name == '/')) name++; continue; } else if ((name[1] == '.') && IS_END_OF_NAME(name[2])) { name += 2; while ((*name == '\\') || (*name == '/')) name++; while ((p > buffer + 2) && (*p != '\\')) p--; *p = '\0'; /* Remove trailing separator */ continue; } } if (p >= buffer + sizeof(buffer) - 1) { DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk ); return 0; } *p++ = '\\'; while (!IS_END_OF_NAME(*name) && (p < buffer + sizeof(buffer) - 1)) *p++ = *name++; *p = '\0'; while ((*name == '\\') || (*name == '/')) name++; } if (!buffer[2]) { buffer[2] = '\\'; buffer[3] = '\0'; } if (!(DRIVE_GetFlags(drive) & DRIVE_CASE_PRESERVING)) CharUpper32A( buffer ); if (unicode) lstrcpynAtoW( (LPWSTR)result, buffer, len ); else lstrcpyn32A( result, buffer, len ); dprintf_dosfs( stddeb, "GetFullPathName: returning %s\n", buffer ); return strlen(buffer); } /*********************************************************************** * GetFullPathName32A (KERNEL32.272) */ DWORD GetFullPathName32A(LPCSTR name, DWORD len, LPSTR buffer, LPSTR *lastpart) { DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer, FALSE ); if (ret && lastpart) { LPSTR p = buffer + strlen(buffer); while ((p > buffer + 2) && (*p != '\\')) p--; *lastpart = p + 1; } return ret; } /*********************************************************************** * GetFullPathName32W (KERNEL32.273) */ DWORD GetFullPathName32W( LPCWSTR name, DWORD len, LPWSTR buffer, LPWSTR *lastpart ) { LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name ); DWORD ret = DOSFS_DoGetFullPathName( nameA, len, (LPSTR)buffer, TRUE ); HeapFree( GetProcessHeap(), 0, nameA ); if (ret && lastpart) { LPWSTR p = buffer + lstrlen32W(buffer); while ((p > buffer + 2) && (*p != '\\')) p--; *lastpart = p + 1; } return ret; } /*********************************************************************** * 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, WIN32_FIND_DATA32A *entry ) { static DOS_DIR *dir = NULL; int count = 0; static char buffer[MAX_PATHNAME_LEN]; static int cur_pos = 0; static int drive_root = 0; char *p; char dos_name[13]; LPCSTR long_name, short_name; UINT32 flags; BY_HANDLE_FILE_INFORMATION info; if ((attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL) { if (skip) return 0; entry->dwFileAttributes = FILE_ATTRIBUTE_LABEL; DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftCreationTime, 0 ); DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastAccessTime, 0 ); DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastWriteTime, 0 ); entry->nFileSizeHigh = 0; entry->nFileSizeLow = 0; entry->dwReserved0 = 0; entry->dwReserved1 = 0; DOSFS_ToDosDTAFormat( DRIVE_GetLabel( drive ), entry->cFileName ); strcpy( entry->cAlternateFileName, entry->cFileName ); 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) DOSFS_CloseDir(dir); if (!*path) path = "/"; if (!(dir = DOSFS_OpenDir(path))) return 0; drive_path = path + strlen(DRIVE_GetRoot(drive)); while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++; drive_root = !*drive_path; 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 ); while (DOSFS_ReadDir( dir, &long_name, &short_name )) { if (skip-- > 0) continue; count++; /* Don't return '.' and '..' in the root of the drive */ if (drive_root && (long_name[0] == '.') && (!long_name[1] || ((long_name[1] == '.') && !long_name[2]))) continue; /* Check the long mask */ if (long_mask) { if (!DOSFS_MatchLong( long_mask, long_name, flags & DRIVE_CASE_SENSITIVE )) continue; } /* Check the short mask */ if (short_mask) { if (!short_name) { DOSFS_Hash( long_name, dos_name, TRUE, !(flags & DRIVE_CASE_SENSITIVE) ); short_name = dos_name; } if (!DOSFS_MatchShort( short_mask, short_name )) continue; } /* Check the file attributes */ lstrcpyn32A( p, long_name, sizeof(buffer) - (int)(p - buffer) ); if (!FILE_Stat( buffer, &info )) { fprintf( stderr, "DOSFS_FindNext: can't stat %s\n", buffer ); continue; } if (info.dwFileAttributes & ~attr) continue; /* We now have a matching entry; fill the result and return */ entry->dwFileAttributes = info.dwFileAttributes; entry->ftCreationTime = info.ftCreationTime; entry->ftLastAccessTime = info.ftLastAccessTime; entry->ftLastWriteTime = info.ftLastWriteTime; entry->nFileSizeHigh = info.nFileSizeHigh; entry->nFileSizeLow = info.nFileSizeLow; if (short_name) DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName ); else DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE, !(flags & DRIVE_CASE_SENSITIVE) ); lstrcpyn32A( entry->cFileName, long_name, sizeof(entry->cFileName) ); if (!(flags & DRIVE_CASE_PRESERVING)) CharLower32A( entry->cFileName ); dprintf_dosfs( stddeb, "DOSFS_FindNext: returning %s (%s) %02lx %ld\n", entry->cFileName, entry->cAlternateFileName, entry->dwFileAttributes, entry->nFileSizeLow ); cur_pos += count; p[-1] = '\0'; /* Remove trailing slash in buffer */ return count; } DOSFS_CloseDir( dir ); dir = NULL; return 0; /* End of directory */ } /************************************************************************* * FindFirstFile16 (KERNEL.413) */ HANDLE16 FindFirstFile16( LPCSTR path, WIN32_FIND_DATA32A *data ) { DOS_FULL_NAME full_name; HGLOBAL16 handle; FIND_FIRST_INFO *info; if (!path) return 0; if (!DOSFS_GetFullName( path, FALSE, &full_name )) return INVALID_HANDLE_VALUE16; if (!(handle = GlobalAlloc16( GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO) ))) return INVALID_HANDLE_VALUE16; info = (FIND_FIRST_INFO *)GlobalLock16( handle ); info->path = HEAP_strdupA( SystemHeap, 0, full_name.long_name ); info->mask = strrchr( info->path, '/' ); *(info->mask++) = '\0'; if (path[0] && (path[1] == ':')) info->drive = toupper(*path) - 'A'; else info->drive = DRIVE_GetCurrentDrive(); info->skip = 0; GlobalUnlock16( handle ); if (!FindNextFile16( handle, data )) { FindClose16( handle ); DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk ); return INVALID_HANDLE_VALUE16; } return handle; } /************************************************************************* * FindFirstFile32A (KERNEL32.123) */ HANDLE32 FindFirstFile32A( LPCSTR path, WIN32_FIND_DATA32A *data ) { HANDLE32 handle = FindFirstFile16( path, data ); if (handle == INVALID_HANDLE_VALUE16) return INVALID_HANDLE_VALUE32; return handle; } /************************************************************************* * FindFirstFile32W (KERNEL32.124) */ HANDLE32 FindFirstFile32W( LPCWSTR path, WIN32_FIND_DATA32W *data ) { WIN32_FIND_DATA32A dataA; LPSTR pathA = HEAP_strdupWtoA( GetProcessHeap(), 0, path ); HANDLE32 handle = FindFirstFile32A( pathA, &dataA ); HeapFree( GetProcessHeap(), 0, pathA ); if (handle != INVALID_HANDLE_VALUE32) { data->dwFileAttributes = dataA.dwFileAttributes; data->ftCreationTime = dataA.ftCreationTime; data->ftLastAccessTime = dataA.ftLastAccessTime; data->ftLastWriteTime = dataA.ftLastWriteTime; data->nFileSizeHigh = dataA.nFileSizeHigh; data->nFileSizeLow = dataA.nFileSizeLow; lstrcpyAtoW( data->cFileName, dataA.cFileName ); lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName ); } return handle; } /************************************************************************* * FindNextFile16 (KERNEL.414) */ BOOL16 FindNextFile16( HANDLE16 handle, WIN32_FIND_DATA32A *data ) { FIND_FIRST_INFO *info; int count; if (!(info = (FIND_FIRST_INFO *)GlobalLock16( handle ))) { DOS_ERROR( ER_InvalidHandle, EC_ProgramError, SA_Abort, EL_Disk ); return FALSE; } GlobalUnlock16( handle ); if (!info->path) { DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk ); return FALSE; } if (!(count = DOSFS_FindNext( info->path, NULL, info->mask, info->drive, 0xff, info->skip, data ))) { HeapFree( SystemHeap, 0, info->path ); info->path = info->mask = NULL; DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk ); return FALSE; } info->skip += count; return TRUE; } /************************************************************************* * FindNextFile32A (KERNEL32.126) */ BOOL32 FindNextFile32A( HANDLE32 handle, WIN32_FIND_DATA32A *data ) { return FindNextFile16( handle, data ); } /************************************************************************* * FindNextFile32W (KERNEL32.127) */ BOOL32 FindNextFile32W( HANDLE32 handle, WIN32_FIND_DATA32W *data ) { WIN32_FIND_DATA32A dataA; if (!FindNextFile32A( handle, &dataA )) return FALSE; data->dwFileAttributes = dataA.dwFileAttributes; data->ftCreationTime = dataA.ftCreationTime; data->ftLastAccessTime = dataA.ftLastAccessTime; data->ftLastWriteTime = dataA.ftLastWriteTime; data->nFileSizeHigh = dataA.nFileSizeHigh; data->nFileSizeLow = dataA.nFileSizeLow; lstrcpyAtoW( data->cFileName, dataA.cFileName ); lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName ); return TRUE; } /************************************************************************* * FindClose16 (KERNEL.415) */ BOOL16 FindClose16( HANDLE16 handle ) { FIND_FIRST_INFO *info; if ((handle == INVALID_HANDLE_VALUE16) || !(info = (FIND_FIRST_INFO *)GlobalLock16( handle ))) { DOS_ERROR( ER_InvalidHandle, EC_ProgramError, SA_Abort, EL_Disk ); return FALSE; } if (info->path) HeapFree( SystemHeap, 0, info->path ); GlobalUnlock16( handle ); GlobalFree16( handle ); return TRUE; } /************************************************************************* * FindClose32 (KERNEL32.119) */ BOOL32 FindClose32( HANDLE32 handle ) { return FindClose16( (HANDLE16)handle ); } /*********************************************************************** * DOSFS_UnixTimeToFileTime * * Convert a Unix time to FILETIME format. * The FILETIME structure is a 64-bit value representing the number of * 100-nanosecond intervals since January 1, 1601, 0:00. * 'remainder' is the nonnegative number of 100-ns intervals * corresponding to the time fraction smaller than 1 second that * couldn't be stored in the time_t value. */ void DOSFS_UnixTimeToFileTime( time_t unix_time, FILETIME *filetime, DWORD remainder ) { /* NOTES: CONSTANTS: The time difference between 1 January 1601, 00:00:00 and 1 January 1970, 00:00:00 is 369 years, plus the leap years from 1604 to 1968, excluding 1700, 1800, 1900. This makes (1968 - 1600) / 4 - 3 = 89 leap days, and a total of 134774 days. Any day in that period had 24 * 60 * 60 = 86400 seconds. The time difference is 134774 * 86400 * 10000000, which can be written 116444736000000000 27111902 * 2^32 + 3577643008 413 * 2^48 + 45534 * 2^32 + 54590 * 2^16 + 32768 If you find that these constants are buggy, please change them in all instances in both conversion functions. VERSIONS: There are two versions, one of them uses long long variables and is presumably faster but not ISO C. The other one uses standard C data types and operations but relies on the assumption that negative numbers are stored as 2's complement (-1 is 0xffff....). If this assumption is violated, dates before 1970 will not convert correctly. This should however work on any reasonable architecture where WINE will run. DETAILS: Take care not to remove the casts. I have tested these functions (in both versions) for a lot of numbers. I would be interested in results on other compilers than GCC. The operations have been designed to account for the possibility of 64-bit time_t in future UNICES. Even the versions without internal long long numbers will work if time_t only is 64 bit. A 32-bit shift, which was necessary for that operation, turned out not to work correctly in GCC, besides giving the warning. So I used a double 16-bit shift instead. Numbers are in the ISO version represented by three limbs, the most significant with 32 bit, the other two with 16 bit each. As the modulo-operator % is not well-defined for negative numbers, negative divisors have been avoided in DOSFS_FileTimeToUnixTime. There might be quicker ways to do this in C. Certainly so in assembler. Claus Fischer, fischer@iue.tuwien.ac.at */ #if __GNUC__ # define USE_LONG_LONG 1 #else # define USE_LONG_LONG 0 #endif #if USE_LONG_LONG /* gcc supports long long type */ long long int t = unix_time; t *= 10000000; t += 116444736000000000LL; t += remainder; filetime->dwLowDateTime = (UINT32)t; filetime->dwHighDateTime = (UINT32)(t >> 32); #else /* ISO version */ UINT32 a0; /* 16 bit, low bits */ UINT32 a1; /* 16 bit, medium bits */ UINT32 a2; /* 32 bit, high bits */ /* Copy the unix time to a2/a1/a0 */ a0 = unix_time & 0xffff; a1 = (unix_time >> 16) & 0xffff; /* This is obsolete if unix_time is only 32 bits, but it does not hurt. Do not replace this by >> 32, it gives a compiler warning and it does not work. */ a2 = (unix_time >= 0 ? (unix_time >> 16) >> 16 : ~((~unix_time >> 16) >> 16)); /* Multiply a by 10000000 (a = a2/a1/a0) Split the factor into 10000 * 1000 which are both less than 0xffff. */ a0 *= 10000; a1 = a1 * 10000 + (a0 >> 16); a2 = a2 * 10000 + (a1 >> 16); a0 &= 0xffff; a1 &= 0xffff; a0 *= 1000; a1 = a1 * 1000 + (a0 >> 16); a2 = a2 * 1000 + (a1 >> 16); a0 &= 0xffff; a1 &= 0xffff; /* Add the time difference and the remainder */ a0 += 32768 + (remainder & 0xffff); a1 += 54590 + (remainder >> 16 ) + (a0 >> 16); a2 += 27111902 + (a1 >> 16); a0 &= 0xffff; a1 &= 0xffff; /* Set filetime */ filetime->dwLowDateTime = (a1 << 16) + a0; filetime->dwHighDateTime = a2; #endif } /*********************************************************************** * DOSFS_FileTimeToUnixTime * * Convert a FILETIME format to Unix time. * If not NULL, 'remainder' contains the fractional part of the filetime, * in the range of [0..9999999] (even if time_t is negative). */ time_t DOSFS_FileTimeToUnixTime( const FILETIME *filetime, DWORD *remainder ) { /* Read the comment in the function DOSFS_UnixTimeToFileTime. */ #if USE_LONG_LONG long long int t = filetime->dwHighDateTime; t <<= 32; t += (UINT32)filetime->dwLowDateTime; t -= 116444736000000000LL; if (t < 0) { if (remainder) *remainder = 9999999 - (-t - 1) % 10000000; return -1 - ((-t - 1) / 10000000); } else { if (remainder) *remainder = t % 10000000; return t / 10000000; } #else /* ISO version */ UINT32 a0; /* 16 bit, low bits */ UINT32 a1; /* 16 bit, medium bits */ UINT32 a2; /* 32 bit, high bits */ UINT32 r; /* remainder of division */ unsigned int carry; /* carry bit for subtraction */ int negative; /* whether a represents a negative value */ /* Copy the time values to a2/a1/a0 */ a2 = (UINT32)filetime->dwHighDateTime; a1 = ((UINT32)filetime->dwLowDateTime ) >> 16; a0 = ((UINT32)filetime->dwLowDateTime ) & 0xffff; /* Subtract the time difference */ if (a0 >= 32768 ) a0 -= 32768 , carry = 0; else a0 += (1 << 16) - 32768 , carry = 1; if (a1 >= 54590 + carry) a1 -= 54590 + carry, carry = 0; else a1 += (1 << 16) - 54590 - carry, carry = 1; a2 -= 27111902 + carry; /* If a is negative, replace a by (-1-a) */ negative = (a2 >= ((UINT32)1) << 31); if (negative) { /* Set a to -a - 1 (a is a2/a1/a0) */ a0 = 0xffff - a0; a1 = 0xffff - a1; a2 = ~a2; } /* Divide a by 10000000 (a = a2/a1/a0), put the rest into r. Split the divisor into 10000 * 1000 which are both less than 0xffff. */ a1 += (a2 % 10000) << 16; a2 /= 10000; a0 += (a1 % 10000) << 16; a1 /= 10000; r = a0 % 10000; a0 /= 10000; a1 += (a2 % 1000) << 16; a2 /= 1000; a0 += (a1 % 1000) << 16; a1 /= 1000; r += (a0 % 1000) * 10000; a0 /= 1000; /* If a was negative, replace a by (-1-a) and r by (9999999 - r) */ if (negative) { /* Set a to -a - 1 (a is a2/a1/a0) */ a0 = 0xffff - a0; a1 = 0xffff - a1; a2 = ~a2; r = 9999999 - r; } if (remainder) *remainder = r; /* Do not replace this by << 32, it gives a compiler warning and it does not work. */ return ((((time_t)a2) << 16) << 16) + (a1 << 16) + a0; #endif } /*********************************************************************** * DosDateTimeToFileTime (KERNEL32.76) */ BOOL32 DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft ) { struct tm newtm; newtm.tm_sec = (fattime & 0x1f) * 2; newtm.tm_min = (fattime >> 5) & 0x3f; newtm.tm_hour = (fattime >> 11); newtm.tm_mday = (fatdate & 0x1f); newtm.tm_mon = ((fatdate >> 5) & 0x0f) - 1; newtm.tm_year = (fatdate >> 9) + 80; DOSFS_UnixTimeToFileTime( mktime( &newtm ), ft, 0 ); return TRUE; } /*********************************************************************** * FileTimeToDosDateTime (KERNEL32.111) */ BOOL32 FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate, LPWORD fattime ) { time_t unixtime = DOSFS_FileTimeToUnixTime( ft, NULL ); struct tm *tm = localtime( &unixtime ); if (fattime) *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2); if (fatdate) *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday; return TRUE; } /*********************************************************************** * LocalFileTimeToFileTime (KERNEL32.373) */ BOOL32 LocalFileTimeToFileTime( const FILETIME *localft, LPFILETIME utcft ) { struct tm *xtm; DWORD remainder; /* convert from local to UTC. Perhaps not correct. FIXME */ time_t unixtime = DOSFS_FileTimeToUnixTime( localft, &remainder ); xtm = gmtime( &unixtime ); DOSFS_UnixTimeToFileTime( mktime(xtm), utcft, remainder ); return TRUE; } /*********************************************************************** * FileTimeToLocalFileTime (KERNEL32.112) */ BOOL32 FileTimeToLocalFileTime( const FILETIME *utcft, LPFILETIME localft ) { struct tm *xtm; DWORD remainder; /* convert from UTC to local. Perhaps not correct. FIXME */ time_t unixtime = DOSFS_FileTimeToUnixTime( utcft, &remainder ); xtm = localtime( &unixtime ); DOSFS_UnixTimeToFileTime( mktime(xtm), localft, remainder ); return TRUE; } /*********************************************************************** * FileTimeToSystemTime (KERNEL32.113) */ BOOL32 FileTimeToSystemTime( const FILETIME *ft, LPSYSTEMTIME syst ) { struct tm *xtm; DWORD remainder; time_t xtime = DOSFS_FileTimeToUnixTime( ft, &remainder ); xtm = gmtime(&xtime); syst->wYear = xtm->tm_year; syst->wMonth = xtm->tm_mon; syst->wDayOfWeek = xtm->tm_wday; syst->wDay = xtm->tm_mday; syst->wHour = xtm->tm_hour; syst->wMinute = xtm->tm_min; syst->wSecond = xtm->tm_sec; syst->wMilliseconds = remainder / 10000; return TRUE; } /*********************************************************************** * QueryDosDeviceA (KERNEL32.413) * * returns array of strings terminated by \0, terminated by \0 */ DWORD QueryDosDevice32A(LPCSTR devname,LPSTR target,DWORD bufsize) { LPSTR s; char buffer[200]; dprintf_dosfs(stddeb,"QueryDosDevice(%s,...)\n",devname?devname:""); if (!devname) { /* return known MSDOS devices */ lstrcpy32A(buffer,"CON COM1 COM2 LPT1 NUL "); while ((s=strchr(buffer,' '))) *s='\0'; lstrcpyn32A(target,buffer,bufsize); return strlen(buffer); } lstrcpy32A(buffer,"\\DEV\\"); lstrcat32A(buffer,devname); if ((s=strchr(buffer,':'))) *s='\0'; lstrcpyn32A(target,buffer,bufsize); return strlen(buffer); } /*********************************************************************** * QueryDosDeviceW (KERNEL32.414) * * returns array of strings terminated by \0, terminated by \0 */ DWORD QueryDosDevice32W(LPCWSTR devname,LPWSTR target,DWORD bufsize) { LPSTR devnameA = devname?HEAP_strdupWtoA(GetProcessHeap(),0,devname):NULL; LPSTR targetA = (LPSTR)HEAP_xalloc(GetProcessHeap(),0,bufsize); DWORD ret = QueryDosDevice32A(devnameA,targetA,bufsize); lstrcpynAtoW(target,targetA,bufsize); if (devnameA) HeapFree(GetProcessHeap(),0,devnameA); if (targetA) HeapFree(GetProcessHeap(),0,targetA); return ret; } /*********************************************************************** * SystemTimeToFileTime (KERNEL32.526) */ BOOL32 SystemTimeToFileTime( const SYSTEMTIME *syst, LPFILETIME ft ) { struct tm xtm; xtm.tm_year = syst->wYear; xtm.tm_mon = syst->wMonth; xtm.tm_wday = syst->wDayOfWeek; xtm.tm_mday = syst->wDay; xtm.tm_hour = syst->wHour; xtm.tm_min = syst->wMinute; xtm.tm_sec = syst->wSecond; DOSFS_UnixTimeToFileTime( mktime(&xtm), ft, syst->wMilliseconds * 10000 ); return TRUE; }