[Midnightbsd-cvs] [MidnightBSD/src] 7142d7: Add prowld prototype. It's a replacement for init...
Lucas Holt
noreply at github.com
Sat Apr 18 23:47:22 EDT 2026
Branch: refs/heads/master
Home: https://github.com/MidnightBSD/src
Commit: 7142d7783ef2bd410117e578da42e5b1af46e05b
https://github.com/MidnightBSD/src/commit/7142d7783ef2bd410117e578da42e5b1af46e05b
Author: Lucas Holt <luke at foolishgames.com>
Date: 2026-04-18 (Sat, 18 Apr 2026)
Changed paths:
M etc/mtree/BSD.var.dist
M libexec/rc/rc.d/Makefile
A libexec/rc/rc.d/prowld
M sbin/Makefile
A sbin/prowlctl/Makefile
A sbin/prowlctl/prowlctl.8
A sbin/prowlctl/prowlctl.c
A sbin/prowld/Makefile
A sbin/prowld/dag.c
A sbin/prowld/ipc.c
A sbin/prowld/job.c
A sbin/prowld/prowld-spec.md
A sbin/prowld/prowld.8
A sbin/prowld/prowld.c
A sbin/prowld/prowld.conf.sample
A sbin/prowld/prowld.h
A sbin/prowld/rcshim.c
A sbin/prowld/socket_activation.c
A sbin/prowld/supervisor.c
A sbin/prowld/unit.c
Log Message:
-----------
Add prowld prototype. It's a replacement for init. (#334)
* Add prowld prototype. It's a replacement for init.
AI-Assisted-by: Claude Sonnet 4.6, Opus 4.7
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: remove unused g_pid_fd variable
Fixes -Werror,-Wunused-variable build failure. The PID file is managed
directly by write_pidfile() and remove_pidfile() via fopen/unlink; no
file descriptor needs to be retained.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: add missing supervisor_signal prototype to prowld.h
Fixes -Werror,-Wmissing-prototypes build failure in supervisor.c.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: fix -Wmissing-variable-declarations for ipc_listen_fd
Move the extern declaration of ipc_listen_fd into prowld.h so that
ipc.c sees it before its own definition, satisfying the compiler.
Remove the now-redundant local extern from prowld.c.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowlctl: initialize ret to avoid uninitialized variable warning
Fixes -Werror,-Wsometimes-uninitialized build failure.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld/prowlctl: enforce IPC peer credentials and sanitize client input
ipc.c:
- Capture peer uid/gid via getpeereid(2) in ipc_accept(); reject
connections where credential fetch fails.
- Cache the wheel group GID at startup via getgrnam("wheel") in
ipc_init(); chown the socket to root:wheel after bind+chmod.
- Add ipc_get_client(), ipc_peer_is_root(), ipc_peer_can_query()
helpers.
- In ipc_dispatch_message(), gate read-only verbs (list/status/show)
to wheel members and root; require root for all state-changing verbs.
prowlctl.c:
- Add validate_label_input() mirroring server-side label_valid(): only
alphanumerics, dots, hyphens, underscores; no leading dot; no "..".
- Validate user-supplied label argument in do_status() and
do_simple_target() before embedding it in snprintf JSON construction.
- Validate --type=TYPE filter in do_list() for the same reason.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld/prowlctl: fix JSON injection, partial send, UB, and fork safety
ipc.c:
- Add json_escape_str() that handles \, ", \n, \r, \t, and other
control chars (\uXXXX). Use it in job_to_json() for label,
description, and rc_name, and in ipc_send_ok/error() for the
client-supplied id field, preventing JSON injection via malformed
job metadata.
- Add ipc_send_all() loop (retrying on EINTR) and use it in ipc_send()
to guarantee full writes; a partial send previously corrupted the
length-prefixed protocol framing.
prowlctl.c:
- Add send_all() loop (retrying on EINTR) and use it in send_request()
for the same framing-correctness reason.
- Fix undefined behaviour in check_response(): msg[] was compared with
strcmp() even when json_get_str() returned NULL, leaving the buffer
uninitialised. Gate the strcmp calls on has_msg boolean.
prowld.h / supervisor.c:
- Add PROWL_GROUPS_MAX (32) and pre-resolved privilege fields
(run_uid, run_gid, run_groups[], run_ngroups, run_priv_set) to job_t.
- Add resolve_job_privileges() called in supervisor_start() in the
parent before fork(): resolves user/group names via getpwnam(3),
getgrnam(3), getgrouplist(3) — none of which are async-signal-safe.
- Rewrite drop_privileges() to use only async-signal-safe syscalls:
setgroups(2), setgid(2), setuid(2), operating on the pre-resolved
numeric IDs stored in the job struct.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: fix privilege drop errors, dir perms, env validation, MSG_MAX doc
supervisor.c:
- Check return values of setgroups(2), setgid(2), setuid(2) in
drop_privileges() and call _exit(1) on failure, logging via
syslog(3) (async-signal-safe). Previously a failed privilege
drop allowed the child to continue with elevated permissions.
prowld.c:
- Give PROWLD_NOTIFY_DIR and PROWLD_MASK_DIR mode 0700 (root-only).
These dirs hold readiness-notification pipes and mask symlinks
that must not be writable or searchable by unprivileged users.
PROWLD_RUN_DIR stays 0755 (world-searchable for socket access)
and PROWLD_DB_DIR stays 0755 (world-readable for tooling).
unit.c:
- Add env_key_valid(): reject environment keys that are empty or
contain characters outside [A-Za-z0-9_], preventing embedding of
NUL bytes or shell metacharacters into the process environment.
- Expand envbuf to PROWL_PATH_MAX and check snprintf return value;
skip and warn if key+value would be truncated.
prowlctl.c:
- Document that MSG_MAX must match IPC_MSG_MAX in prowld.h to keep
protocol framing bounds consistent.
prowld.h:
- Add comment mandating write->fsync->rename atomic-write pattern
for all future persistent state files (timer state etc.).
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: prevent sensitive data from reaching syslog
unit.c:
- Cap ucl_parser_get_error() output at 128 chars when logging unit file
parse failures. UCL error strings can include surrounding token text
from the file being parsed; an environment= block containing a secret
(API key, password) could otherwise appear verbatim in syslog.
- Add explicit comment near env processing: env VALUES must never be
logged; only key names are safe to include in diagnostic messages.
ipc.c:
- Downgrade IPC JSON parse error from LOG_WARNING to LOG_DEBUG.
A misbehaving or adversarial client can trigger this path repeatedly,
flooding syslog at WARNING level. Also cap the error string at 128
chars to limit how much of the IPC payload context can reach the log.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* update
* prowld: implement prowld.conf parsing via libucl
Replace the load_config() stub with a full UCL parser implementation:
- Parse /etc/prowld/prowld.conf on startup and on SIGHUP reload.
- Apply the same file-trust checks used for unit files: config must be
owned by root and not world-writable; otherwise log a warning and
fall back to compiled-in defaults.
- max_concurrent_starts: accepts an integer >= 0 or the string "auto"
(both map to 0, which lets dag.c auto-size to ncpu*2).
- shutdown_timeout: integer > 0 seconds; values <= 0 are rejected with
a warning and the default (300 s) is kept.
- debug: boolean; config file can enable debug logging, but the
command-line -d flag always takes precedence (OR semantics).
Fix ordering bug: g_config.debug was set from argv BEFORE load_config()
which then reset it to false. Moved the argv debug assignment to after
load_config() so -d cannot be silently cleared by the config file.
Add #include <ucl.h> to prowld.c and document the debug key in
prowld.conf.sample.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: implement dependencies, graph, logs commands
- ipc.c: add append_dep_array(), cmd_dependencies(), cmd_graph();
extend job_to_json() with stdout_path/stderr_path; authorize new
read-only verbs; register in dispatch table; bump jbuf to 4096
- prowld.h: add PROWLD_LOG_DIR and PROWLD_JOB_LOG_DIR paths
- prowld.c: create /var/log/prowld and /var/log/prowld/jobs (mode 0750)
in ensure_dirs() at startup
- prowlctl.c: implement do_dependencies(), do_graph() with DOT and text
output, do_logs() with --follow/--lines=N; update usage() and dispatch
- prowlctl.8: document dependencies, graph, and logs commands; add log
path to FILES section
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: implement sd_notify readiness and watchdog (Phase 1)
supervisor.c:
- Add TIMER_* encoding constants for the 2-bit low-field timer ident
scheme; update arm_throttle_timer/arm_stop_timer to use it
- notify_socket_path/open/close: create and manage a per-job Unix
datagram socket at PROWLD_NOTIFY_DIR/<label> registered in kqueue
with the job pointer as udata for O(1) dispatch
- arm/cancel_notify_timeout_timer: one-shot EVFILT_TIMER (TIMER_NOTIFY_TMO)
that kills the job if READY=1 is not received within exit_timeout seconds
- arm/cancel_watchdog_timer: recurring EVFILT_TIMER (TIMER_WATCHDOG) that
calls supervisor_handle_watchdog every watchdog_sec seconds
- supervisor_start: open notify socket before fork; arm notify timeout
instead of throttle timer for NOTIFY_NOTIFY jobs; arm watchdog if
watchdog_sec > 0; close notify fd in child before exec
- supervisor_stop: cancel watchdog timer before sending SIGTERM
- supervisor_reap: cancel watchdog, notify timeout, and close notify
socket on child exit
- child_setup_and_exec: inject NOTIFY_SOCKET=<path> when notify_type is
NOTIFY_NOTIFY; inject WATCHDOG_USEC/WATCHDOG_PID when watchdog_sec > 0;
always use execve when injected vars are present
- supervisor_handle_notify: recvfrom the notify datagram socket; parse
KEY=value\n lines; handle READY=1 (transition to running, cancel
timeout timer), STATUS, MAINPID (re-register EVFILT_PROC watcher),
WATCHDOG=1 (reset ping timestamp), RELOADING, STOPPING, ERRNO,
EXTEND_TIMEOUT_USEC; silently ignore unknown keys
- supervisor_handle_watchdog: kill job via supervisor_stop if time since
last WATCHDOG=1 ping exceeds watchdog_sec
- supervisor_handle_notify_timeout: kill job with SIGKILL; reap path
applies keep_alive policy normally
prowld.c:
- handle_timer_event: switch on 2-bit kind field; add cases for
TIMER_WATCHDOG and TIMER_NOTIFY_TMO
- main_loop EVFILT_READ: if kev->udata != NULL route to
supervisor_handle_notify(udata) instead of ipc_read_client
prowld.h:
- Add watchdog_timer_active and watchdog_last_ping fields to job_t
- Add prototypes for supervisor_handle_notify/watchdog/notify_timeout
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: implement socket activation (§9)
Bind and listen on configured sockets at job load time. On the first
incoming connection the daemon is fork+exec'd with the bound socket fds
passed as fd 3, 4, 5, ... via the LISTEN_FDS / LISTEN_PID /
LISTEN_FDNAMES protocol (systemd-compatible).
- socket_activation.c: new file — socket_bind_one, kqueue add/del,
socket_bind_all (with rollback), socket_handle_activation,
socket_rearm, socket_close_all
- supervisor.c: supervisor_socket_activate() fork+execs the daemon;
child_setup_and_exec() safely dups socket fds to positions 3+ and
injects LISTEN_* env vars; supervisor_reap() re-arms sockets after
socket-activated daemon exits
- dag.c: dag_schedule_ready() calls socket_bind_all for socket jobs
instead of supervisor_start; marks job RUNNING so dependents proceed
- prowld.c: EVFILT_READ dispatch checks bit 0 of udata to distinguish
socket activation fds (prowl_socket_t*|1) from notify fds (job_t*)
- prowld.h: forward typedefs, prowl_socket_t struct, socket fields in
job_t, function prototypes
- unit.c: parse sockets block from unit files (AF_UNIX/INET/INET6)
- job.c: initialise socket fd and back-pointer in job_alloc()
- Makefile: add socket_activation.c to SRCS
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: add rc.d startup script
Adds /etc/rc.d/prowld so prowld(8) can be started by the traditional
BSD rc system after init. The script runs after FILESYSTEMS and
NETWORKING are up, before SERVERS, so prowld is ready to manage
services in the SERVERS run level.
Set prowld_enable="YES" in rc.conf to enable. SIGHUP support is
wired to the reload command via the pidfile.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: add /var/db, /var/log, /var/run directories to mtree
Adds the six directories created by prowld(8) at runtime to
BSD.var.dist so they are created at installworld time:
/var/db/prowld (0755) — persistent state
/var/db/prowld/masked.d (0700) — mask symlinks (root-only)
/var/log/prowld (0750) — daemon log files
/var/log/prowld/jobs (0750) — per-job log files
/var/run/prowld (0755) — runtime sockets and pidfile
/var/run/prowld/notify (0700) — sd_notify sockets (root-only)
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: support read-only media and early/PID-1 operation
Three practical fixes for operation on CD-ROM, read-only USB, or any
environment where /var is not yet writable at startup:
1. Runtime path globals (g_run_dir, g_notify_dir, g_sock_path, …)
Replace all compile-time PROWLD_* macro references in prowld.c,
ipc.c, supervisor.c and job.c with mutable globals defaulting to
the same paths. setup_runtime_paths(base) rebuilds all nine paths
from a single base directory.
2. Read-only fallback in ensure_dirs()
If the primary run directory (/var/run/prowld) cannot be created
(EROFS, EACCES, missing parent), prowld automatically falls back to
/tmp/prowld and rebuilds all runtime paths under that tmpfs tree.
The -s <dir> flag allows an explicit override at startup.
Non-critical directory failures (db, log) are logged as warnings
rather than aborting.
3. PID 1 / init support
If getpid() == 1 (kernel exec'd prowld as init), daemon() is skipped
to avoid orphaning the child and panicking the kernel. Debug/console
output is forced on. After orderly shutdown, reboot(RB_HALT) is
called instead of exit(0) since PID 1 must never return.
4. IPC non-fatal
ipc_init() failure is downgraded from fatal to a warning. prowld
continues managing services; prowlctl commands are unavailable until
the socket directory becomes writable (e.g. after varmfs mounts).
5. Generated unit dir
startup_load() and reload_config() now also scan g_generated_dir so
dynamically generated units are loaded regardless of where the
runtime tree lives.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: fix const-discarding warning on ucl_object_unref
ucl_object_unref() takes ucl_object_t * (non-const) since it mutates
the reference count. Split the combined declaration so root is
non-const and obj (lookup-only) stays const.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: rename shadowing variable s → sock in unit.c socket parser
The inner prowl_socket_t *s in the sockets block loop shadowed the
outer const char *s used by the string value parser above it.
-Wshadow (promoted to error at WARNS=6) caught this at compile time.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: move TIMER_* defines and timer_ident before first use
arm_throttle_timer and arm_stop_timer called timer_ident() and
referenced TIMER_THROTTLE/TIMER_STOP before their definitions,
causing implicit-function-declaration errors under -Werror.
Move the macro block and the two static helpers above the first caller.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
* prowld: remove unused timer_job helper from supervisor.c
prowld.c extracts the job pointer from the timer ident inline;
timer_job() was never called, triggering -Wunused-function under -Werror.
AI-Assisted-by: Claude Sonnet 4.6
Signed-off-by: Lucas Holt <luke at foolishgames.com>
---------
Signed-off-by: Lucas Holt <luke at foolishgames.com>
To unsubscribe from these emails, change your notification settings at https://github.com/MidnightBSD/src/settings/notifications
More information about the Midnightbsd-cvs
mailing list