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