// Windows/FileFind.cpp #include "StdAfx.h" #include "FileFind.h" #include "FileIO.h" #include "../Common/StringConvert.h" #ifndef _UNICODE extern bool g_IsNT; #endif #include #include #include #ifdef ENV_HAVE_LSTAT extern "C" { int global_use_lstat=1; // default behaviour : p7zip stores symlinks instead of dumping the files they point to } #endif #define NEED_NAME_WINDOWS_TO_UNIX #include "myPrivate.h" // #define TRACEN(u) u; #define TRACEN(u) /* */ void my_windows_split_path(const AString &p_path, AString &dir , AString &base) { int pos = p_path.ReverseFind('/'); if (pos == -1) { // no separator dir = "."; if (p_path.IsEmpty()) base = "."; else base = p_path; } else if ((pos+1) < p_path.Len()) { // true separator base = p_path.Ptr(pos+1); while ((pos >= 1) && (p_path[pos-1] == '/')) pos--; if (pos == 0) dir = "/"; else dir = p_path.Left(pos); } else { // separator at the end of the path // pos = p_path.find_last_not_of("/"); pos = -1; int ind = 0; while (p_path[ind]) { if (p_path[ind] != '/') pos = ind; ind++; } if (pos == -1) { base = "/"; dir = "/"; } else { my_windows_split_path(p_path.Left(pos+1),dir,base); } } } static void my_windows_split_path(const UString &p_path, UString &dir , UString &base) { int pos = p_path.ReverseFind(L'/'); if (pos == -1) { // no separator dir = L"."; if (p_path.IsEmpty()) base = L"."; else base = p_path; } else if ((pos+1) < p_path.Len()) { // true separator base = p_path.Ptr(pos+1); while ((pos >= 1) && (p_path[pos-1] == L'/')) pos--; if (pos == 0) dir = L"/"; else dir = p_path.Left(pos); } else { // separator at the end of the path // pos = p_path.find_last_not_of("/"); pos = -1; int ind = 0; while (p_path[ind]) { if (p_path[ind] != L'/') pos = ind; ind++; } if (pos == -1) { base = L"/"; dir = L"/"; } else { my_windows_split_path(p_path.Left(pos+1),dir,base); } } } static int filter_pattern(const char *string , const char *pattern , int flags_nocase) { if ((string == 0) || (*string==0)) { if (pattern == 0) return 1; while (*pattern=='*') ++pattern; return (!*pattern); } switch (*pattern) { case '*': if (!filter_pattern(string+1,pattern,flags_nocase)) return filter_pattern(string,pattern+1,flags_nocase); return 1; case 0: if (*string==0) return 1; break; case '?': return filter_pattern(string+1,pattern+1,flags_nocase); default: if ( ((flags_nocase) && (tolower(*pattern)==tolower(*string))) || (*pattern == *string) ) { return filter_pattern(string+1,pattern+1,flags_nocase); } break; } return 0; } namespace NWindows { namespace NFile { #ifdef SUPPORT_DEVICE_FILE bool IsDeviceName(CFSTR n); #endif #if defined(WIN_LONG_PATH) bool GetLongPath(CFSTR fileName, UString &res); #endif namespace NFind { bool CFileInfo::IsDots() const { if (!IsDir() || Name.IsEmpty()) return false; if (Name[0] != FTEXT('.')) return false; return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == FTEXT('.')); } #define WIN_FD_TO_MY_FI(fi, fd) \ fi.Attrib = fd.dwFileAttributes; \ fi.CTime = fd.ftCreationTime; \ fi.ATime = fd.ftLastAccessTime; \ fi.MTime = fd.ftLastWriteTime; \ fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \ fi.IsDevice = false; /* #ifdef UNDER_CE fi.ObjectID = fd.dwOID; #else fi.ReparseTag = fd.dwReserved0; #endif */ #ifndef _UNICODE static inline UINT GetCurrentCodePage() { return ::AreFileApisANSI() ? CP_ACP : CP_OEMCP; } static void ConvertWIN32_FIND_DATA_To_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi) { WIN_FD_TO_MY_FI(fi, fd); fi.Name = fas2fs(fd.cFileName); } #endif //////////////////////////////// // CFindFile bool CFindFile::Close() { if(_dirp == 0) return true; int ret = closedir(_dirp); if (ret == 0) { _dirp = 0; return true; } return false; } static bool originalFilename(const UString & src, AString & res) { // Try to recover the original filename res = ""; int i=0; while (src[i]) { if (src[i] >= 256) { return false; } else { res += char(src[i]); } i++; } return true; } // Warning this function cannot update "fileInfo.Name" static int fillin_CFileInfo(CFileInfo &fileInfo,const char *filename,bool ignoreLink) { struct stat stat_info; int ret; #ifdef ENV_HAVE_LSTAT if ( (global_use_lstat) && (ignoreLink == false)) { ret = lstat(filename,&stat_info); } else #endif { ret = stat(filename,&stat_info); } // printf("fillin_CFileInfo(%s,%d)=%d mode=%o\n",filename,(int)ignoreLink,ret,(unsigned)stat_info.st_mode); if (ret != 0) return ret; /* FIXME : FILE_ATTRIBUTE_HIDDEN ? */ if (S_ISDIR(stat_info.st_mode)) { fileInfo.Attrib = FILE_ATTRIBUTE_DIRECTORY; } else { fileInfo.Attrib = FILE_ATTRIBUTE_ARCHIVE; } if (!(stat_info.st_mode & S_IWUSR)) fileInfo.Attrib |= FILE_ATTRIBUTE_READONLY; fileInfo.Attrib |= FILE_ATTRIBUTE_UNIX_EXTENSION + ((stat_info.st_mode & 0xFFFF) << 16); RtlSecondsSince1970ToFileTime( stat_info.st_ctime, &fileInfo.CTime ); RtlSecondsSince1970ToFileTime( stat_info.st_mtime, &fileInfo.MTime ); RtlSecondsSince1970ToFileTime( stat_info.st_atime, &fileInfo.ATime ); fileInfo.IsDevice = false; if (S_ISDIR(stat_info.st_mode)) { fileInfo.Size = 0; } else { // file or symbolic link fileInfo.Size = stat_info.st_size; // for a symbolic link, size = size of filename } return 0; } static int fillin_CFileInfo(CFileInfo &fi,const char *dir,const char *name,bool ignoreLink) { char filename[MAX_PATHNAME_LEN]; size_t dir_len = strlen(dir); size_t name_len = strlen(name); size_t total = dir_len + 1 + name_len + 1; // 1 = strlen("/"); + le zero character if (total >= MAX_PATHNAME_LEN) throw "fillin_CFileInfo - internal error - MAX_PATHNAME_LEN"; memcpy(filename,dir,dir_len); if (dir_len >= 1) { if (filename[dir_len-1] == CHAR_PATH_SEPARATOR) { // delete the '/' dir_len--; } } filename[dir_len] = CHAR_PATH_SEPARATOR; memcpy(filename+(dir_len+1),name,name_len+1); // copy also final '\0' #ifdef _UNICODE fi.Name = GetUnicodeString(name, CP_ACP); #else fi.Name = name; #endif int ret = fillin_CFileInfo(fi,filename,ignoreLink); if (ret != 0) { AString err_msg = "stat error for "; err_msg += filename; err_msg += " ("; err_msg += strerror(errno); err_msg += ")"; throw err_msg; } return ret; } bool CFindFile::FindFirst(CFSTR cfWildcard, CFileInfo &fi, bool ignoreLink) { if (!Close()) return false; AString Awildcard = UnicodeStringToMultiByte(cfWildcard, CP_ACP); const char * wildcard = (const char *)Awildcard; if ((!wildcard) || (wildcard[0]==0)) { SetLastError(ERROR_PATH_NOT_FOUND); return false; } my_windows_split_path(nameWindowToUnix(wildcard),_directory,_pattern); TRACEN((printf("CFindFile::FindFirst : %s (dirname=%s,pattern=%s)\n",wildcard,(const char *)_directory,(const char *)_pattern))) _dirp = ::opendir((const char *)_directory); TRACEN((printf("CFindFile::FindFirst : opendir=%p\n",_dirp))) if ((_dirp == 0) && (global_use_utf16_conversion)) { // Try to recover the original filename UString ustr = MultiByteToUnicodeString(_directory, 0); AString resultString; bool is_good = originalFilename(ustr, resultString); if (is_good) { _dirp = ::opendir((const char *)resultString); _directory = resultString; } } if (_dirp == 0) return false; struct dirent *dp; while ((dp = readdir(_dirp)) != NULL) { if (filter_pattern(dp->d_name,(const char *)_pattern,0) == 1) { int retf = fillin_CFileInfo(fi,(const char *)_directory,dp->d_name,ignoreLink); if (retf) { TRACEN((printf("CFindFile::FindFirst : closedir-1(dirp=%p)\n",_dirp))) closedir(_dirp); _dirp = 0; SetLastError( ERROR_NO_MORE_FILES ); return false; } TRACEN((printf("CFindFile::FindFirst -%s- true\n",dp->d_name))) return true; } } TRACEN((printf("CFindFile::FindFirst : closedir-2(dirp=%p)\n",_dirp))) closedir(_dirp); _dirp = 0; SetLastError( ERROR_NO_MORE_FILES ); return false; } bool CFindFile::FindNext(CFileInfo &fi) { if (_dirp == 0) { SetLastError( ERROR_INVALID_HANDLE ); return false; } struct dirent *dp; while ((dp = readdir(_dirp)) != NULL) { if (filter_pattern(dp->d_name,(const char *)_pattern,0) == 1) { int retf = fillin_CFileInfo(fi,(const char *)_directory,dp->d_name,false); if (retf) { TRACEN((printf("FindNextFileA -%s- ret_handle=FALSE (errno=%d)\n",dp->d_name,errno))) return false; } TRACEN((printf("FindNextFileA -%s- true\n",dp->d_name))) return true; } } TRACEN((printf("FindNextFileA ret_handle=FALSE (ERROR_NO_MORE_FILES)\n"))) SetLastError( ERROR_NO_MORE_FILES ); return false; } #define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0; void CFileInfoBase::Clear() { Size = 0; MY_CLEAR_FILETIME(CTime); MY_CLEAR_FILETIME(ATime); MY_CLEAR_FILETIME(MTime); Attrib = 0; } bool CFileInfo::Find(CFSTR wildcard, bool ignoreLink) { #ifdef SUPPORT_DEVICE_FILE if (IsDeviceName(wildcard)) { Clear(); IsDevice = true; NIO::CInFile inFile; if (!inFile.Open(wildcard)) return false; Name = wildcard + 4; if (inFile.LengthDefined) Size = inFile.Length; return true; } #endif CFindFile finder; if (finder.FindFirst(wildcard, *this,ignoreLink)) return true; #ifdef _WIN32 { DWORD lastError = GetLastError(); if (lastError == ERROR_BAD_NETPATH || lastError == ERROR_FILE_NOT_FOUND) { int len = MyStringLen(wildcard); if (len > 2 && wildcard[0] == '\\' && wildcard[1] == '\\') { int pos = FindCharPosInString(wildcard + 2, FTEXT('\\')); if (pos >= 0) { pos += 2 + 1; len -= pos; CFSTR remString = wildcard + pos; int pos2 = FindCharPosInString(remString, FTEXT('\\')); FString s = wildcard; if (pos2 < 0 || pos2 == len - 1) { FString s = wildcard; if (pos2 < 0) { pos2 = len; s += FTEXT('\\'); } s += FCHAR_ANY_MASK; if (finder.FindFirst(s, *this)) if (Name == FTEXT(".")) { Name.SetFrom(s.Ptr(pos), pos2); return true; } ::SetLastError(lastError); } } } } } #endif return false; } bool DoesFileExist(CFSTR name) { CFileInfo fi; return fi.Find(name) && !fi.IsDir(); } bool DoesDirExist(CFSTR name) { CFileInfo fi; return fi.Find(name) && fi.IsDir(); } bool DoesFileOrDirExist(CFSTR name) { CFileInfo fi; return fi.Find(name); } bool CEnumerator::NextAny(CFileInfo &fi) { if (_findFile.IsHandleAllocated()) return _findFile.FindNext(fi); else return _findFile.FindFirst(_wildcard, fi); } bool CEnumerator::Next(CFileInfo &fi) { for (;;) { if (!NextAny(fi)) return false; if (!fi.IsDots()) return true; } } bool CEnumerator::Next(CFileInfo &fi, bool &found) { if (Next(fi)) { found = true; return true; } found = false; return (::GetLastError() == ERROR_NO_MORE_FILES); } //////////////////////////////// // CFindChangeNotification // FindFirstChangeNotification can return 0. MSDN doesn't tell about it. #ifdef _WIN32 bool CFindChangeNotification::Close() { if (!IsHandleAllocated()) return true; if (!::FindCloseChangeNotification(_handle)) return false; _handle = INVALID_HANDLE_VALUE; return true; } HANDLE CFindChangeNotification::FindFirst(CFSTR pathName, bool watchSubtree, DWORD notifyFilter) { #ifndef _UNICODE if (!g_IsNT) _handle = ::FindFirstChangeNotification(fs2fas(pathName), BoolToBOOL(watchSubtree), notifyFilter); else #endif { _handle = ::FindFirstChangeNotificationW(fs2us(pathName), BoolToBOOL(watchSubtree), notifyFilter); #ifdef WIN_LONG_PATH if (!IsHandleAllocated()) { UString longPath; if (GetLongPath(pathName, longPath)) _handle = ::FindFirstChangeNotificationW(longPath, BoolToBOOL(watchSubtree), notifyFilter); } #endif } return _handle; } #ifndef UNDER_CE bool MyGetLogicalDriveStrings(CObjectVector &driveStrings) { driveStrings.Clear(); #ifndef _UNICODE if (!g_IsNT) { driveStrings.Clear(); UINT32 size = GetLogicalDriveStrings(0, NULL); if (size == 0) return false; AString buf; UINT32 newSize = GetLogicalDriveStrings(size, buf.GetBuffer(size)); if (newSize == 0 || newSize > size) return false; AString s; for (UINT32 i = 0; i < newSize; i++) { char c = buf[i]; if (c == '\0') { driveStrings.Add(fas2fs(s)); s.Empty(); } else s += c; } return s.IsEmpty(); } else #endif { UINT32 size = GetLogicalDriveStringsW(0, NULL); if (size == 0) return false; UString buf; UINT32 newSize = GetLogicalDriveStringsW(size, buf.GetBuffer(size)); if (newSize == 0 || newSize > size) return false; UString s; for (UINT32 i = 0; i < newSize; i++) { WCHAR c = buf[i]; if (c == L'\0') { driveStrings.Add(us2fs(s)); s.Empty(); } else s += c; } return s.IsEmpty(); } } #endif #endif // _WIN32 }}}