static void free_watches (DBusTransport *transport) { DBusTransportSocket *socket_transport = (DBusTransportSocket*) transport; _dbus_verbose ("start\n"); if (socket_transport->read_watch) { if (transport->connection) _dbus_connection_remove_watch_unlocked (transport->connection, socket_transport->read_watch); _dbus_watch_invalidate (socket_transport->read_watch); _dbus_watch_unref (socket_transport->read_watch); socket_transport->read_watch = NULL; } if (socket_transport->write_watch) { if (transport->connection) _dbus_connection_remove_watch_unlocked (transport->connection, socket_transport->write_watch); _dbus_watch_invalidate (socket_transport->write_watch); _dbus_watch_unref (socket_transport->write_watch); socket_transport->write_watch = NULL; } _dbus_verbose ("end\n"); }
static dbus_bool_t handle_watch (DBusWatch *watch, unsigned int condition, void *data) { DBusBabysitter *sitter = data; int revents; int fd; revents = 0; if (condition & DBUS_WATCH_READABLE) revents |= _DBUS_POLLIN; if (condition & DBUS_WATCH_ERROR) revents |= _DBUS_POLLERR; if (condition & DBUS_WATCH_HANGUP) revents |= _DBUS_POLLHUP; fd = dbus_watch_get_socket (watch); if (fd == sitter->error_pipe_from_child) handle_error_pipe (sitter, revents); else if (fd == sitter->socket_to_babysitter) handle_babysitter_socket (sitter, revents); while (LIVE_CHILDREN (sitter) && babysitter_iteration (sitter, FALSE)) ; /* Those might have closed the sockets we're watching. Before returning * to the main loop, we must sort that out. */ if (sitter->error_watch != NULL && sitter->error_pipe_from_child == -1) { _dbus_watch_invalidate (sitter->error_watch); if (sitter->watches != NULL) _dbus_watch_list_remove_watch (sitter->watches, sitter->error_watch); _dbus_watch_unref (sitter->error_watch); sitter->error_watch = NULL; } if (sitter->sitter_watch != NULL && sitter->socket_to_babysitter == -1) { _dbus_watch_invalidate (sitter->sitter_watch); if (sitter->watches != NULL) _dbus_watch_list_remove_watch (sitter->watches, sitter->sitter_watch); _dbus_watch_unref (sitter->sitter_watch); sitter->sitter_watch = NULL; } return TRUE; }
/** * Adds a new watch to the watch list, invoking the * application DBusAddWatchFunction if appropriate. * * @param watch_list the watch list. * @param watch the watch to add. * @returns #TRUE on success, #FALSE if no memory. */ dbus_bool_t _dbus_watch_list_add_watch (DBusWatchList *watch_list, DBusWatch *watch) { if (!_dbus_list_append (&watch_list->watches, watch)) return FALSE; _dbus_watch_ref (watch); if (watch_list->add_watch_function != NULL) { _dbus_verbose ("Adding watch on fd %d\n", dbus_watch_get_socket (watch)); if (!(* watch_list->add_watch_function) (watch, watch_list->watch_data)) { _dbus_list_remove_last (&watch_list->watches, watch); _dbus_watch_unref (watch); return FALSE; } } return TRUE; }
static void socket_disconnect (DBusServer *server) { DBusServerSocket *socket_server = (DBusServerSocket*) server; int i; HAVE_LOCK_CHECK (server); for (i = 0 ; i < socket_server->n_fds ; i++) { if (socket_server->watch[i]) { _dbus_server_remove_watch (server, socket_server->watch[i]); _dbus_watch_unref (socket_server->watch[i]); socket_server->watch[i] = NULL; } _dbus_close_socket (socket_server->fds[i], NULL); socket_server->fds[i] = -1; } if (socket_server->socket_name != NULL) { DBusString tmp; _dbus_string_init_const (&tmp, socket_server->socket_name); _dbus_delete_file (&tmp, NULL); } if (server->published_address) _dbus_daemon_unpublish_session_bus_address(); HAVE_LOCK_CHECK (server); }
/** * Adds a new watch to the watch list, invoking the * application DBusAddWatchFunction if appropriate. * * @param watch_list the watch list. * @param watch the watch to add. * @returns #TRUE on success, #FALSE if no memory. */ dbus_bool_t _dbus_watch_list_add_watch (DBusWatchList *watch_list, DBusWatch *watch) { if (!_dbus_list_append (&watch_list->watches, watch)) return FALSE; _dbus_watch_ref (watch); if (watch_list->add_watch_function != NULL) { _dbus_verbose ("Adding watch on fd %" DBUS_POLLABLE_FORMAT "\n", _dbus_pollable_printable (watch->fd)); if (!(* watch_list->add_watch_function) (watch, watch_list->watch_data)) { _dbus_list_remove_last (&watch_list->watches, watch); _dbus_watch_unref (watch); return FALSE; } } return TRUE; }
/** * Handles a watch by reading data, writing data, or disconnecting * the transport, as appropriate for the given condition. * * @param transport the transport. * @param watch the watch. * @param condition the current state of the watched file descriptor. * @returns #FALSE if not enough memory to fully handle the watch */ dbus_bool_t _dbus_transport_handle_watch (DBusTransport *transport, DBusWatch *watch, unsigned int condition) { dbus_bool_t retval; _dbus_assert (transport->vtable->handle_watch != NULL); if (transport->disconnected) return TRUE; if (dbus_watch_get_socket (watch) < 0) { _dbus_warn_check_failed ("Tried to handle an invalidated watch; this watch should have been removed\n"); return TRUE; } _dbus_watch_sanitize_condition (watch, &condition); _dbus_transport_ref (transport); _dbus_watch_ref (watch); retval = (* transport->vtable->handle_watch) (transport, watch, condition); _dbus_watch_unref (watch); _dbus_transport_unref (transport); return retval; }
static void close_reload_pipe (DBusWatch **watch) { _dbus_loop_remove_watch (bus_context_get_loop (context), *watch); _dbus_watch_invalidate (*watch); _dbus_watch_unref (*watch); *watch = NULL; _dbus_close_socket (reload_pipe[RELOAD_READ_END], NULL); reload_pipe[RELOAD_READ_END] = -1; _dbus_close_socket (reload_pipe[RELOAD_WRITE_END], NULL); reload_pipe[RELOAD_WRITE_END] = -1; }
static int _init_kqueue (BusContext *context) { int ret = 0; if (kq < 0) { kq = kqueue (); if (kq < 0) { _dbus_warn ("Cannot create kqueue; error '%s'\n", _dbus_strerror (errno)); goto out; } loop = bus_context_get_loop (context); watch = _dbus_watch_new (kq, DBUS_WATCH_READABLE, TRUE, _handle_kqueue_watch, NULL, NULL); if (watch == NULL) { _dbus_warn ("Unable to create kqueue watch\n"); close (kq); kq = -1; goto out; } if (!_dbus_loop_add_watch (loop, watch)) { _dbus_warn ("Unable to add reload watch to main loop"); _dbus_watch_invalidate (watch); _dbus_watch_unref (watch); watch = NULL; close (kq); kq = -1; goto out; } } ret = 1; out: return ret; }
/** * Removes a watch from the watch list, invoking the * application's DBusRemoveWatchFunction if appropriate. * * @param watch_list the watch list. * @param watch the watch to remove. */ void _dbus_watch_list_remove_watch (DBusWatchList *watch_list, DBusWatch *watch) { if (!_dbus_list_remove (&watch_list->watches, watch)) _dbus_assert_not_reached ("Nonexistent watch was removed"); if (watch_list->remove_watch_function != NULL) { _dbus_verbose ("Removing watch on fd %d\n", dbus_watch_get_socket (watch)); (* watch_list->remove_watch_function) (watch, watch_list->watch_data); } _dbus_watch_unref (watch); }
/** * Removes a watch from the watch list, invoking the * application's DBusRemoveWatchFunction if appropriate. * * @param watch_list the watch list. * @param watch the watch to remove. */ void _dbus_watch_list_remove_watch (DBusWatchList *watch_list, DBusWatch *watch) { if (!_dbus_list_remove (&watch_list->watches, watch)) _dbus_assert_not_reached ("Nonexistent watch was removed"); if (watch_list->remove_watch_function != NULL) { _dbus_verbose ("Removing watch on fd %" DBUS_POLLABLE_FORMAT "\n", _dbus_pollable_printable (watch->fd)); (* watch_list->remove_watch_function) (watch, watch_list->watch_data); } _dbus_watch_unref (watch); }
static void close_error_pipe_from_child (DBusBabysitter *sitter) { _dbus_verbose ("Closing child error\n"); if (sitter->error_watch != NULL) { _dbus_assert (sitter->watches != NULL); _dbus_watch_list_remove_watch (sitter->watches, sitter->error_watch); _dbus_watch_invalidate (sitter->error_watch); _dbus_watch_unref (sitter->error_watch); sitter->error_watch = NULL; } if (sitter->error_pipe_from_child >= 0) { _dbus_close (sitter->error_pipe_from_child, NULL); sitter->error_pipe_from_child = -1; } }
static void close_socket_to_babysitter (DBusBabysitter *sitter) { _dbus_verbose ("Closing babysitter\n"); if (sitter->sitter_watch != NULL) { _dbus_assert (sitter->watches != NULL); _dbus_watch_list_remove_watch (sitter->watches, sitter->sitter_watch); _dbus_watch_invalidate (sitter->sitter_watch); _dbus_watch_unref (sitter->sitter_watch); sitter->sitter_watch = NULL; } if (sitter->socket_to_babysitter.fd >= 0) { _dbus_close_socket (sitter->socket_to_babysitter, NULL); sitter->socket_to_babysitter.fd = -1; } }
static void free_watch_table_entry (void *data) { DBusList **watches = data; DBusWatch *watch; /* DBusHashTable sometimes calls free_function(NULL) even if you never * have NULL as a value */ if (watches == NULL) return; for (watch = _dbus_list_pop_first (watches); watch != NULL; watch = _dbus_list_pop_first (watches)) { _dbus_watch_unref (watch); } _dbus_assert (*watches == NULL); dbus_free (watches); }
static void _shutdown_kqueue (void *data) { int i; if (kq < 0) return; for (i = 0; i < MAX_DIRS_TO_WATCH; i++) { if (fds[i] >= 0) { close (fds[i]); fds[i] = -1; } if (dirs[i] != NULL) { /* dbus_free() is necessary to pass memleak check */ dbus_free (dirs[i]); dirs[i] = NULL; } } if (loop) { _dbus_loop_remove_watch (loop, watch); _dbus_loop_unref (loop); loop = NULL; } if (watch) { _dbus_watch_invalidate (watch); _dbus_watch_unref (watch); watch = NULL; } close (kq); kq = -1; }
static void socket_finalize (DBusServer *server) { DBusServerSocket *socket_server = (DBusServerSocket*) server; int i; _dbus_server_finalize_base (server); for (i = 0 ; i < socket_server->n_fds ; i++) if (socket_server->watch[i]) { _dbus_watch_unref (socket_server->watch[i]); socket_server->watch[i] = NULL; } dbus_free (socket_server->fds); dbus_free (socket_server->watch); dbus_free (socket_server->socket_name); if (socket_server->noncefile) _dbus_noncefile_delete (socket_server->noncefile, NULL); dbus_free (socket_server->noncefile); dbus_free (server); }
static dbus_bool_t _handle_kqueue_watch (DBusWatch *watch, unsigned int flags, void *data) { struct kevent ev; struct timespec nullts = { 0, 0 }; int res; pid_t pid; res = kevent (kq, NULL, 0, &ev, 1, &nullts); /* Sleep for half a second to avoid a race when files are install(1)'d * to system.d. */ usleep(500000); if (res > 0) { pid = getpid (); _dbus_verbose ("Sending SIGHUP signal on reception of a kevent\n"); (void) kill (pid, SIGHUP); } else if (res < 0 && errno == EBADF) { kq = -1; if (watch != NULL) { _dbus_loop_remove_watch (loop, watch); _dbus_watch_invalidate (watch); _dbus_watch_unref (watch); watch = NULL; } pid = getpid (); _dbus_verbose ("Sending SIGHUP signal since kqueue has been closed\n"); (void) kill (pid, SIGHUP); } return TRUE; }
static int _init_kqueue (BusContext *context) { if (kq < 0) { kq = kqueue (); if (kq < 0) { _dbus_warn ("Cannot create kqueue; error '%s'\n", _dbus_strerror (errno)); goto out; } loop = bus_context_get_loop (context); _dbus_loop_ref (loop); watch = _dbus_watch_new (kq, DBUS_WATCH_READABLE, TRUE, _handle_kqueue_watch, NULL, NULL); if (watch == NULL) { _dbus_warn ("Unable to create kqueue watch\n"); goto out1; } if (!_dbus_loop_add_watch (loop, watch)) { _dbus_warn ("Unable to add reload watch to main loop"); goto out2; } if (!_dbus_register_shutdown_func (_shutdown_kqueue, NULL)) { _dbus_warn ("Unable to register shutdown function"); goto out3; } } return 1; out3: _dbus_loop_remove_watch (loop, watch); out2: if (watch) { _dbus_watch_invalidate (watch); _dbus_watch_unref (watch); watch = NULL; } out1: if (kq >= 0) { close (kq); kq = -1; } if (loop) { _dbus_loop_unref (loop); loop = NULL; } out: return 0; }
/** * Creates a new server listening on the given file descriptor. The * file descriptor should be nonblocking (use * _dbus_set_fd_nonblocking() to make it so). The file descriptor * should be listening for connections, that is, listen() should have * been successfully invoked on it. The server will use accept() to * accept new client connections. * * @param fds list of file descriptors. * @param n_fds number of file descriptors * @param address the server's address * @param noncefile to be used for authentication (NULL if not needed) * @returns the new server, or #NULL if no memory. * */ DBusServer* _dbus_server_new_for_socket (int *fds, int n_fds, const DBusString *address, DBusNonceFile *noncefile) { DBusServerSocket *socket_server; DBusServer *server; int i; socket_server = dbus_new0 (DBusServerSocket, 1); if (socket_server == NULL) return NULL; socket_server->noncefile = noncefile; socket_server->fds = dbus_new (int, n_fds); if (!socket_server->fds) goto failed_0; socket_server->watch = dbus_new0 (DBusWatch *, n_fds); if (!socket_server->watch) goto failed_1; for (i = 0 ; i < n_fds ; i++) { DBusWatch *watch; watch = _dbus_watch_new (fds[i], DBUS_WATCH_READABLE, TRUE, socket_handle_watch, socket_server, NULL); if (watch == NULL) goto failed_2; socket_server->n_fds++; socket_server->fds[i] = fds[i]; socket_server->watch[i] = watch; } if (!_dbus_server_init_base (&socket_server->base, &socket_vtable, address)) goto failed_2; server = (DBusServer*)socket_server; SERVER_LOCK (server); for (i = 0 ; i < n_fds ; i++) { if (!_dbus_server_add_watch (&socket_server->base, socket_server->watch[i])) { int j; for (j = 0 ; j < i ; j++) _dbus_server_remove_watch (server, socket_server->watch[j]); SERVER_UNLOCK (server); _dbus_server_finalize_base (&socket_server->base); goto failed_2; } } SERVER_UNLOCK (server); return (DBusServer*) socket_server; failed_2: for (i = 0 ; i < n_fds ; i++) { if (socket_server->watch[i] != NULL) { _dbus_watch_unref (socket_server->watch[i]); socket_server->watch[i] = NULL; } } dbus_free (socket_server->watch); failed_1: dbus_free (socket_server->fds); failed_0: dbus_free (socket_server); return NULL; }
/** * Creates a new transport for the given socket file descriptor. The file * descriptor must be nonblocking (use _dbus_set_fd_nonblocking() to * make it so). This function is shared by various transports that * boil down to a full duplex file descriptor. * * @param fd the file descriptor. * @param server_guid non-#NULL if this transport is on the server side of a connection * @param address the transport's address * @returns the new transport, or #NULL if no memory. */ DBusTransport* _dbus_transport_new_for_socket (DBusSocket fd, const DBusString *server_guid, const DBusString *address) { DBusTransportSocket *socket_transport; socket_transport = dbus_new0 (DBusTransportSocket, 1); if (socket_transport == NULL) return NULL; if (!_dbus_string_init (&socket_transport->encoded_outgoing)) goto failed_0; if (!_dbus_string_init (&socket_transport->encoded_incoming)) goto failed_1; socket_transport->write_watch = _dbus_watch_new (_dbus_socket_get_pollable (fd), DBUS_WATCH_WRITABLE, FALSE, NULL, NULL, NULL); if (socket_transport->write_watch == NULL) goto failed_2; socket_transport->read_watch = _dbus_watch_new (_dbus_socket_get_pollable (fd), DBUS_WATCH_READABLE, FALSE, NULL, NULL, NULL); if (socket_transport->read_watch == NULL) goto failed_3; if (!_dbus_transport_init_base (&socket_transport->base, &socket_vtable, server_guid, address)) goto failed_4; #ifdef HAVE_UNIX_FD_PASSING _dbus_auth_set_unix_fd_possible(socket_transport->base.auth, _dbus_socket_can_pass_unix_fd(fd)); #endif socket_transport->fd = fd; socket_transport->message_bytes_written = 0; /* These values should probably be tunable or something. */ socket_transport->max_bytes_read_per_iteration = 2048; socket_transport->max_bytes_written_per_iteration = 2048; return (DBusTransport*) socket_transport; failed_4: _dbus_watch_invalidate (socket_transport->read_watch); _dbus_watch_unref (socket_transport->read_watch); failed_3: _dbus_watch_invalidate (socket_transport->write_watch); _dbus_watch_unref (socket_transport->write_watch); failed_2: _dbus_string_free (&socket_transport->encoded_incoming); failed_1: _dbus_string_free (&socket_transport->encoded_outgoing); failed_0: dbus_free (socket_transport); return NULL; }
/** * Spawns a new process. * * On Unix platforms, the child_setup function is passed the given * user_data and is run in the child after fork() but before calling exec(). * This can be used to change uid, resource limits and so on. * On Windows, this functionality does not fit the multi-processing model * (Windows does the equivalent of fork() and exec() in a single API call), * and the child_setup function and its user_data are ignored. * * Also creates a "babysitter" which tracks the status of the * child process, advising the parent if the child exits. * If the spawn fails, no babysitter is created. * If sitter_p is #NULL, no babysitter is kept. * * @param sitter_p return location for babysitter or #NULL * @param log_name the name under which to log messages about this process being spawned * @param argv the executable and arguments * @param env the environment, or #NULL to copy the parent's * @param child_setup function to call in child pre-exec() * @param user_data user data for setup function * @param error error object to be filled in if function fails * @returns #TRUE on success, #FALSE if error is filled in */ dbus_bool_t _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, const char *log_name, char * const *argv, char **env, DBusSpawnFlags flags, DBusSpawnChildSetupFunc child_setup, void *user_data, DBusError *error) { DBusBabysitter *sitter; int child_err_report_pipe[2] = { -1, -1 }; DBusSocket babysitter_pipe[2] = { DBUS_SOCKET_INIT, DBUS_SOCKET_INIT }; pid_t pid; #ifdef HAVE_SYSTEMD int fd_out = -1; int fd_err = -1; #endif _DBUS_ASSERT_ERROR_IS_CLEAR (error); _dbus_assert (argv[0] != NULL); if (sitter_p != NULL) *sitter_p = NULL; sitter = NULL; sitter = _dbus_babysitter_new (); if (sitter == NULL) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); return FALSE; } sitter->log_name = _dbus_strdup (log_name); if (sitter->log_name == NULL && log_name != NULL) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } if (sitter->log_name == NULL) sitter->log_name = _dbus_strdup (argv[0]); if (sitter->log_name == NULL) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } if (!make_pipe (child_err_report_pipe, error)) goto cleanup_and_fail; if (!_dbus_socketpair (&babysitter_pipe[0], &babysitter_pipe[1], TRUE, error)) goto cleanup_and_fail; /* Setting up the babysitter is only useful in the parent, * but we don't want to run out of memory and fail * after we've already forked, since then we'd leak * child processes everywhere. */ sitter->error_watch = _dbus_watch_new (child_err_report_pipe[READ_END], DBUS_WATCH_READABLE, TRUE, handle_watch, sitter, NULL); if (sitter->error_watch == NULL) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } if (!_dbus_watch_list_add_watch (sitter->watches, sitter->error_watch)) { /* we need to free it early so the destructor won't try to remove it * without it having been added, which DBusLoop doesn't allow */ _dbus_watch_invalidate (sitter->error_watch); _dbus_watch_unref (sitter->error_watch); sitter->error_watch = NULL; dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } sitter->sitter_watch = _dbus_watch_new (babysitter_pipe[0].fd, DBUS_WATCH_READABLE, TRUE, handle_watch, sitter, NULL); if (sitter->sitter_watch == NULL) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } if (!_dbus_watch_list_add_watch (sitter->watches, sitter->sitter_watch)) { /* we need to free it early so the destructor won't try to remove it * without it having been added, which DBusLoop doesn't allow */ _dbus_watch_invalidate (sitter->sitter_watch); _dbus_watch_unref (sitter->sitter_watch); sitter->sitter_watch = NULL; dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } _DBUS_ASSERT_ERROR_IS_CLEAR (error); #ifdef HAVE_SYSTEMD if (flags & DBUS_SPAWN_REDIRECT_OUTPUT) { /* This may fail, but it's not critical. * In particular, if we were compiled with journald support but are now * running on a non-systemd system, this is going to fail, so we * have to cope gracefully. */ fd_out = sd_journal_stream_fd (sitter->log_name, LOG_INFO, FALSE); fd_err = sd_journal_stream_fd (sitter->log_name, LOG_WARNING, FALSE); } #endif pid = fork (); if (pid < 0) { dbus_set_error (error, DBUS_ERROR_SPAWN_FORK_FAILED, "Failed to fork (%s)", _dbus_strerror (errno)); goto cleanup_and_fail; } else if (pid == 0) { /* Immediate child, this is the babysitter process. */ int grandchild_pid; /* Be sure we crash if the parent exits * and we write to the err_report_pipe */ signal (SIGPIPE, SIG_DFL); /* Close the parent's end of the pipes. */ close_and_invalidate (&child_err_report_pipe[READ_END]); close_and_invalidate (&babysitter_pipe[0].fd); /* Create the child that will exec () */ grandchild_pid = fork (); if (grandchild_pid < 0) { write_err_and_exit (babysitter_pipe[1].fd, CHILD_FORK_FAILED); _dbus_assert_not_reached ("Got to code after write_err_and_exit()"); } else if (grandchild_pid == 0) { #ifdef __linux__ int fd = -1; #ifdef O_CLOEXEC fd = open ("/proc/self/oom_score_adj", O_WRONLY | O_CLOEXEC); #endif if (fd < 0) { fd = open ("/proc/self/oom_score_adj", O_WRONLY); _dbus_fd_set_close_on_exec (fd); } if (fd >= 0) { if (write (fd, "0", sizeof (char)) < 0) _dbus_warn ("writing oom_score_adj error: %s", strerror (errno)); _dbus_close (fd, NULL); } #endif /* Go back to ignoring SIGPIPE, since it's evil */ signal (SIGPIPE, SIG_IGN); close_and_invalidate (&babysitter_pipe[1].fd); #ifdef HAVE_SYSTEMD /* log to systemd journal if possible */ if (fd_out >= 0) dup2 (fd_out, STDOUT_FILENO); if (fd_err >= 0) dup2 (fd_err, STDERR_FILENO); close_and_invalidate (&fd_out); close_and_invalidate (&fd_err); #endif do_exec (child_err_report_pipe[WRITE_END], argv, env, child_setup, user_data); _dbus_assert_not_reached ("Got to code after exec() - should have exited on error"); } else { close_and_invalidate (&child_err_report_pipe[WRITE_END]); #ifdef HAVE_SYSTEMD close_and_invalidate (&fd_out); close_and_invalidate (&fd_err); #endif babysit (grandchild_pid, babysitter_pipe[1].fd); _dbus_assert_not_reached ("Got to code after babysit()"); } } else { /* Close the uncared-about ends of the pipes */ close_and_invalidate (&child_err_report_pipe[WRITE_END]); close_and_invalidate (&babysitter_pipe[1].fd); #ifdef HAVE_SYSTEMD close_and_invalidate (&fd_out); close_and_invalidate (&fd_err); #endif sitter->socket_to_babysitter = babysitter_pipe[0]; babysitter_pipe[0].fd = -1; sitter->error_pipe_from_child = child_err_report_pipe[READ_END]; child_err_report_pipe[READ_END] = -1; sitter->sitter_pid = pid; if (sitter_p != NULL) *sitter_p = sitter; else _dbus_babysitter_unref (sitter); dbus_free_string_array (env); _DBUS_ASSERT_ERROR_IS_CLEAR (error); return TRUE; } cleanup_and_fail: _DBUS_ASSERT_ERROR_IS_SET (error); close_and_invalidate (&child_err_report_pipe[READ_END]); close_and_invalidate (&child_err_report_pipe[WRITE_END]); close_and_invalidate (&babysitter_pipe[0].fd); close_and_invalidate (&babysitter_pipe[1].fd); #ifdef HAVE_SYSTEMD close_and_invalidate (&fd_out); close_and_invalidate (&fd_err); #endif if (sitter != NULL) _dbus_babysitter_unref (sitter); return FALSE; }
/** * Decrement the reference count on the babysitter object. * * @param sitter the babysitter */ void _dbus_babysitter_unref (DBusBabysitter *sitter) { int i; PING(); _dbus_assert (sitter != NULL); _dbus_assert (sitter->refcount > 0); sitter->refcount -= 1; if (sitter->refcount == 0) { if (sitter->socket_to_babysitter != -1) { _dbus_close_socket (sitter->socket_to_babysitter, NULL); sitter->socket_to_babysitter = -1; } if (sitter->socket_to_main != -1) { _dbus_close_socket (sitter->socket_to_main, NULL); sitter->socket_to_main = -1; } PING(); if (sitter->argv != NULL) { for (i = 0; i < sitter->argc; i++) if (sitter->argv[i] != NULL) { dbus_free (sitter->argv[i]); sitter->argv[i] = NULL; } dbus_free (sitter->argv); sitter->argv = NULL; } if (sitter->envp != NULL) { char **e = sitter->envp; while (*e) dbus_free (*e++); dbus_free (sitter->envp); sitter->envp = NULL; } if (sitter->child_handle != NULL) { CloseHandle (sitter->child_handle); sitter->child_handle = NULL; } if (sitter->sitter_watch) { _dbus_watch_invalidate (sitter->sitter_watch); _dbus_watch_unref (sitter->sitter_watch); sitter->sitter_watch = NULL; } if (sitter->watches) _dbus_watch_list_free (sitter->watches); if (sitter->start_sync_event != NULL) { PING(); CloseHandle (sitter->start_sync_event); sitter->start_sync_event = NULL; } #ifdef DBUS_BUILD_TESTS if (sitter->end_sync_event != NULL) { CloseHandle (sitter->end_sync_event); sitter->end_sync_event = NULL; } #endif dbus_free (sitter->executable); dbus_free (sitter); } }
/** * Decrement the reference count on the babysitter object. * When the reference count of the babysitter object reaches * zero, the babysitter is killed and the child that was being * babysat gets emancipated. * * @param sitter the babysitter */ void _dbus_babysitter_unref (DBusBabysitter *sitter) { _dbus_assert (sitter != NULL); _dbus_assert (sitter->refcount > 0); sitter->refcount -= 1; if (sitter->refcount == 0) { if (sitter->socket_to_babysitter >= 0) { /* If we haven't forked other babysitters * since this babysitter and socket were * created then this close will cause the * babysitter to wake up from poll with * a hangup and then the babysitter will * quit itself. */ _dbus_close_socket (sitter->socket_to_babysitter, NULL); sitter->socket_to_babysitter = -1; } if (sitter->error_pipe_from_child >= 0) { _dbus_close_socket (sitter->error_pipe_from_child, NULL); sitter->error_pipe_from_child = -1; } if (sitter->sitter_pid > 0) { int status; int ret; /* It's possible the babysitter died on its own above * from the close, or was killed randomly * by some other process, so first try to reap it */ ret = waitpid (sitter->sitter_pid, &status, WNOHANG); /* If we couldn't reap the child then kill it, and * try again */ if (ret == 0) kill (sitter->sitter_pid, SIGKILL); again: if (ret == 0) ret = waitpid (sitter->sitter_pid, &status, 0); if (ret < 0) { if (errno == EINTR) goto again; else if (errno == ECHILD) _dbus_warn ("Babysitter process not available to be reaped; should not happen\n"); else _dbus_warn ("Unexpected error %d in waitpid() for babysitter: %s\n", errno, _dbus_strerror (errno)); } else { _dbus_verbose ("Reaped %ld, waiting for babysitter %ld\n", (long) ret, (long) sitter->sitter_pid); if (WIFEXITED (sitter->status)) _dbus_verbose ("Babysitter exited with status %d\n", WEXITSTATUS (sitter->status)); else if (WIFSIGNALED (sitter->status)) _dbus_verbose ("Babysitter received signal %d\n", WTERMSIG (sitter->status)); else _dbus_verbose ("Babysitter exited abnormally\n"); } sitter->sitter_pid = -1; } if (sitter->error_watch) { _dbus_watch_invalidate (sitter->error_watch); _dbus_watch_unref (sitter->error_watch); sitter->error_watch = NULL; } if (sitter->sitter_watch) { _dbus_watch_invalidate (sitter->sitter_watch); _dbus_watch_unref (sitter->sitter_watch); sitter->sitter_watch = NULL; } if (sitter->watches) _dbus_watch_list_free (sitter->watches); dbus_free (sitter->executable); dbus_free (sitter); } }
/** * Spawns a new process. The executable name and argv[0] * are the same, both are provided in argv[0]. The child_setup * function is passed the given user_data and is run in the child * just before calling exec(). * * Also creates a "babysitter" which tracks the status of the * child process, advising the parent if the child exits. * If the spawn fails, no babysitter is created. * If sitter_p is #NULL, no babysitter is kept. * * @param sitter_p return location for babysitter or #NULL * @param argv the executable and arguments * @param env the environment (not used on unix yet) * @param child_setup function to call in child pre-exec() * @param user_data user data for setup function * @param error error object to be filled in if function fails * @returns #TRUE on success, #FALSE if error is filled in */ dbus_bool_t _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, char **argv, char **env, DBusSpawnChildSetupFunc child_setup, void *user_data, DBusError *error) { DBusBabysitter *sitter; int child_err_report_pipe[2] = { -1, -1 }; int babysitter_pipe[2] = { -1, -1 }; pid_t pid; _DBUS_ASSERT_ERROR_IS_CLEAR (error); if (sitter_p != NULL) *sitter_p = NULL; sitter = NULL; sitter = _dbus_babysitter_new (); if (sitter == NULL) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); return FALSE; } sitter->executable = _dbus_strdup (argv[0]); if (sitter->executable == NULL) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } if (!make_pipe (child_err_report_pipe, error)) goto cleanup_and_fail; if (!_dbus_full_duplex_pipe (&babysitter_pipe[0], &babysitter_pipe[1], TRUE, error)) goto cleanup_and_fail; /* Setting up the babysitter is only useful in the parent, * but we don't want to run out of memory and fail * after we've already forked, since then we'd leak * child processes everywhere. */ sitter->error_watch = _dbus_watch_new (child_err_report_pipe[READ_END], DBUS_WATCH_READABLE, TRUE, handle_watch, sitter, NULL); if (sitter->error_watch == NULL) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } if (!_dbus_watch_list_add_watch (sitter->watches, sitter->error_watch)) { /* we need to free it early so the destructor won't try to remove it * without it having been added, which DBusLoop doesn't allow */ _dbus_watch_invalidate (sitter->error_watch); _dbus_watch_unref (sitter->error_watch); sitter->error_watch = NULL; dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } sitter->sitter_watch = _dbus_watch_new (babysitter_pipe[0], DBUS_WATCH_READABLE, TRUE, handle_watch, sitter, NULL); if (sitter->sitter_watch == NULL) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } if (!_dbus_watch_list_add_watch (sitter->watches, sitter->sitter_watch)) { /* we need to free it early so the destructor won't try to remove it * without it having been added, which DBusLoop doesn't allow */ _dbus_watch_invalidate (sitter->sitter_watch); _dbus_watch_unref (sitter->sitter_watch); sitter->sitter_watch = NULL; dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; } _DBUS_ASSERT_ERROR_IS_CLEAR (error); pid = fork (); if (pid < 0) { dbus_set_error (error, DBUS_ERROR_SPAWN_FORK_FAILED, "Failed to fork (%s)", _dbus_strerror (errno)); goto cleanup_and_fail; } else if (pid == 0) { /* Immediate child, this is the babysitter process. */ int grandchild_pid; /* Be sure we crash if the parent exits * and we write to the err_report_pipe */ signal (SIGPIPE, SIG_DFL); /* Close the parent's end of the pipes. */ close_and_invalidate (&child_err_report_pipe[READ_END]); close_and_invalidate (&babysitter_pipe[0]); /* Create the child that will exec () */ grandchild_pid = fork (); if (grandchild_pid < 0) { write_err_and_exit (babysitter_pipe[1], CHILD_FORK_FAILED); _dbus_assert_not_reached ("Got to code after write_err_and_exit()"); } else if (grandchild_pid == 0) { do_exec (child_err_report_pipe[WRITE_END], argv, env, child_setup, user_data); _dbus_assert_not_reached ("Got to code after exec() - should have exited on error"); } else { babysit (grandchild_pid, babysitter_pipe[1]); _dbus_assert_not_reached ("Got to code after babysit()"); } } else { /* Close the uncared-about ends of the pipes */ close_and_invalidate (&child_err_report_pipe[WRITE_END]); close_and_invalidate (&babysitter_pipe[1]); sitter->socket_to_babysitter = babysitter_pipe[0]; babysitter_pipe[0] = -1; sitter->error_pipe_from_child = child_err_report_pipe[READ_END]; child_err_report_pipe[READ_END] = -1; sitter->sitter_pid = pid; if (sitter_p != NULL) *sitter_p = sitter; else _dbus_babysitter_unref (sitter); dbus_free_string_array (env); _DBUS_ASSERT_ERROR_IS_CLEAR (error); return TRUE; } cleanup_and_fail: _DBUS_ASSERT_ERROR_IS_SET (error); close_and_invalidate (&child_err_report_pipe[READ_END]); close_and_invalidate (&child_err_report_pipe[WRITE_END]); close_and_invalidate (&babysitter_pipe[0]); close_and_invalidate (&babysitter_pipe[1]); if (sitter != NULL) _dbus_babysitter_unref (sitter); return FALSE; }