diff options
Diffstat (limited to 'lib/std/tar.zig')
| -rw-r--r-- | lib/std/tar.zig | 157 |
1 files changed, 79 insertions, 78 deletions
diff --git a/lib/std/tar.zig b/lib/std/tar.zig index bf96aed35c..024a425919 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -16,6 +16,7 @@ //! pax reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13 const std = @import("std"); +const Io = std.Io; const assert = std.debug.assert; const testing = std.testing; @@ -302,7 +303,7 @@ pub const FileKind = enum { /// Iterator over entries in the tar file represented by reader. pub const Iterator = struct { - reader: *std.Io.Reader, + reader: *Io.Reader, diagnostics: ?*Diagnostics = null, // buffers for heeader and file attributes @@ -328,7 +329,7 @@ pub const Iterator = struct { /// Iterates over files in tar archive. /// `next` returns each file in tar archive. - pub fn init(reader: *std.Io.Reader, options: Options) Iterator { + pub fn init(reader: *Io.Reader, options: Options) Iterator { return .{ .reader = reader, .diagnostics = options.diagnostics, @@ -473,7 +474,7 @@ pub const Iterator = struct { return null; } - pub fn streamRemaining(it: *Iterator, file: File, w: *std.Io.Writer) std.Io.Reader.StreamError!void { + pub fn streamRemaining(it: *Iterator, file: File, w: *Io.Writer) Io.Reader.StreamError!void { try it.reader.streamExact64(w, file.size); it.unread_file_bytes = 0; } @@ -499,14 +500,14 @@ const pax_max_size_attr_len = 64; pub const PaxIterator = struct { size: usize, // cumulative size of all pax attributes - reader: *std.Io.Reader, + reader: *Io.Reader, const Self = @This(); const Attribute = struct { kind: PaxAttributeKind, len: usize, // length of the attribute value - reader: *std.Io.Reader, // reader positioned at value start + reader: *Io.Reader, // reader positioned at value start // Copies pax attribute value into destination buffer. // Must be called with destination buffer of size at least Attribute.len. @@ -573,13 +574,13 @@ pub const PaxIterator = struct { } // Checks that each record ends with new line. - fn validateAttributeEnding(reader: *std.Io.Reader) !void { + fn validateAttributeEnding(reader: *Io.Reader) !void { if (try reader.takeByte() != '\n') return error.PaxInvalidAttributeEnd; } }; /// Saves tar file content to the file systems. -pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOptions) !void { +pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOptions) !void { var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; var file_contents_buffer: [1024]u8 = undefined; @@ -605,13 +606,13 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp switch (file.kind) { .directory => { if (file_name.len > 0 and !options.exclude_empty_directories) { - try dir.makePath(file_name); + try dir.createDirPath(io, file_name); } }, .file => { - if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| { - defer fs_file.close(); - var file_writer = fs_file.writer(&file_contents_buffer); + if (createDirAndFile(io, dir, file_name, filePermissions(file.mode, options))) |fs_file| { + defer fs_file.close(io); + var file_writer = fs_file.writer(io, &file_contents_buffer); try it.streamRemaining(file, &file_writer.interface); try file_writer.interface.flush(); } else |err| { @@ -624,7 +625,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp }, .sym_link => { const link_name = file.link_name; - createDirAndSymlink(dir, link_name, file_name) catch |err| { + createDirAndSymlink(io, dir, link_name, file_name) catch |err| { const d = options.diagnostics orelse return error.UnableToCreateSymLink; try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{ .code = err, @@ -637,12 +638,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp } } -fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mode) !std.fs.File { - const fs_file = dir.createFile(file_name, .{ .exclusive = true, .mode = mode }) catch |err| { +fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, permissions: Io.File.Permissions) !Io.File { + const fs_file = dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions }) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { - try dir.makePath(dir_name); - return try dir.createFile(file_name, .{ .exclusive = true, .mode = mode }); + try dir.createDirPath(io, dir_name); + return try dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions }); } } return err; @@ -651,12 +652,12 @@ fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mo } // Creates a symbolic link at path `file_name` which points to `link_name`. -fn createDirAndSymlink(dir: std.fs.Dir, link_name: []const u8, file_name: []const u8) !void { - dir.symLink(link_name, file_name, .{}) catch |err| { +fn createDirAndSymlink(io: Io, dir: Io.Dir, link_name: []const u8, file_name: []const u8) !void { + dir.symLink(io, link_name, file_name, .{}) catch |err| { if (err == error.FileNotFound) { if (std.fs.path.dirname(file_name)) |dir_name| { - try dir.makePath(dir_name); - return try dir.symLink(link_name, file_name, .{}); + try dir.createDirPath(io, dir_name); + return try dir.symLink(io, link_name, file_name, .{}); } } return err; @@ -783,7 +784,7 @@ test PaxIterator { var buffer: [1024]u8 = undefined; outer: for (cases) |case| { - var reader: std.Io.Reader = .fixed(case.data); + var reader: Io.Reader = .fixed(case.data); var it: PaxIterator = .{ .size = case.data.len, .reader = &reader, @@ -874,25 +875,27 @@ test "header parse mode" { } test "create file and symlink" { + const io = testing.io; + var root = testing.tmpDir(.{}); defer root.cleanup(); - var file = try createDirAndFile(root.dir, "file1", default_mode); - file.close(); - file = try createDirAndFile(root.dir, "a/b/c/file2", default_mode); - file.close(); + var file = try createDirAndFile(io, root.dir, "file1", .default_file); + file.close(io); + file = try createDirAndFile(io, root.dir, "a/b/c/file2", .default_file); + file.close(io); - createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| { + createDirAndSymlink(io, root.dir, "a/b/c/file2", "symlink1") catch |err| { // On Windows when developer mode is not enabled if (err == error.AccessDenied) return error.SkipZigTest; return err; }; - try createDirAndSymlink(root.dir, "../../../file1", "d/e/f/symlink2"); + try createDirAndSymlink(io, root.dir, "../../../file1", "d/e/f/symlink2"); // Danglink symlnik, file created later - try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3"); - file = try createDirAndFile(root.dir, "g/h/i/file4", default_mode); - file.close(); + try createDirAndSymlink(io, root.dir, "../../../g/h/i/file4", "j/k/l/symlink3"); + file = try createDirAndFile(io, root.dir, "g/h/i/file4", .default_file); + file.close(io); } test Iterator { @@ -916,7 +919,7 @@ test Iterator { // example/empty/ const data = @embedFile("tar/testdata/example.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); // User provided buffers to the iterator var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; @@ -942,7 +945,7 @@ test Iterator { .file => { try testing.expectEqualStrings("example/a/file", file.name); var buf: [16]u8 = undefined; - var w: std.Io.Writer = .fixed(&buf); + var w: Io.Writer = .fixed(&buf); try it.streamRemaining(file, &w); try testing.expectEqualStrings("content\n", w.buffered()); }, @@ -955,6 +958,7 @@ test Iterator { } test pipeToFileSystem { + const io = testing.io; // Example tar file is created from this tree structure: // $ tree example // example @@ -975,14 +979,14 @@ test pipeToFileSystem { // example/empty/ const data = @embedFile("tar/testdata/example.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); var tmp = testing.tmpDir(.{ .follow_symlinks = false }); defer tmp.cleanup(); const dir = tmp.dir; // Save tar from reader to the file system `dir` - pipeToFileSystem(dir, &reader, .{ + pipeToFileSystem(io, dir, &reader, .{ .mode_mode = .ignore, .strip_components = 1, .exclude_empty_directories = true, @@ -992,21 +996,22 @@ test pipeToFileSystem { return err; }; - try testing.expectError(error.FileNotFound, dir.statFile("empty")); - try testing.expect((try dir.statFile("a/file")).kind == .file); - try testing.expect((try dir.statFile("b/symlink")).kind == .file); // statFile follows symlink + try testing.expectError(error.FileNotFound, dir.statFile(io, "empty", .{})); + try testing.expect((try dir.statFile(io, "a/file", .{})).kind == .file); + try testing.expect((try dir.statFile(io, "b/symlink", .{})).kind == .file); // statFile follows symlink var buf: [32]u8 = undefined; try testing.expectEqualSlices( u8, "../a/file", - normalizePath(try dir.readLink("b/symlink", &buf)), + normalizePath(buf[0..try dir.readLink(io, "b/symlink", &buf)]), ); } test "pipeToFileSystem root_dir" { + const io = testing.io; const data = @embedFile("tar/testdata/example.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); // with strip_components = 1 { @@ -1015,7 +1020,7 @@ test "pipeToFileSystem root_dir" { var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - pipeToFileSystem(tmp.dir, &reader, .{ + pipeToFileSystem(io, tmp.dir, &reader, .{ .strip_components = 1, .diagnostics = &diagnostics, }) catch |err| { @@ -1037,7 +1042,7 @@ test "pipeToFileSystem root_dir" { var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - pipeToFileSystem(tmp.dir, &reader, .{ + pipeToFileSystem(io, tmp.dir, &reader, .{ .strip_components = 0, .diagnostics = &diagnostics, }) catch |err| { @@ -1053,43 +1058,46 @@ test "pipeToFileSystem root_dir" { } test "findRoot with single file archive" { + const io = testing.io; const data = @embedFile("tar/testdata/22752.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - try pipeToFileSystem(tmp.dir, &reader, .{ .diagnostics = &diagnostics }); + try pipeToFileSystem(io, tmp.dir, &reader, .{ .diagnostics = &diagnostics }); try testing.expectEqualStrings("", diagnostics.root_dir); } test "findRoot without explicit root dir" { + const io = testing.io; const data = @embedFile("tar/testdata/19820.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - try pipeToFileSystem(tmp.dir, &reader, .{ .diagnostics = &diagnostics }); + try pipeToFileSystem(io, tmp.dir, &reader, .{ .diagnostics = &diagnostics }); try testing.expectEqualStrings("root", diagnostics.root_dir); } test "pipeToFileSystem strip_components" { + const io = testing.io; const data = @embedFile("tar/testdata/example.tar"); - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); var tmp = testing.tmpDir(.{ .follow_symlinks = false }); defer tmp.cleanup(); var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); - pipeToFileSystem(tmp.dir, &reader, .{ + pipeToFileSystem(io, tmp.dir, &reader, .{ .strip_components = 3, .diagnostics = &diagnostics, }) catch |err| { @@ -1110,45 +1118,36 @@ fn normalizePath(bytes: []u8) []u8 { return bytes; } -const default_mode = std.fs.File.default_mode; - // File system mode based on tar header mode and mode_mode options. -fn fileMode(mode: u32, options: PipeOptions) std.fs.File.Mode { - if (!std.fs.has_executable_bit or options.mode_mode == .ignore) - return default_mode; - - const S = std.posix.S; - - // The mode from the tar file is inspected for the owner executable bit. - if (mode & S.IXUSR == 0) - return default_mode; - - // This bit is copied to the group and other executable bits. - // Other bits of the mode are left as the default when creating files. - return default_mode | S.IXUSR | S.IXGRP | S.IXOTH; +fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { + return if (!Io.File.Permissions.has_executable_bit or options.mode_mode == .ignore or (mode & 0o100) == 0) + .default_file + else + .executable_file; } -test fileMode { - if (!std.fs.has_executable_bit) return error.SkipZigTest; - try testing.expectEqual(default_mode, fileMode(0o744, PipeOptions{ .mode_mode = .ignore })); - try testing.expectEqual(0o777, fileMode(0o744, PipeOptions{})); - try testing.expectEqual(0o666, fileMode(0o644, PipeOptions{})); - try testing.expectEqual(0o666, fileMode(0o655, PipeOptions{})); +test filePermissions { + if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; + try testing.expectEqual(.default_file, filePermissions(0o744, .{ .mode_mode = .ignore })); + try testing.expectEqual(.executable_file, filePermissions(0o744, .{})); + try testing.expectEqual(.default_file, filePermissions(0o644, .{})); + try testing.expectEqual(.default_file, filePermissions(0o655, .{})); } test "executable bit" { - if (!std.fs.has_executable_bit) return error.SkipZigTest; + if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; + const io = testing.io; const S = std.posix.S; const data = @embedFile("tar/testdata/example.tar"); for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| { - var reader: std.Io.Reader = .fixed(data); + var reader: Io.Reader = .fixed(data); var tmp = testing.tmpDir(.{ .follow_symlinks = false }); //defer tmp.cleanup(); - pipeToFileSystem(tmp.dir, &reader, .{ + pipeToFileSystem(io, tmp.dir, &reader, .{ .strip_components = 1, .exclude_empty_directories = true, .mode_mode = opt, @@ -1158,19 +1157,21 @@ test "executable bit" { return err; }; - const fs = try tmp.dir.statFile("a/file"); + const fs = try tmp.dir.statFile(io, "a/file", .{}); try testing.expect(fs.kind == .file); + const mode = fs.permissions.toMode(); + if (opt == .executable_bit_only) { // Executable bit is set for user, group and others - try testing.expect(fs.mode & S.IXUSR > 0); - try testing.expect(fs.mode & S.IXGRP > 0); - try testing.expect(fs.mode & S.IXOTH > 0); + try testing.expect(mode & S.IXUSR > 0); + try testing.expect(mode & S.IXGRP > 0); + try testing.expect(mode & S.IXOTH > 0); } if (opt == .ignore) { - try testing.expect(fs.mode & S.IXUSR == 0); - try testing.expect(fs.mode & S.IXGRP == 0); - try testing.expect(fs.mode & S.IXOTH == 0); + try testing.expect(mode & S.IXUSR == 0); + try testing.expect(mode & S.IXGRP == 0); + try testing.expect(mode & S.IXOTH == 0); } } } |
