:pserver:cvsanon@mok.lvcm.com:/CVS/ReactOS reactos
[reactos.git] / drivers / fs / vfat / dirwr.c
1 /* $Id$
2  *
3  * COPYRIGHT:        See COPYING in the top level directory
4  * PROJECT:          ReactOS kernel
5  * FILE:             services/fs/vfat/dirwr.c
6  * PURPOSE:          VFAT Filesystem : write in directory
7  *
8  */
9
10 /* INCLUDES *****************************************************************/
11
12 #include <ddk/ntddk.h>
13 #include <ctype.h>
14 #include <wchar.h>
15 #include <string.h>
16
17 #define NDEBUG
18 #include <debug.h>
19
20 #include "vfat.h"
21
22 const char *short_illegals=" ;+=[]',\"*\\<>/?:|";
23
24 static BOOLEAN
25 vfatIsShortIllegal(char c)
26 {
27   int i;
28   for (i = 0; short_illegals[i]; i++)
29     if (c == short_illegals[i])
30       return TRUE;
31   return FALSE;
32 }
33
34 NTSTATUS 
35 VfatUpdateEntry (PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT pFileObject)
36 /*
37  * update an existing FAT entry
38  */
39 {
40   PVOID Context;
41   PVOID Buffer;
42   NTSTATUS status;
43   PVFATFCB pDirFcb = NULL, pFcb = NULL;
44   LARGE_INTEGER Offset;
45
46   DPRINT ("updEntry PathFileName \'%S\'\n", 
47           ((PVFATCCB)(pFileObject->FsContext2))->pFcb->PathName);
48   status = vfatGetFCBForFile(DeviceExt, &pDirFcb, &pFcb, 
49              ((PVFATCCB)(pFileObject->FsContext2))->pFcb->PathName);
50   if (!NT_SUCCESS(status))
51   {
52     if (pDirFcb != NULL)
53     {
54       vfatReleaseFCB(DeviceExt, pDirFcb);
55     }
56     return status;
57   }
58
59   Offset.u.HighPart = 0;
60   Offset.u.LowPart = pFcb->dirIndex * sizeof(FATDirEntry);
61   if (CcMapData (pDirFcb->FileObject, &Offset, sizeof(FATDirEntry),
62     TRUE, &Context, (PVOID*)&Buffer))
63   {
64      memcpy(Buffer, &pFcb->entry, sizeof(FATDirEntry));
65      CcSetDirtyPinnedData(Context, NULL);
66      CcUnpinData(Context);
67   }
68   else
69      DPRINT1 ("Failed write to \'%S\'.\n", pDirFcb->PathName);
70   vfatReleaseFCB(DeviceExt, pDirFcb);
71   vfatReleaseFCB(DeviceExt, pFcb);
72   return STATUS_SUCCESS;
73 }
74
75 BOOLEAN
76 findDirSpace(PDEVICE_EXTENSION DeviceExt,
77              PVFATFCB pDirFcb,
78              ULONG nbSlots,
79              PULONG start)
80 {
81 /*
82  * try to find contiguous entries frees in directory,
83  * extend a directory if is neccesary
84  */
85   LARGE_INTEGER FileOffset;
86   ULONG i, count, size, nbFree = 0;
87   FATDirEntry* pFatEntry;
88   PVOID Context = NULL;
89   NTSTATUS Status;
90   FileOffset.QuadPart = 0;
91   count = pDirFcb->RFCB.FileSize.u.LowPart / sizeof(FATDirEntry);
92   size = DeviceExt->FatInfo.BytesPerCluster / sizeof(FATDirEntry);
93   for (i = 0; i < count; i++, pFatEntry++)
94   {
95     if (Context == NULL || (i % size) == 0)
96     {
97       if (Context)
98       {
99         CcUnpinData(Context);
100       }
101       // FIXME: check return value
102       CcMapData (pDirFcb->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster,
103                  TRUE, &Context, (PVOID*)&pFatEntry);
104       FileOffset.u.LowPart += DeviceExt->FatInfo.BytesPerCluster;
105     }
106     if (vfatIsDirEntryEndMarker(pFatEntry))
107     {
108       break;
109     }
110     if (vfatIsDirEntryDeleted(pFatEntry))
111     {
112       nbFree++;
113     }
114     else
115     {
116       nbFree = 0;
117     }
118     if (nbFree == nbSlots)
119     {
120       break;
121     }
122   }
123   if (Context)
124   {
125     CcUnpinData(Context);
126     Context = NULL;
127   }
128   if (nbFree == nbSlots)
129   {
130     // found enough contiguous free slots
131     *start = i - nbSlots + 1;
132   }
133   else
134   {
135     *start = i - nbFree;
136     if (*start + nbSlots > count)
137     {
138       LARGE_INTEGER AllocationSize;
139       CHECKPOINT;
140       // extend the directory
141       if (vfatFCBIsRoot(pDirFcb) && DeviceExt->FatInfo.FatType != FAT32)
142       {
143         // We can't extend a root directory on a FAT12/FAT16 partition
144         return FALSE;
145       }
146       AllocationSize.QuadPart = pDirFcb->RFCB.FileSize.u.LowPart + DeviceExt->FatInfo.BytesPerCluster;
147       Status = VfatSetAllocationSizeInformation(pDirFcb->FileObject, pDirFcb,
148                                                 DeviceExt, &AllocationSize);
149       if (!NT_SUCCESS(Status))
150       {
151         return FALSE;
152       }
153       // clear the new dir cluster
154       FileOffset.u.LowPart = pDirFcb->RFCB.FileSize.QuadPart -
155                                DeviceExt->FatInfo.BytesPerCluster;
156       CcMapData (pDirFcb->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster,
157                  TRUE, &Context, (PVOID*)&pFatEntry);
158       RtlZeroMemory(pFatEntry, DeviceExt->FatInfo.BytesPerCluster);
159     }
160     else if (*start + nbSlots < count)
161     {
162       // clear the entry after the last new entry
163       FileOffset.u.LowPart = (*start + nbSlots) * sizeof(FATDirEntry);
164       CcMapData (pDirFcb->FileObject, &FileOffset, sizeof(FATDirEntry),
165                  TRUE, &Context, (PVOID*)&pFatEntry);
166       RtlZeroMemory(pFatEntry, sizeof(FATDirEntry));
167     }
168     if (Context)
169     {
170       CcSetDirtyPinnedData(Context, NULL);
171       CcUnpinData(Context);
172     }
173   }
174   DPRINT ("nbSlots %d nbFree %d, entry number %d\n", nbSlots, nbFree, *start);
175   return TRUE;
176 }
177
178 NTSTATUS
179 VfatAddEntry (PDEVICE_EXTENSION DeviceExt,
180               PFILE_OBJECT pFileObject,
181               ULONG RequestedOptions,
182               UCHAR ReqAttr)
183 /*
184   create a new FAT entry
185 */
186 {
187   WCHAR DirName[MAX_PATH], *FileName, *PathFileName;
188   VFATFCB FileFcb;
189   PVOID Context = NULL;
190   FATDirEntry *pFatEntry, *pEntry;
191   slot *pSlots;
192   short nbSlots = 0, nbFree = 0, j, posCar, NameLen;
193   PUCHAR Buffer;
194   BOOLEAN needTilde = FALSE, needLong = FALSE;
195   PVFATFCB newFCB;
196   ULONG CurrentCluster;
197   LARGE_INTEGER SystemTime, LocalTime, FileOffset;
198   NTSTATUS Status = STATUS_SUCCESS;
199   PVFATFCB pDirFcb;
200   ULONG start, size;
201   long i;
202
203   PathFileName = pFileObject->FileName.Buffer;
204   DPRINT ("addEntry: Pathname=%S\n", PathFileName);
205   //find last \ in PathFileName
206   posCar = -1;
207   for (i = 0; PathFileName[i]; i++)
208   {
209     if (PathFileName[i] == L'\\')
210     {
211       posCar = i;
212     }
213   }
214   if (posCar == -1)
215   {
216     return STATUS_UNSUCCESSFUL;
217   }
218   FileName = &PathFileName[posCar + 1];
219   for (NameLen = 0; FileName[NameLen]; NameLen++);
220   // extract directory name from pathname
221   if (posCar == 0)
222   {
223     // root dir
224     DirName[0] = L'\\';
225     DirName[1] = 0;
226   }
227   else
228   {
229     memcpy (DirName, PathFileName, posCar * sizeof (WCHAR));
230     DirName[posCar] = 0;
231   }
232   // open parent directory
233   pDirFcb = vfatGrabFCBFromTable(DeviceExt, DirName);
234   if (pDirFcb == NULL)
235   {
236     return STATUS_UNSUCCESSFUL;
237   }
238   nbSlots = (NameLen + 12) / 13 + 1;    //nb of entry needed for long name+normal entry
239   DPRINT ("NameLen= %d, nbSlots =%d\n", NameLen, nbSlots);
240   Buffer = ExAllocatePool (NonPagedPool, nbSlots * sizeof (FATDirEntry));
241   RtlZeroMemory (Buffer, nbSlots * sizeof (FATDirEntry));
242   pEntry = (FATDirEntry *) (Buffer + (nbSlots - 1) * sizeof (FATDirEntry));
243   pSlots = (slot *) Buffer;
244   // create 8.3 name
245   needTilde = FALSE;
246   // find last point in name
247   posCar = j = 0;
248   for (i = 0; FileName[i]; i++)
249   {
250     if (FileName[i] == '.')
251     {
252       posCar = i;
253       if (i == j)
254       {
255         j++;
256       }
257     }
258   }
259   if (!posCar)
260   {
261     posCar = i;
262   }
263   if (posCar < j)
264   {
265     posCar = i;
266     needTilde = TRUE;
267   }
268   if (posCar > 8)
269   {
270     needTilde = TRUE;
271   }
272   //copy 8 characters max
273   memset (pEntry, ' ', 11);
274   for (i = 0, j = 0; j < 8 && i < posCar; i++)
275   {
276     if (vfatIsShortIllegal (FileName[i]))
277     {
278       needTilde = TRUE;
279       pEntry->Filename[j++] = '_';
280     }
281     else
282     {
283       if (FileName[i] == '.')
284       {
285         needTilde = TRUE;
286       }
287       else
288       {
289         pEntry->Filename[j++] = toupper ((char) FileName[i]);
290       }
291     }
292   }
293   //copy extension
294   if (FileName[posCar])
295   {
296     for (j = 0, i = posCar + 1; FileName[i] && j < 3; i++)
297     {
298       if (vfatIsShortIllegal(FileName[i]))
299       {
300         needTilde = TRUE;
301         pEntry->Ext[j++] = '_';
302       }
303       else
304       {
305         if (FileName[i] == '.')
306         {
307           needTilde = TRUE;
308         }
309         else
310         {
311           pEntry->Ext[j++] = toupper ((char) (FileName[i] & 0x7F));
312         }
313       }
314     }
315   }
316   if (FileName[i])
317   {
318     needTilde = TRUE;
319   }
320   //find good value for tilde
321   if (needTilde)
322   {
323     needLong = TRUE;
324     DPRINT ("searching a good value for tilde\n");
325     for (posCar = 0; posCar < 8 && pEntry->Filename[posCar] != ' '; posCar++);
326     if (posCar == 0) // ??????????????????????
327     {
328       pEntry->Filename[posCar++] = '_';
329     }
330     posCar += 2;
331     if (posCar > 8)
332     {
333       posCar = 8;
334     }
335     pEntry->Filename[posCar - 2] = '~';
336     pEntry->Filename[posCar - 1] = '1';
337     vfat8Dot3ToString (pEntry->Filename, pEntry->Ext, DirName);
338     //try first with xxxxxx~y.zzz
339     for (i = 1; i < 10; i++)
340     {
341       DirName[posCar-1] = '0' + i;
342       pEntry->Filename[posCar - 1] = '0' + i;
343       Status = FindFile (DeviceExt, &FileFcb, pDirFcb, DirName, NULL, NULL);
344       if (!NT_SUCCESS(Status))
345       {
346         break;
347       }
348     }
349     if (i == 10)
350     {
351       posCar++;
352       if (posCar > 8)
353       {
354         posCar = 8;
355       }
356       pEntry->Filename[posCar - 3] = '~';
357       pEntry->Filename[posCar - 2] = '1';
358       pEntry->Filename[posCar - 1] = '0';
359       vfat8Dot3ToString (pEntry->Filename, pEntry->Ext, DirName);
360       //try second with xxxxx~yy.zzz
361       for (i = 10; i < 100; i++)
362       {
363         DirName[posCar - 1] = '0' + i % 10;
364         DirName[posCar - 2] = '0' + i / 10;
365         pEntry->Filename[posCar - 1] = '0' + i % 10;
366         pEntry->Filename[posCar - 2] = '0' + i / 10;
367         Status = FindFile (DeviceExt, &FileFcb, pDirFcb, DirName, NULL, NULL);
368         if (!NT_SUCCESS(Status))
369         {
370           break;
371         }
372       }
373       if (i == 100) //FIXME : what to do after 99 tilde ?
374       {
375         vfatReleaseFCB(DeviceExt, pDirFcb);
376         ExFreePool (Buffer);
377         return STATUS_UNSUCCESSFUL;
378       }
379     }
380   }
381   else
382   {
383     DPRINT ("check if long name entry needed, needlong=%d\n", needLong);
384     for (i = 0; i < posCar; i++)
385     {
386       if ((USHORT) pEntry->Filename[i] != FileName[i])
387       {
388         DPRINT ("i=%d,%d,%d\n", i, pEntry->Filename[i], FileName[i]);
389         needLong = TRUE;
390       }
391     }
392     if (FileName[i])
393     {
394       i++;                      //jump on point char
395       for (j = 0, i = posCar + 1; FileName[i] && i < posCar + 4; i++)
396       {
397         if ((USHORT) pEntry->Ext[j++] != FileName[i])
398         {
399           DPRINT ("i=%d,j=%d,%d,%d\n", i, j, pEntry->Filename[i],
400           FileName[i]);
401           needLong = TRUE;
402         }
403       }
404     }
405   }
406   if (needLong == FALSE)
407   {
408     nbSlots = 1;
409     memcpy (Buffer, pEntry, sizeof (FATDirEntry));
410     memset (pEntry, 0, sizeof (FATDirEntry));
411     pEntry = (FATDirEntry *) Buffer;
412   }
413   else
414   {
415     memset (DirName, 0xff, sizeof (DirName));
416     memcpy (DirName, FileName, NameLen * sizeof (WCHAR));
417     DirName[NameLen] = 0;
418   }
419   DPRINT ("dos name=%11.11s\n", pEntry->Filename);
420
421   /* set attributes */
422   pEntry->Attrib = ReqAttr;
423   if (RequestedOptions & FILE_DIRECTORY_FILE)
424   {
425     pEntry->Attrib |= FILE_ATTRIBUTE_DIRECTORY;
426   }
427   /* set dates and times */
428   KeQuerySystemTime (&SystemTime);
429   ExSystemTimeToLocalTime (&SystemTime, &LocalTime);
430   FsdFileTimeToDosDateTime ((TIME *) & LocalTime, &pEntry->CreationDate,
431                             &pEntry->CreationTime);
432   pEntry->UpdateDate = pEntry->CreationDate;
433   pEntry->UpdateTime = pEntry->CreationTime;
434   pEntry->AccessDate = pEntry->CreationDate;
435
436   // calculate checksum for 8.3 name
437   for (pSlots[0].alias_checksum = i = 0; i < 11; i++)
438   {
439     pSlots[0].alias_checksum = (((pSlots[0].alias_checksum & 1) << 7
440                                 | ((pSlots[0].alias_checksum & 0xfe) >> 1))
441                                 + pEntry->Filename[i]);
442   }
443   //construct slots and entry
444   for (i = nbSlots - 2; i >= 0; i--)
445   {
446     DPRINT ("construct slot %d\n", i);
447     pSlots[i].attr = 0xf;
448     if (i)
449     {
450       pSlots[i].id = nbSlots - i - 1;
451     }
452     else
453     {
454       pSlots[i].id = nbSlots - i - 1 + 0x40;
455     }
456     pSlots[i].alias_checksum = pSlots[0].alias_checksum;
457 //FIXME      pSlots[i].start=;
458     memcpy (pSlots[i].name0_4, DirName + (nbSlots - i - 2) * 13, 10);
459     memcpy (pSlots[i].name5_10, DirName + (nbSlots - i - 2) * 13 + 5, 12);
460     memcpy (pSlots[i].name11_12, DirName + (nbSlots - i - 2) * 13 + 11, 4);
461   }
462
463   //try to find nbSlots contiguous entries frees in directory
464   if (!findDirSpace(DeviceExt, pDirFcb, nbSlots, &start))
465   {
466     vfatReleaseFCB(DeviceExt, pDirFcb);
467     ExFreePool (Buffer);
468     return STATUS_DISK_FULL;
469   }
470
471   if (RequestedOptions & FILE_DIRECTORY_FILE)
472   {
473     CurrentCluster = 0xffffffff;
474     Status = NextCluster (DeviceExt, NULL, 0, &CurrentCluster, TRUE);
475     if (CurrentCluster == 0xffffffff || !NT_SUCCESS(Status))
476     {
477       vfatReleaseFCB(DeviceExt, pDirFcb);
478       ExFreePool (Buffer);
479       if (!NT_SUCCESS(Status))
480       {
481         return Status;
482       }
483       return STATUS_DISK_FULL;
484     }
485     if (DeviceExt->FatInfo.FatType == FAT32)
486     {
487       pEntry->FirstClusterHigh = CurrentCluster >> 16;
488     }
489     pEntry->FirstCluster = CurrentCluster;
490   }
491
492   size = DeviceExt->FatInfo.BytesPerCluster / sizeof(FATDirEntry);
493   FileOffset.u.HighPart = 0;
494   FileOffset.u.LowPart = start * sizeof(FATDirEntry);
495   if (start / size == (start + nbSlots - 1) / size)
496   {
497     // one cluster
498     CHECKPOINT;
499     CcMapData (pDirFcb->FileObject, &FileOffset, nbSlots * sizeof(FATDirEntry),
500                TRUE, &Context, (PVOID*)&pFatEntry);
501     memcpy(pFatEntry, Buffer, nbSlots * sizeof(FATDirEntry));
502   }
503   else
504   {
505     // two clusters
506     CHECKPOINT;
507     size = DeviceExt->FatInfo.BytesPerCluster -
508              (start * sizeof(FATDirEntry)) % DeviceExt->FatInfo.BytesPerCluster;
509     CcMapData (pDirFcb->FileObject, &FileOffset, size, TRUE,
510                &Context, (PVOID*)&pFatEntry);
511     memcpy(pFatEntry, Buffer, size);
512     CcSetDirtyPinnedData(Context, NULL);
513     CcUnpinData(Context);
514     FileOffset.u.LowPart += size;
515     CcMapData (pDirFcb->FileObject, &FileOffset,
516                nbSlots * sizeof(FATDirEntry) - size,
517                TRUE, &Context, (PVOID*)&pFatEntry);
518     memcpy(pFatEntry, (PVOID)Buffer + size, nbSlots * sizeof(FATDirEntry) - size);
519   }
520   CcSetDirtyPinnedData(Context, NULL);
521   CcUnpinData(Context);
522
523   // FEXME: check status
524   vfatMakeFCBFromDirEntry (DeviceExt, pDirFcb, FileName, pEntry,
525                            start + nbSlots - 1, &newFCB);
526   vfatAttachFCBToFileObject (DeviceExt, newFCB, pFileObject);
527
528   DPRINT ("new : entry=%11.11s\n", newFCB->entry.Filename);
529   DPRINT ("new : entry=%11.11s\n", pEntry->Filename);
530
531   if (RequestedOptions & FILE_DIRECTORY_FILE)
532   {
533     FileOffset.QuadPart = 0;
534     CcMapData (pFileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster, TRUE,
535                &Context, (PVOID*)&pFatEntry);
536     // clear the new directory cluster
537     RtlZeroMemory (pFatEntry, DeviceExt->FatInfo.BytesPerCluster);
538     // create '.' and '..'
539     memcpy (&pFatEntry[0].Attrib, &pEntry->Attrib, sizeof(FATDirEntry) - 11);
540     memcpy (pFatEntry[0].Filename, ".          ", 11);
541     memcpy (&pFatEntry[1].Attrib, &pEntry->Attrib, sizeof(FATDirEntry) - 11);
542     memcpy (pFatEntry[1].Filename, "..         ", 11);
543     pFatEntry[1].FirstCluster = pDirFcb->entry.FirstCluster;
544     pFatEntry[1].FirstClusterHigh = pDirFcb->entry.FirstClusterHigh;
545     if (DeviceExt->FatInfo.FatType == FAT32)
546     {
547       if (pFatEntry[1].FirstCluster == (DeviceExt->FatInfo.RootCluster & 0xffff) &&
548           pFatEntry[1].FirstClusterHigh == (DeviceExt->FatInfo.RootCluster >> 16))
549       {
550         pFatEntry[1].FirstCluster = 0;
551         pFatEntry[1].FirstClusterHigh = 0;
552       }
553     }
554     else
555     {
556       if (pFatEntry[1].FirstCluster == 1)
557       {
558         pFatEntry[1].FirstCluster = 0;
559       }
560     }
561     CcSetDirtyPinnedData(Context, NULL);
562     CcUnpinData(Context);
563   }
564   vfatReleaseFCB (DeviceExt, pDirFcb);
565   ExFreePool (Buffer);
566   DPRINT ("addentry ok\n");
567   return STATUS_SUCCESS;
568 }
569
570 NTSTATUS
571 delEntry (PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT pFileObject)
572 /*
573  * deleting an existing FAT entry
574  */
575 {
576   VFATFCB Fcb;
577   PVFATFCB pFcb = NULL, pDirFcb = NULL;
578   NTSTATUS status;
579   PWSTR pName;
580   ULONG Entry = 0, startEntry, Read, CurrentCluster, NextCluster, i;
581   FATDirEntry DirEntry;
582
583   DPRINT ("delEntry PathFileName \'%S\'\n", pFileObject->FileName.Buffer);
584
585   status = vfatGetFCBForFile(DeviceExt, &pDirFcb, &pFcb,
586                              pFileObject->FileName.Buffer);
587   if (pFcb != NULL)
588   {
589     vfatReleaseFCB(DeviceExt, pFcb);
590   }
591   if (!NT_SUCCESS(status))
592   {
593     if (pDirFcb != NULL)
594     {
595       vfatReleaseFCB(DeviceExt, pDirFcb);
596     }
597     return status;
598   }
599   pName = ((PVFATCCB)(pFileObject->FsContext2))->pFcb->ObjectName;
600   if (*pName == L'\\')
601   {
602     pName ++;
603   }
604   status = FindFile (DeviceExt, &Fcb, pDirFcb, pName, &Entry, &startEntry);
605
606   if (NT_SUCCESS(status))
607   {
608     PVOID Context = NULL;
609     LARGE_INTEGER Offset;
610     FATDirEntry* pDirEntry;
611     DPRINT ("delete entry: %d to %d\n", startEntry, Entry);
612     Offset.u.HighPart = 0;
613     for (i = startEntry; i <= Entry; i++)
614     {
615       if (Context == NULL || ((i * sizeof(FATDirEntry)) % PAGE_SIZE) == 0)
616       {
617         if (Context)
618         {
619           CcSetDirtyPinnedData(Context, NULL);
620           CcUnpinData(Context);
621         }
622         Offset.u.LowPart = (i * sizeof(FATDirEntry) / PAGE_SIZE) * PAGE_SIZE;
623         CcMapData (pDirFcb->FileObject, &Offset, PAGE_SIZE, TRUE,
624                    &Context, (PVOID*)&pDirEntry);
625       }
626       pDirEntry[i % (PAGE_SIZE / sizeof(FATDirEntry))].Filename[0] = 0xe5;
627       if (i == Entry)
628       {
629         CurrentCluster =
630           vfatDirEntryGetFirstCluster (DeviceExt,
631             &pDirEntry[i % (PAGE_SIZE / sizeof(FATDirEntry))]);
632       }
633     }
634     if (Context)
635     {
636       CcSetDirtyPinnedData(Context, NULL);
637       CcUnpinData(Context);
638     }
639
640     while (CurrentCluster && CurrentCluster != 0xffffffff)
641     {
642       GetNextCluster (DeviceExt, CurrentCluster, &NextCluster, FALSE);
643       // FIXME: check status
644       WriteCluster(DeviceExt, CurrentCluster, 0);
645       CurrentCluster = NextCluster;
646     }
647   }
648   vfatReleaseFCB(DeviceExt, pDirFcb);
649   return status;
650 }
651
652 /* EOF */