From: Darrick J. Wong Date: Sun, 22 Feb 2026 22:41:12 +0000 (-0800) Subject: libfrog: add support code for starting systemd services programmatically X-Git-Tag: v7.0.0~54 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=6839b8314a49e72dbf7767665783ef292224fc35;p=xfsprogs-dev.git libfrog: add support code for starting systemd services programmatically Add some simple routines for computing the name of systemd service instances and starting systemd services. These will be used by the xfs_healer_start service to start per-filesystem xfs_healer service instances. Note that we run systemd helper programs as subprocesses for a couple of reasons. First, the path-escaping functionality is not a part of any library-accessible API, which means it can only be accessed via systemd-escape(1). Second, although the service startup functionality can be reached via dbus, doing so would introduce a new library dependency. Systemd is also undergoing a dbus -> varlink RPC transition so we avoid that mess by calling the cli systemctl(1) program. Signed-off-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig --- diff --git a/configure.ac b/configure.ac index 8092b865..a9febabc 100644 --- a/configure.ac +++ b/configure.ac @@ -182,6 +182,7 @@ AC_CONFIG_UDEV_RULE_DIR AC_HAVE_BLKID_TOPO AC_HAVE_TRIVIAL_AUTO_VAR_INIT AC_STRERROR_R_RETURNS_STRING +AC_HAVE_CLOSE_RANGE if test "$enable_ubsan" = "yes" || test "$enable_ubsan" = "probe"; then AC_PACKAGE_CHECK_UBSAN diff --git a/include/builddefs.in b/include/builddefs.in index b38a099b..4a2cb757 100644 --- a/include/builddefs.in +++ b/include/builddefs.in @@ -118,6 +118,7 @@ HAVE_UDEV = @have_udev@ UDEV_RULE_DIR = @udev_rule_dir@ HAVE_LIBURCU_ATOMIC64 = @have_liburcu_atomic64@ STRERROR_R_RETURNS_STRING = @strerror_r_returns_string@ +HAVE_CLOSE_RANGE = @have_close_range@ GCCFLAGS = -funsigned-char -fno-strict-aliasing -Wall # -Wbitwise -Wno-transparent-union -Wno-old-initializer -Wno-decl diff --git a/libfrog/Makefile b/libfrog/Makefile index bccd9289..89a0332a 100644 --- a/libfrog/Makefile +++ b/libfrog/Makefile @@ -36,6 +36,7 @@ ptvar.c \ radix-tree.c \ randbytes.c \ scrub.c \ +systemd.c \ util.c \ workqueue.c \ zones.c @@ -70,6 +71,7 @@ radix-tree.h \ randbytes.h \ scrub.h \ statx.h \ +systemd.h \ workqueue.h \ zones.h @@ -90,6 +92,10 @@ ifeq ($(HAVE_GETRANDOM_NONBLOCK),yes) LCFLAGS += -DHAVE_GETRANDOM_NONBLOCK endif +ifeq ($(HAVE_CLOSE_RANGE),yes) +CFLAGS += -DHAVE_CLOSE_RANGE +endif + default: ltdepend $(LTLIBRARY) $(GETTEXT_PY) crc32table.h: gen_crc32table.c crc32defs.h diff --git a/libfrog/systemd.c b/libfrog/systemd.c new file mode 100644 index 00000000..5bff45bb --- /dev/null +++ b/libfrog/systemd.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Oracle. All Rights Reserved. + * Author: Darrick J. Wong + */ +#include "xfs.h" +#include +#include +#include +#include + +#include "libfrog/systemd.h" + +/* Close all fds except for the three standard ones. */ +static void +close_fds(void) +{ + int max_fd = sysconf(_SC_OPEN_MAX); + int fd; + + if (max_fd < 1) + max_fd = 1024; + +#ifdef HAVE_CLOSE_RANGE + if (close_range(STDERR_FILENO + 1, max_fd, 0) == 0) + return; +#endif + + for (fd = STDERR_FILENO + 1; fd < max_fd; fd++) + close(fd); +} + +/* + * Compute the systemd instance unit name for a given path. + * + * The escaping logic is implemented directly in systemctl so there's no + * library or dbus service that we can call. + */ +int +systemd_path_instance_unit_name( + const char *unit_template, + const char *path, + char *unitname, + size_t unitnamelen) +{ + size_t i; + ssize_t bytes; + pid_t child_pid; + int pipe_fds[2]; + int child_status; + int ret; + + ret = pipe(pipe_fds); + if (ret) + return -1; + + child_pid = fork(); + if (child_pid < 0) + return -1; + + if (!child_pid) { + /* child process */ + char *argv[] = { + "systemd-escape", + "--template", + (char *)unit_template, + "--path", + (char *)path, + NULL, + }; + + ret = dup2(pipe_fds[1], STDOUT_FILENO); + if (ret < 0) { + perror(path); + goto fail; + } + + close_fds(); + + ret = execvp("systemd-escape", argv); + if (ret) + perror(path); + +fail: + exit(EXIT_FAILURE); + } + + /* + * Close our connection to stdin so that the read won't hang if the + * child exits without writing anything to stdout. + */ + close(pipe_fds[1]); + bytes = read(pipe_fds[0], unitname, unitnamelen - 1); + close(pipe_fds[0]); + + waitpid(child_pid, &child_status, 0); + if (!WIFEXITED(child_status) || WEXITSTATUS(child_status) != 0) { + errno = ENODATA; + return -1; + } + + if (bytes < 0) { + errno = EIO; + return -1; + } + + /* Terminate string at first newline or end of buffer. */ + for (i = 0; i < bytes; i++) { + if (unitname[i] == '\n') { + unitname[i] = 0; + break; + } + } + if (i == bytes) { + /* + * Didn't find a newline to zero out? That means the unit name + * is longer than the bufffer. + */ + errno = E2BIG; + return -1; + } + + return 0; +} + +static const char *systemd_unit_manage_string(enum systemd_unit_manage how) +{ + switch (how) { + case UM_STOP: + return "stop"; + case UM_START: + return "start"; + case UM_RESTART: + return "restart"; + } + + /* shut up gcc */ + return NULL; +} + +/* + * Start/stop/restart a systemd unit and let it run in the background. + * + * systemctl start wraps a lot of logic around starting a unit, so it's less + * work for xfsprogs to invoke systemctl instead of calling through dbus. + */ +int +systemd_manage_unit( + enum systemd_unit_manage how, + const char *unitname) +{ + pid_t child_pid; + int child_status; + int ret; + + child_pid = fork(); + if (child_pid < 0) + return -1; + + if (!child_pid) { + /* child starts the process */ + char *argv[] = { + "systemctl", + (char *)systemd_unit_manage_string(how), + "--no-block", + (char *)unitname, + NULL, + }; + + close_fds(); + + ret = execvp("systemctl", argv); + if (ret) + perror("systemctl"); + + exit(EXIT_FAILURE); + } + + /* parent waits for process */ + waitpid(child_pid, &child_status, 0); + + /* systemctl (stop/start/restart) --no-block should return quickly */ + if (WIFEXITED(child_status) && WEXITSTATUS(child_status) == 0) + return 0; + + errno = ENOMEM; + return -1; +} diff --git a/libfrog/systemd.h b/libfrog/systemd.h new file mode 100644 index 00000000..4f414bc3 --- /dev/null +++ b/libfrog/systemd.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2026 Oracle. All rights reserved. + * All Rights Reserved. + */ +#ifndef __LIBFROG_SYSTEMD_H__ +#define __LIBFROG_SYSTEMD_H__ + +int systemd_path_instance_unit_name(const char *unit_template, + const char *path, char *unitname, size_t unitnamelen); + +enum systemd_unit_manage { + UM_STOP, + UM_START, + UM_RESTART, +}; + +int systemd_manage_unit(enum systemd_unit_manage how, const char *unitname); + +#endif /* __LIBFROG_SYSTEMD_H__ */ diff --git a/m4/package_libcdev.m4 b/m4/package_libcdev.m4 index c5538c30..b3d87229 100644 --- a/m4/package_libcdev.m4 +++ b/m4/package_libcdev.m4 @@ -347,3 +347,22 @@ puts(strerror_r(0, buf, sizeof(buf))); CFLAGS="$OLD_CFLAGS" AC_SUBST(strerror_r_returns_string) ]) + +# +# Check if close_range exists +# +AC_DEFUN([AC_HAVE_CLOSE_RANGE], + [AC_MSG_CHECKING([for close_range]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[ +#define _GNU_SOURCE +#include +#include + ]], [[ +close_range(0, 0, 0); + ]]) + ], have_close_range=yes + AC_MSG_RESULT(yes), + AC_MSG_RESULT(no)) + AC_SUBST(have_close_range) + ])