/* * gnome-keyring * * Copyright (C) 2009 Stefan Walter * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see * . */ #include "config.h" #include "egg-spawn.h" #include #include #include #include #include #include typedef struct _CallbackSource { GSource source; EggSpawnCallbacks callbacks; GPollFD polls[3]; } CallbackSource; static void close_fd (int *fd) { g_assert (fd); if (*fd >= 0) close (*fd); *fd = -1; } static void close_poll (GSource *source, GPollFD *poll) { g_source_remove_poll (source, poll); close_fd (&poll->fd); poll->revents = 0; } static gboolean unused_callback (gpointer data) { /* Never called */ g_assert_not_reached (); return FALSE; } static gboolean cb_source_prepare (GSource *source, gint *timeout_) { CallbackSource *cb_source = (CallbackSource*)source; gint i; for (i = 0; i < 3; ++i) { if (cb_source->polls[i].fd >= 0) return FALSE; } /* If none of the FDs are valid, then process immediately */ return TRUE; } static gboolean cb_source_check (GSource *source) { CallbackSource *cb_source = (CallbackSource*)source; gint i; for (i = 0; i < 3; ++i) { if (cb_source->polls[i].fd >= 0 && cb_source->polls[i].revents != 0) return TRUE; } return FALSE; } static void cb_source_finalize (GSource *source) { CallbackSource *cb_source = (CallbackSource*)source; gint i; for (i = 0; i < 3; ++i) close_fd (&cb_source->polls[i].fd); } static gboolean cb_source_dispatch (GSource *source, GSourceFunc unused, gpointer user_data) { CallbackSource *cb_source = (CallbackSource*)source; GPollFD *poll; gint i; /* Standard input */ poll = &cb_source->polls[0]; if (poll->fd >= 0 && poll->revents != 0) { g_assert (cb_source->callbacks.standard_input); if (!(cb_source->callbacks.standard_input) (poll->fd, user_data)) close_poll (source, poll); } /* Standard output */ poll = &cb_source->polls[1]; if (poll->fd >= 0 && poll->revents != 0) { g_assert (cb_source->callbacks.standard_output); if (!(cb_source->callbacks.standard_output) (poll->fd, user_data)) close_poll (source, poll); } /* Standard error */ poll = &cb_source->polls[2]; if (poll->fd >= 0 && poll->revents != 0) { g_assert (cb_source->callbacks.standard_error); if (!(cb_source->callbacks.standard_error) (poll->fd, user_data)) close_poll (source, poll); } for (i = 0; i < 3; ++i) { if (cb_source->polls[i].fd >= 0) return TRUE; } /* All input and output is done */ if (cb_source->callbacks.completed) (cb_source->callbacks.completed) (user_data); return FALSE; } static GSourceFuncs cb_source_funcs = { cb_source_prepare, cb_source_check, cb_source_dispatch, cb_source_finalize, }; guint egg_spawn_async_with_callbacks (const gchar *working_directory, gchar **argv, gchar **envp, GSpawnFlags flags, GPid *child_pid, EggSpawnCallbacks *cbs, gpointer user_data, GMainContext *context, GError **error) { gint in_fd, out_fd, err_fd; CallbackSource *cb_source; GSource *source; guint tag; g_return_val_if_fail (argv != NULL, FALSE); g_return_val_if_fail ((cbs && cbs->standard_input == NULL) || !(flags & G_SPAWN_CHILD_INHERITS_STDIN), 0); g_return_val_if_fail ((cbs && cbs->standard_output == NULL) || !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), 0); g_return_val_if_fail ((cbs && cbs->standard_error == NULL) || !(flags & G_SPAWN_STDERR_TO_DEV_NULL), 0); in_fd = out_fd = err_fd = -1; if (!g_spawn_async_with_pipes (working_directory, argv, envp, flags, cbs ? cbs->child_setup : NULL, user_data, child_pid, cbs && cbs->standard_input ? &in_fd : NULL, cbs && cbs->standard_output ? &out_fd : NULL, cbs && cbs->standard_error ? &err_fd : NULL, error)) return 0; source = g_source_new (&cb_source_funcs, sizeof (CallbackSource)); cb_source = (CallbackSource*)source; if (cbs != NULL) memcpy (&cb_source->callbacks, cbs, sizeof (EggSpawnCallbacks)); cb_source->polls[0].fd = in_fd; if (in_fd >= 0) { g_assert (cb_source->callbacks.standard_input); cb_source->polls[0].events = G_IO_ERR | G_IO_OUT; g_source_add_poll (source, &cb_source->polls[0]); } cb_source->polls[1].fd = out_fd; if (out_fd >= 0) { g_assert (cb_source->callbacks.standard_output); cb_source->polls[1].events = G_IO_ERR | G_IO_HUP | G_IO_IN; g_source_add_poll (source, &cb_source->polls[1]); } cb_source->polls[2].fd = err_fd; if (err_fd >= 0) { g_assert (cb_source->callbacks.standard_error); cb_source->polls[2].events = G_IO_ERR | G_IO_HUP | G_IO_IN; g_source_add_poll (source, &cb_source->polls[2]); } if (context == NULL) context = g_main_context_default (); g_source_set_callback (source, unused_callback, user_data, cbs ? cbs->finalize_func : NULL); tag = g_source_attach (source, context); g_source_unref (source); return tag; } gboolean egg_spawn_sync_with_callbacks (const gchar *working_directory, gchar **argv, gchar **envp, GSpawnFlags flags, GPid *child_pid, EggSpawnCallbacks *cbs, gpointer user_data, gint *exit_status, GError **error) { gint in_fd, out_fd, err_fd, max_fd; fd_set read_fds, write_fds; gboolean failed = FALSE; gint status; GPid pid; gint ret; g_return_val_if_fail (argv != NULL, FALSE); g_return_val_if_fail ((cbs && cbs->standard_input == NULL) || !(flags & G_SPAWN_CHILD_INHERITS_STDIN), 0); g_return_val_if_fail ((cbs && cbs->standard_output == NULL) || !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), 0); g_return_val_if_fail ((cbs && cbs->standard_error == NULL) || !(flags & G_SPAWN_STDERR_TO_DEV_NULL), 0); in_fd = out_fd = err_fd = -1; if (exit_status) flags |= G_SPAWN_DO_NOT_REAP_CHILD; if (!g_spawn_async_with_pipes (working_directory, argv, envp, flags, cbs ? cbs->child_setup : NULL, user_data, &pid, cbs && cbs->standard_input ? &in_fd : NULL, cbs && cbs->standard_output ? &out_fd : NULL, cbs && cbs->standard_error ? &err_fd : NULL, error)) return FALSE; if (child_pid) *child_pid = pid; max_fd = MAX (in_fd, MAX (out_fd, err_fd)) + 1; while (in_fd >= 0 || out_fd >= 0 || err_fd >= 0) { FD_ZERO (&write_fds); if (in_fd >= 0) FD_SET (in_fd, &write_fds); FD_ZERO (&read_fds); if (out_fd >= 0) FD_SET (out_fd, &read_fds); if (err_fd >= 0) FD_SET (err_fd, &read_fds); ret = select (max_fd, &read_fds, &write_fds, NULL, NULL); if (ret < 0 && errno != EINTR) { failed = TRUE; g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_READ, _("Unexpected error in select() reading data from a child process (%s)"), g_strerror (errno)); break; } if (in_fd >= 0 && FD_ISSET (in_fd, &write_fds)) { g_assert (cbs && cbs->standard_input); if (!(cbs->standard_input) (in_fd, user_data)) close_fd (&in_fd); } if (out_fd >= 0 && FD_ISSET (out_fd, &read_fds)) { g_assert (cbs && cbs->standard_output); if (!(cbs->standard_output) (out_fd, user_data)) close_fd (&out_fd); } if (err_fd >= 0 && FD_ISSET (err_fd, &read_fds)) { g_assert (cbs && cbs->standard_error); if (!(cbs->standard_error) (err_fd, user_data)) close_fd (&err_fd); } } if (in_fd >= 0) close_fd (&in_fd); if (out_fd >= 0) close_fd (&out_fd); if (err_fd >= 0) close_fd (&err_fd); if (!failed) { if (cbs && cbs->completed) (cbs->completed) (user_data); } again: ret = waitpid (pid, &status, 0); if (ret < 0) { if (errno == EINTR) goto again; else if (errno == ECHILD) { if (exit_status) g_warning ("In call to g_spawn_sync(), exit status of a child process was requested but SIGCHLD action was set to SIG_IGN and ECHILD was received by waitpid(), so exit status can't be returned. This is a bug in the program calling g_spawn_sync(); either don't request the exit status, or don't set the SIGCHLD action."); else ; /* We don't need the exit status. */ } else if (!failed) { /* avoid error pileups */ failed = TRUE; g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_READ, _("Unexpected error in waitpid() (%s)"), g_strerror (errno)); } } else { if (exit_status) *exit_status = status; } if (!child_pid) g_spawn_close_pid (pid); if (cbs && cbs->finalize_func) (cbs->finalize_func) (user_data); return !failed; } gssize egg_spawn_write_input (int fd, gconstpointer data, gsize n_data) { gssize result; g_return_val_if_fail (fd >= 0, -1); for (;;) { result = write (fd, data, n_data); if (result < 0) { if (errno == EINTR) continue; else if (errno == EAGAIN) result = 0; } break; } return result; } gssize egg_spawn_read_output (int fd, gpointer data, gsize n_data) { gssize result; g_return_val_if_fail (fd >= 0, -1); for (;;) { result = read (fd, data, n_data); if (result < 0) { if (errno == EINTR) continue; else if (errno == EAGAIN) result = 0; } break; } return result; }