_dbus_ensure_standard_fds: new function to ensure std* fds are open

This function opens stdin, stdout, stderr pointing to /dev/null
if they aren't already open. Optionally, it can also replace
whatever is available on those fds with /dev/null.

To allow for use in contexts where only async-signal-safe functions
should be used, such as between fork() and a following exec(),
this function does not use conventional libdbus error handling
(which would require malloc). Instead, it sets errno and returns
an explanatory string.

Bug: https://bugs.freedesktop.org/show_bug.cgi?id=97008
Signed-off-by: Simon McVittie <smcv@debian.org>
Reviewed-by: Thiago Macieira <thiago@kde.org>
This commit is contained in:
Simon McVittie 2016-07-21 08:23:12 +01:00 committed by Simon McVittie
parent e3a14eb4fd
commit 69123a6bd2
2 changed files with 86 additions and 0 deletions

View file

@ -134,6 +134,81 @@
#endif /* Solaris */
/**
* Ensure that the standard file descriptors stdin, stdout and stderr
* are open, by opening /dev/null if necessary.
*
* This function does not use DBusError, to avoid calling malloc(), so
* that it can be used in contexts where an async-signal-safe function
* is required (for example after fork()). Instead, on failure it sets
* errno and returns something like "Failed to open /dev/null" in
* *error_str_p. Callers are expected to combine *error_str_p
* with _dbus_strerror (errno) to get a full error report.
*
* This function can only be called while single-threaded: either during
* startup of an executable, or after fork().
*/
dbus_bool_t
_dbus_ensure_standard_fds (DBusEnsureStandardFdsFlags flags,
const char **error_str_p)
{
static int const relevant_flag[] = { DBUS_FORCE_STDIN_NULL,
DBUS_FORCE_STDOUT_NULL,
DBUS_FORCE_STDERR_NULL };
/* Should always get replaced with the real error before use */
const char *error_str = "Failed mysteriously";
int devnull = -1;
int saved_errno;
/* This function relies on the standard fds having their POSIX values. */
_DBUS_STATIC_ASSERT (STDIN_FILENO == 0);
_DBUS_STATIC_ASSERT (STDOUT_FILENO == 1);
_DBUS_STATIC_ASSERT (STDERR_FILENO == 2);
int i;
for (i = STDIN_FILENO; i <= STDERR_FILENO; i++)
{
/* Because we rely on being single-threaded, and we want the
* standard fds to not be close-on-exec, we don't set it
* close-on-exec. */
if (devnull < i)
devnull = open ("/dev/null", O_RDWR);
if (devnull < 0)
{
error_str = "Failed to open /dev/null";
goto out;
}
/* We already opened all fds < i, so the only way this assertion
* could fail is if another thread closed one, and we document
* this function as not safe for multi-threading. */
_dbus_assert (devnull >= i);
if (devnull != i && (flags & relevant_flag[i]) != 0)
{
if (dup2 (devnull, i) < 0)
{
error_str = "Failed to dup2 /dev/null onto a standard fd";
goto out;
}
}
}
error_str = NULL;
out:
saved_errno = errno;
if (devnull > STDERR_FILENO)
close (devnull);
if (error_str_p != NULL)
*error_str_p = error_str;
errno = saved_errno;
return (error_str == NULL);
}
static dbus_bool_t _dbus_set_fd_nonblocking (int fd,
DBusError *error);

View file

@ -154,6 +154,17 @@ dbus_bool_t _dbus_append_address_from_socket (DBusSocket fd,
DBUS_PRIVATE_EXPORT
void _dbus_fd_set_close_on_exec (int fd);
typedef enum
{
DBUS_FORCE_STDIN_NULL = (1 << 0),
DBUS_FORCE_STDOUT_NULL = (1 << 1),
DBUS_FORCE_STDERR_NULL = (1 << 2)
} DBusEnsureStandardFdsFlags;
DBUS_PRIVATE_EXPORT
dbus_bool_t _dbus_ensure_standard_fds (DBusEnsureStandardFdsFlags flags,
const char **error_str_p);
/** @} */
DBUS_END_DECLS