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