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 
GetWindowsDir(FString & path)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 
GetSystemDir(FString & path)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 
SetDirTime(CFSTR path,const FILETIME * cTime,const FILETIME * aTime,const FILETIME * mTime)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 
SetFileAttrib(CFSTR path,DWORD attrib)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 
SetFileAttrib_PosixHighDetect(CFSTR path,DWORD attrib)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 
RemoveDir(CFSTR path)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 
MyMoveFile(CFSTR oldFile,CFSTR newFile)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 
MyCreateHardLink(CFSTR newFileName,CFSTR existFileName)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 
CreateDir(CFSTR path)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 
CreateDir2(CFSTR path)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 
CreateComplexDir(CFSTR _path)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 
DeleteFileAlways(CFSTR path)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 
RemoveDirWithSubItems(const FString & path)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 
MyGetFullPathName(CFSTR path,FString & resFullPath)491 bool MyGetFullPathName(CFSTR path, FString &resFullPath)
492 {
493   resFullPath = path;
494   return true;
495 }
496 
497 #else
498 
MyGetFullPathName(CFSTR path,FString & resFullPath)499 bool MyGetFullPathName(CFSTR path, FString &resFullPath)
500 {
501   return GetFullPath(path, resFullPath);
502 }
503 
SetCurrentDir(CFSTR path)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 
GetCurrentDir(FString & path)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 
GetFullPathAndSplit(CFSTR path,FString & resDirPrefix,FString & resFileName)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 
GetOnlyDirPrefix(CFSTR path,FString & resDirPrefix)555 bool GetOnlyDirPrefix(CFSTR path, FString &resDirPrefix)
556 {
557   FString resFileName;
558   return GetFullPathAndSplit(path, resDirPrefix, resFileName);
559 }
560 
MyGetTempPath(FString & path)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 
CreateTempFile(CFSTR prefix,bool addRandom,FString & path,NIO::COutFile * outFile)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 
Create(CFSTR prefix,NIO::COutFile * outFile)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 
CreateRandomInTempFolder(CFSTR namePrefix,NIO::COutFile * outFile)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 
Remove()660 bool CTempFile::Remove()
661 {
662   if (!_mustBeDeleted)
663     return true;
664   _mustBeDeleted = !DeleteFileAlways(_path);
665   return !_mustBeDeleted;
666 }
667 
MoveTo(CFSTR name,bool deleteDestBefore)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 
Create(CFSTR prefix)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 
Remove()706 bool CTempDir::Remove()
707 {
708   if (!_mustBeDeleted)
709     return true;
710   _mustBeDeleted = !RemoveDirWithSubItems(_path);
711   return !_mustBeDeleted;
712 }
713 
714 }}}
715