fa54c38d2d3f309ba128381765ba77450ee06279
[reactos.git] / drivers / fs / cdfs / fcb.c
1 /*
2  *  ReactOS kernel
3  *  Copyright (C) 2002 ReactOS Team
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19 /* $Id$
20  *
21  * COPYRIGHT:        See COPYING in the top level directory
22  * PROJECT:          ReactOS kernel
23  * FILE:             services/fs/cdfs/fcb.c
24  * PURPOSE:          CDROM (ISO 9660) filesystem driver
25  * PROGRAMMER:       Art Yerkes
26  * UPDATE HISTORY: 
27  */
28
29 /* INCLUDES *****************************************************************/
30
31 #include <ddk/ntddk.h>
32
33 #define NDEBUG
34 #include <debug.h>
35
36 #include "cdfs.h"
37
38
39 /* MACROS *******************************************************************/
40
41 #define TAG(A, B, C, D) (ULONG)(((A)<<0) + ((B)<<8) + ((C)<<16) + ((D)<<24))
42 #define TAG_FCB TAG('I', 'F', 'C', 'B')
43
44 #define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))
45
46
47 /* FUNCTIONS ****************************************************************/
48
49 static PWCHAR
50 CdfsGetNextPathElement(PWCHAR FileName)
51 {
52   if (*FileName == L'\0')
53     {
54       return(NULL);
55     }
56
57   while (*FileName != L'\0' && *FileName != L'\\')
58     {
59       FileName++;
60     }
61
62   return(FileName);
63 }
64
65
66 static VOID
67 CdfsWSubString(PWCHAR pTarget, const PWCHAR pSource, size_t pLength)
68 {
69   wcsncpy (pTarget, pSource, pLength);
70   pTarget [pLength] = L'\0';
71 }
72
73
74 PFCB
75 CdfsCreateFCB(PWSTR FileName)
76 {
77   PFCB Fcb;
78
79   Fcb = ExAllocatePoolWithTag(NonPagedPool, sizeof(FCB), TAG_FCB);
80   RtlZeroMemory(Fcb, sizeof(FCB));
81
82   if (FileName)
83     {
84       wcscpy(Fcb->PathName, FileName);
85       if (wcsrchr(Fcb->PathName, '\\') != 0)
86         {
87           Fcb->ObjectName = wcsrchr(Fcb->PathName, '\\');
88         }
89       else
90         {
91           Fcb->ObjectName = Fcb->PathName;
92         }
93     }
94
95   ExInitializeResourceLite(&Fcb->MainResource);
96
97   return(Fcb);
98 }
99
100
101 VOID
102 CdfsDestroyFCB(PFCB Fcb)
103 {
104   ExDeleteResourceLite(&Fcb->MainResource);
105
106   ExFreePool(Fcb);
107 }
108
109
110 BOOLEAN
111 CdfsFCBIsDirectory(PFCB Fcb)
112 {
113 //  return(Fcb->entry.Attrib & FILE_ATTRIBUTE_DIRECTORY);
114   return(Fcb->Entry.FileFlags & 0x02);
115 }
116
117
118 BOOLEAN
119 CdfsFCBIsRoot(PFCB Fcb)
120 {
121   return(wcscmp(Fcb->PathName, L"\\") == 0);
122 }
123
124
125 VOID
126 CdfsGrabFCB(PDEVICE_EXTENSION Vcb,
127             PFCB Fcb)
128 {
129   KIRQL  oldIrql;
130
131   DPRINT("grabbing FCB at %x: %S, refCount:%d\n",
132          Fcb,
133          Fcb->PathName,
134          Fcb->RefCount);
135
136   KeAcquireSpinLock(&Vcb->FcbListLock, &oldIrql);
137   Fcb->RefCount++;
138   KeReleaseSpinLock(&Vcb->FcbListLock, oldIrql);
139 }
140
141
142 VOID
143 CdfsReleaseFCB(PDEVICE_EXTENSION Vcb,
144                PFCB Fcb)
145 {
146   KIRQL  oldIrql;
147
148   DPRINT("releasing FCB at %x: %S, refCount:%d\n",
149          Fcb,
150          Fcb->PathName,
151          Fcb->RefCount);
152
153   KeAcquireSpinLock(&Vcb->FcbListLock, &oldIrql);
154   Fcb->RefCount--;
155   if (Fcb->RefCount <= 0 && !CdfsFCBIsDirectory(Fcb))
156     {
157       RemoveEntryList(&Fcb->FcbListEntry);
158       CdfsDestroyFCB(Fcb);
159     }
160   KeReleaseSpinLock(&Vcb->FcbListLock, oldIrql);
161 }
162
163
164 VOID
165 CdfsAddFCBToTable(PDEVICE_EXTENSION Vcb,
166                   PFCB Fcb)
167 {
168   KIRQL  oldIrql;
169
170   KeAcquireSpinLock(&Vcb->FcbListLock, &oldIrql);
171   Fcb->DevExt = Vcb;
172   InsertTailList(&Vcb->FcbListHead, &Fcb->FcbListEntry);
173   KeReleaseSpinLock(&Vcb->FcbListLock, oldIrql);
174 }
175
176
177 PFCB
178 CdfsGrabFCBFromTable(PDEVICE_EXTENSION Vcb,
179                      PWSTR FileName)
180 {
181   KIRQL  oldIrql;
182   PFCB Fcb;
183   PLIST_ENTRY  current_entry;
184
185   KeAcquireSpinLock(&Vcb->FcbListLock, &oldIrql);
186
187   if (FileName == NULL || *FileName == 0)
188     {
189       DPRINT("Return FCB for stream file object\n");
190       Fcb = ((PCCB)Vcb->StreamFileObject->FsContext2)->Fcb;
191       Fcb->RefCount++;
192       KeReleaseSpinLock(&Vcb->FcbListLock, oldIrql);
193       return(Fcb);
194     }
195
196   current_entry = Vcb->FcbListHead.Flink;
197   while (current_entry != &Vcb->FcbListHead)
198     {
199       Fcb = CONTAINING_RECORD(current_entry, FCB, FcbListEntry);
200
201       DPRINT("Comparing '%S' and '%S'\n", FileName, Fcb->PathName);
202       if (_wcsicmp(FileName, Fcb->PathName) == 0)
203         {
204           Fcb->RefCount++;
205           KeReleaseSpinLock(&Vcb->FcbListLock, oldIrql);
206           return(Fcb);
207         }
208
209       //FIXME: need to compare against short name in FCB here
210
211       current_entry = current_entry->Flink;
212     }
213   KeReleaseSpinLock(&Vcb->FcbListLock, oldIrql);
214
215   return(NULL);
216 }
217
218
219 NTSTATUS
220 CdfsFCBInitializeCache(PVCB Vcb,
221                        PFCB Fcb)
222 {
223   PFILE_OBJECT FileObject;
224   NTSTATUS Status;
225   PCCB  newCCB;
226
227   FileObject = IoCreateStreamFileObject(NULL, Vcb->StorageDevice);
228
229   newCCB = ExAllocatePoolWithTag(NonPagedPool, sizeof(CCB), TAG_CCB);
230   if (newCCB == NULL)
231     {
232       return(STATUS_INSUFFICIENT_RESOURCES);
233     }
234   RtlZeroMemory(newCCB,
235                 sizeof(CCB));
236
237   FileObject->Flags = FileObject->Flags | FO_FCB_IS_VALID |
238       FO_DIRECT_CACHE_PAGING_READ;
239   FileObject->SectionObjectPointers = &Fcb->SectionObjectPointers;
240   FileObject->FsContext = (PVOID) &Fcb->RFCB;
241   FileObject->FsContext2 = newCCB;
242   newCCB->Fcb = Fcb;
243   newCCB->PtrFileObject = FileObject;
244   Fcb->FileObject = FileObject;
245   Fcb->DevExt = Vcb;
246
247   Status = CcRosInitializeFileCache(FileObject,
248                                     &Fcb->RFCB.Bcb,
249                                     PAGE_SIZE);
250   if (!NT_SUCCESS(Status))
251     {
252       DbgPrint("CcRosInitializeFileCache failed\n");
253       KeBugCheck(0);
254     }
255
256   ObDereferenceObject(FileObject);
257   Fcb->Flags |= FCB_CACHE_INITIALIZED;
258
259   return(Status);
260 }
261
262
263 PFCB
264 CdfsMakeRootFCB(PDEVICE_EXTENSION Vcb)
265 {
266   PFCB Fcb;
267
268   Fcb = CdfsCreateFCB(L"\\");
269
270   Fcb->Entry.DataLengthL = Vcb->CdInfo.RootSize;
271   Fcb->Entry.ExtentLocationL = Vcb->CdInfo.RootStart;
272   Fcb->Entry.FileFlags = 0x02; // FILE_ATTRIBUTE_DIRECTORY;
273   Fcb->RefCount = 1;
274   Fcb->DirIndex = 0;
275   Fcb->RFCB.FileSize.QuadPart = Vcb->CdInfo.RootSize;
276   Fcb->RFCB.ValidDataLength.QuadPart = Vcb->CdInfo.RootSize;
277   Fcb->RFCB.AllocationSize.QuadPart = Vcb->CdInfo.RootSize;
278
279   CdfsFCBInitializeCache(Vcb, Fcb);
280   CdfsAddFCBToTable(Vcb, Fcb);
281   CdfsGrabFCB(Vcb, Fcb);
282
283   return(Fcb);
284 }
285
286
287 PFCB
288 CdfsOpenRootFCB(PDEVICE_EXTENSION Vcb)
289 {
290   PFCB Fcb;
291
292   Fcb = CdfsGrabFCBFromTable(Vcb, L"\\");
293   if (Fcb == NULL)
294     {
295       Fcb = CdfsMakeRootFCB(Vcb);
296     }
297
298   return(Fcb);
299 }
300
301
302 static VOID
303 CdfsGetDirEntryName(PDEVICE_EXTENSION DeviceExt,
304                     PDIR_RECORD Record,
305                     PWSTR Name)
306 /*
307  * FUNCTION: Retrieves the file name from a directory record.
308  */
309 {
310   if (Record->FileIdLength == 1 && Record->FileId[0] == 0)
311     {
312       wcscpy(Name, L".");
313     }
314   else if (Record->FileIdLength == 1 && Record->FileId[0] == 1)
315     {
316       wcscpy(Name, L"..");
317     }
318   else
319     {
320       if (DeviceExt->CdInfo.JolietLevel == 0)
321         {
322           ULONG i;
323
324           for (i = 0; i < Record->FileIdLength && Record->FileId[i] != ';'; i++)
325             Name[i] = (WCHAR)Record->FileId[i];
326           Name[i] = 0;
327         }
328       else
329         {
330           CdfsSwapString(Name,
331                          Record->FileId,
332                          Record->FileIdLength);
333         }
334     }
335
336   DPRINT("Name '%S'\n", Name);
337 }
338
339
340 NTSTATUS
341 CdfsMakeFCBFromDirEntry(PVCB Vcb,
342                         PFCB DirectoryFCB,
343                         PWSTR LongName,
344                         PWSTR ShortName,
345                         PDIR_RECORD Record,
346                         PFCB * fileFCB)
347 {
348   WCHAR pathName[MAX_PATH];
349   PFCB rcFCB;
350   ULONG Size;
351
352   if (LongName [0] != 0 && wcslen (DirectoryFCB->PathName) +
353         sizeof(WCHAR) + wcslen (LongName) > MAX_PATH)
354     {
355       return(STATUS_OBJECT_NAME_INVALID);
356     }
357
358   wcscpy(pathName, DirectoryFCB->PathName);
359   if (!CdfsFCBIsRoot(DirectoryFCB))
360     {
361       wcscat(pathName, L"\\");
362     }
363
364   if (LongName[0] != 0)
365     {
366       wcscat(pathName, LongName);
367     }
368   else
369     {
370       WCHAR entryName[MAX_PATH];
371
372       CdfsGetDirEntryName(Vcb, Record, entryName);
373       wcscat(pathName, entryName);
374     }
375
376   rcFCB = CdfsCreateFCB(pathName);
377   memcpy(&rcFCB->Entry, Record, sizeof(DIR_RECORD));
378
379   /* Copy short name into FCB */
380   rcFCB->ShortNameLength = wcslen(ShortName) * sizeof(WCHAR);
381   wcscpy(rcFCB->ShortName, ShortName);
382
383   Size = rcFCB->Entry.DataLengthL;
384
385   rcFCB->RFCB.FileSize.QuadPart = Size;
386   rcFCB->RFCB.ValidDataLength.QuadPart = Size;
387   rcFCB->RFCB.AllocationSize.QuadPart = ROUND_UP(Size, BLOCKSIZE);
388   if (CdfsFCBIsDirectory(rcFCB))
389   {
390      CdfsFCBInitializeCache(Vcb, rcFCB);
391   }
392   rcFCB->RefCount++;
393   CdfsAddFCBToTable(Vcb, rcFCB);
394   *fileFCB = rcFCB;
395
396   DPRINT("%S %d %I64d\n", LongName, Size, rcFCB->RFCB.AllocationSize.QuadPart);
397
398   return(STATUS_SUCCESS);
399 }
400
401
402 NTSTATUS
403 CdfsAttachFCBToFileObject(PDEVICE_EXTENSION Vcb,
404                           PFCB Fcb,
405                           PFILE_OBJECT FileObject)
406 {
407   NTSTATUS Status;
408   PCCB  newCCB;
409
410   newCCB = ExAllocatePoolWithTag(NonPagedPool, sizeof(CCB), TAG_CCB);
411   if (newCCB == NULL)
412     {
413       return(STATUS_INSUFFICIENT_RESOURCES);
414     }
415   memset(newCCB, 0, sizeof(CCB));
416
417   FileObject->Flags = FileObject->Flags | FO_FCB_IS_VALID |
418       FO_DIRECT_CACHE_PAGING_READ;
419   FileObject->SectionObjectPointers = &Fcb->SectionObjectPointers;
420   FileObject->FsContext = (PVOID)&Fcb->RFCB;
421   FileObject->FsContext2 = newCCB;
422   newCCB->Fcb = Fcb;
423   newCCB->PtrFileObject = FileObject;
424   Fcb->DevExt = Vcb;
425
426   if (CdfsFCBIsDirectory(Fcb))
427     {
428       Status = CcRosInitializeFileCache(FileObject,
429                                         &Fcb->RFCB.Bcb,
430                                         PAGE_SIZE);
431       if (!NT_SUCCESS(Status))
432         {
433           DbgPrint("CcRosInitializeFileCache failed\n");
434           KeBugCheck(0);
435         }
436       Fcb->Flags |= FCB_CACHE_INITIALIZED;
437     }
438
439   DPRINT("file open: fcb:%x file size: %d\n", Fcb, Fcb->Entry.DataLengthL);
440
441   return(STATUS_SUCCESS);
442 }
443
444
445 NTSTATUS
446 CdfsDirFindFile(PDEVICE_EXTENSION DeviceExt,
447                 PFCB DirectoryFcb,
448                 PWSTR FileToFind,
449                 PFCB *FoundFCB)
450 {
451   WCHAR TempName[2];
452   WCHAR Name[256];
453   PVOID Block;
454   ULONG FirstSector;
455   ULONG DirSize;
456   PDIR_RECORD Record;
457   ULONG Offset;
458   ULONG BlockOffset;
459   NTSTATUS Status;
460
461   LARGE_INTEGER StreamOffset;
462   PVOID Context;
463
464   WCHAR ShortNameBuffer[13];
465   UNICODE_STRING ShortName;
466   UNICODE_STRING LongName;
467   BOOLEAN HasSpaces;
468   GENERATE_NAME_CONTEXT NameContext;
469
470
471   assert(DeviceExt);
472   assert(DirectoryFcb);
473   assert(FileToFind);
474
475   DPRINT("CdfsDirFindFile(VCB:%08x, dirFCB:%08x, File:%S)\n",
476          DeviceExt,
477          DirectoryFcb,
478          FileToFind);
479   DPRINT("Dir Path:%S\n", DirectoryFcb->PathName);
480
481   /*  default to '.' if no filename specified */
482   if (wcslen(FileToFind) == 0)
483     {
484       TempName[0] = L'.';
485       TempName[1] = 0;
486       FileToFind = TempName;
487     }
488
489   DirSize = DirectoryFcb->Entry.DataLengthL;
490   StreamOffset.QuadPart = (LONGLONG)DirectoryFcb->Entry.ExtentLocationL * (LONGLONG)BLOCKSIZE;
491
492   if(!CcMapData(DeviceExt->StreamFileObject, &StreamOffset,
493                 BLOCKSIZE, TRUE, &Context, &Block))
494   {
495     DPRINT("CcMapData() failed\n");
496     return(STATUS_UNSUCCESSFUL);
497   }
498
499   Offset = 0;
500   BlockOffset = 0;
501   Record = (PDIR_RECORD)Block;
502   while(TRUE)
503     {
504       if (Record->RecordLength == 0)
505         {
506           DPRINT("RecordLength == 0  Stopped!\n");
507           break;
508         }
509         
510       DPRINT("RecordLength %u  ExtAttrRecordLength %u  NameLength %u\n",
511              Record->RecordLength, Record->ExtAttrRecordLength, Record->FileIdLength);
512
513       CdfsGetDirEntryName(DeviceExt, Record, Name);
514       DPRINT("Name '%S'\n", Name);
515
516       RtlInitUnicodeString(&LongName, Name);
517       ShortName.Length = 0;
518       ShortName.MaximumLength = 26;
519       ShortName.Buffer = ShortNameBuffer;
520       memset(ShortNameBuffer, 0, 26);
521
522       if ((RtlIsNameLegalDOS8Dot3(&LongName, NULL, &HasSpaces) == FALSE) ||
523           (HasSpaces == TRUE))
524         {
525           /* Build short name */
526           RtlGenerate8dot3Name(&LongName,
527                                FALSE,
528                                &NameContext,
529                                &ShortName);
530         }
531       else
532         {
533           /* copy short name */
534           RtlUpcaseUnicodeString(&ShortName,
535                                  &LongName,
536                                  FALSE);
537         }
538
539       DPRINT("ShortName '%wZ'\n", &ShortName);
540
541       if (wstrcmpjoki(Name, FileToFind) || wstrcmpjoki(ShortNameBuffer, FileToFind))
542         {
543           DPRINT("Match found, %S\n", Name);
544           Status = CdfsMakeFCBFromDirEntry(DeviceExt,
545                                            DirectoryFcb,
546                                            Name,
547                                            ShortNameBuffer,
548                                            Record,
549                                            FoundFCB);
550
551           CcUnpinData(Context);
552
553           return(Status);
554         }
555
556       Offset += Record->RecordLength;
557       BlockOffset += Record->RecordLength;
558       Record = (PDIR_RECORD)(Block + BlockOffset);
559       if (BlockOffset >= BLOCKSIZE || Record->RecordLength == 0)
560         {
561           DPRINT("Map next sector\n");
562           CcUnpinData(Context);
563           StreamOffset.QuadPart += BLOCKSIZE;
564           Offset = ROUND_UP(Offset, BLOCKSIZE);
565           BlockOffset = 0;
566
567           if (!CcMapData(DeviceExt->StreamFileObject,
568                          &StreamOffset,
569                          BLOCKSIZE, TRUE,
570                          &Context, &Block))
571             {
572               DPRINT("CcMapData() failed\n");
573               return(STATUS_UNSUCCESSFUL);
574             }
575           Record = (PDIR_RECORD)(Block + BlockOffset);
576         }
577
578       if (Offset >= DirSize)
579         break;
580     }
581
582   CcUnpinData(Context);
583
584   return(STATUS_OBJECT_NAME_NOT_FOUND);
585 }
586
587
588 NTSTATUS
589 CdfsGetFCBForFile(PDEVICE_EXTENSION Vcb,
590                   PFCB *pParentFCB,
591                   PFCB *pFCB,
592                   const PWSTR pFileName)
593 {
594   NTSTATUS Status;
595   WCHAR  pathName [MAX_PATH];
596   WCHAR  elementName [MAX_PATH];
597   PWCHAR  currentElement;
598   PFCB  FCB;
599   PFCB  parentFCB;
600
601   DPRINT("CdfsGetFCBForFile(%x, %x, %x, '%S')\n",
602          Vcb,
603          pParentFCB,
604          pFCB,
605          pFileName);
606
607   /* Trivial case, open of the root directory on volume */
608   if (pFileName [0] == L'\0' || wcscmp(pFileName, L"\\") == 0)
609     {
610       DPRINT("returning root FCB\n");
611
612       FCB = CdfsOpenRootFCB(Vcb);
613       *pFCB = FCB;
614       *pParentFCB = NULL;
615
616       return((FCB != NULL) ? STATUS_SUCCESS : STATUS_OBJECT_PATH_NOT_FOUND);
617     }
618   else
619     {
620       currentElement = pFileName + 1;
621       wcscpy (pathName, L"\\");
622       FCB = CdfsOpenRootFCB (Vcb);
623     }
624   parentFCB = NULL;
625
626   /* Parse filename and check each path element for existance and access */
627   while (CdfsGetNextPathElement(currentElement) != 0)
628     {
629       /*  Skip blank directory levels */
630       if ((CdfsGetNextPathElement(currentElement) - currentElement) == 0)
631         {
632           currentElement++;
633           continue;
634         }
635
636       DPRINT("Parsing, currentElement:%S\n", currentElement);
637       DPRINT("  parentFCB:%x FCB:%x\n", parentFCB, FCB);
638
639       /* Descend to next directory level */
640       if (parentFCB)
641         {
642           CdfsReleaseFCB(Vcb, parentFCB);
643           parentFCB = NULL;
644         }
645
646       /* fail if element in FCB is not a directory */
647       if (!CdfsFCBIsDirectory(FCB))
648         {
649           DPRINT("Element in requested path is not a directory\n");
650
651           CdfsReleaseFCB(Vcb, FCB);
652           FCB = 0;
653           *pParentFCB = NULL;
654           *pFCB = NULL;
655
656           return(STATUS_OBJECT_PATH_NOT_FOUND);
657         }
658       parentFCB = FCB;
659
660       /* Extract next directory level into dirName */
661       CdfsWSubString(pathName,
662                      pFileName,
663                      CdfsGetNextPathElement(currentElement) - pFileName);
664       DPRINT("  pathName:%S\n", pathName);
665
666       FCB = CdfsGrabFCBFromTable(Vcb, pathName);
667       if (FCB == NULL)
668         {
669           CdfsWSubString(elementName,
670                          currentElement,
671                          CdfsGetNextPathElement(currentElement) - currentElement);
672           DPRINT("  elementName:%S\n", elementName);
673
674           Status = CdfsDirFindFile(Vcb, parentFCB, elementName, &FCB);
675           if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
676             {
677               *pParentFCB = parentFCB;
678               *pFCB = NULL;
679               currentElement = CdfsGetNextPathElement(currentElement);
680               if (*currentElement == L'\0' || CdfsGetNextPathElement(currentElement + 1) == 0)
681                 {
682                   return(STATUS_OBJECT_NAME_NOT_FOUND);
683                 }
684               else
685                 {
686                   return(STATUS_OBJECT_PATH_NOT_FOUND);
687                 }
688             }
689           else if (!NT_SUCCESS(Status))
690             {
691               CdfsReleaseFCB(Vcb, parentFCB);
692               *pParentFCB = NULL;
693               *pFCB = NULL;
694
695               return(Status);
696             }
697         }
698       currentElement = CdfsGetNextPathElement(currentElement);
699     }
700
701   *pParentFCB = parentFCB;
702   *pFCB = FCB;
703
704   return(STATUS_SUCCESS);
705 }
706
707 /* EOF */