/src/Ntfs/Directory.cs
C# | 291 lines | 217 code | 48 blank | 26 comment | 40 complexity | 6a5ccc0117aa42da69e85b7f537b0227 MD5 | raw file
- //
- // 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.Text;
- using DirectoryIndexEntry = System.Collections.Generic.KeyValuePair<DiscUtils.Ntfs.FileNameRecord, DiscUtils.Ntfs.FileRecordReference>;
-
- internal class Directory : File
- {
- private IndexView<FileNameRecord, FileRecordReference> _index;
-
- public Directory(INtfsContext context, FileRecord baseRecord)
- : base(context, baseRecord)
- {
- }
-
- public bool IsEmpty
- {
- get { return Index.Count == 0; }
- }
-
- private IndexView<FileNameRecord, FileRecordReference> Index
- {
- get
- {
- if (_index == null && StreamExists(AttributeType.IndexRoot, "$I30"))
- {
- _index = new IndexView<FileNameRecord, FileRecordReference>(GetIndex("$I30"));
- }
-
- return _index;
- }
- }
-
- public IEnumerable<DirectoryEntry> GetAllEntries(bool filter)
- {
- IEnumerable<DirectoryIndexEntry> entries = filter ? FilterEntries(Index.Entries) : Index.Entries;
-
- foreach (var entry in entries)
- {
- yield return new DirectoryEntry(this, entry.Value, entry.Key);
- }
- }
-
- public void UpdateEntry(DirectoryEntry entry)
- {
- Index[entry.Details] = entry.Reference;
- UpdateRecordInMft();
- }
-
- public override void Dump(TextWriter writer, string indent)
- {
- writer.WriteLine(indent + "DIRECTORY (" + base.ToString() + ")");
- writer.WriteLine(indent + " File Number: " + IndexInMft);
-
- if (Index != null)
- {
- foreach (var entry in Index.Entries)
- {
- writer.WriteLine(indent + " DIRECTORY ENTRY (" + entry.Key.FileName + ")");
- writer.WriteLine(indent + " MFT Ref: " + entry.Value);
- entry.Key.Dump(writer, indent + " ");
- }
- }
- }
-
- public override string ToString()
- {
- return base.ToString() + @"\";
- }
-
- internal static new Directory CreateNew(INtfsContext context, FileAttributeFlags parentDirFlags)
- {
- Directory dir = (Directory)context.AllocateFile(FileRecordFlags.IsDirectory);
-
- StandardInformation.InitializeNewFile(
- dir,
- FileAttributeFlags.Archive | (parentDirFlags & FileAttributeFlags.Compressed));
-
- // Create the index root attribute by instantiating a new index
- dir.CreateIndex("$I30", AttributeType.FileName, AttributeCollationRule.Filename);
-
- dir.UpdateRecordInMft();
-
- return dir;
- }
-
- internal DirectoryEntry GetEntryByName(string name)
- {
- string searchName = name;
-
- int streamSepPos = name.IndexOf(':');
- if (streamSepPos >= 0)
- {
- searchName = name.Substring(0, streamSepPos);
- }
-
- DirectoryIndexEntry entry = Index.FindFirst(new FileNameQuery(searchName, _context.UpperCase));
- if (entry.Key != null)
- {
- return new DirectoryEntry(this, entry.Value, entry.Key);
- }
- else
- {
- return null;
- }
- }
-
- internal DirectoryEntry AddEntry(File file, string name, FileNameNamespace nameNamespace)
- {
- if (name.Length > 255)
- {
- throw new IOException("Invalid file name, more than 255 characters: " + name);
- }
- else if (name.IndexOfAny(new char[] { '\0', '/' }) != -1)
- {
- throw new IOException(@"Invalid file name, contains '\0' or '/': " + name);
- }
-
- FileNameRecord newNameRecord = file.GetFileNameRecord(null, true);
- newNameRecord.FileNameNamespace = nameNamespace;
- newNameRecord.FileName = name;
- newNameRecord.ParentDirectory = MftReference;
-
- NtfsStream nameStream = file.CreateStream(AttributeType.FileName, null);
- nameStream.SetContent(newNameRecord);
-
- file.HardLinkCount++;
- file.UpdateRecordInMft();
-
- Index[newNameRecord] = file.MftReference;
-
- Modified();
- UpdateRecordInMft();
-
- return new DirectoryEntry(this, file.MftReference, newNameRecord);
- }
-
- internal void RemoveEntry(DirectoryEntry dirEntry)
- {
- File file = _context.GetFileByRef(dirEntry.Reference);
-
- FileNameRecord nameRecord = dirEntry.Details;
-
- Index.Remove(dirEntry.Details);
-
- foreach (NtfsStream stream in file.GetStreams(AttributeType.FileName, null))
- {
- FileNameRecord streamName = stream.GetContent<FileNameRecord>();
- if (nameRecord.Equals(streamName))
- {
- file.RemoveStream(stream);
- break;
- }
- }
-
- file.HardLinkCount--;
- file.UpdateRecordInMft();
-
- Modified();
- UpdateRecordInMft();
- }
-
- internal string CreateShortName(string name)
- {
- string baseName = string.Empty;
- string ext = string.Empty;
-
- int lastPeriod = name.LastIndexOf('.');
-
- int i = 0;
- while (baseName.Length < 6 && i < name.Length && i != lastPeriod)
- {
- char upperChar = Char.ToUpperInvariant(name[i]);
- if (Utilities.Is8Dot3Char(upperChar))
- {
- baseName += upperChar;
- }
-
- ++i;
- }
-
- if (lastPeriod >= 0)
- {
- i = lastPeriod + 1;
- while (ext.Length < 3 && i < name.Length)
- {
- char upperChar = Char.ToUpperInvariant(name[i]);
- if (Utilities.Is8Dot3Char(upperChar))
- {
- ext += upperChar;
- }
-
- ++i;
- }
- }
-
- i = 1;
- string candidate;
- do
- {
- string suffix = string.Format(CultureInfo.InvariantCulture, "~{0}", i);
- candidate = baseName.Substring(0, Math.Min(8 - suffix.Length, baseName.Length)) + suffix + (ext.Length > 0 ? "." + ext : string.Empty);
- i++;
- }
- while (GetEntryByName(candidate) != null);
-
- return candidate;
- }
-
- private List<DirectoryIndexEntry> FilterEntries(IEnumerable<DirectoryIndexEntry> entriesIter)
- {
- List<DirectoryIndexEntry> entries = new List<DirectoryIndexEntry>(entriesIter);
-
- // Weed out short-name entries for files and any hidden / system / metadata files.
- int i = 0;
- while (i < entries.Count)
- {
- DirectoryIndexEntry entry = entries[i];
-
- if (((entry.Key.Flags & FileAttributeFlags.Hidden) != 0) && _context.Options.HideHiddenFiles)
- {
- entries.RemoveAt(i);
- }
- else if (((entry.Key.Flags & FileAttributeFlags.System) != 0) && _context.Options.HideSystemFiles)
- {
- entries.RemoveAt(i);
- }
- else if (entry.Value.MftIndex < 24 && _context.Options.HideMetafiles)
- {
- entries.RemoveAt(i);
- }
- else if (entry.Key.FileNameNamespace == FileNameNamespace.Dos && _context.Options.HideDosFileNames)
- {
- entries.RemoveAt(i);
- }
- else
- {
- ++i;
- }
- }
-
- return entries;
- }
-
- private sealed class FileNameQuery : IComparable<byte[]>
- {
- private byte[] _query;
- private UpperCase _upperCase;
-
- public FileNameQuery(string query, UpperCase upperCase)
- {
- _query = Encoding.Unicode.GetBytes(query);
- _upperCase = upperCase;
- }
-
- public int CompareTo(byte[] buffer)
- {
- // Note: this is internal knowledge of FileNameRecord structure - but for performance
- // reasons, we don't want to decode the entire structure. In fact can avoid the string
- // conversion as well.
- byte fnLen = buffer[0x40];
- return _upperCase.Compare(_query, 0, _query.Length, buffer, 0x42, fnLen * 2);
- }
- }
- }
- }