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