FileStream.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. //
  2. // System.IO/FileStream.cs
  3. //
  4. // Authors:
  5. // Dietmar Maurer ([email protected])
  6. // Dan Lewis ([email protected])
  7. //
  8. // (C) 2001 Ximian, Inc. http://www.ximian.com
  9. //
  10. using System;
  11. using System.Runtime.CompilerServices;
  12. using System.Runtime.InteropServices;
  13. // FIXME: emit the correct exceptions everywhere. add error handling.
  14. namespace System.IO
  15. {
  16. public class FileStream : Stream
  17. {
  18. // construct from handle
  19. public FileStream (IntPtr handle, FileAccess access)
  20. : this (handle, access, true, DefaultBufferSize, false) {}
  21. public FileStream (IntPtr handle, FileAccess access, bool ownsHandle)
  22. : this (handle, access, ownsHandle, DefaultBufferSize, false) {}
  23. public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
  24. : this (handle, access, ownsHandle, bufferSize, false) {}
  25. public FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
  26. : this (handle, access, ownsHandle, bufferSize, isAsync, false) {}
  27. internal FileStream (IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync, bool noBuffering)
  28. {
  29. this.handle = MonoIO.InvalidHandle;
  30. if (access < FileAccess.Read || access > FileAccess.ReadWrite)
  31. throw new ArgumentOutOfRangeException ("access");
  32. MonoIOError error;
  33. MonoFileType ftype = MonoIO.GetFileType (handle, out error);
  34. if (ftype == MonoFileType.Unknown) {
  35. throw new IOException ("Invalid handle.");
  36. } else if (ftype == MonoFileType.Disk) {
  37. this.canseek = true;
  38. } else {
  39. this.canseek = false;
  40. }
  41. this.handle = handle;
  42. this.access = access;
  43. this.owner = ownsHandle;
  44. this.async = isAsync;
  45. InitBuffer (bufferSize, noBuffering);
  46. /* Can't set append mode */
  47. this.append_startpos=0;
  48. }
  49. // construct from filename
  50. public FileStream (string name, FileMode mode)
  51. : this (name, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, false) { }
  52. public FileStream (string name, FileMode mode, FileAccess access)
  53. : this (name, mode, access, FileShare.ReadWrite, DefaultBufferSize, false) { }
  54. public FileStream (string name, FileMode mode, FileAccess access, FileShare share)
  55. : this (name, mode, access, share, DefaultBufferSize, false) { }
  56. public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize)
  57. : this (name, mode, access, share, bufferSize, false) { }
  58. public FileStream (string name, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool isAsync)
  59. {
  60. if (name == null) {
  61. throw new ArgumentNullException ("name");
  62. }
  63. if (name == "") {
  64. throw new ArgumentException ("Name is empty");
  65. }
  66. if (mode < FileMode.CreateNew || mode > FileMode.Append)
  67. throw new ArgumentOutOfRangeException ("mode");
  68. if (access < FileAccess.Read || access > FileAccess.ReadWrite)
  69. throw new ArgumentOutOfRangeException ("access");
  70. if (share < FileShare.None || share > FileShare.ReadWrite)
  71. throw new ArgumentOutOfRangeException ("share");
  72. if (name.IndexOfAny (Path.InvalidPathChars) != -1) {
  73. throw new ArgumentException ("Name has invalid chars");
  74. }
  75. if (Directory.Exists (name)) {
  76. throw new UnauthorizedAccessException ("Access to the path '" + Path.GetFullPath (name) + "' is denied.");
  77. }
  78. /* Append streams can't be read (see FileMode
  79. * docs)
  80. */
  81. if (mode==FileMode.Append &&
  82. (access&FileAccess.Read)==FileAccess.Read) {
  83. throw new ArgumentException("Append streams can not be read");
  84. }
  85. if ((access & FileAccess.Write) == 0 &&
  86. (mode != FileMode.Open && mode != FileMode.OpenOrCreate))
  87. throw new ArgumentException ("access and mode not compatible");
  88. if (access == FileAccess.Read && mode != FileMode.Create && mode != FileMode.OpenOrCreate &&
  89. mode != FileMode.CreateNew && !File.Exists (name))
  90. throw new FileNotFoundException ("Could not find file \"" + name + "\".");
  91. if (mode == FileMode.CreateNew) {
  92. string dname = Path.GetDirectoryName (name);
  93. string fp = null; ;
  94. if (dname != "" && !Directory.Exists ((fp = Path.GetFullPath (dname))))
  95. throw new DirectoryNotFoundException ("Could not find a part of " +
  96. "the path \"" + fp + "\".");
  97. }
  98. this.name = name;
  99. // TODO: demand permissions
  100. MonoIOError error;
  101. this.handle = MonoIO.Open (name, mode, access, share,
  102. out error);
  103. if (handle == MonoIO.InvalidHandle) {
  104. throw MonoIO.GetException (name, error);
  105. }
  106. this.access = access;
  107. this.owner = true;
  108. this.async = isAsync;
  109. /* Can we open non-files by name? */
  110. if (MonoIO.GetFileType (handle, out error) ==
  111. MonoFileType.Disk) {
  112. this.canseek = true;
  113. } else {
  114. this.canseek = false;
  115. }
  116. if (mode==FileMode.Append) {
  117. this.Seek (0, SeekOrigin.End);
  118. this.append_startpos=this.Position;
  119. } else {
  120. this.append_startpos=0;
  121. }
  122. if (access == FileAccess.Read && canseek && (bufferSize == DefaultBufferSize)) {
  123. /* Avoid allocating a large buffer for small files */
  124. long len = Length;
  125. if (bufferSize > len) {
  126. bufferSize = (int)(len < 1000 ? 1000 : len);
  127. }
  128. }
  129. InitBuffer (bufferSize, false);
  130. }
  131. // properties
  132. public override bool CanRead {
  133. get {
  134. return access == FileAccess.Read ||
  135. access == FileAccess.ReadWrite;
  136. }
  137. }
  138. public override bool CanWrite {
  139. get {
  140. return access == FileAccess.Write ||
  141. access == FileAccess.ReadWrite;
  142. }
  143. }
  144. public override bool CanSeek {
  145. get {
  146. return(canseek);
  147. }
  148. }
  149. public virtual bool IsAsync {
  150. get {
  151. return (async);
  152. }
  153. }
  154. public string Name {
  155. get {
  156. return name;
  157. }
  158. }
  159. public override long Length {
  160. get {
  161. if (handle == MonoIO.InvalidHandle)
  162. throw new ObjectDisposedException ("Stream has been closed");
  163. if (!canseek)
  164. throw new NotSupportedException ("The stream does not support seeking");
  165. // Buffered data might change the length of the stream
  166. FlushBufferIfDirty ();
  167. MonoIOError error;
  168. return MonoIO.GetLength (handle, out error);
  169. }
  170. }
  171. public override long Position {
  172. get {
  173. if (handle == MonoIO.InvalidHandle)
  174. throw new ObjectDisposedException ("Stream has been closed");
  175. if(CanSeek == false)
  176. throw new NotSupportedException("The stream does not support seeking");
  177. return(buf_start + buf_offset);
  178. }
  179. set {
  180. if(CanSeek == false) {
  181. throw new NotSupportedException("The stream does not support seeking");
  182. }
  183. if(value < 0) {
  184. throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
  185. }
  186. Seek (value, SeekOrigin.Begin);
  187. }
  188. }
  189. public virtual IntPtr Handle {
  190. get {
  191. return handle;
  192. }
  193. }
  194. // methods
  195. public override int ReadByte ()
  196. {
  197. if (handle == MonoIO.InvalidHandle)
  198. throw new ObjectDisposedException ("Stream has been closed");
  199. if (!CanRead)
  200. throw new NotSupportedException ("Stream does not support reading");
  201. if (buf_offset >= buf_length) {
  202. RefillBuffer ();
  203. if (buf_length == 0)
  204. return -1;
  205. }
  206. return buf [buf_offset ++];
  207. }
  208. public override void WriteByte (byte value)
  209. {
  210. if (handle == MonoIO.InvalidHandle)
  211. throw new ObjectDisposedException ("Stream has been closed");
  212. if (!CanWrite)
  213. throw new NotSupportedException ("Stream does not support writing");
  214. if (buf_offset == buf_size)
  215. FlushBuffer ();
  216. buf [buf_offset ++] = value;
  217. if (buf_offset > buf_length)
  218. buf_length = buf_offset;
  219. buf_dirty = true;
  220. }
  221. public override int Read ([In,Out] byte[] dest, int dest_offset, int count)
  222. {
  223. if (handle == MonoIO.InvalidHandle)
  224. throw new ObjectDisposedException ("Stream has been closed");
  225. if (dest == null)
  226. throw new ArgumentNullException ("destination array is null");
  227. if (!CanRead)
  228. throw new NotSupportedException ("Stream does not support reading");
  229. int len = dest.Length;
  230. if (dest_offset < 0 || count < 0)
  231. throw new ArgumentException ("dest or count is negative");
  232. if (dest_offset > len)
  233. throw new ArgumentException ("destination offset is beyond array size");
  234. if ((dest_offset + count) > len)
  235. throw new ArgumentException ("Reading would overrun buffer");
  236. int copied = 0;
  237. int n = ReadSegment (dest, dest_offset, count);
  238. copied += n;
  239. count -= n;
  240. if (count == 0) {
  241. /* If there was already enough
  242. * buffered, no need to read
  243. * more from the file.
  244. */
  245. return (copied);
  246. }
  247. if (count > buf_size) {
  248. /* Read as much as we can, up
  249. * to count bytes
  250. */
  251. FlushBuffer();
  252. n = ReadData (handle, dest,
  253. dest_offset+copied,
  254. count);
  255. /* Make the next buffer read
  256. * start from the right place
  257. */
  258. buf_start += n;
  259. } else {
  260. RefillBuffer ();
  261. n = ReadSegment (dest,
  262. dest_offset+copied,
  263. count);
  264. }
  265. copied += n;
  266. return copied;
  267. }
  268. public override void Write (byte[] src, int src_offset, int count)
  269. {
  270. if (handle == MonoIO.InvalidHandle)
  271. throw new ObjectDisposedException ("Stream has been closed");
  272. if (src == null)
  273. throw new ArgumentNullException ("src");
  274. if (src_offset < 0)
  275. throw new ArgumentOutOfRangeException ("src_offset");
  276. if (count < 0)
  277. throw new ArgumentOutOfRangeException ("count");
  278. if (!CanWrite)
  279. throw new NotSupportedException ("Stream does not support writing");
  280. if (count > buf_size) {
  281. // shortcut for long writes
  282. MonoIOError error;
  283. FlushBuffer ();
  284. MonoIO.Write (handle, src, src_offset, count, out error);
  285. buf_start += count;
  286. } else {
  287. int copied = 0;
  288. while (count > 0) {
  289. int n = WriteSegment (src, src_offset + copied, count);
  290. copied += n;
  291. count -= n;
  292. if (count == 0) {
  293. break;
  294. }
  295. FlushBuffer ();
  296. }
  297. }
  298. }
  299. public override long Seek (long offset, SeekOrigin origin)
  300. {
  301. long pos;
  302. if (handle == MonoIO.InvalidHandle)
  303. throw new ObjectDisposedException ("Stream has been closed");
  304. // make absolute
  305. if(CanSeek == false) {
  306. throw new NotSupportedException("The stream does not support seeking");
  307. }
  308. switch (origin) {
  309. case SeekOrigin.End:
  310. pos = Length + offset;
  311. break;
  312. case SeekOrigin.Current:
  313. pos = Position + offset;
  314. break;
  315. case SeekOrigin.Begin: default:
  316. pos = offset;
  317. break;
  318. }
  319. if (pos < 0) {
  320. /* LAMESPEC: shouldn't this be
  321. * ArgumentOutOfRangeException?
  322. */
  323. throw new IOException("Attempted to Seek before the beginning of the stream");
  324. }
  325. if(pos < this.append_startpos) {
  326. /* More undocumented crap */
  327. throw new IOException("Can't seek back over pre-existing data in append mode");
  328. }
  329. if (buf_length > 0) {
  330. if (pos >= buf_start &&
  331. pos <= buf_start + buf_length) {
  332. buf_offset = (int) (pos - buf_start);
  333. return pos;
  334. }
  335. }
  336. FlushBuffer ();
  337. MonoIOError error;
  338. buf_start = MonoIO.Seek (handle, pos,
  339. SeekOrigin.Begin,
  340. out error);
  341. return(buf_start);
  342. }
  343. public override void SetLength (long length)
  344. {
  345. if(CanSeek == false) {
  346. throw new NotSupportedException("The stream does not support seeking");
  347. }
  348. if(CanWrite == false) {
  349. throw new NotSupportedException("The stream does not support writing");
  350. }
  351. if(length < 0) {
  352. throw new ArgumentOutOfRangeException("Length is less than 0");
  353. }
  354. Flush ();
  355. MonoIOError error;
  356. MonoIO.SetLength (handle, length, out error);
  357. }
  358. public override void Flush ()
  359. {
  360. if (handle == MonoIO.InvalidHandle)
  361. throw new ObjectDisposedException ("Stream has been closed");
  362. FlushBuffer ();
  363. // The flushing is not actually required, in
  364. //the mono runtime we were mapping flush to
  365. //`fsync' which is not the same.
  366. //
  367. //MonoIO.Flush (handle);
  368. }
  369. public override void Close ()
  370. {
  371. Dispose (true);
  372. GC.SuppressFinalize (this); // remove from finalize queue
  373. }
  374. [MonoTODO]
  375. public virtual void Lock (long position, long length)
  376. {
  377. throw new NotImplementedException ();
  378. }
  379. [MonoTODO]
  380. public virtual void Unlock (long position, long length)
  381. {
  382. throw new NotImplementedException ();
  383. }
  384. // protected
  385. ~FileStream ()
  386. {
  387. Dispose (false);
  388. }
  389. protected virtual void Dispose (bool disposing) {
  390. if (handle != MonoIO.InvalidHandle) {
  391. FlushBuffer ();
  392. if (owner) {
  393. MonoIOError error;
  394. MonoIO.Close (handle, out error);
  395. handle = MonoIO.InvalidHandle;
  396. }
  397. }
  398. canseek = false;
  399. access = 0;
  400. if (disposing) {
  401. buf = null;
  402. }
  403. }
  404. // private.
  405. // ReadSegment, WriteSegment, FlushBuffer,
  406. // RefillBuffer and ReadData should only be called
  407. // when the Monitor lock is held, but these methods
  408. // grab it again just to be safe.
  409. private int ReadSegment (byte [] dest, int dest_offset,
  410. int count)
  411. {
  412. if (count > buf_length - buf_offset) {
  413. count = buf_length - buf_offset;
  414. }
  415. if (count > 0) {
  416. Buffer.BlockCopy (buf, buf_offset,
  417. dest, dest_offset,
  418. count);
  419. buf_offset += count;
  420. }
  421. return(count);
  422. }
  423. private int WriteSegment (byte [] src, int src_offset,
  424. int count)
  425. {
  426. if (count > buf_size - buf_offset) {
  427. count = buf_size - buf_offset;
  428. }
  429. if (count > 0) {
  430. Buffer.BlockCopy (src, src_offset,
  431. buf, buf_offset,
  432. count);
  433. buf_offset += count;
  434. if (buf_offset > buf_length) {
  435. buf_length = buf_offset;
  436. }
  437. buf_dirty = true;
  438. }
  439. return(count);
  440. }
  441. private void FlushBuffer ()
  442. {
  443. if (buf_dirty) {
  444. MonoIOError error;
  445. if (CanSeek == true) {
  446. MonoIO.Seek (handle, buf_start,
  447. SeekOrigin.Begin,
  448. out error);
  449. }
  450. MonoIO.Write (handle, buf, 0,
  451. buf_length, out error);
  452. }
  453. buf_start += buf_offset;
  454. buf_offset = buf_length = 0;
  455. buf_dirty = false;
  456. }
  457. private void FlushBufferIfDirty ()
  458. {
  459. if (buf_dirty)
  460. FlushBuffer ();
  461. }
  462. private void RefillBuffer ()
  463. {
  464. FlushBuffer();
  465. buf_length = ReadData (handle, buf, 0,
  466. buf_size);
  467. }
  468. private int ReadData (IntPtr handle, byte[] buf, int offset,
  469. int count)
  470. {
  471. MonoIOError error;
  472. int amount = MonoIO.Read (handle, buf, offset,
  473. count, out error);
  474. /* Check for read error */
  475. if(amount == -1) {
  476. /* Kludge around broken pipes */
  477. if(error == MonoIOError.ERROR_BROKEN_PIPE) {
  478. amount = 0;
  479. } else {
  480. throw new IOException ();
  481. }
  482. }
  483. return(amount);
  484. }
  485. private void InitBuffer (int size, bool noBuffering)
  486. {
  487. if (noBuffering)
  488. size = 0;
  489. else {
  490. if (size <= 0)
  491. throw new ArgumentOutOfRangeException ("bufferSize", "Positive number required.");
  492. if (size < 8)
  493. size = 8;
  494. }
  495. buf = new byte [size];
  496. buf_size = size;
  497. buf_start = 0;
  498. buf_offset = buf_length = 0;
  499. buf_dirty = false;
  500. }
  501. // fields
  502. private static int DefaultBufferSize = 8192;
  503. private FileAccess access;
  504. private bool owner;
  505. private bool async;
  506. private bool canseek;
  507. private long append_startpos;
  508. private byte [] buf; // the buffer
  509. private int buf_size; // capacity in bytes
  510. private int buf_length; // number of valid bytes in buffer
  511. private int buf_offset; // position of next byte
  512. private bool buf_dirty; // true if buffer has been written to
  513. private long buf_start; // location of buffer in file
  514. private string name = "[Unknown]"; // name of file.
  515. IntPtr handle; // handle to underlying file
  516. }
  517. }