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
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
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
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
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 = 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
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