//
// Copyright (c) 2008-2011, Kenneth Bell
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
namespace DiscUtils.Ntfs
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security.AccessControl;
using System.Text.RegularExpressions;
///
/// Class for accessing NTFS file systems.
///
public sealed class NtfsFileSystem : DiscFileSystem, IClusterBasedFileSystem, IWindowsFileSystem, IDiagnosticTraceable
{
private const FileAttributes NonSettableFileAttributes = FileAttributes.Directory | FileAttributes.Offline | FileAttributes.ReparsePoint;
private NtfsContext _context;
private VolumeInformation _volumeInfo;
// Top-level file system structures
// Working state
private ObjectCache _fileCache;
///
/// Initializes a new instance of the NtfsFileSystem class.
///
/// The stream containing the NTFS file system.
public NtfsFileSystem(Stream stream)
: base(new NtfsOptions())
{
_context = new NtfsContext();
_context.RawStream = stream;
_context.Options = NtfsOptions;
_context.GetFileByIndex = GetFile;
_context.GetFileByRef = GetFile;
_context.GetDirectoryByRef = GetDirectory;
_context.GetDirectoryByIndex = GetDirectory;
_context.AllocateFile = AllocateFile;
_context.ForgetFile = ForgetFile;
_context.ReadOnly = !stream.CanWrite;
_fileCache = new ObjectCache();
stream.Position = 0;
byte[] bytes = Utilities.ReadFully(stream, 512);
_context.BiosParameterBlock = BiosParameterBlock.FromBytes(bytes, 0);
if (!IsValidBPB(_context.BiosParameterBlock, stream.Length))
{
throw new InvalidFileSystemException("BIOS Parameter Block is invalid for an NTFS file system");
}
if (NtfsOptions.ReadCacheEnabled)
{
BlockCacheSettings cacheSettings = new BlockCacheSettings();
cacheSettings.BlockSize = _context.BiosParameterBlock.BytesPerCluster;
_context.RawStream = new BlockCacheStream(SparseStream.FromStream(stream, Ownership.None), Ownership.None, cacheSettings);
}
// Bootstrap the Master File Table
_context.Mft = new MasterFileTable(_context);
File mftFile = new File(_context, _context.Mft.GetBootstrapRecord());
_fileCache[MasterFileTable.MftIndex] = mftFile;
_context.Mft.Initialize(mftFile);
// Get volume information (includes version number)
File volumeInfoFile = GetFile(MasterFileTable.VolumeIndex);
_volumeInfo = volumeInfoFile.GetStream(AttributeType.VolumeInformation, null).GetContent();
// Initialize access to the other well-known metadata files
_context.ClusterBitmap = new ClusterBitmap(GetFile(MasterFileTable.BitmapIndex));
_context.AttributeDefinitions = new AttributeDefinitions(GetFile(MasterFileTable.AttrDefIndex));
_context.UpperCase = new UpperCase(GetFile(MasterFileTable.UpCaseIndex));
if (_volumeInfo.Version >= VolumeInformation.VersionW2k)
{
_context.SecurityDescriptors = new SecurityDescriptors(GetFile(MasterFileTable.SecureIndex));
_context.ObjectIds = new ObjectIds(GetFile(GetDirectoryEntry(@"$Extend\$ObjId").Reference));
_context.ReparsePoints = new ReparsePoints(GetFile(GetDirectoryEntry(@"$Extend\$Reparse").Reference));
_context.Quotas = new Quotas(GetFile(GetDirectoryEntry(@"$Extend\$Quota").Reference));
}
#if false
byte[] buffer = new byte[1024];
for (int i = 0; i < buffer.Length; ++i)
{
buffer[i] = 0xFF;
}
using (Stream s = OpenFile("$LogFile", FileMode.Open, FileAccess.ReadWrite))
{
while (s.Position != s.Length)
{
s.Write(buffer, 0, (int)Math.Min(buffer.Length, s.Length - s.Position));
}
}
#endif
}
private delegate void StandardInformationModifier(StandardInformation si);
///
/// Gets the options that control how the file system is interpreted.
///
public NtfsOptions NtfsOptions
{
get { return (NtfsOptions)Options; }
}
///
/// Gets the friendly name for the file system.
///
public override string FriendlyName
{
get { return "Microsoft NTFS"; }
}
///
/// Indicates if the file system supports write operations.
///
public override bool CanWrite
{
// For now, we don't...
get { return !_context.ReadOnly; }
}
///
/// Gets the size of each cluster (in bytes).
///
public long ClusterSize
{
get { return _context.BiosParameterBlock.BytesPerCluster; }
}
///
/// Gets the total number of clusters managed by the file system.
///
public long TotalClusters
{
get { return Utilities.Ceil(_context.BiosParameterBlock.TotalSectors64, _context.BiosParameterBlock.SectorsPerCluster); }
}
///
/// Gets the volume label.
///
public override string VolumeLabel
{
get
{
File volumeFile = GetFile(MasterFileTable.VolumeIndex);
NtfsStream volNameStream = volumeFile.GetStream(AttributeType.VolumeName, null);
return volNameStream.GetContent().Name;
}
}
private bool CreateShortNames
{
get
{
return _context.Options.ShortNameCreation == ShortFileNameOption.Enabled
|| (_context.Options.ShortNameCreation == ShortFileNameOption.UseVolumeFlag
&& (_volumeInfo.Flags & VolumeInformationFlags.DisableShortNameCreation) == 0);
}
}
///
/// Initializes a new NTFS file system.
///
/// The stream to write the new file system to.
/// The label for the new file system.
/// The disk geometry of the disk containing the new file system.
/// The first sector of the new file system on the disk.
/// The number of sectors allocated to the new file system on the disk.
/// The newly-initialized file system.
public static NtfsFileSystem Format(
Stream stream,
string label,
Geometry diskGeometry,
long firstSector,
long sectorCount)
{
NtfsFormatter formatter = new NtfsFormatter();
formatter.Label = label;
formatter.DiskGeometry = diskGeometry;
formatter.FirstSector = firstSector;
formatter.SectorCount = sectorCount;
return formatter.Format(stream);
}
///
/// Initializes a new NTFS file system.
///
/// The stream to write the new file system to.
/// The label for the new file system.
/// The disk geometry of the disk containing the new file system.
/// The first sector of the new file system on the disk.
/// The number of sectors allocated to the new file system on the disk.
/// The Operating System's boot code.
/// The newly-initialized file system.
public static NtfsFileSystem Format(
Stream stream,
string label,
Geometry diskGeometry,
long firstSector,
long sectorCount,
byte[] bootCode)
{
NtfsFormatter formatter = new NtfsFormatter();
formatter.Label = label;
formatter.DiskGeometry = diskGeometry;
formatter.FirstSector = firstSector;
formatter.SectorCount = sectorCount;
formatter.BootCode = bootCode;
return formatter.Format(stream);
}
///
/// Initializes a new NTFS file system.
///
/// The stream to write the new file system to.
/// The label for the new file system.
/// The disk geometry of the disk containing the new file system.
/// The first sector of the new file system on the disk.
/// The number of sectors allocated to the new file system on the disk.
/// The formatting options.
/// The newly-initialized file system.
public static NtfsFileSystem Format(
Stream stream,
string label,
Geometry diskGeometry,
long firstSector,
long sectorCount,
NtfsFormatOptions options)
{
NtfsFormatter formatter = new NtfsFormatter();
formatter.Label = label;
formatter.DiskGeometry = diskGeometry;
formatter.FirstSector = firstSector;
formatter.SectorCount = sectorCount;
formatter.BootCode = options.BootCode;
formatter.ComputerAccount = options.ComputerAccount;
return formatter.Format(stream);
}
///
/// Initializes a new NTFS file system.
///
/// The volume to format.
/// The label for the new file system.
/// The newly-initialized file system.
public static NtfsFileSystem Format(
VolumeInfo volume,
string label)
{
NtfsFormatter formatter = new NtfsFormatter();
formatter.Label = label;
formatter.DiskGeometry = volume.BiosGeometry ?? Geometry.Null;
formatter.FirstSector = volume.PhysicalStartSector;
formatter.SectorCount = volume.Length / Sizes.Sector;
return formatter.Format(volume.Open());
}
///
/// Initializes a new NTFS file system.
///
/// The volume to format.
/// The label for the new file system.
/// The Operating System's boot code.
/// The newly-initialized file system.
public static NtfsFileSystem Format(
VolumeInfo volume,
string label,
byte[] bootCode)
{
NtfsFormatter formatter = new NtfsFormatter();
formatter.Label = label;
formatter.DiskGeometry = volume.BiosGeometry ?? Geometry.Null;
formatter.FirstSector = volume.PhysicalStartSector;
formatter.SectorCount = volume.Length / Sizes.Sector;
formatter.BootCode = bootCode;
return formatter.Format(volume.Open());
}
///
/// Initializes a new NTFS file system.
///
/// The volume to format.
/// The label for the new file system.
/// The formatting options.
/// The newly-initialized file system.
public static NtfsFileSystem Format(
VolumeInfo volume,
string label,
NtfsFormatOptions options)
{
NtfsFormatter formatter = new NtfsFormatter();
formatter.Label = label;
formatter.DiskGeometry = volume.BiosGeometry ?? Geometry.Null;
formatter.FirstSector = volume.PhysicalStartSector;
formatter.SectorCount = volume.Length / Sizes.Sector;
formatter.BootCode = options.BootCode;
formatter.ComputerAccount = options.ComputerAccount;
return formatter.Format(volume.Open());
}
///
/// Detects if a stream contains an NTFS file system.
///
/// The stream to inspect.
/// true if NTFS is detected, else false.
public static bool Detect(Stream stream)
{
if (stream.Length < 512)
{
return false;
}
stream.Position = 0;
byte[] bytes = Utilities.ReadFully(stream, 512);
BiosParameterBlock bpb = BiosParameterBlock.FromBytes(bytes, 0);
return IsValidBPB(bpb, stream.Length);
}
///
/// Gets the Master File Table for this file system.
///
///
/// Use the returned object to explore the internals of the file system - most people will
/// never need to use this.
///
/// The Master File Table.
public Internals.MasterFileTable GetMasterFileTable()
{
return new Internals.MasterFileTable(_context, _context.Mft);
}
///
/// Copies an existing file to a new file, allowing overwriting of an existing file.
///
/// The source file.
/// The destination file.
/// Whether to permit over-writing of an existing file.
public override void CopyFile(string sourceFile, string destinationFile, bool overwrite)
{
using (new NtfsTransaction())
{
DirectoryEntry sourceParentDirEntry = GetDirectoryEntry(Utilities.GetDirectoryFromPath(sourceFile));
if (sourceParentDirEntry == null || !sourceParentDirEntry.IsDirectory)
{
throw new FileNotFoundException("No such file", sourceFile);
}
Directory sourceParentDir = GetDirectory(sourceParentDirEntry.Reference);
DirectoryEntry sourceEntry = sourceParentDir.GetEntryByName(Utilities.GetFileFromPath(sourceFile));
if (sourceEntry == null || sourceEntry.IsDirectory)
{
throw new FileNotFoundException("No such file", sourceFile);
}
File origFile = GetFile(sourceEntry.Reference);
DirectoryEntry destParentDirEntry = GetDirectoryEntry(Utilities.GetDirectoryFromPath(destinationFile));
if (destParentDirEntry == null || !destParentDirEntry.IsDirectory)
{
throw new FileNotFoundException("Destination directory not found", destinationFile);
}
Directory destParentDir = GetDirectory(destParentDirEntry.Reference);
DirectoryEntry destDirEntry = destParentDir.GetEntryByName(Utilities.GetFileFromPath(destinationFile));
if (destDirEntry != null && !destDirEntry.IsDirectory)
{
if (overwrite)
{
if (destDirEntry.Reference.MftIndex == sourceEntry.Reference.MftIndex)
{
throw new IOException("Destination file already exists and is the source file");
}
File oldFile = GetFile(destDirEntry.Reference);
destParentDir.RemoveEntry(destDirEntry);
if (oldFile.HardLinkCount == 0)
{
oldFile.Delete();
}
}
else
{
throw new IOException("Destination file already exists");
}
}
File newFile = File.CreateNew(_context, destParentDir.StandardInformation.FileAttributes);
foreach (var origStream in origFile.AllStreams)
{
NtfsStream newStream = newFile.GetStream(origStream.AttributeType, origStream.Name);
switch (origStream.AttributeType)
{
case AttributeType.Data:
if (newStream == null)
{
newStream = newFile.CreateStream(origStream.AttributeType, origStream.Name);
}
using (SparseStream s = origStream.Open(FileAccess.Read))
using (SparseStream d = newStream.Open(FileAccess.Write))
{
byte[] buffer = new byte[64 * Sizes.OneKiB];
int numRead;
do
{
numRead = s.Read(buffer, 0, buffer.Length);
d.Write(buffer, 0, numRead);
}
while (numRead != 0);
}
break;
case AttributeType.StandardInformation:
StandardInformation newSi = origStream.GetContent();
newStream.SetContent(newSi);
break;
}
}
AddFileToDirectory(newFile, destParentDir, Utilities.GetFileFromPath(destinationFile), null);
destParentDirEntry.UpdateFrom(destParentDir);
}
}
///
/// Creates a directory.
///
/// The path of the new directory.
public override void CreateDirectory(string path)
{
CreateDirectory(path, null);
}
///
/// Creates a directory.
///
/// The path of the new directory.
/// Options controlling attributes of the new Director, or null for defaults.
public void CreateDirectory(string path, NewFileOptions options)
{
using (new NtfsTransaction())
{
string[] pathElements = path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
Directory focusDir = GetDirectory(MasterFileTable.RootDirIndex);
DirectoryEntry focusDirEntry = focusDir.DirectoryEntry;
for (int i = 0; i < pathElements.Length; ++i)
{
DirectoryEntry childDirEntry = focusDir.GetEntryByName(pathElements[i]);
if (childDirEntry == null)
{
FileAttributeFlags newDirAttrs = focusDir.StandardInformation.FileAttributes;
if (options != null && options.Compressed.HasValue)
{
if (options.Compressed.Value)
{
newDirAttrs |= FileAttributeFlags.Compressed;
}
else
{
newDirAttrs &= ~FileAttributeFlags.Compressed;
}
}
Directory childDir = Directory.CreateNew(_context, newDirAttrs);
try
{
childDirEntry = AddFileToDirectory(childDir, focusDir, pathElements[i], options);
RawSecurityDescriptor parentSd = DoGetSecurity(focusDir);
RawSecurityDescriptor newSd;
if (options != null && options.SecurityDescriptor != null)
{
newSd = options.SecurityDescriptor;
}
else
{
newSd = SecurityDescriptor.CalcNewObjectDescriptor(parentSd, false);
}
DoSetSecurity(childDir, newSd);
childDirEntry.UpdateFrom(childDir);
// Update the directory entry by which we found the directory we've just modified
focusDirEntry.UpdateFrom(focusDir);
focusDir = childDir;
}
finally
{
if (childDir.HardLinkCount == 0)
{
childDir.Delete();
}
}
}
else
{
focusDir = GetDirectory(childDirEntry.Reference);
}
focusDirEntry = childDirEntry;
}
}
}
///
/// Deletes a directory.
///
/// The path of the directory to delete.
public override void DeleteDirectory(string path)
{
using (new NtfsTransaction())
{
if (string.IsNullOrEmpty(path))
{
throw new IOException("Unable to delete root directory");
}
string parent = Utilities.GetDirectoryFromPath(path);
DirectoryEntry parentDirEntry = GetDirectoryEntry(parent);
if (parentDirEntry == null || !parentDirEntry.IsDirectory)
{
throw new DirectoryNotFoundException("No such directory: " + path);
}
Directory parentDir = GetDirectory(parentDirEntry.Reference);
DirectoryEntry dirEntry = parentDir.GetEntryByName(Utilities.GetFileFromPath(path));
if (dirEntry == null || !dirEntry.IsDirectory)
{
throw new DirectoryNotFoundException("No such directory: " + path);
}
Directory dir = GetDirectory(dirEntry.Reference);
if (!dir.IsEmpty)
{
throw new IOException("Unable to delete non-empty directory");
}
if ((dirEntry.Details.FileAttributes & FileAttributes.ReparsePoint) != 0)
{
RemoveReparsePoint(dir);
}
RemoveFileFromDirectory(parentDir, dir, Utilities.GetFileFromPath(path));
if (dir.HardLinkCount == 0)
{
dir.Delete();
}
}
}
///
/// Deletes a file.
///
/// The path of the file to delete.
public override void DeleteFile(string path)
{
using (new NtfsTransaction())
{
string attributeName;
AttributeType attributeType;
string dirEntryPath = ParsePath(path, out attributeName, out attributeType);
string parentDirPath = Utilities.GetDirectoryFromPath(dirEntryPath);
DirectoryEntry parentDirEntry = GetDirectoryEntry(parentDirPath);
if (parentDirEntry == null || !parentDirEntry.IsDirectory)
{
throw new FileNotFoundException("No such file", path);
}
Directory parentDir = GetDirectory(parentDirEntry.Reference);
DirectoryEntry dirEntry = parentDir.GetEntryByName(Utilities.GetFileFromPath(dirEntryPath));
if (dirEntry == null || dirEntry.IsDirectory)
{
throw new FileNotFoundException("No such file", path);
}
File file = GetFile(dirEntry.Reference);
if (string.IsNullOrEmpty(attributeName) && attributeType == AttributeType.Data)
{
if ((dirEntry.Details.FileAttributes & FileAttributes.ReparsePoint) != 0)
{
RemoveReparsePoint(file);
}
RemoveFileFromDirectory(parentDir, file, Utilities.GetFileFromPath(path));
if (file.HardLinkCount == 0)
{
file.Delete();
}
}
else
{
NtfsStream attrStream = file.GetStream(attributeType, attributeName);
if (attrStream == null)
{
throw new FileNotFoundException("No such attribute: " + attributeName, path);
}
else
{
file.RemoveStream(attrStream);
}
}
}
}
///
/// Indicates if a directory exists.
///
/// The path to test.
/// true if the directory exists.
public override bool DirectoryExists(string path)
{
using (new NtfsTransaction())
{
// Special case - root directory
if (String.IsNullOrEmpty(path))
{
return true;
}
else
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
return dirEntry != null && (dirEntry.Details.FileAttributes & FileAttributes.Directory) != 0;
}
}
}
///
/// Indicates if a file exists.
///
/// The path to test.
/// true if the file exists.
public override bool FileExists(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
return dirEntry != null && (dirEntry.Details.FileAttributes & FileAttributes.Directory) == 0;
}
}
///
/// Gets the names of subdirectories in a specified directory matching a specified
/// search pattern, using a value to determine whether to search subdirectories.
///
/// The path to search.
/// The search string to match against.
/// Indicates whether to search subdirectories.
/// Array of directories matching the search pattern.
public override string[] GetDirectories(string path, string searchPattern, SearchOption searchOption)
{
using (new NtfsTransaction())
{
Regex re = Utilities.ConvertWildcardsToRegEx(searchPattern);
List dirs = new List();
DoSearch(dirs, path, re, searchOption == SearchOption.AllDirectories, true, false);
return dirs.ToArray();
}
}
///
/// Gets the names of files in a specified directory matching a specified
/// search pattern, using a value to determine whether to search subdirectories.
///
/// The path to search.
/// The search string to match against.
/// Indicates whether to search subdirectories.
/// Array of files matching the search pattern.
public override string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
{
using (new NtfsTransaction())
{
Regex re = Utilities.ConvertWildcardsToRegEx(searchPattern);
List results = new List();
DoSearch(results, path, re, searchOption == SearchOption.AllDirectories, false, true);
return results.ToArray();
}
}
///
/// Gets the names of all files and subdirectories in a specified directory.
///
/// The path to search.
/// Array of files and subdirectories.
public override string[] GetFileSystemEntries(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry parentDirEntry = GetDirectoryEntry(path);
if (parentDirEntry == null)
{
throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, "The directory '{0}' does not exist", path));
}
Directory parentDir = GetDirectory(parentDirEntry.Reference);
return Utilities.Map(parentDir.GetAllEntries(true), (m) => Utilities.CombinePaths(path, m.Details.FileName));
}
}
///
/// Gets the names of files and subdirectories in a specified directory matching a specified
/// search pattern.
///
/// The path to search.
/// The search string to match against.
/// Array of files and subdirectories matching the search pattern.
public override string[] GetFileSystemEntries(string path, string searchPattern)
{
using (new NtfsTransaction())
{
// TODO: Be smarter, use the B*Tree for better performance when the start of the pattern is known
// characters
Regex re = Utilities.ConvertWildcardsToRegEx(searchPattern);
DirectoryEntry parentDirEntry = GetDirectoryEntry(path);
if (parentDirEntry == null)
{
throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, "The directory '{0}' does not exist", path));
}
Directory parentDir = GetDirectory(parentDirEntry.Reference);
List result = new List();
foreach (DirectoryEntry dirEntry in parentDir.GetAllEntries(true))
{
if (re.IsMatch(dirEntry.Details.FileName))
{
result.Add(Utilities.CombinePaths(path, dirEntry.Details.FileName));
}
}
return result.ToArray();
}
}
///
/// Moves a directory.
///
/// The directory to move.
/// The target directory name.
public override void MoveDirectory(string sourceDirectoryName, string destinationDirectoryName)
{
using (new NtfsTransaction())
{
using (new NtfsTransaction())
{
DirectoryEntry sourceParentDirEntry = GetDirectoryEntry(Utilities.GetDirectoryFromPath(sourceDirectoryName));
if (sourceParentDirEntry == null || !sourceParentDirEntry.IsDirectory)
{
throw new DirectoryNotFoundException("No such directory: " + sourceDirectoryName);
}
Directory sourceParentDir = GetDirectory(sourceParentDirEntry.Reference);
DirectoryEntry sourceEntry = sourceParentDir.GetEntryByName(Utilities.GetFileFromPath(sourceDirectoryName));
if (sourceEntry == null || !sourceEntry.IsDirectory)
{
throw new DirectoryNotFoundException("No such directory: " + sourceDirectoryName);
}
File file = GetFile(sourceEntry.Reference);
DirectoryEntry destParentDirEntry = GetDirectoryEntry(Utilities.GetDirectoryFromPath(destinationDirectoryName));
if (destParentDirEntry == null || !destParentDirEntry.IsDirectory)
{
throw new DirectoryNotFoundException("Destination directory not found: " + destinationDirectoryName);
}
Directory destParentDir = GetDirectory(destParentDirEntry.Reference);
DirectoryEntry destDirEntry = destParentDir.GetEntryByName(Utilities.GetFileFromPath(destinationDirectoryName));
if (destDirEntry != null)
{
throw new IOException("Destination directory already exists");
}
RemoveFileFromDirectory(sourceParentDir, file, sourceEntry.Details.FileName);
AddFileToDirectory(file, destParentDir, Utilities.GetFileFromPath(destinationDirectoryName), null);
}
}
}
///
/// Moves a file, allowing an existing file to be overwritten.
///
/// The file to move.
/// The target file name.
/// Whether to permit a destination file to be overwritten.
public override void MoveFile(string sourceName, string destinationName, bool overwrite)
{
using (new NtfsTransaction())
{
DirectoryEntry sourceParentDirEntry = GetDirectoryEntry(Utilities.GetDirectoryFromPath(sourceName));
if (sourceParentDirEntry == null || !sourceParentDirEntry.IsDirectory)
{
throw new FileNotFoundException("No such file", sourceName);
}
Directory sourceParentDir = GetDirectory(sourceParentDirEntry.Reference);
DirectoryEntry sourceEntry = sourceParentDir.GetEntryByName(Utilities.GetFileFromPath(sourceName));
if (sourceEntry == null || sourceEntry.IsDirectory)
{
throw new FileNotFoundException("No such file", sourceName);
}
File file = GetFile(sourceEntry.Reference);
DirectoryEntry destParentDirEntry = GetDirectoryEntry(Utilities.GetDirectoryFromPath(destinationName));
if (destParentDirEntry == null || !destParentDirEntry.IsDirectory)
{
throw new FileNotFoundException("Destination directory not found", destinationName);
}
Directory destParentDir = GetDirectory(destParentDirEntry.Reference);
DirectoryEntry destDirEntry = destParentDir.GetEntryByName(Utilities.GetFileFromPath(destinationName));
if (destDirEntry != null && !destDirEntry.IsDirectory)
{
if (overwrite)
{
if (destDirEntry.Reference.MftIndex == sourceEntry.Reference.MftIndex)
{
throw new IOException("Destination file already exists and is the source file");
}
File oldFile = GetFile(destDirEntry.Reference);
destParentDir.RemoveEntry(destDirEntry);
if (oldFile.HardLinkCount == 0)
{
oldFile.Delete();
}
}
else
{
throw new IOException("Destination file already exists");
}
}
RemoveFileFromDirectory(sourceParentDir, file, sourceEntry.Details.FileName);
AddFileToDirectory(file, destParentDir, Utilities.GetFileFromPath(destinationName), null);
}
}
///
/// Opens the specified file.
///
/// The full path of the file to open.
/// The file mode for the created stream.
/// The access permissions for the returned stream.
/// The new stream.
public override SparseStream OpenFile(string path, FileMode mode, FileAccess access)
{
return OpenFile(path, mode, access, null);
}
///
/// Opens the specified file.
///
/// The full path of the file to open.
/// The file mode for the created stream.
/// The access permissions for the returned stream.
/// Options controlling attributes of a new file, or null for defaults (ignored if file exists).
/// The new stream.
public SparseStream OpenFile(string path, FileMode mode, FileAccess access, NewFileOptions options)
{
using (new NtfsTransaction())
{
string attributeName;
AttributeType attributeType;
string dirEntryPath = ParsePath(path, out attributeName, out attributeType);
DirectoryEntry entry = GetDirectoryEntry(dirEntryPath);
if (entry == null)
{
if (mode == FileMode.Open)
{
throw new FileNotFoundException("No such file", path);
}
else
{
entry = CreateNewFile(dirEntryPath, options);
}
}
else if (mode == FileMode.CreateNew)
{
throw new IOException("File already exists");
}
if ((entry.Details.FileAttributes & FileAttributes.Directory) != 0 && attributeType == AttributeType.Data)
{
throw new IOException("Attempt to open directory as a file");
}
else
{
File file = GetFile(entry.Reference);
NtfsStream ntfsStream = file.GetStream(attributeType, attributeName);
if (ntfsStream == null)
{
if (mode == FileMode.Create || mode == FileMode.OpenOrCreate)
{
ntfsStream = file.CreateStream(attributeType, attributeName);
}
else
{
throw new FileNotFoundException("No such attribute on file", path);
}
}
SparseStream stream = new NtfsFileStream(this, entry, attributeType, attributeName, access);
if (mode == FileMode.Create || mode == FileMode.Truncate)
{
stream.SetLength(0);
}
return stream;
}
}
}
///
/// Opens an existing file stream.
///
/// The file containing the stream.
/// The type of the stream.
/// The name of the stream.
/// The desired access to the stream.
/// A stream that can be used to access the file stream.
[Obsolete(@"Use OpenFile with filename:attributename:$attributetype syntax (e.g. \FILE.TXT:STREAM:$DATA)", false)]
public SparseStream OpenRawStream(string file, AttributeType type, string name, FileAccess access)
{
using (new NtfsTransaction())
{
DirectoryEntry entry = GetDirectoryEntry(file);
if (entry == null)
{
throw new FileNotFoundException("No such file", file);
}
File fileObj = GetFile(entry.Reference);
return fileObj.OpenStream(type, name, access);
}
}
///
/// Gets the attributes of a file or directory.
///
/// The file or directory to inspect.
/// The attributes of the file or directory.
public override FileAttributes GetAttributes(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
return dirEntry.Details.FileAttributes;
}
}
}
///
/// Sets the attributes of a file or directory.
///
/// The file or directory to change.
/// The new attributes of the file or directory.
public override void SetAttributes(string path, FileAttributes newValue)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
FileAttributes oldValue = dirEntry.Details.FileAttributes;
FileAttributes changedAttribs = oldValue ^ newValue;
if (changedAttribs == 0)
{
// Abort - nothing changed
return;
}
if ((changedAttribs & NonSettableFileAttributes) != 0)
{
throw new ArgumentException("Attempt to change attributes that are read-only", "newValue");
}
File file = GetFile(dirEntry.Reference);
if ((changedAttribs & FileAttributes.SparseFile) != 0)
{
if (dirEntry.IsDirectory)
{
throw new ArgumentException("Attempt to change sparse attribute on a directory", "newValue");
}
if ((newValue & FileAttributes.SparseFile) == 0)
{
throw new ArgumentException("Attempt to remove sparse attribute from file", "newValue");
}
else
{
NtfsAttribute ntfsAttr = file.GetAttribute(AttributeType.Data, null);
if ((ntfsAttr.Flags & AttributeFlags.Compressed) != 0)
{
throw new ArgumentException("Attempt to mark compressed file as sparse", "newValue");
}
ntfsAttr.Flags |= AttributeFlags.Sparse;
if (ntfsAttr.IsNonResident)
{
ntfsAttr.CompressedDataSize = ntfsAttr.PrimaryRecord.AllocatedLength;
ntfsAttr.CompressionUnitSize = 16;
((NonResidentAttributeBuffer)ntfsAttr.RawBuffer).AlignVirtualClusterCount();
}
}
}
if ((changedAttribs & FileAttributes.Compressed) != 0 && !dirEntry.IsDirectory)
{
if ((newValue & FileAttributes.Compressed) == 0)
{
throw new ArgumentException("Attempt to remove compressed attribute from file", "newValue");
}
else
{
NtfsAttribute ntfsAttr = file.GetAttribute(AttributeType.Data, null);
if ((ntfsAttr.Flags & AttributeFlags.Sparse) != 0)
{
throw new ArgumentException("Attempt to mark sparse file as compressed", "newValue");
}
ntfsAttr.Flags |= AttributeFlags.Compressed;
if (ntfsAttr.IsNonResident)
{
ntfsAttr.CompressedDataSize = ntfsAttr.PrimaryRecord.AllocatedLength;
ntfsAttr.CompressionUnitSize = 16;
((NonResidentAttributeBuffer)ntfsAttr.RawBuffer).AlignVirtualClusterCount();
}
}
}
UpdateStandardInformation(dirEntry, file, delegate (StandardInformation si) { si.FileAttributes = FileNameRecord.SetAttributes(newValue, si.FileAttributes); });
}
}
///
/// Gets the creation time (in UTC) of a file or directory.
///
/// The path of the file or directory.
/// The creation time.
public override DateTime GetCreationTimeUtc(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
return dirEntry.Details.CreationTime;
}
}
}
///
/// Sets the creation time (in UTC) of a file or directory.
///
/// The path of the file or directory.
/// The new time to set.
public override void SetCreationTimeUtc(string path, DateTime newTime)
{
using (new NtfsTransaction())
{
UpdateStandardInformation(path, delegate (StandardInformation si) { si.CreationTime = newTime; });
}
}
///
/// Gets the last access time (in UTC) of a file or directory.
///
/// The path of the file or directory.
/// The last access time.
public override DateTime GetLastAccessTimeUtc(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
return dirEntry.Details.LastAccessTime;
}
}
}
///
/// Sets the last access time (in UTC) of a file or directory.
///
/// The path of the file or directory.
/// The new time to set.
public override void SetLastAccessTimeUtc(string path, DateTime newTime)
{
using (new NtfsTransaction())
{
UpdateStandardInformation(path, delegate (StandardInformation si) { si.LastAccessTime = newTime; });
}
}
///
/// Gets the last modification time (in UTC) of a file or directory.
///
/// The path of the file or directory.
/// The last write time.
public override DateTime GetLastWriteTimeUtc(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
return dirEntry.Details.ModificationTime;
}
}
}
///
/// Sets the last modification time (in local time) of a file or directory.
///
/// The path of the file or directory.
/// The new time to set.
public override void SetLastWriteTimeUtc(string path, DateTime newTime)
{
using (new NtfsTransaction())
{
UpdateStandardInformation(path, delegate (StandardInformation si) { si.ModificationTime = newTime; });
}
}
///
/// Gets the length of a file.
///
/// The path to the file.
/// The length in bytes.
public override long GetFileLength(string path)
{
using (new NtfsTransaction())
{
string attributeName;
AttributeType attributeType;
string dirEntryPath = ParsePath(path, out attributeName, out attributeType);
DirectoryEntry dirEntry = GetDirectoryEntry(dirEntryPath);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
// Ordinary file length request, use info from directory entry for efficiency - if allowed
if (NtfsOptions.FileLengthFromDirectoryEntries && attributeName == null && attributeType == AttributeType.Data)
{
return (long)dirEntry.Details.RealSize;
}
// Alternate stream / attribute, pull info from attribute record
File file = GetFile(dirEntry.Reference);
var attr = file.GetAttribute(attributeType, attributeName);
if (attr == null)
{
throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, "No such attribute '{0}({1})'", attributeName, attributeType));
}
return attr.Length;
}
}
///
/// Converts a cluster (index) into an absolute byte position in the underlying stream.
///
/// The cluster to convert.
/// The corresponding absolute byte position.
public long ClusterToOffset(long cluster)
{
return cluster * ClusterSize;
}
///
/// Converts an absolute byte position in the underlying stream to a cluster (index).
///
/// The byte position to convert.
/// The cluster containing the specified byte.
public long OffsetToCluster(long offset)
{
return offset / ClusterSize;
}
///
/// Converts a file name to the list of clusters occupied by the file's data.
///
/// The path to inspect.
/// The clusters as a list of cluster ranges.
/// Note that in some file systems, small files may not have dedicated
/// clusters. Only dedicated clusters will be returned.
public Range[] PathToClusters(string path)
{
string plainPath;
string attributeName;
SplitPath(path, out plainPath, out attributeName);
DirectoryEntry dirEntry = GetDirectoryEntry(plainPath);
if (dirEntry == null || dirEntry.IsDirectory)
{
throw new FileNotFoundException("No such file", path);
}
File file = GetFile(dirEntry.Reference);
NtfsStream stream = file.GetStream(AttributeType.Data, attributeName);
if (stream == null)
{
throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, "File does not contain '{0}' data attribute", attributeName), path);
}
return stream.GetClusters();
}
///
/// Converts a file name to the extents containing its data.
///
/// The path to inspect.
/// The file extents, as absolute byte positions in the underlying stream.
/// Use this method with caution - NTFS supports encrypted, sparse and compressed files
/// where bytes are not directly stored in extents. Small files may be entirely stored in the
/// Master File Table, where corruption protection algorithms mean that some bytes do not contain
/// the expected values. This method merely indicates where file data is stored,
/// not what's stored. To access the contents of a file, use OpenFile.
public StreamExtent[] PathToExtents(string path)
{
string plainPath;
string attributeName;
SplitPath(path, out plainPath, out attributeName);
DirectoryEntry dirEntry = GetDirectoryEntry(plainPath);
if (dirEntry == null || dirEntry.IsDirectory)
{
throw new FileNotFoundException("No such file", path);
}
File file = GetFile(dirEntry.Reference);
NtfsStream stream = file.GetStream(AttributeType.Data, attributeName);
if (stream == null)
{
throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, "File does not contain '{0}' data attribute", attributeName), path);
}
return stream.GetAbsoluteExtents();
}
///
/// Gets an object that can convert between clusters and files.
///
/// The cluster map.
public ClusterMap BuildClusterMap()
{
return _context.Mft.GetClusterMap();
}
///
/// Creates an NTFS hard link to an existing file.
///
/// An existing name of the file.
/// The name of the new hard link to the file.
public void CreateHardLink(string sourceName, string destinationName)
{
using (new NtfsTransaction())
{
DirectoryEntry sourceDirEntry = GetDirectoryEntry(sourceName);
if (sourceDirEntry == null)
{
throw new FileNotFoundException("Source file not found", sourceName);
}
string destinationDirName = Utilities.GetDirectoryFromPath(destinationName);
DirectoryEntry destinationDirSelfEntry = GetDirectoryEntry(destinationDirName);
if (destinationDirSelfEntry == null || (destinationDirSelfEntry.Details.FileAttributes & FileAttributes.Directory) == 0)
{
throw new FileNotFoundException("Destination directory not found", destinationDirName);
}
Directory destinationDir = GetDirectory(destinationDirSelfEntry.Reference);
if (destinationDir == null)
{
throw new FileNotFoundException("Destination directory not found", destinationDirName);
}
DirectoryEntry destinationDirEntry = GetDirectoryEntry(destinationDir, Utilities.GetFileFromPath(destinationName));
if (destinationDirEntry != null)
{
throw new IOException("A file with this name already exists: " + destinationName);
}
File file = GetFile(sourceDirEntry.Reference);
destinationDir.AddEntry(file, Utilities.GetFileFromPath(destinationName), FileNameNamespace.Posix);
destinationDirSelfEntry.UpdateFrom(destinationDir);
}
}
///
/// Gets the number of hard links to a given file or directory.
///
/// The path of the file or directory.
/// The number of hard links.
/// All files have at least one hard link.
public int GetHardLinkCount(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
File file = GetFile(dirEntry.Reference);
if (!_context.Options.HideDosFileNames)
{
return file.HardLinkCount;
}
else
{
int numHardLinks = 0;
foreach (var fnStream in file.GetStreams(AttributeType.FileName, null))
{
var fnr = fnStream.GetContent();
if (fnr.FileNameNamespace != FileNameNamespace.Dos)
{
++numHardLinks;
}
}
return numHardLinks;
}
}
}
///
/// Indicates whether the file is known by other names.
///
/// The file to inspect.
/// true if the file has other names, else false.
public bool HasHardLinks(string path)
{
return GetHardLinkCount(path) > 1;
}
///
/// Gets the security descriptor associated with the file or directory.
///
/// The file or directory to inspect.
/// The security descriptor.
public RawSecurityDescriptor GetSecurity(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
File file = GetFile(dirEntry.Reference);
return DoGetSecurity(file);
}
}
}
///
/// Sets the security descriptor associated with the file or directory.
///
/// The file or directory to change.
/// The new security descriptor.
public void SetSecurity(string path, RawSecurityDescriptor securityDescriptor)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
File file = GetFile(dirEntry.Reference);
DoSetSecurity(file, securityDescriptor);
// Update the directory entry used to open the file
dirEntry.UpdateFrom(file);
}
}
}
///
/// Sets the reparse point data on a file or directory.
///
/// The file to set the reparse point on.
/// The new reparse point.
public void SetReparsePoint(string path, ReparsePoint reparsePoint)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
File file = GetFile(dirEntry.Reference);
NtfsStream stream = file.GetStream(AttributeType.ReparsePoint, null);
if (stream != null)
{
// If there's an existing reparse point, unhook it.
using (Stream contentStream = stream.Open(FileAccess.Read))
{
byte[] oldRpBuffer = Utilities.ReadFully(contentStream, (int)contentStream.Length);
ReparsePointRecord rp = new ReparsePointRecord();
rp.ReadFrom(oldRpBuffer, 0);
_context.ReparsePoints.Remove(rp.Tag, dirEntry.Reference);
}
}
else
{
stream = file.CreateStream(AttributeType.ReparsePoint, null);
}
// Set the new content
ReparsePointRecord newRp = new ReparsePointRecord();
newRp.Tag = (uint)reparsePoint.Tag;
newRp.Content = reparsePoint.Content;
byte[] contentBuffer = new byte[newRp.Size];
newRp.WriteTo(contentBuffer, 0);
using (Stream contentStream = stream.Open(FileAccess.ReadWrite))
{
contentStream.Write(contentBuffer, 0, contentBuffer.Length);
contentStream.SetLength(contentBuffer.Length);
}
// Update the standard information attribute - so it reflects the actual file state
NtfsStream stdInfoStream = file.GetStream(AttributeType.StandardInformation, null);
StandardInformation si = stdInfoStream.GetContent();
si.FileAttributes = si.FileAttributes | FileAttributeFlags.ReparsePoint;
stdInfoStream.SetContent(si);
// Update the directory entry used to open the file, so it's accurate
dirEntry.Details.EASizeOrReparsePointTag = newRp.Tag;
dirEntry.UpdateFrom(file);
// Write attribute changes back to the Master File Table
file.UpdateRecordInMft();
// Add the reparse point to the index
_context.ReparsePoints.Add(newRp.Tag, dirEntry.Reference);
}
}
}
///
/// Gets the reparse point data associated with a file or directory.
///
/// The file to query.
/// The reparse point information.
public ReparsePoint GetReparsePoint(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
File file = GetFile(dirEntry.Reference);
NtfsStream stream = file.GetStream(AttributeType.ReparsePoint, null);
if (stream != null)
{
ReparsePointRecord rp = new ReparsePointRecord();
using (Stream contentStream = stream.Open(FileAccess.Read))
{
byte[] buffer = Utilities.ReadFully(contentStream, (int)contentStream.Length);
rp.ReadFrom(buffer, 0);
return new ReparsePoint((int)rp.Tag, rp.Content);
}
}
}
}
return null;
}
///
/// Removes a reparse point from a file or directory, without deleting the file or directory.
///
/// The path to the file or directory to remove the reparse point from.
public void RemoveReparsePoint(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
File file = GetFile(dirEntry.Reference);
RemoveReparsePoint(file);
// Update the directory entry used to open the file, so it's accurate
dirEntry.UpdateFrom(file);
// Write attribute changes back to the Master File Table
file.UpdateRecordInMft();
}
}
}
///
/// Gets the short name for a given path.
///
/// The path to convert.
/// The short name.
///
/// This method only gets the short name for the final part of the path, to
/// convert a complete path, call this method repeatedly, once for each path
/// segment. If there is no short name for the given path,null is
/// returned.
///
public string GetShortName(string path)
{
using (new NtfsTransaction())
{
string parentPath = Utilities.GetDirectoryFromPath(path);
DirectoryEntry parentEntry = GetDirectoryEntry(parentPath);
if (parentEntry == null || (parentEntry.Details.FileAttributes & FileAttributes.Directory) == 0)
{
throw new DirectoryNotFoundException("Parent directory not found");
}
Directory dir = GetDirectory(parentEntry.Reference);
if (dir == null)
{
throw new DirectoryNotFoundException("Parent directory not found");
}
DirectoryEntry givenEntry = dir.GetEntryByName(Utilities.GetFileFromPath(path));
if (givenEntry == null)
{
throw new FileNotFoundException("Path not found", path);
}
if (givenEntry.Details.FileNameNamespace == FileNameNamespace.Dos)
{
return givenEntry.Details.FileName;
}
else if (givenEntry.Details.FileNameNamespace == FileNameNamespace.Win32)
{
File file = GetFile(givenEntry.Reference);
foreach (var stream in file.GetStreams(AttributeType.FileName, null))
{
FileNameRecord fnr = stream.GetContent();
if (fnr.ParentDirectory.Equals(givenEntry.Details.ParentDirectory)
&& fnr.FileNameNamespace == FileNameNamespace.Dos)
{
return fnr.FileName;
}
}
}
return null;
}
}
///
/// Sets the short name for a given file or directory.
///
/// The full path to the file or directory to change.
/// The shortName, which should not include a path.
public void SetShortName(string path, string shortName)
{
if (!Utilities.Is8Dot3(shortName))
{
throw new ArgumentException("Short name is not a valid 8.3 file name", "shortName");
}
using (new NtfsTransaction())
{
string parentPath = Utilities.GetDirectoryFromPath(path);
DirectoryEntry parentEntry = GetDirectoryEntry(parentPath);
if (parentEntry == null || (parentEntry.Details.FileAttributes & FileAttributes.Directory) == 0)
{
throw new DirectoryNotFoundException("Parent directory not found");
}
Directory dir = GetDirectory(parentEntry.Reference);
if (dir == null)
{
throw new DirectoryNotFoundException("Parent directory not found");
}
DirectoryEntry givenEntry = dir.GetEntryByName(Utilities.GetFileFromPath(path));
if (givenEntry == null)
{
throw new FileNotFoundException("Path not found", path);
}
FileNameNamespace givenNamespace = givenEntry.Details.FileNameNamespace;
File file = GetFile(givenEntry.Reference);
if (givenNamespace == FileNameNamespace.Posix && file.HasWin32OrDosName)
{
throw new InvalidOperationException("Cannot set a short name on hard links");
}
// Convert Posix/Win32AndDos to just Win32
if (givenEntry.Details.FileNameNamespace != FileNameNamespace.Win32)
{
dir.RemoveEntry(givenEntry);
dir.AddEntry(file, givenEntry.Details.FileName, FileNameNamespace.Win32);
}
// Remove any existing Dos names, and set the new one
List nameStreams = new List(file.GetStreams(AttributeType.FileName, null));
foreach (var stream in nameStreams)
{
FileNameRecord fnr = stream.GetContent();
if (fnr.ParentDirectory.Equals(givenEntry.Details.ParentDirectory)
&& fnr.FileNameNamespace == FileNameNamespace.Dos)
{
DirectoryEntry oldEntry = dir.GetEntryByName(fnr.FileName);
dir.RemoveEntry(oldEntry);
}
}
dir.AddEntry(file, shortName, FileNameNamespace.Dos);
parentEntry.UpdateFrom(dir);
}
}
///
/// Gets the standard file information for a file.
///
/// The full path to the file or directory to query.
/// The standard file information.
public WindowsFileInformation GetFileStandardInformation(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
File file = GetFile(dirEntry.Reference);
StandardInformation si = file.StandardInformation;
return new WindowsFileInformation
{
CreationTime = si.CreationTime,
LastAccessTime = si.LastAccessTime,
ChangeTime = si.MftChangedTime,
LastWriteTime = si.ModificationTime,
FileAttributes = StandardInformation.ConvertFlags(si.FileAttributes, file.IsDirectory)
};
}
}
///
/// Sets the standard file information for a file.
///
/// The full path to the file or directory to query.
/// The standard file information.
public void SetFileStandardInformation(string path, WindowsFileInformation info)
{
using (new NtfsTransaction())
{
UpdateStandardInformation(
path,
delegate (StandardInformation si)
{
si.CreationTime = info.CreationTime;
si.LastAccessTime = info.LastAccessTime;
si.MftChangedTime = info.ChangeTime;
si.ModificationTime = info.LastWriteTime;
si.FileAttributes = StandardInformation.SetFileAttributes(info.FileAttributes, si.FileAttributes);
});
}
}
///
/// Gets the file id for a given path.
///
/// The path to get the id of.
/// The file id.
///
/// The returned file id includes the MFT index of the primary file record for the file.
/// The file id can be used to determine if two paths refer to the same actual file.
/// The MFT index is held in the lower 48 bits of the id.
///
public long GetFileId(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
return (long)dirEntry.Reference.Value;
}
}
}
///
/// Gets the names of the alternate data streams for a file.
///
/// The path to the file.
///
/// The list of alternate data streams (or empty, if none). To access the contents
/// of the alternate streams, use OpenFile(path + ":" + name, ...).
///
public string[] GetAlternateDataStreams(string path)
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
File file = GetFile(dirEntry.Reference);
List names = new List();
foreach (var attr in file.AllStreams)
{
if (attr.AttributeType == AttributeType.Data && !string.IsNullOrEmpty(attr.Name))
{
names.Add(attr.Name);
}
}
return names.ToArray();
}
///
/// Reads the boot code of the file system into a byte array.
///
/// The boot code, or null if not available.
public override byte[] ReadBootCode()
{
using (Stream s = OpenFile(@"\$Boot", FileMode.Open))
{
return Utilities.ReadFully(s, (int)s.Length);
}
}
///
/// Updates the BIOS Parameter Block (BPB) of the file system to reflect a new disk geometry.
///
/// The disk's new BIOS geometry.
/// Having an accurate geometry in the BPB is essential for booting some Operating Systems (e.g. Windows XP).
public void UpdateBiosGeometry(Geometry geometry)
{
_context.BiosParameterBlock.SectorsPerTrack = (ushort)geometry.SectorsPerTrack;
_context.BiosParameterBlock.NumHeads = (ushort)geometry.HeadsPerCylinder;
_context.RawStream.Position = 0;
byte[] bpbSector = Utilities.ReadFully(_context.RawStream, 512);
_context.BiosParameterBlock.ToBytes(bpbSector, 0);
_context.RawStream.Position = 0;
_context.RawStream.Write(bpbSector, 0, bpbSector.Length);
}
///
/// Writes a diagnostic dump of key NTFS structures.
///
/// The writer to receive the dump.
/// The indent to apply to the start of each line of output.
public void Dump(TextWriter writer, string linePrefix)
{
writer.WriteLine(linePrefix + "NTFS File System Dump");
writer.WriteLine(linePrefix + "=====================");
////_context.Mft.Dump(writer, linePrefix);
writer.WriteLine(linePrefix);
_context.BiosParameterBlock.Dump(writer, linePrefix);
if (_context.SecurityDescriptors != null)
{
writer.WriteLine(linePrefix);
_context.SecurityDescriptors.Dump(writer, linePrefix);
}
if (_context.ObjectIds != null)
{
writer.WriteLine(linePrefix);
_context.ObjectIds.Dump(writer, linePrefix);
}
if (_context.ReparsePoints != null)
{
writer.WriteLine(linePrefix);
_context.ReparsePoints.Dump(writer, linePrefix);
}
if (_context.Quotas != null)
{
writer.WriteLine(linePrefix);
_context.Quotas.Dump(writer, linePrefix);
}
writer.WriteLine(linePrefix);
GetDirectory(MasterFileTable.RootDirIndex).Dump(writer, linePrefix);
writer.WriteLine(linePrefix);
writer.WriteLine(linePrefix + "FULL FILE LISTING");
foreach (var record in _context.Mft.Records)
{
// Don't go through cache - these are short-lived, and this is (just!) diagnostics
File f = new File(_context, record);
f.Dump(writer, linePrefix);
foreach (var stream in f.AllStreams)
{
if (stream.AttributeType == AttributeType.IndexRoot)
{
try
{
writer.WriteLine(linePrefix + " INDEX (" + stream.Name + ")");
f.GetIndex(stream.Name).Dump(writer, linePrefix + " ");
}
catch (Exception e)
{
writer.WriteLine(linePrefix + "!Exception: " + e);
}
}
}
}
writer.WriteLine(linePrefix);
writer.WriteLine(linePrefix + "DIRECTORY TREE");
writer.WriteLine(linePrefix + @"\ (5)");
DumpDirectory(GetDirectory(MasterFileTable.RootDirIndex), writer, linePrefix); // 5 = Root Dir
}
#region Internal File access methods (exposed via NtfsContext)
internal Directory GetDirectory(long index)
{
return (Directory)GetFile(index);
}
internal Directory GetDirectory(FileRecordReference fileReference)
{
return (Directory)GetFile(fileReference);
}
internal File GetFile(FileRecordReference fileReference)
{
FileRecord record = _context.Mft.GetRecord(fileReference);
if (record == null)
{
return null;
}
// Don't create file objects for file record segments that are part of another
// logical file.
if (record.BaseFile.Value != 0)
{
return null;
}
File file = _fileCache[fileReference.MftIndex];
if (file != null && file.MftReference.SequenceNumber != fileReference.SequenceNumber)
{
file = null;
}
if (file == null)
{
if ((record.Flags & FileRecordFlags.IsDirectory) != 0)
{
file = new Directory(_context, record);
}
else
{
file = new File(_context, record);
}
_fileCache[fileReference.MftIndex] = file;
}
return file;
}
internal File GetFile(long index)
{
FileRecord record = _context.Mft.GetRecord(index, false);
if (record == null)
{
return null;
}
// Don't create file objects for file record segments that are part of another
// logical file.
if (record.BaseFile.Value != 0)
{
return null;
}
File file = _fileCache[index];
if (file != null && file.MftReference.SequenceNumber != record.SequenceNumber)
{
file = null;
}
if (file == null)
{
if ((record.Flags & FileRecordFlags.IsDirectory) != 0)
{
file = new Directory(_context, record);
}
else
{
file = new File(_context, record);
}
_fileCache[index] = file;
}
return file;
}
internal File AllocateFile(FileRecordFlags flags)
{
File result = null;
if ((flags & FileRecordFlags.IsDirectory) != 0)
{
result = new Directory(_context, _context.Mft.AllocateRecord(flags, false));
}
else
{
result = new File(_context, _context.Mft.AllocateRecord(flags, false));
}
_fileCache[result.MftReference.MftIndex] = result;
return result;
}
internal void ForgetFile(File file)
{
_fileCache.Remove(file.IndexInMft);
}
#endregion
internal DirectoryEntry GetDirectoryEntry(string path)
{
return GetDirectoryEntry(GetDirectory(MasterFileTable.RootDirIndex), path);
}
///
/// Disposes of this instance.
///
/// Whether called from Dispose or from a finalizer.
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_context != null && _context.Mft != null)
{
_context.Mft.Dispose();
_context.Mft = null;
}
IDisposable disposableCompressor = _context.Options.Compressor as IDisposable;
if (disposableCompressor != null)
{
disposableCompressor.Dispose();
_context.Options.Compressor = null;
}
}
base.Dispose(disposing);
}
private static bool IsValidBPB(BiosParameterBlock bpb, long volumeSize)
{
if (bpb.SignatureByte != 0x80 || bpb.TotalSectors16 != 0 || bpb.TotalSectors32 != 0
|| bpb.TotalSectors64 == 0 || bpb.MftRecordSize == 0 || bpb.MftCluster == 0 || bpb.BytesPerSector == 0)
{
return false;
}
long mftPos = bpb.MftCluster * bpb.SectorsPerCluster * bpb.BytesPerSector;
return mftPos < bpb.TotalSectors64 * bpb.BytesPerSector && mftPos < volumeSize;
}
private static void RemoveFileFromDirectory(Directory dir, File file, string name)
{
List aliases = new List();
DirectoryEntry dirEntry = dir.GetEntryByName(name);
if (dirEntry.Details.FileNameNamespace == FileNameNamespace.Dos
|| dirEntry.Details.FileNameNamespace == FileNameNamespace.Win32)
{
foreach (var fnStream in file.GetStreams(AttributeType.FileName, null))
{
var fnr = fnStream.GetContent();
if ((fnr.FileNameNamespace == FileNameNamespace.Win32 || fnr.FileNameNamespace == FileNameNamespace.Dos)
&& fnr.ParentDirectory.Value == dir.MftReference.Value)
{
aliases.Add(fnr.FileName);
}
}
}
else
{
aliases.Add(name);
}
foreach (var alias in aliases)
{
DirectoryEntry de = dir.GetEntryByName(alias);
dir.RemoveEntry(de);
}
}
private static void SplitPath(string path, out string plainPath, out string attributeName)
{
plainPath = path;
string fileName = Utilities.GetFileFromPath(path);
attributeName = null;
int streamSepPos = fileName.IndexOf(':');
if (streamSepPos >= 0)
{
attributeName = fileName.Substring(streamSepPos + 1);
plainPath = plainPath.Substring(0, path.Length - (fileName.Length - streamSepPos));
}
}
private static void UpdateStandardInformation(DirectoryEntry dirEntry, File file, StandardInformationModifier modifier)
{
// Update the standard information attribute - so it reflects the actual file state
NtfsStream stream = file.GetStream(AttributeType.StandardInformation, null);
StandardInformation si = stream.GetContent();
modifier(si);
stream.SetContent(si);
// Update the directory entry used to open the file, so it's accurate
dirEntry.UpdateFrom(file);
// Write attribute changes back to the Master File Table
file.UpdateRecordInMft();
}
private DirectoryEntry CreateNewFile(string path, NewFileOptions options)
{
DirectoryEntry result;
DirectoryEntry parentDirEntry = GetDirectoryEntry(Utilities.GetDirectoryFromPath(path));
Directory parentDir = GetDirectory(parentDirEntry.Reference);
FileAttributeFlags newFileAttrs = parentDir.StandardInformation.FileAttributes;
if (options != null && options.Compressed.HasValue)
{
if (options.Compressed.Value)
{
newFileAttrs |= FileAttributeFlags.Compressed;
}
else
{
newFileAttrs &= ~FileAttributeFlags.Compressed;
}
}
File file = File.CreateNew(_context, newFileAttrs);
try
{
result = AddFileToDirectory(file, parentDir, Utilities.GetFileFromPath(path), options);
RawSecurityDescriptor parentSd = DoGetSecurity(parentDir);
RawSecurityDescriptor newSd;
if (options != null && options.SecurityDescriptor != null)
{
newSd = options.SecurityDescriptor;
}
else
{
newSd = SecurityDescriptor.CalcNewObjectDescriptor(parentSd, false);
}
DoSetSecurity(file, newSd);
result.UpdateFrom(file);
parentDirEntry.UpdateFrom(parentDir);
}
finally
{
if (file.HardLinkCount == 0)
{
file.Delete();
}
}
return result;
}
private DirectoryEntry GetDirectoryEntry(Directory dir, string path)
{
string[] pathElements = path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
return GetDirectoryEntry(dir, pathElements, 0);
}
private void DoSearch(List results, string path, Regex regex, bool subFolders, bool dirs, bool files)
{
DirectoryEntry parentDirEntry = GetDirectoryEntry(path);
if (parentDirEntry == null)
{
throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, "The directory '{0}' was not found", path));
}
Directory parentDir = GetDirectory(parentDirEntry.Reference);
if (parentDir == null)
{
throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture, "The directory '{0}' was not found", path));
}
foreach (DirectoryEntry de in parentDir.GetAllEntries(true))
{
bool isDir = (de.Details.FileAttributes & FileAttributes.Directory) != 0;
if ((isDir && dirs) || (!isDir && files))
{
if (regex.IsMatch(de.SearchName))
{
results.Add(Utilities.CombinePaths(path, de.Details.FileName));
}
}
if (subFolders && isDir)
{
DoSearch(results, Utilities.CombinePaths(path, de.Details.FileName), regex, subFolders, dirs, files);
}
}
}
private DirectoryEntry GetDirectoryEntry(Directory dir, string[] pathEntries, int pathOffset)
{
DirectoryEntry entry;
if (pathEntries.Length == 0)
{
return dir.DirectoryEntry;
}
else
{
entry = dir.GetEntryByName(pathEntries[pathOffset]);
if (entry != null)
{
if (pathOffset == pathEntries.Length - 1)
{
return entry;
}
else if ((entry.Details.FileAttributes & FileAttributes.Directory) != 0)
{
return GetDirectoryEntry(GetDirectory(entry.Reference), pathEntries, pathOffset + 1);
}
else
{
throw new IOException(string.Format(CultureInfo.InvariantCulture, "{0} is a file, not a directory", pathEntries[pathOffset]));
}
}
else
{
return null;
}
}
}
private DirectoryEntry AddFileToDirectory(File file, Directory dir, string name, NewFileOptions options)
{
DirectoryEntry entry;
bool createShortNames;
if (options != null && options.CreateShortNames.HasValue)
{
createShortNames = options.CreateShortNames.Value;
}
else
{
createShortNames = CreateShortNames;
}
if (createShortNames)
{
if (Utilities.Is8Dot3(name.ToUpperInvariant()))
{
entry = dir.AddEntry(file, name, FileNameNamespace.Win32AndDos);
}
else
{
entry = dir.AddEntry(file, name, FileNameNamespace.Win32);
dir.AddEntry(file, dir.CreateShortName(name), FileNameNamespace.Dos);
}
}
else
{
entry = dir.AddEntry(file, name, FileNameNamespace.Posix);
}
return entry;
}
private void RemoveReparsePoint(File file)
{
NtfsStream stream = file.GetStream(AttributeType.ReparsePoint, null);
if (stream != null)
{
ReparsePointRecord rp = new ReparsePointRecord();
using (Stream contentStream = stream.Open(FileAccess.Read))
{
byte[] buffer = Utilities.ReadFully(contentStream, (int)contentStream.Length);
rp.ReadFrom(buffer, 0);
}
file.RemoveStream(stream);
// Update the standard information attribute - so it reflects the actual file state
NtfsStream stdInfoStream = file.GetStream(AttributeType.StandardInformation, null);
StandardInformation si = stdInfoStream.GetContent();
si.FileAttributes = si.FileAttributes & ~FileAttributeFlags.ReparsePoint;
stdInfoStream.SetContent(si);
// Remove the reparse point from the index
_context.ReparsePoints.Remove(rp.Tag, file.MftReference);
}
}
private RawSecurityDescriptor DoGetSecurity(File file)
{
NtfsStream legacyStream = file.GetStream(AttributeType.SecurityDescriptor, null);
if (legacyStream != null)
{
return legacyStream.GetContent().Descriptor;
}
StandardInformation si = file.StandardInformation;
return _context.SecurityDescriptors.GetDescriptorById(si.SecurityId);
}
private void DoSetSecurity(File file, RawSecurityDescriptor securityDescriptor)
{
NtfsStream legacyStream = file.GetStream(AttributeType.SecurityDescriptor, null);
if (legacyStream != null)
{
SecurityDescriptor sd = new SecurityDescriptor();
sd.Descriptor = securityDescriptor;
legacyStream.SetContent(sd);
}
else
{
uint id = _context.SecurityDescriptors.AddDescriptor(securityDescriptor);
// Update the standard information attribute - so it reflects the actual file state
NtfsStream stream = file.GetStream(AttributeType.StandardInformation, null);
StandardInformation si = stream.GetContent();
si.SecurityId = id;
stream.SetContent(si);
// Write attribute changes back to the Master File Table
file.UpdateRecordInMft();
}
}
private void DumpDirectory(Directory dir, TextWriter writer, string indent)
{
foreach (DirectoryEntry dirEntry in dir.GetAllEntries(true))
{
File file = GetFile(dirEntry.Reference);
Directory asDir = file as Directory;
writer.WriteLine(indent + "+-" + file.ToString() + " (" + file.IndexInMft + ")");
// Recurse - but avoid infinite recursion via the root dir...
if (asDir != null && file.IndexInMft != 5)
{
DumpDirectory(asDir, writer, indent + "| ");
}
}
}
private void UpdateStandardInformation(string path, StandardInformationModifier modifier)
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
if (dirEntry == null)
{
throw new FileNotFoundException("File not found", path);
}
else
{
File file = GetFile(dirEntry.Reference);
UpdateStandardInformation(dirEntry, file, modifier);
}
}
private string ParsePath(string path, out string attributeName, out AttributeType attributeType)
{
string fileName = Utilities.GetFileFromPath(path);
attributeName = null;
attributeType = AttributeType.Data;
string[] fileNameElements = fileName.Split(new char[] { ':' }, 3);
fileName = fileNameElements[0];
if (fileNameElements.Length > 1)
{
attributeName = fileNameElements[1];
if (string.IsNullOrEmpty(attributeName))
{
attributeName = null;
}
}
if (fileNameElements.Length > 2)
{
string typeName = fileNameElements[2];
AttributeDefinitionRecord typeDefn = _context.AttributeDefinitions.Lookup(typeName);
if (typeDefn == null)
{
throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture, "No such attribute type '{0}'", typeName), path);
}
attributeType = typeDefn.Type;
}
try
{
string dirName = Utilities.GetDirectoryFromPath(path);
return Utilities.CombinePaths(dirName, fileName);
}
catch (ArgumentException)
{
throw new IOException("Invalid path: " + path);
}
}
}
}