You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
15779 lines
531 KiB
15779 lines
531 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
Create.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the File Create routine for Ntfs called by the
|
|
dispatch driver.
|
|
|
|
Author:
|
|
|
|
Brian Andrew [BrianAn] 10-Dec-1991
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "NtfsProc.h"
|
|
#ifdef NTFSDBG
|
|
#include "lockorder.h"
|
|
#endif
|
|
|
|
//
|
|
// The local debug trace level
|
|
//
|
|
|
|
#define Dbg (DEBUG_TRACE_CREATE)
|
|
|
|
//
|
|
// Define a tag for general pool allocations from this module
|
|
//
|
|
|
|
#undef MODULE_POOL_TAG
|
|
#define MODULE_POOL_TAG ('CFtN')
|
|
|
|
//
|
|
// Check for stack usage prior to the create call.
|
|
//
|
|
|
|
#ifdef _X86_
|
|
#define OVERFLOW_CREATE_THRESHHOLD (0x1200)
|
|
#else
|
|
#define OVERFLOW_CREATE_THRESHHOLD (0x1B00)
|
|
#endif // _X86_
|
|
|
|
#ifdef BRIANDBG
|
|
BOOLEAN NtfsCreateAllSparse = FALSE;
|
|
BOOLEAN NtfsTraverseAccessCheck = FALSE;
|
|
|
|
UNICODE_STRING NtfsTestName = {0x0,0x40,L" "};
|
|
|
|
VOID
|
|
NtfsTestOpenName (
|
|
IN PFILE_OBJECT FileObject
|
|
);
|
|
#endif
|
|
|
|
//
|
|
// Local macros
|
|
//
|
|
|
|
//
|
|
// VOID
|
|
// NtfsPrepareForIrpCompletion (
|
|
// IN PIRP_CONTEXT IrpContext,
|
|
// IN PIRP Irp,
|
|
// IN PNTFS_COMPLETION_CONTEXT Context
|
|
// )
|
|
//
|
|
|
|
#define NtfsPrepareForIrpCompletion(IC,I,C) { \
|
|
(C)->IrpContext = (IC); \
|
|
IoCopyCurrentIrpStackLocationToNext( (I) ); \
|
|
IoSetCompletionRoutine( (I), \
|
|
NtfsCreateCompletionRoutine, \
|
|
(C), \
|
|
TRUE, \
|
|
TRUE, \
|
|
TRUE ); \
|
|
IoSetNextIrpStackLocation( (I) ); \
|
|
}
|
|
|
|
//
|
|
// BOOLEAN
|
|
// NtfsVerifyNameIsDirectory (
|
|
// IN PIRP_CONTEXT IrpContext,
|
|
// IN PUNICODE_STRING AttrName,
|
|
// IN PUNICODE_STRING AttrCodeName
|
|
// )
|
|
//
|
|
|
|
#define NtfsVerifyNameIsDirectory( IC, AN, ACN ) \
|
|
( ( ((ACN)->Length == 0) || \
|
|
NtfsAreNamesEqual( IC->Vcb->UpcaseTable, ACN, &NtfsIndexAllocation, TRUE )) && \
|
|
( ((AN)->Length == 0) || \
|
|
NtfsAreNamesEqual( IC->Vcb->UpcaseTable, AN, &NtfsFileNameIndex, TRUE )))
|
|
|
|
//
|
|
// BOOLEAN
|
|
// NtfsVerifyNameIsBitmap (
|
|
// IN PIRP_CONTEXT IrpContext,
|
|
// IN PUNICODE_STRING AttrName,
|
|
// IN PUNICODE_STRING AttrCodeName
|
|
// )
|
|
//
|
|
|
|
#define NtfsVerifyNameIsBitmap( IC, AN, ACN ) \
|
|
( ( ((ACN)->Length == 0) || \
|
|
NtfsAreNamesEqual( IC->Vcb->UpcaseTable, ACN, &NtfsBitmapString, TRUE )) && \
|
|
\
|
|
( ((AN)->Length == 0) || \
|
|
NtfsAreNamesEqual( IC->Vcb->UpcaseTable, AN, &NtfsFileNameIndex, TRUE )))
|
|
|
|
//
|
|
// BOOLEAN
|
|
// NtfsVerifyNameIsAttributeList (
|
|
// IN PIRP_CONTEXT IrpContext,
|
|
// IN PUNICODE_STRING AttrName,
|
|
// IN PUNICODE_STRING AttrCodeName
|
|
// )
|
|
//
|
|
|
|
#define NtfsVerifyNameIsAttributeList( IC, AN, ACN ) \
|
|
( ((ACN)->Length != 0) && \
|
|
NtfsAreNamesEqual( IC->Vcb->UpcaseTable, ACN, &NtfsAttrListString, TRUE ))
|
|
|
|
//
|
|
// BOOLEAN
|
|
// NtfsVerifyNameIsReparsePoint (
|
|
// IN PIRP_CONTEXT IrpContext,
|
|
// IN PUNICODE_STRING AttrName,
|
|
// IN PUNICODE_STRING AttrCodeName
|
|
// )
|
|
//
|
|
|
|
#define NtfsVerifyNameIsReparsePoint( IC, AN, ACN ) \
|
|
( ((ACN)->Length != 0) && \
|
|
NtfsAreNamesEqual( IC->Vcb->UpcaseTable, ACN, &NtfsReparsePointString, TRUE ))
|
|
|
|
//
|
|
// VOID
|
|
// NtfsRaiseToPost (
|
|
// IN PIRP_CONTEXT IrpContext
|
|
// )
|
|
//
|
|
|
|
#define NtfsRaiseToPost( IC ) \
|
|
SetFlag( (IC)->Flags, IRP_CONTEXT_FLAG_FORCE_POST ); \
|
|
if ((IC)->Union.OplockCleanup->CompletionContext != NULL) { \
|
|
NtfsPrepareForIrpCompletion( (IC), \
|
|
(IC)->OriginatingIrp, \
|
|
(IC)->Union.OplockCleanup->CompletionContext ); \
|
|
} \
|
|
NtfsRaiseStatus( (IC), STATUS_CANT_WAIT, NULL, NULL );
|
|
|
|
//
|
|
// These are the flags used by the I/O system in deciding whether
|
|
// to apply the share access modes.
|
|
//
|
|
|
|
#define NtfsAccessDataFlags ( \
|
|
FILE_EXECUTE \
|
|
| FILE_READ_DATA \
|
|
| FILE_WRITE_DATA \
|
|
| FILE_APPEND_DATA \
|
|
| DELETE \
|
|
)
|
|
|
|
#define NtfsIsStreamNew( IrpInfo ) \
|
|
( (IrpInfo == FILE_CREATED) || \
|
|
(IrpInfo == FILE_SUPERSEDED) || \
|
|
(IrpInfo == FILE_OVERWRITTEN) )
|
|
|
|
//
|
|
// Subset of flags used by IO system to determine whether user has used either
|
|
// BACKUP or RESTORE privilege to get access to file.
|
|
//
|
|
|
|
#define NTFS_REQUIRES_BACKUP (FILE_READ_DATA | FILE_READ_ATTRIBUTES)
|
|
#define NTFS_REQUIRES_RESTORE (FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | DELETE)
|
|
|
|
//
|
|
// Local definitions
|
|
//
|
|
|
|
typedef enum _SHARE_MODIFICATION_TYPE {
|
|
|
|
CheckShareAccess,
|
|
UpdateShareAccess,
|
|
SetShareAccess,
|
|
RecheckShareAccess
|
|
|
|
} SHARE_MODIFICATION_TYPE, *PSHARE_MODIFICATION_TYPE;
|
|
|
|
UNICODE_STRING NtfsVolumeDasd = CONSTANT_UNICODE_STRING ( L"$Volume" );
|
|
|
|
LUID NtfsSecurityPrivilege = { SE_SECURITY_PRIVILEGE, 0 };
|
|
|
|
//
|
|
// VOID
|
|
// NtfsBackoutFailedOpens (
|
|
// IN PIRP_CONTEXT IrpContext,
|
|
// IN PFILE_OBJECT FileObject,
|
|
// IN PFCB ThisFcb,
|
|
// IN PSCB ThisScb OPTIONAL,
|
|
// IN PCCB ThisCcb OPTIONAL
|
|
// );
|
|
//
|
|
|
|
#define NtfsBackoutFailedOpens(IC,FO,F,S,C) { \
|
|
if (((S) != NULL) && ((C) != NULL)) { \
|
|
\
|
|
NtfsBackoutFailedOpensPriv( IC, FO, F, S, C ); \
|
|
} \
|
|
} \
|
|
|
|
//
|
|
// Local support routines.
|
|
//
|
|
|
|
VOID
|
|
NtfsUpdateAllInformation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFCB Fcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN PSCB ParentScb,
|
|
IN PLCB Lcb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsOpenFcbById (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PVCB Vcb,
|
|
IN PLCB ParentLcb OPTIONAL,
|
|
IN FILE_REFERENCE FileReference,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrCode,
|
|
IN PCREATE_CONTEXT CreateContext
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsOpenExistingPrefixFcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB Lcb OPTIONAL,
|
|
IN ULONG FullPathNameLength,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrCode,
|
|
IN PCREATE_CONTEXT CreateContext
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsOpenTargetDirectory (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PFCB ThisFcb,
|
|
IN PLCB ParentLcb OPTIONAL,
|
|
IN OUT PUNICODE_STRING FullPathName,
|
|
IN ULONG FinalNameLength,
|
|
IN PCREATE_CONTEXT CreateContext
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsOpenFile (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PSCB ParentScb,
|
|
IN PINDEX_ENTRY IndexEntry,
|
|
IN UNICODE_STRING FullPathName,
|
|
IN UNICODE_STRING FinalName,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN PQUICK_INDEX QuickIndex,
|
|
IN PCREATE_CONTEXT CreateContext,
|
|
OUT PLCB *LcbForTeardown
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsCreateNewFile (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PSCB ParentScb,
|
|
IN PFILE_NAME FileNameAttr,
|
|
IN UNICODE_STRING FullPathName,
|
|
IN UNICODE_STRING FinalName,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN PINDEX_CONTEXT *IndexContext,
|
|
IN PCREATE_CONTEXT CreateContext,
|
|
OUT PLCB *LcbForTeardown
|
|
);
|
|
|
|
PLCB
|
|
NtfsOpenSubdirectory (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB ParentScb,
|
|
IN PFILE_REFERENCE FileReference,
|
|
IN UNICODE_STRING FileName,
|
|
IN UCHAR FileNameFlags,
|
|
IN PCREATE_CONTEXT CreateContext,
|
|
OUT PLCB *LcbForTeardown
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsOpenAttributeInExistingFile (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFCB ThisFcb,
|
|
IN ULONG LastFileNameOffset,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN ULONG CcbFlags,
|
|
IN ULONG CreateFlags,
|
|
IN PVOID NetworkInfo OPTIONAL,
|
|
OUT PSCB *ThisScb,
|
|
OUT PCCB *ThisCcb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsOpenExistingAttr (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFCB ThisFcb,
|
|
IN ULONG LastFileNameOffset,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN ULONG CcbFlags,
|
|
IN ULONG CreateFlags,
|
|
IN BOOLEAN DirectoryOpen,
|
|
IN PVOID NetworkInfo OPTIONAL,
|
|
OUT PSCB *ThisScb,
|
|
OUT PCCB *ThisCcb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsOverwriteAttr (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFCB ThisFcb,
|
|
IN BOOLEAN Supersede,
|
|
IN ULONG LastFileNameOffset,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN ULONG CcbFlags,
|
|
IN ULONG CreateFlags,
|
|
OUT PSCB *ThisScb,
|
|
OUT PCCB *ThisCcb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsOpenNewAttr (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB ThisLcb,
|
|
IN PFCB ThisFcb,
|
|
IN ULONG LastFileNameOffset,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN LOGICAL CreateFile,
|
|
IN ULONG CcbFlags,
|
|
IN BOOLEAN LogIt,
|
|
IN ULONG CreateFlags,
|
|
OUT PSCB *ThisScb,
|
|
OUT PCCB *ThisCcb
|
|
);
|
|
|
|
BOOLEAN
|
|
NtfsParseNameForCreate (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN UNICODE_STRING String,
|
|
IN OUT PUNICODE_STRING FileObjectString,
|
|
IN OUT PUNICODE_STRING OriginalString,
|
|
IN OUT PUNICODE_STRING NewNameString,
|
|
IN PCREATE_CONTEXT CreateContext,
|
|
OUT PUNICODE_STRING AttrName,
|
|
OUT PATTRIBUTE_TYPE_CODE AttrCode
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsCheckValidAttributeAccess (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PVCB Vcb,
|
|
IN PDUPLICATED_INFORMATION Info OPTIONAL,
|
|
IN OUT PUNICODE_STRING AttrName,
|
|
IN OUT PATTRIBUTE_TYPE_CODE AttrCode,
|
|
IN ULONG CreateFlags,
|
|
OUT PULONG CcbFlags,
|
|
OUT PBOOLEAN IndexedAttribute
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsOpenAttributeCheck (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
OUT PSCB *ThisScb,
|
|
OUT PSHARE_MODIFICATION_TYPE ShareModificationType
|
|
);
|
|
|
|
VOID
|
|
NtfsAddEa (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PFCB ThisFcb,
|
|
IN PFILE_FULL_EA_INFORMATION EaBuffer OPTIONAL,
|
|
IN ULONG EaLength,
|
|
OUT PIO_STATUS_BLOCK Iosb
|
|
);
|
|
|
|
VOID
|
|
NtfsCreateAttribute (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN OUT PFCB ThisFcb,
|
|
IN OUT PSCB ThisScb,
|
|
IN PLCB ThisLcb,
|
|
IN LONGLONG AllocationSize,
|
|
IN BOOLEAN LogIt,
|
|
IN BOOLEAN ForceNonresident,
|
|
IN PUSHORT PreviousFlags OPTIONAL
|
|
);
|
|
|
|
VOID
|
|
NtfsRemoveDataAttributes (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB ThisFcb,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN ULONG LastFileNameOffset,
|
|
IN ULONG CreateFlags
|
|
);
|
|
|
|
VOID
|
|
NtfsRemoveReparsePoint (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB ThisFcb
|
|
);
|
|
|
|
VOID
|
|
NtfsReplaceAttribute (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PFCB ThisFcb,
|
|
IN PSCB ThisScb,
|
|
IN PLCB ThisLcb,
|
|
IN LONGLONG AllocationSize
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsOpenAttribute (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PVCB Vcb,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFCB ThisFcb,
|
|
IN ULONG LastFileNameOffset,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN SHARE_MODIFICATION_TYPE ShareModificationType,
|
|
IN TYPE_OF_OPEN TypeOfOpen,
|
|
IN LOGICAL CreateFile,
|
|
IN ULONG CcbFlags,
|
|
IN PVOID NetworkInfo OPTIONAL,
|
|
IN OUT PSCB *ThisScb,
|
|
OUT PCCB *ThisCcb
|
|
);
|
|
|
|
VOID
|
|
NtfsBackoutFailedOpensPriv (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFCB ThisFcb,
|
|
IN PSCB ThisScb,
|
|
IN PCCB ThisCcb
|
|
);
|
|
|
|
VOID
|
|
NtfsUpdateScbFromMemory (
|
|
IN OUT PSCB Scb,
|
|
IN POLD_SCB_SNAPSHOT ScbSizes
|
|
);
|
|
|
|
VOID
|
|
NtfsOplockPrePostIrp (
|
|
IN PVOID Context,
|
|
IN PIRP Irp
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsCreateCompletionRoutine (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp,
|
|
IN PVOID Contxt
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsCheckExistingFile (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFCB ThisFcb,
|
|
IN BOOLEAN Indexed,
|
|
IN ULONG CcbFlags
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsBreakBatchOplock (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PFCB ThisFcb,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
OUT PSCB *ThisScb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsCompleteLargeAllocation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PLCB Lcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN ULONG CreateFlags
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsEncryptionCreateCallback (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PSCB ThisScb,
|
|
IN PCCB ThisCcb,
|
|
IN PFCB ParentFcb,
|
|
IN PCREATE_CONTEXT CreateContext,
|
|
IN BOOLEAN CreateNewFile
|
|
);
|
|
|
|
VOID
|
|
NtfsPostProcessEncryptedCreate (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN ULONG EncryptionFileDirFlags,
|
|
IN ULONG FailedInPostCreateOnly
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsGetReparsePointValue (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
PIO_STACK_LOCATION IrpSp,
|
|
IN PFCB Fcb,
|
|
IN USHORT RemainingNameLength
|
|
);
|
|
|
|
BOOLEAN
|
|
NtfsCheckValidFileAccess(
|
|
IN PFCB ThisFcb,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
);
|
|
|
|
|
|
VOID
|
|
NtfsWaitForCreateEvent (
|
|
IN PIRP Irp,
|
|
IN PNTFS_COMPLETION_CONTEXT CompletionContextPointer
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsLookupObjectId (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PUNICODE_STRING FileName,
|
|
OUT PFILE_REFERENCE FileReference
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, NtfsAddEa)
|
|
#pragma alloc_text(PAGE, NtfsBackoutFailedOpensPriv)
|
|
#pragma alloc_text(PAGE, NtfsBreakBatchOplock)
|
|
#pragma alloc_text(PAGE, NtfsCheckExistingFile)
|
|
#pragma alloc_text(PAGE, NtfsCheckValidAttributeAccess)
|
|
#pragma alloc_text(PAGE, NtfsCheckValidFileAccess)
|
|
#pragma alloc_text(PAGE, NtfsCommonCreate)
|
|
#pragma alloc_text(PAGE, NtfsCommonVolumeOpen)
|
|
#pragma alloc_text(PAGE, NtfsCompleteLargeAllocation)
|
|
#pragma alloc_text(PAGE, NtfsCreateAttribute)
|
|
#pragma alloc_text(PAGE, NtfsCreateCompletionRoutine)
|
|
#pragma alloc_text(PAGE, NtfsCreateNewFile)
|
|
#pragma alloc_text(PAGE, NtfsEncryptionCreateCallback)
|
|
#pragma alloc_text(PAGE, NtfsFsdCreate)
|
|
#pragma alloc_text(PAGE, NtfsGetReparsePointValue)
|
|
#pragma alloc_text(PAGE, NtfsInitializeFcbAndStdInfo)
|
|
#pragma alloc_text(PAGE, NtfsLookupObjectId)
|
|
#pragma alloc_text(PAGE, NtfsNetworkOpenCreate)
|
|
#pragma alloc_text(PAGE, NtfsOpenAttribute)
|
|
#pragma alloc_text(PAGE, NtfsOpenAttributeCheck)
|
|
#pragma alloc_text(PAGE, NtfsOpenAttributeInExistingFile)
|
|
#pragma alloc_text(PAGE, NtfsOpenExistingAttr)
|
|
#pragma alloc_text(PAGE, NtfsOpenExistingPrefixFcb)
|
|
#pragma alloc_text(PAGE, NtfsOpenFcbById)
|
|
#pragma alloc_text(PAGE, NtfsOpenFile)
|
|
#pragma alloc_text(PAGE, NtfsOpenNewAttr)
|
|
#pragma alloc_text(PAGE, NtfsOpenSubdirectory)
|
|
#pragma alloc_text(PAGE, NtfsOpenTargetDirectory)
|
|
#pragma alloc_text(PAGE, NtfsOplockPrePostIrp)
|
|
#pragma alloc_text(PAGE, NtfsOverwriteAttr)
|
|
#pragma alloc_text(PAGE, NtfsParseNameForCreate)
|
|
#pragma alloc_text(PAGE, NtfsPostProcessEncryptedCreate)
|
|
#pragma alloc_text(PAGE, NtfsRemoveDataAttributes)
|
|
#pragma alloc_text(PAGE, NtfsRemoveReparsePoint)
|
|
#pragma alloc_text(PAGE, NtfsReplaceAttribute)
|
|
#pragma alloc_text(PAGE, NtfsTryOpenFcb)
|
|
#pragma alloc_text(PAGE, NtfsUpdateScbFromMemory)
|
|
#pragma alloc_text(PAGE, NtfsUpdateAllInformation)
|
|
#pragma alloc_text(PAGE, NtfsWaitForCreateEvent)
|
|
#endif
|
|
|
|
|
|
VOID
|
|
NtfsWaitForCreateEvent (
|
|
IN PIRP Irp,
|
|
IN PNTFS_COMPLETION_CONTEXT CompletionContextPointer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine waits for the signal from the async thread that a piece of create is done
|
|
for example if we posted the create for more stack space or are waiting for efs
|
|
|
|
Arguments:
|
|
|
|
CompletionContextPointer - context containing event to wait for
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the wait
|
|
|
|
--*/
|
|
|
|
{
|
|
KPROCESSOR_MODE WaitMode = UserMode;
|
|
LOGICAL PrevStackSwapEnable;
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Don't let the stack get swapped out in case we post.
|
|
//
|
|
|
|
PrevStackSwapEnable = KeSetKernelStackSwapEnable( FALSE );
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
//
|
|
// Retry the wait until it completes successfully.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// Test the wait status to see if someone is trying to rundown the current
|
|
// thread.
|
|
//
|
|
|
|
Status = KeWaitForSingleObject( &CompletionContextPointer->Event,
|
|
Executive,
|
|
WaitMode,
|
|
FALSE,
|
|
NULL );
|
|
|
|
if (Status == STATUS_SUCCESS) {
|
|
|
|
KeClearEvent( &CompletionContextPointer->Event );
|
|
break;
|
|
}
|
|
|
|
if (Status != STATUS_KERNEL_APC) {
|
|
|
|
//
|
|
// In the (unlikely) event that the Irp we want to cancel is
|
|
// waiting for the encryption driver to return from the post
|
|
// create callout, we'll deadlock in here. By signalling the
|
|
// EncryptionPending event, we're certain that any threads
|
|
// in that state will run, and check whether their irp has been
|
|
// cancelled. It's harmless to signal this event, since any
|
|
// requests still actually waiting for the post create callout
|
|
// to return will still see the encryption pending bit set
|
|
// in their FCB and know to retry.
|
|
//
|
|
|
|
IoCancelIrp( Irp );
|
|
KeSetEvent( &NtfsEncryptionPendingEvent, 0, FALSE );
|
|
WaitMode = KernelMode;
|
|
}
|
|
}
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
//
|
|
// Restore the previous value for the stack swap.
|
|
//
|
|
|
|
if (PrevStackSwapEnable) {
|
|
|
|
KeSetKernelStackSwapEnable( TRUE );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
NtfsFsdCreate (
|
|
IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine implements the FSD part of Create.
|
|
|
|
Arguments:
|
|
|
|
VolumeDeviceObject - Supplies the volume device object where the
|
|
file exists
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The FSD status for the IRP
|
|
|
|
--*/
|
|
|
|
{
|
|
TOP_LEVEL_CONTEXT TopLevelContext;
|
|
PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PIRP_CONTEXT IrpContext;
|
|
LOGICAL CallPostCreate = FALSE;
|
|
BOOLEAN Wait;
|
|
CREATE_CONTEXT CreateContext;
|
|
NTFS_COMPLETION_CONTEXT CompletionContext;
|
|
LOGICAL ExitFileSystem;
|
|
|
|
ASSERT_IRP( Irp );
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// If we were called with our file system device object instead of a
|
|
// volume device object, just complete this request with STATUS_SUCCESS
|
|
//
|
|
|
|
if (VolumeDeviceObject->DeviceObject.Size == (USHORT)sizeof( DEVICE_OBJECT )) {
|
|
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = FILE_OPENED;
|
|
|
|
IoCompleteRequest( Irp, IO_DISK_INCREMENT );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsFsdCreate\n") );
|
|
|
|
if (NtfsData.EncryptionCallBackTable.PreCreate != NULL) {
|
|
|
|
ASSERT( NtfsData.EncryptionCallBackTable.PostCreate != NULL );
|
|
Status = NtfsData.EncryptionCallBackTable.PreCreate( (PDEVICE_OBJECT) VolumeDeviceObject,
|
|
Irp,
|
|
IoGetCurrentIrpStackLocation(Irp)->FileObject );
|
|
|
|
//
|
|
// Raise the status if a failure.
|
|
//
|
|
|
|
if (Status != STATUS_SUCCESS) {
|
|
|
|
NtfsCompleteRequest( NULL, Irp, Status );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// We have to pair up our PreCreates with PostCreates, so remember them.
|
|
//
|
|
|
|
CallPostCreate = TRUE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we simply don't have a precreate routine registered, then the precreate
|
|
// routine can't fail. Let's always remember to call post create in this case.
|
|
//
|
|
|
|
CallPostCreate = TRUE;
|
|
}
|
|
|
|
//
|
|
// Call the common Create routine
|
|
//
|
|
|
|
IrpContext = NULL;
|
|
|
|
FsRtlEnterFileSystem();
|
|
ExitFileSystem = TRUE;
|
|
|
|
ThreadTopLevelContext = NtfsInitializeTopLevelIrp( &TopLevelContext, FALSE, FALSE );
|
|
|
|
do {
|
|
|
|
RtlZeroMemory( &CreateContext, sizeof( CREATE_CONTEXT ) );
|
|
|
|
try {
|
|
|
|
if (IrpContext == NULL) {
|
|
|
|
Wait = CanFsdWait( Irp ) || CallPostCreate;
|
|
|
|
//
|
|
// Allocate and initialize the Irp.
|
|
//
|
|
|
|
NtfsInitializeIrpContext( Irp, Wait, &IrpContext );
|
|
|
|
//
|
|
// Initialize the thread top level structure, if needed.
|
|
//
|
|
|
|
NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
|
|
|
|
if (Wait) {
|
|
|
|
KeInitializeEvent( &CompletionContext.Event, NotificationEvent, FALSE );
|
|
}
|
|
|
|
} else if (Status == STATUS_LOG_FILE_FULL) {
|
|
|
|
NtfsCheckpointForLogFileFull( IrpContext );
|
|
}
|
|
|
|
//
|
|
// Setup the completion context for synchronous calls - note we reinit CreateContext
|
|
// each time through the main loop
|
|
//
|
|
|
|
if (Wait) {
|
|
CreateContext.Cleanup.CompletionContext = &CompletionContext;
|
|
}
|
|
|
|
//
|
|
// Lest we complete the IRP without doing the appropriate PostCreate callouts...
|
|
// We'll complete the irp _unless_ we have an attached encryption driver with
|
|
// a post create callout registered. An unfortunate side effect here is that we
|
|
// have (inadvertently) called PreCreate on VolumeOpens as well...
|
|
//
|
|
|
|
if (CallPostCreate) {
|
|
|
|
SetFlag( IrpContext->State,
|
|
IRP_CONTEXT_STATE_EFS_CREATE | IRP_CONTEXT_STATE_PERSISTENT );
|
|
}
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_DASD_OPEN )) {
|
|
|
|
Status = NtfsCommonVolumeOpen( IrpContext, Irp );
|
|
ASSERT( Status != STATUS_PENDING );
|
|
|
|
} else {
|
|
|
|
//
|
|
// Make sure there is sufficient stack to perform the create.
|
|
// If we don't, carefully post this request.
|
|
//
|
|
|
|
if (IoGetRemainingStackSize( ) >= OVERFLOW_CREATE_THRESHHOLD) {
|
|
|
|
Status = NtfsCommonCreate( IrpContext, Irp, &CreateContext );
|
|
|
|
if (Status == STATUS_WAIT_FOR_OPLOCK) {
|
|
|
|
NtfsWaitForCreateEvent( Irp, CreateContext.Cleanup.CompletionContext );
|
|
|
|
//
|
|
// remove pending flag (set by the oplock package)
|
|
// since we retry and finish in this thread
|
|
//
|
|
|
|
ClearFlag( IoGetCurrentIrpStackLocation( Irp )->Control, SL_PENDING_RETURNED );
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
ASSERT( IrpContext->ExceptionStatus == 0 );
|
|
|
|
//
|
|
// Use the next stack location with NtfsCreateCompletionRoutine
|
|
// and post this to a worker thread.
|
|
//
|
|
|
|
if (CreateContext.Cleanup.CompletionContext != NULL) {
|
|
|
|
NtfsPrepareForIrpCompletion( IrpContext, Irp, CreateContext.Cleanup.CompletionContext );
|
|
}
|
|
|
|
//
|
|
// If lock buffer call raises, this'll fall through to ProcessException below.
|
|
// Normally, this'll just return PENDING and we wait for the IRP to complete.
|
|
//
|
|
// Set the create context into the union so it can be picked up in
|
|
// NtfsFspDispatch. We'll reset this to an oplock cleanup in NtfsCommonCreate
|
|
// when its retried
|
|
//
|
|
|
|
IrpContext->Union.CreateContext = &CreateContext;
|
|
Status = NtfsPostRequest( IrpContext, Irp );
|
|
}
|
|
}
|
|
|
|
} except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
|
|
|
|
ASSERT( GetExceptionCode() != STATUS_WAIT_FOR_OPLOCK );
|
|
|
|
//
|
|
// We had some trouble trying to perform the requested
|
|
// operation, so we'll abort the I/O request with
|
|
// the error status that we get back from the
|
|
// exception code
|
|
//
|
|
|
|
//
|
|
// Set the create context into the union in case we post on retry
|
|
// we'll reset this up to an oplock cleanup in common create
|
|
//
|
|
|
|
if (IrpContext) {
|
|
IrpContext->Union.CreateContext = &CreateContext;
|
|
}
|
|
Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
|
|
|
|
}
|
|
|
|
} while (Status == STATUS_CANT_WAIT ||
|
|
Status == STATUS_LOG_FILE_FULL ||
|
|
Status == STATUS_WAIT_FOR_OPLOCK) ;
|
|
|
|
//
|
|
// Check if we need to have control of the Irp. I.e we're synchronous
|
|
// and we were able to allocate the irpcontext so we made it to at least NtfsCommonCreate
|
|
//
|
|
|
|
if (IrpContext && Wait) {
|
|
|
|
//
|
|
// If pending then wait on the event to take control of the Irp again.
|
|
//
|
|
|
|
if (Status == STATUS_PENDING) {
|
|
|
|
NtfsWaitForCreateEvent( Irp, &CompletionContext );
|
|
Status = Irp->IoStatus.Status;
|
|
|
|
if (CallPostCreate) {
|
|
|
|
goto PreCreateComplete;
|
|
}
|
|
|
|
NtfsCompleteRequest( NULL, Irp, Status );
|
|
|
|
} else if (CallPostCreate) {
|
|
|
|
NTSTATUS PostCreateStatus;
|
|
ULONG FailedInPostCreateOnly;
|
|
|
|
PreCreateComplete:
|
|
|
|
if (NtfsData.EncryptionCallBackTable.PostCreate != NULL) {
|
|
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
//
|
|
// Restore the thread context pointer if associated with this IrpContext.
|
|
//
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL )) {
|
|
|
|
NtfsRestoreTopLevelIrp();
|
|
ClearFlag( IrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL );
|
|
}
|
|
|
|
FsRtlExitFileSystem();
|
|
ExitFileSystem = FALSE;
|
|
|
|
#ifdef NTFSDBG
|
|
ASSERT( IrpContext->OwnershipState == None );
|
|
#endif
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
PostCreateStatus = NtfsData.EncryptionCallBackTable.PostCreate( (PDEVICE_OBJECT) VolumeDeviceObject,
|
|
Irp,
|
|
IrpSp->FileObject,
|
|
Status,
|
|
&CreateContext.EncryptionContext );
|
|
|
|
ASSERT( Status != STATUS_REPARSE || PostCreateStatus == STATUS_REPARSE );
|
|
|
|
//
|
|
// If we got STATUS_ACCESS_DENIED and the user asked for MAXIMUM_ALLOWED then simply
|
|
// remove the references that allowed read or write access.
|
|
//
|
|
|
|
if ((PostCreateStatus == STATUS_ACCESS_DENIED) &&
|
|
FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->OriginalDesiredAccess, MAXIMUM_ALLOWED ) &&
|
|
(Irp->IoStatus.Information == FILE_OPENED)) {
|
|
|
|
PSCB Scb = (PSCB) IrpSp->FileObject->FsContext;
|
|
BOOLEAN CapturedDeleteAccess = IrpSp->FileObject->DeleteAccess;
|
|
|
|
//
|
|
// Swallow the error status in this case.
|
|
//
|
|
|
|
PostCreateStatus = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Do all the work to reenter the file system. We should never raise out of this block of
|
|
// code.
|
|
//
|
|
|
|
FsRtlEnterFileSystem();
|
|
ExitFileSystem = TRUE;
|
|
|
|
NtfsAcquireResourceExclusive( IrpContext,
|
|
Scb,
|
|
TRUE );
|
|
|
|
IoRemoveShareAccess( IrpSp->FileObject,
|
|
&Scb->ShareAccess );
|
|
|
|
//
|
|
// Clear out the history in the file object.
|
|
//
|
|
|
|
IrpSp->FileObject->ReadAccess = FALSE;
|
|
IrpSp->FileObject->WriteAccess = FALSE;
|
|
IrpSp->FileObject->DeleteAccess = FALSE;
|
|
|
|
IrpSp->FileObject->SharedRead = FALSE;
|
|
IrpSp->FileObject->SharedWrite = FALSE;
|
|
IrpSp->FileObject->SharedDelete = FALSE;
|
|
|
|
ClearFlag( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
|
|
(FILE_READ_DATA |
|
|
FILE_EXECUTE |
|
|
FILE_WRITE_DATA |
|
|
FILE_APPEND_DATA) );
|
|
|
|
//
|
|
// If we already granted delete access then reapply.
|
|
//
|
|
|
|
if (CapturedDeleteAccess) {
|
|
|
|
PostCreateStatus = IoCheckShareAccess( DELETE,
|
|
IrpSp->Parameters.Create.ShareAccess,
|
|
IrpSp->FileObject,
|
|
&Scb->ShareAccess,
|
|
TRUE );
|
|
}
|
|
|
|
NtfsReleaseResource( IrpContext,
|
|
Scb );
|
|
|
|
FsRtlExitFileSystem();
|
|
ExitFileSystem = FALSE;
|
|
}
|
|
|
|
} else {
|
|
|
|
PostCreateStatus = STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// We may have posted the create due to an oplock, in which case the IrpContext
|
|
// will look like we're in the FSP thread. Let's clear the bit now since we're
|
|
// not in the FSP thread now.
|
|
//
|
|
|
|
ClearFlag( IrpContext->State, IRP_CONTEXT_STATE_IN_FSP );
|
|
|
|
//
|
|
// Do our final cleanup only if we created a new encrypted directory/file or
|
|
// we got an error from the encryption callback above.
|
|
//
|
|
|
|
FailedInPostCreateOnly = NT_SUCCESS( Status ) && !NT_SUCCESS( PostCreateStatus );
|
|
if (FailedInPostCreateOnly ||
|
|
FlagOn( CreateContext.EncryptionFileDirFlags, FILE_NEW | DIRECTORY_NEW )) {
|
|
|
|
//
|
|
// Reenter the filesystem at this point.
|
|
//
|
|
|
|
if (!ExitFileSystem) {
|
|
|
|
FsRtlEnterFileSystem();
|
|
ExitFileSystem = TRUE;
|
|
}
|
|
|
|
//
|
|
// Initialize the thread top level structure, if needed.
|
|
//
|
|
|
|
NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
|
|
|
|
//
|
|
// There's no fileobject to cleanup if the normal part of this create failed.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status ) &&
|
|
(Status != STATUS_REPARSE)) {
|
|
|
|
NtfsPostProcessEncryptedCreate( IrpContext,
|
|
IoGetCurrentIrpStackLocation( Irp )->FileObject,
|
|
CreateContext.EncryptionFileDirFlags,
|
|
FailedInPostCreateOnly );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the encryption driver came up with a new reason to fail this irp, return
|
|
// that status.
|
|
//
|
|
|
|
if (FailedInPostCreateOnly) { Status = PostCreateStatus; }
|
|
|
|
//
|
|
// Now we're really done with both the irp context and the irp, so let's
|
|
// get rid of them.
|
|
//
|
|
|
|
ClearFlag( IrpContext->State, IRP_CONTEXT_STATE_PERSISTENT );
|
|
NtfsCompleteRequest( IrpContext, Irp, Status );
|
|
}
|
|
}
|
|
|
|
if (ExitFileSystem) {
|
|
|
|
FsRtlExitFileSystem();
|
|
}
|
|
|
|
ASSERT( IoGetTopLevelIrp() != (PIRP) &TopLevelContext );
|
|
|
|
//
|
|
// We should never return STATUS_CANT_WAIT or STATUS_PENDING
|
|
//
|
|
|
|
ASSERT( (Status != STATUS_CANT_WAIT) && (Status != STATUS_PENDING ) );
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsFsdCreate -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsNetworkOpenCreate (
|
|
IN PIRP Irp,
|
|
OUT PFILE_NETWORK_OPEN_INFORMATION Buffer,
|
|
IN PDEVICE_OBJECT DeviceObject
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine implements the fast open create for path-based queries.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Buffer - Buffer to return the network query information
|
|
|
|
DeviceObject - Supplies the volume device object where the file exists
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - Indicates whether or not the fast path could be taken.
|
|
|
|
--*/
|
|
|
|
{
|
|
TOP_LEVEL_CONTEXT TopLevelContext;
|
|
PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
|
|
BOOLEAN Result = TRUE;
|
|
BOOLEAN DasdOpen = FALSE;
|
|
CREATE_CONTEXT CreateContext;
|
|
|
|
NTSTATUS Status;
|
|
IRP_CONTEXT LocalIrpContext;
|
|
PIRP_CONTEXT IrpContext = &LocalIrpContext;
|
|
|
|
ASSERT_IRP( Irp );
|
|
|
|
UNREFERENCED_PARAMETER( DeviceObject );
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
//
|
|
// Call the common Create routine
|
|
//
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
ThreadTopLevelContext = NtfsInitializeTopLevelIrp( &TopLevelContext, FALSE, FALSE );
|
|
RtlZeroMemory( &CreateContext, sizeof( CreateContext ) );
|
|
|
|
try {
|
|
|
|
//
|
|
// Allocate the Irp and update the top level storage.
|
|
//
|
|
|
|
NtfsInitializeIrpContext( Irp, TRUE, &IrpContext );
|
|
NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
|
|
|
|
CreateContext.NetworkInfo = Buffer;
|
|
|
|
Status = NtfsCommonCreate( IrpContext, Irp, &CreateContext );
|
|
|
|
} except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
|
|
|
|
//
|
|
// Catch the case where someone in attempting this on a DASD open.
|
|
//
|
|
|
|
if ((IrpContext != NULL) && (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_DASD_OPEN ))) {
|
|
|
|
DasdOpen = TRUE;
|
|
}
|
|
|
|
//
|
|
// We had some trouble trying to perform the requested
|
|
// operation, so we'll abort the I/O request with
|
|
// the error status that we get back from the
|
|
// exception code. Since there is no Irp the exception package
|
|
// will always deallocate the IrpContext so we won't do
|
|
// any retry in this path.
|
|
//
|
|
|
|
Status = GetExceptionCode();
|
|
|
|
//
|
|
// Don't pass a retryable error to ProcessException. We want to
|
|
// force this request to the Irp path in any case.
|
|
//
|
|
|
|
if ((Status == STATUS_CANT_WAIT) || (Status == STATUS_LOG_FILE_FULL)) {
|
|
|
|
Status = STATUS_FILE_LOCK_CONFLICT;
|
|
IrpContext->ExceptionStatus = STATUS_FILE_LOCK_CONFLICT;
|
|
}
|
|
|
|
Status = NtfsProcessException( IrpContext, NULL, Status );
|
|
|
|
//
|
|
// Always fail the DASD case.
|
|
//
|
|
|
|
if (DasdOpen) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
//
|
|
// STATUS_SUCCESS is the typical case. Test for it first.
|
|
//
|
|
|
|
if (Status != STATUS_SUCCESS) {
|
|
|
|
//
|
|
// Return STATUS_FILE_LOCK_CONFLICT for any retryable error.
|
|
//
|
|
|
|
ASSERT( (Status != STATUS_CANT_WAIT) && (Status != STATUS_LOG_FILE_FULL) );
|
|
|
|
if ((Status == STATUS_REPARSE) || (Status == STATUS_FILE_LOCK_CONFLICT)) {
|
|
|
|
Result = FALSE;
|
|
Status = STATUS_FILE_LOCK_CONFLICT;
|
|
}
|
|
}
|
|
|
|
ASSERT( IoGetTopLevelIrp() != (PIRP) &TopLevelContext );
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
Irp->IoStatus.Status = Status;
|
|
return Result;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsCommonCreate (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PCREATE_CONTEXT CreateContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the common routine for Create called by both the fsd and fsp
|
|
threads. If this open has already been detected to be a volume open then
|
|
take we will take the volume open path instead.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the Irp to process
|
|
|
|
CompletionContext - Event used to serialize waiting for the oplock break.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The return status for the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PFILE_OBJECT RelatedFileObject;
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG AcquireFlags = 0;
|
|
|
|
UNICODE_STRING AttrName;
|
|
|
|
ATTRIBUTE_TYPE_CODE AttrCode = $UNUSED;
|
|
|
|
PVCB Vcb;
|
|
|
|
//
|
|
// The following are used to teardown any Lcb/Fcb this
|
|
// routine is responsible for.
|
|
//
|
|
|
|
PLCB LcbForTeardown = NULL;
|
|
|
|
//
|
|
// The following indicate how far down the tree we have scanned.
|
|
//
|
|
|
|
PFCB ParentFcb;
|
|
PLCB CurrentLcb;
|
|
PSCB LastScb = NULL;
|
|
PSCB CurrentScb;
|
|
PLCB NextLcb;
|
|
|
|
//
|
|
// The following are the in-memory structures associated with
|
|
// the relative file object.
|
|
//
|
|
|
|
TYPE_OF_OPEN RelatedFileObjectTypeOfOpen;
|
|
PFCB RelatedFcb;
|
|
PSCB RelatedScb;
|
|
PCCB RelatedCcb;
|
|
|
|
UCHAR CreateDisposition;
|
|
UCHAR FileNameFlags;
|
|
USHORT FileNameAttrLength = 0;
|
|
|
|
PFILE_NAME FileNameAttr = NULL;
|
|
|
|
PINDEX_ENTRY IndexEntry;
|
|
PBCB IndexEntryBcb = NULL;
|
|
|
|
QUICK_INDEX QuickIndex;
|
|
|
|
FILE_REFERENCE FileReference;
|
|
|
|
#if defined(_WIN64)
|
|
INDEX_CONTEXT IndexContextStruct;
|
|
#endif
|
|
PINDEX_CONTEXT IndexContext = NULL;
|
|
|
|
//
|
|
// The following unicode strings are used to track the names
|
|
// during the open operation. They may point to the same
|
|
// buffer so careful checks must be done at cleanup.
|
|
//
|
|
// OriginalFileName - This is the value to restore to the file
|
|
// object on error cleanup. This will containg the
|
|
// attribute type codes and attribute names if present.
|
|
//
|
|
// FullFileName - This is the constructed string which contains
|
|
// only the name components. It may point to the same
|
|
// buffer as the original name but the length value is
|
|
// adjusted to cut off the attribute code and name.
|
|
//
|
|
// ExactCaseName - This is the version of the full filename
|
|
// exactly as given by the caller. Used to preserve the
|
|
// case given by the caller in the event we do a case
|
|
// insensitive lookup. If the user is doing a relative open
|
|
// then we don't need to allocate a new buffer. We can use
|
|
// the original name from above.
|
|
//
|
|
// ExactCaseOffset - This is the offset in the FullFileName where
|
|
// the relative component begins. This is where we position ourselves
|
|
// when restoring the correct case for this name.
|
|
//
|
|
// RemainingName - This is the portion of the full name still
|
|
// to parse.
|
|
//
|
|
// FinalName - This is the current component of the full name.
|
|
//
|
|
// CaseInsensitiveIndex - This is the offset in the full file
|
|
// where we performed upcasing. We need to restore the
|
|
// exact case on failures and if we are creating a file.
|
|
//
|
|
|
|
PUNICODE_STRING OriginalFileName = &CreateContext->Cleanup.OriginalFileName;
|
|
PUNICODE_STRING FullFileName = &CreateContext->Cleanup.FullFileName;
|
|
PUNICODE_STRING ExactCaseName = &CreateContext->Cleanup.ExactCaseName;
|
|
USHORT ExactCaseOffset = 0;
|
|
|
|
UNICODE_STRING RemainingName;
|
|
UNICODE_STRING FinalName;
|
|
ULONG CaseInsensitiveIndex = 0;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_IRP( Irp );
|
|
ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL ));
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Get the current Irp stack location
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
//
|
|
// Innitialize all the remaining fields in the OPLOCK_CLEANUP structure.
|
|
//
|
|
|
|
CreateContext->Cleanup.FileObject = IrpSp->FileObject;
|
|
|
|
CreateContext->Cleanup.RemainingDesiredAccess = IrpSp->Parameters.Create.SecurityContext->AccessState->RemainingDesiredAccess;
|
|
CreateContext->Cleanup.PreviouslyGrantedAccess = IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess;
|
|
CreateContext->Cleanup.DesiredAccess = IrpSp->Parameters.Create.SecurityContext->DesiredAccess;
|
|
CreateContext->Cleanup.AttributeNameLength = 0;
|
|
CreateContext->Cleanup.AttributeCodeNameLength = 0;
|
|
|
|
#ifdef BRIANDBG
|
|
if (NtfsTestName.Length != 0) {
|
|
|
|
NtfsTestOpenName( IrpSp->FileObject );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Initialize the attribute strings.
|
|
//
|
|
|
|
AttrName.Length = 0;
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCommonCreate: Entered\n") );
|
|
DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
|
|
DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
|
|
DebugTrace( 0, Dbg, ("->Flags = %08lx\n", Irp->Flags) );
|
|
DebugTrace( 0, Dbg, ("->FileObject = %08lx\n", IrpSp->FileObject) );
|
|
DebugTrace( 0, Dbg, ("->RelatedFileObject = %08lx\n", IrpSp->FileObject->RelatedFileObject) );
|
|
DebugTrace( 0, Dbg, ("->FileName = %Z\n", &IrpSp->FileObject->FileName) );
|
|
DebugTrace( 0, Dbg, ("->AllocationSize = %08lx %08lx\n", Irp->Overlay.AllocationSize.LowPart,
|
|
Irp->Overlay.AllocationSize.HighPart ) );
|
|
DebugTrace( 0, Dbg, ("->EaBuffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) );
|
|
DebugTrace( 0, Dbg, ("->EaLength = %08lx\n", IrpSp->Parameters.Create.EaLength) );
|
|
DebugTrace( 0, Dbg, ("->DesiredAccess = %08lx\n", IrpSp->Parameters.Create.SecurityContext->DesiredAccess) );
|
|
DebugTrace( 0, Dbg, ("->Options = %08lx\n", IrpSp->Parameters.Create.Options) );
|
|
DebugTrace( 0, Dbg, ("->FileAttributes = %04x\n", IrpSp->Parameters.Create.FileAttributes) );
|
|
DebugTrace( 0, Dbg, ("->ShareAccess = %04x\n", IrpSp->Parameters.Create.ShareAccess) );
|
|
DebugTrace( 0, Dbg, ("->Directory = %04x\n", FlagOn( IrpSp->Parameters.Create.Options,
|
|
FILE_DIRECTORY_FILE )) );
|
|
DebugTrace( 0, Dbg, ("->NonDirectoryFile = %04x\n", FlagOn( IrpSp->Parameters.Create.Options,
|
|
FILE_NON_DIRECTORY_FILE )) );
|
|
DebugTrace( 0, Dbg, ("->NoIntermediateBuffering = %04x\n", FlagOn( IrpSp->Parameters.Create.Options,
|
|
FILE_NO_INTERMEDIATE_BUFFERING )) );
|
|
DebugTrace( 0, Dbg, ("->CreateDisposition = %04x\n", (IrpSp->Parameters.Create.Options >> 24) & 0x000000ff) );
|
|
DebugTrace( 0, Dbg, ("->IsPagingFile = %04x\n", FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )) );
|
|
DebugTrace( 0, Dbg, ("->OpenTargetDirectory = %04x\n", FlagOn( IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY )) );
|
|
DebugTrace( 0, Dbg, ("->CaseSensitive = %04x\n", FlagOn( IrpSp->Flags, SL_CASE_SENSITIVE )) );
|
|
DebugTrace( 0, Dbg, ("->NetworkInfo = %08x\n", CreateContext->NetworkInfo) );
|
|
|
|
DebugTrace( 0, Dbg, ("->EntryRemainingDesiredAccess = %08lx\n", CreateContext->Cleanup.RemainingDesiredAccess) );
|
|
DebugTrace( 0, Dbg, ("->EntryPreviouslyGrantedAccess = %08lx\n", CreateContext->Cleanup.PreviouslyGrantedAccess) );
|
|
|
|
//
|
|
// For NT5, the fact that the user has requested that the file be created
|
|
// encrypted means it will not be created compressed, regardless of the
|
|
// compression state of the parent directory.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.FileAttributes, FILE_ATTRIBUTE_ENCRYPTED )) {
|
|
|
|
SetFlag( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION );
|
|
}
|
|
|
|
//
|
|
// Verify that we can wait and acquire the Vcb exclusively.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Can't wait in create\n") );
|
|
|
|
Status = NtfsPostRequest( IrpContext, Irp );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCommonCreate: Exit -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// If we're retrying this create because we're waiting for the key blob
|
|
// from the encryption driver, we want to wait for our notification
|
|
// event so we don't hog the cpu(s) and prevent the encryption driver
|
|
// from having a chance to give us the key blob.
|
|
//
|
|
|
|
if FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ENCRYPTION_RETRY ) {
|
|
|
|
KeWaitForSingleObject( &NtfsEncryptionPendingEvent,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL );
|
|
|
|
ClearFlag( IrpContext->State, IRP_CONTEXT_STATE_ENCRYPTION_RETRY );
|
|
}
|
|
|
|
//
|
|
// While we were waiting for the encryption driver's post create callout
|
|
// to return OR at the top for an oplock break, the create may have been cancelled,
|
|
// most likely because the user's process is terminating. In that case,
|
|
// let's complete and exit now.
|
|
//
|
|
|
|
if (Irp->Cancel) {
|
|
|
|
Status = STATUS_CANCELLED;
|
|
DebugTrace( -1, Dbg, ("NtfsCommonCreate: Exit -> %08lx\n", Status) );
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_EFS_CREATE ) ||
|
|
(CreateContext->NetworkInfo != NULL)) {
|
|
|
|
NtfsCompleteRequest( IrpContext,
|
|
NULL,
|
|
Status );
|
|
|
|
} else {
|
|
|
|
NtfsCompleteRequest( IrpContext,
|
|
Irp,
|
|
Status );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Update the IrpContext with the oplock cleanup structure.
|
|
//
|
|
|
|
IrpContext->Union.OplockCleanup = &CreateContext->Cleanup;
|
|
|
|
//
|
|
// Locate the volume device object and Vcb that we are trying to access.
|
|
//
|
|
|
|
Vcb = &((PVOLUME_DEVICE_OBJECT)IrpSp->DeviceObject)->Vcb;
|
|
|
|
//
|
|
// We will need to acquire the vcb exclusive for paging file opens
|
|
// in order to do the fspclose that flushes out that vcb
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )) {
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX );
|
|
}
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Let's do some work here if the close lists have exceeded
|
|
// some threshold. Cast 1 to a pointer to indicate who is calling
|
|
// FspClose.
|
|
//
|
|
|
|
if ((NtfsData.AsyncCloseCount + NtfsData.DelayedCloseCount) > NtfsThrottleCreates) {
|
|
|
|
NtfsFspClose( (PVCB) 1 );
|
|
}
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX )) {
|
|
|
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|
|
|
} else {
|
|
|
|
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|
}
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_ACQUIRED_VCB );
|
|
|
|
//
|
|
// Set up local pointers to the file name.
|
|
//
|
|
|
|
*FullFileName = *OriginalFileName = CreateContext->Cleanup.FileObject->FileName;
|
|
|
|
//
|
|
// Make sure that Darryl didn't send us a garbage name
|
|
//
|
|
|
|
ASSERT( CreateContext->Cleanup.FileObject->FileName.Length != 0 ||
|
|
CreateContext->Cleanup.FileObject->FileName.Buffer == 0 );
|
|
|
|
ExactCaseName->Buffer = NULL;
|
|
|
|
//
|
|
// Check a few parameters before we proceed.
|
|
//
|
|
|
|
if ((FlagOn( IrpSp->Parameters.Create.Options, FILE_DIRECTORY_FILE | FILE_NON_DIRECTORY_FILE ) ==
|
|
(FILE_DIRECTORY_FILE | FILE_NON_DIRECTORY_FILE)) ||
|
|
|
|
(Irp->Overlay.AllocationSize.QuadPart > MAXFILESIZE)) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
try_return( Status );
|
|
}
|
|
|
|
//
|
|
// If the Vcb is locked then we cannot open another file. If we have performed
|
|
// a dismount then make sure we have the Vcb acquired exclusive so we can
|
|
// check if we should dismount this volume.
|
|
//
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_PERFORMED_DISMOUNT )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Volume is locked\n") );
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_PERFORMED_DISMOUNT ) &&
|
|
!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX )) {
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
|
|
ClearFlag( CreateContext->CreateFlags, CREATE_FLAG_ACQUIRED_VCB );
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX );
|
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_ACQUIRED_VCB );
|
|
}
|
|
|
|
//
|
|
// Either deny access or show the volume was dismounted. Only show the dismount
|
|
// if the user is opening through a relative handle.
|
|
//
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_EXPLICIT_DISMOUNT ) &&
|
|
(CreateContext->Cleanup.FileObject->RelatedFileObject != NULL)) {
|
|
|
|
Status = STATUS_VOLUME_DISMOUNTED;
|
|
}
|
|
try_return( NOTHING );
|
|
}
|
|
|
|
//
|
|
// Initialize local copies of the stack values.
|
|
//
|
|
|
|
RelatedFileObject = CreateContext->Cleanup.FileObject->RelatedFileObject;
|
|
|
|
if (!FlagOn( IrpSp->Flags, SL_CASE_SENSITIVE )) {
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE );
|
|
}
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID)) {
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID );
|
|
}
|
|
|
|
CreateDisposition = (UCHAR) ((IrpSp->Parameters.Create.Options >> 24) & 0x000000ff);
|
|
|
|
//
|
|
// We don't want any file modifications to go through if the volume is readonly.
|
|
// However, we don't want to fail any _opens_ for writes either, because that
|
|
// could potentially break many apps. So ignore the PreviouslyGrantedAccess,
|
|
// and just look at the CreateDisposition.
|
|
//
|
|
|
|
if (NtfsIsVolumeReadOnly( Vcb )) {
|
|
|
|
if ((CreateDisposition == FILE_CREATE) ||
|
|
(CreateDisposition == FILE_SUPERSEDE) ||
|
|
(CreateDisposition == FILE_OVERWRITE) ||
|
|
(CreateDisposition == FILE_OVERWRITE_IF)) {
|
|
|
|
Status = STATUS_MEDIA_WRITE_PROTECTED;
|
|
try_return( Status );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Acquire the paging io resource if we are superseding/overwriting a
|
|
// file or if we are opening for non-cached access.
|
|
//
|
|
|
|
if ((CreateDisposition == FILE_SUPERSEDE) ||
|
|
(CreateDisposition == FILE_OVERWRITE) ||
|
|
(CreateDisposition == FILE_OVERWRITE_IF) ||
|
|
FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_INTERMEDIATE_BUFFERING )) {
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
|
|
}
|
|
|
|
//
|
|
// We don't allow an open for an existing paging file. To insure that the
|
|
// delayed close Scb is not for this paging file we will unconditionally
|
|
// dereference it if this is a paging file open.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
|
|
(!IsListEmpty( &NtfsData.AsyncCloseList ) ||
|
|
!IsListEmpty( &NtfsData.DelayedCloseList ))) {
|
|
|
|
NtfsFspClose( Vcb );
|
|
}
|
|
|
|
//
|
|
// Set up the file object's Vpb pointer in case anything happens.
|
|
// This will allow us to get a reasonable pop-up.
|
|
// Also set the flag to acquire the paging io resource if we might
|
|
// be creating a stream relative to a file. We need to make
|
|
// sure to acquire the paging IO when we get the file.
|
|
//
|
|
|
|
if (RelatedFileObject != NULL) {
|
|
|
|
CreateContext->Cleanup.FileObject->Vpb = RelatedFileObject->Vpb;
|
|
|
|
if ((OriginalFileName->Length != 0) &&
|
|
(OriginalFileName->Buffer[0] == L':') &&
|
|
((CreateDisposition == FILE_OPEN_IF) ||
|
|
(CreateDisposition == FILE_CREATE))) {
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Ping the volume to make sure the Vcb is still mounted. If we need
|
|
// to verify the volume then do it now, and if it comes out okay
|
|
// then clear the verify volume flag in the device object and continue
|
|
// on. If it doesn't verify okay then dismount the volume and
|
|
// either tell the I/O system to try and create again (with a new mount)
|
|
// or that the volume is wrong. This later code is returned if we
|
|
// are trying to do a relative open and the vcb is no longer mounted.
|
|
//
|
|
|
|
if (!NtfsPingVolume( IrpContext, Vcb, NULL ) ||
|
|
!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX )) {
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX );
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
if (!NtfsPerformVerifyOperation( IrpContext, Vcb )) {
|
|
|
|
//
|
|
// We need checkpoint sync to do a dismount which must
|
|
// be acquired before the vcb - after dropping the vcb
|
|
// we must retest the volume
|
|
//
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
ClearFlag( CreateContext->CreateFlags, CREATE_FLAG_ACQUIRED_VCB );
|
|
|
|
NtfsAcquireCheckpointSynchronization( IrpContext, Vcb );
|
|
|
|
try {
|
|
|
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_ACQUIRED_VCB );
|
|
|
|
if (!NtfsPerformVerifyOperation( IrpContext, Vcb )) {
|
|
|
|
NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE, NULL );
|
|
|
|
if (RelatedFileObject == NULL) {
|
|
|
|
Irp->IoStatus.Information = IO_REMOUNT;
|
|
NtfsRaiseStatus( IrpContext, STATUS_REPARSE, NULL, NULL );
|
|
|
|
} else {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_WRONG_VOLUME, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// After releasing the vcb to do the verify - if the verify passes the voume
|
|
// should be still mounted
|
|
//
|
|
|
|
ASSERT( FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED ) );
|
|
}
|
|
|
|
} finally {
|
|
NtfsReleaseCheckpointSynchronization( IrpContext, Vcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// The volume verified correctly so now clear the verify bit
|
|
// and continue with the create
|
|
//
|
|
|
|
ClearFlag( Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME );
|
|
}
|
|
|
|
//
|
|
// Let's handle the open by Id case immediately.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID )) {
|
|
|
|
FILE_REFERENCE FileReference;
|
|
|
|
if (OriginalFileName->Length == sizeof( FILE_REFERENCE ) ||
|
|
(OriginalFileName->Length == sizeof( FILE_REFERENCE ) + sizeof( WCHAR ))) {
|
|
|
|
//
|
|
// This is the regular open by file id case.
|
|
// Perform a safe copy of the data to our local variable.
|
|
// accept slash prefixed filerefs
|
|
//
|
|
|
|
if (OriginalFileName->Length == sizeof( FILE_REFERENCE )) {
|
|
RtlCopyMemory( &FileReference,
|
|
CreateContext->Cleanup.FileObject->FileName.Buffer,
|
|
sizeof( FILE_REFERENCE ));
|
|
} else {
|
|
RtlCopyMemory( &FileReference,
|
|
CreateContext->Cleanup.FileObject->FileName.Buffer + 1,
|
|
sizeof( FILE_REFERENCE ));
|
|
}
|
|
|
|
//
|
|
// If it's 16 bytes long, it should be an object id. It may
|
|
// also be one WCHAR longer for the Win32 double backslash.
|
|
// This code only works for 5.0 volumes with object id indices.
|
|
//
|
|
|
|
} else if (((OriginalFileName->Length == OBJECT_ID_KEY_LENGTH) ||
|
|
(OriginalFileName->Length == OBJECT_ID_KEY_LENGTH + sizeof( WCHAR ))) &&
|
|
|
|
(Vcb->ObjectIdTableScb != NULL)) {
|
|
|
|
//
|
|
// In the open by object id case, we need to do some
|
|
// more work to find the file reference.
|
|
//
|
|
|
|
Status = NtfsLookupObjectId( IrpContext, Vcb, OriginalFileName, &FileReference );
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
try_return( Status = STATUS_OBJECT_NAME_NOT_FOUND );
|
|
}
|
|
|
|
} else {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
//
|
|
// Clear the name in the file object.
|
|
//
|
|
|
|
CreateContext->Cleanup.FileObject->FileName.Buffer = NULL;
|
|
CreateContext->Cleanup.FileObject->FileName.Length = 0;
|
|
|
|
ASSERT( CreateContext->CurrentFcb == NULL );
|
|
|
|
Status = NtfsOpenFcbById( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
Vcb,
|
|
NULL,
|
|
FileReference,
|
|
NtfsEmptyString,
|
|
$UNUSED,
|
|
CreateContext );
|
|
|
|
if ((Status != STATUS_PENDING) && (Status != STATUS_WAIT_FOR_OPLOCK)) {
|
|
|
|
//
|
|
// Remember if we can let the user see the name for this file opened by id.
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->Flags,
|
|
TOKEN_HAS_TRAVERSE_PRIVILEGE )) {
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK );
|
|
} else {
|
|
ClearFlag( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK );
|
|
}
|
|
|
|
//
|
|
// Put the name back into the file object so that the IO system doesn't
|
|
// think this is a dasd handle. Leave the max length at zero so
|
|
// we know this is not a real name.
|
|
//
|
|
|
|
CreateContext->Cleanup.FileObject->FileName.Buffer = OriginalFileName->Buffer;
|
|
CreateContext->Cleanup.FileObject->FileName.Length = OriginalFileName->Length;
|
|
}
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
//
|
|
// Test for double beginning backslashes from the Win32 layer. Apparently
|
|
// they can't test for this.
|
|
//
|
|
|
|
if ((CreateContext->Cleanup.FileObject->FileName.Length > sizeof( WCHAR )) &&
|
|
(CreateContext->Cleanup.FileObject->FileName.Buffer[1] == L'\\') &&
|
|
(CreateContext->Cleanup.FileObject->FileName.Buffer[0] == L'\\')) {
|
|
|
|
CreateContext->Cleanup.FileObject->FileName.Length -= sizeof( WCHAR );
|
|
|
|
RtlMoveMemory( &CreateContext->Cleanup.FileObject->FileName.Buffer[0],
|
|
&CreateContext->Cleanup.FileObject->FileName.Buffer[1],
|
|
CreateContext->Cleanup.FileObject->FileName.Length );
|
|
|
|
*FullFileName = *OriginalFileName = CreateContext->Cleanup.FileObject->FileName;
|
|
|
|
//
|
|
// If there are still two beginning backslashes, the name is bogus.
|
|
//
|
|
|
|
if ((CreateContext->Cleanup.FileObject->FileName.Length > sizeof( WCHAR )) &&
|
|
(CreateContext->Cleanup.FileObject->FileName.Buffer[1] == L'\\')) {
|
|
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
try_return( Status );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Remember if we need to perform any traverse access checks.
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->Flags,
|
|
TOKEN_HAS_TRAVERSE_PRIVILEGE )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Performing traverse access on this open\n") );
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK );
|
|
|
|
} else {
|
|
|
|
ClearFlag( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK );
|
|
|
|
#ifdef BRIANDBG
|
|
if (NtfsTraverseAccessCheck) {
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
//
|
|
// If there is a related file object, we decode it to verify that this
|
|
// is a valid relative open.
|
|
//
|
|
|
|
if (RelatedFileObject != NULL) {
|
|
|
|
PVCB DecodeVcb;
|
|
|
|
//
|
|
// Check for a valid name. The name can't begin with a backslash
|
|
// and can't end with two backslashes.
|
|
//
|
|
|
|
if (OriginalFileName->Length != 0) {
|
|
|
|
//
|
|
// Check for a leading backslash.
|
|
//
|
|
|
|
if (OriginalFileName->Buffer[0] == L'\\') {
|
|
|
|
DebugTrace( 0, Dbg, ("Invalid name for relative open\n") );
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// Trim off any trailing backslash.
|
|
//
|
|
|
|
if (OriginalFileName->Buffer[ (OriginalFileName->Length / sizeof( WCHAR )) - 1 ] == L'\\') {
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_TRAILING_BACKSLASH );
|
|
CreateContext->Cleanup.FileObject->FileName.Length -= sizeof( WCHAR );
|
|
*OriginalFileName = *FullFileName = CreateContext->Cleanup.FileObject->FileName;
|
|
}
|
|
|
|
//
|
|
// Now check if there is a trailing backslash. Note that if
|
|
// there was already a trailing backslash then there must
|
|
// be at least one more character or we would have failed
|
|
// with the original test.
|
|
//
|
|
|
|
if (OriginalFileName->Buffer[ (OriginalFileName->Length / sizeof( WCHAR )) - 1 ] == L'\\') {
|
|
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
try_return( Status );
|
|
}
|
|
}
|
|
|
|
RelatedFileObjectTypeOfOpen = NtfsDecodeFileObject( IrpContext,
|
|
RelatedFileObject,
|
|
&DecodeVcb,
|
|
&RelatedFcb,
|
|
&RelatedScb,
|
|
&RelatedCcb,
|
|
TRUE );
|
|
|
|
//
|
|
// Make sure the file object is one that we have seen
|
|
//
|
|
|
|
if (RelatedFileObjectTypeOfOpen == UnopenedFileObject) {
|
|
|
|
DebugTrace( 0, Dbg, ("Can't use unopend file for relative open\n") );
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// If the related file object was not opened as a file then we need to
|
|
// get the name and code if our caller passed a name length of zero.
|
|
// We need to fail this otherwise.
|
|
//
|
|
|
|
if (!FlagOn( RelatedCcb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
//
|
|
// If the name length is zero then we want the attribute name and
|
|
// type code from the related file object.
|
|
//
|
|
|
|
if (OriginalFileName->Length == 0) {
|
|
|
|
AttrName = RelatedScb->AttributeName;
|
|
AttrCode = RelatedScb->AttributeTypeCode;
|
|
//
|
|
// The relative file has to have been opened as a file. We
|
|
// cannot do relative opens relative to an opened attribute.
|
|
//
|
|
|
|
} else {
|
|
|
|
DebugTrace( 0, Dbg, ("Invalid File object for relative open\n") );
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
}
|
|
|
|
//
|
|
// USN_V2 Remember the source info flags for this Ccb.
|
|
//
|
|
|
|
IrpContext->SourceInfo = RelatedCcb->UsnSourceInfo;
|
|
|
|
//
|
|
// If the related Ccb is was opened by file Id, we will
|
|
// remember that for future use.
|
|
//
|
|
|
|
if (FlagOn( RelatedCcb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID );
|
|
}
|
|
|
|
//
|
|
// Remember if the related Ccb was opened through a Dos-Only
|
|
// component.
|
|
//
|
|
|
|
if (FlagOn( RelatedCcb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT )) {
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_DOS_ONLY_COMPONENT );
|
|
}
|
|
|
|
} else {
|
|
|
|
RelatedFileObjectTypeOfOpen = UnopenedFileObject;
|
|
|
|
if ((OriginalFileName->Length > 2) &&
|
|
(OriginalFileName->Buffer[ (OriginalFileName->Length / sizeof( WCHAR )) - 1 ] == L'\\')) {
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_TRAILING_BACKSLASH );
|
|
CreateContext->Cleanup.FileObject->FileName.Length -= sizeof( WCHAR );
|
|
*OriginalFileName = *FullFileName = CreateContext->Cleanup.FileObject->FileName;
|
|
|
|
//
|
|
// If there is still a trailing backslash on the name then
|
|
// the name is invalid.
|
|
//
|
|
|
|
if ((OriginalFileName->Length > 2) &&
|
|
(OriginalFileName->Buffer[ (OriginalFileName->Length / sizeof( WCHAR )) - 1 ] == L'\\')) {
|
|
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
try_return( Status );
|
|
}
|
|
}
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("Related File Object, TypeOfOpen -> %08lx\n", RelatedFileObjectTypeOfOpen) );
|
|
|
|
//
|
|
// We check if this is a user volume open in that there is no name
|
|
// specified and the related file object is valid if present. In that
|
|
// case set the correct flags in the IrpContext and raise so we can take
|
|
// the volume open path.
|
|
//
|
|
|
|
if ((OriginalFileName->Length == 0) &&
|
|
((RelatedFileObjectTypeOfOpen == UnopenedFileObject) ||
|
|
(RelatedFileObjectTypeOfOpen == UserVolumeOpen))) {
|
|
|
|
DebugTrace( 0, Dbg, ("Attempting to open entire volume\n") );
|
|
|
|
SetFlag( IrpContext->State,
|
|
IRP_CONTEXT_STATE_ACQUIRE_EX | IRP_CONTEXT_STATE_DASD_OPEN );
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// If the related file object was a volume open, then this open is
|
|
// illegal.
|
|
//
|
|
|
|
if (RelatedFileObjectTypeOfOpen == UserVolumeOpen) {
|
|
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// We enter the loop that does the processing for the prefix lookup.
|
|
// We optimize the case where we can match a prefix hit. If there is
|
|
// no hit we will check if the name is legal or might possibly require
|
|
// parsing to handle the case where there is a named data stream.
|
|
//
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_FIRST_PASS );
|
|
|
|
while (TRUE) {
|
|
|
|
PUNICODE_STRING FileObjectName;
|
|
LONG Index;
|
|
BOOLEAN ComplexName;
|
|
|
|
//
|
|
// Lets make sure we have acquired the starting point for our
|
|
// name search. If we have a relative file object then use
|
|
// that. Otherwise we will start from the root.
|
|
//
|
|
|
|
if (RelatedFileObject != NULL) {
|
|
|
|
CreateContext->CurrentFcb = RelatedFcb;
|
|
|
|
} else {
|
|
|
|
CreateContext->CurrentFcb = Vcb->RootIndexScb->Fcb;
|
|
}
|
|
|
|
//
|
|
// Init NextLcb
|
|
//
|
|
|
|
FileObjectName = &CreateContext->Cleanup.FileObject->FileName;
|
|
NextLcb = NULL;
|
|
|
|
//
|
|
// We would like to get the starting point shared, unless
|
|
// we know for certain we need it exclusively.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK | CREATE_FLAG_OPEN_BY_ID ) ||
|
|
!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE) ||
|
|
(FileObjectName->Length == 0) ||
|
|
(FileObjectName->Buffer[0] == L':') ||
|
|
((RelatedFileObject == NULL) &&
|
|
((FileObjectName->Length <= sizeof( WCHAR )) ||
|
|
(FileObjectName->Buffer[1] == L':'))) ||
|
|
((RelatedFileObject != NULL) &&
|
|
(RelatedFileObjectTypeOfOpen != UserDirectoryOpen))) {
|
|
|
|
NtfsAcquireFcbWithPaging( IrpContext, CreateContext->CurrentFcb, 0);
|
|
ClearFlag( CreateContext->CreateFlags, CREATE_FLAG_SHARED_PARENT_FCB );
|
|
|
|
} else {
|
|
|
|
NtfsAcquireSharedFcb( IrpContext, CreateContext->CurrentFcb, NULL, FALSE );
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_SHARED_PARENT_FCB );
|
|
}
|
|
|
|
if (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_FIRST_PASS )) {
|
|
|
|
if (!NtfsParseNameForCreate( IrpContext,
|
|
RemainingName,
|
|
FileObjectName,
|
|
OriginalFileName,
|
|
FullFileName,
|
|
CreateContext,
|
|
&AttrName,
|
|
&AttrCode )) {
|
|
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
}
|
|
|
|
//
|
|
// If we might be creating a named stream acquire the
|
|
// paging IO as well. This will keep anyone from peeking
|
|
// at the allocation size of any other streams we are converting
|
|
// to non-resident.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING ) &&
|
|
(AttrName.Length != 0) &&
|
|
((CreateDisposition == FILE_OPEN_IF) ||
|
|
(CreateDisposition == FILE_CREATE))) {
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
|
|
}
|
|
|
|
//
|
|
// Build up the full name if this is not the open by file Id case.
|
|
//
|
|
|
|
} else if (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID )) {
|
|
|
|
//
|
|
// If we have a related file object, then we build up the
|
|
// combined name.
|
|
//
|
|
|
|
if (RelatedFileObject != NULL) {
|
|
|
|
WCHAR *CurrentPosition;
|
|
USHORT AddSeparator;
|
|
ULONG FullNameLengthTemp;
|
|
|
|
if ((FileObjectName->Length == 0) ||
|
|
(RelatedCcb->FullFileName.Length == 2) ||
|
|
(FileObjectName->Buffer[0] == L':')) {
|
|
|
|
AddSeparator = 0;
|
|
|
|
} else {
|
|
|
|
AddSeparator = sizeof( WCHAR );
|
|
}
|
|
|
|
ExactCaseOffset = RelatedCcb->FullFileName.Length + AddSeparator;
|
|
|
|
FullNameLengthTemp = (ULONG) RelatedCcb->FullFileName.Length + AddSeparator + FileObjectName->Length;
|
|
|
|
//
|
|
// A crude test to see if the total length exceeds a ushort.
|
|
//
|
|
|
|
if ((FullNameLengthTemp & 0xffff0000L) != 0) {
|
|
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
}
|
|
|
|
FullFileName->MaximumLength =
|
|
FullFileName->Length = (USHORT) FullNameLengthTemp;
|
|
|
|
//
|
|
// We need to allocate a name buffer.
|
|
//
|
|
|
|
FullFileName->Buffer = FsRtlAllocatePoolWithTag(PagedPool, FullFileName->Length, MODULE_POOL_TAG);
|
|
|
|
CurrentPosition = (WCHAR *) FullFileName->Buffer;
|
|
|
|
RtlCopyMemory( CurrentPosition,
|
|
RelatedCcb->FullFileName.Buffer,
|
|
RelatedCcb->FullFileName.Length );
|
|
|
|
CurrentPosition = (WCHAR *) Add2Ptr( CurrentPosition, RelatedCcb->FullFileName.Length );
|
|
|
|
if (AddSeparator != 0) {
|
|
|
|
*CurrentPosition = L'\\';
|
|
|
|
CurrentPosition += 1;
|
|
}
|
|
|
|
if (FileObjectName->Length != 0) {
|
|
|
|
RtlCopyMemory( CurrentPosition,
|
|
FileObjectName->Buffer,
|
|
FileObjectName->Length );
|
|
}
|
|
|
|
//
|
|
// If the user specified a case sensitive comparison, then the
|
|
// case insensitive index is the full length of the resulting
|
|
// string. Otherwise it is the length of the string in
|
|
// the related file object. We adjust for the case when the
|
|
// original file name length is zero.
|
|
//
|
|
|
|
if (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE )) {
|
|
|
|
CaseInsensitiveIndex = FullFileName->Length;
|
|
|
|
} else {
|
|
|
|
CaseInsensitiveIndex = RelatedCcb->FullFileName.Length +
|
|
AddSeparator;
|
|
}
|
|
|
|
//
|
|
// The entire name is in the FileObjectName. We check the buffer for
|
|
// validity.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// We look at the name string for detectable errors. The
|
|
// length must be non-zero and the first character must be
|
|
// '\'
|
|
//
|
|
|
|
if (FileObjectName->Length == 0) {
|
|
|
|
DebugTrace( 0, Dbg, ("There is no name to open\n") );
|
|
try_return( Status = STATUS_OBJECT_PATH_NOT_FOUND );
|
|
}
|
|
|
|
if (FileObjectName->Buffer[0] != L'\\') {
|
|
|
|
DebugTrace( 0, Dbg, ("Name does not begin with a backslash\n") );
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// If the user specified a case sensitive comparison, then the
|
|
// case insensitive index is the full length of the resulting
|
|
// string. Otherwise it is zero.
|
|
//
|
|
|
|
if (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE )) {
|
|
|
|
CaseInsensitiveIndex = FullFileName->Length;
|
|
|
|
} else {
|
|
|
|
CaseInsensitiveIndex = 0;
|
|
}
|
|
}
|
|
|
|
} else if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE )) {
|
|
|
|
CaseInsensitiveIndex = 0;
|
|
|
|
} else {
|
|
|
|
CaseInsensitiveIndex = FullFileName->Length;
|
|
}
|
|
|
|
//
|
|
// The remaining name is stored in the FullFileName variable.
|
|
// If we are doing a case-insensitive operation and have to
|
|
// upcase part of the remaining name then allocate a buffer
|
|
// now. No need to allocate a buffer if we already allocated
|
|
// a new buffer for the full file name.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE ) &&
|
|
(CaseInsensitiveIndex < FullFileName->Length)) {
|
|
|
|
UNICODE_STRING StringToUpcase;
|
|
|
|
//
|
|
// Original file name and full file name better have the same buffer or there
|
|
// should be a related file object. If there is already an allocated
|
|
// buffer for the ExactCaseName then it should already be big enough for us.
|
|
//
|
|
|
|
ASSERT( (RelatedFileObject != NULL) ||
|
|
(FullFileName->Buffer == OriginalFileName->Buffer) );
|
|
|
|
//
|
|
// If there is a related name then we can use the original buffer
|
|
// unless the full name is using the same buffer.
|
|
//
|
|
|
|
if (OriginalFileName->Buffer != FullFileName->Buffer) {
|
|
|
|
//
|
|
// We might have already used the original buffer for the case
|
|
// where we are retrying the request.
|
|
//
|
|
|
|
ASSERT( (ExactCaseName->Buffer == NULL) ||
|
|
(ExactCaseName->Buffer == OriginalFileName->Buffer) );
|
|
|
|
ExactCaseName->Buffer = OriginalFileName->Buffer;
|
|
|
|
//
|
|
// MaximumLength includes any stream descriptors.
|
|
// Length is limited to the Length in the FullName.
|
|
//
|
|
|
|
ExactCaseName->MaximumLength = OriginalFileName->Length;
|
|
ExactCaseName->Length = FullFileName->Length - ExactCaseOffset;
|
|
ASSERT( FullFileName->Length >= ExactCaseOffset );
|
|
|
|
//
|
|
// We need to store the exact case name away for any of the create type
|
|
// operations and target directory opens since they are used in rename operations
|
|
// and we depend on the case being preserved in the ignored part of the name -
|
|
// otherwise we'll upcase in place.
|
|
//
|
|
|
|
} else if ((CreateDisposition == FILE_CREATE) ||
|
|
(CreateDisposition == FILE_OPEN_IF) ||
|
|
(CreateDisposition == FILE_OVERWRITE_IF) ||
|
|
(CreateDisposition == FILE_SUPERSEDE) ||
|
|
FlagOn( IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY )) {
|
|
|
|
//
|
|
// Allocate a buffer if we don't already have one.
|
|
//
|
|
|
|
ExactCaseName->MaximumLength = OriginalFileName->Length;
|
|
|
|
if (ExactCaseName->Buffer == NULL) {
|
|
|
|
ExactCaseName->Buffer = FsRtlAllocatePoolWithTag( PagedPool,
|
|
OriginalFileName->MaximumLength,
|
|
MODULE_POOL_TAG );
|
|
}
|
|
|
|
RtlCopyMemory( ExactCaseName->Buffer,
|
|
FullFileName->Buffer,
|
|
FullFileName->MaximumLength );
|
|
|
|
ExactCaseName->Length = FullFileName->Length - ExactCaseOffset;
|
|
ASSERT( FullFileName->Length >= ExactCaseOffset );
|
|
}
|
|
|
|
//
|
|
// Upcase the file name portion of the full name.
|
|
//
|
|
|
|
StringToUpcase.Buffer = Add2Ptr( FullFileName->Buffer,
|
|
CaseInsensitiveIndex );
|
|
|
|
StringToUpcase.Length =
|
|
StringToUpcase.MaximumLength = FullFileName->Length - (USHORT) CaseInsensitiveIndex;
|
|
|
|
NtfsUpcaseName( Vcb->UpcaseTable, Vcb->UpcaseTableSize, &StringToUpcase );
|
|
}
|
|
|
|
RemainingName = *FullFileName;
|
|
|
|
//
|
|
// Make it plain we don't have any hash values.
|
|
//
|
|
|
|
CreateContext->FileHashLength = CreateContext->ParentHashLength = 0;
|
|
|
|
//
|
|
// If this is the traverse access case or the open by file id case we start
|
|
// relative to the file object we have or the root directory.
|
|
// This is also true for the case where the file name in the file object is
|
|
// empty.
|
|
//
|
|
|
|
if ((FileObjectName->Length == 0) ||
|
|
(FlagOn( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK ) &&
|
|
(FileObjectName->Buffer[0] != L':'))) {
|
|
|
|
//
|
|
// We should already have the parent exclusive if we hit this path.
|
|
//
|
|
|
|
ASSERT( !FlagOn( CreateContext->CreateFlags, CREATE_FLAG_SHARED_PARENT_FCB ) );
|
|
|
|
if (RelatedFileObject != NULL) {
|
|
|
|
CurrentLcb = RelatedCcb->Lcb;
|
|
CurrentScb = RelatedScb;
|
|
|
|
if (FileObjectName->Length == 0) {
|
|
|
|
RemainingName.Length = 0;
|
|
|
|
} else if (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID )) {
|
|
|
|
USHORT Increment;
|
|
|
|
Increment = RelatedCcb->FullFileName.Length
|
|
+ (RelatedCcb->FullFileName.Length == 2
|
|
? 0
|
|
: 2);
|
|
|
|
RemainingName.Buffer = (WCHAR *) Add2Ptr( RemainingName.Buffer,
|
|
Increment );
|
|
|
|
RemainingName.Length -= Increment;
|
|
}
|
|
|
|
} else {
|
|
|
|
CurrentLcb = Vcb->RootLcb;
|
|
CurrentScb = Vcb->RootIndexScb;
|
|
|
|
RemainingName.Buffer = (WCHAR *) Add2Ptr( RemainingName.Buffer, sizeof( WCHAR ));
|
|
RemainingName.Length -= sizeof( WCHAR );
|
|
}
|
|
|
|
//
|
|
// Otherwise we will try a prefix lookup.
|
|
//
|
|
|
|
} else {
|
|
|
|
if (RelatedFileObject != NULL) {
|
|
|
|
if (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID )) {
|
|
|
|
//
|
|
// Skip over the characters in the related file object.
|
|
//
|
|
|
|
RemainingName.Buffer = (WCHAR *) Add2Ptr( RemainingName.Buffer,
|
|
RelatedCcb->FullFileName.Length );
|
|
RemainingName.Length -= RelatedCcb->FullFileName.Length;
|
|
|
|
//
|
|
// Step over the backslash if present.
|
|
//
|
|
|
|
if ((RemainingName.Length != 0) &&
|
|
(RemainingName.Buffer[0] == L'\\')) {
|
|
|
|
RemainingName.Buffer += 1;
|
|
RemainingName.Length -= sizeof( WCHAR );
|
|
}
|
|
}
|
|
|
|
CurrentLcb = RelatedCcb->Lcb;
|
|
CurrentScb = RelatedScb;
|
|
|
|
} else {
|
|
|
|
CurrentLcb = Vcb->RootLcb;
|
|
CurrentScb = Vcb->RootIndexScb;
|
|
|
|
//
|
|
// Skip over the lead-in '\' character.
|
|
//
|
|
|
|
RemainingName.Buffer = (WCHAR *) Add2Ptr( RemainingName.Buffer,
|
|
sizeof( WCHAR ));
|
|
RemainingName.Length -= sizeof( WCHAR );
|
|
}
|
|
|
|
LcbForTeardown = NULL;
|
|
|
|
//
|
|
// If we don't have the starting Scb exclusively then let's try for
|
|
// a hash hit first.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_SHARED_PARENT_FCB )) {
|
|
|
|
NextLcb = NtfsFindPrefixHashEntry( IrpContext,
|
|
&Vcb->HashTable,
|
|
CurrentScb,
|
|
&CreateContext->CreateFlags,
|
|
&CreateContext->CurrentFcb,
|
|
&CreateContext->FileHashValue,
|
|
&CreateContext->FileHashLength,
|
|
&CreateContext->ParentHashValue,
|
|
&CreateContext->ParentHashLength,
|
|
&RemainingName );
|
|
|
|
//
|
|
// If we didn't get an Lcb then release the starting Scb
|
|
// and reacquire exclusively.
|
|
//
|
|
|
|
if (NextLcb == NULL) {
|
|
|
|
NtfsReleaseFcbWithPaging( IrpContext, CreateContext->CurrentFcb );
|
|
NtfsAcquireFcbWithPaging( IrpContext, CreateContext->CurrentFcb, 0 );
|
|
|
|
} else {
|
|
|
|
//
|
|
// Remember the Lcb we found. If there is still a
|
|
// portion of the name remaining then check if there
|
|
// is an existing $INDEX_ALLOCATION scb on the file.
|
|
// It is possible that we aren't even at a directory
|
|
// in the reparse case.
|
|
//
|
|
|
|
CurrentLcb = NextLcb;
|
|
|
|
//
|
|
// We have progressed parsing the name. Mark it as one that needs to be inspected
|
|
// for possible reparse behavior.
|
|
//
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_INSPECT_NAME_FOR_REPARSE );
|
|
|
|
if (RemainingName.Length != 0) {
|
|
|
|
CurrentScb = NtfsCreateScb( IrpContext,
|
|
CreateContext->CurrentFcb,
|
|
$INDEX_ALLOCATION,
|
|
&NtfsFileNameIndex,
|
|
TRUE,
|
|
NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// In both cases here we own the Fcb exclusive.
|
|
//
|
|
|
|
ClearFlag( CreateContext->CreateFlags, CREATE_FLAG_SHARED_PARENT_FCB );
|
|
#ifdef NTFS_HASH_DATA
|
|
} else {
|
|
Vcb->HashTable.SkipHashLookupCount += 1;
|
|
#endif
|
|
}
|
|
|
|
if ((RemainingName.Length != 0) &&
|
|
(CurrentScb != NULL)) {
|
|
|
|
NextLcb = NtfsFindPrefix( IrpContext,
|
|
CurrentScb,
|
|
&CreateContext->CurrentFcb,
|
|
&LcbForTeardown,
|
|
RemainingName,
|
|
&CreateContext->CreateFlags,
|
|
&RemainingName );
|
|
}
|
|
|
|
//
|
|
// If we found another link then update the CurrentLcb value.
|
|
//
|
|
|
|
if (NextLcb != NULL) {
|
|
|
|
CurrentLcb = NextLcb;
|
|
|
|
//
|
|
// We have progressed parsing the name. Mark it as one that needs to be inspected
|
|
// for possible reparse behavior.
|
|
//
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_INSPECT_NAME_FOR_REPARSE );
|
|
}
|
|
}
|
|
|
|
if ((RemainingName.Length == 0) || !FlagOn( CreateContext->CreateFlags, CREATE_FLAG_FIRST_PASS )) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If we get here, it means that this is the first pass and we didn't
|
|
// have a prefix match. If there is a colon in the
|
|
// remaining name, then we need to analyze the name in more detail.
|
|
//
|
|
|
|
ComplexName = FALSE;
|
|
|
|
for (Index = (RemainingName.Length / sizeof( WCHAR )) - 1, ComplexName = FALSE;
|
|
Index >= 0;
|
|
Index -= 1) {
|
|
|
|
if (RemainingName.Buffer[Index] == L':') {
|
|
|
|
ComplexName = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ComplexName) {
|
|
|
|
break;
|
|
}
|
|
|
|
ClearFlag( CreateContext->CreateFlags, CREATE_FLAG_FIRST_PASS);
|
|
|
|
//
|
|
// Copy the exact name back to the full name. In this case we want to
|
|
// restore the entire name including stream descriptors.
|
|
//
|
|
|
|
if (ExactCaseName->Buffer != NULL) {
|
|
|
|
ASSERT( ExactCaseName->Length != 0 );
|
|
ASSERT( FullFileName->MaximumLength >= ExactCaseName->Length );
|
|
|
|
RtlCopyMemory( Add2Ptr( FullFileName->Buffer, ExactCaseOffset ),
|
|
ExactCaseName->Buffer,
|
|
ExactCaseName->MaximumLength );
|
|
|
|
//
|
|
// Save the buffer for now but set the lengths to zero as a
|
|
// flag to indicate that we have already copied the data back.
|
|
//
|
|
|
|
ExactCaseName->Length = ExactCaseName->MaximumLength = 0;
|
|
}
|
|
|
|
//
|
|
// Let's release the Fcb we have currently acquired.
|
|
//
|
|
|
|
NtfsReleaseFcbWithPaging( IrpContext, CreateContext->CurrentFcb );
|
|
LcbForTeardown = NULL;
|
|
}
|
|
|
|
//
|
|
// Check if the link or the Fcb is pending delete.
|
|
//
|
|
|
|
if (((CurrentLcb != NULL) && LcbLinkIsDeleted( CurrentLcb )) ||
|
|
CreateContext->CurrentFcb->LinkCount == 0) {
|
|
|
|
try_return( Status = STATUS_DELETE_PENDING );
|
|
}
|
|
|
|
//
|
|
// Put the new name into the file object.
|
|
//
|
|
|
|
CreateContext->Cleanup.FileObject->FileName = *FullFileName;
|
|
|
|
//
|
|
// If the entire path was parsed, then we have access to the Fcb to
|
|
// open. We either open the parent of the prefix match or the prefix
|
|
// match itself, depending on whether the user wanted to open
|
|
// the target directory.
|
|
//
|
|
|
|
if (RemainingName.Length == 0) {
|
|
|
|
//
|
|
// Check the attribute name length.
|
|
//
|
|
|
|
if (AttrName.Length > (NTFS_MAX_ATTR_NAME_LEN * sizeof( WCHAR ))) {
|
|
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
}
|
|
|
|
//
|
|
// If this is a target directory we check that the open is for the
|
|
// entire file.
|
|
// We assume that the final component can only have an attribute
|
|
// which corresponds to the type of file this is. Meaning
|
|
// $INDEX_ALLOCATION for directory, $DATA (unnamed) for a file.
|
|
// We verify that the matching Lcb is not the root Lcb.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY )) {
|
|
|
|
if (CurrentLcb == Vcb->RootLcb) {
|
|
|
|
DebugTrace( 0, Dbg, ("Can't open parent of root\n") );
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// We don't allow attribute names or attribute codes to
|
|
// be specified.
|
|
//
|
|
|
|
if ((AttrName.Length != 0) ||
|
|
FlagOn( CreateContext->CreateFlags, CREATE_FLAG_EXPLICIT_ATTRIBUTE_CODE )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Can't specify complex name for rename\n") );
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
}
|
|
|
|
//
|
|
// When SL_OPEN_TARGET_DIRECTORY is set, the directory should not be opened
|
|
// as a reparse point; FILE_OPEN_REPARSE_POINT should not be set.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_REPARSE_POINT )) {
|
|
|
|
//
|
|
// Wrong open flag, invalid parameter.
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("Can't open intermediate directory as reparse point 1.\n") );
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
try_return( Status );
|
|
}
|
|
|
|
//
|
|
// We want to copy the exact case of the name back into the
|
|
// input buffer for this case.
|
|
//
|
|
|
|
if (ExactCaseName->Buffer != NULL) {
|
|
|
|
ASSERT( ExactCaseName->Length != 0 );
|
|
ASSERT( FullFileName->MaximumLength >= ExactCaseName->Length + ExactCaseOffset );
|
|
|
|
RtlCopyMemory( Add2Ptr( FullFileName->Buffer, ExactCaseOffset ),
|
|
ExactCaseName->Buffer,
|
|
ExactCaseName->MaximumLength );
|
|
}
|
|
|
|
//
|
|
// Acquire the parent of the last Fcb. This is the actual file we
|
|
// are opening.
|
|
//
|
|
|
|
ParentFcb = CurrentLcb->Scb->Fcb;
|
|
NtfsAcquireFcbWithPaging( IrpContext, ParentFcb, 0 );
|
|
|
|
//
|
|
// Call our open target directory, remembering the target
|
|
// file existed.
|
|
//
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_FOUND_ENTRY );
|
|
|
|
Status = NtfsOpenTargetDirectory( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ParentFcb,
|
|
NULL,
|
|
&CreateContext->Cleanup.FileObject->FileName,
|
|
CurrentLcb->ExactCaseLink.LinkName.Length,
|
|
CreateContext );
|
|
|
|
try_return( NOTHING );
|
|
}
|
|
|
|
//
|
|
// Otherwise we simply attempt to open the Fcb we matched.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID )) {
|
|
|
|
Status = NtfsOpenFcbById( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
Vcb,
|
|
CurrentLcb,
|
|
CreateContext->CurrentFcb->FileReference,
|
|
AttrName,
|
|
AttrCode,
|
|
CreateContext );
|
|
|
|
//
|
|
// If the status is pending, the irp or the file object may have gone
|
|
// away already.
|
|
//
|
|
|
|
if ((Status != STATUS_PENDING) && (Status != STATUS_WAIT_FOR_OPLOCK)) {
|
|
|
|
//
|
|
// There should be no need to set TraverseAccessCheck now, it should
|
|
// already be set correctly.
|
|
//
|
|
|
|
ASSERT( (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK ) &&
|
|
FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->Flags, TOKEN_HAS_TRAVERSE_PRIVILEGE )) ||
|
|
|
|
(FlagOn( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK ) &&
|
|
!FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->Flags, TOKEN_HAS_TRAVERSE_PRIVILEGE )) );
|
|
|
|
//
|
|
// Set the maximum length in the file object name to
|
|
// zero so we know that this is not a full name.
|
|
//
|
|
|
|
CreateContext->Cleanup.FileObject->FileName.MaximumLength = 0;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// The current Fcb is acquired.
|
|
//
|
|
|
|
Status = NtfsOpenExistingPrefixFcb( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
CurrentLcb,
|
|
FullFileName->Length,
|
|
AttrName,
|
|
AttrCode,
|
|
CreateContext );
|
|
}
|
|
|
|
try_return( NOTHING );
|
|
}
|
|
|
|
//
|
|
// Check if the current Lcb is a Dos-Only Name.
|
|
//
|
|
|
|
if ((CurrentLcb != NULL) &&
|
|
(CurrentLcb->FileNameAttr->Flags == FILE_NAME_DOS)) {
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_DOS_ONLY_COMPONENT );
|
|
}
|
|
|
|
//
|
|
// We have a remaining portion of the file name which was unmatched in the
|
|
// prefix table. We walk through these name components until we reach the
|
|
// last element. If necessary, we add Fcb and Scb's into the graph as we
|
|
// walk through the names.
|
|
//
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_FIRST_PASS);
|
|
|
|
while (TRUE) {
|
|
|
|
PFILE_NAME IndexFileName;
|
|
|
|
//
|
|
// We check to see whether we need to inspect this name for possible reparse behavior
|
|
// and whether the CurrentFcb is a reparse point.
|
|
// Notice that if a directory is a reparse point, there should be no
|
|
// prefix match possible in NtfsFindPrefix beyond the directory name,
|
|
// as longer matches could bypass a reparse point.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_INSPECT_NAME_FOR_REPARSE ) &&
|
|
FlagOn( CreateContext->CurrentFcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT )) {
|
|
|
|
USHORT AttributeNameLength = 0;
|
|
|
|
//
|
|
// Traverse access is done before accessing the disk.
|
|
// For a directory we check for traverse access.
|
|
// For a file we check for read access.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK )) {
|
|
|
|
if (IsDirectory( &CreateContext->CurrentFcb->Info )) {
|
|
|
|
NtfsTraverseCheck( IrpContext,
|
|
CreateContext->CurrentFcb,
|
|
Irp );
|
|
} else {
|
|
|
|
NtfsAccessCheck ( IrpContext,
|
|
CreateContext->CurrentFcb,
|
|
NULL,
|
|
Irp,
|
|
FILE_GENERIC_READ,
|
|
TRUE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Middle-of-name reparse point call.
|
|
// Notice that the FILE_OPEN_REPARSE_POINT flag only alters the behavior of
|
|
// a final element of a named path, not the intermediate components.
|
|
// Notice further that we need this check here prior to the directory check
|
|
// below as it is legal to have an intermediate name that is a file
|
|
// containing a symbolic link.
|
|
//
|
|
|
|
//
|
|
// When NetworkInfo is present, we are in the fast-I/O path to retrieve
|
|
// the attributes of a target file. The fast path does not process retries
|
|
// due to reparse points. We return indicating that a reparse point has
|
|
// been encountered without returning the reparse point data.
|
|
//
|
|
|
|
if (CreateContext->NetworkInfo) {
|
|
|
|
DebugTrace( 0, Dbg, ("Reparse point encountered with NetworkInfo present.\n") );
|
|
Status = STATUS_REPARSE;
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
//
|
|
// We account for the byte size of the attribute name delimiter : (colon)
|
|
// in unicode.
|
|
// If the name of the code or type of the attribute has been passed on explicitly,
|
|
// like $DATA or $INDEX_ALLOCATION, we also account for it.
|
|
//
|
|
// Notice that the code below ignores the case when no attribute name has been specified
|
|
// yet the name of its code, or type, has been specified.
|
|
//
|
|
|
|
ASSERT( CreateContext->Cleanup.AttributeNameLength == AttrName.Length );
|
|
if (CreateContext->Cleanup.AttributeNameLength > 0) {
|
|
|
|
AttributeNameLength += CreateContext->Cleanup.AttributeNameLength + 2;
|
|
}
|
|
if (CreateContext->Cleanup.AttributeCodeNameLength > 0) {
|
|
|
|
AttributeNameLength += CreateContext->Cleanup.AttributeCodeNameLength + 2;
|
|
}
|
|
if (RemainingName.Length > 0) {
|
|
|
|
//
|
|
// Account for the backslash delimeter.
|
|
//
|
|
|
|
AttributeNameLength += 2;
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("RemainingName.Length = %d CreateContext->Cleanup.AttributeNameLength = %d CreateContext->Cleanup.AttributeCodeNameLength = %d AttributeNameLength = %d sum = %d\n",
|
|
RemainingName.Length, CreateContext->Cleanup.AttributeNameLength, CreateContext->Cleanup.AttributeCodeNameLength, AttributeNameLength, (RemainingName.Length + AttributeNameLength)) );
|
|
|
|
Status = NtfsGetReparsePointValue( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
CreateContext->CurrentFcb,
|
|
(USHORT)(RemainingName.Length + AttributeNameLength) );
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
//
|
|
// We check that the last Fcb we have is in fact a directory.
|
|
//
|
|
|
|
if (!IsDirectory( &CreateContext->CurrentFcb->Info )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Intermediate node is not a directory\n") );
|
|
try_return( Status = STATUS_OBJECT_PATH_NOT_FOUND );
|
|
}
|
|
|
|
//
|
|
// We dissect the name into the next component and the remaining name string.
|
|
// We don't need to check for a valid name if we examined the name already.
|
|
//
|
|
|
|
Status = NtfsDissectName( RemainingName,
|
|
&FinalName,
|
|
&RemainingName );
|
|
if (!NT_SUCCESS( Status )) {
|
|
try_return( Status );
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("Final name -> %Z\n", &FinalName) );
|
|
DebugTrace( 0, Dbg, ("Remaining Name -> %Z\n", &RemainingName) );
|
|
|
|
//
|
|
// If the final name is too long then either the path or the
|
|
// name is invalid.
|
|
//
|
|
|
|
if (FinalName.Length > (NTFS_MAX_FILE_NAME_LENGTH * sizeof( WCHAR ))) {
|
|
|
|
if (RemainingName.Length == 0) {
|
|
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
|
|
} else {
|
|
|
|
try_return( Status = STATUS_OBJECT_PATH_NOT_FOUND );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Catch single dot names (.) before scanning the index. We don't
|
|
// want to allow someone to open the self entry in the root.
|
|
//
|
|
|
|
if ((FinalName.Length == 2) &&
|
|
(FinalName.Buffer[0] == L'.')) {
|
|
|
|
if (RemainingName.Length != 0) {
|
|
|
|
DebugTrace( 0, Dbg, ("Intermediate component in path doesn't exist\n") );
|
|
try_return( Status = STATUS_OBJECT_PATH_NOT_FOUND );
|
|
|
|
//
|
|
// If the final component is illegal, then return the appropriate error.
|
|
//
|
|
|
|
} else {
|
|
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the index allocation Scb for the current Fcb.
|
|
//
|
|
|
|
//
|
|
// We need to look for the next component in the name string in the directory
|
|
// we've reached. We need to get a Scb to perform the index search.
|
|
// To do the search we need to build a filename attribute to perform the
|
|
// search with and then call the index package to perform the search.
|
|
//
|
|
|
|
CurrentScb = NtfsCreateScb( IrpContext,
|
|
CreateContext->CurrentFcb,
|
|
$INDEX_ALLOCATION,
|
|
&NtfsFileNameIndex,
|
|
FALSE,
|
|
NULL );
|
|
|
|
//
|
|
// If the CurrentScb does not have its normalized name and we have a valid
|
|
// parent, then update the normalized name.
|
|
//
|
|
|
|
if ((LastScb != NULL) &&
|
|
(CurrentScb->ScbType.Index.NormalizedName.Length == 0) &&
|
|
(LastScb->ScbType.Index.NormalizedName.Length != 0)) {
|
|
|
|
NtfsUpdateNormalizedName( IrpContext, LastScb, CurrentScb, NULL, FALSE, FALSE );
|
|
|
|
}
|
|
|
|
//
|
|
// Release the parent Scb if we own it.
|
|
//
|
|
|
|
if (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_FIRST_PASS )) {
|
|
|
|
NtfsReleaseFcbWithPaging( IrpContext, ParentFcb );
|
|
}
|
|
|
|
LastScb = CurrentScb;
|
|
|
|
//
|
|
// If traverse access is required, we do so now before accessing the
|
|
// disk.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK )) {
|
|
|
|
NtfsTraverseCheck( IrpContext,
|
|
CreateContext->CurrentFcb,
|
|
Irp );
|
|
}
|
|
|
|
ASSERT( IndexEntryBcb == NULL );
|
|
|
|
//
|
|
// Check that the name is valid before scanning the disk.
|
|
//
|
|
|
|
if (!NtfsIsFileNameValid( &FinalName, FALSE )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Component name is invalid\n") );
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
}
|
|
|
|
//
|
|
// Initialize or reinitialize the context as necessary.
|
|
//
|
|
|
|
if (IndexContext == NULL) {
|
|
|
|
#if defined(_WIN64)
|
|
IndexContext = &IndexContextStruct;
|
|
#else
|
|
//
|
|
// AllocateFromStack can raise but the create exception filter will catch it.
|
|
// We can only do this in one pass through the loop. Otherwise we could
|
|
// walk off stack.
|
|
//
|
|
|
|
IndexContext = NtfsAllocateFromStack( sizeof( INDEX_CONTEXT ));
|
|
#endif
|
|
NtfsInitializeIndexContext( IndexContext );
|
|
}
|
|
|
|
//
|
|
// Look on the disk to see if we can find the last component on the path.
|
|
//
|
|
|
|
if (NtfsLookupEntry( IrpContext,
|
|
CurrentScb,
|
|
BooleanFlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE ),
|
|
&FinalName,
|
|
&FileNameAttr,
|
|
&FileNameAttrLength,
|
|
&QuickIndex,
|
|
&IndexEntry,
|
|
&IndexEntryBcb,
|
|
IndexContext )) {
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_FOUND_ENTRY );
|
|
} else {
|
|
ClearFlag( CreateContext->CreateFlags, CREATE_FLAG_FOUND_ENTRY );
|
|
}
|
|
|
|
//
|
|
// This call to NtfsLookupEntry may decide to push the root index.
|
|
// Create needs to free resources as it walks down the tree to prevent
|
|
// deadlocks. If there is a transaction, commit it now so we will be
|
|
// able to free this resource.
|
|
//
|
|
|
|
if (IrpContext->TransactionId != 0) {
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Go through and free any Scb's in the queue of shared
|
|
// Scb's for transactions.
|
|
//
|
|
|
|
if (IrpContext->SharedScb != NULL) {
|
|
NtfsReleaseSharedResources( IrpContext );
|
|
ASSERT( IrpContext->SharedScb == NULL );
|
|
}
|
|
|
|
//
|
|
// Release the MftScb, if we acquired it in pushing the root index.
|
|
//
|
|
|
|
NtfsReleaseExclusiveScbIfOwned( IrpContext, Vcb->MftScb );
|
|
}
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_FOUND_ENTRY )) {
|
|
|
|
ASSERT( !FlagOn( CreateContext->CurrentFcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT ) );
|
|
|
|
//
|
|
// Get the file name attribute so we can get the name out of it.
|
|
//
|
|
|
|
IndexFileName = (PFILE_NAME) NtfsFoundIndexEntry( IndexEntry );
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE )) {
|
|
|
|
RtlCopyMemory( FinalName.Buffer,
|
|
IndexFileName->FileName,
|
|
FinalName.Length );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we didn't find a matching entry in the index, we need to check if the
|
|
// name is illegal or simply isn't present on the disk.
|
|
//
|
|
|
|
if (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_FOUND_ENTRY )) {
|
|
|
|
//
|
|
// We're going to attempt to create the file. We can immediately reject
|
|
//
|
|
// 1. paths where an intermediate piece doesn't exist
|
|
// 2. non create type opens unless this is an open target directory type open
|
|
// 3. an overwrite_if on directories
|
|
// 4. any attempt on a read-only volume
|
|
// 5. creation attempts in a reparse point directory (not its target)
|
|
// we'd only reach this point if the file_flag_open_reparse_point flag was specified
|
|
//
|
|
|
|
if (RemainingName.Length != 0) {
|
|
|
|
DebugTrace( 0, Dbg, ("Intermediate component in path doesn't exist\n") );
|
|
try_return( Status = STATUS_OBJECT_PATH_NOT_FOUND );
|
|
|
|
}
|
|
|
|
if (FlagOn( CreateContext->CurrentFcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT )) {
|
|
|
|
DebugTrace( 0, Dbg, ("For reparse points subdirectories are not allowed.\n") );
|
|
try_return( Status = STATUS_DIRECTORY_IS_A_REPARSE_POINT );
|
|
}
|
|
|
|
if (!FlagOn( IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY )) {
|
|
|
|
if ((CreateDisposition == FILE_OPEN) || (CreateDisposition == FILE_OVERWRITE)) {
|
|
|
|
DebugTrace( 0, Dbg, ("Final component in path doesn't exist\n") );
|
|
try_return( Status = STATUS_OBJECT_NAME_NOT_FOUND );
|
|
}
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_DIRECTORY_FILE ) &&
|
|
(CreateDisposition == FILE_OVERWRITE_IF)) {
|
|
|
|
DebugTrace( 0, Dbg, ("Can't create directory with overwrite_if flag\n") );
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
}
|
|
|
|
if (NtfsIsVolumeReadOnly( Vcb )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Readonly volume can't create file\n") );
|
|
try_return( Status = STATUS_MEDIA_WRITE_PROTECTED );
|
|
}
|
|
|
|
//
|
|
// Now copy the exact case of the name specified by the user back
|
|
// in the file name buffer and file name attribute in order to
|
|
// create the name.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE)) {
|
|
|
|
ASSERT( ExactCaseName->Length != 0 );
|
|
ASSERT( FullFileName->Length >= ExactCaseName->Length + ExactCaseOffset );
|
|
|
|
RtlCopyMemory( FinalName.Buffer,
|
|
Add2Ptr( ExactCaseName->Buffer,
|
|
ExactCaseName->Length - FinalName.Length ),
|
|
FinalName.Length );
|
|
|
|
RtlCopyMemory( FileNameAttr->FileName,
|
|
Add2Ptr( ExactCaseName->Buffer,
|
|
ExactCaseName->Length - FinalName.Length ),
|
|
FinalName.Length );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// If we're at the last component in the path, then this is the file
|
|
// to open or create
|
|
//
|
|
|
|
if (RemainingName.Length == 0) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Otherwise we create an Fcb for the subdirectory and the link between
|
|
// it and its parent Scb.
|
|
//
|
|
|
|
//
|
|
// Discard any mapping information we have for the parent.
|
|
//
|
|
|
|
NtfsRemoveFromFileRecordCache( IrpContext,
|
|
NtfsSegmentNumber( &CurrentScb->Fcb->FileReference ));
|
|
|
|
FileReference = IndexEntry->FileReference;
|
|
FileNameFlags = ((PFILE_NAME) NtfsFoundIndexEntry( IndexEntry ))->Flags;
|
|
|
|
//
|
|
// Close any mappings we have since open subdir may drop the parent
|
|
//
|
|
|
|
NtfsUnpinBcb( IrpContext, &IndexEntryBcb );
|
|
NtfsReinitializeIndexContext( IrpContext, IndexContext );
|
|
IndexEntry = NULL;
|
|
|
|
//
|
|
// Remember that the current values will become the parent values.
|
|
//
|
|
|
|
ParentFcb = CreateContext->CurrentFcb;
|
|
|
|
CurrentLcb = NtfsOpenSubdirectory( IrpContext,
|
|
CurrentScb,
|
|
&FileReference,
|
|
FinalName,
|
|
FileNameFlags,
|
|
CreateContext,
|
|
&LcbForTeardown );
|
|
|
|
//
|
|
// Check that this link is a valid existing link.
|
|
//
|
|
|
|
if (LcbLinkIsDeleted( CurrentLcb ) ||
|
|
CreateContext->CurrentFcb->LinkCount == 0) {
|
|
|
|
try_return( Status = STATUS_DELETE_PENDING );
|
|
}
|
|
|
|
//
|
|
// We have progressed parsing the name. Mark it as one that needs to be inspected
|
|
// for possible reparse behavior.
|
|
//
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_INSPECT_NAME_FOR_REPARSE );
|
|
|
|
//
|
|
// Go ahead and insert this link into the splay tree if it is not
|
|
// a system file.
|
|
//
|
|
|
|
if (!FlagOn( CurrentLcb->Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
//
|
|
// See if we can insert the hash for the parent.
|
|
//
|
|
|
|
if ((CreateContext->ParentHashLength != 0) &&
|
|
(RemainingName.Length == CreateContext->FileHashLength - CreateContext->ParentHashLength - sizeof( WCHAR )) &&
|
|
!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_DOS_ONLY_COMPONENT ) &&
|
|
(CurrentLcb->FileNameAttr->Flags != FILE_NAME_DOS)) {
|
|
|
|
//
|
|
// Remove any exising hash value.
|
|
//
|
|
|
|
if (FlagOn( CurrentLcb->LcbState, LCB_STATE_VALID_HASH_VALUE )) {
|
|
|
|
NtfsRemoveHashEntriesForLcb( CurrentLcb );
|
|
#ifdef NTFS_HASH_DATA
|
|
Vcb->HashTable.ParentConflict += 1;
|
|
#endif
|
|
}
|
|
|
|
NtfsInsertHashEntry( &Vcb->HashTable,
|
|
CurrentLcb,
|
|
CreateContext->ParentHashLength,
|
|
CreateContext->ParentHashValue );
|
|
#ifdef NTFS_HASH_DATA
|
|
Vcb->HashTable.ParentInsert += 1;
|
|
#endif
|
|
}
|
|
|
|
NtfsInsertPrefix( CurrentLcb, CreateContext->CreateFlags );
|
|
}
|
|
|
|
//
|
|
// Since we have the location of this entry store the information into
|
|
// the Lcb.
|
|
//
|
|
|
|
RtlCopyMemory( &CurrentLcb->QuickIndex,
|
|
&QuickIndex,
|
|
sizeof( QUICK_INDEX ));
|
|
|
|
//
|
|
// Check if the current Lcb is a Dos-Only Name.
|
|
//
|
|
|
|
if (CurrentLcb->FileNameAttr->Flags == FILE_NAME_DOS) {
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_DOS_ONLY_COMPONENT );
|
|
}
|
|
|
|
ClearFlag( CreateContext->CreateFlags, CREATE_FLAG_FIRST_PASS);
|
|
}
|
|
|
|
//
|
|
// We now have the parent of the file to open and know whether the file exists on
|
|
// the disk. At this point we either attempt to open the target directory or
|
|
// the file itself.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY )) {
|
|
|
|
ASSERT( IndexContext != NULL );
|
|
|
|
NtfsCleanupIndexContext( IrpContext, IndexContext );
|
|
IndexContext = NULL;
|
|
|
|
//
|
|
// We don't allow attribute names or attribute codes to
|
|
// be specified.
|
|
//
|
|
|
|
if ((AttrName.Length != 0) ||
|
|
FlagOn( CreateContext->CreateFlags, CREATE_FLAG_EXPLICIT_ATTRIBUTE_CODE)) {
|
|
|
|
DebugTrace( 0, Dbg, ("Can't specify complex name for rename\n") );
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
}
|
|
|
|
//
|
|
// When SL_OPEN_TARGET_DIRECTORY is set, the directory should not be opened
|
|
// as a reparse point; FILE_OPEN_REPARSE_POINT should not be set.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_REPARSE_POINT )) {
|
|
|
|
//
|
|
// Wrong open flag, invalid parameter.
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("Can't open intermediate directory as reparse point 2.\n") );
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
try_return( Status );
|
|
}
|
|
|
|
//
|
|
// We want to copy the exact case of the name back into the
|
|
// input buffer for this case.
|
|
//
|
|
|
|
if (ExactCaseName->Buffer != NULL) {
|
|
|
|
ASSERT( ExactCaseName->Length != 0 );
|
|
ASSERT( FullFileName->MaximumLength >= ExactCaseName->MaximumLength + ExactCaseOffset );
|
|
|
|
RtlCopyMemory( Add2Ptr( FullFileName->Buffer, ExactCaseOffset ),
|
|
ExactCaseName->Buffer,
|
|
ExactCaseName->MaximumLength );
|
|
}
|
|
|
|
//
|
|
// Call our open target directory, remembering the target
|
|
// file existed.
|
|
//
|
|
|
|
Status = NtfsOpenTargetDirectory( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
CreateContext->CurrentFcb,
|
|
CurrentLcb,
|
|
&CreateContext->Cleanup.FileObject->FileName,
|
|
FinalName.Length,
|
|
CreateContext );
|
|
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
//
|
|
// If we didn't find an entry, we will try to create the file.
|
|
//
|
|
|
|
if (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_FOUND_ENTRY )) {
|
|
|
|
//
|
|
// Update our pointers to reflect that we are at the
|
|
// parent of the file we want.
|
|
//
|
|
|
|
ParentFcb = CreateContext->CurrentFcb;
|
|
|
|
//
|
|
// No point in going down the create path for a Network Query.
|
|
//
|
|
|
|
if (CreateContext->NetworkInfo) {
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
} else {
|
|
|
|
Status = NtfsCreateNewFile( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
CurrentScb,
|
|
FileNameAttr,
|
|
*FullFileName,
|
|
FinalName,
|
|
AttrName,
|
|
AttrCode,
|
|
&IndexContext,
|
|
CreateContext,
|
|
&LcbForTeardown );
|
|
}
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_CREATE_FILE_CASE );
|
|
|
|
//
|
|
// Otherwise we call our routine to open the file.
|
|
//
|
|
|
|
} else {
|
|
|
|
ASSERT( IndexContext != NULL );
|
|
|
|
NtfsCleanupIndexContext( IrpContext, IndexContext );
|
|
IndexContext = NULL;
|
|
|
|
ParentFcb = CreateContext->CurrentFcb;
|
|
|
|
#ifdef BENL_DBG
|
|
ASSERT( IrpContext->TransactionId == 0 );
|
|
#endif
|
|
|
|
Status = NtfsOpenFile( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
CurrentScb,
|
|
IndexEntry,
|
|
*FullFileName,
|
|
FinalName,
|
|
AttrName,
|
|
AttrCode,
|
|
&QuickIndex,
|
|
CreateContext,
|
|
&LcbForTeardown );
|
|
}
|
|
|
|
try_exit: NOTHING;
|
|
|
|
//
|
|
// If we raise below then we need to back out any failed opens.
|
|
//
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_BACKOUT_FAILED_OPENS );
|
|
|
|
//
|
|
// Abort transaction on err by raising.
|
|
//
|
|
|
|
if ((Status != STATUS_PENDING) && (Status != STATUS_WAIT_FOR_OPLOCK)) {
|
|
|
|
NtfsCleanupTransaction( IrpContext, Status, FALSE );
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCommonCreate );
|
|
|
|
//
|
|
// If we only have the current Fcb shared then simply give it up.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_SHARED_PARENT_FCB ) && (CreateContext->CurrentFcb != NULL)) {
|
|
|
|
NtfsReleaseFcb( IrpContext, CreateContext->CurrentFcb );
|
|
CreateContext->CurrentFcb = NULL;
|
|
}
|
|
|
|
//
|
|
// Unpin the index entry.
|
|
//
|
|
|
|
NtfsUnpinBcb( IrpContext, &IndexEntryBcb );
|
|
|
|
//
|
|
// Cleanup the index context if used.
|
|
//
|
|
|
|
if (IndexContext != NULL) {
|
|
|
|
NtfsCleanupIndexContext( IrpContext, IndexContext );
|
|
}
|
|
|
|
//
|
|
// Free the file name attribute if we allocated it.
|
|
//
|
|
|
|
if (FileNameAttr != NULL) {
|
|
|
|
NtfsFreePool( FileNameAttr );
|
|
}
|
|
|
|
//
|
|
// Capture the status code from the IrpContext if we are in the exception path.
|
|
//
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
Status = IrpContext->ExceptionStatus;
|
|
}
|
|
|
|
//
|
|
// If this is the oplock completion path then don't do any of this completion work,
|
|
// The Irp may already have been posted to another thread.
|
|
//
|
|
|
|
if ((Status != STATUS_PENDING) && (Status != STATUS_WAIT_FOR_OPLOCK)) {
|
|
|
|
//
|
|
// If we successfully opened the file, we need to update the in-memory
|
|
// structures.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status ) && (Status != STATUS_REPARSE)) {
|
|
|
|
//
|
|
// If the create completed, there's no reason why we shouldn't have
|
|
// a valid ThisScb now.
|
|
//
|
|
|
|
ASSERT( CreateContext->ThisScb != NULL );
|
|
|
|
//
|
|
// If we modified the original file name, we can delete the original
|
|
// buffer.
|
|
//
|
|
|
|
if ((OriginalFileName->Buffer != NULL) &&
|
|
(OriginalFileName->Buffer != FullFileName->Buffer)) {
|
|
|
|
NtfsFreePool( OriginalFileName->Buffer );
|
|
}
|
|
|
|
//
|
|
// Do our normal processing if this is not a Network Info query.
|
|
//
|
|
|
|
if (CreateContext->NetworkInfo == NULL) {
|
|
|
|
//
|
|
// Find the Lcb for this open.
|
|
//
|
|
|
|
CurrentLcb = CreateContext->ThisCcb->Lcb;
|
|
|
|
//
|
|
// Check if we were opening a paging file and if so then make sure that
|
|
// the internal attribute stream is all closed down
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )) {
|
|
|
|
NtfsDeleteInternalAttributeStream( CreateContext->ThisScb, TRUE, FALSE );
|
|
}
|
|
|
|
//
|
|
// If we are not done with a large allocation for a new attribute,
|
|
// then we must make sure that no one can open the file until we
|
|
// try to get it extended. Do this before dropping the Vcb.
|
|
//
|
|
|
|
if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_LARGE_ALLOCATION )) {
|
|
|
|
//
|
|
// For a new file, we can clear the link count and mark the
|
|
// Lcb (if there is one) delete on close.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_CREATE_FILE_CASE )) {
|
|
|
|
CreateContext->CurrentFcb->LinkCount = 0;
|
|
|
|
SetFlag( CurrentLcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
|
|
|
|
//
|
|
// If we just created an attribute, then we will mark that attribute
|
|
// delete on close to prevent it from being opened.
|
|
//
|
|
|
|
} else {
|
|
|
|
SetFlag( CreateContext->ThisScb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Remember the POSIX flag and whether we had to do any traverse
|
|
// access checking.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE )) {
|
|
|
|
SetFlag( CreateContext->ThisCcb->Flags, CCB_FLAG_IGNORE_CASE );
|
|
}
|
|
|
|
//
|
|
// Remember if this user needs to do traverse checks so we can show him the
|
|
// name from the root.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK )) {
|
|
|
|
SetFlag( CreateContext->ThisCcb->Flags, CCB_FLAG_TRAVERSE_CHECK );
|
|
}
|
|
|
|
//
|
|
// Remember who this user is so we know whether to allow
|
|
// raw reads and writes of encrypted data.
|
|
//
|
|
|
|
{
|
|
PACCESS_STATE AccessState;
|
|
PRIVILEGE_SET PrivilegeSet;
|
|
|
|
AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState;
|
|
|
|
//
|
|
// No flags should be preset
|
|
//
|
|
|
|
ASSERT( CreateContext->ThisCcb->AccessFlags == 0 );
|
|
|
|
//
|
|
// This will set the READ_DATA_ACCESS, WRITE_DATA_ACCESS,
|
|
// APPEND_DATA_ACCESS, and EXECUTE_ACCESS bits correctly.
|
|
//
|
|
|
|
SetFlag( CreateContext->ThisCcb->AccessFlags,
|
|
FlagOn( AccessState->PreviouslyGrantedAccess, FILE_READ_DATA |
|
|
FILE_WRITE_DATA |
|
|
FILE_APPEND_DATA |
|
|
FILE_EXECUTE |
|
|
FILE_WRITE_ATTRIBUTES |
|
|
FILE_READ_ATTRIBUTES ));
|
|
|
|
//
|
|
// Here we're setting BACKUP_ACCESS and RESTORE_ACCESS. We want to set
|
|
// the Ccb flag if the user has the privilege AND they opened the file up
|
|
// with an access that is interesting. For example backup or restore will give
|
|
// you synchronize but if you open the file up only for that we don't want
|
|
// to remember the privileges (its too ambiguous and you'll backup or restore
|
|
// depending op whether you're local or remote))
|
|
//
|
|
|
|
if (FlagOn( AccessState->PreviouslyGrantedAccess, NTFS_REQUIRES_BACKUP ) &&
|
|
FlagOn( AccessState->Flags, TOKEN_HAS_BACKUP_PRIVILEGE )) {
|
|
|
|
SetFlag( CreateContext->ThisCcb->AccessFlags, BACKUP_ACCESS );
|
|
}
|
|
|
|
if (FlagOn( AccessState->PreviouslyGrantedAccess, NTFS_REQUIRES_RESTORE ) &&
|
|
FlagOn( AccessState->Flags, TOKEN_HAS_RESTORE_PRIVILEGE )) {
|
|
|
|
SetFlag( CreateContext->ThisCcb->AccessFlags, RESTORE_ACCESS );
|
|
}
|
|
|
|
PrivilegeSet.PrivilegeCount = 1;
|
|
PrivilegeSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
|
|
PrivilegeSet.Privilege[0].Luid = RtlConvertLongToLuid( SE_MANAGE_VOLUME_PRIVILEGE );
|
|
PrivilegeSet.Privilege[0].Attributes = 0;
|
|
|
|
if (SePrivilegeCheck( &PrivilegeSet,
|
|
&AccessState->SubjectSecurityContext,
|
|
NtfsEffectiveMode( Irp, IrpSp ))) {
|
|
|
|
SetFlag( CreateContext->ThisCcb->AccessFlags, MANAGE_VOLUME_ACCESS );
|
|
}
|
|
}
|
|
|
|
//
|
|
// We don't do "delete on close" for directories or open
|
|
// by ID files.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_DELETE_ON_CLOSE ) &&
|
|
(!FlagOn( CreateContext->ThisCcb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) ||
|
|
!FlagOn( CreateContext->ThisCcb->Flags, CCB_FLAG_OPEN_AS_FILE ))) {
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_DELETE_ON_CLOSE );
|
|
|
|
//
|
|
// We modify the Scb and Lcb here only if we aren't in the
|
|
// large allocation case.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_LARGE_ALLOCATION )) {
|
|
|
|
SetFlag( CreateContext->ThisCcb->Flags, CCB_FLAG_DELETE_ON_CLOSE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is a named stream open and we have set any of our notify
|
|
// flags then report the changes.
|
|
//
|
|
|
|
if ((Vcb->NotifyCount != 0) &&
|
|
!FlagOn( CreateContext->ThisCcb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) &&
|
|
(CreateContext->ThisScb->AttributeName.Length != 0) &&
|
|
NtfsIsTypeCodeUserData( CreateContext->ThisScb->AttributeTypeCode ) &&
|
|
FlagOn( CreateContext->ThisScb->ScbState,
|
|
SCB_STATE_NOTIFY_ADD_STREAM |
|
|
SCB_STATE_NOTIFY_RESIZE_STREAM |
|
|
SCB_STATE_NOTIFY_MODIFY_STREAM )) {
|
|
|
|
ULONG Filter = 0;
|
|
ULONG Action;
|
|
|
|
//
|
|
// Start by checking for an add.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->ThisScb->ScbState, SCB_STATE_NOTIFY_ADD_STREAM )) {
|
|
|
|
Filter = FILE_NOTIFY_CHANGE_STREAM_NAME;
|
|
Action = FILE_ACTION_ADDED_STREAM;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Check if the file size changed.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->ThisScb->ScbState, SCB_STATE_NOTIFY_RESIZE_STREAM )) {
|
|
|
|
Filter = FILE_NOTIFY_CHANGE_STREAM_SIZE;
|
|
}
|
|
|
|
//
|
|
// Now check if the stream data was modified.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->ThisScb->ScbState, SCB_STATE_NOTIFY_MODIFY_STREAM )) {
|
|
|
|
Filter |= FILE_NOTIFY_CHANGE_STREAM_WRITE;
|
|
}
|
|
|
|
Action = FILE_ACTION_MODIFIED_STREAM;
|
|
}
|
|
|
|
ASSERT( CreateContext->ThisScb && CreateContext->ThisCcb );
|
|
ASSERT( (CreateContext->ThisCcb->NodeTypeCode == NTFS_NTC_CCB_INDEX) || (CreateContext->ThisCcb->NodeTypeCode == NTFS_NTC_CCB_DATA) );
|
|
|
|
NtfsUnsafeReportDirNotify( IrpContext,
|
|
Vcb,
|
|
&CreateContext->ThisCcb->FullFileName,
|
|
CreateContext->ThisCcb->LastFileNameOffset,
|
|
&CreateContext->ThisScb->AttributeName,
|
|
((FlagOn( CreateContext->ThisCcb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
|
|
(CreateContext->ThisCcb->Lcb != NULL) &&
|
|
(CreateContext->ThisCcb->Lcb->Scb->ScbType.Index.NormalizedName.Length != 0)) ?
|
|
&CreateContext->ThisCcb->Lcb->Scb->ScbType.Index.NormalizedName :
|
|
NULL),
|
|
Filter,
|
|
Action,
|
|
NULL );
|
|
}
|
|
|
|
ClearFlag( CreateContext->ThisScb->ScbState,
|
|
SCB_STATE_NOTIFY_ADD_STREAM |
|
|
SCB_STATE_NOTIFY_REMOVE_STREAM |
|
|
SCB_STATE_NOTIFY_RESIZE_STREAM |
|
|
SCB_STATE_NOTIFY_MODIFY_STREAM );
|
|
|
|
//
|
|
// Otherwise copy the data out of the Scb/Fcb and return to our caller.
|
|
//
|
|
|
|
} else {
|
|
|
|
NtfsFillNetworkOpenInfo( CreateContext->NetworkInfo, CreateContext->ThisScb );
|
|
|
|
//
|
|
// Teardown the Fcb if we should. We're in a success path
|
|
// here so we don't have to worry about aborting anymore and the
|
|
// need to hold any resources
|
|
//
|
|
|
|
if (!CreateContext->ThisScb->CleanupCount && !CreateContext->ThisScb->Fcb->DelayedCloseCount) {
|
|
if (!NtfsAddScbToFspClose( IrpContext, CreateContext->ThisScb, TRUE )) {
|
|
|
|
if (NtfsIsExclusiveScb( Vcb->MftScb ) ||
|
|
(NtfsPerformQuotaOperation( CreateContext->CurrentFcb ) &&
|
|
NtfsIsSharedScb( Vcb->QuotaTableScb ))) {
|
|
|
|
SetFlag( AcquireFlags, ACQUIRE_DONT_WAIT );
|
|
}
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
CreateContext->CurrentFcb,
|
|
LcbForTeardown,
|
|
(BOOLEAN) (IrpContext->TransactionId != 0),
|
|
AcquireFlags,
|
|
NULL );
|
|
}
|
|
}
|
|
|
|
Irp->IoStatus.Information = sizeof( FILE_NETWORK_OPEN_INFORMATION );
|
|
|
|
Status = Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Start a teardown on the last Fcb found and restore the name strings on
|
|
// a retryable error.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Perform the necessary cleanup if we raised writing a UsnJournal.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_BACKOUT_FAILED_OPENS )) {
|
|
|
|
NtfsBackoutFailedOpens( IrpContext, IrpSp->FileObject, CreateContext->CurrentFcb, CreateContext->ThisScb, CreateContext->ThisCcb );
|
|
|
|
}
|
|
|
|
//
|
|
// Start the cleanup process if we have looked at any Fcb's.
|
|
// We tell TeardownStructures not to remove any Scb's in
|
|
// the open attribute table if there is a transaction underway.
|
|
//
|
|
|
|
if (CreateContext->CurrentFcb != NULL) {
|
|
|
|
if (NtfsIsExclusiveScb( Vcb->MftScb ) ||
|
|
(NtfsPerformQuotaOperation( CreateContext->CurrentFcb ) &&
|
|
NtfsIsSharedScb( Vcb->QuotaTableScb ))) {
|
|
|
|
SetFlag( AcquireFlags, ACQUIRE_DONT_WAIT );
|
|
}
|
|
|
|
//
|
|
// Someone may have tried to open the $Bitmap stream. We catch that and
|
|
// fail it but the Fcb won't be in the exclusive list to be released.
|
|
//
|
|
|
|
if (NtfsEqualMftRef( &CreateContext->CurrentFcb->FileReference, &BitmapFileReference )) {
|
|
|
|
NtfsReleaseFcb( IrpContext, CreateContext->CurrentFcb );
|
|
|
|
} else {
|
|
|
|
BOOLEAN RemovedFcb;
|
|
|
|
//
|
|
// In transactions that don't own any system resources we must
|
|
// make sure that we don't release all of the resources before
|
|
// the transaction commits. Otherwise we won't correctly serialize
|
|
// with clean checkpoints who wants to know the transaction
|
|
// table is empty. Case in point is if we create the parent Scb
|
|
// and file Fcb in this call and tear them down in Teardown below.
|
|
// If there are no other resources held then we have an open
|
|
// transaction but no serialization.
|
|
//
|
|
// In general we can simply acquire a system resource and put it
|
|
// in the exlusive list in the IrpContext. The best choice is
|
|
// the Mft. HOWEVER there is a strange deadlock path if we
|
|
// try to acquire this while owning the security mutext. This
|
|
// can happen in the CreateNewFile path if we are creating a
|
|
// new security descriptor. So we need to add this check
|
|
// before we acquire the Mft, owning the security stream will
|
|
// give us the transaction protection we need.
|
|
//
|
|
// Possible future cleanup is to change how we acquire the security
|
|
// file after the security mutex. Ideally the security mutex would
|
|
// be a true end resource.
|
|
//
|
|
|
|
if ((IrpContext->TransactionId != 0) &&
|
|
(CreateContext->CurrentFcb->CleanupCount == 0) &&
|
|
((CreateDisposition == FILE_OVERWRITE_IF) ||
|
|
(CreateDisposition == FILE_OVERWRITE) ||
|
|
(CreateDisposition == FILE_SUPERSEDE)) &&
|
|
((Vcb->SecurityDescriptorStream == NULL) ||
|
|
(!NtfsIsSharedScb( Vcb->SecurityDescriptorStream )))) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->MftScb );
|
|
SetFlag( AcquireFlags, ACQUIRE_DONT_WAIT );
|
|
}
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
(CreateContext->ThisScb != NULL) ? (PVOID) CreateContext->ThisScb : CreateContext->CurrentFcb,
|
|
LcbForTeardown,
|
|
(BOOLEAN) (IrpContext->TransactionId != 0),
|
|
AcquireFlags,
|
|
&RemovedFcb );
|
|
|
|
}
|
|
}
|
|
|
|
if ((Status == STATUS_LOG_FILE_FULL) ||
|
|
(Status == STATUS_CANT_WAIT) ||
|
|
(Status == STATUS_REPARSE)) {
|
|
|
|
//
|
|
// Recover the exact case name if present for a retryable condition.
|
|
// and we haven't already recopied it back (ExactCaseName->Length == 0)
|
|
//
|
|
|
|
if ((ExactCaseName->Buffer != OriginalFileName->Buffer) &&
|
|
(ExactCaseName->Buffer != NULL) &&
|
|
(ExactCaseName->Length != 0)) {
|
|
|
|
ASSERT( OriginalFileName->MaximumLength >= ExactCaseName->MaximumLength );
|
|
|
|
RtlCopyMemory( OriginalFileName->Buffer,
|
|
ExactCaseName->Buffer,
|
|
ExactCaseName->MaximumLength );
|
|
}
|
|
|
|
//
|
|
// Restitute the access control state to what it was when we entered the request.
|
|
//
|
|
|
|
IrpSp->Parameters.Create.SecurityContext->AccessState->RemainingDesiredAccess = CreateContext->Cleanup.RemainingDesiredAccess;
|
|
IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess = CreateContext->Cleanup.PreviouslyGrantedAccess;
|
|
IrpSp->Parameters.Create.SecurityContext->DesiredAccess = CreateContext->Cleanup.DesiredAccess;
|
|
}
|
|
|
|
//
|
|
// Free any buffer we allocated.
|
|
//
|
|
|
|
if ((FullFileName->Buffer != NULL) &&
|
|
(OriginalFileName->Buffer != FullFileName->Buffer)) {
|
|
|
|
DebugTrace( 0, Dbg, ("FullFileName->Buffer will be de-allocated %x\n", FullFileName->Buffer) );
|
|
NtfsFreePool( FullFileName->Buffer );
|
|
DebugDoit( FullFileName->Buffer = NULL );
|
|
}
|
|
|
|
//
|
|
// Set the file name in the file object back to it's original value.
|
|
//
|
|
|
|
CreateContext->Cleanup.FileObject->FileName = *OriginalFileName;
|
|
|
|
//
|
|
// Always clear the LARGE_ALLOCATION flag so we don't get
|
|
// spoofed by STATUS_REPARSE.
|
|
//
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_LARGE_ALLOCATION );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Always free the exact case name if allocated and it doesn't match the original
|
|
// name buffer.
|
|
//
|
|
|
|
if ((ExactCaseName->Buffer != OriginalFileName->Buffer) &&
|
|
(ExactCaseName->Buffer != NULL)) {
|
|
|
|
DebugTrace( 0, Dbg, ("ExactCaseName->Buffer will be de-allocated %x\n", ExactCaseName->Buffer) );
|
|
NtfsFreePool( ExactCaseName->Buffer );
|
|
DebugDoit( ExactCaseName->Buffer = NULL );
|
|
}
|
|
|
|
//
|
|
// We always give up the Vcb.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_ACQUIRED_VCB )) {
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we didn't post this Irp then take action to complete the irp.
|
|
//
|
|
|
|
if ((Status != STATUS_PENDING) && (Status != STATUS_WAIT_FOR_OPLOCK)) {
|
|
|
|
//
|
|
// If the current status is success and there is more allocation to
|
|
// allocate then complete the allocation.
|
|
//
|
|
|
|
if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_LARGE_ALLOCATION ) &&
|
|
NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// If the Create was successful, but we did not get all of the space
|
|
// allocated that we wanted, we have to complete the allocation now.
|
|
// Basically what we do is commit the current transaction and call
|
|
// NtfsAddAllocation to get the rest of the space. Then if the log
|
|
// file fills up (or we are posting for other reasons) we turn the
|
|
// Irp into an Irp which is just trying to extend the file. If we
|
|
// get any other kind of error, then we just delete the file and
|
|
// return with the error from create.
|
|
//
|
|
|
|
Status = NtfsCompleteLargeAllocation( IrpContext,
|
|
Irp,
|
|
CurrentLcb,
|
|
CreateContext->ThisScb,
|
|
CreateContext->ThisCcb,
|
|
CreateContext->CreateFlags );
|
|
}
|
|
|
|
//
|
|
// If our caller told us not to complete the irp, or if this
|
|
// is a network open, we don't really complete the irp.
|
|
// EFS_CREATES have PostCreate callouts to do before the
|
|
// irp gets completed, and before the irp context gets deleted,
|
|
// and the caller will do that for us. We should at least
|
|
// cleanup the irp context if our caller won't.
|
|
//
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_EFS_CREATE ) ||
|
|
(CreateContext->NetworkInfo != NULL)) {
|
|
|
|
NtfsCompleteRequest( IrpContext,
|
|
NULL,
|
|
Status );
|
|
#ifdef NTFSDBG
|
|
ASSERT( None == IrpContext->OwnershipState );
|
|
#endif
|
|
|
|
} else {
|
|
|
|
NtfsCompleteRequest( IrpContext,
|
|
Irp,
|
|
Status );
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCommonCreate: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsCommonVolumeOpen (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is opening the Volume Dasd file. We have already done all the
|
|
checks needed to verify that the user is opening the $DATA attribute.
|
|
We check the security attached to the file and take some special action
|
|
based on a volume open.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of this operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PFILE_OBJECT FileObject;
|
|
|
|
PVCB Vcb;
|
|
PFCB ThisFcb;
|
|
PCCB ThisCcb;
|
|
|
|
BOOLEAN VcbAcquired = FALSE;
|
|
|
|
BOOLEAN SharingViolation;
|
|
BOOLEAN LockVolume = FALSE;
|
|
BOOLEAN NotifyLockFailed = FALSE;
|
|
BOOLEAN DelayFlush = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL ));
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCommonVolumeOpen: Entered\n") );
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
FileObject = IrpSp->FileObject;
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Start by checking the create disposition. We can only open this
|
|
// file.
|
|
//
|
|
|
|
{
|
|
ULONG CreateDisposition;
|
|
|
|
CreateDisposition = (IrpSp->Parameters.Create.Options >> 24) & 0x000000ff;
|
|
|
|
if ((CreateDisposition != FILE_OPEN) && (CreateDisposition != FILE_OPEN_IF)) {
|
|
|
|
try_return( Status = STATUS_ACCESS_DENIED );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure the directory flag isn't set for the volume open.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_DIRECTORY_FILE )) {
|
|
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// If this volume open is going to generate an implicit volume lock
|
|
// (a la autochk), notify anyone who wants to close their handles so
|
|
// the lock can happen. We need to do this before we acquire any resources.
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.ShareAccess,
|
|
FILE_SHARE_WRITE | FILE_SHARE_DELETE )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Sending lock notification\n") );
|
|
FsRtlNotifyVolumeEvent( FileObject, FSRTL_VOLUME_LOCK );
|
|
NotifyLockFailed = TRUE;
|
|
}
|
|
|
|
//
|
|
// Acquire the Vcb and verify the volume isn't locked.
|
|
//
|
|
|
|
Vcb = &((PVOLUME_DEVICE_OBJECT) IrpSp->DeviceObject)->Vcb;
|
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|
VcbAcquired = TRUE;
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_PERFORMED_DISMOUNT )) {
|
|
|
|
try_return( Status = STATUS_ACCESS_DENIED );
|
|
}
|
|
|
|
//
|
|
// We do give READ-WRITE access to the volume even when
|
|
// it's actually write protected. This is just so that we won't break
|
|
// any apps. However, we don't let the user actually do any modifications.
|
|
//
|
|
|
|
// if ((NtfsIsVolumeReadOnly( Vcb )) &&
|
|
// (FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
|
|
// FILE_WRITE_DATA | FILE_APPEND_DATA | DELETE ))) {
|
|
//
|
|
// try_return( Status = STATUS_MEDIA_WRITE_PROTECTED );
|
|
//}
|
|
|
|
//
|
|
// Ping the volume to make sure the Vcb is still mounted. If we need
|
|
// to verify the volume then do it now, and if it comes out okay
|
|
// then clear the verify volume flag in the device object and continue
|
|
// on. If it doesn't verify okay then dismount the volume and
|
|
// either tell the I/O system to try and create again (with a new mount)
|
|
// or that the volume is wrong. This later code is returned if we
|
|
// are trying to do a relative open and the vcb is no longer mounted.
|
|
//
|
|
|
|
if (!NtfsPingVolume( IrpContext, Vcb, NULL ) ||
|
|
!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
|
|
|
|
if (!NtfsPerformVerifyOperation( IrpContext, Vcb )) {
|
|
|
|
//
|
|
// We need checkpoint sync to do a dismount which must
|
|
// be acquired before the vcb - after dropping the vcb
|
|
// we must retest the volume
|
|
//
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
VcbAcquired = FALSE;
|
|
NtfsAcquireCheckpointSynchronization( IrpContext, Vcb );
|
|
|
|
try {
|
|
|
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|
VcbAcquired = TRUE;
|
|
|
|
if (!NtfsPerformVerifyOperation( IrpContext, Vcb )) {
|
|
|
|
NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE, NULL );
|
|
NtfsRaiseStatus( IrpContext, STATUS_WRONG_VOLUME, NULL, NULL );
|
|
}
|
|
} finally {
|
|
NtfsReleaseCheckpointSynchronization( IrpContext, Vcb );
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// The volume verified correctly so now clear the verify bit
|
|
// and continue with the create
|
|
//
|
|
|
|
ClearFlag( Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME );
|
|
}
|
|
|
|
//
|
|
// Now acquire the Fcb for the VolumeDasd and verify the user has
|
|
// permission to open the volume.
|
|
//
|
|
|
|
ThisFcb = Vcb->VolumeDasdScb->Fcb;
|
|
|
|
NtfsAcquireExclusiveFcb( IrpContext, ThisFcb, NULL, 0 );
|
|
NtfsOpenCheck( IrpContext, ThisFcb, NULL, Irp );
|
|
NtfsReleaseFcb( IrpContext, ThisFcb );
|
|
|
|
//
|
|
// If the user does not want to share write or delete then we will try
|
|
// and take out a lock on the volume.
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.ShareAccess,
|
|
FILE_SHARE_WRITE | FILE_SHARE_DELETE )) {
|
|
|
|
//
|
|
// Do a quick test of the volume cleanup count if this opener won't
|
|
// share with anyone. We can safely examine the cleanup count without
|
|
// further synchronization because we are guaranteed to have the
|
|
// Vcb exclusive at this point.
|
|
//
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (!FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_READ ) &&
|
|
((Vcb->SyscacheScb && (Vcb->CleanupCount != 1)) ||
|
|
(!Vcb->SyscacheScb && (Vcb->CleanupCount != 0)))) {
|
|
#else
|
|
if (!FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_READ ) &&
|
|
(Vcb->CleanupCount != 0)) {
|
|
#endif
|
|
|
|
try_return( Status = STATUS_SHARING_VIOLATION );
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
}
|
|
#else
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Go ahead and flush and purge the volume. Then test to see if all
|
|
// of the user file objects were closed. This will release the dasd fcb
|
|
//
|
|
|
|
Status = NtfsFlushVolume( IrpContext, Vcb, TRUE, TRUE, TRUE, FALSE );
|
|
|
|
//
|
|
// We don't care about certain errors in the flush path.
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// If there are no conflicts but the status indicates disk corruption
|
|
// or a section that couldn't be removed then ignore the error. We
|
|
// allow this open to succeed so that chkdsk can open the volume to
|
|
// repair the damage.
|
|
//
|
|
|
|
if ((Status == STATUS_UNABLE_TO_DELETE_SECTION) ||
|
|
(Status == STATUS_DISK_CORRUPT_ERROR) ||
|
|
(Status == STATUS_FILE_CORRUPT_ERROR)) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the flush and purge was successful but there are still file objects
|
|
// that block this open it is possible that the FspClose thread is
|
|
// blocked behind the Vcb. Drop the Fcb and Vcb to allow this thread
|
|
// to get in and then reacquire them. This will give this Dasd open
|
|
// another chance to succeed on the first try.
|
|
//
|
|
|
|
SharingViolation = FALSE;
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_READ)) {
|
|
|
|
if (Vcb->ReadOnlyCloseCount != (Vcb->CloseCount - Vcb->SystemFileCloseCount)) {
|
|
|
|
SharingViolation = TRUE;
|
|
}
|
|
|
|
} else if (Vcb->CloseCount != Vcb->SystemFileCloseCount) {
|
|
|
|
SharingViolation = TRUE;
|
|
}
|
|
|
|
if (SharingViolation && NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// We need to commit the current transaction and release any
|
|
// resources. This will release the Fcb for the volume as
|
|
// well. Explicitly release the Vcb.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
while (!IsListEmpty(&IrpContext->ExclusiveFcbList)) {
|
|
|
|
NtfsReleaseFcbWithPaging( IrpContext,
|
|
(PFCB)CONTAINING_RECORD(IrpContext->ExclusiveFcbList.Flink,
|
|
FCB,
|
|
ExclusiveFcbLinks ));
|
|
}
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RELEASE_USN_JRNL |
|
|
IRP_CONTEXT_FLAG_RELEASE_MFT );
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
VcbAcquired = FALSE;
|
|
|
|
CcWaitForCurrentLazyWriterActivity();
|
|
|
|
//
|
|
// Now explicitly reacquire the Vcb. Test that no one
|
|
// else got in to lock the volume in the meantime.
|
|
//
|
|
|
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|
VcbAcquired = TRUE;
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_PERFORMED_DISMOUNT )) {
|
|
|
|
try_return( Status = STATUS_ACCESS_DENIED );
|
|
}
|
|
|
|
//
|
|
// Duplicate the flush/purge and test if there is no sharing
|
|
// violation. This will release the dasd fcb
|
|
//
|
|
|
|
Status = NtfsFlushVolume( IrpContext, Vcb, TRUE, TRUE, TRUE, FALSE );
|
|
|
|
//
|
|
// We don't care about certain errors in the flush path.
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// If there are no conflicts but the status indicates disk corruption
|
|
// or a section that couldn't be removed then ignore the error. We
|
|
// allow this open to succeed so that chkdsk can open the volume to
|
|
// repair the damage.
|
|
//
|
|
|
|
if ((Status == STATUS_UNABLE_TO_DELETE_SECTION) ||
|
|
(Status == STATUS_DISK_CORRUPT_ERROR) ||
|
|
(Status == STATUS_FILE_CORRUPT_ERROR)) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
SharingViolation = FALSE;
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_READ)) {
|
|
|
|
if (Vcb->ReadOnlyCloseCount != (Vcb->CloseCount - Vcb->SystemFileCloseCount)) {
|
|
|
|
SharingViolation = TRUE;
|
|
}
|
|
|
|
} else if (Vcb->CloseCount != Vcb->SystemFileCloseCount) {
|
|
|
|
SharingViolation = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return an error if there are still conflicting file objects.
|
|
//
|
|
|
|
if (SharingViolation) {
|
|
|
|
//
|
|
// If there was an error in the flush then return it. Otherwise
|
|
// return SHARING_VIOLATION.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
try_return( Status = STATUS_SHARING_VIOLATION );
|
|
|
|
} else {
|
|
|
|
try_return( Status );
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// If there are no conflicts but the status indicates disk corruption
|
|
// or a section that couldn't be removed then ignore the error. We
|
|
// allow this open to succeed so that chkdsk can open the volume to
|
|
// repair the damage.
|
|
//
|
|
|
|
if ((Status == STATUS_UNABLE_TO_DELETE_SECTION) ||
|
|
(Status == STATUS_DISK_CORRUPT_ERROR) ||
|
|
(Status == STATUS_FILE_CORRUPT_ERROR)) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Fail this request on any other failures.
|
|
//
|
|
|
|
} else {
|
|
|
|
try_return( Status );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Remember that we want to lock the volume if the user plans to write.
|
|
// This is to allow autochk to fiddle with the volume.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
|
|
FILE_WRITE_DATA | FILE_APPEND_DATA )) {
|
|
|
|
LockVolume = TRUE;
|
|
}
|
|
|
|
//
|
|
// Just flush the volume data if the user requested read or write and the volume isn't
|
|
// readonly. No need to purge or lock the volume.
|
|
//
|
|
|
|
} else if (FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
|
|
FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA ) &&
|
|
!FlagOn( Vcb->VcbState, VCB_STATE_MOUNT_READ_ONLY )) {
|
|
|
|
DelayFlush = TRUE;
|
|
}
|
|
|
|
//
|
|
// Put the Volume Dasd name in the file object. This is during the create / open path
|
|
// we're the only one with access to the fileobject
|
|
//
|
|
|
|
{
|
|
PVOID Temp = FileObject->FileName.Buffer;
|
|
|
|
FileObject->FileName.Buffer =
|
|
FsRtlAllocatePoolWithTag(PagedPool, 8 * sizeof( WCHAR ), MODULE_POOL_TAG );
|
|
|
|
if (Temp != NULL) {
|
|
|
|
NtfsFreePool( Temp );
|
|
}
|
|
|
|
RtlCopyMemory( FileObject->FileName.Buffer, L"\\$Volume", 8 * sizeof( WCHAR ));
|
|
FileObject->FileName.MaximumLength =
|
|
FileObject->FileName.Length = 8*2;
|
|
}
|
|
|
|
//
|
|
// We never allow cached access to the volume file.
|
|
//
|
|
|
|
ClearFlag( FileObject->Flags, FO_CACHE_SUPPORTED );
|
|
SetFlag( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING );
|
|
|
|
//
|
|
// Go ahead open the attribute. This should only fail if there is an
|
|
// allocation failure or share access failure.
|
|
//
|
|
|
|
NtfsAcquireExclusiveFcb( IrpContext, ThisFcb, NULL, 0 );
|
|
|
|
if (NT_SUCCESS( Status = NtfsOpenAttribute( IrpContext,
|
|
IrpSp,
|
|
Vcb,
|
|
NULL,
|
|
ThisFcb,
|
|
2,
|
|
NtfsEmptyString,
|
|
$DATA,
|
|
(ThisFcb->CleanupCount == 0 ?
|
|
SetShareAccess :
|
|
CheckShareAccess),
|
|
UserVolumeOpen,
|
|
FALSE,
|
|
CCB_FLAG_OPEN_AS_FILE,
|
|
NULL,
|
|
&Vcb->VolumeDasdScb,
|
|
&ThisCcb ))) {
|
|
|
|
//
|
|
// Perform the final initialization.
|
|
//
|
|
|
|
//
|
|
// Check if we can administer the volume and note it in the ccb
|
|
//
|
|
|
|
//
|
|
// If the user was granted both read and write access then
|
|
// he can administer the volume. This allows the interactive
|
|
// user to manage removable media if allowed by the access.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
|
|
FILE_READ_DATA | FILE_WRITE_DATA ) == (FILE_READ_DATA | FILE_WRITE_DATA)) {
|
|
|
|
SetFlag( ThisCcb->AccessFlags, MANAGE_VOLUME_ACCESS );
|
|
|
|
//
|
|
// We can also grant it through our ACL.
|
|
//
|
|
|
|
} else if (NtfsCanAdministerVolume( IrpContext, Irp, ThisFcb, NULL, NULL )) {
|
|
|
|
SetFlag( ThisCcb->AccessFlags, MANAGE_VOLUME_ACCESS );
|
|
|
|
//
|
|
// We can also grant this through the MANAGE_VOLUME_PRIVILEGE.
|
|
//
|
|
|
|
} else {
|
|
|
|
PRIVILEGE_SET PrivilegeSet;
|
|
|
|
PrivilegeSet.PrivilegeCount = 1;
|
|
PrivilegeSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
|
|
PrivilegeSet.Privilege[0].Luid = RtlConvertLongToLuid( SE_MANAGE_VOLUME_PRIVILEGE );
|
|
PrivilegeSet.Privilege[0].Attributes = 0;
|
|
|
|
if (SePrivilegeCheck( &PrivilegeSet,
|
|
&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext,
|
|
NtfsEffectiveMode( Irp, IrpSp ))) {
|
|
|
|
SetFlag( ThisCcb->AccessFlags, MANAGE_VOLUME_ACCESS );
|
|
|
|
//
|
|
// Well nothing else worked. Now we need to look at the security
|
|
// descriptor on the device.
|
|
//
|
|
|
|
} else {
|
|
|
|
NTSTATUS SeStatus;
|
|
BOOLEAN MemoryAllocated = FALSE;
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor = NULL;
|
|
ULONG RequestedAccess = FILE_READ_DATA | FILE_WRITE_DATA;
|
|
|
|
SeStatus = ObGetObjectSecurity( Vcb->Vpb->RealDevice,
|
|
&SecurityDescriptor,
|
|
&MemoryAllocated );
|
|
|
|
if (SeStatus == STATUS_SUCCESS) {
|
|
|
|
//
|
|
// If there is a security descriptor then check the access.
|
|
//
|
|
|
|
if (SecurityDescriptor != NULL) {
|
|
|
|
if (NtfsCanAdministerVolume( IrpContext,
|
|
Irp,
|
|
ThisFcb,
|
|
SecurityDescriptor,
|
|
&RequestedAccess )) {
|
|
|
|
SetFlag( ThisCcb->AccessFlags, MANAGE_VOLUME_ACCESS );
|
|
}
|
|
|
|
//
|
|
// Free up the descriptor.
|
|
//
|
|
|
|
ObReleaseObjectSecurity( SecurityDescriptor,
|
|
MemoryAllocated );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DelayFlush) {
|
|
|
|
SetFlag( ThisCcb->Flags, CCB_FLAG_FLUSH_VOLUME_ON_IO );
|
|
}
|
|
|
|
//
|
|
// If we are locking the volume, do so now.
|
|
//
|
|
|
|
if (LockVolume) {
|
|
|
|
SetFlag( Vcb->VcbState, VCB_STATE_LOCKED );
|
|
Vcb->FileObjectWithVcbLocked = FileObject;
|
|
|
|
//
|
|
// Looks like the lock succeeded, so we don't have to do the
|
|
// lock failed notification now.
|
|
//
|
|
|
|
NotifyLockFailed = FALSE;
|
|
}
|
|
|
|
//
|
|
// Report that we opened the volume.
|
|
//
|
|
|
|
Irp->IoStatus.Information = FILE_OPENED;
|
|
}
|
|
|
|
try_exit: NOTHING;
|
|
|
|
NtfsCleanupTransaction( IrpContext, Status, FALSE );
|
|
|
|
//
|
|
// If we have a successful open then remove the name out of
|
|
// the file object. The IO system gets confused when it
|
|
// is there. We will deallocate the buffer with the Ccb
|
|
// when the handle is closed.
|
|
//
|
|
|
|
if (Status == STATUS_SUCCESS) {
|
|
|
|
FileObject->FileName.Buffer = NULL;
|
|
FileObject->FileName.MaximumLength =
|
|
FileObject->FileName.Length = 0;
|
|
|
|
SetFlag( ThisCcb->Flags, CCB_FLAG_ALLOCATED_FILE_NAME );
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCommonVolumeOpen );
|
|
|
|
if (VcbAcquired) {
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
}
|
|
|
|
//
|
|
// Now that we aren't holding any resources, notify everyone
|
|
// who might want to reopen their handles. We want to do this
|
|
// before we complete the request because the FileObject might
|
|
// not exist beyond the life of the Irp.
|
|
//
|
|
|
|
if (NotifyLockFailed) {
|
|
|
|
DebugTrace( 0, Dbg, ("Sending lock_failed notification\n") );
|
|
FsRtlNotifyVolumeEvent( FileObject, FSRTL_VOLUME_LOCK_FAILED );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCommonVolumeOpen: Exit -> %08lx\n", Status) );
|
|
}
|
|
|
|
//
|
|
// If we have already done a PreCreate for this IRP (in FsdCreate),
|
|
// we should do the corresponding PostCreate before we complete the IRP. So,
|
|
// in that case, don't complete the IRP here -- just free the IrpContext.
|
|
// The IRP will be completed by the caller.
|
|
//
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_EFS_CREATE )) {
|
|
NtfsCompleteRequest( IrpContext, NULL, Status );
|
|
} else {
|
|
NtfsCompleteRequest( IrpContext, Irp, Status );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsUpdateAllInformation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFCB Fcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN PSCB ParentScb OPTIONAL,
|
|
IN PLCB Lcb OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reads on disk data for the fcb/scb as well as anything accumulated in the fileobject
|
|
and updates the stdinfo / filesizes and duplicate info for all links. If the dup
|
|
info is updated all prev. work will be commited first.
|
|
|
|
Arguments:
|
|
|
|
IrpContext -
|
|
|
|
FileObject - the fileobject associated with the fcb and scb to update from
|
|
|
|
Fcb - the fcb to be updated
|
|
|
|
Scb - the scb to be updated
|
|
|
|
Ccb - The ccb for the file (used in updatedupinfo)
|
|
|
|
ParentScb - optional parent to use on update
|
|
|
|
Lcb - optional lcb to use on update
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
|
|
|
|
//
|
|
// Do the standard information, file sizes and then duplicate information
|
|
// if needed.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
|
|
|
|
NtfsUpdateStandardInformation( IrpContext, Fcb );
|
|
}
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE )) {
|
|
|
|
NtfsWriteFileSizes( IrpContext,
|
|
Scb,
|
|
&Scb->Header.ValidDataLength.QuadPart,
|
|
FALSE,
|
|
TRUE,
|
|
FALSE );
|
|
}
|
|
|
|
if (FlagOn( Fcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS ) &&
|
|
!FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE)) {
|
|
|
|
NtfsUpdateDuplicateInfo( IrpContext, Fcb, Lcb, ParentScb );
|
|
|
|
//
|
|
// Do dir notification if update came along a pathname
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( Lcb ) && (Fcb->Vcb->NotifyCount != 0)) {
|
|
|
|
ULONG FilterMatch;
|
|
|
|
ASSERT( ARGUMENT_PRESENT( ParentScb ) );
|
|
|
|
//
|
|
// We map the Fcb info flags into the dir notify flags.
|
|
//
|
|
|
|
FilterMatch = NtfsBuildDirNotifyFilter( IrpContext,
|
|
Fcb->InfoFlags | Lcb->InfoFlags );
|
|
|
|
//
|
|
// If the filter match is non-zero, that means we also need to
|
|
// dir notify call.
|
|
//
|
|
|
|
if ((FilterMatch != 0) && (Ccb != NULL)) {
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
Fcb->Vcb,
|
|
&(Ccb)->FullFileName,
|
|
(Ccb)->LastFileNameOffset,
|
|
NULL,
|
|
((FlagOn( Ccb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
|
|
Ccb->Lcb != NULL &&
|
|
Ccb->Lcb->Scb->ScbType.Index.NormalizedName.Length != 0) ?
|
|
&Ccb->Lcb->Scb->ScbType.Index.NormalizedName :
|
|
NULL),
|
|
FilterMatch,
|
|
FILE_ACTION_MODIFIED,
|
|
ParentScb->Fcb )
|
|
}
|
|
}
|
|
|
|
NtfsUpdateLcbDuplicateInfo( Fcb, Lcb );
|
|
Fcb->InfoFlags = 0;
|
|
|
|
}
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsOpenFcbById (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PVCB Vcb,
|
|
IN PLCB ParentLcb OPTIONAL,
|
|
IN FILE_REFERENCE FileReference,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN PCREATE_CONTEXT CreateContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to open a file by its file Id. We need to
|
|
verify that this file Id exists and then compare the type of the
|
|
file with the requested type of open.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
IrpSp - This is the Irp stack pointer for the filesystem.
|
|
|
|
Vcb - Vcb for this volume.
|
|
|
|
ParentLcb - Lcb used to reach this Fcb. Only specified when opening
|
|
a file by name relative to a directory opened by file Id.
|
|
|
|
FileReference - This is the file Id for the file to open.
|
|
|
|
AttrName - This is the name of the attribute to open.
|
|
|
|
AttrTypeCode - This is the attribute code to open.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Indicates the result of this create file operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
LONGLONG MftOffset;
|
|
PFILE_RECORD_SEGMENT_HEADER FileRecord;
|
|
PBCB Bcb = NULL;
|
|
|
|
BOOLEAN IndexedAttribute;
|
|
|
|
PFCB ThisFcb = NULL;
|
|
BOOLEAN ExistingFcb = FALSE;
|
|
|
|
ULONG CcbFlags = 0;
|
|
ULONG Flags = 0;
|
|
OLD_SCB_SNAPSHOT ScbSizes;
|
|
BOOLEAN HaveScbSizes = FALSE;
|
|
BOOLEAN DecrementCloseCount = FALSE;
|
|
|
|
PSCB ParentScb = NULL;
|
|
PLCB Lcb = ParentLcb;
|
|
BOOLEAN AcquiredParentScb = FALSE;
|
|
BOOLEAN AcquiredMft = FALSE;
|
|
|
|
BOOLEAN AcquiredFcbTable = FALSE;
|
|
UCHAR CreateDisposition = (UCHAR) ((IrpSp->Parameters.Create.Options >> 24) & 0x000000ff);
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOpenFcbById: Entered\n") );
|
|
|
|
//
|
|
// The next thing to do is to figure out what type
|
|
// of attribute the caller is trying to open. This involves the
|
|
// directory/non-directory bits, the attribute name and code strings,
|
|
// the type of file, whether he passed in an ea buffer and whether
|
|
// there was a trailing backslash.
|
|
//
|
|
|
|
if (NtfsEqualMftRef( &FileReference,
|
|
&VolumeFileReference )) {
|
|
|
|
if ((AttrName.Length != 0) ||
|
|
FlagOn( CreateContext->CreateFlags, CREATE_FLAG_EXPLICIT_ATTRIBUTE_CODE)) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
DebugTrace( -1, Dbg, ("NtfsOpenFcbById: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
SetFlag( IrpContext->State,
|
|
IRP_CONTEXT_STATE_ACQUIRE_EX | IRP_CONTEXT_STATE_DASD_OPEN );
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// If we don't already have the Fcb then look up the file record
|
|
// from the disk.
|
|
//
|
|
|
|
if (CreateContext->CurrentFcb == NULL) {
|
|
|
|
//
|
|
// We start by reading the disk and checking that the file record
|
|
// sequence number matches and that the file record is in use.
|
|
// We remember whether this is a directory. We will only go to
|
|
// the file if the file Id will lie within the Mft File.
|
|
//
|
|
|
|
MftOffset = NtfsFullSegmentNumber( &FileReference );
|
|
|
|
MftOffset = Int64ShllMod32(MftOffset, Vcb->MftShift);
|
|
|
|
//
|
|
// Make sure we are serialized with access to the Mft. Otherwise
|
|
// someone else could be deleting the file as we speak.
|
|
//
|
|
|
|
NtfsAcquireSharedFcb( IrpContext, Vcb->MftScb->Fcb, NULL, 0 );
|
|
AcquiredMft = TRUE;
|
|
|
|
if (MftOffset >= Vcb->MftScb->Header.FileSize.QuadPart) {
|
|
|
|
DebugTrace( 0, Dbg, ("File Id doesn't lie within Mft\n") );
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
|
|
NtfsReadMftRecord( IrpContext,
|
|
Vcb,
|
|
&FileReference,
|
|
FALSE,
|
|
&Bcb,
|
|
&FileRecord,
|
|
NULL );
|
|
|
|
//
|
|
// This file record better be in use, have a matching sequence number and
|
|
// be the primary file record for this file.
|
|
//
|
|
|
|
if ((FileRecord->SequenceNumber != FileReference.SequenceNumber) ||
|
|
!FlagOn( FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE ) ||
|
|
(*((PLONGLONG) &FileRecord->BaseFileRecordSegment) != 0) ||
|
|
(*((PULONG) FileRecord->MultiSectorHeader.Signature) != *((PULONG) FileSignature))) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// If indexed then use the name for the file name index.
|
|
//
|
|
|
|
if (FlagOn( FileRecord->Flags, FILE_FILE_NAME_INDEX_PRESENT )) {
|
|
|
|
AttrName = NtfsFileNameIndex;
|
|
AttrTypeCode = $INDEX_ALLOCATION;
|
|
SetFlag( Flags, CREATE_FLAG_EXPLICIT_ATTRIBUTE_CODE );
|
|
}
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
|
|
} else {
|
|
|
|
ThisFcb = CreateContext->CurrentFcb;
|
|
ExistingFcb = TRUE;
|
|
}
|
|
|
|
Status = NtfsCheckValidAttributeAccess( IrpContext,
|
|
IrpSp,
|
|
Vcb,
|
|
ExistingFcb ? &ThisFcb->Info : NULL,
|
|
&AttrName,
|
|
&AttrTypeCode,
|
|
Flags,
|
|
&CcbFlags,
|
|
&IndexedAttribute );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// If we don't have an Fcb then create one now.
|
|
//
|
|
|
|
if (CreateContext->CurrentFcb == NULL) {
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = TRUE;
|
|
|
|
//
|
|
// We know that it is safe to continue the open. We start by creating
|
|
// an Fcb for this file. It is possible that the Fcb exists.
|
|
// We create the Fcb first, if we need to update the Fcb info structure
|
|
// we copy the one from the index entry. We look at the Fcb to discover
|
|
// if it has any links, if it does then we make this the last Fcb we
|
|
// reached. If it doesn't then we have to clean it up from here.
|
|
//
|
|
|
|
ThisFcb = NtfsCreateFcb( IrpContext,
|
|
Vcb,
|
|
FileReference,
|
|
BooleanFlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ),
|
|
TRUE,
|
|
&ExistingFcb );
|
|
|
|
ThisFcb->ReferenceCount += 1;
|
|
|
|
//
|
|
// Try to do a fast acquire, otherwise we need to release
|
|
// the Fcb table, acquire the Fcb, acquire the Fcb table to
|
|
// dereference Fcb. This should only be the case if the Fcb already
|
|
// existed. In that case all of the flags indicating whether it
|
|
// has been deleted will be valid when we reacquire it. We don't
|
|
// have to worry about Mft synchronization.
|
|
//
|
|
|
|
if (!NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, ACQUIRE_DONT_WAIT )) {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
NtfsReleaseFcb( IrpContext, Vcb->MftScb->Fcb );
|
|
AcquiredMft = FALSE;
|
|
NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, 0 );
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
|
|
} else {
|
|
|
|
NtfsReleaseFcb( IrpContext, Vcb->MftScb->Fcb );
|
|
AcquiredMft = FALSE;
|
|
}
|
|
|
|
ThisFcb->ReferenceCount -= 1;
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
|
|
//
|
|
// Store this Fcb into our caller's parameter and remember to
|
|
// to show we acquired it.
|
|
//
|
|
|
|
CreateContext->CurrentFcb = ThisFcb;
|
|
}
|
|
|
|
//
|
|
// We perform a check to see whether we will allow the system
|
|
// files to be opened.
|
|
//
|
|
// No test to make if this is not a system file or it is the VolumeDasd file.
|
|
// The ACL will protect the volume file.
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_SYSTEM_FILE ) &&
|
|
(NtfsSegmentNumber( &ThisFcb->FileReference ) != VOLUME_DASD_NUMBER) &&
|
|
NtfsProtectSystemFiles) {
|
|
|
|
if (!NtfsCheckValidFileAccess( ThisFcb, IrpSp )) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
DebugTrace( 0, Dbg, ("Invalid access to system files\n") );
|
|
leave;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the Fcb existed and this is a paging file then either return
|
|
// sharing violation or force the Fcb and Scb's to go away.
|
|
// Do this for the case where the user is opening a paging file
|
|
// but the Fcb is non-paged or the user is opening a non-paging
|
|
// file and the Fcb is for a paging file.
|
|
//
|
|
|
|
if (ExistingFcb &&
|
|
|
|
((FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
|
|
!FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )) ||
|
|
|
|
(FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
|
|
!FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )))) {
|
|
|
|
if (ThisFcb->CleanupCount != 0) {
|
|
|
|
Status = STATUS_SHARING_VIOLATION;
|
|
leave;
|
|
|
|
//
|
|
// If we have a persistent paging file then give up and
|
|
// return SHARING_VIOLATION.
|
|
//
|
|
|
|
} else if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_IN_FSP )) {
|
|
|
|
Status = STATUS_SHARING_VIOLATION;
|
|
leave;
|
|
|
|
//
|
|
// If there was an existing Fcb for a paging file we need to force
|
|
// all of the Scb's to be torn down. The easiest way to do this
|
|
// is to flush and purge all of the Scb's (saving any attribute list
|
|
// for last) and then raise LOG_FILE_FULL to allow this request to
|
|
// be posted.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Reference the Fcb so it doesn't go away.
|
|
//
|
|
|
|
InterlockedIncrement( &ThisFcb->CloseCount );
|
|
DecrementCloseCount = TRUE;
|
|
|
|
//
|
|
// Flush and purge this Fcb.
|
|
//
|
|
|
|
NtfsFlushAndPurgeFcb( IrpContext, ThisFcb );
|
|
|
|
InterlockedDecrement( &ThisFcb->CloseCount );
|
|
DecrementCloseCount = FALSE;
|
|
|
|
//
|
|
// Force this request to be posted and then raise
|
|
// CANT_WAIT.
|
|
//
|
|
|
|
NtfsRaiseToPost( IrpContext );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the Fcb Info field needs to be initialized, we do so now.
|
|
// We read this information from the disk.
|
|
//
|
|
|
|
if (!FlagOn( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
|
|
|
|
HaveScbSizes = NtfsUpdateFcbInfoFromDisk( IrpContext,
|
|
TRUE,
|
|
ThisFcb,
|
|
&ScbSizes );
|
|
|
|
//
|
|
// Fix the quota for this file if necessary.
|
|
//
|
|
|
|
NtfsConditionallyFixupQuota( IrpContext, ThisFcb );
|
|
|
|
}
|
|
|
|
//
|
|
// Now that we have the dup info off disk recheck the create options
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_DIRECTORY_FILE ) &&
|
|
!IsViewIndex( &ThisFcb->Info ) &&
|
|
!IsDirectory( &ThisFcb->Info )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_NOT_A_DIRECTORY, NULL, NULL );
|
|
}
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_NON_DIRECTORY_FILE ) &&
|
|
(IsViewIndex( &ThisFcb->Info ) || IsDirectory( &ThisFcb->Info ))) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_IS_A_DIRECTORY, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// If the link count is zero on this Fcb, then delete is pending. Otherwise
|
|
// this might be an unused system file.
|
|
//
|
|
|
|
if (ThisFcb->LinkCount == 0) {
|
|
|
|
if (NtfsSegmentNumber( &ThisFcb->FileReference ) >= FIRST_USER_FILE_NUMBER) {
|
|
|
|
Status = STATUS_DELETE_PENDING;
|
|
leave;
|
|
|
|
} else {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We now call the worker routine to open an attribute on an existing file.
|
|
//
|
|
|
|
Status = NtfsOpenAttributeInExistingFile( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ParentLcb,
|
|
ThisFcb,
|
|
0,
|
|
AttrName,
|
|
AttrTypeCode,
|
|
CcbFlags,
|
|
CREATE_FLAG_OPEN_BY_ID,
|
|
NULL,
|
|
&CreateContext->ThisScb,
|
|
&CreateContext->ThisCcb );
|
|
|
|
//
|
|
// Check to see if we should update the last access time.
|
|
// We skip this for reparse points as *ThisScb and *ThisCcb may be NULL.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status ) &&
|
|
(Status != STATUS_PENDING) &&
|
|
(Status != STATUS_WAIT_FOR_OPLOCK) &&
|
|
(Status != STATUS_REPARSE)) {
|
|
|
|
PSCB Scb = CreateContext->ThisScb;
|
|
|
|
//
|
|
// Now look at whether we need to update the Fcb and on disk
|
|
// structures.
|
|
//
|
|
|
|
if (NtfsCheckLastAccess( IrpContext, ThisFcb )) {
|
|
|
|
SetFlag( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
|
|
}
|
|
|
|
//
|
|
// Perform the last bit of work. If this a user file open, we need
|
|
// to check if we initialize the Scb.
|
|
//
|
|
|
|
if (!IndexedAttribute) {
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
|
|
|
|
//
|
|
// We may have the sizes from our Fcb update call.
|
|
//
|
|
|
|
if (HaveScbSizes &&
|
|
(AttrTypeCode == $DATA) &&
|
|
(AttrName.Length == 0) &&
|
|
!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB )) {
|
|
|
|
NtfsUpdateScbFromMemory( Scb, &ScbSizes );
|
|
|
|
} else {
|
|
|
|
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Let's check if we need to set the cache bit.
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_INTERMEDIATE_BUFFERING )) {
|
|
|
|
SetFlag( IrpSp->FileObject->Flags, FO_CACHE_SUPPORTED );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If everything has gone well so far, we may want to call the
|
|
// encryption callback if one is registered. We do not do
|
|
// this for network opens or reparse points. We have to pass
|
|
// FILE_EXISTING since we have no parent directory and the
|
|
// encryption callback needs a parent directory to handle a
|
|
// new file create.
|
|
//
|
|
|
|
if (CreateContext->NetworkInfo == NULL) {
|
|
|
|
NtfsEncryptionCreateCallback( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
CreateContext->ThisScb,
|
|
CreateContext->ThisCcb,
|
|
NULL,
|
|
CreateContext,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// If this operation was a supersede/overwrite or we created a new
|
|
// attribute stream then we want to perform the file record and
|
|
// directory update now. Otherwise we will defer the updates until
|
|
// the user closes his handle.
|
|
//
|
|
|
|
if (NtfsIsStreamNew(Irp->IoStatus.Information)) {
|
|
NtfsUpdateAllInformation( IrpContext, IrpSp->FileObject, ThisFcb, CreateContext->ThisScb, NULL, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsOpenFcbById );
|
|
|
|
if (AcquiredFcbTable) {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
}
|
|
|
|
if (AcquiredMft) {
|
|
|
|
NtfsReleaseFcb( IrpContext, Vcb->MftScb->Fcb );
|
|
}
|
|
|
|
//
|
|
// If this operation was not totally successful we need to
|
|
// back out the following changes.
|
|
//
|
|
// Modifications to the Info fields in the Fcb.
|
|
// Any changes to the allocation of the Scb.
|
|
// Any changes in the open counts in the various structures.
|
|
// Changes to the share access values in the Fcb.
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status ) || AbnormalTermination()) {
|
|
|
|
NtfsBackoutFailedOpens( IrpContext,
|
|
IrpSp->FileObject,
|
|
ThisFcb,
|
|
CreateContext->ThisScb,
|
|
CreateContext->ThisCcb );
|
|
}
|
|
|
|
if (DecrementCloseCount) {
|
|
|
|
InterlockedDecrement( &ThisFcb->CloseCount );
|
|
}
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenFcbById: Exit -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsOpenExistingPrefixFcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB Lcb OPTIONAL,
|
|
IN ULONG FullPathNameLength,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN PCREATE_CONTEXT CreateContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will open an attribute in a file whose Fcb was found
|
|
with a prefix search.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
IrpSp - This is the Irp stack pointer for the filesystem.
|
|
|
|
Lcb - This is the Lcb used to reach this Fcb. Not specified if this is a volume open.
|
|
|
|
FullPathNameLength - This is the length of the full path name.
|
|
|
|
AttrName - This is the name of the attribute to open.
|
|
|
|
AttrCode - This is the attribute type to open.
|
|
|
|
CreateFlags - Flags for create operation - we care about the dos only component and trailing back slash
|
|
flag
|
|
|
|
CreateContext - Context with create variables.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Indicates the result of this attribute based operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG CcbFlags;
|
|
BOOLEAN IndexedAttribute;
|
|
BOOLEAN DecrementCloseCount = FALSE;
|
|
|
|
ULONG LastFileNameOffset;
|
|
|
|
OLD_SCB_SNAPSHOT ScbSizes;
|
|
BOOLEAN HaveScbSizes = FALSE;
|
|
|
|
ULONG CreateDisposition;
|
|
|
|
PSCB ParentScb = NULL;
|
|
PFCB ParentFcb = NULL;
|
|
BOOLEAN AcquiredParentScb = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOpenExistingPrefixFcb: Entered\n") );
|
|
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_DOS_ONLY_COMPONENT )) {
|
|
|
|
CcbFlags = CCB_FLAG_PARENT_HAS_DOS_COMPONENT;
|
|
|
|
} else {
|
|
|
|
CcbFlags = 0;
|
|
}
|
|
|
|
//
|
|
// The first thing to do is to figure out what type
|
|
// of attribute the caller is trying to open. This involves the
|
|
// directory/non-directory bits, the attribute name and code strings,
|
|
// the type of file, whether he passed in an ea buffer and whether
|
|
// there was a trailing backslash.
|
|
//
|
|
|
|
if (NtfsEqualMftRef( &CreateContext->CurrentFcb->FileReference, &VolumeFileReference )) {
|
|
|
|
if ((AttrName.Length != 0) ||
|
|
(FlagOn( CreateContext->CreateFlags, CREATE_FLAG_EXPLICIT_ATTRIBUTE_CODE ))) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
DebugTrace( -1, Dbg, ("NtfsOpenExistingPrefixFcb: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX | IRP_CONTEXT_STATE_DASD_OPEN );
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
ParentScb = Lcb->Scb;
|
|
|
|
LastFileNameOffset = FullPathNameLength - Lcb->ExactCaseLink.LinkName.Length;
|
|
|
|
if (ParentScb != NULL) {
|
|
|
|
ParentFcb = ParentScb->Fcb;
|
|
}
|
|
|
|
Status = NtfsCheckValidAttributeAccess( IrpContext,
|
|
IrpSp,
|
|
CreateContext->CurrentFcb->Vcb,
|
|
&CreateContext->CurrentFcb->Info,
|
|
&AttrName,
|
|
&AttrTypeCode,
|
|
CreateContext->CreateFlags,
|
|
&CcbFlags,
|
|
&IndexedAttribute );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenExistingPrefixFcb: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
CreateDisposition = (IrpSp->Parameters.Create.Options >> 24) & 0x000000ff;
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// If the Fcb existed and this is a paging file then either return
|
|
// sharing violation or force the Fcb and Scb's to go away.
|
|
// Do this for the case where the user is opening a paging file
|
|
// but the Fcb is non-paged or the user is opening a non-paging
|
|
// file and the Fcb is for a paging file.
|
|
//
|
|
|
|
if ((FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
|
|
!FlagOn( CreateContext->CurrentFcb->FcbState, FCB_STATE_PAGING_FILE )) ||
|
|
|
|
(FlagOn( CreateContext->CurrentFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
|
|
!FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ))) {
|
|
|
|
if (CreateContext->CurrentFcb->CleanupCount != 0) {
|
|
|
|
Status = STATUS_SHARING_VIOLATION;
|
|
leave;
|
|
|
|
//
|
|
// If we have a persistent paging file then give up and
|
|
// return SHARING_VIOLATION.
|
|
//
|
|
|
|
} else if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_IN_FSP )) {
|
|
|
|
Status = STATUS_SHARING_VIOLATION;
|
|
leave;
|
|
|
|
//
|
|
// If there was an existing Fcb for a paging file we need to force
|
|
// all of the Scb's to be torn down. The easiest way to do this
|
|
// is to flush and purge all of the Scb's (saving any attribute list
|
|
// for last) and then raise LOG_FILE_FULL to allow this request to
|
|
// be posted.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Make sure this Fcb won't go away as a result of purging
|
|
// the Fcb.
|
|
//
|
|
|
|
InterlockedIncrement( &CreateContext->CurrentFcb->CloseCount );
|
|
DecrementCloseCount = TRUE;
|
|
|
|
//
|
|
// Flush and purge this Fcb.
|
|
//
|
|
|
|
NtfsFlushAndPurgeFcb( IrpContext, CreateContext->CurrentFcb );
|
|
|
|
//
|
|
// Now decrement the close count we have already biased.
|
|
//
|
|
|
|
InterlockedDecrement( &CreateContext->CurrentFcb->CloseCount );
|
|
DecrementCloseCount = FALSE;
|
|
|
|
NtfsRaiseToPost( IrpContext );
|
|
}
|
|
}
|
|
|
|
//
|
|
// This file might have been recently created, and we might have dropped the
|
|
// Fcb to call the PostCreate encryption callout, so the encryption driver
|
|
// hasn't yet called us back to set the encryption bit on the file. If we're
|
|
// asked to open the file in this window, we would introduce corruption by
|
|
// writing plaintext now. Let's just raise cant_wait and try again later.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CurrentFcb->FcbState, FCB_STATE_ENCRYPTION_PENDING )) {
|
|
|
|
#ifdef KEITHKA
|
|
EncryptionPendingCount += 1;
|
|
#endif
|
|
|
|
//
|
|
// Raise CANT_WAIT so we can wait on the encryption event at the top.
|
|
//
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ENCRYPTION_RETRY );
|
|
|
|
//
|
|
// Clear the pending event so we can wait for it when we retry.
|
|
//
|
|
|
|
KeClearEvent( &NtfsEncryptionPendingEvent );
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// If this is a directory, it's possible that we hav an existing Fcb
|
|
// in the prefix table which needs to be initialized from the disk.
|
|
// We look in the InfoInitialized flag to know whether to go to
|
|
// disk.
|
|
//
|
|
|
|
if (!FlagOn( CreateContext->CurrentFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
|
|
|
|
//
|
|
// If we have a parent Fcb then make sure to acquire it.
|
|
//
|
|
|
|
if (ParentScb != NULL) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
|
|
AcquiredParentScb = TRUE;
|
|
}
|
|
|
|
HaveScbSizes = NtfsUpdateFcbInfoFromDisk( IrpContext,
|
|
TRUE,
|
|
CreateContext->CurrentFcb,
|
|
&ScbSizes );
|
|
|
|
NtfsConditionallyFixupQuota( IrpContext, CreateContext->CurrentFcb );
|
|
}
|
|
|
|
//
|
|
// Check now whether we will need to acquire the parent to
|
|
// perform a update duplicate info. We need to acquire it
|
|
// now to enforce our locking order in case any of the
|
|
// routines below acquire the Mft Scb. Acquire it if we
|
|
// are doing a supersede/overwrite or possibly creating
|
|
// a named data stream.
|
|
//
|
|
|
|
if ((CreateDisposition == FILE_SUPERSEDE) ||
|
|
(CreateDisposition == FILE_OVERWRITE) ||
|
|
(CreateDisposition == FILE_OVERWRITE_IF) ||
|
|
((AttrName.Length != 0) &&
|
|
((CreateDisposition == FILE_OPEN_IF) ||
|
|
(CreateDisposition == FILE_CREATE)))) {
|
|
|
|
NtfsPrepareForUpdateDuplicate( IrpContext,
|
|
CreateContext->CurrentFcb,
|
|
&Lcb,
|
|
&ParentScb,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Call to open an attribute on an existing file.
|
|
// Remember we need to restore the Fcb info structure
|
|
// on errors.
|
|
//
|
|
|
|
Status = NtfsOpenAttributeInExistingFile( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
Lcb,
|
|
CreateContext->CurrentFcb,
|
|
LastFileNameOffset,
|
|
AttrName,
|
|
AttrTypeCode,
|
|
CcbFlags,
|
|
CreateContext->CreateFlags,
|
|
CreateContext->NetworkInfo,
|
|
&CreateContext->ThisScb,
|
|
&CreateContext->ThisCcb );
|
|
|
|
//
|
|
// Check to see if we should update the last access time.
|
|
// We skip this for reparse points as *ThisScb and *ThisCcb may be NULL.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status ) &&
|
|
(Status != STATUS_PENDING) &&
|
|
(Status != STATUS_WAIT_FOR_OPLOCK) &&
|
|
(Status != STATUS_REPARSE)) {
|
|
|
|
PSCB Scb = CreateContext->ThisScb;
|
|
|
|
//
|
|
// This is a rare case. There must have been an allocation failure
|
|
// to cause this but make sure the normalized name is stored.
|
|
//
|
|
|
|
if ((SafeNodeType( Scb ) == NTFS_NTC_SCB_INDEX) &&
|
|
(Scb->ScbType.Index.NormalizedName.Length == 0)) {
|
|
|
|
//
|
|
// We may be able to use the parent.
|
|
//
|
|
|
|
if ((ParentScb != NULL) &&
|
|
(ParentScb->ScbType.Index.NormalizedName.Length != 0)) {
|
|
|
|
NtfsUpdateNormalizedName( IrpContext,
|
|
ParentScb,
|
|
Scb,
|
|
NULL,
|
|
FALSE,
|
|
FALSE );
|
|
|
|
} else {
|
|
|
|
NtfsBuildNormalizedName( IrpContext,
|
|
Scb->Fcb,
|
|
Scb,
|
|
&Scb->ScbType.Index.NormalizedName );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Perform the last bit of work. If this a user file open, we need
|
|
// to check if we initialize the Scb.
|
|
//
|
|
|
|
if (!IndexedAttribute) {
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
|
|
|
|
//
|
|
// We may have the sizes from our Fcb update call.
|
|
//
|
|
|
|
if (HaveScbSizes &&
|
|
(AttrTypeCode == $DATA) &&
|
|
(AttrName.Length == 0) &&
|
|
!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB )) {
|
|
|
|
NtfsUpdateScbFromMemory( Scb, &ScbSizes );
|
|
|
|
} else {
|
|
|
|
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Let's check if we need to set the cache bit.
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.Options,
|
|
FILE_NO_INTERMEDIATE_BUFFERING )) {
|
|
|
|
SetFlag( IrpSp->FileObject->Flags, FO_CACHE_SUPPORTED );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is the paging file, we want to be sure the allocation
|
|
// is loaded.
|
|
//
|
|
|
|
if (FlagOn( CreateContext->CurrentFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
|
|
(Scb->Header.AllocationSize.QuadPart != 0) &&
|
|
!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
LCN Lcn;
|
|
VCN Vcn;
|
|
VCN AllocatedVcns;
|
|
|
|
AllocatedVcns = Int64ShraMod32(Scb->Header.AllocationSize.QuadPart, Scb->Vcb->ClusterShift);
|
|
|
|
//
|
|
// First make sure the Mcb is loaded.
|
|
//
|
|
|
|
NtfsPreloadAllocation( IrpContext, Scb, 0, AllocatedVcns );
|
|
|
|
//
|
|
// Now make sure the allocation is correctly loaded. The last
|
|
// Vcn should correspond to the allocation size for the file.
|
|
//
|
|
|
|
if (!NtfsLookupLastNtfsMcbEntry( &Scb->Mcb,
|
|
&Vcn,
|
|
&Lcn ) ||
|
|
(Vcn + 1) != AllocatedVcns) {
|
|
|
|
NtfsRaiseStatus( IrpContext,
|
|
STATUS_FILE_CORRUPT_ERROR,
|
|
NULL,
|
|
CreateContext->CurrentFcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this open is for an executable image we will want to update the
|
|
// last access time.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess, FILE_EXECUTE ) &&
|
|
(Scb->AttributeTypeCode == $DATA)) {
|
|
|
|
SetFlag( IrpSp->FileObject->Flags, FO_FILE_FAST_IO_READ );
|
|
}
|
|
|
|
//
|
|
// If everything has gone well so far, we may want to call the
|
|
// encryption callback if one is registered. We do not do
|
|
// this for network opens or reparse points.
|
|
//
|
|
|
|
if (CreateContext->NetworkInfo == NULL) {
|
|
|
|
NtfsEncryptionCreateCallback( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
CreateContext->ThisScb,
|
|
CreateContext->ThisCcb,
|
|
ParentFcb,
|
|
CreateContext,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Check if should insert the hash entry.
|
|
//
|
|
|
|
if ((CreateContext->FileHashLength != 0) &&
|
|
!FlagOn( CcbFlags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
|
|
(Lcb->FileNameAttr->Flags != FILE_NAME_DOS) ) {
|
|
|
|
//
|
|
// Remove any exising hash value.
|
|
//
|
|
|
|
if (FlagOn( Lcb->LcbState, LCB_STATE_VALID_HASH_VALUE )) {
|
|
|
|
NtfsRemoveHashEntriesForLcb( Lcb );
|
|
#ifdef NTFS_HASH_DATA
|
|
CreateContext->CurrentFcb->Vcb->HashTable.OpenExistingConflict += 1;
|
|
#endif
|
|
}
|
|
|
|
NtfsInsertHashEntry( &CreateContext->CurrentFcb->Vcb->HashTable,
|
|
Lcb,
|
|
CreateContext->FileHashLength,
|
|
CreateContext->FileHashValue );
|
|
#ifdef NTFS_HASH_DATA
|
|
CreateContext->CurrentFcb->Vcb->HashTable.OpenExistingInsert += 1;
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// If this operation was a supersede/overwrite or we created a new
|
|
// attribute stream then we want to perform the file record and
|
|
// directory update now. Otherwise we will defer the updates until
|
|
// the user closes his handle.
|
|
//
|
|
|
|
if (NtfsIsStreamNew( Irp->IoStatus.Information )) {
|
|
NtfsUpdateAllInformation( IrpContext, IrpSp->FileObject, CreateContext->CurrentFcb, CreateContext->ThisScb, CreateContext->ThisCcb, ParentScb, Lcb );
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsOpenExistingPrefixFcb );
|
|
|
|
if (DecrementCloseCount) {
|
|
|
|
InterlockedDecrement( &CreateContext->CurrentFcb->CloseCount );
|
|
}
|
|
|
|
//
|
|
// If this operation was not totally successful we need to
|
|
// back out the following changes.
|
|
//
|
|
// Modifications to the Info fields in the Fcb.
|
|
// Any changes to the allocation of the Scb.
|
|
// Any changes in the open counts in the various structures.
|
|
// Changes to the share access values in the Fcb.
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status ) || AbnormalTermination()) {
|
|
|
|
NtfsBackoutFailedOpens( IrpContext,
|
|
IrpSp->FileObject,
|
|
CreateContext->CurrentFcb,
|
|
CreateContext->ThisScb,
|
|
CreateContext->ThisCcb );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenExistingPrefixFcb: Exit -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsOpenTargetDirectory (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PFCB ThisFcb,
|
|
IN PLCB ParentLcb OPTIONAL,
|
|
IN OUT PUNICODE_STRING FullPathName,
|
|
IN ULONG FinalNameLength,
|
|
IN PCREATE_CONTEXT CreateContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will perform the work of opening a target directory. When the
|
|
open is complete the Ccb and Lcb for this file object will be identical
|
|
to any other open. We store the full name for the rename in the
|
|
file object but set the 'Length' field to include only the
|
|
name upto the parent directory. We use the 'MaximumLength' field to
|
|
indicate the full name.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this create operation.
|
|
|
|
IrpSp - This is the Irp stack pointer for the filesystem.
|
|
|
|
ThisFcb - This is the Fcb for the directory to open.
|
|
|
|
ParentLcb - This is the Lcb used to reach the parent directory. If not
|
|
specified, we will have to find it here. There will be no Lcb to
|
|
find if this Fcb was opened by Id.
|
|
|
|
FullPathName - This is the normalized string for open operation. It now
|
|
contains the full name as it appears on the disk for this open path.
|
|
It may not reach all the way to the root if the relative file object
|
|
was opened by Id.
|
|
|
|
FinalNameLength - This is the length of the final component in the
|
|
full path name.
|
|
|
|
CreateFlags - Flags for create operation - we care about the dos only component flag
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Indicating the outcome of opening this target directory.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG CcbFlags = CCB_FLAG_OPEN_AS_FILE;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOpenTargetDirectory: Entered\n") );
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_DOS_ONLY_COMPONENT )) {
|
|
|
|
SetFlag( CcbFlags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT );
|
|
}
|
|
|
|
//
|
|
// If the name doesn't begin with a backslash, remember this as
|
|
// an open by file ID.
|
|
//
|
|
|
|
if (FullPathName->Buffer[0] != L'\\') {
|
|
|
|
SetFlag( CcbFlags, CCB_FLAG_OPEN_BY_FILE_ID );
|
|
}
|
|
|
|
//
|
|
// Modify the full path name so that the Maximum length field describes
|
|
// the full name and the Length field describes the name for the
|
|
// parent.
|
|
//
|
|
|
|
FullPathName->MaximumLength = FullPathName->Length;
|
|
|
|
//
|
|
// If we don't have an Lcb, we will find it now. We look at each Lcb
|
|
// for the parent Fcb and find one which matches the component
|
|
// ahead of the last component of the full name.
|
|
//
|
|
|
|
FullPathName->Length -= (USHORT)FinalNameLength;
|
|
|
|
//
|
|
// If we are not at the root then subtract the bytes for the '\\'
|
|
// separator.
|
|
//
|
|
|
|
if (FullPathName->Length > sizeof( WCHAR )) {
|
|
|
|
FullPathName->Length -= sizeof( WCHAR );
|
|
}
|
|
|
|
if (!ARGUMENT_PRESENT( ParentLcb ) && (FullPathName->Length != 0)) {
|
|
|
|
PLIST_ENTRY Links;
|
|
PLCB NextLcb;
|
|
|
|
//
|
|
// If the length is two then the parent Lcb is the root Lcb.
|
|
//
|
|
|
|
if (FullPathName->Length == sizeof( WCHAR )
|
|
&& FullPathName->Buffer[0] == L'\\') {
|
|
|
|
ParentLcb = (PLCB) ThisFcb->Vcb->RootLcb;
|
|
|
|
} else {
|
|
|
|
for (Links = ThisFcb->LcbQueue.Flink;
|
|
Links != &ThisFcb->LcbQueue;
|
|
Links = Links->Flink) {
|
|
|
|
SHORT NameOffset;
|
|
|
|
NextLcb = CONTAINING_RECORD( Links,
|
|
LCB,
|
|
FcbLinks );
|
|
|
|
NameOffset = (SHORT) FullPathName->Length - (SHORT) NextLcb->ExactCaseLink.LinkName.Length;
|
|
|
|
if (NameOffset >= 0) {
|
|
|
|
if (RtlEqualMemory( Add2Ptr( FullPathName->Buffer,
|
|
NameOffset ),
|
|
NextLcb->ExactCaseLink.LinkName.Buffer,
|
|
NextLcb->ExactCaseLink.LinkName.Length )) {
|
|
|
|
//
|
|
// We found a matching Lcb. Remember this and exit
|
|
// the loop.
|
|
//
|
|
|
|
ParentLcb = NextLcb;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check this open for security access.
|
|
//
|
|
|
|
NtfsOpenCheck( IrpContext, ThisFcb, NULL, Irp );
|
|
|
|
//
|
|
// Now actually open the attribute.
|
|
//
|
|
|
|
Status = NtfsOpenAttribute( IrpContext,
|
|
IrpSp,
|
|
ThisFcb->Vcb,
|
|
ParentLcb,
|
|
ThisFcb,
|
|
(ARGUMENT_PRESENT( ParentLcb )
|
|
? FullPathName->Length - ParentLcb->ExactCaseLink.LinkName.Length
|
|
: 0),
|
|
NtfsFileNameIndex,
|
|
$INDEX_ALLOCATION,
|
|
(ThisFcb->CleanupCount == 0 ? SetShareAccess : CheckShareAccess),
|
|
UserDirectoryOpen,
|
|
FALSE,
|
|
CcbFlags,
|
|
NULL,
|
|
&CreateContext->ThisScb,
|
|
&CreateContext->ThisCcb );
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// If the Scb does not have a normalized name then update it now.
|
|
//
|
|
|
|
if (CreateContext->ThisScb->ScbType.Index.NormalizedName.Length == 0) {
|
|
|
|
NtfsBuildNormalizedName( IrpContext,
|
|
CreateContext->ThisScb->Fcb,
|
|
CreateContext->ThisScb,
|
|
&CreateContext->ThisScb->ScbType.Index.NormalizedName );
|
|
}
|
|
|
|
//
|
|
// If the file object name is not from the root then use the normalized name
|
|
// to obtain the full name.
|
|
//
|
|
|
|
if (FlagOn( CcbFlags, CCB_FLAG_OPEN_BY_FILE_ID )) {
|
|
|
|
USHORT BytesNeeded;
|
|
USHORT Index;
|
|
ULONG ComponentCount;
|
|
ULONG NormalizedComponentCount;
|
|
PWCHAR NewBuffer;
|
|
PWCHAR NextChar;
|
|
|
|
//
|
|
// Count the number of components in the directory portion of the
|
|
// name in the file object.
|
|
//
|
|
|
|
ComponentCount = 0;
|
|
|
|
if (FullPathName->Length != 0) {
|
|
|
|
ComponentCount = 1;
|
|
Index = (FullPathName->Length / sizeof( WCHAR )) - 1;
|
|
|
|
do {
|
|
|
|
if (FullPathName->Buffer[Index] == L'\\') {
|
|
|
|
ComponentCount += 1;
|
|
}
|
|
|
|
Index -= 1;
|
|
|
|
} while (Index != 0);
|
|
}
|
|
|
|
//
|
|
// Count back this number of components in the normalized name.
|
|
//
|
|
|
|
NormalizedComponentCount = 0;
|
|
Index = CreateContext->ThisScb->ScbType.Index.NormalizedName.Length / sizeof( WCHAR );
|
|
|
|
//
|
|
// Special case the root to point directory to the leading backslash.
|
|
//
|
|
|
|
if (Index == 1) {
|
|
|
|
Index = 0;
|
|
}
|
|
|
|
while (NormalizedComponentCount < ComponentCount) {
|
|
|
|
Index -= 1;
|
|
while (CreateContext->ThisScb->ScbType.Index.NormalizedName.Buffer[Index] != L'\\') {
|
|
|
|
Index -= 1;
|
|
}
|
|
|
|
NormalizedComponentCount += 1;
|
|
}
|
|
|
|
//
|
|
// Compute the size of the buffer needed for the full name. This
|
|
// will be:
|
|
//
|
|
// - Portion of normalized name used plus a separator
|
|
// - MaximumLength currently in FullPathName
|
|
//
|
|
|
|
BytesNeeded = (Index + 1) * sizeof( WCHAR );
|
|
|
|
if (MAXUSHORT - FullPathName->MaximumLength < BytesNeeded) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
|
|
}
|
|
|
|
BytesNeeded += FullPathName->MaximumLength;
|
|
|
|
NextChar =
|
|
NewBuffer = NtfsAllocatePool( PagedPool, BytesNeeded );
|
|
|
|
//
|
|
// Copy over the portion of the name from the normalized name.
|
|
//
|
|
|
|
if (Index != 0) {
|
|
|
|
RtlCopyMemory( NextChar,
|
|
CreateContext->ThisScb->ScbType.Index.NormalizedName.Buffer,
|
|
Index * sizeof( WCHAR ));
|
|
|
|
NextChar += Index;
|
|
}
|
|
|
|
*NextChar = L'\\';
|
|
NextChar += 1;
|
|
|
|
//
|
|
// Now copy over the remaining part of the name from the file object.
|
|
//
|
|
|
|
RtlCopyMemory( NextChar,
|
|
FullPathName->Buffer,
|
|
FullPathName->MaximumLength );
|
|
|
|
//
|
|
// Now free the pool from the file object and update with the newly
|
|
// allocated pool. Don't forget to update the Ccb to point to this new
|
|
// buffer.
|
|
//
|
|
|
|
NtfsFreePool( FullPathName->Buffer );
|
|
|
|
FullPathName->Buffer = NewBuffer;
|
|
FullPathName->MaximumLength =
|
|
FullPathName->Length = BytesNeeded;
|
|
FullPathName->Length -= (USHORT) FinalNameLength;
|
|
|
|
if (FullPathName->Length > sizeof( WCHAR )) {
|
|
|
|
FullPathName->Length -= sizeof( WCHAR );
|
|
}
|
|
|
|
CreateContext->ThisCcb->FullFileName = *FullPathName;
|
|
CreateContext->ThisCcb->LastFileNameOffset = FullPathName->MaximumLength - (USHORT) FinalNameLength;
|
|
}
|
|
|
|
Irp->IoStatus.Information = (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_FOUND_ENTRY ) ? FILE_EXISTS : FILE_DOES_NOT_EXIST);
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenTargetDirectory: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsOpenFile (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PSCB ParentScb,
|
|
IN PINDEX_ENTRY IndexEntry,
|
|
IN UNICODE_STRING FullPathName,
|
|
IN UNICODE_STRING FinalName,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN PQUICK_INDEX QuickIndex,
|
|
IN PCREATE_CONTEXT CreateContext,
|
|
OUT PLCB *LcbForTeardown
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when we need to open an attribute on a file
|
|
which currently exists. We have the ParentScb and the file reference
|
|
for the existing file. We will create the Fcb for this file and the
|
|
link between it and its parent directory. We will add this link to the
|
|
prefix table as well as the link for its parent Scb if specified.
|
|
|
|
On entry the caller owns the parent Scb.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
IrpSp - This is the Irp stack pointer for the filesystem.
|
|
|
|
ParentScb - This is the Scb for the parent directory.
|
|
|
|
IndexEntry - This is the index entry from the disk for this file.
|
|
|
|
FullPathName - This is the string containing the full path name of
|
|
this Fcb. Meaningless for an open by Id call.
|
|
|
|
FinalName - This is the string for the final component only. If the length
|
|
is zero then this is an open by Id call.
|
|
|
|
AttrName - This is the name of the attribute to open.
|
|
|
|
AttriCodeName - This is the name of the attribute code to open.
|
|
|
|
CreateFlags - Flags for create option - we use open by id / ignore case / trailing backslash and
|
|
dos only component
|
|
|
|
CreateContext - Context with create variables.
|
|
|
|
LcbForTeardown - This is the Lcb to use in teardown if we add an Lcb
|
|
into the tree.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Indicates the result of this create file operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG CcbFlags = 0;
|
|
PFILE_NAME IndexFileName;
|
|
|
|
OLD_SCB_SNAPSHOT ScbSizes;
|
|
|
|
PVCB Vcb = ParentScb->Vcb;
|
|
|
|
PFCB LocalFcbForTeardown = NULL;
|
|
PFCB ThisFcb;
|
|
PLCB ThisLcb;
|
|
|
|
FILE_REFERENCE PreviousFileReference;
|
|
|
|
BOOLEAN IndexedAttribute;
|
|
BOOLEAN DecrementCloseCount = FALSE;
|
|
BOOLEAN ExistingFcb;
|
|
BOOLEAN AcquiredFcbTable = FALSE;
|
|
BOOLEAN UpdateFcbInfo = FALSE;
|
|
BOOLEAN DroppedParent = FALSE;
|
|
BOOLEAN HaveScbSizes = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOpenFile: Entered\n") );
|
|
|
|
IndexFileName = (PFILE_NAME) NtfsFoundIndexEntry( IndexEntry );
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_DOS_ONLY_COMPONENT )) {
|
|
|
|
SetFlag( CcbFlags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT );
|
|
}
|
|
|
|
//
|
|
// The first thing to do is to figure out what type
|
|
// of attribute the caller is trying to open. This involves the
|
|
// directory/non-directory bits, the attribute name and code strings,
|
|
// the type of file, whether he passed in an ea buffer and whether
|
|
// there was a trailing backslash.
|
|
//
|
|
|
|
if (NtfsEqualMftRef( &IndexEntry->FileReference,
|
|
&VolumeFileReference )) {
|
|
|
|
if ((AttrName.Length != 0) ||
|
|
FlagOn( CreateContext->CreateFlags, CREATE_FLAG_EXPLICIT_ATTRIBUTE_CODE )) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
DebugTrace( -1, Dbg, ("NtfsOpenFile: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
SetFlag( IrpContext->State,
|
|
IRP_CONTEXT_STATE_ACQUIRE_EX | IRP_CONTEXT_STATE_DASD_OPEN );
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
Status = NtfsCheckValidAttributeAccess( IrpContext,
|
|
IrpSp,
|
|
Vcb,
|
|
&IndexFileName->Info,
|
|
&AttrName,
|
|
&AttrTypeCode,
|
|
CreateContext->CreateFlags,
|
|
&CcbFlags,
|
|
&IndexedAttribute );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenFile: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = TRUE;
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// We know that it is safe to continue the open. We start by creating
|
|
// an Fcb and Lcb for this file. It is possible that the Fcb and Lcb
|
|
// both exist. If the Lcb exists, then the Fcb must definitely exist.
|
|
// We create the Fcb first, if we need to update the Fcb info structure
|
|
// we copy the one from the index entry. We look at the Fcb to discover
|
|
// if it has any links, if it does then we make this the last Fcb we
|
|
// reached. If it doesn't then we have to clean it up from here.
|
|
//
|
|
|
|
ThisFcb = NtfsCreateFcb( IrpContext,
|
|
ParentScb->Vcb,
|
|
IndexEntry->FileReference,
|
|
BooleanFlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ),
|
|
BooleanFlagOn( IndexFileName->Info.FileAttributes,
|
|
DUP_FILE_NAME_INDEX_PRESENT ),
|
|
&ExistingFcb );
|
|
|
|
ThisFcb->ReferenceCount += 1;
|
|
|
|
//
|
|
// If we created this Fcb we must make sure to start teardown
|
|
// on it.
|
|
//
|
|
|
|
if (!ExistingFcb) {
|
|
|
|
LocalFcbForTeardown = ThisFcb;
|
|
|
|
} else {
|
|
|
|
*LcbForTeardown = NULL;
|
|
CreateContext->CurrentFcb = ThisFcb;
|
|
}
|
|
|
|
//
|
|
// Try to do a fast acquire, otherwise we need to release
|
|
// the Fcb table, acquire the Fcb, acquire the Fcb table to
|
|
// dereference Fcb.
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_SYSTEM_FILE) &&
|
|
(NtfsSegmentNumber( &ParentScb->Fcb->FileReference ) == ROOT_FILE_NAME_INDEX_NUMBER)) {
|
|
|
|
ASSERT( FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ) );
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, 0 );
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
|
|
} else if (!NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, ACQUIRE_DONT_WAIT )) {
|
|
|
|
//
|
|
// Remember the current file reference in the index entry.
|
|
// We want to be able to detect whether an entry is removed.
|
|
//
|
|
|
|
PreviousFileReference = IndexEntry->FileReference;
|
|
DroppedParent = TRUE;
|
|
|
|
ParentScb->Fcb->ReferenceCount += 1;
|
|
InterlockedIncrement( &ParentScb->CleanupCount );
|
|
|
|
//
|
|
// Set the IrpContext to acquire paging io resources if our target
|
|
// has one. This will lock the MappedPageWriter out of this file.
|
|
//
|
|
|
|
if (ThisFcb->PagingIoResource != NULL) {
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
|
|
}
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
NtfsReleaseScbWithPaging( IrpContext, ParentScb );
|
|
NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, 0 );
|
|
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
InterlockedDecrement( &ParentScb->CleanupCount );
|
|
ParentScb->Fcb->ReferenceCount -= 1;
|
|
}
|
|
|
|
ThisFcb->ReferenceCount -= 1;
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
|
|
//
|
|
// If the Fcb existed and this is a paging file then either return
|
|
// sharing violation or force the Fcb and Scb's to go away.
|
|
// Do this for the case where the user is opening a paging file
|
|
// but the Fcb is non-paged or the user is opening a non-paging
|
|
// file and the Fcb is for a paging file.
|
|
//
|
|
|
|
if (ExistingFcb &&
|
|
|
|
((FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
|
|
!FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )) ||
|
|
|
|
(FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
|
|
!FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )))) {
|
|
|
|
if (ThisFcb->CleanupCount != 0) {
|
|
|
|
try_return( Status = STATUS_SHARING_VIOLATION );
|
|
|
|
//
|
|
// If we have a persistent paging file then give up and
|
|
// return SHARING_VIOLATION.
|
|
//
|
|
|
|
} else if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_IN_FSP )) {
|
|
|
|
try_return( Status = STATUS_SHARING_VIOLATION );
|
|
|
|
//
|
|
// If there was an existing Fcb for a paging file we need to force
|
|
// all of the Scb's to be torn down. The easiest way to do this
|
|
// is to flush and purge all of the Scb's (saving any attribute list
|
|
// for last) and then raise LOG_FILE_FULL to allow this request to
|
|
// be posted.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Reference the Fcb so it won't go away on any flushes.
|
|
//
|
|
|
|
InterlockedIncrement( &ThisFcb->CloseCount );
|
|
DecrementCloseCount = TRUE;
|
|
|
|
//
|
|
// Flush and purge this Fcb.
|
|
//
|
|
|
|
NtfsFlushAndPurgeFcb( IrpContext, ThisFcb );
|
|
|
|
InterlockedDecrement( &ThisFcb->CloseCount );
|
|
DecrementCloseCount = FALSE;
|
|
|
|
//
|
|
// Force this request to be posted and then raise
|
|
// CANT_WAIT. The Fcb should be torn down in the finally
|
|
// clause below.
|
|
//
|
|
|
|
NtfsRaiseToPost( IrpContext );
|
|
}
|
|
}
|
|
|
|
//
|
|
// We perform a check to see whether we will allow the system
|
|
// files to be opened.
|
|
//
|
|
// No test to make if this is not a system file or it is the VolumeDasd file.
|
|
// The ACL will protect the volume file.
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_SYSTEM_FILE ) &&
|
|
(NtfsSegmentNumber( &ThisFcb->FileReference ) != VOLUME_DASD_NUMBER) &&
|
|
NtfsProtectSystemFiles) {
|
|
|
|
if (!NtfsCheckValidFileAccess( ThisFcb, IrpSp )) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
DebugTrace( 0, Dbg, ("Invalid access to system files\n") );
|
|
try_return( NOTHING );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the Fcb Info field needs to be initialized, we do so now.
|
|
// We read this information from the disk as the duplicate information
|
|
// in the index entry is not guaranteed to be correct.
|
|
//
|
|
|
|
if (!FlagOn( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
|
|
|
|
HaveScbSizes = NtfsUpdateFcbInfoFromDisk( IrpContext,
|
|
TRUE,
|
|
ThisFcb,
|
|
&ScbSizes );
|
|
|
|
//
|
|
// Remember the last access time in the directory entry.
|
|
//
|
|
|
|
ThisFcb->Info.LastAccessTime = IndexFileName->Info.LastAccessTime;
|
|
|
|
NtfsConditionallyFixupQuota( IrpContext, ThisFcb );
|
|
}
|
|
|
|
//
|
|
// Check if something happened to this file in the window where
|
|
// we dropped the parent.
|
|
//
|
|
|
|
if (DroppedParent) {
|
|
|
|
//
|
|
// Check if the file has been deleted.
|
|
//
|
|
|
|
if (ExistingFcb && (ThisFcb->LinkCount == 0)) {
|
|
|
|
try_return( Status = STATUS_DELETE_PENDING );
|
|
|
|
//
|
|
// Check if the link may have been deleted.
|
|
//
|
|
|
|
} else if (!NtfsEqualMftRef( &IndexEntry->FileReference,
|
|
&PreviousFileReference )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// We have the actual data from the disk stored in the duplicate
|
|
// information in the Fcb. We compare this with the duplicate
|
|
// information in the DUPLICATE_INFORMATION structure in the
|
|
// filename attribute. If they don't match, we remember that
|
|
// we need to update the duplicate information.
|
|
//
|
|
|
|
if (!RtlEqualMemory( &ThisFcb->Info,
|
|
&IndexFileName->Info,
|
|
FIELD_OFFSET( DUPLICATED_INFORMATION, LastAccessTime ))) {
|
|
|
|
UpdateFcbInfo = TRUE;
|
|
|
|
//
|
|
// We expect this to be very rare but let's find the ones being changed.
|
|
//
|
|
|
|
if (ThisFcb->Info.CreationTime != IndexFileName->Info.CreationTime) {
|
|
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_CREATE );
|
|
}
|
|
|
|
if (ThisFcb->Info.LastModificationTime != IndexFileName->Info.LastModificationTime) {
|
|
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_LAST_MOD );
|
|
}
|
|
|
|
if (ThisFcb->Info.LastChangeTime != IndexFileName->Info.LastChangeTime) {
|
|
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
|
|
}
|
|
}
|
|
|
|
if (!RtlEqualMemory( &ThisFcb->Info.AllocatedLength,
|
|
&IndexFileName->Info.AllocatedLength,
|
|
FIELD_OFFSET( DUPLICATED_INFORMATION, Reserved ) -
|
|
FIELD_OFFSET( DUPLICATED_INFORMATION, AllocatedLength ))) {
|
|
|
|
UpdateFcbInfo = TRUE;
|
|
|
|
if (ThisFcb->Info.AllocatedLength != IndexFileName->Info.AllocatedLength) {
|
|
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
|
|
}
|
|
|
|
if (ThisFcb->Info.FileSize != IndexFileName->Info.FileSize) {
|
|
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_FILE_SIZE );
|
|
}
|
|
|
|
if (ThisFcb->Info.FileAttributes != IndexFileName->Info.FileAttributes) {
|
|
|
|
ASSERTMSG( "conflict with flush",
|
|
NtfsIsSharedFcb( ThisFcb ) ||
|
|
(ThisFcb->PagingIoResource != NULL &&
|
|
NtfsIsSharedFcbPagingIo( ThisFcb )) );
|
|
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_FILE_ATTR );
|
|
}
|
|
|
|
if (ThisFcb->Info.PackedEaSize != IndexFileName->Info.PackedEaSize) {
|
|
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_EA_SIZE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Don't update last access unless more than an hour.
|
|
//
|
|
|
|
if (NtfsCheckLastAccess( IrpContext, ThisFcb )) {
|
|
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
|
|
UpdateFcbInfo = TRUE;
|
|
}
|
|
|
|
//
|
|
// Now get the link for this traversal.
|
|
//
|
|
|
|
ThisLcb = NtfsCreateLcb( IrpContext,
|
|
ParentScb,
|
|
ThisFcb,
|
|
FinalName,
|
|
IndexFileName->Flags,
|
|
NULL );
|
|
|
|
//
|
|
// We now know the Fcb is linked into the tree.
|
|
//
|
|
|
|
LocalFcbForTeardown = NULL;
|
|
|
|
*LcbForTeardown = ThisLcb;
|
|
CreateContext->CurrentFcb = ThisFcb;
|
|
|
|
//
|
|
// If the link has been deleted, we cut off the open.
|
|
//
|
|
|
|
if (LcbLinkIsDeleted( ThisLcb )) {
|
|
|
|
try_return( Status = STATUS_DELETE_PENDING );
|
|
}
|
|
|
|
//
|
|
// We now call the worker routine to open an attribute on an existing file.
|
|
//
|
|
|
|
Status = NtfsOpenAttributeInExistingFile( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
(FlagOn( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID )
|
|
? 0
|
|
: FullPathName.Length - FinalName.Length),
|
|
AttrName,
|
|
AttrTypeCode,
|
|
CcbFlags,
|
|
CreateContext->CreateFlags,
|
|
CreateContext->NetworkInfo,
|
|
&CreateContext->ThisScb,
|
|
&CreateContext->ThisCcb );
|
|
|
|
//
|
|
// Check to see if we should insert any prefix table entries
|
|
// and update the last access time.
|
|
// We skip this for reparse points as *ThisScb and *ThisCcb may be NULL.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status ) &&
|
|
(Status != STATUS_PENDING) &&
|
|
(Status != STATUS_WAIT_FOR_OPLOCK) &&
|
|
(Status != STATUS_REPARSE)) {
|
|
|
|
PSCB Scb = CreateContext->ThisScb;
|
|
|
|
//
|
|
// Go ahead and insert this link into the splay tree if it is not
|
|
// a system file.
|
|
//
|
|
|
|
if (!FlagOn( ThisLcb->Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
if ((CreateContext->FileHashLength != 0) &&
|
|
!FlagOn( CcbFlags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
|
|
(ThisLcb->FileNameAttr->Flags != FILE_NAME_DOS) ) {
|
|
|
|
//
|
|
// Remove any exising hash value.
|
|
//
|
|
|
|
if (FlagOn( ThisLcb->LcbState, LCB_STATE_VALID_HASH_VALUE )) {
|
|
|
|
NtfsRemoveHashEntriesForLcb( ThisLcb );
|
|
#ifdef NTFS_HASH_DATA
|
|
ThisFcb->Vcb->HashTable.OpenFileConflict += 1;
|
|
#endif
|
|
}
|
|
|
|
NtfsInsertHashEntry( &Vcb->HashTable,
|
|
ThisLcb,
|
|
CreateContext->FileHashLength,
|
|
CreateContext->FileHashValue );
|
|
#ifdef NTFS_HASH_DATA
|
|
Vcb->HashTable.OpenFileInsert += 1;
|
|
#endif
|
|
}
|
|
|
|
NtfsInsertPrefix( ThisLcb, CreateContext->CreateFlags );
|
|
}
|
|
|
|
//
|
|
// If this is a directory open and the normalized name is not in
|
|
// the Scb then do so now.
|
|
//
|
|
|
|
if ((SafeNodeType( CreateContext->ThisScb ) == NTFS_NTC_SCB_INDEX) &&
|
|
(CreateContext->ThisScb->ScbType.Index.NormalizedName.Length == 0)) {
|
|
|
|
//
|
|
// We may be able to use the parent.
|
|
//
|
|
|
|
if (ParentScb->ScbType.Index.NormalizedName.Length != 0) {
|
|
|
|
NtfsUpdateNormalizedName( IrpContext,
|
|
ParentScb,
|
|
CreateContext->ThisScb,
|
|
IndexFileName,
|
|
FALSE,
|
|
FALSE );
|
|
|
|
} else {
|
|
|
|
NtfsBuildNormalizedName( IrpContext,
|
|
CreateContext->ThisScb->Fcb,
|
|
CreateContext->ThisScb,
|
|
&CreateContext->ThisScb->ScbType.Index.NormalizedName );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Perform the last bit of work. If this a user file open, we need
|
|
// to check if we initialize the Scb.
|
|
//
|
|
|
|
if (!IndexedAttribute) {
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
|
|
|
|
//
|
|
// We may have the sizes from our Fcb update call.
|
|
//
|
|
|
|
if (HaveScbSizes &&
|
|
(AttrTypeCode == $DATA) &&
|
|
(AttrName.Length == 0) &&
|
|
!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB )) {
|
|
|
|
NtfsUpdateScbFromMemory( Scb, &ScbSizes );
|
|
|
|
} else {
|
|
|
|
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Let's check if we need to set the cache bit.
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.Options,
|
|
FILE_NO_INTERMEDIATE_BUFFERING )) {
|
|
|
|
SetFlag( IrpSp->FileObject->Flags, FO_CACHE_SUPPORTED );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is the paging file, we want to be sure the allocation
|
|
// is loaded.
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
|
|
(Scb->Header.AllocationSize.QuadPart != 0) &&
|
|
!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
LCN Lcn;
|
|
VCN Vcn;
|
|
VCN AllocatedVcns;
|
|
|
|
AllocatedVcns = Int64ShraMod32(Scb->Header.AllocationSize.QuadPart, Scb->Vcb->ClusterShift);
|
|
|
|
NtfsPreloadAllocation( IrpContext, Scb, 0, AllocatedVcns );
|
|
|
|
//
|
|
// Now make sure the allocation is correctly loaded. The last
|
|
// Vcn should correspond to the allocation size for the file.
|
|
//
|
|
|
|
if (!NtfsLookupLastNtfsMcbEntry( &Scb->Mcb,
|
|
&Vcn,
|
|
&Lcn ) ||
|
|
(Vcn + 1) != AllocatedVcns) {
|
|
|
|
NtfsRaiseStatus( IrpContext,
|
|
STATUS_FILE_CORRUPT_ERROR,
|
|
NULL,
|
|
ThisFcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this open is for an executable image we update the last
|
|
// access time.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess, FILE_EXECUTE ) &&
|
|
(Scb->AttributeTypeCode == $DATA)) {
|
|
|
|
SetFlag( IrpSp->FileObject->Flags, FO_FILE_FAST_IO_READ );
|
|
}
|
|
|
|
//
|
|
// Let's update the quick index information in the Lcb.
|
|
//
|
|
|
|
RtlCopyMemory( &ThisLcb->QuickIndex,
|
|
QuickIndex,
|
|
sizeof( QUICK_INDEX ));
|
|
|
|
//
|
|
// If everything has gone well so far, we may want to call the
|
|
// encryption callback if one is registered. We do not do
|
|
// this for network opens or reparse points.
|
|
//
|
|
|
|
if (CreateContext->NetworkInfo == NULL) {
|
|
|
|
NtfsEncryptionCreateCallback( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
CreateContext->ThisScb,
|
|
CreateContext->ThisCcb,
|
|
ParentScb->Fcb,
|
|
CreateContext,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// If this operation was a supersede/overwrite or we created a new
|
|
// attribute stream then we want to perform the file record and
|
|
// directory update now. Otherwise we will defer the updates until
|
|
// the user closes his handle.
|
|
//
|
|
|
|
if (UpdateFcbInfo || NtfsIsStreamNew( Irp->IoStatus.Information )) {
|
|
NtfsUpdateAllInformation( IrpContext, IrpSp->FileObject, ThisFcb, CreateContext->ThisScb, CreateContext->ThisCcb, ParentScb, *LcbForTeardown );
|
|
}
|
|
}
|
|
|
|
try_exit: NOTHING;
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsOpenFile );
|
|
|
|
if (AcquiredFcbTable) {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
}
|
|
|
|
//
|
|
// If this operation was not totally successful we need to
|
|
// back out the following changes.
|
|
//
|
|
// Modifications to the Info fields in the Fcb.
|
|
// Any changes to the allocation of the Scb.
|
|
// Any changes in the open counts in the various structures.
|
|
// Changes to the share access values in the Fcb.
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status ) || AbnormalTermination()) {
|
|
|
|
NtfsBackoutFailedOpens( IrpContext,
|
|
IrpSp->FileObject,
|
|
ThisFcb,
|
|
CreateContext->ThisScb,
|
|
CreateContext->ThisCcb );
|
|
|
|
}
|
|
|
|
if (DecrementCloseCount) {
|
|
|
|
InterlockedDecrement( &ThisFcb->CloseCount );
|
|
}
|
|
|
|
//
|
|
// If we are to cleanup the Fcb we, look to see if we created it.
|
|
// If we did we can call our teardown routine. Otherwise we
|
|
// leave it alone.
|
|
//
|
|
|
|
if ((LocalFcbForTeardown != NULL) &&
|
|
(Status != STATUS_PENDING) &&
|
|
(Status != STATUS_WAIT_FOR_OPLOCK)) {
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
ThisFcb,
|
|
NULL,
|
|
(BOOLEAN) (IrpContext->TransactionId != 0),
|
|
0,
|
|
NULL );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenFile: Exit -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsCreateNewFile (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PSCB ParentScb,
|
|
IN PFILE_NAME FileNameAttr,
|
|
IN UNICODE_STRING FullPathName,
|
|
IN UNICODE_STRING FinalName,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN PINDEX_CONTEXT *IndexContext,
|
|
IN PCREATE_CONTEXT CreateContext,
|
|
OUT PLCB *LcbForTeardown
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when we need to open an attribute on a file
|
|
which does not exist yet. We have the ParentScb and the name to use
|
|
for this create. We will attempt to create the file and necessary
|
|
attributes. This will cause us to create an Fcb and the link between
|
|
it and its parent Scb. We will add this link to the prefix table as
|
|
well as the link for its parent Scb if specified.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
IrpSp - This is the Irp stack pointer for the filesystem.
|
|
|
|
ParentScb - This is the Scb for the parent directory.
|
|
|
|
FileNameAttr - This is the file name attribute we used to perform the
|
|
search. The file name is correct but the other fields need to
|
|
be initialized.
|
|
|
|
FullPathName - This is the string containing the full path name of
|
|
this Fcb.
|
|
|
|
FinalName - This is the string for the final component only.
|
|
|
|
AttrName - This is the name of the attribute to open.
|
|
|
|
AttriCodeName - This is the name of the attribute code to open.
|
|
|
|
CreateFlags - Flags for create - we care about ignore case, dos only component,
|
|
trailingbackslashes and open by id
|
|
|
|
IndexContext - If this contains a non-NULL value then this is the result of a
|
|
lookup which did not find the file. It can be used to insert the name into the index.
|
|
We will clean it up here in the error path to prevent a deadlock if we call
|
|
TeardownStructures within this routine.
|
|
|
|
CreateContext - Context with create variables.
|
|
|
|
LcbForTeardown - This is the Lcb to use in teardown if we add an Lcb
|
|
into the tree.
|
|
|
|
Tunnel - This is the property tunnel to search for restoration
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Indicates the result of this create file operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PVCB Vcb;
|
|
|
|
ULONG CcbFlags = 0;
|
|
ULONG UsnReasons = 0;
|
|
BOOLEAN IndexedAttribute;
|
|
|
|
BOOLEAN CleanupAttrContext = FALSE;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
|
|
PBCB FileRecordBcb = NULL;
|
|
LONGLONG FileRecordOffset;
|
|
FILE_REFERENCE ThisFileReference;
|
|
PFILE_RECORD_SEGMENT_HEADER FileRecord;
|
|
|
|
PSCB Scb;
|
|
PLCB ThisLcb = NULL;
|
|
PFCB ThisFcb = NULL;
|
|
BOOLEAN AcquiredFcbTable = FALSE;
|
|
BOOLEAN RemovedFcb = FALSE;
|
|
BOOLEAN DecrementCloseCount = FALSE;
|
|
|
|
PACCESS_STATE AccessState;
|
|
BOOLEAN ReturnedExistingFcb;
|
|
|
|
BOOLEAN LoggedFileRecord = FALSE;
|
|
|
|
BOOLEAN HaveTunneledInformation = FALSE;
|
|
|
|
NAME_PAIR NamePair;
|
|
NTFS_TUNNELED_DATA TunneledData;
|
|
ULONG TunneledDataSize;
|
|
ULONG OwnerId;
|
|
PQUOTA_CONTROL_BLOCK QuotaControl = NULL;
|
|
|
|
PSHARED_SECURITY SharedSecurity = NULL;
|
|
|
|
VCN Cluster;
|
|
LCN Lcn;
|
|
VCN Vcn;
|
|
|
|
ULONG DesiredAccess;
|
|
|
|
UCHAR FileNameFlags;
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
BOOLEAN Acquired;
|
|
ULONG CreateDisposition;
|
|
#endif
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCreateNewFile: Entered\n") );
|
|
|
|
|
|
NtfsInitializeNamePair(&NamePair);
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_DOS_ONLY_COMPONENT )) {
|
|
|
|
SetFlag( CcbFlags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT );
|
|
}
|
|
|
|
//
|
|
// We will do all the checks to see if this open can fail.
|
|
// This includes checking the specified attribute names, checking
|
|
// the security access and checking the create disposition.
|
|
//
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
CreateDisposition = (IrpSp->Parameters.Create.Options >> 24) & 0x000000ff;
|
|
#endif
|
|
|
|
ASSERT( (CreateDisposition != FILE_OPEN) && (CreateDisposition != FILE_OVERWRITE) );
|
|
ASSERT( !NtfsIsVolumeReadOnly( ParentScb->Vcb ));
|
|
ASSERT( !FlagOn( IrpSp->Parameters.Create.Options, FILE_DIRECTORY_FILE ) ||
|
|
(CreateDisposition != FILE_OVERWRITE_IF));
|
|
|
|
Vcb = ParentScb->Vcb;
|
|
Status = NtfsCheckValidAttributeAccess( IrpContext,
|
|
IrpSp,
|
|
Vcb,
|
|
NULL,
|
|
&AttrName,
|
|
&AttrTypeCode,
|
|
CreateContext->CreateFlags,
|
|
&CcbFlags,
|
|
&IndexedAttribute );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Fail this request if this is an indexed attribute and the TEMPORARY
|
|
// bit is set.
|
|
//
|
|
|
|
if (IndexedAttribute &&
|
|
FlagOn( IrpSp->Parameters.Create.FileAttributes, FILE_ATTRIBUTE_TEMPORARY )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", STATUS_INVALID_PARAMETER) );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// We won't allow someone to create a read-only file with DELETE_ON_CLOSE.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.FileAttributes, FILE_ATTRIBUTE_READONLY ) &&
|
|
FlagOn( IrpSp->Parameters.Create.Options, FILE_DELETE_ON_CLOSE )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", STATUS_CANNOT_DELETE) );
|
|
return STATUS_CANNOT_DELETE;
|
|
}
|
|
|
|
//
|
|
// We do not allow that anything be created in a directory that is a reparse
|
|
// point. We verify that the parent is not in this category.
|
|
//
|
|
|
|
if (IsDirectory( &ParentScb->Fcb->Info ) &&
|
|
(FlagOn( ParentScb->Fcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT ))) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", STATUS_DIRECTORY_IS_A_REPARSE_POINT) );
|
|
return STATUS_DIRECTORY_IS_A_REPARSE_POINT;
|
|
}
|
|
|
|
//
|
|
// We do not allow anything to be created in a system directory (unless it is the root directory).
|
|
// we only allow creates for indices and data streams
|
|
//
|
|
|
|
if ((FlagOn( ParentScb->Fcb->FcbState, FCB_STATE_SYSTEM_FILE ) &&
|
|
(ParentScb != Vcb->RootIndexScb)) ||
|
|
!((AttrTypeCode == $DATA) || (AttrTypeCode == $INDEX_ALLOCATION))) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", STATUS_ACCESS_DENIED) );
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Now perform the security checks. The first is to check if we
|
|
// may create a file in the parent. The second checks if the user
|
|
// desires ACCESS_SYSTEM_SECURITY and has the required privilege.
|
|
//
|
|
|
|
AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState;
|
|
|
|
//
|
|
// Calculate desired access needed in parent
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.Options, FILE_DIRECTORY_FILE )) {
|
|
DesiredAccess = FILE_ADD_FILE;
|
|
} else {
|
|
DesiredAccess = FILE_ADD_SUBDIRECTORY;
|
|
}
|
|
|
|
//
|
|
// If we have restore privilege auto grant all the access other than ACCESS_SYSTEM_SECURITY
|
|
// which always requires a privilege check
|
|
//
|
|
|
|
if (FlagOn( AccessState->Flags, TOKEN_HAS_RESTORE_PRIVILEGE )) {
|
|
|
|
SetFlag( AccessState->PreviouslyGrantedAccess, FlagOn( AccessState->RemainingDesiredAccess, ~ACCESS_SYSTEM_SECURITY ) );
|
|
ClearFlag( AccessState->RemainingDesiredAccess, AccessState->PreviouslyGrantedAccess );
|
|
|
|
//
|
|
// We don't need any desired access in the parent since we have the privilege
|
|
//
|
|
|
|
DesiredAccess = 0;
|
|
}
|
|
|
|
//
|
|
// Always do an explicity access check - to guarantee auditing is done. This is done
|
|
// in checking only mode since we're asking about the parent not the file itself so
|
|
// the access state shouldn't change - in fact when creating a file you get whatever
|
|
// access state you ask for if you are allowed to create the file
|
|
//
|
|
|
|
NtfsAccessCheck( IrpContext, ParentScb->Fcb, NULL, Irp , DesiredAccess, TRUE );
|
|
|
|
//
|
|
// We want to allow this user maximum access to this file. We will
|
|
// use his desired access and check if he specified MAXIMUM_ALLOWED.
|
|
//
|
|
|
|
SetFlag( AccessState->PreviouslyGrantedAccess,
|
|
AccessState->RemainingDesiredAccess );
|
|
|
|
if (FlagOn( AccessState->PreviouslyGrantedAccess, MAXIMUM_ALLOWED )) {
|
|
|
|
SetFlag( AccessState->PreviouslyGrantedAccess, FILE_ALL_ACCESS );
|
|
ClearFlag( AccessState->PreviouslyGrantedAccess, MAXIMUM_ALLOWED );
|
|
}
|
|
|
|
AccessState->RemainingDesiredAccess = 0;
|
|
|
|
//
|
|
// Find/cache the security descriptor being passed in. This call may
|
|
// create new data in the security indexes/stream and commits
|
|
// before any subsequent disk modifications occur.
|
|
//
|
|
|
|
SharedSecurity = NtfsCacheSharedSecurityForCreate( IrpContext, ParentScb->Fcb );
|
|
|
|
//
|
|
// Make sure the parent has a normalized name. We want to construct it now
|
|
// while we can still walk up the Lcb queue. Otherwise we can deadlock
|
|
// on the Mft and other resources.
|
|
//
|
|
|
|
if ((AttrTypeCode == $INDEX_ALLOCATION) &&
|
|
(ParentScb->ScbType.Index.NormalizedName.Length == 0)) {
|
|
|
|
NtfsBuildNormalizedName( IrpContext,
|
|
ParentScb->Fcb,
|
|
ParentScb,
|
|
&ParentScb->ScbType.Index.NormalizedName );
|
|
}
|
|
|
|
//
|
|
// Decide whether there's anything in the tunnel cache for this create.
|
|
// We don't do tunnelling in POSIX mode, hence the test for IgnoreCase.
|
|
//
|
|
|
|
if (!IndexedAttribute && FlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE )) {
|
|
|
|
TunneledDataSize = sizeof(NTFS_TUNNELED_DATA);
|
|
|
|
if (FsRtlFindInTunnelCache( &Vcb->Tunnel,
|
|
*(PULONGLONG)&ParentScb->Fcb->FileReference,
|
|
&FinalName,
|
|
&NamePair.Short,
|
|
&NamePair.Long,
|
|
&TunneledDataSize,
|
|
&TunneledData)) {
|
|
|
|
ASSERT( TunneledDataSize == sizeof(NTFS_TUNNELED_DATA) );
|
|
|
|
HaveTunneledInformation = TRUE;
|
|
|
|
//
|
|
// If we have tunneled data and there's an object in the
|
|
// tunnel cache for this file, we need to acquire the object
|
|
// id index now (before acquiring any quota resources) to
|
|
// prevent a deadlock. If there's no object id, then we
|
|
// won't try to set the object id later, and there's no
|
|
// deadlock to worry about.
|
|
//
|
|
|
|
if (TunneledData.HasObjectId) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->ObjectIdTableScb );
|
|
|
|
ASSERT( !FlagOn( CreateContext->CreateFlags, CREATE_FLAG_ACQUIRED_OBJECT_ID_INDEX ) );
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_ACQUIRED_OBJECT_ID_INDEX );
|
|
|
|
//
|
|
// The object id package won't post the Usn reason if it
|
|
// sees it's been called in the create path, since the
|
|
// file name is not yet in the file record, so it's unsafe
|
|
// to call the Usn package. When we post the create to the
|
|
// Usn package below, we'll remember to post this one, too.
|
|
//
|
|
|
|
UsnReasons |= USN_REASON_OBJECT_ID_CHANGE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If quota tracking is enabled then get a owner id for the file.
|
|
//
|
|
|
|
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED )) {
|
|
|
|
PSID Sid;
|
|
BOOLEAN OwnerDefaulted;
|
|
|
|
//
|
|
// The quota index must be acquired before the MFT SCB is acquired.
|
|
//
|
|
|
|
ASSERT( !NtfsIsExclusiveScb( Vcb->MftScb ) || NtfsIsExclusiveScb( Vcb->QuotaTableScb ));
|
|
|
|
//
|
|
// Extract the security id from the security descriptor.
|
|
//
|
|
|
|
Status = RtlGetOwnerSecurityDescriptor( SharedSecurity->SecurityDescriptor,
|
|
&Sid,
|
|
&OwnerDefaulted );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Generate a owner id.
|
|
//
|
|
|
|
OwnerId = NtfsGetOwnerId( IrpContext, Sid, TRUE, NULL );
|
|
|
|
QuotaControl = NtfsInitializeQuotaControlBlock( Vcb, OwnerId );
|
|
|
|
//
|
|
// Acquire the quota control block. This is done here since it
|
|
// must be acquired before the MFT.
|
|
//
|
|
|
|
NtfsAcquireQuotaControl( IrpContext, QuotaControl );
|
|
}
|
|
|
|
//
|
|
// We will now try to do all of the on-disk operations. This means first
|
|
// allocating and initializing an Mft record. After that we create
|
|
// an Fcb to use to access this record.
|
|
//
|
|
|
|
ThisFileReference = NtfsAllocateMftRecord( IrpContext,
|
|
Vcb,
|
|
FALSE );
|
|
|
|
//
|
|
// Pin the file record we need.
|
|
//
|
|
|
|
NtfsPinMftRecord( IrpContext,
|
|
Vcb,
|
|
&ThisFileReference,
|
|
TRUE,
|
|
&FileRecordBcb,
|
|
&FileRecord,
|
|
&FileRecordOffset );
|
|
|
|
//
|
|
// Initialize the file record header.
|
|
//
|
|
|
|
NtfsInitializeMftRecord( IrpContext,
|
|
Vcb,
|
|
&ThisFileReference,
|
|
FileRecord,
|
|
FileRecordBcb,
|
|
IndexedAttribute );
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = TRUE;
|
|
|
|
ThisFcb = NtfsCreateFcb( IrpContext,
|
|
Vcb,
|
|
ThisFileReference,
|
|
BooleanFlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ),
|
|
IndexedAttribute,
|
|
&ReturnedExistingFcb );
|
|
|
|
ASSERT( !ReturnedExistingFcb );
|
|
|
|
//
|
|
// Set the flag indicating we want to acquire the paging io resource
|
|
// if it doesn't already exist. Use acquire don't wait for lock order
|
|
// package. Since this is a new file and we haven't dropped the fcb table
|
|
// mutex yet, no one else can own it. So this will always succeed.
|
|
//
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
Acquired =
|
|
#endif
|
|
NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, ACQUIRE_DONT_WAIT );
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
ASSERT( Acquired );
|
|
#endif
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
|
|
//
|
|
// Reference the Fcb so it won't go away.
|
|
//
|
|
|
|
InterlockedIncrement( &ThisFcb->CloseCount );
|
|
DecrementCloseCount = TRUE;
|
|
|
|
//
|
|
// The first thing to create is the Ea's for the file. This will
|
|
// update the Ea length field in the Fcb.
|
|
// We test here that the opener is opening the entire file and
|
|
// is not Ea blind.
|
|
//
|
|
|
|
if (Irp->AssociatedIrp.SystemBuffer != NULL) {
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_EA_KNOWLEDGE ) ||
|
|
!FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
leave;
|
|
}
|
|
}
|
|
|
|
SetFlag( ThisFcb->FcbState, FCB_STATE_LARGE_STD_INFO );
|
|
|
|
//
|
|
// Set up the security Id (if we've found one earlier).
|
|
// We need to be careful so that this works on upgraded and
|
|
// non-upgraded volumes.
|
|
//
|
|
|
|
if (ThisFcb->Vcb->SecurityDescriptorStream != NULL) {
|
|
ThisFcb->SecurityId = SharedSecurity->Header.HashKey.SecurityId;
|
|
ThisFcb->SharedSecurity = SharedSecurity;
|
|
DebugTrace(0, (DEBUG_TRACE_SECURSUP | DEBUG_TRACE_ACLINDEX),
|
|
( "SetFcbSecurity( %08x, %08x )\n", ThisFcb, SharedSecurity ));
|
|
SharedSecurity = NULL;
|
|
|
|
} else {
|
|
|
|
ASSERT( ThisFcb->SecurityId == SECURITY_ID_INVALID );
|
|
}
|
|
|
|
ASSERT( SharedSecurity == NULL );
|
|
|
|
//
|
|
// Assign the owner Id and quota control block to the fcb. Once the
|
|
// quota control block is in the FCB this routine is not responsible
|
|
// for the reference to the quota control block.
|
|
//
|
|
|
|
if (QuotaControl != NULL) {
|
|
|
|
//
|
|
// Assign the onwer Id and quota control block to the fcb. Once the
|
|
// quota control block is in the FCB this routine is not responsible
|
|
// for the reference to the quota control block.
|
|
//
|
|
|
|
ThisFcb->OwnerId = OwnerId;
|
|
ThisFcb->QuotaControl = QuotaControl;
|
|
QuotaControl = NULL;
|
|
}
|
|
|
|
//
|
|
// Update the FileAttributes with the state of the CONTENT_INDEXED bit from the
|
|
// parent.
|
|
//
|
|
|
|
if (!FlagOn( ParentScb->Fcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
|
|
|
|
NtfsUpdateFcbInfoFromDisk( IrpContext, FALSE, ParentScb->Fcb, NULL );
|
|
}
|
|
|
|
ClearFlag( IrpSp->Parameters.Create.FileAttributes, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED );
|
|
SetFlag( IrpSp->Parameters.Create.FileAttributes,
|
|
(ParentScb->Fcb->Info.FileAttributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) );
|
|
|
|
//
|
|
// The changes to make on disk are first to create a standard information
|
|
// attribute. We start by filling the Fcb with the information we
|
|
// know and creating the attribute on disk.
|
|
//
|
|
|
|
NtfsInitializeFcbAndStdInfo( IrpContext,
|
|
ThisFcb,
|
|
IndexedAttribute,
|
|
FALSE,
|
|
(BOOLEAN) (!FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION ) &&
|
|
!FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
|
|
FlagOn( ParentScb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )),
|
|
IrpSp->Parameters.Create.FileAttributes,
|
|
(HaveTunneledInformation ? &TunneledData : NULL) );
|
|
|
|
//
|
|
// Next we create the Index for a directory or the unnamed data for
|
|
// a file if they are not explicitly being opened.
|
|
//
|
|
|
|
if (!IndexedAttribute) {
|
|
|
|
if (!FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
//
|
|
// Update the quota
|
|
//
|
|
|
|
LONGLONG Delta = NtfsResidentStreamQuota( ThisFcb->Vcb );
|
|
|
|
NtfsConditionallyUpdateQuota( IrpContext,
|
|
ThisFcb,
|
|
&Delta,
|
|
FALSE,
|
|
TRUE );
|
|
|
|
//
|
|
// Create the attribute
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
CleanupAttrContext = TRUE;
|
|
|
|
NtfsCreateAttributeWithValue( IrpContext,
|
|
ThisFcb,
|
|
$DATA,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
(USHORT) ((!FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
|
|
!FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION )) ?
|
|
(ParentScb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) :
|
|
0),
|
|
NULL,
|
|
FALSE,
|
|
&AttrContext );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
CleanupAttrContext = FALSE;
|
|
|
|
ThisFcb->Info.AllocatedLength = 0;
|
|
ThisFcb->Info.FileSize = 0;
|
|
}
|
|
|
|
} else {
|
|
|
|
NtfsCreateIndex( IrpContext,
|
|
ThisFcb,
|
|
$FILE_NAME,
|
|
COLLATION_FILE_NAME,
|
|
Vcb->DefaultBytesPerIndexAllocationBuffer,
|
|
(UCHAR)Vcb->DefaultBlocksPerIndexAllocationBuffer,
|
|
NULL,
|
|
(USHORT) (!FlagOn( IrpSp->Parameters.Create.Options,
|
|
FILE_NO_COMPRESSION ) ?
|
|
(ParentScb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) :
|
|
0),
|
|
TRUE,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Now we create the Lcb, this means that this Fcb is in the graph.
|
|
//
|
|
|
|
ThisLcb = NtfsCreateLcb( IrpContext,
|
|
ParentScb,
|
|
ThisFcb,
|
|
FinalName,
|
|
0,
|
|
NULL );
|
|
|
|
ASSERT( ThisLcb != NULL );
|
|
|
|
//
|
|
// Finally we create and open the desired attribute for the user.
|
|
//
|
|
|
|
if (AttrTypeCode == $INDEX_ALLOCATION) {
|
|
|
|
Status = NtfsOpenAttribute( IrpContext,
|
|
IrpSp,
|
|
Vcb,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
(FlagOn( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID )
|
|
? 0
|
|
: FullPathName.Length - FinalName.Length),
|
|
NtfsFileNameIndex,
|
|
$INDEX_ALLOCATION,
|
|
SetShareAccess,
|
|
UserDirectoryOpen,
|
|
TRUE,
|
|
(FlagOn( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID )
|
|
? CcbFlags | CCB_FLAG_OPEN_BY_FILE_ID
|
|
: CcbFlags),
|
|
NULL,
|
|
&CreateContext->ThisScb,
|
|
&CreateContext->ThisCcb );
|
|
|
|
} else {
|
|
|
|
Status = NtfsOpenNewAttr( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
(FlagOn( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID )
|
|
? 0
|
|
: FullPathName.Length - FinalName.Length),
|
|
AttrName,
|
|
AttrTypeCode,
|
|
TRUE,
|
|
CcbFlags,
|
|
FALSE,
|
|
CreateContext->CreateFlags,
|
|
&CreateContext->ThisScb,
|
|
&CreateContext->ThisCcb );
|
|
}
|
|
|
|
//
|
|
// If we are successful, we add the parent Lcb to the prefix table if
|
|
// desired. We will always add our link to the prefix queue.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
Scb = CreateContext->ThisScb;
|
|
|
|
//
|
|
// Initialize the Scb if we need to do so.
|
|
//
|
|
|
|
if (!IndexedAttribute) {
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
|
|
|
|
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
|
|
}
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.Options,
|
|
FILE_NO_INTERMEDIATE_BUFFERING )) {
|
|
|
|
SetFlag( IrpSp->FileObject->Flags, FO_CACHE_SUPPORTED );
|
|
}
|
|
|
|
//
|
|
// If this is the unnamed data attribute, we store the sizes
|
|
// in the Fcb.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
|
|
|
|
ThisFcb->Info.AllocatedLength = Scb->TotalAllocated;
|
|
ThisFcb->Info.FileSize = Scb->Header.FileSize.QuadPart;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Next add this entry to parent. It is possible that this is a link,
|
|
// an Ntfs name, a DOS name or Ntfs/Dos name. We use the filename
|
|
// attribute structure from earlier, but need to add more information.
|
|
//
|
|
|
|
NtfsAddLink( IrpContext,
|
|
(BOOLEAN) !BooleanFlagOn( IrpSp->Flags, SL_CASE_SENSITIVE ),
|
|
ParentScb,
|
|
ThisFcb,
|
|
FileNameAttr,
|
|
&LoggedFileRecord,
|
|
&FileNameFlags,
|
|
&ThisLcb->QuickIndex,
|
|
(HaveTunneledInformation? &NamePair : NULL),
|
|
*IndexContext );
|
|
|
|
//
|
|
// We created the Lcb without knowing the correct value for the
|
|
// flags. We update it now.
|
|
//
|
|
|
|
ThisLcb->FileNameAttr->Flags = FileNameFlags;
|
|
FileNameAttr->Flags = FileNameFlags;
|
|
|
|
//
|
|
// We also have to fix up the ExactCaseLink of the Lcb since we may have had
|
|
// a short name create turned into a tunneled long name create, meaning that
|
|
// it should be full uppercase. And the filename in the IRP.
|
|
//
|
|
|
|
if (FileNameFlags == FILE_NAME_DOS) {
|
|
|
|
RtlUpcaseUnicodeString( &ThisLcb->ExactCaseLink.LinkName, &ThisLcb->ExactCaseLink.LinkName, FALSE );
|
|
RtlUpcaseUnicodeString( &IrpSp->FileObject->FileName, &IrpSp->FileObject->FileName, FALSE );
|
|
}
|
|
|
|
//
|
|
// Clear the flags in the Fcb that indicate we need to update on
|
|
// disk structures. Also clear any file object and Ccb flags
|
|
// which also indicate we may need to do an update.
|
|
//
|
|
|
|
ThisFcb->InfoFlags = 0;
|
|
ClearFlag( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
|
|
ClearFlag( IrpSp->FileObject->Flags,
|
|
FO_FILE_MODIFIED | FO_FILE_FAST_IO_READ | FO_FILE_SIZE_CHANGED );
|
|
|
|
ClearFlag( CreateContext->ThisCcb->Flags,
|
|
(CCB_FLAG_UPDATE_LAST_MODIFY |
|
|
CCB_FLAG_UPDATE_LAST_CHANGE |
|
|
CCB_FLAG_SET_ARCHIVE) );
|
|
|
|
//
|
|
// This code is still necessary for non-upgraded volumes.
|
|
//
|
|
|
|
NtfsAssignSecurity( IrpContext,
|
|
ParentScb->Fcb,
|
|
Irp,
|
|
ThisFcb,
|
|
FileRecord,
|
|
FileRecordBcb,
|
|
FileRecordOffset,
|
|
&LoggedFileRecord );
|
|
|
|
//
|
|
// Log the file record.
|
|
//
|
|
|
|
FileRecord->Lsn = NtfsWriteLog( IrpContext,
|
|
Vcb->MftScb,
|
|
FileRecordBcb,
|
|
InitializeFileRecordSegment,
|
|
FileRecord,
|
|
FileRecord->FirstFreeByte,
|
|
Noop,
|
|
NULL,
|
|
0,
|
|
FileRecordOffset,
|
|
0,
|
|
0,
|
|
Vcb->BytesPerFileRecordSegment );
|
|
|
|
//
|
|
// Now add the eas for the file. We need to add them now because
|
|
// they are logged and we have to make sure we don't modify the
|
|
// attribute record after adding them.
|
|
//
|
|
|
|
if (Irp->AssociatedIrp.SystemBuffer != NULL) {
|
|
|
|
NtfsAddEa( IrpContext,
|
|
Vcb,
|
|
ThisFcb,
|
|
(PFILE_FULL_EA_INFORMATION) Irp->AssociatedIrp.SystemBuffer,
|
|
IrpSp->Parameters.Create.EaLength,
|
|
&Irp->IoStatus );
|
|
}
|
|
|
|
//
|
|
// Change the last modification time and last change time for the
|
|
// parent.
|
|
//
|
|
|
|
NtfsUpdateFcb( ParentScb->Fcb,
|
|
(FCB_INFO_CHANGED_LAST_CHANGE |
|
|
FCB_INFO_CHANGED_LAST_MOD |
|
|
FCB_INFO_UPDATE_LAST_ACCESS) );
|
|
|
|
//
|
|
// If this is the paging file, we want to be sure the allocation
|
|
// is loaded.
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )) {
|
|
|
|
Cluster = Int64ShraMod32( Scb->Header.AllocationSize.QuadPart, Scb->Vcb->ClusterShift );
|
|
|
|
NtfsPreloadAllocation( IrpContext, Scb, 0, Cluster );
|
|
|
|
//
|
|
// Now make sure the allocation is correctly loaded. The last
|
|
// Vcn should correspond to the allocation size for the file.
|
|
//
|
|
|
|
if (!NtfsLookupLastNtfsMcbEntry( &Scb->Mcb,
|
|
&Vcn,
|
|
&Lcn ) ||
|
|
(Vcn + 1) != Cluster) {
|
|
|
|
NtfsRaiseStatus( IrpContext,
|
|
STATUS_FILE_CORRUPT_ERROR,
|
|
NULL,
|
|
ThisFcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If everything has gone well so far, we may want to call the
|
|
// encryption callback if one is registered.
|
|
//
|
|
// We need to do this now because the encryption driver may fail
|
|
// the create, and we don't want that to happen _after_ we've
|
|
// added the entry to the prefix table.
|
|
//
|
|
|
|
try {
|
|
|
|
CreateContext->CurrentFcb = ThisFcb;
|
|
|
|
NtfsEncryptionCreateCallback( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
CreateContext->ThisScb,
|
|
CreateContext->ThisCcb,
|
|
ParentScb->Fcb,
|
|
CreateContext,
|
|
TRUE );
|
|
} finally {
|
|
CreateContext->CurrentFcb = NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// Now that there are no other failures, but *before* inserting the prefix
|
|
// entry and returning to code that assumes it cannot fail, we will post the
|
|
// UsnJournal change and actually attempt to write the UsnJournal. Then we
|
|
// actually commit the transaction in order to reduce UsnJournal contention.
|
|
// This call must be made _after_ the call to NtfsInitializeFcbAndStdInfo,
|
|
// since that's where the object id gets set from the tunnel cache, and we
|
|
// wouldn't want to post the usn reason for the object id change if we
|
|
// haven't actually set the object id yet.
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext, ThisFcb, (UsnReasons | USN_REASON_FILE_CREATE) );
|
|
|
|
//
|
|
// If this is a directory open and the normalized name is not in
|
|
// the Scb then do so now. We should always have a normalized name in the
|
|
// parent to build from.
|
|
//
|
|
|
|
if ((SafeNodeType( CreateContext->ThisScb ) == NTFS_NTC_SCB_INDEX) &&
|
|
(CreateContext->ThisScb->ScbType.Index.NormalizedName.Length == 0)) {
|
|
|
|
//
|
|
// We may be able to use the parent.
|
|
//
|
|
|
|
if (ParentScb->ScbType.Index.NormalizedName.Length != 0) {
|
|
|
|
NtfsUpdateNormalizedName( IrpContext,
|
|
ParentScb,
|
|
CreateContext->ThisScb,
|
|
FileNameAttr,
|
|
FALSE,
|
|
TRUE );
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now, if anything at all is posted to the Usn Journal, we must write it now
|
|
// so that we do not get a log file full later.
|
|
//
|
|
|
|
ASSERT( IrpContext->Usn.NextUsnFcb == NULL );
|
|
if (IrpContext->Usn.CurrentUsnFcb != NULL) {
|
|
|
|
//
|
|
// Now write the journal, checkpoint the transaction, and free the UsnJournal to
|
|
// reduce contention.
|
|
//
|
|
|
|
NtfsWriteUsnJournalChanges( IrpContext );
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
}
|
|
|
|
//
|
|
// We report to our parent that we created a new file.
|
|
//
|
|
|
|
if (!FlagOn( CreateContext->CreateFlags, CREATE_FLAG_OPEN_BY_ID ) && (Vcb->NotifyCount != 0)) {
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
ThisFcb->Vcb,
|
|
&CreateContext->ThisCcb->FullFileName,
|
|
CreateContext->ThisCcb->LastFileNameOffset,
|
|
NULL,
|
|
((FlagOn( CreateContext->ThisCcb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
|
|
(CreateContext->ThisCcb->Lcb != NULL) &&
|
|
(CreateContext->ThisCcb->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != 0)) ?
|
|
&CreateContext->ThisCcb->Lcb->Scb->ScbType.Index.NormalizedName :
|
|
NULL),
|
|
(IndexedAttribute
|
|
? FILE_NOTIFY_CHANGE_DIR_NAME
|
|
: FILE_NOTIFY_CHANGE_FILE_NAME),
|
|
FILE_ACTION_ADDED,
|
|
ParentScb->Fcb );
|
|
}
|
|
|
|
ThisFcb->InfoFlags = 0;
|
|
|
|
//
|
|
// Insert the hash entry for this as well.
|
|
//
|
|
|
|
if ((CreateContext->FileHashLength != 0) &&
|
|
!FlagOn( CcbFlags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
|
|
(ThisLcb->FileNameAttr->Flags != FILE_NAME_DOS) ) {
|
|
|
|
//
|
|
// Remove any exising hash value.
|
|
//
|
|
|
|
if (FlagOn( ThisLcb->LcbState, LCB_STATE_VALID_HASH_VALUE )) {
|
|
|
|
NtfsRemoveHashEntriesForLcb( ThisLcb );
|
|
}
|
|
|
|
NtfsInsertHashEntry( &Vcb->HashTable,
|
|
ThisLcb,
|
|
CreateContext->FileHashLength,
|
|
CreateContext->FileHashValue );
|
|
|
|
#ifdef NTFS_HASH_DATA
|
|
Vcb->HashTable.CreateNewFileInsert += 1;
|
|
#endif
|
|
}
|
|
|
|
|
|
//
|
|
// Now we insert the Lcb for this Fcb.
|
|
//
|
|
|
|
NtfsInsertPrefix( ThisLcb, CreateContext->CreateFlags );
|
|
|
|
Irp->IoStatus.Information = FILE_CREATED;
|
|
|
|
//
|
|
// If we'll be calling a post create callout, make sure
|
|
// NtfsEncryptionCreateCallback set this Fcb bit.
|
|
//
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_EFS_CREATE ) &&
|
|
FlagOn( IrpContext->State, IRP_CONTEXT_STATE_PERSISTENT ) &&
|
|
FlagOn( CreateContext->EncryptionFileDirFlags, FILE_NEW )) {
|
|
|
|
ASSERT( FlagOn( ThisFcb->FcbState, FCB_STATE_ENCRYPTION_PENDING ) );
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCreateNewFile );
|
|
|
|
if (AcquiredFcbTable) {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
}
|
|
|
|
NtfsUnpinBcb( IrpContext, &FileRecordBcb );
|
|
|
|
if (CleanupAttrContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
|
|
if (DecrementCloseCount) {
|
|
|
|
InterlockedDecrement( &ThisFcb->CloseCount );
|
|
}
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_ACQUIRED_OBJECT_ID_INDEX )) {
|
|
|
|
NtfsReleaseScb( IrpContext, Vcb->ObjectIdTableScb );
|
|
ClearFlag( CreateContext->CreateFlags, CREATE_FLAG_ACQUIRED_OBJECT_ID_INDEX );
|
|
}
|
|
|
|
if (NamePair.Long.Buffer != NamePair.LongBuffer) {
|
|
|
|
NtfsFreePool(NamePair.Long.Buffer);
|
|
}
|
|
|
|
if (SharedSecurity != NULL) {
|
|
ASSERT( ThisFcb == NULL || ThisFcb->SharedSecurity == NULL );
|
|
NtfsAcquireFcbSecurity( Vcb );
|
|
RemoveReferenceSharedSecurityUnsafe( &SharedSecurity );
|
|
NtfsReleaseFcbSecurity( Vcb );
|
|
}
|
|
|
|
//
|
|
// We need to cleanup any changes to the in memory
|
|
// structures if there is an error.
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status ) || AbnormalTermination()) {
|
|
|
|
ASSERT( !(AbnormalTermination()) || IrpContext->ExceptionStatus != STATUS_SUCCESS );
|
|
|
|
if (*IndexContext != NULL) {
|
|
|
|
NtfsCleanupIndexContext( IrpContext, *IndexContext );
|
|
*IndexContext = NULL;
|
|
}
|
|
|
|
NtfsBackoutFailedOpens( IrpContext,
|
|
IrpSp->FileObject,
|
|
ThisFcb,
|
|
CreateContext->ThisScb,
|
|
CreateContext->ThisCcb );
|
|
|
|
//
|
|
// Derefence the quota control block if it was not assigned
|
|
// to the FCB.
|
|
//
|
|
|
|
if (QuotaControl != NULL) {
|
|
NtfsDereferenceQuotaControlBlock( Vcb, &QuotaControl );
|
|
}
|
|
|
|
//
|
|
// Always force the Fcb to reinitialized.
|
|
//
|
|
|
|
if (ThisFcb != NULL) {
|
|
|
|
PLIST_ENTRY Links;
|
|
|
|
ClearFlag( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED );
|
|
|
|
//
|
|
// Mark the Fcb and all Scb's as deleted to force all subsequent
|
|
// operations to fail.
|
|
//
|
|
|
|
SetFlag( ThisFcb->FcbState, FCB_STATE_FILE_DELETED );
|
|
|
|
//
|
|
// We need to mark all of the Scbs as gone.
|
|
//
|
|
|
|
for (Links = ThisFcb->ScbQueue.Flink;
|
|
Links != &ThisFcb->ScbQueue;
|
|
Links = Links->Flink) {
|
|
|
|
Scb = CONTAINING_RECORD( Links, SCB, FcbLinks );
|
|
|
|
Scb->ValidDataToDisk =
|
|
Scb->Header.AllocationSize.QuadPart =
|
|
Scb->Header.FileSize.QuadPart =
|
|
Scb->Header.ValidDataLength.QuadPart = 0;
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
|
|
}
|
|
|
|
//
|
|
// Clear the Scb field so our caller doesn't try to teardown
|
|
// from this point.
|
|
//
|
|
|
|
CreateContext->ThisScb = NULL;
|
|
|
|
//
|
|
// If we created an Fcb then we want to check if we need to
|
|
// unwind any structure allocation. We don't want to remove any
|
|
// structures needed for the coming AbortTransaction. This
|
|
// includes the parent Scb as well as the current Fcb if we
|
|
// logged the ACL creation.
|
|
//
|
|
|
|
//
|
|
// Make sure the parent Fcb doesn't go away. Then
|
|
// start a teardown from the Fcb we just found.
|
|
//
|
|
|
|
InterlockedIncrement( &ParentScb->CleanupCount );
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
ThisFcb,
|
|
NULL,
|
|
LoggedFileRecord,
|
|
0,
|
|
&RemovedFcb );
|
|
|
|
//
|
|
// If the Fcb was removed then both the Fcb and Lcb are gone.
|
|
//
|
|
|
|
if (RemovedFcb) {
|
|
|
|
ThisFcb = NULL;
|
|
ThisLcb = NULL;
|
|
}
|
|
|
|
InterlockedDecrement( &ParentScb->CleanupCount );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the new Fcb is still present then either return it as the
|
|
// deepest Fcb encountered in this open or release it.
|
|
//
|
|
|
|
if (ThisFcb != NULL) {
|
|
|
|
//
|
|
// If the Lcb is present then this is part of the tree. Our
|
|
// caller knows to release it.
|
|
//
|
|
|
|
if (ThisLcb != NULL) {
|
|
|
|
*LcbForTeardown = ThisLcb;
|
|
CreateContext->CurrentFcb = ThisFcb;
|
|
}
|
|
}
|
|
|
|
ASSERT( QuotaControl == NULL );
|
|
DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
PLCB
|
|
NtfsOpenSubdirectory (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB ParentScb,
|
|
IN PFILE_REFERENCE FileReference,
|
|
IN UNICODE_STRING FileName,
|
|
IN UCHAR FileNameFlags,
|
|
IN PCREATE_CONTEXT CreateContext,
|
|
OUT PLCB *LcbForTeardown
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will create an Fcb for an intermediate node on an open path.
|
|
We use the ParentScb and the information in the FileName attribute returned
|
|
from the disk to create the Fcb and create a link between the Scb and Fcb.
|
|
It's possible that the Fcb and Lcb already exist but the 'CreateXcb' calls
|
|
handle that already. This routine does not expect to fail.
|
|
|
|
Arguments:
|
|
|
|
ParentScb - This is the Scb for the parent directory.
|
|
|
|
FileName - This is the name for the entry.
|
|
|
|
CreateFlags - Indicates if this open is using traverse access checking.
|
|
|
|
CreatContext - Context containing current fcb
|
|
|
|
FileReference - FileId of the subdirectory to open
|
|
|
|
FileNameFlags - file name flags of the subdirectory being opened
|
|
|
|
LcbForTeardown - This is the Lcb to use in teardown if we add an Lcb
|
|
into the tree.
|
|
|
|
Return Value:
|
|
|
|
PLCB - Pointer to the Link control block between the Fcb and its parent.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFCB ThisFcb;
|
|
PLCB ThisLcb;
|
|
PFCB LocalFcbForTeardown = NULL;
|
|
|
|
BOOLEAN AcquiredFcbTable = FALSE;
|
|
BOOLEAN ExistingFcb;
|
|
|
|
PVCB Vcb = ParentScb->Vcb;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOpenSubdirectory: Entered\n") );
|
|
DebugTrace( 0, Dbg, ("ParentScb -> %08lx\n") );
|
|
DebugTrace( 0, Dbg, ("IndexEntry -> %08lx\n") );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = TRUE;
|
|
|
|
//
|
|
// The steps here are very simple create the Fcb, remembering if it
|
|
// already existed. We don't update the information in the Fcb as
|
|
// we can't rely on the information in the duplicated information.
|
|
// A subsequent open of this Fcb will need to perform that work.
|
|
//
|
|
|
|
ThisFcb = NtfsCreateFcb( IrpContext,
|
|
ParentScb->Vcb,
|
|
*FileReference,
|
|
FALSE,
|
|
TRUE,
|
|
&ExistingFcb );
|
|
|
|
ThisFcb->ReferenceCount += 1;
|
|
|
|
//
|
|
// If we created this Fcb we must make sure to start teardown
|
|
// on it.
|
|
//
|
|
|
|
if (!ExistingFcb) {
|
|
|
|
LocalFcbForTeardown = ThisFcb;
|
|
|
|
} else {
|
|
|
|
CreateContext->CurrentFcb = ThisFcb;
|
|
*LcbForTeardown = NULL;
|
|
}
|
|
|
|
//
|
|
// Try to do a fast acquire, otherwise we need to release
|
|
// the Fcb table, acquire the Fcb, acquire the Fcb table to
|
|
// dereference Fcb. Just do an acquire for system files under the root i.e $Extend - this will match
|
|
// their canonical order
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_SYSTEM_FILE) &&
|
|
(NtfsSegmentNumber( &ParentScb->Fcb->FileReference ) == ROOT_FILE_NAME_INDEX_NUMBER)) {
|
|
|
|
ASSERT( FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ) );
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, 0 );
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
|
|
} else if (!NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, ACQUIRE_DONT_WAIT )) {
|
|
|
|
ParentScb->Fcb->ReferenceCount += 1;
|
|
InterlockedIncrement( &ParentScb->CleanupCount );
|
|
|
|
//
|
|
// Set the IrpContext to acquire paging io resources if our target
|
|
// has one. This will lock the MappedPageWriter out of this file.
|
|
//
|
|
|
|
if (ThisFcb->PagingIoResource != NULL) {
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
|
|
}
|
|
|
|
//
|
|
// Release the fcb table first because its an end resource and release
|
|
// scb with paging might reacquire a fast mutex if freeing snapshots
|
|
//
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
NtfsReleaseScbWithPaging( IrpContext, ParentScb );
|
|
NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, 0 );
|
|
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
InterlockedDecrement( &ParentScb->CleanupCount );
|
|
ParentScb->Fcb->ReferenceCount -= 1;
|
|
}
|
|
|
|
ThisFcb->ReferenceCount -= 1;
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
|
|
//
|
|
// If this is a directory, it's possible that we hav an existing Fcb
|
|
// in the prefix table which needs to be initialized from the disk.
|
|
// We look in the InfoInitialized flag to know whether to go to
|
|
// disk.
|
|
//
|
|
|
|
ThisLcb = NtfsCreateLcb( IrpContext,
|
|
ParentScb,
|
|
ThisFcb,
|
|
FileName,
|
|
FileNameFlags,
|
|
NULL );
|
|
|
|
LocalFcbForTeardown = NULL;
|
|
|
|
*LcbForTeardown = ThisLcb;
|
|
CreateContext->CurrentFcb = ThisFcb;
|
|
|
|
if (!FlagOn( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
|
|
|
|
NtfsUpdateFcbInfoFromDisk( IrpContext,
|
|
BooleanFlagOn( CreateContext->CreateFlags, CREATE_FLAG_TRAVERSE_CHECK ),
|
|
ThisFcb,
|
|
NULL );
|
|
|
|
NtfsConditionallyFixupQuota( IrpContext, ThisFcb );
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsOpenSubdirectory );
|
|
|
|
if (AcquiredFcbTable) {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
}
|
|
|
|
//
|
|
// If we are to cleanup the Fcb we, look to see if we created it.
|
|
// If we did we can call our teardown routine. Otherwise we
|
|
// leave it alone.
|
|
//
|
|
|
|
if (LocalFcbForTeardown != NULL) {
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
ThisFcb,
|
|
NULL,
|
|
FALSE,
|
|
0,
|
|
NULL );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenSubdirectory: Lcb -> %08lx\n", ThisLcb) );
|
|
}
|
|
|
|
return ThisLcb;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsOpenAttributeInExistingFile (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFCB ThisFcb,
|
|
IN ULONG LastFileNameOffset,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN ULONG CcbFlags,
|
|
IN ULONG CreateFlags,
|
|
IN PVOID NetworkInfo OPTIONAL,
|
|
OUT PSCB *ThisScb,
|
|
OUT PCCB *ThisCcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is the worker routine for opening an attribute on an
|
|
existing file. It will handle volume opens, indexed opens, opening
|
|
or overwriting existing attributes as well as creating new attributes.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
IrpSp - This is the stack location for this open.
|
|
|
|
ThisLcb - This is the Lcb we used to reach this Fcb.
|
|
|
|
ThisFcb - This is the Fcb for the file being opened.
|
|
|
|
LastFileNameOffset - This is the offset in the full path name of the
|
|
final component.
|
|
|
|
AttrName - This is the attribute name in case we need to create
|
|
an Scb.
|
|
|
|
AttrTypeCode - This is the attribute type code to use to create
|
|
the Scb.
|
|
|
|
CcbFlags - This is the flag field for the Ccb.
|
|
|
|
CreateFlags - Indicates if this open is an open by Id.
|
|
|
|
NetworkInfo - If specified then this call is a fast open call to query
|
|
the network information. We don't update any of the in-memory structures
|
|
for this.
|
|
|
|
ThisScb - This is the address to store the Scb from this open.
|
|
|
|
ThisCcb - This is the address to store the Ccb from this open.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of opening this indexed attribute.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG CreateDisposition;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
BOOLEAN FoundAttribute;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOpenAttributeInExistingFile: Entered\n") );
|
|
|
|
//
|
|
// When the Fcb denotes a reparse point, it will be retrieved below by one of
|
|
// NtfsOpenExistingAttr, NtfsOverwriteAttr or this routine prior to calling
|
|
// NtfsOpenNewAttr.
|
|
//
|
|
// We do not retrieve the reparse point here, as we could, because in
|
|
// NtfsOpenExistingAttr and in NtfsOverwriteAttr there are extensive access
|
|
// control checks that need to be preserved. NtfsOpenNewAttr has no access
|
|
// checks.
|
|
//
|
|
|
|
//
|
|
// If the caller is ea blind, let's check the need ea count on the
|
|
// file. We skip this check if he is accessing a named data stream.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_EA_KNOWLEDGE ) &&
|
|
FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
PEA_INFORMATION ThisEaInformation;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT EaInfoAttrContext;
|
|
|
|
NtfsInitializeAttributeContext( &EaInfoAttrContext );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// If we find the Ea information attribute we look in there for
|
|
// Need ea count.
|
|
//
|
|
|
|
if (NtfsLookupAttributeByCode( IrpContext,
|
|
ThisFcb,
|
|
&ThisFcb->FileReference,
|
|
$EA_INFORMATION,
|
|
&EaInfoAttrContext )) {
|
|
|
|
ThisEaInformation = (PEA_INFORMATION) NtfsAttributeValue( NtfsFoundAttribute( &EaInfoAttrContext ));
|
|
|
|
if (ThisEaInformation->NeedEaCount != 0) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &EaInfoAttrContext );
|
|
}
|
|
|
|
if (Status != STATUS_SUCCESS) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenAttributeInExistingFile: Exit - %x\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
CreateDisposition = (IrpSp->Parameters.Create.Options >> 24) & 0x000000ff;
|
|
|
|
//
|
|
// If the result is a directory operation, then we know the attribute
|
|
// must exist.
|
|
//
|
|
|
|
if (AttrTypeCode == $INDEX_ALLOCATION) {
|
|
|
|
//
|
|
// If this is not a file name index then we need to verify that the specified index
|
|
// exists. We need to look for the $INDEX_ROOT attribute though not the
|
|
// $INDEX_ALLOCATION attribute.
|
|
//
|
|
|
|
if ((AttrName.Buffer != NtfsFileNameIndex.Buffer) || FlagOn( ThisFcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
FoundAttribute = NtfsLookupAttributeByName( IrpContext,
|
|
ThisFcb,
|
|
&ThisFcb->FileReference,
|
|
$INDEX_ROOT,
|
|
&AttrName,
|
|
NULL,
|
|
(BOOLEAN) !BooleanFlagOn( IrpSp->Flags, SL_CASE_SENSITIVE ),
|
|
&AttrContext );
|
|
} finally {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
|
|
//
|
|
// If we didn't find the name then we want to fail the request.
|
|
//
|
|
|
|
if (!FoundAttribute) {
|
|
|
|
if ((CreateDisposition == FILE_OPEN) || (CreateDisposition == FILE_OVERWRITE)) {
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
} else {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenAttributeInExistingFile: Exit - %x\n", Status) );
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check the create disposition.
|
|
//
|
|
|
|
if ((CreateDisposition != FILE_OPEN) && (CreateDisposition != FILE_OPEN_IF)) {
|
|
|
|
Status = (ThisLcb == ThisFcb->Vcb->RootLcb
|
|
? STATUS_ACCESS_DENIED
|
|
: STATUS_OBJECT_NAME_COLLISION);
|
|
|
|
} else {
|
|
|
|
Status = NtfsOpenExistingAttr( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
LastFileNameOffset,
|
|
AttrName,
|
|
$INDEX_ALLOCATION,
|
|
CcbFlags,
|
|
CreateFlags,
|
|
TRUE,
|
|
NetworkInfo,
|
|
ThisScb,
|
|
ThisCcb );
|
|
|
|
//
|
|
// The IsEncrypted test below is meaningless for an uninitialized Fcb.
|
|
//
|
|
|
|
ASSERT( FlagOn( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED ) );
|
|
|
|
if ((Status == STATUS_SUCCESS) &&
|
|
ARGUMENT_PRESENT( NetworkInfo ) &&
|
|
IsEncrypted( &ThisFcb->Info )) {
|
|
|
|
//
|
|
// We need to initialize the Scb now, otherwise we won't have set the
|
|
// encryption bit in the index Scb's attribute flags, and we will not
|
|
// return the right file attributes to the network opener.
|
|
//
|
|
|
|
if ((*ThisScb)->ScbType.Index.BytesPerIndexBuffer == 0) {
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
if (NtfsLookupAttributeByCode( IrpContext,
|
|
ThisFcb,
|
|
&ThisFcb->FileReference,
|
|
$INDEX_ROOT,
|
|
&AttrContext )) {
|
|
|
|
NtfsUpdateIndexScbFromAttribute( IrpContext,
|
|
*ThisScb,
|
|
NtfsFoundAttribute( &AttrContext ),
|
|
FALSE );
|
|
|
|
} else {
|
|
|
|
Status = STATUS_FILE_CORRUPT_ERROR;
|
|
}
|
|
|
|
} finally {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
|
|
if (Status != STATUS_SUCCESS) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenAttributeInExistingFile: Exit - %x\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// If it exists, we first check if the caller wanted to open that attribute.
|
|
// If the open is for a system file then look for that attribute explicitly.
|
|
//
|
|
|
|
if ((AttrName.Length == 0) &&
|
|
(AttrTypeCode == $DATA) &&
|
|
!FlagOn( ThisFcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
FoundAttribute = TRUE;
|
|
|
|
//
|
|
// Otherwise we see if the attribute exists.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Check that we own the paging io resource. If we are creating the stream and
|
|
// need to break up the allocation then we must own the paging IO resource.
|
|
//
|
|
|
|
ASSERT( !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING ) ||
|
|
(IrpContext->CleanupStructure != NULL) ||
|
|
(ThisFcb->PagingIoResource == NULL) ||
|
|
(ThisFcb == ThisFcb->Vcb->RootIndexScb->Fcb) );
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
FoundAttribute = NtfsLookupAttributeByName( IrpContext,
|
|
ThisFcb,
|
|
&ThisFcb->FileReference,
|
|
AttrTypeCode,
|
|
&AttrName,
|
|
NULL,
|
|
(BOOLEAN) !BooleanFlagOn( IrpSp->Flags, SL_CASE_SENSITIVE ),
|
|
&AttrContext );
|
|
|
|
if (FoundAttribute && (AttrTypeCode == $DATA)) {
|
|
|
|
//
|
|
// If there is an attribute name, we will copy the case of the name
|
|
// to the input attribute name for data streams. For others the storage is common read-only regions.
|
|
//
|
|
|
|
PATTRIBUTE_RECORD_HEADER DataAttribute;
|
|
|
|
DataAttribute = NtfsFoundAttribute( &AttrContext );
|
|
|
|
RtlCopyMemory( AttrName.Buffer,
|
|
Add2Ptr( DataAttribute, DataAttribute->NameOffset ),
|
|
AttrName.Length );
|
|
}
|
|
|
|
} finally {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
}
|
|
|
|
if (FoundAttribute) {
|
|
|
|
//
|
|
// In this case we call our routine to open this attribute.
|
|
//
|
|
|
|
if ((CreateDisposition == FILE_OPEN) ||
|
|
(CreateDisposition == FILE_OPEN_IF)) {
|
|
|
|
Status = NtfsOpenExistingAttr( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
LastFileNameOffset,
|
|
AttrName,
|
|
AttrTypeCode,
|
|
CcbFlags,
|
|
CreateFlags,
|
|
FALSE,
|
|
NetworkInfo,
|
|
ThisScb,
|
|
ThisCcb );
|
|
|
|
if ((Status != STATUS_PENDING) &&
|
|
(Status != STATUS_WAIT_FOR_OPLOCK) &&
|
|
(*ThisScb != NULL)) {
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB );
|
|
}
|
|
|
|
//
|
|
// If he wanted to overwrite this attribute, we call our overwrite routine.
|
|
//
|
|
|
|
} else if ((CreateDisposition == FILE_SUPERSEDE) ||
|
|
(CreateDisposition == FILE_OVERWRITE) ||
|
|
(CreateDisposition == FILE_OVERWRITE_IF)) {
|
|
|
|
if (!NtfsIsVolumeReadOnly( IrpContext->Vcb )) {
|
|
|
|
//
|
|
// Check if mm will allow us to modify this file.
|
|
//
|
|
|
|
Status = NtfsOverwriteAttr( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
(BOOLEAN) (CreateDisposition == FILE_SUPERSEDE),
|
|
LastFileNameOffset,
|
|
AttrName,
|
|
AttrTypeCode,
|
|
CcbFlags,
|
|
CreateFlags,
|
|
ThisScb,
|
|
ThisCcb );
|
|
|
|
//
|
|
// Remember that this Scb was modified.
|
|
//
|
|
|
|
if ((Status != STATUS_PENDING) &&
|
|
(Status != STATUS_WAIT_FOR_OPLOCK) &&
|
|
(*ThisScb != NULL)) {
|
|
|
|
SetFlag( IrpSp->FileObject->Flags, FO_FILE_MODIFIED );
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB );
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// We can't do any overwrite/supersede on R/O media.
|
|
//
|
|
|
|
Status = STATUS_MEDIA_WRITE_PROTECTED;
|
|
}
|
|
|
|
//
|
|
// Otherwise he is trying to create the attribute.
|
|
//
|
|
|
|
} else {
|
|
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
}
|
|
|
|
//
|
|
// The attribute doesn't exist. If the user expected it to exist, we fail.
|
|
// Otherwise we call our routine to create an attribute.
|
|
//
|
|
|
|
} else if ((CreateDisposition == FILE_OPEN) ||
|
|
(CreateDisposition == FILE_OVERWRITE)) {
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Perform the open check for this existing file.
|
|
//
|
|
|
|
Status = NtfsCheckExistingFile( IrpContext,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
(AttrTypeCode == $INDEX_ALLOCATION),
|
|
CcbFlags );
|
|
|
|
//
|
|
// End-of-name call to retrieve a reparse point.
|
|
// As NtfsOpenNewAttr has not access checks, we see whether we need to
|
|
// retrieve the reparse point here, prior to calling NtfsOpenNewAttr.
|
|
// The file information in ThisFcb tells whether this is a reparse point.
|
|
//
|
|
// If we have succeded in the previous check and we do not have
|
|
// FILE_OPEN_REPARSE_POINT set, we retrieve the reparse point.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status ) &&
|
|
FlagOn( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT ) &&
|
|
!FlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_REPARSE_POINT )) {
|
|
|
|
USHORT AttributeNameLength = 0;
|
|
|
|
//
|
|
// We exclude the case when we get the $I30 name and $INDEX_ALLOCATION type
|
|
// as this is the standard manner of opening a directory.
|
|
//
|
|
|
|
if (!((AttrName.Length == NtfsFileNameIndex.Length) &&
|
|
(AttrTypeCode == $INDEX_ALLOCATION) &&
|
|
(RtlEqualMemory( AttrName.Buffer, NtfsFileNameIndex.Buffer, AttrName.Length )))) {
|
|
|
|
if (AttrName.Length > 0) {
|
|
ASSERT( AttrName.Length == ((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeNameLength );
|
|
AttributeNameLength += AttrName.Length + 2;
|
|
}
|
|
if (((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeCodeNameLength > 0) {
|
|
AttributeNameLength += ((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeCodeNameLength + 2;
|
|
}
|
|
}
|
|
DebugTrace( 0, Dbg, ("AttrTypeCode %x AttrName.Length (1) = %d AttributeCodeNameLength %d LastFileNameOffset %d\n",
|
|
AttrTypeCode, AttrName.Length, ((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeCodeNameLength, LastFileNameOffset) );
|
|
|
|
Status = NtfsGetReparsePointValue( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisFcb,
|
|
AttributeNameLength );
|
|
}
|
|
|
|
//
|
|
// If this didn't fail and we did not encounter a reparse point,
|
|
// then attempt to create the stream.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status ) &&
|
|
(Status != STATUS_REPARSE)) {
|
|
|
|
//
|
|
// Don't allow this operation on a system file (except the root directory which can have user data streams)
|
|
// or for anything other than user data streams
|
|
//
|
|
|
|
if ((FlagOn( ThisFcb->FcbState, FCB_STATE_SYSTEM_FILE ) &&
|
|
(NtfsSegmentNumber( &ThisFcb->FileReference ) != ROOT_FILE_NAME_INDEX_NUMBER)) ||
|
|
(!NtfsIsTypeCodeUserData( AttrTypeCode ))) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
|
|
} else if (!NtfsIsVolumeReadOnly( IrpContext->Vcb )) {
|
|
|
|
NtfsPostUsnChange( IrpContext, ThisFcb, USN_REASON_STREAM_CHANGE );
|
|
Status = NtfsOpenNewAttr( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
LastFileNameOffset,
|
|
AttrName,
|
|
AttrTypeCode,
|
|
FALSE,
|
|
CcbFlags,
|
|
TRUE,
|
|
CreateFlags,
|
|
ThisScb,
|
|
ThisCcb );
|
|
} else {
|
|
|
|
Status = STATUS_MEDIA_WRITE_PROTECTED;
|
|
|
|
}
|
|
}
|
|
|
|
if (*ThisScb != NULL) {
|
|
|
|
if (*ThisCcb != NULL) {
|
|
|
|
SetFlag( (*ThisCcb)->Flags,
|
|
CCB_FLAG_UPDATE_LAST_CHANGE | CCB_FLAG_SET_ARCHIVE );
|
|
}
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB );
|
|
}
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenAttributeInExistingFile: Exit - %x\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsOpenExistingAttr (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFCB ThisFcb,
|
|
IN ULONG LastFileNameOffset,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN ULONG CcbFlags,
|
|
IN ULONG CreateFlags,
|
|
IN BOOLEAN DirectoryOpen,
|
|
IN PVOID NetworkInfo OPTIONAL,
|
|
OUT PSCB *ThisScb,
|
|
OUT PCCB *ThisCcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to open an existing attribute. We check the
|
|
requested file access, the existance of
|
|
an Ea buffer and the security on this file. If these succeed then
|
|
we check the batch oplocks and regular oplocks on the file.
|
|
We also verify whether we need to retrieve a reparse point or not.
|
|
If we have gotten this far, we simply call our routine to open the
|
|
attribute.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
IrpSp - This is the Irp stack pointer for the filesystem.
|
|
|
|
ThisLcb - This is the Lcb used to reach this Fcb.
|
|
|
|
ThisFcb - This is the Fcb to open.
|
|
|
|
LastFileNameOffset - This is the offset in the full path name of the
|
|
final component.
|
|
|
|
AttrName - This is the attribute name in case we need to create
|
|
an Scb.
|
|
|
|
AttrTypeCode - This is the attribute type code to use to create
|
|
the Scb.
|
|
|
|
CcbFlags - This is the flag field for the Ccb.
|
|
|
|
CreateFlags - Indicates if this open is by file Id.
|
|
|
|
DirectoryOpen - Indicates whether this open is a directory open or a data stream.
|
|
|
|
NetworkInfo - If specified then this call is a fast open call to query
|
|
the network information. We don't update any of the in-memory structures
|
|
for this.
|
|
|
|
ThisScb - This is the address to store the address of the Scb.
|
|
|
|
ThisCcb - This is the address to store the address of the Ccb.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of opening this indexed attribute.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
NTSTATUS OplockStatus;
|
|
|
|
SHARE_MODIFICATION_TYPE ShareModificationType;
|
|
TYPE_OF_OPEN TypeOfOpen;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOpenExistingAttr: Entered\n") );
|
|
|
|
//
|
|
// For data streams we need to do a check that includes an oplock check.
|
|
// For directories we just need to figure the share modification type.
|
|
//
|
|
// We also figure the type of open and the node type code based on the
|
|
// directory flag.
|
|
//
|
|
|
|
if (DirectoryOpen) {
|
|
|
|
//
|
|
// Check for valid access on an existing file.
|
|
//
|
|
|
|
Status = NtfsCheckExistingFile( IrpContext,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
(AttrTypeCode == $INDEX_ALLOCATION),
|
|
CcbFlags );
|
|
|
|
ShareModificationType = (ThisFcb->CleanupCount == 0 ? SetShareAccess : CheckShareAccess);
|
|
TypeOfOpen = UserDirectoryOpen;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Don't break the batch oplock if opening to query the network info.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT( NetworkInfo )) {
|
|
|
|
Status = NtfsBreakBatchOplock( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisFcb,
|
|
AttrName,
|
|
AttrTypeCode,
|
|
ThisScb );
|
|
|
|
if ((Status != STATUS_PENDING) && (Status != STATUS_WAIT_FOR_OPLOCK)) {
|
|
|
|
if (NT_SUCCESS( Status = NtfsCheckExistingFile( IrpContext,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
(AttrTypeCode == $INDEX_ALLOCATION),
|
|
CcbFlags ))) {
|
|
|
|
Status = NtfsOpenAttributeCheck( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisScb,
|
|
&ShareModificationType );
|
|
|
|
TypeOfOpen = UserFileOpen ;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We want to perform the ACL check but not break any oplocks for the
|
|
// NetworkInformation query.
|
|
//
|
|
|
|
} else {
|
|
|
|
Status = NtfsCheckExistingFile( IrpContext,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
(AttrTypeCode == $INDEX_ALLOCATION),
|
|
CcbFlags );
|
|
|
|
TypeOfOpen = UserFileOpen;
|
|
|
|
ASSERT( NtfsIsTypeCodeUserData( AttrTypeCode ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// End-of-name call to retrieve a reparse point.
|
|
// The file information in ThisFcb tells whether this is a reparse point.
|
|
//
|
|
// In three cases we proceed with the normal open for the file:
|
|
//
|
|
// (1) When FILE_OPEN_REPARSE_POINT is set, as the caller wants a handle on the
|
|
// reparse point itself.
|
|
// (2) When we are retrieving the NetworkInfo, as then the caller can identify
|
|
// the reparse points and decide what to do, without having the need of apriori
|
|
// knowledge of where they are in the system.
|
|
// Note: when we retrieve NetworkInfo we can have FILE_OPEN_REPARSE_POINT set.
|
|
// (3) The data manipulation aspect of the DesiredAccess for this request was, exactly,
|
|
// FILE_READ_ATTRIBUTES, in which case we give a handle to the local entity.
|
|
//
|
|
// Otherwise, we retrieve the value of the $REPARSE_POINT attribute.
|
|
//
|
|
// Note: The logic in the if was re-arranged for performance. It used to read:
|
|
//
|
|
// NT_SUCCESS( Status ) &&
|
|
// (Status != STATUS_PENDING) &&
|
|
// !ARGUMENT_PRESENT( NetworkInfo ) &&
|
|
// FlagOn( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT ) &&
|
|
// !FlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_REPARSE_POINT )
|
|
//
|
|
|
|
if ((Status != STATUS_PENDING) &&
|
|
(Status != STATUS_WAIT_FOR_OPLOCK) &&
|
|
FlagOn( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT ) &&
|
|
NT_SUCCESS( Status ) &&
|
|
!FlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_REPARSE_POINT )) {
|
|
|
|
USHORT AttributeNameLength = 0;
|
|
|
|
//
|
|
// We exclude the case when we get the $I30 name and $INDEX_ALLOCATION type
|
|
// as this is the standard manner of opening a directory.
|
|
//
|
|
|
|
if (!((AttrName.Length == NtfsFileNameIndex.Length) &&
|
|
(AttrTypeCode == $INDEX_ALLOCATION) &&
|
|
(RtlEqualMemory( AttrName.Buffer, NtfsFileNameIndex.Buffer, AttrName.Length )))) {
|
|
|
|
if (AttrName.Length > 0) {
|
|
ASSERT( AttrName.Length == ((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeNameLength );
|
|
AttributeNameLength += AttrName.Length + 2;
|
|
}
|
|
if (((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeCodeNameLength > 0) {
|
|
AttributeNameLength += ((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeCodeNameLength + 2;
|
|
}
|
|
}
|
|
DebugTrace( 0, Dbg, ("AttrTypeCode %x AttrName.Length (2) = %d AttributeCodeNameLength %d LastFileNameOffset %d\n",
|
|
AttrTypeCode, AttrName.Length, ((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeCodeNameLength, LastFileNameOffset) );
|
|
|
|
Status = NtfsGetReparsePointValue( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisFcb,
|
|
AttributeNameLength );
|
|
}
|
|
|
|
//
|
|
// If we didn't post the Irp and we did not retrieve a reparse point
|
|
// and the operations above were successful, we proceed with the open.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status ) &&
|
|
(Status != STATUS_PENDING) &&
|
|
(Status != STATUS_WAIT_FOR_OPLOCK) &&
|
|
(Status != STATUS_REPARSE)) {
|
|
|
|
//
|
|
// Now actually open the attribute.
|
|
//
|
|
|
|
OplockStatus = Status;
|
|
|
|
Status = NtfsOpenAttribute( IrpContext,
|
|
IrpSp,
|
|
ThisFcb->Vcb,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
LastFileNameOffset,
|
|
AttrName,
|
|
AttrTypeCode,
|
|
ShareModificationType,
|
|
TypeOfOpen,
|
|
FALSE,
|
|
(FlagOn( CreateFlags, CREATE_FLAG_OPEN_BY_ID )
|
|
? CcbFlags | CCB_FLAG_OPEN_BY_FILE_ID
|
|
: CcbFlags),
|
|
NetworkInfo,
|
|
ThisScb,
|
|
ThisCcb );
|
|
|
|
//
|
|
// If there are no errors at this point, we set the caller's Iosb.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// We need to remember if the oplock break is in progress.
|
|
//
|
|
|
|
Status = OplockStatus;
|
|
Irp->IoStatus.Information = FILE_OPENED;
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenExistingAttr: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsOverwriteAttr (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFCB ThisFcb,
|
|
IN BOOLEAN Supersede,
|
|
IN ULONG LastFileNameOffset,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN ULONG CcbFlags,
|
|
IN ULONG CreateFlags,
|
|
OUT PSCB *ThisScb,
|
|
OUT PCCB *ThisCcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to overwrite an existing attribute. We do all of
|
|
the same work as opening an attribute except that we can change the
|
|
allocation of a file. This routine will handle the case where a
|
|
file is being overwritten and the case where just an attribute is
|
|
being overwritten. In the case of the former, we may change the
|
|
file attributes of the file as well as modify the Ea's on the file.
|
|
After doing all the access checks, we also verify whether we need to
|
|
retrieve a reparse point or not.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
IrpSp - This is the stack location for this open.
|
|
|
|
ThisLcb - This is the Lcb we used to reach this Fcb.
|
|
|
|
ThisFcb - This is the Fcb for the file being opened.
|
|
|
|
Supersede - This indicates whether this is a supersede or overwrite
|
|
operation.
|
|
|
|
LastFileNameOffset - This is the offset in the full path name of the
|
|
final component.
|
|
|
|
AttrName - This is the attribute name in case we need to create
|
|
an Scb.
|
|
|
|
AttrTypeCode - This is the attribute type code to use to create
|
|
the Scb.
|
|
|
|
CcbFlags - This is the flag field for the Ccb.
|
|
|
|
CreateFlags - Indicates if this open is by file Id.
|
|
|
|
ThisScb - This is the address to store the address of the Scb.
|
|
|
|
ThisCcb - This is the address to store the address of the Ccb.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of opening this indexed attribute.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
NTSTATUS OplockStatus;
|
|
|
|
ULONG FileAttributes;
|
|
ULONG PreviousFileAttributes;
|
|
PACCESS_MASK DesiredAccess;
|
|
ACCESS_MASK AddedAccess = 0;
|
|
BOOLEAN MaximumRequested = FALSE;
|
|
|
|
SHARE_MODIFICATION_TYPE ShareModificationType;
|
|
|
|
PFILE_FULL_EA_INFORMATION FullEa = NULL;
|
|
ULONG FullEaLength = 0;
|
|
|
|
ULONG IncomingFileAttributes = 0; // invalid value
|
|
ULONG IncomingReparsePointTag = IO_REPARSE_TAG_RESERVED_ZERO; // invalid value
|
|
|
|
BOOLEAN DecrementScbCloseCount = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOverwriteAttr: Entered\n") );
|
|
|
|
DesiredAccess = &IrpSp->Parameters.Create.SecurityContext->DesiredAccess;
|
|
|
|
if (FlagOn( *DesiredAccess, MAXIMUM_ALLOWED )) {
|
|
|
|
MaximumRequested = TRUE;
|
|
}
|
|
|
|
//
|
|
// Check the oplock state of this file.
|
|
//
|
|
|
|
Status = NtfsBreakBatchOplock( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisFcb,
|
|
AttrName,
|
|
AttrTypeCode,
|
|
ThisScb );
|
|
|
|
if ((Status == STATUS_WAIT_FOR_OPLOCK) || (Status == STATUS_PENDING)) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Remember the value of the file attribute flags and of the reparse point.
|
|
// If we succeed in NtfsRemoveReparsePoint but fail afterwards, we leave the duplicate
|
|
// information in an inconsistent state.
|
|
//
|
|
|
|
IncomingFileAttributes = ThisFcb->Info.FileAttributes;
|
|
IncomingReparsePointTag = ThisFcb->Info.ReparsePointTag;
|
|
|
|
//
|
|
// We first want to check that the caller's desired access and specified
|
|
// file attributes are compatible with the state of the file. There
|
|
// are the two overwrite cases to consider.
|
|
//
|
|
// OverwriteFile - The hidden and system bits passed in by the
|
|
// caller must match the current values.
|
|
//
|
|
// OverwriteAttribute - We also modify the requested desired access
|
|
// to explicitly add the implicit access needed by overwrite.
|
|
//
|
|
// We also check that for the overwrite attribute case, there isn't
|
|
// an Ea buffer specified.
|
|
//
|
|
|
|
if (FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
BOOLEAN Hidden;
|
|
BOOLEAN System;
|
|
|
|
//
|
|
// Get the file attributes and clear any unsupported bits.
|
|
//
|
|
|
|
FileAttributes = (ULONG) IrpSp->Parameters.Create.FileAttributes;
|
|
|
|
//
|
|
// Always set the archive bit in this operation.
|
|
//
|
|
|
|
SetFlag( FileAttributes, FILE_ATTRIBUTE_ARCHIVE );
|
|
ClearFlag( FileAttributes,
|
|
~FILE_ATTRIBUTE_VALID_SET_FLAGS | FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED );
|
|
|
|
if (IsEncrypted( &ThisFcb->Info )) {
|
|
|
|
SetFlag( FileAttributes, FILE_ATTRIBUTE_ENCRYPTED );
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("Checking hidden/system for overwrite/supersede\n") );
|
|
|
|
Hidden = BooleanIsHidden( &ThisFcb->Info );
|
|
System = BooleanIsSystem( &ThisFcb->Info );
|
|
|
|
if ((Hidden && !FlagOn(FileAttributes, FILE_ATTRIBUTE_HIDDEN)
|
|
||
|
|
System && !FlagOn(FileAttributes, FILE_ATTRIBUTE_SYSTEM))
|
|
|
|
&&
|
|
|
|
!FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )) {
|
|
|
|
DebugTrace( 0, Dbg, ("The hidden and/or system bits do not match\n") );
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// If the user specified an Ea buffer and they are Ea blind, we deny
|
|
// access.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_EA_KNOWLEDGE ) &&
|
|
(Irp->AssociatedIrp.SystemBuffer != NULL)) {
|
|
|
|
DebugTrace( 0, Dbg, ("This opener cannot create Ea's\n") );
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Add in the extra required access bits if we don't have restore privilege
|
|
// which would automatically grant them to us
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
|
|
!FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->Flags, TOKEN_HAS_RESTORE_PRIVILEGE )) {
|
|
|
|
SetFlag( AddedAccess,
|
|
(FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES) & ~(*DesiredAccess) );
|
|
|
|
SetFlag( *DesiredAccess, FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES );
|
|
}
|
|
|
|
} else if (Irp->AssociatedIrp.SystemBuffer != NULL) {
|
|
|
|
DebugTrace( 0, Dbg, ("Can't specifiy an Ea buffer on an attribute overwrite\n") );
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Supersede or overwrite require specific access. We skip this step if we have the restore privilege
|
|
// which already grants these to us
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
|
|
!FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->Flags, TOKEN_HAS_RESTORE_PRIVILEGE )) {
|
|
|
|
ULONG NewAccess = FILE_WRITE_DATA;
|
|
|
|
if (Supersede) {
|
|
|
|
NewAccess = DELETE;
|
|
}
|
|
|
|
//
|
|
// Check if the user already has this new access.
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
|
|
NewAccess )) {
|
|
|
|
SetFlag( AddedAccess,
|
|
NewAccess & ~(*DesiredAccess) );
|
|
|
|
SetFlag( *DesiredAccess, NewAccess );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check whether we can open this existing file.
|
|
//
|
|
|
|
Status = NtfsCheckExistingFile( IrpContext,
|
|
IrpSp,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
(AttrTypeCode == $INDEX_ALLOCATION),
|
|
CcbFlags );
|
|
|
|
//
|
|
// If we have a success status then proceed with the oplock check and
|
|
// open the attribute.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
Status = NtfsOpenAttributeCheck( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisScb,
|
|
&ShareModificationType );
|
|
|
|
//
|
|
// End-of-name call to retrieve a reparse point.
|
|
// The file information in ThisFcb tells whether this is a reparse point.
|
|
//
|
|
// If we didn't post the Irp and the check operation was successful, and
|
|
// we do not have FILE_OPEN_REPARSE_POINT set, we retrieve the reparse point.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status ) &&
|
|
(Status != STATUS_PENDING) &&
|
|
(Status != STATUS_WAIT_FOR_OPLOCK)) {
|
|
|
|
//
|
|
// If we can't truncate the file size then return now. Since
|
|
// NtfsRemoveDataAttributes will be truncating all the data
|
|
// streams for this file, we need to loop through any existing
|
|
// scbs we have to make sure they are all truncatable.
|
|
//
|
|
|
|
PSCB Scb = NULL;
|
|
|
|
//
|
|
// We need to reset the share access once we open the file. This is because
|
|
// we may have added WRITE or DELETE access into the granted bits and
|
|
// they may be reflected in the file object. We don't want them
|
|
// present after the create.
|
|
//
|
|
|
|
if (ShareModificationType == UpdateShareAccess) {
|
|
|
|
ShareModificationType = RecheckShareAccess;
|
|
}
|
|
|
|
//
|
|
// If we biased the desired access we need to remove the same
|
|
// bits from the granted access. If maximum allowed was
|
|
// requested then we can skip this.
|
|
//
|
|
|
|
if (!MaximumRequested) {
|
|
|
|
ClearFlag( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
|
|
AddedAccess );
|
|
}
|
|
|
|
//
|
|
// Also remove the bits from the desired access field so we won't
|
|
// see them if this request gets posted for any reason.
|
|
//
|
|
|
|
ClearFlag( *DesiredAccess, AddedAccess );
|
|
|
|
if (FlagOn( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT ) &&
|
|
!FlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_REPARSE_POINT )) {
|
|
|
|
USHORT AttributeNameLength = 0;
|
|
|
|
//
|
|
// We exclude the case when we get the $I30 name and $INDEX_ALLOCATION type
|
|
// as this is the standard manner of opening a directory.
|
|
//
|
|
|
|
if (!((AttrName.Length == NtfsFileNameIndex.Length) &&
|
|
(AttrTypeCode == $INDEX_ALLOCATION) &&
|
|
(RtlEqualMemory( AttrName.Buffer, NtfsFileNameIndex.Buffer, AttrName.Length )))) {
|
|
|
|
if (AttrName.Length > 0) {
|
|
ASSERT( AttrName.Length == ((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeNameLength );
|
|
AttributeNameLength += AttrName.Length + 2;
|
|
}
|
|
if (((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeCodeNameLength > 0) {
|
|
AttributeNameLength += ((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeCodeNameLength + 2;
|
|
}
|
|
}
|
|
DebugTrace( 0, Dbg, ("AttrTypeCode %x AttrName.Length (3) = %d AttributeCodeNameLength %d LastFileNameOffset %d\n",
|
|
AttrTypeCode, AttrName.Length, ((POPLOCK_CLEANUP)(IrpContext->Union.OplockCleanup))->AttributeCodeNameLength, LastFileNameOffset) );
|
|
|
|
Status = NtfsGetReparsePointValue( IrpContext,
|
|
Irp,
|
|
IrpSp,
|
|
ThisFcb,
|
|
AttributeNameLength );
|
|
|
|
//
|
|
// Exit if we failed or this is a reparse point.
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status ) || (Status == STATUS_REPARSE)) {
|
|
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Reference the Fcb so it doesn't go away.
|
|
//
|
|
|
|
InterlockedIncrement( &ThisFcb->CloseCount );
|
|
|
|
//
|
|
// Use a try-finally to restore the close count correctly.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Make sure the current Scb doesn't get deallocated in the test below.
|
|
//
|
|
|
|
if (*ThisScb != NULL) {
|
|
|
|
InterlockedIncrement( &(*ThisScb)->CloseCount );
|
|
DecrementScbCloseCount = TRUE;
|
|
}
|
|
|
|
while (TRUE) {
|
|
|
|
Scb = NtfsGetNextChildScb( ThisFcb, Scb );
|
|
|
|
if (Scb == NULL) { break; }
|
|
|
|
InterlockedIncrement( &Scb->CloseCount );
|
|
if (!MmCanFileBeTruncated( &(Scb)->NonpagedScb->SegmentObject,
|
|
&Li0 )) {
|
|
|
|
Status = STATUS_USER_MAPPED_FILE;
|
|
DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
|
|
|
|
//
|
|
// The Scb close count will get decremented when we test
|
|
// for Scb != NULL below.
|
|
//
|
|
|
|
try_return( Status );
|
|
}
|
|
InterlockedDecrement( &Scb->CloseCount );
|
|
}
|
|
|
|
//
|
|
// Remember the status from the oplock check.
|
|
//
|
|
|
|
OplockStatus = Status;
|
|
|
|
//
|
|
// We perform the on-disk changes. For a file overwrite, this includes
|
|
// the Ea changes and modifying the file attributes. For an attribute,
|
|
// this refers to modifying the allocation size. We need to keep the
|
|
// Fcb updated and remember which values we changed.
|
|
//
|
|
|
|
if (Irp->AssociatedIrp.SystemBuffer != NULL) {
|
|
|
|
//
|
|
// Remember the values in the Irp.
|
|
//
|
|
|
|
FullEa = (PFILE_FULL_EA_INFORMATION) Irp->AssociatedIrp.SystemBuffer;
|
|
FullEaLength = IrpSp->Parameters.Create.EaLength;
|
|
}
|
|
|
|
//
|
|
// Now do the file attributes and either remove or mark for
|
|
// delete all of the other $DATA attributes on the file.
|
|
//
|
|
|
|
if (FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
//
|
|
// When appropriate, delete the reparse point attribute.
|
|
// This needs to be done prior to any modification to the Fcb, as we use
|
|
// the value of the reparse point tag stored in ThisFcb.Info
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT )) {
|
|
|
|
//
|
|
// Verify that the volume is of the appropriate kind.
|
|
// Otherwise access a non-existing index.
|
|
//
|
|
|
|
if (!NtfsVolumeVersionCheck( ThisFcb->Vcb, NTFS_REPARSE_POINT_VERSION )) {
|
|
|
|
//
|
|
// Return a volume not upgraded error.
|
|
//
|
|
|
|
Status = STATUS_VOLUME_NOT_UPGRADED;
|
|
DebugTrace( 0, Dbg, ("Trying to delete a reparse point in a back-level volume.\n") );
|
|
DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
//
|
|
// Remove the reparse point attribute.
|
|
//
|
|
|
|
NtfsRemoveReparsePoint( IrpContext,
|
|
ThisFcb );
|
|
|
|
//
|
|
// NtfsRemoveReparsPoint will commit if it removes the reparse point. Update our
|
|
// captured info values if there is no transaction.
|
|
//
|
|
|
|
if (IrpContext->TransactionId == 0) {
|
|
|
|
IncomingFileAttributes = ThisFcb->Info.FileAttributes;
|
|
IncomingReparsePointTag = ThisFcb->Info.ReparsePointTag;
|
|
}
|
|
}
|
|
|
|
//
|
|
// This needs to happen after we delete the reparse point attribute to not
|
|
// alter the value of the reparse point tag stored in ThisFcb.Info
|
|
// Replace the current Ea's on the file. This operation will update
|
|
// the Fcb for the file.
|
|
//
|
|
|
|
NtfsAddEa( IrpContext,
|
|
ThisFcb->Vcb,
|
|
ThisFcb,
|
|
FullEa,
|
|
FullEaLength,
|
|
&Irp->IoStatus );
|
|
|
|
//
|
|
// Copy the directory bit from the current Info structure.
|
|
//
|
|
|
|
if (IsDirectory( &ThisFcb->Info)) {
|
|
|
|
SetFlag( FileAttributes, DUP_FILE_NAME_INDEX_PRESENT );
|
|
}
|
|
|
|
//
|
|
// Copy the view index bit from the current Info structure.
|
|
//
|
|
|
|
if (IsViewIndex( &ThisFcb->Info)) {
|
|
|
|
SetFlag( FileAttributes, DUP_VIEW_INDEX_PRESENT );
|
|
}
|
|
|
|
//
|
|
// Remember the previous file attribute to capture the
|
|
// state of the CONTENT_INDEX flag.
|
|
//
|
|
|
|
PreviousFileAttributes = ThisFcb->Info.FileAttributes;
|
|
|
|
//
|
|
// Now either add to the current attributes or replace them.
|
|
//
|
|
|
|
if (Supersede) {
|
|
|
|
ThisFcb->Info.FileAttributes = FileAttributes;
|
|
|
|
} else {
|
|
|
|
ThisFcb->Info.FileAttributes |= FileAttributes;
|
|
}
|
|
|
|
//
|
|
// Get rid of any named $DATA attributes in the file.
|
|
//
|
|
|
|
NtfsRemoveDataAttributes( IrpContext,
|
|
ThisFcb,
|
|
ThisLcb,
|
|
IrpSp->FileObject,
|
|
LastFileNameOffset,
|
|
CreateFlags );
|
|
|
|
//
|
|
// Check if the CONTENT_INDEX bit changed.
|
|
//
|
|
|
|
ASSERT( *ThisScb != NULL );
|
|
|
|
if (FlagOn( PreviousFileAttributes ^ ThisFcb->Info.FileAttributes,
|
|
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED )) {
|
|
|
|
NtfsPostUsnChange( IrpContext, *ThisScb, USN_REASON_INDEXABLE_CHANGE );
|
|
}
|
|
}
|
|
// **** CONSIDER SETTING SCB ENCRYPTED FLAG HERE??? ****
|
|
//
|
|
// Now we perform the operation of opening the attribute.
|
|
//
|
|
|
|
NtfsReplaceAttribute( IrpContext,
|
|
IrpSp,
|
|
ThisFcb,
|
|
*ThisScb,
|
|
ThisLcb,
|
|
*(PLONGLONG)&Irp->Overlay.AllocationSize );
|
|
|
|
NtfsPostUsnChange( IrpContext, *ThisScb, USN_REASON_DATA_TRUNCATION );
|
|
|
|
//
|
|
// If we are overwriting a fle and the user doesn't want it marked as
|
|
// compressed, then change the attribute flag.
|
|
// If we are overwriting a file and its previous state was sparse
|
|
// then also clear the sparse flag.
|
|
//
|
|
|
|
if (FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
if (!FlagOn( (*ThisScb)->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
|
|
|
|
ClearFlag( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
|
|
}
|
|
|
|
if (!FlagOn( (*ThisScb)->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
ClearFlag( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_SPARSE_FILE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now attempt to open the attribute.
|
|
//
|
|
|
|
ASSERT( NtfsIsTypeCodeUserData( AttrTypeCode ));
|
|
|
|
Status = NtfsOpenAttribute( IrpContext,
|
|
IrpSp,
|
|
ThisFcb->Vcb,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
LastFileNameOffset,
|
|
AttrName,
|
|
AttrTypeCode,
|
|
ShareModificationType,
|
|
UserFileOpen,
|
|
FALSE,
|
|
(FlagOn( CreateFlags, CREATE_FLAG_OPEN_BY_ID )
|
|
? CcbFlags | CCB_FLAG_OPEN_BY_FILE_ID
|
|
: CcbFlags),
|
|
NULL,
|
|
ThisScb,
|
|
ThisCcb );
|
|
|
|
try_exit: NOTHING;
|
|
} finally {
|
|
|
|
//
|
|
// Roll back any temporary changes to the close counts.
|
|
//
|
|
|
|
if (DecrementScbCloseCount) {
|
|
|
|
InterlockedDecrement( &(*ThisScb)->CloseCount );
|
|
}
|
|
|
|
if (Scb != NULL) {
|
|
|
|
InterlockedDecrement( &Scb->CloseCount );
|
|
}
|
|
InterlockedDecrement( &ThisFcb->CloseCount );
|
|
|
|
//
|
|
// Need to roll-back the value of the reparse point flag in case of
|
|
// problems.
|
|
//
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
ThisFcb->Info.FileAttributes = IncomingFileAttributes;
|
|
ThisFcb->Info.ReparsePointTag = IncomingReparsePointTag;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// Set the flag in the Scb to indicate that the size of the
|
|
// attribute has changed.
|
|
//
|
|
|
|
SetFlag( (*ThisScb)->ScbState, SCB_STATE_NOTIFY_RESIZE_STREAM );
|
|
|
|
//
|
|
// Since this is an supersede/overwrite, purge the section
|
|
// so that mappers will see zeros.
|
|
//
|
|
|
|
CcPurgeCacheSection( IrpSp->FileObject->SectionObjectPointer,
|
|
NULL,
|
|
0,
|
|
FALSE );
|
|
|
|
//
|
|
// Remember the status of the oplock in the success code.
|
|
//
|
|
|
|
Status = OplockStatus;
|
|
|
|
//
|
|
// Now update the Iosb information.
|
|
//
|
|
|
|
if (Supersede) {
|
|
|
|
Irp->IoStatus.Information = FILE_SUPERSEDED;
|
|
|
|
} else {
|
|
|
|
Irp->IoStatus.Information = FILE_OVERWRITTEN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsOpenNewAttr (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB ThisLcb,
|
|
IN PFCB ThisFcb,
|
|
IN ULONG LastFileNameOffset,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN LOGICAL CreateFile,
|
|
IN ULONG CcbFlags,
|
|
IN BOOLEAN LogIt,
|
|
IN ULONG CreateFlags,
|
|
OUT PSCB *ThisScb,
|
|
OUT PCCB *ThisCcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to create a new attribute on the disk.
|
|
All access and security checks have been done outside of this
|
|
routine, all we do is create the attribute and open it.
|
|
We test if the attribute will fit in the Mft record. If so we
|
|
create it there. Otherwise we call the create attribute through
|
|
allocation.
|
|
|
|
We then open the attribute with our common routine. In the
|
|
resident case the Scb will have all file values set to
|
|
the allocation size. We set the valid data size back to zero
|
|
and mark the Scb as truncate on close.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
IrpSp - This is the stack location for this open.
|
|
|
|
ThisLcb - This is the Lcb we used to reach this Fcb.
|
|
|
|
ThisFcb - This is the Fcb for the file being opened.
|
|
|
|
LastFileNameOffset - This is the offset in the full path name of the
|
|
final component.
|
|
|
|
AttrName - This is the attribute name in case we need to create
|
|
an Scb.
|
|
|
|
AttrTypeCode - This is the attribute type code to use to create
|
|
the Scb.
|
|
|
|
CreateFile - Indicates if we are in the create file path.
|
|
|
|
CcbFlags - This is the flag field for the Ccb.
|
|
|
|
LogIt - Indicates if we need to log the create operations.
|
|
|
|
CreateFlags - Indicates if this open is related to a OpenByFile open.
|
|
|
|
ThisScb - This is the address to store the address of the Scb.
|
|
|
|
ThisCcb - This is the address to store the address of the Ccb.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of opening this indexed attribute.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
|
|
BOOLEAN ScbExisted;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOpenNewAttr: Entered\n") );
|
|
|
|
//
|
|
// Check that the attribute name is legal. The only restriction is the name length.
|
|
//
|
|
|
|
if (AttrName.Length > NTFS_MAX_ATTR_NAME_LEN * sizeof( WCHAR )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenNewAttr: Exit -> %08lx\n", Status) );
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
}
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// We create the Scb because we will use it.
|
|
//
|
|
|
|
*ThisScb = NtfsCreateScb( IrpContext,
|
|
ThisFcb,
|
|
AttrTypeCode,
|
|
&AttrName,
|
|
FALSE,
|
|
&ScbExisted );
|
|
|
|
//
|
|
// An attribute has gone away but the Scb hasn't left yet.
|
|
// Also mark the header as unitialized.
|
|
//
|
|
|
|
ClearFlag( (*ThisScb)->ScbState, SCB_STATE_HEADER_INITIALIZED |
|
|
SCB_STATE_ATTRIBUTE_RESIDENT |
|
|
SCB_STATE_FILE_SIZE_LOADED );
|
|
|
|
//
|
|
// If we're creating an alternate stream in an encrypted file, and the
|
|
// loaded encryption driver wants the stream to be encrypted and uncompressed,
|
|
// we need to make sure the new stream is indeed created uncompressed.
|
|
//
|
|
|
|
if (IsEncrypted( &ThisFcb->Info ) &&
|
|
(FlagOn( NtfsData.EncryptionCallBackTable.ImplementationFlags, ENCRYPTION_ALL_STREAMS | ENCRYPTION_ALLOW_COMPRESSION ) == ENCRYPTION_ALL_STREAMS)) {
|
|
|
|
DebugTrace( 0, Dbg, ("Encrypted file, creating alternate stream uncompressed") );
|
|
SetFlag( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION );
|
|
}
|
|
|
|
//
|
|
// Create the attribute on disk and update the Scb and Fcb.
|
|
//
|
|
|
|
NtfsCreateAttribute( IrpContext,
|
|
IrpSp,
|
|
ThisFcb,
|
|
*ThisScb,
|
|
ThisLcb,
|
|
*(PLONGLONG)&Irp->Overlay.AllocationSize,
|
|
LogIt,
|
|
FALSE,
|
|
NULL );
|
|
|
|
//
|
|
// Now actually open the attribute.
|
|
//
|
|
|
|
ASSERT( NtfsIsTypeCodeUserData( AttrTypeCode ));
|
|
|
|
Status = NtfsOpenAttribute( IrpContext,
|
|
IrpSp,
|
|
ThisFcb->Vcb,
|
|
ThisLcb,
|
|
ThisFcb,
|
|
LastFileNameOffset,
|
|
AttrName,
|
|
AttrTypeCode,
|
|
(ThisFcb->CleanupCount != 0 ? CheckShareAccess : SetShareAccess),
|
|
UserFileOpen,
|
|
CreateFile,
|
|
(CcbFlags | (FlagOn( CreateFlags, CREATE_FLAG_OPEN_BY_ID ) ? CCB_FLAG_OPEN_BY_FILE_ID : 0)),
|
|
NULL,
|
|
ThisScb,
|
|
ThisCcb );
|
|
|
|
//
|
|
// If there are no errors at this point, we set the caller's Iosb.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// Read the attribute information from the disk.
|
|
//
|
|
|
|
NtfsUpdateScbFromAttribute( IrpContext, *ThisScb, NULL );
|
|
|
|
//
|
|
// Set the flag to indicate that we created a stream and also remember to
|
|
// to check if we need to truncate on close.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader( *ThisScb );
|
|
SetFlag( (*ThisScb)->ScbState,
|
|
SCB_STATE_TRUNCATE_ON_CLOSE | SCB_STATE_NOTIFY_ADD_STREAM );
|
|
|
|
//
|
|
// If we created a temporary stream then mark the Scb.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.FileAttributes, FILE_ATTRIBUTE_TEMPORARY )) {
|
|
|
|
SetFlag( (*ThisScb)->ScbState, SCB_STATE_TEMPORARY );
|
|
SetFlag( IrpSp->FileObject->Flags, FO_TEMPORARY_FILE );
|
|
}
|
|
|
|
NtfsReleaseFsrtlHeader( *ThisScb );
|
|
|
|
Irp->IoStatus.Information = FILE_CREATED;
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsOpenNewAttr );
|
|
|
|
//
|
|
// Uninitialize the attribute context.
|
|
//
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenNewAttr: Exit -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
BOOLEAN
|
|
NtfsParseNameForCreate (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN UNICODE_STRING String,
|
|
IN OUT PUNICODE_STRING FileObjectString,
|
|
IN OUT PUNICODE_STRING OriginalString,
|
|
IN OUT PUNICODE_STRING NewNameString,
|
|
IN PCREATE_CONTEXT CreateContext,
|
|
OUT PUNICODE_STRING AttrName,
|
|
OUT PATTRIBUTE_TYPE_CODE AttrCode
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine parses the input string and remove any intermediate
|
|
named attributes from intermediate nodes. It verifies that all
|
|
intermediate nodes specify the file name index attribute if any
|
|
at all. On output it will store the modified string which contains
|
|
component names only, into the file object name pointer pointer. It is legal
|
|
for the last component to have attribute strings. We pass those
|
|
back via the attribute name strings. We also construct the string to be stored
|
|
back in the file object if we need to post this request.
|
|
|
|
Arguments:
|
|
|
|
String - This is the string to normalize.
|
|
|
|
FileObjectString - We store the normalized string into this pointer, removing the
|
|
attribute and attribute code strings from all component.
|
|
|
|
OriginalString - This is the same as the file object string except we append the
|
|
attribute name and attribute code strings. We assume that the buffer for this
|
|
string is the same as the buffer for the FileObjectString.
|
|
|
|
NewNameString - This is the string which contains the full name being parsed.
|
|
If the buffer is different than the buffer for the Original string then any
|
|
character shifts will be duplicated here.
|
|
|
|
CreateContext - create context contains the flags field
|
|
|
|
AttrName - We store the attribute name specified in the last component
|
|
in this string.
|
|
|
|
AttrCode - We store the attribute type specified in the last
|
|
component in this string if there is any. We'll also mark the create context flags
|
|
with a flag if there was one
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if the path is legal, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
PARSE_TERMINATION_REASON TerminationReason;
|
|
UNICODE_STRING ParsedPath;
|
|
|
|
NTFS_NAME_DESCRIPTOR NameDescript;
|
|
|
|
BOOLEAN RemovedComplexName = FALSE;
|
|
|
|
LONG FileObjectIndex;
|
|
LONG NewNameIndex;
|
|
|
|
BOOLEAN SameBuffers = (OriginalString->Buffer == NewNameString->Buffer);
|
|
|
|
PCUNICODE_STRING TestAttrName;
|
|
PCUNICODE_STRING TestAttrCodeName;
|
|
|
|
POPLOCK_CLEANUP OplockCleanup = IrpContext->Union.OplockCleanup;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsParseNameForCreate: Entered\n") );
|
|
|
|
//
|
|
// We loop through the input string calling ParsePath to swallow the
|
|
// biggest chunk we can. The main case we want to deal with is
|
|
// when we encounter a non-simple name. If this is not the
|
|
// final component, the attribute name and code type better
|
|
// indicate that this is a directory. The only other special
|
|
// case we consider is the case where the string is an
|
|
// attribute only. This is legal only for the first component
|
|
// of the file, and then only if there is no leading backslash.
|
|
//
|
|
|
|
//
|
|
// Initialize some return values.
|
|
//
|
|
|
|
AttrName->Length = 0;
|
|
*AttrCode = $UNUSED;
|
|
|
|
//
|
|
// Set up the indexes into our starting file object string.
|
|
//
|
|
|
|
FileObjectIndex = (LONG) FileObjectString->Length - (LONG) String.Length;
|
|
NewNameIndex = (LONG) NewNameString->Length - (LONG) String.Length;
|
|
|
|
//
|
|
// We don't allow trailing colons.
|
|
//
|
|
|
|
if (String.Buffer[(String.Length / sizeof( WCHAR )) - 1] == L':') {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (String.Length != 0) {
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// Parse the next chunk in the input string.
|
|
//
|
|
|
|
TerminationReason = NtfsParsePath( String,
|
|
FALSE,
|
|
&ParsedPath,
|
|
&NameDescript,
|
|
&String );
|
|
|
|
//
|
|
// Analyze the termination reason to discover if we can abort the
|
|
// parse process.
|
|
//
|
|
|
|
switch (TerminationReason) {
|
|
|
|
case NonSimpleName :
|
|
|
|
//
|
|
// We will do the work below.
|
|
//
|
|
|
|
break;
|
|
|
|
case IllegalCharacterInName :
|
|
case VersionNumberPresent :
|
|
case MalFormedName :
|
|
|
|
//
|
|
// We simply return an error.
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsParseNameForCreate: Illegal character\n") );
|
|
return FALSE;
|
|
|
|
case AttributeOnly :
|
|
|
|
//
|
|
// This is legal only if it is the only component of a relative open. We
|
|
// test this by checking that we are at the end of string and the file
|
|
// object name has a lead in ':' character or this is the root directory
|
|
// and the lead in characters are '\:'.
|
|
//
|
|
|
|
if ((String.Length != 0) ||
|
|
RemovedComplexName ||
|
|
(FileObjectString->Buffer[0] == L'\\' ?
|
|
FileObjectString->Buffer[1] != L':' :
|
|
FileObjectString->Buffer[0] != L':')) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsParseNameForCreate: Illegal character\n") );
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// We can drop down to the EndOfPath case as it will copy over
|
|
// the parsed path portion.
|
|
//
|
|
|
|
case EndOfPathReached :
|
|
|
|
NOTHING;
|
|
}
|
|
|
|
//
|
|
// We add the filename part of the non-simple name to the parsed
|
|
// path. Check if we can include the separator.
|
|
//
|
|
|
|
if ((TerminationReason != EndOfPathReached)
|
|
&& (FlagOn( NameDescript.FieldsPresent, FILE_NAME_PRESENT_FLAG ))) {
|
|
|
|
if (ParsedPath.Length > sizeof( WCHAR )
|
|
|| (ParsedPath.Length == sizeof( WCHAR )
|
|
&& ParsedPath.Buffer[0] != L'\\')) {
|
|
|
|
ParsedPath.Length += sizeof( WCHAR );
|
|
}
|
|
|
|
ParsedPath.Length += NameDescript.FileName.Length;
|
|
}
|
|
|
|
FileObjectIndex += ParsedPath.Length;
|
|
NewNameIndex += ParsedPath.Length;
|
|
|
|
//
|
|
// If the remaining string is empty, then we remember any attributes and
|
|
// exit now.
|
|
//
|
|
|
|
if (String.Length == 0) {
|
|
|
|
//
|
|
// If the name specified either an attribute or attribute
|
|
// name, we remember them.
|
|
//
|
|
|
|
if (FlagOn( NameDescript.FieldsPresent, ATTRIBUTE_NAME_PRESENT_FLAG )) {
|
|
|
|
*AttrName = NameDescript.AttributeName;
|
|
}
|
|
|
|
if (FlagOn( NameDescript.FieldsPresent, ATTRIBUTE_TYPE_PRESENT_FLAG )) {
|
|
|
|
SetFlag( CreateContext->CreateFlags, CREATE_FLAG_EXPLICIT_ATTRIBUTE_CODE );
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_IGNORE_CASE )) {
|
|
NtfsUpcaseName( IrpContext->Vcb->UpcaseTable, IrpContext->Vcb->UpcaseTableSize, &NameDescript.AttributeType );
|
|
}
|
|
|
|
*AttrCode = NtfsGetAttributeTypeCode( IrpContext->Vcb, &NameDescript.AttributeType );
|
|
|
|
//
|
|
// Reject names with $UNUSED in them
|
|
//
|
|
|
|
if (*AttrCode == $UNUSED) {
|
|
return FALSE;
|
|
}
|
|
|
|
OplockCleanup->AttributeCodeNameLength = NameDescript.AttributeType.Length;
|
|
}
|
|
break;
|
|
}
|
|
|
|
//
|
|
// This can only be the non-simple case. If there is more to the
|
|
// name, then the attributes better describe a directory. We also shift the
|
|
// remaining bytes of the string down.
|
|
//
|
|
|
|
ASSERT( FlagOn( NameDescript.FieldsPresent, ATTRIBUTE_NAME_PRESENT_FLAG | ATTRIBUTE_TYPE_PRESENT_FLAG ));
|
|
|
|
TestAttrName = FlagOn( NameDescript.FieldsPresent,
|
|
ATTRIBUTE_NAME_PRESENT_FLAG )
|
|
? &NameDescript.AttributeName
|
|
: &NtfsEmptyString;
|
|
|
|
TestAttrCodeName = FlagOn( NameDescript.FieldsPresent,
|
|
ATTRIBUTE_TYPE_PRESENT_FLAG )
|
|
? &NameDescript.AttributeType
|
|
: &NtfsEmptyString;
|
|
|
|
//
|
|
// Valid Complex names are [$I30]:$INDEX_ALLOCATION
|
|
// [$I30]:$BITMAP
|
|
// :$ATTRIBUTE_LIST
|
|
// :$REPARSE_POINT
|
|
//
|
|
|
|
if (!NtfsVerifyNameIsDirectory( IrpContext,
|
|
TestAttrName,
|
|
TestAttrCodeName ) &&
|
|
|
|
!NtfsVerifyNameIsBitmap( IrpContext,
|
|
TestAttrName,
|
|
TestAttrCodeName ) &&
|
|
|
|
!NtfsVerifyNameIsAttributeList( IrpContext,
|
|
TestAttrName,
|
|
TestAttrCodeName ) &&
|
|
|
|
!NtfsVerifyNameIsReparsePoint( IrpContext,
|
|
TestAttrName,
|
|
TestAttrCodeName )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsParseNameForCreate: Invalid intermediate component\n") );
|
|
return FALSE;
|
|
}
|
|
|
|
RemovedComplexName = TRUE;
|
|
|
|
//
|
|
// We need to insert a separator and then move the rest of the string
|
|
// down.
|
|
//
|
|
|
|
FileObjectString->Buffer[FileObjectIndex / sizeof( WCHAR )] = L'\\';
|
|
|
|
if (!SameBuffers) {
|
|
|
|
NewNameString->Buffer[NewNameIndex / sizeof( WCHAR )] = L'\\';
|
|
}
|
|
|
|
FileObjectIndex += sizeof( WCHAR );
|
|
NewNameIndex += sizeof( WCHAR );
|
|
|
|
RtlMoveMemory( &FileObjectString->Buffer[FileObjectIndex / sizeof( WCHAR )],
|
|
String.Buffer,
|
|
String.Length );
|
|
|
|
if (!SameBuffers) {
|
|
|
|
RtlMoveMemory( &NewNameString->Buffer[NewNameIndex / sizeof( WCHAR )],
|
|
String.Buffer,
|
|
String.Length );
|
|
}
|
|
|
|
String.Buffer = &NewNameString->Buffer[NewNameIndex / sizeof( WCHAR )];
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point the original string is the same as the file object string.
|
|
//
|
|
|
|
FileObjectString->Length = (USHORT) FileObjectIndex;
|
|
NewNameString->Length = (USHORT) NewNameIndex;
|
|
|
|
OriginalString->Length = FileObjectString->Length;
|
|
|
|
//
|
|
// We want to store the attribute index values in the original name
|
|
// string. We just need to extend the original name length.
|
|
//
|
|
|
|
if ((AttrName->Length != 0) ||
|
|
FlagOn( CreateContext->CreateFlags, CREATE_FLAG_EXPLICIT_ATTRIBUTE_CODE )) {
|
|
|
|
OriginalString->Length += (2 + AttrName->Length);
|
|
|
|
if (FlagOn( CreateContext->CreateFlags, CREATE_FLAG_EXPLICIT_ATTRIBUTE_CODE )) {
|
|
|
|
OriginalString->Length += (2 + NameDescript.AttributeType.Length);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Store in the OPLOCK_CLEANUP structure the lengths of the names of the attribute and
|
|
// of the code.
|
|
//
|
|
|
|
OplockCleanup->AttributeNameLength = AttrName->Length;
|
|
|
|
DebugTrace( 0, Dbg, ("AttrName->Length %d AttrCodeName->Length %d\n", OplockCleanup->AttributeNameLength, OplockCleanup->AttributeCodeNameLength) );
|
|
DebugTrace( -1, Dbg, ("NtfsParseNameForCreate: Exit\n") );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
BOOLEAN
|
|
NtfsCheckValidFileAccess(
|
|
IN PFCB ThisFcb,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Common routine used to rule out access to files in open path. This only disallows
|
|
always invalid open reqests / acl checks, oplocks sharing are done elsewhere
|
|
|
|
Fail immediately if this is a special system file or the user wants an illegal access.
|
|
|
|
We allow READ_ATTRIBUTES and some ACL access to a subset of system files. Deny all
|
|
access to the following files.
|
|
|
|
USN Journal
|
|
Volume Log File
|
|
Volume Bitmap
|
|
Boot File
|
|
Bad Cluster File
|
|
As of now undefined system files
|
|
|
|
Check for supersede/overwrite first.
|
|
|
|
|
|
Arguments:
|
|
|
|
Fcb - Address of the Fcb pointer where the $REPARSE_POINT attribute is located.
|
|
|
|
IrpSp - This is the Irp stack pointer for the filesystem.
|
|
|
|
Return Value:
|
|
|
|
TRUE if access is allowed
|
|
|
|
--*/
|
|
{
|
|
ULONG CreateDisposition = (UCHAR) ((IrpSp->Parameters.Create.Options >> 24) & 0x000000ff);
|
|
ULONG InvalidAccess;
|
|
BOOLEAN Result = TRUE;
|
|
|
|
PAGED_CODE()
|
|
|
|
//
|
|
// Verify we don't have the system flag set on the root.
|
|
//
|
|
|
|
ASSERT( NtfsSegmentNumber( &ThisFcb->FileReference ) != ROOT_FILE_NAME_INDEX_NUMBER );
|
|
|
|
if ((CreateDisposition == FILE_SUPERSEDE) ||
|
|
(CreateDisposition == FILE_OVERWRITE) ||
|
|
(CreateDisposition == FILE_OVERWRITE_IF) ||
|
|
|
|
//
|
|
// Check for special system files.
|
|
//
|
|
|
|
(NtfsSegmentNumber( &ThisFcb->FileReference ) == LOG_FILE_NUMBER) ||
|
|
(NtfsSegmentNumber( &ThisFcb->FileReference ) == BIT_MAP_FILE_NUMBER) ||
|
|
(NtfsSegmentNumber( &ThisFcb->FileReference ) == BOOT_FILE_NUMBER) ||
|
|
(NtfsSegmentNumber( &ThisFcb->FileReference ) == BAD_CLUSTER_FILE_NUMBER) ||
|
|
FlagOn( ThisFcb->FcbState, FCB_STATE_USN_JOURNAL ) ||
|
|
|
|
//
|
|
// Check for currently undefined system files.
|
|
//
|
|
|
|
((NtfsSegmentNumber( &ThisFcb->FileReference ) < FIRST_USER_FILE_NUMBER) &&
|
|
(NtfsSegmentNumber( &ThisFcb->FileReference ) > LAST_SYSTEM_FILE_NUMBER))) {
|
|
|
|
Result = FALSE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we are beyond the reserved range then use the ACL to protect the file.
|
|
//
|
|
|
|
if (NtfsSegmentNumber( &ThisFcb->FileReference ) >= FIRST_USER_FILE_NUMBER) {
|
|
|
|
InvalidAccess = 0;
|
|
|
|
//
|
|
// If we are looking at the $Extend directory then permit the ACL operations.
|
|
//
|
|
|
|
} else if (NtfsSegmentNumber( &ThisFcb->FileReference ) == EXTEND_NUMBER) {
|
|
|
|
InvalidAccess = ~(FILE_READ_ATTRIBUTES | SYNCHRONIZE | READ_CONTROL | WRITE_DAC | WRITE_OWNER);
|
|
|
|
//
|
|
// Otherwise restrict access severely.
|
|
//
|
|
|
|
} else {
|
|
|
|
InvalidAccess = ~(FILE_READ_ATTRIBUTES | SYNCHRONIZE);
|
|
}
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess, InvalidAccess )) {
|
|
|
|
Result = FALSE;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsCheckValidAttributeAccess (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PVCB Vcb,
|
|
IN PDUPLICATED_INFORMATION Info OPTIONAL,
|
|
IN OUT PUNICODE_STRING AttrName,
|
|
IN OUT PATTRIBUTE_TYPE_CODE AttrCode,
|
|
IN ULONG CreateFlags,
|
|
OUT PULONG CcbFlags,
|
|
OUT PBOOLEAN IndexedAttribute
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine looks at the file, the specified attribute name and
|
|
code to determine if an attribute of this file may be opened
|
|
by this user. If there is a conflict between the file type
|
|
and the attribute name and code, or the specified type of attribute
|
|
(directory/nondirectory) we will return FALSE.
|
|
We also check that the attribute code string is defined for the
|
|
volume at this time.
|
|
|
|
The final check of this routine is just whether a user is allowed
|
|
to open the particular attribute or if Ntfs will guard them.
|
|
|
|
Arguments:
|
|
|
|
IrpSp - This is the stack location for this open.
|
|
|
|
Vcb - This is the Vcb for this volume.
|
|
|
|
Info - If specified, this is the duplicated information for this file.
|
|
|
|
AttrName - This is the attribute name specified.
|
|
|
|
AttrCode - This is the attribute type to use to open the attribute - we will
|
|
replace with the real type if it hasn't been specified yet.
|
|
|
|
AttrTypeCode - Used to store the attribute type code determined here.
|
|
|
|
CreateFlags - Create flags - we care about the trailing backslash
|
|
|
|
CcbFlags - We set the Ccb flags here to store in the Ccb later.
|
|
|
|
IndexedAttribute - Set to indicate the type of open.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS if access is allowed, the status code indicating
|
|
the reason for denial otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN Indexed;
|
|
ULONG CreateDisposition = ((IrpSp->Parameters.Create.Options >> 24) & 0x000000ff);
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCheckValidAttributeAccess: Entered\n") );
|
|
|
|
//
|
|
// If the user specified a attribute code string, we find the
|
|
// corresponding attribute. If there is no matching attribute
|
|
// type code then we report that this access is invalid.
|
|
//
|
|
|
|
if (FlagOn( CreateFlags, CREATE_FLAG_EXPLICIT_ATTRIBUTE_CODE )) {
|
|
|
|
ASSERT( (*AttrCode) != $UNUSED );
|
|
|
|
if ((*AttrCode) == $INDEX_ALLOCATION) {
|
|
|
|
if (AttrName->Length != 0) {
|
|
|
|
if (NtfsAreNamesEqual( Vcb->UpcaseTable, AttrName, &NtfsFileNameIndex, TRUE )) {
|
|
|
|
AttrName->Length = 0;
|
|
|
|
} else {
|
|
|
|
//
|
|
// This isn't a filename index, so it better be a view index.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT(Info) || !IsViewIndex( Info )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: Bad name for index allocation\n") );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if (*AttrCode != $DATA) {
|
|
|
|
//
|
|
// never allow supersede on any other name attributes
|
|
//
|
|
|
|
if ((CreateDisposition == FILE_SUPERSEDE) ||
|
|
(CreateDisposition == FILE_OVERWRITE) ||
|
|
(CreateDisposition == FILE_OVERWRITE_IF)) {
|
|
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Pull some values out of the Irp and IrpSp.
|
|
//
|
|
|
|
Indexed = BooleanFlagOn( IrpSp->Parameters.Create.Options,
|
|
FILE_DIRECTORY_FILE );
|
|
|
|
//
|
|
// We need to determine whether the user expects to open an
|
|
// indexed or non-indexed attribute. If either of the
|
|
// directory/non-directory flags in the Irp stack are set,
|
|
// we will use those.
|
|
//
|
|
// Otherwise we need to examine some of the other input parameters.
|
|
// We have the following information:
|
|
//
|
|
// 1 - We may have a duplicated information structure for the file.
|
|
// (Not present on a create).
|
|
// 2 - The user specified the name with a trailing backslash.
|
|
// 3 - The user passed in an attribute name.
|
|
// 4 - The user passed in an attribute type.
|
|
//
|
|
// We first look at the attribute type code and name. If they are
|
|
// both unspecified we determine the type of access by following
|
|
// the following steps.
|
|
//
|
|
// 1 - If there is a duplicated information structure we
|
|
// set the code to $INDEX_ALLOCATION and remember
|
|
// this is indexed. Otherwise this is a $DATA
|
|
// attribute.
|
|
//
|
|
// 2 - If there is a trailing backslash we assume this is
|
|
// an indexed attribute.
|
|
//
|
|
// If have an attribute code type or name, then if the code type is
|
|
// $INDEX_ALLOCATION without a name this is an indexed attribute.
|
|
// Otherwise we assume a non-indexed attribute.
|
|
//
|
|
|
|
if (!FlagOn( IrpSp->Parameters.Create.Options,
|
|
FILE_NON_DIRECTORY_FILE | FILE_DIRECTORY_FILE) &&
|
|
(AttrName->Length == 0)) {
|
|
|
|
if (*AttrCode == $UNUSED) {
|
|
|
|
if (ARGUMENT_PRESENT( Info )) {
|
|
|
|
Indexed = BooleanIsDirectory( Info );
|
|
|
|
} else {
|
|
|
|
Indexed = FALSE;
|
|
}
|
|
|
|
} else if (*AttrCode == $INDEX_ALLOCATION) {
|
|
|
|
Indexed = TRUE;
|
|
}
|
|
|
|
} else if (*AttrCode == $INDEX_ALLOCATION) {
|
|
|
|
Indexed = TRUE;
|
|
}
|
|
|
|
//
|
|
// If the type code was unspecified, we can assume it from the attribute
|
|
// name and the type of the file. If the file is a directory and
|
|
// there is no attribute name, we assume this is an indexed open.
|
|
// Otherwise it is a non-indexed open.
|
|
//
|
|
|
|
if (*AttrCode == $UNUSED) {
|
|
|
|
if (Indexed && AttrName->Length == 0) {
|
|
|
|
*AttrCode = $INDEX_ALLOCATION;
|
|
|
|
} else {
|
|
|
|
*AttrCode = $DATA;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the user specified directory all we need to do is check the
|
|
// following condition.
|
|
//
|
|
// 1 - If the file was specified, it must be a directory.
|
|
// 2 - The attribute type code must be $INDEX_ALLOCATION with either:
|
|
// no attribute name
|
|
// or
|
|
// duplicate info present & view index bit set in dupe info
|
|
// 3 - The user isn't trying to open the volume.
|
|
//
|
|
|
|
if (Indexed) {
|
|
|
|
if ((*AttrCode != $INDEX_ALLOCATION) ||
|
|
|
|
((AttrName->Length != 0) &&
|
|
((!ARGUMENT_PRESENT( Info )) || !IsViewIndex( Info )))) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: Conflict in directory\n") );
|
|
return STATUS_NOT_A_DIRECTORY;
|
|
|
|
//
|
|
// If there is a current file and it is not a directory and
|
|
// the caller wanted to perform a create. We return
|
|
// STATUS_OBJECT_NAME_COLLISION, otherwise we return STATUS_NOT_A_DIRECTORY.
|
|
//
|
|
|
|
} else if (ARGUMENT_PRESENT( Info ) &&
|
|
!IsDirectory( Info ) &&
|
|
!IsViewIndex( Info)) {
|
|
|
|
if (((IrpSp->Parameters.Create.Options >> 24) & 0x000000ff) == FILE_CREATE) {
|
|
|
|
return STATUS_OBJECT_NAME_COLLISION;
|
|
|
|
} else {
|
|
|
|
return STATUS_NOT_A_DIRECTORY;
|
|
}
|
|
}
|
|
|
|
SetFlag( *CcbFlags, CCB_FLAG_OPEN_AS_FILE );
|
|
|
|
//
|
|
// If the user specified a non-directory that means he is opening a non-indexed
|
|
// attribute. We check for the following condition.
|
|
//
|
|
// 1 - Only the unnamed data attribute may be opened for a volume.
|
|
// 2 - We can't be opening an unnamed $INDEX_ALLOCATION attribute.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Now determine if we are opening the entire file.
|
|
//
|
|
|
|
if (*AttrCode == $DATA) {
|
|
|
|
if (AttrName->Length == 0) {
|
|
SetFlag( *CcbFlags, CCB_FLAG_OPEN_AS_FILE );
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// For all other attributes only support read attributes access
|
|
//
|
|
|
|
if (IrpSp->Parameters.Create.SecurityContext->AccessState->OriginalDesiredAccess & ~(FILE_READ_ATTRIBUTES | SYNCHRONIZE)) {
|
|
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT( Info ) &&
|
|
IsDirectory( Info ) &&
|
|
FlagOn( *CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: Can't open directory as file\n") );
|
|
return STATUS_FILE_IS_A_DIRECTORY;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we make it this far, lets check that we will allow access to
|
|
// the attribute specified. Typically we only allow the user to
|
|
// access non system files. Also only the Data attributes and
|
|
// attributes created by the user may be opened. We will protect
|
|
// these with boolean flags to allow the developers to enable
|
|
// reading any attributes.
|
|
//
|
|
|
|
if (NtfsProtectSystemAttributes) {
|
|
|
|
if (!NtfsIsTypeCodeUserData( *AttrCode ) &&
|
|
((*AttrCode != $INDEX_ALLOCATION) || !Indexed) &&
|
|
(*AttrCode != $BITMAP) &&
|
|
(*AttrCode != $ATTRIBUTE_LIST) &&
|
|
(*AttrCode != $REPARSE_POINT) &&
|
|
(*AttrCode < $FIRST_USER_DEFINED_ATTRIBUTE)) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: System attribute code\n") );
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Now check if the trailing backslash is compatible with the
|
|
// file being opened.
|
|
//
|
|
|
|
if (FlagOn( CreateFlags, CREATE_FLAG_TRAILING_BACKSLASH )) {
|
|
|
|
if (!Indexed ||
|
|
FlagOn( IrpSp->Parameters.Create.Options, FILE_NON_DIRECTORY_FILE )) {
|
|
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
|
|
} else {
|
|
|
|
Indexed = TRUE;
|
|
*AttrCode = $INDEX_ALLOCATION;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we are opening the default index allocation stream or bitmap
|
|
// for a directory, set its attribute name appropriately.
|
|
// Note: if info is not present we're creating the attribute and
|
|
// it also must be a directory in this case
|
|
//
|
|
|
|
if (((ARGUMENT_PRESENT( Info ) && IsDirectory( Info )) ||
|
|
(!ARGUMENT_PRESENT( Info ))) &&
|
|
|
|
(((*AttrCode == $INDEX_ALLOCATION) || (*AttrCode == $BITMAP)) &&
|
|
(AttrName->Length == 0))) {
|
|
|
|
*AttrName = NtfsFileNameIndex;
|
|
}
|
|
|
|
*IndexedAttribute = Indexed;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: Exit\n") );
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsOpenAttributeCheck (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
OUT PSCB *ThisScb,
|
|
OUT PSHARE_MODIFICATION_TYPE ShareModificationType
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is a general routine which checks if an existing
|
|
non-indexed attribute may be opened. It considers only the oplock
|
|
state of the file and the current share access. In the course of
|
|
performing these checks, the Scb for the attribute may be
|
|
created and the share modification for the actual OpenAttribute
|
|
call is determined.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
IrpSp - This is the stack location for this open.
|
|
|
|
ThisScb - Address to store the Scb if found or created.
|
|
|
|
ShareModificationType - Address to store the share modification type
|
|
for a subsequent OpenAttribute call.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of opening this indexed attribute.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
BOOLEAN DeleteOnClose;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOpenAttributeCheck: Entered\n") );
|
|
|
|
//
|
|
// We should already have the Scb for this file.
|
|
//
|
|
|
|
ASSERT_SCB( *ThisScb );
|
|
|
|
//
|
|
// If there are other opens on this file, we need to check the share
|
|
// access before we check the oplocks. We remember that
|
|
// we did the share access check by simply updating the share
|
|
// access we open the attribute.
|
|
//
|
|
|
|
if ((*ThisScb)->CleanupCount != 0) {
|
|
|
|
//
|
|
// We check the share access for this file without updating it.
|
|
//
|
|
|
|
Status = IoCheckShareAccess( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
|
|
IrpSp->Parameters.Create.ShareAccess,
|
|
IrpSp->FileObject,
|
|
&(*ThisScb)->ShareAccess,
|
|
FALSE );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenAttributeCheck: Exit -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("Check oplock state of existing Scb\n") );
|
|
|
|
if (SafeNodeType( *ThisScb ) == NTFS_NTC_SCB_DATA) {
|
|
|
|
//
|
|
// If the handle count is greater than 1 then fail this
|
|
// open now if the caller wants a filter oplock.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_RESERVE_OPFILTER ) &&
|
|
((*ThisScb)->CleanupCount > 1)) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_OPLOCK_NOT_GRANTED, NULL, NULL );
|
|
}
|
|
|
|
Status = FsRtlCheckOplock( &(*ThisScb)->ScbType.Data.Oplock,
|
|
Irp,
|
|
IrpContext,
|
|
NtfsOplockComplete,
|
|
NtfsOplockPrePostIrp );
|
|
|
|
//
|
|
// If the return value isn't success or oplock break in progress
|
|
// the irp has been posted. We return right now.
|
|
//
|
|
|
|
if (Status == STATUS_PENDING) {
|
|
|
|
DebugTrace( 0, Dbg, ("Irp posted through oplock routine\n") );
|
|
DebugTrace( -1, Dbg, ("NtfsOpenAttributeCheck: Exit -> %08lx\n", Status) );
|
|
|
|
return STATUS_WAIT_FOR_OPLOCK;
|
|
}
|
|
}
|
|
|
|
*ShareModificationType = UpdateShareAccess;
|
|
|
|
//
|
|
// If the unclean count in the Fcb is 0, we will simply set the
|
|
// share access.
|
|
//
|
|
|
|
} else {
|
|
|
|
*ShareModificationType = SetShareAccess;
|
|
}
|
|
|
|
DeleteOnClose = BooleanFlagOn( IrpSp->Parameters.Create.Options,
|
|
FILE_DELETE_ON_CLOSE );
|
|
|
|
//
|
|
// Can't do DELETE_ON_CLOSE on read only volumes.
|
|
//
|
|
|
|
if (DeleteOnClose && NtfsIsVolumeReadOnly( (*ThisScb)->Vcb )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenAttributeCheck: Exit -> %08lx\n", STATUS_CANNOT_DELETE) );
|
|
return STATUS_CANNOT_DELETE;
|
|
}
|
|
|
|
//
|
|
// If the user wants write access access to the file make sure there
|
|
// is process mapping this file as an image. Any attempt to delete
|
|
// the file will be stopped in fileinfo.c
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess,
|
|
FILE_WRITE_DATA )
|
|
|| DeleteOnClose) {
|
|
|
|
//
|
|
// Use a try-finally to decrement the open count. This is a little
|
|
// bit of trickery to keep the scb around while we are doing the
|
|
// flush call.
|
|
//
|
|
|
|
InterlockedIncrement( &(*ThisScb)->CloseCount );
|
|
|
|
try {
|
|
|
|
//
|
|
// If there is an image section then we better have the file
|
|
// exclusively.
|
|
//
|
|
|
|
if ((*ThisScb)->NonpagedScb->SegmentObject.ImageSectionObject != NULL) {
|
|
|
|
if (!MmFlushImageSection( &(*ThisScb)->NonpagedScb->SegmentObject,
|
|
MmFlushForWrite )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Couldn't flush image section\n") );
|
|
|
|
Status = DeleteOnClose ? STATUS_CANNOT_DELETE :
|
|
STATUS_SHARING_VIOLATION;
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
InterlockedDecrement( &(*ThisScb)->CloseCount );
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenAttributeCheck: Exit -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
VOID
|
|
NtfsAddEa (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PFCB ThisFcb,
|
|
IN PFILE_FULL_EA_INFORMATION EaBuffer OPTIONAL,
|
|
IN ULONG EaLength,
|
|
OUT PIO_STATUS_BLOCK Iosb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will add an ea set to the file. It writes the attributes
|
|
to disk and updates the Fcb info structure with the packed ea size.
|
|
|
|
Arguments:
|
|
|
|
Vcb - This is the volume being opened.
|
|
|
|
ThisFcb - This is the Fcb for the file being opened.
|
|
|
|
EaBuffer - This is the buffer passed by the user.
|
|
|
|
EaLength - This is the stated length of the buffer.
|
|
|
|
Iosb - This is the Status Block to use to fill in the offset of an
|
|
offending Ea.
|
|
|
|
Return Value:
|
|
|
|
None - This routine will raise on error.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
EA_LIST_HEADER EaList;
|
|
ULONG Length;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsAddEa: Entered\n") );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Initialize the EaList header.
|
|
//
|
|
|
|
EaList.PackedEaSize = 0;
|
|
EaList.NeedEaCount = 0;
|
|
EaList.UnpackedEaSize = 0;
|
|
EaList.BufferSize = 0;
|
|
EaList.FullEa = NULL;
|
|
|
|
if (ARGUMENT_PRESENT( EaBuffer )) {
|
|
|
|
//
|
|
// Check the user's buffer for validity.
|
|
//
|
|
|
|
Status = IoCheckEaBufferValidity( EaBuffer,
|
|
EaLength,
|
|
&Length );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsAddEa: Invalid ea list\n") );
|
|
Iosb->Information = Length;
|
|
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// **** Maybe this routine should raise.
|
|
//
|
|
|
|
Status = NtfsBuildEaList( IrpContext,
|
|
Vcb,
|
|
&EaList,
|
|
EaBuffer,
|
|
&Iosb->Information );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsAddEa: Couldn't build Ea list\n") );
|
|
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now replace the existing EAs.
|
|
//
|
|
|
|
NtfsReplaceFileEas( IrpContext, ThisFcb, &EaList );
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsAddEa );
|
|
|
|
//
|
|
// Free the in-memory copy of the Eas.
|
|
//
|
|
|
|
if (EaList.FullEa != NULL) {
|
|
|
|
NtfsFreePool( EaList.FullEa );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsAddEa: Exit -> %08lx\n", Status) );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsInitializeFcbAndStdInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB ThisFcb,
|
|
IN BOOLEAN Directory,
|
|
IN BOOLEAN ViewIndex,
|
|
IN BOOLEAN Compressed,
|
|
IN ULONG FileAttributes,
|
|
IN PNTFS_TUNNELED_DATA SetTunneledData OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will initialize an Fcb for a newly created file and create
|
|
the standard information attribute on disk. We assume that some information
|
|
may already have been placed in the Fcb so we don't zero it out. We will
|
|
initialize the allocation size to zero, but that may be changed later in
|
|
the create process.
|
|
|
|
Arguments:
|
|
|
|
ThisFcb - This is the Fcb for the file being opened.
|
|
|
|
Directory - Indicates if this is a directory file.
|
|
|
|
ViewIndex - Indicates if this is a view index.
|
|
|
|
Compressed - Indicates if this is a compressed file.
|
|
|
|
FileAttributes - These are the attributes the user wants to attach to
|
|
the file. We will just clear any unsupported bits.
|
|
|
|
SetTunneledData - Optionally force the creation time and/or object id
|
|
to a given value
|
|
|
|
Return Value:
|
|
|
|
None - This routine will raise on error.
|
|
|
|
--*/
|
|
|
|
{
|
|
STANDARD_INFORMATION StandardInformation;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsInitializeFcbAndStdInfo: Entered\n") );
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Mask out the invalid bits of the file atributes. Then set the
|
|
// file name index bit if this is a directory.
|
|
//
|
|
|
|
if (!Directory) {
|
|
|
|
SetFlag( FileAttributes, FILE_ATTRIBUTE_ARCHIVE );
|
|
}
|
|
|
|
ClearFlag( FileAttributes, ~FILE_ATTRIBUTE_VALID_SET_FLAGS | FILE_ATTRIBUTE_NORMAL );
|
|
|
|
if (Directory) {
|
|
|
|
SetFlag( FileAttributes, DUP_FILE_NAME_INDEX_PRESENT );
|
|
}
|
|
|
|
if (ViewIndex) {
|
|
|
|
SetFlag( FileAttributes, DUP_VIEW_INDEX_PRESENT );
|
|
}
|
|
|
|
if (Compressed) {
|
|
|
|
SetFlag( FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
|
|
}
|
|
|
|
ThisFcb->Info.FileAttributes = FileAttributes;
|
|
|
|
//
|
|
// Fill in the rest of the Fcb Info structure.
|
|
//
|
|
|
|
if (SetTunneledData == NULL) {
|
|
|
|
NtfsGetCurrentTime( IrpContext, ThisFcb->Info.CreationTime );
|
|
|
|
ThisFcb->Info.LastModificationTime = ThisFcb->Info.CreationTime;
|
|
ThisFcb->Info.LastChangeTime = ThisFcb->Info.CreationTime;
|
|
ThisFcb->Info.LastAccessTime = ThisFcb->Info.CreationTime;
|
|
|
|
ThisFcb->CurrentLastAccess = ThisFcb->Info.CreationTime;
|
|
|
|
} else {
|
|
|
|
NtfsSetTunneledData( IrpContext,
|
|
ThisFcb,
|
|
SetTunneledData );
|
|
|
|
NtfsGetCurrentTime( IrpContext, ThisFcb->Info.LastModificationTime );
|
|
|
|
ThisFcb->Info.LastChangeTime = ThisFcb->Info.LastModificationTime;
|
|
ThisFcb->Info.LastAccessTime = ThisFcb->Info.LastModificationTime;
|
|
|
|
ThisFcb->CurrentLastAccess = ThisFcb->Info.LastModificationTime;
|
|
}
|
|
|
|
//
|
|
// We assume these sizes are zero.
|
|
//
|
|
|
|
ThisFcb->Info.AllocatedLength = 0;
|
|
ThisFcb->Info.FileSize = 0;
|
|
|
|
//
|
|
// Copy the standard information fields from the Fcb and create the
|
|
// attribute.
|
|
//
|
|
|
|
RtlZeroMemory( &StandardInformation, sizeof( STANDARD_INFORMATION ));
|
|
|
|
StandardInformation.CreationTime = ThisFcb->Info.CreationTime;
|
|
StandardInformation.LastModificationTime = ThisFcb->Info.LastModificationTime;
|
|
StandardInformation.LastChangeTime = ThisFcb->Info.LastChangeTime;
|
|
StandardInformation.LastAccessTime = ThisFcb->Info.LastAccessTime;
|
|
StandardInformation.FileAttributes = ThisFcb->Info.FileAttributes;
|
|
|
|
StandardInformation.ClassId = 0;
|
|
StandardInformation.OwnerId = ThisFcb->OwnerId;
|
|
StandardInformation.SecurityId = ThisFcb->SecurityId;
|
|
StandardInformation.Usn = ThisFcb->Usn;
|
|
|
|
SetFlag( ThisFcb->FcbState, FCB_STATE_LARGE_STD_INFO );
|
|
|
|
NtfsCreateAttributeWithValue( IrpContext,
|
|
ThisFcb,
|
|
$STANDARD_INFORMATION,
|
|
NULL,
|
|
&StandardInformation,
|
|
sizeof( STANDARD_INFORMATION ),
|
|
0,
|
|
NULL,
|
|
FALSE,
|
|
&AttrContext );
|
|
|
|
//
|
|
// We know that the open call will generate a single link.
|
|
// (Remember that a separate 8.3 name is not considered a link)
|
|
//
|
|
|
|
ThisFcb->LinkCount =
|
|
ThisFcb->TotalLinks = 1;
|
|
|
|
//
|
|
// Now set the header initialized flag in the Fcb.
|
|
//
|
|
|
|
SetFlag( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED );
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsInitializeFcbAndStdInfo );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsInitializeFcbAndStdInfo: Exit\n") );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
VOID
|
|
NtfsCreateAttribute (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN OUT PFCB ThisFcb,
|
|
IN OUT PSCB ThisScb,
|
|
IN PLCB ThisLcb,
|
|
IN LONGLONG AllocationSize,
|
|
IN BOOLEAN LogIt,
|
|
IN BOOLEAN ForceNonresident,
|
|
IN PUSHORT PreviousFlags OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to create an attribute of a given size on the
|
|
disk. This path will only create non-resident attributes unless the
|
|
allocation size is zero.
|
|
|
|
The Scb will contain the attribute name and type code on entry.
|
|
|
|
Arguments:
|
|
|
|
IrpSp - Stack location in the Irp for this request.
|
|
|
|
ThisFcb - This is the Fcb for the file to create the attribute in.
|
|
|
|
ThisScb - This is the Scb for the attribute to create.
|
|
|
|
ThisLcb - This is the Lcb for propagating compression parameters
|
|
|
|
AllocationSize - This is the size of the attribute to create.
|
|
|
|
LogIt - Indicates whether we should log the creation of the attribute.
|
|
Also indicates if this is a create file operation.
|
|
|
|
ForceNonresident - Indicates that we want to create this stream non-resident.
|
|
This is the case if this is a supersede of a previously non-resident
|
|
stream. Once a stream is non-resident it can't go back to resident.
|
|
|
|
PreviousFlags - If specified then this is a supersede operation and
|
|
this is the previous compression flags for the file.
|
|
|
|
Return Value:
|
|
|
|
None - This routine will raise on error.
|
|
|
|
--*/
|
|
|
|
{
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
PATTRIBUTE_RECORD_HEADER ThisAttribute = NULL;
|
|
|
|
USHORT AttributeFlags = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCreateAttribute: Entered\n") );
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )) {
|
|
|
|
//
|
|
// Always force this to be non-resident.
|
|
//
|
|
|
|
ForceNonresident = TRUE;
|
|
|
|
} else if (!FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION )) {
|
|
|
|
//
|
|
// If this is the root directory then use the Scb from the Vcb.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( PreviousFlags)) {
|
|
|
|
AttributeFlags = *PreviousFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK;
|
|
|
|
} else if (ThisLcb == ThisFcb->Vcb->RootLcb) {
|
|
|
|
AttributeFlags = (USHORT)(ThisFcb->Vcb->RootIndexScb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK);
|
|
|
|
} else if (ThisLcb != NULL) {
|
|
|
|
AttributeFlags = (USHORT)(ThisLcb->Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK);
|
|
|
|
} else if (IsCompressed( &ThisFcb->Info )) {
|
|
|
|
AttributeFlags = COMPRESSION_FORMAT_LZNT1 - 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is a supersede we need to check whether to propagate
|
|
// the sparse bit.
|
|
//
|
|
|
|
if ((AllocationSize != 0) && ARGUMENT_PRESENT( PreviousFlags )) {
|
|
|
|
SetFlag( AttributeFlags, FlagOn( *PreviousFlags, ATTRIBUTE_FLAG_SPARSE ));
|
|
}
|
|
|
|
#ifdef BRIANDBG
|
|
if (!ARGUMENT_PRESENT( PreviousFlags ) &&
|
|
!FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
|
|
(ThisScb->AttributeTypeCode == $DATA) &&
|
|
(NtfsCreateAllSparse)) {
|
|
|
|
SetFlag( AttributeFlags, ATTRIBUTE_FLAG_SPARSE );
|
|
|
|
if (!FlagOn( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_SPARSE_FILE )) {
|
|
|
|
ASSERTMSG( "conflict with flush",
|
|
NtfsIsSharedFcb( ThisFcb ) ||
|
|
(ThisFcb->PagingIoResource != NULL &&
|
|
NtfsIsSharedFcbPagingIo( ThisFcb )) );
|
|
|
|
SetFlag( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_SPARSE_FILE );
|
|
SetFlag( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_FILE_ATTR );
|
|
}
|
|
|
|
//
|
|
// Set the FastIo state.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader( ThisScb );
|
|
ThisScb->Header.IsFastIoPossible = NtfsIsFastIoPossible( ThisScb );
|
|
NtfsReleaseFsrtlHeader( ThisScb );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// If we are creating a sparse or compressed stream then set the size to a
|
|
// compression unit boundary.
|
|
//
|
|
|
|
if (FlagOn( AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK | ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
ULONG CompressionUnit = BytesFromClusters( ThisScb->Vcb, 1 << NTFS_CLUSTERS_PER_COMPRESSION );
|
|
|
|
if (ThisScb->Vcb->SparseFileUnit < CompressionUnit) {
|
|
|
|
CompressionUnit = ThisScb->Vcb->SparseFileUnit;
|
|
}
|
|
|
|
AllocationSize = BlockAlign( AllocationSize, (LONG)CompressionUnit );
|
|
}
|
|
|
|
//
|
|
// We lookup that attribute again and it better not be there.
|
|
// We need the file record in order to know whether the attribute
|
|
// is resident or not.
|
|
//
|
|
|
|
if (ForceNonresident || (AllocationSize != 0)) {
|
|
|
|
DebugTrace( 0, Dbg, ("Create non-resident attribute\n") );
|
|
|
|
//
|
|
// If the file is sparse then set the allocation size to zero
|
|
// and add a sparse range after this call.
|
|
//
|
|
|
|
if (!NtfsAllocateAttribute( IrpContext,
|
|
ThisScb,
|
|
ThisScb->AttributeTypeCode,
|
|
&ThisScb->AttributeName,
|
|
AttributeFlags,
|
|
FALSE,
|
|
LogIt,
|
|
(FlagOn( AttributeFlags, ATTRIBUTE_FLAG_SPARSE ) ?
|
|
0 :
|
|
AllocationSize),
|
|
NULL )) {
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_LARGE_ALLOCATION );
|
|
}
|
|
|
|
//
|
|
// Now add the sparse allocation for a sparse file if the size is
|
|
// non-zero.
|
|
//
|
|
|
|
if (FlagOn( AttributeFlags, ATTRIBUTE_FLAG_SPARSE ) &&
|
|
(AllocationSize != 0)) {
|
|
|
|
//
|
|
// If the sparse flag is set then we better be doing a supersede
|
|
// with logging enabled.
|
|
//
|
|
|
|
ASSERT( LogIt );
|
|
NtfsAddSparseAllocation( IrpContext,
|
|
NULL,
|
|
ThisScb,
|
|
0,
|
|
AllocationSize );
|
|
}
|
|
|
|
SetFlag( ThisScb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
|
|
|
|
} else {
|
|
|
|
//
|
|
// Update the quota if this is a user stream.
|
|
//
|
|
|
|
if (FlagOn( ThisScb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) {
|
|
|
|
LONGLONG Delta = NtfsResidentStreamQuota( ThisFcb->Vcb );
|
|
|
|
NtfsConditionallyUpdateQuota( IrpContext,
|
|
ThisFcb,
|
|
&Delta,
|
|
LogIt,
|
|
TRUE );
|
|
}
|
|
|
|
NtfsCreateAttributeWithValue( IrpContext,
|
|
ThisFcb,
|
|
ThisScb->AttributeTypeCode,
|
|
&ThisScb->AttributeName,
|
|
NULL,
|
|
(ULONG) AllocationSize,
|
|
AttributeFlags,
|
|
NULL,
|
|
LogIt,
|
|
&AttrContext );
|
|
|
|
ThisAttribute = NtfsFoundAttribute( &AttrContext );
|
|
|
|
}
|
|
|
|
//
|
|
// Clear the header initialized bit and read the sizes from the
|
|
// disk.
|
|
//
|
|
|
|
ClearFlag( ThisScb->ScbState, SCB_STATE_HEADER_INITIALIZED );
|
|
NtfsUpdateScbFromAttribute( IrpContext,
|
|
ThisScb,
|
|
ThisAttribute );
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCreateAttribute );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateAttribute: Exit\n") );
|
|
}
|
|
|
|
return;
|
|
|
|
UNREFERENCED_PARAMETER( PreviousFlags );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsRemoveDataAttributes (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB ThisFcb,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN ULONG LastFileNameOffset,
|
|
IN ULONG CreateFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to remove (or mark for delete) all of the named
|
|
data attributes on a file. This is done during an overwrite
|
|
or supersede operation.
|
|
|
|
Arguments:
|
|
|
|
Context - Pointer to the IrpContext to be queued to the Fsp
|
|
|
|
ThisFcb - This is the Fcb for the file in question.
|
|
|
|
ThisLcb - This is the Lcb used to reach this Fcb (if specified).
|
|
|
|
FileObject - This is the file object for the file.
|
|
|
|
LastFileNameOffset - This is the offset of the file in the full name.
|
|
|
|
CreateFlags - Indicates if this open is being performed by file id.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
|
PATTRIBUTE_RECORD_HEADER Attribute;
|
|
ATTRIBUTE_TYPE_CODE TypeCode = $DATA;
|
|
|
|
UNICODE_STRING AttributeName;
|
|
PSCB ThisScb;
|
|
|
|
BOOLEAN MoreToGo;
|
|
|
|
ASSERT_EXCLUSIVE_FCB( ThisFcb );
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
NtfsInitializeAttributeContext( &Context );
|
|
|
|
//
|
|
// Enumerate all of the attributes with the matching type code
|
|
//
|
|
|
|
MoreToGo = NtfsLookupAttributeByCode( IrpContext,
|
|
ThisFcb,
|
|
&ThisFcb->FileReference,
|
|
TypeCode,
|
|
&Context );
|
|
|
|
while (MoreToGo) {
|
|
|
|
//
|
|
// Point to the current attribute.
|
|
//
|
|
|
|
Attribute = NtfsFoundAttribute( &Context );
|
|
|
|
//
|
|
// We only look at named data attributes.
|
|
//
|
|
|
|
if (Attribute->NameLength != 0) {
|
|
|
|
//
|
|
// Construct the name and find the Scb for the attribute.
|
|
//
|
|
|
|
AttributeName.Buffer = (PWSTR) Add2Ptr( Attribute, Attribute->NameOffset );
|
|
AttributeName.MaximumLength = AttributeName.Length = Attribute->NameLength * sizeof( WCHAR );
|
|
|
|
ThisScb = NtfsCreateScb( IrpContext,
|
|
ThisFcb,
|
|
TypeCode,
|
|
&AttributeName,
|
|
FALSE,
|
|
NULL );
|
|
|
|
//
|
|
// If there is an open handle on this file, we simply mark
|
|
// the Scb as delete pending.
|
|
//
|
|
|
|
if (ThisScb->CleanupCount != 0) {
|
|
|
|
SetFlag( ThisScb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
|
|
|
|
//
|
|
// Otherwise we remove the attribute and mark the Scb as
|
|
// deleted. The Scb will be cleaned up when the Fcb is
|
|
// cleaned up.
|
|
//
|
|
|
|
} else {
|
|
|
|
NtfsDeleteAttributeRecord( IrpContext,
|
|
ThisFcb,
|
|
(DELETE_LOG_OPERATION |
|
|
DELETE_RELEASE_FILE_RECORD |
|
|
DELETE_RELEASE_ALLOCATION),
|
|
&Context );
|
|
|
|
SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
|
|
|
|
//
|
|
// If this is a named stream, then report this to the dir notify
|
|
// package.
|
|
//
|
|
|
|
if (!FlagOn( CreateFlags, CREATE_FLAG_OPEN_BY_ID ) &&
|
|
(ThisScb->Vcb->NotifyCount != 0) &&
|
|
(ThisScb->AttributeName.Length != 0) &&
|
|
(ThisScb->AttributeTypeCode == TypeCode)) {
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
ThisFcb->Vcb,
|
|
&FileObject->FileName,
|
|
LastFileNameOffset,
|
|
&ThisScb->AttributeName,
|
|
((ARGUMENT_PRESENT( ThisLcb ) &&
|
|
(ThisLcb->Scb->ScbType.Index.NormalizedName.Length != 0)) ?
|
|
&ThisLcb->Scb->ScbType.Index.NormalizedName :
|
|
NULL),
|
|
FILE_NOTIFY_CHANGE_STREAM_NAME,
|
|
FILE_ACTION_REMOVED_STREAM,
|
|
NULL );
|
|
}
|
|
|
|
//
|
|
// Since we have marked this stream as deleted then we need to checkpoint so
|
|
// that we can uninitialize the Scb. Otherwise some stray operation may
|
|
// attempt to operate on the Scb.
|
|
//
|
|
|
|
ThisScb->ValidDataToDisk =
|
|
ThisScb->Header.AllocationSize.QuadPart =
|
|
ThisScb->Header.FileSize.QuadPart =
|
|
ThisScb->Header.ValidDataLength.QuadPart = 0;
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
ThisScb->AttributeTypeCode = $UNUSED;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the next attribute.
|
|
//
|
|
|
|
MoreToGo = NtfsLookupNextAttributeByCode( IrpContext,
|
|
ThisFcb,
|
|
TypeCode,
|
|
&Context );
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsRemoveReparsePoint (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB ThisFcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to remove the reparse point that exists in a file.
|
|
|
|
Arguments:
|
|
|
|
Context - Pointer to the IrpContext to be queued to the Fsp
|
|
|
|
ThisFcb - This is the Fcb for the file in question.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
|
PATTRIBUTE_RECORD_HEADER Attribute;
|
|
|
|
PSCB ThisScb = NULL;
|
|
PVCB Vcb = ThisFcb->Vcb;
|
|
|
|
MAP_HANDLE MapHandle;
|
|
|
|
BOOLEAN ThisScbAcquired = FALSE;
|
|
BOOLEAN CleanupAttributeContext = FALSE;
|
|
BOOLEAN IndexAcquired = FALSE;
|
|
BOOLEAN InitializedMapHandle = FALSE;
|
|
|
|
ULONG IncomingFileAttributes = 0; // invalid value
|
|
ULONG IncomingReparsePointTag = IO_REPARSE_TAG_RESERVED_ZERO; // invalid value
|
|
|
|
ASSERT_EXCLUSIVE_FCB( ThisFcb );
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Remember the values of the file attribute flags and of the reparse tag
|
|
// for abnormal termination recovery.
|
|
//
|
|
|
|
IncomingFileAttributes = ThisFcb->Info.FileAttributes;
|
|
IncomingReparsePointTag = ThisFcb->Info.ReparsePointTag;
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
NtfsInitializeAttributeContext( &Context );
|
|
CleanupAttributeContext = TRUE;
|
|
|
|
//
|
|
// Lookup the reparse point attribute.
|
|
//
|
|
|
|
if (NtfsLookupAttributeByCode( IrpContext,
|
|
ThisFcb,
|
|
&ThisFcb->FileReference,
|
|
$REPARSE_POINT,
|
|
&Context )) {
|
|
|
|
//
|
|
// Delete the record from the reparse point index.
|
|
//
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
INDEX_KEY IndexKey;
|
|
INDEX_ROW IndexRow;
|
|
REPARSE_INDEX_KEY KeyValue;
|
|
|
|
//
|
|
// Acquire the mount table index so that the following two operations on it
|
|
// are atomic for this call.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->ReparsePointTableScb );
|
|
IndexAcquired = TRUE;
|
|
|
|
//
|
|
// Verify that this file is in the reparse point index and delete it.
|
|
//
|
|
|
|
KeyValue.FileReparseTag = ThisFcb->Info.ReparsePointTag;
|
|
KeyValue.FileId = *(PLARGE_INTEGER)&ThisFcb->FileReference;
|
|
|
|
IndexKey.Key = (PVOID)&KeyValue;
|
|
IndexKey.KeyLength = sizeof(KeyValue);
|
|
|
|
NtOfsInitializeMapHandle( &MapHandle );
|
|
InitializedMapHandle = TRUE;
|
|
|
|
//
|
|
// NtOfsFindRecord will return an error status if the key is not found.
|
|
//
|
|
|
|
Status = NtOfsFindRecord( IrpContext,
|
|
Vcb->ReparsePointTableScb,
|
|
&IndexKey,
|
|
&IndexRow,
|
|
&MapHandle,
|
|
NULL );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// Should not happen. The reparse point should be in the index.
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("Record not found in the reparse point index.\n") );
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, ThisFcb );
|
|
}
|
|
|
|
//
|
|
// Remove the entry from the reparse point index.
|
|
//
|
|
|
|
NtOfsDeleteRecords( IrpContext,
|
|
Vcb->ReparsePointTableScb,
|
|
1, // deleting one record from the index
|
|
&IndexKey );
|
|
}
|
|
|
|
//
|
|
// Point to the current attribute.
|
|
//
|
|
|
|
Attribute = NtfsFoundAttribute( &Context );
|
|
|
|
//
|
|
// If the stream is non-resident, then get a hold of an Scb for it.
|
|
//
|
|
|
|
if (!NtfsIsAttributeResident( Attribute )) {
|
|
|
|
ThisScb = NtfsCreateScb( IrpContext,
|
|
ThisFcb,
|
|
$REPARSE_POINT,
|
|
&NtfsEmptyString,
|
|
FALSE,
|
|
NULL );
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, ThisScb );
|
|
ThisScbAcquired = TRUE;
|
|
}
|
|
|
|
//
|
|
// Post the change to the Usn Journal (on errors change is backed out)
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext, ThisFcb, USN_REASON_REPARSE_POINT_CHANGE );
|
|
|
|
NtfsDeleteAttributeRecord( IrpContext,
|
|
ThisFcb,
|
|
DELETE_LOG_OPERATION |
|
|
DELETE_RELEASE_FILE_RECORD |
|
|
DELETE_RELEASE_ALLOCATION,
|
|
&Context );
|
|
|
|
//
|
|
// Set the change attribute flag.
|
|
//
|
|
|
|
ASSERTMSG( "conflict with flush",
|
|
NtfsIsSharedFcb( ThisFcb ) ||
|
|
(ThisFcb->PagingIoResource != NULL &&
|
|
NtfsIsSharedFcbPagingIo( ThisFcb )) );
|
|
|
|
SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_FILE_ATTR );
|
|
|
|
//
|
|
// Clear the reparse point bit in the duplicate file attribute.
|
|
//
|
|
|
|
ClearFlag( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT );
|
|
|
|
//
|
|
// Clear the ReparsePointTag field in the duplicate file attribute.
|
|
//
|
|
|
|
ThisFcb->Info.ReparsePointTag = IO_REPARSE_TAG_RESERVED_ZERO;
|
|
|
|
//
|
|
// Put the reparse point deletion and the attribute flag into the
|
|
// the same transaction.
|
|
//
|
|
|
|
NtfsUpdateStandardInformation( IrpContext, ThisFcb );
|
|
|
|
//
|
|
// If we have acquired the Scb then set the sizes back to zero.
|
|
// Flag that the attribute has been deleted.
|
|
// Always commit this change since we update the field in the Fcb.
|
|
//
|
|
|
|
if (ThisScbAcquired) {
|
|
|
|
ThisScb->Header.FileSize =
|
|
ThisScb->Header.ValidDataLength =
|
|
ThisScb->Header.AllocationSize = Li0;
|
|
}
|
|
|
|
//
|
|
// Since we've been called from NtfsOverwriteAttr before
|
|
// NtfsRemoveDataAttributes gets called, we need to make sure
|
|
// that if we're holding the Mft, we drop itwhen we checkpoint.
|
|
// Otherwise we have a potential deadlock when
|
|
// NtfsRemoveDataAttributes tries to acquire the quota index
|
|
// while holding the Mft.
|
|
//
|
|
|
|
if ((Vcb->MftScb != NULL) &&
|
|
(Vcb->MftScb->Fcb->ExclusiveFcbLinks.Flink != NULL) &&
|
|
NtfsIsExclusiveScb( Vcb->MftScb )) {
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RELEASE_MFT );
|
|
}
|
|
|
|
//
|
|
// Checkpoint the Txn to commit the changes.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
ClearFlag( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
|
|
if (ThisScbAcquired) {
|
|
|
|
//
|
|
// Set the Scb flag to indicate that the attribute is gone.
|
|
//
|
|
|
|
ThisScb->AttributeTypeCode = $UNUSED;
|
|
SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (ThisScbAcquired) {
|
|
|
|
NtfsReleaseScb( IrpContext, ThisScb );
|
|
}
|
|
|
|
if (CleanupAttributeContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
}
|
|
|
|
//
|
|
// Release the reparse point index Scb and the map handle.
|
|
//
|
|
|
|
if (IndexAcquired) {
|
|
|
|
NtfsReleaseScb( IrpContext, Vcb->ReparsePointTableScb );
|
|
}
|
|
|
|
if (InitializedMapHandle) {
|
|
|
|
NtOfsReleaseMap( IrpContext, &MapHandle );
|
|
}
|
|
|
|
//
|
|
// Need to roll-back the value of the file attributes and the reparse point
|
|
// flag in case of problems.
|
|
//
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
ThisFcb->Info.FileAttributes = IncomingFileAttributes;
|
|
ThisFcb->Info.ReparsePointTag = IncomingReparsePointTag;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
VOID
|
|
NtfsReplaceAttribute (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PFCB ThisFcb,
|
|
IN PSCB ThisScb,
|
|
IN PLCB ThisLcb,
|
|
IN LONGLONG AllocationSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to replace an existing attribute with
|
|
an attribute of the given allocation size. This routine will
|
|
handle the case whether the existing attribute is resident
|
|
or non-resident and the resulting attribute is resident or
|
|
non-resident.
|
|
|
|
There are two cases to consider. The first is the case where the
|
|
attribute is currently non-resident. In this case we will always
|
|
leave the attribute non-resident regardless of the new allocation
|
|
size. The argument being that the file will probably be used
|
|
as it was before. In this case we will add or delete allocation.
|
|
The second case is where the attribute is currently resident. In
|
|
This case we will remove the old attribute and add a new one.
|
|
|
|
Arguments:
|
|
|
|
IrpSp - This is the Irp stack location for this request.
|
|
|
|
ThisFcb - This is the Fcb for the file being opened.
|
|
|
|
ThisScb - This is the Scb for the given attribute.
|
|
|
|
ThisLcb - This is the Lcb via which this file is created. It
|
|
is used to propagate compression info.
|
|
|
|
AllocationSize - This is the new allocation size.
|
|
|
|
Return Value:
|
|
|
|
None. This routine will raise.
|
|
|
|
--*/
|
|
|
|
{
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsReplaceAttribute: Entered\n") );
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Initialize the Scb if needed.
|
|
//
|
|
|
|
if (!FlagOn( ThisScb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
|
|
|
|
NtfsUpdateScbFromAttribute( IrpContext, ThisScb, NULL );
|
|
}
|
|
|
|
NtfsSnapshotScb( IrpContext, ThisScb );
|
|
|
|
//
|
|
// If the attribute is resident, simply remove the old attribute and create
|
|
// a new one.
|
|
//
|
|
|
|
if (FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
USHORT AttributeFlags;
|
|
|
|
//
|
|
// Find the attribute on the disk.
|
|
//
|
|
|
|
NtfsLookupAttributeForScb( IrpContext,
|
|
ThisScb,
|
|
NULL,
|
|
&AttrContext );
|
|
|
|
AttributeFlags = ThisScb->AttributeFlags;
|
|
|
|
NtfsDeleteAttributeRecord( IrpContext,
|
|
ThisFcb,
|
|
DELETE_LOG_OPERATION |
|
|
DELETE_RELEASE_FILE_RECORD |
|
|
DELETE_RELEASE_ALLOCATION,
|
|
&AttrContext );
|
|
|
|
//
|
|
// Set all the attribute sizes to zero.
|
|
//
|
|
|
|
ThisScb->ValidDataToDisk =
|
|
ThisScb->Header.AllocationSize.QuadPart =
|
|
ThisScb->Header.ValidDataLength.QuadPart =
|
|
ThisScb->Header.FileSize.QuadPart = 0;
|
|
ThisScb->TotalAllocated = 0;
|
|
|
|
//
|
|
// Create a stream file for the attribute in order to
|
|
// truncate the cache. Set the initialized bit in
|
|
// the Scb so we don't go to disk, but clear it afterwords.
|
|
//
|
|
|
|
if ((ThisScb->NonpagedScb->SegmentObject.DataSectionObject != NULL) ||
|
|
#ifdef COMPRESS_ON_WIRE
|
|
(ThisScb->Header.FileObjectC != NULL))
|
|
#else
|
|
FALSE
|
|
#endif
|
|
) {
|
|
|
|
NtfsCreateInternalAttributeStream( IrpContext,
|
|
ThisScb,
|
|
FALSE,
|
|
&NtfsInternalUseFile[REPLACEATTRIBUTE_FILE_NUMBER] );
|
|
|
|
NtfsSetBothCacheSizes( ThisScb->FileObject,
|
|
(PCC_FILE_SIZES)&ThisScb->Header.AllocationSize,
|
|
ThisScb );
|
|
}
|
|
|
|
//
|
|
// Call our create attribute routine.
|
|
//
|
|
|
|
NtfsCreateAttribute( IrpContext,
|
|
IrpSp,
|
|
ThisFcb,
|
|
ThisScb,
|
|
ThisLcb,
|
|
AllocationSize,
|
|
TRUE,
|
|
FALSE,
|
|
&AttributeFlags );
|
|
|
|
//
|
|
// Otherwise the attribute will stay non-resident, we simply need to
|
|
// add or remove allocation.
|
|
//
|
|
|
|
} else {
|
|
|
|
ULONG AllocationUnit;
|
|
|
|
//
|
|
// Create an internal attribute stream for the file.
|
|
//
|
|
|
|
if ((ThisScb->NonpagedScb->SegmentObject.DataSectionObject != NULL) ||
|
|
#ifdef COMPRESS_ON_WIRE
|
|
(ThisScb->Header.FileObjectC != NULL)
|
|
#else
|
|
FALSE
|
|
#endif
|
|
) {
|
|
|
|
NtfsCreateInternalAttributeStream( IrpContext,
|
|
ThisScb,
|
|
FALSE,
|
|
&NtfsInternalUseFile[REPLACEATTRIBUTE2_FILE_NUMBER] );
|
|
}
|
|
|
|
//
|
|
// If the file is sparse or compressed then always round the
|
|
// new size to a compression unit boundary. Otherwise round
|
|
// to a cluster boundary.
|
|
//
|
|
|
|
AllocationUnit = ThisScb->Vcb->BytesPerCluster;
|
|
|
|
if (FlagOn( ThisScb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK | ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
ASSERT( ThisScb->CompressionUnit != 0 );
|
|
AllocationUnit = ThisScb->CompressionUnit;
|
|
}
|
|
|
|
AllocationSize = BlockAlign( AllocationSize, (LONG)AllocationUnit );
|
|
|
|
//
|
|
// Set the file size and valid data size to zero.
|
|
//
|
|
|
|
ThisScb->ValidDataToDisk = 0;
|
|
ThisScb->Header.ValidDataLength = Li0;
|
|
ThisScb->Header.FileSize = Li0;
|
|
|
|
DebugTrace( 0, Dbg, ("AllocationSize -> %016I64x\n", AllocationSize) );
|
|
|
|
//
|
|
// Write these changes to the file
|
|
//
|
|
|
|
//
|
|
// If the attribute is currently compressed or sparse then go ahead and discard
|
|
// all of the allocation.
|
|
//
|
|
|
|
if (FlagOn( ThisScb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK | ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
NtfsDeleteAllocation( IrpContext,
|
|
ThisScb->FileObject,
|
|
ThisScb,
|
|
0,
|
|
MAXLONGLONG,
|
|
TRUE,
|
|
TRUE );
|
|
|
|
//
|
|
// Checkpoint the current transaction so we have these clusters
|
|
// available again.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// If the user doesn't want this stream to be compressed then
|
|
// remove the entire stream and recreate it non-compressed. If
|
|
// the stream is currently sparse and the new file size
|
|
// is zero then also create the stream non-sparse.
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) ||
|
|
(FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION ) &&
|
|
!FlagOn( ThisScb->ScbState, SCB_STATE_COMPRESSION_CHANGE )) ||
|
|
(FlagOn( ThisScb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE ) &&
|
|
(AllocationSize == 0))) {
|
|
|
|
//
|
|
// We may need to preserve one or the other of the sparse/compressed
|
|
// flags.
|
|
//
|
|
|
|
USHORT PreviousFlags = ThisScb->AttributeFlags;
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )) {
|
|
|
|
PreviousFlags = 0;
|
|
|
|
} else {
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION )) {
|
|
|
|
ClearFlag( PreviousFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK );
|
|
}
|
|
|
|
if ((AllocationSize == 0) &&
|
|
FlagOn( ThisScb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
ClearFlag( PreviousFlags, ATTRIBUTE_FLAG_SPARSE );
|
|
}
|
|
}
|
|
|
|
NtfsLookupAttributeForScb( IrpContext,
|
|
ThisScb,
|
|
NULL,
|
|
&AttrContext );
|
|
|
|
NtfsDeleteAttributeRecord( IrpContext,
|
|
ThisFcb,
|
|
DELETE_LOG_OPERATION |
|
|
DELETE_RELEASE_FILE_RECORD |
|
|
DELETE_RELEASE_ALLOCATION,
|
|
&AttrContext );
|
|
|
|
//
|
|
// Call our create attribute routine.
|
|
//
|
|
|
|
NtfsCreateAttribute( IrpContext,
|
|
IrpSp,
|
|
ThisFcb,
|
|
ThisScb,
|
|
ThisLcb,
|
|
AllocationSize,
|
|
TRUE,
|
|
TRUE,
|
|
&PreviousFlags );
|
|
|
|
//
|
|
// Since the attribute may have changed state we need to
|
|
// checkpoint.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now if the file allocation is being increased then we need to only add allocation
|
|
// to the attribute
|
|
//
|
|
|
|
if (ThisScb->Header.AllocationSize.QuadPart < AllocationSize) {
|
|
|
|
NtfsAddAllocation( IrpContext,
|
|
ThisScb->FileObject,
|
|
ThisScb,
|
|
LlClustersFromBytes( ThisScb->Vcb, ThisScb->Header.AllocationSize.QuadPart ),
|
|
LlClustersFromBytes( ThisScb->Vcb, AllocationSize - ThisScb->Header.AllocationSize.QuadPart ),
|
|
FALSE,
|
|
NULL );
|
|
//
|
|
// Otherwise the allocation is being decreased so we need to delete some allocation
|
|
//
|
|
|
|
} else if (ThisScb->Header.AllocationSize.QuadPart > AllocationSize) {
|
|
|
|
NtfsDeleteAllocation( IrpContext,
|
|
ThisScb->FileObject,
|
|
ThisScb,
|
|
LlClustersFromBytes( ThisScb->Vcb, AllocationSize ),
|
|
MAXLONGLONG,
|
|
TRUE,
|
|
TRUE );
|
|
}
|
|
|
|
//
|
|
// We always unitialize the cache size to zero and write the new
|
|
// file size to disk.
|
|
//
|
|
|
|
NtfsWriteFileSizes( IrpContext,
|
|
ThisScb,
|
|
&ThisScb->Header.ValidDataLength.QuadPart,
|
|
FALSE,
|
|
TRUE,
|
|
TRUE );
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
if (ThisScb->FileObject != NULL) {
|
|
|
|
NtfsSetBothCacheSizes( ThisScb->FileObject,
|
|
(PCC_FILE_SIZES)&ThisScb->Header.AllocationSize,
|
|
ThisScb );
|
|
}
|
|
|
|
//
|
|
// Make sure the reservation bitmap shows no reserved bits.
|
|
//
|
|
|
|
if (ThisScb->ScbType.Data.ReservedBitMap != NULL) {
|
|
|
|
NtfsDeleteReservedBitmap( ThisScb );
|
|
ThisScb->ScbType.Data.TotalReserved = 0;
|
|
}
|
|
|
|
//
|
|
// Set the FastIo state.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader( ThisScb );
|
|
ThisScb->Header.IsFastIoPossible = NtfsIsFastIoPossible( ThisScb );
|
|
NtfsReleaseFsrtlHeader( ThisScb );
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsReplaceAttribute );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsReplaceAttribute: Exit\n") );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsOpenAttribute (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PVCB Vcb,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFCB ThisFcb,
|
|
IN ULONG LastFileNameOffset,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
IN SHARE_MODIFICATION_TYPE ShareModificationType,
|
|
IN TYPE_OF_OPEN TypeOfOpen,
|
|
IN LOGICAL CreateFile,
|
|
IN ULONG CcbFlags,
|
|
IN PVOID NetworkInfo OPTIONAL,
|
|
IN OUT PSCB *ThisScb,
|
|
OUT PCCB *ThisCcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine does the work of creating the Scb and updating the
|
|
ShareAccess in the Fcb. It also initializes the Scb if neccessary
|
|
and creates Ccb. Its final job is to set the file object type of
|
|
open.
|
|
|
|
Arguments:
|
|
|
|
IrpSp - This is the stack location for this volume. We use it to get the
|
|
file object, granted access and share access for this open.
|
|
|
|
Vcb - Vcb for this volume.
|
|
|
|
ThisLcb - This is the Lcb to the Fcb for the file being opened. Not present
|
|
if this is an open by id.
|
|
|
|
ThisFcb - This is the Fcb for this file.
|
|
|
|
LastFileNameOffset - This is the offset in the full path of the final component.
|
|
|
|
AttrName - This is the attribute name to open.
|
|
|
|
AttrTypeCode - This is the type code for the attribute being opened.
|
|
|
|
ShareModificationType - This indicates how we should modify the
|
|
current share modification on the Fcb.
|
|
|
|
TypeOfOpen - This indicates how this attribute is being opened.
|
|
|
|
CreateFile - Indicates if we are in the create file path.
|
|
|
|
CcbFlags - This is the flag field for the Ccb.
|
|
|
|
NetworkInfo - If specified then this open is on behalf of a fast query
|
|
and we don't want to increment the counts or modify the share
|
|
access on the file.
|
|
|
|
ThisScb - If this points to a non-NULL value, it is the Scb to use. Otherwise we
|
|
store the Scb we create here.
|
|
|
|
ThisCcb - Address to store address of created Ccb.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Indicating the outcome of opening this attribute.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
BOOLEAN RemoveShareAccess = FALSE;
|
|
ACCESS_MASK GrantedAccess;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsOpenAttribute: Entered\n") );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Remember the granted access.
|
|
//
|
|
|
|
GrantedAccess = IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess;
|
|
|
|
//
|
|
// Create the Scb for this attribute if it doesn't exist.
|
|
//
|
|
|
|
if (*ThisScb == NULL) {
|
|
|
|
DebugTrace( 0, Dbg, ("Looking for Scb\n") );
|
|
|
|
*ThisScb = NtfsCreateScb( IrpContext,
|
|
ThisFcb,
|
|
AttrTypeCode,
|
|
&AttrName,
|
|
FALSE,
|
|
NULL );
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("ThisScb -> %08lx\n", *ThisScb) );
|
|
DebugTrace( 0, Dbg, ("ThisLcb -> %08lx\n", ThisLcb) );
|
|
|
|
//
|
|
// If this Scb is delete pending, we return an error.
|
|
//
|
|
|
|
if (FlagOn( (*ThisScb)->ScbState, SCB_STATE_DELETE_ON_CLOSE )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Scb delete is pending\n") );
|
|
|
|
Status = STATUS_DELETE_PENDING;
|
|
try_return( NOTHING );
|
|
}
|
|
|
|
//
|
|
// Skip all of the operations below if the user is doing a fast
|
|
// path open.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT( NetworkInfo )) {
|
|
|
|
//
|
|
// If this caller wanted a filter oplock and the cleanup count
|
|
// is non-zero then fail the request.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_RESERVE_OPFILTER )) {
|
|
|
|
if (SafeNodeType( *ThisScb ) != NTFS_NTC_SCB_DATA) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
try_return( NOTHING );
|
|
|
|
//
|
|
// This must be the only open on the file and the requested
|
|
// access must be FILE_READ/WRITE_ATTRIBUTES and the
|
|
// share access must share with everyone.
|
|
//
|
|
|
|
} else if (((*ThisScb)->CleanupCount != 0) ||
|
|
(FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess,
|
|
~(FILE_READ_ATTRIBUTES))) ||
|
|
((IrpSp->Parameters.Create.ShareAccess &
|
|
(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)) !=
|
|
(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE))) {
|
|
|
|
Status = STATUS_OPLOCK_NOT_GRANTED;
|
|
try_return( NOTHING );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update the share access structure.
|
|
//
|
|
|
|
//
|
|
// Case on the requested share modification value.
|
|
//
|
|
|
|
switch (ShareModificationType) {
|
|
|
|
case UpdateShareAccess :
|
|
|
|
DebugTrace( 0, Dbg, ("Updating share access\n") );
|
|
|
|
IoUpdateShareAccess( IrpSp->FileObject,
|
|
&(*ThisScb)->ShareAccess );
|
|
break;
|
|
|
|
case SetShareAccess :
|
|
|
|
DebugTrace( 0, Dbg, ("Setting share access\n") );
|
|
|
|
//
|
|
// This case is when this is the first open for the file
|
|
// and we simply set the share access.
|
|
//
|
|
|
|
IoSetShareAccess( GrantedAccess,
|
|
IrpSp->Parameters.Create.ShareAccess,
|
|
IrpSp->FileObject,
|
|
&(*ThisScb)->ShareAccess );
|
|
break;
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
case RecheckShareAccess :
|
|
|
|
DebugTrace( 0, Dbg, ("Rechecking share access\n") );
|
|
|
|
ASSERT( NT_SUCCESS( IoCheckShareAccess( GrantedAccess,
|
|
IrpSp->Parameters.Create.ShareAccess,
|
|
IrpSp->FileObject,
|
|
&(*ThisScb)->ShareAccess,
|
|
FALSE )));
|
|
#endif
|
|
default:
|
|
|
|
DebugTrace( 0, Dbg, ("Checking share access\n") );
|
|
|
|
//
|
|
// For this case we need to check the share access and
|
|
// fail this request if access is denied.
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status = IoCheckShareAccess( GrantedAccess,
|
|
IrpSp->Parameters.Create.ShareAccess,
|
|
IrpSp->FileObject,
|
|
&(*ThisScb)->ShareAccess,
|
|
TRUE ))) {
|
|
|
|
try_return( NOTHING );
|
|
}
|
|
}
|
|
|
|
RemoveShareAccess = TRUE;
|
|
|
|
//
|
|
// If this happens to be the first time we see write access on this
|
|
// Scb, then we need to remember it, and check if we have a disk full
|
|
// condition.
|
|
//
|
|
|
|
if (IrpSp->FileObject->WriteAccess &&
|
|
!FlagOn((*ThisScb)->ScbState, SCB_STATE_WRITE_ACCESS_SEEN) &&
|
|
(SafeNodeType( (*ThisScb) ) == NTFS_NTC_SCB_DATA)) {
|
|
|
|
if ((*ThisScb)->ScbType.Data.TotalReserved != 0) {
|
|
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
|
|
//
|
|
// Does this Scb have reserved space that causes us to exceed the free
|
|
// space on the volume?
|
|
//
|
|
|
|
if (((LlClustersFromBytes(Vcb, (*ThisScb)->ScbType.Data.TotalReserved) + Vcb->TotalReserved) >
|
|
Vcb->FreeClusters)) {
|
|
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
|
|
try_return( Status = STATUS_DISK_FULL );
|
|
}
|
|
|
|
//
|
|
// Otherwise tally in the reserved space now for this Scb, and
|
|
// remember that we have seen write access.
|
|
//
|
|
|
|
Vcb->TotalReserved += LlClustersFromBytes(Vcb, (*ThisScb)->ScbType.Data.TotalReserved);
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
}
|
|
|
|
SetFlag( (*ThisScb)->ScbState, SCB_STATE_WRITE_ACCESS_SEEN );
|
|
}
|
|
|
|
//
|
|
// Create the Ccb and put the remaining name in it.
|
|
//
|
|
|
|
*ThisCcb = NtfsCreateCcb( IrpContext,
|
|
ThisFcb,
|
|
*ThisScb,
|
|
(BOOLEAN)(AttrTypeCode == $INDEX_ALLOCATION),
|
|
ThisFcb->EaModificationCount,
|
|
CcbFlags,
|
|
IrpSp->FileObject,
|
|
LastFileNameOffset );
|
|
|
|
if (FlagOn( ThisFcb->Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED ) &&
|
|
FlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_FOR_FREE_SPACE_QUERY )) {
|
|
|
|
//
|
|
// Get the owner id of the calling thread. This must be done at
|
|
// create time since that is the only time the owner is valid.
|
|
//
|
|
|
|
(*ThisCcb)->OwnerId = NtfsGetCallersUserId( IrpContext );
|
|
}
|
|
|
|
//
|
|
// Link the Ccb into the Lcb.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( ThisLcb )) {
|
|
|
|
NtfsLinkCcbToLcb( IrpContext, ThisFcb, *ThisCcb, ThisLcb );
|
|
}
|
|
|
|
//
|
|
// Update the Fcb delete counts if necessary.
|
|
//
|
|
|
|
if (RemoveShareAccess) {
|
|
|
|
//
|
|
// Update the count in the Fcb and store a flag in the Ccb
|
|
// if the user is not sharing the file for deletes. We only
|
|
// set these values if the user is accessing the file
|
|
// for read/write/delete access. The I/O system ignores
|
|
// the sharing mode unless the file is opened with one
|
|
// of these accesses.
|
|
//
|
|
|
|
if (FlagOn( GrantedAccess, NtfsAccessDataFlags )
|
|
&& !FlagOn( IrpSp->Parameters.Create.ShareAccess,
|
|
FILE_SHARE_DELETE )) {
|
|
|
|
ThisFcb->FcbDenyDelete += 1;
|
|
SetFlag( (*ThisCcb)->Flags, CCB_FLAG_DENY_DELETE );
|
|
}
|
|
|
|
//
|
|
// Do the same for the file delete count for any user
|
|
// who opened the file as a file and requested delete access.
|
|
//
|
|
|
|
if (FlagOn( (*ThisCcb)->Flags, CCB_FLAG_OPEN_AS_FILE )
|
|
&& FlagOn( GrantedAccess,DELETE )) {
|
|
|
|
ThisFcb->FcbDeleteFile += 1;
|
|
SetFlag( (*ThisCcb)->Flags, CCB_FLAG_DELETE_FILE | CCB_FLAG_DELETE_ACCESS );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Let our cleanup routine undo the share access change now.
|
|
//
|
|
|
|
RemoveShareAccess = FALSE;
|
|
|
|
//
|
|
// Increment the cleanup and close counts
|
|
//
|
|
|
|
NtfsIncrementCleanupCounts( *ThisScb,
|
|
ThisLcb,
|
|
BooleanFlagOn( IrpSp->FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ));
|
|
|
|
NtfsIncrementCloseCounts( *ThisScb,
|
|
BooleanFlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ),
|
|
(BOOLEAN) IsFileObjectReadOnly( IrpSp->FileObject ));
|
|
|
|
//
|
|
// If this is a user view index open, we want to set TypeOfOpen in
|
|
// time to get it copied into the Ccb.
|
|
//
|
|
|
|
if (FlagOn( (*ThisScb)->ScbState, SCB_STATE_VIEW_INDEX )) {
|
|
|
|
TypeOfOpen = UserViewIndexOpen;
|
|
}
|
|
|
|
if (TypeOfOpen != UserDirectoryOpen) {
|
|
|
|
DebugTrace( 0, Dbg, ("Updating Vcb and File object for user open\n") );
|
|
|
|
//
|
|
// Set the section object pointer if this is a data Scb
|
|
//
|
|
|
|
IrpSp->FileObject->SectionObjectPointer = &(*ThisScb)->NonpagedScb->SegmentObject;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Set the Scb encrypted bit from the Fcb.
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_DIRECTORY_ENCRYPTED )) {
|
|
|
|
SetFlag( (*ThisScb)->AttributeFlags, ATTRIBUTE_FLAG_ENCRYPTED );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the file object type.
|
|
//
|
|
|
|
NtfsSetFileObject( IrpSp->FileObject,
|
|
TypeOfOpen,
|
|
*ThisScb,
|
|
*ThisCcb );
|
|
|
|
//
|
|
// If this is a non-cached open and there are only non-cached opens
|
|
// then go ahead and try to delete the section. We may go through here
|
|
// twice due to a logfile full and on the 2nd time no longer have a section
|
|
// The filesize then is updated in the close path
|
|
// We will never flush and purge system files like the mft in this path
|
|
//
|
|
|
|
if (FlagOn( IrpSp->FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ) &&
|
|
!CreateFile &&
|
|
((*ThisScb)->AttributeTypeCode == $DATA) &&
|
|
((*ThisScb)->CleanupCount == (*ThisScb)->NonCachedCleanupCount) &&
|
|
((*ThisScb)->NonpagedScb->SegmentObject.ImageSectionObject == NULL) &&
|
|
((*ThisScb)->CompressionUnit == 0) &&
|
|
MmCanFileBeTruncated( &(*ThisScb)->NonpagedScb->SegmentObject, NULL ) &&
|
|
FlagOn( (*ThisScb)->ScbState, SCB_STATE_HEADER_INITIALIZED ) &&
|
|
!FlagOn( (*ThisScb)->Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
//
|
|
// Only do this in the Fsp so we have enough stack space for the flush.
|
|
// Also only call if we really have a datasection
|
|
//
|
|
|
|
if (((*ThisScb)->NonpagedScb->SegmentObject.DataSectionObject != NULL) &&
|
|
!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_IN_FSP )) {
|
|
|
|
NtfsRaiseToPost( IrpContext );
|
|
}
|
|
|
|
|
|
//
|
|
// Flush and purge the stream.
|
|
//
|
|
|
|
NtfsFlushAndPurgeScb( IrpContext,
|
|
*ThisScb,
|
|
(ARGUMENT_PRESENT( ThisLcb ) ?
|
|
ThisLcb->Scb :
|
|
NULL) );
|
|
}
|
|
|
|
//
|
|
// Check if we should request a filter oplock.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_RESERVE_OPFILTER )) {
|
|
|
|
FsRtlOplockFsctrl( &(*ThisScb)->ScbType.Data.Oplock,
|
|
IrpContext->OriginatingIrp,
|
|
1 );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Mark the Scb if this is a temporary file.
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_TEMPORARY )) {
|
|
|
|
SetFlag( (*ThisScb)->ScbState, SCB_STATE_TEMPORARY );
|
|
SetFlag( IrpSp->FileObject->Flags, FO_TEMPORARY_FILE );
|
|
}
|
|
|
|
try_exit: NOTHING;
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsOpenAttribute );
|
|
|
|
//
|
|
// Back out local actions on error.
|
|
//
|
|
|
|
if (AbnormalTermination()
|
|
&& RemoveShareAccess) {
|
|
|
|
IoRemoveShareAccess( IrpSp->FileObject, &(*ThisScb)->ShareAccess );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsOpenAttribute: Status -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
VOID
|
|
NtfsBackoutFailedOpensPriv (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFCB ThisFcb,
|
|
IN PSCB ThisScb,
|
|
IN PCCB ThisCcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called during an open that has failed after
|
|
modifying in-memory structures. We will repair the following
|
|
structures.
|
|
|
|
Vcb - Decrement the open counts. Check if we locked the volume.
|
|
|
|
ThisFcb - Restore he Share Access fields and decrement open counts.
|
|
|
|
ThisScb - Decrement the open counts.
|
|
|
|
ThisCcb - Remove from the Lcb and delete.
|
|
|
|
Arguments:
|
|
|
|
FileObject - This is the file object for this open.
|
|
|
|
ThisFcb - This is the Fcb for the file being opened.
|
|
|
|
ThisScb - This is the Scb for the given attribute.
|
|
|
|
ThisCcb - This is the Ccb for this open.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLCB Lcb;
|
|
PVCB Vcb = ThisFcb->Vcb;
|
|
PSCB CurrentParentScb;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsBackoutFailedOpens: Entered\n") );
|
|
|
|
//
|
|
// If there is an Scb and Ccb, we remove the share access from the
|
|
// Fcb. We also remove all of the open and unclean counts incremented
|
|
// by us.
|
|
//
|
|
|
|
//
|
|
// Remove this Ccb from the Lcb.
|
|
//
|
|
|
|
Lcb = ThisCcb->Lcb;
|
|
NtfsUnlinkCcbFromLcb( IrpContext, ThisFcb, ThisCcb );
|
|
|
|
//
|
|
// Check if we need to remove the share access for this open.
|
|
//
|
|
|
|
IoRemoveShareAccess( FileObject, &ThisScb->ShareAccess );
|
|
|
|
//
|
|
// Modify the delete counts in the Fcb.
|
|
//
|
|
|
|
if (FlagOn( ThisCcb->Flags, CCB_FLAG_DELETE_FILE )) {
|
|
|
|
ThisFcb->FcbDeleteFile -= 1;
|
|
ClearFlag( ThisCcb->Flags, CCB_FLAG_DELETE_FILE );
|
|
}
|
|
|
|
if (FlagOn( ThisCcb->Flags, CCB_FLAG_DENY_DELETE )) {
|
|
|
|
ThisFcb->FcbDenyDelete -= 1;
|
|
ClearFlag( ThisCcb->Flags, CCB_FLAG_DENY_DELETE );
|
|
}
|
|
|
|
//
|
|
// Decrement the cleanup and close counts
|
|
//
|
|
|
|
NtfsDecrementCleanupCounts( ThisScb,
|
|
Lcb,
|
|
BooleanFlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ));
|
|
|
|
//
|
|
// Trim any normalized names created in this open if no cleanup counts left
|
|
//
|
|
|
|
if (0 == ThisScb->CleanupCount ) {
|
|
|
|
switch (ThisCcb->TypeOfOpen) {
|
|
|
|
case UserDirectoryOpen :
|
|
|
|
//
|
|
// Cleanup the current scb node if it has a name
|
|
//
|
|
|
|
if (ThisScb->ScbType.Index.NormalizedName.MaximumLength > LONGNAME_THRESHOLD) {
|
|
|
|
NtfsDeleteNormalizedName( ThisScb );
|
|
}
|
|
|
|
//
|
|
// Fallthrough to deal with parents - in some case the current node failed to get a name
|
|
// but we populated a tree of long names on the way down
|
|
//
|
|
|
|
case UserFileOpen :
|
|
|
|
if (Lcb != NULL) {
|
|
CurrentParentScb = Lcb->Scb;
|
|
} else {
|
|
CurrentParentScb = NULL;
|
|
}
|
|
|
|
//
|
|
// Try to trim normalized names if the name is suff. long and we don't own the mft
|
|
// which would cause a deadlock
|
|
//
|
|
|
|
if ((CurrentParentScb != NULL) &&
|
|
(CurrentParentScb->ScbType.Index.NormalizedName.MaximumLength > LONGNAME_THRESHOLD) &&
|
|
!NtfsIsSharedScb( Vcb->MftScb )) {
|
|
|
|
NtfsTrimNormalizedNames( IrpContext, ThisFcb, CurrentParentScb);
|
|
}
|
|
break;
|
|
|
|
} // endif switch
|
|
}
|
|
|
|
NtfsDecrementCloseCounts( IrpContext,
|
|
ThisScb,
|
|
Lcb,
|
|
(BOOLEAN) BooleanFlagOn(ThisFcb->FcbState, FCB_STATE_PAGING_FILE),
|
|
(BOOLEAN) IsFileObjectReadOnly( FileObject ),
|
|
TRUE,
|
|
NULL );
|
|
|
|
//
|
|
// Now clean up the Ccb.
|
|
//
|
|
|
|
NtfsDeleteCcb( ThisFcb, &ThisCcb );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsBackoutFailedOpens: Exit\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsUpdateScbFromMemory (
|
|
IN OUT PSCB Scb,
|
|
IN POLD_SCB_SNAPSHOT ScbSizes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
All of the information from the attribute is stored in the snapshot. We process
|
|
this data identically to NtfsUpdateScbFromAttribute.
|
|
|
|
Arguments:
|
|
|
|
Scb - This is the Scb to update.
|
|
|
|
ScbSizes - This contains the sizes to store in the scb.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsUpdateScbFromMemory: Entered\n") );
|
|
|
|
//
|
|
// Check whether this is resident or nonresident
|
|
//
|
|
|
|
if (ScbSizes->Resident) {
|
|
|
|
Scb->Header.AllocationSize.QuadPart = ScbSizes->FileSize;
|
|
|
|
if (!FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) {
|
|
|
|
Scb->Header.ValidDataLength =
|
|
Scb->Header.FileSize = Scb->Header.AllocationSize;
|
|
}
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_VDL_CHANGE, SCE_FLAG_UPDATE_FROM_DISK, Scb->Header.ValidDataLength.QuadPart, 0, 0 );
|
|
}
|
|
#endif
|
|
|
|
Scb->Header.AllocationSize.LowPart =
|
|
QuadAlign( Scb->Header.AllocationSize.LowPart );
|
|
|
|
Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart;
|
|
|
|
NtfsVerifySizes( &Scb->Header );
|
|
|
|
//
|
|
// Set the resident flag in the Scb.
|
|
//
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT );
|
|
|
|
} else {
|
|
|
|
VCN FileClusters;
|
|
VCN AllocationClusters;
|
|
|
|
if (!FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) {
|
|
|
|
Scb->Header.ValidDataLength.QuadPart = ScbSizes->ValidDataLength;
|
|
Scb->Header.FileSize.QuadPart = ScbSizes->FileSize;
|
|
|
|
if (FlagOn( ScbSizes->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
|
|
Scb->ValidDataToDisk = ScbSizes->ValidDataLength;
|
|
}
|
|
}
|
|
|
|
Scb->TotalAllocated = ScbSizes->TotalAllocated;
|
|
Scb->Header.AllocationSize.QuadPart = ScbSizes->AllocationSize;
|
|
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_VDL_CHANGE, SCE_FLAG_UPDATE_FROM_DISK, Scb->Header.ValidDataLength.QuadPart, 1, 0 );
|
|
}
|
|
#endif
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT );
|
|
|
|
//
|
|
// Get the size of the compression unit.
|
|
//
|
|
|
|
ASSERT( (ScbSizes->CompressionUnit == 0) ||
|
|
(ScbSizes->CompressionUnit == NTFS_CLUSTERS_PER_COMPRESSION) ||
|
|
FlagOn( ScbSizes->AttributeFlags, ATTRIBUTE_FLAG_SPARSE ));
|
|
|
|
if ((ScbSizes->CompressionUnit != 0) &&
|
|
(ScbSizes->CompressionUnit < 31)) {
|
|
Scb->CompressionUnit = BytesFromClusters( Scb->Vcb,
|
|
1 << ScbSizes->CompressionUnit );
|
|
Scb->CompressionUnitShift = ScbSizes->CompressionUnit;
|
|
}
|
|
|
|
ASSERT( (Scb->CompressionUnit == 0) ||
|
|
(Scb->AttributeTypeCode == $INDEX_ALLOCATION) ||
|
|
NtfsIsTypeCodeCompressible( Scb->AttributeTypeCode ));
|
|
|
|
//
|
|
// Compute the clusters for the file and its allocation.
|
|
//
|
|
|
|
AllocationClusters = LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart );
|
|
|
|
if (Scb->CompressionUnit == 0) {
|
|
|
|
FileClusters = LlClustersFromBytes(Scb->Vcb, Scb->Header.FileSize.QuadPart);
|
|
|
|
} else {
|
|
|
|
FileClusters = BlockAlign( Scb->Header.FileSize.QuadPart, (LONG)Scb->CompressionUnit );
|
|
}
|
|
|
|
//
|
|
// If allocated clusters are greater than file clusters, mark
|
|
// the Scb to truncate on close.
|
|
//
|
|
|
|
if (AllocationClusters > FileClusters) {
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
|
|
}
|
|
}
|
|
|
|
Scb->AttributeFlags = ScbSizes->AttributeFlags;
|
|
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK | ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
//
|
|
// If sparse CC should flush and purge when the file is mapped to
|
|
// keep reservations accurate
|
|
//
|
|
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
|
|
SetFlag( Scb->Header.Flags2, FSRTL_FLAG2_PURGE_WHEN_MAPPED );
|
|
}
|
|
|
|
if (NtfsIsTypeCodeCompressible( Scb->AttributeTypeCode )) {
|
|
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_WRITE_COMPRESSED );
|
|
}
|
|
|
|
//
|
|
// If the attribute is resident, then we will use our current
|
|
// default.
|
|
//
|
|
|
|
if (Scb->CompressionUnit == 0) {
|
|
|
|
Scb->CompressionUnit = BytesFromClusters( Scb->Vcb, 1 << NTFS_CLUSTERS_PER_COMPRESSION );
|
|
Scb->CompressionUnitShift = NTFS_CLUSTERS_PER_COMPRESSION;
|
|
|
|
//
|
|
// Trim the compression unit for large sparse clusters.
|
|
//
|
|
|
|
while (Scb->CompressionUnit > Scb->Vcb->SparseFileUnit) {
|
|
|
|
Scb->CompressionUnit >>= 1;
|
|
Scb->CompressionUnitShift -= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the compression unit is non-zero or this is a resident file
|
|
// then set the flag in the common header for the Modified page writer.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
|
|
SetFlag( Scb->ScbState,
|
|
SCB_STATE_UNNAMED_DATA | SCB_STATE_FILE_SIZE_LOADED | SCB_STATE_HEADER_INITIALIZED );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsUpdateScbFromMemory: Exit\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsOplockPrePostIrp (
|
|
IN PVOID Context,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs any neccessary work before STATUS_PENDING is
|
|
returned with the Fsd thread. This routine is called within the
|
|
filesystem and by the oplock package. This routine will update
|
|
the originating Irp in the IrpContext and release all of the Fcbs and
|
|
paging io resources in the IrpContext.
|
|
|
|
Arguments:
|
|
|
|
Context - Pointer to the IrpContext to be queued to the Fsp
|
|
|
|
Irp - I/O Request Packet
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PIRP_CONTEXT IrpContext;
|
|
POPLOCK_CLEANUP OplockCleanup;
|
|
|
|
PAGED_CODE();
|
|
|
|
IrpContext = (PIRP_CONTEXT) Context;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT( FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_FROM_POOL ));
|
|
|
|
IrpContext->OriginatingIrp = Irp;
|
|
OplockCleanup = IrpContext->Union.OplockCleanup;
|
|
|
|
//
|
|
// Get the current Irp stack location
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
//
|
|
// Adjust the filename strings as needed.
|
|
//
|
|
|
|
if ((OplockCleanup->ExactCaseName.Buffer != OplockCleanup->OriginalFileName.Buffer) &&
|
|
(OplockCleanup->ExactCaseName.Buffer != NULL)) {
|
|
|
|
ASSERT( OplockCleanup->ExactCaseName.Length != 0 );
|
|
ASSERT( OplockCleanup->OriginalFileName.MaximumLength >= OplockCleanup->ExactCaseName.MaximumLength );
|
|
|
|
RtlCopyMemory( OplockCleanup->OriginalFileName.Buffer,
|
|
OplockCleanup->ExactCaseName.Buffer,
|
|
OplockCleanup->ExactCaseName.MaximumLength );
|
|
}
|
|
|
|
//
|
|
// Restitute the access control state to what it was when we entered the request.
|
|
//
|
|
|
|
IrpSp->Parameters.Create.SecurityContext->AccessState->RemainingDesiredAccess = OplockCleanup->RemainingDesiredAccess;
|
|
IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess = OplockCleanup->PreviouslyGrantedAccess;
|
|
IrpSp->Parameters.Create.SecurityContext->DesiredAccess = OplockCleanup->DesiredAccess;
|
|
|
|
//
|
|
// Free any buffer we allocated.
|
|
//
|
|
|
|
if ((OplockCleanup->FullFileName.Buffer != NULL) &&
|
|
(OplockCleanup->OriginalFileName.Buffer != OplockCleanup->FullFileName.Buffer)) {
|
|
|
|
NtfsFreePool( OplockCleanup->FullFileName.Buffer );
|
|
OplockCleanup->FullFileName.Buffer = NULL;
|
|
}
|
|
|
|
//
|
|
// If in the fsp restore the thread context pointer if its associated with this IrpContext since
|
|
// we're really going to post to another worker thread item.
|
|
// Non-fsp creates will continue in the same thread. We use the same test as NtfsOplockComplete
|
|
//
|
|
|
|
if ((IrpContext->Union.OplockCleanup == NULL) ||
|
|
(IrpContext->Union.OplockCleanup->CompletionContext == NULL)) {
|
|
|
|
ASSERT( FlagOn( IrpContext->State, IRP_CONTEXT_STATE_IN_FSP ) );
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL)) {
|
|
|
|
NtfsRestoreTopLevelIrp();
|
|
ClearFlag( IrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Cleanup the IrpContext.
|
|
//
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE );
|
|
NtfsCleanupIrpContext( IrpContext, FALSE );
|
|
|
|
//
|
|
// Set the file name in the file object back to it's original value.
|
|
//
|
|
|
|
OplockCleanup->FileObject->FileName = OplockCleanup->OriginalFileName;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsCreateCompletionRoutine (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp,
|
|
IN PVOID Contxt
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the completion routine for synchronous creates. It is only called if
|
|
STATUS_PENDING was returned. We return MORE_PROCESSING_REQUIRED to take
|
|
control of the Irp again and also clear the top level thread storage. We have to
|
|
do this because we could be calling this routine in an Fsp thread and are
|
|
waiting for the event in an Fsd thread.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Pointer to the file system device object.
|
|
|
|
Irp - Pointer to the Irp for this request. (This Irp will no longer
|
|
be accessible after this routine returns.)
|
|
|
|
Contxt - This is the event to signal.
|
|
|
|
Return Value:
|
|
|
|
The routine returns STATUS_MORE_PROCESSING_REQUIRED so that we can take
|
|
control of the Irp in the original thread.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
ASSERT_IRP_CONTEXT( ((PNTFS_COMPLETION_CONTEXT) Contxt)->IrpContext );
|
|
|
|
//
|
|
// Restore the thread context pointer if associated with this IrpContext.
|
|
// It is important for the create irp because we we might be completing
|
|
// the irp but take control of it again in a separate thread.
|
|
//
|
|
|
|
if (FlagOn( ((PNTFS_COMPLETION_CONTEXT) Contxt)->IrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL )) {
|
|
|
|
NtfsRestoreTopLevelIrp();
|
|
ClearFlag( ((PNTFS_COMPLETION_CONTEXT) Contxt)->IrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL );
|
|
}
|
|
|
|
KeSetEvent( &((PNTFS_COMPLETION_CONTEXT) Contxt)->Event, 0, FALSE );
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
|
|
UNREFERENCED_PARAMETER( DeviceObject );
|
|
UNREFERENCED_PARAMETER( Irp );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsCheckExistingFile (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PLCB ThisLcb OPTIONAL,
|
|
IN PFCB ThisFcb,
|
|
IN BOOLEAN Index,
|
|
IN ULONG CcbFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to check the desired access on an existing file
|
|
against the ACL's and the read-only status of the file. If we fail on
|
|
the access check, that routine will raise. Otherwise we will return a
|
|
status to indicate success or the failure cause. This routine will access
|
|
and update the PreviouslyGrantedAccess field in the security context.
|
|
|
|
Arguments:
|
|
|
|
IrpSp - This is the Irp stack location for this open.
|
|
|
|
ThisLcb - This is the Lcb used to reach the Fcb to open.
|
|
|
|
ThisFcb - This is the Fcb where the open will occur.
|
|
|
|
Index - Whether this is an index or not
|
|
|
|
CcbFlags - This is the flag field for the Ccb.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN MaximumAllowed = FALSE;
|
|
|
|
PACCESS_STATE AccessState;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Save a pointer to the access state for convenience.
|
|
//
|
|
|
|
AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState;
|
|
|
|
//
|
|
// Start by checking that there are no bits in the desired access that
|
|
// conflict with the read-only state of the file if the file is not an index
|
|
//
|
|
|
|
if (IsReadOnly( &ThisFcb->Info ) && !Index) {
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess, FILE_WRITE_DATA | FILE_APPEND_DATA )) {
|
|
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the volume itself is mounted readonly, we still let open-for-writes
|
|
// go through for legacy reasons. DELETE_ON_CLOSE is an exception.
|
|
//
|
|
|
|
if ((IsReadOnly( &ThisFcb->Info )) ||
|
|
(NtfsIsVolumeReadOnly( ThisFcb->Vcb ))) {
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_DELETE_ON_CLOSE )) {
|
|
|
|
return STATUS_CANNOT_DELETE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Otherwise we need to check the requested access vs. the allowable
|
|
// access in the ACL on the file. We will want to remember if
|
|
// MAXIMUM_ALLOWED was requested and remove the invalid bits for
|
|
// a read-only file.
|
|
//
|
|
|
|
//
|
|
// Remember if maximum allowed was requested.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess,
|
|
MAXIMUM_ALLOWED )) {
|
|
|
|
MaximumAllowed = TRUE;
|
|
}
|
|
|
|
NtfsOpenCheck( IrpContext,
|
|
ThisFcb,
|
|
(((ThisLcb != NULL) && (ThisLcb != ThisFcb->Vcb->RootLcb))
|
|
? ThisLcb->Scb->Fcb
|
|
: NULL),
|
|
IrpContext->OriginatingIrp );
|
|
|
|
//
|
|
// If this is a read-only file (not directory) and we requested maximum allowed then
|
|
// remove the invalid bits. Ditto for readonly volumes.
|
|
//
|
|
|
|
if (MaximumAllowed &&
|
|
((IsReadOnly( &ThisFcb->Info ) & !Index) ||
|
|
NtfsIsVolumeReadOnly( ThisFcb->Vcb ))) {
|
|
|
|
ClearFlag( AccessState->PreviouslyGrantedAccess,
|
|
FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD );
|
|
}
|
|
|
|
//
|
|
// We do a check here to see if we conflict with the delete status on the
|
|
// file. Right now we check if there is already an opener who has delete
|
|
// access on the file and this opener doesn't allow delete access.
|
|
// We can skip this test if the opener is not requesting read, write or
|
|
// delete access.
|
|
//
|
|
|
|
if (ThisFcb->FcbDeleteFile != 0
|
|
&& FlagOn( AccessState->PreviouslyGrantedAccess, NtfsAccessDataFlags )
|
|
&& !FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_DELETE )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCheckExistingFile: Exit\n") );
|
|
return STATUS_SHARING_VIOLATION;
|
|
}
|
|
|
|
//
|
|
// We do a check here to see if we conflict with the delete status on the
|
|
// file. If we are opening the file and requesting delete, then there can
|
|
// be no current handles which deny delete.
|
|
//
|
|
|
|
if (ThisFcb->FcbDenyDelete != 0
|
|
&& FlagOn( AccessState->PreviouslyGrantedAccess, DELETE )
|
|
&& FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
return STATUS_SHARING_VIOLATION;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsBreakBatchOplock (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PFCB ThisFcb,
|
|
IN UNICODE_STRING AttrName,
|
|
IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
|
|
OUT PSCB *ThisScb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called for each open of an existing attribute to
|
|
check for current batch oplocks on the file. We will also check
|
|
whether we will want to flush and purge this stream in the case
|
|
where only non-cached handles remain on the file. We only want
|
|
to do that in an Fsp thread because we will require every bit
|
|
of stack we can get.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
IrpSp - This is the stack location for this open.
|
|
|
|
ThisFcb - This is the Fcb for the file being opened.
|
|
|
|
AttrName - This is the attribute name in case we need to create
|
|
an Scb.
|
|
|
|
AttrTypeCode - This is the attribute type code to use to create
|
|
the Scb.
|
|
|
|
ThisScb - Address to store the Scb if found or created.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Will be either STATUS_SUCCESS or STATUS_PENDING.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN ScbExisted;
|
|
PSCB NextScb;
|
|
PLIST_ENTRY Links;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsBreakBatchOplock: Entered\n") );
|
|
|
|
//
|
|
// In general we will just break the batch oplock for the stream we
|
|
// are trying to open. However if we are trying to delete the file
|
|
// and someone has a batch oplock on a different stream which
|
|
// will cause our open to fail then we need to try to break those
|
|
// batch oplocks. Likewise if we are opening a stream and won't share
|
|
// with a file delete then we need to break any batch oplocks on the main
|
|
// stream of the file.
|
|
//
|
|
|
|
//
|
|
// Consider the case where we are opening a stream and there is a
|
|
// batch oplock on the main data stream.
|
|
//
|
|
|
|
if (AttrName.Length != 0) {
|
|
|
|
if (ThisFcb->FcbDeleteFile != 0 &&
|
|
!FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_DELETE )) {
|
|
|
|
Links = ThisFcb->ScbQueue.Flink;
|
|
|
|
while (Links != &ThisFcb->ScbQueue) {
|
|
|
|
NextScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
|
|
|
|
if (NextScb->AttributeTypeCode == $DATA &&
|
|
NextScb->AttributeName.Length == 0) {
|
|
|
|
if (FsRtlCurrentBatchOplock( &NextScb->ScbType.Data.Oplock )) {
|
|
|
|
//
|
|
// We remember if a batch oplock break is underway for the
|
|
// case where the sharing check fails.
|
|
//
|
|
|
|
Irp->IoStatus.Information = FILE_OPBATCH_BREAK_UNDERWAY;
|
|
|
|
//
|
|
// If the oplock break is pending raise can't wait and retry at the top
|
|
//
|
|
|
|
if (FsRtlCheckOplock( &NextScb->ScbType.Data.Oplock,
|
|
Irp,
|
|
(PVOID) IrpContext,
|
|
NtfsOplockComplete,
|
|
NtfsOplockPrePostIrp ) == STATUS_PENDING) {
|
|
|
|
return STATUS_WAIT_FOR_OPLOCK;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
Links = Links->Flink;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now consider the case where we are opening the main stream and want to
|
|
// delete the file but an opener on a stream is preventing us.
|
|
//
|
|
|
|
} else if (ThisFcb->FcbDenyDelete != 0 &&
|
|
FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->RemainingDesiredAccess,
|
|
MAXIMUM_ALLOWED | DELETE )) {
|
|
|
|
//
|
|
// Find all of the other data Scb and check their oplock status.
|
|
//
|
|
|
|
Links = ThisFcb->ScbQueue.Flink;
|
|
|
|
while (Links != &ThisFcb->ScbQueue) {
|
|
|
|
NextScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
|
|
|
|
if (NextScb->AttributeTypeCode == $DATA &&
|
|
NextScb->AttributeName.Length != 0) {
|
|
|
|
if (FsRtlCurrentBatchOplock( &NextScb->ScbType.Data.Oplock )) {
|
|
|
|
//
|
|
// We remember if a batch oplock break is underway for the
|
|
// case where the sharing check fails.
|
|
//
|
|
|
|
Irp->IoStatus.Information = FILE_OPBATCH_BREAK_UNDERWAY;
|
|
|
|
//
|
|
// We wait on the oplock.
|
|
//
|
|
|
|
if (FsRtlCheckOplock( &NextScb->ScbType.Data.Oplock,
|
|
Irp,
|
|
(PVOID) IrpContext,
|
|
NtfsOplockComplete,
|
|
NtfsOplockPrePostIrp ) == STATUS_PENDING) {
|
|
|
|
return STATUS_WAIT_FOR_OPLOCK;
|
|
}
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
}
|
|
}
|
|
|
|
Links = Links->Flink;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We try to find the Scb for this file.
|
|
//
|
|
|
|
*ThisScb = NtfsCreateScb( IrpContext,
|
|
ThisFcb,
|
|
AttrTypeCode,
|
|
&AttrName,
|
|
FALSE,
|
|
&ScbExisted );
|
|
|
|
//
|
|
// If there was a previous Scb, we examine the oplocks.
|
|
//
|
|
|
|
if (ScbExisted &&
|
|
(SafeNodeType( *ThisScb ) == NTFS_NTC_SCB_DATA)) {
|
|
|
|
//
|
|
// If we have to flush and purge then we want to be in the Fsp.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_IN_FSP ) &&
|
|
FlagOn( IrpSp->FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ) &&
|
|
((*ThisScb)->CleanupCount == (*ThisScb)->NonCachedCleanupCount) &&
|
|
((*ThisScb)->NonpagedScb->SegmentObject.DataSectionObject != NULL)) {
|
|
|
|
|
|
NtfsRaiseToPost( IrpContext );
|
|
}
|
|
|
|
if (FsRtlCurrentBatchOplock( &(*ThisScb)->ScbType.Data.Oplock )) {
|
|
|
|
//
|
|
// If the handle count is greater than 1 then fail this
|
|
// open now.
|
|
//
|
|
|
|
if (FlagOn( IrpSp->Parameters.Create.Options, FILE_RESERVE_OPFILTER ) &&
|
|
((*ThisScb)->CleanupCount > 1)) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_OPLOCK_NOT_GRANTED, NULL, NULL );
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("Breaking batch oplock\n") );
|
|
|
|
//
|
|
// We remember if a batch oplock break is underway for the
|
|
// case where the sharing check fails.
|
|
//
|
|
|
|
Irp->IoStatus.Information = FILE_OPBATCH_BREAK_UNDERWAY;
|
|
|
|
if (FsRtlCheckOplock( &(*ThisScb)->ScbType.Data.Oplock,
|
|
Irp,
|
|
(PVOID) IrpContext,
|
|
NtfsOplockComplete,
|
|
NtfsOplockPrePostIrp ) == STATUS_PENDING) {
|
|
|
|
return STATUS_WAIT_FOR_OPLOCK;
|
|
}
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsBreakBatchOplock: Exit - %08lx\n", STATUS_SUCCESS) );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsCompleteLargeAllocation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PLCB Lcb OPTIONAL,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN ULONG CreateFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when we need to add more allocation to a stream
|
|
being opened. This stream could have been reallocated or created with
|
|
this call but we didn't allocate all of the space in the main path.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for this open operation.
|
|
|
|
Lcb - This is the Lcb used to reach the stream being opened. Won't be
|
|
specified in the open by ID case.
|
|
|
|
Scb - This is the Scb for the stream being opened.
|
|
|
|
Ccb - This is the Ccb for the this user handle.
|
|
|
|
CreateFlags - Indicates if this handle requires delete on close and
|
|
if we created or reallocated this stream.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - the result of this operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
FILE_ALLOCATION_INFORMATION AllInfo;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Commit the current transaction and free all resources.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
NtfsReleaseAllResources( IrpContext );
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CALL_SELF );
|
|
AllInfo.AllocationSize = Irp->Overlay.AllocationSize;
|
|
|
|
Status = IoSetInformation( IoGetCurrentIrpStackLocation( Irp )->FileObject,
|
|
FileAllocationInformation,
|
|
sizeof( FILE_ALLOCATION_INFORMATION ),
|
|
&AllInfo );
|
|
|
|
ASSERT( (Scb->CompressionUnit == 0) || (Scb->Header.AllocationSize.QuadPart % Scb->CompressionUnit == 0) );
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CALL_SELF );
|
|
|
|
//
|
|
// Success! We will reacquire the Vcb quickly to undo the
|
|
// actions taken above to block access to the new file/attribute.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
NtfsAcquireExclusiveVcb( IrpContext, Scb->Vcb, TRUE );
|
|
|
|
//
|
|
// Enable access to new file.
|
|
//
|
|
|
|
if (FlagOn( CreateFlags, CREATE_FLAG_CREATE_FILE_CASE )) {
|
|
|
|
Scb->Fcb->LinkCount = 1;
|
|
|
|
if (ARGUMENT_PRESENT( Lcb )) {
|
|
|
|
ClearFlag( Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
|
|
|
|
if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
|
|
|
|
ClearFlag( Scb->Fcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Enable access to new attribute.
|
|
//
|
|
|
|
} else {
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
|
|
}
|
|
|
|
//
|
|
// If this is the DeleteOnClose case, we mark the Scb and Lcb
|
|
// appropriately.
|
|
//
|
|
|
|
if (FlagOn( CreateFlags, CREATE_FLAG_DELETE_ON_CLOSE )) {
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_DELETE_ON_CLOSE );
|
|
}
|
|
|
|
NtfsReleaseVcb( IrpContext, Scb->Vcb );
|
|
|
|
//
|
|
// Else there was some sort of error, and we need to let cleanup
|
|
// and close execute, since when we complete Create with an error
|
|
// cleanup and close would otherwise never occur. Cleanup will
|
|
// delete or truncate a file or attribute as appropriate, based on
|
|
// how we left the Fcb/Lcb or Scb above.
|
|
//
|
|
|
|
} else {
|
|
|
|
NtfsIoCallSelf( IrpContext,
|
|
IoGetCurrentIrpStackLocation( Irp )->FileObject,
|
|
IRP_MJ_CLEANUP );
|
|
|
|
NtfsIoCallSelf( IrpContext,
|
|
IoGetCurrentIrpStackLocation( Irp )->FileObject,
|
|
IRP_MJ_CLOSE );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ULONG
|
|
NtfsOpenExistingEncryptedStream (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB ThisScb,
|
|
IN PFCB CurrentFcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines with which FileDirFlags, if any, we should call
|
|
the encryption driver's create callback.
|
|
|
|
Arguments:
|
|
|
|
ThisScb - This is the Scb for the file being opened.
|
|
|
|
CurrentFcb - This is the Fcb for the file being opened.
|
|
|
|
Return Value:
|
|
|
|
ULONG - The set of flags, such as FILE_EXISTING or DIRECTORY_EXISTING that
|
|
should be passed to the encryption driver. If 0 is returned, there
|
|
is no need to call the encryption driver for this create.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG EncryptionFileDirFlags = 0;
|
|
|
|
//
|
|
// If we don't have an encryption driver then raise ACCESS_DENIED unless
|
|
// this is a directory, in which case there really isn't any encrypted data
|
|
// that we need to worry about. Consider the case where the user has
|
|
// marked a directory as encrypted and then removed the encryption driver.
|
|
// There may be unencrypted files in that directory, and there's no reason
|
|
// to prevent the user from getting to them.
|
|
//
|
|
|
|
if (!FlagOn( NtfsData.Flags, NTFS_FLAGS_ENCRYPTION_DRIVER ) &&
|
|
!IsDirectory( &CurrentFcb->Info )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_ACCESS_DENIED, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// In NT5, we have not tested with encrypted compressed files, so if we
|
|
// encounter one (perhaps NT6 created it and the user has gone back to
|
|
// an NT5 safe build) let's not allow opening it for read/write access.
|
|
// Like the test above, this is only an issue for files, not directories.
|
|
//
|
|
|
|
if (FlagOn( ThisScb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK ) &&
|
|
!IsDirectory( &CurrentFcb->Info )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_ACCESS_DENIED, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Set the appropriate flags for the 3 existing stream cases.
|
|
//
|
|
|
|
if (IsDirectory( &CurrentFcb->Info )) {
|
|
|
|
EncryptionFileDirFlags = DIRECTORY_EXISTING | STREAM_EXISTING;
|
|
|
|
} else if (IsEncrypted( &CurrentFcb->Info )) {
|
|
|
|
EncryptionFileDirFlags = FILE_EXISTING | STREAM_EXISTING | EXISTING_FILE_ENCRYPTED ;
|
|
|
|
} else {
|
|
|
|
EncryptionFileDirFlags = FILE_EXISTING | STREAM_EXISTING;
|
|
}
|
|
|
|
return EncryptionFileDirFlags;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsEncryptionCreateCallback (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PSCB ThisScb,
|
|
IN PCCB ThisCcb,
|
|
IN PFCB ParentFcb,
|
|
IN PCREATE_CONTEXT CreateContext,
|
|
IN BOOLEAN CreateNewFile
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the create callback to the encryption driver if one
|
|
is registered, and it is appropriate to do the callback. We do the
|
|
callback for the open of an existing stream that is marked as encrypted,
|
|
and for the creation of a new file/stream that will be encrypted.
|
|
|
|
There are a number of interesting cases, each of which requires its own
|
|
set of flags to be passed to the encryption engine. Some optimization may
|
|
be possible by setting and clearing individual bits for certain semi-general
|
|
cases, but at a massive cost in readability/maintainability.
|
|
|
|
Note: The encryption context is created if necc. in EfsPostCreateCall and not
|
|
at this point
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the Irp to process.
|
|
|
|
ThisScb - This is the Scb for the file being opened.
|
|
|
|
ThisCcb - This is the Ccb for the file being opened
|
|
|
|
ParentFcb - This is the Fcb for the parent of the file being opened.
|
|
Although not truly optional, it may be NULL for an
|
|
existing file being opened, such as an open by id.
|
|
|
|
CreateNewFile - TRUE if we're being called from NtfsCreateNewFile, FALSE otherwise.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The return status for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS EncryptionStatus = STATUS_SUCCESS;
|
|
ULONG FileAttributes = (ULONG) IrpSp->Parameters.Create.FileAttributes;
|
|
ULONG EncryptionFileDirFlags = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// If this is an existing stream and the encryption bit is set then either
|
|
// call the driver or fail the request. We have to test CreateNewFile
|
|
// also in case our caller has not set the Information field of the Irp yet.
|
|
//
|
|
|
|
if (!NtfsIsStreamNew( Irp->IoStatus.Information ) &&
|
|
!CreateNewFile) {
|
|
|
|
if (FlagOn( ThisScb->AttributeFlags, ATTRIBUTE_FLAG_ENCRYPTED ) &&
|
|
FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
|
|
FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_EXECUTE)) {
|
|
|
|
EncryptionFileDirFlags = NtfsOpenExistingEncryptedStream( IrpContext, ThisScb, CreateContext->CurrentFcb );
|
|
} // else EncryptionFileDirFlags = 0;
|
|
|
|
//
|
|
// We need the encryption driver for new creates. We may be dealing with a
|
|
// new file create or a supersede/overwrite.
|
|
//
|
|
|
|
} else if (FlagOn( NtfsData.Flags, NTFS_FLAGS_ENCRYPTION_DRIVER )) {
|
|
|
|
if (CreateNewFile) {
|
|
|
|
//
|
|
// This is a new stream in a new file.
|
|
//
|
|
|
|
ASSERT( (ParentFcb == NULL) ||
|
|
FlagOn( ParentFcb->FcbState, FCB_STATE_DUP_INITIALIZED ));
|
|
|
|
//
|
|
// We want this new file/directory to be created encrypted if
|
|
// its parent directory is encrypted, or our caller has asked
|
|
// to have it created encrypted.
|
|
//
|
|
|
|
if (((ParentFcb != NULL) &&
|
|
(IsEncrypted( &ParentFcb->Info ))) ||
|
|
|
|
FlagOn( FileAttributes, FILE_ATTRIBUTE_ENCRYPTED )) {
|
|
|
|
if (IsDirectory( &CreateContext->CurrentFcb->Info )) {
|
|
|
|
EncryptionFileDirFlags = DIRECTORY_NEW | STREAM_NEW;
|
|
|
|
} else {
|
|
|
|
EncryptionFileDirFlags = FILE_NEW | STREAM_NEW;
|
|
}
|
|
} // else EncryptionFileDirFlags = 0;
|
|
|
|
} else {
|
|
|
|
//
|
|
// This is a supersede/overwrite or else a new stream being created
|
|
// in an existing file.
|
|
//
|
|
|
|
ASSERT( CreateContext->CurrentFcb != NULL );
|
|
ASSERT( NtfsIsStreamNew( Irp->IoStatus.Information ) );
|
|
|
|
if ((Irp->IoStatus.Information == FILE_SUPERSEDED) ||
|
|
(Irp->IoStatus.Information == FILE_OVERWRITTEN)) {
|
|
|
|
if (FlagOn( FileAttributes, FILE_ATTRIBUTE_ENCRYPTED )) {
|
|
|
|
//
|
|
// This is a supersede/overwrite where the caller set the encrypted flag.
|
|
//
|
|
|
|
if (IsDirectory( &CreateContext->CurrentFcb->Info )) {
|
|
|
|
EncryptionFileDirFlags = DIRECTORY_NEW | STREAM_NEW;
|
|
|
|
} else if (FlagOn( ThisScb->ScbState, SCB_STATE_UNNAMED_DATA )) {
|
|
|
|
//
|
|
// When superseding/overwriting the unnamed stream, the flags we
|
|
// pass depend on the encrypted state of the old file.
|
|
//
|
|
|
|
if (IsEncrypted( &CreateContext->CurrentFcb->Info )) {
|
|
|
|
EncryptionFileDirFlags = FILE_EXISTING | STREAM_NEW | EXISTING_FILE_ENCRYPTED;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If there are open handles to this or any other stream, and the
|
|
// encryption engine will wish it could encrypt all streams, we
|
|
// may as well just fail the create now.
|
|
//
|
|
|
|
if ((CreateContext->CurrentFcb->CleanupCount > 1) &&
|
|
FlagOn( NtfsData.EncryptionCallBackTable.ImplementationFlags, ENCRYPTION_ALL_STREAMS )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_SHARING_VIOLATION, NULL, NULL );
|
|
}
|
|
|
|
EncryptionFileDirFlags = FILE_NEW | STREAM_NEW;
|
|
}
|
|
|
|
} else if (!FlagOn( NtfsData.EncryptionCallBackTable.ImplementationFlags, ENCRYPTION_ALL_STREAMS )) {
|
|
|
|
//
|
|
// We're superseding a named stream; if the encryption engine allows individual
|
|
// streams to be encrypted, notify it.
|
|
//
|
|
|
|
EncryptionFileDirFlags = FILE_EXISTING | STREAM_NEW | EXISTING_FILE_ENCRYPTED;
|
|
} // else EncryptionFileDirFlags = 0;
|
|
|
|
} else if (!FlagOn( ThisScb->ScbState, SCB_STATE_UNNAMED_DATA ) &&
|
|
IsEncrypted( &CreateContext->CurrentFcb->Info )) {
|
|
|
|
//
|
|
// This is a supersede/overwrite of a named stream within an encrypted file.
|
|
//
|
|
|
|
if (IsDirectory( &CreateContext->CurrentFcb->Info )) {
|
|
|
|
EncryptionFileDirFlags = DIRECTORY_EXISTING | STREAM_NEW;
|
|
|
|
} else {
|
|
|
|
EncryptionFileDirFlags = FILE_EXISTING | STREAM_NEW | EXISTING_FILE_ENCRYPTED;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// We're superseding/overwriting the unnamed stream, and it's retaining
|
|
// its encryption from before the overwrite.
|
|
//
|
|
|
|
if (FlagOn( ThisScb->AttributeFlags, ATTRIBUTE_FLAG_ENCRYPTED ) &&
|
|
FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
|
|
FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_EXECUTE)) {
|
|
|
|
EncryptionFileDirFlags = NtfsOpenExistingEncryptedStream( IrpContext, ThisScb, CreateContext->CurrentFcb );
|
|
}
|
|
}
|
|
|
|
} else if (IsEncrypted( &CreateContext->CurrentFcb->Info )) {
|
|
|
|
ASSERT( Irp->IoStatus.Information == FILE_CREATED );
|
|
|
|
//
|
|
// This is a new stream being created in an existing encrypted file.
|
|
//
|
|
|
|
if (IsDirectory( &CreateContext->CurrentFcb->Info )) {
|
|
|
|
EncryptionFileDirFlags = DIRECTORY_EXISTING | STREAM_NEW;
|
|
|
|
} else {
|
|
|
|
EncryptionFileDirFlags = FILE_EXISTING | STREAM_NEW | EXISTING_FILE_ENCRYPTED;
|
|
}
|
|
} // else EncryptionFileDirFlags = 0;
|
|
}
|
|
} // else EncryptionFileDirFlags = 0;
|
|
|
|
//
|
|
// Remember the EncryptionFileDirFlags in case we need to use them to
|
|
// cleanup later.
|
|
//
|
|
|
|
ASSERT( CreateContext->EncryptionFileDirFlags == 0 ||
|
|
CreateContext->EncryptionFileDirFlags == EncryptionFileDirFlags );
|
|
|
|
CreateContext->EncryptionFileDirFlags = EncryptionFileDirFlags;
|
|
|
|
//
|
|
// Perform the update if we have encryption flags and there is a callback.
|
|
//
|
|
|
|
if (EncryptionFileDirFlags != 0) {
|
|
|
|
if (FlagOn( EncryptionFileDirFlags, FILE_NEW | DIRECTORY_NEW )) {
|
|
|
|
//
|
|
// While we're still holding the fcb, set the bit that reminds us
|
|
// to block other creates until the encryption engine has had its
|
|
// chance to set the key context for this stream.
|
|
//
|
|
|
|
ASSERT_EXCLUSIVE_FCB( CreateContext->CurrentFcb );
|
|
SetFlag( CreateContext->CurrentFcb->FcbState, FCB_STATE_ENCRYPTION_PENDING );
|
|
}
|
|
|
|
if (NtfsData.EncryptionCallBackTable.FileCreate != NULL) {
|
|
|
|
//
|
|
// Find the parent, if we can't find a parent (most likely in
|
|
// the supersede by id case) just pass the current fcb as the
|
|
// parent.
|
|
//
|
|
|
|
if ((ParentFcb == NULL)) {
|
|
|
|
if ((ThisCcb->Lcb != NULL) &&
|
|
(ThisCcb->Lcb->Scb != NULL )) {
|
|
|
|
ParentFcb = ThisCcb->Lcb->Scb->Fcb;
|
|
|
|
} else {
|
|
|
|
ParentFcb = CreateContext->CurrentFcb;
|
|
}
|
|
}
|
|
|
|
ASSERT( ParentFcb != NULL );
|
|
|
|
EncryptionStatus = NtfsData.EncryptionCallBackTable.FileCreate(
|
|
CreateContext->CurrentFcb,
|
|
ParentFcb,
|
|
IrpSp,
|
|
EncryptionFileDirFlags,
|
|
(NtfsIsVolumeReadOnly( CreateContext->CurrentFcb->Vcb )) ? READ_ONLY_VOLUME : 0,
|
|
IrpContext,
|
|
(PDEVICE_OBJECT) CONTAINING_RECORD( CreateContext->CurrentFcb->Vcb,
|
|
VOLUME_DEVICE_OBJECT,
|
|
Vcb ),
|
|
NULL,
|
|
&ThisScb->EncryptionContext,
|
|
&ThisScb->EncryptionContextLength,
|
|
&CreateContext->EncryptionContext,
|
|
NULL );
|
|
|
|
if (EncryptionStatus != STATUS_SUCCESS) {
|
|
|
|
NtfsRaiseStatus( IrpContext, EncryptionStatus, NULL, NULL );
|
|
}
|
|
}
|
|
}
|
|
|
|
return EncryptionStatus;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsPostProcessEncryptedCreate (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN ULONG EncryptionFileDirFlags,
|
|
IN ULONG FailedInPostCreateOnly
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called after the encryption driver's post create callout
|
|
returns. If we failed a create in the post create callout that had been
|
|
successful before the post create callout, we have to cleanup the file.
|
|
If we just created the file, we need to clear the encryption_pending bit
|
|
safely.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the FileObject being created.
|
|
|
|
EncryptionFileDirFlags - Some combination of FILE_NEW, FILE_EXISTING, etc.
|
|
|
|
FailedInPostCreateOnly - Pass TRUE if the create operation had succeeded
|
|
until the PostCreate callout.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVCB Vcb;
|
|
PFCB Fcb;
|
|
PSCB Scb;
|
|
PCCB Ccb;
|
|
PLCB Lcb;
|
|
|
|
NTSTATUS Status;
|
|
|
|
BOOLEAN FcbStillExists = TRUE;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// In some failure cases, we'll have no FileObject, in which case we have
|
|
// no cleanup to do. We can't do much without a FileObject anyway.
|
|
//
|
|
|
|
if (FileObject == NULL) {
|
|
|
|
return;
|
|
}
|
|
|
|
NtfsDecodeFileObject( IrpContext,
|
|
FileObject,
|
|
&Vcb,
|
|
&Fcb,
|
|
&Scb,
|
|
&Ccb,
|
|
FALSE );
|
|
|
|
//
|
|
// If we failed only in the post create, backout this create.
|
|
//
|
|
|
|
if (FailedInPostCreateOnly) {
|
|
|
|
if (FlagOn( EncryptionFileDirFlags, FILE_NEW | DIRECTORY_NEW ) ||
|
|
|
|
(FlagOn( EncryptionFileDirFlags, STREAM_NEW ) &&
|
|
FlagOn( EncryptionFileDirFlags, FILE_EXISTING ))) {
|
|
|
|
//
|
|
// Delete the stream if we still can. First acquire
|
|
// the Scb so we can safely test some bits in it.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Scb );
|
|
|
|
//
|
|
// If a dismount happened while we weren't holding the Scb,
|
|
// we should just do the cleanup & close and get out of here.
|
|
//
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED )) {
|
|
|
|
//
|
|
// See if we can still delete the stream. N.B. If we're
|
|
// working with the unnamed data stream, deleting the
|
|
// stream will delete the file.
|
|
//
|
|
|
|
Lcb = Ccb->Lcb;
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_MULTIPLE_OPENS ) &&
|
|
(Lcb != NULL)) {
|
|
|
|
//
|
|
// Now see if the file is really deleteable according to indexsup
|
|
//
|
|
|
|
if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
BOOLEAN LastLink;
|
|
BOOLEAN NonEmptyIndex = FALSE;
|
|
|
|
//
|
|
// If the link is not deleted, we check if it can be deleted.
|
|
// Since we dropped all our resources for the PostCreate callout,
|
|
// this might be a nonempty index or a file with multiple
|
|
// links already.
|
|
//
|
|
|
|
if (!LcbLinkIsDeleted( Lcb ) && NtfsIsLinkDeleteable( IrpContext, Scb->Fcb, &NonEmptyIndex, &LastLink )) {
|
|
|
|
|
|
//
|
|
// It is ok to get rid of this guy. All we need to do is
|
|
// mark this Lcb for delete and decrement the link count
|
|
// in the Fcb. If this is a primary link, then we
|
|
// indicate that the primary link has been deleted.
|
|
//
|
|
|
|
SetFlag( Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
|
|
|
|
ASSERTMSG( "Link count should not be 0\n", Scb->Fcb->LinkCount != 0 );
|
|
Scb->Fcb->LinkCount -= 1;
|
|
|
|
if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
|
|
|
|
SetFlag( Scb->Fcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED );
|
|
}
|
|
|
|
//
|
|
// Indicate in the file object that a delete is pending
|
|
//
|
|
|
|
FileObject->DeletePending = TRUE;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Otherwise we are simply removing the attribute.
|
|
//
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
|
|
|
|
//
|
|
// Indicate in the file object that a delete is pending
|
|
//
|
|
|
|
FileObject->DeletePending = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We can clear the pending bit now that we're done handling the
|
|
// failure.
|
|
//
|
|
|
|
if (FlagOn( EncryptionFileDirFlags, FILE_NEW | DIRECTORY_NEW )) {
|
|
|
|
ASSERT( FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ) );
|
|
NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, 0 );
|
|
ASSERT( (Scb->EncryptionContext != NULL) || FailedInPostCreateOnly );
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_ENCRYPTION_PENDING );
|
|
KeSetEvent( &NtfsEncryptionPendingEvent, 0, FALSE );
|
|
NtfsReleaseFcb( IrpContext, Fcb );
|
|
}
|
|
|
|
//
|
|
// We need to release the Scb now, since the close may
|
|
// result in the Scb getting freed.
|
|
//
|
|
|
|
NtfsReleaseScb( IrpContext, Scb );
|
|
|
|
Status = NtfsIoCallSelf( IrpContext,
|
|
FileObject,
|
|
IRP_MJ_CLEANUP );
|
|
|
|
ASSERT( STATUS_SUCCESS == Status );
|
|
|
|
FcbStillExists = FALSE;
|
|
|
|
Status = NtfsIoCallSelf( IrpContext,
|
|
FileObject,
|
|
IRP_MJ_CLOSE );
|
|
|
|
ASSERT( STATUS_SUCCESS == Status );
|
|
|
|
} else if ((FlagOn( EncryptionFileDirFlags, FILE_EXISTING ) &&
|
|
FlagOn( EncryptionFileDirFlags, STREAM_EXISTING )) ||
|
|
|
|
FlagOn( EncryptionFileDirFlags, DIRECTORY_EXISTING )) {
|
|
|
|
#ifdef NTFSDBG
|
|
ASSERT( None == IrpContext->OwnershipState );
|
|
#endif
|
|
|
|
//
|
|
// All we have to do in this case is a cleanup and a close.
|
|
//
|
|
|
|
Status = NtfsIoCallSelf( IrpContext,
|
|
FileObject,
|
|
IRP_MJ_CLEANUP );
|
|
|
|
ASSERT( STATUS_SUCCESS == Status );
|
|
|
|
FcbStillExists = FALSE;
|
|
|
|
Status = NtfsIoCallSelf( IrpContext,
|
|
FileObject,
|
|
IRP_MJ_CLOSE );
|
|
|
|
ASSERT( STATUS_SUCCESS == Status );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we've done a cleanup & close, the Fcb may have been freed already,
|
|
// in which case we should just set the pending event and get out of here.
|
|
// If we still have the Fcb, let's make sure we've cleared the pending bit.
|
|
//
|
|
|
|
if (FlagOn( EncryptionFileDirFlags, FILE_NEW | DIRECTORY_NEW )) {
|
|
|
|
if (FcbStillExists) {
|
|
|
|
ASSERT( FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ) );
|
|
NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, 0 );
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_ENCRYPTION_PENDING );
|
|
KeSetEvent( &NtfsEncryptionPendingEvent, 0, FALSE );
|
|
NtfsReleaseFcb( IrpContext, Fcb );
|
|
|
|
} else {
|
|
|
|
KeSetEvent( &NtfsEncryptionPendingEvent, 0, FALSE );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsTryOpenFcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
OUT PFCB *CurrentFcb,
|
|
IN FILE_REFERENCE FileReference
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to open a file by its file segment number.
|
|
We need to verify that this file Id exists. This code is
|
|
patterned after open by Id.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for this volume.
|
|
|
|
CurrentFcb - Address of Fcb pointer. Store the Fcb we find here.
|
|
|
|
FileReference - This is the file Id for the file to open the
|
|
sequence number is ignored.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Indicates the result of this create file operation.
|
|
|
|
Note:
|
|
|
|
If the status is successful then the FCB is returned with its reference
|
|
count incremented and the FCB held exclusive.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
LONGLONG MftOffset;
|
|
PFILE_RECORD_SEGMENT_HEADER FileRecord;
|
|
PBCB Bcb = NULL;
|
|
|
|
PFCB ThisFcb;
|
|
|
|
BOOLEAN AcquiredFcbTable = FALSE;
|
|
BOOLEAN AcquiredMft = TRUE;
|
|
BOOLEAN ThisFcbFree = TRUE;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( *CurrentFcb == NULL );
|
|
|
|
//
|
|
// Do not bother with system files.
|
|
//
|
|
|
|
//
|
|
// If this is a system fcb then return.
|
|
//
|
|
|
|
if (NtfsFullSegmentNumber( &FileReference ) < FIRST_USER_FILE_NUMBER &&
|
|
NtfsFullSegmentNumber( &FileReference ) != ROOT_FILE_NAME_INDEX_NUMBER) {
|
|
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// Calculate the offset in the MFT. Use the full segment number since the user
|
|
// can specify any 48-bit value.
|
|
//
|
|
|
|
MftOffset = NtfsFullSegmentNumber( &FileReference );
|
|
|
|
MftOffset = Int64ShllMod32(MftOffset, Vcb->MftShift);
|
|
|
|
//
|
|
// Acquire the MFT shared so it cannot shrink on us.
|
|
//
|
|
|
|
NtfsAcquireSharedScb( IrpContext, Vcb->MftScb );
|
|
|
|
try {
|
|
|
|
if (MftOffset >= Vcb->MftScb->Header.FileSize.QuadPart) {
|
|
|
|
DebugTrace( 0, Dbg, ("File Id doesn't lie within Mft\n") );
|
|
|
|
Status = STATUS_END_OF_FILE;
|
|
leave;
|
|
}
|
|
|
|
NtfsReadMftRecord( IrpContext,
|
|
Vcb,
|
|
&FileReference,
|
|
FALSE,
|
|
&Bcb,
|
|
&FileRecord,
|
|
NULL );
|
|
|
|
//
|
|
// This file record better be in use, better not be one of the other system files,
|
|
// and have a matching sequence number and be the primary file record for this file.
|
|
//
|
|
|
|
if (!FlagOn( FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE ) ||
|
|
FlagOn( FileRecord->Flags, FILE_SYSTEM_FILE ) ||
|
|
(*((PLONGLONG) &FileRecord->BaseFileRecordSegment) != 0) ||
|
|
(*((PULONG) FileRecord->MultiSectorHeader.Signature) != *((PULONG) FileSignature))) {
|
|
|
|
Status = STATUS_NOT_FOUND;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Get the current sequence number.
|
|
//
|
|
|
|
FileReference.SequenceNumber = FileRecord->SequenceNumber;
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = TRUE;
|
|
|
|
//
|
|
// We know that it is safe to continue the open. We start by creating
|
|
// an Fcb for this file. It is possible that the Fcb exists.
|
|
// We create the Fcb first, if we need to update the Fcb info structure
|
|
// we copy the one from the index entry. We look at the Fcb to discover
|
|
// if it has any links, if it does then we make this the last Fcb we
|
|
// reached. If it doesn't then we have to clean it up from here.
|
|
//
|
|
|
|
ThisFcb = NtfsCreateFcb( IrpContext,
|
|
Vcb,
|
|
FileReference,
|
|
FALSE,
|
|
TRUE,
|
|
NULL );
|
|
|
|
//
|
|
// ReferenceCount the fcb so it does no go away.
|
|
//
|
|
|
|
ThisFcb->ReferenceCount += 1;
|
|
|
|
//
|
|
// Release the mft and fcb table before acquiring the FCB exclusive.
|
|
//
|
|
|
|
NtfsReleaseScb( IrpContext, Vcb->MftScb );
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredMft = FALSE;
|
|
AcquiredFcbTable = FALSE;
|
|
|
|
NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, 0 );
|
|
ThisFcbFree = FALSE;
|
|
|
|
//
|
|
// Repin the file record with synchronization to the fcb
|
|
//
|
|
|
|
NtfsReadMftRecord( IrpContext,
|
|
Vcb,
|
|
&FileReference,
|
|
FALSE,
|
|
&Bcb,
|
|
&FileRecord,
|
|
NULL );
|
|
|
|
//
|
|
// Skip any deleted files.
|
|
//
|
|
|
|
if (FlagOn( ThisFcb->FcbState, FCB_STATE_FILE_DELETED ) ||
|
|
!FlagOn( FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE )) {
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
|
|
#ifdef QUOTADBG
|
|
DbgPrint( "NtfsTryOpenFcb: Deleted fcb found. Fcb = %lx\n", ThisFcb );
|
|
#endif
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
ASSERT( ThisFcb->ReferenceCount > 0 );
|
|
ThisFcb->ReferenceCount--;
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
ThisFcb,
|
|
NULL,
|
|
FALSE,
|
|
0,
|
|
&ThisFcbFree );
|
|
|
|
//
|
|
// Release the fcb if it has not been deleted.
|
|
//
|
|
|
|
if (!ThisFcbFree) {
|
|
NtfsReleaseFcb( IrpContext, ThisFcb );
|
|
ThisFcbFree = TRUE;
|
|
}
|
|
|
|
//
|
|
// Teardown may generate a transaction, clean it up.
|
|
//
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE | IRP_CONTEXT_FLAG_RETAIN_FLAGS );
|
|
NtfsCompleteRequest( IrpContext, NULL, Status );
|
|
|
|
Status = STATUS_NOT_FOUND;
|
|
leave;
|
|
}
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
|
|
//
|
|
// Store this Fcb into our caller's parameter and remember to
|
|
// to show we acquired it.
|
|
//
|
|
|
|
*CurrentFcb = ThisFcb;
|
|
ThisFcbFree = TRUE;
|
|
|
|
|
|
//
|
|
// If the Fcb Info field needs to be initialized, we do so now.
|
|
// We read this information from the disk.
|
|
//
|
|
|
|
if (!FlagOn( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
|
|
|
|
NtfsUpdateFcbInfoFromDisk( IrpContext,
|
|
TRUE,
|
|
ThisFcb,
|
|
NULL );
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (AcquiredFcbTable) {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
}
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
|
|
if (AcquiredMft) {
|
|
NtfsReleaseScb( IrpContext, Vcb->MftScb );
|
|
}
|
|
|
|
if (!ThisFcbFree) {
|
|
NtfsReleaseFcb( IrpContext, ThisFcb );
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Worker routine.
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsGetReparsePointValue (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
PIO_STACK_LOCATION IrpSp,
|
|
IN PFCB Fcb,
|
|
IN USHORT RemainingNameLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine retrieves the value of the specified reparse point and returns it to
|
|
the caller.
|
|
|
|
The user-controlled data in the reparse point is returned in a new buffer pointed
|
|
from Irp->Tail.Overlay.AuxiliaryBuffer. When the request traverses the stack of
|
|
layered drivers and not one operates on it, it is freed by the I/O subsystem in
|
|
IoCompleteRequest.
|
|
|
|
To provide callers with an indication of where in the name the parsing stoped, in
|
|
the Reserved field of the REPARSE_DATA_BUFFER structure we return the length of the
|
|
portion of the name that remains to be parsed by NTFS. We account for the file
|
|
delimiter in our value to make the paste of names easy in IopParseDevice.
|
|
|
|
The name offset arithmetic is correct only if:
|
|
(1) All the intermediate names in the path are simple, that is, they do not contain
|
|
any : (colon) in them.
|
|
(2) The RemainingNameLength includes all the parts present in the last name component.
|
|
|
|
When this function succeeds, it sets in Irp->IoStatus.Information the Tag of the
|
|
reparse point that we have just copied out. In this case we return STATUS_REPARSE
|
|
and set Irp->IoStatus.Status to STATUS_REPARSE.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - Supplies the Irp context of the call.
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
IrpSp - This is the Irp stack pointer for the filesystem.
|
|
|
|
Fcb - Address of the Fcb pointer where the $REPARSE_POINT attribute is located.
|
|
|
|
RemainingNameLength - Length of the part of the name that still needs to be parsed.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The return status for the operation.
|
|
If successful, STATUS_REPARSE will be returned.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_REPARSE;
|
|
PREPARSE_DATA_BUFFER ReparseBuffer = NULL;
|
|
|
|
POPLOCK_CLEANUP OplockCleanup = IrpContext->Union.OplockCleanup;
|
|
|
|
BOOLEAN CleanupAttributeContext = FALSE;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
|
|
PATTRIBUTE_RECORD_HEADER AttributeHeader = NULL;
|
|
ULONG AttributeLengthInBytes = 0; // Invalid value
|
|
PVOID AttributeData = NULL;
|
|
|
|
PBCB Bcb = NULL;
|
|
|
|
PAGED_CODE( );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsGetReparsePointValue, Fcb %08lx\n", Fcb) );
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
DebugTrace( 0, Dbg, ("OplockCleanup->OriginalFileName %x %Z\n", OplockCleanup->OriginalFileName.Buffer, &OplockCleanup->OriginalFileName) );
|
|
DebugTrace( 0, Dbg, ("OplockCleanup->FullFileName %x %Z\n", OplockCleanup->FullFileName.Buffer, &OplockCleanup->FullFileName) );
|
|
DebugTrace( 0, Dbg, ("OplockCleanup->ExactCaseName %x %Z\n", OplockCleanup->ExactCaseName.Buffer, &OplockCleanup->ExactCaseName) );
|
|
DebugTrace( 0, Dbg, ("IrpSP...->FileName %x %Z\n", IrpSp->FileObject->FileName.Buffer, &IrpSp->FileObject->FileName) );
|
|
#endif
|
|
|
|
DebugTrace( 0,
|
|
Dbg,
|
|
("Length of remaining name [d] %04ld %04lx OriginalFileName.Length [d] %04ld %04lx\n",
|
|
RemainingNameLength,
|
|
RemainingNameLength,
|
|
OplockCleanup->OriginalFileName.Length,
|
|
OplockCleanup->OriginalFileName.Length) );
|
|
|
|
ASSERT( FlagOn( Fcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT ));
|
|
ASSERT( Irp->Tail.Overlay.AuxiliaryBuffer == NULL );
|
|
|
|
//
|
|
// Now it is time to use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Find the reparse point attribute in the file.
|
|
//
|
|
|
|
CleanupAttributeContext = TRUE;
|
|
NtfsInitializeAttributeContext( &AttributeContext );
|
|
|
|
if (!NtfsLookupAttributeByCode( IrpContext,
|
|
Fcb,
|
|
&Fcb->FileReference,
|
|
$REPARSE_POINT,
|
|
&AttributeContext )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Can't find the $REPARSE_POINT attribute.\n") );
|
|
|
|
//
|
|
// Should not happen. Raise an exeption as we are in an
|
|
// inconsistent state. The attribute flag says that
|
|
// $REPARSE_POINT has to be present.
|
|
//
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
|
}
|
|
|
|
//
|
|
// Find the size of the attribute and map its value to AttributeData.
|
|
//
|
|
|
|
AttributeHeader = NtfsFoundAttribute( &AttributeContext );
|
|
|
|
if (NtfsIsAttributeResident( AttributeHeader )) {
|
|
|
|
AttributeLengthInBytes = AttributeHeader->Form.Resident.ValueLength;
|
|
DebugTrace( 0, Dbg, ("Attribute is resident with length %08lx\n", AttributeLengthInBytes) );
|
|
|
|
if (AttributeLengthInBytes > MAXIMUM_REPARSE_DATA_BUFFER_SIZE) {
|
|
|
|
//
|
|
// Return STATUS_IO_REPARSE_DATA_INVALID
|
|
//
|
|
|
|
Status = STATUS_IO_REPARSE_DATA_INVALID;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Point to the value of the attribute.
|
|
//
|
|
|
|
AttributeData = NtfsAttributeValue( AttributeHeader );
|
|
|
|
} else {
|
|
|
|
ULONG Length;
|
|
|
|
if (AttributeHeader->Form.Nonresident.FileSize > MAXIMUM_REPARSE_DATA_BUFFER_SIZE) {
|
|
|
|
//
|
|
// Return STATUS_IO_REPARSE_DATA_INVALID
|
|
//
|
|
|
|
Status = STATUS_IO_REPARSE_DATA_INVALID;
|
|
DebugTrace( 0, Dbg, ("Nonresident.FileSize is too long.\n") );
|
|
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Note that we coerse different LENGTHS
|
|
//
|
|
|
|
AttributeLengthInBytes = (ULONG)AttributeHeader->Form.Nonresident.FileSize;
|
|
DebugTrace( 0, Dbg, ("Attribute is non-resident with length %05lx\n", AttributeLengthInBytes) );
|
|
|
|
NtfsMapAttributeValue( IrpContext,
|
|
Fcb,
|
|
&AttributeData,
|
|
&Length,
|
|
&Bcb,
|
|
&AttributeContext );
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
if (AttributeLengthInBytes != Length) {
|
|
DebugTrace( 0, Dbg, ("AttributeLengthInBytes [d]%05ld and Length [d]%05ld differ.\n", AttributeLengthInBytes, Length) );
|
|
}
|
|
ASSERT( AttributeLengthInBytes == Length );
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Reference the reparse point data.
|
|
// It is appropriate to use this cast, and not concern ourselves with the GUID
|
|
// buffer, because we only read the common fields.
|
|
//
|
|
|
|
ReparseBuffer = (PREPARSE_DATA_BUFFER)AttributeData;
|
|
DebugTrace( 0, Dbg, ("ReparseDataLength [d]%08ld %08lx\n",
|
|
ReparseBuffer->ReparseDataLength, ReparseBuffer->ReparseDataLength) );
|
|
|
|
//
|
|
// Validate the reparse point further
|
|
//
|
|
|
|
Status = NtfsValidateReparsePointBuffer( AttributeLengthInBytes,
|
|
ReparseBuffer );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// Return the error status
|
|
//
|
|
|
|
leave;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Return STATUS_REPARSE as successful status.
|
|
//
|
|
|
|
Status = STATUS_REPARSE;
|
|
}
|
|
|
|
//
|
|
// We leave all the names in their original state.
|
|
// Return the complete reparse point data buffer off
|
|
// Irp->Tail.Overlay.AuxiliaryBuffer, already including the ReparseDataLength.
|
|
//
|
|
|
|
Irp->Tail.Overlay.AuxiliaryBuffer = NtfsAllocatePool( NonPagedPool,
|
|
AttributeLengthInBytes );
|
|
DebugTrace( 0, Dbg, ("Irp->Tail.Overlay.AuxiliaryBuffer %08lx\n", Irp->Tail.Overlay.AuxiliaryBuffer) );
|
|
RtlCopyMemory( (PCHAR)Irp->Tail.Overlay.AuxiliaryBuffer,
|
|
(PCHAR)AttributeData,
|
|
AttributeLengthInBytes );
|
|
|
|
//
|
|
// We also return the length of the portion of the name that remains to be parsed using the
|
|
// Reserved field in the REPARSE_DATA_BUFFER structure.
|
|
//
|
|
// The \ (backslash) in a multi-component name is always accounted for by the code before
|
|
// calling this routine.
|
|
// The : (colon) in a complex name is always accounted for by the code before calling this
|
|
// routine.
|
|
//
|
|
|
|
ReparseBuffer = (PREPARSE_DATA_BUFFER)Irp->Tail.Overlay.AuxiliaryBuffer;
|
|
|
|
ReparseBuffer->Reserved = RemainingNameLength;
|
|
|
|
//
|
|
// Better not have a non-zero length if opened by file id.
|
|
//
|
|
|
|
ASSERT( (RemainingNameLength == 0) ||
|
|
!FlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID ));
|
|
|
|
DebugTrace( 0, Dbg, ("Final value for ReparseBuffer->Reserved = %d\n", ReparseBuffer->Reserved) );
|
|
|
|
//
|
|
// When the Reserved field is positive, the offset should always denote the backslash character
|
|
// or the colon character.
|
|
//
|
|
// Assert this here.
|
|
//
|
|
|
|
if (ReparseBuffer->Reserved) {
|
|
|
|
DebugTrace( 0, Dbg, ("NameOffset = %d\n", (OplockCleanup->OriginalFileName.Length - ReparseBuffer->Reserved)) );
|
|
|
|
ASSERT( (*((PCHAR)(OplockCleanup->OriginalFileName.Buffer) + (OplockCleanup->OriginalFileName.Length - ReparseBuffer->Reserved)) == L'\\') ||
|
|
(*((PCHAR)(OplockCleanup->OriginalFileName.Buffer) + (OplockCleanup->OriginalFileName.Length - ReparseBuffer->Reserved)) == L':') );
|
|
|
|
ASSERT( (OplockCleanup->OriginalFileName.Buffer[(OplockCleanup->OriginalFileName.Length - ReparseBuffer->Reserved)/sizeof(WCHAR)] == L'\\') ||
|
|
(OplockCleanup->OriginalFileName.Buffer[(OplockCleanup->OriginalFileName.Length - ReparseBuffer->Reserved)/sizeof(WCHAR)] == L':') );
|
|
}
|
|
|
|
//
|
|
// Set the Information field to the ReparseTag.
|
|
//
|
|
|
|
Irp->IoStatus.Information = ReparseBuffer->ReparseTag;
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsGetReparsePointValue );
|
|
|
|
if (CleanupAttributeContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttributeContext );
|
|
}
|
|
|
|
//
|
|
// Unpin the Bcb ... in case you needed to pin it above.
|
|
// The unpin routine checks for NULL.
|
|
//
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsGetReparsePointValue -> IoStatus.Information %08lx Status %08lx\n", Irp->IoStatus.Information, Status) );
|
|
|
|
return Status;
|
|
|
|
UNREFERENCED_PARAMETER( IrpSp );
|
|
}
|
|
|
|
NTSTATUS
|
|
NtfsLookupObjectId (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PUNICODE_STRING FileName,
|
|
OUT PFILE_REFERENCE FileReference
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine retrieves the value of the specified objectid and returns it to
|
|
the caller.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - Supplies the Irp context of the call.
|
|
|
|
Vcb - the volume to look it up in
|
|
|
|
FileName - Contains the objectid embedded in the unicode string
|
|
|
|
FileReference - on success contains the file that this objectid refers to
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The return status for the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
INDEX_KEY IndexKey;
|
|
INDEX_ROW IndexRow;
|
|
UCHAR ObjectId[OBJECT_ID_KEY_LENGTH];
|
|
NTFS_OBJECTID_INFORMATION ObjectIdInfo;
|
|
MAP_HANDLE MapHandle;
|
|
|
|
BOOLEAN CleanupMapHandle = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Copy the object id out of the file name, optionally skipping
|
|
// over the Win32 backslash at the start of the buffer.
|
|
//
|
|
|
|
if (FileName->Length == OBJECT_ID_KEY_LENGTH) {
|
|
|
|
RtlCopyMemory( ObjectId,
|
|
&FileName->Buffer[0],
|
|
sizeof( ObjectId ) );
|
|
|
|
} else {
|
|
|
|
RtlCopyMemory( ObjectId,
|
|
&FileName->Buffer[1],
|
|
sizeof( ObjectId ) );
|
|
}
|
|
|
|
//
|
|
// Acquire the object id index for the volume.
|
|
//
|
|
|
|
NtfsAcquireSharedScb( IrpContext, Vcb->ObjectIdTableScb );
|
|
|
|
//
|
|
// Find the ObjectId.
|
|
//
|
|
|
|
try {
|
|
IndexKey.Key = ObjectId;
|
|
IndexKey.KeyLength = sizeof( ObjectId );
|
|
|
|
NtOfsInitializeMapHandle( &MapHandle );
|
|
CleanupMapHandle = TRUE;
|
|
|
|
Status = NtOfsFindRecord( IrpContext,
|
|
Vcb->ObjectIdTableScb,
|
|
&IndexKey,
|
|
&IndexRow,
|
|
&MapHandle,
|
|
NULL );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
leave;
|
|
}
|
|
|
|
ASSERT( IndexRow.DataPart.DataLength == sizeof( NTFS_OBJECTID_INFORMATION ) );
|
|
|
|
RtlZeroMemory( &ObjectIdInfo,
|
|
sizeof( NTFS_OBJECTID_INFORMATION ) );
|
|
|
|
RtlCopyMemory( &ObjectIdInfo,
|
|
IndexRow.DataPart.Data,
|
|
sizeof( NTFS_OBJECTID_INFORMATION ) );
|
|
|
|
RtlCopyMemory( FileReference,
|
|
&ObjectIdInfo.FileSystemReference,
|
|
sizeof( FILE_REFERENCE ) );
|
|
|
|
//
|
|
// Now we have a file reference number, we're ready to proceed
|
|
// normally and open the file. There's no point in holding the
|
|
// object id index anymore, we've looked up all we needed in there.
|
|
//
|
|
|
|
} finally {
|
|
NtfsReleaseScb( IrpContext, Vcb->ObjectIdTableScb );
|
|
|
|
if (CleanupMapHandle) {
|
|
NtOfsReleaseMap( IrpContext, &MapHandle );
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
#ifdef BRIANDBG
|
|
VOID
|
|
NtfsTestOpenName (
|
|
IN PFILE_OBJECT FileObject
|
|
)
|
|
{
|
|
ULONG Count = NtfsTestName.Length;
|
|
|
|
//
|
|
// This will let us catch particular opens through the debugger.
|
|
//
|
|
|
|
if ((Count != 0) &&
|
|
(FileObject->FileName.Length >= Count)) {
|
|
|
|
PWCHAR TestChar;
|
|
PWCHAR SourceChar = &FileObject->FileName.Buffer[ FileObject->FileName.Length / sizeof( WCHAR ) ];
|
|
|
|
Count = Count / sizeof( WCHAR );
|
|
TestChar = &NtfsTestName.Buffer[ Count ];
|
|
|
|
do {
|
|
TestChar -= 1;
|
|
SourceChar -= 1;
|
|
|
|
if ((*TestChar | 0x20) != (*SourceChar | 0x20)) {
|
|
|
|
break;
|
|
}
|
|
|
|
Count -= 1;
|
|
|
|
} while (Count != 0);
|
|
|
|
ASSERT( Count != 0 );
|
|
}
|
|
}
|
|
#endif
|