1 /*
2 * PROJECT: VFAT Filesystem
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Routines to manipulate directory entries
5 * COPYRIGHT: Copyright 1998 Jason Filby <jasonfilby@yahoo.com>
6 * Copyright 2001 Rex Jolliff <rex@lvcablemodem.com>
7 * Copyright 2004-2022 Hervé Poussineau <hpoussin@reactos.org>
8 */
9
10 /* ------------------------------------------------------- INCLUDES */
11
12 #include "vfat.h"
13
14 #define NDEBUG
15 #include <debug.h>
16
17 ULONG
vfatDirEntryGetFirstCluster(PDEVICE_EXTENSION pDeviceExt,PDIR_ENTRY pFatDirEntry)18 vfatDirEntryGetFirstCluster(
19 PDEVICE_EXTENSION pDeviceExt,
20 PDIR_ENTRY pFatDirEntry)
21 {
22 ULONG cluster;
23
24 if (pDeviceExt->FatInfo.FatType == FAT32)
25 {
26 cluster = pFatDirEntry->Fat.FirstCluster |
27 (pFatDirEntry->Fat.FirstClusterHigh << 16);
28 }
29 else if (vfatVolumeIsFatX(pDeviceExt))
30 {
31 cluster = pFatDirEntry->FatX.FirstCluster;
32 }
33 else
34 {
35 cluster = pFatDirEntry->Fat.FirstCluster;
36 }
37
38 return cluster;
39 }
40
41 BOOLEAN
FATIsDirectoryEmpty(PDEVICE_EXTENSION DeviceExt,PVFATFCB Fcb)42 FATIsDirectoryEmpty(
43 PDEVICE_EXTENSION DeviceExt,
44 PVFATFCB Fcb)
45 {
46 LARGE_INTEGER FileOffset;
47 PVOID Context = NULL;
48 PFAT_DIR_ENTRY FatDirEntry;
49 ULONG Index, MaxIndex;
50 NTSTATUS Status;
51
52 if (vfatFCBIsRoot(Fcb))
53 {
54 Index = 0;
55 }
56 else
57 {
58 Index = 2;
59 }
60
61 FileOffset.QuadPart = 0;
62 MaxIndex = Fcb->RFCB.FileSize.u.LowPart / sizeof(FAT_DIR_ENTRY);
63
64 Status = vfatFCBInitializeCacheFromVolume(DeviceExt, Fcb);
65 if (!NT_SUCCESS(Status))
66 {
67 return FALSE;
68 }
69
70 while (Index < MaxIndex)
71 {
72 if (Context == NULL || (Index % FAT_ENTRIES_PER_PAGE) == 0)
73 {
74 if (Context != NULL)
75 {
76 CcUnpinData(Context);
77 }
78
79 _SEH2_TRY
80 {
81 CcMapData(Fcb->FileObject, &FileOffset, sizeof(FAT_DIR_ENTRY), MAP_WAIT, &Context, (PVOID*)&FatDirEntry);
82 }
83 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
84 {
85 _SEH2_YIELD(return TRUE);
86 }
87 _SEH2_END;
88
89 FatDirEntry += Index % FAT_ENTRIES_PER_PAGE;
90 FileOffset.QuadPart += PAGE_SIZE;
91 }
92
93 if (FAT_ENTRY_END(FatDirEntry))
94 {
95 CcUnpinData(Context);
96 return TRUE;
97 }
98
99 if (!FAT_ENTRY_DELETED(FatDirEntry))
100 {
101 CcUnpinData(Context);
102 return FALSE;
103 }
104
105 Index++;
106 FatDirEntry++;
107 }
108
109 if (Context)
110 {
111 CcUnpinData(Context);
112 }
113
114 return TRUE;
115 }
116
117 BOOLEAN
FATXIsDirectoryEmpty(PDEVICE_EXTENSION DeviceExt,PVFATFCB Fcb)118 FATXIsDirectoryEmpty(
119 PDEVICE_EXTENSION DeviceExt,
120 PVFATFCB Fcb)
121 {
122 LARGE_INTEGER FileOffset;
123 PVOID Context = NULL;
124 PFATX_DIR_ENTRY FatXDirEntry;
125 ULONG Index = 0, MaxIndex;
126 NTSTATUS Status;
127
128 FileOffset.QuadPart = 0;
129 MaxIndex = Fcb->RFCB.FileSize.u.LowPart / sizeof(FATX_DIR_ENTRY);
130
131 Status = vfatFCBInitializeCacheFromVolume(DeviceExt, Fcb);
132 if (!NT_SUCCESS(Status))
133 {
134 return FALSE;
135 }
136
137 while (Index < MaxIndex)
138 {
139 if (Context == NULL || (Index % FATX_ENTRIES_PER_PAGE) == 0)
140 {
141 if (Context != NULL)
142 {
143 CcUnpinData(Context);
144 }
145
146 _SEH2_TRY
147 {
148 CcMapData(Fcb->FileObject, &FileOffset, sizeof(FATX_DIR_ENTRY), MAP_WAIT, &Context, (PVOID*)&FatXDirEntry);
149 }
150 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
151 {
152 _SEH2_YIELD(return TRUE);
153 }
154 _SEH2_END;
155
156 FatXDirEntry += Index % FATX_ENTRIES_PER_PAGE;
157 FileOffset.QuadPart += PAGE_SIZE;
158 }
159
160 if (FATX_ENTRY_END(FatXDirEntry))
161 {
162 CcUnpinData(Context);
163 return TRUE;
164 }
165
166 if (!FATX_ENTRY_DELETED(FatXDirEntry))
167 {
168 CcUnpinData(Context);
169 return FALSE;
170 }
171
172 Index++;
173 FatXDirEntry++;
174 }
175
176 if (Context)
177 {
178 CcUnpinData(Context);
179 }
180
181 return TRUE;
182 }
183
184 NTSTATUS
FATGetNextDirEntry(PVOID * pContext,PVOID * pPage,IN PVFATFCB pDirFcb,PVFAT_DIRENTRY_CONTEXT DirContext,BOOLEAN First)185 FATGetNextDirEntry(
186 PVOID *pContext,
187 PVOID *pPage,
188 IN PVFATFCB pDirFcb,
189 PVFAT_DIRENTRY_CONTEXT DirContext,
190 BOOLEAN First)
191 {
192 ULONG dirMap;
193 PWCHAR pName;
194 LARGE_INTEGER FileOffset;
195 PFAT_DIR_ENTRY fatDirEntry;
196 slot * longNameEntry;
197 ULONG index;
198
199 UCHAR CheckSum, shortCheckSum;
200 USHORT i;
201 BOOLEAN Valid = TRUE;
202 BOOLEAN Back = FALSE;
203
204 NTSTATUS Status;
205
206 DirContext->LongNameU.Length = 0;
207 DirContext->LongNameU.Buffer[0] = UNICODE_NULL;
208
209 FileOffset.u.HighPart = 0;
210 FileOffset.u.LowPart = ROUND_DOWN(DirContext->DirIndex * sizeof(FAT_DIR_ENTRY), PAGE_SIZE);
211
212 Status = vfatFCBInitializeCacheFromVolume(DirContext->DeviceExt, pDirFcb);
213 if (!NT_SUCCESS(Status))
214 {
215 return Status;
216 }
217
218 if (*pContext == NULL || (DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == 0)
219 {
220 if (*pContext != NULL)
221 {
222 CcUnpinData(*pContext);
223 }
224
225 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart)
226 {
227 *pContext = NULL;
228 return STATUS_NO_MORE_ENTRIES;
229 }
230
231 _SEH2_TRY
232 {
233 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage);
234 }
235 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
236 {
237 *pContext = NULL;
238 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES);
239 }
240 _SEH2_END;
241 }
242
243 fatDirEntry = (PFAT_DIR_ENTRY)(*pPage) + DirContext->DirIndex % FAT_ENTRIES_PER_PAGE;
244 longNameEntry = (slot*) fatDirEntry;
245 dirMap = 0;
246
247 if (First)
248 {
249 /* This is the first call to vfatGetNextDirEntry. Possible the start index points
250 * into a long name or points to a short name with an assigned long name.
251 * We must go back to the real start of the entry */
252 while (DirContext->DirIndex > 0 &&
253 !FAT_ENTRY_END(fatDirEntry) &&
254 !FAT_ENTRY_DELETED(fatDirEntry) &&
255 ((!FAT_ENTRY_LONG(fatDirEntry) && !Back) ||
256 (FAT_ENTRY_LONG(fatDirEntry) && !(longNameEntry->id & 0x40))))
257 {
258 DirContext->DirIndex--;
259 Back = TRUE;
260
261 if ((DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == FAT_ENTRIES_PER_PAGE - 1)
262 {
263 CcUnpinData(*pContext);
264 FileOffset.u.LowPart -= PAGE_SIZE;
265
266 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart)
267 {
268 *pContext = NULL;
269 return STATUS_NO_MORE_ENTRIES;
270 }
271
272 _SEH2_TRY
273 {
274 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage);
275 }
276 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
277 {
278 *pContext = NULL;
279 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES);
280 }
281 _SEH2_END;
282
283 fatDirEntry = (PFAT_DIR_ENTRY)(*pPage) + DirContext->DirIndex % FAT_ENTRIES_PER_PAGE;
284 longNameEntry = (slot*) fatDirEntry;
285 }
286 else
287 {
288 fatDirEntry--;
289 longNameEntry--;
290 }
291 }
292
293 if (Back && !FAT_ENTRY_END(fatDirEntry) &&
294 (FAT_ENTRY_DELETED(fatDirEntry) || !FAT_ENTRY_LONG(fatDirEntry)))
295 {
296 DirContext->DirIndex++;
297
298 if ((DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == 0)
299 {
300 CcUnpinData(*pContext);
301 FileOffset.u.LowPart += PAGE_SIZE;
302
303 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart)
304 {
305 *pContext = NULL;
306 return STATUS_NO_MORE_ENTRIES;
307 }
308
309 _SEH2_TRY
310 {
311 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage);
312 }
313 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
314 {
315 *pContext = NULL;
316 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES);
317 }
318 _SEH2_END;
319
320 fatDirEntry = (PFAT_DIR_ENTRY)*pPage;
321 longNameEntry = (slot*) *pPage;
322 }
323 else
324 {
325 fatDirEntry++;
326 longNameEntry++;
327 }
328 }
329 }
330
331 DirContext->StartIndex = DirContext->DirIndex;
332 CheckSum = 0;
333
334 while (TRUE)
335 {
336 if (FAT_ENTRY_END(fatDirEntry))
337 {
338 CcUnpinData(*pContext);
339 *pContext = NULL;
340 return STATUS_NO_MORE_ENTRIES;
341 }
342
343 if (FAT_ENTRY_DELETED(fatDirEntry))
344 {
345 dirMap = 0;
346 DirContext->LongNameU.Buffer[0] = 0;
347 DirContext->StartIndex = DirContext->DirIndex + 1;
348 }
349 else
350 {
351 if (FAT_ENTRY_LONG(fatDirEntry))
352 {
353 if (dirMap == 0)
354 {
355 DPRINT (" long name entry found at %u\n", DirContext->DirIndex);
356 RtlZeroMemory(DirContext->LongNameU.Buffer, DirContext->LongNameU.MaximumLength);
357 CheckSum = longNameEntry->alias_checksum;
358 Valid = TRUE;
359 }
360
361 DPRINT(" name chunk1:[%.*S] chunk2:[%.*S] chunk3:[%.*S]\n",
362 5, longNameEntry->name0_4,
363 6, longNameEntry->name5_10,
364 2, longNameEntry->name11_12);
365
366 index = longNameEntry->id & 0x3f; // Note: it can be 0 for corrupted FS
367
368 /* Make sure index is valid and we have enough space in buffer
369 (we count one char for \0) */
370 if (index > 0 &&
371 index * 13 < DirContext->LongNameU.MaximumLength / sizeof(WCHAR))
372 {
373 index--; // make index 0 based
374 dirMap |= 1 << index;
375
376 pName = DirContext->LongNameU.Buffer + index * 13;
377 RtlCopyMemory(pName, longNameEntry->name0_4, 5 * sizeof(WCHAR));
378 RtlCopyMemory(pName + 5, longNameEntry->name5_10, 6 * sizeof(WCHAR));
379 RtlCopyMemory(pName + 11, longNameEntry->name11_12, 2 * sizeof(WCHAR));
380
381 if (longNameEntry->id & 0x40)
382 {
383 /* It's last LFN entry. Terminate filename with \0 */
384 pName[13] = UNICODE_NULL;
385 }
386 }
387 else
388 DPRINT1("Long name entry has invalid index: %x!\n", longNameEntry->id);
389
390 DPRINT (" longName: [%S]\n", DirContext->LongNameU.Buffer);
391
392 if (CheckSum != longNameEntry->alias_checksum)
393 {
394 DPRINT1("Found wrong alias checksum in long name entry (first %x, current %x, %S)\n",
395 CheckSum, longNameEntry->alias_checksum, DirContext->LongNameU.Buffer);
396 Valid = FALSE;
397 }
398 }
399 else
400 {
401 shortCheckSum = 0;
402 for (i = 0; i < 11; i++)
403 {
404 shortCheckSum = (((shortCheckSum & 1) << 7)
405 | ((shortCheckSum & 0xfe) >> 1))
406 + fatDirEntry->ShortName[i];
407 }
408
409 if (shortCheckSum != CheckSum && DirContext->LongNameU.Buffer[0])
410 {
411 DPRINT1("Checksum from long and short name is not equal (short: %x, long: %x, %S)\n",
412 shortCheckSum, CheckSum, DirContext->LongNameU.Buffer);
413 DirContext->LongNameU.Buffer[0] = 0;
414 }
415
416 if (Valid == FALSE)
417 {
418 DirContext->LongNameU.Buffer[0] = 0;
419 }
420
421 RtlCopyMemory (&DirContext->DirEntry.Fat, fatDirEntry, sizeof (FAT_DIR_ENTRY));
422 break;
423 }
424 }
425
426 DirContext->DirIndex++;
427
428 if ((DirContext->DirIndex % FAT_ENTRIES_PER_PAGE) == 0)
429 {
430 CcUnpinData(*pContext);
431 FileOffset.u.LowPart += PAGE_SIZE;
432
433 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart)
434 {
435 *pContext = NULL;
436 return STATUS_NO_MORE_ENTRIES;
437 }
438
439 _SEH2_TRY
440 {
441 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage);
442 }
443 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
444 {
445 *pContext = NULL;
446 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES);
447 }
448 _SEH2_END;
449
450 fatDirEntry = (PFAT_DIR_ENTRY)*pPage;
451 longNameEntry = (slot*) *pPage;
452 }
453 else
454 {
455 fatDirEntry++;
456 longNameEntry++;
457 }
458 }
459
460 /* Make sure filename is NULL terminate and calculate length */
461 DirContext->LongNameU.Buffer[DirContext->LongNameU.MaximumLength / sizeof(WCHAR) - 1]
462 = UNICODE_NULL;
463 DirContext->LongNameU.Length = (USHORT)(wcslen(DirContext->LongNameU.Buffer) * sizeof(WCHAR));
464
465 /* Init short name */
466 vfat8Dot3ToString(&DirContext->DirEntry.Fat, &DirContext->ShortNameU);
467
468 /* If we found no LFN, use short name as long */
469 if (DirContext->LongNameU.Length == 0)
470 RtlCopyUnicodeString(&DirContext->LongNameU, &DirContext->ShortNameU);
471
472 return STATUS_SUCCESS;
473 }
474
475 NTSTATUS
FATXGetNextDirEntry(PVOID * pContext,PVOID * pPage,IN PVFATFCB pDirFcb,PVFAT_DIRENTRY_CONTEXT DirContext,BOOLEAN First)476 FATXGetNextDirEntry(
477 PVOID *pContext,
478 PVOID *pPage,
479 IN PVFATFCB pDirFcb,
480 PVFAT_DIRENTRY_CONTEXT DirContext,
481 BOOLEAN First)
482 {
483 LARGE_INTEGER FileOffset;
484 PFATX_DIR_ENTRY fatxDirEntry;
485 OEM_STRING StringO;
486 ULONG DirIndex = DirContext->DirIndex;
487 NTSTATUS Status;
488
489 FileOffset.u.HighPart = 0;
490
491 UNREFERENCED_PARAMETER(First);
492
493 if (!vfatFCBIsRoot(pDirFcb))
494 {
495 /* need to add . and .. entries */
496 switch (DirContext->DirIndex)
497 {
498 case 0: /* entry . */
499 wcscpy(DirContext->LongNameU.Buffer, L".");
500 DirContext->LongNameU.Length = sizeof(WCHAR);
501 DirContext->ShortNameU = DirContext->LongNameU;
502 RtlCopyMemory(&DirContext->DirEntry.FatX, &pDirFcb->entry.FatX, sizeof(FATX_DIR_ENTRY));
503 DirContext->DirEntry.FatX.Filename[0] = '.';
504 DirContext->DirEntry.FatX.FilenameLength = 1;
505 DirContext->StartIndex = 0;
506 return STATUS_SUCCESS;
507
508 case 1: /* entry .. */
509 wcscpy(DirContext->LongNameU.Buffer, L"..");
510 DirContext->LongNameU.Length = 2 * sizeof(WCHAR);
511 DirContext->ShortNameU = DirContext->LongNameU;
512 RtlCopyMemory(&DirContext->DirEntry.FatX, &pDirFcb->entry.FatX, sizeof(FATX_DIR_ENTRY));
513 DirContext->DirEntry.FatX.Filename[0] = DirContext->DirEntry.FatX.Filename[1] = '.';
514 DirContext->DirEntry.FatX.FilenameLength = 2;
515 DirContext->StartIndex = 1;
516 return STATUS_SUCCESS;
517
518 default:
519 DirIndex -= 2;
520 }
521 }
522
523 Status = vfatFCBInitializeCacheFromVolume(DirContext->DeviceExt, pDirFcb);
524 if (!NT_SUCCESS(Status))
525 {
526 return Status;
527 }
528
529 if (*pContext == NULL || (DirIndex % FATX_ENTRIES_PER_PAGE) == 0)
530 {
531 if (*pContext != NULL)
532 {
533 CcUnpinData(*pContext);
534 }
535 FileOffset.u.LowPart = ROUND_DOWN(DirIndex * sizeof(FATX_DIR_ENTRY), PAGE_SIZE);
536 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart)
537 {
538 *pContext = NULL;
539 return STATUS_NO_MORE_ENTRIES;
540 }
541
542 _SEH2_TRY
543 {
544 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage);
545 }
546 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
547 {
548 *pContext = NULL;
549 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES);
550 }
551 _SEH2_END;
552 }
553
554 fatxDirEntry = (PFATX_DIR_ENTRY)(*pPage) + DirIndex % FATX_ENTRIES_PER_PAGE;
555
556 DirContext->StartIndex = DirContext->DirIndex;
557
558 while (TRUE)
559 {
560 if (FATX_ENTRY_END(fatxDirEntry))
561 {
562 CcUnpinData(*pContext);
563 *pContext = NULL;
564 return STATUS_NO_MORE_ENTRIES;
565 }
566
567 if (!FATX_ENTRY_DELETED(fatxDirEntry))
568 {
569 RtlCopyMemory(&DirContext->DirEntry.FatX, fatxDirEntry, sizeof(FATX_DIR_ENTRY));
570 break;
571 }
572 DirContext->DirIndex++;
573 DirContext->StartIndex++;
574 DirIndex++;
575 if ((DirIndex % FATX_ENTRIES_PER_PAGE) == 0)
576 {
577 CcUnpinData(*pContext);
578 FileOffset.u.LowPart += PAGE_SIZE;
579 if (FileOffset.u.LowPart >= pDirFcb->RFCB.FileSize.u.LowPart)
580 {
581 *pContext = NULL;
582 return STATUS_NO_MORE_ENTRIES;
583 }
584
585 _SEH2_TRY
586 {
587 CcMapData(pDirFcb->FileObject, &FileOffset, PAGE_SIZE, MAP_WAIT, pContext, pPage);
588 }
589 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
590 {
591 *pContext = NULL;
592 _SEH2_YIELD(return STATUS_NO_MORE_ENTRIES);
593 }
594 _SEH2_END;
595
596 fatxDirEntry = (PFATX_DIR_ENTRY)*pPage;
597 }
598 else
599 {
600 fatxDirEntry++;
601 }
602 }
603 StringO.Buffer = (PCHAR)fatxDirEntry->Filename;
604 StringO.Length = StringO.MaximumLength = fatxDirEntry->FilenameLength;
605 RtlOemStringToUnicodeString(&DirContext->LongNameU, &StringO, FALSE);
606 DirContext->ShortNameU = DirContext->LongNameU;
607 return STATUS_SUCCESS;
608 }
609