TarArchive.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  1. using System;
  2. using System.IO;
  3. using System.Text;
  4. namespace ICSharpCode.SharpZipLib.Tar
  5. {
  6. /// <summary>
  7. /// Used to advise clients of 'events' while processing archives
  8. /// </summary>
  9. public delegate void ProgressMessageHandler(TarArchive archive, TarEntry entry, string message);
  10. /// <summary>
  11. /// The TarArchive class implements the concept of a
  12. /// 'Tape Archive'. A tar archive is a series of entries, each of
  13. /// which represents a file system object. Each entry in
  14. /// the archive consists of a header block followed by 0 or more data blocks.
  15. /// Directory entries consist only of the header block, and are followed by entries
  16. /// for the directory's contents. File entries consist of a
  17. /// header followed by the number of blocks needed to
  18. /// contain the file's contents. All entries are written on
  19. /// block boundaries. Blocks are 512 bytes long.
  20. ///
  21. /// TarArchives are instantiated in either read or write mode,
  22. /// based upon whether they are instantiated with an InputStream
  23. /// or an OutputStream. Once instantiated TarArchives read/write
  24. /// mode can not be changed.
  25. ///
  26. /// There is currently no support for random access to tar archives.
  27. /// However, it seems that subclassing TarArchive, and using the
  28. /// TarBuffer.CurrentRecord and TarBuffer.CurrentBlock
  29. /// properties, this would be rather trivial.
  30. /// </summary>
  31. public class TarArchive : IDisposable
  32. {
  33. /// <summary>
  34. /// Client hook allowing detailed information to be reported during processing
  35. /// </summary>
  36. public event ProgressMessageHandler ProgressMessageEvent;
  37. /// <summary>
  38. /// Raises the ProgressMessage event
  39. /// </summary>
  40. /// <param name="entry">The <see cref="TarEntry">TarEntry</see> for this event</param>
  41. /// <param name="message">message for this event. Null is no message</param>
  42. protected virtual void OnProgressMessageEvent(TarEntry entry, string message)
  43. {
  44. ProgressMessageHandler handler = ProgressMessageEvent;
  45. if (handler != null) {
  46. handler(this, entry, message);
  47. }
  48. }
  49. #region Constructors
  50. /// <summary>
  51. /// Constructor for a default <see cref="TarArchive"/>.
  52. /// </summary>
  53. protected TarArchive()
  54. {
  55. }
  56. /// <summary>
  57. /// Initalise a TarArchive for input.
  58. /// </summary>
  59. /// <param name="stream">The <see cref="TarInputStream"/> to use for input.</param>
  60. protected TarArchive(TarInputStream stream)
  61. {
  62. if (stream == null) {
  63. throw new ArgumentNullException("nameof(stream)");
  64. }
  65. tarIn = stream;
  66. }
  67. /// <summary>
  68. /// Initialise a TarArchive for output.
  69. /// </summary>
  70. /// <param name="stream">The <see cref="TarOutputStream"/> to use for output.</param>
  71. protected TarArchive(TarOutputStream stream)
  72. {
  73. if (stream == null) {
  74. throw new ArgumentNullException("nameof(stream)");
  75. }
  76. tarOut = stream;
  77. }
  78. #endregion
  79. #region Static factory methods
  80. /// <summary>
  81. /// The InputStream based constructors create a TarArchive for the
  82. /// purposes of extracting or listing a tar archive. Thus, use
  83. /// these constructors when you wish to extract files from or list
  84. /// the contents of an existing tar archive.
  85. /// </summary>
  86. /// <param name="inputStream">The stream to retrieve archive data from.</param>
  87. /// <returns>Returns a new <see cref="TarArchive"/> suitable for reading from.</returns>
  88. public static TarArchive CreateInputTarArchive(Stream inputStream)
  89. {
  90. if (inputStream == null) {
  91. throw new ArgumentNullException("nameof(inputStream)");
  92. }
  93. var tarStream = inputStream as TarInputStream;
  94. TarArchive result;
  95. if (tarStream != null) {
  96. result = new TarArchive(tarStream);
  97. } else {
  98. result = CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor);
  99. }
  100. return result;
  101. }
  102. /// <summary>
  103. /// Create TarArchive for reading setting block factor
  104. /// </summary>
  105. /// <param name="inputStream">A stream containing the tar archive contents</param>
  106. /// <param name="blockFactor">The blocking factor to apply</param>
  107. /// <returns>Returns a <see cref="TarArchive"/> suitable for reading.</returns>
  108. public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor)
  109. {
  110. if (inputStream == null) {
  111. throw new ArgumentNullException("nameof(inputStream)");
  112. }
  113. if (inputStream is TarInputStream) {
  114. throw new ArgumentException("TarInputStream not valid");
  115. }
  116. return new TarArchive(new TarInputStream(inputStream, blockFactor));
  117. }
  118. /// <summary>
  119. /// Create a TarArchive for writing to, using the default blocking factor
  120. /// </summary>
  121. /// <param name="outputStream">The <see cref="Stream"/> to write to</param>
  122. /// <returns>Returns a <see cref="TarArchive"/> suitable for writing.</returns>
  123. public static TarArchive CreateOutputTarArchive(Stream outputStream)
  124. {
  125. if (outputStream == null) {
  126. throw new ArgumentNullException("nameof(outputStream)");
  127. }
  128. var tarStream = outputStream as TarOutputStream;
  129. TarArchive result;
  130. if (tarStream != null) {
  131. result = new TarArchive(tarStream);
  132. } else {
  133. result = CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor);
  134. }
  135. return result;
  136. }
  137. /// <summary>
  138. /// Create a <see cref="TarArchive">tar archive</see> for writing.
  139. /// </summary>
  140. /// <param name="outputStream">The stream to write to</param>
  141. /// <param name="blockFactor">The blocking factor to use for buffering.</param>
  142. /// <returns>Returns a <see cref="TarArchive"/> suitable for writing.</returns>
  143. public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor)
  144. {
  145. if (outputStream == null) {
  146. throw new ArgumentNullException("nameof(outputStream)");
  147. }
  148. if (outputStream is TarOutputStream) {
  149. throw new ArgumentException("TarOutputStream is not valid");
  150. }
  151. return new TarArchive(new TarOutputStream(outputStream, blockFactor));
  152. }
  153. #endregion
  154. /// <summary>
  155. /// Set the flag that determines whether existing files are
  156. /// kept, or overwritten during extraction.
  157. /// </summary>
  158. /// <param name="keepExistingFiles">
  159. /// If true, do not overwrite existing files.
  160. /// </param>
  161. public void SetKeepOldFiles(bool keepExistingFiles)
  162. {
  163. if (isDisposed) {
  164. throw new ObjectDisposedException("TarArchive");
  165. }
  166. keepOldFiles = keepExistingFiles;
  167. }
  168. /// <summary>
  169. /// Get/set the ascii file translation flag. If ascii file translation
  170. /// is true, then the file is checked to see if it a binary file or not.
  171. /// If the flag is true and the test indicates it is ascii text
  172. /// file, it will be translated. The translation converts the local
  173. /// operating system's concept of line ends into the UNIX line end,
  174. /// '\n', which is the defacto standard for a TAR archive. This makes
  175. /// text files compatible with UNIX.
  176. /// </summary>
  177. public bool AsciiTranslate {
  178. get {
  179. if (isDisposed) {
  180. throw new ObjectDisposedException("TarArchive");
  181. }
  182. return asciiTranslate;
  183. }
  184. set {
  185. if (isDisposed) {
  186. throw new ObjectDisposedException("TarArchive");
  187. }
  188. asciiTranslate = value;
  189. }
  190. }
  191. /// <summary>
  192. /// Set the ascii file translation flag.
  193. /// </summary>
  194. /// <param name= "translateAsciiFiles">
  195. /// If true, translate ascii text files.
  196. /// </param>
  197. [Obsolete("Use the AsciiTranslate property")]
  198. public void SetAsciiTranslation(bool translateAsciiFiles)
  199. {
  200. if (isDisposed) {
  201. throw new ObjectDisposedException("TarArchive");
  202. }
  203. asciiTranslate = translateAsciiFiles;
  204. }
  205. /// <summary>
  206. /// PathPrefix is added to entry names as they are written if the value is not null.
  207. /// A slash character is appended after PathPrefix
  208. /// </summary>
  209. public string PathPrefix {
  210. get {
  211. if (isDisposed) {
  212. throw new ObjectDisposedException("TarArchive");
  213. }
  214. return pathPrefix;
  215. }
  216. set {
  217. if (isDisposed) {
  218. throw new ObjectDisposedException("TarArchive");
  219. }
  220. pathPrefix = value;
  221. }
  222. }
  223. /// <summary>
  224. /// RootPath is removed from entry names if it is found at the
  225. /// beginning of the name.
  226. /// </summary>
  227. public string RootPath {
  228. get {
  229. if (isDisposed) {
  230. throw new ObjectDisposedException("TarArchive");
  231. }
  232. return rootPath;
  233. }
  234. set {
  235. if (isDisposed) {
  236. throw new ObjectDisposedException("TarArchive");
  237. }
  238. // Convert to forward slashes for matching. Trim trailing / for correct final path
  239. rootPath = value.Replace('\\', '/').TrimEnd('/');
  240. }
  241. }
  242. /// <summary>
  243. /// Set user and group information that will be used to fill in the
  244. /// tar archive's entry headers. This information is based on that available
  245. /// for the linux operating system, which is not always available on other
  246. /// operating systems. TarArchive allows the programmer to specify values
  247. /// to be used in their place.
  248. /// <see cref="ApplyUserInfoOverrides"/> is set to true by this call.
  249. /// </summary>
  250. /// <param name="userId">
  251. /// The user id to use in the headers.
  252. /// </param>
  253. /// <param name="userName">
  254. /// The user name to use in the headers.
  255. /// </param>
  256. /// <param name="groupId">
  257. /// The group id to use in the headers.
  258. /// </param>
  259. /// <param name="groupName">
  260. /// The group name to use in the headers.
  261. /// </param>
  262. public void SetUserInfo(int userId, string userName, int groupId, string groupName)
  263. {
  264. if (isDisposed) {
  265. throw new ObjectDisposedException("TarArchive");
  266. }
  267. this.userId = userId;
  268. this.userName = userName;
  269. this.groupId = groupId;
  270. this.groupName = groupName;
  271. applyUserInfoOverrides = true;
  272. }
  273. /// <summary>
  274. /// Get or set a value indicating if overrides defined by <see cref="SetUserInfo">SetUserInfo</see> should be applied.
  275. /// </summary>
  276. /// <remarks>If overrides are not applied then the values as set in each header will be used.</remarks>
  277. public bool ApplyUserInfoOverrides {
  278. get {
  279. if (isDisposed) {
  280. throw new ObjectDisposedException("TarArchive");
  281. }
  282. return applyUserInfoOverrides;
  283. }
  284. set {
  285. if (isDisposed) {
  286. throw new ObjectDisposedException("TarArchive");
  287. }
  288. applyUserInfoOverrides = value;
  289. }
  290. }
  291. /// <summary>
  292. /// Get the archive user id.
  293. /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
  294. /// on how to allow setting values on a per entry basis.
  295. /// </summary>
  296. /// <returns>
  297. /// The current user id.
  298. /// </returns>
  299. public int UserId {
  300. get {
  301. if (isDisposed) {
  302. throw new ObjectDisposedException("TarArchive");
  303. }
  304. return userId;
  305. }
  306. }
  307. /// <summary>
  308. /// Get the archive user name.
  309. /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
  310. /// on how to allow setting values on a per entry basis.
  311. /// </summary>
  312. /// <returns>
  313. /// The current user name.
  314. /// </returns>
  315. public string UserName {
  316. get {
  317. if (isDisposed) {
  318. throw new ObjectDisposedException("TarArchive");
  319. }
  320. return userName;
  321. }
  322. }
  323. /// <summary>
  324. /// Get the archive group id.
  325. /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
  326. /// on how to allow setting values on a per entry basis.
  327. /// </summary>
  328. /// <returns>
  329. /// The current group id.
  330. /// </returns>
  331. public int GroupId {
  332. get {
  333. if (isDisposed) {
  334. throw new ObjectDisposedException("TarArchive");
  335. }
  336. return groupId;
  337. }
  338. }
  339. /// <summary>
  340. /// Get the archive group name.
  341. /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
  342. /// on how to allow setting values on a per entry basis.
  343. /// </summary>
  344. /// <returns>
  345. /// The current group name.
  346. /// </returns>
  347. public string GroupName {
  348. get {
  349. if (isDisposed) {
  350. throw new ObjectDisposedException("TarArchive");
  351. }
  352. return groupName;
  353. }
  354. }
  355. /// <summary>
  356. /// Get the archive's record size. Tar archives are composed of
  357. /// a series of RECORDS each containing a number of BLOCKS.
  358. /// This allowed tar archives to match the IO characteristics of
  359. /// the physical device being used. Archives are expected
  360. /// to be properly "blocked".
  361. /// </summary>
  362. /// <returns>
  363. /// The record size this archive is using.
  364. /// </returns>
  365. public int RecordSize {
  366. get {
  367. if (isDisposed) {
  368. throw new ObjectDisposedException("TarArchive");
  369. }
  370. if (tarIn != null) {
  371. return tarIn.RecordSize;
  372. } else if (tarOut != null) {
  373. return tarOut.RecordSize;
  374. }
  375. return TarBuffer.DefaultRecordSize;
  376. }
  377. }
  378. /// <summary>
  379. /// Sets the IsStreamOwner property on the underlying stream.
  380. /// Set this to false to prevent the Close of the TarArchive from closing the stream.
  381. /// </summary>
  382. public bool IsStreamOwner {
  383. set {
  384. if (tarIn != null) {
  385. tarIn.IsStreamOwner = value;
  386. } else {
  387. tarOut.IsStreamOwner = value;
  388. }
  389. }
  390. }
  391. /// <summary>
  392. /// Close the archive.
  393. /// </summary>
  394. [Obsolete("Use Close instead")]
  395. public void CloseArchive()
  396. {
  397. Close();
  398. }
  399. /// <summary>
  400. /// Perform the "list" command for the archive contents.
  401. ///
  402. /// NOTE That this method uses the <see cref="ProgressMessageEvent"> progress event</see> to actually list
  403. /// the contents. If the progress display event is not set, nothing will be listed!
  404. /// </summary>
  405. public void ListContents()
  406. {
  407. if (isDisposed) {
  408. throw new ObjectDisposedException("TarArchive");
  409. }
  410. while (true) {
  411. TarEntry entry = tarIn.GetNextEntry();
  412. if (entry == null) {
  413. break;
  414. }
  415. OnProgressMessageEvent(entry, null);
  416. }
  417. }
  418. /// <summary>
  419. /// Perform the "extract" command and extract the contents of the archive.
  420. /// </summary>
  421. /// <param name="destinationDirectory">
  422. /// The destination directory into which to extract.
  423. /// </param>
  424. public void ExtractContents(string destinationDirectory)
  425. {
  426. if (isDisposed) {
  427. throw new ObjectDisposedException("TarArchive");
  428. }
  429. while (true) {
  430. TarEntry entry = tarIn.GetNextEntry();
  431. if (entry == null) {
  432. break;
  433. }
  434. if (entry.TarHeader.TypeFlag == TarHeader.LF_LINK || entry.TarHeader.TypeFlag == TarHeader.LF_SYMLINK)
  435. continue;
  436. ExtractEntry(destinationDirectory, entry);
  437. }
  438. }
  439. /// <summary>
  440. /// Extract an entry from the archive. This method assumes that the
  441. /// tarIn stream has been properly set with a call to GetNextEntry().
  442. /// </summary>
  443. /// <param name="destDir">
  444. /// The destination directory into which to extract.
  445. /// </param>
  446. /// <param name="entry">
  447. /// The TarEntry returned by tarIn.GetNextEntry().
  448. /// </param>
  449. void ExtractEntry(string destDir, TarEntry entry)
  450. {
  451. OnProgressMessageEvent(entry, null);
  452. string name = entry.Name;
  453. if (Path.IsPathRooted(name)) {
  454. // NOTE:
  455. // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt
  456. name = name.Substring(Path.GetPathRoot(name).Length);
  457. }
  458. name = name.Replace('/', Path.DirectorySeparatorChar);
  459. string destFile = Path.Combine(destDir, name);
  460. if (entry.IsDirectory) {
  461. EnsureDirectoryExists(destFile);
  462. } else {
  463. string parentDirectory = Path.GetDirectoryName(destFile);
  464. EnsureDirectoryExists(parentDirectory);
  465. bool process = true;
  466. var fileInfo = new FileInfo(destFile);
  467. if (fileInfo.Exists) {
  468. if (keepOldFiles) {
  469. OnProgressMessageEvent(entry, "Destination file already exists");
  470. process = false;
  471. } else if ((fileInfo.Attributes & FileAttributes.ReadOnly) != 0) {
  472. OnProgressMessageEvent(entry, "Destination file already exists, and is read-only");
  473. process = false;
  474. }
  475. }
  476. if (process) {
  477. bool asciiTrans = false;
  478. Stream outputStream = File.Create(destFile);
  479. if (this.asciiTranslate) {
  480. asciiTrans = !IsBinary(destFile);
  481. }
  482. StreamWriter outw = null;
  483. if (asciiTrans) {
  484. outw = new StreamWriter(outputStream);
  485. }
  486. byte[] rdbuf = new byte[32 * 1024];
  487. while (true) {
  488. int numRead = tarIn.Read(rdbuf, 0, rdbuf.Length);
  489. if (numRead <= 0) {
  490. break;
  491. }
  492. if (asciiTrans) {
  493. for (int off = 0, b = 0; b < numRead; ++b) {
  494. if (rdbuf[b] == 10) {
  495. string s = Encoding.ASCII.GetString(rdbuf, off, (b - off));
  496. outw.WriteLine(s);
  497. off = b + 1;
  498. }
  499. }
  500. } else {
  501. outputStream.Write(rdbuf, 0, numRead);
  502. }
  503. }
  504. if (asciiTrans) {
  505. outw.Dispose();
  506. } else {
  507. outputStream.Dispose();
  508. }
  509. }
  510. }
  511. }
  512. /// <summary>
  513. /// Write an entry to the archive. This method will call the putNextEntry
  514. /// and then write the contents of the entry, and finally call closeEntry()
  515. /// for entries that are files. For directories, it will call putNextEntry(),
  516. /// and then, if the recurse flag is true, process each entry that is a
  517. /// child of the directory.
  518. /// </summary>
  519. /// <param name="sourceEntry">
  520. /// The TarEntry representing the entry to write to the archive.
  521. /// </param>
  522. /// <param name="recurse">
  523. /// If true, process the children of directory entries.
  524. /// </param>
  525. public void WriteEntry(TarEntry sourceEntry, bool recurse)
  526. {
  527. if (sourceEntry == null) {
  528. throw new ArgumentNullException("nameof(sourceEntry)");
  529. }
  530. if (isDisposed) {
  531. throw new ObjectDisposedException("TarArchive");
  532. }
  533. try {
  534. if (recurse) {
  535. TarHeader.SetValueDefaults(sourceEntry.UserId, sourceEntry.UserName,
  536. sourceEntry.GroupId, sourceEntry.GroupName);
  537. }
  538. WriteEntryCore(sourceEntry, recurse);
  539. } finally {
  540. if (recurse) {
  541. TarHeader.RestoreSetValues();
  542. }
  543. }
  544. }
  545. /// <summary>
  546. /// Write an entry to the archive. This method will call the putNextEntry
  547. /// and then write the contents of the entry, and finally call closeEntry()
  548. /// for entries that are files. For directories, it will call putNextEntry(),
  549. /// and then, if the recurse flag is true, process each entry that is a
  550. /// child of the directory.
  551. /// </summary>
  552. /// <param name="sourceEntry">
  553. /// The TarEntry representing the entry to write to the archive.
  554. /// </param>
  555. /// <param name="recurse">
  556. /// If true, process the children of directory entries.
  557. /// </param>
  558. void WriteEntryCore(TarEntry sourceEntry, bool recurse)
  559. {
  560. string tempFileName = null;
  561. string entryFilename = sourceEntry.File;
  562. var entry = (TarEntry)sourceEntry.Clone();
  563. if (applyUserInfoOverrides) {
  564. entry.GroupId = groupId;
  565. entry.GroupName = groupName;
  566. entry.UserId = userId;
  567. entry.UserName = userName;
  568. }
  569. OnProgressMessageEvent(entry, null);
  570. if (asciiTranslate && !entry.IsDirectory) {
  571. if (!IsBinary(entryFilename)) {
  572. tempFileName = Path.GetTempFileName();
  573. using (StreamReader inStream = File.OpenText(entryFilename)) {
  574. using (Stream outStream = File.Create(tempFileName)) {
  575. while (true) {
  576. string line = inStream.ReadLine();
  577. if (line == null) {
  578. break;
  579. }
  580. byte[] data = Encoding.ASCII.GetBytes(line);
  581. outStream.Write(data, 0, data.Length);
  582. outStream.WriteByte((byte)'\n');
  583. }
  584. outStream.Flush();
  585. }
  586. }
  587. entry.Size = new FileInfo(tempFileName).Length;
  588. entryFilename = tempFileName;
  589. }
  590. }
  591. string newName = null;
  592. if (rootPath != null) {
  593. if (entry.Name.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase)) {
  594. newName = entry.Name.Substring(rootPath.Length + 1);
  595. }
  596. }
  597. if (pathPrefix != null) {
  598. newName = (newName == null) ? pathPrefix + "/" + entry.Name : pathPrefix + "/" + newName;
  599. }
  600. if (newName != null) {
  601. entry.Name = newName;
  602. }
  603. tarOut.PutNextEntry(entry);
  604. if (entry.IsDirectory) {
  605. if (recurse) {
  606. TarEntry[] list = entry.GetDirectoryEntries();
  607. for (int i = 0; i < list.Length; ++i) {
  608. WriteEntryCore(list[i], recurse);
  609. }
  610. }
  611. } else {
  612. using (Stream inputStream = File.OpenRead(entryFilename)) {
  613. byte[] localBuffer = new byte[32 * 1024];
  614. while (true) {
  615. int numRead = inputStream.Read(localBuffer, 0, localBuffer.Length);
  616. if (numRead <= 0) {
  617. break;
  618. }
  619. tarOut.Write(localBuffer, 0, numRead);
  620. }
  621. }
  622. if (!string.IsNullOrEmpty(tempFileName)) {
  623. File.Delete(tempFileName);
  624. }
  625. tarOut.CloseEntry();
  626. }
  627. }
  628. /// <summary>
  629. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  630. /// </summary>
  631. public void Dispose()
  632. {
  633. Dispose(true);
  634. GC.SuppressFinalize(this);
  635. }
  636. /// <summary>
  637. /// Releases the unmanaged resources used by the FileStream and optionally releases the managed resources.
  638. /// </summary>
  639. /// <param name="disposing">true to release both managed and unmanaged resources;
  640. /// false to release only unmanaged resources.</param>
  641. protected virtual void Dispose(bool disposing)
  642. {
  643. if (!isDisposed) {
  644. isDisposed = true;
  645. if (disposing) {
  646. if (tarOut != null) {
  647. tarOut.Flush();
  648. tarOut.Dispose();
  649. }
  650. if (tarIn != null) {
  651. tarIn.Dispose();
  652. }
  653. }
  654. }
  655. }
  656. /// <summary>
  657. /// Closes the archive and releases any associated resources.
  658. /// </summary>
  659. public virtual void Close()
  660. {
  661. Dispose(true);
  662. }
  663. /// <summary>
  664. /// Ensures that resources are freed and other cleanup operations are performed
  665. /// when the garbage collector reclaims the <see cref="TarArchive"/>.
  666. /// </summary>
  667. ~TarArchive()
  668. {
  669. Dispose(false);
  670. }
  671. static void EnsureDirectoryExists(string directoryName)
  672. {
  673. if (!Directory.Exists(directoryName)) {
  674. try {
  675. Directory.CreateDirectory(directoryName);
  676. } catch (Exception e) {
  677. throw new TarException("Exception creating directory '" + directoryName + "', " + e.Message, e);
  678. }
  679. }
  680. }
  681. // TODO: TarArchive - Is there a better way to test for a text file?
  682. // It no longer reads entire files into memory but is still a weak test!
  683. // This assumes that byte values 0-7, 14-31 or 255 are binary
  684. // and that all non text files contain one of these values
  685. static bool IsBinary(string filename)
  686. {
  687. using (FileStream fs = File.OpenRead(filename)) {
  688. int sampleSize = Math.Min(4096, (int)fs.Length);
  689. byte[] content = new byte[sampleSize];
  690. int bytesRead = fs.Read(content, 0, sampleSize);
  691. for (int i = 0; i < bytesRead; ++i) {
  692. byte b = content[i];
  693. if ((b < 8) || ((b > 13) && (b < 32)) || (b == 255)) {
  694. return true;
  695. }
  696. }
  697. }
  698. return false;
  699. }
  700. #region Instance Fields
  701. bool keepOldFiles;
  702. bool asciiTranslate;
  703. int userId;
  704. string userName = string.Empty;
  705. int groupId;
  706. string groupName = string.Empty;
  707. string rootPath;
  708. string pathPrefix;
  709. bool applyUserInfoOverrides;
  710. TarInputStream tarIn;
  711. TarOutputStream tarOut;
  712. bool isDisposed;
  713. #endregion
  714. }
  715. }