FileStream.cs 15 KB

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