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