_dbus_ensure_standard_fds: new function to ensure std* fds are open
authorSimon McVittie <smcv@debian.org>
Thu, 21 Jul 2016 07:23:12 +0000 (08:23 +0100)
committerSimon McVittie <simon.mcvittie@collabora.co.uk>
Mon, 25 Jul 2016 10:26:01 +0000 (11:26 +0100)
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>
dbus/dbus-sysdeps-unix.c
dbus/dbus-sysdeps-unix.h

index ce8cbf7..700b831 100644 (file)
 
 #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);
 
index 72f3ecf..279cae2 100644 (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