commit:     b6a8b93349b7f163cb8d35e19d2afcbb768f5ccc
Author:     Michał Górny <mgorny <AT> gentoo <DOT> org>
AuthorDate: Sat Nov 29 13:30:29 2025 +0000
Commit:     Michał Górny <mgorny <AT> gentoo <DOT> org>
CommitDate: Sat Nov 29 13:30:29 2025 +0000
URL:        https://gitweb.gentoo.org/proj/steve.git/commit/?id=b6a8b933

Add the initial client version

Add a client that acquires a token and runs the specified command.

Signed-off-by: Michał Górny <mgorny <AT> gentoo.org>

 meson.build   |  20 +++++---
 meson.options |   8 ++++
 stevie.cxx    | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 165 insertions(+), 6 deletions(-)

diff --git a/meson.build b/meson.build
index 2bc5c58..b0d8864 100644
--- a/meson.build
+++ b/meson.build
@@ -6,10 +6,18 @@ project('steve', 'cpp',
 
 version = meson.project_version()
 
-fuse3 = dependency('fuse3')
-libevent = dependency('libevent', version: '>= 2')
+if get_option('client')
+  executable('stevie', ['stevie.cxx'],
+             cpp_args : [f'-DSTEVE_VERSION="@version@"'],
+             install : true)
+endif
 
-executable('steve', ['steve.cxx'],
-           cpp_args : [f'-DSTEVE_VERSION="@version@"'],
-           dependencies: [fuse3, libevent],
-           install : true)
+if get_option('server')
+  fuse3 = dependency('fuse3')
+  libevent = dependency('libevent', version: '>= 2')
+
+  executable('steve', ['steve.cxx'],
+             cpp_args : [f'-DSTEVE_VERSION="@version@"'],
+             dependencies: [fuse3, libevent],
+             install : true)
+endif

diff --git a/meson.options b/meson.options
new file mode 100644
index 0000000..8a312bf
--- /dev/null
+++ b/meson.options
@@ -0,0 +1,8 @@
+option('client',
+       type: 'boolean',
+       value: true,
+       description: 'Build the client')
+option('server',
+       type: 'boolean',
+       value: true,
+       description: 'Build the server')

diff --git a/stevie.cxx b/stevie.cxx
new file mode 100644
index 0000000..28a0d88
--- /dev/null
+++ b/stevie.cxx
@@ -0,0 +1,143 @@
+/* Stevie, the client for stevie
+ * (c) 2025 Michał Górny
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Inspired by nixos-jobserver (draft) and guildmaster:
+ * 
https://github.com/RaitoBezarius/nixpkgs/blob/e97220ecf1e8887b949e4e16547bf0334826d076/pkgs/by-name/ni/nixos-jobserver/nixos-jobserver.cpp#L213
+ * https://codeberg.org/amonakov/guildmaster/
+ */
+
+#include <cerrno>
+#include <cstring>
+#include <print>
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+struct token_guard {
+       int jobserver_fd;
+       char job_token;
+
+       ~token_guard() {
+               ssize_t wr;
+               while ((wr = write(jobserver_fd, &job_token, 1)) == -1 && errno 
== EINTR);
+               if (wr == -1)
+                       perror("Writing job token failed");
+       }
+};
+
+static int run_command(int jobserver_fd, char **argv, const char 
*jobserver_path)
+{
+       char job_token;
+
+       ssize_t res;
+       while ((res = read(jobserver_fd, &job_token, 1)) == -1 && errno == 
EINTR);
+       if (res == 0) {
+               std::print("EOF while waiting for job token\n");
+               return 1;
+       }
+       if (res == -1) {
+               perror("Reading job token failed");
+               return 1;
+       }
+
+       pid_t pid = fork();
+       if (pid == 0) {
+               const char *old_makeflags = getenv("MAKEFLAGS");
+               std::string new_makeflags;
+               if (old_makeflags) {
+                       new_makeflags = old_makeflags;
+                       new_makeflags += ' ';
+               }
+               new_makeflags += "--jobserver-auth=fifo:";
+               new_makeflags += jobserver_path;
+               if (setenv("MAKEFLAGS", new_makeflags.c_str(), 1) == -1) {
+                       std::print(stderr, "Unable to set MAKEFLAGS={}\n", 
new_makeflags);
+                       _exit(1);
+               }
+
+               execvp(argv[0], argv);
+               std::print("exec for {} failed: {}\n", argv[0], 
strerror(errno));
+               _exit(1);
+       }
+
+       token_guard job_token_guard{jobserver_fd, job_token};
+       if (pid == -1) {
+               perror("Forking failed");
+               return 1;
+       }
+
+       int wret;
+       while ((res = waitpid(pid, &wret, 0)) == -1 && errno == EINTR);
+       if (res == -1) {
+               std::print("Waiting for PID {} failed: {}\n", pid, 
strerror(errno));
+               return 1;
+       }
+
+       return WIFSIGNALED(wret) ? WTERMSIG(wret) + 128 : WEXITSTATUS(wret);
+}
+
+struct fd_guard {
+       int fd;
+       ~fd_guard() {
+               close(fd);
+       }
+};
+
+static constexpr char stevie_usage[] =
+       "usage: {} [options] <argv>...\n"
+       "\n"
+       "options:\n"
+       "    --help, -h             print this help message\n"
+       "    --version, -V          print version\n"
+       "    --jobserver PATH,\n"
+       "    -j PATH                jobserver FIFO path (default: 
/dev/steve)\n";
+
+static const struct option stevie_long_opts[] = {
+       {"help", no_argument, 0, 'h'},
+       {"version", no_argument, 0, 'V'},
+       {"jobserver", required_argument, 0, 'j'},
+       {},
+};
+
+static const char *stevie_short_opts = "+hVj:";
+
+int main(int argc, char **argv)
+{
+       const char *jobserver_path = "/dev/steve";
+
+       int opt;
+       while ((opt = getopt_long(argc, argv, stevie_short_opts,
+                               stevie_long_opts, nullptr)) != -1) {
+               switch (opt) {
+               case 'h':
+                       std::print(stevie_usage, argv[0]);
+                       return 0;
+               case 'V':
+                       std::print("stevie {}\n", STEVE_VERSION);
+                       return 0;
+               case 'j':
+                       jobserver_path = optarg;
+                       break;
+               default:
+                       std::print(stderr, stevie_usage, argv[0]);
+                       return 1;
+               }
+       }
+
+       if (!argv[optind]) {
+               std::print(stderr, "{}: no command provided\n", argv[0]);
+               return 1;
+       }
+
+       int jobserver_fd = open(jobserver_path, O_RDWR | O_CLOEXEC);
+       if (jobserver_fd == -1) {
+               std::print(stderr, "unable to open {}: {}\n", jobserver_path, 
strerror(errno));
+               return 1;
+       }
+       fd_guard jobserver_fd_guard{jobserver_fd};
+
+       return run_command(jobserver_fd, &argv[optind], jobserver_path);
+}

Reply via email to