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