Files
WPinternals/DiscUtils/Ntfs/NtfsFileSystem.cs
T
Gustave Monce a2a1c2302b Code cleanup
2019-12-22 12:25:48 +01:00

2441 lines
100 KiB
C#

//
// 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;
/// <summary>
/// Class for accessing NTFS file systems.
/// </summary>
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<long, File> _fileCache;
/// <summary>
/// Initializes a new instance of the NtfsFileSystem class.
/// </summary>
/// <param name="stream">The stream containing the NTFS file system.</param>
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<long, File>();
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<VolumeInformation>();
// 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);
/// <summary>
/// Gets the options that control how the file system is interpreted.
/// </summary>
public NtfsOptions NtfsOptions
{
get { return (NtfsOptions)Options; }
}
/// <summary>
/// Gets the friendly name for the file system.
/// </summary>
public override string FriendlyName
{
get { return "Microsoft NTFS"; }
}
/// <summary>
/// Indicates if the file system supports write operations.
/// </summary>
public override bool CanWrite
{
// For now, we don't...
get { return !_context.ReadOnly; }
}
/// <summary>
/// Gets the size of each cluster (in bytes).
/// </summary>
public long ClusterSize
{
get { return _context.BiosParameterBlock.BytesPerCluster; }
}
/// <summary>
/// Gets the total number of clusters managed by the file system.
/// </summary>
public long TotalClusters
{
get { return Utilities.Ceil(_context.BiosParameterBlock.TotalSectors64, _context.BiosParameterBlock.SectorsPerCluster); }
}
/// <summary>
/// Gets the volume label.
/// </summary>
public override string VolumeLabel
{
get
{
File volumeFile = GetFile(MasterFileTable.VolumeIndex);
NtfsStream volNameStream = volumeFile.GetStream(AttributeType.VolumeName, null);
return volNameStream.GetContent<VolumeName>().Name;
}
}
private bool CreateShortNames
{
get
{
return _context.Options.ShortNameCreation == ShortFileNameOption.Enabled
|| (_context.Options.ShortNameCreation == ShortFileNameOption.UseVolumeFlag
&& (_volumeInfo.Flags & VolumeInformationFlags.DisableShortNameCreation) == 0);
}
}
/// <summary>
/// Initializes a new NTFS file system.
/// </summary>
/// <param name="stream">The stream to write the new file system to.</param>
/// <param name="label">The label for the new file system.</param>
/// <param name="diskGeometry">The disk geometry of the disk containing the new file system.</param>
/// <param name="firstSector">The first sector of the new file system on the disk.</param>
/// <param name="sectorCount">The number of sectors allocated to the new file system on the disk.</param>
/// <returns>The newly-initialized file system.</returns>
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);
}
/// <summary>
/// Initializes a new NTFS file system.
/// </summary>
/// <param name="stream">The stream to write the new file system to.</param>
/// <param name="label">The label for the new file system.</param>
/// <param name="diskGeometry">The disk geometry of the disk containing the new file system.</param>
/// <param name="firstSector">The first sector of the new file system on the disk.</param>
/// <param name="sectorCount">The number of sectors allocated to the new file system on the disk.</param>
/// <param name="bootCode">The Operating System's boot code.</param>
/// <returns>The newly-initialized file system.</returns>
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);
}
/// <summary>
/// Initializes a new NTFS file system.
/// </summary>
/// <param name="stream">The stream to write the new file system to.</param>
/// <param name="label">The label for the new file system.</param>
/// <param name="diskGeometry">The disk geometry of the disk containing the new file system.</param>
/// <param name="firstSector">The first sector of the new file system on the disk.</param>
/// <param name="sectorCount">The number of sectors allocated to the new file system on the disk.</param>
/// <param name="options">The formatting options.</param>
/// <returns>The newly-initialized file system.</returns>
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);
}
/// <summary>
/// Initializes a new NTFS file system.
/// </summary>
/// <param name="volume">The volume to format.</param>
/// <param name="label">The label for the new file system.</param>
/// <returns>The newly-initialized file system.</returns>
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());
}
/// <summary>
/// Initializes a new NTFS file system.
/// </summary>
/// <param name="volume">The volume to format.</param>
/// <param name="label">The label for the new file system.</param>
/// <param name="bootCode">The Operating System's boot code.</param>
/// <returns>The newly-initialized file system.</returns>
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());
}
/// <summary>
/// Initializes a new NTFS file system.
/// </summary>
/// <param name="volume">The volume to format.</param>
/// <param name="label">The label for the new file system.</param>
/// <param name="options">The formatting options.</param>
/// <returns>The newly-initialized file system.</returns>
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());
}
/// <summary>
/// Detects if a stream contains an NTFS file system.
/// </summary>
/// <param name="stream">The stream to inspect.</param>
/// <returns><c>true</c> if NTFS is detected, else <c>false</c>.</returns>
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);
}
/// <summary>
/// Gets the Master File Table for this file system.
/// </summary>
/// <remarks>
/// Use the returned object to explore the internals of the file system - most people will
/// never need to use this.
/// </remarks>
/// <returns>The Master File Table.</returns>
public Internals.MasterFileTable GetMasterFileTable()
{
return new Internals.MasterFileTable(_context, _context.Mft);
}
/// <summary>
/// Copies an existing file to a new file, allowing overwriting of an existing file.
/// </summary>
/// <param name="sourceFile">The source file.</param>
/// <param name="destinationFile">The destination file.</param>
/// <param name="overwrite">Whether to permit over-writing of an existing file.</param>
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<StandardInformation>();
newStream.SetContent<StandardInformation>(newSi);
break;
}
}
AddFileToDirectory(newFile, destParentDir, Utilities.GetFileFromPath(destinationFile), null);
destParentDirEntry.UpdateFrom(destParentDir);
}
}
/// <summary>
/// Creates a directory.
/// </summary>
/// <param name="path">The path of the new directory.</param>
public override void CreateDirectory(string path)
{
CreateDirectory(path, null);
}
/// <summary>
/// Creates a directory.
/// </summary>
/// <param name="path">The path of the new directory.</param>
/// <param name="options">Options controlling attributes of the new Director, or <c>null</c> for defaults.</param>
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;
}
}
}
/// <summary>
/// Deletes a directory.
/// </summary>
/// <param name="path">The path of the directory to delete.</param>
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();
}
}
}
/// <summary>
/// Deletes a file.
/// </summary>
/// <param name="path">The path of the file to delete.</param>
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);
}
}
}
}
/// <summary>
/// Indicates if a directory exists.
/// </summary>
/// <param name="path">The path to test.</param>
/// <returns>true if the directory exists.</returns>
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;
}
}
}
/// <summary>
/// Indicates if a file exists.
/// </summary>
/// <param name="path">The path to test.</param>
/// <returns>true if the file exists.</returns>
public override bool FileExists(string path)
{
using (new NtfsTransaction())
{
DirectoryEntry dirEntry = GetDirectoryEntry(path);
return dirEntry != null && (dirEntry.Details.FileAttributes & FileAttributes.Directory) == 0;
}
}
/// <summary>
/// Gets the names of subdirectories in a specified directory matching a specified
/// search pattern, using a value to determine whether to search subdirectories.
/// </summary>
/// <param name="path">The path to search.</param>
/// <param name="searchPattern">The search string to match against.</param>
/// <param name="searchOption">Indicates whether to search subdirectories.</param>
/// <returns>Array of directories matching the search pattern.</returns>
public override string[] GetDirectories(string path, string searchPattern, SearchOption searchOption)
{
using (new NtfsTransaction())
{
Regex re = Utilities.ConvertWildcardsToRegEx(searchPattern);
List<string> dirs = new List<string>();
DoSearch(dirs, path, re, searchOption == SearchOption.AllDirectories, true, false);
return dirs.ToArray();
}
}
/// <summary>
/// Gets the names of files in a specified directory matching a specified
/// search pattern, using a value to determine whether to search subdirectories.
/// </summary>
/// <param name="path">The path to search.</param>
/// <param name="searchPattern">The search string to match against.</param>
/// <param name="searchOption">Indicates whether to search subdirectories.</param>
/// <returns>Array of files matching the search pattern.</returns>
public override string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
{
using (new NtfsTransaction())
{
Regex re = Utilities.ConvertWildcardsToRegEx(searchPattern);
List<string> results = new List<string>();
DoSearch(results, path, re, searchOption == SearchOption.AllDirectories, false, true);
return results.ToArray();
}
}
/// <summary>
/// Gets the names of all files and subdirectories in a specified directory.
/// </summary>
/// <param name="path">The path to search.</param>
/// <returns>Array of files and subdirectories.</returns>
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<DirectoryEntry, string>(parentDir.GetAllEntries(true), (m) => Utilities.CombinePaths(path, m.Details.FileName));
}
}
/// <summary>
/// Gets the names of files and subdirectories in a specified directory matching a specified
/// search pattern.
/// </summary>
/// <param name="path">The path to search.</param>
/// <param name="searchPattern">The search string to match against.</param>
/// <returns>Array of files and subdirectories matching the search pattern.</returns>
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<string> result = new List<string>();
foreach (DirectoryEntry dirEntry in parentDir.GetAllEntries(true))
{
if (re.IsMatch(dirEntry.Details.FileName))
{
result.Add(Utilities.CombinePaths(path, dirEntry.Details.FileName));
}
}
return result.ToArray();
}
}
/// <summary>
/// Moves a directory.
/// </summary>
/// <param name="sourceDirectoryName">The directory to move.</param>
/// <param name="destinationDirectoryName">The target directory name.</param>
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);
}
}
}
/// <summary>
/// Moves a file, allowing an existing file to be overwritten.
/// </summary>
/// <param name="sourceName">The file to move.</param>
/// <param name="destinationName">The target file name.</param>
/// <param name="overwrite">Whether to permit a destination file to be overwritten.</param>
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);
}
}
/// <summary>
/// Opens the specified file.
/// </summary>
/// <param name="path">The full path of the file to open.</param>
/// <param name="mode">The file mode for the created stream.</param>
/// <param name="access">The access permissions for the returned stream.</param>
/// <returns>The new stream.</returns>
public override SparseStream OpenFile(string path, FileMode mode, FileAccess access)
{
return OpenFile(path, mode, access, null);
}
/// <summary>
/// Opens the specified file.
/// </summary>
/// <param name="path">The full path of the file to open.</param>
/// <param name="mode">The file mode for the created stream.</param>
/// <param name="access">The access permissions for the returned stream.</param>
/// <param name="options">Options controlling attributes of a new file, or <c>null</c> for defaults (ignored if file exists).</param>
/// <returns>The new stream.</returns>
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;
}
}
}
/// <summary>
/// Opens an existing file stream.
/// </summary>
/// <param name="file">The file containing the stream.</param>
/// <param name="type">The type of the stream.</param>
/// <param name="name">The name of the stream.</param>
/// <param name="access">The desired access to the stream.</param>
/// <returns>A stream that can be used to access the file stream.</returns>
[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);
}
}
/// <summary>
/// Gets the attributes of a file or directory.
/// </summary>
/// <param name="path">The file or directory to inspect.</param>
/// <returns>The attributes of the file or directory.</returns>
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;
}
}
}
/// <summary>
/// Sets the attributes of a file or directory.
/// </summary>
/// <param name="path">The file or directory to change.</param>
/// <param name="newValue">The new attributes of the file or directory.</param>
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); });
}
}
/// <summary>
/// Gets the creation time (in UTC) of a file or directory.
/// </summary>
/// <param name="path">The path of the file or directory.</param>
/// <returns>The creation time.</returns>
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;
}
}
}
/// <summary>
/// Sets the creation time (in UTC) of a file or directory.
/// </summary>
/// <param name="path">The path of the file or directory.</param>
/// <param name="newTime">The new time to set.</param>
public override void SetCreationTimeUtc(string path, DateTime newTime)
{
using (new NtfsTransaction())
{
UpdateStandardInformation(path, delegate (StandardInformation si) { si.CreationTime = newTime; });
}
}
/// <summary>
/// Gets the last access time (in UTC) of a file or directory.
/// </summary>
/// <param name="path">The path of the file or directory.</param>
/// <returns>The last access time.</returns>
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;
}
}
}
/// <summary>
/// Sets the last access time (in UTC) of a file or directory.
/// </summary>
/// <param name="path">The path of the file or directory.</param>
/// <param name="newTime">The new time to set.</param>
public override void SetLastAccessTimeUtc(string path, DateTime newTime)
{
using (new NtfsTransaction())
{
UpdateStandardInformation(path, delegate (StandardInformation si) { si.LastAccessTime = newTime; });
}
}
/// <summary>
/// Gets the last modification time (in UTC) of a file or directory.
/// </summary>
/// <param name="path">The path of the file or directory.</param>
/// <returns>The last write time.</returns>
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;
}
}
}
/// <summary>
/// Sets the last modification time (in local time) of a file or directory.
/// </summary>
/// <param name="path">The path of the file or directory.</param>
/// <param name="newTime">The new time to set.</param>
public override void SetLastWriteTimeUtc(string path, DateTime newTime)
{
using (new NtfsTransaction())
{
UpdateStandardInformation(path, delegate (StandardInformation si) { si.ModificationTime = newTime; });
}
}
/// <summary>
/// Gets the length of a file.
/// </summary>
/// <param name="path">The path to the file.</param>
/// <returns>The length in bytes.</returns>
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;
}
}
/// <summary>
/// Converts a cluster (index) into an absolute byte position in the underlying stream.
/// </summary>
/// <param name="cluster">The cluster to convert.</param>
/// <returns>The corresponding absolute byte position.</returns>
public long ClusterToOffset(long cluster)
{
return cluster * ClusterSize;
}
/// <summary>
/// Converts an absolute byte position in the underlying stream to a cluster (index).
/// </summary>
/// <param name="offset">The byte position to convert.</param>
/// <returns>The cluster containing the specified byte.</returns>
public long OffsetToCluster(long offset)
{
return offset / ClusterSize;
}
/// <summary>
/// Converts a file name to the list of clusters occupied by the file's data.
/// </summary>
/// <param name="path">The path to inspect.</param>
/// <returns>The clusters as a list of cluster ranges.</returns>
/// <remarks>Note that in some file systems, small files may not have dedicated
/// clusters. Only dedicated clusters will be returned.</remarks>
public Range<long, long>[] 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();
}
/// <summary>
/// Converts a file name to the extents containing its data.
/// </summary>
/// <param name="path">The path to inspect.</param>
/// <returns>The file extents, as absolute byte positions in the underlying stream.</returns>
/// <remarks>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.</remarks>
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();
}
/// <summary>
/// Gets an object that can convert between clusters and files.
/// </summary>
/// <returns>The cluster map.</returns>
public ClusterMap BuildClusterMap()
{
return _context.Mft.GetClusterMap();
}
/// <summary>
/// Creates an NTFS hard link to an existing file.
/// </summary>
/// <param name="sourceName">An existing name of the file.</param>
/// <param name="destinationName">The name of the new hard link to the file.</param>
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);
}
}
/// <summary>
/// Gets the number of hard links to a given file or directory.
/// </summary>
/// <param name="path">The path of the file or directory.</param>
/// <returns>The number of hard links.</returns>
/// <remarks>All files have at least one hard link.</remarks>
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<FileNameRecord>();
if (fnr.FileNameNamespace != FileNameNamespace.Dos)
{
++numHardLinks;
}
}
return numHardLinks;
}
}
}
/// <summary>
/// Indicates whether the file is known by other names.
/// </summary>
/// <param name="path">The file to inspect.</param>
/// <returns><c>true</c> if the file has other names, else <c>false</c>.</returns>
public bool HasHardLinks(string path)
{
return GetHardLinkCount(path) > 1;
}
/// <summary>
/// Gets the security descriptor associated with the file or directory.
/// </summary>
/// <param name="path">The file or directory to inspect.</param>
/// <returns>The security descriptor.</returns>
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);
}
}
}
/// <summary>
/// Sets the security descriptor associated with the file or directory.
/// </summary>
/// <param name="path">The file or directory to change.</param>
/// <param name="securityDescriptor">The new security descriptor.</param>
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);
}
}
}
/// <summary>
/// Sets the reparse point data on a file or directory.
/// </summary>
/// <param name="path">The file to set the reparse point on.</param>
/// <param name="reparsePoint">The new reparse point.</param>
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<StandardInformation>();
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);
}
}
}
/// <summary>
/// Gets the reparse point data associated with a file or directory.
/// </summary>
/// <param name="path">The file to query.</param>
/// <returns>The reparse point information.</returns>
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;
}
/// <summary>
/// Removes a reparse point from a file or directory, without deleting the file or directory.
/// </summary>
/// <param name="path">The path to the file or directory to remove the reparse point from.</param>
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();
}
}
}
/// <summary>
/// Gets the short name for a given path.
/// </summary>
/// <param name="path">The path to convert.</param>
/// <returns>The short name.</returns>
/// <remarks>
/// 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,<c>null</c> is
/// returned.
/// </remarks>
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<FileNameRecord>();
if (fnr.ParentDirectory.Equals(givenEntry.Details.ParentDirectory)
&& fnr.FileNameNamespace == FileNameNamespace.Dos)
{
return fnr.FileName;
}
}
}
return null;
}
}
/// <summary>
/// Sets the short name for a given file or directory.
/// </summary>
/// <param name="path">The full path to the file or directory to change.</param>
/// <param name="shortName">The shortName, which should not include a path.</param>
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<NtfsStream> nameStreams = new List<NtfsStream>(file.GetStreams(AttributeType.FileName, null));
foreach (var stream in nameStreams)
{
FileNameRecord fnr = stream.GetContent<FileNameRecord>();
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);
}
}
/// <summary>
/// Gets the standard file information for a file.
/// </summary>
/// <param name="path">The full path to the file or directory to query.</param>
/// <returns>The standard file information.</returns>
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)
};
}
}
/// <summary>
/// Sets the standard file information for a file.
/// </summary>
/// <param name="path">The full path to the file or directory to query.</param>
/// <param name="info">The standard file information.</param>
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);
});
}
}
/// <summary>
/// Gets the file id for a given path.
/// </summary>
/// <param name="path">The path to get the id of.</param>
/// <returns>The file id.</returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
}
/// <summary>
/// Gets the names of the alternate data streams for a file.
/// </summary>
/// <param name="path">The path to the file.</param>
/// <returns>
/// The list of alternate data streams (or empty, if none). To access the contents
/// of the alternate streams, use OpenFile(path + ":" + name, ...).
/// </returns>
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<string> names = new List<string>();
foreach (var attr in file.AllStreams)
{
if (attr.AttributeType == AttributeType.Data && !string.IsNullOrEmpty(attr.Name))
{
names.Add(attr.Name);
}
}
return names.ToArray();
}
/// <summary>
/// Reads the boot code of the file system into a byte array.
/// </summary>
/// <returns>The boot code, or <c>null</c> if not available.</returns>
public override byte[] ReadBootCode()
{
using (Stream s = OpenFile(@"\$Boot", FileMode.Open))
{
return Utilities.ReadFully(s, (int)s.Length);
}
}
/// <summary>
/// Updates the BIOS Parameter Block (BPB) of the file system to reflect a new disk geometry.
/// </summary>
/// <param name="geometry">The disk's new BIOS geometry.</param>
/// <remarks>Having an accurate geometry in the BPB is essential for booting some Operating Systems (e.g. Windows XP).</remarks>
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);
}
/// <summary>
/// Writes a diagnostic dump of key NTFS structures.
/// </summary>
/// <param name="writer">The writer to receive the dump.</param>
/// <param name="linePrefix">The indent to apply to the start of each line of output.</param>
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);
}
/// <summary>
/// Disposes of this instance.
/// </summary>
/// <param name="disposing">Whether called from Dispose or from a finalizer.</param>
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<string> aliases = new List<string>();
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<FileNameRecord>();
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<StandardInformation>();
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<string> 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<StandardInformation>();
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<SecurityDescriptor>().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<StandardInformation>();
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);
}
}
}
}