authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2023-12-11 20:18:59+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-13 19:37:33-07:00
log4a6d67ab1a26e0c89d55453877c1fd8b03ab1976
tree93d329ebeac6d46da461d6fef145bcb7a7773e46
parent9f7dd323082941d66c18af3da88a432835c5e3e5

tar: remove stratch from tar reader

Use explicit buffers for name, link_name instead. It is cleaner that way.

1 files changed, 74 insertions(+), 92 deletions(-)

lib/std/tar.zig+74-92
...@@ -83,11 +83,12 @@ pub const Options = struct {...@@ -83,11 +83,12 @@ pub const Options = struct {
83 };83 };
84};84};
8585
86const BLOCK_SIZE = 512;
87const MAX_HEADER_NAME_SIZE = 100 + 1 + 155; // name(100) + separator(1) + prefix(155)
88
89pub const Header = struct {86pub 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,
9192
92 pub const FileType = enum(u8) {93 pub const FileType = enum(u8) {
93 normal_alias = 0,94 normal_alias = 0,
...@@ -110,7 +111,7 @@ pub const Header = struct {...@@ -110,7 +111,7 @@ pub const Header = struct {
110111
111 /// Includes prefix concatenated, if any.112 /// Includes prefix concatenated, if any.
112 /// TODO: check against "../" and other nefarious things113 /// 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 const n = name(header);115 const n = name(header);
115 const p = prefix(header);116 const p = prefix(header);
116 if (!is_ustar(header) or p.len == 0) {117 if (!is_ustar(header) or p.len == 0) {
...@@ -123,6 +124,16 @@ pub const Header = struct {...@@ -123,6 +124,16 @@ pub const Header = struct {
123 return buffer[0 .. p.len + 1 + n.len];124 return buffer[0 .. p.len + 1 + n.len];
124 }125 }
125126
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 pub fn name(header: Header) []const u8 {137 pub fn name(header: Header) []const u8 {
127 return header.str(0, 100);138 return header.str(0, 100);
128 }139 }
...@@ -139,10 +150,6 @@ pub const Header = struct {...@@ -139,10 +150,6 @@ pub const Header = struct {
139 return header.octal(148, 8);150 return header.octal(148, 8);
140 }151 }
141152
142 pub fn linkName(header: Header) []const u8 {
143 return header.str(157, 100);
144 }
145
146 pub fn is_ustar(header: Header) bool {153 pub fn is_ustar(header: Header) bool {
147 const magic = header.bytes[257..][0..6];154 const magic = header.bytes[257..][0..6];
148 return std.mem.eql(u8, magic[0..5], "ustar") and (magic[5] == 0 or magic[5] == ' ');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,12 +226,6 @@ fn nullStr(str: []const u8) []const u8 {
219 return str;226 return str;
220}227}
221228
222// Number of padding bytes in the last file block.
223inline 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
228pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void {229pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void {
229 switch (options.mode_mode) {230 switch (options.mode_mode) {
230 .ignore => {},231 .ignore => {},
...@@ -936,56 +937,18 @@ pub fn tarReader(reader: anytype, diagnostics: ?*Options.Diagnostics) TarReader(...@@ -936,56 +937,18 @@ pub fn tarReader(reader: anytype, diagnostics: ?*Options.Diagnostics) TarReader(
936937
937fn TarReader(comptime ReaderType: type) type {938fn TarReader(comptime ReaderType: type) type {
938 return struct {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 reader: ReaderType,940 reader: ReaderType,
984 diagnostics: ?*Options.Diagnostics,941 diagnostics: ?*Options.Diagnostics,
985 padding: usize = 0, // bytes of padding to the end of the block
986 header_buffer: [BLOCK_SIZE]u8 = undefined,
987942
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,
989952
990 pub const File = struct {953 pub const File = struct {
991 name: []const u8, // name of file, symlink or directory954 name: []const u8, // name of file, symlink or directory
...@@ -994,14 +957,18 @@ fn TarReader(comptime ReaderType: type) type {...@@ -994,14 +957,18 @@ fn TarReader(comptime ReaderType: type) type {
994 mode: u32,957 mode: u32,
995 file_type: Header.FileType,958 file_type: Header.FileType,
996959
997 reader: *ReaderType,960 reader: ReaderType,
998961
999 // Writes file content to writer.962 // Writes file content to writer.
1000 pub fn write(self: File, writer: anytype) !void {963 pub fn write(self: File, writer: anytype) !void {
1001 var n = self.size;964 var buffer: [4096]u8 = undefined;
1002 while (n > 0) : (n -= 1) {965
1003 const byte: u8 = try self.reader.readByte();966 var n: usize = 0;
1004 try writer.writeByte(byte);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 }
1007974
...@@ -1011,34 +978,44 @@ fn TarReader(comptime ReaderType: type) type {...@@ -1011,34 +978,44 @@ fn TarReader(comptime ReaderType: type) type {
1011 }978 }
1012 };979 };
1013980
981 const Self = @This();
982
1014 fn readHeader(self: *Self) !?Header {983 fn readHeader(self: *Self) !?Header {
1015 if (self.padding > 0) {984 if (self.padding > 0) {
1016 try self.reader.skipBytes(self.padding, .{});985 try self.reader.skipBytes(self.padding, .{});
1017 }986 }
1018 const n = try self.reader.readAll(&self.header_buffer);987 const n = try self.reader.readAll(&self.header_buffer);
1019 if (n == 0) return null;988 if (n == 0) return null;
1020 if (n < BLOCK_SIZE) return error.UnexpectedEndOfStream;989 if (n < Header.SIZE) return error.UnexpectedEndOfStream;
1021 const header = Header{ .bytes = self.header_buffer[0..BLOCK_SIZE] };990 const header = Header{ .bytes = self.header_buffer[0..Header.SIZE] };
1022 if (try header.checkChksum() == 0) return null;991 if (try header.checkChksum() == 0) return null;
1023 return header;992 return header;
1024 }993 }
1025994
1026 fn readString(self: *Self, size: usize) ![]const u8 {995 inline fn readString(self: *Self, size: usize, buffer: []u8) ![]const u8 {
1027 const buf = try self.scratch.alloc(size);996 assert(buffer.len >= size);
997 const buf = buffer[0..size];
1028 try self.reader.readNoEof(buf);998 try self.reader.readNoEof(buf);
1029 return nullStr(buf);999 return nullStr(buf);
1030 }1000 }
10311001
1032 fn reset(self: *Self) void {1002 inline fn initFile(self: *Self) void {
1033 self.file = File{1003 self.file = File{
1034 .name = self.file_name_buffer[0..0],1004 .name = self.file_name_buffer[0..0],
1035 .link_name = self.link_name_buffer[0..0],1005 .link_name = self.link_name_buffer[0..0],
1036 .size = 0,1006 .size = 0,
1037 .file_type = 0xff,1007 .file_type = .normal,
1038 .mode = 0,1008 .mode = 0,
1009 .reader = self.reader,
1039 };1010 };
1040 }1011 }
10411012
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 // Externally, `next` iterates through the tar archive as if it is a1019 // Externally, `next` iterates through the tar archive as if it is a
1043 // series of files. Internally, the tar format often uses fake "files"1020 // series of files. Internally, the tar format often uses fake "files"
1044 // to add meta data that describes the next file. These meta data1021 // to add meta data that describes the next file. These meta data
...@@ -1046,7 +1023,7 @@ fn TarReader(comptime ReaderType: type) type {...@@ -1046,7 +1023,7 @@ fn TarReader(comptime ReaderType: type) type {
1046 // loop iterates through one or more "header files" until it finds a1023 // loop iterates through one or more "header files" until it finds a
1047 // "normal file".1024 // "normal file".
1048 pub fn next(self: *Self) !?File {1025 pub fn next(self: *Self) !?File {
1049 self.scratch.reset();1026 self.initFile();
10501027
1051 while (try self.readHeader()) |header| {1028 while (try self.readHeader()) |header| {
1052 const file_type = header.fileType();1029 const file_type = header.fileType();
...@@ -1056,41 +1033,46 @@ fn TarReader(comptime ReaderType: type) type {...@@ -1056,41 +1033,46 @@ fn TarReader(comptime ReaderType: type) type {
1056 switch (file_type) {1033 switch (file_type) {
1057 // File types to retrun upstream1034 // File types to retrun upstream
1058 .directory, .normal, .symbolic_link => {1035 .directory, .normal, .symbolic_link => {
1059 try self.scratch.append(header);1036 self.file.file_type = file_type;
1060 const file = File{1037 self.file.mode = try header.mode();
1061 .file_type = file_type,1038
1062 .name = self.scratch.name,1039 // set file attributes if not already set by prefix/extended headers
1063 .link_name = self.scratch.link_name,1040 if (self.file.size == 0) {
1064 .size = self.scratch.size,1041 self.file.size = size;
1065 .reader = &self.reader,1042 }
1066 .mode = try header.mode(),1043 if (self.file.link_name.len == 0) {
1067 };1044 self.file.link_name = header.linkName(self.link_name_buffer[0..Header.LINK_NAME_SIZE]);
1068 self.padding = blockPadding(file.size);1045 }
1069 return file;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 // Prefix header types1053 // Prefix header types
1072 .gnu_long_name => {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 .gnu_long_link => {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 .extended_header => {1060 .extended_header => {
1079 if (size == 0) continue;
1080 // Use just attributes from last extended header.1061 // Use just attributes from last extended header.
1081 self.scratch.reset();1062 self.initFile();
10821063
1083 var rdr = paxReader(self.reader, size);1064 var rdr = paxReader(self.reader, size);
1084 while (try rdr.next()) |attr| {1065 while (try rdr.next()) |attr| {
1085 switch (attr.kind) {1066 switch (attr.kind) {
1086 .path => {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 .linkpath => {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 .size => {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 }