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