From 585b9970ef58e17dd88f454007949a57c4f378c8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 9 Jan 2023 22:36:35 -0700 Subject: [PATCH] add std.tar for tar file unpacking --- lib/std/std.zig | 2 + lib/std/tar.zig | 144 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 lib/std/tar.zig diff --git a/lib/std/std.zig b/lib/std/std.zig index 87772bf8f8..ba52784b45 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -21,6 +21,7 @@ pub const EnumMap = enums.EnumMap; pub const EnumSet = enums.EnumSet; pub const HashMap = hash_map.HashMap; pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; +pub const Ini = @import("Ini.zig"); pub const MultiArrayList = @import("multi_array_list.zig").MultiArrayList; pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; @@ -85,6 +86,7 @@ pub const rand = @import("rand.zig"); pub const sort = @import("sort.zig"); pub const simd = @import("simd.zig"); pub const ascii = @import("ascii.zig"); +pub const tar = @import("tar.zig"); pub const testing = @import("testing.zig"); pub const time = @import("time.zig"); pub const tz = @import("tz.zig"); diff --git a/lib/std/tar.zig b/lib/std/tar.zig new file mode 100644 index 0000000000..e7b727d41c --- /dev/null +++ b/lib/std/tar.zig @@ -0,0 +1,144 @@ +pub const Options = struct {}; + +pub const Header = struct { + bytes: *const [512]u8, + + pub const FileType = enum(u8) { + normal = '0', + hard_link = '1', + symbolic_link = '2', + character_special = '3', + block_special = '4', + directory = '5', + fifo = '6', + contiguous = '7', + global_extended_header = 'g', + extended_header = 'x', + _, + }; + + pub fn fileSize(header: Header) !u64 { + const raw = header.bytes[124..][0..12]; + const ltrimmed = std.mem.trimLeft(u8, raw, "0"); + const rtrimmed = std.mem.trimRight(u8, ltrimmed, "\x00"); + if (rtrimmed.len == 0) return 0; + return std.fmt.parseInt(u64, rtrimmed, 8); + } + + pub fn is_ustar(header: Header) bool { + return std.mem.eql(u8, header.bytes[257..][0..6], "ustar\x00"); + } + + /// Includes prefix concatenated, if any. + /// Return value may point into Header buffer, or might point into the + /// argument buffer. + /// TODO: check against "../" and other nefarious things + pub fn fullFileName(header: Header, buffer: *[255]u8) ![]const u8 { + const n = name(header); + if (!is_ustar(header)) + return n; + const p = prefix(header); + if (p.len == 0) + return n; + std.mem.copy(u8, buffer[0..p.len], p); + buffer[p.len] = '/'; + std.mem.copy(u8, buffer[p.len + 1 ..], n); + return buffer[0 .. p.len + 1 + n.len]; + } + + pub fn name(header: Header) []const u8 { + return str(header, 0, 0 + 100); + } + + pub fn prefix(header: Header) []const u8 { + return str(header, 345, 345 + 155); + } + + pub fn fileType(header: Header) FileType { + const result = @intToEnum(FileType, header.bytes[156]); + return if (result == @intToEnum(FileType, 0)) .normal else result; + } + + fn str(header: Header, start: usize, end: usize) []const u8 { + var i: usize = start; + while (i < end) : (i += 1) { + if (header.bytes[i] == 0) break; + } + return header.bytes[start..i]; + } +}; + +pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void { + _ = options; + var file_name_buffer: [255]u8 = undefined; + var buffer: [512 * 8]u8 = undefined; + var start: usize = 0; + var end: usize = 0; + header: while (true) { + if (buffer.len - start < 1024) { + std.mem.copy(u8, &buffer, buffer[start..end]); + end -= start; + start = 0; + } + const ask_header = @min(buffer.len - end, 1024 -| (end - start)); + end += try reader.readAtLeast(buffer[end..], ask_header); + switch (end - start) { + 0 => return, + 1...511 => return error.UnexpectedEndOfStream, + else => {}, + } + const header: Header = .{ .bytes = buffer[start..][0..512] }; + start += 512; + const file_size = try header.fileSize(); + const rounded_file_size = std.mem.alignForwardGeneric(u64, file_size, 512); + const pad_len = rounded_file_size - file_size; + const file_name = try header.fullFileName(&file_name_buffer); + switch (header.fileType()) { + .directory => { + try dir.makeDir(file_name); + }, + .normal => { + if (file_size == 0 and file_name.len == 0) return; + + var file = try dir.createFile(file_name, .{}); + defer file.close(); + + var file_off: usize = 0; + while (true) { + if (buffer.len - start < 1024) { + std.mem.copy(u8, &buffer, buffer[start..end]); + end -= start; + start = 0; + } + // Ask for the rounded up file size + 512 for the next header. + const ask = @min( + buffer.len - end, + rounded_file_size + 512 - file_off -| (end - start), + ); + end += try reader.readAtLeast(buffer[end..], ask); + if (end - start < ask) return error.UnexpectedEndOfStream; + const slice = buffer[start..@min(file_size - file_off + start, end)]; + try file.writeAll(slice); + file_off += slice.len; + start += slice.len; + if (file_off >= file_size) { + start += pad_len; + // Guaranteed since we use a buffer divisible by 512. + assert(start <= end); + continue :header; + } + } + }, + .global_extended_header, .extended_header => { + start += rounded_file_size; + if (start > end) return error.TarHeadersTooBig; + }, + .hard_link => return error.TarUnsupportedFileType, + .symbolic_link => return error.TarUnsupportedFileType, + else => return error.TarUnsupportedFileType, + } + } +} + +const std = @import("std.zig"); +const assert = std.debug.assert;