//
// 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);
            }
        }
    }
}
