Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

Header

A struct that is exactly 512 bytes and matches tar file format. This is intended to be used for outputting tar files; for parsing there is std.tar.Header.

Writer.Header
pub const Header = extern struct

File

lib/std/tar/Writer.zig:186

Code

pub const Header = extern struct {
    // This struct was originally copied from
    // https://github.com/mattnite/tar/blob/main/src/main.zig which is MIT
    // licensed.
    //
    // The name, linkname, magic, uname, and gname are null-terminated character
    // strings. All other fields are zero-filled octal numbers in ASCII. Each
    // numeric field of width w contains w minus 1 digits, and a null.
    // Reference: https://www.gnu.org/software/tar/manual/html_node/Standard.html
    // POSIX header:                                  byte offset
    name: [100]u8 = @splat(0), //                               0
    mode: [7:0]u8 = default_mode.file, //                     100
    uid: [7:0]u8 = @splat(0), // unused                       108
    gid: [7:0]u8 = @splat(0), // unused                       116
    size: [11:0]u8 = @splat('0'), //                          124
    mtime: [11:0]u8 = @splat('0'), //                         136
    checksum: [7:0]u8 = @splat(' '), //                       148
    typeflag: FileType = .regular, //                         156
    linkname: [100]u8 = @splat(0), //                         157
    magic: [6]u8 = .{ 'u', 's', 't', 'a', 'r', 0 }, //        257
    version: [2]u8 = .{ '0', '0' }, //                        263
    uname: [32]u8 = @splat(0), // unused                      265
    gname: [32]u8 = @splat(0), // unused                      297
    devmajor: [7:0]u8 = @splat(0), // unused                  329
    devminor: [7:0]u8 = @splat(0), // unused                  337
    prefix: [155]u8 = @splat(0), //                           345
    pad: [12]u8 = @splat(0), // unused                        500

    pub const FileType = enum(u8) {
        regular = '0',
        symbolic_link = '2',
        directory = '5',
        gnu_long_name = 'L',
        gnu_long_link = 'K',
    };

    const default_mode = struct {
        const file = [_:0]u8{ '0', '0', '0', '0', '6', '6', '4' }; // 0o664
        const dir = [_:0]u8{ '0', '0', '0', '0', '7', '7', '5' }; // 0o775
        const sym_link = [_:0]u8{ '0', '0', '0', '0', '7', '7', '7' }; // 0o777
        const other = [_:0]u8{ '0', '0', '0', '0', '0', '0', '0' }; // 0o000
    };

    pub fn init(typeflag: FileType) Header {
        return .{
            .typeflag = typeflag,
            .mode = switch (typeflag) {
                .directory => default_mode.dir,
                .symbolic_link => default_mode.sym_link,
                .regular => default_mode.file,
                else => default_mode.other,
            },
        };
    }

    pub fn setSize(w: *Header, size: u64) error{OctalOverflow}!void {
        try octal(&w.size, size);
    }

    fn octal(buf: []u8, value: u64) error{OctalOverflow}!void {
        var remainder: u64 = value;
        var pos: usize = buf.len;
        while (remainder > 0 and pos > 0) {
            pos -= 1;
            const c: u8 = @as(u8, @intCast(remainder % 8)) + '0';
            buf[pos] = c;
            remainder /= 8;
            if (pos == 0 and remainder > 0) return error.OctalOverflow;
        }
    }

    pub fn setMode(w: *Header, mode: u32) error{OctalOverflow}!void {
        try octal(&w.mode, mode);
    }

    // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time.
    pub fn setMtime(w: *Header, mtime: u64) error{OctalOverflow}!void {
        try octal(&w.mtime, mtime);
    }

    pub fn updateChecksum(w: *Header) !void {
        var checksum: usize = ' '; // other 7 w.checksum bytes are initialized to ' '
        for (std.mem.asBytes(w)) |val|
            checksum += val;
        try octal(&w.checksum, checksum);
    }

    pub fn write(h: *Header, bw: *Io.Writer) error{ OctalOverflow, WriteFailed }!void {
        try h.updateChecksum();
        try bw.writeAll(std.mem.asBytes(h));
    }

    pub fn setLinkname(w: *Header, link: []const u8) !void {
        if (link.len > w.linkname.len) return error.NameTooLong;
        @memcpy(w.linkname[0..link.len], link);
    }

    pub fn setPath(w: *Header, prefix: []const u8, sub_path: []const u8) !void {
        const max_prefix = w.prefix.len;
        const max_name = w.name.len;
        const sep = std.fs.path.sep_posix;

        if (prefix.len + sub_path.len > max_name + max_prefix or prefix.len > max_prefix)
            return error.NameTooLong;

        // both fit into name
        if (prefix.len > 0 and prefix.len + sub_path.len < max_name) {
            @memcpy(w.name[0..prefix.len], prefix);
            w.name[prefix.len] = sep;
            @memcpy(w.name[prefix.len + 1 ..][0..sub_path.len], sub_path);
            return;
        }

        // sub_path fits into name
        // there is no prefix or prefix fits into prefix
        if (sub_path.len <= max_name) {
            @memcpy(w.name[0..sub_path.len], sub_path);
            @memcpy(w.prefix[0..prefix.len], prefix);
            return;
        }

        if (prefix.len > 0) {
            @memcpy(w.prefix[0..prefix.len], prefix);
            w.prefix[prefix.len] = sep;
        }
        const prefix_pos = if (prefix.len > 0) prefix.len + 1 else 0;

        // add as much to prefix as you can, must split at /
        const prefix_remaining = max_prefix - prefix_pos;
        if (std.mem.lastIndexOf(u8, sub_path[0..@min(prefix_remaining, sub_path.len)], &.{'/'})) |sep_pos| {
            @memcpy(w.prefix[prefix_pos..][0..sep_pos], sub_path[0..sep_pos]);
            if ((sub_path.len - sep_pos - 1) > max_name) return error.NameTooLong;
            @memcpy(w.name[0..][0 .. sub_path.len - sep_pos - 1], sub_path[sep_pos + 1 ..]);
            return;
        }

        return error.NameTooLong;
    }

    comptime {
        assert(@sizeOf(Header) == 512);
    }

    test "setPath" {
        const cases = [_]struct {
            in: []const []const u8,
            out: []const []const u8,
        }{
            .{
                .in = &.{ "", "123456789" },
                .out = &.{ "", "123456789" },
            },
            // can fit into name
            .{
                .in = &.{ "prefix", "sub_path" },
                .out = &.{ "", "prefix/sub_path" },
            },
            // no more both fits into name
            .{
                .in = &.{ "prefix", repeatString(8, "0123456789/") ++ "basename" },
                .out = &.{ "prefix", repeatString(8, "0123456789/") ++ "basename" },
            },
            // put as much as you can into prefix the rest goes into name
            .{
                .in = &.{ "prefix", repeatString(10, "0123456789/") ++ "basename" },
                .out = &.{ "prefix/" ++ repeatString(9, "0123456789/") ++ "0123456789", "basename" },
            },

            .{
                .in = &.{ "prefix", repeatString(15, "0123456789/") ++ "basename" },
                .out = &.{ "prefix/" ++ repeatString(12, "0123456789/") ++ "0123456789", "0123456789/0123456789/basename" },
            },
            .{
                .in = &.{ "prefix", repeatString(21, "0123456789/") ++ "basename" },
                .out = &.{ "prefix/" ++ repeatString(12, "0123456789/") ++ "0123456789", repeatString(8, "0123456789/") ++ "basename" },
            },
            .{
                .in = &.{ "", repeatString(10, "012345678/") ++ "foo" },
                .out = &.{ repeatString(9, "012345678/") ++ "012345678", "foo" },
            },
        };

        for (cases) |case| {
            var header = Header.init(.regular);
            try header.setPath(case.in[0], case.in[1]);
            try testing.expectEqualStrings(case.out[0], std.mem.sliceTo(&header.prefix, 0));
            try testing.expectEqualStrings(case.out[1], std.mem.sliceTo(&header.name, 0));
        }

        const error_cases = [_]struct {
            in: []const []const u8,
        }{
            // basename can't fit into name (106 characters)
            .{ .in = &.{ "zig", "test/cases/compile_errors/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig" } },
            // cant fit into 255 + sep
            .{ .in = &.{ "prefix", repeatString(22, "0123456789/") ++ "basename" } },
            // can fit but sub_path can't be split (there is no separator)
            .{ .in = &.{ "prefix", repeatString(10, "0123456789") ++ "a" } },
            .{ .in = &.{ "prefix", repeatString(14, "0123456789") ++ "basename" } },
        };

        for (error_cases) |case| {
            var header = Header.init(.regular);
            try testing.expectError(
                error.NameTooLong,
                header.setPath(case.in[0], case.in[1]),
            );
        }
    }
}