]> git-server-git.apps.pok.os.sepia.ceph.com Git - xfsprogs-dev.git/commitdiff
libfrog: add support code for starting systemd services programmatically
authorDarrick J. Wong <djwong@kernel.org>
Sun, 22 Feb 2026 22:41:12 +0000 (14:41 -0800)
committerDarrick J. Wong <djwong@kernel.org>
Thu, 9 Apr 2026 22:30:16 +0000 (15:30 -0700)
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" <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
configure.ac
include/builddefs.in
libfrog/Makefile
libfrog/systemd.c [new file with mode: 0644]
libfrog/systemd.h [new file with mode: 0644]
m4/package_libcdev.m4

index 8092b8656ef94b1688a3b3d0af8571867b6d9a4a..a9febabc71cfc7a305944a94111346102ff991f1 100644 (file)
@@ -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
index b38a099b7d525ab8fdc8aa17743831fba15e0de3..4a2cb757c0bdb3c24727812838b8b3afb4e133b5 100644 (file)
@@ -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
index bccd9289e5dd799e00a428f344b2e0e82d663f3d..89a0332ae85372f320ed9e5e8ae85ee79c3aceaa 100644 (file)
@@ -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 (file)
index 0000000..5bff45b
--- /dev/null
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Oracle.  All Rights Reserved.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ */
+#include "xfs.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#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 (file)
index 0000000..4f414bc
--- /dev/null
@@ -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__ */
index c5538c30d2518a8871c19464559696ba7b7c1ea3..b3d87229d3367a6ee38d1239ad4544030804bd9f 100644 (file)
@@ -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 <unistd.h>
+#include <linux/close_range.h>
+  ]], [[
+close_range(0, 0, 0);
+  ]])
+    ], have_close_range=yes
+       AC_MSG_RESULT(yes),
+       AC_MSG_RESULT(no))
+    AC_SUBST(have_close_range)
+  ])