bus: Raise file descriptor limit to match configuration
[platform/upstream/dbus.git] / dbus / dbus-sysdeps-util-unix.c
index 981d4e3..02954d5 100644 (file)
@@ -1,4 +1,4 @@
-/* -*- mode: C; c-file-style: "gnu" -*- */
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 /* dbus-sysdeps-util-unix.c Would be in dbus-sysdeps-unix.c, but not used in libdbus
  * 
  * Copyright (C) 2002, 2003, 2004, 2005  Red Hat, Inc.
  * 
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
+
+#include <config.h>
 #include "dbus-sysdeps.h"
+#include "dbus-sysdeps-unix.h"
 #include "dbus-internals.h"
+#include "dbus-pipe.h"
 #include "dbus-protocol.h"
 #include "dbus-string.h"
 #define DBUS_USERDB_INCLUDES_PRIVATE 1
 #include <errno.h>
 #include <fcntl.h>
 #include <sys/stat.h>
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
 #include <grp.h>
 #include <sys/socket.h>
 #include <dirent.h>
 #include <sys/un.h>
+#include <syslog.h>
+
+#ifdef HAVE_SYS_SYSLIMITS_H
+#include <sys/syslimits.h>
+#endif
 
 #ifndef O_BINARY
 #define O_BINARY 0
  * @{
  */
 
+
 /**
  * Does the chdir, fork, setsid, etc. to become a daemon process.
  *
  * @param pidfile #NULL, or pidfile to create
- * @param print_pid_fd file descriptor to print daemon's pid to, or -1 for none
+ * @param print_pid_pipe pipe to print daemon's pid to, or -1 for none
  * @param error return location for errors
+ * @param keep_umask #TRUE to keep the original umask
  * @returns #FALSE on failure
  */
 dbus_bool_t
 _dbus_become_daemon (const DBusString *pidfile,
-                    int               print_pid_fd,
-                     DBusError        *error)
+                     DBusPipe         *print_pid_pipe,
+                     DBusError        *error,
+                     dbus_bool_t       keep_umask)
 {
   const char *s;
   pid_t child_pid;
@@ -110,68 +125,33 @@ _dbus_become_daemon (const DBusString *pidfile,
             _dbus_verbose ("keeping stderr open due to DBUS_DEBUG_OUTPUT\n");
         }
 
-      /* Get a predictable umask */
-      _dbus_verbose ("setting umask\n");
-      umask (022);
+      if (!keep_umask)
+        {
+          /* Get a predictable umask */
+          _dbus_verbose ("setting umask\n");
+          umask (022);
+        }
+
+      _dbus_verbose ("calling setsid()\n");
+      if (setsid () == -1)
+        _dbus_assert_not_reached ("setsid() failed");
+      
       break;
 
     default:
-      if (pidfile)
+      if (!_dbus_write_pid_to_file_and_pipe (pidfile, print_pid_pipe,
+                                             child_pid, error))
         {
-          _dbus_verbose ("parent writing pid file\n");
-          if (!_dbus_write_pid_file (pidfile,
-                                     child_pid,
-                                     error))
-            {
-              _dbus_verbose ("pid file write failed, killing child\n");
-              kill (child_pid, SIGTERM);
-              return FALSE;
-            }
+          _dbus_verbose ("pid file or pipe write failed: %s\n",
+                         error->message);
+          kill (child_pid, SIGTERM);
+          return FALSE;
         }
 
-      /* Write PID if requested */
-      if (print_pid_fd >= 0)
-       {
-         DBusString pid;
-         int bytes;
-         
-         if (!_dbus_string_init (&pid))
-           {
-             _DBUS_SET_OOM (error);
-              kill (child_pid, SIGTERM);
-             return FALSE;
-           }
-         
-         if (!_dbus_string_append_int (&pid, child_pid) ||
-             !_dbus_string_append (&pid, "\n"))
-           {
-             _dbus_string_free (&pid);
-             _DBUS_SET_OOM (error);
-              kill (child_pid, SIGTERM);
-             return FALSE;
-           }
-         
-         bytes = _dbus_string_get_length (&pid);
-         if (_dbus_write (print_pid_fd, &pid, 0, bytes) != bytes)
-           {
-             dbus_set_error (error, DBUS_ERROR_FAILED,
-                             "Printing message bus PID: %s\n",
-                             _dbus_strerror (errno));
-             _dbus_string_free (&pid);
-              kill (child_pid, SIGTERM);
-             return FALSE;
-           }
-         
-         _dbus_string_free (&pid);
-       }
       _dbus_verbose ("parent exiting\n");
       _exit (0);
       break;
     }
