Create a branch for network fixes.
[reactos.git] / drivers / filesystems / cdfs / fcb.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 2002, 2004 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 "cdfs.h"
32
33 #define NDEBUG
34 #include <debug.h>
35
36 /* FUNCTIONS ****************************************************************/
37
38 #define TAG_FCB TAG('I', 'F', 'C', 'B')
39
40 #define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))
41
42
43 /* FUNCTIONS ****************************************************************/
44
45 static PWCHAR
46 CdfsGetNextPathElement(PWCHAR FileName)
47 {
48 if (*FileName == L'\0')
49 {
50 return(NULL);
51 }
52
53 while (*FileName != L'\0' && *FileName != L'\\')
54 {
55 FileName++;
56 }
57
58 return(FileName);
59 }
60
61
62 static VOID
63 CdfsWSubString(PWCHAR pTarget, const PWCHAR pSource, size_t pLength)
64 {
65 wcsncpy (pTarget, pSource, pLength);
66 pTarget [pLength] = L'\0';
67 }
68
69
70 PFCB
71 CdfsCreateFCB(PCWSTR FileName)
72 {
73 PFCB Fcb;
74
75 Fcb = ExAllocatePoolWithTag(NonPagedPool, sizeof(FCB), TAG_FCB);
76 RtlZeroMemory(Fcb, sizeof(FCB));
77
78 if (FileName)
79 {
80 wcscpy(Fcb->PathName, FileName);
81 if (wcsrchr(Fcb->PathName, '\\') != 0)
82 {
83 Fcb->ObjectName = wcsrchr(Fcb->PathName, '\\');
84 }
85 else
86 {
87 Fcb->ObjectName = Fcb->PathName;
88 }
89 }
90
91 ExInitializeResourceLite(&Fcb->PagingIoResource);
92 ExInitializeResourceLite(&Fcb->MainResource);
93 Fcb->RFCB.PagingIoResource = &Fcb->PagingIoResource;
94 Fcb->RFCB.Resource = &Fcb->MainResource;
95 Fcb->RFCB.IsFastIoPossible = FastIoIsNotPossible;
96
97 return(Fcb);
98 }
99
100
101 VOID
102 CdfsDestroyFCB(PFCB Fcb)
103 {
104 ExDeleteResourceLite(&Fcb->PagingIoResource);
105 ExDeleteResourceLite(&Fcb->MainResource);
106
107 ExFreePool(Fcb);
108 }
109
110
111 BOOLEAN
112 CdfsFCBIsDirectory(PFCB Fcb)
113 {
114 return(Fcb->Entry.FileFlags & FILE_FLAG_DIRECTORY);
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 PUNICODE_STRING 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->Length == 0 || FileName->Buffer[0] == 0)
188 {
189 DPRINT("Return FCB for stream file object\n");
190 Fcb = Vcb->StreamFileObject->FsContext;
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 '%wZ' and '%S'\n", FileName, Fcb->PathName);
202 if (_wcsicmp(FileName->Buffer, 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->SectionObjectPointer = &Fcb->SectionObjectPointers;
238 FileObject->FsContext = Fcb;
239 FileObject->FsContext2 = newCCB;
240 newCCB->PtrFileObject = FileObject;
241 Fcb->FileObject = FileObject;
242 Fcb->DevExt = Vcb;
243
244 Status = STATUS_SUCCESS;
245 CcInitializeCacheMap(FileObject,
246 (PCC_FILE_SIZES)(&Fcb->RFCB.AllocationSize),
247 FALSE,
248 &(CdfsGlobalData->CacheMgrCallbacks),
249 Fcb);
250
251 ObDereferenceObject(FileObject);
252 Fcb->Flags |= FCB_CACHE_INITIALIZED;
253
254 return(Status);
255 }
256
257
258 PFCB
259 CdfsMakeRootFCB(PDEVICE_EXTENSION Vcb)
260 {
261 PFCB Fcb;
262
263 Fcb = CdfsCreateFCB(L"\\");
264
265 Fcb->Entry.DataLengthL = Vcb->CdInfo.RootSize;
266 Fcb->Entry.ExtentLocationL = Vcb->CdInfo.RootStart;
267 Fcb->Entry.FileFlags = FILE_FLAG_DIRECTORY;
268 Fcb->IndexNumber.QuadPart = 0LL;
269 Fcb->RefCount = 1;
270 Fcb->DirIndex = 0;
271 Fcb->RFCB.FileSize.QuadPart = Vcb->CdInfo.RootSize;
272 Fcb->RFCB.ValidDataLength.QuadPart = Vcb->CdInfo.RootSize;
273 Fcb->RFCB.AllocationSize.QuadPart = Vcb->CdInfo.RootSize;
274
275 CdfsFCBInitializeCache(Vcb, Fcb);
276 CdfsAddFCBToTable(Vcb, Fcb);
277 CdfsGrabFCB(Vcb, Fcb);
278
279 return(Fcb);
280 }
281
282
283 PFCB
284 CdfsOpenRootFCB(PDEVICE_EXTENSION Vcb)
285 {
286 UNICODE_STRING FileName;
287 PFCB Fcb;
288
289 RtlInitUnicodeString(&FileName, L"\\");
290
291 Fcb = CdfsGrabFCBFromTable(Vcb,
292 &FileName);
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 ULONG DirectorySector,
347 ULONG DirectoryOffset,
348 PFCB * fileFCB)
349 {
350 WCHAR pathName[MAX_PATH];
351 PFCB rcFCB;
352 ULONG Size;
353
354 if (LongName [0] != 0 && wcslen (DirectoryFCB->PathName) +
355 sizeof(WCHAR) + wcslen (LongName) > MAX_PATH)
356 {
357 return(STATUS_OBJECT_NAME_INVALID);
358 }
359
360 wcscpy(pathName, DirectoryFCB->PathName);
361 if (!CdfsFCBIsRoot(DirectoryFCB))
362 {
363 wcscat(pathName, L"\\");
364 }
365
366 if (LongName[0] != 0)
367 {
368 wcscat(pathName, LongName);
369 }
370 else
371 {
372 WCHAR entryName[MAX_PATH];
373
374 CdfsGetDirEntryName(Vcb, Record, entryName);
375 wcscat(pathName, entryName);
376 }
377
378 rcFCB = CdfsCreateFCB(pathName);
379 memcpy(&rcFCB->Entry, Record, sizeof(DIR_RECORD));
380
381 /* Copy short name into FCB */
382 rcFCB->ShortNameU.Length = wcslen(ShortName) * sizeof(WCHAR);
383 rcFCB->ShortNameU.MaximumLength = rcFCB->ShortNameU.Length;
384 rcFCB->ShortNameU.Buffer = rcFCB->ShortNameBuffer;
385 wcscpy(rcFCB->ShortNameBuffer, ShortName);
386
387 Size = rcFCB->Entry.DataLengthL;
388
389 rcFCB->RFCB.FileSize.QuadPart = Size;
390 rcFCB->RFCB.ValidDataLength.QuadPart = Size;
391 rcFCB->RFCB.AllocationSize.QuadPart = ROUND_UP(Size, BLOCKSIZE);
392 if (CdfsFCBIsDirectory(rcFCB))
393 {
394 CdfsFCBInitializeCache(Vcb, rcFCB);
395 }
396 rcFCB->IndexNumber.u.HighPart = DirectorySector;
397 rcFCB->IndexNumber.u.LowPart = DirectoryOffset;
398 rcFCB->RefCount++;
399 CdfsAddFCBToTable(Vcb, rcFCB);
400 *fileFCB = rcFCB;
401
402 DPRINT("%S %d %I64d\n", LongName, Size, rcFCB->RFCB.AllocationSize.QuadPart);
403
404 return(STATUS_SUCCESS);
405 }
406
407
408 NTSTATUS
409 CdfsAttachFCBToFileObject(PDEVICE_EXTENSION Vcb,
410 PFCB Fcb,
411 PFILE_OBJECT FileObject)
412 {
413 PCCB newCCB;
414
415 newCCB = ExAllocatePoolWithTag(NonPagedPool, sizeof(CCB), TAG_CCB);
416 if (newCCB == NULL)
417 {
418 return(STATUS_INSUFFICIENT_RESOURCES);
419 }
420 memset(newCCB, 0, sizeof(CCB));
421
422 FileObject->SectionObjectPointer = &Fcb->SectionObjectPointers;
423 FileObject->FsContext = Fcb;
424 FileObject->FsContext2 = newCCB;
425 newCCB->PtrFileObject = FileObject;
426 Fcb->DevExt = Vcb;
427
428 if (CdfsFCBIsDirectory(Fcb))
429 {
430 CcInitializeCacheMap(FileObject,
431 (PCC_FILE_SIZES)(&Fcb->RFCB.AllocationSize),
432 FALSE,
433 &(CdfsGlobalData->CacheMgrCallbacks),
434 Fcb);
435 Fcb->Flags |= FCB_CACHE_INITIALIZED;
436 }
437
438 DPRINT("file open: fcb:%x file size: %d\n", Fcb, Fcb->Entry.DataLengthL);
439
440 return(STATUS_SUCCESS);
441 }
442
443
444 NTSTATUS
445 CdfsDirFindFile(PDEVICE_EXTENSION DeviceExt,
446 PFCB DirectoryFcb,
447 PUNICODE_STRING FileToFind,
448 PFCB *FoundFCB)
449 {
450 UNICODE_STRING TempName;
451 WCHAR Name[256];
452 PVOID Block;
453 ULONG DirSize;
454 PDIR_RECORD Record;
455 ULONG Offset;
456 ULONG BlockOffset;
457 NTSTATUS Status;
458
459 LARGE_INTEGER StreamOffset;
460 PVOID Context;
461
462 WCHAR ShortNameBuffer[13];
463 UNICODE_STRING ShortName;
464 UNICODE_STRING LongName;
465 BOOLEAN HasSpaces;
466 GENERATE_NAME_CONTEXT NameContext;
467
468
469 ASSERT(DeviceExt);
470 ASSERT(DirectoryFcb);
471 ASSERT(FileToFind);
472
473 DPRINT("CdfsDirFindFile(VCB:%p, dirFCB:%p, File:%wZ)\n",
474 DeviceExt,
475 DirectoryFcb,
476 FileToFind);
477 DPRINT("Dir Path:%S\n", DirectoryFcb->PathName);
478
479 /* default to '.' if no filename specified */
480 if (FileToFind->Length == 0)
481 {
482 RtlInitUnicodeString(&TempName, L".");
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,
490 &StreamOffset,
491 BLOCKSIZE,
492 TRUE,
493 &Context,
494 &Block))
495 {
496 DPRINT("CcMapData() failed\n");
497 return STATUS_UNSUCCESSFUL;
498 }
499
500 Offset = 0;
501 BlockOffset = 0;
502 Record = (PDIR_RECORD)Block;
503 while(TRUE)
504 {
505 if (Record->RecordLength == 0)
506 {
507 DPRINT("RecordLength == 0 Stopped!\n");
508 break;
509 }
510
511 DPRINT("RecordLength %u ExtAttrRecordLength %u NameLength %u\n",
512 Record->RecordLength, Record->ExtAttrRecordLength, Record->FileIdLength);
513
514 CdfsGetDirEntryName(DeviceExt, Record, Name);
515 DPRINT ("Name '%S'\n", Name);
516 DPRINT ("Sector %lu\n", DirectoryFcb->Entry.ExtentLocationL);
517 DPRINT ("Offset %lu\n", Offset);
518
519 RtlInitUnicodeString(&LongName, Name);
520 ShortName.Length = 0;
521 ShortName.MaximumLength = 26;
522 ShortName.Buffer = ShortNameBuffer;
523 memset(ShortNameBuffer, 0, 26);
524
525 if ((RtlIsNameLegalDOS8Dot3(&LongName, NULL, &HasSpaces) == FALSE) ||
526 (HasSpaces == TRUE))
527 {
528 /* Build short name */
529 RtlGenerate8dot3Name(&LongName,
530 FALSE,
531 &NameContext,
532 &ShortName);
533 }
534 else
535 {
536 /* copy short name */
537 RtlUpcaseUnicodeString(&ShortName,
538 &LongName,
539 FALSE);
540 }
541
542 DPRINT("ShortName '%wZ'\n", &ShortName);
543
544 if (FsRtlIsNameInExpression(FileToFind, &LongName, TRUE, NULL) ||
545 FsRtlIsNameInExpression(FileToFind, &ShortName, TRUE, NULL))
546 {
547 DPRINT("Match found, %S\n", Name);
548 Status = CdfsMakeFCBFromDirEntry(DeviceExt,
549 DirectoryFcb,
550 Name,
551 ShortNameBuffer,
552 Record,
553 DirectoryFcb->Entry.ExtentLocationL,
554 Offset,
555 FoundFCB);
556
557 CcUnpinData(Context);
558
559 return(Status);
560 }
561
562 Offset += Record->RecordLength;
563 BlockOffset += Record->RecordLength;
564 Record = (PDIR_RECORD)((ULONG_PTR)Block + BlockOffset);
565 if (BlockOffset >= BLOCKSIZE || Record->RecordLength == 0)
566 {
567 DPRINT("Map next sector\n");
568 CcUnpinData(Context);
569 StreamOffset.QuadPart += BLOCKSIZE;
570 Offset = ROUND_UP(Offset, BLOCKSIZE);
571 BlockOffset = 0;
572
573 if (!CcMapData(DeviceExt->StreamFileObject,
574 &StreamOffset,
575 BLOCKSIZE, TRUE,
576 &Context, &Block))
577 {
578 DPRINT("CcMapData() failed\n");
579 return(STATUS_UNSUCCESSFUL);
580 }
581 Record = (PDIR_RECORD)((ULONG_PTR)Block + BlockOffset);
582 }
583
584 if (Offset >= DirSize)
585 break;
586 }
587
588 CcUnpinData(Context);
589
590 return(STATUS_OBJECT_NAME_NOT_FOUND);
591 }
592
593
594 NTSTATUS
595 CdfsGetFCBForFile(PDEVICE_EXTENSION Vcb,
596 PFCB *pParentFCB,
597 PFCB *pFCB,
598 PUNICODE_STRING FileName)
599 {
600 UNICODE_STRING PathName;
601 UNICODE_STRING ElementName;
602 NTSTATUS Status;
603 WCHAR pathName [MAX_PATH];
604 WCHAR elementName [MAX_PATH];
605 PWCHAR currentElement;
606 PFCB FCB;
607 PFCB parentFCB;
608
609 DPRINT("CdfsGetFCBForFile(%x, %x, %x, '%wZ')\n",
610 Vcb,
611 pParentFCB,
612 pFCB,
613 FileName);
614
615 /* Trivial case, open of the root directory on volume */
616 if (FileName->Buffer[0] == L'\0' || wcscmp(FileName->Buffer, L"\\") == 0)
617 {
618 DPRINT("returning root FCB\n");
619
620 FCB = CdfsOpenRootFCB(Vcb);
621 *pFCB = FCB;
622 *pParentFCB = NULL;
623
624 return((FCB != NULL) ? STATUS_SUCCESS : STATUS_OBJECT_PATH_NOT_FOUND);
625 }
626 else
627 {
628 currentElement = &FileName->Buffer[1];
629 wcscpy (pathName, L"\\");
630 FCB = CdfsOpenRootFCB (Vcb);
631 }
632 parentFCB = NULL;
633
634 /* Parse filename and check each path element for existance and access */
635 while (CdfsGetNextPathElement(currentElement) != 0)
636 {
637 /* Skip blank directory levels */
638 if ((CdfsGetNextPathElement(currentElement) - currentElement) == 0)
639 {
640 currentElement++;
641 continue;
642 }
643
644 DPRINT("Parsing, currentElement:%S\n", currentElement);
645 DPRINT(" parentFCB:%x FCB:%x\n", parentFCB, FCB);
646
647 /* Descend to next directory level */
648 if (parentFCB)
649 {
650 CdfsReleaseFCB(Vcb, parentFCB);
651 parentFCB = NULL;
652 }
653
654 /* fail if element in FCB is not a directory */
655 if (!CdfsFCBIsDirectory(FCB))
656 {
657 DPRINT("Element in requested path is not a directory\n");
658
659 CdfsReleaseFCB(Vcb, FCB);
660 FCB = 0;
661 *pParentFCB = NULL;
662 *pFCB = NULL;
663
664 return(STATUS_OBJECT_PATH_NOT_FOUND);
665 }
666 parentFCB = FCB;
667
668 /* Extract next directory level into dirName */
669 CdfsWSubString(pathName,
670 FileName->Buffer,
671 CdfsGetNextPathElement(currentElement) - FileName->Buffer);
672 DPRINT(" pathName:%S\n", pathName);
673
674 RtlInitUnicodeString(&PathName, pathName);
675
676 FCB = CdfsGrabFCBFromTable(Vcb, &PathName);
677 if (FCB == NULL)
678 {
679 CdfsWSubString(elementName,
680 currentElement,
681 CdfsGetNextPathElement(currentElement) - currentElement);
682 DPRINT(" elementName:%S\n", elementName);
683
684 RtlInitUnicodeString(&ElementName, elementName);
685 Status = CdfsDirFindFile(Vcb,
686 parentFCB,
687 &ElementName,
688 &FCB);
689 if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
690 {
691 *pParentFCB = parentFCB;
692 *pFCB = NULL;
693 currentElement = CdfsGetNextPathElement(currentElement);
694 if (*currentElement == L'\0' || CdfsGetNextPathElement(currentElement + 1) == 0)
695 {
696 return(STATUS_OBJECT_NAME_NOT_FOUND);
697 }
698 else
699 {
700 return(STATUS_OBJECT_PATH_NOT_FOUND);
701 }
702 }
703 else if (!NT_SUCCESS(Status))
704 {
705 CdfsReleaseFCB(Vcb, parentFCB);
706 *pParentFCB = NULL;
707 *pFCB = NULL;
708
709 return(Status);
710 }
711 }
712 currentElement = CdfsGetNextPathElement(currentElement);
713 }
714
715 *pParentFCB = parentFCB;
716 *pFCB = FCB;
717
718 return STATUS_SUCCESS;
719 }
720
721 /* EOF */