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