1 // Windows/FileFind.cpp 2 3 #include "StdAfx.h" 4 5 #ifndef _UNICODE 6 #include "../Common/StringConvert.h" 7 #endif 8 9 #include "FileFind.h" 10 #include "FileIO.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 #if defined(_WIN32) && !defined(UNDER_CE) 22 23 EXTERN_C_BEGIN 24 25 typedef enum 26 { 27 My_FindStreamInfoStandard, 28 My_FindStreamInfoMaxInfoLevel 29 } MY_STREAM_INFO_LEVELS; 30 31 typedef struct 32 { 33 LARGE_INTEGER StreamSize; 34 WCHAR cStreamName[MAX_PATH + 36]; 35 } MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA; 36 37 typedef WINBASEAPI HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel, 38 LPVOID findStreamData, DWORD flags); 39 40 typedef WINBASEAPI BOOL (APIENTRY *FindNextStreamW_Ptr)(HANDLE findStream, LPVOID findStreamData); 41 42 EXTERN_C_END 43 44 #endif 45 46 namespace NWindows { 47 namespace NFile { 48 49 #ifdef SUPPORT_DEVICE_FILE 50 namespace NSystem 51 { 52 bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize); 53 } 54 #endif 55 56 namespace NFind { 57 58 bool CFileInfo::IsDots() const throw() 59 { 60 if (!IsDir() || Name.IsEmpty()) 61 return false; 62 if (Name[0] != '.') 63 return false; 64 return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == '.'); 65 } 66 67 #define WIN_FD_TO_MY_FI(fi, fd) \ 68 fi.Attrib = fd.dwFileAttributes; \ 69 fi.CTime = fd.ftCreationTime; \ 70 fi.ATime = fd.ftLastAccessTime; \ 71 fi.MTime = fd.ftLastWriteTime; \ 72 fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \ 73 fi.IsAltStream = false; \ 74 fi.IsDevice = false; 75 76 /* 77 #ifdef UNDER_CE 78 fi.ObjectID = fd.dwOID; 79 #else 80 fi.ReparseTag = fd.dwReserved0; 81 #endif 82 */ 83 84 static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi) 85 { 86 WIN_FD_TO_MY_FI(fi, fd); 87 fi.Name = us2fs(fd.cFileName); 88 #if defined(_WIN32) && !defined(UNDER_CE) 89 // fi.ShortName = us2fs(fd.cAlternateFileName); 90 #endif 91 } 92 93 #ifndef _UNICODE 94 95 static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi) 96 { 97 WIN_FD_TO_MY_FI(fi, fd); 98 fi.Name = fas2fs(fd.cFileName); 99 #if defined(_WIN32) && !defined(UNDER_CE) 100 // fi.ShortName = fas2fs(fd.cAlternateFileName); 101 #endif 102 } 103 #endif 104 105 //////////////////////////////// 106 // CFindFile 107 108 bool CFindFileBase::Close() throw() 109 { 110 if (_handle == INVALID_HANDLE_VALUE) 111 return true; 112 if (!::FindClose(_handle)) 113 return false; 114 _handle = INVALID_HANDLE_VALUE; 115 return true; 116 } 117 118 /* 119 WinXP-64 FindFirstFile(): 120 "" - ERROR_PATH_NOT_FOUND 121 folder\ - ERROR_FILE_NOT_FOUND 122 \ - ERROR_FILE_NOT_FOUND 123 c:\ - ERROR_FILE_NOT_FOUND 124 c: - ERROR_FILE_NOT_FOUND, if current dir is ROOT ( c:\ ) 125 c: - OK, if current dir is NOT ROOT ( c:\folder ) 126 folder - OK 127 128 \\ - ERROR_INVALID_NAME 129 \\Server - ERROR_INVALID_NAME 130 \\Server\ - ERROR_INVALID_NAME 131 132 \\Server\Share - ERROR_BAD_NETPATH 133 \\Server\Share - ERROR_BAD_NET_NAME (Win7). 134 !!! There is problem : Win7 makes some requests for "\\Server\Shar" (look in Procmon), 135 when we call it for "\\Server\Share" 136 137 \\Server\Share\ - ERROR_FILE_NOT_FOUND 138 139 \\?\UNC\Server\Share - ERROR_INVALID_NAME 140 \\?\UNC\Server\Share - ERROR_BAD_PATHNAME (Win7) 141 \\?\UNC\Server\Share\ - ERROR_FILE_NOT_FOUND 142 143 \\Server\Share_RootDrive - ERROR_INVALID_NAME 144 \\Server\Share_RootDrive\ - ERROR_INVALID_NAME 145 146 c:\* - ERROR_FILE_NOT_FOUND, if thare are no item in that folder 147 */ 148 149 bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi) 150 { 151 if (!Close()) 152 return false; 153 #ifndef _UNICODE 154 if (!g_IsNT) 155 { 156 WIN32_FIND_DATAA fd; 157 _handle = ::FindFirstFileA(fs2fas(path), &fd); 158 if (_handle == INVALID_HANDLE_VALUE) 159 return false; 160 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); 161 } 162 else 163 #endif 164 { 165 WIN32_FIND_DATAW fd; 166 167 IF_USE_MAIN_PATH 168 _handle = ::FindFirstFileW(fs2us(path), &fd); 169 #ifdef WIN_LONG_PATH 170 if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH) 171 { 172 UString superPath; 173 if (GetSuperPath(path, superPath, USE_MAIN_PATH)) 174 _handle = ::FindFirstFileW(superPath, &fd); 175 } 176 #endif 177 if (_handle == INVALID_HANDLE_VALUE) 178 return false; 179 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); 180 } 181 return true; 182 } 183 184 bool CFindFile::FindNext(CFileInfo &fi) 185 { 186 #ifndef _UNICODE 187 if (!g_IsNT) 188 { 189 WIN32_FIND_DATAA fd; 190 if (!::FindNextFileA(_handle, &fd)) 191 return false; 192 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); 193 } 194 else 195 #endif 196 { 197 WIN32_FIND_DATAW fd; 198 if (!::FindNextFileW(_handle, &fd)) 199 return false; 200 Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); 201 } 202 return true; 203 } 204 205 #if defined(_WIN32) && !defined(UNDER_CE) 206 207 //////////////////////////////// 208 // AltStreams 209 210 static FindFirstStreamW_Ptr g_FindFirstStreamW; 211 static FindNextStreamW_Ptr g_FindNextStreamW; 212 213 struct CFindStreamLoader 214 { 215 CFindStreamLoader() 216 { 217 g_FindFirstStreamW = (FindFirstStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindFirstStreamW"); 218 g_FindNextStreamW = (FindNextStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindNextStreamW"); 219 } 220 } g_FindStreamLoader; 221 222 bool CStreamInfo::IsMainStream() const throw() 223 { 224 return StringsAreEqualNoCase_Ascii(Name, "::$DATA"); 225 }; 226 227 UString CStreamInfo::GetReducedName() const 228 { 229 // remove ":$DATA" postfix, but keep postfix, if Name is "::$DATA" 230 UString s = Name; 231 if (s.Len() > 6 + 1 && StringsAreEqualNoCase_Ascii(s.RightPtr(6), ":$DATA")) 232 s.DeleteFrom(s.Len() - 6); 233 return s; 234 } 235 236 /* 237 UString CStreamInfo::GetReducedName2() const 238 { 239 UString s = GetReducedName(); 240 if (!s.IsEmpty() && s[0] == ':') 241 s.Delete(0); 242 return s; 243 } 244 */ 245 246 static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si) 247 { 248 si.Size = sd.StreamSize.QuadPart; 249 si.Name = sd.cStreamName; 250 } 251 252 /* 253 WinXP-64 FindFirstStream(): 254 "" - ERROR_PATH_NOT_FOUND 255 folder\ - OK 256 folder - OK 257 \ - OK 258 c:\ - OK 259 c: - OK, if current dir is ROOT ( c:\ ) 260 c: - OK, if current dir is NOT ROOT ( c:\folder ) 261 \\Server\Share - OK 262 \\Server\Share\ - OK 263 264 \\ - ERROR_INVALID_NAME 265 \\Server - ERROR_INVALID_NAME 266 \\Server\ - ERROR_INVALID_NAME 267 */ 268 269 bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si) 270 { 271 if (!Close()) 272 return false; 273 if (!g_FindFirstStreamW) 274 { 275 ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); 276 return false; 277 } 278 { 279 MY_WIN32_FIND_STREAM_DATA sd; 280 SetLastError(0); 281 IF_USE_MAIN_PATH 282 _handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0); 283 if (_handle == INVALID_HANDLE_VALUE) 284 { 285 if (::GetLastError() == ERROR_HANDLE_EOF) 286 return false; 287 // long name can be tricky for path like ".\dirName". 288 #ifdef WIN_LONG_PATH 289 if (USE_SUPER_PATH) 290 { 291 UString superPath; 292 if (GetSuperPath(path, superPath, USE_MAIN_PATH)) 293 _handle = g_FindFirstStreamW(superPath, My_FindStreamInfoStandard, &sd, 0); 294 } 295 #endif 296 } 297 if (_handle == INVALID_HANDLE_VALUE) 298 return false; 299 Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si); 300 } 301 return true; 302 } 303 304 bool CFindStream::FindNext(CStreamInfo &si) 305 { 306 if (!g_FindNextStreamW) 307 { 308 ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); 309 return false; 310 } 311 { 312 MY_WIN32_FIND_STREAM_DATA sd; 313 if (!g_FindNextStreamW(_handle, &sd)) 314 return false; 315 Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si); 316 } 317 return true; 318 } 319 320 bool CStreamEnumerator::Next(CStreamInfo &si, bool &found) 321 { 322 bool res; 323 if (_find.IsHandleAllocated()) 324 res = _find.FindNext(si); 325 else 326 res = _find.FindFirst(_filePath, si); 327 if (res) 328 { 329 found = true; 330 return true; 331 } 332 found = false; 333 return (::GetLastError() == ERROR_HANDLE_EOF); 334 } 335 336 #endif 337 338 339 #define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0; 340 341 void CFileInfoBase::ClearBase() throw() 342 { 343 Size = 0; 344 MY_CLEAR_FILETIME(CTime); 345 MY_CLEAR_FILETIME(ATime); 346 MY_CLEAR_FILETIME(MTime); 347 Attrib = 0; 348 IsAltStream = false; 349 IsDevice = false; 350 } 351 352 /* 353 WinXP-64 GetFileAttributes(): 354 If the function fails, it returns INVALID_FILE_ATTRIBUTES and use GetLastError() to get error code 355 356 \ - OK 357 C:\ - OK, if there is such drive, 358 D:\ - ERROR_PATH_NOT_FOUND, if there is no such drive, 359 360 C:\folder - OK 361 C:\folder\ - OK 362 C:\folderBad - ERROR_FILE_NOT_FOUND 363 364 \\Server\BadShare - ERROR_BAD_NETPATH 365 \\Server\Share - WORKS OK, but MSDN says: 366 GetFileAttributes for a network share, the function fails, and GetLastError 367 returns ERROR_BAD_NETPATH. You must specify a path to a subfolder on that share. 368 */ 369 370 DWORD GetFileAttrib(CFSTR path) 371 { 372 #ifndef _UNICODE 373 if (!g_IsNT) 374 return ::GetFileAttributes(fs2fas(path)); 375 else 376 #endif 377 { 378 IF_USE_MAIN_PATH 379 { 380 DWORD dw = ::GetFileAttributesW(fs2us(path)); 381 if (dw != INVALID_FILE_ATTRIBUTES) 382 return dw; 383 } 384 #ifdef WIN_LONG_PATH 385 if (USE_SUPER_PATH) 386 { 387 UString superPath; 388 if (GetSuperPath(path, superPath, USE_MAIN_PATH)) 389 return ::GetFileAttributesW(superPath); 390 } 391 #endif 392 return INVALID_FILE_ATTRIBUTES; 393 } 394 } 395 396 /* if path is "c:" or "c::" then CFileInfo::Find() returns name of current folder for that disk 397 so instead of absolute path we have relative path in Name. That is not good in some calls */ 398 399 /* In CFileInfo::Find() we want to support same names for alt streams as in CreateFile(). */ 400 401 /* CFileInfo::Find() 402 We alow the following paths (as FindFirstFile): 403 C:\folder 404 c: - if current dir is NOT ROOT ( c:\folder ) 405 406 also we support paths that are not supported by FindFirstFile: 407 \ 408 \\.\c: 409 c:\ - Name will be without tail slash ( c: ) 410 \\?\c:\ - Name will be without tail slash ( c: ) 411 \\Server\Share 412 \\?\UNC\Server\Share 413 414 c:\folder:stream - Name = folder:stream 415 c:\:stream - Name = :stream 416 c::stream - Name = c::stream 417 */ 418 419 bool CFileInfo::Find(CFSTR path) 420 { 421 #ifdef SUPPORT_DEVICE_FILE 422 if (IsDevicePath(path)) 423 { 424 ClearBase(); 425 Name = path + 4; 426 IsDevice = true; 427 428 if (NName::IsDrivePath2(path + 4) && path[6] == 0) 429 { 430 FChar drive[4] = { path[4], ':', '\\', 0 }; 431 UInt64 clusterSize, totalSize, freeSize; 432 if (NSystem::MyGetDiskFreeSpace(drive, clusterSize, totalSize, freeSize)) 433 { 434 Size = totalSize; 435 return true; 436 } 437 } 438 439 NIO::CInFile inFile; 440 // ::OutputDebugStringW(path); 441 if (!inFile.Open(path)) 442 return false; 443 // ::OutputDebugStringW(L"---"); 444 if (inFile.SizeDefined) 445 Size = inFile.Size; 446 return true; 447 } 448 #endif 449 450 #if defined(_WIN32) && !defined(UNDER_CE) 451 452 int colonPos = FindAltStreamColon(path); 453 if (colonPos >= 0 && path[(unsigned)colonPos + 1] != 0) 454 { 455 UString streamName = fs2us(path + (unsigned)colonPos); 456 FString filePath = path; 457 filePath.DeleteFrom(colonPos); 458 /* we allow both cases: 459 name:stream 460 name:stream:$DATA 461 */ 462 const unsigned kPostfixSize = 6; 463 if (streamName.Len() <= kPostfixSize 464 || !StringsAreEqualNoCase_Ascii(streamName.RightPtr(kPostfixSize), ":$DATA")) 465 streamName += L":$DATA"; 466 467 bool isOk = true; 468 469 if (IsDrivePath2(filePath) && 470 (colonPos == 2 || colonPos == 3 && filePath[2] == '\\')) 471 { 472 // FindFirstFile doesn't work for "c:\" and for "c:" (if current dir is ROOT) 473 ClearBase(); 474 Name.Empty(); 475 if (colonPos == 2) 476 Name = filePath; 477 } 478 else 479 isOk = Find(filePath); 480 481 if (isOk) 482 { 483 Attrib &= ~(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT); 484 Size = 0; 485 CStreamEnumerator enumerator(filePath); 486 for (;;) 487 { 488 CStreamInfo si; 489 bool found; 490 if (!enumerator.Next(si, found)) 491 return false; 492 if (!found) 493 { 494 ::SetLastError(ERROR_FILE_NOT_FOUND); 495 return false; 496 } 497 if (si.Name.IsEqualTo_NoCase(streamName)) 498 { 499 // we delete postfix, if alt stream name is not "::$DATA" 500 if (si.Name.Len() > kPostfixSize + 1) 501 si.Name.DeleteFrom(si.Name.Len() - kPostfixSize); 502 Name += us2fs(si.Name); 503 Size = si.Size; 504 IsAltStream = true; 505 return true; 506 } 507 } 508 } 509 } 510 511 #endif 512 513 CFindFile finder; 514 515 #if defined(_WIN32) && !defined(UNDER_CE) 516 { 517 /* 518 DWORD lastError = GetLastError(); 519 if (lastError == ERROR_FILE_NOT_FOUND 520 || lastError == ERROR_BAD_NETPATH // XP64: "\\Server\Share" 521 || lastError == ERROR_BAD_NET_NAME // Win7: "\\Server\Share" 522 || lastError == ERROR_INVALID_NAME // XP64: "\\?\UNC\Server\Share" 523 || lastError == ERROR_BAD_PATHNAME // Win7: "\\?\UNC\Server\Share" 524 ) 525 */ 526 527 unsigned rootSize = 0; 528 if (IsSuperPath(path)) 529 rootSize = kSuperPathPrefixSize; 530 531 if (NName::IsDrivePath(path + rootSize) && path[rootSize + 3] == 0) 532 { 533 DWORD attrib = GetFileAttrib(path); 534 if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) 535 { 536 ClearBase(); 537 Attrib = attrib; 538 Name = path + rootSize; 539 Name.DeleteFrom(2); // we don't need backslash (C:) 540 return true; 541 } 542 } 543 else if (IS_PATH_SEPAR(path[0])) 544 if (path[1] == 0) 545 { 546 DWORD attrib = GetFileAttrib(path); 547 if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) 548 { 549 ClearBase(); 550 Name.Empty(); 551 Attrib = attrib; 552 return true; 553 } 554 } 555 else 556 { 557 const unsigned prefixSize = GetNetworkServerPrefixSize(path); 558 if (prefixSize > 0 && path[prefixSize] != 0) 559 { 560 if (NName::FindSepar(path + prefixSize) < 0) 561 { 562 FString s = path; 563 s.Add_PathSepar(); 564 s += FCHAR_ANY_MASK; 565 566 bool isOK = false; 567 if (finder.FindFirst(s, *this)) 568 { 569 if (Name == FTEXT(".")) 570 { 571 Name = path + prefixSize; 572 return true; 573 } 574 isOK = true; 575 /* if "\\server\share" maps to root folder "d:\", there is no "." item. 576 But it's possible that there are another items */ 577 } 578 { 579 DWORD attrib = GetFileAttrib(path); 580 if (isOK || attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) 581 { 582 ClearBase(); 583 if (attrib != INVALID_FILE_ATTRIBUTES) 584 Attrib = attrib; 585 else 586 SetAsDir(); 587 Name = path + prefixSize; 588 return true; 589 } 590 } 591 // ::SetLastError(lastError); 592 } 593 } 594 } 595 } 596 #endif 597 598 return finder.FindFirst(path, *this); 599 } 600 601 602 bool DoesFileExist(CFSTR name) 603 { 604 CFileInfo fi; 605 return fi.Find(name) && !fi.IsDir(); 606 } 607 608 bool DoesDirExist(CFSTR name) 609 { 610 CFileInfo fi; 611 return fi.Find(name) && fi.IsDir(); 612 } 613 614 bool DoesFileOrDirExist(CFSTR name) 615 { 616 CFileInfo fi; 617 return fi.Find(name); 618 } 619 620 621 bool CEnumerator::NextAny(CFileInfo &fi) 622 { 623 if (_findFile.IsHandleAllocated()) 624 return _findFile.FindNext(fi); 625 else 626 return _findFile.FindFirst(_wildcard, fi); 627 } 628 629 bool CEnumerator::Next(CFileInfo &fi) 630 { 631 for (;;) 632 { 633 if (!NextAny(fi)) 634 return false; 635 if (!fi.IsDots()) 636 return true; 637 } 638 } 639 640 bool CEnumerator::Next(CFileInfo &fi, bool &found) 641 { 642 if (Next(fi)) 643 { 644 found = true; 645 return true; 646 } 647 found = false; 648 return (::GetLastError() == ERROR_NO_MORE_FILES); 649 } 650 651 //////////////////////////////// 652 // CFindChangeNotification 653 // FindFirstChangeNotification can return 0. MSDN doesn't tell about it. 654 655 bool CFindChangeNotification::Close() throw() 656 { 657 if (!IsHandleAllocated()) 658 return true; 659 if (!::FindCloseChangeNotification(_handle)) 660 return false; 661 _handle = INVALID_HANDLE_VALUE; 662 return true; 663 } 664 665 HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter) 666 { 667 #ifndef _UNICODE 668 if (!g_IsNT) 669 _handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter); 670 else 671 #endif 672 { 673 IF_USE_MAIN_PATH 674 _handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter); 675 #ifdef WIN_LONG_PATH 676 if (!IsHandleAllocated()) 677 { 678 UString superPath; 679 if (GetSuperPath(path, superPath, USE_MAIN_PATH)) 680 _handle = ::FindFirstChangeNotificationW(superPath, BoolToBOOL(watchSubtree), notifyFilter); 681 } 682 #endif 683 } 684 return _handle; 685 } 686 687 #ifndef UNDER_CE 688 689 bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings) 690 { 691 driveStrings.Clear(); 692 #ifndef _UNICODE 693 if (!g_IsNT) 694 { 695 driveStrings.Clear(); 696 UINT32 size = GetLogicalDriveStrings(0, NULL); 697 if (size == 0) 698 return false; 699 CObjArray<char> buf(size); 700 UINT32 newSize = GetLogicalDriveStrings(size, buf); 701 if (newSize == 0 || newSize > size) 702 return false; 703 AString s; 704 UINT32 prev = 0; 705 for (UINT32 i = 0; i < newSize; i++) 706 { 707 if (buf[i] == 0) 708 { 709 s = buf + prev; 710 prev = i + 1; 711 driveStrings.Add(fas2fs(s)); 712 } 713 } 714 return prev == newSize; 715 } 716 else 717 #endif 718 { 719 UINT32 size = GetLogicalDriveStringsW(0, NULL); 720 if (size == 0) 721 return false; 722 CObjArray<wchar_t> buf(size); 723 UINT32 newSize = GetLogicalDriveStringsW(size, buf); 724 if (newSize == 0 || newSize > size) 725 return false; 726 UString s; 727 UINT32 prev = 0; 728 for (UINT32 i = 0; i < newSize; i++) 729 { 730 if (buf[i] == 0) 731 { 732 s = buf + prev; 733 prev = i + 1; 734 driveStrings.Add(us2fs(s)); 735 } 736 } 737 return prev == newSize; 738 } 739 } 740 741 #endif 742 743 }}} 744