Home | History | Annotate | Download | only in Windows
      1 // Windows/FileDir.cpp
      2 
      3 #include "StdAfx.h"
      4 
      5 #ifndef _UNICODE
      6 #include "../Common/StringConvert.h"
      7 #endif
      8 
      9 #include "FileDir.h"
     10 #include "FileFind.h"
     11 #include "FileName.h"
     12 
     13 #ifndef _UNICODE
     14 extern bool g_IsNT;
     15 #endif
     16 
     17 using namespace NWindows;
     18 using namespace NFile;
     19 using namespace NName;
     20 
     21 namespace NWindows {
     22 namespace NFile {
     23 namespace NDir {
     24 
     25 #ifndef UNDER_CE
     26 
     27 bool GetWindowsDir(FString &path)
     28 {
     29   UINT needLength;
     30   #ifndef _UNICODE
     31   if (!g_IsNT)
     32   {
     33     TCHAR s[MAX_PATH + 2];
     34     s[0] = 0;
     35     needLength = ::GetWindowsDirectory(s, MAX_PATH + 1);
     36     path = fas2fs(s);
     37   }
     38   else
     39   #endif
     40   {
     41     WCHAR s[MAX_PATH + 2];
     42     s[0] = 0;
     43     needLength = ::GetWindowsDirectoryW(s, MAX_PATH + 1);
     44     path = us2fs(s);
     45   }
     46   return (needLength > 0 && needLength <= MAX_PATH);
     47 }
     48 
     49 bool GetSystemDir(FString &path)
     50 {
     51   UINT needLength;
     52   #ifndef _UNICODE
     53   if (!g_IsNT)
     54   {
     55     TCHAR s[MAX_PATH + 2];
     56     s[0] = 0;
     57     needLength = ::GetSystemDirectory(s, MAX_PATH + 1);
     58     path = fas2fs(s);
     59   }
     60   else
     61   #endif
     62   {
     63     WCHAR s[MAX_PATH + 2];
     64     s[0] = 0;
     65     needLength = ::GetSystemDirectoryW(s, MAX_PATH + 1);
     66     path = us2fs(s);
     67   }
     68   return (needLength > 0 && needLength <= MAX_PATH);
     69 }
     70 #endif
     71 
     72 bool SetDirTime(CFSTR path, const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime)
     73 {
     74   #ifndef _UNICODE
     75   if (!g_IsNT)
     76   {
     77     ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
     78     return false;
     79   }
     80   #endif
     81 
     82   HANDLE hDir = INVALID_HANDLE_VALUE;
     83   IF_USE_MAIN_PATH
     84     hDir = ::CreateFileW(fs2us(path), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
     85         NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
     86   #ifdef WIN_LONG_PATH
     87   if (hDir == INVALID_HANDLE_VALUE && USE_SUPER_PATH)
     88   {
     89     UString superPath;
     90     if (GetSuperPath(path, superPath, USE_MAIN_PATH))
     91       hDir = ::CreateFileW(superPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
     92           NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
     93   }
     94   #endif
     95 
     96   bool res = false;
     97   if (hDir != INVALID_HANDLE_VALUE)
     98   {
     99     res = BOOLToBool(::SetFileTime(hDir, cTime, aTime, mTime));
    100     ::CloseHandle(hDir);
    101   }
    102   return res;
    103 }
    104 
    105 bool SetFileAttrib(CFSTR path, DWORD attrib)
    106 {
    107   #ifndef _UNICODE
    108   if (!g_IsNT)
    109   {
    110     if (::SetFileAttributes(fs2fas(path), attrib))
    111       return true;
    112   }
    113   else
    114   #endif
    115   {
    116     IF_USE_MAIN_PATH
    117       if (::SetFileAttributesW(fs2us(path), attrib))
    118         return true;
    119     #ifdef WIN_LONG_PATH
    120     if (USE_SUPER_PATH)
    121     {
    122       UString superPath;
    123       if (GetSuperPath(path, superPath, USE_MAIN_PATH))
    124         return BOOLToBool(::SetFileAttributesW(superPath, attrib));
    125     }
    126     #endif
    127   }
    128   return false;
    129 }
    130 
    131 bool RemoveDir(CFSTR path)
    132 {
    133   #ifndef _UNICODE
    134   if (!g_IsNT)
    135   {
    136     if (::RemoveDirectory(fs2fas(path)))
    137       return true;
    138   }
    139   else
    140   #endif
    141   {
    142     IF_USE_MAIN_PATH
    143       if (::RemoveDirectoryW(fs2us(path)))
    144         return true;
    145     #ifdef WIN_LONG_PATH
    146     if (USE_SUPER_PATH)
    147     {
    148       UString superPath;
    149       if (GetSuperPath(path, superPath, USE_MAIN_PATH))
    150         return BOOLToBool(::RemoveDirectoryW(superPath));
    151     }
    152     #endif
    153   }
    154   return false;
    155 }
    156 
    157 bool MyMoveFile(CFSTR oldFile, CFSTR newFile)
    158 {
    159   #ifndef _UNICODE
    160   if (!g_IsNT)
    161   {
    162     if (::MoveFile(fs2fas(oldFile), fs2fas(newFile)))
    163       return true;
    164   }
    165   else
    166   #endif
    167   {
    168     IF_USE_MAIN_PATH_2(oldFile, newFile)
    169       if (::MoveFileW(fs2us(oldFile), fs2us(newFile)))
    170         return true;
    171     #ifdef WIN_LONG_PATH
    172     if (USE_SUPER_PATH_2)
    173     {
    174       UString d1, d2;
    175       if (GetSuperPaths(oldFile, newFile, d1, d2, USE_MAIN_PATH_2))
    176         return BOOLToBool(::MoveFileW(d1, d2));
    177     }
    178     #endif
    179   }
    180   return false;
    181 }
    182 
    183 #ifndef UNDER_CE
    184 
    185 EXTERN_C_BEGIN
    186 typedef BOOL (WINAPI *Func_CreateHardLinkW)(
    187     LPCWSTR lpFileName,
    188     LPCWSTR lpExistingFileName,
    189     LPSECURITY_ATTRIBUTES lpSecurityAttributes
    190     );
    191 EXTERN_C_END
    192 
    193 bool MyCreateHardLink(CFSTR newFileName, CFSTR existFileName)
    194 {
    195   #ifndef _UNICODE
    196   if (!g_IsNT)
    197   {
    198     SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    199     return false;
    200     /*
    201     if (::CreateHardLink(fs2fas(newFileName), fs2fas(existFileName), NULL))
    202       return true;
    203     */
    204   }
    205   else
    206   #endif
    207   {
    208     Func_CreateHardLinkW my_CreateHardLinkW = (Func_CreateHardLinkW)
    209         ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW");
    210     if (!my_CreateHardLinkW)
    211       return false;
    212     IF_USE_MAIN_PATH_2(newFileName, existFileName)
    213       if (my_CreateHardLinkW(fs2us(newFileName), fs2us(existFileName), NULL))
    214         return true;
    215     #ifdef WIN_LONG_PATH
    216     if (USE_SUPER_PATH_2)
    217     {
    218       UString d1, d2;
    219       if (GetSuperPaths(newFileName, existFileName, d1, d2, USE_MAIN_PATH_2))
    220         return BOOLToBool(my_CreateHardLinkW(d1, d2, NULL));
    221     }
    222     #endif
    223   }
    224   return false;
    225 }
    226 
    227 #endif
    228 
    229 /*
    230 WinXP-64 CreateDir():
    231   ""                  - ERROR_PATH_NOT_FOUND
    232   \                   - ERROR_ACCESS_DENIED
    233   C:\                 - ERROR_ACCESS_DENIED, if there is such drive,
    234 
    235   D:\folder             - ERROR_PATH_NOT_FOUND, if there is no such drive,
    236   C:\nonExistent\folder - ERROR_PATH_NOT_FOUND
    237 
    238   C:\existFolder      - ERROR_ALREADY_EXISTS
    239   C:\existFolder\     - ERROR_ALREADY_EXISTS
    240 
    241   C:\folder   - OK
    242   C:\folder\  - OK
    243 
    244   \\Server\nonExistent    - ERROR_BAD_NETPATH
    245   \\Server\Share_Readonly - ERROR_ACCESS_DENIED
    246   \\Server\Share          - ERROR_ALREADY_EXISTS
    247 
    248   \\Server\Share_NTFS_drive - ERROR_ACCESS_DENIED
    249   \\Server\Share_FAT_drive  - ERROR_ALREADY_EXISTS
    250 */
    251 
    252 bool CreateDir(CFSTR path)
    253 {
    254   #ifndef _UNICODE
    255   if (!g_IsNT)
    256   {
    257     if (::CreateDirectory(fs2fas(path), NULL))
    258       return true;
    259   }
    260   else
    261   #endif
    262   {
    263     IF_USE_MAIN_PATH
    264       if (::CreateDirectoryW(fs2us(path), NULL))
    265         return true;
    266     #ifdef WIN_LONG_PATH
    267     if ((!USE_MAIN_PATH || ::GetLastError() != ERROR_ALREADY_EXISTS) && USE_SUPER_PATH)
    268     {
    269       UString superPath;
    270       if (GetSuperPath(path, superPath, USE_MAIN_PATH))
    271         return BOOLToBool(::CreateDirectoryW(superPath, NULL));
    272     }
    273     #endif
    274   }
    275   return false;
    276 }
    277 
    278 /*
    279   CreateDir2 returns true, if directory can contain files after the call (two cases):
    280     1) the directory already exists
    281     2) the directory was created
    282   path must be WITHOUT trailing path separator.
    283 
    284   We need CreateDir2, since fileInfo.Find() for reserved names like "com8"
    285    returns FILE instead of DIRECTORY. And we need to use SuperPath */
    286 
    287 static bool CreateDir2(CFSTR path)
    288 {
    289   #ifndef _UNICODE
    290   if (!g_IsNT)
    291   {
    292     if (::CreateDirectory(fs2fas(path), NULL))
    293       return true;
    294   }
    295   else
    296   #endif
    297   {
    298     IF_USE_MAIN_PATH
    299       if (::CreateDirectoryW(fs2us(path), NULL))
    300         return true;
    301     #ifdef WIN_LONG_PATH
    302     if ((!USE_MAIN_PATH || ::GetLastError() != ERROR_ALREADY_EXISTS) && USE_SUPER_PATH)
    303     {
    304       UString superPath;
    305       if (GetSuperPath(path, superPath, USE_MAIN_PATH))
    306       {
    307         if (::CreateDirectoryW(superPath, NULL))
    308           return true;
    309         if (::GetLastError() != ERROR_ALREADY_EXISTS)
    310           return false;
    311         NFind::CFileInfo fi;
    312         if (!fi.Find(us2fs(superPath)))
    313           return false;
    314         return fi.IsDir();
    315       }
    316     }
    317     #endif
    318   }
    319   if (::GetLastError() != ERROR_ALREADY_EXISTS)
    320     return false;
    321   NFind::CFileInfo fi;
    322   if (!fi.Find(path))
    323     return false;
    324   return fi.IsDir();
    325 }
    326 
    327 bool CreateComplexDir(CFSTR _path)
    328 {
    329   #ifdef _WIN32
    330 
    331   {
    332     DWORD attrib = NFind::GetFileAttrib(_path);
    333     if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
    334       return true;
    335   }
    336 
    337   #ifndef UNDER_CE
    338 
    339   if (IsDriveRootPath_SuperAllowed(_path))
    340     return false;
    341 
    342   unsigned prefixSize = GetRootPrefixSize(_path);
    343 
    344   #endif
    345 
    346   #endif
    347 
    348   FString path = _path;
    349 
    350   int pos = path.ReverseFind_PathSepar();
    351   if (pos >= 0 && (unsigned)pos == path.Len() - 1)
    352   {
    353     if (path.Len() == 1)
    354       return true;
    355     path.DeleteBack();
    356   }
    357 
    358   const FString path2 = path;
    359   pos = path.Len();
    360 
    361   for (;;)
    362   {
    363     if (CreateDir2(path))
    364       break;
    365     if (::GetLastError() == ERROR_ALREADY_EXISTS)
    366       return false;
    367     pos = path.ReverseFind_PathSepar();
    368     if (pos < 0 || pos == 0)
    369       return false;
    370 
    371     #if defined(_WIN32) && !defined(UNDER_CE)
    372     if (pos == 1 && IS_PATH_SEPAR(path[0]))
    373       return false;
    374     if (prefixSize >= (unsigned)pos + 1)
    375       return false;
    376     #endif
    377 
    378     path.DeleteFrom(pos);
    379   }
    380 
    381   while (pos < (int)path2.Len())
    382   {
    383     int pos2 = NName::FindSepar(path2.Ptr(pos + 1));
    384     if (pos2 < 0)
    385       pos = path2.Len();
    386     else
    387       pos += 1 + pos2;
    388     path.SetFrom(path2, pos);
    389     if (!CreateDir(path))
    390       return false;
    391   }
    392 
    393   return true;
    394 }
    395 
    396 bool DeleteFileAlways(CFSTR path)
    397 {
    398   /* If alt stream, we also need to clear READ-ONLY attribute of main file before delete.
    399      SetFileAttrib("name:stream", ) changes attributes of main file. */
    400   {
    401     DWORD attrib = NFind::GetFileAttrib(path);
    402     if (attrib != INVALID_FILE_ATTRIBUTES
    403         && (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0
    404         && (attrib & FILE_ATTRIBUTE_READONLY) != 0)
    405     {
    406       if (!SetFileAttrib(path, attrib & ~FILE_ATTRIBUTE_READONLY))
    407         return false;
    408     }
    409   }
    410 
    411   #ifndef _UNICODE
    412   if (!g_IsNT)
    413   {
    414     if (::DeleteFile(fs2fas(path)))
    415       return true;
    416   }
    417   else
    418   #endif
    419   {
    420     /* DeleteFile("name::$DATA") deletes all alt streams (same as delete DeleteFile("name")).
    421        Maybe it's better to open "name::$DATA" and clear data for unnamed stream? */
    422     IF_USE_MAIN_PATH
    423       if (::DeleteFileW(fs2us(path)))
    424         return true;
    425     #ifdef WIN_LONG_PATH
    426     if (USE_SUPER_PATH)
    427     {
    428       UString superPath;
    429       if (GetSuperPath(path, superPath, USE_MAIN_PATH))
    430         return BOOLToBool(::DeleteFileW(superPath));
    431     }
    432     #endif
    433   }
    434   return false;
    435 }
    436 
    437 bool RemoveDirWithSubItems(const FString &path)
    438 {
    439   bool needRemoveSubItems = true;
    440   {
    441     NFind::CFileInfo fi;
    442     if (!fi.Find(path))
    443       return false;
    444     if (!fi.IsDir())
    445     {
    446       ::SetLastError(ERROR_DIRECTORY);
    447       return false;
    448     }
    449     if (fi.HasReparsePoint())
    450       needRemoveSubItems = false;
    451   }
    452 
    453   if (needRemoveSubItems)
    454   {
    455     FString s = path;
    456     s.Add_PathSepar();
    457     unsigned prefixSize = s.Len();
    458     s += FCHAR_ANY_MASK;
    459     NFind::CEnumerator enumerator(s);
    460     NFind::CFileInfo fi;
    461     while (enumerator.Next(fi))
    462     {
    463       s.DeleteFrom(prefixSize);
    464       s += fi.Name;
    465       if (fi.IsDir())
    466       {
    467         if (!RemoveDirWithSubItems(s))
    468           return false;
    469       }
    470       else if (!DeleteFileAlways(s))
    471         return false;
    472     }
    473   }
    474 
    475   if (!SetFileAttrib(path, 0))
    476     return false;
    477   return RemoveDir(path);
    478 }
    479 
    480 #ifdef UNDER_CE
    481 
    482 bool MyGetFullPathName(CFSTR path, FString &resFullPath)
    483 {
    484   resFullPath = path;
    485   return true;
    486 }
    487 
    488 #else
    489 
    490 bool MyGetFullPathName(CFSTR path, FString &resFullPath)
    491 {
    492   return GetFullPath(path, resFullPath);
    493 }
    494 
    495 bool SetCurrentDir(CFSTR path)
    496 {
    497   // SetCurrentDirectory doesn't support \\?\ prefix
    498   #ifndef _UNICODE
    499   if (!g_IsNT)
    500   {
    501     return BOOLToBool(::SetCurrentDirectory(fs2fas(path)));
    502   }
    503   else
    504   #endif
    505   {
    506     return BOOLToBool(::SetCurrentDirectoryW(fs2us(path)));
    507   }
    508 }
    509 
    510 bool GetCurrentDir(FString &path)
    511 {
    512   path.Empty();
    513   DWORD needLength;
    514   #ifndef _UNICODE
    515   if (!g_IsNT)
    516   {
    517     TCHAR s[MAX_PATH + 2];
    518     s[0] = 0;
    519     needLength = ::GetCurrentDirectory(MAX_PATH + 1, s);
    520     path = fas2fs(s);
    521   }
    522   else
    523   #endif
    524   {
    525     WCHAR s[MAX_PATH + 2];
    526     s[0] = 0;
    527     needLength = ::GetCurrentDirectoryW(MAX_PATH + 1, s);
    528     path = us2fs(s);
    529   }
    530   return (needLength > 0 && needLength <= MAX_PATH);
    531 }
    532 
    533 #endif
    534 
    535 bool GetFullPathAndSplit(CFSTR path, FString &resDirPrefix, FString &resFileName)
    536 {
    537   bool res = MyGetFullPathName(path, resDirPrefix);
    538   if (!res)
    539     resDirPrefix = path;
    540   int pos = resDirPrefix.ReverseFind_PathSepar();
    541   resFileName = resDirPrefix.Ptr(pos + 1);
    542   resDirPrefix.DeleteFrom(pos + 1);
    543   return res;
    544 }
    545 
    546 bool GetOnlyDirPrefix(CFSTR path, FString &resDirPrefix)
    547 {
    548   FString resFileName;
    549   return GetFullPathAndSplit(path, resDirPrefix, resFileName);
    550 }
    551 
    552 bool MyGetTempPath(FString &path)
    553 {
    554   path.Empty();
    555   DWORD needLength;
    556   #ifndef _UNICODE
    557   if (!g_IsNT)
    558   {
    559     TCHAR s[MAX_PATH + 2];
    560     s[0] = 0;
    561     needLength = ::GetTempPath(MAX_PATH + 1, s);
    562     path = fas2fs(s);
    563   }
    564   else
    565   #endif
    566   {
    567     WCHAR s[MAX_PATH + 2];
    568     s[0] = 0;
    569     needLength = ::GetTempPathW(MAX_PATH + 1, s);;
    570     path = us2fs(s);
    571   }
    572   return (needLength > 0 && needLength <= MAX_PATH);
    573 }
    574 
    575 static bool CreateTempFile(CFSTR prefix, bool addRandom, FString &path, NIO::COutFile *outFile)
    576 {
    577   UInt32 d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId();
    578   for (unsigned i = 0; i < 100; i++)
    579   {
    580     path = prefix;
    581     if (addRandom)
    582     {
    583       FChar s[16];
    584       UInt32 value = d;
    585       unsigned k;
    586       for (k = 0; k < 8; k++)
    587       {
    588         unsigned t = value & 0xF;
    589         value >>= 4;
    590         s[k] = (FChar)((t < 10) ? ('0' + t) : ('A' + (t - 10)));
    591       }
    592       s[k] = '\0';
    593       if (outFile)
    594         path += FChar('.');
    595       path += s;
    596       UInt32 step = GetTickCount() + 2;
    597       if (step == 0)
    598         step = 1;
    599       d += step;
    600     }
    601     addRandom = true;
    602     if (outFile)
    603       path += FTEXT(".tmp");
    604     if (NFind::DoesFileOrDirExist(path))
    605     {
    606       SetLastError(ERROR_ALREADY_EXISTS);
    607       continue;
    608     }
    609     if (outFile)
    610     {
    611       if (outFile->Create(path, false))
    612         return true;
    613     }
    614     else
    615     {
    616       if (CreateDir(path))
    617         return true;
    618     }
    619     DWORD error = GetLastError();
    620     if (error != ERROR_FILE_EXISTS &&
    621         error != ERROR_ALREADY_EXISTS)
    622       break;
    623   }
    624   path.Empty();
    625   return false;
    626 }
    627 
    628 bool CTempFile::Create(CFSTR prefix, NIO::COutFile *outFile)
    629 {
    630   if (!Remove())
    631     return false;
    632   if (!CreateTempFile(prefix, false, _path, outFile))
    633     return false;
    634   _mustBeDeleted = true;
    635   return true;
    636 }
    637 
    638 bool CTempFile::CreateRandomInTempFolder(CFSTR namePrefix, NIO::COutFile *outFile)
    639 {
    640   if (!Remove())
    641     return false;
    642   FString tempPath;
    643   if (!MyGetTempPath(tempPath))
    644     return false;
    645   if (!CreateTempFile(tempPath + namePrefix, true, _path, outFile))
    646     return false;
    647   _mustBeDeleted = true;
    648   return true;
    649 }
    650 
    651 bool CTempFile::Remove()
    652 {
    653   if (!_mustBeDeleted)
    654     return true;
    655   _mustBeDeleted = !DeleteFileAlways(_path);
    656   return !_mustBeDeleted;
    657 }
    658 
    659 bool CTempFile::MoveTo(CFSTR name, bool deleteDestBefore)
    660 {
    661   if (deleteDestBefore)
    662     if (NFind::DoesFileExist(name))
    663       if (!DeleteFileAlways(name))
    664         return false;
    665   DisableDeleting();
    666   return MyMoveFile(_path, name);
    667 }
    668 
    669 bool CTempDir::Create(CFSTR prefix)
    670 {
    671   if (!Remove())
    672     return false;
    673   FString tempPath;
    674   if (!MyGetTempPath(tempPath))
    675     return false;
    676   if (!CreateTempFile(tempPath + prefix, true, _path, NULL))
    677     return false;
    678   _mustBeDeleted = true;
    679   return true;
    680 }
    681 
    682 bool CTempDir::Remove()
    683 {
    684   if (!_mustBeDeleted)
    685     return true;
    686   _mustBeDeleted = !RemoveDirWithSubItems(_path);
    687   return !_mustBeDeleted;
    688 }
    689 
    690 }}}
    691