--- /dev/null
+// 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;
+}
--- /dev/null
+/* 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__ */