NTFileSystemAdapter.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using Utilities;
  11. namespace SMBLibrary
  12. {
  13. public partial class NTFileSystemAdapter : INTFileStore
  14. {
  15. private const int BytesPerSector = 512;
  16. private const int ClusterSize = 4096;
  17. private IFileSystem m_fileSystem;
  18. public event EventHandler<LogEntry> OnLogEntry;
  19. public NTFileSystemAdapter(IFileSystem fileSystem)
  20. {
  21. m_fileSystem = fileSystem;
  22. }
  23. public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string path, AccessMask desiredAccess, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, SecurityContext securityContext)
  24. {
  25. handle = null;
  26. fileStatus = FileStatus.FILE_DOES_NOT_EXIST;
  27. FileAccess createAccess = NTFileStoreHelper.ToCreateFileAccess(desiredAccess, createDisposition);
  28. bool requestedWriteAccess = (createAccess & FileAccess.Write) > 0;
  29. bool forceDirectory = (createOptions & CreateOptions.FILE_DIRECTORY_FILE) > 0;
  30. bool forceFile = (createOptions & CreateOptions.FILE_NON_DIRECTORY_FILE) > 0;
  31. if (forceDirectory & (createDisposition != CreateDisposition.FILE_CREATE &&
  32. createDisposition != CreateDisposition.FILE_OPEN &&
  33. createDisposition != CreateDisposition.FILE_OPEN_IF &&
  34. createDisposition != CreateDisposition.FILE_SUPERSEDE))
  35. {
  36. return NTStatus.STATUS_INVALID_PARAMETER;
  37. }
  38. // Windows will try to access named streams (alternate data streams) regardless of the FILE_NAMED_STREAMS flag, we need to prevent this behaviour.
  39. if (path.Contains(":"))
  40. {
  41. // Windows Server 2003 will return STATUS_OBJECT_NAME_NOT_FOUND
  42. return NTStatus.STATUS_NO_SUCH_FILE;
  43. }
  44. FileSystemEntry entry;
  45. try
  46. {
  47. entry = m_fileSystem.GetEntry(path);
  48. }
  49. catch (Exception ex)
  50. {
  51. NTStatus status = ToNTStatus(ex);
  52. Log(Severity.Verbose, "CreateFile: Error retrieving '{0}'. {1}.", path, status);
  53. return status;
  54. }
  55. if (createDisposition == CreateDisposition.FILE_OPEN)
  56. {
  57. if (entry == null)
  58. {
  59. return NTStatus.STATUS_NO_SUCH_FILE;
  60. }
  61. fileStatus = FileStatus.FILE_EXISTS;
  62. if (entry.IsDirectory && forceFile)
  63. {
  64. return NTStatus.STATUS_FILE_IS_A_DIRECTORY;
  65. }
  66. if (!entry.IsDirectory && forceDirectory)
  67. {
  68. return NTStatus.STATUS_OBJECT_PATH_INVALID;
  69. }
  70. }
  71. else if (createDisposition == CreateDisposition.FILE_CREATE)
  72. {
  73. if (entry != null)
  74. {
  75. // File already exists, fail the request
  76. Log(Severity.Verbose, "CreateFile: File '{0}' already exist", path);
  77. fileStatus = FileStatus.FILE_EXISTS;
  78. return NTStatus.STATUS_OBJECT_NAME_COLLISION;
  79. }
  80. if (!requestedWriteAccess)
  81. {
  82. return NTStatus.STATUS_ACCESS_DENIED;
  83. }
  84. try
  85. {
  86. if (forceDirectory)
  87. {
  88. Log(Severity.Information, "CreateFile: Creating directory '{0}'", path);
  89. entry = m_fileSystem.CreateDirectory(path);
  90. }
  91. else
  92. {
  93. Log(Severity.Information, "CreateFile: Creating file '{0}'", path);
  94. entry = m_fileSystem.CreateFile(path);
  95. }
  96. }
  97. catch (Exception ex)
  98. {
  99. NTStatus status = ToNTStatus(ex);
  100. Log(Severity.Verbose, "CreateFile: Error creating '{0}'. {1}.", path, status);
  101. return status;
  102. }
  103. fileStatus = FileStatus.FILE_CREATED;
  104. }
  105. else if (createDisposition == CreateDisposition.FILE_OPEN_IF ||
  106. createDisposition == CreateDisposition.FILE_OVERWRITE ||
  107. createDisposition == CreateDisposition.FILE_OVERWRITE_IF ||
  108. createDisposition == CreateDisposition.FILE_SUPERSEDE)
  109. {
  110. if (entry == null)
  111. {
  112. if (createDisposition == CreateDisposition.FILE_OVERWRITE)
  113. {
  114. return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND;
  115. }
  116. if (!requestedWriteAccess)
  117. {
  118. return NTStatus.STATUS_ACCESS_DENIED;
  119. }
  120. try
  121. {
  122. if (forceDirectory)
  123. {
  124. Log(Severity.Information, "CreateFile: Creating directory '{0}'", path);
  125. entry = m_fileSystem.CreateDirectory(path);
  126. }
  127. else
  128. {
  129. Log(Severity.Information, "CreateFile: Creating file '{0}'", path);
  130. entry = m_fileSystem.CreateFile(path);
  131. }
  132. }
  133. catch (Exception ex)
  134. {
  135. NTStatus status = ToNTStatus(ex);
  136. Log(Severity.Verbose, "CreateFile: Error creating '{0}'. {1}.", path, status);
  137. return status;
  138. }
  139. fileStatus = FileStatus.FILE_CREATED;
  140. }
  141. else
  142. {
  143. fileStatus = FileStatus.FILE_EXISTS;
  144. if (createDisposition == CreateDisposition.FILE_OPEN_IF)
  145. {
  146. if (entry.IsDirectory && forceFile)
  147. {
  148. return NTStatus.STATUS_FILE_IS_A_DIRECTORY;
  149. }
  150. if (!entry.IsDirectory && forceDirectory)
  151. {
  152. return NTStatus.STATUS_OBJECT_PATH_INVALID;
  153. }
  154. }
  155. else
  156. {
  157. if (!requestedWriteAccess)
  158. {
  159. return NTStatus.STATUS_ACCESS_DENIED;
  160. }
  161. if (createDisposition == CreateDisposition.FILE_OVERWRITE ||
  162. createDisposition == CreateDisposition.FILE_OVERWRITE_IF)
  163. {
  164. // Truncate the file
  165. try
  166. {
  167. Stream temp = m_fileSystem.OpenFile(path, FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite);
  168. temp.Close();
  169. }
  170. catch (Exception ex)
  171. {
  172. NTStatus status = ToNTStatus(ex);
  173. Log(Severity.Verbose, "CreateFile: Error truncating '{0}'. {1}.", path, status);
  174. return status;
  175. }
  176. fileStatus = FileStatus.FILE_OVERWRITTEN;
  177. }
  178. else if (createDisposition == CreateDisposition.FILE_SUPERSEDE)
  179. {
  180. // Delete the old file
  181. try
  182. {
  183. m_fileSystem.Delete(path);
  184. }
  185. catch (Exception ex)
  186. {
  187. NTStatus status = ToNTStatus(ex);
  188. Log(Severity.Verbose, "CreateFile: Error deleting '{0}'. {1}.", path, status);
  189. return status;
  190. }
  191. try
  192. {
  193. if (forceDirectory)
  194. {
  195. Log(Severity.Information, "CreateFile: Creating directory '{0}'", path);
  196. entry = m_fileSystem.CreateDirectory(path);
  197. }
  198. else
  199. {
  200. Log(Severity.Information, "CreateFile: Creating file '{0}'", path);
  201. entry = m_fileSystem.CreateFile(path);
  202. }
  203. }
  204. catch (Exception ex)
  205. {
  206. NTStatus status = ToNTStatus(ex);
  207. Log(Severity.Verbose, "CreateFile: Error creating '{0}'. {1}.", path, status);
  208. return status;
  209. }
  210. fileStatus = FileStatus.FILE_SUPERSEDED;
  211. }
  212. }
  213. }
  214. }
  215. else
  216. {
  217. return NTStatus.STATUS_INVALID_PARAMETER;
  218. }
  219. FileAccess fileAccess = NTFileStoreHelper.ToFileAccess(desiredAccess.File);
  220. Stream stream;
  221. bool deleteOnClose = false;
  222. if (fileAccess == (FileAccess)0 || entry.IsDirectory)
  223. {
  224. stream = null;
  225. }
  226. else
  227. {
  228. NTStatus openStatus = OpenFileStream(out stream, path, fileAccess, shareAccess, createOptions);
  229. if (openStatus != NTStatus.STATUS_SUCCESS)
  230. {
  231. return openStatus;
  232. }
  233. }
  234. deleteOnClose = (createOptions & CreateOptions.FILE_DELETE_ON_CLOSE) > 0;
  235. handle = new FileHandle(path, entry.IsDirectory, stream, deleteOnClose);
  236. if (fileStatus != FileStatus.FILE_CREATED &&
  237. fileStatus != FileStatus.FILE_OVERWRITTEN &&
  238. fileStatus != FileStatus.FILE_SUPERSEDED)
  239. {
  240. fileStatus = FileStatus.FILE_OPENED;
  241. }
  242. return NTStatus.STATUS_SUCCESS;
  243. }
  244. private NTStatus OpenFileStream(out Stream stream, string path, FileAccess fileAccess, ShareAccess shareAccess, CreateOptions openOptions)
  245. {
  246. stream = null;
  247. // When FILE_OPEN_REPARSE_POINT is specified, the operation should continue normally if the file is not a reparse point.
  248. // FILE_OPEN_REPARSE_POINT is a hint that the caller does not intend to actually read the file, with the exception
  249. // of a file copy operation (where the caller will attempt to simply copy the reparse point).
  250. bool openReparsePoint = (openOptions & CreateOptions.FILE_OPEN_REPARSE_POINT) > 0;
  251. bool disableBuffering = (openOptions & CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING) > 0;
  252. bool buffered = (openOptions & CreateOptions.FILE_RANDOM_ACCESS) == 0 && !disableBuffering && !openReparsePoint;
  253. FileShare fileShare = NTFileStoreHelper.ToFileShare(shareAccess);
  254. string fileShareString = fileShare.ToString().Replace(", ", "|");
  255. try
  256. {
  257. stream = m_fileSystem.OpenFile(path, FileMode.Open, fileAccess, fileShare);
  258. }
  259. catch (Exception ex)
  260. {
  261. NTStatus status = ToNTStatus(ex);
  262. Log(Severity.Verbose, "OpenFile: Cannot open '{0}', Access={1}, Share={2}. NTStatus: {3}.", path, fileAccess, fileShareString, status);
  263. return status;
  264. }
  265. Log(Severity.Information, "OpenFileStream: Opened '{0}', Access={1}, Share={2}, Buffered={3}", path, fileAccess, fileShareString, buffered);
  266. if (buffered)
  267. {
  268. stream = new PrefetchedStream(stream);
  269. }
  270. return NTStatus.STATUS_SUCCESS;
  271. }
  272. public NTStatus CloseFile(object handle)
  273. {
  274. FileHandle fileHandle = (FileHandle)handle;
  275. if (fileHandle.Stream != null)
  276. {
  277. Log(Severity.Verbose, "CloseFile: Closing '{0}'.", fileHandle.Path);
  278. fileHandle.Stream.Close();
  279. }
  280. if (fileHandle.DeleteOnClose)
  281. {
  282. try
  283. {
  284. m_fileSystem.Delete(fileHandle.Path);
  285. Log(Severity.Verbose, "CloseFile: Deleted '{0}'.", fileHandle.Path);
  286. }
  287. catch
  288. {
  289. Log(Severity.Verbose, "CloseFile: Error deleting '{0}'.", fileHandle.Path);
  290. }
  291. }
  292. return NTStatus.STATUS_SUCCESS;
  293. }
  294. public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCount)
  295. {
  296. data = null;
  297. FileHandle fileHandle = (FileHandle)handle;
  298. string path = fileHandle.Path;
  299. Stream stream = fileHandle.Stream;
  300. if (stream == null || !stream.CanRead)
  301. {
  302. Log(Severity.Verbose, "ReadFile: Cannot read '{0}', Invalid Operation.", path);
  303. return NTStatus.STATUS_ACCESS_DENIED;
  304. }
  305. int bytesRead;
  306. try
  307. {
  308. stream.Seek(offset, SeekOrigin.Begin);
  309. data = new byte[maxCount];
  310. bytesRead = stream.Read(data, 0, maxCount);
  311. }
  312. catch (Exception ex)
  313. {
  314. NTStatus status = ToNTStatus(ex);
  315. Log(Severity.Verbose, "ReadFile: Cannot read '{0}'. {1}.", path, status);
  316. return status;
  317. }
  318. if (bytesRead < maxCount)
  319. {
  320. // EOF, we must trim the response data array
  321. data = ByteReader.ReadBytes(data, 0, bytesRead);
  322. }
  323. return NTStatus.STATUS_SUCCESS;
  324. }
  325. public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offset, byte[] data)
  326. {
  327. numberOfBytesWritten = 0;
  328. FileHandle fileHandle = (FileHandle)handle;
  329. string path = fileHandle.Path;
  330. Stream stream = fileHandle.Stream;
  331. if (stream == null || !stream.CanWrite)
  332. {
  333. Log(Severity.Verbose, "WriteFile: Cannot write '{0}'. Invalid Operation.", path);
  334. return NTStatus.STATUS_ACCESS_DENIED;
  335. }
  336. try
  337. {
  338. stream.Seek(offset, SeekOrigin.Begin);
  339. stream.Write(data, 0, data.Length);
  340. }
  341. catch (Exception ex)
  342. {
  343. NTStatus status = ToNTStatus(ex);
  344. Log(Severity.Verbose, "WriteFile: Cannot write '{0}'. {1}.", path, status);
  345. return status;
  346. }
  347. numberOfBytesWritten = data.Length;
  348. return NTStatus.STATUS_SUCCESS;
  349. }
  350. public NTStatus FlushFileBuffers(object handle)
  351. {
  352. FileHandle fileHandle = (FileHandle)handle;
  353. if (fileHandle.Stream != null)
  354. {
  355. fileHandle.Stream.Flush();
  356. }
  357. return NTStatus.STATUS_SUCCESS;
  358. }
  359. public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out byte[] output, int maxOutputLength)
  360. {
  361. output = null;
  362. return NTStatus.STATUS_NOT_SUPPORTED;
  363. }
  364. public void Log(Severity severity, string message)
  365. {
  366. // To be thread-safe we must capture the delegate reference first
  367. EventHandler<LogEntry> handler = OnLogEntry;
  368. if (handler != null)
  369. {
  370. handler(this, new LogEntry(DateTime.Now, severity, "NT FileSystem Adapter", message));
  371. }
  372. }
  373. public void Log(Severity severity, string message, params object[] args)
  374. {
  375. Log(severity, String.Format(message, args));
  376. }
  377. /// <param name="exception">IFileSystem exception</param>
  378. private static NTStatus ToNTStatus(Exception exception)
  379. {
  380. if (exception is ArgumentException)
  381. {
  382. return NTStatus.STATUS_OBJECT_PATH_SYNTAX_BAD;
  383. }
  384. else if (exception is DirectoryNotFoundException)
  385. {
  386. return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND;
  387. }
  388. else if (exception is FileNotFoundException)
  389. {
  390. return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND;
  391. }
  392. else if (exception is IOException)
  393. {
  394. ushort errorCode = IOExceptionHelper.GetWin32ErrorCode((IOException)exception);
  395. if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION)
  396. {
  397. return NTStatus.STATUS_SHARING_VIOLATION;
  398. }
  399. else if (errorCode == (ushort)Win32Error.ERROR_DISK_FULL)
  400. {
  401. return NTStatus.STATUS_DISK_FULL;
  402. }
  403. else if (errorCode == (ushort)Win32Error.ERROR_DIR_NOT_EMPTY)
  404. {
  405. // If a user tries to rename folder1 to folder2 when folder2 already exists, Windows 7 will offer to merge folder1 into folder2.
  406. // In such case, Windows 7 will delete folder 1 and will expect STATUS_DIRECTORY_NOT_EMPTY if there are files to merge.
  407. return NTStatus.STATUS_DIRECTORY_NOT_EMPTY;
  408. }
  409. else if (errorCode == (ushort)Win32Error.ERROR_ALREADY_EXISTS)
  410. {
  411. // According to [MS-FSCC], FileRenameInformation MUST return STATUS_OBJECT_NAME_COLLISION when the specified name already exists and ReplaceIfExists is zero.
  412. return NTStatus.STATUS_OBJECT_NAME_COLLISION;
  413. }
  414. else
  415. {
  416. return NTStatus.STATUS_DATA_ERROR;
  417. }
  418. }
  419. else if (exception is UnauthorizedAccessException)
  420. {
  421. return NTStatus.STATUS_ACCESS_DENIED;
  422. }
  423. else
  424. {
  425. return NTStatus.STATUS_DATA_ERROR;
  426. }
  427. }
  428. /// <summary>
  429. /// Will return a virtual allocation size, assuming 4096 bytes per cluster
  430. /// </summary>
  431. public static ulong GetAllocationSize(ulong size)
  432. {
  433. return (ulong)Math.Ceiling((double)size / ClusterSize) * ClusterSize;
  434. }
  435. public static string GetShortName(string fileName)
  436. {
  437. string fileNameWithoutExt = System.IO.Path.GetFileNameWithoutExtension(fileName);
  438. string extension = System.IO.Path.GetExtension(fileName);
  439. if (fileNameWithoutExt.Length > 8 || extension.Length > 4)
  440. {
  441. if (fileNameWithoutExt.Length > 8)
  442. {
  443. fileNameWithoutExt = fileNameWithoutExt.Substring(0, 8);
  444. }
  445. if (extension.Length > 4)
  446. {
  447. extension = extension.Substring(0, 4);
  448. }
  449. return fileNameWithoutExt + extension;
  450. }
  451. else
  452. {
  453. return fileName;
  454. }
  455. }
  456. }
  457. }