Fix FSF address (Tobias Mueller, #470445)
[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) 2003 Ximian Inc.
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 "camel-file-utils.h"
43 #include "camel-stream-process.h"
44
45 extern int camel_verbose_debug;
46
47 static CamelObjectClass *parent_class = NULL;
48
49 /* Returns the class for a CamelStream */
50 #define CS_CLASS(so) CAMEL_STREAM_PROCESS_CLASS(CAMEL_OBJECT_GET_CLASS(so))
51
52 /* dummy implementations, for a PROCESS stream */
53 static ssize_t   stream_read       (CamelStream *stream, char *buffer, size_t n);
54 static ssize_t   stream_write      (CamelStream *stream, const char *buffer, size_t n);
55 static int       stream_close      (CamelStream *stream);
56 static int       stream_flush      (CamelStream *stream);
57
58 static void
59 camel_stream_process_finalise (CamelObject *object)
60 {
61         /* Ensure we clean up after ourselves -- kill
62            the child process and reap it. */
63         stream_close (CAMEL_STREAM (object));
64 }
65
66 static void
67 camel_stream_process_class_init (CamelStreamProcessClass *camel_stream_process_class)
68 {
69         CamelStreamClass *camel_stream_class = (CamelStreamClass *) camel_stream_process_class;
70         
71         parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE);
72         
73         /* virtual method definition */
74         camel_stream_class->read = stream_read;
75         camel_stream_class->write = stream_write;
76         camel_stream_class->close = stream_close;
77         camel_stream_class->flush = stream_flush;
78 }
79
80 static void
81 camel_stream_process_init (gpointer object, gpointer klass)
82 {
83         CamelStreamProcess *stream = CAMEL_STREAM_PROCESS (object);
84         
85         stream->sockfd = -1;
86         stream->childpid = 0;
87 }
88
89
90 CamelType
91 camel_stream_process_get_type (void)
92 {
93         static CamelType type = CAMEL_INVALID_TYPE;
94         
95         if (type == CAMEL_INVALID_TYPE) {
96                 type =  camel_type_register (camel_stream_get_type (),
97                                              "CamelStreamProcess",
98                                              sizeof (CamelStreamProcess),
99                                              sizeof (CamelStreamProcessClass),
100                                              (CamelObjectClassInitFunc) camel_stream_process_class_init,
101                                              NULL,
102                                              (CamelObjectInitFunc) camel_stream_process_init,
103                                              (CamelObjectFinalizeFunc) camel_stream_process_finalise);
104         }
105         
106         return type;
107 }
108
109 /**
110  * camel_stream_process_new:
111  *
112  * Returns a PROCESS stream.
113  *
114  * Return value: the stream
115  **/
116 CamelStream *
117 camel_stream_process_new (void)
118 {
119         return (CamelStream *) camel_object_new (camel_stream_process_get_type ());
120 }
121
122
123 static ssize_t
124 stream_read (CamelStream *stream, char *buffer, size_t n)
125 {
126         CamelStreamProcess *stream_process = CAMEL_STREAM_PROCESS (stream);
127         
128         return camel_read (stream_process->sockfd, buffer, n);
129 }
130
131 static ssize_t
132 stream_write (CamelStream *stream, const char *buffer, size_t n)
133 {
134         CamelStreamProcess *stream_process = CAMEL_STREAM_PROCESS (stream);
135         
136         return camel_write (stream_process->sockfd, buffer, n);
137 }
138
139 static int
140 stream_flush (CamelStream *stream)
141 {
142         return 0;
143 }
144
145 static int
146 stream_close (CamelStream *object)
147 {
148         CamelStreamProcess *stream = CAMEL_STREAM_PROCESS (object);
149         
150         if (camel_verbose_debug)
151                 fprintf (stderr, "Process stream close. sockfd %d, childpid %d\n",
152                          stream->sockfd, stream->childpid);
153         
154         if (stream->sockfd != -1) {
155                 close (stream->sockfd);
156                 stream->sockfd = -1;
157         }
158         
159         if (stream->childpid) {
160                 int ret, i;
161                 for (i = 0; i < 4; i++) {
162                         ret = waitpid (stream->childpid, NULL, WNOHANG);
163                         if (camel_verbose_debug)
164                                 fprintf (stderr, "waitpid() for pid %d returned %d (errno %d)\n",
165                                          stream->childpid, ret, ret == -1 ? errno : 0);
166                         if (ret == stream->childpid || errno == ECHILD)
167                                 break;
168                         switch (i) {
169                         case 0:
170                                 if (camel_verbose_debug)
171                                         fprintf (stderr, "Sending SIGTERM to pid %d\n",
172                                                  stream->childpid);
173                                 kill (stream->childpid, SIGTERM);
174                                 break;
175                         case 2:
176                                 if (camel_verbose_debug)
177                                         fprintf (stderr, "Sending SIGKILL to pid %d\n",
178                                                  stream->childpid);
179                                 kill (stream->childpid, SIGKILL);
180                                 break;
181                         case 1:
182                         case 3:
183                                 sleep (1);
184                                 break;
185                         }
186                 }
187                 
188                 stream->childpid = 0;
189         }
190         
191         return 0;
192 }
193
194 static void
195 do_exec_command (int fd, const char *command, char **env)
196 {
197         int i, maxopen;
198         
199         /* Not a lot we can do if there's an error other than bail. */
200         if (dup2 (fd, 0) == -1)
201                 exit (1);
202         if (dup2 (fd, 1) == -1)
203                 exit (1);
204         
205         /* What to do with stderr? Possibly put it through a separate pipe
206            and bring up a dialog box with its output if anything does get
207            spewed to it? It'd help the user understand what was going wrong
208            with their command, but it's hard to do cleanly. For now we just
209            leave it as it is. Perhaps we should close it and reopen /dev/null? */
210         
211         maxopen = sysconf (_SC_OPEN_MAX);
212         for (i = 3; i < maxopen; i++)
213                 fcntl (i, F_SETFD, FD_CLOEXEC);
214         
215         setsid ();
216 #ifdef TIOCNOTTY
217         /* Detach from the controlling tty if we have one. Otherwise, 
218            SSH might do something stupid like trying to use it instead 
219            of running $SSH_ASKPASS. Doh. */
220         if ((fd = open ("/dev/tty", O_RDONLY)) != -1) {
221                 ioctl (fd, TIOCNOTTY, NULL);
222                 close (fd);
223         }
224 #endif /* TIOCNOTTY */
225         
226         /* Set up child's environment. We _add_ to it, don't use execle, 
227            because otherwise we'd destroy stuff like SSH_AUTH_SOCK etc. */
228         for ( ; env && *env; env++)
229                 putenv(*env);
230         
231         execl ("/bin/sh", "/bin/sh", "-c", command, NULL);
232         
233         if (camel_verbose_debug)
234                 fprintf (stderr, "exec failed %d\n", errno);
235         
236         exit (1);
237 }
238
239 int
240 camel_stream_process_connect (CamelStreamProcess *stream, const char *command, const char **env)
241 {
242         int sockfds[2];
243         
244         if (stream->sockfd != -1 || stream->childpid)
245                 stream_close (CAMEL_STREAM (stream));
246         
247         if (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfds))
248                 return -1;
249         
250         stream->childpid = fork ();
251         if (!stream->childpid) {
252                 do_exec_command (sockfds[1], command, (char **)env);
253         } else if (stream->childpid == -1) {
254                 close (sockfds[0]);
255                 close (sockfds[1]);
256                 stream->sockfd = -1;
257                 return -1;
258         }
259         
260         close (sockfds[1]);
261         stream->sockfd = sockfds[0];
262         
263         return 0;
264 }