diff --git a/build.zig.zon b/build.zig.zon index 3ceb020..0a7f9fe 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,12 +4,14 @@ .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/CascadeOS/zfat#2806bfd983dd810c9c74b0ea6a63f783208518b5", + // .hash = "zfat-0.16.0-SNNK9fKtTgASssfmCblZwRMLU4pndVtwxTNhYCegBOyA", + .path = "../zfat", }, .args = .{ - .url = "git+https://github.com/ikskuh/zig-args.git#9425b94c103a031777fdd272c555ce93a7dea581", - .hash = "args-0.0.0-CiLiqv_NAAC97fGpk9hS2K681jkiqPsWP6w3ucb_ctGH", + .path = "../zig-args", + // .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..4f5aea6 100644 --- a/src/BuildInterface.zig +++ b/src/BuildInterface.zig @@ -32,6 +32,8 @@ pub fn createDisk(dimmer: Interface, size: u64, content: Content) std.Build.Lazy const compile_script = b.addRunArtifact(dimmer.dimmer_exe); + compile_script.setCwd(script_file.dirname()); + _ = compile_script.addPrefixedDepFileOutputArg("--deps-file=", "image.d"); compile_script.addArg(b.fmt("--size={d}", .{size})); @@ -63,13 +65,13 @@ pub fn createDisk(dimmer: Interface, size: u64, content: Content) std.Build.Lazy } 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); + 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 +101,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 +119,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 +160,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 +178,10 @@ 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.part_guid) |pg| { + try cw.code.print(" guid \"{s}\"", .{&pg}); } 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,29 +218,29 @@ 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 = std.fmt.Alt([]const u8, formatPath); + 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 { @@ -248,13 +253,9 @@ const ContentWriter = struct { fn formatLazyPath( data: struct { ContentWriter, std.Build.LazyPath, UsageHint }, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { + writer: *std.Io.Writer, + ) error{WriteFailed}!void { const cw, const path, const hint = data; - _ = fmt; - _ = options; switch (path) { .cwd_relative, @@ -267,7 +268,7 @@ const ContentWriter = struct { std.debug.assert(std.fs.path.isAbsolute(full_path)); - try writer.print("{}", .{ + try writer.print("{f}", .{ fmtPath(full_path), }); }, @@ -278,7 +279,7 @@ 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}); }, @@ -287,13 +288,8 @@ const ContentWriter = struct { fn formatPath( path: []const u8, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, + writer: *std.Io.Writer, ) !void { - _ = fmt; - _ = options; - const is_safe_word = for (path) |char| { switch (char) { 'A'...'Z', @@ -318,7 +314,7 @@ const ContentWriter = struct { if (c == '\\') { try writer.writeAll("/"); } else { - try writer.print("{}", .{std.zig.fmtEscapes(&[_]u8{c})}); + try writer.print("{f}", .{std.zig.fmtString(&[_]u8{c})}); } } @@ -385,7 +381,7 @@ pub const GptPartTable = struct { @"microsoft-basic-data", @"microsoft-reserved", @"windows-recovery", - @"plan9", + plan9, @"linux-swap", @"linux-fs", @"linux-reserved", @@ -394,6 +390,7 @@ pub const GptPartTable = struct { guid: [36]u8, }, name: ?[]const u8 = null, + part_guid: ?[36]u8 = null, size: ?u64 = null, offset: ?u64 = null, data: Content, @@ -419,7 +416,7 @@ pub const FatFs = struct { pub const FileSystemBuilder = struct { b: *std.Build, - list: std.ArrayListUnmanaged(FileSystem.Item), + list: std.ArrayList(FileSystem.Item), pub fn init(b: *std.Build) FileSystemBuilder { return FileSystemBuilder{ diff --git a/src/Parser.zig b/src/Parser.zig index ff7c179..d656f73 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -18,18 +18,19 @@ pub const Error = Tokenizer.Error || error{ UnknownDirective, OutOfMemory, InvalidEscapeSequence, + Canceled, }; pub const IO = struct { - fetch_file_fn: *const fn (io: *const IO, std.mem.Allocator, path: []const u8) error{ FileNotFound, IoError, OutOfMemory, InvalidPath }![]const u8, - resolve_variable_fn: *const fn (io: *const IO, name: []const u8) error{UnknownVariable}![]const u8, + fetch_file_fn: *const fn (stdio: std.Io, io: *const IO, std.mem.Allocator, path: []const u8) error{ FileNotFound, IoError, OutOfMemory, InvalidPath, Canceled }![]const u8, + resolve_variable_fn: *const fn (stdio: std.Io, io: *const IO, name: []const u8) error{UnknownVariable}![]const u8, - pub fn fetch_file(io: *const IO, allocator: std.mem.Allocator, path: []const u8) error{ FileNotFound, IoError, OutOfMemory, InvalidPath }![]const u8 { - return io.fetch_file_fn(io, allocator, path); + pub fn fetch_file(io: *const IO, stdio: std.Io, allocator: std.mem.Allocator, path: []const u8) error{ FileNotFound, IoError, OutOfMemory, InvalidPath, Canceled }![]const u8 { + return io.fetch_file_fn(stdio,io, allocator, path); } - pub fn resolve_variable(io: *const IO, name: []const u8) error{UnknownVariable}![]const u8 { - return io.resolve_variable_fn(io, name); + pub fn resolve_variable(io: *const IO, stdio: std.Io, name: []const u8) error{UnknownVariable}![]const u8 { + return io.resolve_variable_fn(stdio,io, name); } }; @@ -84,10 +85,10 @@ pub fn push_source(parser: *Parser, options: struct { }; } -pub fn push_file(parser: *Parser, include_path: []const u8) !void { +pub fn push_file(parser: *Parser, stdio: std.Io, include_path: []const u8) !void { const abs_include_path = try parser.get_include_path(parser.arena.allocator(), include_path); - const file_contents = try parser.io.fetch_file(parser.arena.allocator(), abs_include_path); + const file_contents = try parser.io.fetch_file(stdio , parser.arena.allocator(), abs_include_path); const index = parser.file_stack.len; parser.file_stack.len += 1; @@ -120,14 +121,14 @@ pub fn get_include_path(parser: Parser, allocator: std.mem.Allocator, rel_includ return abs_include_path; } -pub fn next(parser: *Parser) (Error || error{UnexpectedEndOfFile})![]const u8 { - return if (try parser.next_or_eof()) |word| +pub fn next(parser: *Parser, stdio: std.Io) (Error || error{UnexpectedEndOfFile})![]const u8 { + return if (try parser.next_or_eof(stdio)) |word| word else error.UnexpectedEndOfFile; } -pub fn next_or_eof(parser: *Parser) Error!?[]const u8 { +pub fn next_or_eof(parser: *Parser, stdio: std.Io) Error!?[]const u8 { fetch_loop: while (parser.file_stack.len > 0) { const top = &parser.file_stack[parser.file_stack.len - 1]; @@ -141,7 +142,7 @@ pub fn next_or_eof(parser: *Parser) Error!?[]const u8 { switch (token.type) { .whitespace, .comment => unreachable, - .word, .variable, .string => return try parser.resolve_value( + .word, .variable, .string => return try parser.resolve_value(stdio, token.type, top.tokenizer.get_text(token), ), @@ -152,14 +153,14 @@ pub fn next_or_eof(parser: *Parser) Error!?[]const u8 { if (std.mem.eql(u8, directive, "!include")) { if (try fetch_token(&top.tokenizer)) |path_token| { const rel_include_path = switch (path_token.type) { - .word, .variable, .string => try parser.resolve_value( + .word, .variable, .string => try parser.resolve_value(stdio, path_token.type, top.tokenizer.get_text(path_token), ), .comment, .directive, .whitespace => return error.BadDirective, }; - try parser.push_file(rel_include_path); + try parser.push_file(stdio, rel_include_path); } else { return error.ExpectedIncludePath; } @@ -189,11 +190,11 @@ fn fetch_token(tok: *Tokenizer) Tokenizer.Error!?Token { } } -fn resolve_value(parser: *Parser, token_type: TokenType, text: []const u8) ![]const u8 { +fn resolve_value(parser: *Parser, stdio: std.Io, token_type: TokenType, text: []const u8) ![]const u8 { return switch (token_type) { .word => text, - .variable => try parser.io.resolve_variable( + .variable => try parser.io.resolve_variable(stdio, text[1..], ), @@ -208,10 +209,12 @@ 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()); - defer unescaped.deinit(); + const allocator = parser.arena.allocator(); - try unescaped.ensureTotalCapacityPrecise(content_slice.len); + var unescaped = std.ArrayList(u8).empty; + defer unescaped.deinit(allocator); + + try unescaped.ensureTotalCapacityPrecise(allocator, content_slice.len); { var i: usize = 0; @@ -220,7 +223,7 @@ fn resolve_value(parser: *Parser, token_type: TokenType, text: []const u8) ![]co i += 1; if (c != '\\') { - try unescaped.append(c); + try unescaped.append(allocator, c); continue; } @@ -233,20 +236,20 @@ fn resolve_value(parser: *Parser, token_type: TokenType, text: []const u8) ![]co errdefer std.log.err("invalid escape sequence: \\{s}", .{[_]u8{esc_code}}); switch (esc_code) { - 'r' => try unescaped.append('\r'), - 'n' => try unescaped.append('\n'), - 't' => try unescaped.append('\t'), - '\\' => try unescaped.append('\\'), - '\"' => try unescaped.append('\"'), - '\'' => try unescaped.append('\''), - 'e' => try unescaped.append('\x1B'), + 'r' => try unescaped.append(allocator, '\r'), + 'n' => try unescaped.append(allocator, '\n'), + 't' => try unescaped.append(allocator, '\t'), + '\\' => try unescaped.append(allocator, '\\'), + '\"' => try unescaped.append(allocator, '\"'), + '\'' => try unescaped.append(allocator, '\''), + 'e' => try unescaped.append(allocator, '\x1B'), else => return error.InvalidEscapeSequence, } } } - return try unescaped.toOwnedSlice(); + return try unescaped.toOwnedSlice(allocator); }, .comment, .directive, .whitespace => unreachable, 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/EmptyData.zig b/src/components/EmptyData.zig index da335f0..6393a1f 100644 --- a/src/components/EmptyData.zig +++ b/src/components/EmptyData.zig @@ -8,14 +8,16 @@ const dim = @import("../dim.zig"); const EmptyData = @This(); -pub fn parse(ctx: dim.Context) !dim.Content { +pub fn parse(ctx: dim.Context, stdio: std.Io) !dim.Content { _ = ctx; + _ = stdio; return .create_handle(undefined, .create(@This(), .{ .render_fn = render, })); } -fn render(self: *EmptyData, stream: *dim.BinaryStream) dim.Content.RenderError!void { +fn render(self: *EmptyData, io: std.Io, stream: *dim.BinaryStream) dim.Content.RenderError!void { _ = self; _ = stream; + _ = io; } diff --git a/src/components/FillData.zig b/src/components/FillData.zig index 82b8cb4..da01ef4 100644 --- a/src/components/FillData.zig +++ b/src/components/FillData.zig @@ -9,19 +9,21 @@ const FillData = @This(); fill_value: u8, -pub fn parse(ctx: dim.Context) !dim.Content { +pub fn parse(ctx: dim.Context, stdio: std.Io) !dim.Content { const pf = try ctx.alloc_object(FillData); pf.* = .{ - .fill_value = try ctx.parse_integer(u8, 0), + .fill_value = try ctx.parse_integer(stdio, u8, 0), }; return .create_handle(pf, .create(@This(), .{ .render_fn = render, })); } -fn render(self: *FillData, stream: *dim.BinaryStream) dim.Content.RenderError!void { - try stream.writer().writeByteNTimes( +fn render(self: *FillData, io: std.Io, stream: *dim.BinaryStream) dim.Content.RenderError!void { + var writer = stream.writer(io); + writer.interface.splatByteAll( self.fill_value, stream.length, - ); + ) catch return error.Overflow; // TODO FIX we don't know actually why this failed. + // std.Io.Writer only returns error.WriteFailed. } diff --git a/src/components/PasteFile.zig b/src/components/PasteFile.zig index 3cbd9e7..fec4b8a 100644 --- a/src/components/PasteFile.zig +++ b/src/components/PasteFile.zig @@ -5,16 +5,16 @@ const PasteFile = @This(); file_handle: dim.FileName, -pub fn parse(ctx: dim.Context) !dim.Content { +pub fn parse(ctx: dim.Context, stdio: std.Io) !dim.Content { const pf = try ctx.alloc_object(PasteFile); pf.* = .{ - .file_handle = try ctx.parse_file_name(), + .file_handle = try ctx.parse_file_name(stdio), }; return .create_handle(pf, .create(@This(), .{ .render_fn = render, })); } -fn render(self: *PasteFile, stream: *dim.BinaryStream) dim.Content.RenderError!void { - try self.file_handle.copy_to(stream); +fn render(self: *PasteFile, io: std.Io, stream: *dim.BinaryStream) dim.Content.RenderError!void { + try self.file_handle.copy_to(io, stream); } diff --git a/src/components/fs/FatFileSystem.zig b/src/components/fs/FatFileSystem.zig index 5b5de50..587c132 100644 --- a/src/components/fs/FatFileSystem.zig +++ b/src/components/fs/FatFileSystem.zig @@ -14,12 +14,12 @@ 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, -pub fn parse(ctx: dim.Context) !dim.Content { - const fat_type = try ctx.parse_enum(FatType); +pub fn parse(ctx: dim.Context, stdio: std.Io) !dim.Content { + const fat_type = try ctx.parse_enum(stdio,FatType); const pf = try ctx.alloc_object(FAT); pf.* = .{ @@ -32,7 +32,7 @@ pub fn parse(ctx: dim.Context) !dim.Content { .updater = .init(ctx, pf), }; - try common.parse_ops(ctx, "endfat", &appender); + try common.parse_ops(ctx, stdio,"endfat", &appender); try appender.updater.validate(); @@ -59,7 +59,7 @@ const Appender = struct { try self.fat.ops.append(op); } - pub fn parse_custom_op(self: *@This(), ctx: dim.Context, str_op: []const u8) !void { + pub fn parse_custom_op(self: *@This(), stdio: std.Io, ctx: dim.Context, str_op: []const u8) !void { const Op = enum { label, fats, @@ -72,23 +72,24 @@ const Appender = struct { .{str_op}, ); switch (op) { - .label => try self.updater.set(.label, try ctx.parse_string()), - .fats => try self.updater.set(.fats, try ctx.parse_enum(fatfs.FatTables)), - .@"root-size" => try self.updater.set(.rootdir_size, try ctx.parse_integer(c_uint, 0)), - .@"sector-align" => try self.updater.set(.sector_align, try ctx.parse_integer(c_uint, 0)), - .@"cluster-size" => try self.updater.set(.cluster_size, try ctx.parse_integer(u32, 0)), + .label => try self.updater.set(.label, try ctx.parse_string(stdio)), + .fats => try self.updater.set(.fats, try ctx.parse_enum(stdio, fatfs.FatTables)), + .@"root-size" => try self.updater.set(.rootdir_size, try ctx.parse_integer(stdio,c_uint, 0)), + .@"sector-align" => try self.updater.set(.sector_align, try ctx.parse_integer(stdio, c_uint, 0)), + .@"cluster-size" => try self.updater.set(.cluster_size, try ctx.parse_integer(stdio, u32, 0)), } } }; -fn render(self: *FAT, stream: *dim.BinaryStream) dim.Content.RenderError!void { +fn render(self: *FAT, io: std.Io, stream: *dim.BinaryStream) dim.Content.RenderError!void { + fatfs.io = io; var bsd: BinaryStreamDisk = .{ .stream = stream }; const min_size, const max_size = self.format_as.get_size_limits(); 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,7 +99,7 @@ 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)), @@ -147,8 +148,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, }); @@ -158,7 +159,7 @@ fn render(self: *FAT, stream: *dim.BinaryStream) dim.Content.RenderError!void { const wrapper = AtomicOps{}; for (ops) |op| { - try op.execute(wrapper); + try op.execute(io, wrapper); } } @@ -212,7 +213,7 @@ const AtomicOps = struct { }; } - 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 +245,28 @@ 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 writer_buf: [8192]u8 = undefined; + var writer = fs_file.writer(&writer_buf); + + _ = reader.streamRemaining(&writer.interface) catch return error.IoError; + + writer.interface.flush() catch return error.IoError; + + // TODO we've lost a lot of error specificity due to the use of the new APIs + // See old code: + + // 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"), + // }; } }; @@ -283,19 +293,20 @@ const BinaryStreamDisk = struct { return disk_getStatus(intf); } - fn disk_read(intf: *fatfs.Disk, buff: [*]u8, sector: fatfs.LBA, count: c_uint) fatfs.Disk.Error!void { + fn disk_read(intf: *fatfs.Disk, io: std.Io, 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(io, block_size * sector, buff[0 .. count * block_size]) catch return error.IoError; } - fn disk_write(intf: *fatfs.Disk, buff: [*]const u8, sector: fatfs.LBA, count: c_uint) fatfs.Disk.Error!void { + fn disk_write(intf: *fatfs.Disk, io: std.Io, 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(io, block_size * sector, buff[0 .. count * block_size]) catch return error.IoError; } - fn disk_ioctl(intf: *fatfs.Disk, cmd: fatfs.IoCtl, buff: [*]u8) fatfs.Disk.Error!void { + fn disk_ioctl(intf: *fatfs.Disk, io: std.Io, cmd: fatfs.IoCtl, buff: [*]u8) fatfs.Disk.Error!void { + _ = io; const bsd: *BinaryStreamDisk = @fieldParentPtr("disk", intf); switch (cmd) { diff --git a/src/components/fs/common.zig b/src/components/fs/common.zig index a3421e9..68ba7f1 100644 --- a/src/components/fs/common.zig +++ b/src/components/fs/common.zig @@ -26,10 +26,10 @@ pub const FsOperation = union(enum) { contents: dim.Content, }, - pub fn execute(op: FsOperation, executor: anytype) !void { + pub fn execute(op: FsOperation, io: std.Io, executor: anytype) !void { const exec: Executor(@TypeOf(executor)) = .init(executor); - try exec.execute(op); + try exec.execute(io, op); } }; @@ -43,27 +43,29 @@ fn Executor(comptime T: type) type { return .{ .inner = wrapped }; } - fn execute(exec: Exec, op: FsOperation) dim.Content.RenderError!void { + fn execute(exec: Exec, io: std.Io, op: FsOperation) dim.Content.RenderError!void { switch (op) { .make_dir => |data| { try exec.recursive_mkdir(data.path); }, .copy_file => |data| { - var handle = data.source.open() catch |err| switch (err) { + var handle = data.source.open(io) catch |err| switch (err) { error.FileNotFound => return, // open() already reporeted the error else => |e| return e, }; - defer handle.close(); + defer handle.close(io); - try exec.add_file(data.path, handle.reader()); + var reader_buf: [1024]u8 = undefined; + var reader = handle.reader(io, &reader_buf); + try exec.add_file(data.path, &reader.interface); }, .copy_dir => |data| { - var iter_dir = data.source.open_dir() catch |err| switch (err) { + var iter_dir = data.source.open_dir(io) catch |err| switch (err) { error.FileNotFound => return, // open() already reporeted the error else => |e| return e, }; - defer iter_dir.close(); + defer iter_dir.close(io); var walker_memory: [16384]u8 = undefined; var temp_allocator: std.heap.FixedBufferAllocator = .init(&walker_memory); @@ -73,7 +75,7 @@ fn Executor(comptime T: type) type { var walker = try iter_dir.walk(temp_allocator.allocator()); defer walker.deinit(); - while (walker.next() catch |err| return walk_err(err)) |entry| { + while (walker.next(io) catch |err| return walk_err(err)) |entry| { const path = std.fmt.bufPrintZ(&path_memory, "{s}/{s}", .{ data.path, entry.path, @@ -88,10 +90,13 @@ fn Executor(comptime T: type) type { .rel_path = entry.basename, }; - var file = try fname.open(); - defer file.close(); + var file = try fname.open(io); + defer file.close(io); - try exec.add_file(path, file.reader()); + var reader_buf: [1024]u8 = undefined; + var reader = file.reader(io, &reader_buf); + + try exec.add_file(path, &reader.interface); }, .directory => { @@ -100,8 +105,8 @@ fn Executor(comptime T: type) type { else => { var realpath_buffer: [std.fs.max_path_bytes]u8 = undefined; - std.log.warn("cannot copy file {!s}: {s} is not a supported file type!", .{ - entry.dir.realpath(entry.path, &realpath_buffer), + std.log.warn("cannot copy file {s}: {s} is not a supported file type!", .{ + if (entry.dir.realPathFile(io,entry.path, &realpath_buffer)) |l| realpath_buffer[0..l] else |e| @errorName(e), @tagName(entry.kind), }); }, @@ -115,16 +120,15 @@ fn Executor(comptime T: type) type { var bs: dim.BinaryStream = .init_buffer(buffer); - try data.contents.render(&bs); - - var fbs: std.io.FixedBufferStream([]u8) = .{ .buffer = buffer, .pos = 0 }; + try data.contents.render(io, &bs); - try exec.add_file(data.path, fbs.reader()); + var reader = std.Io.Reader.fixed(buffer); + 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 +147,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); } @@ -151,33 +155,34 @@ fn Executor(comptime T: type) type { try exec.inner.mkdir(path); } - fn walk_err(err: (std.fs.Dir.OpenError || std.mem.Allocator.Error)) dim.Content.RenderError { + fn walk_err(err: (std.Io.Dir.OpenError || std.mem.Allocator.Error)) dim.Content.RenderError { return switch (err) { - error.InvalidUtf8 => error.InvalidPath, - error.InvalidWtf8 => error.InvalidPath, - error.BadPathName => error.InvalidPath, - error.NameTooLong => error.InvalidPath, + error.BadPathName, error.NameTooLong => error.InvalidPath, error.OutOfMemory => error.OutOfMemory, error.FileNotFound => error.FileNotFound, - error.DeviceBusy => error.IoError, - error.AccessDenied => error.IoError, - error.SystemResources => error.IoError, - error.NoDevice => error.IoError, - error.Unexpected => error.IoError, - error.NetworkNotFound => error.IoError, - error.SymLinkLoop => error.IoError, - error.ProcessFdQuotaExceeded => error.IoError, - error.SystemFdQuotaExceeded => error.IoError, - error.NotDir => error.IoError, + error.Canceled => error.Canceled, + + // error.DeviceBusy, + error.AccessDenied, + error.SystemResources, + error.NoDevice, + error.Unexpected, + error.NetworkNotFound, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + error.NotDir, + error.PermissionDenied, + => error.IoError, }; } }; } -fn parse_path(ctx: dim.Context) ![:0]const u8 { - const path = try ctx.parse_string(); +fn parse_path(ctx: dim.Context, stdio: std.Io) ![:0]const u8 { + const path = try ctx.parse_string(stdio); if (path.len == 0) { try ctx.report_nonfatal_error("Path cannot be empty!", .{}); @@ -185,23 +190,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), }); }; @@ -209,48 +214,48 @@ fn parse_path(ctx: dim.Context) ![:0]const u8 { return try normalize(ctx.get_arena(), path); } -pub fn parse_ops(ctx: dim.Context, end_seq: []const u8, handler: anytype) !void { +pub fn parse_ops(ctx: dim.Context, stdio: std.Io, end_seq: []const u8, handler: anytype) !void { while (true) { - const opsel = try ctx.parse_string(); + const opsel = try ctx.parse_string(stdio); if (std.mem.eql(u8, opsel, end_seq)) return; if (std.mem.eql(u8, opsel, "mkdir")) { - const path = try parse_path(ctx); + const path = try parse_path(ctx, stdio); try handler.append_common_op(FsOperation{ .make_dir = .{ .path = path }, }); } else if (std.mem.eql(u8, opsel, "copy-dir")) { - const path = try parse_path(ctx); - const src = try ctx.parse_file_name(); + const path = try parse_path(ctx, stdio); + const src = try ctx.parse_file_name(stdio); try handler.append_common_op(FsOperation{ .copy_dir = .{ .path = path, .source = src }, }); } else if (std.mem.eql(u8, opsel, "copy-file")) { - const path = try parse_path(ctx); - const src = try ctx.parse_file_name(); + const path = try parse_path(ctx, stdio); + const src = try ctx.parse_file_name(stdio); try handler.append_common_op(FsOperation{ .copy_file = .{ .path = path, .source = src }, }); } else if (std.mem.eql(u8, opsel, "create-file")) { - const path = try parse_path(ctx); - const size = try ctx.parse_mem_size(); - const contents = try ctx.parse_content(); + const path = try parse_path(ctx, stdio); + const size = try ctx.parse_mem_size(stdio); + const contents = try ctx.parse_content(stdio); try handler.append_common_op(FsOperation{ .create_file = .{ .path = path, .size = size, .contents = contents }, }); } else { - try handler.parse_custom_op(ctx, opsel); + try handler.parse_custom_op(stdio, ctx, opsel); } } } 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 +268,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..01b9547 100644 --- a/src/components/part/GptPartitionTable.zig +++ b/src/components/part/GptPartitionTable.zig @@ -10,16 +10,18 @@ disk_id: ?Guid, legacy_bootable: bool = false, partitions: []Partition, -pub fn parse(ctx: dim.Context) !dim.Content { +pub fn parse(ctx: dim.Context, stdio: std.Io) !dim.Content { const pt = try ctx.alloc_object(PartTable); pt.* = PartTable{ .disk_id = null, .partitions = undefined, }; - var partitions = std.ArrayList(Partition).init(ctx.get_arena()); + const allocator = ctx.get_arena(); + + var partitions: std.ArrayList(Partition) = .empty; loop: while (true) { - const kw = try ctx.parse_enum(enum { + const kw = try ctx.parse_enum(stdio, enum { guid, part, endgpt, @@ -27,14 +29,14 @@ pub fn parse(ctx: dim.Context) !dim.Content { }); switch (kw) { .guid => { - const guid_str = try ctx.parse_string(); + const guid_str = try ctx.parse_string(stdio); if (guid_str.len != 36) return ctx.report_fatal_error("Invalid disk GUID: wrong length", .{}); 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(allocator)).* = try parsePartition(ctx, stdio), .@"legacy-bootable" => pt.legacy_bootable = true, .endgpt => break :loop, } @@ -74,7 +76,7 @@ pub fn parse(ctx: dim.Context) !dim.Content { })); } -fn parsePartition(ctx: dim.Context) !Partition { +fn parsePartition(ctx: dim.Context, stdio: std.Io) !Partition { var part = Partition{ .type = undefined, .part_id = null, @@ -98,7 +100,7 @@ fn parsePartition(ctx: dim.Context) !Partition { }) = .init(ctx, &part); parse_loop: while (true) { - const kw = try ctx.parse_enum(enum { + const kw = try ctx.parse_enum(stdio, enum { type, name, guid, @@ -109,23 +111,23 @@ fn parsePartition(ctx: dim.Context) !Partition { }); switch (kw) { .type => { - const type_name = try ctx.parse_string(); + const type_name = try ctx.parse_string(stdio); 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); }, .name => { - const string = try ctx.parse_string(); + const string = try ctx.parse_string(stdio); const name = stringToName(string) catch return error.BadStringLiteral; try updater.set(.name, name); }, .guid => { - const string = try ctx.parse_string(); + const string = try ctx.parse_string(stdio); if (string.len != 36) return ctx.report_fatal_error("Invalid partition GUID: wrong length", .{}); @@ -133,9 +135,9 @@ fn parsePartition(ctx: dim.Context) !Partition { return ctx.report_fatal_error("Invalid partition GUID: {}", .{err}); }); }, - .size => try updater.set(.size, try ctx.parse_mem_size()), - .offset => try updater.set(.offset, try ctx.parse_mem_size()), - .contains => try updater.set(.contains, try ctx.parse_content()), + .size => try updater.set(.size, try ctx.parse_mem_size(stdio)), + .offset => try updater.set(.offset, try ctx.parse_mem_size(stdio)), + .contains => try updater.set(.contains, try ctx.parse_content(stdio)), .endpart => break :parse_loop, } } @@ -145,8 +147,9 @@ fn parsePartition(ctx: dim.Context) !Partition { return part; } -pub fn render(table: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderError!void { - const random = std.crypto.random; +pub fn render(table: *PartTable, io: std.Io, stream: *dim.BinaryStream) dim.Content.RenderError!void { + var r: std.Random.IoSource = .{ .io = io }; + const random = r.interface(); const lba_len = stream.length / block_size; const secondary_pth_lba = lba_len - 1; @@ -167,7 +170,7 @@ pub fn render(table: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderEr for (table.partitions[0..], 0..) |partition, i| { @memset(&pe_block, 0); - const offset = partition.offset orelse 33 * block_size; + const offset = partition.offset orelse 34 * block_size; const size = partition.size orelse if (i == table.partitions.len - 1) ((max_partition_lba + 1) * block_size) - offset else @@ -204,11 +207,11 @@ pub fn render(table: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderEr pe_block[0x38..].* = @bitCast(partition.name); pe_crc.update(&pe_block); - try stream.write(block_size * 2 + pe_ofs, &pe_block); - try stream.write(block_size * secondary_pe_array_lba + pe_ofs, &pe_block); + try stream.write(io, block_size * 2 + pe_ofs, &pe_block); + try stream.write(io, block_size * secondary_pe_array_lba + pe_ofs, &pe_block); var sub_view = try stream.slice(offset, size); - try partition.contains.render(&sub_view); + try partition.contains.render(io, &sub_view); pe_ofs += 0x80; } @@ -217,8 +220,8 @@ pub fn render(table: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderEr @memset(&pe_block, 0); for (table.partitions.len..0x80) |_| { pe_crc.update(&pe_block); - try stream.write(block_size * 2 + pe_ofs, &pe_block); - try stream.write(block_size * secondary_pe_array_lba + pe_ofs, &pe_block); + try stream.write(io, block_size * 2 + pe_ofs, &pe_block); + try stream.write(io, block_size * secondary_pe_array_lba + pe_ofs, &pe_block); pe_ofs += 0x80; } } @@ -270,9 +273,9 @@ pub fn render(table: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderEr std.mem.writeInt(u32, backup_gpt_header[0x10..0x14], backup_gpt_header_crc32, .little); // CRC32 of backup header // write everything we generated to disk - try mbr.render(stream); - try stream.write(block_size, &gpt_header_block); - try stream.write(block_size * secondary_pth_lba, &backup_gpt_header_block); + try mbr.render(io, stream); + try stream.write(io, block_size, &gpt_header_block); + try stream.write(io, block_size * secondary_pth_lba, &backup_gpt_header_block); } pub const Guid = extern struct { diff --git a/src/components/part/MbrPartitionTable.zig b/src/components/part/MbrPartitionTable.zig index 66ceca8..35f6833 100644 --- a/src/components/part/MbrPartitionTable.zig +++ b/src/components/part/MbrPartitionTable.zig @@ -13,7 +13,7 @@ bootloader: ?dim.Content, disk_id: ?u32, partitions: [4]?Partition, -pub fn parse(ctx: dim.Context) !dim.Content { +pub fn parse(ctx: dim.Context, stdio: std.Io) !dim.Content { const pf = try ctx.alloc_object(PartTable); pf.* = .{ .bootloader = null, @@ -29,14 +29,14 @@ pub fn parse(ctx: dim.Context) !dim.Content { var next_part_id: usize = 0; var last_part_id: ?usize = null; while (next_part_id < pf.partitions.len) { - const kw = try ctx.parse_enum(enum { + const kw = try ctx.parse_enum(stdio, enum { bootloader, part, ignore, }); switch (kw) { .bootloader => { - const bootloader_content = try ctx.parse_content(); + const bootloader_content = try ctx.parse_content(stdio); if (pf.bootloader != null) { try ctx.report_nonfatal_error("mbr-part.bootloader specified twice!", .{}); } @@ -47,7 +47,7 @@ pub fn parse(ctx: dim.Context) !dim.Content { next_part_id += 1; }, .part => { - pf.partitions[next_part_id] = try parse_partition(ctx); + pf.partitions[next_part_id] = try parse_partition(ctx, stdio); last_part_id = next_part_id; next_part_id += 1; }, @@ -83,7 +83,7 @@ pub fn parse(ctx: dim.Context) !dim.Content { })); } -fn parse_partition(ctx: dim.Context) !Partition { +fn parse_partition(ctx: dim.Context, stdio: std.Io) !Partition { var part: Partition = .{ .offset = null, .size = null, @@ -99,7 +99,7 @@ fn parse_partition(ctx: dim.Context) !Partition { }) = .init(ctx, &part); parse_loop: while (true) { - const kw = try ctx.parse_enum(enum { + const kw = try ctx.parse_enum(stdio, enum { type, bootable, size, @@ -109,22 +109,22 @@ fn parse_partition(ctx: dim.Context) !Partition { }); try switch (kw) { .type => { - const part_name = try ctx.parse_string(); + const part_name = try ctx.parse_string(stdio); const encoded = if (std.fmt.parseInt(u8, part_name, 0)) |value| 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; }; try updater.set(.type, encoded); }, .bootable => updater.set(.bootable, true), - .size => updater.set(.size, try ctx.parse_mem_size()), - .offset => updater.set(.offset, try ctx.parse_mem_size()), - .contains => updater.set(.contains, try ctx.parse_content()), + .size => updater.set(.size, try ctx.parse_mem_size(stdio)), + .offset => updater.set(.offset, try ctx.parse_mem_size(stdio)), + .contains => updater.set(.contains, try ctx.parse_content(stdio)), .endpart => break :parse_loop, }; } @@ -134,7 +134,7 @@ fn parse_partition(ctx: dim.Context) !Partition { return part; } -pub fn render(table: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderError!void { +pub fn render(table: *PartTable, io: std.Io, stream: *dim.BinaryStream) dim.Content.RenderError!void { const last_part_id = blk: { var last: usize = 0; for (table.partitions, 0..) |p, i| { @@ -158,7 +158,7 @@ pub fn render(table: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderEr if (table.bootloader) |bootloader| { var sector: dim.BinaryStream = .init_buffer(&boot_sector); - try bootloader.render(§or); + try bootloader.render(io, §or); const upper_limit: u64 = if (table.disk_id != null) 0x01B8 @@ -235,7 +235,7 @@ pub fn render(table: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderEr boot_sector[0x01FE] = 0x55; boot_sector[0x01FF] = 0xAA; - try stream.write(0, &boot_sector); + try stream.write(io,0, &boot_sector); } for (part_infos, table.partitions) |maybe_info, maybe_part| { @@ -244,7 +244,7 @@ pub fn render(table: *PartTable, stream: *dim.BinaryStream) dim.Content.RenderEr var sub_view = try stream.slice(info.offset, info.size); - try part.contains.render(&sub_view); + try part.contains.render(io, &sub_view); } } diff --git a/src/dim.zig b/src/dim.zig index 01438d8..a384b3d 100644 --- a/src/dim.zig +++ b/src/dim.zig @@ -53,15 +53,17 @@ const usage = const VariableMap = std.StringArrayHashMapUnmanaged([]const u8); -var global_deps_file: ?std.fs.File = null; +var global_deps_file: ?std.Io.File = null; -pub fn main() !u8 { +pub fn main(init: std.process.Init) !u8 { var gpa_impl: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa_impl.deinit(); const gpa = gpa_impl.allocator(); - const opts = try args.parseForCurrentProcess(Options, gpa, .print); + const io_iface = init.io; + + const opts = try args.parseForCurrentProcess(Options, gpa, init.minimal.args, .print); defer opts.deinit(); const options = opts.options; @@ -72,8 +74,7 @@ pub fn main() !u8 { var var_map: VariableMap = .empty; defer var_map.deinit(gpa); - var env_map = try std.process.getEnvMap(gpa); - defer env_map.deinit(); + var env_map = init.environ_map; if (options.@"import-env") { var iter = env_map.iterator(); @@ -89,8 +90,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; } @@ -103,24 +104,36 @@ pub fn main() !u8 { return fatal("--size must be given!"); } - var current_dir = try std.fs.cwd().openDir(".", .{}); - defer current_dir.close(); + var current_dir = try std.Io.Dir.cwd().openDir(io_iface, ".", .{}); + defer current_dir.close(io_iface); - const script_source = try current_dir.readFileAlloc(gpa, script_path, max_script_size); + const script_source = try current_dir.readFileAlloc(io_iface, script_path, gpa, .limited(max_script_size)); defer gpa.free(script_source); if (options.@"deps-file") |deps_file_path| { - global_deps_file = try std.fs.cwd().createFile(deps_file_path, .{}); + global_deps_file = try std.Io.Dir.cwd().createFile(io_iface, deps_file_path, .{}); + + var writer = global_deps_file.?.writerStreaming(io_iface, &.{}); + // TODO would it be better to store the writer and just reuse it? that way we can utilise + // buffering and not have the risk of someone positional writes and breaking things. + + // TODO Zig has a bug in dependency file handling: relative paths are taken not to the run + // step cwd, but to the project root. hence, we need to absolute paths until this is fixed. + var buf_output: [std.fs.max_path_bytes]u8 = undefined; + const output_path_abs = try std.Io.Dir.cwd().realPathFile(io_iface, std.fs.path.dirname(output_path) orelse ".", &buf_output); + var buf_script: [std.fs.max_path_bytes]u8 = undefined; + const script_path_abs = try std.Io.Dir.cwd().realPathFile(io_iface, script_path, &buf_script); - try global_deps_file.?.writer().print( - \\{s}: {s} + try writer.interface.print( + \\{s}/{s}: {s} , .{ - output_path, - script_path, + buf_output[0..output_path_abs], + std.fs.path.basename(output_path), + buf_script[0..script_path_abs], }); } defer if (global_deps_file) |deps_file| - deps_file.close(); + deps_file.close(io_iface); var mem_arena: std.heap.ArenaAllocator = .init(gpa); defer mem_arena.deinit(); @@ -149,7 +162,7 @@ pub fn main() !u8 { .contents = script_source, }); - const root_content: Content = env.parse_content() catch |err| switch (err) { + const root_content: Content = env.parse_content(io_iface) catch |err| switch (err) { error.FatalConfigError => return 1, else => |e| return e, @@ -160,33 +173,33 @@ pub fn main() !u8 { } { - var output_file = try current_dir.createFile(output_path, .{ .read = true }); - defer output_file.close(); + var output_file = try current_dir.createFile(io_iface, output_path, .{ .read = true }); + defer output_file.close(io_iface); - try output_file.setEndPos(size_limit); + try output_file.setLength(io_iface, size_limit); var stream: BinaryStream = .init_file(output_file, size_limit); - try root_content.render(&stream); + try root_content.render(io_iface, &stream); } if (global_deps_file) |deps_file| { - try deps_file.writeAll("\n"); + try deps_file.writeStreamingAll(io_iface, "\n"); } return 0; } -pub fn declare_file_dependency(path: []const u8) !void { +pub fn declare_file_dependency(stdio: std.Io, path: []const u8) !void { const deps_file = global_deps_file orelse return; - const stat = std.fs.cwd().statFile(path) catch |err| switch (err) { + const stat = std.Io.Dir.cwd().statFile(stdio, 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 deps_file.writeStreamingAll(stdio, " \\\n "); + try deps_file.writeStreamingAll(stdio, path); } } @@ -225,14 +238,14 @@ pub const Context = struct { return error.FatalConfigError; } - pub fn parse_string(ctx: Context) Environment.ParseError![]const u8 { - const str = try ctx.env.parser.next(); + pub fn parse_string(ctx: Context, stdio: std.Io) Environment.ParseError![]const u8 { + const str = try ctx.env.parser.next(stdio); // std.debug.print("token: '{}'\n", .{std.zig.fmtEscapes(str)}); return str; } - pub fn parse_file_name(ctx: Context) Environment.ParseError!FileName { - const rel_path = try ctx.parse_string(); + pub fn parse_file_name(ctx: Context, stdio: std.Io) Environment.ParseError!FileName { + const rel_path = try ctx.parse_string(stdio); const abs_path = try ctx.env.parser.get_include_path(ctx.env.arena, rel_path); @@ -242,17 +255,17 @@ pub const Context = struct { }; } - pub fn parse_enum(ctx: Context, comptime E: type) Environment.ParseError!E { + pub fn parse_enum(ctx: Context, stdio: std.Io, comptime E: type) Environment.ParseError!E { if (@typeInfo(E) != .@"enum") @compileError("get_enum requires an enum type!"); - const tag_name = try ctx.parse_string(); + const tag_name = try ctx.parse_string(stdio); const converted = std.meta.stringToEnum( E, tag_name, ); 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| { @@ -262,39 +275,39 @@ pub const Context = struct { return error.InvalidEnumTag; } - pub fn parse_integer(ctx: Context, comptime I: type, base: u8) Environment.ParseError!I { + pub fn parse_integer(ctx: Context, stdio: std.Io, comptime I: type, base: u8) Environment.ParseError!I { if (@typeInfo(I) != .int) @compileError("get_integer requires an integer type!"); return std.fmt.parseInt( I, - try ctx.parse_string(), + try ctx.parse_string(stdio), base, ) catch return error.InvalidNumber; } - pub fn parse_mem_size(ctx: Context) Environment.ParseError!u64 { - const str = try ctx.parse_string(); + pub fn parse_mem_size(ctx: Context, stdio: std.Io) Environment.ParseError!u64 { + const str = try ctx.parse_string(stdio); const ds: DiskSize = try .parse(str); return ds.size_in_bytes(); } - pub fn parse_content(ctx: Context) Environment.ParseError!Content { - const content_type_str = try ctx.env.parser.next(); + pub fn parse_content(ctx: Context, stdio: std.Io) Environment.ParseError!Content { + const content_type_str = try ctx.env.parser.next(stdio); inline for (content_types) |tn| { const name, const impl = tn; if (std.mem.eql(u8, name, content_type_str)) { - const content: Content = try impl.parse(ctx); + const content: Content = try impl.parse(ctx, stdio); return content; } } - 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), }); } }; @@ -360,7 +373,7 @@ const Environment = struct { arena: std.mem.Allocator, allocator: std.mem.Allocator, parser: *Parser, - include_base: std.fs.Dir, + include_base: std.Io.Dir, vars: *const VariableMap, error_flag: bool = false, @@ -369,10 +382,10 @@ const Environment = struct { .resolve_variable_fn = resolve_var, }, - fn parse_content(env: *Environment) ParseError!Content { + fn parse_content(env: *Environment, stdio: std.Io) ParseError!Content { var ctx = Context{ .env = env }; - return try ctx.parse_content(); + return try ctx.parse_content(stdio); } fn report_error(env: *Environment, comptime fmt: []const u8, params: anytype) error{OutOfMemory}!void { @@ -380,17 +393,17 @@ const Environment = struct { std.log.err("PARSE ERROR: " ++ fmt, params); } - fn fetch_file(io: *const Parser.IO, allocator: std.mem.Allocator, path: []const u8) error{ FileNotFound, IoError, OutOfMemory, InvalidPath }![]const u8 { + fn fetch_file(stdio: std.Io, io: *const Parser.IO, allocator: std.mem.Allocator, path: []const u8) error{ FileNotFound, IoError, OutOfMemory, InvalidPath, Canceled }![]const u8 { const env: *const Environment = @fieldParentPtr("io", io); - const contents = env.include_base.readFileAlloc(allocator, path, max_script_size) catch |err| switch (err) { + const contents = env.include_base.readFileAlloc(stdio, path, allocator, .limited(max_script_size)) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, 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(buffer[0 .. env.include_base.realPath(stdio, &buffer) catch return error.FileNotFound]), + std.zig.fmtString(path), }); return error.FileNotFound; }, @@ -399,12 +412,13 @@ const Environment = struct { errdefer allocator.free(contents); const name: FileName = .{ .root_dir = env.include_base, .rel_path = path }; - try name.declare_dependency(); + try name.declare_dependency(stdio); return contents; } - fn resolve_var(io: *const Parser.IO, name: []const u8) error{UnknownVariable}![]const u8 { + fn resolve_var(stdio: std.Io, io: *const Parser.IO, name: []const u8) error{UnknownVariable}![]const u8 { + _ = stdio; const env: *const Environment = @fieldParentPtr("io", io); return env.vars.get(name) orelse return error.UnknownVariable; } @@ -419,36 +433,38 @@ pub const Content = struct { ConfigurationError, OutOfBounds, OutOfMemory, + Canceled, }; pub const GuessError = FileName.GetSizeError; obj: *anyopaque, vtable: *const VTable, - pub const empty: Content = @import("components/EmptyData.zig").parse(undefined) catch unreachable; + pub const empty: Content = @import("components/EmptyData.zig").parse(undefined, undefined) catch unreachable; pub fn create_handle(obj: *anyopaque, vtable: *const VTable) Content { return .{ .obj = obj, .vtable = vtable }; } /// Emits the content into a binary stream. - pub fn render(content: Content, stream: *BinaryStream) RenderError!void { - try content.vtable.render_fn(content.obj, stream); + pub fn render(content: Content, io: std.Io, stream: *BinaryStream) RenderError!void { + try content.vtable.render_fn(content.obj, io, stream); } pub const VTable = struct { - render_fn: *const fn (*anyopaque, *BinaryStream) RenderError!void, + render_fn: *const fn (*anyopaque, std.Io, *BinaryStream) RenderError!void, pub fn create( comptime Container: type, comptime funcs: struct { - render_fn: *const fn (*Container, *BinaryStream) RenderError!void, + render_fn: *const fn (*Container, std.Io, *BinaryStream) RenderError!void, }, ) *const VTable { const Wrap = struct { - fn render(self: *anyopaque, stream: *BinaryStream) RenderError!void { + fn render(self: *anyopaque, io: std.Io, stream: *BinaryStream) RenderError!void { return funcs.render_fn( @ptrCast(@alignCast(self)), + io, stream, ); } @@ -461,28 +477,29 @@ pub const Content = struct { }; pub const FileName = struct { - root_dir: std.fs.Dir, + root_dir: std.Io.Dir, rel_path: []const u8, - pub const OpenError = error{ FileNotFound, InvalidPath, IoError }; + pub const OpenError = error{ FileNotFound, InvalidPath, IoError, Canceled }; - pub fn open(name: FileName) OpenError!FileHandle { - const file = name.root_dir.openFile(name.rel_path, .{}) catch |err| switch (err) { + pub fn open(name: FileName, stdio: std.Io) OpenError!FileHandle { + const file = name.root_dir.openFile(stdio, 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), + std.log.err("failed to open \"{f}/{f}\": not found", .{ + std.zig.fmtString(if (name.root_dir.realPath(stdio, &buffer)) |l| buffer[0..l] else |e| @errorName(e)), + std.zig.fmtString(name.rel_path), }); return error.FileNotFound; }, + error.Canceled => return error.Canceled, + error.NameTooLong, - error.InvalidWtf8, error.BadPathName, - error.InvalidUtf8, => return error.InvalidPath, + // error.DiskQuota, error.NoSpaceLeft, error.FileTooBig, error.DeviceBusy, @@ -501,34 +518,35 @@ pub const FileName = struct { error.SystemFdQuotaExceeded, error.IsDir, error.NotDir, - error.FileLocksNotSupported, + error.FileLocksUnsupported, error.FileBusy, + error.PermissionDenied, => return error.IoError, }; - try name.declare_dependency(); + try name.declare_dependency(stdio); return .{ .file = file }; } - pub fn open_dir(name: FileName) OpenError!std.fs.Dir { - const dir = name.root_dir.openDir(name.rel_path, .{ .iterate = true }) catch |err| switch (err) { + pub fn open_dir(name: FileName, stdio: std.Io) OpenError!std.Io.Dir { + const dir = name.root_dir.openDir(stdio, 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), + std.log.err("failed to open \"{f}/{f}\": not found", .{ + std.zig.fmtString(if (name.root_dir.realPath(stdio, &buffer)) |l| buffer[0..l] else |e| @errorName(e)), + std.zig.fmtString(name.rel_path), }); return error.FileNotFound; }, + error.Canceled => return error.Canceled, + error.NameTooLong, - error.InvalidWtf8, error.BadPathName, - error.InvalidUtf8, => return error.InvalidPath, - error.DeviceBusy, + // error.DeviceBusy, error.AccessDenied, error.SystemResources, error.NoDevice, @@ -538,22 +556,24 @@ pub const FileName = struct { error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded, error.NotDir, + error.PermissionDenied, => return error.IoError, }; - try name.declare_dependency(); + try name.declare_dependency(stdio); return dir; } - pub fn declare_dependency(name: FileName) OpenError!void { + pub fn declare_dependency(name: FileName, stdio: std.Io) OpenError!void { var buffer: [std.fs.max_path_bytes]u8 = undefined; - const realpath = name.root_dir.realpath( + const realpath = name.root_dir.realPathFile( + stdio, name.rel_path, &buffer, ) catch |e| std.debug.panic("failed to determine real path for dependency file: {s}", .{@errorName(e)}); - declare_file_dependency(realpath) catch |e| std.debug.panic("Failed to write to deps file: {s}", .{@errorName(e)}); + declare_file_dependency(stdio, buffer[0..realpath]) catch |e| std.debug.panic("Failed to write to deps file: {s}", .{@errorName(e)}); } pub const GetSizeError = error{ FileNotFound, InvalidPath, IoError }; @@ -562,9 +582,7 @@ pub const FileName = struct { error.FileNotFound => return error.FileNotFound, error.NameTooLong, - error.InvalidWtf8, error.BadPathName, - error.InvalidUtf8, => return error.InvalidPath, error.NoSpaceLeft, @@ -593,61 +611,41 @@ pub const FileName = struct { return stat.size; } - pub fn copy_to(file: FileName, stream: *BinaryStream) (OpenError || FileHandle.ReadError || BinaryStream.WriteError)!void { - var handle = try file.open(); - defer handle.close(); + pub fn copy_to(file: FileName, io: std.Io, stream: *BinaryStream) (OpenError || FileHandle.ReadError || BinaryStream.WriteError)!void { + var handle = try file.open(io); + defer handle.close(io); - var fifo: std.fifo.LinearFifo(u8, .{ .Static = 8192 }) = .init(); + var reader_buf: [8192]u8 = undefined; + var reader = handle.reader(io, &reader_buf); - try fifo.pump( - handle.reader(), - stream.writer(), - ); + var writer = stream.writer(io); + + _ = writer.interface.sendFileAll(&reader, .unlimited) catch |e| return switch (e) { + error.WriteFailed => error.IoError, // TODO this isn't great + else => |err| err, + }; } }; pub const FileHandle = struct { - pub const ReadError = error{ReadFileFailed}; - - pub const Reader = std.io.Reader(std.fs.File, ReadError, read_some); + pub const ReadError = std.Io.Reader.Error; - file: std.fs.File, + file: std.Io.File, - pub fn close(fd: *FileHandle) void { - fd.file.close(); + pub fn close(fd: *FileHandle, io: std.Io) void { + fd.file.close(io); 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, io: std.Io, buf: []u8) std.Io.File.Reader { + return fd.file.reader(io, buf); } }; 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 WriterError = std.Io.Writer.Error; + pub const ReadError = error{ Overflow, IoError, Canceled }; backing: Backing, @@ -665,7 +663,7 @@ pub const BinaryStream = struct { } /// Constructs a BinaryStream from a file. - pub fn init_file(file: std.fs.File, max_len: u64) BinaryStream { + pub fn init_file(file: std.Io.File, max_len: u64) BinaryStream { return .{ .backing = .{ .file = .{ @@ -699,7 +697,7 @@ pub const BinaryStream = struct { }; } - pub fn read(bs: *BinaryStream, offset: u64, data: []u8) ReadError!void { + pub fn read(bs: *BinaryStream, io: std.Io, offset: u64, data: []u8) ReadError!void { const end_pos = offset + data.len; if (end_pos > bs.length) return error.Overflow; @@ -707,31 +705,14 @@ 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, - }; + var reader = state.file.reader(io, &.{}); + reader.seekTo(state.base + offset) catch return error.IoError; + reader.interface.readSliceAll(data) catch return error.IoError; }, } } - pub fn write(bs: *BinaryStream, offset: u64, data: []const u8) WriteError!void { + pub fn write(bs: *BinaryStream, io: std.Io, offset: u64, data: []const u8) WriteError!void { const end_pos = offset + data.len; if (end_pos > bs.length) return error.Overflow; @@ -739,26 +720,10 @@ 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, - }; + var w = state.file.writer(io, &.{}); + w.seekTo(state.base + offset) catch return error.IoError; + w.interface.writeAll(data) catch return error.IoError; + w.interface.flush() catch return error.IoError; }, } } @@ -769,16 +734,51 @@ pub const BinaryStream = struct { bs.virtual_offset = offset; } - pub fn writer(bs: *BinaryStream) Writer { - return .{ .context = bs }; + pub fn writer(bs: *BinaryStream, stdio: std.Io) Writer { + return .{ + .interface = .{ + .vtable = &.{ + .drain = Writer.drain, + }, + .buffer = &.{}, + }, + .stream = bs, + .stdio = stdio, + }; } - fn write_some(stream: *BinaryStream, data: []const u8) WriteError!usize { + pub const Writer = struct { + interface: std.Io.Writer, + stream: *BinaryStream, + stdio: std.Io, + + pub fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { + const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); + + var written: usize = 0; + + for (data[0 .. data.len - 1]) |bytes| { + written += try w.stream.write_some(w.stdio, bytes); + } + + const pattern = data[data.len - 1]; + switch (pattern.len) { + 0 => {}, + else => for (0..splat) |_| { + written += try w.stream.write_some(w.stdio, pattern); + }, + } + + return written; + } + }; + + fn write_some(stream: *BinaryStream, stdio: std.Io, data: []const u8) std.Io.Writer.Error!usize { const remaining_len = stream.length - stream.virtual_offset; const written_len: usize = @intCast(@min(remaining_len, data.len)); - try stream.write(stream.virtual_offset, data[0..written_len]); + stream.write(stdio, stream.virtual_offset, data[0..written_len]) catch return error.WriteFailed; stream.virtual_offset += written_len; return written_len; @@ -786,7 +786,7 @@ pub const BinaryStream = struct { pub const Backing = union(enum) { file: struct { - file: std.fs.File, + file: std.Io.File, base: u64, }, buffer: [*]u8, @@ -838,10 +838,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,13 +858,13 @@ 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); const prefix, const suffix = .{ divided[0 .. divided.len - 3], - std.mem.trimRight(u8, divided[divided.len - 3 ..], "0"), + std.mem.trimEnd(u8, divided[divided.len - 3 ..], "0"), }; if (suffix.len > 0) {