From: Adrian Vovk Date: Tue, 13 Feb 2024 20:09:54 +0000 (-0500) Subject: fd-util: Expose helper to pack fds into 3,4,5,... X-Git-Tag: v256-rc1~817^2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=85f660d46bf73d6b2853fc19acddf3e099a36d03;p=thirdparty%2Fsystemd.git fd-util: Expose helper to pack fds into 3,4,5,... This is useful for situations where an array of FDs is to be passed into a child process (i.e. by passing it through safe_fork). This function can be called in the child (before calling exec) to pack the FDs to all be next to each-other starting from SD_LISTEN_FDS_START (i.e. 3) --- diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index d2ff456cc7b..fa78284100f 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -464,6 +464,53 @@ int close_all_fds(const int except[], size_t n_except) { return r; } +int pack_fds(int fds[], size_t n_fds) { + if (n_fds <= 0) + return 0; + + /* Shifts around the fds in the provided array such that they + * all end up packed next to each-other, in order, starting + * from SD_LISTEN_FDS_START. This must be called after close_all_fds(); + * it is likely to freeze up otherwise. You should probably use safe_fork_full + * with FORK_CLOSE_ALL_FDS|FORK_PACK_FDS set, to ensure that this is done correctly. + * The fds array is modified in place with the new FD numbers. */ + + assert(fds); + + for (int start = 0;;) { + int restart_from = -1; + + for (int i = start; i < (int) n_fds; i++) { + int nfd; + + /* Already at right index? */ + if (fds[i] == i + 3) + continue; + + nfd = fcntl(fds[i], F_DUPFD, i + 3); + if (nfd < 0) + return -errno; + + safe_close(fds[i]); + fds[i] = nfd; + + /* Hmm, the fd we wanted isn't free? Then + * let's remember that and try again from here */ + if (nfd != i + 3 && restart_from < 0) + restart_from = i; + } + + if (restart_from < 0) + break; + + start = restart_from; + } + + assert(fds[0] == 3); + + return 0; +} + int same_fd(int a, int b) { struct stat sta, stb; pid_t pid; diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 183266513ac..3691f33ffdb 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -77,6 +77,8 @@ int get_max_fd(void); int close_all_fds(const int except[], size_t n_except); int close_all_fds_without_malloc(const int except[], size_t n_except); +int pack_fds(int fds[], size_t n); + int same_fd(int a, int b); void cmsg_close_all(struct msghdr *mh); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 002aea9e8aa..fb8f4ef06ea 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -1468,7 +1468,7 @@ static int fork_flags_to_signal(ForkFlags flags) { int safe_fork_full( const char *name, const int stdio_fds[3], - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid) { @@ -1697,6 +1697,19 @@ int safe_fork_full( } } + if (flags & FORK_PACK_FDS) { + /* FORK_CLOSE_ALL_FDS ensures that except_fds are the only FDs >= 3 that are + * open, this is including the log. This is required by pack_fds, which will + * get stuck in an infinite loop of any FDs other than except_fds are open. */ + assert(FLAGS_SET(flags, FORK_CLOSE_ALL_FDS)); + + r = pack_fds(except_fds, n_except_fds); + if (r < 0) { + log_full_errno(prio, r, "Failed to pack file descriptors: %m"); + _exit(EXIT_FAILURE); + } + } + if (flags & FORK_CLOEXEC_OFF) { r = fd_cloexec_many(except_fds, n_except_fds, false); if (r < 0) { @@ -1736,7 +1749,7 @@ int safe_fork_full( int pidref_safe_fork_full( const char *name, const int stdio_fds[3], - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, PidRef *ret_pid) { @@ -1760,7 +1773,7 @@ int pidref_safe_fork_full( int namespace_fork( const char *outer_name, const char *inner_name, - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 9ca67533349..83084028478 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -185,12 +185,13 @@ typedef enum ForkFlags { FORK_KEEP_NOTIFY_SOCKET = 1 << 17, /* Unless this specified, $NOTIFY_SOCKET will be unset. */ FORK_DETACH = 1 << 18, /* Double fork if needed to ensure PID1/subreaper is parent */ FORK_NEW_NETNS = 1 << 19, /* Run child in its own network namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_PACK_FDS = 1 << 20, /* Rearrange the passed FDs to be FD 3,4,5,etc. Updates the array in place (combine with FORK_CLOSE_ALL_FDS!) */ } ForkFlags; int safe_fork_full( const char *name, const int stdio_fds[3], - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid); @@ -202,7 +203,7 @@ static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) { int pidref_safe_fork_full( const char *name, const int stdio_fds[3], - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, PidRef *ret_pid); @@ -211,7 +212,18 @@ static inline int pidref_safe_fork(const char *name, ForkFlags flags, PidRef *re return pidref_safe_fork_full(name, NULL, NULL, 0, flags, ret_pid); } -int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid); +int namespace_fork( + const char *outer_name, + const char *inner_name, + int except_fds[], + size_t n_except_fds, + ForkFlags flags, + int pidns_fd, + int mntns_fd, + int netns_fd, + int userns_fd, + int root_fd, + pid_t *ret_pid); int set_oom_score_adjust(int value); int get_oom_score_adjust(int *ret); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index c012a359a0b..023ceef699e 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -66,46 +66,6 @@ #define SNDBUF_SIZE (8*1024*1024) -static int shift_fds(int fds[], size_t n_fds) { - if (n_fds <= 0) - return 0; - - /* Modifies the fds array! (sorts it) */ - - assert(fds); - - for (int start = 0;;) { - int restart_from = -1; - - for (int i = start; i < (int) n_fds; i++) { - int nfd; - - /* Already at right index? */ - if (fds[i] == i+3) - continue; - - nfd = fcntl(fds[i], F_DUPFD, i + 3); - if (nfd < 0) - return -errno; - - safe_close(fds[i]); - fds[i] = nfd; - - /* Hmm, the fd we wanted isn't free? Then - * let's remember that and try again from here */ - if (nfd != i+3 && restart_from < 0) - restart_from = i; - } - - if (restart_from < 0) - break; - - start = restart_from; - } - - return 0; -} - static int flag_fds( const int fds[], size_t n_socket_fds, @@ -4900,7 +4860,7 @@ int exec_invoke( r = close_all_fds(keep_fds, n_keep_fds); if (r >= 0) - r = shift_fds(params->fds, n_fds); + r = pack_fds(params->fds, n_fds); if (r >= 0) r = flag_fds(params->fds, n_socket_fds, n_fds, context->non_blocking); if (r < 0) { diff --git a/src/shared/exec-util.c b/src/shared/exec-util.c index c27f3a54c1e..1c7b14d98d0 100644 --- a/src/shared/exec-util.c +++ b/src/shared/exec-util.c @@ -539,7 +539,7 @@ int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret r = safe_fork_full(name, NULL, - except, + (int*) except, /* safe_fork_full only changes except if you pass in FORK_PACK_FDS, which we don't */ n_except, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE, ret_pid);