mirror of
https://github.com/ziglang/zig.git
synced 2024-12-03 18:38:45 +00:00
f26dda2117
Most of this migration was performed automatically with `zig fmt`. There were a few exceptions which I had to manually fix: * `@alignCast` and `@addrSpaceCast` cannot be automatically rewritten * `@truncate`'s fixup is incorrect for vectors * Test cases are not formatted, and their error locations change
201 lines
7.6 KiB
Zig
201 lines
7.6 KiB
Zig
pub const Options = struct {
|
|
/// Number of directory levels to skip when extracting files.
|
|
strip_components: u32 = 0,
|
|
/// How to handle the "mode" property of files from within the tar file.
|
|
mode_mode: ModeMode = .executable_bit_only,
|
|
|
|
const ModeMode = enum {
|
|
/// The mode from the tar file is completely ignored. Files are created
|
|
/// with the default mode when creating files.
|
|
ignore,
|
|
/// The mode from the tar file is inspected for the owner executable bit
|
|
/// only. This bit is copied to the group and other executable bits.
|
|
/// Other bits of the mode are left as the default when creating files.
|
|
executable_bit_only,
|
|
};
|
|
};
|
|
|
|
pub const Header = struct {
|
|
bytes: *const [512]u8,
|
|
|
|
pub const FileType = enum(u8) {
|
|
normal = '0',
|
|
hard_link = '1',
|
|
symbolic_link = '2',
|
|
character_special = '3',
|
|
block_special = '4',
|
|
directory = '5',
|
|
fifo = '6',
|
|
contiguous = '7',
|
|
global_extended_header = 'g',
|
|
extended_header = 'x',
|
|
_,
|
|
};
|
|
|
|
pub fn fileSize(header: Header) !u64 {
|
|
const raw = header.bytes[124..][0..12];
|
|
const ltrimmed = std.mem.trimLeft(u8, raw, "0");
|
|
const rtrimmed = std.mem.trimRight(u8, ltrimmed, " \x00");
|
|
if (rtrimmed.len == 0) return 0;
|
|
return std.fmt.parseInt(u64, rtrimmed, 8);
|
|
}
|
|
|
|
pub fn is_ustar(header: Header) bool {
|
|
return std.mem.eql(u8, header.bytes[257..][0..6], "ustar\x00");
|
|
}
|
|
|
|
/// Includes prefix concatenated, if any.
|
|
/// Return value may point into Header buffer, or might point into the
|
|
/// argument buffer.
|
|
/// TODO: check against "../" and other nefarious things
|
|
pub fn fullFileName(header: Header, buffer: *[255]u8) ![]const u8 {
|
|
const n = name(header);
|
|
if (!is_ustar(header))
|
|
return n;
|
|
const p = prefix(header);
|
|
if (p.len == 0)
|
|
return n;
|
|
@memcpy(buffer[0..p.len], p);
|
|
buffer[p.len] = '/';
|
|
@memcpy(buffer[p.len + 1 ..][0..n.len], n);
|
|
return buffer[0 .. p.len + 1 + n.len];
|
|
}
|
|
|
|
pub fn name(header: Header) []const u8 {
|
|
return str(header, 0, 0 + 100);
|
|
}
|
|
|
|
pub fn prefix(header: Header) []const u8 {
|
|
return str(header, 345, 345 + 155);
|
|
}
|
|
|
|
pub fn fileType(header: Header) FileType {
|
|
const result = @as(FileType, @enumFromInt(header.bytes[156]));
|
|
return if (result == @as(FileType, @enumFromInt(0))) .normal else result;
|
|
}
|
|
|
|
fn str(header: Header, start: usize, end: usize) []const u8 {
|
|
var i: usize = start;
|
|
while (i < end) : (i += 1) {
|
|
if (header.bytes[i] == 0) break;
|
|
}
|
|
return header.bytes[start..i];
|
|
}
|
|
};
|
|
|
|
pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void {
|
|
switch (options.mode_mode) {
|
|
.ignore => {},
|
|
.executable_bit_only => {
|
|
// This code does not look at the mode bits yet. To implement this feature,
|
|
// the implementation must be adjusted to look at the mode, and check the
|
|
// user executable bit, then call fchmod on newly created files when
|
|
// the executable bit is supposed to be set.
|
|
// It also needs to properly deal with ACLs on Windows.
|
|
@panic("TODO: unimplemented: tar ModeMode.executable_bit_only");
|
|
},
|
|
}
|
|
var file_name_buffer: [255]u8 = undefined;
|
|
var buffer: [512 * 8]u8 = undefined;
|
|
var start: usize = 0;
|
|
var end: usize = 0;
|
|
header: while (true) {
|
|
if (buffer.len - start < 1024) {
|
|
const dest_end = end - start;
|
|
@memcpy(buffer[0..dest_end], buffer[start..end]);
|
|
end = dest_end;
|
|
start = 0;
|
|
}
|
|
const ask_header = @min(buffer.len - end, 1024 -| (end - start));
|
|
end += try reader.readAtLeast(buffer[end..], ask_header);
|
|
switch (end - start) {
|
|
0 => return,
|
|
1...511 => return error.UnexpectedEndOfStream,
|
|
else => {},
|
|
}
|
|
const header: Header = .{ .bytes = buffer[start..][0..512] };
|
|
start += 512;
|
|
const file_size = try header.fileSize();
|
|
const rounded_file_size = std.mem.alignForward(u64, file_size, 512);
|
|
const pad_len = @as(usize, @intCast(rounded_file_size - file_size));
|
|
const unstripped_file_name = try header.fullFileName(&file_name_buffer);
|
|
switch (header.fileType()) {
|
|
.directory => {
|
|
const file_name = try stripComponents(unstripped_file_name, options.strip_components);
|
|
if (file_name.len != 0) {
|
|
try dir.makePath(file_name);
|
|
}
|
|
},
|
|
.normal => {
|
|
if (file_size == 0 and unstripped_file_name.len == 0) return;
|
|
const file_name = try stripComponents(unstripped_file_name, options.strip_components);
|
|
|
|
if (std.fs.path.dirname(file_name)) |dir_name| {
|
|
try dir.makePath(dir_name);
|
|
}
|
|
var file = try dir.createFile(file_name, .{});
|
|
defer file.close();
|
|
|
|
var file_off: usize = 0;
|
|
while (true) {
|
|
if (buffer.len - start < 1024) {
|
|
const dest_end = end - start;
|
|
@memcpy(buffer[0..dest_end], buffer[start..end]);
|
|
end = dest_end;
|
|
start = 0;
|
|
}
|
|
// Ask for the rounded up file size + 512 for the next header.
|
|
// TODO: https://github.com/ziglang/zig/issues/14039
|
|
const ask = @as(usize, @intCast(@min(
|
|
buffer.len - end,
|
|
rounded_file_size + 512 - file_off -| (end - start),
|
|
)));
|
|
end += try reader.readAtLeast(buffer[end..], ask);
|
|
if (end - start < ask) return error.UnexpectedEndOfStream;
|
|
// TODO: https://github.com/ziglang/zig/issues/14039
|
|
const slice = buffer[start..@as(usize, @intCast(@min(file_size - file_off + start, end)))];
|
|
try file.writeAll(slice);
|
|
file_off += slice.len;
|
|
start += slice.len;
|
|
if (file_off >= file_size) {
|
|
start += pad_len;
|
|
// Guaranteed since we use a buffer divisible by 512.
|
|
assert(start <= end);
|
|
continue :header;
|
|
}
|
|
}
|
|
},
|
|
.global_extended_header, .extended_header => {
|
|
if (start + rounded_file_size > end) return error.TarHeadersTooBig;
|
|
start = @as(usize, @intCast(start + rounded_file_size));
|
|
},
|
|
.hard_link => return error.TarUnsupportedFileType,
|
|
.symbolic_link => return error.TarUnsupportedFileType,
|
|
else => return error.TarUnsupportedFileType,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn stripComponents(path: []const u8, count: u32) ![]const u8 {
|
|
var i: usize = 0;
|
|
var c = count;
|
|
while (c > 0) : (c -= 1) {
|
|
if (std.mem.indexOfScalarPos(u8, path, i, '/')) |pos| {
|
|
i = pos + 1;
|
|
} else {
|
|
return error.TarComponentsOutsideStrippedPrefix;
|
|
}
|
|
}
|
|
return path[i..];
|
|
}
|
|
|
|
test stripComponents {
|
|
const expectEqualStrings = std.testing.expectEqualStrings;
|
|
try expectEqualStrings("a/b/c", try stripComponents("a/b/c", 0));
|
|
try expectEqualStrings("b/c", try stripComponents("a/b/c", 1));
|
|
try expectEqualStrings("c", try stripComponents("a/b/c", 2));
|
|
}
|
|
|
|
const std = @import("std.zig");
|
|
const assert = std.debug.assert;
|