tools

various tools I have been using throughout the years
Log | Files | Refs | README | LICENSE

commit 35da86bc7a51572e5aadf0c487b8162752a2b58c
parent efbc80c6bd270370108e9f918e82b3fef8421ebf
Author: mtmn <miro@haravara.org>
Date:   Thu,  7 May 2026 22:34:15 +0200

feat: rewrite to zig 0.16

Diffstat:
Mpmn/build.zig.zon | 2+-
Mpmn/src/main.zig | 197++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mpracomer/build.zig.zon | 2+-
Mpracomer/src/command.zig | 55++++++++++++++++++++++++++-----------------------------
Mpracomer/src/main.zig | 69++++++++++++++++++++++++++++++++++-----------------------------------
Mpracomer/src/options.zig | 13++-----------
Mpracomer/src/pausable_timer.zig | 28+++++++++++++---------------
Mpracomer/src/signals.zig | 39++++++++++++++++++++++-----------------
Mpracomer/src/terminal.zig | 8--------
Mspeediness/build.zig.zon | 2+-
Mspeediness/src/main.zig | 71+++++++++++++++++++++++++++++++++++++----------------------------------
11 files changed, 237 insertions(+), 249 deletions(-)

diff --git a/pmn/build.zig.zon b/pmn/build.zig.zon @@ -2,7 +2,7 @@ .name = .pmn, .version = "0.1.0", .fingerprint = 0xd1e9e8f723c1d143, - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", .dependencies = .{}, .paths = .{ "build.zig", diff --git a/pmn/src/main.zig b/pmn/src/main.zig @@ -6,21 +6,19 @@ const Entry = struct { accessed: i128, }; -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); - const alloc = gpa.allocator(); +pub fn main(init: std.process.Init) !void { + const alloc = init.gpa; - const pass_dir = try dir(alloc); + const pass_dir = try dir(alloc, init.environ_map); defer alloc.free(pass_dir); - var passwords = std.ArrayListUnmanaged(Entry){}; + var passwords = std.ArrayListUnmanaged(Entry).empty; defer { for (passwords.items) |p| alloc.free(p.path); passwords.deinit(alloc); } - try collect(alloc, pass_dir, "", &passwords); + try collect(alloc, init.io, pass_dir, "", &passwords); std.mem.sort(Entry, passwords.items, {}, struct { fn lessThan(_: void, a: Entry, b: Entry) bool { @@ -28,13 +26,13 @@ pub fn main() !void { } }.lessThan); - var paths = std.ArrayListUnmanaged([]const u8){}; + var paths = std.ArrayListUnmanaged([]const u8).empty; defer paths.deinit(alloc); for (passwords.items) |entry| { try paths.append(alloc, entry.path); } - const selected = try fzf(alloc, paths.items) orelse return; + const selected = try fzf(init, alloc, paths.items) orelse return; defer alloc.free(selected); const exists = blk: { @@ -44,37 +42,37 @@ pub fn main() !void { break :blk false; }; const basename_start = if (std.mem.lastIndexOfScalar(u8, selected, '/')) |idx| idx + 1 else 0; - const has_otp = std.mem.indexOf(u8, selected[basename_start..], "otp") != null; + const has_otp = std.mem.find(u8, selected[basename_start..], "otp") != null; const secret = if (has_otp and exists) - try run(alloc, &.{ "pass", "otp", "code", selected }) + try run(init, alloc, &.{ "pass", "otp", "code", selected }) else if (!exists) - try store(alloc, selected) + try store(init, alloc, selected) else - run(alloc, &.{ "pass", "show", selected }) catch try store(alloc, selected); + run(init, alloc, &.{ "pass", "show", selected }) catch try store(init, alloc, selected); - try clip(alloc, secret); + try clip(init, secret); alloc.free(secret); } -fn dir(alloc: std.mem.Allocator) ![]const u8 { - if (std.process.getEnvVarOwned(alloc, "PASSWORD_STORE_DIR")) |p| { - return p; - } else |_| {} +fn dir(alloc: std.mem.Allocator, environ: *std.process.Environ.Map) ![]const u8 { + if (environ.get("PASSWORD_STORE_DIR")) |p| { + return try alloc.dupe(u8, p); + } - const home = std.posix.getenv("HOME") orelse return error.NoHome; + const home = environ.get("HOME") orelse return error.NoHome; return try std.fs.path.join(alloc, &.{ home, ".password-store" }); } -fn collect(alloc: std.mem.Allocator, base_dir: []const u8, sub_path: []const u8, list: *std.ArrayListUnmanaged(Entry)) anyerror!void { +fn collect(alloc: std.mem.Allocator, io: std.Io, base_dir: []const u8, sub_path: []const u8, list: *std.ArrayListUnmanaged(Entry)) anyerror!void { const full_path = try std.fs.path.join(alloc, &.{ base_dir, sub_path }); defer alloc.free(full_path); - var d = std.fs.openDirAbsolute(full_path, .{ .iterate = true }) catch return; - defer d.close(); + var d = std.Io.Dir.openDirAbsolute(io, full_path, .{ .iterate = true }) catch return; + defer d.close(io); var it = d.iterate(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { if (entry.name.len > 0 and entry.name[0] == '.') continue; const entry_sub_path = if (sub_path.len == 0) @@ -84,7 +82,7 @@ fn collect(alloc: std.mem.Allocator, base_dir: []const u8, sub_path: []const u8, errdefer alloc.free(entry_sub_path); if (entry.kind == .directory) { - try collect(alloc, base_dir, entry_sub_path, list); + try collect(alloc, io, base_dir, entry_sub_path, list); alloc.free(entry_sub_path); } else if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".gpg")) { const name = entry_sub_path[0 .. entry_sub_path.len - 4]; @@ -92,18 +90,19 @@ fn collect(alloc: std.mem.Allocator, base_dir: []const u8, sub_path: []const u8, defer alloc.free(file_path); const accessed = blk: { - const stat = std.fs.openFileAbsolute(file_path, .{}) catch |err| { + const file = std.Io.Dir.openFileAbsolute(io, file_path, .{}) catch |err| { std.debug.print("Failed to stat {s}: {}\n", .{ file_path, err }); break :blk @as(i128, 0); }; - defer stat.close(); + defer file.close(io); - const file_stat = stat.stat() catch |err| { + const file_stat = file.stat(io) catch |err| { std.debug.print("Failed to stat {s}: {}\n", .{ file_path, err }); break :blk @as(i128, 0); }; - break :blk @as(i128, @intCast(file_stat.atime)); + const atime_ts = file_stat.atime orelse break :blk @as(i128, 0); + break :blk @as(i128, @intCast(atime_ts.nanoseconds)); }; try list.append(alloc, Entry{ @@ -117,33 +116,36 @@ fn collect(alloc: std.mem.Allocator, base_dir: []const u8, sub_path: []const u8, } } -fn fzf(alloc: std.mem.Allocator, items: []const []const u8) !?[]u8 { - var child = std.process.Child.init(&.{ "fzf", "--print-query" }, alloc); - child.stdin_behavior = .Pipe; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Inherit; - - try child.spawn(); +fn fzf(init: std.process.Init, alloc: std.mem.Allocator, items: []const []const u8) !?[]u8 { + var child = try std.process.spawn(init.io, .{ + .argv = &.{ "fzf", "--print-query" }, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .inherit, + }); if (child.stdin) |stdin| { + var buf: [4096]u8 = undefined; + var fw = stdin.writer(init.io, &buf); for (items) |item| { - try stdin.writeAll(item); - try stdin.writeAll("\n"); + try fw.interface.writeAll(item); + try fw.interface.writeAll("\n"); } - stdin.close(); + try fw.flush(); + stdin.close(init.io); child.stdin = null; } - const output = try read(alloc, &child); + const output = try read(init.io, alloc, &child); defer alloc.free(output); - const term = try child.wait(); - if (term != .Exited) return null; + const term = try child.wait(init.io); + if (term != .exited) return null; const trimmed = std.mem.trim(u8, output, " \n\r\t"); if (trimmed.len == 0) return null; - if (std.mem.indexOfScalar(u8, trimmed, '\n')) |idx| { + if (std.mem.findScalar(u8, trimmed, '\n')) |idx| { const selected = std.mem.trim(u8, trimmed[idx + 1 ..], " \n\r\t"); if (selected.len > 0) return try alloc.dupe(u8, selected); const query = std.mem.trim(u8, trimmed[0..idx], " \n\r\t"); @@ -153,22 +155,22 @@ fn fzf(alloc: std.mem.Allocator, items: []const []const u8) !?[]u8 { return try alloc.dupe(u8, trimmed); } -fn run(alloc: std.mem.Allocator, args: []const []const u8) ![]u8 { - var child = std.process.Child.init(args, alloc); - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Inherit; +fn run(init: std.process.Init, alloc: std.mem.Allocator, args: []const []const u8) ![]u8 { + var child = try std.process.spawn(init.io, .{ + .argv = args, + .stdout = .pipe, + .stderr = .inherit, + }); - try child.spawn(); + const output = try read(init.io, alloc, &child); - const output = try read(alloc, &child); - - const term = try child.wait(); - if (term != .Exited or term.Exited != 0) { + const term = try child.wait(init.io); + if (term != .exited or term.exited != 0) { alloc.free(output); return error.FailedExit; } - if (std.mem.indexOfScalar(u8, output, '\n')) |idx| { + if (std.mem.findScalar(u8, output, '\n')) |idx| { const result = try alloc.dupe(u8, output[0..idx]); alloc.free(output); return result; @@ -177,105 +179,106 @@ fn run(alloc: std.mem.Allocator, args: []const []const u8) ![]u8 { return output; } -fn read(alloc: std.mem.Allocator, child: *std.process.Child) ![]u8 { - var stdout = std.ArrayListUnmanaged(u8){}; - defer stdout.deinit(alloc); - - if (child.stdout) |reader| { - var buffer: [1024]u8 = undefined; - while (true) { - const n = try reader.read(&buffer); - if (n == 0) break; - try stdout.appendSlice(alloc, buffer[0..n]); - } +fn read(io: std.Io, alloc: std.mem.Allocator, child: *std.process.Child) ![]u8 { + if (child.stdout) |file| { + var buf: [4096]u8 = undefined; + var fr = file.reader(io, &buf); + return try fr.interface.allocRemaining(alloc, .unlimited); } - - return try alloc.dupe(u8, stdout.items); + return try alloc.dupe(u8, ""); } -fn gen(alloc: std.mem.Allocator) ![]u8 { +fn gen(init: std.process.Init, alloc: std.mem.Allocator) ![]u8 { const charset = std.ascii.letters ++ "0123456789"; const special_chars = "!@#$%^&*()-_=+[]{}|;:,.<>?"; var password = try alloc.alloc(u8, 16); errdefer alloc.free(password); - std.crypto.random.bytes(password); + init.io.random(password); for (password) |*c| { c.* = charset[c.* % charset.len]; } - const pos = std.crypto.random.intRangeLessThan(usize, 0, password.len); - const idx = std.crypto.random.intRangeLessThan(usize, 0, special_chars.len); + var idx_bytes: [8]u8 = undefined; + init.io.random(&idx_bytes); + const pos = std.mem.readInt(u64, idx_bytes[0..8], .little) % password.len; + init.io.random(&idx_bytes); + const idx = std.mem.readInt(u64, idx_bytes[0..8], .little) % special_chars.len; password[pos] = special_chars[idx]; return password; } -fn store(alloc: std.mem.Allocator, name: []const u8) ![]u8 { - const password = try gen(alloc); +fn store(init: std.process.Init, alloc: std.mem.Allocator, name: []const u8) ![]u8 { + const password = try gen(init, alloc); errdefer alloc.free(password); - inner(alloc, name, password) catch |err| { + inner(init, alloc, name, password) catch |err| { std.debug.print("Failed to store password: {}\n", .{err}); return err; }; return password; } -fn inner(alloc: std.mem.Allocator, name: []const u8, password: []const u8) !void { - const pass_dir = try dir(alloc); +fn inner(init: std.process.Init, alloc: std.mem.Allocator, name: []const u8, password: []const u8) !void { + const pass_dir = try dir(alloc, init.environ_map); defer alloc.free(pass_dir); const full_path = try std.fs.path.join(alloc, &.{ pass_dir, name }); defer alloc.free(full_path); if (std.fs.path.dirname(full_path)) |dir_path| { - std.fs.makeDirAbsolute(dir_path) catch |err| { + std.Io.Dir.createDirAbsolute(init.io, dir_path, .default_dir) catch |err| { if (err != error.PathAlreadyExists) return err; }; } - var child = std.process.Child.init(&.{ "pass", "insert", "-e", name }, alloc); - child.stdin_behavior = .Pipe; - child.stderr_behavior = .Inherit; - - try child.spawn(); + var child = try std.process.spawn(init.io, .{ + .argv = &.{ "pass", "insert", "-e", name }, + .stdin = .pipe, + .stderr = .inherit, + }); if (child.stdin) |stdin| { - try stdin.writeAll(password); - try stdin.writeAll("\n"); - stdin.close(); + var buf: [4096]u8 = undefined; + var fw = stdin.writer(init.io, &buf); + try fw.interface.writeAll(password); + try fw.interface.writeAll("\n"); + try fw.flush(); + stdin.close(init.io); child.stdin = null; } - const term = try child.wait(); - if (term != .Exited or term.Exited != 0) return error.FailedExit; + const term = try child.wait(init.io); + if (term != .exited or term.exited != 0) return error.FailedExit; } -fn clip(alloc: std.mem.Allocator, content: []const u8) !void { +fn clip(init: std.process.Init, content: []const u8) !void { const args: []const []const u8 = switch (builtin.os.tag) { .macos => &.{"pbcopy"}, .linux => args: { - if (std.process.getEnvVarOwned(alloc, "WAYLAND_DISPLAY")) |v| { - alloc.free(v); + if (init.environ_map.get("WAYLAND_DISPLAY")) |_| { break :args &.{"wl-copy"}; - } else |_| break :args &.{ "xclip", "-selection", "clipboard" }; + } else break :args &.{ "xclip", "-selection", "clipboard" }; }, else => return, }; - var child = std.process.Child.init(args, alloc); - child.stdin_behavior = .Pipe; - - try child.spawn(); + var child = try std.process.spawn(init.io, .{ + .argv = args, + .stdin = .pipe, + }); if (child.stdin) |stdin| { - try stdin.writeAll(content); - stdin.close(); + var buf: [4096]u8 = undefined; + var fw = stdin.writer(init.io, &buf); + try fw.interface.writeAll(content); + try fw.flush(); + stdin.close(init.io); child.stdin = null; } - const term = try child.wait(); - if (term != .Exited) return error.ClipboardFailed; + const term = try child.wait(init.io); + if (term != .exited) return error.ClipboardFailed; } diff --git a/pracomer/build.zig.zon b/pracomer/build.zig.zon @@ -28,7 +28,7 @@ // Tracks the earliest Zig version that the package considers to be a // supported use case. - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. diff --git a/pracomer/src/command.zig b/pracomer/src/command.zig @@ -8,15 +8,13 @@ pub const Command = enum { Reset, }; -const FdType = enum { stdin, signals }; - pub const Poller = struct { - poller: std.io.Poller(FdType), - signals_file: std.fs.File, - /// Option set to the terminal, is null when running in non-interactive mode + stdin_fd: std.posix.fd_t, + signals_fd: std.posix.fd_t, terminal: ?terminal.Terminal, pub fn init(allocator: std.mem.Allocator) !Poller { + _ = allocator; const opt_terminal = blk: { break :blk terminal.Terminal.init() catch |err| { std.log.debug("failed to init terminal: {any}", .{err}); @@ -30,19 +28,12 @@ pub const Poller = struct { } const signals_file = try signals.open(); - errdefer signals_file.close(); + const signals_fd = signals_file.handle; return Poller{ .terminal = opt_terminal, - .signals_file = signals_file, - .poller = std.io.poll( - allocator, - FdType, - .{ - .stdin = std.fs.File.stdin(), - .signals = signals_file, - }, - ), + .stdin_fd = std.Io.File.stdin().handle, + .signals_fd = signals_fd, }; } @@ -51,28 +42,34 @@ pub const Poller = struct { } pub fn deinit(self: *Poller) void { - self.poller.deinit(); if (self.terminal) |terminal_| terminal_.deinit(); - self.signals_file.close(); } pub fn pollTimeout(self: *Poller, nanoseconds: u64) !?Command { - if (!try self.poller.pollTimeout(nanoseconds)) { - return null; - } + var fds = [2]std.posix.pollfd{ + .{ .fd = self.stdin_fd, .events = std.posix.POLL.IN, .revents = 0 }, + .{ .fd = self.signals_fd, .events = std.posix.POLL.IN, .revents = 0 }, + }; + + const timeout_ms = @as(i32, @intCast(@divTrunc(nanoseconds, std.time.ns_per_ms))); + const n = try std.posix.poll(&fds, timeout_ms); + if (n == 0) return null; - if (self.poller.reader(.stdin).buffered().len > 0) { - const char = try self.poller.reader(.stdin).takeByte(); - switch (char) { - 'p' => return Command.Pause, - 'q' => return Command.Quit, - 'r' => return Command.Reset, - else => {}, + if (fds[0].revents & std.posix.POLL.IN != 0) { + var buf: [1]u8 = undefined; + const bytes_read = try std.posix.read(self.stdin_fd, &buf); + if (bytes_read > 0) { + switch (buf[0]) { + 'p' => return Command.Pause, + 'q' => return Command.Quit, + 'r' => return Command.Reset, + else => {}, + } } } - if (self.poller.reader(.signals).buffered().len > 0) { - const signo = try signals.readSignal(self.poller.reader(.signals)); + if (fds[1].revents & std.posix.POLL.IN != 0) { + const signo = try signals.readSignal(self.signals_fd); switch (signo) { std.posix.SIG.USR1 => return Command.Pause, std.posix.SIG.USR2 => return Command.Reset, diff --git a/pracomer/src/main.zig b/pracomer/src/main.zig @@ -5,23 +5,19 @@ const std = @import("std"); const builtin = @import("builtin"); const step = @import("step.zig"); -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; - const allocator = gpa.allocator(); - defer std.debug.assert(gpa.deinit() == .ok); - - const program_and_args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, program_and_args); + const program_and_args = try init.minimal.args.toSlice(init.arena.allocator()); const program = program_and_args[0]; - const args = program_and_args[1..]; + const program_args = program_and_args[1..]; var stdout_buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writer(init.io, &stdout_buffer); const stdout = &stdout_writer.interface; - const opts = options.parseArgs(args) catch { + const opts = options.parseArgs(program_args) catch { try printUsage(allocator, stdout, program); std.process.exit(1); }; @@ -31,22 +27,21 @@ pub fn main() !void { try printUsage(allocator, stdout, program); }, .settings => |settings| { - try run(allocator, stdout, settings); + try run(init, allocator, stdout, settings); }, } } -fn run(allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Settings) !void { +fn run(init: std.process.Init, allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Settings) !void { var poller = try command.Poller.init(allocator); defer poller.deinit(); - const status_file: ?std.fs.File = if (settings.file) |path| try std.fs.cwd().createFile(path, .{}) else null; - defer if (status_file) |f| f.close(); - defer if (settings.file) |path| std.fs.cwd().deleteFile(path) catch |err| { + const status_file: ?std.Io.File = if (settings.file) |path| try std.Io.Dir.cwd().createFile(init.io, path, .{}) else null; + defer if (status_file) |f| f.close(init.io); + defer if (settings.file) |path| std.Io.Dir.cwd().deleteFile(init.io, path) catch |err| { std.log.debug("failed to delete status file: {any}", .{err}); }; - // Print a placeholder to be cleared by the first status if (poller.interactive()) { try out.print("\n", .{}); try out.flush(); @@ -54,14 +49,13 @@ fn run(allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Sett var current = step.Step{}; while (true) { - var timer = try pausable_timer.PausableTimer.init(); + var timer = pausable_timer.PausableTimer.init(init.io); var remaining = current.length(settings); while (remaining > 0) : ({ - remaining = current.length(settings) -| timer.read(); + remaining = current.length(settings) -| timer.read(init.io); }) { - // Clear the last status if (poller.interactive()) { try out.print("\x1b[F\x1b[2K\r", .{}); try out.flush(); @@ -70,6 +64,7 @@ fn run(allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Sett allocator, out, status_file, + init.io, current, remaining, ); @@ -82,15 +77,14 @@ fn run(allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Sett return; }, command.Command.Pause => { - try timer.togglePause(); + timer.togglePause(init.io); }, command.Command.Reset => { - // if the step is not started yet if (timer.paused and remaining == current.length(settings)) { current = step.Step{}; } - try timer.reset(); + timer.reset(init.io); }, } } @@ -98,26 +92,28 @@ fn run(allocator: std.mem.Allocator, out: *std.Io.Writer, settings: options.Sett current = current.next(); if (settings.notifications and current.length(settings) > 0) { - notify(allocator, current.step_type.message()); + notify(init.io, allocator, current.step_type.message()); } } } -fn notify(allocator: std.mem.Allocator, message: []const u8) void { +fn notify(io: std.Io, allocator: std.mem.Allocator, message: []const u8) void { if (builtin.os.tag == .macos) { const script = std.fmt.allocPrint(allocator, "display notification \"{s}\" with title \"pracomer\"", .{message}) catch return; defer allocator.free(script); const argv = [_][]const u8{ "osascript", "-e", script }; - var proc = std.process.Child.init(&argv, allocator); - _ = proc.spawn() catch return; - _ = proc.wait() catch |err| { + var proc = std.process.spawn(io, .{ + .argv = &argv, + }) catch return; + _ = proc.wait(io) catch |err| { std.log.debug("failed to wait for process: {any}", .{err}); }; } else { const argv = [_][]const u8{ "notify-send", "-u", "critical", message }; - var proc = std.process.Child.init(&argv, allocator); - _ = proc.spawn() catch return; - _ = proc.wait() catch |err| { + var proc = std.process.spawn(io, .{ + .argv = &argv, + }) catch return; + _ = proc.wait(io) catch |err| { std.log.debug("failed to wait for process: {any}", .{err}); }; } @@ -126,7 +122,8 @@ fn notify(allocator: std.mem.Allocator, message: []const u8) void { fn printStatus( allocator: std.mem.Allocator, out: *std.Io.Writer, - status_file: ?std.fs.File, + status_file: ?std.Io.File, + io: std.Io, current: step.Step, remaining: u64, ) !void { @@ -137,10 +134,12 @@ fn printStatus( try out.flush(); if (status_file) |f| { - try f.seekTo(0); - try f.writeAll(msg); - try f.writeAll("\n"); - try f.sync(); + var buf: [1024]u8 = undefined; + var fw = f.writer(io, &buf); + try fw.seekTo(0); + try fw.interface.writeAll(msg); + try fw.interface.writeAll("\n"); + try fw.flush(); } } diff --git a/pracomer/src/options.zig b/pracomer/src/options.zig @@ -8,13 +8,9 @@ pub const Options = union(OptionsTag) { }; pub const Settings = struct { - /// Length of a task in minutes task_length: u32 = 26, - /// Length of a break in minutes task_break: u32 = 6, - /// Enable notifications notifications: bool = true, - /// Write output to a file file: ?[]const u8 = null, }; @@ -24,7 +20,6 @@ pub fn parseArgs(args: []const []const u8) !Options { var i: u32 = 0; while (i < args.len) : (i += 1) { const arg = args[i]; - // settings options if (i + 1 < args.len and std.mem.eql(u8, arg, "-t")) { i += 1; settings.task_length = try std.fmt.parseInt(u32, args[i], 10); @@ -36,13 +31,9 @@ pub fn parseArgs(args: []const []const u8) !Options { } else if (i + 1 < args.len and std.mem.eql(u8, arg, "--file")) { i += 1; settings.file = args[i]; - } - // help - else if (std.mem.eql(u8, arg, "-h")) { + } else if (std.mem.eql(u8, arg, "-h")) { return .{ .help = void{} }; - } - // error - else { + } else { return error.InvalidArgument; } } diff --git a/pracomer/src/pausable_timer.zig b/pracomer/src/pausable_timer.zig @@ -1,43 +1,41 @@ const std = @import("std"); pub const PausableTimer = struct { - timer: std.time.Timer, + start_time: std.Io.Timestamp, elapsed_before_pause: u64, paused: bool, - pub fn init() !PausableTimer { - return PausableTimer{ - .timer = try std.time.Timer.start(), + pub fn init(io: std.Io) PausableTimer { + return .{ + .start_time = std.Io.Timestamp.now(io, .awake), .elapsed_before_pause = 0, .paused = true, }; } - pub fn togglePause(self: *PausableTimer) !void { + pub fn togglePause(self: *PausableTimer, io: std.Io) void { if (self.paused) { - // Resuming - self.timer.reset(); + self.start_time = std.Io.Timestamp.now(io, .awake); self.paused = false; } else { - // Pausing - self.elapsed_before_pause += self.timer.read(); + const now = std.Io.Timestamp.now(io, .awake); + self.elapsed_before_pause += @as(u64, @intCast(self.start_time.durationTo(now).nanoseconds)); self.paused = true; } } - pub fn reset(self: *PausableTimer) !void { + pub fn reset(self: *PausableTimer, io: std.Io) void { self.elapsed_before_pause = 0; - self.timer.reset(); + self.start_time = std.Io.Timestamp.now(io, .awake); self.paused = true; } - pub fn read(self: *PausableTimer) u64 { + pub fn read(self: *PausableTimer, io: std.Io) u64 { if (self.paused) { return self.elapsed_before_pause; } else { - // In Zig, timer.read() returns the time since the last reset() or lap() - // and does NOT update the internal 'previous' field. - return self.elapsed_before_pause + self.timer.read(); + const now = std.Io.Timestamp.now(io, .awake); + return self.elapsed_before_pause + @as(u64, @intCast(self.start_time.durationTo(now).nanoseconds)); } } }; diff --git a/pracomer/src/signals.zig b/pracomer/src/signals.zig @@ -5,27 +5,26 @@ var signal_pipe: [2]std.posix.fd_t = undefined; fn signalHandler(sig: i32) callconv(.c) void { const s: i32 = sig; - _ = std.posix.write(signal_pipe[1], std.mem.asBytes(&s)) catch |err| { - std.log.debug("failed to write to signal pipe: {any}", .{err}); - }; + _ = std.c.write(signal_pipe[1], std.mem.asBytes(&s), @sizeOf(i32)); } -pub fn open() !std.fs.File { +pub fn open() !std.Io.File { if (builtin.os.tag == .linux) { var mask = std.posix.sigemptyset(); std.posix.sigaddset(&mask, std.posix.SIG.USR1); std.posix.sigaddset(&mask, std.posix.SIG.USR2); - // Block signals so they can be handled by signalfd - _ = std.posix.sigprocmask(std.posix.SIG.BLOCK, &mask, null); + std.posix.sigprocmask(std.posix.SIG.BLOCK, &mask, null); const fd = try std.posix.signalfd(-1, &mask, std.os.linux.SFD.NONBLOCK); - return .{ .handle = fd }; + return .{ .handle = fd, .flags = .{ .nonblocking = false } }; } else { - // macOS / BSD / etc. use self-pipe - signal_pipe = try std.posix.pipe(); + const rc = std.c.pipe(&signal_pipe); + switch (std.posix.errno(@intCast(rc))) { + .SUCCESS => {}, + else => |err| return std.posix.unexpectedErrno(err), + } - // Set up signal handlers const sa = std.posix.Sigaction{ .handler = .{ .handler = signalHandler }, .mask = std.posix.sigemptyset(), @@ -35,17 +34,23 @@ pub fn open() !std.fs.File { std.posix.sigaction(std.posix.SIG.USR1, &sa, null); std.posix.sigaction(std.posix.SIG.USR2, &sa, null); - // Return the read end of the pipe - return .{ .handle = signal_pipe[0] }; + return .{ .handle = signal_pipe[0], .flags = .{ .nonblocking = false } }; } } -pub fn readSignal(reader: anytype) !i32 { +pub fn readSignal(fd: std.posix.fd_t) !std.posix.SIG { if (builtin.os.tag == .linux) { - const siginfo = try reader.takeStruct(std.os.linux.signalfd_siginfo, .little); - return @intCast(siginfo.signo); + var buf: [@sizeOf(std.os.linux.signalfd_siginfo)]u8 align(@alignOf(std.os.linux.signalfd_siginfo)) = undefined; + const n = try std.posix.read(fd, &buf); + if (n == 0) return error.Unexpected; + const siginfo: *const std.os.linux.signalfd_siginfo = @ptrCast(@alignCast(&buf)); + return @enumFromInt(siginfo.signo); } else { - // Read the signal number from the pipe - return try reader.takeInt(i32, .little); + var buf: [4]u8 = undefined; + const n = try std.posix.read(fd, &buf); + if (n < 4) return error.Unexpected; + var result: i32 = undefined; + @memcpy(std.mem.asBytes(&result), &buf); + return @enumFromInt(result); } } diff --git a/pracomer/src/terminal.zig b/pracomer/src/terminal.zig @@ -1,20 +1,12 @@ const std = @import("std"); -/// Turn on these two options on stdin: -/// - ICANON: disable line buffering -/// - ECHO: disable echo -/// -/// Used to be able to read a single character from stdin. Only works when -/// running in interactive mode pub const Terminal = struct { termios: std.posix.termios, pub fn init() !Terminal { const old = try std.posix.tcgetattr(std.posix.STDIN_FILENO); var termios = old; - // unbuffered input termios.lflag.ICANON = false; - // no echo termios.lflag.ECHO = false; try std.posix.tcsetattr(std.posix.STDIN_FILENO, .NOW, termios); return .{ .termios = old }; diff --git a/speediness/build.zig.zon b/speediness/build.zig.zon @@ -2,7 +2,7 @@ .name = .speediness, .version = "0.0.0", .fingerprint = 0x106e839d55011469, - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", .dependencies = .{}, .paths = .{ "build.zig", diff --git a/speediness/src/main.zig b/speediness/src/main.zig @@ -1,5 +1,3 @@ -// speediness: download and upload speed benchmark - const std = @import("std"); fn mbps(bytes: usize, ns: u64) f64 { @@ -8,13 +6,12 @@ fn mbps(bytes: usize, ns: u64) f64 { return (@as(f64, @floatFromInt(bytes)) / secs) / (1024.0 * 1024.0); } -// ── Download ────────────────────────────────────────────────────────────────── - -fn downloadTest(allocator: std.mem.Allocator) !void { +fn downloadTest(io: std.Io, allocator: std.mem.Allocator) !void { const url = "https://proof.ovh.net/files/100Mb.dat"; var client: std.http.Client = .{ .allocator = allocator, + .io = io, }; defer client.deinit(); @@ -33,20 +30,27 @@ fn downloadTest(allocator: std.mem.Allocator) !void { return error.HttpError; } + var transfer_buffer: [8192]u8 = undefined; + var reader = response.reader(&transfer_buffer); + var buf: [131072]u8 = undefined; var total: usize = 0; var iter: usize = 0; - const start = try std.time.Instant.now(); - var reader = response.reader(&.{}); + const start = std.Io.Timestamp.now(io, .awake); + const start_ns: u64 = @intCast(start.nanoseconds); while (true) { - const n = reader.readSliceShort(&buf) catch break; + const n = reader.readSliceShort(&buf) catch |err| { + std.debug.print("\nRead error: {}\n", .{err}); + break; + }; if (n == 0) break; total += n; iter += 1; if (iter % 8 == 0) { - const now = try std.time.Instant.now(); - const speed = mbps(total, now.since(start)); + const now_ns: u64 = @intCast(std.Io.Timestamp.now(io, .awake).nanoseconds); + const elapsed = now_ns - start_ns; + const speed = mbps(total, elapsed); std.debug.print("\r{d:6.1} MB {d:6.2} MB/s {d:6.1} Mbps ", .{ @as(f64, @floatFromInt(total)) / 1e6, speed, @@ -55,22 +59,22 @@ fn downloadTest(allocator: std.mem.Allocator) !void { } } - const end = try std.time.Instant.now(); - const speed = mbps(total, end.since(start)); - const secs = @as(f64, @floatFromInt(end.since(start))) / 1e9; + const end_ns: u64 = @intCast(std.Io.Timestamp.now(io, .awake).nanoseconds); + const elapsed = end_ns - start_ns; + const speed = mbps(total, elapsed); + const secs = @as(f64, @floatFromInt(elapsed)) / 1e9; std.debug.print("\n{d:.1} MB in {d:.2}s => {d:.2} MB/s ({d:.1} Mbps)\n", .{ @as(f64, @floatFromInt(total)) / 1e6, secs, speed, speed * 8, }); } -// ── Upload ──────────────────────────────────────────────────────────────────── - -fn uploadTest(allocator: std.mem.Allocator) !void { +fn uploadTest(io: std.Io, allocator: std.mem.Allocator) !void { const url = "https://speed.cloudflare.com/__upload"; - const upload_bytes: usize = 20 * 1024 * 1024; // 20 MB + const upload_bytes: usize = 20 * 1024 * 1024; var client: std.http.Client = .{ .allocator = allocator, + .io = io, }; defer client.deinit(); @@ -86,22 +90,23 @@ fn uploadTest(allocator: std.mem.Allocator) !void { var body = try req.sendBodyUnflushed(&.{}); - // Stream payload in 128 KB chunks const chunk_size: usize = 131072; const chunk = try allocator.alloc(u8, chunk_size); defer allocator.free(chunk); @memset(chunk, 0x55); var sent: usize = 0; - const start = try std.time.Instant.now(); + const start = std.Io.Timestamp.now(io, .awake); + const start_ns: u64 = @intCast(start.nanoseconds); while (sent < upload_bytes) { const to_send = @min(chunk_size, upload_bytes - sent); try body.writer.writeAll(chunk[0..to_send]); sent += to_send; - const now = try std.time.Instant.now(); - const speed = mbps(sent, now.since(start)); + const now_ns: u64 = @intCast(std.Io.Timestamp.now(io, .awake).nanoseconds); + const elapsed = now_ns - start_ns; + const speed = mbps(sent, elapsed); std.debug.print("\r {d:6.1} MB {d:6.2} MB/s {d:6.1} Mbps ", .{ @as(f64, @floatFromInt(sent)) / 1e6, speed, @@ -110,33 +115,31 @@ fn uploadTest(allocator: std.mem.Allocator) !void { } try body.end(); - try req.connection.?.flush(); + if (req.connection) |conn| { + try conn.flush(); + } - // Finish timing after data is flushed - const end = try std.time.Instant.now(); + const end_ns: u64 = @intCast(std.Io.Timestamp.now(io, .awake).nanoseconds); var redirect_buffer: [8192]u8 = undefined; _ = try req.receiveHead(&redirect_buffer); - const speed = mbps(sent, end.since(start)); - const secs = @as(f64, @floatFromInt(end.since(start))) / 1e9; + const elapsed = end_ns - start_ns; + const speed = mbps(sent, elapsed); + const secs = @as(f64, @floatFromInt(elapsed)) / 1e9; std.debug.print("\n{d:.1} MB in {d:.2}s => {d:.2} MB/s ({d:.1} Mbps)\n", .{ @as(f64, @floatFromInt(sent)) / 1e6, secs, speed, speed * 8, }); } -// ── Main ────────────────────────────────────────────────────────────────────── - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; - downloadTest(allocator) catch |err| { + downloadTest(init.io, allocator) catch |err| { std.debug.print("Download error: {}\n", .{err}); }; - uploadTest(allocator) catch |err| { + uploadTest(init.io, allocator) catch |err| { std.debug.print("Upload error: {}\n", .{err}); }; }