// Windows/FileDir.cpp #include "StdAfx.h" #ifndef _UNICODE #include "../Common/StringConvert.h" #endif #include "FileDir.h" #include "FileFind.h" #include "FileName.h" #include "../Common/StringConvert.h" #include "../Common/IntToString.h" #define NEED_NAME_WINDOWS_TO_UNIX #include "myPrivate.h" #include "Windows/Synchronization.h" #include // rmdir #include #include // mkdir #include #include #include // #define TRACEN(u) u; #define TRACEN(u) /* */ int g_filedir = 1; static NWindows::NSynchronization::CCriticalSection g_CountCriticalSection; class Umask { public: mode_t current_umask; mode_t mask; Umask() { current_umask = umask (0); /* get and set the umask */ umask(current_umask); /* restore the umask */ mask = 0777 & (~current_umask); } }; static Umask gbl_umask; extern BOOLEAN WINAPI RtlTimeToSecondsSince1970( const LARGE_INTEGER *Time, DWORD *Seconds ); #ifdef _UNICODE AString nameWindowToUnix2(LPCWSTR name) // FIXME : optimization ? { AString astr = UnicodeStringToMultiByte(name); return AString(nameWindowToUnix((const char *)astr)); } #endif DWORD WINAPI GetFullPathNameW( LPCTSTR name, DWORD len, LPTSTR buffer, LPTSTR *lastpart ) { // FIXME if (name == 0) return 0; DWORD name_len = lstrlen(name); if (name[0] == '/') { DWORD ret = name_len+2; if (ret >= len) { TRACEN((printf("GetFullPathNameA(%ls,%d,)=0000 (case 0)\n",name, (int)len))) return 0; } lstrcpy(buffer,L"c:"); lstrcat(buffer,name); *lastpart=buffer; TCHAR *ptr=buffer; while (*ptr) { if (*ptr == '/') *lastpart=ptr+1; ptr++; } TRACEN((printf("GetFullPathNameA(%ls,%d,%ls,%ls)=%d\n",name, (int)len,buffer, *lastpart,(int)ret))) return ret; } if (isascii(name[0]) && (name[1] == ':')) { // FIXME isascii DWORD ret = name_len; if (ret >= len) { TRACEN((printf("GetFullPathNameA(%ls,%d,)=0000 (case 1)\n",name, (int)len))) return 0; } lstrcpy(buffer,name); *lastpart=buffer; TCHAR *ptr=buffer; while (*ptr) { if (*ptr == '/') *lastpart=ptr+1; ptr++; } TRACEN((printf("GetFullPathNameA(%ls,%d,%ls,%ls)=%d\n",name, (int)len,buffer, *lastpart,(int)ret))) return ret; } // name is a relative pathname. // if (len < 2) { TRACEN((printf("GetFullPathNameA(%ls,%d,)=0000 (case 2)\n",name, (int)len))) return 0; } DWORD ret = 0; char begin[MAX_PATHNAME_LEN]; /* DWORD begin_len = GetCurrentDirectoryA(MAX_PATHNAME_LEN,begin); */ DWORD begin_len = 0; begin[0]='c'; begin[1]=':'; char * cret = getcwd(begin+2, MAX_PATHNAME_LEN - 3); if (cret) { begin_len = strlen(begin); } if (begin_len >= 1) { // strlen(begin) + strlen("/") + strlen(name) ret = begin_len + 1 + name_len; if (ret >= len) { TRACEN((printf("GetFullPathNameA(%ls,%d,)=0000 (case 4)\n",name, (int)len))) return 0; } UString wbegin = GetUnicodeString(begin); lstrcpy(buffer,wbegin); lstrcat(buffer,L"/"); lstrcat(buffer,name); *lastpart=buffer + begin_len + 1; TCHAR *ptr=buffer; while (*ptr) { if (*ptr == '/') *lastpart=ptr+1; ptr++; } TRACEN((printf("GetFullPathNameA(%ls,%d,%ls,%ls)=%d\n",name, (int)len,buffer, *lastpart,(int)ret))) } else { ret = 0; TRACEN((printf("GetFullPathNameA(%ls,%d,)=0000 (case 5)\n",name, (int)len))) } return ret; } static int copy_fd(int fin,int fout) { char buffer[16384]; ssize_t ret_in; ssize_t ret_out; do { ret_out = -1; do { ret_in = read(fin, buffer,sizeof(buffer)); } while (ret_in < 0 && (errno == EINTR)); if (ret_in >= 1) { do { ret_out = write (fout, buffer, ret_in); } while (ret_out < 0 && (errno == EINTR)); } else if (ret_in == 0) { ret_out = 0; } } while (ret_out >= 1); return ret_out; } static BOOL CopyFile(const char *src,const char *dst) { int ret = -1; #ifdef O_BINARY int flags = O_BINARY; #else int flags = 0; #endif #ifdef O_LARGEFILE flags |= O_LARGEFILE; #endif // printf("##DBG CopyFile(%s,%s)\n",src,dst); int fout = open(dst,O_CREAT | O_WRONLY | O_EXCL | flags, 0600); if (fout != -1) { int fin = open(src,O_RDONLY | flags , 0600); if (fin != -1) { ret = copy_fd(fin,fout); if (ret == 0) ret = close(fin); else close(fin); } if (ret == 0) ret = close(fout); else close(fout); } if (ret == 0) return TRUE; return FALSE; } #ifndef _UNICODE extern bool g_IsNT; #endif namespace NWindows { namespace NFile { // SetCurrentDirectory doesn't support \\?\ prefix #ifdef WIN_LONG_PATH bool GetLongPathBase(CFSTR fileName, UString &res); bool GetLongPath(CFSTR fileName, UString &res); #endif namespace NDir { #ifdef _WIN32 #ifndef UNDER_CE bool MyGetWindowsDirectory(FString &path) { UINT needLength; #ifndef _UNICODE if (!g_IsNT) { TCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetWindowsDirectory(s, MAX_PATH + 1); path = fas2fs(s); } else #endif { WCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetWindowsDirectoryW(s, MAX_PATH + 1); path = us2fs(s); } return (needLength > 0 && needLength <= MAX_PATH); } bool MyGetSystemDirectory(FString &path) { UINT needLength; #ifndef _UNICODE if (!g_IsNT) { TCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetSystemDirectory(s, MAX_PATH + 1); path = fas2fs(s); } else #endif { WCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetSystemDirectoryW(s, MAX_PATH + 1); path = us2fs(s); } return (needLength > 0 && needLength <= MAX_PATH); } #endif #endif // _WIN32 bool SetDirTime(CFSTR fileName, const FILETIME * /* cTime */ , const FILETIME *aTime, const FILETIME *mTime) { AString cfilename = UnicodeStringToMultiByte(fileName); const char * unix_filename = nameWindowToUnix((const char *)cfilename); struct utimbuf buf; struct stat oldbuf; int ret = stat(unix_filename,&oldbuf); if (ret == 0) { buf.actime = oldbuf.st_atime; buf.modtime = oldbuf.st_mtime; } else { time_t current_time = time(0); buf.actime = current_time; buf.modtime = current_time; } if (aTime) { LARGE_INTEGER ltime; DWORD dw; ltime.QuadPart = aTime->dwHighDateTime; ltime.QuadPart = (ltime.QuadPart << 32) | aTime->dwLowDateTime; RtlTimeToSecondsSince1970( <ime, &dw ); buf.actime = dw; } if (mTime) { LARGE_INTEGER ltime; DWORD dw; ltime.QuadPart = mTime->dwHighDateTime; ltime.QuadPart = (ltime.QuadPart << 32) | mTime->dwLowDateTime; RtlTimeToSecondsSince1970( <ime, &dw ); buf.modtime = dw; } /* ret = */ utime(unix_filename, &buf); return true; } #ifdef WIN_LONG_PATH bool GetLongPaths(CFSTR s1, CFSTR s2, UString &d1, UString &d2) { if (!GetLongPathBase(s1, d1) || !GetLongPathBase(s2, d2)) return false; if (d1.IsEmpty() && d2.IsEmpty()) return false; if (d1.IsEmpty()) d1 = fs2us(s1); if (d2.IsEmpty()) d2 = fs2us(s2); return true; } #endif static int convert_to_symlink(const char * name) { FILE *file = fopen(name,"rb"); if (file) { char buf[MAX_PATHNAME_LEN+1]; char * ret = fgets(buf,sizeof(buf)-1,file); fclose(file); if (ret) { int ir = unlink(name); if (ir == 0) { ir = symlink(buf,name); } return ir; } } return -1; } bool SetFileAttrib(CFSTR fileName, DWORD fileAttributes) { if (!fileName) { SetLastError(ERROR_PATH_NOT_FOUND); TRACEN((printf("SetFileAttrib(NULL,%d) : false-1\n",fileAttributes))) return false; } #ifdef _UNICODE AString name = nameWindowToUnix2(fileName); #else const char * name = nameWindowToUnix(fileName); #endif struct stat stat_info; #ifdef ENV_HAVE_LSTAT if (global_use_lstat) { if(lstat(name,&stat_info)!=0) { TRACEN((printf("SetFileAttrib(%s,%d) : false-2-1\n",(const char *)name,fileAttributes))) return false; } } else #endif { if(stat(name,&stat_info)!=0) { TRACEN((printf("SetFileAttrib(%s,%d) : false-2-2\n",(const char *)name,fileAttributes))) return false; } } if (fileAttributes & FILE_ATTRIBUTE_UNIX_EXTENSION) { stat_info.st_mode = fileAttributes >> 16; #ifdef ENV_HAVE_LSTAT if (S_ISLNK(stat_info.st_mode)) { if ( convert_to_symlink(name) != 0) { TRACEN((printf("SetFileAttrib(%s,%d) : false-3\n",(const char *)name,fileAttributes))) return false; } } else #endif if (S_ISREG(stat_info.st_mode)) { TRACEN((printf("##DBG chmod-2(%s,%o)\n",(const char *)name,(unsigned)stat_info.st_mode & gbl_umask.mask))) chmod(name,stat_info.st_mode & gbl_umask.mask); } else if (S_ISDIR(stat_info.st_mode)) { // user/7za must be able to create files in this directory stat_info.st_mode |= (S_IRUSR | S_IWUSR | S_IXUSR); TRACEN((printf("##DBG chmod-3(%s,%o)\n",(const char *)name,(unsigned)stat_info.st_mode & gbl_umask.mask))) chmod(name,stat_info.st_mode & gbl_umask.mask); } #ifdef ENV_HAVE_LSTAT } else if (!S_ISLNK(stat_info.st_mode)) { // do not use chmod on a link #else } else { #endif /* Only Windows Attributes */ if( S_ISDIR(stat_info.st_mode)) { /* Remark : FILE_ATTRIBUTE_READONLY ignored for directory. */ TRACEN((printf("##DBG chmod-4(%s,%o)\n",(const char *)name,(unsigned)stat_info.st_mode & gbl_umask.mask))) chmod(name,stat_info.st_mode & gbl_umask.mask); } else { if (fileAttributes & FILE_ATTRIBUTE_READONLY) stat_info.st_mode &= ~0222; /* octal!, clear write permission bits */ TRACEN((printf("##DBG chmod-5(%s,%o)\n",(const char *)name,(unsigned)stat_info.st_mode & gbl_umask.mask))) chmod(name,stat_info.st_mode & gbl_umask.mask); } } TRACEN((printf("SetFileAttrib(%s,%d) : true\n",(const char *)name,fileAttributes))) return true; } bool RemoveDir(CFSTR path) { if (!path || !*path) { SetLastError(ERROR_PATH_NOT_FOUND); return FALSE; } AString name = nameWindowToUnix2(path); TRACEN((printf("RemoveDirectoryA(%s)\n",(const char *)name))) if (rmdir( (const char *)name ) != 0) { return FALSE; } return TRUE; } bool MyMoveFile(CFSTR existFileName, CFSTR newFileName) { #ifdef _UNICODE AString src = nameWindowToUnix2(existFileName); AString dst = nameWindowToUnix2(newFileName); #else const char * src = nameWindowToUnix(existFileName); const char * dst = nameWindowToUnix(newFileName); #endif TRACEN((printf("MyMoveFile(%s,%s)\n",(const char *)src,(const char *)dst))) int ret = rename(src,dst); if (ret != 0) { if (errno == EXDEV) // FIXED : bug #1112167 (Temporary directory must be on same partition as target) { BOOL bret = CopyFile(src,dst); if (bret == FALSE) return false; struct stat info_file; ret = stat(src,&info_file); if (ret == 0) { TRACEN((printf("##DBG chmod-1(%s,%o)\n",(const char *)dst,(unsigned)info_file.st_mode & gbl_umask.mask))) ret = chmod(dst,info_file.st_mode & gbl_umask.mask); } if (ret == 0) { ret = unlink(src); } if (ret == 0) return true; } return false; } return true; } bool CreateDir(CFSTR path) { if (!path || !*path) { SetLastError(ERROR_PATH_NOT_FOUND); return false; } #ifdef _UNICODE AString name = nameWindowToUnix2(path); #else const char * name = nameWindowToUnix(path); #endif bool bret = false; if (mkdir( name, 0700 ) == 0) bret = true; TRACEN((printf("CreateDir(%s)=%d\n",(const char *)name,(int)bret))) return bret; } bool CreateComplexDir(CFSTR _aPathName) { AString name = nameWindowToUnix2(_aPathName); TRACEN((printf("CreateComplexDir(%s)\n",(const char *)name))) FString pathName = _aPathName; int pos = pathName.ReverseFind(FCHAR_PATH_SEPARATOR); if (pos > 0 && pos == pathName.Len() - 1) { if (pathName.Len() == 3 && pathName[1] == L':') return true; // Disk folder; pathName.Delete(pos); } FString pathName2 = pathName; pos = pathName.Len(); TRACEN((printf("CreateComplexDir(%s) pathName2=%ls\n",(const char *)name,(CFSTR)pathName2))) for (;;) { if (CreateDir(pathName)) break; TRACEN((printf("CreateComplexDir(%s) GetLastError=%d (ERROR_ALREADY_EXISTS=%d)\n",(const char *)name,::GetLastError(), ERROR_ALREADY_EXISTS))) if (::GetLastError() == ERROR_ALREADY_EXISTS) { #ifdef _WIN32 // FIXED for supporting symbolic link instead of a directory NFind::CFileInfo fileInfo; if (!fileInfo.Find(pathName)) // For network folders return true; if (!fileInfo.IsDir()) return false; #endif break; } pos = pathName.ReverseFind(FCHAR_PATH_SEPARATOR); if (pos < 0 || pos == 0) return false; if (pathName[pos - 1] == L':') return false; pathName = pathName.Left(pos); } pathName = pathName2; while (pos < pathName.Len()) { pos = pathName.Find(FCHAR_PATH_SEPARATOR, pos + 1); if (pos < 0) pos = pathName.Len(); if (!CreateDir(pathName.Left(pos))) return false; } return true; } bool DeleteFileAlways(CFSTR name) { if (!name || !*name) { SetLastError(ERROR_PATH_NOT_FOUND); return false; } #ifdef _UNICODE AString unixname = nameWindowToUnix2(name); #else const char * unixname = nameWindowToUnix(name); #endif bool bret = false; if (remove(unixname) == 0) bret = true; TRACEN((printf("DeleteFileAlways(%s)=%d\n",(const char *)unixname,(int)bret))) return bret; } bool RemoveDirWithSubItems(const FString &path) { bool needRemoveSubItems = true; { NFind::CFileInfo fi; if (!fi.Find(path)) return false; if (!fi.IsDir()) { ::SetLastError(ERROR_DIRECTORY); return false; } if (fi.HasReparsePoint()) needRemoveSubItems = false; } if (needRemoveSubItems) { FString s = path; s += FCHAR_PATH_SEPARATOR; unsigned prefixSize = s.Len(); s += FCHAR_ANY_MASK; NFind::CEnumerator enumerator(s); NFind::CFileInfo fi; while (enumerator.Next(fi)) { s.DeleteFrom(prefixSize); s += fi.Name; if (fi.IsDir()) { if (!RemoveDirWithSubItems(s)) return false; } else if (!DeleteFileAlways(s)) return false; } } if (!SetFileAttrib(path, 0)) return false; return RemoveDir(path); } bool RemoveDirectoryWithSubItems(const FString &path); // FIXME static bool RemoveDirectorySubItems2(const FString pathPrefix, const NFind::CFileInfo &fileInfo) { if (fileInfo.IsDir()) return RemoveDirectoryWithSubItems(pathPrefix + fileInfo.Name); return DeleteFileAlways(pathPrefix + fileInfo.Name); } bool RemoveDirectoryWithSubItems(const FString &path) { NFind::CFileInfo fileInfo; FString pathPrefix = path + FCHAR_PATH_SEPARATOR; { NFind::CEnumerator enumerator(pathPrefix + FCHAR_ANY_MASK); while (enumerator.Next(fileInfo)) if (!RemoveDirectorySubItems2(pathPrefix, fileInfo)) return false; } if (!SetFileAttrib(path, 0)) return false; return RemoveDir(path); } #ifdef UNDER_CE bool MyGetFullPathName(CFSTR fileName, FString &resFullPath) { resFullPath = fileName; return true; } #else #ifdef WIN_LONG_PATH static FString GetLastPart(CFSTR path) { int i = MyStringLen(path); for (; i > 0; i--) { FChar c = path[i - 1]; if (c == FCHAR_PATH_SEPARATOR || c == '/') break; } return path + i; } static void AddTrailingDots(CFSTR oldPath, FString &newPath) { int len = MyStringLen(oldPath); int i; for (i = len; i > 0 && oldPath[i - 1] == '.'; i--); if (i == 0 || i == len) return; FString oldName = GetLastPart(oldPath); FString newName = GetLastPart(newPath); int nonDotsLen = oldName.Len() - (len - i); if (nonDotsLen == 0 || newName.CompareNoCase(oldName.Left(nonDotsLen)) != 0) return; for (; i != len; i++) newPath += '.'; } #endif bool MyGetFullPathName(CFSTR fileName, FString &resFullPath) { resFullPath.Empty(); #ifndef _UNICODE if (!g_IsNT) { TCHAR s[MAX_PATH + 2]; s[0] = 0; LPTSTR fileNamePointer = 0; DWORD needLength = ::GetFullPathName(fs2fas(fileName), MAX_PATH + 1, s, &fileNamePointer); if (needLength == 0 || needLength > MAX_PATH) return false; resFullPath = fas2fs(s); return true; } else #endif { LPWSTR fileNamePointer = 0; WCHAR s[MAX_PATH + 2]; s[0] = 0; DWORD needLength = ::GetFullPathNameW(fs2us(fileName), MAX_PATH + 1, s, &fileNamePointer); if (needLength == 0) return false; if (needLength <= MAX_PATH) { resFullPath = us2fs(s); return true; } #ifdef WIN_LONG_PATH needLength++; UString temp; LPWSTR buffer = temp.GetBuffer(needLength + 1); buffer[0] = 0; DWORD needLength2 = ::GetFullPathNameW(fs2us(fileName), needLength, buffer, &fileNamePointer); temp.ReleaseBuffer(); if (needLength2 > 0 && needLength2 <= needLength) { resFullPath = us2fs(temp); AddTrailingDots(fileName, resFullPath); return true; } #endif return false; } } bool SetCurrentDir(CFSTR path) { AString apath = UnicodeStringToMultiByte(path); return chdir((const char*)apath) == 0; } bool GetCurrentDir(FString &path) { char begin[MAX_PATHNAME_LEN]; begin[0]='c'; begin[1]=':'; char * cret = getcwd(begin+2, MAX_PATHNAME_LEN - 3); if (cret) { #ifdef _UNICODE path = GetUnicodeString(begin); #else path = begin; #endif return true; } return false; } #endif bool GetFullPathAndSplit(CFSTR path, FString &resDirPrefix, FString &resFileName) { bool res = MyGetFullPathName(path, resDirPrefix); if (!res) resDirPrefix = path; int pos = resDirPrefix.ReverseFind(FCHAR_PATH_SEPARATOR); resFileName = resDirPrefix.Ptr(pos + 1); resDirPrefix.DeleteFrom(pos + 1); return res; } bool GetOnlyDirPrefix(CFSTR path, FString &resDirPrefix) { FString resFileName; return GetFullPathAndSplit(path, resDirPrefix, resFileName); } bool MyGetTempPath(FString &path) { path = L"c:/tmp/"; // final '/' is needed return true; } static bool CreateTempFile(CFSTR prefix, bool addRandom, FString &path, NIO::COutFile *outFile) { #ifdef _WIN32 UInt32 d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId(); #else static UInt32 memo_count = 0; UInt32 count; g_CountCriticalSection.Enter(); count = memo_count++; g_CountCriticalSection.Leave(); UINT number = (UINT)getpid(); UInt32 d = (GetTickCount() << 12) ^ (count << 14) ^ number; #endif for (unsigned i = 0; i < 100; i++) { path = prefix; if (addRandom) { FChar s[16]; UInt32 value = d; unsigned k; for (k = 0; k < 8; k++) { unsigned t = value & 0xF; value >>= 4; s[k] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); } s[k] = '\0'; if (outFile) path += FChar('.'); path += s; UInt32 step = GetTickCount() + 2; if (step == 0) step = 1; d += step; } addRandom = true; if (outFile) path += FTEXT(".tmp"); if (NFind::DoesFileOrDirExist(path)) { SetLastError(ERROR_ALREADY_EXISTS); continue; } if (outFile) { if (outFile->Create(path, false)) return true; } else { if (CreateDir(path)) return true; } DWORD error = GetLastError(); if (error != ERROR_FILE_EXISTS && error != ERROR_ALREADY_EXISTS) break; } path.Empty(); return false; } bool CTempFile::Create(CFSTR prefix, NIO::COutFile *outFile) { if (!Remove()) return false; if (!CreateTempFile(prefix, false, _path, outFile)) return false; _mustBeDeleted = true; return true; } bool CTempFile::CreateRandomInTempFolder(CFSTR namePrefix, NIO::COutFile *outFile) { if (!Remove()) return false; FString tempPath; if (!MyGetTempPath(tempPath)) return false; if (!CreateTempFile(tempPath + namePrefix, true, _path, outFile)) return false; _mustBeDeleted = true; return true; } bool CTempFile::Remove() { if (!_mustBeDeleted) return true; _mustBeDeleted = !DeleteFileAlways(_path); return !_mustBeDeleted; } bool CTempFile::MoveTo(CFSTR name, bool deleteDestBefore) { if (deleteDestBefore) if (NFind::DoesFileExist(name)) if (!DeleteFileAlways(name)) return false; DisableDeleting(); return MyMoveFile(_path, name); } bool CTempDir::Create(CFSTR prefix) { if (!Remove()) return false; FString tempPath; if (!MyGetTempPath(tempPath)) return false; if (!CreateTempFile(tempPath + prefix, true, _path, NULL)) return false; _mustBeDeleted = true; return true; } bool CTempDir::Remove() { if (!_mustBeDeleted) return true; _mustBeDeleted = !RemoveDirectoryWithSubItems(_path); return !_mustBeDeleted; } }}}