This commit is contained in:
parent
376201cfa5
commit
8fb8bc9b05
32
skyscraper8.Tests/IntegrationTests.cs
Normal file
32
skyscraper8.Tests/IntegrationTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
skyscraper8/Skyscraper/Scraper/Storage/Tar/TarArchive.cs
Normal file
77
skyscraper8/Skyscraper/Scraper/Storage/Tar/TarArchive.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
158
skyscraper8/Skyscraper/Scraper/Storage/Tar/TarHeader.cs
Normal file
158
skyscraper8/Skyscraper/Scraper/Storage/Tar/TarHeader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user