/* Copyright (C) 2017-2020 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved. * * You can redistribute this program and/or modify it under the terms of * the GNU Lesser Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. */ using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Win32.SafeHandles; using Utilities; namespace SMBLibrary.Win32 { [StructLayout(LayoutKind.Sequential)] public struct UNICODE_STRING : IDisposable { public ushort Length; public ushort MaximumLength; private IntPtr Buffer; public UNICODE_STRING(string value) { Length = (ushort)(value.Length * 2); MaximumLength = (ushort)(value.Length + 2); Buffer = Marshal.StringToHGlobalUni(value); } public void Dispose() { Marshal.FreeHGlobal(Buffer); Buffer = IntPtr.Zero; } public override string ToString() { return Marshal.PtrToStringUni(Buffer); } } [StructLayoutAttribute(LayoutKind.Sequential)] public struct OBJECT_ATTRIBUTES { public int Length; public IntPtr RootDirectory; public IntPtr ObjectName; public uint Attributes; public IntPtr SecurityDescriptor; public IntPtr SecurityQualityOfService; } [StructLayoutAttribute(LayoutKind.Sequential)] public struct IO_STATUS_BLOCK { public UInt32 Status; public IntPtr Information; } internal class PendingRequest { public IntPtr FileHandle; public uint ThreadID; public IO_STATUS_BLOCK IOStatusBlock; public bool Cleanup; } public class NTDirectoryFileSystem : INTFileStore { [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtCreateFile(out IntPtr handle, uint desiredAccess, ref OBJECT_ATTRIBUTES objectAttributes, out IO_STATUS_BLOCK ioStatusBlock, ref long allocationSize, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, IntPtr eaBuffer, uint eaLength); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtClose(IntPtr handle); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtReadFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] buffer, uint length, ref long byteOffset, IntPtr key); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtWriteFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] buffer, uint length, ref long byteOffset, IntPtr key); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtFlushBuffersFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtLockFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, ref long byteOffset, ref long length, uint key, bool failImmediately, bool exclusiveLock); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtUnlockFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, ref long byteOffset, ref long length, uint key); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtQueryDirectoryFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] fileInformation, uint length, uint fileInformationClass, bool returnSingleEntry, ref UNICODE_STRING fileName, bool restartScan); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtQueryInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fileInformation, uint length, uint fileInformationClass); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtSetInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fileInformation, uint length, uint fileInformationClass); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtQueryVolumeInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fsInformation, uint length, uint fsInformationClass); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtSetVolumeInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fsInformation, uint length, uint fsInformationClass); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtQuerySecurityObject(IntPtr handle, SecurityInformation securityInformation, byte[] securityDescriptor, uint length, out uint lengthNeeded); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtSetSecurityObject(IntPtr handle, SecurityInformation securityInformation, byte[] securityDescriptor); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtNotifyChangeDirectoryFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] buffer, uint bufferSize, NotifyChangeFilter completionFilter, bool watchTree); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtFsControlFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, uint ioControlCode, byte[] inputBuffer, uint inputBufferLength, byte[] outputBuffer, uint outputBufferLength); [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtAlertThread(IntPtr threadHandle); // Available starting from Windows Vista. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)] private static extern NTStatus NtCancelSynchronousIoFile(IntPtr threadHandle, IntPtr ioRequestToCancel, out IO_STATUS_BLOCK ioStatusBlock); private static readonly int QueryDirectoryBufferSize = 4096; private static readonly int FileInformationBufferSize = 8192; private static readonly int FileSystemInformationBufferSize = 4096; private DirectoryInfo m_directory; private PendingRequestCollection m_pendingRequests = new PendingRequestCollection(); public NTDirectoryFileSystem(string path) : this(new DirectoryInfo(path)) { } public NTDirectoryFileSystem(DirectoryInfo directory) { m_directory = directory; } private OBJECT_ATTRIBUTES InitializeObjectAttributes(UNICODE_STRING objectName) { OBJECT_ATTRIBUTES objectAttributes = new OBJECT_ATTRIBUTES(); objectAttributes.RootDirectory = IntPtr.Zero; objectAttributes.ObjectName = Marshal.AllocHGlobal(Marshal.SizeOf(objectName)); Marshal.StructureToPtr(objectName, objectAttributes.ObjectName, false); objectAttributes.SecurityDescriptor = IntPtr.Zero; objectAttributes.SecurityQualityOfService = IntPtr.Zero; objectAttributes.Length = Marshal.SizeOf(objectAttributes); return objectAttributes; } private NTStatus CreateFile(out IntPtr handle, out FileStatus fileStatus, string nativePath, AccessMask desiredAccess, long allocationSize, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions) { UNICODE_STRING objectName = new UNICODE_STRING(nativePath); OBJECT_ATTRIBUTES objectAttributes = InitializeObjectAttributes(objectName); IO_STATUS_BLOCK ioStatusBlock; NTStatus status = NtCreateFile(out handle, (uint)desiredAccess, ref objectAttributes, out ioStatusBlock, ref allocationSize, fileAttributes, shareAccess, createDisposition, createOptions, IntPtr.Zero, 0); fileStatus = (FileStatus)ioStatusBlock.Information; return status; } private string ToNativePath(string path) { if (!path.StartsWith(@"\")) { path = @"\" + path; } return @"\??\" + m_directory.FullName + path; } public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string path, AccessMask desiredAccess, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, SecurityContext securityContext) { IntPtr fileHandle; string nativePath = ToNativePath(path); // NtQueryDirectoryFile will return STATUS_PENDING if the directory handle was not opened with SYNCHRONIZE and FILE_SYNCHRONOUS_IO_ALERT or FILE_SYNCHRONOUS_IO_NONALERT. // Our usage of NtNotifyChangeDirectoryFile assumes the directory handle is opened with SYNCHRONIZE and FILE_SYNCHRONOUS_IO_ALERT (or FILE_SYNCHRONOUS_IO_NONALERT starting from Windows Vista). // Note: Sometimes a directory will be opened without specifying FILE_DIRECTORY_FILE. desiredAccess |= AccessMask.SYNCHRONIZE; createOptions &= ~CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT; createOptions |= CreateOptions.FILE_SYNCHRONOUS_IO_ALERT; if ((createOptions & CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING) > 0 && ((FileAccessMask)desiredAccess & FileAccessMask.FILE_APPEND_DATA) > 0) { // FILE_NO_INTERMEDIATE_BUFFERING is incompatible with FILE_APPEND_DATA // [MS-SMB2] 3.3.5.9 suggests setting FILE_APPEND_DATA to zero in this case. desiredAccess = (AccessMask)((uint)desiredAccess & (uint)~FileAccessMask.FILE_APPEND_DATA); } NTStatus status = CreateFile(out fileHandle, out fileStatus, nativePath, desiredAccess, 0, fileAttributes, shareAccess, createDisposition, createOptions); handle = fileHandle; return status; } public NTStatus CloseFile(object handle) { // [MS-FSA] 2.1.5.4 The close operation has to complete any pending ChangeNotify request with STATUS_NOTIFY_CLEANUP. // - When closing a synchronous handle we must explicitly cancel any pending ChangeNotify request, otherwise the call to NtClose will hang. // We use request.Cleanup to tell that we should complete such ChangeNotify request with STATUS_NOTIFY_CLEANUP. // - When closing an asynchronous handle Windows will implicitly complete any pending ChangeNotify request with STATUS_NOTIFY_CLEANUP as required. List<PendingRequest> pendingRequests = m_pendingRequests.GetRequestsByHandle((IntPtr)handle); foreach (PendingRequest request in pendingRequests) { request.Cleanup = true; Cancel(request); } return NtClose((IntPtr)handle); } public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCount) { IO_STATUS_BLOCK ioStatusBlock; data = new byte[maxCount]; NTStatus status = NtReadFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, data, (uint)maxCount, ref offset, IntPtr.Zero); if (status == NTStatus.STATUS_SUCCESS) { int bytesRead = (int)ioStatusBlock.Information; if (bytesRead < maxCount) { data = ByteReader.ReadBytes(data, 0, bytesRead); } } return status; } public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offset, byte[] data) { IO_STATUS_BLOCK ioStatusBlock; NTStatus status = NtWriteFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, data, (uint)data.Length, ref offset, IntPtr.Zero); if (status == NTStatus.STATUS_SUCCESS) { numberOfBytesWritten = (int)ioStatusBlock.Information; } else { numberOfBytesWritten = 0; } return status; } public NTStatus FlushFileBuffers(object handle) { IO_STATUS_BLOCK ioStatusBlock; return NtFlushBuffersFile((IntPtr)handle, out ioStatusBlock); } public NTStatus LockFile(object handle, long byteOffset, long length, bool exclusiveLock) { IO_STATUS_BLOCK ioStatusBlock; return NtLockFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, ref byteOffset, ref length, 0, true, exclusiveLock); } public NTStatus UnlockFile(object handle, long byteOffset, long length) { IO_STATUS_BLOCK ioStatusBlock; return NtUnlockFile((IntPtr)handle, out ioStatusBlock, ref byteOffset, ref length, 0); } public NTStatus QueryDirectory(out List<QueryDirectoryFileInformation> result, object handle, string fileName, FileInformationClass informationClass) { IO_STATUS_BLOCK ioStatusBlock; byte[] buffer = new byte[QueryDirectoryBufferSize]; UNICODE_STRING fileNameStructure = new UNICODE_STRING(fileName); result = new List<QueryDirectoryFileInformation>(); bool restartScan = true; while (true) { NTStatus status = NtQueryDirectoryFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, buffer, (uint)buffer.Length, (byte)informationClass, false, ref fileNameStructure, restartScan); if (status == NTStatus.STATUS_NO_MORE_FILES) { break; } else if (status != NTStatus.STATUS_SUCCESS) { return status; } int numberOfBytesWritten = (int)ioStatusBlock.Information; List<QueryDirectoryFileInformation> page = QueryDirectoryFileInformation.ReadFileInformationList(buffer, 0, informationClass); result.AddRange(page); restartScan = false; } fileNameStructure.Dispose(); return NTStatus.STATUS_SUCCESS; } public NTStatus GetFileInformation(out FileInformation result, object handle, FileInformationClass informationClass) { IO_STATUS_BLOCK ioStatusBlock; byte[] buffer = new byte[FileInformationBufferSize]; NTStatus status = NtQueryInformationFile((IntPtr)handle, out ioStatusBlock, buffer, (uint)buffer.Length, (uint)informationClass); if (status == NTStatus.STATUS_SUCCESS) { int numberOfBytesWritten = (int)ioStatusBlock.Information; buffer = ByteReader.ReadBytes(buffer, 0, numberOfBytesWritten); result = FileInformation.GetFileInformation(buffer, 0, informationClass); } else { result = null; } return status; } public NTStatus SetFileInformation(object handle, FileInformation information) { IO_STATUS_BLOCK ioStatusBlock; if (information is FileRenameInformationType2) { FileRenameInformationType2 fileRenameInformationRemote = (FileRenameInformationType2)information; if (ProcessHelper.Is64BitProcess) { // We should not modify the FileRenameInformationType2 instance we received - the caller may use it later. FileRenameInformationType2 fileRenameInformationLocal = new FileRenameInformationType2(); fileRenameInformationLocal.ReplaceIfExists = fileRenameInformationRemote.ReplaceIfExists; fileRenameInformationLocal.FileName = ToNativePath(fileRenameInformationRemote.FileName); information = fileRenameInformationLocal; } else { // Note: WOW64 process should use FILE_RENAME_INFORMATION_TYPE_1. // Note: Server 2003 x64 has issues with using FILE_RENAME_INFORMATION under WOW64. FileRenameInformationType1 fileRenameInformationLocal = new FileRenameInformationType1(); fileRenameInformationLocal.ReplaceIfExists = fileRenameInformationRemote.ReplaceIfExists; fileRenameInformationLocal.FileName = ToNativePath(fileRenameInformationRemote.FileName); information = fileRenameInformationLocal; } } else if (information is FileLinkInformationType2) { FileLinkInformationType2 fileLinkInformationRemote = (FileLinkInformationType2)information; if (ProcessHelper.Is64BitProcess) { FileRenameInformationType2 fileLinkInformationLocal = new FileRenameInformationType2(); fileLinkInformationLocal.ReplaceIfExists = fileLinkInformationRemote.ReplaceIfExists; fileLinkInformationLocal.FileName = ToNativePath(fileLinkInformationRemote.FileName); information = fileLinkInformationRemote; } else { FileLinkInformationType1 fileLinkInformationLocal = new FileLinkInformationType1(); fileLinkInformationLocal.ReplaceIfExists = fileLinkInformationRemote.ReplaceIfExists; fileLinkInformationLocal.FileName = ToNativePath(fileLinkInformationRemote.FileName); information = fileLinkInformationRemote; } } byte[] buffer = information.GetBytes(); return NtSetInformationFile((IntPtr)handle, out ioStatusBlock, buffer, (uint)buffer.Length, (uint)information.FileInformationClass); } public NTStatus GetFileSystemInformation(out FileSystemInformation result, FileSystemInformationClass informationClass) { IO_STATUS_BLOCK ioStatusBlock; byte[] buffer = new byte[FileSystemInformationBufferSize]; IntPtr volumeHandle; FileStatus fileStatus; string nativePath = @"\??\" + m_directory.FullName.Substring(0, 3); NTStatus status = CreateFile(out volumeHandle, out fileStatus, nativePath, AccessMask.GENERIC_READ, 0, (FileAttributes)0, ShareAccess.Read, CreateDisposition.FILE_OPEN, (CreateOptions)0); result = null; if (status != NTStatus.STATUS_SUCCESS) { return status; } status = NtQueryVolumeInformationFile((IntPtr)volumeHandle, out ioStatusBlock, buffer, (uint)buffer.Length, (uint)informationClass); CloseFile(volumeHandle); if (status == NTStatus.STATUS_SUCCESS) { int numberOfBytesWritten = (int)ioStatusBlock.Information; buffer = ByteReader.ReadBytes(buffer, 0, numberOfBytesWritten); result = FileSystemInformation.GetFileSystemInformation(buffer, 0, informationClass); } return status; } public NTStatus SetFileSystemInformation(FileSystemInformation information) { return NTStatus.STATUS_NOT_SUPPORTED; } public NTStatus GetSecurityInformation(out SecurityDescriptor result, object handle, SecurityInformation securityInformation) { result = null; return NTStatus.STATUS_INVALID_DEVICE_REQUEST; } public NTStatus SetSecurityInformation(object handle, SecurityInformation securityInformation, SecurityDescriptor securityDescriptor) { // [MS-FSA] If the object store does not implement security, the operation MUST be failed with STATUS_INVALID_DEVICE_REQUEST. return NTStatus.STATUS_INVALID_DEVICE_REQUEST; } public NTStatus NotifyChange(out object ioRequest, object handle, NotifyChangeFilter completionFilter, bool watchTree, int outputBufferSize, OnNotifyChangeCompleted onNotifyChangeCompleted, object context) { byte[] buffer = new byte[outputBufferSize]; ManualResetEvent requestAddedEvent = new ManualResetEvent(false); PendingRequest request = new PendingRequest(); Thread m_thread = new Thread(delegate() { request.FileHandle = (IntPtr)handle; request.ThreadID = ThreadingHelper.GetCurrentThreadId(); m_pendingRequests.Add(request); // The request has been added, we can now return STATUS_PENDING. requestAddedEvent.Set(); // There is a possibility of race condition if the caller will wait for STATUS_PENDING and then immediate call Cancel, but this scenario is very unlikely. NTStatus status = NtNotifyChangeDirectoryFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out request.IOStatusBlock, buffer, (uint)buffer.Length, completionFilter, watchTree); if (status == NTStatus.STATUS_SUCCESS) { int length = (int)request.IOStatusBlock.Information; buffer = ByteReader.ReadBytes(buffer, 0, length); } else { const NTStatus STATUS_ALERTED = (NTStatus)0x00000101; const NTStatus STATUS_OBJECT_TYPE_MISMATCH = (NTStatus)0xC0000024; buffer = new byte[0]; if (status == STATUS_OBJECT_TYPE_MISMATCH) { status = NTStatus.STATUS_INVALID_HANDLE; } else if (status == STATUS_ALERTED) { status = NTStatus.STATUS_CANCELLED; } // If the handle is closing and we had to cancel a ChangeNotify request as part of a cleanup, // we return STATUS_NOTIFY_CLEANUP as specified in [MS-FSA] 2.1.5.4. if (status == NTStatus.STATUS_CANCELLED && request.Cleanup) { status = NTStatus.STATUS_NOTIFY_CLEANUP; } } onNotifyChangeCompleted(status, buffer, context); m_pendingRequests.Remove((IntPtr)handle, request.ThreadID); }); m_thread.Start(); // We must wait for the request to be added in order for Cancel to function properly. requestAddedEvent.WaitOne(); ioRequest = request; return NTStatus.STATUS_PENDING; } public NTStatus Cancel(object ioRequest) { PendingRequest request = (PendingRequest)ioRequest; const uint THREAD_TERMINATE = 0x00000001; const uint THREAD_ALERT = 0x00000004; uint threadID = request.ThreadID; IntPtr threadHandle = ThreadingHelper.OpenThread(THREAD_TERMINATE | THREAD_ALERT, false, threadID); if (threadHandle == IntPtr.Zero) { Win32Error error = (Win32Error)Marshal.GetLastWin32Error(); if (error == Win32Error.ERROR_INVALID_PARAMETER) { return NTStatus.STATUS_INVALID_HANDLE; } else { throw new Exception("OpenThread failed, Win32 error: " + error.ToString("D")); } } NTStatus status; if (Environment.OSVersion.Version.Major >= 6) { IO_STATUS_BLOCK ioStatusBlock; status = NtCancelSynchronousIoFile(threadHandle, IntPtr.Zero, out ioStatusBlock); } else { // The handle was opened for synchronous operation so NtNotifyChangeDirectoryFile is blocking. // We MUST use NtAlertThread to send a signal to stop the wait. The handle cannot be closed otherwise. // Note: The handle was opened with CreateOptions.FILE_SYNCHRONOUS_IO_ALERT as required. status = NtAlertThread(threadHandle); } ThreadingHelper.CloseHandle(threadHandle); m_pendingRequests.Remove(request.FileHandle, request.ThreadID); return status; } public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out byte[] output, int maxOutputLength) { switch ((IoControlCode)ctlCode) { case IoControlCode.FSCTL_IS_PATHNAME_VALID: case IoControlCode.FSCTL_GET_COMPRESSION: case IoControlCode.FSCTL_GET_RETRIEVAL_POINTERS: case IoControlCode.FSCTL_SET_OBJECT_ID: case IoControlCode.FSCTL_GET_OBJECT_ID: case IoControlCode.FSCTL_DELETE_OBJECT_ID: case IoControlCode.FSCTL_SET_OBJECT_ID_EXTENDED: case IoControlCode.FSCTL_CREATE_OR_GET_OBJECT_ID: case IoControlCode.FSCTL_SET_SPARSE: case IoControlCode.FSCTL_READ_FILE_USN_DATA: case IoControlCode.FSCTL_SET_DEFECT_MANAGEMENT: case IoControlCode.FSCTL_SET_COMPRESSION: case IoControlCode.FSCTL_QUERY_SPARING_INFO: case IoControlCode.FSCTL_QUERY_ON_DISK_VOLUME_INFO: case IoControlCode.FSCTL_SET_ZERO_ON_DEALLOCATION: case IoControlCode.FSCTL_QUERY_FILE_REGIONS: case IoControlCode.FSCTL_QUERY_ALLOCATED_RANGES: case IoControlCode.FSCTL_SET_ZERO_DATA: { IO_STATUS_BLOCK ioStatusBlock; output = new byte[maxOutputLength]; NTStatus status = NtFsControlFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, ctlCode, input, (uint)input.Length, output, (uint)maxOutputLength); if (status == NTStatus.STATUS_SUCCESS) { int numberOfBytesWritten = (int)ioStatusBlock.Information; output = ByteReader.ReadBytes(output, 0, numberOfBytesWritten); } return status; } default: { output = null; return NTStatus.STATUS_NOT_SUPPORTED; } } } } }