| ... | @@ -174,6 +174,144 @@ const Buffer = struct { | ... | @@ -174,6 +174,144 @@ const Buffer = struct { |
| 174 | } | 174 | } |
| 175 | }; | 175 | }; |
| 176 | | 176 | |
| | 177 | fn Iterator(comptime ReaderType: type) type { |
| | 178 | return struct { |
| | 179 | file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined, |
| | 180 | file_name_len: usize = 0, |
| | 181 | buffer: Buffer = .{}, |
| | 182 | reader: ReaderType, |
| | 183 | pad_len: usize = 0, |
| | 184 | diagnostics: ?*Options.Diagnostics, |
| | 185 | |
| | 186 | const Self = @This(); |
| | 187 | |
| | 188 | const File = struct { |
| | 189 | file_name: []const u8, |
| | 190 | link_name: []const u8, |
| | 191 | size: usize, |
| | 192 | file_type: Header.FileType, |
| | 193 | iter: *Self, |
| | 194 | |
| | 195 | pub fn write(self: File, writer: anytype) !void { |
| | 196 | const rounded_file_size = std.mem.alignForward(u64, self.size, 512); |
| | 197 | var file_off: usize = 0; |
| | 198 | while (true) { |
| | 199 | const temp = try self.iter.buffer.readChunk(self.iter.reader, @intCast(rounded_file_size + 512 - file_off)); |
| | 200 | if (temp.len == 0) return error.UnexpectedEndOfStream; |
| | 201 | const slice = temp[0..@intCast(@min(self.size - file_off, temp.len))]; |
| | 202 | try writer.writeAll(slice); |
| | 203 | |
| | 204 | file_off += slice.len; |
| | 205 | self.iter.buffer.advance(slice.len); |
| | 206 | if (file_off >= self.size) { |
| | 207 | return; |
| | 208 | // self.iter.buffer.advance(pad_len); |
| | 209 | // continue :header; |
| | 210 | } |
| | 211 | } |
| | 212 | } |
| | 213 | |
| | 214 | pub fn skip(self: File) void { |
| | 215 | _ = self; |
| | 216 | unreachable; |
| | 217 | } |
| | 218 | }; |
| | 219 | |
| | 220 | pub fn next(self: *Self) !?File { |
| | 221 | self.buffer.advance(self.pad_len); |
| | 222 | self.pad_len = 0; |
| | 223 | self.file_name_len = 0; |
| | 224 | |
| | 225 | while (true) { |
| | 226 | const chunk = try self.buffer.readChunk(self.reader, 1024); |
| | 227 | switch (chunk.len) { |
| | 228 | 0 => return null, |
| | 229 | 1...511 => return error.UnexpectedEndOfStream, |
| | 230 | else => {}, |
| | 231 | } |
| | 232 | self.buffer.advance(512); |
| | 233 | |
| | 234 | const header: Header = .{ .bytes = chunk[0..512] }; |
| | 235 | const file_size = try header.fileSize(); |
| | 236 | const file_type = header.fileType(); |
| | 237 | const link_name = header.linkName(); |
| | 238 | const rounded_file_size = std.mem.alignForward(u64, file_size, 512); |
| | 239 | self.pad_len = @intCast(rounded_file_size - file_size); |
| | 240 | const file_name = if (self.file_name_len == 0) |
| | 241 | try header.fullFileName(&self.file_name_buffer) |
| | 242 | else |
| | 243 | self.file_name_buffer[0..self.file_name_len]; |
| | 244 | |
| | 245 | switch (file_type) { |
| | 246 | .directory, .normal, .symbolic_link => { |
| | 247 | return File{ |
| | 248 | .file_name = file_name, |
| | 249 | .link_name = link_name, |
| | 250 | .size = file_size, |
| | 251 | .file_type = file_type, |
| | 252 | .iter = self, |
| | 253 | }; |
| | 254 | }, |
| | 255 | .global_extended_header => { |
| | 256 | self.buffer.skip(self.reader, @intCast(rounded_file_size)) catch return error.TarHeadersTooBig; |
| | 257 | }, |
| | 258 | .extended_header => { |
| | 259 | if (file_size == 0) { |
| | 260 | self.buffer.advance(@intCast(rounded_file_size)); |
| | 261 | continue; |
| | 262 | } |
| | 263 | |
| | 264 | const chunk_size: usize = @intCast(rounded_file_size + 512); |
| | 265 | var data_off: usize = 0; |
| | 266 | const file_name_override_len = while (data_off < file_size) { |
| | 267 | const slice = try self.buffer.readChunk(self.reader, chunk_size - data_off); |
| | 268 | if (slice.len == 0) return error.UnexpectedEndOfStream; |
| | 269 | const remaining_size: usize = @intCast(file_size - data_off); |
| | 270 | const attr_info = try parsePaxAttribute(slice[0..@min(remaining_size, slice.len)], remaining_size); |
| | 271 | |
| | 272 | if (std.mem.eql(u8, attr_info.key, "path")) { |
| | 273 | if (attr_info.value_len > self.file_name_buffer.len) return error.NameTooLong; |
| | 274 | self.buffer.advance(attr_info.value_off); |
| | 275 | data_off += attr_info.value_off; |
| | 276 | break attr_info.value_len; |
| | 277 | } |
| | 278 | |
| | 279 | try self.buffer.skip(self.reader, attr_info.size); |
| | 280 | data_off += attr_info.size; |
| | 281 | } else 0; |
| | 282 | |
| | 283 | var i: usize = 0; |
| | 284 | while (i < file_name_override_len) { |
| | 285 | const slice = try self.buffer.readChunk(self.reader, chunk_size - data_off - i); |
| | 286 | if (slice.len == 0) return error.UnexpectedEndOfStream; |
| | 287 | const copy_size: usize = @intCast(@min(file_name_override_len - i, slice.len)); |
| | 288 | @memcpy(self.file_name_buffer[i .. i + copy_size], slice[0..copy_size]); |
| | 289 | self.buffer.advance(copy_size); |
| | 290 | i += copy_size; |
| | 291 | } |
| | 292 | |
| | 293 | try self.buffer.skip(self.reader, @intCast(rounded_file_size - data_off - file_name_override_len)); |
| | 294 | self.file_name_len = file_name_override_len; |
| | 295 | continue; |
| | 296 | }, |
| | 297 | .hard_link => return error.TarUnsupportedFileType, |
| | 298 | else => { |
| | 299 | const d = self.diagnostics orelse return error.TarUnsupportedFileType; |
| | 300 | try d.errors.append(d.allocator, .{ .unsupported_file_type = .{ |
| | 301 | .file_name = try d.allocator.dupe(u8, file_name), |
| | 302 | .file_type = file_type, |
| | 303 | } }); |
| | 304 | }, |
| | 305 | } |
| | 306 | } |
| | 307 | } |
| | 308 | }; |
| | 309 | } |
| | 310 | |
| | 311 | pub fn iterator(reader: anytype, diagnostics: ?*Options.Diagnostics) Iterator(@TypeOf(reader)) { |
| | 312 | return .{ .reader = reader, .diagnostics = diagnostics }; |
| | 313 | } |
| | 314 | |
| 177 | pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void { | 315 | pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void { |
| 178 | switch (options.mode_mode) { | 316 | switch (options.mode_mode) { |
| 179 | .ignore => {}, | 317 | .ignore => {}, |
| ... | @@ -186,37 +324,20 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi | ... | @@ -186,37 +324,20 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi |
| 186 | @panic("TODO: unimplemented: tar ModeMode.executable_bit_only"); | 324 | @panic("TODO: unimplemented: tar ModeMode.executable_bit_only"); |
| 187 | }, | 325 | }, |
| 188 | } | 326 | } |
| 189 | var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; | 327 | |
| 190 | var file_name_override_len: usize = 0; | 328 | var iter = iterator(reader, options.diagnostics); |
| 191 | var buffer: Buffer = .{}; | 329 | |
| 192 | header: while (true) { | 330 | while (try iter.next()) |iter_file| { |
| 193 | const chunk = try buffer.readChunk(reader, 1024); | 331 | switch (iter_file.file_type) { |
| 194 | switch (chunk.len) { | | |
| 195 | 0 => return, | | |
| 196 | 1...511 => return error.UnexpectedEndOfStream, | | |
| 197 | else => {}, | | |
| 198 | } | | |
| 199 | buffer.advance(512); | | |
| 200 | | | |
| 201 | const header: Header = .{ .bytes = chunk[0..512] }; | | |
| 202 | const file_size = try header.fileSize(); | | |
| 203 | const rounded_file_size = std.mem.alignForward(u64, file_size, 512); | | |
| 204 | const pad_len: usize = @intCast(rounded_file_size - file_size); | | |
| 205 | const unstripped_file_name = if (file_name_override_len > 0) | | |
| 206 | file_name_buffer[0..file_name_override_len] | | |
| 207 | else | | |
| 208 | try header.fullFileName(&file_name_buffer); | | |
| 209 | file_name_override_len = 0; | | |
| 210 | switch (header.fileType()) { | | |
| 211 | .directory => { | 332 | .directory => { |
| 212 | const file_name = try stripComponents(unstripped_file_name, options.strip_components); | 333 | const file_name = try stripComponents(iter_file.file_name, options.strip_components); |
| 213 | if (file_name.len != 0 and !options.exclude_empty_directories) { | 334 | if (file_name.len != 0 and !options.exclude_empty_directories) { |
| 214 | try dir.makePath(file_name); | 335 | try dir.makePath(file_name); |
| 215 | } | 336 | } |
| 216 | }, | 337 | }, |
| 217 | .normal => { | 338 | .normal => { |
| 218 | if (file_size == 0 and unstripped_file_name.len == 0) return; | 339 | if (iter_file.size == 0 and iter_file.file_name.len == 0) return; |
| 219 | const file_name = try stripComponents(unstripped_file_name, options.strip_components); | 340 | const file_name = try stripComponents(iter_file.file_name, options.strip_components); |
| 220 | | 341 | |
| 221 | const file = dir.createFile(file_name, .{}) catch |err| switch (err) { | 342 | const file = dir.createFile(file_name, .{}) catch |err| switch (err) { |
| 222 | error.FileNotFound => again: { | 343 | error.FileNotFound => again: { |
| ... | @@ -240,68 +361,17 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi | ... | @@ -240,68 +361,17 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi |
| 240 | }; | 361 | }; |
| 241 | defer if (file) |f| f.close(); | 362 | defer if (file) |f| f.close(); |
| 242 | | 363 | |
| 243 | var file_off: usize = 0; | 364 | if (file) |f| { |
| 244 | while (true) { | 365 | try iter_file.write(f); |
| 245 | const temp = try buffer.readChunk(reader, @intCast(rounded_file_size + 512 - file_off)); | 366 | } else { |
| 246 | if (temp.len == 0) return error.UnexpectedEndOfStream; | 367 | iter_file.skip(); |
| 247 | const slice = temp[0..@intCast(@min(file_size - file_off, temp.len))]; | | |
| 248 | if (file) |f| try f.writeAll(slice); | | |
| 249 | | | |
| 250 | file_off += slice.len; | | |
| 251 | buffer.advance(slice.len); | | |
| 252 | if (file_off >= file_size) { | | |
| 253 | buffer.advance(pad_len); | | |
| 254 | continue :header; | | |
| 255 | } | | |
| 256 | } | 368 | } |
| 257 | }, | 369 | }, |
| 258 | .extended_header => { | | |
| 259 | if (file_size == 0) { | | |
| 260 | buffer.advance(@intCast(rounded_file_size)); | | |
| 261 | continue; | | |
| 262 | } | | |
| 263 | | | |
| 264 | const chunk_size: usize = @intCast(rounded_file_size + 512); | | |
| 265 | var data_off: usize = 0; | | |
| 266 | file_name_override_len = while (data_off < file_size) { | | |
| 267 | const slice = try buffer.readChunk(reader, chunk_size - data_off); | | |
| 268 | if (slice.len == 0) return error.UnexpectedEndOfStream; | | |
| 269 | const remaining_size: usize = @intCast(file_size - data_off); | | |
| 270 | const attr_info = try parsePaxAttribute(slice[0..@min(remaining_size, slice.len)], remaining_size); | | |
| 271 | | | |
| 272 | if (std.mem.eql(u8, attr_info.key, "path")) { | | |
| 273 | if (attr_info.value_len > file_name_buffer.len) return error.NameTooLong; | | |
| 274 | buffer.advance(attr_info.value_off); | | |
| 275 | data_off += attr_info.value_off; | | |
| 276 | break attr_info.value_len; | | |
| 277 | } | | |
| 278 | | | |
| 279 | try buffer.skip(reader, attr_info.size); | | |
| 280 | data_off += attr_info.size; | | |
| 281 | } else 0; | | |
| 282 | | | |
| 283 | var i: usize = 0; | | |
| 284 | while (i < file_name_override_len) { | | |
| 285 | const slice = try buffer.readChunk(reader, chunk_size - data_off - i); | | |
| 286 | if (slice.len == 0) return error.UnexpectedEndOfStream; | | |
| 287 | const copy_size: usize = @intCast(@min(file_name_override_len - i, slice.len)); | | |
| 288 | @memcpy(file_name_buffer[i .. i + copy_size], slice[0..copy_size]); | | |
| 289 | buffer.advance(copy_size); | | |
| 290 | i += copy_size; | | |
| 291 | } | | |
| 292 | | | |
| 293 | try buffer.skip(reader, @intCast(rounded_file_size - data_off - file_name_override_len)); | | |
| 294 | continue :header; | | |
| 295 | }, | | |
| 296 | .global_extended_header => { | | |
| 297 | buffer.skip(reader, @intCast(rounded_file_size)) catch return error.TarHeadersTooBig; | | |
| 298 | }, | | |
| 299 | .hard_link => return error.TarUnsupportedFileType, | | |
| 300 | .symbolic_link => { | 370 | .symbolic_link => { |
| 301 | // The file system path of the symbolic link. | 371 | // The file system path of the symbolic link. |
| 302 | const file_name = try stripComponents(unstripped_file_name, options.strip_components); | 372 | const file_name = try stripComponents(iter_file.file_name, options.strip_components); |
| 303 | // The data inside the symbolic link. | 373 | // The data inside the symbolic link. |
| 304 | const link_name = header.linkName(); | 374 | const link_name = iter_file.link_name; |
| 305 | | 375 | |
| 306 | dir.symLink(link_name, file_name, .{}) catch |err| again: { | 376 | dir.symLink(link_name, file_name, .{}) catch |err| again: { |
| 307 | const code = code: { | 377 | const code = code: { |
| ... | @@ -323,13 +393,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi | ... | @@ -323,13 +393,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi |
| 323 | } }); | 393 | } }); |
| 324 | }; | 394 | }; |
| 325 | }, | 395 | }, |
| 326 | else => |file_type| { | 396 | else => unreachable, |
| 327 | const d = options.diagnostics orelse return error.TarUnsupportedFileType; | | |
| 328 | try d.errors.append(d.allocator, .{ .unsupported_file_type = .{ | | |
| 329 | .file_name = try d.allocator.dupe(u8, unstripped_file_name), | | |
| 330 | .file_type = file_type, | | |
| 331 | } }); | | |
| 332 | }, | | |
| 333 | } | 397 | } |
| 334 | } | 398 | } |
| 335 | } | 399 | } |