/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */ /* camel-stream-process.c : stream over piped process * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This library 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. * * This library 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 library. If not, see . * * Authors: David Woodhouse * Jeffrey Stedfast */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "camel-file-utils.h" #include "camel-stream-process.h" #define CHECK_CALL(x) G_STMT_START { \ if ((x) == -1) { \ g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \ } \ } G_STMT_END extern gint camel_verbose_debug; G_DEFINE_TYPE (CamelStreamProcess, camel_stream_process, CAMEL_TYPE_STREAM) static void stream_process_finalize (GObject *object) { /* Ensure we clean up after ourselves -- kill * the child process and reap it. */ camel_stream_close (CAMEL_STREAM (object), NULL, NULL); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (camel_stream_process_parent_class)->finalize (object); } static gssize stream_process_read (CamelStream *stream, gchar *buffer, gsize n, GCancellable *cancellable, GError **error) { CamelStreamProcess *stream_process = CAMEL_STREAM_PROCESS (stream); gint fd = stream_process->sockfd; return camel_read (fd, buffer, n, cancellable, error); } static gssize stream_process_write (CamelStream *stream, const gchar *buffer, gsize n, GCancellable *cancellable, GError **error) { CamelStreamProcess *stream_process = CAMEL_STREAM_PROCESS (stream); gint fd = stream_process->sockfd; return camel_write (fd, buffer, n, cancellable, error); } static gint stream_process_close (CamelStream *object, GCancellable *cancellable, GError **error) { CamelStreamProcess *stream = CAMEL_STREAM_PROCESS (object); if (camel_verbose_debug) fprintf ( stderr, "Process stream close. sockfd %d, childpid %d\n", stream->sockfd, stream->childpid); if (stream->sockfd != -1) { close (stream->sockfd); stream->sockfd = -1; } if (stream->childpid) { gint ret, i; for (i = 0; i < 4; i++) { ret = waitpid (stream->childpid, NULL, WNOHANG); if (camel_verbose_debug) fprintf ( stderr, "waitpid() for pid %d returned %d (errno %d)\n", stream->childpid, ret, ret == -1 ? errno : 0); if (ret == stream->childpid || errno == ECHILD) break; switch (i) { case 0: if (camel_verbose_debug) fprintf ( stderr, "Sending SIGTERM to pid %d\n", stream->childpid); kill (stream->childpid, SIGTERM); break; case 2: if (camel_verbose_debug) fprintf ( stderr, "Sending SIGKILL to pid %d\n", stream->childpid); kill (stream->childpid, SIGKILL); break; case 1: case 3: sleep (1); break; } } stream->childpid = 0; } return 0; } static gint stream_process_flush (CamelStream *stream, GCancellable *cancellable, GError **error) { return 0; } static void camel_stream_process_class_init (CamelStreamProcessClass *class) { GObjectClass *object_class; CamelStreamClass *stream_class; object_class = G_OBJECT_CLASS (class); object_class->finalize = stream_process_finalize; stream_class = CAMEL_STREAM_CLASS (class); stream_class->read = stream_process_read; stream_class->write = stream_process_write; stream_class->close = stream_process_close; stream_class->flush = stream_process_flush; } static void camel_stream_process_init (CamelStreamProcess *stream) { stream->sockfd = -1; stream->childpid = 0; } /** * camel_stream_process_new: * * Returns a PROCESS stream. * * Returns: the stream **/ CamelStream * camel_stream_process_new (void) { return g_object_new (CAMEL_TYPE_STREAM_PROCESS, NULL); } G_GNUC_NORETURN static void do_exec_command (gint fd, const gchar *command, gchar **env) { gint i, maxopen; /* Not a lot we can do if there's an error other than bail. */ if (dup2 (fd, 0) == -1) exit (1); if (dup2 (fd, 1) == -1) exit (1); /* What to do with stderr? Possibly put it through a separate pipe * and bring up a dialog box with its output if anything does get * spewed to it? It'd help the user understand what was going wrong * with their command, but it's hard to do cleanly. For now we just * leave it as it is. Perhaps we should close it and reopen /dev/null? */ maxopen = sysconf (_SC_OPEN_MAX); for (i = 3; i < maxopen; i++) { CHECK_CALL (fcntl (i, F_SETFD, FD_CLOEXEC)); } setsid (); #ifdef TIOCNOTTY /* Detach from the controlling tty if we have one. Otherwise, * SSH might do something stupid like trying to use it instead * of running $SSH_ASKPASS. Doh. */ if ((fd = open ("/dev/tty", O_RDONLY)) != -1) { ioctl (fd, TIOCNOTTY, NULL); close (fd); } #endif /* TIOCNOTTY */ /* Set up child's environment. We _add_ to it, don't use execle, * because otherwise we'd destroy stuff like SSH_AUTH_SOCK etc. */ for (; env && *env; env++) putenv (*env); execl ("/bin/sh", "/bin/sh", "-c", command, NULL); if (camel_verbose_debug) fprintf (stderr, "exec failed %d\n", errno); exit (1); } gint camel_stream_process_connect (CamelStreamProcess *stream, const gchar *command, const gchar **env, GError **error) { gint sockfds[2]; g_return_val_if_fail (CAMEL_IS_STREAM_PROCESS (stream), -1); g_return_val_if_fail (command != NULL, -1); if (stream->sockfd != -1 || stream->childpid) camel_stream_close (CAMEL_STREAM (stream), NULL, NULL); if (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfds)) goto fail; stream->childpid = fork (); if (!stream->childpid) { do_exec_command (sockfds[1], command, (gchar **) env); } else if (stream->childpid == -1) { close (sockfds[0]); close (sockfds[1]); stream->sockfd = -1; goto fail; } close (sockfds[1]); stream->sockfd = sockfds[0]; return 0; fail: if (errno == EINTR) g_set_error ( error, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Connection cancelled")); else g_set_error ( error, G_IO_ERROR, g_io_error_from_errno (errno), _("Could not connect with command \"%s\": %s"), command, g_strerror (errno)); return -1; }