1 /* 2 * PROJECT: VFAT Filesystem 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Write in directory 5 * COPYRIGHT: Copyright 1999-2001 Rex Jolliff <rex@lvcablemodem.com> 6 * Copyright 2004-2008 Hervé Poussineau <hpoussin@reactos.org> 7 * Copyright 2010-2018 Pierre Schweitzer <pierre@reactos.org> 8 */ 9 10 /* INCLUDES *****************************************************************/ 11 12 #include "vfat.h" 13 14 #define NDEBUG 15 #include <debug.h> 16 17 #ifdef KDBG 18 extern UNICODE_STRING DebugFile; 19 #endif 20 21 NTSTATUS 22 vfatFCBInitializeCacheFromVolume( 23 PVCB vcb, 24 PVFATFCB fcb) 25 { 26 PFILE_OBJECT fileObject; 27 PVFATCCB newCCB; 28 NTSTATUS status; 29 BOOLEAN Acquired; 30 31 /* Don't re-initialize if already done */ 32 if (BooleanFlagOn(fcb->Flags, FCB_CACHE_INITIALIZED)) 33 { 34 return STATUS_SUCCESS; 35 } 36 37 ASSERT(vfatFCBIsDirectory(fcb)); 38 ASSERT(fcb->FileObject == NULL); 39 40 Acquired = FALSE; 41 if (!ExIsResourceAcquiredExclusive(&vcb->DirResource)) 42 { 43 ExAcquireResourceExclusiveLite(&vcb->DirResource, TRUE); 44 Acquired = TRUE; 45 } 46 47 fileObject = IoCreateStreamFileObject (NULL, vcb->StorageDevice); 48 if (fileObject == NULL) 49 { 50 status = STATUS_INSUFFICIENT_RESOURCES; 51 goto Quit; 52 } 53 54 #ifdef KDBG 55 if (DebugFile.Buffer != NULL && FsRtlIsNameInExpression(&DebugFile, &fcb->LongNameU, FALSE, NULL)) 56 { 57 DPRINT1("Attaching %p to %p (%d)\n", fcb, fileObject, fcb->RefCount); 58 } 59 #endif 60 61 newCCB = ExAllocateFromNPagedLookasideList(&VfatGlobalData->CcbLookasideList); 62 if (newCCB == NULL) 63 { 64 status = STATUS_INSUFFICIENT_RESOURCES; 65 ObDereferenceObject(fileObject); 66 goto Quit; 67 } 68 RtlZeroMemory(newCCB, sizeof (VFATCCB)); 69 70 fileObject->SectionObjectPointer = &fcb->SectionObjectPointers; 71 fileObject->FsContext = fcb; 72 fileObject->FsContext2 = newCCB; 73 fileObject->Vpb = vcb->IoVPB; 74 fcb->FileObject = fileObject; 75 76 _SEH2_TRY 77 { 78 CcInitializeCacheMap(fileObject, 79 (PCC_FILE_SIZES)(&fcb->RFCB.AllocationSize), 80 TRUE, 81 &VfatGlobalData->CacheMgrCallbacks, 82 fcb); 83 } 84 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 85 { 86 status = _SEH2_GetExceptionCode(); 87 fcb->FileObject = NULL; 88 ExFreeToNPagedLookasideList(&VfatGlobalData->CcbLookasideList, newCCB); 89 ObDereferenceObject(fileObject); 90 if (Acquired) 91 { 92 ExReleaseResourceLite(&vcb->DirResource); 93 } 94 return status; 95 } 96 _SEH2_END; 97 98 vfatGrabFCB(vcb, fcb); 99 SetFlag(fcb->Flags, FCB_CACHE_INITIALIZED); 100 status = STATUS_SUCCESS; 101 102 Quit: 103 if (Acquired) 104 { 105 ExReleaseResourceLite(&vcb->DirResource); 106 } 107 108 return status; 109 } 110 111 /* 112 * update an existing FAT entry 113 */ 114 NTSTATUS 115 VfatUpdateEntry( 116 IN PDEVICE_EXTENSION DeviceExt, 117 IN PVFATFCB pFcb) 118 { 119 PVOID Context; 120 PDIR_ENTRY PinEntry; 121 LARGE_INTEGER Offset; 122 ULONG SizeDirEntry; 123 ULONG dirIndex; 124 NTSTATUS Status; 125 126 ASSERT(pFcb); 127 128 if (vfatVolumeIsFatX(DeviceExt)) 129 { 130 SizeDirEntry = sizeof(FATX_DIR_ENTRY); 131 dirIndex = pFcb->startIndex; 132 } 133 else 134 { 135 SizeDirEntry = sizeof(FAT_DIR_ENTRY); 136 dirIndex = pFcb->dirIndex; 137 } 138 139 DPRINT("updEntry dirIndex %u, PathName \'%wZ\'\n", dirIndex, &pFcb->PathNameU); 140 141 if (vfatFCBIsRoot(pFcb) || BooleanFlagOn(pFcb->Flags, FCB_IS_FAT | FCB_IS_VOLUME)) 142 { 143 return STATUS_SUCCESS; 144 } 145 146 ASSERT(pFcb->parentFcb); 147 148 Status = vfatFCBInitializeCacheFromVolume(DeviceExt, pFcb->parentFcb); 149 if (!NT_SUCCESS(Status)) 150 { 151 return Status; 152 } 153 154 Offset.u.HighPart = 0; 155 Offset.u.LowPart = dirIndex * SizeDirEntry; 156 _SEH2_TRY 157 { 158 CcPinRead(pFcb->parentFcb->FileObject, &Offset, SizeDirEntry, PIN_WAIT, &Context, (PVOID*)&PinEntry); 159 } 160 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 161 { 162 DPRINT1("Failed write to \'%wZ\'.\n", &pFcb->parentFcb->PathNameU); 163 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 164 } 165 _SEH2_END; 166 167 pFcb->Flags &= ~FCB_IS_DIRTY; 168 RtlCopyMemory(PinEntry, &pFcb->entry, SizeDirEntry); 169 CcSetDirtyPinnedData(Context, NULL); 170 CcUnpinData(Context); 171 return STATUS_SUCCESS; 172 } 173 174 /* 175 * rename an existing FAT entry 176 */ 177 NTSTATUS 178 vfatRenameEntry( 179 IN PDEVICE_EXTENSION DeviceExt, 180 IN PVFATFCB pFcb, 181 IN PUNICODE_STRING FileName, 182 IN BOOLEAN CaseChangeOnly) 183 { 184 OEM_STRING NameA; 185 ULONG StartIndex; 186 PVOID Context = NULL; 187 LARGE_INTEGER Offset; 188 PFATX_DIR_ENTRY pDirEntry; 189 NTSTATUS Status; 190 191 DPRINT("vfatRenameEntry(%p, %p, %wZ, %d)\n", DeviceExt, pFcb, FileName, CaseChangeOnly); 192 193 Status = vfatFCBInitializeCacheFromVolume(DeviceExt, pFcb->parentFcb); 194 if (!NT_SUCCESS(Status)) 195 { 196 return Status; 197 } 198 199 if (vfatVolumeIsFatX(DeviceExt)) 200 { 201 VFAT_DIRENTRY_CONTEXT DirContext; 202 203 /* Open associated dir entry */ 204 StartIndex = pFcb->startIndex; 205 Offset.u.HighPart = 0; 206 Offset.u.LowPart = (StartIndex * sizeof(FATX_DIR_ENTRY) / PAGE_SIZE) * PAGE_SIZE; 207 _SEH2_TRY 208 { 209 CcPinRead(pFcb->parentFcb->FileObject, &Offset, PAGE_SIZE, PIN_WAIT, &Context, (PVOID*)&pDirEntry); 210 } 211 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 212 { 213 DPRINT1("CcPinRead(Offset %x:%x, Length %d) failed\n", Offset.u.HighPart, Offset.u.LowPart, PAGE_SIZE); 214 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 215 } 216 _SEH2_END; 217 218 pDirEntry = &pDirEntry[StartIndex % (PAGE_SIZE / sizeof(FATX_DIR_ENTRY))]; 219 220 /* Set file name */ 221 NameA.Buffer = (PCHAR)pDirEntry->Filename; 222 NameA.Length = 0; 223 NameA.MaximumLength = 42; 224 RtlUnicodeStringToOemString(&NameA, FileName, FALSE); 225 pDirEntry->FilenameLength = (unsigned char)NameA.Length; 226 227 /* Update FCB */ 228 DirContext.DeviceExt = DeviceExt; 229 DirContext.ShortNameU.Length = 0; 230 DirContext.ShortNameU.MaximumLength = 0; 231 DirContext.ShortNameU.Buffer = NULL; 232 DirContext.LongNameU = *FileName; 233 DirContext.DirEntry.FatX = *pDirEntry; 234 235 CcSetDirtyPinnedData(Context, NULL); 236 CcUnpinData(Context); 237 238 Status = vfatUpdateFCB(DeviceExt, pFcb, &DirContext, pFcb->parentFcb); 239 if (NT_SUCCESS(Status)) 240 { 241 CcFlushCache(&pFcb->parentFcb->SectionObjectPointers, NULL, 0, NULL); 242 } 243 244 return Status; 245 } 246 else 247 { 248 /* This we cannot handle properly, move file - would likely need love */ 249 return VfatMoveEntry(DeviceExt, pFcb, FileName, pFcb->parentFcb); 250 } 251 } 252 253 /* 254 * try to find contiguous entries frees in directory, 255 * extend a directory if is necessary 256 */ 257 BOOLEAN 258 vfatFindDirSpace( 259 IN PDEVICE_EXTENSION DeviceExt, 260 IN PVFATFCB pDirFcb, 261 IN ULONG nbSlots, 262 OUT PULONG start) 263 { 264 LARGE_INTEGER FileOffset; 265 ULONG i, count, size, nbFree = 0; 266 PDIR_ENTRY pFatEntry = NULL; 267 PVOID Context = NULL; 268 NTSTATUS Status; 269 ULONG SizeDirEntry; 270 BOOLEAN IsFatX = vfatVolumeIsFatX(DeviceExt); 271 FileOffset.QuadPart = 0; 272 273 if (IsFatX) 274 SizeDirEntry = sizeof(FATX_DIR_ENTRY); 275 else 276 SizeDirEntry = sizeof(FAT_DIR_ENTRY); 277 278 Status = vfatFCBInitializeCacheFromVolume(DeviceExt, pDirFcb); 279 if (!NT_SUCCESS(Status)) 280 { 281 return FALSE; 282 } 283 284 count = pDirFcb->RFCB.FileSize.u.LowPart / SizeDirEntry; 285 size = DeviceExt->FatInfo.BytesPerCluster / SizeDirEntry; 286 for (i = 0; i < count; i++, pFatEntry = (PDIR_ENTRY)((ULONG_PTR)pFatEntry + SizeDirEntry)) 287 { 288 if (Context == NULL || (i % size) == 0) 289 { 290 if (Context) 291 { 292 CcUnpinData(Context); 293 } 294 _SEH2_TRY 295 { 296 CcPinRead(pDirFcb->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster, PIN_WAIT, &Context, (PVOID*)&pFatEntry); 297 } 298 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 299 { 300 _SEH2_YIELD(return FALSE); 301 } 302 _SEH2_END; 303 304 FileOffset.u.LowPart += DeviceExt->FatInfo.BytesPerCluster; 305 } 306 if (ENTRY_END(IsFatX, pFatEntry)) 307 { 308 break; 309 } 310 if (ENTRY_DELETED(IsFatX, pFatEntry)) 311 { 312 nbFree++; 313 } 314 else 315 { 316 nbFree = 0; 317 } 318 if (nbFree == nbSlots) 319 { 320 break; 321 } 322 } 323 if (Context) 324 { 325 CcUnpinData(Context); 326 Context = NULL; 327 } 328 if (nbFree == nbSlots) 329 { 330 /* found enough contiguous free slots */ 331 *start = i - nbSlots + 1; 332 } 333 else 334 { 335 *start = i - nbFree; 336 if (*start + nbSlots > count) 337 { 338 LARGE_INTEGER AllocationSize; 339 /* extend the directory */ 340 if (vfatFCBIsRoot(pDirFcb) && DeviceExt->FatInfo.FatType != FAT32) 341 { 342 /* We can't extend a root directory on a FAT12/FAT16/FATX partition */ 343 return FALSE; 344 } 345 AllocationSize.QuadPart = pDirFcb->RFCB.FileSize.u.LowPart + DeviceExt->FatInfo.BytesPerCluster; 346 Status = VfatSetAllocationSizeInformation(pDirFcb->FileObject, pDirFcb, 347 DeviceExt, &AllocationSize); 348 if (!NT_SUCCESS(Status)) 349 { 350 return FALSE; 351 } 352 /* clear the new dir cluster */ 353 FileOffset.u.LowPart = (ULONG)(pDirFcb->RFCB.FileSize.QuadPart - 354 DeviceExt->FatInfo.BytesPerCluster); 355 _SEH2_TRY 356 { 357 CcPinRead(pDirFcb->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster, PIN_WAIT, &Context, (PVOID*)&pFatEntry); 358 } 359 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 360 { 361 _SEH2_YIELD(return FALSE); 362 } 363 _SEH2_END; 364 365 if (IsFatX) 366 memset(pFatEntry, 0xff, DeviceExt->FatInfo.BytesPerCluster); 367 else 368 RtlZeroMemory(pFatEntry, DeviceExt->FatInfo.BytesPerCluster); 369 } 370 else if (*start + nbSlots < count) 371 { 372 /* clear the entry after the last new entry */ 373 FileOffset.u.LowPart = (*start + nbSlots) * SizeDirEntry; 374 _SEH2_TRY 375 { 376 CcPinRead(pDirFcb->FileObject, &FileOffset, SizeDirEntry, PIN_WAIT, &Context, (PVOID*)&pFatEntry); 377 } 378 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 379 { 380 _SEH2_YIELD(return FALSE); 381 } 382 _SEH2_END; 383 384 if (IsFatX) 385 memset(pFatEntry, 0xff, SizeDirEntry); 386 else 387 RtlZeroMemory(pFatEntry, SizeDirEntry); 388 } 389 if (Context) 390 { 391 CcSetDirtyPinnedData(Context, NULL); 392 CcUnpinData(Context); 393 } 394 } 395 DPRINT("nbSlots %u nbFree %u, entry number %u\n", nbSlots, nbFree, *start); 396 return TRUE; 397 } 398 399 /* 400 create a new FAT entry 401 */ 402 static NTSTATUS 403 FATAddEntry( 404 IN PDEVICE_EXTENSION DeviceExt, 405 IN PUNICODE_STRING NameU, 406 IN PVFATFCB* Fcb, 407 IN PVFATFCB ParentFcb, 408 IN ULONG RequestedOptions, 409 IN UCHAR ReqAttr, 410 IN PVFAT_MOVE_CONTEXT MoveContext) 411 { 412 PVOID Context = NULL; 413 PFAT_DIR_ENTRY pFatEntry; 414 slot *pSlots; 415 USHORT nbSlots = 0, j; 416 PUCHAR Buffer; 417 BOOLEAN needTilde = FALSE, needLong = FALSE; 418 BOOLEAN BaseAllLower, BaseAllUpper; 419 BOOLEAN ExtensionAllLower, ExtensionAllUpper; 420 BOOLEAN InExtension; 421 BOOLEAN IsDirectory; 422 WCHAR c; 423 ULONG CurrentCluster; 424 LARGE_INTEGER SystemTime, FileOffset; 425 NTSTATUS Status = STATUS_SUCCESS; 426 ULONG size; 427 long i; 428 429 OEM_STRING NameA; 430 CHAR aName[13]; 431 BOOLEAN IsNameLegal; 432 BOOLEAN SpacesFound; 433 434 VFAT_DIRENTRY_CONTEXT DirContext; 435 WCHAR LongNameBuffer[LONGNAME_MAX_LENGTH + 1]; 436 WCHAR ShortNameBuffer[13]; 437 438 DPRINT("addEntry: Name='%wZ', Dir='%wZ'\n", NameU, &ParentFcb->PathNameU); 439 440 DirContext.LongNameU = *NameU; 441 IsDirectory = BooleanFlagOn(RequestedOptions, FILE_DIRECTORY_FILE); 442 443 /* nb of entry needed for long name+normal entry */ 444 nbSlots = (DirContext.LongNameU.Length / sizeof(WCHAR) + 12) / 13 + 1; 445 DPRINT("NameLen= %u, nbSlots =%u\n", DirContext.LongNameU.Length / sizeof(WCHAR), nbSlots); 446 Buffer = ExAllocatePoolWithTag(NonPagedPool, (nbSlots - 1) * sizeof(FAT_DIR_ENTRY), TAG_DIRENT); 447 if (Buffer == NULL) 448 { 449 return STATUS_INSUFFICIENT_RESOURCES; 450 } 451 RtlZeroMemory(Buffer, (nbSlots - 1) * sizeof(FAT_DIR_ENTRY)); 452 pSlots = (slot *) Buffer; 453 454 NameA.Buffer = aName; 455 NameA.Length = 0; 456 NameA.MaximumLength = sizeof(aName); 457 458 DirContext.DeviceExt = DeviceExt; 459 DirContext.ShortNameU.Buffer = ShortNameBuffer; 460 DirContext.ShortNameU.Length = 0; 461 DirContext.ShortNameU.MaximumLength = sizeof(ShortNameBuffer); 462 463 RtlZeroMemory(&DirContext.DirEntry.Fat, sizeof(FAT_DIR_ENTRY)); 464 465 IsNameLegal = RtlIsNameLegalDOS8Dot3(&DirContext.LongNameU, &NameA, &SpacesFound); 466 467 if (!IsNameLegal || SpacesFound) 468 { 469 GENERATE_NAME_CONTEXT NameContext; 470 VFAT_DIRENTRY_CONTEXT SearchContext; 471 WCHAR ShortSearchName[13]; 472 needTilde = TRUE; 473 needLong = TRUE; 474 RtlZeroMemory(&NameContext, sizeof(GENERATE_NAME_CONTEXT)); 475 SearchContext.DeviceExt = DeviceExt; 476 SearchContext.LongNameU.Buffer = LongNameBuffer; 477 SearchContext.LongNameU.MaximumLength = sizeof(LongNameBuffer); 478 SearchContext.ShortNameU.Buffer = ShortSearchName; 479 SearchContext.ShortNameU.MaximumLength = sizeof(ShortSearchName); 480 481 for (i = 0; i < 100; i++) 482 { 483 RtlGenerate8dot3Name(&DirContext.LongNameU, FALSE, &NameContext, &DirContext.ShortNameU); 484 DirContext.ShortNameU.Buffer[DirContext.ShortNameU.Length / sizeof(WCHAR)] = 0; 485 SearchContext.DirIndex = 0; 486 Status = FindFile(DeviceExt, ParentFcb, &DirContext.ShortNameU, &SearchContext, TRUE); 487 if (!NT_SUCCESS(Status)) 488 { 489 break; 490 } 491 else if (MoveContext) 492 { 493 ASSERT(*Fcb); 494 if (strncmp((char *)SearchContext.DirEntry.Fat.ShortName, (char *)(*Fcb)->entry.Fat.ShortName, 11) == 0) 495 { 496 if (MoveContext->InPlace) 497 { 498 ASSERT(SearchContext.DirEntry.Fat.FileSize == MoveContext->FileSize); 499 break; 500 } 501 } 502 } 503 } 504 if (i == 100) /* FIXME : what to do after this ? */ 505 { 506 ExFreePoolWithTag(Buffer, TAG_DIRENT); 507 return STATUS_UNSUCCESSFUL; 508 } 509 IsNameLegal = RtlIsNameLegalDOS8Dot3(&DirContext.ShortNameU, &NameA, &SpacesFound); 510 } 511 else 512 { 513 BaseAllLower = BaseAllUpper = TRUE; 514 ExtensionAllLower = ExtensionAllUpper = TRUE; 515 InExtension = FALSE; 516 for (i = 0; i < DirContext.LongNameU.Length / sizeof(WCHAR); i++) 517 { 518 c = DirContext.LongNameU.Buffer[i]; 519 if (c >= L'A' && c <= L'Z') 520 { 521 if (InExtension) 522 ExtensionAllLower = FALSE; 523 else 524 BaseAllLower = FALSE; 525 } 526 else if (c >= L'a' && c <= L'z') 527 { 528 if (InExtension) 529 ExtensionAllUpper = FALSE; 530 else 531 BaseAllUpper = FALSE; 532 } 533 else if (c > 0x7f) 534 { 535 needLong = TRUE; 536 break; 537 } 538 539 if (c == L'.') 540 { 541 InExtension = TRUE; 542 } 543 } 544 545 if ((!BaseAllLower && !BaseAllUpper) || 546 (!ExtensionAllLower && !ExtensionAllUpper)) 547 { 548 needLong = TRUE; 549 } 550 551 RtlUpcaseUnicodeString(&DirContext.ShortNameU, &DirContext.LongNameU, FALSE); 552 DirContext.ShortNameU.Buffer[DirContext.ShortNameU.Length / sizeof(WCHAR)] = 0; 553 } 554 aName[NameA.Length] = 0; 555 DPRINT("'%s', '%wZ', needTilde=%u, needLong=%u\n", 556 aName, &DirContext.LongNameU, needTilde, needLong); 557 memset(DirContext.DirEntry.Fat.ShortName, ' ', 11); 558 for (i = 0; i < 8 && aName[i] && aName[i] != '.'; i++) 559 { 560 DirContext.DirEntry.Fat.Filename[i] = aName[i]; 561 } 562 if (aName[i] == '.') 563 { 564 i++; 565 for (j = 0; j < 3 && aName[i]; j++, i++) 566 { 567 DirContext.DirEntry.Fat.Ext[j] = aName[i]; 568 } 569 } 570 if (DirContext.DirEntry.Fat.Filename[0] == 0xe5) 571 { 572 DirContext.DirEntry.Fat.Filename[0] = 0x05; 573 } 574 575 if (needLong) 576 { 577 RtlCopyMemory(LongNameBuffer, DirContext.LongNameU.Buffer, DirContext.LongNameU.Length); 578 DirContext.LongNameU.Buffer = LongNameBuffer; 579 DirContext.LongNameU.MaximumLength = sizeof(LongNameBuffer); 580 DirContext.LongNameU.Buffer[DirContext.LongNameU.Length / sizeof(WCHAR)] = 0; 581 memset(DirContext.LongNameU.Buffer + DirContext.LongNameU.Length / sizeof(WCHAR) + 1, 0xff, 582 DirContext.LongNameU.MaximumLength - DirContext.LongNameU.Length - sizeof(WCHAR)); 583 } 584 else 585 { 586 nbSlots = 1; 587 if (BaseAllLower && !BaseAllUpper) 588 { 589 DirContext.DirEntry.Fat.lCase |= VFAT_CASE_LOWER_BASE; 590 } 591 if (ExtensionAllLower && !ExtensionAllUpper) 592 { 593 DirContext.DirEntry.Fat.lCase |= VFAT_CASE_LOWER_EXT; 594 } 595 } 596 597 DPRINT ("dos name=%11.11s\n", DirContext.DirEntry.Fat.Filename); 598 599 /* set attributes */ 600 DirContext.DirEntry.Fat.Attrib = ReqAttr; 601 if (IsDirectory) 602 { 603 DirContext.DirEntry.Fat.Attrib |= FILE_ATTRIBUTE_DIRECTORY; 604 } 605 /* set dates and times */ 606 KeQuerySystemTime(&SystemTime); 607 FsdSystemTimeToDosDateTime(DeviceExt, &SystemTime, &DirContext.DirEntry.Fat.CreationDate, 608 &DirContext.DirEntry.Fat.CreationTime); 609 DirContext.DirEntry.Fat.UpdateDate = DirContext.DirEntry.Fat.CreationDate; 610 DirContext.DirEntry.Fat.UpdateTime = DirContext.DirEntry.Fat.CreationTime; 611 DirContext.DirEntry.Fat.AccessDate = DirContext.DirEntry.Fat.CreationDate; 612 /* If it's moving, preserve creation time and file size */ 613 if (MoveContext != NULL) 614 { 615 DirContext.DirEntry.Fat.CreationDate = MoveContext->CreationDate; 616 DirContext.DirEntry.Fat.CreationTime = MoveContext->CreationTime; 617 DirContext.DirEntry.Fat.FileSize = MoveContext->FileSize; 618 } 619 620 if (needLong) 621 { 622 /* calculate checksum for 8.3 name */ 623 for (pSlots[0].alias_checksum = 0, i = 0; i < 11; i++) 624 { 625 pSlots[0].alias_checksum = (((pSlots[0].alias_checksum & 1) << 7 626 | ((pSlots[0].alias_checksum & 0xfe) >> 1)) 627 + DirContext.DirEntry.Fat.ShortName[i]); 628 } 629 /* construct slots and entry */ 630 for (i = nbSlots - 2; i >= 0; i--) 631 { 632 DPRINT("construct slot %d\n", i); 633 pSlots[i].attr = 0xf; 634 if (i) 635 { 636 pSlots[i].id = (unsigned char)(nbSlots - i - 1); 637 } 638 else 639 { 640 pSlots[i].id = (unsigned char)(nbSlots - i - 1 + 0x40); 641 } 642 pSlots[i].alias_checksum = pSlots[0].alias_checksum; 643 RtlCopyMemory(pSlots[i].name0_4, DirContext.LongNameU.Buffer + (nbSlots - i - 2) * 13, 10); 644 RtlCopyMemory(pSlots[i].name5_10, DirContext.LongNameU.Buffer + (nbSlots - i - 2) * 13 + 5, 12); 645 RtlCopyMemory(pSlots[i].name11_12, DirContext.LongNameU.Buffer + (nbSlots - i - 2) * 13 + 11, 4); 646 } 647 } 648 /* try to find nbSlots contiguous entries frees in directory */ 649 if (!vfatFindDirSpace(DeviceExt, ParentFcb, nbSlots, &DirContext.StartIndex)) 650 { 651 ExFreePoolWithTag(Buffer, TAG_DIRENT); 652 return STATUS_DISK_FULL; 653 } 654 DirContext.DirIndex = DirContext.StartIndex + nbSlots - 1; 655 if (IsDirectory) 656 { 657 /* If we aren't moving, use next */ 658 if (MoveContext == NULL) 659 { 660 CurrentCluster = 0; 661 Status = NextCluster(DeviceExt, 0, &CurrentCluster, TRUE); 662 if (CurrentCluster == 0xffffffff || !NT_SUCCESS(Status)) 663 { 664 ExFreePoolWithTag(Buffer, TAG_DIRENT); 665 if (!NT_SUCCESS(Status)) 666 { 667 return Status; 668 } 669 return STATUS_DISK_FULL; 670 } 671 672 if (DeviceExt->FatInfo.FatType == FAT32) 673 { 674 FAT32UpdateFreeClustersCount(DeviceExt); 675 } 676 } 677 else 678 { 679 CurrentCluster = MoveContext->FirstCluster; 680 } 681 682 if (DeviceExt->FatInfo.FatType == FAT32) 683 { 684 DirContext.DirEntry.Fat.FirstClusterHigh = (unsigned short)(CurrentCluster >> 16); 685 } 686 DirContext.DirEntry.Fat.FirstCluster = (unsigned short)CurrentCluster; 687 } 688 else if (MoveContext != NULL) 689 { 690 CurrentCluster = MoveContext->FirstCluster; 691 692 if (DeviceExt->FatInfo.FatType == FAT32) 693 { 694 DirContext.DirEntry.Fat.FirstClusterHigh = (unsigned short)(CurrentCluster >> 16); 695 } 696 DirContext.DirEntry.Fat.FirstCluster = (unsigned short)CurrentCluster; 697 } 698 699 /* No need to init cache here, vfatFindDirSpace() will have done it for us */ 700 ASSERT(BooleanFlagOn(ParentFcb->Flags, FCB_CACHE_INITIALIZED)); 701 702 i = DeviceExt->FatInfo.BytesPerCluster / sizeof(FAT_DIR_ENTRY); 703 FileOffset.u.HighPart = 0; 704 FileOffset.u.LowPart = DirContext.StartIndex * sizeof(FAT_DIR_ENTRY); 705 if (DirContext.StartIndex / i == DirContext.DirIndex / i) 706 { 707 /* one cluster */ 708 _SEH2_TRY 709 { 710 CcPinRead(ParentFcb->FileObject, &FileOffset, nbSlots * sizeof(FAT_DIR_ENTRY), PIN_WAIT, &Context, (PVOID*)&pFatEntry); 711 } 712 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 713 { 714 ExFreePoolWithTag(Buffer, TAG_DIRENT); 715 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 716 } 717 _SEH2_END; 718 719 if (nbSlots > 1) 720 { 721 RtlCopyMemory(pFatEntry, Buffer, (nbSlots - 1) * sizeof(FAT_DIR_ENTRY)); 722 } 723 RtlCopyMemory(pFatEntry + (nbSlots - 1), &DirContext.DirEntry.Fat, sizeof(FAT_DIR_ENTRY)); 724 } 725 else 726 { 727 /* two clusters */ 728 size = DeviceExt->FatInfo.BytesPerCluster - 729 (DirContext.StartIndex * sizeof(FAT_DIR_ENTRY)) % DeviceExt->FatInfo.BytesPerCluster; 730 i = size / sizeof(FAT_DIR_ENTRY); 731 _SEH2_TRY 732 { 733 CcPinRead(ParentFcb->FileObject, &FileOffset, size, PIN_WAIT, &Context, (PVOID*)&pFatEntry); 734 } 735 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 736 { 737 ExFreePoolWithTag(Buffer, TAG_DIRENT); 738 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 739 } 740 _SEH2_END; 741 RtlCopyMemory(pFatEntry, Buffer, size); 742 CcSetDirtyPinnedData(Context, NULL); 743 CcUnpinData(Context); 744 FileOffset.u.LowPart += size; 745 _SEH2_TRY 746 { 747 CcPinRead(ParentFcb->FileObject, &FileOffset, nbSlots * sizeof(FAT_DIR_ENTRY) - size, PIN_WAIT, &Context, (PVOID*)&pFatEntry); 748 } 749 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 750 { 751 ExFreePoolWithTag(Buffer, TAG_DIRENT); 752 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 753 } 754 _SEH2_END; 755 if (nbSlots - 1 > i) 756 { 757 RtlCopyMemory(pFatEntry, (PVOID)(Buffer + size), (nbSlots - 1 - i) * sizeof(FAT_DIR_ENTRY)); 758 } 759 RtlCopyMemory(pFatEntry + nbSlots - 1 - i, &DirContext.DirEntry.Fat, sizeof(FAT_DIR_ENTRY)); 760 } 761 CcSetDirtyPinnedData(Context, NULL); 762 CcUnpinData(Context); 763 764 if (MoveContext != NULL) 765 { 766 /* We're modifying an existing FCB - likely rename/move */ 767 Status = vfatUpdateFCB(DeviceExt, *Fcb, &DirContext, ParentFcb); 768 } 769 else 770 { 771 Status = vfatMakeFCBFromDirEntry(DeviceExt, ParentFcb, &DirContext, Fcb); 772 } 773 if (!NT_SUCCESS(Status)) 774 { 775 ExFreePoolWithTag(Buffer, TAG_DIRENT); 776 return Status; 777 } 778 779 DPRINT("new : entry=%11.11s\n", (*Fcb)->entry.Fat.Filename); 780 DPRINT("new : entry=%11.11s\n", DirContext.DirEntry.Fat.Filename); 781 782 if (IsDirectory) 783 { 784 Status = vfatFCBInitializeCacheFromVolume(DeviceExt, (*Fcb)); 785 if (!NT_SUCCESS(Status)) 786 { 787 ExFreePoolWithTag(Buffer, TAG_DIRENT); 788 return Status; 789 } 790 791 FileOffset.QuadPart = 0; 792 _SEH2_TRY 793 { 794 CcPinRead((*Fcb)->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster, PIN_WAIT, &Context, (PVOID*)&pFatEntry); 795 } 796 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 797 { 798 ExFreePoolWithTag(Buffer, TAG_DIRENT); 799 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 800 } 801 _SEH2_END; 802 /* clear the new directory cluster if not moving */ 803 if (MoveContext == NULL) 804 { 805 RtlZeroMemory(pFatEntry, DeviceExt->FatInfo.BytesPerCluster); 806 /* create '.' and '..' */ 807 pFatEntry[0] = DirContext.DirEntry.Fat; 808 RtlCopyMemory(pFatEntry[0].ShortName, ". ", 11); 809 pFatEntry[1] = DirContext.DirEntry.Fat; 810 RtlCopyMemory(pFatEntry[1].ShortName, ".. ", 11); 811 } 812 813 pFatEntry[1].FirstCluster = ParentFcb->entry.Fat.FirstCluster; 814 pFatEntry[1].FirstClusterHigh = ParentFcb->entry.Fat.FirstClusterHigh; 815 if (vfatFCBIsRoot(ParentFcb)) 816 { 817 pFatEntry[1].FirstCluster = 0; 818 pFatEntry[1].FirstClusterHigh = 0; 819 } 820 CcSetDirtyPinnedData(Context, NULL); 821 CcUnpinData(Context); 822 } 823 ExFreePoolWithTag(Buffer, TAG_DIRENT); 824 DPRINT("addentry ok\n"); 825 return STATUS_SUCCESS; 826 } 827 828 /* 829 create a new FAT entry 830 */ 831 static NTSTATUS 832 FATXAddEntry( 833 IN PDEVICE_EXTENSION DeviceExt, 834 IN PUNICODE_STRING NameU, 835 IN PVFATFCB* Fcb, 836 IN PVFATFCB ParentFcb, 837 IN ULONG RequestedOptions, 838 IN UCHAR ReqAttr, 839 IN PVFAT_MOVE_CONTEXT MoveContext) 840 { 841 PVOID Context = NULL; 842 LARGE_INTEGER SystemTime, FileOffset; 843 OEM_STRING NameA; 844 VFAT_DIRENTRY_CONTEXT DirContext; 845 PFATX_DIR_ENTRY pFatXDirEntry; 846 ULONG Index; 847 848 DPRINT("addEntry: Name='%wZ', Dir='%wZ'\n", NameU, &ParentFcb->PathNameU); 849 850 DirContext.LongNameU = *NameU; 851 852 if (DirContext.LongNameU.Length / sizeof(WCHAR) > 42) 853 { 854 /* name too long */ 855 return STATUS_NAME_TOO_LONG; 856 } 857 858 /* try to find 1 entry free in directory */ 859 if (!vfatFindDirSpace(DeviceExt, ParentFcb, 1, &DirContext.StartIndex)) 860 { 861 return STATUS_DISK_FULL; 862 } 863 Index = DirContext.DirIndex = DirContext.StartIndex; 864 if (!vfatFCBIsRoot(ParentFcb)) 865 { 866 DirContext.DirIndex += 2; 867 DirContext.StartIndex += 2; 868 } 869 870 DirContext.ShortNameU.Buffer = 0; 871 DirContext.ShortNameU.Length = 0; 872 DirContext.ShortNameU.MaximumLength = 0; 873 DirContext.DeviceExt = DeviceExt; 874 RtlZeroMemory(&DirContext.DirEntry.FatX, sizeof(FATX_DIR_ENTRY)); 875 memset(DirContext.DirEntry.FatX.Filename, 0xff, 42); 876 /* Use cluster, if moving */ 877 if (MoveContext != NULL) 878 { 879 DirContext.DirEntry.FatX.FirstCluster = MoveContext->FirstCluster; 880 } 881 else 882 { 883 DirContext.DirEntry.FatX.FirstCluster = 0; 884 } 885 DirContext.DirEntry.FatX.FileSize = 0; 886 887 /* set file name */ 888 NameA.Buffer = (PCHAR)DirContext.DirEntry.FatX.Filename; 889 NameA.Length = 0; 890 NameA.MaximumLength = 42; 891 RtlUnicodeStringToOemString(&NameA, &DirContext.LongNameU, FALSE); 892 DirContext.DirEntry.FatX.FilenameLength = (unsigned char)NameA.Length; 893 894 /* set attributes */ 895 DirContext.DirEntry.FatX.Attrib = ReqAttr; 896 if (BooleanFlagOn(RequestedOptions, FILE_DIRECTORY_FILE)) 897 { 898 DirContext.DirEntry.FatX.Attrib |= FILE_ATTRIBUTE_DIRECTORY; 899 } 900 901 /* set dates and times */ 902 KeQuerySystemTime(&SystemTime); 903 FsdSystemTimeToDosDateTime(DeviceExt, &SystemTime, &DirContext.DirEntry.FatX.CreationDate, 904 &DirContext.DirEntry.FatX.CreationTime); 905 DirContext.DirEntry.FatX.UpdateDate = DirContext.DirEntry.FatX.CreationDate; 906 DirContext.DirEntry.FatX.UpdateTime = DirContext.DirEntry.FatX.CreationTime; 907 DirContext.DirEntry.FatX.AccessDate = DirContext.DirEntry.FatX.CreationDate; 908 DirContext.DirEntry.FatX.AccessTime = DirContext.DirEntry.FatX.CreationTime; 909 /* If it's moving, preserve creation time and file size */ 910 if (MoveContext != NULL) 911 { 912 DirContext.DirEntry.FatX.CreationDate = MoveContext->CreationDate; 913 DirContext.DirEntry.FatX.CreationTime = MoveContext->CreationTime; 914 DirContext.DirEntry.FatX.FileSize = MoveContext->FileSize; 915 } 916 917 /* No need to init cache here, vfatFindDirSpace() will have done it for us */ 918 ASSERT(BooleanFlagOn(ParentFcb->Flags, FCB_CACHE_INITIALIZED)); 919 920 /* add entry into parent directory */ 921 FileOffset.u.HighPart = 0; 922 FileOffset.u.LowPart = Index * sizeof(FATX_DIR_ENTRY); 923 _SEH2_TRY 924 { 925 CcPinRead(ParentFcb->FileObject, &FileOffset, sizeof(FATX_DIR_ENTRY), PIN_WAIT, &Context, (PVOID*)&pFatXDirEntry); 926 } 927 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 928 { 929 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 930 } 931 _SEH2_END; 932 RtlCopyMemory(pFatXDirEntry, &DirContext.DirEntry.FatX, sizeof(FATX_DIR_ENTRY)); 933 CcSetDirtyPinnedData(Context, NULL); 934 CcUnpinData(Context); 935 936 if (MoveContext != NULL) 937 { 938 /* We're modifying an existing FCB - likely rename/move */ 939 /* FIXME: check status */ 940 vfatUpdateFCB(DeviceExt, *Fcb, &DirContext, ParentFcb); 941 } 942 else 943 { 944 /* FIXME: check status */ 945 vfatMakeFCBFromDirEntry(DeviceExt, ParentFcb, &DirContext, Fcb); 946 } 947 948 DPRINT("addentry ok\n"); 949 return STATUS_SUCCESS; 950 } 951 952 /* 953 * deleting an existing FAT entry 954 */ 955 static NTSTATUS 956 FATDelEntry( 957 IN PDEVICE_EXTENSION DeviceExt, 958 IN PVFATFCB pFcb, 959 OUT PVFAT_MOVE_CONTEXT MoveContext) 960 { 961 ULONG CurrentCluster = 0, NextCluster, i; 962 PVOID Context = NULL; 963 LARGE_INTEGER Offset; 964 PFAT_DIR_ENTRY pDirEntry = NULL; 965 NTSTATUS Status; 966 967 ASSERT(pFcb); 968 ASSERT(pFcb->parentFcb); 969 970 Status = vfatFCBInitializeCacheFromVolume(DeviceExt, pFcb->parentFcb); 971 if (!NT_SUCCESS(Status)) 972 { 973 return Status; 974 } 975 976 DPRINT("delEntry PathName \'%wZ\'\n", &pFcb->PathNameU); 977 DPRINT("delete entry: %u to %u\n", pFcb->startIndex, pFcb->dirIndex); 978 Offset.u.HighPart = 0; 979 for (i = pFcb->startIndex; i <= pFcb->dirIndex; i++) 980 { 981 if (Context == NULL || ((i * sizeof(FAT_DIR_ENTRY)) % PAGE_SIZE) == 0) 982 { 983 if (Context) 984 { 985 CcSetDirtyPinnedData(Context, NULL); 986 CcUnpinData(Context); 987 } 988 Offset.u.LowPart = (i * sizeof(FAT_DIR_ENTRY) / PAGE_SIZE) * PAGE_SIZE; 989 _SEH2_TRY 990 { 991 CcPinRead(pFcb->parentFcb->FileObject, &Offset, PAGE_SIZE, PIN_WAIT, &Context, (PVOID*)&pDirEntry); 992 } 993 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 994 { 995 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 996 } 997 _SEH2_END; 998 } 999 pDirEntry[i % (PAGE_SIZE / sizeof(FAT_DIR_ENTRY))].Filename[0] = 0xe5; 1000 if (i == pFcb->dirIndex) 1001 { 1002 CurrentCluster = 1003 vfatDirEntryGetFirstCluster(DeviceExt, 1004 (PDIR_ENTRY)&pDirEntry[i % (PAGE_SIZE / sizeof(FAT_DIR_ENTRY))]); 1005 } 1006 } 1007 1008 /* In case of moving, save properties */ 1009 if (MoveContext != NULL) 1010 { 1011 pDirEntry = &pDirEntry[pFcb->dirIndex % (PAGE_SIZE / sizeof(FAT_DIR_ENTRY))]; 1012 MoveContext->FirstCluster = CurrentCluster; 1013 MoveContext->FileSize = pDirEntry->FileSize; 1014 MoveContext->CreationTime = pDirEntry->CreationTime; 1015 MoveContext->CreationDate = pDirEntry->CreationDate; 1016 } 1017 1018 if (Context) 1019 { 1020 CcSetDirtyPinnedData(Context, NULL); 1021 CcUnpinData(Context); 1022 } 1023 1024 /* In case of moving, don't delete data */ 1025 if (MoveContext == NULL) 1026 { 1027 while (CurrentCluster && CurrentCluster != 0xffffffff) 1028 { 1029 GetNextCluster(DeviceExt, CurrentCluster, &NextCluster); 1030 /* FIXME: check status */ 1031 WriteCluster(DeviceExt, CurrentCluster, 0); 1032 CurrentCluster = NextCluster; 1033 } 1034 1035 if (DeviceExt->FatInfo.FatType == FAT32) 1036 { 1037 FAT32UpdateFreeClustersCount(DeviceExt); 1038 } 1039 } 1040 1041 return STATUS_SUCCESS; 1042 } 1043 1044 /* 1045 * deleting an existing FAT entry 1046 */ 1047 static NTSTATUS 1048 FATXDelEntry( 1049 IN PDEVICE_EXTENSION DeviceExt, 1050 IN PVFATFCB pFcb, 1051 OUT PVFAT_MOVE_CONTEXT MoveContext) 1052 { 1053 ULONG CurrentCluster = 0, NextCluster; 1054 PVOID Context = NULL; 1055 LARGE_INTEGER Offset; 1056 PFATX_DIR_ENTRY pDirEntry; 1057 ULONG StartIndex; 1058 NTSTATUS Status; 1059 1060 ASSERT(pFcb); 1061 ASSERT(pFcb->parentFcb); 1062 ASSERT(vfatVolumeIsFatX(DeviceExt)); 1063 1064 StartIndex = pFcb->startIndex; 1065 1066 Status = vfatFCBInitializeCacheFromVolume(DeviceExt, pFcb->parentFcb); 1067 if (!NT_SUCCESS(Status)) 1068 { 1069 return Status; 1070 } 1071 1072 DPRINT("delEntry PathName \'%wZ\'\n", &pFcb->PathNameU); 1073 DPRINT("delete entry: %u\n", StartIndex); 1074 Offset.u.HighPart = 0; 1075 Offset.u.LowPart = (StartIndex * sizeof(FATX_DIR_ENTRY) / PAGE_SIZE) * PAGE_SIZE; 1076 _SEH2_TRY 1077 { 1078 CcPinRead(pFcb->parentFcb->FileObject, &Offset, PAGE_SIZE, PIN_WAIT, &Context, (PVOID*)&pDirEntry); 1079 } 1080 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 1081 { 1082 DPRINT1("CcPinRead(Offset %x:%x, Length %d) failed\n", Offset.u.HighPart, Offset.u.LowPart, PAGE_SIZE); 1083 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 1084 } 1085 _SEH2_END; 1086 pDirEntry = &pDirEntry[StartIndex % (PAGE_SIZE / sizeof(FATX_DIR_ENTRY))]; 1087 pDirEntry->FilenameLength = 0xe5; 1088 CurrentCluster = vfatDirEntryGetFirstCluster(DeviceExt, 1089 (PDIR_ENTRY)pDirEntry); 1090 1091 /* In case of moving, save properties */ 1092 if (MoveContext != NULL) 1093 { 1094 MoveContext->FirstCluster = CurrentCluster; 1095 MoveContext->FileSize = pDirEntry->FileSize; 1096 MoveContext->CreationTime = pDirEntry->CreationTime; 1097 MoveContext->CreationDate = pDirEntry->CreationDate; 1098 } 1099 1100 CcSetDirtyPinnedData(Context, NULL); 1101 CcUnpinData(Context); 1102 1103 /* In case of moving, don't delete data */ 1104 if (MoveContext == NULL) 1105 { 1106 while (CurrentCluster && CurrentCluster != 0xffffffff) 1107 { 1108 GetNextCluster(DeviceExt, CurrentCluster, &NextCluster); 1109 /* FIXME: check status */ 1110 WriteCluster(DeviceExt, CurrentCluster, 0); 1111 CurrentCluster = NextCluster; 1112 } 1113 } 1114 1115 return STATUS_SUCCESS; 1116 } 1117 1118 /* 1119 * move an existing FAT entry 1120 */ 1121 NTSTATUS 1122 VfatMoveEntry( 1123 IN PDEVICE_EXTENSION DeviceExt, 1124 IN PVFATFCB pFcb, 1125 IN PUNICODE_STRING FileName, 1126 IN PVFATFCB ParentFcb) 1127 { 1128 NTSTATUS Status; 1129 PVFATFCB OldParent; 1130 VFAT_MOVE_CONTEXT MoveContext; 1131 1132 DPRINT("VfatMoveEntry(%p, %p, %wZ, %p)\n", DeviceExt, pFcb, FileName, ParentFcb); 1133 1134 /* Delete old entry while keeping data */ 1135 Status = VfatDelEntry(DeviceExt, pFcb, &MoveContext); 1136 if (!NT_SUCCESS(Status)) 1137 { 1138 return Status; 1139 } 1140 1141 OldParent = pFcb->parentFcb; 1142 CcFlushCache(&OldParent->SectionObjectPointers, NULL, 0, NULL); 1143 MoveContext.InPlace = (OldParent == ParentFcb); 1144 1145 /* Add our new entry with our cluster */ 1146 Status = VfatAddEntry(DeviceExt, 1147 FileName, 1148 &pFcb, 1149 ParentFcb, 1150 (vfatFCBIsDirectory(pFcb) ? FILE_DIRECTORY_FILE : 0), 1151 *pFcb->Attributes, 1152 &MoveContext); 1153 1154 CcFlushCache(&pFcb->parentFcb->SectionObjectPointers, NULL, 0, NULL); 1155 1156 return Status; 1157 } 1158 1159 extern BOOLEAN FATXIsDirectoryEmpty(PDEVICE_EXTENSION DeviceExt, PVFATFCB Fcb); 1160 extern BOOLEAN FATIsDirectoryEmpty(PDEVICE_EXTENSION DeviceExt, PVFATFCB Fcb); 1161 extern NTSTATUS FATGetNextDirEntry(PVOID *pContext, PVOID *pPage, PVFATFCB pDirFcb, PVFAT_DIRENTRY_CONTEXT DirContext, BOOLEAN First); 1162 extern NTSTATUS FATXGetNextDirEntry(PVOID *pContext, PVOID *pPage, PVFATFCB pDirFcb, PVFAT_DIRENTRY_CONTEXT DirContext, BOOLEAN First); 1163 1164 VFAT_DISPATCH FatXDispatch = { 1165 FATXIsDirectoryEmpty, // .IsDirectoryEmpty 1166 FATXAddEntry, // .AddEntry 1167 FATXDelEntry, // .DelEntry 1168 FATXGetNextDirEntry, // .GetNextDirEntry 1169 }; 1170 1171 VFAT_DISPATCH FatDispatch = { 1172 FATIsDirectoryEmpty, // .IsDirectoryEmpty 1173 FATAddEntry, // .AddEntry 1174 FATDelEntry, // .DelEntry 1175 FATGetNextDirEntry, // .GetNextDirEntry 1176 }; 1177 1178 /* EOF */ 1179