Files
systemd/src/basic/fd-util.h
T
Michael Vogt 2201147451 fd-util: dup regular files on stdout instead of reopening them
ptyfwd and varlinkctl reopen stdin/stdout to set O_NONBLOCK on
the then private open file description. The issue with this
code is that it does not work for the following scenario:

   { echo header; systemd-run --pty echo hello; echo trailer; } > out

Because the shell's file offset is not advanced (only the one of
our private open file description is) this ends up with just
"header" and "trailer" in the "out" file.

So we add a new fd_dup_or_reopen_stdio_nonblock() that simply dups
regular files and block devices on stdout, which shares the file
position (and O_APPEND) with the shell. Those never block, so we
don't need O_NONBLOCK on them. Everything else, and stdin in
particular, is still reopened privately via the existing
fd_reopen_propagate_append_and_position(): we read ahead from stdin
whether or not the child consumes the data, so a shared offset would
eat the input the shell still expects to read itself, as in

   while read -r l; do systemd-run --pty ...; done < list

This also consolidates the flags O_CLOEXEC|O_NOCTTY|O_NONBLOCK into
the helper instead of passing them from every caller. As all callers
now go through fd_dup_or_reopen_stdio_nonblock(),
fd_reopen_propagate_append_and_position() becomes private to
fd-util.c.

This also adds a unit test for the helper and integration tests for
systemd-run --pty and varlinkctl call --upgrade with a regular file
on stdout, and for systemd-run --pty inside a shell read loop.

Follow-up for a33034e714

Addresses:
https://github.com/systemd/systemd/pull/44374#discussion_r4364836266
(cherry picked from commit 34d8764144)
(cherry picked from commit 4257987646)
2026-11-05 14:48:10 +00:00

