You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

894 lines
25 KiB

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