| // 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; |
| } |