mirror of
https://github.com/ReneLergner/WPinternals.git
synced 2026-06-16 04:10:11 +10:00
292 lines
10 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|