build: Add an --enable-code-coverage configure option to enable gcov support
[platform/upstream/evolution-data-server.git] / camel / camel-stream-process.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
2 /* camel-stream-process.c : stream over piped process */
3
4 /*
5  *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6  *
7  *  Authors: David Woodhouse <dwmw2@infradead.org>,
8  *           Jeffrey Stedfast <fejj@ximian.com>
9  *
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of version 2 of the GNU Lesser General Public
13  * License as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
23  * USA
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <signal.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <sys/ioctl.h>
38 #include <sys/socket.h>
39 #include <sys/types.h>
40 #include <sys/wait.h>
41
42 #include <glib/gi18n-lib.h>
43
44 #include "camel-file-utils.h"
45 #include "camel-stream-process.h"
46
47 extern gint camel_verbose_debug;
48
49 G_DEFINE_TYPE (CamelStreamProcess, camel_stream_process, CAMEL_TYPE_STREAM)
50
51 static void
52 stream_process_finalize (GObject *object)
53 {
54         /* Ensure we clean up after ourselves -- kill
55          * the child process and reap it. */
56         camel_stream_close (CAMEL_STREAM (object), NULL, NULL);
57
58         /* Chain up to parent's finalize() method. */
59         G_OBJECT_CLASS (camel_stream_process_parent_class)->finalize (object);
60 }
61
62 static gssize
63 stream_process_read (CamelStream *stream,
64                      gchar *buffer,
65                      gsize n,
66                      GCancellable *cancellable,
67                      GError **error)
68 {
69         CamelStreamProcess *stream_process = CAMEL_STREAM_PROCESS (stream);
70         gint fd = stream_process->sockfd;
71
72         return camel_read (fd, buffer, n, cancellable, error);
73 }
74
75 static gssize
76 stream_process_write (CamelStream *stream,
77                       const gchar *buffer,
78                       gsize n,
79                       GCancellable *cancellable,
80                       GError **error)
81 {
82         CamelStreamProcess *stream_process = CAMEL_STREAM_PROCESS (stream);
83         gint fd = stream_process->sockfd;
84
85         return camel_write (fd, buffer, n, cancellable, error);
86 }
87
88 static gint
89 stream_process_close (CamelStream *object,
90                       GCancellable *cancellable,
91                       GError **error)
92 {
93         CamelStreamProcess *stream = CAMEL_STREAM_PROCESS (object);
94
95         if (camel_verbose_debug)
96                 fprintf (stderr, "Process stream close. sockfd %d, childpid %d\n",
97                          stream->sockfd, stream->childpid);
98
99         if (stream->sockfd != -1) {
100                 close (stream->sockfd);
101                 stream->sockfd = -1;
102         }
103
104         if (stream->childpid) {
105                 gint ret, i;
106                 for (i = 0; i < 4; i++) {
107                         ret = waitpid (stream->childpid, NULL, WNOHANG);
108                         if (camel_verbose_debug)
109                                 fprintf (stderr, "waitpid() for pid %d returned %d (errno %d)\n",
110                                          stream->childpid, ret, ret == -1 ? errno : 0);
111                         if (ret == stream->childpid || errno == ECHILD)
112                                 break;
113                         switch (i) {
114                         case 0:
115                                 if (camel_verbose_debug)
116                                         fprintf (stderr, "Sending SIGTERM to pid %d\n",
117                                                  stream->childpid);
118                                 kill (stream->childpid, SIGTERM);
119                                 break;
120                         case 2:
121                                 if (camel_verbose_debug)
122                                         fprintf (stderr, "Sending SIGKILL to pid %d\n",
123                                                  stream->childpid);
124                                 kill (stream->childpid, SIGKILL);
125                                 break;
126                         case 1:
127                         case 3:
128                                 sleep (1);
129                                 break;
130                         }
131                 }
132
133                 stream->childpid = 0;
134         }
135
136         return 0;
137 }
138
139 static gint
140 stream_process_flush (CamelStream *stream,
141                       GCancellable *cancellable,
142                       GError **error)
143 {
144         return 0;
145 }
146
147 static void
148 camel_stream_process_class_init (CamelStreamProcessClass *class)
149 {
150         GObjectClass *object_class;
151         CamelStreamClass *stream_class;
152
153         object_class = G_OBJECT_CLASS (class);
154         object_class->finalize = stream_process_finalize;
155
156         stream_class = CAMEL_STREAM_CLASS (class);
157         stream_class->read = stream_process_read;
158         stream_class->write = stream_process_write;
159         stream_class->close = stream_process_close;
160         stream_class->flush = stream_process_flush;
161 }
162
163 static void
164 camel_stream_process_init (CamelStreamProcess *stream)
165 {
166         stream->sockfd = -1;
167         stream->childpid = 0;
168 }
169
170 /**
171  * camel_stream_process_new:
172  *
173  * Returns a PROCESS stream.
174  *
175  * Returns: the stream
176  **/
177 CamelStream *
178 camel_stream_process_new (void)
179 {
180         return g_object_new (CAMEL_TYPE_STREAM_PROCESS, NULL);
181 }
182
183 G_GNUC_NORETURN static void
184 do_exec_command (gint fd,
185                  const gchar *command,
186                  gchar **env)
187 {
188         gint i, maxopen;
189
190         /* Not a lot we can do if there's an error other than bail. */
191         if (dup2 (fd, 0) == -1)
192                 exit (1);
193         if (dup2 (fd, 1) == -1)
194                 exit (1);
195
196         /* What to do with stderr? Possibly put it through a separate pipe
197          * and bring up a dialog box with its output if anything does get
198          * spewed to it? It'd help the user understand what was going wrong
199          * with their command, but it's hard to do cleanly. For now we just
200          * leave it as it is. Perhaps we should close it and reopen /dev/null? */
201
202         maxopen = sysconf (_SC_OPEN_MAX);
203         for (i = 3; i < maxopen; i++)
204                 fcntl (i, F_SETFD, FD_CLOEXEC);
205
206         setsid ();
207 #ifdef TIOCNOTTY
208         /* Detach from the controlling tty if we have one. Otherwise,
209          * SSH might do something stupid like trying to use it instead
210          * of running $SSH_ASKPASS. Doh. */
211         if ((fd = open ("/dev/tty", O_RDONLY)) != -1) {
212                 ioctl (fd, TIOCNOTTY, NULL);
213                 close (fd);
214         }
215 #endif /* TIOCNOTTY */
216
217         /* Set up child's environment. We _add_ to it, don't use execle,
218          * because otherwise we'd destroy stuff like SSH_AUTH_SOCK etc. */
219         for (; env && *env; env++)
220                 putenv (*env);
221
222         execl ("/bin/sh", "/bin/sh", "-c", command, NULL);
223
224         if (camel_verbose_debug)
225                 fprintf (stderr, "exec failed %d\n", errno);
226
227         exit (1);
228 }
229
230 gint
231 camel_stream_process_connect (CamelStreamProcess *stream,
232                               const gchar *command,
233                               const gchar **env,
234                               GError **error)
235 {
236         gint sockfds[2];
237
238         g_return_val_if_fail (CAMEL_IS_STREAM_PROCESS (stream), -1);
239         g_return_val_if_fail (command != NULL, -1);
240
241         if (stream->sockfd != -1 || stream->childpid)
242                 camel_stream_close (CAMEL_STREAM (stream), NULL, NULL);
243
244         if (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfds))
245                 goto fail;
246
247         stream->childpid = fork ();
248         if (!stream->childpid) {
249                 do_exec_command (sockfds[1], command, (gchar **) env);
250         } else if (stream->childpid == -1) {
251                 close (sockfds[0]);
252                 close (sockfds[1]);
253                 stream->sockfd = -1;
254                 goto fail;
255         }
256
257         close (sockfds[1]);
258         stream->sockfd = sockfds[0];
259
260         return 0;
261
262 fail:
263         if (errno == EINTR)
264                 g_set_error (
265                         error, G_IO_ERROR,
266                         G_IO_ERROR_CANCELLED,
267                         _("Connection cancelled"));
268         else
269                 g_set_error (
270                         error, G_IO_ERROR,
271                         g_io_error_from_errno (errno),
272                         _("Could not connect with command \"%s\": %s"),
273                         command, g_strerror (errno));
274
275         return -1;
276 }