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