code wiki / (root) / tar_header.nx

tar_header.nx source

↩ module page · 181 lines · 6506 B

1// tar_header.nx -- parse POSIX ustar tar headers (IEEE 1003.1). 2// 3// Each tar entry is a 512-byte header followed by its file data 4// padded to a 512-byte boundary. The archive ends with at least 5// two zero-filled 512-byte blocks. 6// 7// Header layout: 8// offset size field 9// 0 100 name (NUL-terminated if shorter) 10// 100 8 mode (octal ASCII) 11// 108 8 uid (octal ASCII) 12// 116 8 gid (octal ASCII) 13// 124 12 size (octal ASCII, bytes of file data) 14// 136 12 mtime (octal ASCII seconds since epoch) 15// 148 8 header checksum (octal ASCII) 16// 156 1 typeflag ('0'=regular, '5'=dir, '2'=symlink, 17// '1'=hardlink, 'x'=pax extended, 'g'=pax global) 18// 157 100 linkname 19// 257 6 magic "ustar\0" (POSIX) or "ustar " (GNU) 20// 263 2 version "00" (POSIX) or " \0" (GNU) 21// 265 32 uname (owner name) 22// 297 32 gname (group name) 23// 329 8 devmajor (octal) 24// 337 8 devminor (octal) 25// 345 155 prefix (prepended to name for long paths) 26// 27// Invariants: 28// T1 Magic must be "ustar" -- the first 5 bytes at offset 257. 29// T2 Size fields are parsed as big-endian octal ASCII. GNU tar 30// optionally encodes large sizes as binary with MSB=0x80; 31// we reject those (caller can detect and extend). 32// T3 Checksum verification not performed here (future work); 33// we parse the field value but don't validate. 34 35import "syscalls.nx" 36 37const TAR_BLOCK: i64 = 512 38 39const TAR_ERR_FORMAT: i64 = -1 40const TAR_ERR_SHORT: i64 = -2 41const TAR_ERR_OCTAL: i64 = -3 42 43struct TarHeader { 44 name_off: i64, name_len: i64, 45 prefix_off: i64, prefix_len: i64, 46 link_off: i64, link_len: i64, 47 mode: i64, 48 uid: i64, 49 gid: i64, 50 size: i64, // file payload size 51 mtime: i64, 52 checksum: i64, 53 typeflag: i64, // raw byte 54 // Total record size including payload + padding, so caller can 55 // advance to the next header. 56 record_len: i64, 57} 58 59// Parse an octal ASCII number field of given length. Stops at 60// NUL, space, or end. Returns value or TAR_ERR_OCTAL on digit 61// out of range. 62func tar_parse_octal(buf: *u8, off: i64, n: i64) -> i64 { 63 var v: i64 = 0 64 var i: i64 = 0 65 while i < n { 66 let c: i64 = buf[off + i] 67 if c == 0 { return v } 68 if c == 0x20 { return v } // space = end 69 if c < 0x30 { return TAR_ERR_OCTAL } 70 if c > 0x37 { return TAR_ERR_OCTAL } 71 v = v * 8 + (c - 0x30) 72 i = i + 1 73 } 74 return v 75} 76 77// Scan a NUL terminator within a bounded window. Returns length 78// or the window size if no NUL found (tar name field without 79// terminator means "full 100 bytes"). 80func tar_scan_nul(buf: *u8, off: i64, max: i64) -> i64 { 81 var i: i64 = 0 82 while i < max { 83 if buf[off + i] == 0 { return i } 84 i = i + 1 85 } 86 return max 87} 88 89// Check for the end-of-archive marker: a block of 512 zero bytes. 90// Returns 1 if this block is all zero, 0 otherwise. 91func tar_is_zero_block(buf: *u8, off: i64) -> i64 { 92 var i: i64 = 0 93 while i < TAR_BLOCK { 94 if buf[off + i] != 0 { return 0 } 95 i = i + 1 96 } 97 return 1 98} 99 100// Parse one tar header at offset. Returns 0 on success and fills 101// h; returns TAR_ERR_* on failure. 102func tar_parse_header(buf: *u8, n: i64, off: i64, h: *TarHeader) -> i64 { 103 if n < off + TAR_BLOCK { return TAR_ERR_SHORT } 104 105 // Magic "ustar" at offset 257. 106 if buf[off + 257] != 0x75 { return TAR_ERR_FORMAT } // 'u' 107 if buf[off + 258] != 0x73 { return TAR_ERR_FORMAT } // 's' 108 if buf[off + 259] != 0x74 { return TAR_ERR_FORMAT } // 't' 109 if buf[off + 260] != 0x61 { return TAR_ERR_FORMAT } // 'a' 110 if buf[off + 261] != 0x72 { return TAR_ERR_FORMAT } // 'r' 111 112 h.name_off = off + 0 113 h.name_len = tar_scan_nul(buf, off + 0, 100) 114 h.prefix_off = off + 345 115 h.prefix_len = tar_scan_nul(buf, off + 345, 155) 116 h.link_off = off + 157 117 h.link_len = tar_scan_nul(buf, off + 157, 100) 118 119 h.mode = tar_parse_octal(buf, off + 100, 8) 120 h.uid = tar_parse_octal(buf, off + 108, 8) 121 h.gid = tar_parse_octal(buf, off + 116, 8) 122 h.size = tar_parse_octal(buf, off + 124, 12) 123 h.mtime = tar_parse_octal(buf, off + 136, 12) 124 h.checksum = tar_parse_octal(buf, off + 148, 8) 125 h.typeflag = buf[off + 156] 126 127 if h.size < 0 { return TAR_ERR_OCTAL } 128 129 // Data payload rounds up to 512. record_len = header + padded data. 130 var padded: i64 = h.size 131 let rem: i64 = h.size % TAR_BLOCK 132 if rem != 0 { padded = padded + (TAR_BLOCK - rem) } 133 h.record_len = TAR_BLOCK + padded 134 return 0 135} 136 137// Compile-only smoke -- forge a minimal ustar header. 138func main() -> i64 { 139 let raw: *u8 = sys_mmap(TAR_BLOCK + 512) 140 var i: i64 = 0 141 while i < TAR_BLOCK + 512 { raw[i] = 0; i = i + 1 } 142 143 // name = "hello.txt" 144 raw[0] = 0x68; raw[1] = 0x65; raw[2] = 0x6C; raw[3] = 0x6C 145 raw[4] = 0x6F; raw[5] = 0x2E; raw[6] = 0x74; raw[7] = 0x78; raw[8] = 0x74 146 147 // mode "000644\0 " occupies offset 100..107, NUL-terminated. 148 raw[100] = 0x30; raw[101] = 0x30; raw[102] = 0x30 149 raw[103] = 0x36; raw[104] = 0x34; raw[105] = 0x34 150 151 // size "13\0" at offset 124 (11 bytes then NUL). Octal 13 = dec 11. 152 raw[124] = 0x31; raw[125] = 0x33 153 154 // typeflag '0' = regular file 155 raw[156] = 0x30 156 157 // magic "ustar" at offset 257 + NUL + version "00" 158 raw[257] = 0x75; raw[258] = 0x73; raw[259] = 0x74 159 raw[260] = 0x61; raw[261] = 0x72 160 raw[263] = 0x30; raw[264] = 0x30 161 162 let h_raw: *u8 = sys_mmap(256) 163 let h: *TarHeader = h_raw as *TarHeader 164 if tar_parse_header(raw, 1024, 0, h) != 0 { return 1 } 165 166 if h.name_len != 9 { return 2 } 167 if h.mode != 420 { return 3 } // 0o644 = 420 decimal 168 if h.size != 11 { return 4 } 169 if h.typeflag != 0x30 { return 5 } 170 171 // Record length = 512 header + 512 padded data (11 rounds up). 172 if h.record_len != 1024 { return 6 } 173 174 // End-of-archive: the (unused) second block should be all zero. 175 if tar_is_zero_block(raw, 512) != 1 { return 7 } 176 177 // Bad magic -> TAR_ERR_FORMAT. 178 raw[257] = 0 179 if tar_parse_header(raw, 1024, 0, h) != TAR_ERR_FORMAT { return 8 } 180 return 0 181}