FileStream.cs 14 KB

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