diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ad0ffe4..23b86c8 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -20,9 +20,9 @@ jobs: uses: actions/checkout@v2 - name: Setup Zig - uses: mlugg/setup-zig@v1 + uses: mlugg/setup-zig@v2 with: - version: 0.14.0 + version: 0.15.2 - name: Basic Build run: | diff --git a/build.zig b/build.zig index 3962284..e770a2c 100644 --- a/build.zig +++ b/build.zig @@ -18,6 +18,8 @@ pub fn build(b: *std.Build) void { .mkfs = true, .exfat = true, .label = true, + .target = target, + .optimize = optimize, }); const zfat_mod = zfat_dep.module("zfat"); @@ -30,9 +32,11 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, .link_libc = true, + .imports = &.{ + .{ .name = "args", .module = args_mod }, + .{ .name = "zfat", .module = zfat_mod }, + }, }); - dim_mod.addImport("args", args_mod); - dim_mod.addImport("zfat", zfat_mod); const dim_exe = b.addExecutable(.{ .name = "dimmer", @@ -53,6 +57,7 @@ pub fn build(b: *std.Build) void { const script_test = b.step(step_name, b.fmt("Run {s} behaviour test", .{script})); const run_behaviour = b.addRunArtifact(dim_exe); + run_behaviour.setCwd(b.path(script).dirname()); run_behaviour.addArg("--output"); _ = run_behaviour.addOutputFileArg("disk.img"); run_behaviour.addArg("--script"); diff --git a/build.zig.zon b/build.zig.zon index 3ceb020..c710cf7 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,12 +4,12 @@ .fingerprint = 0x9947018c924eecb2, .dependencies = .{ .zfat = .{ - .url = "https://github.com/ZigEmbeddedGroup/zfat/archive/3ce06d43a4e04d387034dcae2f486b050701f321.tar.gz", - .hash = "zfat-0.0.0-AAAAAMYlcABdh06Mn9CNk8Ccy_3bBFgJr8wo4jKza1q-", + .url = "git+https://github.com/ZigEmbeddedGroup/zfat.git#0571b0d8c8cc4fcb037a1d5e7ea5666cb2f83ddf", + .hash = "zfat-0.15.0-SNNK9RRqcgCLQ5mjXghbB6mokzcHORsGnY7GtbfOt2k3", }, .args = .{ - .url = "git+https://github.com/ikskuh/zig-args.git#9425b94c103a031777fdd272c555ce93a7dea581", - .hash = "args-0.0.0-CiLiqv_NAAC97fGpk9hS2K681jkiqPsWP6w3ucb_ctGH", + .url = "git+https://github.com/ikskuh/zig-args.git#8ae26b44a884ff20dca98ee84c098e8f8e94902f", + .hash = "args-0.0.0-CiLiqojRAACGzDRO7A9dw7kWSchNk29caJZkXuMCb0Cn", }, }, .paths = .{ diff --git a/src/BuildInterface.zig b/src/BuildInterface.zig index 4d9ebda..c1aedc5 100644 --- a/src/BuildInterface.zig +++ b/src/BuildInterface.zig @@ -37,6 +37,7 @@ pub fn createDisk(dimmer: Interface, size: u64, content: Content) std.Build.Lazy compile_script.addArg(b.fmt("--size={d}", .{size})); compile_script.addPrefixedFileArg("--script=", script_file); + compile_script.addPrefixedDirectoryArg("--script-root=", .{ .cwd_relative = "." }); const result_file = compile_script.addPrefixedOutputFileArg("--output=", "disk.img"); @@ -62,14 +63,18 @@ pub fn createDisk(dimmer: Interface, size: u64, content: Content) std.Build.Lazy return result_file; } -fn renderContent(wfs: *std.Build.Step.WriteFile, allocator: std.mem.Allocator, content: Content) struct { []const u8, ContentWriter.VariableMap } { - var code: std.ArrayList(u8) = .init(allocator); +fn renderContent( + wfs: *std.Build.Step.WriteFile, + allocator: std.mem.Allocator, + content: Content, +) struct { []const u8, ContentWriter.VariableMap } { + var code: std.Io.Writer.Allocating = .init(allocator); defer code.deinit(); var variables: ContentWriter.VariableMap = .init(allocator); var cw: ContentWriter = .{ - .code = code.writer(), + .code = &code.writer, .wfs = wfs, .vars = &variables, }; @@ -99,7 +104,7 @@ const ContentWriter = struct { pub const VariableMap = std.StringArrayHashMap(struct { std.Build.LazyPath, ContentWriter.UsageHint }); wfs: *std.Build.Step.WriteFile, - code: std.ArrayList(u8).Writer, + code: *std.Io.Writer, vars: *VariableMap, fn render(cw: ContentWriter, content: Content) !void { @@ -117,7 +122,7 @@ const ContentWriter = struct { }, .paste_file => |data| { - try cw.code.print("paste-file {}", .{cw.fmtLazyPath(data, .file)}); + try cw.code.print("paste-file {f}", .{cw.fmtLazyPath(data, .file)}); }, .mbr_part_table => |data| { @@ -158,7 +163,7 @@ const ContentWriter = struct { .gpt_part_table => |data| { try cw.code.writeAll("gpt-part\n"); - if(data.legacy_bootable) { + if (data.legacy_bootable) { try cw.code.writeAll(" legacy-bootable\n"); } @@ -176,7 +181,7 @@ const ContentWriter = struct { try cw.code.writeByte('\n'); if (part.name) |name| { - try cw.code.print(" name \"{}\"\n", .{std.zig.fmtEscapes(name)}); + try cw.code.print(" name \"{f}\"\n", .{std.zig.fmtString(name)}); } if (part.offset) |offset| { try cw.code.print(" offset {d}\n", .{offset}); @@ -198,7 +203,7 @@ const ContentWriter = struct { @tagName(data.format), }); if (data.label) |label| { - try cw.code.print(" label {}\n", .{ + try cw.code.print(" label {f}\n", .{ fmtPath(label), }); } @@ -213,61 +218,117 @@ const ContentWriter = struct { fn renderFileSystemTree(cw: ContentWriter, fs: FileSystem) !void { for (fs.items) |item| { switch (item) { - .empty_dir => |dir| try cw.code.print("mkdir {}\n", .{ + .empty_dir => |dir| try cw.code.print("mkdir {f}\n", .{ fmtPath(dir), }), - .copy_dir => |copy| try cw.code.print("copy-dir {} {}\n", .{ + .copy_dir => |copy| try cw.code.print("copy-dir {f} {f}\n", .{ fmtPath(copy.destination), cw.fmtLazyPath(copy.source, .directory), }), - .copy_file => |copy| try cw.code.print("copy-file {} {}\n", .{ + .copy_file => |copy| try cw.code.print("copy-file {f} {f}\n", .{ fmtPath(copy.destination), cw.fmtLazyPath(copy.source, .file), }), - .include_script => |script| try cw.code.print("!include {}\n", .{ + .include_script => |script| try cw.code.print("!include {f}\n", .{ cw.fmtLazyPath(script, .file), }), } } } - const PathFormatter = std.fmt.Formatter(formatPath); - const LazyPathFormatter = std.fmt.Formatter(formatLazyPath); + const PathFormatter = struct { + path: []const u8, + + pub fn format( + p: PathFormatter, + writer: *std.Io.Writer, + ) std.Io.Writer.Error!void { + const path = p.path; + const is_safe_word = for (path) |char| { + switch (char) { + 'A'...'Z', + 'a'...'z', + '0'...'9', + '_', + '-', + '/', + '.', + ':', + => {}, + else => break false, + } + } else true; + + if (is_safe_word) { + try writer.writeAll(path); + } else { + try writer.writeAll("\""); + + for (path) |c| { + if (c == '\\') { + try writer.writeAll("/"); + } else { + try writer.print("{f}", .{std.zig.fmtString(&[_]u8{c})}); + } + } + + try writer.writeAll("\""); + } + } + }; + const LazyPathFormatter = std.fmt.Alt( + struct { ContentWriter, std.Build.LazyPath, UsageHint }, + formatLazyPath, + ); const UsageHint = enum { file, directory }; - fn fmtLazyPath(cw: ContentWriter, path: std.Build.LazyPath, hint: UsageHint) LazyPathFormatter { + fn fmtLazyPath( + cw: ContentWriter, + path: std.Build.LazyPath, + hint: UsageHint, + ) LazyPathFormatter { return .{ .data = .{ cw, path, hint } }; } fn fmtPath(path: []const u8) PathFormatter { - return .{ .data = path }; + return .{ .path = path }; } fn formatLazyPath( data: struct { ContentWriter, std.Build.LazyPath, UsageHint }, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { + writer: *std.Io.Writer, + ) std.Io.Writer.Error!void { const cw, const path, const hint = data; - _ = fmt; - _ = options; switch (path) { .cwd_relative, .dependency, .src_path, => { + // We can safely call getPath2 as we can fully resolve the path // already - const full_path = path.getPath2(cw.wfs.step.owner, &cw.wfs.step); - - std.debug.assert(std.fs.path.isAbsolute(full_path)); + const rel_path = path.getPath2(cw.wfs.step.owner, &cw.wfs.step); + + const full_path = if (!std.fs.path.isAbsolute(rel_path)) + std.fs.cwd().realpathAlloc(cw.wfs.step.owner.allocator, rel_path) catch @panic("oom") + else + rel_path; + + if (!std.fs.path.isAbsolute(full_path)) { + const cwd = std.fs.cwd().realpathAlloc(cw.wfs.step.owner.allocator, ".") catch @panic("oom"); + std.debug.print("non-absolute path detected for {t}: cwd=\"{f}\" path=\"{f}\"\n", .{ + path, + std.zig.fmtString(cwd), + std.zig.fmtString(full_path), + }); + @panic("non-absolute path detected!"); + } - try writer.print("{}", .{ + try writer.print("{f}", .{ fmtPath(full_path), }); }, @@ -278,53 +339,12 @@ const ContentWriter = struct { const var_id = cw.vars.count() + 1; const var_name = cw.wfs.step.owner.fmt("PATH{}", .{var_id}); - try cw.vars.put(var_name, .{ path, hint }); + cw.vars.put(var_name, .{ path, hint }) catch return error.WriteFailed; try writer.print("${s}", .{var_name}); }, } } - - fn formatPath( - path: []const u8, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = fmt; - _ = options; - - const is_safe_word = for (path) |char| { - switch (char) { - 'A'...'Z', - 'a'...'z', - '0'...'9', - '_', - '-', - '/', - '.', - ':', - => {}, - else => break false, - } - } else true; - - if (is_safe_word) { - try writer.writeAll(path); - } else { - try writer.writeAll("\""); - - for (path) |c| { - if (c == '\\') { - try writer.writeAll("/"); - } else { - try writer.print("{}", .{std.zig.fmtEscapes(&[_]u8{c})}); - } - } - - try writer.writeAll("\""); - } - } }; pub const Content = union(enum) { @@ -385,7 +405,7 @@ pub const GptPartTable = struct { @"microsoft-basic-data", @"microsoft-reserved", @"windows-recovery", - @"plan9", + plan9, @"linux-swap", @"linux-fs", @"linux-reserved", diff --git a/src/Parser.zig b/src/Parser.zig index ff7c179..edfdc13 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -208,7 +208,7 @@ fn resolve_value(parser: *Parser, token_type: TokenType, text: []const u8) ![]co if (!has_includes) return content_slice; - var unescaped: std.ArrayList(u8) = .init(parser.arena.allocator()); + var unescaped: std.array_list.Managed(u8) = .init(parser.arena.allocator()); defer unescaped.deinit(); try unescaped.ensureTotalCapacityPrecise(content_slice.len); diff --git a/src/Tokenizer.zig b/src/Tokenizer.zig index cdac717..c8a0ab5 100644 --- a/src/Tokenizer.zig +++ b/src/Tokenizer.zig @@ -186,9 +186,9 @@ test Tokenizer { var offset: u32 = 0; for (seq) |expected| { const actual = (try tokenizer.next()) orelse return error.Unexpected; - errdefer std.debug.print("unexpected token: .{} \"{}\"\n", .{ + errdefer std.debug.print("unexpected token: .{f} \"{f}\"\n", .{ std.zig.fmtId(@tagName(actual.type)), - std.zig.fmtEscapes(tokenizer.source[actual.offset..][0..actual.len]), + std.zig.fmtString(tokenizer.source[actual.offset..][0..actual.len]), }); try std.testing.expectEqualStrings(expected.@"1", tokenizer.get_text(actual)); try std.testing.expectEqual(offset, actual.offset); diff --git a/src/components/fs/FatFileSystem.zig b/src/components/fs/FatFileSystem.zig index 5b5de50..4c35085 100644 --- a/src/components/fs/FatFileSystem.zig +++ b/src/components/fs/FatFileSystem.zig @@ -14,7 +14,7 @@ format_as: FatType, label: ?[]const u8 = null, fats: ?fatfs.FatTables = null, rootdir_size: ?c_uint = null, -ops: std.ArrayList(common.FsOperation), +ops: std.array_list.Managed(common.FsOperation), sector_align: ?c_uint = null, cluster_size: ?u32 = null, @@ -88,7 +88,7 @@ fn render(self: *FAT, stream: *dim.BinaryStream) dim.Content.RenderError!void { if (stream.length < min_size) { // TODO(fqu): Report fatal erro! - std.log.err("cannot format {} bytes with {s}: min required size is {}", .{ + std.log.err("cannot format {f} bytes with {s}: min required size is {f}", .{ @as(dim.DiskSize, @enumFromInt(stream.length)), @tagName(self.format_as), @as(dim.DiskSize, @enumFromInt(min_size)), @@ -98,15 +98,13 @@ fn render(self: *FAT, stream: *dim.BinaryStream) dim.Content.RenderError!void { if (stream.length > max_size) { // TODO(fqu): Report warning - std.log.warn("will not use all available space: available space is {}, but maximum size for {s} is {}", .{ + std.log.warn("will not use all available space: available space is {f}, but maximum size for {s} is {f}", .{ @as(dim.DiskSize, @enumFromInt(stream.length)), @tagName(self.format_as), @as(dim.DiskSize, @enumFromInt(min_size)), }); } - var filesystem: fatfs.FileSystem = undefined; - fatfs.disks[0] = &bsd.disk; defer fatfs.disks[0] = null; @@ -128,8 +126,7 @@ fn render(self: *FAT, stream: *dim.BinaryStream) dim.Content.RenderError!void { error.MkfsAborted => return error.IoError, }; - const ops = self.ops.items; - + var filesystem: fatfs.FileSystem = undefined; filesystem.mount("0:", true) catch |err| switch (err) { error.NotEnabled => @panic("bug in zfat"), error.DiskErr => return error.IoError, @@ -147,8 +144,8 @@ fn render(self: *FAT, stream: *dim.BinaryStream) dim.Content.RenderError!void { return error.IoError; } } else { - std.log.err("label \"{}\" is {} characters long, but only up to {} are permitted.", .{ - std.zig.fmtEscapes(label), + std.log.err("label \"{f}\" is {} characters long, but only up to {} are permitted.", .{ + std.zig.fmtString(label), label.len, max_label_len, }); @@ -156,8 +153,7 @@ fn render(self: *FAT, stream: *dim.BinaryStream) dim.Content.RenderError!void { } const wrapper = AtomicOps{}; - - for (ops) |op| { + for (self.ops.items) |op| { try op.execute(wrapper); } } @@ -206,13 +202,13 @@ const AtomicOps = struct { error.InvalidDrive => @panic("implementation bug in fatfs glue"), error.NotEnabled => @panic("implementation bug in fatfs glue"), error.NoFilesystem => @panic("implementation bug in fatfs glue"), - error.IntErr => return error.IoError, + error.IntErr => @panic("Assertion failed and an insanity is detected in the internal process."), error.NoPath => @panic("implementation bug in fatfs glue"), error.Denied => @panic("implementation bug in fatfs glue"), }; } - pub fn mkfile(ops: AtomicOps, path: []const u8, reader: anytype) dim.Content.RenderError!void { + pub fn mkfile(ops: AtomicOps, path: []const u8, reader: *std.Io.Reader) dim.Content.RenderError!void { _ = ops; var path_buffer: [max_path_len:0]u8 = undefined; @@ -244,19 +240,12 @@ const AtomicOps = struct { }; defer fs_file.close(); - var fifo: std.fifo.LinearFifo(u8, .{ .Static = 8192 }) = .init(); - fifo.pump( - reader, - fs_file.writer(), - ) catch |err| switch (@as(dim.FileHandle.ReadError || fatfs.File.ReadError.Error, err)) { - error.Overflow => return error.IoError, - error.ReadFileFailed => return error.IoError, - error.Timeout => @panic("implementation bug in fatfs glue"), - error.DiskErr => return error.IoError, - error.IntErr => return error.IoError, - error.Denied => @panic("implementation bug in fatfs glue"), - error.InvalidObject => @panic("implementation bug in fatfs glue"), - }; + var fs_file_buffer: [1024]u8 = undefined; + var adapter = fs_file.writer(&fs_file_buffer); + + _ = try reader.streamRemaining(&adapter.writer); + + try adapter.writer.flush(); } }; @@ -269,6 +258,7 @@ const BinaryStreamDisk = struct { .ioctlFn = disk_ioctl, }, stream: *dim.BinaryStream, + disk_error: ?(dim.BinaryStream.WriteError || dim.BinaryStream.ReadError) = null, fn disk_getStatus(intf: *fatfs.Disk) fatfs.Disk.Status { _ = intf; @@ -286,13 +276,19 @@ const BinaryStreamDisk = struct { fn disk_read(intf: *fatfs.Disk, buff: [*]u8, sector: fatfs.LBA, count: c_uint) fatfs.Disk.Error!void { const bsd: *BinaryStreamDisk = @fieldParentPtr("disk", intf); - bsd.stream.read(block_size * sector, buff[0 .. count * block_size]) catch return error.IoError; + bsd.stream.read(block_size * sector, buff[0 .. count * block_size]) catch |err| { + bsd.disk_error = err; + return error.IoError; + }; } fn disk_write(intf: *fatfs.Disk, buff: [*]const u8, sector: fatfs.LBA, count: c_uint) fatfs.Disk.Error!void { const bsd: *BinaryStreamDisk = @fieldParentPtr("disk", intf); - bsd.stream.write(block_size * sector, buff[0 .. count * block_size]) catch return error.IoError; + bsd.stream.write(block_size * sector, buff[0 .. count * block_size]) catch |err| { + bsd.disk_error = err; + return error.IoError; + }; } fn disk_ioctl(intf: *fatfs.Disk, cmd: fatfs.IoCtl, buff: [*]u8) fatfs.Disk.Error!void { diff --git a/src/components/fs/common.zig b/src/components/fs/common.zig index a3421e9..0efdc7b 100644 --- a/src/components/fs/common.zig +++ b/src/components/fs/common.zig @@ -28,7 +28,6 @@ pub const FsOperation = union(enum) { pub fn execute(op: FsOperation, executor: anytype) !void { const exec: Executor(@TypeOf(executor)) = .init(executor); - try exec.execute(op); } }; @@ -51,16 +50,19 @@ fn Executor(comptime T: type) type { .copy_file => |data| { var handle = data.source.open() catch |err| switch (err) { - error.FileNotFound => return, // open() already reporeted the error + error.FileNotFound => return, // open() already reported the error else => |e| return e, }; defer handle.close(); - try exec.add_file(data.path, handle.reader()); + var buffer: [1024]u8 = undefined; + var adapter = handle.reader(&buffer); + + try exec.add_file(data.path, &adapter.interface); }, .copy_dir => |data| { var iter_dir = data.source.open_dir() catch |err| switch (err) { - error.FileNotFound => return, // open() already reporeted the error + error.FileNotFound => return, // open() already reported the error else => |e| return e, }; defer iter_dir.close(); @@ -84,6 +86,7 @@ fn Executor(comptime T: type) type { switch (entry.kind) { .file => { const fname: dim.FileName = .{ + .env = data.source.env, .root_dir = entry.dir, .rel_path = entry.basename, }; @@ -91,7 +94,10 @@ fn Executor(comptime T: type) type { var file = try fname.open(); defer file.close(); - try exec.add_file(path, file.reader()); + var buffer: [1024]u8 = undefined; + var adapter = file.reader(&buffer); + + try exec.add_file(path, &adapter.interface); }, .directory => { @@ -117,14 +123,14 @@ fn Executor(comptime T: type) type { try data.contents.render(&bs); - var fbs: std.io.FixedBufferStream([]u8) = .{ .buffer = buffer, .pos = 0 }; + var reader: std.Io.Reader = .fixed(buffer); - try exec.add_file(data.path, fbs.reader()); + try exec.add_file(data.path, &reader); }, } } - fn add_file(exec: Exec, path: [:0]const u8, reader: anytype) !void { + fn add_file(exec: Exec, path: [:0]const u8, reader: *std.Io.Reader) !void { if (std.fs.path.dirnamePosix(path)) |dir| { try exec.recursive_mkdir(dir); } @@ -143,7 +149,7 @@ fn Executor(comptime T: type) type { try exec.inner_mkdir(path); } - fn inner_mkfile(exec: Exec, path: []const u8, reader: anytype) dim.Content.RenderError!void { + fn inner_mkfile(exec: Exec, path: []const u8, reader: *std.Io.Reader) dim.Content.RenderError!void { try exec.inner.mkfile(path, reader); } @@ -171,6 +177,9 @@ fn Executor(comptime T: type) type { error.ProcessFdQuotaExceeded => error.IoError, error.SystemFdQuotaExceeded => error.IoError, error.NotDir => error.IoError, + error.ProcessNotFound, + error.PermissionDenied, + => error.IoError, }; } }; @@ -185,23 +194,23 @@ fn parse_path(ctx: dim.Context) ![:0]const u8 { } if (!std.mem.startsWith(u8, path, "/")) { - try ctx.report_nonfatal_error("Path '{}' did not start with a \"/\"", .{ - std.zig.fmtEscapes(path), + try ctx.report_nonfatal_error("Path '{f}' did not start with a \"/\"", .{ + std.zig.fmtString(path), }); } for (path) |c| { if (c < 0x20 or c == 0x7F or c == '\\') { - try ctx.report_nonfatal_error("Path '{}' contains invalid character 0x{X:0>2}", .{ - std.zig.fmtEscapes(path), + try ctx.report_nonfatal_error("Path '{f}' contains invalid character 0x{X:0>2}", .{ + std.zig.fmtString(path), c, }); } } _ = std.unicode.Utf8View.init(path) catch |err| { - try ctx.report_nonfatal_error("Path '{}' is not a valid UTF-8 string: {s}", .{ - std.zig.fmtEscapes(path), + try ctx.report_nonfatal_error("Path '{f}' is not a valid UTF-8 string: {s}", .{ + std.zig.fmtString(path), @errorName(err), }); }; @@ -249,8 +258,8 @@ pub fn parse_ops(ctx: dim.Context, end_seq: []const u8, handler: anytype) !void } fn normalize(allocator: std.mem.Allocator, src_path: []const u8) ![:0]const u8 { - var list = std.ArrayList([]const u8).init(allocator); - defer list.deinit(); + var list: std.ArrayList([]const u8) = .empty; + defer list.deinit(allocator); var parts = std.mem.tokenizeAny(u8, src_path, "\\/"); @@ -263,7 +272,7 @@ fn normalize(allocator: std.mem.Allocator, src_path: []const u8) ![:0]const u8 { _ = list.pop(); } else { // this is an actual "descend" - try list.append(part); + try list.append(allocator, part); } } diff --git a/src/components/part/GptPartitionTable.zig b/src/components/part/GptPartitionTable.zig index e85007c..daff951 100644 --- a/src/components/part/GptPartitionTable.zig +++ b/src/components/part/GptPartitionTable.zig @@ -12,12 +12,12 @@ partitions: []Partition, pub fn parse(ctx: dim.Context) !dim.Content { const pt = try ctx.alloc_object(PartTable); - pt.* = PartTable{ + pt.* = .{ .disk_id = null, .partitions = undefined, }; - var partitions = std.ArrayList(Partition).init(ctx.get_arena()); + var partitions: std.ArrayList(Partition) = .empty; loop: while (true) { const kw = try ctx.parse_enum(enum { guid, @@ -34,7 +34,7 @@ pub fn parse(ctx: dim.Context) !dim.Content { pt.disk_id = Guid.parse(guid_str[0..36].*) catch |err| return ctx.report_fatal_error("Invalid disk GUID: {}", .{err}); }, - .part => (try partitions.addOne()).* = try parsePartition(ctx), + .part => (try partitions.addOne(ctx.get_arena())).* = try parsePartition(ctx), .@"legacy-bootable" => pt.legacy_bootable = true, .endgpt => break :loop, } @@ -75,7 +75,7 @@ pub fn parse(ctx: dim.Context) !dim.Content { } fn parsePartition(ctx: dim.Context) !Partition { - var part = Partition{ + var part: Partition = .{ .type = undefined, .part_id = null, .size = null, @@ -113,7 +113,10 @@ fn parsePartition(ctx: dim.Context) !Partition { const type_guid = known_types.get(type_name) orelse blk: { if (type_name.len == 36) if (Guid.parse(type_name[0..36].*)) |guid| break :blk guid else |_| {}; - return ctx.report_fatal_error("unknown partition type: `{}`", .{std.zig.fmtEscapes(type_name)}); + return ctx.report_fatal_error( + "unknown partition type: `{f}`", + .{std.zig.fmtString(type_name)}, + ); }; try updater.set(.type, type_guid); diff --git a/src/components/part/MbrPartitionTable.zig b/src/components/part/MbrPartitionTable.zig index 66ceca8..2245de4 100644 --- a/src/components/part/MbrPartitionTable.zig +++ b/src/components/part/MbrPartitionTable.zig @@ -115,7 +115,10 @@ fn parse_partition(ctx: dim.Context) !Partition { value else |_| known_partition_types.get(part_name) orelse blk: { - try ctx.report_nonfatal_error("unknown partition type '{}'", .{std.zig.fmtEscapes(part_name)}); + try ctx.report_nonfatal_error( + "unknown partition type '{f}'", + .{std.zig.fmtString(part_name)}, + ); break :blk 0x00; }; diff --git a/src/dim.zig b/src/dim.zig index 01438d8..d96260d 100644 --- a/src/dim.zig +++ b/src/dim.zig @@ -31,20 +31,28 @@ const Options = struct { script: ?[]const u8 = null, @"import-env": bool = false, @"deps-file": ?[]const u8 = null, + @"script-root": ?[]const u8 = null, }; const usage = \\dim OPTIONS [VARS] \\ \\OPTIONS: - \\ --output - \\ mandatory: where to store the output file - \\ --size - \\ mandatory: how big is the resulting disk image? allowed suffixes: k,K,M,G - \\ --script - \\ mandatory: which script file to execute? - \\[--import-env] - \\ optional: if set, imports the current process environment into the variables + \\ --output + \\ mandatory: where to store the output file + \\ --size + \\ mandatory: how big is the resulting disk image? allowed suffixes: k,K,M,G + \\ --script + \\ mandatory: which script file to execute? + \\ --deps-file + \\ optional: writes a Makefile snippet containing the additional file dependencies + \\ --import-env + \\ optional: if set, imports the current process environment into the variables + \\ --script-root + \\ optional: if given, will make dim behave like --script would have this instead of the actual one. + \\ this is especially useful when you use the Zig build integration which may pass relative paths + \\ to the build root, but the script is stored inside the .zig-cache folder. + \\ \\VARS: \\{ KEY=VALUE }* \\ multiple ≥ 0: Sets variable KEY to VALUE @@ -54,6 +62,9 @@ const usage = const VariableMap = std.StringArrayHashMapUnmanaged([]const u8); var global_deps_file: ?std.fs.File = null; +var global_deps_buffer: []u8 = undefined; +var global_deps_file_writer: std.fs.File.Writer = undefined; +var global_deps_writer: *std.Io.Writer = undefined; pub fn main() !u8 { var gpa_impl: std.heap.DebugAllocator(.{}) = .init; @@ -89,8 +100,8 @@ pub fn main() !u8 { const val = pos[idx + 1 ..]; try var_map.put(gpa, key, val); } else { - std.debug.print("unexpected argument positional '{}'\n", .{ - std.zig.fmtEscapes(pos), + std.debug.print("unexpected argument positional '{f}'\n", .{ + std.zig.fmtString(pos), }); bad_args = true; } @@ -111,21 +122,28 @@ pub fn main() !u8 { if (options.@"deps-file") |deps_file_path| { global_deps_file = try std.fs.cwd().createFile(deps_file_path, .{}); + global_deps_buffer = try gpa.alloc(u8, 1024); + + global_deps_file_writer = global_deps_file.?.writer(global_deps_buffer); + global_deps_writer = &global_deps_file_writer.interface; - try global_deps_file.?.writer().print( + try global_deps_writer.print( \\{s}: {s} , .{ output_path, script_path, }); } - defer if (global_deps_file) |deps_file| + defer if (global_deps_file) |deps_file| { + global_deps_file_writer.end() catch {}; deps_file.close(); + gpa.free(global_deps_buffer); + }; var mem_arena: std.heap.ArenaAllocator = .init(gpa); defer mem_arena.deinit(); - var env = Environment{ + var env: Environment = .{ .allocator = gpa, .arena = mem_arena.allocator(), .vars = &var_map, @@ -145,7 +163,7 @@ pub fn main() !u8 { env.parser = &parser; try parser.push_source(.{ - .path = script_path, + .path = options.@"script-root" orelse script_path, .contents = script_source, }); @@ -159,6 +177,8 @@ pub fn main() !u8 { return 1; } + env.mode = .execute; + { var output_file = try current_dir.createFile(output_path, .{ .read = true }); defer output_file.close(); @@ -170,23 +190,27 @@ pub fn main() !u8 { try root_content.render(&stream); } - if (global_deps_file) |deps_file| { - try deps_file.writeAll("\n"); + if (global_deps_file != null) { + try global_deps_writer.writeAll("\n"); + } + + if (env.error_flag) { + return 1; } return 0; } pub fn declare_file_dependency(path: []const u8) !void { - const deps_file = global_deps_file orelse return; + if (global_deps_file == null) return; const stat = std.fs.cwd().statFile(path) catch |err| switch (err) { error.IsDir => return, else => |e| return e, }; if (stat.kind != .directory) { - try deps_file.writeAll(" \\\n "); - try deps_file.writeAll(path); + try global_deps_writer.writeAll(" \\\n "); + try global_deps_writer.writeAll(path); } } @@ -227,7 +251,7 @@ pub const Context = struct { pub fn parse_string(ctx: Context) Environment.ParseError![]const u8 { const str = try ctx.env.parser.next(); - // std.debug.print("token: '{}'\n", .{std.zig.fmtEscapes(str)}); + // std.debug.print("token: '{f}'\n", .{std.zig.fmtString(str)}); return str; } @@ -237,6 +261,7 @@ pub const Context = struct { const abs_path = try ctx.env.parser.get_include_path(ctx.env.arena, rel_path); return .{ + .env = ctx.env, .root_dir = ctx.env.include_base, .rel_path = abs_path, }; @@ -244,7 +269,7 @@ pub const Context = struct { pub fn parse_enum(ctx: Context, comptime E: type) Environment.ParseError!E { if (@typeInfo(E) != .@"enum") - @compileError("get_enum requires an enum type!"); + @compileError("parse_enum requires an enum type!"); const tag_name = try ctx.parse_string(); const converted = std.meta.stringToEnum( E, @@ -252,7 +277,10 @@ pub const Context = struct { ); if (converted) |ok| return ok; - std.debug.print("detected invalid enum tag for {s}: \"{}\"\n", .{ @typeName(E), std.zig.fmtEscapes(tag_name) }); + std.debug.print( + "detected invalid enum tag for {s}: \"{f}\"\n", + .{ @typeName(E), std.zig.fmtString(tag_name) }, + ); std.debug.print("valid options are:\n", .{}); for (std.enums.values(E)) |val| { @@ -264,7 +292,7 @@ pub const Context = struct { pub fn parse_integer(ctx: Context, comptime I: type, base: u8) Environment.ParseError!I { if (@typeInfo(I) != .int) - @compileError("get_integer requires an integer type!"); + @compileError("parse_integer requires an integer type!"); return std.fmt.parseInt( I, try ctx.parse_string(), @@ -293,8 +321,8 @@ pub const Context = struct { } } - return ctx.report_fatal_error("unknown content type: '{}'", .{ - std.zig.fmtEscapes(content_type_str), + return ctx.report_fatal_error("unknown content type: '{f}'", .{ + std.zig.fmtString(content_type_str), }); } }; @@ -363,6 +391,7 @@ const Environment = struct { include_base: std.fs.Dir, vars: *const VariableMap, error_flag: bool = false, + mode: enum { parse, execute } = .parse, io: Parser.IO = .{ .fetch_file_fn = fetch_file, @@ -377,7 +406,10 @@ const Environment = struct { fn report_error(env: *Environment, comptime fmt: []const u8, params: anytype) error{OutOfMemory}!void { env.error_flag = true; - std.log.err("PARSE ERROR: " ++ fmt, params); + switch (env.mode) { + .parse => std.log.err("PARSE ERROR: " ++ fmt, params), + .execute => std.log.err("EXECUTE ERROR: " ++ fmt, params), + } } fn fetch_file(io: *const Parser.IO, allocator: std.mem.Allocator, path: []const u8) error{ FileNotFound, IoError, OutOfMemory, InvalidPath }![]const u8 { @@ -388,9 +420,9 @@ const Environment = struct { error.FileNotFound => { const ctx = Context{ .env = @constCast(env) }; var buffer: [std.fs.max_path_bytes]u8 = undefined; - try ctx.report_nonfatal_error("failed to open file: \"{}/{}\"", .{ - std.zig.fmtEscapes(env.include_base.realpath(".", &buffer) catch return error.FileNotFound), - std.zig.fmtEscapes(path), + try ctx.report_nonfatal_error("failed to open file: \"{f}/{f}\"", .{ + std.zig.fmtString(env.include_base.realpath(".", &buffer) catch return error.FileNotFound), + std.zig.fmtString(path), }); return error.FileNotFound; }, @@ -398,7 +430,11 @@ const Environment = struct { }; errdefer allocator.free(contents); - const name: FileName = .{ .root_dir = env.include_base, .rel_path = path }; + const name: FileName = .{ + .env = undefined, + .root_dir = env.include_base, + .rel_path = path, + }; try name.declare_dependency(); return contents; @@ -415,10 +451,12 @@ const Environment = struct { /// /// pub const Content = struct { - pub const RenderError = FileName.OpenError || FileHandle.ReadError || BinaryStream.WriteError || error{ + pub const RenderError = FileName.OpenError || std.Io.Reader.Error || BinaryStream.WriteError || error{ ConfigurationError, OutOfBounds, OutOfMemory, + ReadFailed, + WriteFailed, }; pub const GuessError = FileName.GetSizeError; @@ -461,6 +499,7 @@ pub const Content = struct { }; pub const FileName = struct { + env: *Environment, root_dir: std.fs.Dir, rel_path: []const u8, @@ -470,10 +509,10 @@ pub const FileName = struct { const file = name.root_dir.openFile(name.rel_path, .{}) catch |err| switch (err) { error.FileNotFound => { var buffer: [std.fs.max_path_bytes]u8 = undefined; - std.log.err("failed to open \"{}/{}\": not found", .{ - std.zig.fmtEscapes(name.root_dir.realpath(".", &buffer) catch |e| @errorName(e)), - std.zig.fmtEscapes(name.rel_path), - }); + name.env.report_error("failed to open \"{f}/{f}\": not found", .{ + std.zig.fmtString(name.root_dir.realpath(".", &buffer) catch |e| @errorName(e)), + std.zig.fmtString(name.rel_path), + }) catch |e| std.debug.assert(e == error.OutOfMemory); return error.FileNotFound; }, @@ -503,6 +542,8 @@ pub const FileName = struct { error.NotDir, error.FileLocksNotSupported, error.FileBusy, + error.ProcessNotFound, + error.PermissionDenied, => return error.IoError, }; @@ -515,10 +556,10 @@ pub const FileName = struct { const dir = name.root_dir.openDir(name.rel_path, .{ .iterate = true }) catch |err| switch (err) { error.FileNotFound => { var buffer: [std.fs.max_path_bytes]u8 = undefined; - std.log.err("failed to open \"{}/{}\": not found", .{ - std.zig.fmtEscapes(name.root_dir.realpath(".", &buffer) catch |e| @errorName(e)), - std.zig.fmtEscapes(name.rel_path), - }); + name.env.report_error("failed to open \"{f}/{f}\": not found", .{ + std.zig.fmtString(name.root_dir.realpath(".", &buffer) catch |e| @errorName(e)), + std.zig.fmtString(name.rel_path), + }) catch |e| std.debug.assert(e == error.OutOfMemory); return error.FileNotFound; }, @@ -538,6 +579,8 @@ pub const FileName = struct { error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded, error.NotDir, + error.ProcessNotFound, + error.PermissionDenied, => return error.IoError, }; @@ -593,24 +636,20 @@ pub const FileName = struct { return stat.size; } - pub fn copy_to(file: FileName, stream: *BinaryStream) (OpenError || FileHandle.ReadError || BinaryStream.WriteError)!void { + pub fn copy_to(file: FileName, stream: *BinaryStream) (OpenError || error{ ReadFailed, WriteFailed })!void { var handle = try file.open(); defer handle.close(); - var fifo: std.fifo.LinearFifo(u8, .{ .Static = 8192 }) = .init(); + var file_reader = handle.file.reader(&.{}); - try fifo.pump( - handle.reader(), - stream.writer(), - ); + var buffer: [8192]u8 = undefined; + var writer = stream.writer().adaptToNewApi(&buffer); + + _ = try file_reader.interface.streamRemaining(&writer.new_interface); } }; pub const FileHandle = struct { - pub const ReadError = error{ReadFileFailed}; - - pub const Reader = std.io.Reader(std.fs.File, ReadError, read_some); - file: std.fs.File, pub fn close(fd: *FileHandle) void { @@ -618,42 +657,21 @@ pub const FileHandle = struct { fd.* = undefined; } - pub fn reader(fd: FileHandle) Reader { - return .{ .context = fd.file }; - } - - fn read_some(file: std.fs.File, data: []u8) ReadError!usize { - return file.read(data) catch |err| switch (err) { - error.InputOutput, - error.AccessDenied, - error.BrokenPipe, - error.SystemResources, - error.OperationAborted, - error.LockViolation, - error.WouldBlock, - error.ConnectionResetByPeer, - error.ProcessNotFound, - error.Unexpected, - error.IsDir, - error.ConnectionTimedOut, - error.NotOpenForReading, - error.SocketNotConnected, - error.Canceled, - => return error.ReadFileFailed, - }; + pub fn reader(fd: FileHandle, buffer: []u8) std.fs.File.Reader { + return fd.file.reader(buffer); } }; pub const BinaryStream = struct { pub const WriteError = error{ Overflow, IoError }; pub const ReadError = error{ Overflow, IoError }; - pub const Writer = std.io.Writer(*BinaryStream, WriteError, write_some); + pub const Writer = std.io.GenericWriter(*BinaryStream, WriteError, write_some); backing: Backing, virtual_offset: u64 = 0, - /// Max number of bytes that can be written + /// Max number of bytes that an be written length: u64, /// Constructs a BinaryStream from a slice. @@ -707,26 +725,9 @@ pub const BinaryStream = struct { switch (bs.backing) { .buffer => |ptr| @memcpy(data, ptr[@intCast(offset)..][0..data.len]), .file => |state| { - state.file.seekTo(state.base + offset) catch return error.IoError; - state.file.reader().readNoEof(data) catch |err| switch (err) { - error.InputOutput, - error.AccessDenied, - error.BrokenPipe, - error.SystemResources, - error.OperationAborted, - error.LockViolation, - error.WouldBlock, - error.ConnectionResetByPeer, - error.ProcessNotFound, - error.Unexpected, - error.IsDir, - error.ConnectionTimedOut, - error.NotOpenForReading, - error.SocketNotConnected, - error.Canceled, - error.EndOfStream, - => return error.IoError, - }; + const len = state.file.pread(data, state.base + offset) catch return error.IoError; + if (len != data.len) + return error.Overflow; }, } } @@ -739,26 +740,9 @@ pub const BinaryStream = struct { switch (bs.backing) { .buffer => |ptr| @memcpy(ptr[@intCast(offset)..][0..data.len], data), .file => |state| { - state.file.seekTo(state.base + offset) catch return error.IoError; - state.file.writeAll(data) catch |err| switch (err) { - error.DiskQuota, error.NoSpaceLeft, error.FileTooBig => return error.Overflow, - - error.InputOutput, - error.DeviceBusy, - error.InvalidArgument, - error.AccessDenied, - error.BrokenPipe, - error.SystemResources, - error.OperationAborted, - error.NotOpenForWriting, - error.LockViolation, - error.WouldBlock, - error.ConnectionResetByPeer, - error.ProcessNotFound, - error.NoDevice, - error.Unexpected, - => return error.IoError, - }; + const len = state.file.pwrite(data, state.base + offset) catch return error.IoError; + if (len != data.len) + return error.Overflow; }, } } @@ -838,10 +822,7 @@ pub const DiskSize = enum(u64) { return @intFromEnum(ds); } - pub fn format(ds: DiskSize, fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = opt; - + pub fn format(ds: DiskSize, writer: *std.Io.Writer) std.Io.Writer.Error!void { const size = ds.size_in_bytes(); const div: u64, const unit: []const u8 = if (size > GiB) @@ -861,7 +842,7 @@ pub const DiskSize = enum(u64) { const scaled_value = (1000 * size) / div; var buf: [std.math.log2_int_ceil(u64, std.math.maxInt(u64))]u8 = undefined; - const divided = try std.fmt.bufPrint(&buf, "{d}", .{scaled_value}); + const divided = std.fmt.bufPrint(&buf, "{d}", .{scaled_value}) catch return error.WriteFailed; std.debug.assert(divided.len >= 3);