using System; using System.IO; using System.Runtime.InteropServices; using System.Text; namespace Packaging.Targets.IO { /// /// The cpio archive format collects any number of files, directories, and /// other file system objects(symbolic links, device nodes, etc.) into a /// single stream of bytes. /// /// /// Each file system object in a cpio archive comprises a header record with /// basic numeric metadata followed by the full pathname of the entry and the /// file data.The header record stores a series of integer values that generally /// follow the fields in struct stat. (See stat(2) for details.) The /// variants differ primarily in how they store those integers(binary, /// octal, or hexadecimal). The header is followed by the pathname of the /// entry(the length of the pathname is stored in the header) and any file /// data.The end of the archive is indicated by a special record with the /// pathname TRAILER!!!. /// /// public class CpioFile : ArchiveFile, IDisposable { /// /// The entry which is currently available. /// private CpioHeader entryHeader; /// /// The position at which the data for the current entry starts. /// private long entryDataOffset; /// /// The length of the data for the current entry. /// private long entryDataLength; /// /// Initializes a new instance of the class. /// /// /// A which represents the CPIO data. /// /// /// to leave the underlying open when this /// is disposed of; otherwise, . /// public CpioFile(Stream stream, bool leaveOpen) : base(stream, leaveOpen) { } /// /// Gets the header of the current entry. /// public CpioHeader EntryHeader { get { return this.entryHeader; } } /// /// Gets a value indicating whether the current entry is a directory. /// public bool IsDirectory { get { return this.entryDataLength == 0; } } /// /// Adds an entry to the /// /// /// A with the item metaata. The , /// and values are overwritten. /// /// /// The file name of the entry. /// /// /// A which contains the file data. /// public void Write(CpioHeader header, string name, Stream data) { if (name == null) { throw new ArgumentNullException(nameof(name)); } if (data == null) { throw new ArgumentNullException(nameof(data)); } byte[] nameBytes = Encoding.UTF8.GetBytes(name); // We make sure the magic and size fields have the correct values. All other fields // are the responsibility of the caller. header.Signature = "070701"; header.NameSize = (uint)(nameBytes.Length + 1); header.FileSize = (uint)data.Length; this.Stream.WriteStruct(header); this.Stream.Write(nameBytes, 0, nameBytes.Length); this.Stream.WriteByte(0); // Trailing 0 // The pathname is followed by NUL bytes so that the total size of the fixed // header plus pathname is a multiple of four. var headerSize = Marshal.SizeOf() + (int)header.NameSize; var paddingSize = PaddingSize(4, headerSize); for (int i = 0; i < paddingSize; i++) { this.Stream.WriteByte(0); } data.Position = 0; data.CopyTo(this.Stream); // The file data is padded to a multiple of four bytes. paddingSize = PaddingSize(4, (int)data.Length); for (int i = 0; i < paddingSize; i++) { this.Stream.WriteByte(0); } } /// /// Writes the trailer entry. /// public void WriteTrailer() { this.Write(CpioHeader.Empty, "TRAILER!!!", new MemoryStream(Array.Empty())); } /// /// Reads the next entry in the . /// /// /// if more data is available; otherwise, . /// public override bool Read() { if (this.EntryStream != null) { this.EntryStream.Dispose(); } this.Align(4); this.entryHeader = this.Stream.ReadStruct(); this.FileHeader = this.entryHeader; if (this.entryHeader.Signature != "070701") { throw new InvalidDataException("The magic for the file entry is invalid"); } byte[] nameBytes = new byte[this.entryHeader.NameSize]; this.Stream.Read(nameBytes, 0, nameBytes.Length); this.FileName = Encoding.UTF8.GetString(nameBytes, 0, (int)this.entryHeader.NameSize - 1); // The pathname is followed by NUL bytes so that the total size of the fixed // header plus pathname is a multiple of four. var headerSize = Marshal.SizeOf() + nameBytes.Length; var paddingSize = PaddingSize(4, headerSize); if (nameBytes.Length < paddingSize) { nameBytes = new byte[paddingSize]; } this.Stream.Read(nameBytes, 0, paddingSize); this.entryDataOffset = this.Stream.Position; this.entryDataLength = this.entryHeader.FileSize; this.EntryStream = new SubStream(this.Stream, this.entryDataOffset, this.entryDataLength, leaveParentOpen: true); return this.FileName != "TRAILER!!!"; } } }