Files
WPinternals/DiscUtils/Ntfs/Directory.cs
T
2018-10-25 22:35:49 +02:00

292 lines
10 KiB
C#

//
// Copyright (c) 2008-2011, Kenneth Bell
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
namespace DiscUtils.Ntfs
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.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);
}
}
}
}