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