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 
RemoveDir(CFSTR path)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 
MyMoveFile(CFSTR oldFile,CFSTR newFile)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 
MyCreateHardLink(CFSTR newFileName,CFSTR existFileName)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 
CreateDir(CFSTR path)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 
CreateDir2(CFSTR path)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 
CreateComplexDir(CFSTR _path)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 
DeleteFileAlways(CFSTR path)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 
RemoveDirWithSubItems(const FString & path)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 
MyGetFullPathName(CFSTR path,FString & resFullPath)482 bool MyGetFullPathName(CFSTR path, FString &resFullPath)
483 {
484   resFullPath = path;
485   return true;
486 }
487 
488 #else
489 
MyGetFullPathName(CFSTR path,FString & resFullPath)490 bool MyGetFullPathName(CFSTR path, FString &resFullPath)
491 {
492   return GetFullPath(path, resFullPath);
493 }
494 
SetCurrentDir(CFSTR path)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 
GetCurrentDir(FString & path)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 
GetFullPathAndSplit(CFSTR path,FString & resDirPrefix,FString & resFileName)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 
GetOnlyDirPrefix(CFSTR path,FString & resDirPrefix)546 bool GetOnlyDirPrefix(CFSTR path, FString &resDirPrefix)
547 {
548   FString resFileName;
549   return GetFullPathAndSplit(path, resDirPrefix, resFileName);
550 }
551 
MyGetTempPath(FString & path)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 
CreateTempFile(CFSTR prefix,bool addRandom,FString & path,NIO::COutFile * outFile)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 
Create(CFSTR prefix,NIO::COutFile * outFile)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 
CreateRandomInTempFolder(CFSTR namePrefix,NIO::COutFile * outFile)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 
Remove()651 bool CTempFile::Remove()
652 {
653   if (!_mustBeDeleted)
654     return true;
655   _mustBeDeleted = !DeleteFileAlways(_path);
656   return !_mustBeDeleted;
657 }
658 
MoveTo(CFSTR name,bool deleteDestBefore)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 
Create(CFSTR prefix)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 
Remove()682 bool CTempDir::Remove()
683 {
684   if (!_mustBeDeleted)
685     return true;
686   _mustBeDeleted = !RemoveDirWithSubItems(_path);
687   return !_mustBeDeleted;
688 }
689 
690 }}}
691