-
-  _dbus_verbose ("calling setsid()\n");
-  if (setsid () == -1)
-    _dbus_assert_not_reached ("setsid() failed");
   
   return TRUE;
 }
@@ -185,7 +165,7 @@ _dbus_become_daemon (const DBusString *pidfile,
  * @param error return location for errors
  * @returns #FALSE on failure
  */
-dbus_bool_t
+static dbus_bool_t
 _dbus_write_pid_file (const DBusString *filename,
                       unsigned long     pid,
                      DBusError        *error)
@@ -210,7 +190,7 @@ _dbus_write_pid_file (const DBusString *filename,
     {
       dbus_set_error (error, _dbus_error_from_errno (errno),
                       "Failed to fdopen fd %d: %s", fd, _dbus_strerror (errno));
-      close (fd);
+      _dbus_close (fd, NULL);
       return FALSE;
     }
   
@@ -235,20 +215,129 @@ _dbus_write_pid_file (const DBusString *filename,
   return TRUE;
 }
 
+/**
+ * Writes the given pid_to_write to a pidfile (if non-NULL) and/or to a
+ * pipe (if non-NULL). Does nothing if pidfile and print_pid_pipe are both
+ * NULL.
+ *
+ * @param pidfile the file to write to or #NULL
+ * @param print_pid_pipe the pipe to write to or #NULL
+ * @param pid_to_write the pid to write out
+ * @param error error on failure
+ * @returns FALSE if error is set
+ */
+dbus_bool_t
+_dbus_write_pid_to_file_and_pipe (const DBusString *pidfile,
+                                  DBusPipe         *print_pid_pipe,
+                                  dbus_pid_t        pid_to_write,
+                                  DBusError        *error)
+{
+  if (pidfile)
+    {
+      _dbus_verbose ("writing pid file %s\n", _dbus_string_get_const_data (pidfile));
+      if (!_dbus_write_pid_file (pidfile,
+                                 pid_to_write,
+                                 error))
+        {
+          _dbus_verbose ("pid file write failed\n");
+          _DBUS_ASSERT_ERROR_IS_SET(error);
+          return FALSE;
+        }
+    }
+  else
+    {
+      _dbus_verbose ("No pid file requested\n");
+    }
+
+  if (print_pid_pipe != NULL && _dbus_pipe_is_valid (print_pid_pipe))
+    {
+      DBusString pid;
+      int bytes;
+
+      _dbus_verbose ("writing our pid to pipe %"PRIuPTR"\n",
+                     print_pid_pipe->fd_or_handle);
+      
+      if (!_dbus_string_init (&pid))
+        {
+          _DBUS_SET_OOM (error);
+          return FALSE;
+        }
+         
+      if (!_dbus_string_append_int (&pid, pid_to_write) ||
+          !_dbus_string_append (&pid, "\n"))
+        {
+          _dbus_string_free (&pid);
+          _DBUS_SET_OOM (error);
+          return FALSE;
+        }
+         
+      bytes = _dbus_string_get_length (&pid);
+      if (_dbus_pipe_write (print_pid_pipe, &pid, 0, bytes, error) != bytes)
+        {
+          /* _dbus_pipe_write sets error only on failure, not short write */
+          if (error != NULL && !dbus_error_is_set(error))
+            {
+              dbus_set_error (error, DBUS_ERROR_FAILED,
+                              "Printing message bus PID: did not write enough bytes\n");
+            }
+          _dbus_string_free (&pid);
+          return FALSE;
+        }
+         
+      _dbus_string_free (&pid);
+    }
+  else
+    {
+      _dbus_verbose ("No pid pipe to write to\n");
+    }
+
+  return TRUE;
+}
+
+/**
+ * Verify that after the fork we can successfully change to this user.
+ *
+ * @param user the username given in the daemon configuration
+ * @returns #TRUE if username is valid
+ */
+dbus_bool_t
+_dbus_verify_daemon_user (const char *user)
+{
+  DBusString u;
+
+  _dbus_string_init_const (&u, user);
+
+  return _dbus_get_user_id_and_primary_group (&u, NULL, NULL);
+}
+
 
+/* The HAVE_LIBAUDIT case lives in selinux.c */
+#ifndef HAVE_LIBAUDIT
 /**
  * Changes the user and group the bus is running as.
  *
- * @param uid the new user ID
- * @param gid the new group ID
+ * @param user the user to become
  * @param error return location for errors
  * @returns #FALSE on failure
  */
 dbus_bool_t
-_dbus_change_identity  (dbus_uid_t     uid,
-                        dbus_gid_t     gid,
-                        DBusError     *error)
+_dbus_change_to_daemon_user  (const char    *user,
+                              DBusError     *error)
 {
+  dbus_uid_t uid;
+  dbus_gid_t gid;
+  DBusString u;
+
+  _dbus_string_init_const (&u, user);
+
+  if (!_dbus_get_user_id_and_primary_group (&u, &uid, &gid))
+    {
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "User '%s' does not appear to exist?",
+                      user);
+      return FALSE;
+    }
+
   /* setgroups() only works if we are a privileged process,
    * so we don't return error on failure; the only possible
    * failure is that we don't have perms to do it.
@@ -259,7 +348,7 @@ _dbus_change_identity  (dbus_uid_t     uid,
   if (setgroups (0, NULL) < 0)
     _dbus_warn ("Failed to drop supplementary groups: %s\n",
                 _dbus_strerror (errno));
-  
+
   /* Set GID first, or the setuid may remove our permission
    * to change the GID
    */
@@ -270,7 +359,7 @@ _dbus_change_identity  (dbus_uid_t     uid,
                       _dbus_strerror (errno));
       return FALSE;
     }
-  
+
   if (setuid (uid) < 0)
     {
       dbus_set_error (error, _dbus_error_from_errno (errno),
@@ -278,9 +367,120 @@ _dbus_change_identity  (dbus_uid_t     uid,
                       _dbus_strerror (errno));
       return FALSE;
     }
-  
+
   return TRUE;
 }
+#endif /* !HAVE_LIBAUDIT */
+
+
+/**
+ * Attempt to ensure that the current process can open
+ * at least @limit file descriptors.
+ *
+ * If @limit is lower than the current, it will not be
+ * lowered.  No error is returned if the request can
+ * not be satisfied.
+ *
+ * @limit Number of file descriptors
+ */
+void
+_dbus_request_file_descriptor_limit (unsigned int limit)
+{
+#ifdef HAVE_SETRLIMIT
+  struct rlimit lim;
+  struct rlimit target_lim;
+  unsigned int current_limit;
+
+  /* No point to doing this practically speaking
+   * if we're not uid 0.  We expect the system
+   * bus to use this before we change UID, and
+   * the session bus takes the Linux default
+   * of 1024 for both cur and max.
+   */
+  if (getuid () != 0)
+    return;
+
+  if (getrlimit (RLIMIT_NOFILE, &lim) < 0)
+    return;
+
+  if (lim.rlim_cur >= limit)
+    return;
+
+  /* Ignore "maximum limit", assume we have the "superuser"
+   * privileges.  On Linux this is CAP_SYS_RESOURCE.
+   */
+  target_lim.rlim_cur = target_lim.rlim_max = limit;
+  /* Also ignore errors; if we fail, we will at least work
+   * up to whatever limit we had, which seems better than
+   * just outright aborting.
+   *
+   * However, in the future we should probably log this so OS builders
+   * have a chance to notice any misconfiguration like dbus-daemon
+   * being started without CAP_SYS_RESOURCE.
+   */
+  setrlimit (RLIMIT_NOFILE, &target_lim);
+#endif
+}
+
+void 
+_dbus_init_system_log (void)
+{
+  openlog ("dbus", LOG_PID, LOG_DAEMON);
+}
+/**
+ * Log a message to the system log file (e.g. syslog on Unix).
+ *
+ * @param severity a severity value
+ * @param msg a printf-style format string
+ * @param args arguments for the format string
+ *
+ */
+void
+_dbus_system_log (DBusSystemLogSeverity severity, const char *msg, ...)
+{
+  va_list args;
+
+  va_start (args, msg);
+
+  _dbus_system_logv (severity, msg, args);
+
+  va_end (args);
+}
+
+/**
+ * Log a message to the system log file (e.g. syslog on Unix).
+ *
+ * @param severity a severity value
+ * @param msg a printf-style format string
+ * @param args arguments for the format string
+ *
+ * If the FATAL severity is given, this function will terminate the program
+ * with an error code.
+ */
+void
+_dbus_system_logv (DBusSystemLogSeverity severity, const char *msg, va_list args)
+{
+  int flags;
+  switch (severity)
+    {
+      case DBUS_SYSTEM_LOG_INFO:
+        flags =  LOG_DAEMON | LOG_NOTICE;
+        break;
+      case DBUS_SYSTEM_LOG_SECURITY:
+        flags = LOG_AUTH | LOG_NOTICE;
+        break;
+      case DBUS_SYSTEM_LOG_FATAL:
+        flags = LOG_DAEMON|LOG_CRIT;
+        break;
+      default:
+        return;
+    }
+
+  vsyslog (flags, msg, args);
+
+  if (severity == DBUS_SYSTEM_LOG_FATAL)
+    exit (1);
+}
 
 /** Installs a UNIX signal handler
  *
@@ -301,35 +501,6 @@ _dbus_set_signal_handler (int               sig,
   sigaction (sig,  &act, NULL);
 }
 
-
-/**
- * Removes a directory; Directory must be empty
- * 
- * @param filename directory filename
- * @param error initialized error object
- * @returns #TRUE on success
- */
-dbus_bool_t
-_dbus_delete_directory (const DBusString *filename,
-                       DBusError        *error)
-{
-  const char *filename_c;
-  
-  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
-
-  filename_c = _dbus_string_get_const_data (filename);
-
-  if (rmdir (filename_c) != 0)
-    {
-      dbus_set_error (error, DBUS_ERROR_FAILED,
-                     "Failed to remove directory %s: %s\n",
-                     filename_c, _dbus_strerror (errno));
-      return FALSE;
-    }
-  
-  return TRUE;
-}
-
 /** Checks if a file exists
 *
 * @param file full path to the file
@@ -495,8 +666,8 @@ _dbus_directory_open (const DBusString *filename,
  * UNIX. If an error occurs, the contents of "filename" are
  * undefined. The error is never set if the function succeeds.
  *
- * @todo 1.0 for thread safety, I think we have to use
- * readdir_r(). (GLib has the same issue, should file a bug.)
+ * This function is not re-entrant, and not necessarily thread-safe.
+ * Only use it for test code or single-threaded utilities.
  *
  * @param iter the iterator
  * @param filename string to be set to the next file in the dir
@@ -509,18 +680,23 @@ _dbus_directory_get_next_file (DBusDirIter      *iter,
                                DBusError        *error)
 {
   struct dirent *ent;
+  int err;
 
   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
-  
+
  again:
   errno = 0;
   ent = readdir (iter->d);
-  if (ent == NULL)
+
+  if (!ent)
     {
-      if (errno != 0)
+      err = errno;
+
+      if (err != 0)
         dbus_set_error (error,
-                        _dbus_error_from_errno (errno),
-                        "%s", _dbus_strerror (errno));
+                        _dbus_error_from_errno (err),
+                        "%s", _dbus_strerror (err));
+
       return FALSE;
     }
   else if (ent->d_name[0] == '.' &&
@@ -537,7 +713,9 @@ _dbus_directory_get_next_file (DBusDirIter      *iter,
           return FALSE;
         }
       else
-        return TRUE;
+        {
+          return TRUE;
+        }
     }
 }
 
@@ -597,31 +775,69 @@ fill_group_info (DBusGroupInfo    *info,
   {
     struct group *g;
     int result;
-    char buf[1024];
+    size_t buflen;
+    char *buf;
     struct group g_str;
+    dbus_bool_t b;
 
-    g = NULL;
-#ifdef HAVE_POSIX_GETPWNAM_R
+    /* retrieve maximum needed size for buf */
+    buflen = sysconf (_SC_GETGR_R_SIZE_MAX);
 
-    if (group_c_str)
-      result = getgrnam_r (group_c_str, &g_str, buf, sizeof (buf),
-                           &g);
-    else
-      result = getgrgid_r (gid, &g_str, buf, sizeof (buf),
-                           &g);
+    /* sysconf actually returns a long, but everything else expects size_t,
+     * so just recast here.
+     * https://bugs.freedesktop.org/show_bug.cgi?id=17061
+     */
+    if ((long) buflen <= 0)
+      buflen = 1024;
+
+    result = -1;
+    while (1)
+      {
+        buf = dbus_malloc (buflen);
+        if (buf == NULL)
+          {
+            dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+            return FALSE;
+          }
+
+        g = NULL;
+#ifdef HAVE_POSIX_GETPWNAM_R
+        if (group_c_str)
+          result = getgrnam_r (group_c_str, &g_str, buf, buflen,
+                               &g);
+        else
+          result = getgrgid_r (gid, &g_str, buf, buflen,
+                               &g);
 #else
-    g = getgrnam_r (group_c_str, &g_str, buf, sizeof (buf));
-    result = 0;
+        g = getgrnam_r (group_c_str, &g_str, buf, buflen);
+        result = 0;
 #endif /* !HAVE_POSIX_GETPWNAM_R */
+        /* Try a bigger buffer if ERANGE was returned:
+           https://bugs.freedesktop.org/show_bug.cgi?id=16727
+        */
+        if (result == ERANGE && buflen < 512 * 1024)
+          {
+            dbus_free (buf);
+            buflen *= 2;
+          }
+        else
+          {
+            break;
+          }
+      }
+
     if (result == 0 && g == &g_str)
       {
-        return fill_user_info_from_group (g, info, error);
+        b = fill_user_info_from_group (g, info, error);
+        dbus_free (buf);
+        return b;
       }
     else
       {
         dbus_set_error (error, _dbus_error_from_errno (errno),
                         "Group %s unknown or failed to look it up\n",
                         group_c_str ? group_c_str : "???");
+        dbus_free (buf);
         return FALSE;
       }
   }
