commit: 6231c713ae2813b560473bd7f38904b22b64afd4
Author: Michał Górny <mgorny <AT> gentoo <DOT> org>
AuthorDate: Sat Nov 29 17:36:35 2025 +0000
Commit: Michał Górny <mgorny <AT> gentoo <DOT> org>
CommitDate: Sat Nov 29 17:36:51 2025 +0000
URL: https://gitweb.gentoo.org/proj/steve.git/commit/?id=6231c713
Add initial ioctls
Add ioctls to get token count, get/set job number and get/set min-jobs.
Signed-off-by: Michał Górny <mgorny <AT> gentoo.org>
steve.cxx | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
stevie.cxx | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++--------
2 files changed, 172 insertions(+), 20 deletions(-)
diff --git a/steve.cxx b/steve.cxx
index c567a13..eb2c6f4 100644
--- a/steve.cxx
+++ b/steve.cxx
@@ -10,6 +10,7 @@
#define FUSE_USE_VERSION 31
+#include <cassert>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
@@ -34,6 +35,7 @@
#include <fuse.h>
#include <fuse_opt.h>
+#include "steve.h"
#include "util.hxx"
struct steve_read_waiter {
@@ -69,11 +71,11 @@ struct steve_process {
struct steve_state {
bool verbose;
- size_t jobs;
- size_t min_jobs;
+ uint64_t jobs;
+ uint64_t min_jobs;
double max_load_avg{-1}; /* < 0 implies no load average */
double load_avg;
- size_t tokens;
+ int64_t tokens;
std::deque<steve_read_waiter> read_waiters;
std::deque<steve_poll_waiter> poll_waiters;
std::unordered_map<uint64_t, steve_process> processes;
@@ -93,7 +95,7 @@ enum class steve_token_availability {
static steve_token_availability steve_can_give_token(steve_state *state)
{
- if (state->tokens == 0)
+ if (state->tokens <= 0)
return steve_token_availability::no_tokens;
if (state->max_load_avg > 0) {
if (state->jobs < state->min_jobs + state->tokens)
@@ -393,6 +395,88 @@ static void steve_poll(
fuse_reply_poll(req, events);
}
+static void steve_ioctl(
+ fuse_req_t req, int cmd, void *, fuse_file_info *fi,
+ unsigned flags, const void *in_buf, size_t in_buf_sz, size_t out_buf_sz)
+{
+ steve_state *state = static_cast<steve_state *>(fuse_req_userdata(req));
+ /* FUSE uses the wrong type, sigh */
+ unsigned ioctl_num = cmd;
+
+ if (flags & FUSE_IOCTL_COMPAT) {
+ fuse_reply_err(req, ENOSYS);
+ return;
+ }
+
+ if (state->verbose)
+ std::print(stderr, "PID {} requested ioctl 0x{:08x}\n",
+ fi->fh, ioctl_num);
+
+ /* TODO: is this generated by fuse? would assert() be better? */
+ if (STEVE_IOC_IS_GET(ioctl_num) && out_buf_sz != sizeof(int64_t)) {
+ fuse_reply_err(req, EINVAL);
+ return;
+ }
+ if (STEVE_IOC_IS_SET(ioctl_num) && in_buf_sz != sizeof(int64_t)) {
+ fuse_reply_err(req, EINVAL);
+ return;
+ }
+
+ int64_t val;
+ if (STEVE_IOC_IS_SET(ioctl_num)) {
+ const int64_t *in_val = static_cast<const int64_t *>(in_buf);
+ if (*in_val < 0 || *in_val >= INT_MAX) {
+ fuse_reply_err(req, EINVAL);
+ return;
+ }
+
+ val = *in_val;
+ }
+
+ switch (ioctl_num) {
+ case STEVE_IOC_GET_TOKENS:
+ val = state->tokens;
+ fuse_reply_ioctl(req, 0, &val, sizeof(val));
+ break;
+ case STEVE_IOC_GET_JOBS:
+ val = state->jobs;
+ fuse_reply_ioctl(req, 0, &val, sizeof(val));
+ break;
+ case STEVE_IOC_GET_MIN_JOBS:
+ val = state->min_jobs;
+ fuse_reply_ioctl(req, 0, &val, sizeof(val));
+ break;
+ case STEVE_IOC_SET_JOBS:
+ if (val == 0)
+ val = sysconf(_SC_NPROCESSORS_ONLN);
+ state->tokens += val - state->jobs;
+ state->jobs = val;
+ std::print("PID {} set jobs to {}\n", fi->fh,
state->jobs);
+ if (state->verbose)
+ std::print(" new token availability: {}\n",
state->tokens);
+ if (state->min_jobs > state->jobs) {
+ state->min_jobs = state->jobs;
+ if (state->verbose)
+ std::print(" capping min-jobs to
{}\n", state->min_jobs);
+ }
+ fuse_reply_ioctl(req, 0, nullptr, 0);
+ steve_wake_waiters(state);
+ break;
+ case STEVE_IOC_SET_MIN_JOBS:
+ if (static_cast<uint64_t>(val) > state->jobs) {
+ fuse_reply_err(req, EINVAL);
+ return;
+ }
+ state->min_jobs = val;
+ std::print("PID {} set min-jobs to {}\n", fi->fh,
state->min_jobs);
+ fuse_reply_ioctl(req, 0, nullptr, 0);
+ steve_wake_waiters(state);
+ break;
+ default:
+ fuse_reply_err(req, ENOTTY);
+ }
+}
+
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
static const struct cuse_lowlevel_ops steve_ops = {
@@ -402,6 +486,7 @@ static const struct cuse_lowlevel_ops steve_ops = {
.read = steve_read,
.write = steve_write,
.release = steve_release,
+ .ioctl = steve_ioctl,
.poll = steve_poll,
};
#pragma GCC diagnostic pop
@@ -467,10 +552,8 @@ int main(int argc, char **argv)
case 'j':
case 'm':
{
- char *endptr;
- errno = 0;
- long jobs_arg = strtol(optarg, &endptr,
10);
- if (*endptr || errno == ERANGE ||
jobs_arg < 0 || jobs_arg > INT_MAX) {
+ long jobs_arg;
+ if (!arg_to_long(optarg, &jobs_arg)) {
std::print(stderr, "invalid job
number: {}\n", optarg);
return 1;
}
diff --git a/stevie.cxx b/stevie.cxx
index 523ed0c..2957825 100644
--- a/stevie.cxx
+++ b/stevie.cxx
@@ -7,15 +7,20 @@
* https://codeberg.org/amonakov/guildmaster/
*/
+#include <cassert>
#include <cerrno>
#include <cstring>
#include <print>
+#include <utility>
+#include <vector>
#include <fcntl.h>
#include <getopt.h>
+#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>
+#include "steve.h"
#include "util.hxx"
struct token_guard {
@@ -120,30 +125,46 @@ static int run_command(int jobserver_fd, char **argv,
const char *jobserver_path
}
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";
+"usage: {0} [options] <argv>...\n"
+"\n"
+"options:\n"
+" --help, -h print this help message\n"
+" --version, -V print version\n"
+" --jobserver PATH, -j PATH\n"
+" jobserver FIFO path (default: /dev/steve)\n"
+"\n"
+"other actions (executed before the command):\n"
+" --get-tokens, -t print available token count\n"
+" --get-jobs, -j print total job number\n"
+" --set-jobs JOBS, -J JOBS\n"
+" set total job number\n"
+" --get-min-jobs, -m print min-job number\n"
+" --set-min-jobs JOBS, -M JOBS\n"
+" set min-job number\n";
static const struct option stevie_long_opts[] = {
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
- {"jobserver", required_argument, 0, 'j'},
+ {"jobserver", required_argument, 0, 'p'},
+ {"get-tokens", no_argument, 0, 't'},
+ {"get-jobs", no_argument, 0, 'j'},
+ {"set-jobs", required_argument, 0, 'J'},
+ {"get-min-jobs", no_argument, 0, 'm'},
+ {"set-min-jobs", required_argument, 0, 'M'},
{},
};
-static const char *stevie_short_opts = "+hVj:";
+static const char *stevie_short_opts = "+hVtjJ:mM:";
int main(int argc, char **argv)
{
const char *jobserver_path = "/dev/steve";
int opt;
+ std::vector<std::pair<unsigned long, int64_t>> actions;
while ((opt = getopt_long(argc, argv, stevie_short_opts,
stevie_long_opts, nullptr)) != -1) {
+ unsigned long ioctl_num = 0;
switch (opt) {
case 'h':
std::print(stevie_usage, argv[0]);
@@ -151,16 +172,45 @@ int main(int argc, char **argv)
case 'V':
std::print("stevie {}\n", STEVE_VERSION);
return 0;
- case 'j':
+ case 'p':
jobserver_path = optarg;
break;
+ case 't':
+ ioctl_num = STEVE_IOC_GET_TOKENS;
+ break;
+ case 'j':
+ ioctl_num = STEVE_IOC_GET_JOBS;
+ break;
+ case 'J':
+ ioctl_num = STEVE_IOC_SET_JOBS;
+ break;
+ case 'm':
+ ioctl_num = STEVE_IOC_GET_MIN_JOBS;
+ break;
+ case 'M':
+ ioctl_num = STEVE_IOC_SET_MIN_JOBS;
+ break;
default:
std::print(stderr, stevie_usage, argv[0]);
return 1;
}
+
+ if (ioctl_num != 0) {
+ if (STEVE_IOC_IS_GET(ioctl_num)) {
+ actions.emplace_back(ioctl_num, 0);
+ } else if (STEVE_IOC_IS_SET(ioctl_num)) {
+ long long_arg;
+ if (!arg_to_long(optarg, &long_arg)) {
+ std::print(stderr, "invalid value:
{}\n", optarg);
+ return 1;
+ }
+ actions.emplace_back(ioctl_num, long_arg);
+ } else
+ assert(0 && "not reached");
+ }
}
- if (!argv[optind]) {
+ if (actions.empty() && !argv[optind]) {
std::print(stderr, "{}: no command provided\n", argv[0]);
return 1;
}
@@ -172,5 +222,24 @@ int main(int argc, char **argv)
}
fd_guard jobserver_fd_guard{jobserver_fd};
- return run_command(jobserver_fd, &argv[optind], jobserver_path);
+ for (auto &action : actions) {
+ unsigned long ioctl_num = action.first;
+ int64_t ioctl_val = action.second;
+
+ if (ioctl(jobserver_fd, ioctl_num, &ioctl_val) != 0) {
+ perror("ioctl failed");
+ return 1;
+ }
+
+ if (STEVE_IOC_IS_GET(ioctl_num))
+ std::print("{}\n", ioctl_val);
+ else if (STEVE_IOC_IS_SET(ioctl_num))
+ std::print("ok\n");
+ else
+ assert(0 && "not reached");
+ }
+
+ if (argv[optind])
+ return run_command(jobserver_fd, &argv[optind], jobserver_path);
+ return 0;
}