/// <summary> /// Put an entry on the output stream. This writes the entry's /// header and positions the output stream for writing /// the contents of the entry. Once this method is called, the /// stream is ready for calls to write() to write the entry's /// contents. Once the contents are written, closeEntry() /// <B>MUST</B> be called to ensure that all buffered data /// is completely written to the output stream. /// </summary> /// <param name="entry"> /// The TarEntry to be written to the archive. /// </param> public void PutNextEntry(TarEntry entry) { if (entry == null) { throw new ArgumentNullException("entry"); } if (entry.TarHeader.Name.Length >= TarHeader.NAMELEN) { var longHeader = new TarHeader{TypeFlag = TarHeader.LF_GNU_LONGNAME}; longHeader.Name = longHeader.Name + "././@LongLink"; longHeader.UserId = 0; longHeader.GroupId = 0; longHeader.GroupName = String.Empty; longHeader.UserName = String.Empty; longHeader.LinkName = String.Empty; longHeader.Size = entry.TarHeader.Name.Length; longHeader.WriteHeader(blockBuffer); _buffer.WriteBlock(blockBuffer); // Add special long filename header block var nameCharIndex = 0; while (nameCharIndex < entry.TarHeader.Name.Length) { Array.Clear(blockBuffer, 0, blockBuffer.Length); TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, blockBuffer, 0, TarBuffer.BlockSize); nameCharIndex += TarBuffer.BlockSize; _buffer.WriteBlock(blockBuffer); } } entry.WriteEntryHeader(blockBuffer); _buffer.WriteBlock(blockBuffer); currBytes = 0; currSize = entry.IsDirectory ? 0 : entry.Size; }
void EntryCounter(TarArchive archive, TarEntry entry, string message) { entryCount++; }
public void CloningAndUniqueness() { // Partial test of cloning for TarHeader and TarEntry TarEntry e = TarEntry.CreateTarEntry("ohsogood"); e.GroupId = 47; e.GroupName = "GroupName"; e.ModTime = DateTime.Now; e.Size = 123234; TarHeader headerE = e.TarHeader; headerE.DevMajor = 99; headerE.DevMinor = 98; headerE.LinkName = "LanceLink"; TarEntry d = (TarEntry)e.Clone(); Assert.AreEqual(d.File, e.File); Assert.AreEqual(d.GroupId, e.GroupId); Assert.AreEqual(d.GroupName, e.GroupName); Assert.AreEqual(d.IsDirectory, e.IsDirectory); Assert.AreEqual(d.ModTime, e.ModTime); Assert.AreEqual(d.Size, e.Size); TarHeader headerD = d.TarHeader; Assert.AreEqual(headerE.Checksum, headerD.Checksum); Assert.AreEqual(headerE.LinkName, headerD.LinkName); Assert.AreEqual(99, headerD.DevMajor); Assert.AreEqual(98, headerD.DevMinor); Assert.AreEqual("LanceLink", headerD.LinkName); TarEntry entryf = new TarEntry(headerD); headerD.LinkName = "Something different"; Assert.AreNotEqual(headerD.LinkName, entryf.TarHeader.LinkName, "Entry headers should be unique"); }
/// <summary> /// Write an entry to the archive. This method will call the putNextEntry /// and then write the contents of the entry, and finally call closeEntry() /// for entries that are files. For directories, it will call putNextEntry(), /// and then, if the recurse flag is true, process each entry that is a /// child of the directory. /// </summary> /// <param name="sourceEntry"> /// The TarEntry representing the entry to write to the archive. /// </param> /// <param name="recurse"> /// If true, process the children of directory entries. /// </param> private void InternalWriteEntry(TarEntry sourceEntry, bool recurse) { string tempFileName = null; var entryFilename = sourceEntry.File; var entry = (TarEntry) sourceEntry.Clone(); if (_applyUserInfoOverrides) { entry.GroupId = _groupId; entry.GroupName = _groupName; entry.UserId = _userId; entry.UserName = _userName; } OnProgressMessageEvent(entry, null); if (_asciiTranslate && !entry.IsDirectory) { var asciiTrans = !IsBinary(entryFilename); if (asciiTrans) { tempFileName = Path.GetTempFileName(); using (var inStream = File.OpenText(entryFilename)) { using (Stream outStream = File.Create(tempFileName)) { while (true) { var line = inStream.ReadLine(); if (line == null) { break; } var data = Encoding.UTF8.GetBytes(line); outStream.Write(data, 0, data.Length); outStream.WriteByte((byte) '\n'); } outStream.Flush(); } } entry.Size = new FileInfo(tempFileName).Length; entryFilename = tempFileName; } } string newName = null; if (rootPath != null) { if (entry.Name.StartsWith(rootPath)) { newName = entry.Name.Substring(rootPath.Length + 1); } } if (pathPrefix != null) { newName = (newName == null) ? string.Format("{0}/{1}", pathPrefix, entry.Name) : pathPrefix + "/" + newName; } if (newName != null) { entry.Name = newName; } tarOut.PutNextEntry(entry); if (entry.IsDirectory) { if (recurse) { var list = entry.GetDirectoryEntries(); for (var i = 0; i < list.Length; ++i) { InternalWriteEntry(list[i], recurse); } } } else { using (Stream inputStream = File.OpenRead(entryFilename)) { var localBuffer = new byte[32*1024]; while (true) { var numRead = inputStream.Read(localBuffer, 0, localBuffer.Length); if (numRead <= 0) { break; } tarOut.Write(localBuffer, 0, numRead); } } if (!string.IsNullOrEmpty(tempFileName)) { File.Delete(tempFileName); } tarOut.CloseEntry(); } }
/// <summary> /// Get the next entry in this tar archive. This will skip /// over any remaining data in the current entry, if there /// is one, and place the input stream at the header of the /// next entry, and read the header and instantiate a new /// TarEntry from the header bytes and return that entry. /// If there are no more entries in the archive, null will /// be returned to indicate that the end of the archive has /// been reached. /// </summary> /// <returns> /// The next TarEntry in the archive, or null. /// </returns> public TarEntry GetNextEntry() { if (hasHitEOF) { return null; } if (currentEntry != null) { SkipToNextEntry(); } var headerBuf = _buffer.ReadBlock(); if (headerBuf == null) { hasHitEOF = true; } else if (_buffer.IsEOFBlock(headerBuf)) { hasHitEOF = true; } if (hasHitEOF) { currentEntry = null; } else { try { var header = new TarHeader(); header.ParseBuffer(headerBuf); if (!header.IsChecksumValid) { throw new TarException("Header checksum is invalid"); } entryOffset = 0; entrySize = header.Size; StringBuilder longName = null; if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME) { var nameBuffer = new byte[TarBuffer.BlockSize]; var numToRead = entrySize; longName = new StringBuilder(); while (numToRead > 0) { var numRead = Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int) numToRead)); if (numRead == -1) { throw new InvalidHeaderException("Failed to read long name entry"); } longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead).ToString()); numToRead -= numRead; } SkipToNextEntry(); headerBuf = _buffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_GHDR) { // POSIX global extended header // Ignore things we dont understand completely for now SkipToNextEntry(); headerBuf = _buffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_XHDR) { // POSIX extended header // Ignore things we dont understand completely for now SkipToNextEntry(); headerBuf = _buffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR) { // TODO: could show volume name when verbose SkipToNextEntry(); headerBuf = _buffer.ReadBlock(); } else if (header.TypeFlag != TarHeader.LF_NORMAL && header.TypeFlag != TarHeader.LF_OLDNORM && header.TypeFlag != TarHeader.LF_DIR) { // Ignore things we dont understand completely for now SkipToNextEntry(); headerBuf = _buffer.ReadBlock(); } if (entryFactory == null) { currentEntry = new TarEntry(headerBuf); if (longName != null) { currentEntry.Name = longName.ToString(); } } else { currentEntry = entryFactory.CreateEntry(headerBuf); } // Magic was checked here for 'ustar' but there are multiple valid possibilities // so this is not done anymore. entryOffset = 0; // TODO: Review How do we resolve this discrepancy?! entrySize = currentEntry.Size; } catch (InvalidHeaderException ex) { entrySize = 0; entryOffset = 0; currentEntry = null; var errorText = string.Format("Bad header in record {0} block {1} {2}", _buffer.CurrentRecord, _buffer.CurrentBlock, ex.Message); throw new InvalidHeaderException(errorText); } } return currentEntry; }
/// <summary> /// Extract an entry from the archive. This method assumes that the /// tarIn stream has been properly set with a call to GetNextEntry(). /// </summary> /// <param name="destDir"> /// The destination directory into which to extract. /// </param> /// <param name="entry"> /// The TarEntry returned by tarIn.GetNextEntry(). /// </param> private void ExtractEntry(string destDir, TarEntry entry) { OnProgressMessageEvent(entry, null); var name = entry.Name; if (Path.IsPathRooted(name)) { // NOTE: // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt name = name.Substring(Path.GetPathRoot(name).Length); } name = name.Replace('/', Path.DirectorySeparatorChar); var destFile = Path.Combine(destDir, name); if (entry.IsDirectory) { EnsureDirectoryExists(destFile); } else { var parentDirectory = Path.GetDirectoryName(destFile); EnsureDirectoryExists(parentDirectory); var process = true; var fileInfo = new FileInfo(destFile); if (fileInfo.Exists) { if (_keepOldFiles) { OnProgressMessageEvent(entry, "Destination file already exists"); process = false; } else if ((fileInfo.Attributes & FileAttributes.ReadOnly) != 0) { OnProgressMessageEvent(entry, "Destination file already exists, and is read-only"); process = false; } } if (process) { var asciiTrans = false; Stream outputStream = File.Create(destFile); if (_asciiTranslate) { asciiTrans = !IsBinary(destFile); } StreamWriter outw = null; if (asciiTrans) { outw = new StreamWriter(outputStream); } var rdbuf = new byte[32*1024]; while (true) { var numRead = tarIn.Read(rdbuf, 0, rdbuf.Length); if (numRead <= 0) { break; } if (asciiTrans) { for (int off = 0, b = 0; b < numRead; ++b) { if (rdbuf[b] == 10) { var s = Encoding.UTF8.GetString(rdbuf, off, (b - off)); outw.WriteLine(s); off = b + 1; } } } else { outputStream.Write(rdbuf, 0, numRead); } } if (asciiTrans) { outw.Close(); } else { outputStream.Close(); } } } }
/// <summary> /// Write an entry to the archive. This method will call the putNextEntry /// and then write the contents of the entry, and finally call closeEntry() /// for entries that are files. For directories, it will call putNextEntry(), /// and then, if the recurse flag is true, process each entry that is a /// child of the directory. /// </summary> /// <param name="sourceEntry"> /// The TarEntry representing the entry to write to the archive. /// </param> /// <param name="recurse"> /// If true, process the children of directory entries. /// </param> public void WriteEntry(TarEntry sourceEntry, bool recurse) { if (sourceEntry == null) { throw new ArgumentNullException("sourceEntry"); } if (isDisposed) { throw new ObjectDisposedException("TarArchive"); } try { if (recurse) { TarHeader.SetValueDefaults(sourceEntry.UserId, sourceEntry.UserName, sourceEntry.GroupId, sourceEntry.GroupName); } InternalWriteEntry(sourceEntry, recurse); } finally { if (recurse) { TarHeader.RestoreSetValues(); } } }
/// <summary> /// Get entries for all files present in this entries directory. /// If this entry doesnt represent a directory zero entries are returned. /// </summary> /// <returns> /// An array of TarEntry's for this entry's children. /// </returns> public TarEntry[] GetDirectoryEntries() { if ((_file == null) || !Directory.Exists(_file)) { return new TarEntry[0]; } #if !SL4 var list = Directory.GetFileSystemEntries(_file); #else var list = Directory.EnumerateFileSystemEntries(_file).ToArray(); #endif var result = new TarEntry[list.Length]; for (var i = 0; i < list.Length; ++i) { result[i] = CreateEntryFromFile(list[i]); } return result; }
/// <summary> /// Raises the ProgressMessage event /// </summary> /// <param name="entry">The <see cref="TarEntry">TarEntry</see> for this event</param> /// <param name="message">message for this event. Null is no message</param> protected virtual void OnProgressMessageEvent(TarEntry entry, string message) { if (ProgressMessageEvent != null) { ProgressMessageEvent(this, entry, message); } }
/// <summary> /// Construct an entry for a _file. File is set to _file, and the /// _header is constructed from information from the _file. /// </summary> /// <param name = "fileName">The _file name that the entry represents.</param> /// <returns>Returns the newly created <see cref="TarEntry"/></returns> public static TarEntry CreateEntryFromFile(string fileName) { var entry = new TarEntry(); entry.GetFileTarHeader(entry._header, fileName); return entry; }
/// <summary> /// Determine if the given entry is a descendant of this entry. /// Descendancy is determined by the name of the descendant /// starting with this entry's name. /// </summary> /// <param name = "toTest"> /// Entry to be checked as a descendent of this. /// </param> /// <returns> /// True if entry is a descendant of this. /// </returns> public bool IsDescendent(TarEntry toTest) { if (toTest == null) { throw new ArgumentNullException("toTest"); } return toTest.Name.StartsWith(Name); }
/// <summary> /// Construct an entry with only a <paramref name="name">name</paramref>. /// This allows the programmer to construct the entry's _header "by hand". /// </summary> /// <param name="name">The name to use for the entry</param> /// <returns>Returns the newly created <see cref="TarEntry"/></returns> public static TarEntry CreateTarEntry(string name) { var entry = new TarEntry(); NameTarHeader(entry._header, name); return entry; }
/// <summary> /// Clone this tar entry. /// </summary> /// <returns>Returns a clone of this entry.</returns> public object Clone() { var entry = new TarEntry{_file = _file, _header = ((TarHeader) _header.Clone()), Name = Name}; return entry; }
/// <summary> /// Create a tar entry with details obtained from <paramref name="fileName">file</paramref> /// </summary> /// <param name="fileName">The name of the file to retrieve details from.</param> /// <returns>A new <see cref="TarEntry"/></returns> public TarEntry CreateEntryFromFile(string fileName) { return(TarEntry.CreateEntryFromFile(fileName)); }
/// <summary> /// Create a <see cref="TarEntry"/> based on named /// </summary> /// <param name="name">The name to use for the entry</param> /// <returns>A new <see cref="TarEntry"/></returns> public TarEntry CreateEntry(string name) { return(TarEntry.CreateTarEntry(name)); }
/// <summary> /// Get the next entry in this tar archive. This will skip /// over any remaining data in the current entry, if there /// is one, and place the input stream at the header of the /// next entry, and read the header and instantiate a new /// TarEntry from the header bytes and return that entry. /// If there are no more entries in the archive, null will /// be returned to indicate that the end of the archive has /// been reached. /// </summary> /// <returns> /// The next TarEntry in the archive, or null. /// </returns> public TarEntry GetNextEntry() { if (hasHitEOF) { return(null); } if (currentEntry != null) { SkipToNextEntry(); } var headerBuf = _buffer.ReadBlock(); if (headerBuf == null) { hasHitEOF = true; } else if (_buffer.IsEOFBlock(headerBuf)) { hasHitEOF = true; } if (hasHitEOF) { currentEntry = null; } else { try { var header = new TarHeader(); header.ParseBuffer(headerBuf); if (!header.IsChecksumValid) { throw new TarException("Header checksum is invalid"); } entryOffset = 0; entrySize = header.Size; StringBuilder longName = null; if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME) { var nameBuffer = new byte[TarBuffer.BlockSize]; var numToRead = entrySize; longName = new StringBuilder(); while (numToRead > 0) { var numRead = Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead)); if (numRead == -1) { throw new InvalidHeaderException("Failed to read long name entry"); } longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead).ToString()); numToRead -= numRead; } SkipToNextEntry(); headerBuf = _buffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_GHDR) { // POSIX global extended header // Ignore things we dont understand completely for now SkipToNextEntry(); headerBuf = _buffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_XHDR) { // POSIX extended header // Ignore things we dont understand completely for now SkipToNextEntry(); headerBuf = _buffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR) { // TODO: could show volume name when verbose SkipToNextEntry(); headerBuf = _buffer.ReadBlock(); } else if (header.TypeFlag != TarHeader.LF_NORMAL && header.TypeFlag != TarHeader.LF_OLDNORM && header.TypeFlag != TarHeader.LF_DIR) { // Ignore things we dont understand completely for now SkipToNextEntry(); headerBuf = _buffer.ReadBlock(); } if (entryFactory == null) { currentEntry = new TarEntry(headerBuf); if (longName != null) { currentEntry.Name = longName.ToString(); } } else { currentEntry = entryFactory.CreateEntry(headerBuf); } // Magic was checked here for 'ustar' but there are multiple valid possibilities // so this is not done anymore. entryOffset = 0; // TODO: Review How do we resolve this discrepancy?! entrySize = currentEntry.Size; } catch (InvalidHeaderException ex) { entrySize = 0; entryOffset = 0; currentEntry = null; var errorText = string.Format("Bad header in record {0} block {1} {2}", _buffer.CurrentRecord, _buffer.CurrentBlock, ex.Message); throw new InvalidHeaderException(errorText); } } return(currentEntry); }