@@ -683,6 +899,98 @@ _dbus_group_info_fill_gid (DBusGroupInfo *info,
   return fill_group_info (info, gid, NULL, error);
 }
 
+/**
+ * Parse a UNIX user from the bus config file. On Windows, this should
+ * simply always fail (just return #FALSE).
+ *
+ * @param username the username text
+ * @param uid_p place to return the uid
+ * @returns #TRUE on success
+ */
+dbus_bool_t
+_dbus_parse_unix_user_from_config (const DBusString  *username,
+                                   dbus_uid_t        *uid_p)
+{
+  return _dbus_get_user_id (username, uid_p);
+
+}
+
+/**
+ * Parse a UNIX group from the bus config file. On Windows, this should
+ * simply always fail (just return #FALSE).
+ *
+ * @param groupname the groupname text
+ * @param gid_p place to return the gid
+ * @returns #TRUE on success
+ */
+dbus_bool_t
+_dbus_parse_unix_group_from_config (const DBusString  *groupname,
+                                    dbus_gid_t        *gid_p)
+{
+  return _dbus_get_group_id (groupname, gid_p);
+}
+
+/**
+ * Gets all groups corresponding to the given UNIX user ID. On UNIX,
+ * just calls _dbus_groups_from_uid(). On Windows, should always
+ * fail since we don't know any UNIX groups.
+ *
+ * @param uid the UID
+ * @param group_ids return location for array of group IDs
+ * @param n_group_ids return location for length of returned array
+ * @returns #TRUE if the UID existed and we got some credentials
+ */
+dbus_bool_t
+_dbus_unix_groups_from_uid (dbus_uid_t            uid,
+                            dbus_gid_t          **group_ids,
+                            int                  *n_group_ids)
+{
+  return _dbus_groups_from_uid (uid, group_ids, n_group_ids);
+}
+
+/**
+ * Checks to see if the UNIX user ID is at the console.
+ * Should always fail on Windows (set the error to
+ * #DBUS_ERROR_NOT_SUPPORTED).
+ *
+ * @param uid UID of person to check 
+ * @param error return location for errors
+ * @returns #TRUE if the UID is the same as the console user and there are no errors
+ */
+dbus_bool_t
+_dbus_unix_user_is_at_console (dbus_uid_t         uid,
+                               DBusError         *error)
+{
+  return _dbus_is_console_user (uid, error);
+
+}
+
+/**
+ * Checks to see if the UNIX user ID matches the UID of
+ * the process. Should always return #FALSE on Windows.
+ *
+ * @param uid the UNIX user ID
+ * @returns #TRUE if this uid owns the process.
+ */
+dbus_bool_t
+_dbus_unix_user_is_process_owner (dbus_uid_t uid)
+{
+  return uid == _dbus_geteuid ();
+}
+
+/**
+ * Checks to see if the Windows user SID matches the owner of
+ * the process. Should always return #FALSE on UNIX.
+ *
+ * @param windows_sid the Windows user SID
+ * @returns #TRUE if this user owns the process.
+ */
+dbus_bool_t
+_dbus_windows_user_is_process_owner (const char *windows_sid)
+{
+  return FALSE;
+}
+
 /** @} */ /* End of DBusInternalsUtils functions */
 
 /**
@@ -739,3 +1047,102 @@ _dbus_string_get_dirname  (const DBusString *filename,
 }
 /** @} */ /* DBusString stuff */
 
