hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

reader.ha (4856B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bytes;
      5 use endian;
      6 use io;
      7 use memio;
      8 use strconv;
      9 use types::c;
     10 
     11 export type reader = struct {
     12 	src: io::handle,
     13 	name: [255]u8,
     14 };
     15 
     16 // Creates a new reader for a tar file. Use [[next]] to iterate through entries
     17 // present in the tar file.
     18 export fn read(src: io::handle) reader = {
     19 	return reader {
     20 		src = src,
     21 		...
     22 	};
     23 };
     24 
     25 // Returns the next entry from a tar [[reader]]. Parts of this structure
     26 // (specifically the file name) are borrowed from the reader itself and will not
     27 // be valid after subsequent calls.
     28 //
     29 // If the return value is a file (i.e. entry.etype == entry_type::FILE), the
     30 // caller must either call [[io::read]] using the return value until it returns
     31 // [[io::EOF]], or call [[skip]] to seek to the next entry in the archive.
     32 //
     33 // Note that reading from the header will modify the file size.
     34 export fn next(rd: *reader) (entry | error | io::EOF) = {
     35 	static let buf: [BLOCKSZ]u8 = [0...];
     36 	io::readall(rd.src, buf)?;
     37 
     38 	if (zeroed(buf)) {
     39 		io::readall(rd.src, buf)?;
     40 		if (!zeroed(buf)) {
     41 			return invalid;
     42 		};
     43 		return io::EOF;
     44 	};
     45 
     46 	const reader = memio::fixed(buf);
     47 	const name = readstr(&reader, 100);
     48 	const mode = readoct(&reader, 8)?;
     49 	const uid = readoct(&reader, 8)?;
     50 	const gid = readoct(&reader, 8)?;
     51 	const fsize = readsize(&reader, 12)?;
     52 	const mtime = readoct(&reader, 12)?;
     53 	const checksum = readoct(&reader, 8)?;
     54 	const etype = readoct(&reader, 1)?: entry_type;
     55 	const link = readstr(&reader, 100);
     56 
     57 	let ent = entry {
     58 		vtable = if (etype == entry_type::FILE) &file_vtable
     59 			else &nonfile_vtable,
     60 		src = rd.src,
     61 		orig = fsize,
     62 		remain = fsize,
     63 		name = name,
     64 		mode = mode,
     65 		uid = uid,
     66 		gid = gid,
     67 		fsize = fsize,
     68 		mtime = mtime,
     69 		checksum = checksum,
     70 		etype = etype,
     71 		link = link,
     72 		...
     73 	};
     74 
     75 	const ustar = readstr(&reader, 6);
     76 	if (ustar != "ustar") {
     77 		ent.name = name;
     78 		return ent;
     79 	};
     80 
     81 	const version = readstr(&reader, 2);
     82 	// XXX: We could check the version here
     83 	ent.uname = readstr(&reader, 32);
     84 	ent.gname = readstr(&reader, 32);
     85 	ent.devmajor = readoct(&reader, 8)?;
     86 	ent.devminor = readoct(&reader, 8)?;
     87 	const prefix = readstr(&reader, 155);
     88 	let writer = memio::fixed(rd.name);
     89 	memio::join(&writer, prefix, name)!;
     90 	ent.name = memio::string(&writer)!;
     91 	return ent;
     92 };
     93 
     94 // Seeks the underlying tar file to the entry following this one.
     95 export fn skip(ent: *entry) (void | io::error) = {
     96 	let amt = ent.remain;
     97 	if (amt % BLOCKSZ != 0) {
     98 		amt += BLOCKSZ - (amt % BLOCKSZ);
     99 	};
    100 	match (io::seek(ent.src, amt: io::off, io::whence::CUR)) {
    101 	case io::off =>
    102 		return;
    103 	case io::error => void;
    104 	};
    105 	io::copy(io::empty, ent)?;
    106 };
    107 
    108 const file_vtable: io::vtable = io::vtable {
    109 	reader = &file_read,
    110 	seeker = &file_seek,
    111 	...
    112 };
    113 
    114 const nonfile_vtable: io::vtable = io::vtable { ... };
    115 
    116 fn file_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
    117 	let ent = s: *ent_reader;
    118 	assert(ent.vtable == &file_vtable);
    119 	if (ent.remain == 0) {
    120 		return io::EOF;
    121 	};
    122 
    123 	let z = len(buf);
    124 	if (z > ent.remain) {
    125 		z = ent.remain;
    126 	};
    127 	z = match (io::read(ent.src, buf[..z])?) {
    128 	case let z: size =>
    129 		yield z;
    130 	case io::EOF =>
    131 		// TODO: Truncated flag
    132 		return io::EOF;
    133 	};
    134 	ent.remain -= z;
    135 
    136 	// Read until we reach the block size
    137 	if (ent.remain == 0 && ent.orig % BLOCKSZ != 0) {
    138 		static let buf: [BLOCKSZ]u8 = [0...];
    139 		io::readall(ent.src, buf[..BLOCKSZ - (ent.orig % BLOCKSZ)])?;
    140 	};
    141 
    142 	return z;
    143 };
    144 
    145 fn file_seek(
    146 	s: *io::stream,
    147 	off: io::off,
    148 	w: io::whence,
    149 ) (io::off | io::error) = {
    150 	let ent = s: *ent_reader;
    151 	assert(ent.vtable == &file_vtable);
    152 
    153 	const orig = ent.orig: io::off;
    154 	const cur = (ent.orig - ent.remain): io::off;
    155 	let new = switch (w) {
    156 	case io::whence::SET =>
    157 		yield off;
    158 	case io::whence::CUR =>
    159 		yield cur + off;
    160 	case io::whence::END =>
    161 		yield orig + off;
    162 	};
    163 
    164 	if (new < 0) {
    165 		new = 0;
    166 	} else if (new > orig) {
    167 		new = orig;
    168 	};
    169 
    170 	const rel = new - cur;
    171 	io::seek(ent.src, rel, io::whence::CUR)?;
    172 
    173 	ent.remain = (orig - new): size;
    174 	return new;
    175 };
    176 
    177 fn readstr(rd: *memio::stream, ln: size) str = {
    178 	const buf = match (memio::borrowedread(rd, ln)) {
    179 	case let buf: []u8 =>
    180 		assert(len(buf) == ln);
    181 		yield buf;
    182 	case io::EOF =>
    183 		abort();
    184 	};
    185 	return c::tostr(buf: *[*]u8: *const c::char)!;
    186 };
    187 
    188 fn readoct(rd: *memio::stream, ln: size) (uint | invalid) = {
    189 	const string = readstr(rd, ln);
    190 	match (strconv::stou(string, strconv::base::OCT)) {
    191 	case let u: uint =>
    192 		return u;
    193 	case =>
    194 		return invalid;
    195 	};
    196 };
    197 
    198 fn readsize(rd: *memio::stream, ln: size) (size | invalid) = {
    199 	const string = readstr(rd, ln);
    200 	match (strconv::stoz(string, strconv::base::OCT)) {
    201 	case let z: size =>
    202 		return z;
    203 	case =>
    204 		return invalid;
    205 	};
    206 };
    207 
    208 fn zeroed(buf: []u8) bool = {
    209 	for (let i = 0z; i < len(buf); i += 1) {
    210 		if (buf[i] != 0) {
    211 			return false;
    212 		};
    213 	};
    214 	return true;
    215 };