1 // Windows/FileFind.cpp
2
3 #include "StdAfx.h"
4
5 #include "FileFind.h"
6 #include "FileIO.h"
7 #include "FileName.h"
8 #ifndef _UNICODE
9 #include "../Common/StringConvert.h"
10 #endif
11
12 #ifndef _UNICODE
13 extern bool g_IsNT;
14 #endif
15
16 using namespace NWindows;
17 using namespace NFile;
18 using namespace NName;
19
20 #if defined(_WIN32) && !defined(UNDER_CE)
21
22 EXTERN_C_BEGIN
23
24 typedef enum
25 {
26 My_FindStreamInfoStandard,
27 My_FindStreamInfoMaxInfoLevel
28 } MY_STREAM_INFO_LEVELS;
29
30 typedef struct
31 {
32 LARGE_INTEGER StreamSize;
33 WCHAR cStreamName[MAX_PATH + 36];
34 } MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA;
35
36 typedef WINBASEAPI HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel,
37 LPVOID findStreamData, DWORD flags);
38
39 typedef WINBASEAPI BOOL (APIENTRY *FindNextStreamW_Ptr)(HANDLE findStream, LPVOID findStreamData);
40
41 EXTERN_C_END
42
43 #endif
44
45 namespace NWindows {
46 namespace NFile {
47
48 #ifdef SUPPORT_DEVICE_FILE
49 namespace NSystem
50 {
51 bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);
52 }
53 #endif
54
55 namespace NFind {
56
IsDots() const57 bool CFileInfo::IsDots() const throw()
58 {
59 if (!IsDir() || Name.IsEmpty())
60 return false;
61 if (Name[0] != FTEXT('.'))
62 return false;
63 return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == FTEXT('.'));
64 }
65
66 #define WIN_FD_TO_MY_FI(fi, fd) \
67 fi.Attrib = fd.dwFileAttributes; \
68 fi.CTime = fd.ftCreationTime; \
69 fi.ATime = fd.ftLastAccessTime; \
70 fi.MTime = fd.ftLastWriteTime; \
71 fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \
72 fi.IsAltStream = false; \
73 fi.IsDevice = false;
74
75 /*
76 #ifdef UNDER_CE
77 fi.ObjectID = fd.dwOID;
78 #else
79 fi.ReparseTag = fd.dwReserved0;
80 #endif
81 */
82
Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW & fd,CFileInfo & fi)83 static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi)
84 {
85 WIN_FD_TO_MY_FI(fi, fd);
86 fi.Name = us2fs(fd.cFileName);
87 #if defined(_WIN32) && !defined(UNDER_CE)
88 // fi.ShortName = us2fs(fd.cAlternateFileName);
89 #endif
90 }
91
92 #ifndef _UNICODE
93
Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA & fd,CFileInfo & fi)94 static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi)
95 {
96 WIN_FD_TO_MY_FI(fi, fd);
97 fi.Name = fas2fs(fd.cFileName);
98 #if defined(_WIN32) && !defined(UNDER_CE)
99 // fi.ShortName = fas2fs(fd.cAlternateFileName);
100 #endif
101 }
102 #endif
103
104 ////////////////////////////////
105 // CFindFile
106
Close()107 bool CFindFileBase::Close() throw()
108 {
109 if (_handle == INVALID_HANDLE_VALUE)
110 return true;
111 if (!::FindClose(_handle))
112 return false;
113 _handle = INVALID_HANDLE_VALUE;
114 return true;
115 }
116
FindFirst(CFSTR path,CFileInfo & fi)117 bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi)
118 {
119 if (!Close())
120 return false;
121 #ifndef _UNICODE
122 if (!g_IsNT)
123 {
124 WIN32_FIND_DATAA fd;
125 _handle = ::FindFirstFileA(fs2fas(path), &fd);
126 if (_handle == INVALID_HANDLE_VALUE)
127 return false;
128 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
129 }
130 else
131 #endif
132 {
133 WIN32_FIND_DATAW fd;
134
135 IF_USE_MAIN_PATH
136 _handle = ::FindFirstFileW(fs2us(path), &fd);
137 #ifdef WIN_LONG_PATH
138 if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH)
139 {
140 UString longPath;
141 if (GetSuperPath(path, longPath, USE_MAIN_PATH))
142 _handle = ::FindFirstFileW(longPath, &fd);
143 }
144 #endif
145 if (_handle == INVALID_HANDLE_VALUE)
146 return false;
147 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
148 }
149 return true;
150 }
151
FindNext(CFileInfo & fi)152 bool CFindFile::FindNext(CFileInfo &fi)
153 {
154 #ifndef _UNICODE
155 if (!g_IsNT)
156 {
157 WIN32_FIND_DATAA fd;
158 if (!::FindNextFileA(_handle, &fd))
159 return false;
160 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
161 }
162 else
163 #endif
164 {
165 WIN32_FIND_DATAW fd;
166 if (!::FindNextFileW(_handle, &fd))
167 return false;
168 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
169 }
170 return true;
171 }
172
173 #if defined(_WIN32) && !defined(UNDER_CE)
174
175 ////////////////////////////////
176 // AltStreams
177
178 static FindFirstStreamW_Ptr g_FindFirstStreamW;
179 static FindNextStreamW_Ptr g_FindNextStreamW;
180
181 struct CFindStreamLoader
182 {
CFindStreamLoaderNWindows::NFile::NFind::CFindStreamLoader183 CFindStreamLoader()
184 {
185 g_FindFirstStreamW = (FindFirstStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindFirstStreamW");
186 g_FindNextStreamW = (FindNextStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindNextStreamW");
187 }
188 } g_FindStreamLoader;
189
IsMainStream() const190 bool CStreamInfo::IsMainStream() const throw()
191 {
192 return Name == L"::$DATA";
193 };
194
GetReducedName() const195 UString CStreamInfo::GetReducedName() const
196 {
197 UString s = Name;
198 if (s.Len() >= 6)
199 if (wcscmp(s.RightPtr(6), L":$DATA") == 0)
200 s.DeleteFrom(s.Len() - 6);
201 return s;
202 }
203
Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA & sd,CStreamInfo & si)204 static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si)
205 {
206 si.Size = sd.StreamSize.QuadPart;
207 si.Name = sd.cStreamName;
208 }
209
FindFirst(CFSTR path,CStreamInfo & si)210 bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si)
211 {
212 if (!Close())
213 return false;
214 if (!g_FindFirstStreamW)
215 {
216 ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
217 return false;
218 }
219 {
220 MY_WIN32_FIND_STREAM_DATA sd;
221 IF_USE_MAIN_PATH
222 _handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0);
223 if (_handle == INVALID_HANDLE_VALUE)
224 {
225 if (::GetLastError() == ERROR_HANDLE_EOF)
226 return false;
227 // long name can be tricky for path like ".\dirName".
228 #ifdef WIN_LONG_PATH
229 if (USE_SUPER_PATH)
230 {
231 UString longPath;
232 if (GetSuperPath(path, longPath, USE_MAIN_PATH))
233 _handle = g_FindFirstStreamW(longPath, My_FindStreamInfoStandard, &sd, 0);
234 }
235 #endif
236 }
237 if (_handle == INVALID_HANDLE_VALUE)
238 return false;
239 Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
240 }
241 return true;
242 }
243
FindNext(CStreamInfo & si)244 bool CFindStream::FindNext(CStreamInfo &si)
245 {
246 if (!g_FindNextStreamW)
247 {
248 ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
249 return false;
250 }
251 {
252 MY_WIN32_FIND_STREAM_DATA sd;
253 if (!g_FindNextStreamW(_handle, &sd))
254 return false;
255 Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
256 }
257 return true;
258 }
259
Next(CStreamInfo & si,bool & found)260 bool CStreamEnumerator::Next(CStreamInfo &si, bool &found)
261 {
262 bool res;
263 if (_find.IsHandleAllocated())
264 res = _find.FindNext(si);
265 else
266 res = _find.FindFirst(_filePath, si);
267 if (res)
268 {
269 found = true;
270 return true;
271 }
272 found = false;
273 return (::GetLastError() == ERROR_HANDLE_EOF);
274 }
275
276 #endif
277
278
279 #define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0;
280
Clear()281 void CFileInfoBase::Clear() throw()
282 {
283 Size = 0;
284 MY_CLEAR_FILETIME(CTime);
285 MY_CLEAR_FILETIME(ATime);
286 MY_CLEAR_FILETIME(MTime);
287 Attrib = 0;
288 IsAltStream = false;
289 IsDevice = false;
290 }
291
292 #if defined(_WIN32) && !defined(UNDER_CE)
293
FindAltStreamColon(CFSTR path)294 static int FindAltStreamColon(CFSTR path)
295 {
296 for (int i = 0;; i++)
297 {
298 FChar c = path[i];
299 if (c == 0)
300 return -1;
301 if (c == ':')
302 {
303 if (path[i + 1] == '\\')
304 if (i == 1 || (i > 1 && path[i - 2] == '\\'))
305 {
306 wchar_t c0 = path[i - 1];
307 if (c0 >= 'a' && c0 <= 'z' ||
308 c0 >= 'A' && c0 <= 'Z')
309 continue;
310 }
311 return i;
312 }
313 }
314 }
315
316 #endif
317
Find(CFSTR path)318 bool CFileInfo::Find(CFSTR path)
319 {
320 #ifdef SUPPORT_DEVICE_FILE
321 if (IsDevicePath(path))
322 {
323 Clear();
324 Name = path + 4;
325
326 IsDevice = true;
327 if (/* path[0] == '\\' && path[1] == '\\' && path[2] == '.' && path[3] == '\\' && */
328 path[5] == ':' && path[6] == 0)
329 {
330 FChar drive[4] = { path[4], ':', '\\', 0 };
331 UInt64 clusterSize, totalSize, freeSize;
332 if (NSystem::MyGetDiskFreeSpace(drive, clusterSize, totalSize, freeSize))
333 {
334 Size = totalSize;
335 return true;
336 }
337 }
338
339 NIO::CInFile inFile;
340 // ::OutputDebugStringW(path);
341 if (!inFile.Open(path))
342 return false;
343 // ::OutputDebugStringW(L"---");
344 if (inFile.SizeDefined)
345 Size = inFile.Size;
346 return true;
347 }
348 #endif
349
350 #if defined(_WIN32) && !defined(UNDER_CE)
351
352 int colonPos = FindAltStreamColon(path);
353 if (colonPos >= 0)
354 {
355 UString streamName = fs2us(path + (unsigned)colonPos);
356 FString filePath = path;
357 filePath.DeleteFrom(colonPos);
358 streamName += L":$DATA"; // change it!!!!
359 if (Find(filePath))
360 {
361 // if (IsDir())
362 Attrib &= ~FILE_ATTRIBUTE_DIRECTORY;
363 Size = 0;
364 CStreamEnumerator enumerator(filePath);
365 for (;;)
366 {
367 CStreamInfo si;
368 bool found;
369 if (!enumerator.Next(si, found))
370 return false;
371 if (!found)
372 {
373 ::SetLastError(ERROR_FILE_NOT_FOUND);
374 return false;
375 }
376 if (si.Name.IsEqualToNoCase(streamName))
377 {
378 Name += us2fs(si.Name);
379 Name.DeleteFrom(Name.Len() - 6);
380 Size = si.Size;
381 IsAltStream = true;
382 return true;
383 }
384 }
385 }
386 }
387
388 #endif
389
390 CFindFile finder;
391 if (finder.FindFirst(path, *this))
392 return true;
393 #ifdef _WIN32
394 {
395 DWORD lastError = GetLastError();
396 if (lastError == ERROR_BAD_NETPATH ||
397 lastError == ERROR_FILE_NOT_FOUND ||
398 lastError == ERROR_INVALID_NAME // for "\\SERVER\shared" paths that are translated to "\\?\UNC\SERVER\shared"
399 )
400 {
401 unsigned len = MyStringLen(path);
402 if (len > 2 && path[0] == '\\' && path[1] == '\\')
403 {
404 int startPos = 2;
405 if (len > kSuperUncPathPrefixSize && IsSuperUncPath(path))
406 startPos = kSuperUncPathPrefixSize;
407 int pos = FindCharPosInString(path + startPos, FTEXT('\\'));
408 if (pos >= 0)
409 {
410 pos += startPos + 1;
411 len -= pos;
412 int pos2 = FindCharPosInString(path + pos, FTEXT('\\'));
413 if (pos2 < 0 || pos2 == (int)len - 1)
414 {
415 FString s = path;
416 if (pos2 < 0)
417 {
418 pos2 = len;
419 s += FTEXT('\\');
420 }
421 s += FCHAR_ANY_MASK;
422 if (finder.FindFirst(s, *this))
423 if (Name == FTEXT("."))
424 {
425 Name.SetFrom(s.Ptr(pos), pos2);
426 return true;
427 }
428 ::SetLastError(lastError);
429 }
430 }
431 }
432 }
433 }
434 #endif
435 return false;
436 }
437
DoesFileExist(CFSTR name)438 bool DoesFileExist(CFSTR name)
439 {
440 CFileInfo fi;
441 return fi.Find(name) && !fi.IsDir();
442 }
443
DoesDirExist(CFSTR name)444 bool DoesDirExist(CFSTR name)
445 {
446 CFileInfo fi;
447 return fi.Find(name) && fi.IsDir();
448 }
DoesFileOrDirExist(CFSTR name)449 bool DoesFileOrDirExist(CFSTR name)
450 {
451 CFileInfo fi;
452 return fi.Find(name);
453 }
454
NextAny(CFileInfo & fi)455 bool CEnumerator::NextAny(CFileInfo &fi)
456 {
457 if (_findFile.IsHandleAllocated())
458 return _findFile.FindNext(fi);
459 else
460 return _findFile.FindFirst(_wildcard, fi);
461 }
462
Next(CFileInfo & fi)463 bool CEnumerator::Next(CFileInfo &fi)
464 {
465 for (;;)
466 {
467 if (!NextAny(fi))
468 return false;
469 if (!fi.IsDots())
470 return true;
471 }
472 }
473
Next(CFileInfo & fi,bool & found)474 bool CEnumerator::Next(CFileInfo &fi, bool &found)
475 {
476 if (Next(fi))
477 {
478 found = true;
479 return true;
480 }
481 found = false;
482 return (::GetLastError() == ERROR_NO_MORE_FILES);
483 }
484
485 ////////////////////////////////
486 // CFindChangeNotification
487 // FindFirstChangeNotification can return 0. MSDN doesn't tell about it.
488
Close()489 bool CFindChangeNotification::Close() throw()
490 {
491 if (!IsHandleAllocated())
492 return true;
493 if (!::FindCloseChangeNotification(_handle))
494 return false;
495 _handle = INVALID_HANDLE_VALUE;
496 return true;
497 }
498
FindFirst(CFSTR path,bool watchSubtree,DWORD notifyFilter)499 HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter)
500 {
501 #ifndef _UNICODE
502 if (!g_IsNT)
503 _handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter);
504 else
505 #endif
506 {
507 IF_USE_MAIN_PATH
508 _handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter);
509 #ifdef WIN_LONG_PATH
510 if (!IsHandleAllocated())
511 {
512 UString longPath;
513 if (GetSuperPath(path, longPath, USE_MAIN_PATH))
514 _handle = ::FindFirstChangeNotificationW(longPath, BoolToBOOL(watchSubtree), notifyFilter);
515 }
516 #endif
517 }
518 return _handle;
519 }
520
521 #ifndef UNDER_CE
522
MyGetLogicalDriveStrings(CObjectVector<FString> & driveStrings)523 bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings)
524 {
525 driveStrings.Clear();
526 #ifndef _UNICODE
527 if (!g_IsNT)
528 {
529 driveStrings.Clear();
530 UINT32 size = GetLogicalDriveStrings(0, NULL);
531 if (size == 0)
532 return false;
533 AString buf;
534 UINT32 newSize = GetLogicalDriveStrings(size, buf.GetBuffer(size));
535 if (newSize == 0 || newSize > size)
536 return false;
537 AString s;
538 for (UINT32 i = 0; i < newSize; i++)
539 {
540 char c = buf[i];
541 if (c == '\0')
542 {
543 driveStrings.Add(fas2fs(s));
544 s.Empty();
545 }
546 else
547 s += c;
548 }
549 return s.IsEmpty();
550 }
551 else
552 #endif
553 {
554 UINT32 size = GetLogicalDriveStringsW(0, NULL);
555 if (size == 0)
556 return false;
557 UString buf;
558 UINT32 newSize = GetLogicalDriveStringsW(size, buf.GetBuffer(size));
559 if (newSize == 0 || newSize > size)
560 return false;
561 UString s;
562 for (UINT32 i = 0; i < newSize; i++)
563 {
564 WCHAR c = buf[i];
565 if (c == L'\0')
566 {
567 driveStrings.Add(us2fs(s));
568 s.Empty();
569 }
570 else
571 s += c;
572 }
573 return s.IsEmpty();
574 }
575 }
576
577 #endif
578
579 }}}
580