From 09c75b343dc4c5b8287c11bc032525b6bb7571fc Mon Sep 17 00:00:00 2001 From: Jiacai Liu Date: Thu, 23 Jan 2025 22:58:13 +0800 Subject: [PATCH] add timeout (#34) --- build.zig | 8 +++- docs/content/programs/timeout.org | 18 ++++++++ docs/content/programs/zigfetch.org | 6 +-- src/bin/timeout.zig | 66 ++++++++++++++++++++++++++++++ src/bin/zigfetch.zig | 26 ++++++++---- 5 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 docs/content/programs/timeout.org create mode 100644 src/bin/timeout.zig diff --git a/build.zig b/build.zig index 268351b..c6a15e9 100644 --- a/build.zig +++ b/build.zig @@ -124,6 +124,7 @@ fn buildBinaries( "dark-mode", "repeat", "tcp-proxy", + "timeout", }) |name| { try buildBinary( b, @@ -207,7 +208,7 @@ fn makeCompileStep( // We can't use `target.result.isDarwin()` here // Since when cross compile to darwin, there is no framework in the host! const is_darwin = @import("builtin").os.tag == .macos; - + const is_win = target.result.os.tag == .windows; if (!is_darwin) { if (std.mem.eql(u8, name, "night-shift") or std.mem.eql(u8, name, "dark-mode")) { return null; @@ -229,6 +230,11 @@ fn makeCompileStep( exe.linkFramework("SkyLight"); } else if (std.mem.eql(u8, name, "tcp-proxy")) { exe.linkLibC(); + } else if (std.mem.eql(u8, name, "timeout")) { + if (is_win) { // error: TODO windows Sigaction definition + return null; + } + exe.linkLibC(); } else if (std.mem.eql(u8, name, "zigfetch")) { if (skip_zigfetch) { return null; diff --git a/docs/content/programs/timeout.org b/docs/content/programs/timeout.org new file mode 100644 index 0000000..5cdf52b --- /dev/null +++ b/docs/content/programs/timeout.org @@ -0,0 +1,18 @@ +#+TITLE: timeout +#+DATE: 2025-01-23T22:28:53+0800 +#+LASTMOD: 2025-01-23T22:35:32+0800 +#+TYPE: docs +#+DESCRIPTION: Run a command with bounded time + +#+begin_example +timeout SECONDS COMMAND [ARG]... +#+end_example + +Start a command, and kill it if the specified timeout expires. + +The =timeout= command is crucial for: + +- Process Control +- Limits execution time of commands +- Prevents resource-consuming tasks from running indefinitely +- Provides automatic process termination diff --git a/docs/content/programs/zigfetch.org b/docs/content/programs/zigfetch.org index f3207b7..11dd4ec 100644 --- a/docs/content/programs/zigfetch.org +++ b/docs/content/programs/zigfetch.org @@ -1,12 +1,12 @@ #+TITLE: zigfetch #+DATE: 2025-01-01T18:01:47+0800 -#+LASTMOD: 2025-01-04T09:46:39+0800 +#+LASTMOD: 2025-01-04T17:16:15+0800 #+TYPE: docs #+DESCRIPTION: Fetch zig packages, utilizing libcurl. -=zigfetch= behaves similarly to =zig fetch=, utilizing the capabilities of libcurl for its functionality. +=zigfetch= behaves similarly to =zig fetch=, but utilizing the capabilities of libcurl for its functionality. -Since the HTTP support within Zig's standard library isn't currently stable, [[https://github.com/ziglang/zig/issues/21792][this proxy issue]] make it even harder, resulting in multiple errors occurring during dependency downloads when building Zig projects. +HTTP support within Zig's standard library isn't currently stable, [[https://github.com/ziglang/zig/issues/21792][this proxy issue]] make it even harder, resulting in multiple errors occurring during dependency downloads when building Zig projects. {{< figure src="https://fs.liujiacai.net/cdn-img/zigcli/zig-fetch-errors.webp" >}} diff --git a/src/bin/timeout.zig b/src/bin/timeout.zig new file mode 100644 index 0000000..e53d665 --- /dev/null +++ b/src/bin/timeout.zig @@ -0,0 +1,66 @@ +//! Run a command with bounded time +//! https://github.com/coreutils/coreutils/blob/v9.6/src/timeout.c + +const std = @import("std"); +const posix = std.posix; +const Child = std.process.Child; + +pub var child: Child = undefined; +pub var spawn_success = false; + +pub fn main() !void { + try posix.sigaction(posix.SIG.ALRM, &posix.Sigaction{ + .handler = .{ + .handler = struct { + pub fn handler(got: c_int) callconv(.C) void { + std.debug.assert(got == posix.SIG.ALRM); + _ = child.kill() catch |e| { + std.log.err("Kill child failed, err:{any}", .{e}); + return; + }; + posix.exit(124); // timeout + } + }.handler, + }, + .mask = posix.empty_sigset, + .flags = 0, + }, null); + + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer if (gpa.deinit() != .ok) @panic("leak"); + const allocator = gpa.allocator(); + + const args = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, args); + + if (args.len < 3) { + std.debug.print( + \\Usage: + \\ {s} SECONDS COMMAND [ARG]... + \\ + , .{args[0]}); + posix.exit(1); + } + + const ttl_seconds = try std.fmt.parseInt(c_uint, args[1], 10); + const cmds = args[2..]; + const ret = std.c.alarm(ttl_seconds); + if (ret != 0) { + std.log.err("Set alarm signal failed, retcode:{d}", .{ret}); + posix.exit(1); + } + + child = Child.init(cmds, allocator); + try child.spawn(); + spawn_success = true; + const term = try child.wait(); + switch (term) { + .Exited => |status| { + posix.exit(status); + }, + else => { + std.log.err("Child internal error, term:{any}", .{term}); + posix.exit(125); + }, + } +} diff --git a/src/bin/zigfetch.zig b/src/bin/zigfetch.zig index d0d81c6..a7efa7e 100644 --- a/src/bin/zigfetch.zig +++ b/src/bin/zigfetch.zig @@ -365,15 +365,15 @@ fn moveToCache(allocator: Allocator, src_dir: []const u8, hex: Manifest.MultiHas const dst = try std.fmt.allocPrint(allocator, "{s}/p/{s}", .{ cache_dirname, hex }); defer allocator.free(dst); - _ = fs.openDirAbsolute(dst, .{}) catch |err| switch (err) { - error.FileNotFound => { - return fs.renameAbsolute(src_dir, dst); - }, - else => return err, - }; - if (args.verbose) { - log.info("Dir({s}) already exists, skip copy...", .{dst}); + const found = try checkFileExists(dst); + if (found) { + if (args.verbose) { + log.info("Dir({s}) already exists, skip copy...", .{dst}); + } + return; } + + try fs.renameAbsolute(src_dir, dst); } fn fetchPackage(allocator: Allocator, url: [:0]const u8, out_dir: fs.Dir) ![]const u8 { @@ -921,3 +921,13 @@ fn recursiveDirectoryCopy(allocator: Allocator, dir: fs.Dir, tmp_dir: fs.Dir) an } } } + +// Returns true if path exists +fn checkFileExists(path: []const u8) !bool { + fs.cwd().access(path, .{}) catch |e| switch (e) { + error.FileNotFound => return false, + else => return e, + }; + + return true; +}