using System; using System.IO; using System.Text; namespace AMWD.Common.Packing.Ar { /// /// Writes UNIX ar (archive) files in the GNU format. /// /// /// Copied from: DotnetMakeDeb /// public class ArWriter { // Source: http://en.wikipedia.org/wiki/Ar_%28Unix%29 private readonly Stream _outStream; /// /// Initialises a new instance of the class. /// /// The stream to write the archive to. public ArWriter(Stream outStream) { if (!outStream.CanWrite) throw new ArgumentException("Stream not writable", nameof(outStream)); _outStream = outStream; Initialize(); } /// /// Writes a file from disk to the archive. /// /// The name of the file to copy. /// The user ID of the file in the archive. /// The group ID of the file in the archive. /// The mode of the file in the archive (decimal). public void WriteFile(string fileName, int userId = 0, int groupId = 0, int mode = 33188 /* 0100644 */) { var fi = new FileInfo(fileName); using var fs = File.OpenRead(fileName); WriteFile(fs, fi.Name, fi.LastWriteTimeUtc, userId, groupId, mode); } /// /// Writes a file from a Stream to the archive. /// /// The stream to read the file contents from. /// The name of the file in the archive. /// The last modification time of the file in the archive. /// The user ID of the file in the archive. /// The group ID of the file in the archive. /// The mode of the file in the archive (decimal). public void WriteFile(Stream stream, string fileName, DateTime modifyTime, int userId = 0, int groupId = 0, int mode = 33188 /* 0100644 */) { // Write file header WriteFileHeader(fileName, modifyTime, userId, groupId, mode, stream.Length); // Write file contents stream.CopyTo(_outStream); // Align to even offsets, pad with LF bytes if ((_outStream.Position % 2) != 0) { byte[] bytes = new byte[] { 0x0A }; _outStream.Write(bytes, 0, 1); } } /// /// Writes the archive header. /// private void Initialize() { WriteAsciiString("!\n"); } /// /// Writes a file header. /// private void WriteFileHeader(string fileName, DateTime modifyTime, int userId, int groupId, int mode, long fileSize) { // File name if (fileName.Length > 16) throw new ArgumentException("Long file names are not supported."); WriteAsciiString(fileName.PadRight(16, ' ')); // File modification timestamp long unixTime = ((DateTimeOffset)DateTime.SpecifyKind(modifyTime, DateTimeKind.Utc)).ToUnixTimeSeconds(); WriteAsciiString(unixTime.ToString().PadRight(12, ' ')); // User ID if (userId >= 0) { WriteAsciiString(userId.ToString().PadRight(6, ' ')); } else { WriteAsciiString(" "); } // Group ID if (groupId >= 0) { WriteAsciiString(groupId.ToString().PadRight(6, ' ')); } else { WriteAsciiString(" "); } // File mode if (mode >= 0) { WriteAsciiString(Convert.ToString(mode, 8).PadRight(8, ' ')); } else { WriteAsciiString(" "); } // File size in bytes if (fileSize < 0 || 10000000000 <= fileSize) throw new ArgumentOutOfRangeException("Invalid file size."); // above 9.32 GB WriteAsciiString(fileSize.ToString().PadRight(10, ' ')); // File magic byte[] bytes = new byte[] { 0x60, 0x0A }; _outStream.Write(bytes, 0, 2); } /// /// Writes a string using ASCII encoding. /// /// The string to write to the output stream. private void WriteAsciiString(string str) { byte[] bytes = Encoding.ASCII.GetBytes(str); _outStream.Write(bytes, 0, bytes.Length); } } }