authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-09-30 23:00:39-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-10-02 17:02:24-07:00
loga5144d19b7a3585122dafbe05f7a1ce21f61a992
treedd50ab6152444bfa2de23509a2753ae0c0eee550
parent412d863ba5801c1376af7ab8f04a71b839a820a6

std.tar: support symlinks

closes #16678

1 files changed, 53 insertions(+), 3 deletions(-)

lib/std/tar.zig+53-3
...@@ -3,8 +3,13 @@ pub const Options = struct {...@@ -3,8 +3,13 @@ pub const Options = struct {
3 strip_components: u32 = 0,3 strip_components: u32 = 0,
4 /// How to handle the "mode" property of files from within the tar file.4 /// How to handle the "mode" property of files from within the tar file.
5 mode_mode: ModeMode = .executable_bit_only,5 mode_mode: ModeMode = .executable_bit_only,
6 /// Provide this to receive detailed error messages.
7 /// When this is provided, some errors which would otherwise be returned immediately
8 /// will instead be added to this structure. The API user must check the errors
9 /// in diagnostics to know whether the operation succeeded or failed.
10 diagnostics: ?*Diagnostics = null,
611
7 const ModeMode = enum {12 pub const ModeMode = enum {
8 /// The mode from the tar file is completely ignored. Files are created13 /// The mode from the tar file is completely ignored. Files are created
9 /// with the default mode when creating files.14 /// with the default mode when creating files.
10 ignore,15 ignore,
...@@ -13,6 +18,32 @@ pub const Options = struct {...@@ -13,6 +18,32 @@ pub const Options = struct {
13 /// Other bits of the mode are left as the default when creating files.18 /// Other bits of the mode are left as the default when creating files.
14 executable_bit_only,19 executable_bit_only,
15 };20 };
21
22 pub const Diagnostics = struct {
23 allocator: std.mem.Allocator,
24 errors: std.ArrayListUnmanaged(Error) = .{},
25
26 pub const Error = union(enum) {
27 unable_to_create_sym_link: struct {
28 code: anyerror,
29 file_name: []const u8,
30 link_name: []const u8,
31 },
32 };
33
34 pub fn deinit(d: *Diagnostics) void {
35 for (d.errors.items) |item| {
36 switch (item) {
37 .unable_to_create_sym_link => |info| {
38 d.allocator.free(info.file_name);
39 d.allocator.free(info.link_name);
40 },
41 }
42 }
43 d.errors.deinit(d.allocator);
44 d.* = undefined;
45 }
46 };
16};47};
1748
18pub const Header = struct {49pub const Header = struct {
...@@ -65,6 +96,10 @@ pub const Header = struct {...@@ -65,6 +96,10 @@ pub const Header = struct {
65 return str(header, 0, 0 + 100);96 return str(header, 0, 0 + 100);
66 }97 }
6798
99 pub fn linkName(header: Header) []const u8 {
100 return str(header, 157, 157 + 100);
101 }
102
68 pub fn prefix(header: Header) []const u8 {103 pub fn prefix(header: Header) []const u8 {
69 return str(header, 345, 345 + 155);104 return str(header, 345, 345 + 155);
70 }105 }
...@@ -148,7 +183,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi...@@ -148,7 +183,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi
148 const header: Header = .{ .bytes = chunk[0..512] };183 const header: Header = .{ .bytes = chunk[0..512] };
149 const file_size = try header.fileSize();184 const file_size = try header.fileSize();
150 const rounded_file_size = std.mem.alignForward(u64, file_size, 512);185 const rounded_file_size = std.mem.alignForward(u64, file_size, 512);
151 const pad_len = @as(usize, @intCast(rounded_file_size - file_size));186 const pad_len: usize = @intCast(rounded_file_size - file_size);
152 const unstripped_file_name = if (file_name_override_len > 0)187 const unstripped_file_name = if (file_name_override_len > 0)
153 file_name_buffer[0..file_name_override_len]188 file_name_buffer[0..file_name_override_len]
154 else189 else
...@@ -228,7 +263,22 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi...@@ -228,7 +263,22 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi
228 buffer.skip(reader, @intCast(rounded_file_size)) catch return error.TarHeadersTooBig;263 buffer.skip(reader, @intCast(rounded_file_size)) catch return error.TarHeadersTooBig;
229 },264 },
230 .hard_link => return error.TarUnsupportedFileType,265 .hard_link => return error.TarUnsupportedFileType,
231 .symbolic_link => return error.TarUnsupportedFileType,266 .symbolic_link => {
267 const file_name = try stripComponents(unstripped_file_name, options.strip_components);
268 const link_name = header.linkName();
269
270 dir.symLink(link_name, file_name, .{}) catch |err| {
271 if (options.diagnostics) |d| {
272 try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{
273 .code = err,
274 .file_name = try d.allocator.dupe(u8, file_name),
275 .link_name = try d.allocator.dupe(u8, link_name),
276 } });
277 } else {
278 return error.UnableToCreateSymLink;
279 }
280 };
281 },
232 else => return error.TarUnsupportedFileType,282 else => return error.TarUnsupportedFileType,
233 }283 }
234 }284 }