1 /* 2 * PROJECT: VFAT Filesystem 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Routines to manipulate directory entries 5 * COPYRIGHT: Copyright 1998 Jason Filby <jasonfilby@yahoo.com> 6 * Copyright 2001 Rex Jolliff <rex@lvcablemodem.com> 7 * Copyright 2004-2022 Hervé Poussineau <hpoussin@reactos.org> 8 */ 9 10 /* ------------------------------------------------------- INCLUDES */ 11 12 #include "vfat.h" 13 14 #define NDEBUG 15 #include <debug.h> 16 17 ULONG 18 vfatDirEntryGetFirstCluster( 19 PDEVICE_EXTENSION pDeviceExt, 20 PDIR_ENTRY pFatDirEntry) 21 { 22 ULONG cluster; 23 24 if (pDeviceExt->FatInfo.FatType == FAT32) 25 { 26 cluster = pFatDirEntry->Fat.FirstCluster | 27 (pFatDirEntry->Fat.FirstClusterHigh << 16); 28 } 29 else if (vfatVolumeIsFatX(pDeviceExt)) 30 { 31 cluster = pFatDirEntry->FatX.FirstCluster; 32 } 33 else 34 { 35 cluster = pFatDirEntry->Fat.FirstCluster; 36 } 37 38 return cluster; 39 } 40 41 BOOLEAN 42 FATIsDirectoryEmpty( 43 PDEVICE_EXTENSION DeviceExt, 44 PVFATFCB Fcb) 45 { 46 LARGE_INTEGER FileOffset; 47 PVOID Context = NULL; 48 PFAT_DIR_ENTRY FatDirEntry; 49 ULONG Index, MaxIndex; 50 NTSTATUS Status; 51 52 if (vfatFCBIsRoot(Fcb)) 53 { 54 Index = 0; 55 } 56 else 57 { 58 Index = 2; 59 } 60 61 FileOffset.QuadPart = 0; 62 MaxIndex = Fcb->RFCB.FileSize.u.LowPart / sizeof(FAT_DIR_ENTRY); 63 64 Status = vfatFCBInitializeCacheFromVolume(DeviceExt, Fcb); 65 if (!NT_SUCCESS(Status)) 66 { 67 return FALSE; 68 } 69 70 while (Index < MaxIndex) 71 { 72 if (Context == NULL || (Index % FAT_ENTRIES_PER_PAGE) == 0) 73 { 74 if (Context != NULL) 75 { 76 CcUnpinData(Context); 77 } 78 79 _SEH2_TRY 80 { 81 CcMapData(Fcb->FileObject, &FileOffset, sizeof(FAT_DIR_ENTRY), MAP_WAIT, &Context, (PVOID*)&FatDirEntry); 82 } 83 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 84 { 85 _SEH2_YIELD(return TRUE); 86 } 87 _SEH2_END; 88 89 FatDirEntry += Index % FAT_ENTRIES_PER_PAGE; 90 FileOffset.QuadPart += PAGE_SIZE; 91 } 92 93 if (FAT_ENTRY_END(FatDirEntry)) 94 { 95 CcUnpinData(Context); 96 return TRUE; 97 } 98 99 if (!FAT_ENTRY_DELETED(FatDirEntry)) 100 { 101 CcUnpinData(Context); 102 return FALSE; 103 } 104 105 Index++; 106 FatDirEntry++; 107 } 108 109 if (Context) 110 { 111 CcUnpinData(Context); 112 } 113 114 return TRUE; 115 } 116 117 BOOLEAN 118 FATXIsDirectoryEmpty( 119 PDEVICE_EXTENSION DeviceExt, 120 PVFATFCB Fcb) 121 { 122 LARGE_INTEGER FileOffset; 123 PVOID Context = NULL; 124 PFATX_DIR_ENTRY FatXDirEntry; 125 ULONG Index = 0, MaxIndex; 126 NTSTATUS Status; 127 128 FileOffset.QuadPart = 0; 129 MaxIndex = Fcb->RFCB.FileSize.u.LowPart / sizeof(FATX_DIR_ENTRY); 130 131 Status = vfatFCBInitializeCacheFromVolume(DeviceExt, Fcb); 132 if (!NT_SUCCESS(Status)) 133 { 134 return FALSE; 135 } 136 137 while (Index < MaxIndex) 138 { 139 if (Context == NULL || (Index % FATX_ENTRIES_PER_PAGE) == 0) 140 { 141 if (Context != NULL) 142 { 143 CcUnpinData(Context); 144 } 145 146 _SEH2_TRY 147 { 148 CcMapData(Fcb->FileObject, &FileOffset, sizeof(FATX_DIR_ENTRY), MAP_WAIT, &Context, (PVOID*)&FatXDirEntry); 149 } 150 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 151 { 152 _SEH2_YIELD(return TRUE); 153 } 154 _SEH2_END; 155 156 FatXDirEntry += Index % FATX_ENTRIES_PER_PAGE; 157 FileOffset.QuadPart += PAGE_SIZE; 158 } 159 160 if (FATX_ENTRY_END(FatXDirEntry)) 161 { 162 CcUnpinData(Context); 163 return TRUE; 164 } 165 166 if (!FATX_ENTRY_DELETED(FatXDirEntry)) 167 { 168 CcUnpinData(Context); 169 return FALSE; 170 } 171 172 Index++; 173 FatXDirEntry++; 174 } 175 176 if (Context) 177 { 178 CcUnpinData(Context); 179 } 180 181 return TRUE; 182 } 183 184 NTSTATUS 185 FATGetNextDirEntry( 186 PVOID *pContext, 187 PVOID *pPage, 188 IN PVFATFCB pDirFcb, 189 PVFAT_DIRENTRY_CONTEXT DirContext, 190 BOOLEAN First) 191 { 192 ULONG dirMap; 193 PWCHAR pName; 194 LARGE_INTEGER FileOffset; 195 PFAT_DIR_ENTRY fatDirEntry; 196 slot * longNameEntry; 197 ULONG index; 198 199 UCHAR CheckSum, shortCheckSum; 200 USHORT i; 201 BOOLEAN Valid = TRUE; 202 BOOLEAN Back = FALSE; 203 204 NTSTATUS Status; 205 206 DirContext->LongNameU.Length = 0; 207 DirContext->LongNameU.Buffer[0] = UNICODE_NULL; 208 209 FileOffset.u.HighPart = 0; 210 FileOffset.u.LowPart = ROUND_DOWN(DirContext->DirIndex * sizeof(FAT_DIR_ENTRY), PAGE_SIZE); 211 212 Status = vfatFCBInitializeCacheFromVolume(DirContext->DeviceExt, pDirFcb); 213 if (!NT_SUCCESS(Status)) 214 { 215 return Status; 216 } 217 218 if (*pContext == NULL || (DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == 0) 219 { 220 if (*pContext != NULL) 221 { 222 CcUnpinData(*pContext); 223 } 224 225 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart) 226 { 227 *pContext = NULL; 228 return STATUS_NO_MORE_ENTRIES; 229 } 230 231 _SEH2_TRY 232 { 233 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage); 234 } 235 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 236 { 237 *pContext = NULL; 238 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES); 239 } 240 _SEH2_END; 241 } 242 243 fatDirEntry = (PFAT_DIR_ENTRY)(*pPage) + DirContext->DirIndex % FAT_ENTRIES_PER_PAGE; 244 longNameEntry = (slot*) fatDirEntry; 245 dirMap = 0; 246 247 if (First) 248 { 249 /* This is the first call to vfatGetNextDirEntry. Possible the start index points 250 * into a long name or points to a short name with an assigned long name. 251 * We must go back to the real start of the entry */ 252 while (DirContext->DirIndex > 0 && 253 !FAT_ENTRY_END(fatDirEntry) && 254 !FAT_ENTRY_DELETED(fatDirEntry) && 255 ((!FAT_ENTRY_LONG(fatDirEntry) && !Back) || 256 (FAT_ENTRY_LONG(fatDirEntry) && !(longNameEntry->id & 0x40)))) 257 { 258 DirContext->DirIndex--; 259 Back = TRUE; 260 261 if ((DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == FAT_ENTRIES_PER_PAGE - 1) 262 { 263 CcUnpinData(*pContext); 264 FileOffset.u.LowPart -= PAGE_SIZE; 265 266 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart) 267 { 268 *pContext = NULL; 269 return STATUS_NO_MORE_ENTRIES; 270 } 271 272 _SEH2_TRY 273 { 274 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage); 275 } 276 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 277 { 278 *pContext = NULL; 279 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES); 280 } 281 _SEH2_END; 282 283 fatDirEntry = (PFAT_DIR_ENTRY)(*pPage) + DirContext->DirIndex % FAT_ENTRIES_PER_PAGE; 284 longNameEntry = (slot*) fatDirEntry; 285 } 286 else 287 { 288 fatDirEntry--; 289 longNameEntry--; 290 } 291 } 292 293 if (Back && !FAT_ENTRY_END(fatDirEntry) && 294 (FAT_ENTRY_DELETED(fatDirEntry) || !FAT_ENTRY_LONG(fatDirEntry))) 295 { 296 DirContext->DirIndex++; 297 298 if ((DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == 0) 299 { 300 CcUnpinData(*pContext); 301 FileOffset.u.LowPart += PAGE_SIZE; 302 303 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart) 304 { 305 *pContext = NULL; 306 return STATUS_NO_MORE_ENTRIES; 307 } 308 309 _SEH2_TRY 310 { 311 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage); 312 } 313 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 314 { 315 *pContext = NULL; 316 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES); 317 } 318 _SEH2_END; 319 320 fatDirEntry = (PFAT_DIR_ENTRY)*pPage; 321 longNameEntry = (slot*) *pPage; 322 } 323 else 324 { 325 fatDirEntry++; 326 longNameEntry++; 327 } 328 } 329 } 330 331 DirContext->StartIndex = DirContext->DirIndex; 332 CheckSum = 0; 333 334 while (TRUE) 335 { 336 if (FAT_ENTRY_END(fatDirEntry)) 337 { 338 CcUnpinData(*pContext); 339 *pContext = NULL; 340 return STATUS_NO_MORE_ENTRIES; 341 } 342 343 if (FAT_ENTRY_DELETED(fatDirEntry)) 344 { 345 dirMap = 0; 346 DirContext->LongNameU.Buffer[0] = 0; 347 DirContext->StartIndex = DirContext->DirIndex + 1; 348 } 349 else 350 { 351 if (FAT_ENTRY_LONG(fatDirEntry)) 352 { 353 if (dirMap == 0) 354 { 355 DPRINT (" long name entry found at %u\n", DirContext->DirIndex); 356 RtlZeroMemory(DirContext->LongNameU.Buffer, DirContext->LongNameU.MaximumLength); 357 CheckSum = longNameEntry->alias_checksum; 358 Valid = TRUE; 359 } 360 361 DPRINT(" name chunk1:[%.*S] chunk2:[%.*S] chunk3:[%.*S]\n", 362 5, longNameEntry->name0_4, 363 6, longNameEntry->name5_10, 364 2, longNameEntry->name11_12); 365 366 index = longNameEntry->id & 0x3f; // Note: it can be 0 for corrupted FS 367 368 /* Make sure index is valid and we have enough space in buffer 369 (we count one char for \0) */ 370 if (index > 0 && 371 index * 13 < DirContext->LongNameU.MaximumLength / sizeof(WCHAR)) 372 { 373 index--; // make index 0 based 374 dirMap |= 1 << index; 375 376 pName = DirContext->LongNameU.Buffer + index * 13; 377 RtlCopyMemory(pName, longNameEntry->name0_4, 5 * sizeof(WCHAR)); 378 RtlCopyMemory(pName + 5, longNameEntry->name5_10, 6 * sizeof(WCHAR)); 379 RtlCopyMemory(pName + 11, longNameEntry->name11_12, 2 * sizeof(WCHAR)); 380 381 if (longNameEntry->id & 0x40) 382 { 383 /* It's last LFN entry. Terminate filename with \0 */ 384 pName[13] = UNICODE_NULL; 385 } 386 } 387 else 388 DPRINT1("Long name entry has invalid index: %x!\n", longNameEntry->id); 389 390 DPRINT (" longName: [%S]\n", DirContext->LongNameU.Buffer); 391 392 if (CheckSum != longNameEntry->alias_checksum) 393 { 394 DPRINT1("Found wrong alias checksum in long name entry (first %x, current %x, %S)\n", 395 CheckSum, longNameEntry->alias_checksum, DirContext->LongNameU.Buffer); 396 Valid = FALSE; 397 } 398 } 399 else 400 { 401 shortCheckSum = 0; 402 for (i = 0; i < 11; i++) 403 { 404 shortCheckSum = (((shortCheckSum & 1) << 7) 405 | ((shortCheckSum & 0xfe) >> 1)) 406 + fatDirEntry->ShortName[i]; 407 } 408 409 if (shortCheckSum != CheckSum && DirContext->LongNameU.Buffer[0]) 410 { 411 DPRINT1("Checksum from long and short name is not equal (short: %x, long: %x, %S)\n", 412 shortCheckSum, CheckSum, DirContext->LongNameU.Buffer); 413 DirContext->LongNameU.Buffer[0] = 0; 414 } 415 416 if (Valid == FALSE) 417 { 418 DirContext->LongNameU.Buffer[0] = 0; 419 } 420 421 RtlCopyMemory (&DirContext->DirEntry.Fat, fatDirEntry, sizeof (FAT_DIR_ENTRY)); 422 break; 423 } 424 } 425 426 DirContext->DirIndex++; 427 428 if ((DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == 0) 429 { 430 CcUnpinData(*pContext); 431 FileOffset.u.LowPart += PAGE_SIZE; 432 433 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart) 434 { 435 *pContext = NULL; 436 return STATUS_NO_MORE_ENTRIES; 437 } 438 439 _SEH2_TRY 440 { 441 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage); 442 } 443 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 444 { 445 *pContext = NULL; 446 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES); 447 } 448 _SEH2_END; 449 450 fatDirEntry = (PFAT_DIR_ENTRY)*pPage; 451 longNameEntry = (slot*) *pPage; 452 } 453 else 454 { 455 fatDirEntry++; 456 longNameEntry++; 457 } 458 } 459 460 /* Make sure filename is NULL terminate and calculate length */ 461 DirContext->LongNameU.Buffer[DirContext->LongNameU.MaximumLength / sizeof(WCHAR) - 1] 462 = UNICODE_NULL; 463 DirContext->LongNameU.Length = wcslen(DirContext->LongNameU.Buffer) * sizeof(WCHAR); 464 465 /* Init short name */ 466 vfat8Dot3ToString(&DirContext->DirEntry.Fat, &DirContext->ShortNameU); 467 468 /* If we found no LFN, use short name as long */ 469 if (DirContext->LongNameU.Length == 0) 470 RtlCopyUnicodeString(&DirContext->LongNameU, &DirContext->ShortNameU); 471 472 return STATUS_SUCCESS; 473 } 474 475 NTSTATUS 476 FATXGetNextDirEntry( 477 PVOID *pContext, 478 PVOID *pPage, 479 IN PVFATFCB pDirFcb, 480 PVFAT_DIRENTRY_CONTEXT DirContext, 481 BOOLEAN First) 482 { 483 LARGE_INTEGER FileOffset; 484 PFATX_DIR_ENTRY fatxDirEntry; 485 OEM_STRING StringO; 486 ULONG DirIndex = DirContext->DirIndex; 487 NTSTATUS Status; 488 489 FileOffset.u.HighPart = 0; 490 491 UNREFERENCED_PARAMETER(First); 492 493 if (!vfatFCBIsRoot(pDirFcb)) 494 { 495 /* need to add . and .. entries */ 496 switch (DirContext->DirIndex) 497 { 498 case 0: /* entry . */ 499 wcscpy(DirContext->LongNameU.Buffer, L"."); 500 DirContext->LongNameU.Length = sizeof(WCHAR); 501 DirContext->ShortNameU = DirContext->LongNameU; 502 RtlCopyMemory(&DirContext->DirEntry.FatX, &pDirFcb->entry.FatX, sizeof(FATX_DIR_ENTRY)); 503 DirContext->DirEntry.FatX.Filename[0] = '.'; 504 DirContext->DirEntry.FatX.FilenameLength = 1; 505 DirContext->StartIndex = 0; 506 return STATUS_SUCCESS; 507 508 case 1: /* entry .. */ 509 wcscpy(DirContext->LongNameU.Buffer, L".."); 510 DirContext->LongNameU.Length = 2 * sizeof(WCHAR); 511 DirContext->ShortNameU = DirContext->LongNameU; 512 RtlCopyMemory(&DirContext->DirEntry.FatX, &pDirFcb->entry.FatX, sizeof(FATX_DIR_ENTRY)); 513 DirContext->DirEntry.FatX.Filename[0] = DirContext->DirEntry.FatX.Filename[1] = '.'; 514 DirContext->DirEntry.FatX.FilenameLength = 2; 515 DirContext->StartIndex = 1; 516 return STATUS_SUCCESS; 517 518 default: 519 DirIndex -= 2; 520 } 521 } 522 523 Status = vfatFCBInitializeCacheFromVolume(DirContext->DeviceExt, pDirFcb); 524 if (!NT_SUCCESS(Status)) 525 { 526 return Status; 527 } 528 529 if (*pContext == NULL || (DirIndex % FATX_ENTRIES_PER_PAGE) == 0) 530 { 531 if (*pContext != NULL) 532 { 533 CcUnpinData(*pContext); 534 } 535 FileOffset.u.LowPart = ROUND_DOWN(DirIndex * sizeof(FATX_DIR_ENTRY), PAGE_SIZE); 536 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart) 537 { 538 *pContext = NULL; 539 return STATUS_NO_MORE_ENTRIES; 540 } 541 542 _SEH2_TRY 543 { 544 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage); 545 } 546 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 547 { 548 *pContext = NULL; 549 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES); 550 } 551 _SEH2_END; 552 } 553 554 fatxDirEntry = (PFATX_DIR_ENTRY)(*pPage) + DirIndex % FATX_ENTRIES_PER_PAGE; 555 556 DirContext->StartIndex = DirContext->DirIndex; 557 558 while (TRUE) 559 { 560 if (FATX_ENTRY_END(fatxDirEntry)) 561 { 562 CcUnpinData(*pContext); 563 *pContext = NULL; 564 return STATUS_NO_MORE_ENTRIES; 565 } 566 567 if (!FATX_ENTRY_DELETED(fatxDirEntry)) 568 { 569 RtlCopyMemory(&DirContext->DirEntry.FatX, fatxDirEntry, sizeof(FATX_DIR_ENTRY)); 570 break; 571 } 572 DirContext->DirIndex++; 573 DirContext->StartIndex++; 574 DirIndex++; 575 if ((DirIndex % FATX_ENTRIES_PER_PAGE) == 0) 576 { 577 CcUnpinData(*pContext); 578 FileOffset.u.LowPart += PAGE_SIZE; 579 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart) 580 { 581 *pContext = NULL; 582 return STATUS_NO_MORE_ENTRIES; 583 } 584 585 _SEH2_TRY 586 { 587 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage); 588 } 589 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 590 { 591 *pContext = NULL; 592 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES); 593 } 594 _SEH2_END; 595 596 fatxDirEntry = (PFATX_DIR_ENTRY)*pPage; 597 } 598 else 599 { 600 fatxDirEntry++; 601 } 602 } 603 StringO.Buffer = (PCHAR)fatxDirEntry->Filename; 604 StringO.Length = StringO.MaximumLength = fatxDirEntry->FilenameLength; 605 RtlOemStringToUnicodeString(&DirContext->LongNameU, &StringO, FALSE); 606 DirContext->ShortNameU = DirContext->LongNameU; 607 return STATUS_SUCCESS; 608 } 609