PageRenderTime 97ms CodeModel.GetById 39ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Ntfs/Directory.cs

#
C# | 291 lines | 217 code | 48 blank | 26 comment | 40 complexity | 6a5ccc0117aa42da69e85b7f537b0227 MD5 | raw file
  1. //
  2. // Copyright (c) 2008-2011, Kenneth Bell
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a
  5. // copy of this software and associated documentation files (the "Software"),
  6. // to deal in the Software without restriction, including without limitation
  7. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. // and/or sell copies of the Software, and to permit persons to whom the
  9. // Software is furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. // DEALINGS IN THE SOFTWARE.
  21. //
  22. namespace DiscUtils.Ntfs
  23. {
  24. using System;
  25. using System.Collections.Generic;
  26. using System.Globalization;
  27. using System.IO;
  28. using System.Text;
  29. using DirectoryIndexEntry = System.Collections.Generic.KeyValuePair<DiscUtils.Ntfs.FileNameRecord, DiscUtils.Ntfs.FileRecordReference>;
  30. internal class Directory : File
  31. {
  32. private IndexView<FileNameRecord, FileRecordReference> _index;
  33. public Directory(INtfsContext context, FileRecord baseRecord)
  34. : base(context, baseRecord)
  35. {
  36. }
  37. public bool IsEmpty
  38. {
  39. get { return Index.Count == 0; }
  40. }
  41. private IndexView<FileNameRecord, FileRecordReference> Index
  42. {
  43. get
  44. {
  45. if (_index == null && StreamExists(AttributeType.IndexRoot, "$I30"))
  46. {
  47. _index = new IndexView<FileNameRecord, FileRecordReference>(GetIndex("$I30"));
  48. }
  49. return _index;
  50. }
  51. }
  52. public IEnumerable<DirectoryEntry> GetAllEntries(bool filter)
  53. {
  54. IEnumerable<DirectoryIndexEntry> entries = filter ? FilterEntries(Index.Entries) : Index.Entries;
  55. foreach (var entry in entries)
  56. {
  57. yield return new DirectoryEntry(this, entry.Value, entry.Key);
  58. }
  59. }
  60. public void UpdateEntry(DirectoryEntry entry)
  61. {
  62. Index[entry.Details] = entry.Reference;
  63. UpdateRecordInMft();
  64. }
  65. public override void Dump(TextWriter writer, string indent)
  66. {
  67. writer.WriteLine(indent + "DIRECTORY (" + base.ToString() + ")");
  68. writer.WriteLine(indent + " File Number: " + IndexInMft);
  69. if (Index != null)
  70. {
  71. foreach (var entry in Index.Entries)
  72. {
  73. writer.WriteLine(indent + " DIRECTORY ENTRY (" + entry.Key.FileName + ")");
  74. writer.WriteLine(indent + " MFT Ref: " + entry.Value);
  75. entry.Key.Dump(writer, indent + " ");
  76. }
  77. }
  78. }
  79. public override string ToString()
  80. {
  81. return base.ToString() + @"\";
  82. }
  83. internal static new Directory CreateNew(INtfsContext context, FileAttributeFlags parentDirFlags)
  84. {
  85. Directory dir = (Directory)context.AllocateFile(FileRecordFlags.IsDirectory);
  86. StandardInformation.InitializeNewFile(
  87. dir,
  88. FileAttributeFlags.Archive | (parentDirFlags & FileAttributeFlags.Compressed));
  89. // Create the index root attribute by instantiating a new index
  90. dir.CreateIndex("$I30", AttributeType.FileName, AttributeCollationRule.Filename);
  91. dir.UpdateRecordInMft();
  92. return dir;
  93. }
  94. internal DirectoryEntry GetEntryByName(string name)
  95. {
  96. string searchName = name;
  97. int streamSepPos = name.IndexOf(':');
  98. if (streamSepPos >= 0)
  99. {
  100. searchName = name.Substring(0, streamSepPos);
  101. }
  102. DirectoryIndexEntry entry = Index.FindFirst(new FileNameQuery(searchName, _context.UpperCase));
  103. if (entry.Key != null)
  104. {
  105. return new DirectoryEntry(this, entry.Value, entry.Key);
  106. }
  107. else
  108. {
  109. return null;
  110. }
  111. }
  112. internal DirectoryEntry AddEntry(File file, string name, FileNameNamespace nameNamespace)
  113. {
  114. if (name.Length > 255)
  115. {
  116. throw new IOException("Invalid file name, more than 255 characters: " + name);
  117. }
  118. else if (name.IndexOfAny(new char[] { '\0', '/' }) != -1)
  119. {
  120. throw new IOException(@"Invalid file name, contains '\0' or '/': " + name);
  121. }
  122. FileNameRecord newNameRecord = file.GetFileNameRecord(null, true);
  123. newNameRecord.FileNameNamespace = nameNamespace;
  124. newNameRecord.FileName = name;
  125. newNameRecord.ParentDirectory = MftReference;
  126. NtfsStream nameStream = file.CreateStream(AttributeType.FileName, null);
  127. nameStream.SetContent(newNameRecord);
  128. file.HardLinkCount++;
  129. file.UpdateRecordInMft();
  130. Index[newNameRecord] = file.MftReference;
  131. Modified();
  132. UpdateRecordInMft();
  133. return new DirectoryEntry(this, file.MftReference, newNameRecord);
  134. }
  135. internal void RemoveEntry(DirectoryEntry dirEntry)
  136. {
  137. File file = _context.GetFileByRef(dirEntry.Reference);
  138. FileNameRecord nameRecord = dirEntry.Details;
  139. Index.Remove(dirEntry.Details);
  140. foreach (NtfsStream stream in file.GetStreams(AttributeType.FileName, null))
  141. {
  142. FileNameRecord streamName = stream.GetContent<FileNameRecord>();
  143. if (nameRecord.Equals(streamName))
  144. {
  145. file.RemoveStream(stream);
  146. break;
  147. }
  148. }
  149. file.HardLinkCount--;
  150. file.UpdateRecordInMft();
  151. Modified();
  152. UpdateRecordInMft();
  153. }
  154. internal string CreateShortName(string name)
  155. {
  156. string baseName = string.Empty;
  157. string ext = string.Empty;
  158. int lastPeriod = name.LastIndexOf('.');
  159. int i = 0;
  160. while (baseName.Length < 6 && i < name.Length && i != lastPeriod)
  161. {
  162. char upperChar = Char.ToUpperInvariant(name[i]);
  163. if (Utilities.Is8Dot3Char(upperChar))
  164. {
  165. baseName += upperChar;
  166. }
  167. ++i;
  168. }
  169. if (lastPeriod >= 0)
  170. {
  171. i = lastPeriod + 1;
  172. while (ext.Length < 3 && i < name.Length)
  173. {
  174. char upperChar = Char.ToUpperInvariant(name[i]);
  175. if (Utilities.Is8Dot3Char(upperChar))
  176. {
  177. ext += upperChar;
  178. }
  179. ++i;
  180. }
  181. }
  182. i = 1;
  183. string candidate;
  184. do
  185. {
  186. string suffix = string.Format(CultureInfo.InvariantCulture, "~{0}", i);
  187. candidate = baseName.Substring(0, Math.Min(8 - suffix.Length, baseName.Length)) + suffix + (ext.Length > 0 ? "." + ext : string.Empty);
  188. i++;
  189. }
  190. while (GetEntryByName(candidate) != null);
  191. return candidate;
  192. }
  193. private List<DirectoryIndexEntry> FilterEntries(IEnumerable<DirectoryIndexEntry> entriesIter)
  194. {
  195. List<DirectoryIndexEntry> entries = new List<DirectoryIndexEntry>(entriesIter);
  196. // Weed out short-name entries for files and any hidden / system / metadata files.
  197. int i = 0;
  198. while (i < entries.Count)
  199. {
  200. DirectoryIndexEntry entry = entries[i];
  201. if (((entry.Key.Flags & FileAttributeFlags.Hidden) != 0) && _context.Options.HideHiddenFiles)
  202. {
  203. entries.RemoveAt(i);
  204. }
  205. else if (((entry.Key.Flags & FileAttributeFlags.System) != 0) && _context.Options.HideSystemFiles)
  206. {
  207. entries.RemoveAt(i);
  208. }
  209. else if (entry.Value.MftIndex < 24 && _context.Options.HideMetafiles)
  210. {
  211. entries.RemoveAt(i);
  212. }
  213. else if (entry.Key.FileNameNamespace == FileNameNamespace.Dos && _context.Options.HideDosFileNames)
  214. {
  215. entries.RemoveAt(i);
  216. }
  217. else
  218. {
  219. ++i;
  220. }
  221. }
  222. return entries;
  223. }
  224. private sealed class FileNameQuery : IComparable<byte[]>
  225. {
  226. private byte[] _query;
  227. private UpperCase _upperCase;
  228. public FileNameQuery(string query, UpperCase upperCase)
  229. {
  230. _query = Encoding.Unicode.GetBytes(query);
  231. _upperCase = upperCase;
  232. }
  233. public int CompareTo(byte[] buffer)
  234. {
  235. // Note: this is internal knowledge of FileNameRecord structure - but for performance
  236. // reasons, we don't want to decode the entire structure. In fact can avoid the string
  237. // conversion as well.
  238. byte fnLen = buffer[0x40];
  239. return _upperCase.Compare(_query, 0, _query.Length, buffer, 0x42, fnLen * 2);
  240. }
  241. }
  242. }
  243. }