Added a TAR writer.
Some checks failed
🚀 Pack skyscraper8 / make-zip (push) Failing after 7m6s

This commit is contained in:
feyris-tan 2026-01-05 20:19:50 +01:00
parent 376201cfa5
commit 8fb8bc9b05
4 changed files with 381 additions and 0 deletions

View File

@ -0,0 +1,32 @@
using System;
using System.IO;
using skyscraper5.Skyscraper;
using skyscraper8.Skyscraper.Scraper.Storage.Tar;
namespace skyscraper8.Tests;
[TestClass]
public class IntegrationTests
{
[TestMethod]
public void CreateTarArchive()
{
string filename = String.Format("{0}.tar", DateTime.Now.ToUnixTime());
FileInfo fi = new FileInfo(filename);
FileStream fileStream = fi.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
Random rng = new Random();
TarArchive tar = new TarArchive(fileStream);
for (int i = 0; i < 42; i++)
{
filename = String.Format("{0}.bin", i);
int randomSize = rng.Next(4096);
byte[] buffer = new byte[randomSize];
rng.NextBytes(buffer);
tar.WriteEntry(filename, buffer);
}
}
}

View File

@ -0,0 +1,77 @@
namespace skyscraper8.Skyscraper.Scraper.Storage.Tar;
public class TarArchive : IDisposable, IAsyncDisposable
{
public TarArchive(Stream stream)
{
if (!stream.CanSeek)
throw new Exception("TarArchive must be able to seek.");
backend = stream;
byte[] headerBuffer = null;
while (stream.Position < stream.Length)
{
if (headerBuffer == null)
headerBuffer = new byte[512];
stream.Read(headerBuffer, 0, 512);
TarHeader header = TarHeader.Deserialize(headerBuffer);
TarArchiveEntry entry = new TarArchiveEntry();
entry.FileName = header.Filename;
entry.FileSize = header.Size;
entry.Offset = stream.Position;
stream.Position += header.GetSizeOnTape();
if (entries == null)
entries = new List<TarArchiveEntry>();
entries.Add(entry);
}
}
struct TarArchiveEntry
{
public string FileName;
public long FileSize;
public long Offset;
}
private List<TarArchiveEntry> entries;
private Stream backend;
public void WriteEntry(string filename, byte[] buffer)
{
backend.Position = backend.Length;
//Write header
TarHeader header = new TarHeader(filename, buffer.Length);
byte[] serialize = header.Serialize();
backend.Write(serialize);
//Write actual data
backend.Write(buffer, 0, buffer.Length);
//Write stuffing
long stuffingLength64 = header.GetSizeOnTape();
stuffingLength64 -= header.Size;
if (stuffingLength64 > 0)
{
byte[] stuffingBuffer = new byte[stuffingLength64];
backend.Write(stuffingBuffer, 0, (int)stuffingLength64);
}
backend.Flush();
}
public void Dispose()
{
backend.Close();
backend.Dispose();
}
public async ValueTask DisposeAsync()
{
backend.Close();
await backend.DisposeAsync();
}
}

View File

