MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections;6using System.Collections.Generic;7using System.Diagnostics.CodeAnalysis;8using System.IO;9using System.Management.Automation;10using System.Management.Automation.Internal;11using System.Management.Automation.Provider;12using System.Runtime.CompilerServices;13using System.Runtime.InteropServices;14using System.Text;15using System.Threading.Tasks;16 17using Dbg = System.Management.Automation;18 19namespace Microsoft.PowerShell.Commands20{21 /// <summary>22 /// The content stream class for the file system provider. It implements both23 /// the IContentReader and IContentWriter interfaces.24 /// </summary>25 /// <remarks>26 /// Note, this class does no specific error handling. All errors are allowed to27 /// propagate to the caller so that they can be written to the error pipeline28 /// if necessary.29 /// </remarks>30 internal class FileSystemContentReaderWriter : IContentReader, IContentWriter31 {32 #region tracer33 34 /// <summary>35 /// An instance of the PSTraceSource class used for trace output36 /// using "FileSystemContentStream" as the category.37 /// </summary>38 [Dbg.TraceSource(39 "FileSystemContentStream",40 "The provider content reader and writer for the file system")]41 private static readonly Dbg.PSTraceSource s_tracer =42 Dbg.PSTraceSource.GetTracer("FileSystemContentStream",43 "The provider content reader and writer for the file system");44 45 #endregion tracer46 47 private readonly string _path;48 private readonly string _streamName;49 private readonly FileMode _mode;50 private readonly FileAccess _access;51 private readonly FileShare _share;52 private readonly Encoding _encoding;53 private readonly CmdletProvider _provider;54 55 private FileStream _stream;56 private StreamReader _reader;57 private StreamWriter _writer;58 private readonly bool _usingByteEncoding;59 60 private const char DefaultDelimiter = '\n';61 62 private readonly string _delimiter = $"{DefaultDelimiter}";63 private readonly int[] _offsetDictionary;64 private readonly bool _usingDelimiter;65 private readonly StringBuilder _currentLineContent;66 private bool _waitForChanges;67 private readonly bool _isRawStream;68 private long _fileOffset;69 70 private FileAttributes _oldAttributes;71 private bool _haveOldAttributes;72 73 // The reader to read file content backward74 private FileStreamBackReader _backReader;75 private bool _alreadyDetectEncoding = false;76 77 // False to add a newline to the end of the output string, true if not.78 private readonly bool _suppressNewline = false;79 80 /// <summary>81 /// Constructor for the content stream.82 /// </summary>83 /// <param name="path">84 /// The path to the file to get the content from.85 /// </param>86 /// <param name="mode">87 /// The file mode to open the file with.88 /// </param>89 /// <param name="access">90 /// The file access requested in the file.91 /// </param>92 /// <param name="share">93 /// The file share to open the file with94 /// </param>95 /// <param name="encoding">96 /// The encoding of the file to be read or written.97 /// </param>98 /// <param name="usingByteEncoding">99 /// If true, bytes will be read from the file. If false, the specified encoding100 /// will be used to read the file.101 /// </param>102 /// <param name="waitForChanges">103 /// If true, we will perform blocking reads on the file, waiting for new content to be appended104 /// </param>105 /// <param name="provider">106 /// The CmdletProvider invoking this stream107 /// </param>108 /// <param name="isRawStream">109 /// Indicates raw stream.110 /// </param>111 public FileSystemContentReaderWriter(112 string path,113 FileMode mode,114 FileAccess access,115 FileShare share,116 Encoding encoding,117 bool usingByteEncoding,118 bool waitForChanges,119 CmdletProvider provider,120 bool isRawStream)121 : this(122 path,123 streamName: null,124 mode,125 access,126 share,127 encoding,128 usingByteEncoding,129 waitForChanges,130 provider,131 isRawStream)132 {133 }134 135 /// <summary>136 /// Constructor for the content stream.137 /// </summary>138 /// <param name="path">139 /// The path to the file to get the content from.140 /// </param>141 /// <param name="streamName">142 /// The name of the Alternate Data Stream to get the content from. If null or empty, returns143 /// the file's primary content.144 /// </param>145 /// <param name="mode">146 /// The file mode to open the file with.147 /// </param>148 /// <param name="access">149 /// The file access requested in the file.150 /// </param>151 /// <param name="share">152 /// The file share to open the file with153 /// </param>154 /// <param name="encoding">155 /// The encoding of the file to be read or written.156 /// </param>157 /// <param name="usingByteEncoding">158 /// If true, bytes will be read from the file. If false, the specified encoding159 /// will be used to read the file.160 /// </param>161 /// <param name="waitForChanges">162 /// If true, we will perform blocking reads on the file, waiting for new content to be appended163 /// </param>164 /// <param name="provider">165 /// The CmdletProvider invoking this stream166 /// </param>167 /// <param name="isRawStream">168 /// Indicates raw stream.169 /// </param>170 public FileSystemContentReaderWriter(171 string path, string streamName, FileMode mode, FileAccess access, FileShare share,172 Encoding encoding, bool usingByteEncoding, bool waitForChanges, CmdletProvider provider,173 bool isRawStream)174 {175 if (string.IsNullOrEmpty(path))176 {177 throw PSTraceSource.NewArgumentNullException(nameof(path));178 }179 180 if (s_tracer.IsEnabled)181 {182 s_tracer.WriteLine("path = {0}", path);183 s_tracer.WriteLine("mode = {0}", mode);184 s_tracer.WriteLine("access = {0}", access);185 }186 187 _path = path;188 _streamName = streamName;189 _mode = mode;190 _access = access;191 _share = share;192 _encoding = encoding;193 _usingByteEncoding = usingByteEncoding;194 _waitForChanges = waitForChanges;195 _provider = provider;196 _isRawStream = isRawStream;197 198 CreateStreams(path, streamName, mode, access, share, encoding);199 }200 201 /// <summary>202 /// Constructor for the content stream.203 /// </summary>204 /// <param name="path">205 /// The path to the file to get the content from.206 /// </param>207 /// <param name="streamName">208 /// The name of the Alternate Data Stream to get the content from. If null or empty, returns209 /// the file's primary content.210 /// </param>211 /// <param name="mode">212 /// The file mode to open the file with.213 /// </param>214 /// <param name="access">215 /// The file access requested in the file.216 /// </param>217 /// <param name="share">218 /// The file share to open the file with219 /// </param>220 /// <param name="encoding">221 /// The encoding of the file to be read or written.222 /// </param>223 /// <param name="usingByteEncoding">224 /// If true, bytes will be read from the file. If false, the specified encoding225 /// will be used to read the file.226 /// </param>227 /// <param name="waitForChanges">228 /// If true, we will perform blocking reads on the file, waiting for new content to be appended229 /// </param>230 /// <param name="provider">231 /// The CmdletProvider invoking this stream232 /// </param>233 /// <param name="isRawStream">234 /// Indicates raw stream.235 /// </param>236 /// <param name="suppressNewline">237 /// False to add a newline to the end of the output string, true if not.238 /// </param>239 public FileSystemContentReaderWriter(240 string path, string streamName, FileMode mode, FileAccess access, FileShare share,241 Encoding encoding, bool usingByteEncoding, bool waitForChanges, CmdletProvider provider,242 bool isRawStream, bool suppressNewline)243 : this(path, streamName, mode, access, share, encoding, usingByteEncoding, waitForChanges, provider, isRawStream)244 {245 _suppressNewline = suppressNewline;246 }247 248 /// <summary>249 /// Constructor for the content stream.250 /// </summary>251 /// <param name="path">252 /// The path to the file to get the content from.253 /// </param>254 /// <param name="streamName">255 /// The name of the Alternate Data Stream to get the content from. If null or empty, returns256 /// the file's primary content.257 /// </param>258 /// <param name="mode">259 /// The file mode to open the file with.260 /// </param>261 /// <param name="access">262 /// The file access requested in the file.263 /// </param>264 /// <param name="share">265 /// The file share to open the file with266 /// </param>267 /// <param name="delimiter">268 /// The delimiter to use when reading strings. Each time read is called, all contents up to an including269 /// the delimiter is read.270 /// </param>271 /// <param name="encoding">272 /// The encoding of the file to be read or written.273 /// </param>274 /// <param name="waitForChanges">275 /// If true, we will perform blocking reads on the file, waiting for new content to be appended276 /// </param>277 /// <param name="provider">278 /// The CmdletProvider invoking this stream279 /// </param>280 /// <param name="isRawStream">281 /// Indicates raw stream.282 /// </param>283 public FileSystemContentReaderWriter(284 string path,285 string streamName,286 FileMode mode,287 FileAccess access,288 FileShare share,289 string delimiter,290 Encoding encoding,291 bool waitForChanges,292 CmdletProvider provider,293 bool isRawStream)294 : this(path, streamName, mode, access, share, encoding, false, waitForChanges, provider, isRawStream)295 {296 // If the delimiter is default ('\n') we'll use ReadLine() method.297 // Otherwise allocate temporary structures for ReadDelimited() method.298 if (!(delimiter.Length == 1 && delimiter[0] == DefaultDelimiter))299 {300 _delimiter = delimiter;301 _usingDelimiter = true;302 303 // We expect that we are parsing files where line lengths can be relatively long.304 const int DefaultLineLength = 256;305 _currentLineContent = new StringBuilder(DefaultLineLength);306 307 // For Boyer-Moore string search algorithm.308 // Populate the offset lookups.309 // These will tell us the maximum number of characters310 // we can read to generate another possible match (safe shift).311 // If we read more characters than this, we risk consuming312 // more of the stream than we need.313 //314 // Because an unicode character size is 2 byte we would to have use315 // very large array with 65535 size to keep this safe offsets.316 // One solution is to pack unicode character to byte.317 // The workaround is to use low byte from unicode character.318 // This allow us to use small array with size 256.319 // This workaround is the fastest and provides excellent results320 // in regular search scenarios when the file contains321 // mostly characters from the same alphabet.322 _offsetDictionary = new int[256];323 324 // If next char from file is not in search pattern safe shift is the search pattern length.325 for (var n = 0; n < _offsetDictionary.Length; n++)326 {327 _offsetDictionary[n] = _delimiter.Length;328 }329 330 // If next char from file is in search pattern we should calculate a safe shift.331 char currentChar;332 byte lowByte;333 for (var i = 0; i < _delimiter.Length; i++)334 {335 currentChar = _delimiter[i];336 lowByte = Unsafe.As<char, byte>(ref currentChar);337 _offsetDictionary[lowByte] = _delimiter.Length - i - 1;338 }339 }340 }341 342 /// <summary>343 /// Reads the specified number of characters or a lines from the file.344 /// </summary>345 /// <param name="readCount">346 /// If less than 1, then the entire file is read at once. If 1 or greater, then347 /// readCount is used to determine how many items (ie: lines, bytes, delimited tokens)348 /// to read per call.349 /// </param>350 /// <returns>351 /// An array of strings representing the character(s) or line(s) read from352 /// the file.353 /// </returns>354 public IList Read(long readCount)355 {356 if (_isRawStream && _waitForChanges)357 {358 throw PSTraceSource.NewInvalidOperationException(FileSystemProviderStrings.RawAndWaitCannotCoexist);359 }360 361 bool waitChanges = _waitForChanges;362 363 s_tracer.WriteLine("blocks requested = {0}", readCount);364 365 var blocks = new List<object>();366 bool readToEnd = (readCount <= 0);367 368 if (_alreadyDetectEncoding && _reader.BaseStream.Position == 0)369 {370 Encoding curEncoding = _reader.CurrentEncoding;371 // Close the stream, and reopen the stream to make the BOM correctly processed.372 // The reader has already detected encoding, so if we don't reopen the stream, the BOM (if there is any)373 // will be treated as a regular character.374 _stream.Dispose();375 CreateStreams(_path, null, _mode, _access, _share, curEncoding);376 _alreadyDetectEncoding = false;377 }378 379 try380 {381 for (long currentBlock = 0; (currentBlock < readCount) || (readToEnd); ++currentBlock)382 {383 if (waitChanges && _provider.Stopping)384 waitChanges = false;385 386 if (_usingByteEncoding)387 {388 if (!ReadByteEncoded(waitChanges, blocks, readBackward: false))389 break;390 }391 else392 {393 if (_usingDelimiter || _isRawStream)394 {395 if (!ReadDelimited(waitChanges, blocks, readBackward: false, _delimiter))396 break;397 }398 else399 {400 if (!ReadByLine(waitChanges, blocks, readBackward: false))401 break;402 }403 }404 }405 406 s_tracer.WriteLine("blocks read = {0}", blocks.Count);407 }408 catch (Exception e)409 {410 if ((e is IOException) ||411 (e is ArgumentException) ||412 (e is System.Security.SecurityException) ||413 (e is UnauthorizedAccessException) ||414 (e is ArgumentNullException))415 {416 // Exception contains specific message about the error occurred and so no need for errordetails.417 _provider.WriteError(new ErrorRecord(e, "GetContentReaderIOError", ErrorCategory.ReadError, _path));418 return null;419 }420 else421 throw;422 }423 424 return blocks.ToArray();425 }426 427 /// <summary>428 /// Read the content regardless of the 'waitForChanges' flag.429 /// </summary>430 /// <param name="readCount"></param>431 /// <returns></returns>432 internal IList ReadWithoutWaitingChanges(long readCount)433 {434 bool oldWaitChanges = _waitForChanges;435 _waitForChanges = false;436 try437 {438 return Read(readCount);439 }440 finally441 {442 _waitForChanges = oldWaitChanges;443 }444 }445 446 /// <summary>447 /// Move the pointer of the stream to the position where there are 'backCount' number448 /// of items (depends on what we are using: delimiter? line? byts?) to the end of the file.449 /// </summary>450 /// <param name="backCount"></param>451 internal void SeekItemsBackward(int backCount)452 {453 if (backCount < 0)454 {455 // The caller needs to guarantee that 'backCount' is greater or equals to 0456 throw PSTraceSource.NewArgumentException(nameof(backCount));457 }458 459 if (_isRawStream && _waitForChanges)460 {461 throw PSTraceSource.NewInvalidOperationException(FileSystemProviderStrings.RawAndWaitCannotCoexist);462 }463 464 s_tracer.WriteLine("blocks seek backwards = {0}", backCount);465 466 var blocks = new List<object>();467 if (_reader != null)468 {469 // Make the reader automatically detect the encoding470 Seek(0, SeekOrigin.Begin);471 _reader.Peek();472 _alreadyDetectEncoding = true;473 }474 475 Seek(0, SeekOrigin.End);476 477 if (backCount == 0)478 {479 // If backCount is 0, we should move the position to the end of the file.480 // Maybe the "waitForChanges" is true in this case, which means that we are waiting for new inputs.481 return;482 }483 484 string actualDelimiter = string.Create(485 _delimiter.Length,486 _delimiter,487 (chars, buf) =>488 {489 for (int i = 0, j = buf.Length - 1; i < chars.Length; i++, j--)490 {491 chars[i] = buf[j];492 }493 });494 495 long currentBlock = 0;496 string lastDelimiterMatch = null;497 498 try499 {500 if (_isRawStream)501 {502 // We always read to the end for the raw data.503 // If it's indicated as RawStream, we move the pointer to the504 // beginning of the file505 Seek(0, SeekOrigin.Begin);506 return;507 }508 509 for (; currentBlock < backCount; ++currentBlock)510 {511 if (_usingByteEncoding)512 {513 if (!ReadByteEncoded(waitChanges: false, blocks, readBackward: true))514 break;515 }516 else517 {518 if (_usingDelimiter)519 {520 if (!ReadDelimited(waitChanges: false, blocks, readBackward: true, actualDelimiter))521 break;522 // If the delimiter is at the end of the file, we need to read one more523 // to get to the right position. For example:524 // ua123ua456ua -- -Tail 1525 // If we read backward only once, we get 'ua', and cannot get to the right position526 // So we read one more time, get 'ua456ua', and then we can get the right position527 lastDelimiterMatch = (string)blocks[0];528 if (currentBlock == 0 && lastDelimiterMatch.Equals(actualDelimiter, StringComparison.Ordinal))529 backCount++;530 }531 else532 {533 if (!ReadByLine(waitChanges: false, blocks, readBackward: true))534 break;535 }536 }537 538 blocks.Clear();539 }540 541 // If usingByteEncoding is true, we don't create the reader and _backReader542 if (!_usingByteEncoding)543 {544 long curStreamPosition = _backReader.GetCurrentPosition();545 if (_usingDelimiter)546 {547 if (currentBlock == backCount)548 {549 Dbg.Diagnostics.Assert(lastDelimiterMatch != null, "lastDelimiterMatch should not be null when currentBlock == backCount");550 if (lastDelimiterMatch.EndsWith(actualDelimiter, StringComparison.Ordinal))551 {552 curStreamPosition += _backReader.GetByteCount(_delimiter);553 }554 }555 }556 557 Seek(curStreamPosition, SeekOrigin.Begin);558 }559 560 s_tracer.WriteLine("blocks seek position = {0}", _stream.Position);561 }562 catch (Exception e)563 {564 if ((e is IOException) ||565 (e is ArgumentException) ||566 (e is System.Security.SecurityException) ||567 (e is UnauthorizedAccessException) ||568 (e is ArgumentNullException))569 {570 // Exception contains specific message about the error occurred and so no need for errordetails.571 _provider.WriteError(new ErrorRecord(e, "GetContentReaderIOError", ErrorCategory.ReadError, _path));572 }573 else574 throw;575 }576 }577 578 private bool ReadByLine(bool waitChanges, List<object> blocks, bool readBackward)579 {580 // Reading lines as strings581 string line = readBackward ? _backReader.ReadLine() : _reader.ReadLine();582 583 if (line == null)584 {585 if (waitChanges)586 {587 // We only wait for changes when read forwards. So here we use reader, instead of 'localReader'588 do589 {590 WaitForChanges(_path, _mode, _access, _share, _reader.CurrentEncoding);591 line = _reader.ReadLine();592 }593 while ((line == null) && (!_provider.Stopping));594 }595 }596 597 if (line != null)598 {599 blocks.Add(line);600 }601 602 int peekResult = readBackward ? _backReader.Peek() : _reader.Peek();603 if (peekResult == -1)604 {605 return false;606 }607 else608 {609 return true;610 }611 }612 613 private bool ReadDelimited(bool waitChanges, List<object> blocks, bool readBackward, string actualDelimiter)614 {615 if (_isRawStream)616 {617 // when -Raw is used we want to anyway read the whole thing618 // so avoiding the while loop by reading the entire content.619 string contentRead = _reader.ReadToEnd();620 if (contentRead.Length > 0)621 {622 blocks.Add(contentRead);623 }624 625 // We already read whole file so return EOF.626 return false;627 }628 629 // Since the delimiter is a string, we're essentially630 // dealing with a "find the substring" algorithm, but with631 // the additional restriction that we cannot read past the632 // end of the delimiter. If we read past the end of the delimiter,633 // then we'll eat up bytes that we need from the filestream.634 // The solution is a modified Boyer-Moore string search algorithm.635 // This version retains the sub-linear search performance (via the636 // lookup tables).637 int numRead = 0;638 int currentOffset = actualDelimiter.Length;639 Span<char> readBuffer = stackalloc char[currentOffset];640 bool delimiterNotFound = true;641 _currentLineContent.Clear();642 643 do644 {645 // Read in the required batch of characters646 numRead = readBackward647 ? _backReader.Read(readBuffer.Slice(0, currentOffset))648 : _reader.Read(readBuffer.Slice(0, currentOffset));649 650 // If we want to wait for changes, then we'll keep on attempting to read651 // until we fill the buffer.652 if (numRead == 0)653 {654 if (waitChanges)655 {656 // But stop reading if the provider is stopping657 while ((numRead < currentOffset) && (!_provider.Stopping))658 {659 // Get the change, and try to read more characters660 // We only wait for changes when read forwards, so here we don't need to check if 'readBackward' is661 // true or false, we only use 'reader'. The member 'reader' will be updated by WaitForChanges.662 WaitForChanges(_path, _mode, _access, _share, _reader.CurrentEncoding);663 numRead += _reader.Read(readBuffer.Slice(0, currentOffset - numRead));664 }665 }666 }667 668 if (numRead > 0)669 {670 _currentLineContent.Append(readBuffer.Slice(0, numRead));671 672 // Look up the final character in our offset table.673 // If the character doesn't exist in the lookup table, then it's not in674 // our search key. That means the match must happen strictly /after/ the675 // current position. Because of that, we can feel confident reading in the676 // number of characters in the search key, without the risk of reading too many.677 var currentChar = _currentLineContent[_currentLineContent.Length - 1];678 currentOffset = _offsetDictionary[Unsafe.As<char, byte>(ref currentChar)];679 680 // We want to keep reading if delimiter not found and we haven't hit the end of file681 delimiterNotFound = true;682 683 // If the final letters matched, then we will get an offset of "0".684 // In that case, we'll either have a match (and break from the while loop,)685 // or we need to move the scan forward one position.686 if (currentOffset == 0)687 {688 currentOffset = 1;689 690 if (actualDelimiter.Length <= _currentLineContent.Length)691 {692 delimiterNotFound = false;693 int i = 0;694 int j = _currentLineContent.Length - actualDelimiter.Length;695 for (; i < actualDelimiter.Length; i++, j++)696 {697 if (actualDelimiter[i] != _currentLineContent[j])698 {699 delimiterNotFound = true;700 break;701 }702 }703 }704 }705 }706 }707 while (delimiterNotFound && (numRead != 0));708 709 // We've reached the end of file or end of line.710 if (_currentLineContent.Length > 0)711 {712 // Add the block read to the output array list, trimming a trailing delimiter, if present.713 // Note: If -Tail was specified, we get here in the course of 2 distinct passes:714 // - Once while reading backward simply to determine the appropriate *start position* for later forward reading, ignoring the content of the blocks read (in reverse).715 // - Then again during forward reading, for regular output processing; it is only then that trimming the delimiter is necessary.716 // (Trimming it during backward reading would not only be unnecessary, but could interfere with determining the correct start position.)717 blocks.Add(718 !readBackward && !delimiterNotFound719 ? _currentLineContent.ToString(0, _currentLineContent.Length - actualDelimiter.Length)720 : _currentLineContent.ToString());721 }722 723 int peekResult = readBackward ? _backReader.Peek() : _reader.Peek();724 if (peekResult != -1)725 {726 return true;727 }728 else729 {730 if (readBackward && _currentLineContent.Length > 0)731 {732 return true;733 }734 735 return false;736 }737 }738 739 private bool ReadByteEncoded(bool waitChanges, List<object> blocks, bool readBackward)740 {741 if (_isRawStream)742 {743 // if RawSteam, read all bytes and return. When RawStream is used, we dont744 // support -first, -last745 byte[] bytes = new byte[_stream.Length];746 int numBytesToRead = (int)_stream.Length;747 int numBytesRead = 0;748 while (numBytesToRead > 0)749 {750 // Read may return anything from 0 to numBytesToRead.751 int n = _stream.Read(bytes, numBytesRead, numBytesToRead);752 753 // Break when the end of the file is reached.754 if (n == 0)755 {756 break;757 }758 759 numBytesRead += n;760 numBytesToRead -= n;761 }762 763 if (numBytesRead == 0)764 {765 return false;766 }767 else768 {769 blocks.Add(bytes);770 return true;771 }772 }773 774 if (readBackward)775 {776 if (_stream.Position == 0)777 {778 return false;779 }780 781 _stream.Position--;782 blocks.Add((byte)_stream.ReadByte());783 _stream.Position--;784 return true;785 }786 787 // Reading bytes not strings788 int byteRead = _stream.ReadByte();789 790 // We've found the end of the file.791 if (byteRead == -1)792 {793 // If we want to tail the file, wait for794 // the changes795 if (waitChanges)796 {797 WaitForChanges(_path, _mode, _access, _share, Encoding.Default);798 byteRead = _stream.ReadByte();799 }800 }801 802 // Add the byte we read to the list of blocks803 if (byteRead != -1)804 {805 blocks.Add((byte)byteRead);806 return true;807 }808 else809 {810 return false;811 }812 }813 814 private void CreateStreams(string filePath, string streamName, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, Encoding fileEncoding)815 {816 // Try to mask off the ReadOnly, and Hidden attributes817 // if they've specified Force.818 if (File.Exists(filePath) && _provider.Force)819 {820 // Store the old attributes so that we can recover them821 // in the Close();822 _oldAttributes = File.GetAttributes(filePath);823 _haveOldAttributes = true;824 825 // Clear the hidden attribute, and if we're writing, also clear readonly.826 var attributesToClear = FileAttributes.Hidden;827 if ((fileAccess & (FileAccess.Write)) != 0)828 {829 attributesToClear |= FileAttributes.ReadOnly;830 }831 832 File.SetAttributes(_path, (File.GetAttributes(filePath) & ~attributesToClear));833 }834 835 // If we want to write to the stream, attempt to open it for reading as well836 // so that we can determine the file encoding as we append to it837 FileAccess requestedAccess = fileAccess;838 if ((fileAccess & (FileAccess.Write)) != 0)839 {840 fileAccess = FileAccess.ReadWrite;841 }842 843 try844 {845#if !UNIX846 if (!string.IsNullOrEmpty(streamName))847 {848 _stream = AlternateDataStreamUtilities.CreateFileStream(filePath, streamName, fileMode, fileAccess, fileShare);849 }850 else851#endif852 {853 _stream = new FileStream(filePath, fileMode, fileAccess, fileShare);854 }855 }856 catch (IOException)857 {858#if !UNIX859 if (!string.IsNullOrEmpty(streamName))860 {861 _stream = AlternateDataStreamUtilities.CreateFileStream(filePath, streamName, fileMode, requestedAccess, fileShare);862 }863 else864#endif865 {866 _stream = new FileStream(filePath, fileMode, requestedAccess, fileShare);867 }868 }869 870 if (!_usingByteEncoding)871 {872 // Open the reader stream873 if ((fileAccess & (FileAccess.Read)) != 0)874 {875 _reader = new StreamReader(_stream, fileEncoding);876 _backReader = new FileStreamBackReader(_stream, fileEncoding);877 }878 879 // Open the writer stream880 if ((fileAccess & (FileAccess.Write)) != 0)881 {882 // Ensure we are using the proper encoding883 if ((_reader != null) &&884 ((fileAccess & (FileAccess.Read)) != 0))885 {886 _reader.Peek();887 fileEncoding = _reader.CurrentEncoding;888 }889 890 _writer = new StreamWriter(_stream, fileEncoding);891 }892 }893 }894 895 /// <summary>896 /// Waits for changes to the specified file. To do this, it closes the file897 /// and then monitors for changes. Once a change appears, it reopens the streams898 /// and seeks to the last read position.899 /// </summary>900 /// <param name="filePath">The path of the file to read / monitor.</param>901 /// <param name="fileMode">The FileMode of the file (ie: Open / Append).</param>902 /// <param name="fileAccess">The access properties of the file (ie: Read / Write).</param>903 /// <param name="fileShare">The sharing properties of the file (ie: Read / ReadWrite).</param>904 /// <param name="fileEncoding">The encoding of the file.</param>905 private void WaitForChanges(string filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, Encoding fileEncoding)906 {907 // Close the old stream, and store our current position.908 if (_stream != null)909 {910 _fileOffset = _stream.Position;911 _stream.Dispose();912 }913 914 // Watch for changes, as a blocking call.915 FileInfo watchFile = new FileInfo(filePath);916 long originalLength = watchFile.Length;917 918 using (FileSystemWatcher watcher = new FileSystemWatcher(watchFile.DirectoryName, watchFile.Name))919 {920 ErrorEventArgs errorEventArgs = null;921 var tcs = new TaskCompletionSource<FileSystemEventArgs>();922 FileSystemEventHandler onChangedHandler = (object source, FileSystemEventArgs e) => tcs.TrySetResult(e);923 RenamedEventHandler onRenamedHandler = (object source, RenamedEventArgs e) => tcs.TrySetResult(e);924 ErrorEventHandler onErrorHandler = (object source, ErrorEventArgs e) =>925 {926 errorEventArgs = e;927 tcs.TrySetResult(new FileSystemEventArgs(WatcherChangeTypes.All, watchFile.DirectoryName, watchFile.Name));928 };929 930 // With WaitForChanged, we registered for all change types, so we do the same here.931 watcher.Changed += onChangedHandler;932 watcher.Created += onChangedHandler;933 watcher.Deleted += onChangedHandler;934 watcher.Renamed += onRenamedHandler;935 watcher.Error += onErrorHandler;936 937 try938 {939 watcher.EnableRaisingEvents = true;940 941 while (!_provider.Stopping)942 {943 bool isTaskCompleted = tcs.Task.Wait(500);944 945 if (errorEventArgs != null)946 {947 throw errorEventArgs.GetException();948 }949 950 if (isTaskCompleted)951 break;952 953 // If a process is still writing, .NET doesn't generate a change notification.954 // So do a simple comparison on file size955 watchFile.Refresh();956 if (originalLength != watchFile.Length)957 {958 break;959 }960 }961 }962 finally963 {964 // Done here to guarantee that the handlers are removed prior965 // to the call to ManualResetEvent's Dispose() method.966 watcher.EnableRaisingEvents = false;967 watcher.Changed -= onChangedHandler;968 watcher.Created -= onChangedHandler;969 watcher.Deleted -= onChangedHandler;970 watcher.Renamed -= onRenamedHandler;971 watcher.Error -= onErrorHandler;972 }973 }974 975 // Let the change complete.976 // This is a fairly arbitrary number. Without it, though,977 // some of the filesystem streams cannot be reopened.978 System.Threading.Thread.Sleep(100);979 980 // Reopen the streams.981 CreateStreams(filePath, null, fileMode, fileAccess, fileShare, fileEncoding);982 983 // If the file has been shortened, restart from zero.984 // That will let us catch log roll-overs.985 if (_fileOffset > _stream.Length)986 _fileOffset = 0;987 988 // Seek to the place we last left off.989 _stream.Seek(_fileOffset, SeekOrigin.Begin);990 _reader?.DiscardBufferedData();991 _backReader?.DiscardBufferedData();992 }993 994 /// <summary>995 /// Moves the current stream position in the file.996 /// </summary>997 /// <param name="offset">998 /// The offset from the origin to move the position to.999 /// </param>1000 /// <param name="origin">1001 /// The origin from which the offset is calculated.1002 /// </param>1003 public void Seek(long offset, SeekOrigin origin)1004 {1005 _writer?.Flush();1006 1007 _stream.Seek(offset, origin);1008 1009 _writer?.Flush();1010 _reader?.DiscardBufferedData();1011 _backReader?.DiscardBufferedData();1012 }1013 1014 /// <summary>1015 /// Closes the file.1016 /// </summary>1017 public void Close()1018 {1019 bool streamClosed = false;1020 1021 if (_writer != null)1022 {1023 try1024 {1025 _writer.Flush();1026 _writer.Dispose();1027 }1028 finally1029 {1030 streamClosed = true;1031 }1032 }1033 1034 if (_reader != null)1035 {1036 _reader.Dispose();1037 streamClosed = true;1038 }1039 1040 if (_backReader != null)1041 {1042 _backReader.Dispose();1043 streamClosed = true;1044 }1045 1046 if (!streamClosed)1047 {1048 _stream.Flush();1049 _stream.Dispose();1050 }1051 1052 // Reset the attributes1053 if (_haveOldAttributes && _provider.Force)1054 {1055 File.SetAttributes(_path, _oldAttributes);1056 }1057 }1058 1059 /// <summary>1060 /// Writes the specified object to the file.1061 /// </summary>1062 /// <param name="content">1063 /// The objects to write to the file1064 /// </param>1065 /// <returns>1066 /// The objects written to the file.1067 /// </returns>1068 public IList Write(IList content)1069 {1070 foreach (object line in content)1071 {1072 object[] contentArray = line as object[];1073 if (contentArray != null)1074 {1075 foreach (object obj in contentArray)1076 {1077 WriteObject(obj);1078 }1079 }1080 else1081 {1082 WriteObject(line);1083 }1084 }1085 1086 return content;1087 }1088 1089 private void WriteObject(object content)1090 {1091 if (content == null)1092 {1093 return;1094 }1095 1096 if (_usingByteEncoding)1097 {1098 try1099 {1100 byte byteToWrite = (byte)content;1101 1102 _stream.WriteByte(byteToWrite);1103 }1104 catch (InvalidCastException)1105 {1106 throw PSTraceSource.NewArgumentException(nameof(content), FileSystemProviderStrings.ByteEncodingError);1107 }1108 }1109 else1110 {1111 if (_suppressNewline)1112 {1113 _writer.Write(content.ToString());1114 }1115 else1116 {1117 _writer.WriteLine(content.ToString());1118 }1119 }1120 }1121 1122 /// <summary>1123 /// Closes the file stream.1124 /// </summary>1125 public void Dispose()1126 {1127 Dispose(true);1128 GC.SuppressFinalize(this);1129 }1130 1131 internal void Dispose(bool isDisposing)1132 {1133 if (isDisposing)1134 {1135 _stream?.Dispose();1136 _reader?.Dispose();1137 _backReader?.Dispose();1138 _writer?.Dispose();1139 }1140 }1141 }1142 1143 internal sealed class FileStreamBackReader : StreamReader1144 {1145 internal FileStreamBackReader(FileStream fileStream, Encoding encoding)1146 : base(fileStream, encoding)1147 {1148 _stream = fileStream;1149 if (_stream.Length > 0)1150 {1151 long curPosition = _stream.Position;1152 _stream.Seek(0, SeekOrigin.Begin);1153 base.Peek();1154 _stream.Position = curPosition;1155 _currentEncoding = base.CurrentEncoding;1156 _currentPosition = _stream.Position;1157 1158 // Get the oem encoding and system current ANSI code page1159 _oemEncoding = EncodingConversion.Convert(null, EncodingConversion.OEM);1160 _defaultAnsiEncoding = EncodingConversion.Convert(null, EncodingConversion.Default);1161 }1162 }1163 1164 private readonly FileStream _stream;1165 private readonly Encoding _currentEncoding;1166 private readonly Encoding _oemEncoding;1167 private readonly Encoding _defaultAnsiEncoding;1168 1169 private const int BuffSize = 4096;1170 1171 private readonly byte[] _byteBuff = new byte[BuffSize];1172 private readonly char[] _charBuff = new char[BuffSize];1173 private int _byteCount = 0;1174 private int _charCount = 0;1175 private long _currentPosition = 0;1176 private const byte BothTopBitsSet = 0xC0;1177 private const byte TopBitUnset = 0x80;1178 1179 /// <summary>1180 /// We don't support this method because it is not used by the ReadBackward method in FileStreamContentReaderWriter.1181 /// </summary>1182 /// <param name="buffer"></param>1183 /// <param name="index"></param>1184 /// <param name="count"></param>1185 /// <returns></returns>1186 public override int ReadBlock(char[] buffer, int index, int count)1187 {1188 // This method is not supposed to be used1189 throw PSTraceSource.NewNotSupportedException();1190 }1191 1192 /// <summary>1193 /// We don't support this method because it is not used by the ReadBackward method in FileStreamContentReaderWriter.1194 /// </summary>1195 /// <returns></returns>1196 public override string ReadToEnd()1197 {1198 // This method is not supposed to be used1199 throw PSTraceSource.NewNotSupportedException();1200 }