[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