147 lines
4.3 KiB
C#
147 lines
4.3 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace AMWD.Common.Packing.Ar
|
|
{
|
|
/// <summary>
|
|
/// Writes UNIX ar (archive) files in the GNU format.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Copied from <a href="https://github.com/ygoe/DotnetMakeDeb/blob/v1.1.0/DotnetMakeDeb/Ar/ArWriter.cs"/>
|
|
/// </remarks>
|
|
public class ArWriter
|
|
{
|
|
// Source: http://en.wikipedia.org/wiki/Ar_%28Unix%29
|
|
|
|
private readonly Stream _outStream;
|
|
|
|
/// <summary>
|
|
/// Initialises a new instance of the <see cref="ArWriter"/> class.
|
|
/// </summary>
|
|
/// <param name="outStream">The stream to write the archive to.</param>
|
|
public ArWriter(Stream outStream)
|
|
{
|
|
if (!outStream.CanWrite)
|
|
throw new ArgumentException("Stream not writable", nameof(outStream));
|
|
|
|
_outStream = outStream;
|
|
Initialize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a file from disk to the archive.
|
|
/// </summary>
|
|
/// <param name="fileName">The name of the file to copy.</param>
|
|
/// <param name="userId">The user ID of the file in the archive.</param>
|
|
/// <param name="groupId">The group ID of the file in the archive.</param>
|
|
/// <param name="mode">The mode of the file in the archive (decimal).</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a file from a Stream to the archive.
|
|
/// </summary>
|
|
/// <param name="stream">The stream to read the file contents from.</param>
|
|
/// <param name="fileName">The name of the file in the archive.</param>
|
|
/// <param name="modifyTime">The last modification time of the file in the archive.</param>
|
|
/// <param name="userId">The user ID of the file in the archive.</param>
|
|
/// <param name="groupId">The group ID of the file in the archive.</param>
|
|
/// <param name="mode">The mode of the file in the archive (decimal).</param>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the archive header.
|
|
/// </summary>
|
|
private void Initialize()
|
|
{
|
|
WriteAsciiString("!<arch>\n");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a file header.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a string using ASCII encoding.
|
|
/// </summary>
|
|
/// <param name="str">The string to write to the output stream.</param>
|
|
private void WriteAsciiString(string str)
|
|
{
|
|
byte[] bytes = Encoding.ASCII.GetBytes(str);
|
|
_outStream.Write(bytes, 0, bytes.Length);
|
|
}
|
|
}
|
|
}
|