~sircmpwn/hare-tar

ba96253ce2b1038a33de9e505ee01e642479463a — Drew DeVault 3 months ago b9a09c7 master
Backport improvements from Mercury fork

Signed-off-by: Drew DeVault <sir@cmpwn.com>
2 files changed, 70 insertions(+), 36 deletions(-)

M format/tar/reader.ha
M format/tar/types.ha
M format/tar/reader.ha => format/tar/reader.ha +64 -35
@@ 11,6 11,7 @@ use types::c;
export type reader = struct {
	src: io::handle,
	name: [255]u8,
	padding: size,
};

// Creates a new reader for a tar file. Use [[next]] to iterate through entries


@@ 33,45 34,74 @@ export fn read(src: io::handle) reader = {
// Note that reading from the header will modify the file size.
export fn next(rd: *reader) (entry | error | io::EOF) = {
	static let buf: [BLOCKSZ]u8 = [0...];
	io::readall(rd.src, buf)?;

	// Read any padding data from the previous file, if necessary
	if (rd.padding != 0) {
		match (io::read(rd.src, buf[..rd.padding])) {
		case let z: size =>
			if (z != rd.padding) {
				return truncated;
			};
			rd.padding = 0;
		case io::EOF =>
			return truncated;
		};
	};

	match (io::read(rd.src, buf)?) {
	case let z: size =>
		if (z != len(buf)) {
			return truncated;
		};
	case io::EOF =>
		return io::EOF;
	};

	if (zeroed(buf)) {
		io::readall(rd.src, buf)?;
		match (io::read(rd.src, buf)?) {
		case let z: size =>
			if (z != len(buf)) {
				return truncated;
			};
		case io::EOF =>
			return io::EOF;
		};
		if (!zeroed(buf)) {
			return invalid;
			return truncated;
		};
		return io::EOF;
	};

	const reader = memio::fixed(buf);
	const name = readstr(&reader, 100);
	const mode = readoct(&reader, 8)?;
	const uid = readoct(&reader, 8)?;
	const gid = readoct(&reader, 8)?;
	const fsize = readsize(&reader, 12)?;
	const mtime = readoct(&reader, 12)?;
	const checksum = readoct(&reader, 8)?;
	const etype = readoct(&reader, 1)?: entry_type;
	const link = readstr(&reader, 100);

	let name = readstr(&reader, 100);
	let ent = entry {
		vtable = if (etype == entry_type::FILE) &file_vtable
			else &nonfile_vtable,
		src = rd.src,
		orig = fsize,
		remain = fsize,
		vtable = &null_vtable,
		src = &(null: io::stream),
		name = name,
		mode = mode,
		uid = uid,
		gid = gid,
		fsize = fsize,
		mtime = mtime,
		checksum = checksum,
		etype = etype,
		link = link,
		mode = readoct(&reader, 8)?,
		uid = readoct(&reader, 8)?,
		gid = readoct(&reader, 8)?,
		fsize = readsize(&reader, 12)?,
		mtime = readoct(&reader, 12)?,
		checksum = readoct(&reader, 8)?,
		etype = readoct(&reader, 1)?: entry_type,
		link = readstr(&reader, 100),
		...
	};

	if (ent.etype == entry_type::FILE) {
		ent.vtable = &file_vtable;
		ent.src = rd.src;
		ent.orig = ent.fsize;
		ent.remain = ent.orig;
	} else {
		ent.vtable = &null_vtable;
	};

	if (ent.fsize % BLOCKSZ != 0) {
		rd.padding = BLOCKSZ - (ent.orig % BLOCKSZ);
	};

	const ustar = readstr(&reader, 6);
	if (ustar != "ustar") {
		ent.name = name;


@@ 91,6 121,12 @@ export fn next(rd: *reader) (entry | error | io::EOF) = {
	return ent;
};

// Seeks the tar file to the start.
export fn reset(rd: *reader) (void | io::error) = {
	io::seek(rd.src, 0, io::whence::SET)?;
	rd.padding = 0;
};

// Seeks the underlying tar file to the entry following this one.
export fn skip(ent: *entry) (void | io::error) = {
	let amt = ent.remain;


@@ 105,14 141,14 @@ export fn skip(ent: *entry) (void | io::error) = {
	io::copy(io::empty, ent)?;
};

const null_vtable = io::vtable { ... };

const file_vtable: io::vtable = io::vtable {
	reader = &file_read,
	seeker = &file_seek,
	...
};

const nonfile_vtable: io::vtable = io::vtable { ... };

fn file_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
	let ent = s: *ent_reader;
	assert(ent.vtable == &file_vtable);


@@ 132,13 168,6 @@ fn file_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
		return io::EOF;
	};
	ent.remain -= z;

	// Read until we reach the block size
	if (ent.remain == 0 && ent.orig % BLOCKSZ != 0) {
		static let buf: [BLOCKSZ]u8 = [0...];
		io::readall(ent.src, buf[..BLOCKSZ - (ent.orig % BLOCKSZ)])?;
	};

	return z;
};


M format/tar/types.ha => format/tar/types.ha +6 -1
@@ 47,14 47,19 @@ export type entry_type = enum u8 {
// Returned if the source file does not contain a valid ustar archive.
export type invalid = !void;

// Returned if the source file size is not aligned on [[BLOCKSIZE]].
export type truncated = !void;

// Tagged union of all possible error types.
export type error = !(invalid | io::error);
export type error = !(invalid | truncated | io::error);

// Converts an [[error]] to a human-friendly representation.
export fn strerror(err: error) const str = {
	match (err) {
	case invalid =>
		return "Tar file is invalid";
	case truncated =>
		return "Tar file is truncated";
	case let err: io::error =>
		return io::strerror(err);
	};