Lots of changes here... - Removed code using RecursivExtractor due to bad usage of /tmp Note: the code that used RecursiveExtractor may not have been in the previous commit - Created _functioning_ implementation of DebUnpacker - Restructured Project layout. TODO: - Possibly rewrite how the DebUnpacker works to have the file contents part of an entry. - Further restructuring and refactoring to make the code a little neater. - Add code for the final unpack steps Un-XZ -> untar -> write to disk - Add code to install - Add code for modified post-install pre-remove etc.. This is kinda needed to ensure proper system integration of new packages.
146 lines
4.5 KiB
C#
146 lines
4.5 KiB
C#
using System.Text;
|
|
using Serilog;
|
|
namespace EdgeInstaller;
|
|
|
|
sealed class DebFile : IDisposable
|
|
{
|
|
const int HeaderBytes = 60;
|
|
const string FileMagic = "!<arch>\n";
|
|
|
|
private MemoryStream _fileStream;
|
|
|
|
public List<DebEntry> fileEntries { get; private set; }
|
|
|
|
public DebFile(Stream fs)
|
|
{
|
|
Log.Information("Creating new debFile from stream");
|
|
fileEntries = new();
|
|
if (fs is null)
|
|
{
|
|
throw new NullReferenceException();
|
|
}
|
|
|
|
fs.Position = 0;
|
|
|
|
// read the first 8 bytes and check for the signature
|
|
byte[] magicBuffer = new byte[8];
|
|
|
|
fs.Read(magicBuffer);
|
|
Log.Debug("Magic = {magicBuffer}", Encoding.ASCII.GetString(magicBuffer));
|
|
if (Encoding.ASCII.GetString(magicBuffer) != FileMagic)
|
|
{
|
|
throw new ArgumentException(message: "Magic Fail");
|
|
}
|
|
|
|
fs.Position = 0;
|
|
_fileStream = new();
|
|
fs.CopyTo(_fileStream);
|
|
|
|
fs.Dispose();
|
|
// if we got here then we must have a proper archive.
|
|
// We shall now get the files that are inside.
|
|
|
|
// from OUR copy of the stream we seek to after the magic byte
|
|
_fileStream.Position = 8;
|
|
while (true) // beware making an infinite loop
|
|
{
|
|
// Read a header
|
|
byte[] header = new byte[60];
|
|
var read = _fileStream.Read(header);
|
|
// If nothing was read we have hit the end
|
|
if (read < 60) // also handle broken headers
|
|
{
|
|
break;
|
|
}
|
|
// Otherwise process as a header
|
|
var entry = new DebEntry(header, _fileStream.Position);
|
|
fileEntries.Add(entry);
|
|
|
|
// Using the obtained size seek forwards to the next potential header
|
|
_fileStream.Seek(entry.sizeBytes, SeekOrigin.Current);
|
|
}
|
|
}
|
|
|
|
public bool getFile(DebEntry file, out Stream output)
|
|
{
|
|
// If we are given a file that isn't in this archive we exit nicely
|
|
if (!fileEntries.Contains(file))
|
|
{
|
|
Log.Error("The given file entry is not valid for this DebFile");
|
|
output = Stream.Null;
|
|
return false;
|
|
}
|
|
|
|
// Since we have a file that is in the archive
|
|
// grab the metadata we need
|
|
long offset = file.offset;
|
|
long size = file.sizeBytes;
|
|
// stream the file to the output stream
|
|
_fileStream.Seek(offset, SeekOrigin.Begin);
|
|
byte[] fileBuffer = new byte[size];
|
|
|
|
var count = _fileStream.Read(fileBuffer);
|
|
if (count != size)
|
|
{
|
|
Log.Error("Didn't read the correct amount of data from stream (expected:{size} got:{count})", size, count);
|
|
output = Stream.Null;
|
|
return false;
|
|
}
|
|
|
|
output = new MemoryStream(fileBuffer); // convert the read buffer to a stream
|
|
return true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{ // make sure we can clean up our own mess
|
|
_fileStream.Dispose();
|
|
}
|
|
}
|
|
|
|
sealed class DebEntry
|
|
{
|
|
// Escape sequnces are used in this string read as "`\n"
|
|
const string HeaderMagic = "\x60\n";
|
|
|
|
public string name { get; private set; }
|
|
public long mtime { get; private set; }
|
|
public int mode { get; private set; }
|
|
public int gid { get; private set; }
|
|
public int uid { get; private set; }
|
|
public long sizeBytes { get; private set; }
|
|
public long offset { get; private set; } // where does the file start
|
|
|
|
public DebEntry(byte[] buffer, long Position)
|
|
{
|
|
Log.Debug("Reading header at position {p}", Position - 60); // since we are being passed a header we take 60 to get the header position
|
|
if (buffer is null)
|
|
{
|
|
throw new NullReferenceException();
|
|
}
|
|
|
|
if (buffer.Length < 60)
|
|
{
|
|
throw new ArgumentException("buffer too short");
|
|
}
|
|
|
|
name = Encoding.ASCII.GetString(buffer[0..16]);
|
|
mtime = long.Parse(Encoding.ASCII.GetString(buffer[16..28]));
|
|
mode = int.Parse(Encoding.ASCII.GetString(buffer[28..34]));
|
|
gid = int.Parse(Encoding.ASCII.GetString(buffer[34..40]));
|
|
uid = int.Parse(Encoding.ASCII.GetString(buffer[40..48]));
|
|
sizeBytes = long.Parse(Encoding.ASCII.GetString(buffer[48..58]));
|
|
string magic = Encoding.ASCII.GetString(buffer[58..^0]);
|
|
|
|
offset = Position;
|
|
|
|
Log.Debug("Header Magic = {magic}", magic);
|
|
Log.Debug("{@this}", this);
|
|
if (magic != HeaderMagic)
|
|
{
|
|
throw new ArgumentException("magic failed");
|
|
}
|
|
|
|
}
|
|
|
|
}
|