@ -0,0 +1,158 @@
using System.Text;
using skyscraper5.Skyscraper;
namespace skyscraper8.Skyscraper.Scraper.Storage.Tar;
public class TarHeader
{
public TarHeader(string filename, long size)
{
Filename = filename;
Permissions = new UnixFilePermissions();
Uid = 1000;
Gid = 1000;
Size = size;
ModificationTime = DateTime.Now;
OwnerUsername = "sophia";
OwnerGroupName = "sophia";
}
private string _filename;
public string Filename
{
get => _filename;
set
{
if (value.Length > 99)
throw new ArgumentOutOfRangeException(nameof(Filename), "Filename must not exceed 99 characters.");
if (value.Contains("\0"))
throw new ArgumentOutOfRangeException(nameof(Filename), "Filename must not contain NULL characters.");
_filename = value;
}
}
public UnixFilePermissions Permissions {get; set;}
public int Uid { get; set; }
public int Gid { get; set; }
public long Size { get; set; }
public DateTime ModificationTime { get; set; }
private string _username;
public string OwnerUsername
{
get => _username;
set
{
if (value.Length > 31)
throw new ArgumentOutOfRangeException("Username", "Username must not exceed 31 characters.");
_username = value;
}
}
private string _groupname;
public string OwnerGroupName
{
get => _groupname;
set
{
if (value.Length > 31)
throw new ArgumentOutOfRangeException("GroupName", "Group name must not exceed 31 characters.");
_groupname = value;
}
}
public long GetSizeOnTape()
{
long fileSize = Size;
if (fileSize % 512 == 0)
return fileSize;
fileSize /= 512;
fileSize++;
fileSize *= 512;
return fileSize;
}
public static TarHeader Deserialize(byte[] buffer)
{
if (buffer.Length != 512)
throw new InvalidDataException("Invalid TAR header length");
throw new NotImplementedException();
}
public byte[] Serialize()
{
byte[] buffer = new byte[512];
Encoding.UTF8.GetBytes(Filename,0,Filename.Length,buffer,0);
string numericFilemode = "0000" + Permissions.ToString();
Encoding.ASCII.GetBytes(numericFilemode, 0, numericFilemode.Length, buffer, 100);
string numericUid = LongToOctal(Gid, 7);
Encoding.ASCII.GetBytes(numericUid, 0, numericUid.Length, buffer, 108);
string numericGid = LongToOctal(Gid, 7);
Encoding.ASCII.GetBytes(numericGid, 0, numericGid.Length, buffer, 116);
string numericSize = LongToOctal(Size, 11);
Encoding.ASCII.GetBytes(numericSize, 0, numericSize.Length, buffer, 124);
string numericLastModification = LongToOctal(ModificationTime.ToUnixTime(),11);
Encoding.ASCII.GetBytes(numericLastModification, 0, numericLastModification.Length, buffer, 136);
buffer[257] = (byte)'u';
buffer[258] = (byte)'s';
buffer[259] = (byte)'t';
buffer[260] = (byte)'a';
buffer[261] = (byte)'r';
buffer[262] = 0x20;
buffer[263] = 0x20;
buffer[264] = 0;
string username = OwnerUsername;
Encoding.ASCII.GetBytes(username, 0, username.Length, buffer, 265);
string groupname = OwnerGroupName;
Encoding.ASCII.GetBytes(groupname, 0, groupname.Length, buffer, 297);
string checksumBlanco = " ";
Encoding.ASCII.GetBytes(checksumBlanco, 0, checksumBlanco.Length, buffer, 148);
long checkSum = 0;
for (int i = 0; i < 512; ++i)
{
checkSum += buffer[i];
}
checksumBlanco = LongToOctal(checkSum, 6);
Encoding.ASCII.GetBytes(checksumBlanco, 0, checksumBlanco.Length, buffer, 148);
return buffer;
}
private static string LongToOctal(long value, int length)
{
string result = string.Empty;
while (value > 0)
{
result = (char)('0' + value % 8) + result;
value = value / 8;
}
return new string('0', length - result.Length) + result;
}
private static long OctalToLong(string value)
{
long result = 0;
for (int i = 0; i < value.Length; ++i)
{
result = result * 8 + (value[i] - '0');
}
return result;
}
}

View File

@ -0,0 +1,114 @@
namespace skyscraper8.Skyscraper.Scraper.Storage.Tar;
public class UnixFilePermissions
{
public UnixFilePermissions()
{
UserRead = true;
UserWrite = true;
GroupRead = true;
GroupWrite = true;
}
public UnixFilePermissions(string permissionString)
{
if (permissionString.Length == 7)
permissionString = permissionString.Substring(4);
string userChar = permissionString.Substring(0, 1);
string groupChar = permissionString.Substring(1, 1);
string worldChar = permissionString.Substring(2, 1);
int userInt = Int32.Parse(userChar);
int groupInt = Int32.Parse(groupChar);
int worldInt = Int32.Parse(worldChar);
if (userInt >= 4)
{
UserRead = true;
userInt -= 4;
}
if (userInt >= 2)
{
UserWrite = true;
userInt -= 2;
}
if (userInt >= 1)
{
UserExecute = true;
userInt -= 1;
}
if (groupInt >= 4)
{
GroupRead = true;
groupInt -= 4;
}
if (groupInt >= 2)
{
GroupWrite = true;
groupInt -= 2;
}
if (groupInt >= 1)
{
GroupExecute = true;
groupInt -= 1;
}
if (worldInt >= 4)
{
WorldRead = true;
worldInt -= 4;
}
if (worldInt >= 2)
{
WorldWrite = true;
worldInt -= 2;
}
if (worldInt >= 1)
{
WorldExecute = true;
worldInt -= 1;
}
}
public string ToString()
{
int user = Fuse(UserRead, UserWrite, UserExecute);
int group = Fuse(GroupRead, GroupWrite, GroupExecute);
int world = Fuse(WorldRead, WorldWrite, WorldExecute);
string result = String.Format("{0}{1}{2}",user,group,world);
return result;
}
public int ToInt32()
{
int user = Fuse(UserRead, UserWrite, UserExecute);
int group = Fuse(GroupRead, GroupWrite, GroupExecute);
int world = Fuse(WorldRead, WorldWrite, WorldExecute);
return (user * 100) + (group * 10) + world;
}
private int Fuse(bool read, bool write, bool execute)
{
int result = 0;
if (read)
result += 4;
if (write)
result += 2;
if (execute)
result += 1;
return result;
}
public bool UserRead { get; set; }
public bool UserWrite { get; set; }
public bool UserExecute { get; set; }
public bool GroupRead { get; set; }
public bool GroupWrite { get; set; }
public bool GroupExecute { get; set; }
public bool WorldRead { get; set; }
public bool WorldWrite { get; set; }
public bool WorldExecute { get; set; }
}