xref: /reactos/drivers/filesystems/ntfs/create.c (revision 53221834)
1 /*
2  *  ReactOS kernel
3  *  Copyright (C) 2002, 2014 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * COPYRIGHT:        See COPYING in the top level directory
20  * PROJECT:          ReactOS kernel
21  * FILE:             drivers/filesystem/ntfs/create.c
22  * PURPOSE:          NTFS filesystem driver
23  * PROGRAMMERS:      Eric Kohl
24  *                   Pierre Schweitzer (pierre@reactos.org)
25  */
26 
27 /* INCLUDES *****************************************************************/
28 
29 #include "ntfs.h"
30 
31 #define NDEBUG
32 #include <debug.h>
33 
34 static PCWSTR MftIdToName[] = {
35     L"$MFT",
36     L"$MFTMirr",
37     L"$LogFile",
38     L"$Volume",
39     L"AttrDef",
40     L".",
41     L"$Bitmap",
42     L"$Boot",
43     L"$BadClus",
44     L"$Quota",
45     L"$UpCase",
46     L"$Extended",
47 };
48 
49 /* FUNCTIONS ****************************************************************/
50 
51 static
52 NTSTATUS
53 NtfsMakeAbsoluteFilename(PFILE_OBJECT pFileObject,
54                          PWSTR pRelativeFileName,
55                          PWSTR *pAbsoluteFilename)
56 {
57     PWSTR rcName;
58     PNTFS_FCB Fcb;
59 
60     DPRINT("try related for %S\n", pRelativeFileName);
61     Fcb = pFileObject->FsContext;
62     ASSERT(Fcb);
63 
64     if (Fcb->Flags & FCB_IS_VOLUME)
65     {
66         /* This is likely to be an opening by ID, return ourselves */
67         if (pRelativeFileName[0] == L'\\')
68         {
69             *pAbsoluteFilename = NULL;
70             return STATUS_SUCCESS;
71         }
72 
73         return STATUS_INVALID_PARAMETER;
74     }
75 
76     /* verify related object is a directory and target name
77        don't start with \. */
78     if (NtfsFCBIsDirectory(Fcb) == FALSE ||
79         pRelativeFileName[0] == L'\\')
80     {
81         return STATUS_INVALID_PARAMETER;
82     }
83 
84     /* construct absolute path name */
85     ASSERT(wcslen (Fcb->PathName) + 1 + wcslen (pRelativeFileName) + 1 <= MAX_PATH);
86     rcName = ExAllocatePoolWithTag(NonPagedPool, MAX_PATH * sizeof(WCHAR), TAG_NTFS);
87     if (!rcName)
88     {
89         return STATUS_INSUFFICIENT_RESOURCES;
90     }
91 
92     wcscpy(rcName, Fcb->PathName);
93     if (!NtfsFCBIsRoot(Fcb))
94         wcscat (rcName, L"\\");
95     wcscat (rcName, pRelativeFileName);
96     *pAbsoluteFilename = rcName;
97 
98     return STATUS_SUCCESS;
99 }
100 
101 
102 static
103 NTSTATUS
104 NtfsMoonWalkID(PDEVICE_EXTENSION DeviceExt,
105                ULONGLONG Id,
106                PUNICODE_STRING OutPath)
107 {
108     NTSTATUS Status;
109     PFILE_RECORD_HEADER MftRecord;
110     PFILENAME_ATTRIBUTE FileName;
111     WCHAR FullPath[MAX_PATH];
112     ULONG WritePosition = MAX_PATH - 1;
113 
114     DPRINT("NtfsMoonWalkID(%p, %I64x, %p)\n", DeviceExt, Id, OutPath);
115 
116     RtlZeroMemory(FullPath, sizeof(FullPath));
117     MftRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
118     if (MftRecord == NULL)
119     {
120         return STATUS_INSUFFICIENT_RESOURCES;
121     }
122 
123     while (TRUE)
124     {
125         Status = ReadFileRecord(DeviceExt, Id, MftRecord);
126         if (!NT_SUCCESS(Status))
127             break;
128 
129         ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
130         if (!(MftRecord->Flags & FRH_IN_USE))
131         {
132             Status = STATUS_OBJECT_PATH_NOT_FOUND;
133             break;
134         }
135 
136         FileName = GetBestFileNameFromRecord(DeviceExt, MftRecord);
137         if (FileName == NULL)
138         {
139             DPRINT1("$FILE_NAME attribute not found for %I64x\n", Id);
140             Status = STATUS_OBJECT_PATH_NOT_FOUND;
141             break;
142         }
143 
144         WritePosition -= FileName->NameLength;
145         ASSERT(WritePosition < MAX_PATH);
146         RtlCopyMemory(FullPath + WritePosition, FileName->Name, FileName->NameLength * sizeof(WCHAR));
147         WritePosition -= 1;
148         ASSERT(WritePosition < MAX_PATH);
149         FullPath[WritePosition] = L'\\';
150 
151         Id = FileName->DirectoryFileReferenceNumber & NTFS_MFT_MASK;
152         if (Id == NTFS_FILE_ROOT)
153             break;
154     }
155 
156     ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, MftRecord);
157 
158     if (!NT_SUCCESS(Status))
159         return Status;
160 
161     OutPath->Length = (MAX_PATH - WritePosition - 1) * sizeof(WCHAR);
162     OutPath->MaximumLength = (MAX_PATH - WritePosition) * sizeof(WCHAR);
163     OutPath->Buffer = ExAllocatePoolWithTag(NonPagedPool, OutPath->MaximumLength, TAG_NTFS);
164     if (OutPath->Buffer == NULL)
165     {
166         return STATUS_INSUFFICIENT_RESOURCES;
167     }
168     RtlCopyMemory(OutPath->Buffer, FullPath + WritePosition, OutPath->MaximumLength);
169 
170     return Status;
171 }
172 
173 static
174 NTSTATUS
175 NtfsOpenFileById(PDEVICE_EXTENSION DeviceExt,
176                  PFILE_OBJECT FileObject,
177                  ULONGLONG MftId,
178                  PNTFS_FCB * FoundFCB)
179 {
180     NTSTATUS Status;
181     PNTFS_FCB FCB;
182     PFILE_RECORD_HEADER MftRecord;
183 
184     DPRINT("NtfsOpenFileById(%p, %p, %I64x, %p)\n", DeviceExt, FileObject, MftId, FoundFCB);
185 
186     ASSERT(MftId < NTFS_FILE_FIRST_USER_FILE);
187     if (MftId > 0xb) /* No entries are used yet beyond this */
188     {
189         return STATUS_OBJECT_NAME_NOT_FOUND;
190     }
191 
192     MftRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
193     if (MftRecord == NULL)
194     {
195         return STATUS_INSUFFICIENT_RESOURCES;
196     }
197 
198     Status = ReadFileRecord(DeviceExt, MftId, MftRecord);
199     if (!NT_SUCCESS(Status))
200     {
201         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, MftRecord);
202         return Status;
203     }
204 
205     if (!(MftRecord->Flags & FRH_IN_USE))
206     {
207         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, MftRecord);
208         return STATUS_OBJECT_PATH_NOT_FOUND;
209     }
210 
211     FCB = NtfsGrabFCBFromTable(DeviceExt, MftIdToName[MftId]);
212     if (FCB == NULL)
213     {
214         UNICODE_STRING Name;
215 
216         RtlInitUnicodeString(&Name, MftIdToName[MftId]);
217         Status = NtfsMakeFCBFromDirEntry(DeviceExt, NULL, &Name, NULL, MftRecord, MftId, &FCB);
218         if (!NT_SUCCESS(Status))
219         {
220             ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, MftRecord);
221             return Status;
222         }
223     }
224 
225     ASSERT(FCB != NULL);
226 
227     ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, MftRecord);
228 
229     Status = NtfsAttachFCBToFileObject(DeviceExt,
230                                        FCB,
231                                        FileObject);
232     *FoundFCB = FCB;
233 
234     return Status;
235 }
236 
237 /*
238  * FUNCTION: Opens a file
239  */
240 static
241 NTSTATUS
242 NtfsOpenFile(PDEVICE_EXTENSION DeviceExt,
243              PFILE_OBJECT FileObject,
244              PWSTR FileName,
245              BOOLEAN CaseSensitive,
246              PNTFS_FCB * FoundFCB)
247 {
248     PNTFS_FCB ParentFcb;
249     PNTFS_FCB Fcb;
250     NTSTATUS Status;
251     PWSTR AbsFileName = NULL;
252 
253     DPRINT("NtfsOpenFile(%p, %p, %S, %s, %p)\n",
254             DeviceExt,
255             FileObject,
256             FileName,
257             CaseSensitive ? "TRUE" : "FALSE",
258             FoundFCB);
259 
260     *FoundFCB = NULL;
261 
262     if (FileObject->RelatedFileObject)
263     {
264         DPRINT("Converting relative filename to absolute filename\n");
265 
266         Status = NtfsMakeAbsoluteFilename(FileObject->RelatedFileObject,
267                                           FileName,
268                                           &AbsFileName);
269         if (AbsFileName) FileName = AbsFileName;
270         if (!NT_SUCCESS(Status))
271         {
272             return Status;
273         }
274     }
275 
276     //FIXME: Get canonical path name (remove .'s, ..'s and extra separators)
277 
278     DPRINT("PathName to open: %S\n", FileName);
279 
280     /*  try first to find an existing FCB in memory  */
281     DPRINT("Checking for existing FCB in memory\n");
282     Fcb = NtfsGrabFCBFromTable(DeviceExt,
283                                FileName);
284     if (Fcb == NULL)
285     {
286         DPRINT("No existing FCB found, making a new one if file exists.\n");
287         Status = NtfsGetFCBForFile(DeviceExt,
288                                    &ParentFcb,
289                                    &Fcb,
290                                    FileName,
291                                    CaseSensitive);
292         if (ParentFcb != NULL)
293         {
294             NtfsReleaseFCB(DeviceExt,
295                            ParentFcb);
296         }
297 
298         if (!NT_SUCCESS(Status))
299         {
300             DPRINT("Could not make a new FCB, status: %x\n", Status);
301 
302             if (AbsFileName)
303                 ExFreePoolWithTag(AbsFileName, TAG_NTFS);
304 
305             return Status;
306         }
307     }
308 
309     DPRINT("Attaching FCB to fileObject\n");
310     Status = NtfsAttachFCBToFileObject(DeviceExt,
311                                        Fcb,
312                                        FileObject);
313 
314     if (AbsFileName)
315         ExFreePool(AbsFileName);
316 
317     *FoundFCB = Fcb;
318 
319     return Status;
320 }
321 
322 
323 /*
324  * FUNCTION: Opens a file
325  */
326 static
327 NTSTATUS
328 NtfsCreateFile(PDEVICE_OBJECT DeviceObject,
329                PNTFS_IRP_CONTEXT IrpContext)
330 {
331     PDEVICE_EXTENSION DeviceExt;
332     PIO_STACK_LOCATION Stack;
333     PFILE_OBJECT FileObject;
334     ULONG RequestedDisposition;
335     ULONG RequestedOptions;
336     PNTFS_FCB Fcb = NULL;
337 //    PWSTR FileName;
338     NTSTATUS Status;
339     UNICODE_STRING FullPath;
340     PIRP Irp = IrpContext->Irp;
341 
342     DPRINT("NtfsCreateFile(%p, %p) called\n", DeviceObject, IrpContext);
343 
344     DeviceExt = DeviceObject->DeviceExtension;
345     ASSERT(DeviceExt);
346     Stack = IoGetCurrentIrpStackLocation(Irp);
347     ASSERT(Stack);
348 
349     RequestedDisposition = ((Stack->Parameters.Create.Options >> 24) & 0xff);
350     RequestedOptions = Stack->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS;
351 //  PagingFileCreate = (Stack->Flags & SL_OPEN_PAGING_FILE) ? TRUE : FALSE;
352     if (RequestedOptions & FILE_DIRECTORY_FILE &&
353         RequestedDisposition == FILE_SUPERSEDE)
354     {
355         return STATUS_INVALID_PARAMETER;
356     }
357 
358     /* Deny create if the volume is locked */
359     if (DeviceExt->Flags & VCB_VOLUME_LOCKED)
360     {
361         return STATUS_ACCESS_DENIED;
362     }
363 
364     FileObject = Stack->FileObject;
365 
366     if ((RequestedOptions & FILE_OPEN_BY_FILE_ID) == FILE_OPEN_BY_FILE_ID)
367     {
368         ULONGLONG MFTId;
369 
370         if (FileObject->FileName.Length != sizeof(ULONGLONG))
371             return STATUS_INVALID_PARAMETER;
372 
373         MFTId = (*(PULONGLONG)FileObject->FileName.Buffer) & NTFS_MFT_MASK;
374         if (MFTId < NTFS_FILE_FIRST_USER_FILE)
375         {
376             Status = NtfsOpenFileById(DeviceExt, FileObject, MFTId, &Fcb);
377         }
378         else
379         {
380             Status = NtfsMoonWalkID(DeviceExt, MFTId, &FullPath);
381         }
382 
383         if (!NT_SUCCESS(Status))
384         {
385             return Status;
386         }
387 
388         DPRINT1("Open by ID: %I64x -> %wZ\n", (*(PULONGLONG)FileObject->FileName.Buffer) & NTFS_MFT_MASK, &FullPath);
389     }
390 
391     /* This a open operation for the volume itself */
392     if (FileObject->FileName.Length == 0 &&
393         (FileObject->RelatedFileObject == NULL || FileObject->RelatedFileObject->FsContext2 != NULL))
394     {
395         if (RequestedDisposition != FILE_OPEN &&
396             RequestedDisposition != FILE_OPEN_IF)
397         {
398             return STATUS_ACCESS_DENIED;
399         }
400 
401         if (RequestedOptions & FILE_DIRECTORY_FILE)
402         {
403             return STATUS_NOT_A_DIRECTORY;
404         }
405 
406         NtfsAttachFCBToFileObject(DeviceExt, DeviceExt->VolumeFcb, FileObject);
407         DeviceExt->VolumeFcb->RefCount++;
408 
409         Irp->IoStatus.Information = FILE_OPENED;
410         return STATUS_SUCCESS;
411     }
412 
413     if (Fcb == NULL)
414     {
415         Status = NtfsOpenFile(DeviceExt,
416                               FileObject,
417                               ((RequestedOptions & FILE_OPEN_BY_FILE_ID) ? FullPath.Buffer : FileObject->FileName.Buffer),
418                               BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE),
419                               &Fcb);
420 
421         if (RequestedOptions & FILE_OPEN_BY_FILE_ID)
422         {
423             ExFreePoolWithTag(FullPath.Buffer, TAG_NTFS);
424         }
425     }
426 
427     if (NT_SUCCESS(Status))
428     {
429         if (RequestedDisposition == FILE_CREATE)
430         {
431             Irp->IoStatus.Information = FILE_EXISTS;
432             NtfsCloseFile(DeviceExt, FileObject);
433             return STATUS_OBJECT_NAME_COLLISION;
434         }
435 
436         if (RequestedOptions & FILE_NON_DIRECTORY_FILE &&
437             NtfsFCBIsDirectory(Fcb))
438         {
439             NtfsCloseFile(DeviceExt, FileObject);
440             return STATUS_FILE_IS_A_DIRECTORY;
441         }
442 
443         if (RequestedOptions & FILE_DIRECTORY_FILE &&
444             !NtfsFCBIsDirectory(Fcb))
445         {
446             NtfsCloseFile(DeviceExt, FileObject);
447             return STATUS_NOT_A_DIRECTORY;
448         }
449 
450         /*
451          * If it is a reparse point & FILE_OPEN_REPARSE_POINT, then allow opening it
452          * as a normal file.
453          * Otherwise, attempt to read reparse data and hand them to the Io manager
454          * with status reparse to force a reparse.
455          */
456         if (NtfsFCBIsReparsePoint(Fcb) &&
457             ((RequestedOptions & FILE_OPEN_REPARSE_POINT) != FILE_OPEN_REPARSE_POINT))
458         {
459             PREPARSE_DATA_BUFFER ReparseData = NULL;
460 
461             Status = NtfsReadFCBAttribute(DeviceExt, Fcb,
462                                           AttributeReparsePoint, L"", 0,
463                                           (PVOID *)&Irp->Tail.Overlay.AuxiliaryBuffer);
464             if (NT_SUCCESS(Status))
465             {
466                 ReparseData = (PREPARSE_DATA_BUFFER)Irp->Tail.Overlay.AuxiliaryBuffer;
467                 if (ReparseData->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
468                 {
469                     Status = STATUS_REPARSE;
470                 }
471                 else
472                 {
473                     Status = STATUS_NOT_IMPLEMENTED;
474                     ExFreePoolWithTag(ReparseData, TAG_NTFS);
475                 }
476             }
477 
478             Irp->IoStatus.Information = ((Status == STATUS_REPARSE) ? ReparseData->ReparseTag : 0);
479 
480             NtfsCloseFile(DeviceExt, FileObject);
481             return Status;
482         }
483 
484         if (RequestedDisposition == FILE_OVERWRITE ||
485             RequestedDisposition == FILE_OVERWRITE_IF ||
486             RequestedDisposition == FILE_SUPERSEDE)
487         {
488             PFILE_RECORD_HEADER fileRecord = NULL;
489             PNTFS_ATTR_CONTEXT dataContext = NULL;
490             ULONG DataAttributeOffset;
491             LARGE_INTEGER Zero;
492             Zero.QuadPart = 0;
493 
494             if (!NtfsGlobalData->EnableWriteSupport)
495             {
496                 DPRINT1("NTFS write-support is EXPERIMENTAL and is disabled by default!\n");
497                 NtfsCloseFile(DeviceExt, FileObject);
498                 return STATUS_ACCESS_DENIED;
499             }
500 
501             // TODO: check for appropriate access
502 
503             ExAcquireResourceExclusiveLite(&(Fcb->MainResource), TRUE);
504 
505             fileRecord = ExAllocateFromNPagedLookasideList(&Fcb->Vcb->FileRecLookasideList);
506             if (fileRecord)
507             {
508 
509                 Status = ReadFileRecord(Fcb->Vcb,
510                                         Fcb->MFTIndex,
511                                         fileRecord);
512                 if (!NT_SUCCESS(Status))
513                     goto DoneOverwriting;
514 
515                 // find the data attribute and set it's length to 0 (TODO: Handle Alternate Data Streams)
516                 Status = FindAttribute(Fcb->Vcb, fileRecord, AttributeData, L"", 0, &dataContext, &DataAttributeOffset);
517                 if (!NT_SUCCESS(Status))
518                     goto DoneOverwriting;
519 
520                 Status = SetAttributeDataLength(FileObject, Fcb, dataContext, DataAttributeOffset, fileRecord, &Zero);
521             }
522             else
523             {
524                 Status = STATUS_NO_MEMORY;
525             }
526 
527         DoneOverwriting:
528             if (fileRecord)
529                 ExFreeToNPagedLookasideList(&Fcb->Vcb->FileRecLookasideList, fileRecord);
530             if (dataContext)
531                 ReleaseAttributeContext(dataContext);
532 
533             ExReleaseResourceLite(&(Fcb->MainResource));
534 
535             if (!NT_SUCCESS(Status))
536             {
537                 NtfsCloseFile(DeviceExt, FileObject);
538                 return Status;
539             }
540 
541             if (RequestedDisposition == FILE_SUPERSEDE)
542             {
543                 Irp->IoStatus.Information = FILE_SUPERSEDED;
544             }
545             else
546             {
547                 Irp->IoStatus.Information = FILE_OVERWRITTEN;
548             }
549         }
550     }
551     else
552     {
553         /* HUGLY HACK: Can't create new files yet... */
554         if (RequestedDisposition == FILE_CREATE ||
555             RequestedDisposition == FILE_OPEN_IF ||
556             RequestedDisposition == FILE_OVERWRITE_IF ||
557             RequestedDisposition == FILE_SUPERSEDE)
558         {
559             if (!NtfsGlobalData->EnableWriteSupport)
560             {
561                 DPRINT1("NTFS write-support is EXPERIMENTAL and is disabled by default!\n");
562                 NtfsCloseFile(DeviceExt, FileObject);
563                 return STATUS_ACCESS_DENIED;
564             }
565 
566             // Was the user trying to create a directory?
567             if (RequestedOptions & FILE_DIRECTORY_FILE)
568             {
569                 // Create the directory on disk
570                 Status = NtfsCreateDirectory(DeviceExt,
571                                              FileObject,
572                                              BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE),
573                                              BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT));
574             }
575             else
576             {
577                 // Create the file record on disk
578                 Status = NtfsCreateFileRecord(DeviceExt,
579                                               FileObject,
580                                               BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE),
581                                               BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT));
582             }
583 
584             if (!NT_SUCCESS(Status))
585             {
586                 DPRINT1("ERROR: Couldn't create file record!\n");
587                 return Status;
588             }
589 
590             // Before we open the file/directory we just created, we need to change the disposition (upper 8 bits of ULONG)
591             // from create to open, since we already created the file
592             Stack->Parameters.Create.Options = (ULONG)FILE_OPEN << 24 | RequestedOptions;
593 
594             // Now we should be able to open the file using NtfsCreateFile()
595             Status = NtfsCreateFile(DeviceObject, IrpContext);
596             if (NT_SUCCESS(Status))
597             {
598                 // We need to change Irp->IoStatus.Information to reflect creation
599                 Irp->IoStatus.Information = FILE_CREATED;
600             }
601             return Status;
602         }
603     }
604 
605     if (NT_SUCCESS(Status))
606     {
607         Fcb->OpenHandleCount++;
608         DeviceExt->OpenHandleCount++;
609     }
610 
611     /*
612      * If the directory containing the file to open doesn't exist then
613      * fail immediately
614      */
615     Irp->IoStatus.Information = (NT_SUCCESS(Status)) ? FILE_OPENED : 0;
616 
617     return Status;
618 }
619 
620 
621 NTSTATUS
622 NtfsCreate(PNTFS_IRP_CONTEXT IrpContext)
623 {
624     PDEVICE_EXTENSION DeviceExt;
625     NTSTATUS Status;
626     PDEVICE_OBJECT DeviceObject;
627 
628     DeviceObject = IrpContext->DeviceObject;
629     if (DeviceObject == NtfsGlobalData->DeviceObject)
630     {
631         /* DeviceObject represents FileSystem instead of logical volume */
632         DPRINT("Opening file system\n");
633         IrpContext->Irp->IoStatus.Information = FILE_OPENED;
634         return STATUS_SUCCESS;
635     }
636 
637     DeviceExt = DeviceObject->DeviceExtension;
638 
639     if (!(IrpContext->Flags & IRPCONTEXT_CANWAIT))
640     {
641         return NtfsMarkIrpContextForQueue(IrpContext);
642     }
643 
644     ExAcquireResourceExclusiveLite(&DeviceExt->DirResource,
645                                    TRUE);
646     Status = NtfsCreateFile(DeviceObject,
647                             IrpContext);
648     ExReleaseResourceLite(&DeviceExt->DirResource);
649 
650     return Status;
651 }
652 
653 /**
654 * @name NtfsCreateDirectory()
655 * @implemented
656 *
657 * Creates a file record for a new directory and saves it to the MFT. Adds the filename attribute of the
658 * created directory to the parent directory's index.
659 *
660 * @param DeviceExt
661 * Points to the target disk's DEVICE_EXTENSION
662 *
663 * @param FileObject
664 * Pointer to a FILE_OBJECT describing the directory to be created
665 *
666 * @param CaseSensitive
667 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
668 * if an application created the folder with the FILE_FLAG_POSIX_SEMANTICS flag.
669 *
670 * @param CanWait
671 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
672 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
673 *
674 * @return
675 * STATUS_SUCCESS on success.
676 * STATUS_INSUFFICIENT_RESOURCES if unable to allocate memory for the file record.
677 * STATUS_CANT_WAIT if CanWait was FALSE and the function needed to resize the MFT but
678 * couldn't get immediate, exclusive access to it.
679 */
680 NTSTATUS
681 NtfsCreateDirectory(PDEVICE_EXTENSION DeviceExt,
682                     PFILE_OBJECT FileObject,
683                     BOOLEAN CaseSensitive,
684                     BOOLEAN CanWait)
685 {
686 
687     NTSTATUS Status = STATUS_SUCCESS;
688     PFILE_RECORD_HEADER FileRecord;
689     PNTFS_ATTR_RECORD NextAttribute;
690     PFILENAME_ATTRIBUTE FilenameAttribute;
691     ULONGLONG ParentMftIndex;
692     ULONGLONG FileMftIndex;
693     PB_TREE Tree;
694     PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
695     ULONG MaxIndexRootSize;
696     ULONG RootLength;
697 
698     DPRINT("NtfsCreateFileRecord(%p, %p, %s, %s)\n",
699             DeviceExt,
700             FileObject,
701             CaseSensitive ? "TRUE" : "FALSE",
702             CanWait ? "TRUE" : "FALSE");
703 
704     // Start with an empty file record
705     FileRecord = NtfsCreateEmptyFileRecord(DeviceExt);
706     if (!FileRecord)
707     {
708         DPRINT1("ERROR: Unable to allocate memory for file record!\n");
709         return STATUS_INSUFFICIENT_RESOURCES;
710     }
711 
712     // Set the directory flag
713     FileRecord->Flags |= FRH_DIRECTORY;
714 
715     // find where the first attribute will be added
716     NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
717 
718     // add first attribute, $STANDARD_INFORMATION
719     AddStandardInformation(FileRecord, NextAttribute);
720 
721     // advance NextAttribute pointer to the next attribute
722     NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length);
723 
724     // Add the $FILE_NAME attribute
725     AddFileName(FileRecord, NextAttribute, DeviceExt, FileObject, CaseSensitive, &ParentMftIndex);
726 
727     // save a pointer to the filename attribute
728     FilenameAttribute = (PFILENAME_ATTRIBUTE)((ULONG_PTR)NextAttribute + NextAttribute->Resident.ValueOffset);
729 
730     // advance NextAttribute pointer to the next attribute
731     NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length);
732 
733     // Create an empty b-tree to represent our new index
734     Status = CreateEmptyBTree(&Tree);
735     if (!NT_SUCCESS(Status))
736     {
737         DPRINT1("ERROR: Failed to create empty B-Tree!\n");
738         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
739         return Status;
740     }
741 
742     // Calculate maximum size of index root
743     MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord
744                        - ((ULONG_PTR)NextAttribute - (ULONG_PTR)FileRecord)
745                        - sizeof(ULONG) * 2;
746 
747     // Create a new index record from the tree
748     Status = CreateIndexRootFromBTree(DeviceExt,
749                                       Tree,
750                                       MaxIndexRootSize,
751                                       &NewIndexRoot,
752                                       &RootLength);
753     if (!NT_SUCCESS(Status))
754     {
755         DPRINT1("ERROR: Unable to create empty index root!\n");
756         DestroyBTree(Tree);
757         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
758         return Status;
759     }
760 
761     // We're done with the B-Tree
762     DestroyBTree(Tree);
763 
764     // add the $INDEX_ROOT attribute
765     Status = AddIndexRoot(DeviceExt, FileRecord, NextAttribute, NewIndexRoot, RootLength, L"$I30", 4);
766     if (!NT_SUCCESS(Status))
767     {
768         DPRINT1("ERROR: Failed to add index root to new file record!\n");
769         ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
770         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
771         return Status;
772     }
773 
774 
775 #ifndef NDEBUG
776     NtfsDumpFileRecord(DeviceExt, FileRecord);
777 #endif
778 
779     // Now that we've built the file record in memory, we need to store it in the MFT.
780     Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex, CanWait);
781     if (NT_SUCCESS(Status))
782     {
783         // The highest 2 bytes should be the sequence number, unless the parent happens to be root
784         if (FileMftIndex == NTFS_FILE_ROOT)
785             FileMftIndex = FileMftIndex + ((ULONGLONG)NTFS_FILE_ROOT << 48);
786         else
787             FileMftIndex = FileMftIndex + ((ULONGLONG)FileRecord->SequenceNumber << 48);
788 
789         DPRINT1("New File Reference: 0x%016I64x\n", FileMftIndex);
790 
791         // Add the filename attribute to the filename-index of the parent directory
792         Status = NtfsAddFilenameToDirectory(DeviceExt,
793                                             ParentMftIndex,
794                                             FileMftIndex,
795                                             FilenameAttribute,
796                                             CaseSensitive);
797     }
798 
799     ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
800     ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
801 
802     return Status;
803 }
804 
805 /**
806 * @name NtfsCreateEmptyFileRecord
807 * @implemented
808 *
809 * Creates a new, empty file record, with no attributes.
810 *
811 * @param DeviceExt
812 * Pointer to the DEVICE_EXTENSION of the target volume the file record will be stored on.
813 *
814 * @return
815 * A pointer to the newly-created FILE_RECORD_HEADER if the function succeeds, NULL otherwise.
816 */
817 PFILE_RECORD_HEADER
818 NtfsCreateEmptyFileRecord(PDEVICE_EXTENSION DeviceExt)
819 {
820     PFILE_RECORD_HEADER FileRecord;
821     PNTFS_ATTR_RECORD NextAttribute;
822 
823     DPRINT("NtfsCreateEmptyFileRecord(%p)\n", DeviceExt);
824 
825     // allocate memory for file record
826     FileRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
827     if (!FileRecord)
828     {
829         DPRINT1("ERROR: Unable to allocate memory for file record!\n");
830         return NULL;
831     }
832 
833     RtlZeroMemory(FileRecord, DeviceExt->NtfsInfo.BytesPerFileRecord);
834 
835     FileRecord->Ntfs.Type = NRH_FILE_TYPE;
836 
837     // calculate USA offset and count
838     FileRecord->Ntfs.UsaOffset = FIELD_OFFSET(FILE_RECORD_HEADER, MFTRecordNumber) + sizeof(ULONG);
839 
840     // size of USA (in ULONG's) will be 1 (for USA number) + 1 for every sector the file record uses
841     FileRecord->BytesAllocated = DeviceExt->NtfsInfo.BytesPerFileRecord;
842     FileRecord->Ntfs.UsaCount = (FileRecord->BytesAllocated / DeviceExt->NtfsInfo.BytesPerSector) + 1;
843 
844     // setup other file record fields
845     FileRecord->SequenceNumber = 1;
846     FileRecord->AttributeOffset = FileRecord->Ntfs.UsaOffset + (2 * FileRecord->Ntfs.UsaCount);
847     FileRecord->AttributeOffset = ALIGN_UP_BY(FileRecord->AttributeOffset, ATTR_RECORD_ALIGNMENT);
848     FileRecord->Flags = FRH_IN_USE;
849     FileRecord->BytesInUse = FileRecord->AttributeOffset + sizeof(ULONG) * 2;
850 
851     // find where the first attribute will be added
852     NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
853 
854     // mark the (temporary) end of the file-record
855     NextAttribute->Type = AttributeEnd;
856     NextAttribute->Length = FILE_RECORD_END;
857 
858     return FileRecord;
859 }
860 
861 
862 /**
863 * @name NtfsCreateFileRecord()
864 * @implemented
865 *
866 * Creates a file record and saves it to the MFT. Adds the filename attribute of the
867 * created file to the parent directory's index.
868 *
869 * @param DeviceExt
870 * Points to the target disk's DEVICE_EXTENSION
871 *
872 * @param FileObject
873 * Pointer to a FILE_OBJECT describing the file to be created
874 *
875 * @param CanWait
876 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
877 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
878 *
879 * @return
880 * STATUS_SUCCESS on success.
881 * STATUS_INSUFFICIENT_RESOURCES if unable to allocate memory for the file record.
882 * STATUS_CANT_WAIT if CanWait was FALSE and the function needed to resize the MFT but
883 * couldn't get immediate, exclusive access to it.
884 */
885 NTSTATUS
886 NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
887                      PFILE_OBJECT FileObject,
888                      BOOLEAN CaseSensitive,
889                      BOOLEAN CanWait)
890 {
891     NTSTATUS Status = STATUS_SUCCESS;
892     PFILE_RECORD_HEADER FileRecord;
893     PNTFS_ATTR_RECORD NextAttribute;
894     PFILENAME_ATTRIBUTE FilenameAttribute;
895     ULONGLONG ParentMftIndex;
896     ULONGLONG FileMftIndex;
897 
898     DPRINT("NtfsCreateFileRecord(%p, %p, %s, %s)\n",
899             DeviceExt,
900             FileObject,
901             CaseSensitive ? "TRUE" : "FALSE",
902             CanWait ? "TRUE" : "FALSE");
903 
904     // allocate memory for file record
905     FileRecord = NtfsCreateEmptyFileRecord(DeviceExt);
906     if (!FileRecord)
907     {
908         DPRINT1("ERROR: Unable to allocate memory for file record!\n");
909         return STATUS_INSUFFICIENT_RESOURCES;
910     }
911 
912     // find where the first attribute will be added
913     NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
914 
915     // add first attribute, $STANDARD_INFORMATION
916     AddStandardInformation(FileRecord, NextAttribute);
917 
918     // advance NextAttribute pointer to the next attribute
919     NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length);
920 
921     // Add the $FILE_NAME attribute
922     AddFileName(FileRecord, NextAttribute, DeviceExt, FileObject, CaseSensitive, &ParentMftIndex);
923 
924     // save a pointer to the filename attribute
925     FilenameAttribute = (PFILENAME_ATTRIBUTE)((ULONG_PTR)NextAttribute + NextAttribute->Resident.ValueOffset);
926 
927     // advance NextAttribute pointer to the next attribute
928     NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length);
929 
930     // add the $DATA attribute
931     AddData(FileRecord, NextAttribute);
932 
933 #ifndef NDEBUG
934     // dump file record in memory (for debugging)
935     NtfsDumpFileRecord(DeviceExt, FileRecord);
936 #endif
937 
938     // Now that we've built the file record in memory, we need to store it in the MFT.
939     Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex, CanWait);
940     if (NT_SUCCESS(Status))
941     {
942         // The highest 2 bytes should be the sequence number, unless the parent happens to be root
943         if (FileMftIndex == NTFS_FILE_ROOT)
944             FileMftIndex = FileMftIndex + ((ULONGLONG)NTFS_FILE_ROOT << 48);
945         else
946             FileMftIndex = FileMftIndex + ((ULONGLONG)FileRecord->SequenceNumber << 48);
947 
948         DPRINT1("New File Reference: 0x%016I64x\n", FileMftIndex);
949 
950         // Add the filename attribute to the filename-index of the parent directory
951         Status = NtfsAddFilenameToDirectory(DeviceExt,
952                                             ParentMftIndex,
953                                             FileMftIndex,
954                                             FilenameAttribute,
955                                             CaseSensitive);
956     }
957 
958     ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
959 
960     return Status;
961 }
962 
963 /* EOF */
964