| ... | ... | @@ -83,11 +83,12 @@ pub const Options = struct { |
| 83 | 83 | }; |
| 84 | 84 | }; |
| 85 | 85 | |
| 86 | | const BLOCK_SIZE = 512; |
| 87 | | const MAX_HEADER_NAME_SIZE = 100 + 1 + 155; // name(100) + separator(1) + prefix(155) |
| 88 | | |
| 89 | 86 | pub const Header = struct { |
| 90 | | bytes: *const [BLOCK_SIZE]u8, |
| 87 | const SIZE = 512; |
| 88 | const MAX_NAME_SIZE = 100 + 1 + 155; // name(100) + separator(1) + prefix(155) |
| 89 | const LINK_NAME_SIZE = 100; |
| 90 | |
| 91 | bytes: *const [SIZE]u8, |
| 91 | 92 | |
| 92 | 93 | pub const FileType = enum(u8) { |
| 93 | 94 | normal_alias = 0, |
| ... | ... | @@ -110,7 +111,7 @@ pub const Header = struct { |
| 110 | 111 | |
| 111 | 112 | /// Includes prefix concatenated, if any. |
| 112 | 113 | /// TODO: check against "../" and other nefarious things |
| 113 | | pub fn fullName(header: Header, buffer: *[MAX_HEADER_NAME_SIZE]u8) ![]const u8 { |
| 114 | pub fn fullName(header: Header, buffer: *[MAX_NAME_SIZE]u8) ![]const u8 { |
| 114 | 115 | const n = name(header); |
| 115 | 116 | const p = prefix(header); |
| 116 | 117 | if (!is_ustar(header) or p.len == 0) { |
| ... | ... | @@ -123,6 +124,16 @@ pub const Header = struct { |
| 123 | 124 | return buffer[0 .. p.len + 1 + n.len]; |
| 124 | 125 | } |
| 125 | 126 | |
| 127 | pub fn linkName(header: Header, buffer: *[LINK_NAME_SIZE]u8) []const u8 { |
| 128 | const link_name = header.str(157, 100); |
| 129 | if (link_name.len == 0) { |
| 130 | return buffer[0..0]; |
| 131 | } |
| 132 | const buf = buffer[0..link_name.len]; |
| 133 | @memcpy(buf, link_name); |
| 134 | return buf; |
| 135 | } |
| 136 | |
| 126 | 137 | pub fn name(header: Header) []const u8 { |
| 127 | 138 | return header.str(0, 100); |
| 128 | 139 | } |
| ... | ... | @@ -139,10 +150,6 @@ pub const Header = struct { |
| 139 | 150 | return header.octal(148, 8); |
| 140 | 151 | } |
| 141 | 152 | |
| 142 | | pub fn linkName(header: Header) []const u8 { |
| 143 | | return header.str(157, 100); |
| 144 | | } |
| 145 | | |
| 146 | 153 | pub fn is_ustar(header: Header) bool { |
| 147 | 154 | const magic = header.bytes[257..][0..6]; |
| 148 | 155 | return std.mem.eql(u8, magic[0..5], "ustar") and (magic[5] == 0 or magic[5] == ' '); |
| ... | ... | @@ -219,12 +226,6 @@ fn nullStr(str: []const u8) []const u8 { |
| 219 | 226 | return str; |
| 220 | 227 | } |
| 221 | 228 | |
| 222 | | // Number of padding bytes in the last file block. |
| 223 | | inline fn blockPadding(size: usize) usize { |
| 224 | | const block_rounded = std.mem.alignForward(usize, size, BLOCK_SIZE); // size rounded to te block boundary |
| 225 | | return block_rounded - size; |
| 226 | | } |
| 227 | | |
| 228 | 229 | pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void { |
| 229 | 230 | switch (options.mode_mode) { |
| 230 | 231 | .ignore => {}, |
| ... | ... | @@ -936,56 +937,18 @@ pub fn tarReader(reader: anytype, diagnostics: ?*Options.Diagnostics) TarReader( |
| 936 | 937 | |
| 937 | 938 | fn TarReader(comptime ReaderType: type) type { |
| 938 | 939 | return struct { |
| 939 | | // scratch buffer for file attributes |
| 940 | | scratch: struct { |
| 941 | | // size: two paths (name and link_name) and files size bytes (24 in pax attribute) |
| 942 | | buffer: [std.fs.MAX_PATH_BYTES * 2 + 24]u8 = undefined, |
| 943 | | tail: usize = 0, |
| 944 | | |
| 945 | | name: []const u8 = undefined, |
| 946 | | link_name: []const u8 = undefined, |
| 947 | | size: usize = 0, |
| 948 | | |
| 949 | | // Allocate size of the buffer for some attribute. |
| 950 | | fn alloc(self: *@This(), size: usize) ![]u8 { |
| 951 | | const free_size = self.buffer.len - self.tail; |
| 952 | | if (size > free_size) return error.TarScratchBufferOverflow; |
| 953 | | const head = self.tail; |
| 954 | | self.tail += size; |
| 955 | | assert(self.tail <= self.buffer.len); |
| 956 | | return self.buffer[head..self.tail]; |
| 957 | | } |
| 958 | | |
| 959 | | // Reset buffer and all fields. |
| 960 | | fn reset(self: *@This()) void { |
| 961 | | self.tail = 0; |
| 962 | | self.name = self.buffer[0..0]; |
| 963 | | self.link_name = self.buffer[0..0]; |
| 964 | | self.size = 0; |
| 965 | | } |
| 966 | | |
| 967 | | fn append(self: *@This(), header: Header) !void { |
| 968 | | if (self.size == 0) self.size = try header.fileSize(); |
| 969 | | if (self.link_name.len == 0) { |
| 970 | | const link_name = header.linkName(); |
| 971 | | if (link_name.len > 0) { |
| 972 | | const buf = try self.alloc(link_name.len); |
| 973 | | @memcpy(buf, link_name); |
| 974 | | self.link_name = buf; |
| 975 | | } |
| 976 | | } |
| 977 | | if (self.name.len == 0) { |
| 978 | | self.name = try header.fullName((try self.alloc(MAX_HEADER_NAME_SIZE))[0..MAX_HEADER_NAME_SIZE]); |
| 979 | | } |
| 980 | | } |
| 981 | | } = .{}, |
| 982 | | |
| 983 | 940 | reader: ReaderType, |
| 984 | 941 | diagnostics: ?*Options.Diagnostics, |
| 985 | | padding: usize = 0, // bytes of padding to the end of the block |
| 986 | | header_buffer: [BLOCK_SIZE]u8 = undefined, |
| 987 | 942 | |
| 988 | | const Self = @This(); |
| 943 | // buffers for heeader and file attributes |
| 944 | header_buffer: [Header.SIZE]u8 = undefined, |
| 945 | file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined, |
| 946 | link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined, |
| 947 | |
| 948 | // bytes of padding to the end of the block |
| 949 | padding: usize = 0, |
| 950 | // current tar file |
| 951 | file: File = undefined, |
| 989 | 952 | |
| 990 | 953 | pub const File = struct { |
| 991 | 954 | name: []const u8, // name of file, symlink or directory |
| ... | ... | @@ -994,14 +957,18 @@ fn TarReader(comptime ReaderType: type) type { |
| 994 | 957 | mode: u32, |
| 995 | 958 | file_type: Header.FileType, |
| 996 | 959 | |
| 997 | | reader: *ReaderType, |
| 960 | reader: ReaderType, |
| 998 | 961 | |
| 999 | 962 | // Writes file content to writer. |
| 1000 | 963 | pub fn write(self: File, writer: anytype) !void { |
| 1001 | | var n = self.size; |
| 1002 | | while (n > 0) : (n -= 1) { |
| 1003 | | const byte: u8 = try self.reader.readByte(); |
| 1004 | | try writer.writeByte(byte); |
| 964 | var buffer: [4096]u8 = undefined; |
| 965 | |
| 966 | var n: usize = 0; |
| 967 | while (n < self.size) { |
| 968 | const buf = buffer[0..@min(buffer.len, self.size - n)]; |
| 969 | try self.reader.readNoEof(buf); |
| 970 | try writer.writeAll(buf); |
| 971 | n += buf.len; |
| 1005 | 972 | } |
| 1006 | 973 | } |
| 1007 | 974 | |
| ... | ... | @@ -1011,34 +978,44 @@ fn TarReader(comptime ReaderType: type) type { |
| 1011 | 978 | } |
| 1012 | 979 | }; |
| 1013 | 980 | |
| 981 | const Self = @This(); |
| 982 | |
| 1014 | 983 | fn readHeader(self: *Self) !?Header { |
| 1015 | 984 | if (self.padding > 0) { |
| 1016 | 985 | try self.reader.skipBytes(self.padding, .{}); |
| 1017 | 986 | } |
| 1018 | 987 | const n = try self.reader.readAll(&self.header_buffer); |
| 1019 | 988 | if (n == 0) return null; |
| 1020 | | if (n < BLOCK_SIZE) return error.UnexpectedEndOfStream; |
| 1021 | | const header = Header{ .bytes = self.header_buffer[0..BLOCK_SIZE] }; |
| 989 | if (n < Header.SIZE) return error.UnexpectedEndOfStream; |
| 990 | const header = Header{ .bytes = self.header_buffer[0..Header.SIZE] }; |
| 1022 | 991 | if (try header.checkChksum() == 0) return null; |
| 1023 | 992 | return header; |
| 1024 | 993 | } |
| 1025 | 994 | |
| 1026 | | fn readString(self: *Self, size: usize) ![]const u8 { |
| 1027 | | const buf = try self.scratch.alloc(size); |
| 995 | inline fn readString(self: *Self, size: usize, buffer: []u8) ![]const u8 { |
| 996 | assert(buffer.len >= size); |
| 997 | const buf = buffer[0..size]; |
| 1028 | 998 | try self.reader.readNoEof(buf); |
| 1029 | 999 | return nullStr(buf); |
| 1030 | 1000 | } |
| 1031 | 1001 | |
| 1032 | | fn reset(self: *Self) void { |
| 1002 | inline fn initFile(self: *Self) void { |
| 1033 | 1003 | self.file = File{ |
| 1034 | 1004 | .name = self.file_name_buffer[0..0], |
| 1035 | 1005 | .link_name = self.link_name_buffer[0..0], |
| 1036 | 1006 | .size = 0, |
| 1037 | | .file_type = 0xff, |
| 1007 | .file_type = .normal, |
| 1038 | 1008 | .mode = 0, |
| 1009 | .reader = self.reader, |
| 1039 | 1010 | }; |
| 1040 | 1011 | } |
| 1041 | 1012 | |
| 1013 | // Number of padding bytes in the last file block. |
| 1014 | inline fn blockPadding(size: usize) usize { |
| 1015 | const block_rounded = std.mem.alignForward(usize, size, Header.SIZE); // size rounded to te block boundary |
| 1016 | return block_rounded - size; |
| 1017 | } |
| 1018 | |
| 1042 | 1019 | // Externally, `next` iterates through the tar archive as if it is a |
| 1043 | 1020 | // series of files. Internally, the tar format often uses fake "files" |
| 1044 | 1021 | // to add meta data that describes the next file. These meta data |
| ... | ... | @@ -1046,7 +1023,7 @@ fn TarReader(comptime ReaderType: type) type { |
| 1046 | 1023 | // loop iterates through one or more "header files" until it finds a |
| 1047 | 1024 | // "normal file". |
| 1048 | 1025 | pub fn next(self: *Self) !?File { |
| 1049 | | self.scratch.reset(); |
| 1026 | self.initFile(); |
| 1050 | 1027 | |
| 1051 | 1028 | while (try self.readHeader()) |header| { |
| 1052 | 1029 | const file_type = header.fileType(); |
| ... | ... | @@ -1056,41 +1033,46 @@ fn TarReader(comptime ReaderType: type) type { |
| 1056 | 1033 | switch (file_type) { |
| 1057 | 1034 | // File types to retrun upstream |
| 1058 | 1035 | .directory, .normal, .symbolic_link => { |
| 1059 | | try self.scratch.append(header); |
| 1060 | | const file = File{ |
| 1061 | | .file_type = file_type, |
| 1062 | | .name = self.scratch.name, |
| 1063 | | .link_name = self.scratch.link_name, |
| 1064 | | .size = self.scratch.size, |
| 1065 | | .reader = &self.reader, |
| 1066 | | .mode = try header.mode(), |
| 1067 | | }; |
| 1068 | | self.padding = blockPadding(file.size); |
| 1069 | | return file; |
| 1036 | self.file.file_type = file_type; |
| 1037 | self.file.mode = try header.mode(); |
| 1038 | |
| 1039 | // set file attributes if not already set by prefix/extended headers |
| 1040 | if (self.file.size == 0) { |
| 1041 | self.file.size = size; |
| 1042 | } |
| 1043 | if (self.file.link_name.len == 0) { |
| 1044 | self.file.link_name = header.linkName(self.link_name_buffer[0..Header.LINK_NAME_SIZE]); |
| 1045 | } |
| 1046 | if (self.file.name.len == 0) { |
| 1047 | self.file.name = try header.fullName(self.file_name_buffer[0..Header.MAX_NAME_SIZE]); |
| 1048 | } |
| 1049 | |
| 1050 | self.padding = blockPadding(self.file.size); |
| 1051 | return self.file; |
| 1070 | 1052 | }, |
| 1071 | 1053 | // Prefix header types |
| 1072 | 1054 | .gnu_long_name => { |
| 1073 | | self.scratch.name = try self.readString(size); |
| 1055 | self.file.name = try self.readString(size, &self.file_name_buffer); |
| 1074 | 1056 | }, |
| 1075 | 1057 | .gnu_long_link => { |
| 1076 | | self.scratch.link_name = try self.readString(size); |
| 1058 | self.file.link_name = try self.readString(size, &self.link_name_buffer); |
| 1077 | 1059 | }, |
| 1078 | 1060 | .extended_header => { |
| 1079 | | if (size == 0) continue; |
| 1080 | 1061 | // Use just attributes from last extended header. |
| 1081 | | self.scratch.reset(); |
| 1062 | self.initFile(); |
| 1082 | 1063 | |
| 1083 | 1064 | var rdr = paxReader(self.reader, size); |
| 1084 | 1065 | while (try rdr.next()) |attr| { |
| 1085 | 1066 | switch (attr.kind) { |
| 1086 | 1067 | .path => { |
| 1087 | | self.scratch.name = try attr.value(try self.scratch.alloc(attr.len)); |
| 1068 | self.file.name = try attr.value(&self.file_name_buffer); |
| 1088 | 1069 | }, |
| 1089 | 1070 | .linkpath => { |
| 1090 | | self.scratch.link_name = try attr.value(try self.scratch.alloc(attr.len)); |
| 1071 | self.file.link_name = try attr.value(&self.link_name_buffer); |
| 1091 | 1072 | }, |
| 1092 | 1073 | .size => { |
| 1093 | | self.scratch.size = try std.fmt.parseInt(usize, try attr.value(try self.scratch.alloc(attr.len)), 10); |
| 1074 | var buf: [64]u8 = undefined; |
| 1075 | self.file.size = try std.fmt.parseInt(usize, try attr.value(&buf), 10); |
| 1094 | 1076 | }, |
| 1095 | 1077 | } |
| 1096 | 1078 | } |