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