+static void
+string_squash_nonprintable (DBusString *str)
+{
+  unsigned char *buf;
+  int i, len; 
+  
+  buf = _dbus_string_get_data (str);
+  len = _dbus_string_get_length (str);
+  
+  for (i = 0; i < len; i++)
+    {
+         unsigned char c = (unsigned char) buf[i];
+      if (c == '\0')
+        c = ' ';
+      else if (c < 0x20 || c > 127)
+        c = '?';
+    }
+}
+
+/**
+ * Get a printable string describing the command used to execute
+ * the process with pid.  This string should only be used for
+ * informative purposes such as logging; it may not be trusted.
+ * 
+ * The command is guaranteed to be printable ASCII and no longer
+ * than max_len.
+ * 
+ * @param pid Process id
+ * @param str Append command to this string
+ * @param max_len Maximum length of returned command
+ * @param error return location for errors
+ * @returns #FALSE on error
+ */
+dbus_bool_t 
+_dbus_command_for_pid (unsigned long  pid,
+                       DBusString    *str,
+                       int            max_len,
+                       DBusError     *error)
+{
+  /* This is all Linux-specific for now */
+  DBusString path;
+  DBusString cmdline;
+  int fd;
+  
+  if (!_dbus_string_init (&path)) 
+    {
+      _DBUS_SET_OOM (error);
+      return FALSE;
+    }
+  
+  if (!_dbus_string_init (&cmdline))
+    {
+      _DBUS_SET_OOM (error);
+      _dbus_string_free (&path);
+      return FALSE;
+    }
+  
+  if (!_dbus_string_append_printf (&path, "/proc/%ld/cmdline", pid))
+    goto oom;
+  
+  fd = open (_dbus_string_get_const_data (&path), O_RDONLY);
+  if (fd < 0) 
+    {
+      dbus_set_error (error,
+                      _dbus_error_from_errno (errno),
+                      "Failed to open \"%s\": %s",
+                      _dbus_string_get_const_data (&path),
+                      _dbus_strerror (errno));
+      goto fail;
+    }
+  
+  if (!_dbus_read (fd, &cmdline, max_len))
+    {
+      dbus_set_error (error,
+                      _dbus_error_from_errno (errno),
+                      "Failed to read from \"%s\": %s",
+                      _dbus_string_get_const_data (&path),
+                      _dbus_strerror (errno));      
+      goto fail;
+    }
+  
+  if (!_dbus_close (fd, error))
+    goto fail;
+  
+  string_squash_nonprintable (&cmdline);  
+  
+  if (!_dbus_string_copy (&cmdline, 0, str, _dbus_string_get_length (str)))
+    goto oom;
+  
+  _dbus_string_free (&cmdline);  
+  _dbus_string_free (&path);
+  return TRUE;
+oom:
+  _DBUS_SET_OOM (error);
+fail:
+  _dbus_string_free (&cmdline);
+  _dbus_string_free (&path);
+  return FALSE;
+}