1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  *  Copyright (C) 2007-2011  Kouhei Sutou <kou@clear-code.com>
4  *
5  *  This library is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Lesser General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #  include <config.h>
22 #endif /* HAVE_CONFIG_H */
23 
24 #include <stdlib.h>
25 #include <string.h>
26 #include <fcntl.h>
27 #include <signal.h>
28 #include <errno.h>
29 #include <sys/types.h>
30 
31 #ifdef HAVE_SYS_WAIT_H
32 #  include <sys/wait.h>
33 #endif
34 
35 #include <glib.h>
36 #include <glib/gstdio.h>
37 #include <gmodule.h>
38 
39 #include "cut-process.h"
40 #include "cut-experimental.h"
41 #include "cut-utils.h"
42 
43 #ifdef G_OS_WIN32
44 #  include <io.h>
45 #  define pipe(phandles) _pipe(phandles, 4096, _O_BINARY)
46 #else
47 #  include <unistd.h>
48 #endif
49 
50 #define CUT_PROCESS_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CUT_TYPE_PROCESS, CutProcessPrivate))
51 
52 typedef struct _CutProcessPrivate	CutProcessPrivate;
53 struct _CutProcessPrivate
54 {
55 #ifdef G_OS_WIN32
56     void *dummy;
57 #else
58     pid_t pid;
59     gchar *stdout_string;
60     gchar *stderr_string;
61     GString *cutter_string;
62     GIOChannel *child_io;
63     GIOChannel *parent_io;
64     GIOChannel *stdout_read_io;
65     GIOChannel *stderr_read_io;
66 #endif
67 };
68 
69 enum
70 {
71     STDOUT,
72     STDERR,
73     CUTTER_PIPE
74 };
75 
76 G_DEFINE_TYPE (CutProcess, cut_process, G_TYPE_OBJECT)
77 
78 static void dispose         (GObject               *object);
79 
80 static void
cut_process_class_init(CutProcessClass * klass)81 cut_process_class_init (CutProcessClass *klass)
82 {
83     GObjectClass *gobject_class;
84 
85     gobject_class = G_OBJECT_CLASS(klass);
86 
87     gobject_class->dispose      = dispose;
88 
89     g_type_class_add_private(gobject_class, sizeof(CutProcessPrivate));
90 }
91 
92 #ifndef G_OS_WIN32
93 static int
sane_dup2(int fd1,int fd2)94 sane_dup2 (int fd1, int fd2)
95 {
96     int ret;
97     do
98         ret = dup2(fd1, fd2);
99     while (ret < 0 && errno == EINTR);
100     return ret;
101 }
102 
103 static GIOChannel *
create_io_channel(int pipe,GIOFlags flag)104 create_io_channel (int pipe, GIOFlags flag)
105 {
106     GIOChannel *channel;
107 
108     channel = g_io_channel_unix_new(pipe);
109     g_io_channel_set_encoding(channel, NULL, NULL);
110     g_io_channel_set_flags(channel, flag, NULL);
111     g_io_channel_set_close_on_unref(channel, TRUE);
112 
113     return channel;
114 }
115 
116 static GIOChannel *
create_read_io_channel(int pipe)117 create_read_io_channel (int pipe)
118 {
119     return create_io_channel(pipe, G_IO_FLAG_IS_READABLE);
120 }
121 
122 static GIOChannel *
create_write_io_channel(int pipe)123 create_write_io_channel (int pipe)
124 {
125     return create_io_channel(pipe, G_IO_FLAG_IS_WRITEABLE);
126 }
127 
128 static gboolean
read_from_child(GIOChannel * source,GIOCondition * condition,gpointer data)129 read_from_child (GIOChannel *source, GIOCondition *condition,
130                  gpointer data)
131 {
132     CutProcessPrivate *priv = CUT_PROCESS_GET_PRIVATE(data);
133     GIOStatus status;
134     gsize bytes_read;
135     gchar buffer[4096];
136 
137     status = g_io_channel_read_chars(source, buffer,
138                                      sizeof(buffer),
139                                      &bytes_read,
140                                      NULL);
141     g_string_append_len(priv->cutter_string, buffer, bytes_read);
142     if (status == G_IO_STATUS_EOF)
143         return FALSE;
144 
145     return TRUE;
146 }
147 
148 static pid_t
prepare_pipes(CutProcess * process)149 prepare_pipes (CutProcess *process)
150 {
151     CutProcessPrivate *priv = CUT_PROCESS_GET_PRIVATE(process);
152     pid_t pid;
153     int fork_errno = 0;
154     int stdout_pipe[2];
155     int stderr_pipe[2];
156     int cutter_pipe[2];
157 
158     priv->pid = 0;
159 
160     if (pipe(stdout_pipe) < 0 ||
161         pipe(stderr_pipe) < 0 ||
162         pipe(cutter_pipe) < 0) {
163         return -1;
164     }
165 
166     pid = fork();
167     if (pid == -1)
168         fork_errno = errno;
169 
170     if (pid == 0) {
171         cut_utils_close_pipe(stdout_pipe, CUT_READ);
172         cut_utils_close_pipe(stderr_pipe, CUT_READ);
173         cut_utils_close_pipe(cutter_pipe, CUT_READ);
174 
175         if (sane_dup2(stdout_pipe[CUT_WRITE], STDOUT_FILENO) < 0 ||
176             sane_dup2(stderr_pipe[CUT_WRITE], STDERR_FILENO) < 0) {
177         }
178 
179         priv->child_io = create_write_io_channel(cutter_pipe[CUT_WRITE]);
180 
181         if (stdout_pipe[CUT_WRITE] >= 3)
182             cut_utils_close_pipe(stdout_pipe, CUT_WRITE);
183         if (stderr_pipe[CUT_WRITE] >= 3)
184             cut_utils_close_pipe(stderr_pipe, CUT_WRITE);
185     } else {
186         priv->pid = pid;
187 
188         cut_utils_close_pipe(stdout_pipe, CUT_WRITE);
189         cut_utils_close_pipe(stderr_pipe, CUT_WRITE);
190         cut_utils_close_pipe(cutter_pipe, CUT_WRITE);
191 
192         priv->parent_io = create_read_io_channel(cutter_pipe[CUT_READ]);
193         priv->stdout_read_io = create_read_io_channel(stdout_pipe[CUT_READ]);
194         priv->stderr_read_io = create_read_io_channel(stderr_pipe[CUT_READ]);
195     }
196 
197     errno = fork_errno;
198     return pid;
199 }
200 
201 static void
ensure_collect_result(CutProcess * process,unsigned int usec_timeout)202 ensure_collect_result (CutProcess *process, unsigned int usec_timeout)
203 {
204     CutProcessPrivate *priv = CUT_PROCESS_GET_PRIVATE(process);
205 
206     /* workaround since g_io_add_watch() does not work I expect. */
207     while (read_from_child(priv->parent_io, NULL, process))
208         ;
209 }
210 #endif
211 
212 static void
cut_process_init(CutProcess * process)213 cut_process_init (CutProcess *process)
214 {
215 #ifndef G_OS_WIN32
216     CutProcessPrivate *priv = CUT_PROCESS_GET_PRIVATE(process);
217 
218     priv->pid = 0;
219     priv->stdout_string = NULL;
220     priv->stderr_string = NULL;
221     priv->cutter_string = g_string_new(NULL);
222     priv->child_io = NULL;
223     priv->parent_io = NULL;
224 #endif
225 }
226 
227 static void
dispose(GObject * object)228 dispose (GObject *object)
229 {
230 #ifndef G_OS_WIN32
231     CutProcessPrivate *priv = CUT_PROCESS_GET_PRIVATE(object);
232 
233     if (priv->pid) {
234         kill(priv->pid, SIGKILL);
235         priv->pid = 0;
236     }
237 
238     if (priv->stdout_string) {
239         g_free(priv->stdout_string);
240         priv->stdout_string = NULL;
241     }
242 
243     if (priv->stderr_string) {
244         g_free(priv->stderr_string);
245         priv->stderr_string = NULL;
246     }
247 
248     if (priv->cutter_string) {
249         g_string_free(priv->cutter_string, TRUE);
250         priv->cutter_string = NULL;
251     }
252 
253     if (priv->child_io) {
254         g_io_channel_unref(priv->child_io);
255         priv->child_io = NULL;
256     }
257 
258     if (priv->parent_io) {
259         g_io_channel_unref(priv->parent_io);
260         priv->parent_io = NULL;
261     }
262 
263     if (priv->stdout_read_io) {
264         g_io_channel_unref(priv->stdout_read_io);
265         priv->stdout_read_io = NULL;
266     }
267 
268     if (priv->stderr_read_io) {
269         g_io_channel_unref(priv->stderr_read_io);
270         priv->stderr_read_io = NULL;
271     }
272 #endif
273 
274     G_OBJECT_CLASS(cut_process_parent_class)->dispose(object);
275 }
276 
277 CutProcess *
cut_process_new()278 cut_process_new ()
279 {
280     return g_object_new(CUT_TYPE_PROCESS, NULL);
281 }
282 
283 
284 int
cut_process_fork(CutProcess * process)285 cut_process_fork (CutProcess *process)
286 {
287 #ifdef G_OS_WIN32
288     errno = ENOSYS;
289     return -1;
290 #else
291     return prepare_pipes(process);
292 #endif
293 }
294 
295 int
cut_process_wait(CutProcess * process,unsigned int usec_timeout)296 cut_process_wait (CutProcess *process, unsigned int usec_timeout)
297 {
298 #ifdef G_OS_WIN32
299     return 0;
300 #else
301     int status;
302     pid_t pid;
303 
304     pid = CUT_PROCESS_GET_PRIVATE(process)->pid;
305 
306     if (pid == 0)
307         return 0;
308 
309     while (waitpid(pid, &status, 0) == -1 && errno == EINTR)
310         /* do nothing */;
311 
312     ensure_collect_result(process, usec_timeout);
313 
314     return status;
315 #endif
316 }
317 
318 int
cut_process_get_pid(CutProcess * process)319 cut_process_get_pid (CutProcess *process)
320 {
321 #ifdef G_OS_WIN32
322     return 0;
323 #else
324     return CUT_PROCESS_GET_PRIVATE(process)->pid;
325 #endif
326 }
327 
328 #ifndef G_OS_WIN32
329 static gchar *
read_from_channel(GIOChannel * source)330 read_from_channel (GIOChannel *source)
331 {
332     gsize bytes_read;
333     gchar *buffer = NULL;
334 
335     g_io_channel_read_to_end(source, &buffer,
336                              &bytes_read,
337                              NULL);
338     return buffer;
339 }
340 #endif
341 
342 const gchar *
cut_process_get_stdout_message(CutProcess * process)343 cut_process_get_stdout_message (CutProcess *process)
344 {
345 #ifdef G_OS_WIN32
346     return "";
347 #else
348     CutProcessPrivate *priv = CUT_PROCESS_GET_PRIVATE(process);
349 
350     if (priv->stdout_string)
351         g_free(priv->stdout_string);
352 
353     priv->stdout_string = read_from_channel(priv->stdout_read_io);
354 
355     return (const gchar*)priv->stdout_string;
356 #endif
357 }
358 
359 const gchar *
cut_process_get_stderr_message(CutProcess * process)360 cut_process_get_stderr_message (CutProcess *process)
361 {
362 #ifdef G_OS_WIN32
363     return "";
364 #else
365     CutProcessPrivate *priv = CUT_PROCESS_GET_PRIVATE(process);
366 
367     if (priv->stderr_string)
368         g_free(priv->stderr_string);
369 
370     priv->stderr_string = read_from_channel(priv->stderr_read_io);
371 
372     return (const gchar*)priv->stderr_string;
373 #endif
374 }
375 
376 gboolean
cut_process_send_test_result_to_parent(CutProcess * process,CutTestResult * result)377 cut_process_send_test_result_to_parent (CutProcess *process, CutTestResult *result)
378 {
379 #ifdef G_OS_WIN32
380     return TRUE;
381 #else
382     CutProcessPrivate *priv = CUT_PROCESS_GET_PRIVATE(process);
383     gchar *xml, *buffer;
384     gsize bytes_written, length;
385     GError *error = NULL;
386 
387     xml = cut_test_result_to_xml(result);
388     if (!xml)
389         return FALSE;
390 
391     length = strlen(xml);
392     buffer = xml;
393 
394     while (length > 0) {
395         g_io_channel_write_chars(priv->child_io,
396                                  buffer, length,
397                                  &bytes_written, &error);
398         if (error) {
399             g_error_free (error);
400             return FALSE;
401         }
402 
403         buffer += bytes_written;
404         length -= bytes_written;
405     }
406 
407     g_io_channel_flush(priv->child_io, NULL);
408 
409     g_free(xml);
410 
411     return TRUE;
412 #endif
413 }
414 
415 const gchar *
cut_process_get_result_from_child(CutProcess * process)416 cut_process_get_result_from_child (CutProcess *process)
417 {
418 #ifdef G_OS_WIN32
419     return "";
420 #else
421     return CUT_PROCESS_GET_PRIVATE(process)->cutter_string->str;
422 #endif
423 }
424 
425 void
cut_process_exit(CutProcess * process)426 cut_process_exit (CutProcess *process)
427 {
428 #ifndef G_OS_WIN32
429     CutProcessPrivate *priv = CUT_PROCESS_GET_PRIVATE(process);
430 
431     g_io_channel_unref(priv->child_io);
432     priv->child_io = NULL;
433 #endif
434     _exit(EXIT_SUCCESS);
435 }
436 
437 /*
438 vi:ts=4:nowrap:ai:expandtab:sw=4
439 */
440