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