FileStream.cs 15 KB

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