# std.tar - Tar Archive API Reference (Zig 0.16.0) Tar archive reading and writing. Zig 0.16 file and stream APIs use `std.Io.Dir`, `std.Io.File`, `std.Io.Reader`, and `std.Io.Writer`. Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html The examples below use Zig 0.16's explicit `std.Io` APIs. `tar.extract` also sanitizes archive paths to prevent traversal outside the destination. ## Table of Contents - [Module Structure](#module-structure) - [Reading Tar Archives](#reading-tar-archives) - [Extracting to Filesystem](#extracting-to-filesystem) - [Writing Tar Archives](#writing-tar-archives) - [Diagnostics and Error Handling](#diagnostics-and-error-handling) - [Common Patterns](#common-patterns) ## Module Structure ```zig std.tar.Iterator // Iterate over entries in tar archive std.tar.Writer // Create tar archives std.tar.Diagnostics // Collect errors during extraction std.tar.FileKind // .file, .directory, .sym_link std.tar.ExtractOptions // Options for extract std.tar.extract() // Extract archive to directory ``` ## Reading Tar Archives ### Iterator API Iterate over files, directories, and symlinks in a tar archive: ```zig const data = @embedFile("archive.tar"); var reader: std.Io.Reader = .fixed(data); // Buffers must be provided by caller var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; var it: std.tar.Iterator = .init(&reader, .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer, }); while (try it.next()) |file| { switch (file.kind) { .directory => std.debug.print("Dir: {s}\n", .{file.name}), .file => { std.debug.print("File: {s} ({d} bytes)\n", .{ file.name, file.size }); // Read file content - see below }, .sym_link => std.debug.print("Link: {s} -> {s}\n", .{ file.name, file.link_name }), } } ``` ### Iterator.File Structure ```zig pub const File = struct { name: []const u8, // file/dir/symlink path link_name: []const u8, // symlink target (empty for files/dirs) size: u64, // file size in bytes mode: u32, // POSIX permission mode kind: FileKind, // .file, .directory, .sym_link }; ``` ### Reading File Contents Read or copy file content before calling `next()` again if the content is needed. If content remains unread, `next()` discards it while advancing rather than failing: ```zig while (try it.next()) |file| { if (file.kind == .file) { // Option 1: Stream to writer var buf: [1024]u8 = undefined; var output_file = try dir.createFile(io, file.name, .{}); defer output_file.close(io); var file_writer = output_file.writer(io, &buf); try it.streamRemaining(file, &file_writer.interface); try file_writer.interface.flush(); // Option 2: Stream to allocated buffer var content: std.Io.Writer.Allocating = .init(allocator); defer content.deinit(); try it.streamRemaining(file, &content.writer); const bytes = content.written(); // []const u8 } } ``` ### Iterator Options ```zig pub const Options = struct { file_name_buffer: []u8, // buffer for file paths (use max_path_bytes) link_name_buffer: []u8, // buffer for symlink targets diagnostics: ?*Diagnostics, // optional error collection }; ``` ## Extracting to Filesystem ### extract Extract entire archive to a directory: ```zig const data = @embedFile("archive.tar"); var reader: std.Io.Reader = .fixed(data); try std.tar.extract(io, std.Io.Dir.cwd(), &reader, .{ .strip_components = 1, // remove leading path component .mode_mode = .executable_bit_only, .exclude_empty_directories = false, }); ``` ### From File ```zig const file = try std.Io.Dir.cwd().openFile(io, "archive.tar", .{}); defer file.close(io); var buf: [4096]u8 = undefined; var file_reader = file.reader(io, &buf); try std.tar.extract(io, output_dir, &file_reader.interface, .{}); ``` ### ExtractOptions ```zig pub const ExtractOptions = struct { strip_components: u32 = 0, // directories to strip from paths mode_mode: ModeMode = .executable_bit_only, exclude_empty_directories: bool = false, diagnostics: ?*Diagnostics = null, pub const ModeMode = enum { ignore, // ignore tar mode, use system defaults executable_bit_only, // copy only executable bit to group/other }; }; ``` **strip_components**: Removes leading path components. `strip_components = 1` converts `archive/src/main.zig` to `src/main.zig`. **mode_mode**: - `.ignore`: All files created with default permissions - `.executable_bit_only`: If owner has execute bit, set it for group and other too ## Writing Tar Archives ### Writer API ```zig var output: std.Io.Writer.Allocating = .init(allocator); defer output.deinit(); var w: std.tar.Writer = .{ .underlying_writer = &output.writer }; // Optional: set root directory prefix try w.setRoot("myproject"); // Write files try w.writeFileBytes("README.md", "# My Project\n", .{}); try w.writeFileBytes("src/main.zig", source_code, .{ .mode = 0o644 }); // Write directory try w.writeDir("data", .{}); // Write symlink try w.writeLink("latest", "v1.0", .{}); // Get tar data const tar_bytes = output.written(); ``` `setRoot` first emits a directory entry for a non-empty root and then applies that prefix to subsequent entries. ### Writing from File ```zig var output_file = try std.Io.Dir.cwd().createFile(io, "archive.tar", .{}); defer output_file.close(io); var buf: [4096]u8 = undefined; var file_writer = output_file.writer(io, &buf); var w: std.tar.Writer = .{ .underlying_writer = &file_writer.interface }; // Write file from disk var src_file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{}); defer src_file.close(io); var src_buf: [4096]u8 = undefined; var src_reader = src_file.reader(io, &src_buf); const stat = try src_file.stat(io); try w.writeFileTimestamp("data.txt", &src_reader, stat.mtime); try file_writer.interface.flush(); ``` ### Writing from Stream ```zig // When you know the size upfront var content_reader: std.Io.Reader = .fixed(content_bytes); try w.writeFileStream("file.txt", content_bytes.len, &content_reader, .{}); ``` ### Writer Methods ```zig // Set prefix for all subsequent paths pub fn setRoot(w: *Writer, root: []const u8) Error!void // Write directory entry pub fn writeDir(w: *Writer, sub_path: []const u8, options: Options) Error!void // Write file from bytes pub fn writeFileBytes(w: *Writer, sub_path: []const u8, content: []const u8, options: Options) Error!void // Write file from reader with known size pub fn writeFileStream(w: *Writer, sub_path: []const u8, size: u64, reader: *std.Io.Reader, options: Options) WriteFileStreamError!void // Write file from file reader pub fn writeFile(w: *Writer, sub_path: []const u8, file_reader: *std.Io.File.Reader, mtime_seconds: u64) WriteFileError!void // Convenience overload for std.Io.Timestamp pub fn writeFileTimestamp(w: *Writer, sub_path: []const u8, file_reader: *std.Io.File.Reader, mtime: std.Io.Timestamp) WriteFileError!void // Write symbolic link pub fn writeLink(w: *Writer, sub_path: []const u8, link_name: []const u8, options: Options) Error!void // Write two zero blocks. The stdlib recommends not calling this: readers must // accept archives without them, and omitting them avoids unnecessary output. pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void ``` ### Writer Options ```zig pub const Options = struct { mode: u32 = 0, // POSIX mode (0 = default: 0o664 for files) mtime: u64 = 0, // seconds since POSIX epoch; zero is written as zero }; ``` ## Diagnostics and Error Handling ### Using Diagnostics Collect errors instead of failing immediately: ```zig var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator }; defer diagnostics.deinit(); std.tar.extract(io, dir, &reader, .{ .diagnostics = &diagnostics, }) catch |err| { // Some errors are still fatal return err; }; // Check collected errors for (diagnostics.errors.items) |item| { switch (item) { .unable_to_create_file => |info| { std.debug.print("Failed to create {s}: {}\n", .{ info.file_name, info.code }); }, .unable_to_create_sym_link => |info| { std.debug.print("Failed to link {s} -> {s}\n", .{ info.file_name, info.link_name }); }, .unsupported_file_type => |info| { std.debug.print("Unsupported: {s} (type {})\n", .{ info.file_name, info.file_type }); }, .components_outside_stripped_prefix => |info| { std.debug.print("Stripped: {s}\n", .{info.file_name}); }, } } // Diagnostics also tracks root directory discovery std.debug.print("Root dir: {s}, entries: {d}\n", .{ diagnostics.root_dir, diagnostics.entries }); ``` ### Diagnostics.Error Variants ```text // Descriptive shape only: the unsupported_file_type payload contains a // private header-kind enum, so std.tar.Header.Kind is not a public type name. union(enum) { unable_to_create_sym_link: struct { code: anyerror, file_name: []const u8, link_name: []const u8, }, unable_to_create_file: struct { code: anyerror, file_name: []const u8, }, unsupported_file_type: struct { file_name: []const u8, file_type: /* private archive header kind */, }, components_outside_stripped_prefix: struct { file_name: []const u8, }, }; ``` ## Common Patterns ### Extract and Process Archive ```zig fn extractTar(io: std.Io, allocator: Allocator, tar_data: []const u8, dest: std.Io.Dir) !void { var reader: std.Io.Reader = .fixed(tar_data); var diagnostics: std.tar.Diagnostics = .{ .allocator = allocator }; defer diagnostics.deinit(); try std.tar.extract(io, dest, &reader, .{ .strip_components = 1, .diagnostics = &diagnostics, }); if (diagnostics.errors.items.len > 0) { for (diagnostics.errors.items) |err| { std.log.warn("tar extraction issue: {}", .{err}); } } } ``` ### List Archive Contents ```zig fn listTar(allocator: Allocator, tar_data: []const u8) !void { _ = allocator; var reader: std.Io.Reader = .fixed(tar_data); var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; var it: std.tar.Iterator = .init(&reader, .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer, }); while (try it.next()) |file| { const kind_char: u8 = switch (file.kind) { .directory => 'd', .file => '-', .sym_link => 'l', }; std.debug.print("{c} {o:0>4} {d:>10} {s}", .{ kind_char, file.mode, file.size, file.name, }); if (file.kind == .sym_link) { std.debug.print(" -> {s}", .{file.link_name}); } std.debug.print("\n", .{}); } } ``` ### Create Archive from Directory ```zig fn createTarFromDir(io: std.Io, allocator: Allocator, source_dir: std.Io.Dir, root_name: []const u8) ![]u8 { var output: std.Io.Writer.Allocating = .init(allocator); errdefer output.deinit(); var w: std.tar.Writer = .{ .underlying_writer = &output.writer }; try w.setRoot(root_name); var walker = try source_dir.walk(allocator); defer walker.deinit(); while (try walker.next(io)) |entry| { switch (entry.kind) { .directory => try w.writeDir(entry.path, .{}), .file => { var file = try entry.dir.openFile(io, entry.basename, .{}); defer file.close(io); var buf: [4096]u8 = undefined; var file_reader = file.reader(io, &buf); const stat = try file.stat(io); try w.writeFileTimestamp(entry.path, &file_reader, stat.mtime); }, .sym_link => { var link_buf: [std.Io.Dir.max_path_bytes]u8 = undefined; const target_len = try entry.dir.readLink(io, entry.basename, &link_buf); try w.writeLink(entry.path, link_buf[0..target_len], .{}); }, else => {}, // skip special files } } return output.toOwnedSlice(); } ``` ### Extract Single File ```zig fn extractFile(tar_data: []const u8, target_name: []const u8, allocator: Allocator) !?[]u8 { var reader: std.Io.Reader = .fixed(tar_data); var file_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; var link_name_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; var it: std.tar.Iterator = .init(&reader, .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer, }); while (try it.next()) |file| { if (file.kind == .file and std.mem.eql(u8, file.name, target_name)) { var content: std.Io.Writer.Allocating = .init(allocator); errdefer content.deinit(); try it.streamRemaining(file, &content.writer); return content.toOwnedSlice(); } } return null; } ``` ## Supported Features **Formats**: A deliberately non-comprehensive reader/writer for POSIX ustar, GNU long name/link extensions, and selected PAX attributes (`path`, `linkpath`, and `size`). Global PAX headers are ignored. **Entry types**: Regular files, directories, symbolic links **Not supported**: Hard links, device nodes, FIFOs, sparse files, and other special entries. With diagnostics supplied, unsupported entries can be recorded; without diagnostics, extraction returns `error.TarUnsupportedHeader`. **Path handling**: Automatic prefix/name splitting, GNU extended headers for paths > 256 bytes