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 };