224 lines
7.9 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include "basic-forward.h"
/* maximum length of fdname */
#define FDNAME_MAX 255
/* Make sure we can distinguish fd 0 and NULL */
#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1)
#define PTR_TO_FD(p) (PTR_TO_INT(p)-1)
/* Useful helpers for initializing pipe(), socketpair() or stdio fd arrays */
#define EBADF_PAIR { -EBADF, -EBADF }
#define EBADF_TRIPLET { -EBADF, -EBADF, -EBADF }
/* So O_LARGEFILE is generally implied by glibc, and defined to zero hence, because we only build in LFS
* mode. However, when invoking fcntl(F_GETFL) the flag is ORed into the result anyway — glibc does not mask
* it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves.
*
* The precise definition is arch specific, so we use the values defined in the kernel (note that some
* are hexa and others are octal; duplicated as-is from the kernel definitions):
* - alpha, arm, arm64, m68k, mips, parisc, powerpc, sparc: each has a specific value;
* - others: they use the "generic" value (defined in include/uapi/asm-generic/fcntl.h) */
#if O_LARGEFILE != 0
# define RAW_O_LARGEFILE O_LARGEFILE
#else
# if defined(__alpha__) || defined(__arm__) || defined(__aarch64__) || defined(__m68k__)
# define RAW_O_LARGEFILE 0400000
# elif defined(__mips__)
# define RAW_O_LARGEFILE 0x2000
# elif defined(__parisc__) || defined(__hppa__)
# define RAW_O_LARGEFILE 000004000
# elif defined(__powerpc__)
# define RAW_O_LARGEFILE 0200000
# elif defined(__sparc__)
# define RAW_O_LARGEFILE 0x40000
# else
# define RAW_O_LARGEFILE 00100000
# endif
#endif
/* On musl, O_ACCMODE is defined as (03|O_SEARCH), unlike glibc which defines it as
* (O_RDONLY|O_WRONLY|O_RDWR). Additionally, O_SEARCH is simply defined as O_PATH. This changes the behaviour
* of O_ACCMODE in certain situations, which we don't want. This definition is copied from glibc and works
* around the problems with musl's definition. */
#define O_ACCMODE_STRICT (O_RDONLY|O_WRONLY|O_RDWR)
/* The default, minimum, and maximum values of /proc/sys/fs/nr_open. See kernel's fs/file.c.
* These values have been unchanged since kernel-2.6.26:
* https://github.com/torvalds/linux/commit/eceea0b3df05ed262ae32e0c6340cc7a3626632d */
#define NR_OPEN_DEFAULT ((unsigned) (1024 * 1024))
#define NR_OPEN_MINIMUM ((unsigned) (sizeof(long) * 8))
#define NR_OPEN_MAXIMUM ((unsigned) (CONST_MIN((size_t) INT_MAX, SIZE_MAX / __SIZEOF_POINTER__) & ~(sizeof(long) * 8 - 1)))
/* A special fd that can be passed in various helpers instead of an fd indicating the root dir. Inspired by,
* and an alternative to AT_FDCWD. We use specific negative value that is outside of the negative errno
* range, to avoid any potential ambiguities. */
#define XAT_FDROOT -8192
assert_cc(XAT_FDROOT != AT_FDCWD);
assert_cc(XAT_FDROOT < -ERRNO_MAX);
/* Checks whether the specified fd is acceptable as a *at() directory fd that supports the two "wildcard"
* values: it's either a regular, valid fd (i.e. >= 0), or one of the special AT_FDCWD/XAT_FDROOT values. */
static inline bool wildcard_fd_is_valid(int fd) {
return fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT);
}
int close_nointr(int fd);
int safe_close(int fd);
void safe_close_pair(int p[static 2]);
static inline int safe_close_above_stdio(int fd) {
if (fd < 3) /* Don't close stdin/stdout/stderr, but still invalidate the fd by returning -EBADF. */
return -EBADF;
return safe_close(fd);
}
static inline void* close_fd_ptr(void *p) {
safe_close(PTR_TO_FD(p));
return NULL;
}
void close_many(const int fds[], size_t n_fds);
void close_many_unset(int fds[], size_t n_fds);
void close_many_and_free(int *fds, size_t n_fds);
int fclose_nointr(FILE *f);
FILE* safe_fclose(FILE *f);
DIR* safe_closedir(DIR *d);
static inline void closep(int *fd) {
safe_close(*fd);
}
static inline void close_pairp(int (*p)[2]) {
safe_close_pair(*p);
}
static inline void fclosep(FILE **f) {
safe_fclose(*f);
}
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(FILE*, pclose, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL);
#define _cleanup_close_ _cleanup_(closep)
#define _cleanup_fclose_ _cleanup_(fclosep)
#define _cleanup_pclose_ _cleanup_(pclosep)
#define _cleanup_closedir_ _cleanup_(closedirp)
#define _cleanup_close_pair_ _cleanup_(close_pairp)
int fd_nonblock(int fd, bool nonblock);
int stdio_disable_nonblock(void);
void nonblock_resetp(int *fd);
int fd_cloexec(int fd, bool cloexec);
int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec);
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 close_all_fds_frugal(const int except[], size_t n_except);
int pack_fds(int fds[], size_t n);
int fd_validate(int fd);
int same_fd(int a, int b);
bool fdname_is_valid(const char *s);
int fd_get_path(int fd, char **ret);
int move_fd(int from, int to, int cloexec);
int fd_move_above_stdio(int fd);
int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd);
static inline int make_null_stdio(void) {
return rearrange_stdio(-EBADF, -EBADF, -EBADF);
}
/* Like TAKE_PTR() but for file descriptors, resetting them to -EBADF */
#define TAKE_FD(fd) TAKE_GENERIC(fd, int, -EBADF)
/* Like free_and_replace(), but for file descriptors */
#define close_and_replace(a, b) \
({ \
int *_fdp_ = &(a); \
safe_close(*_fdp_); \
*_fdp_ = TAKE_FD(b); \
0; \
})
int fd_reopen(int fd, int flags);
int fd_dup_or_reopen_stdio_nonblock(int fd, int mode);
int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd);
int fd_is_opath(int fd);
int fd_vet_accmode(int fd, int mode);
int fd_is_writable(int fd);
int fd_verify_safe_flags_full(int fd, int extra_flags);
static inline int fd_verify_safe_flags(int fd) {
return fd_verify_safe_flags_full(fd, 0);
}
unsigned read_nr_open(void);
int fd_get_diskseq(int fd, uint64_t *ret);
int path_is_root_at(int dir_fd, const char *path);
static inline int path_is_root(const char *path) {
return path_is_root_at(AT_FDCWD, path);
}
static inline int dir_fd_is_root(int dir_fd) {
return dir_fd == XAT_FDROOT ? true : path_is_root_at(dir_fd, NULL);
}
static inline int dir_fd_is_root_or_cwd(int dir_fd) {
return IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT) ? true : path_is_root_at(dir_fd, NULL);
}
int fds_inode_and_mount_same(int fd1, int fd2);
int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer);
/* The maximum length a buffer for a /proc/self/fd/<fd> path needs */
#define PROC_FD_PATH_MAX \
(STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int))
char* format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int fd);
#define FORMAT_PROC_FD_PATH(fd) \
format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd))
/* The maximum length a buffer for a /proc/<pid>/fd/<fd> path needs */
#define PROC_PID_FD_PATH_MAX \
(STRLEN("/proc//fd/") + DECIMAL_STR_MAX(pid_t) + DECIMAL_STR_MAX(int))
char* format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd);
/* Kinda the same as FORMAT_PROC_FD_PATH(), but goes by PID rather than "self" symlink */
#define FORMAT_PROC_PID_FD_PATH(pid, fd) \
format_proc_pid_fd_path((char[PROC_PID_FD_PATH_MAX]) {}, (pid), (fd))
int proc_fd_enoent_errno(void);
const char* accmode_to_string(int flags);
/* Like ASSERT_PTR, but for fds */
#define ASSERT_FD(fd) \
({ \
int _fd_ = (fd); \
assert(_fd_ >= 0); \
_fd_; \
})