FileStream.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  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. InitBuffer (bufferSize, false);
  79. /* Append streams can't be read (see FileMode
  80. * docs)
  81. */
  82. if (mode==FileMode.Append &&
  83. (access&FileAccess.Read)==FileAccess.Read) {
  84. throw new ArgumentException("Append streams can not be read");
  85. }
  86. if ((access & FileAccess.Write) == 0 &&
  87. (mode != FileMode.Open && mode != FileMode.OpenOrCreate))
  88. throw new ArgumentException ("access and mode not compatible");
  89. this.name = name;
  90. // TODO: demand permissions
  91. MonoIOError error;
  92. this.handle = MonoIO.Open (name, mode, access, share,
  93. out error);
  94. if (handle == MonoIO.InvalidHandle) {
  95. throw MonoIO.GetException (name, error);
  96. }
  97. this.access = access;
  98. this.owner = true;
  99. this.async = isAsync;
  100. /* Can we open non-files by name? */
  101. if (MonoIO.GetFileType (handle, out error) ==
  102. MonoFileType.Disk) {
  103. this.canseek = true;
  104. } else {
  105. this.canseek = false;
  106. }
  107. if (mode==FileMode.Append) {
  108. this.Seek (0, SeekOrigin.End);
  109. this.append_startpos=this.Position;
  110. } else {
  111. this.append_startpos=0;
  112. }
  113. }
  114. // properties
  115. public override bool CanRead {
  116. get {
  117. return access == FileAccess.Read ||
  118. access == FileAccess.ReadWrite;
  119. }
  120. }
  121. public override bool CanWrite {
  122. get {
  123. return access == FileAccess.Write ||
  124. access == FileAccess.ReadWrite;
  125. }
  126. }
  127. public override bool CanSeek {
  128. get {
  129. return(canseek);
  130. }
  131. }
  132. public virtual bool IsAsync {
  133. get {
  134. return (async);
  135. }
  136. }
  137. public string Name {
  138. get {
  139. return name;
  140. }
  141. }
  142. public override long Length {
  143. get {
  144. if (handle == MonoIO.InvalidHandle)
  145. throw new ObjectDisposedException ("Stream has been closed");
  146. if (!canseek)
  147. throw new NotSupportedException ("The stream does not support seeking");
  148. // Buffered data might change the length of the stream
  149. FlushBufferIfDirty ();
  150. MonoIOError error;
  151. return MonoIO.GetLength (handle, out error);
  152. }
  153. }
  154. public override long Position {
  155. get {
  156. if (handle == MonoIO.InvalidHandle)
  157. throw new ObjectDisposedException ("Stream has been closed");
  158. if(CanSeek == false)
  159. throw new NotSupportedException("The stream does not support seeking");
  160. lock(this) {
  161. return(buf_start + buf_offset);
  162. }
  163. }
  164. set {
  165. if(CanSeek == false) {
  166. throw new NotSupportedException("The stream does not support seeking");
  167. }
  168. if(value < 0) {
  169. throw new ArgumentOutOfRangeException("Attempt to set the position to a negative value");
  170. }
  171. Seek (value, SeekOrigin.Begin);
  172. }
  173. }
  174. public virtual IntPtr Handle {
  175. get {
  176. return handle;
  177. }
  178. }
  179. // methods
  180. public override int ReadByte ()
  181. {
  182. if (handle == MonoIO.InvalidHandle)
  183. throw new ObjectDisposedException ("Stream has been closed");
  184. if (!CanRead)
  185. throw new NotSupportedException ("Stream does not support reading");
  186. lock(this) {
  187. if (buf_offset >= buf_length) {
  188. RefillBuffer ();
  189. if (buf_length == 0) {
  190. return -1;
  191. }
  192. }
  193. return(buf [buf_offset ++]);
  194. }
  195. }
  196. public override void WriteByte (byte value)
  197. {
  198. if (handle == MonoIO.InvalidHandle)
  199. throw new ObjectDisposedException ("Stream has been closed");
  200. if (!CanWrite)
  201. throw new NotSupportedException ("Stream does not support writing");
  202. lock(this) {
  203. if (buf_offset == buf_size) {
  204. FlushBuffer ();
  205. }
  206. buf [buf_offset ++] = value;
  207. if (buf_offset > buf_length) {
  208. buf_length = buf_offset;
  209. }
  210. buf_dirty = true;
  211. }
  212. }
  213. public override int Read ([In,Out] byte[] dest, int dest_offset, int count)
  214. {
  215. if (handle == MonoIO.InvalidHandle)
  216. throw new ObjectDisposedException ("Stream has been closed");
  217. if (dest == null)
  218. throw new ArgumentNullException ("destination array is null");
  219. if (!CanRead)
  220. throw new NotSupportedException ("Stream does not support reading");
  221. int len = dest.Length;
  222. if (dest_offset < 0 || count < 0)
  223. throw new ArgumentException ("dest or count is negative");
  224. if (dest_offset > len)
  225. throw new ArgumentException ("destination offset is beyond array size");
  226. if ((dest_offset + count) > len)
  227. throw new ArgumentException ("Reading would overrun buffer");
  228. int copied = 0;
  229. lock(this) {
  230. int n = ReadSegment (dest, dest_offset, count);
  231. copied += n;
  232. count -= n;
  233. if (count == 0) {
  234. /* If there was already enough
  235. * buffered, no need to read
  236. * more from the file.
  237. */
  238. return (copied);
  239. }
  240. if (count > buf_size) {
  241. /* Read as much as we can, up
  242. * to count bytes
  243. */
  244. FlushBuffer();
  245. n = ReadData (handle, dest,
  246. dest_offset+copied,
  247. count);
  248. /* Make the next buffer read
  249. * start from the right place
  250. */
  251. buf_start += n;
  252. } else {
  253. RefillBuffer ();
  254. n = ReadSegment (dest,
  255. dest_offset+copied,
  256. count);
  257. }
  258. copied += n;
  259. return(copied);
  260. }
  261. }
  262. public override void Write (byte[] src, int src_offset, int count)
  263. {
  264. if (handle == MonoIO.InvalidHandle)
  265. throw new ObjectDisposedException ("Stream has been closed");
  266. if (src == null)
  267. throw new ArgumentNullException ("src");
  268. if (src_offset < 0)
  269. throw new ArgumentOutOfRangeException ("src_offset");
  270. if (count < 0)
  271. throw new ArgumentOutOfRangeException ("count");
  272. if (!CanWrite)
  273. throw new NotSupportedException ("Stream does not support writing");
  274. if (count > buf_size) {
  275. // shortcut for long writes
  276. MonoIOError error;
  277. FlushBuffer ();
  278. MonoIO.Write (handle, src, src_offset, count, out error);
  279. buf_start += count;
  280. } else {
  281. int copied = 0;
  282. while (count > 0) {
  283. int n = WriteSegment (src, src_offset + copied, count);
  284. copied += n;
  285. count -= n;
  286. if (count == 0) {
  287. break;
  288. }
  289. FlushBuffer ();
  290. }
  291. }
  292. }
  293. public override long Seek (long offset, SeekOrigin origin)
  294. {
  295. long pos;
  296. if (handle == MonoIO.InvalidHandle)
  297. throw new ObjectDisposedException ("Stream has been closed");
  298. // make absolute
  299. if(CanSeek == false) {
  300. throw new NotSupportedException("The stream does not support seeking");
  301. }
  302. lock(this) {
  303. switch (origin) {
  304. case SeekOrigin.End:
  305. pos = Length + offset;
  306. break;
  307. case SeekOrigin.Current:
  308. pos = Position + offset;
  309. break;
  310. case SeekOrigin.Begin: default:
  311. pos = offset;
  312. break;
  313. }
  314. if (pos < 0) {
  315. /* LAMESPEC: shouldn't this be
  316. * ArgumentOutOfRangeException?
  317. */
  318. throw new IOException("Attempted to Seek before the beginning of the stream");
  319. }
  320. if(pos < this.append_startpos) {
  321. /* More undocumented crap */
  322. throw new IOException("Can't seek back over pre-existing data in append mode");
  323. }
  324. if (buf_length > 0) {
  325. if (pos >= buf_start &&
  326. pos <= buf_start + buf_length) {
  327. buf_offset = (int) (pos - buf_start);
  328. return pos;
  329. }
  330. }
  331. FlushBuffer ();
  332. MonoIOError error;
  333. buf_start = MonoIO.Seek (handle, pos,
  334. SeekOrigin.Begin,
  335. out error);
  336. return(buf_start);
  337. }
  338. }
  339. public override void SetLength (long length)
  340. {
  341. if(CanSeek == false) {
  342. throw new NotSupportedException("The stream does not support seeking");
  343. }
  344. if(CanWrite == false) {
  345. throw new NotSupportedException("The stream does not support writing");
  346. }
  347. if(length < 0) {
  348. throw new ArgumentOutOfRangeException("Length is less than 0");
  349. }
  350. Flush ();
  351. MonoIOError error;
  352. MonoIO.SetLength (handle, length, out error);
  353. }
  354. public override void Flush ()
  355. {
  356. if (handle == MonoIO.InvalidHandle)
  357. throw new ObjectDisposedException ("Stream has been closed");
  358. lock(this) {
  359. FlushBuffer ();
  360. }
  361. // The flushing is not actually required, in
  362. //the mono runtime we were mapping flush to
  363. //`fsync' which is not the same.
  364. //
  365. //MonoIO.Flush (handle);
  366. }
  367. public override void Close ()
  368. {
  369. Dispose (true);
  370. GC.SuppressFinalize (this); // remove from finalize queue
  371. }
  372. [MonoTODO]
  373. public virtual void Lock (long position, long length)
  374. {
  375. throw new NotImplementedException ();
  376. }
  377. [MonoTODO]
  378. public virtual void Unlock (long position, long length)
  379. {
  380. throw new NotImplementedException ();
  381. }
  382. // protected
  383. ~FileStream ()
  384. {
  385. Dispose (false);
  386. }
  387. protected virtual void Dispose (bool disposing) {
  388. if (handle != MonoIO.InvalidHandle) {
  389. lock(this) {
  390. FlushBuffer ();
  391. }
  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. }