Line data Source code
1 : /*
2 : * Copyright (C) 2014 Red Hat, Inc.
3 : *
4 : * This library is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU Lesser General Public
6 : * License as published by the Free Software Foundation; either
7 : * version 2.1 of the License, or (at your option) any later version.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Lesser General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU Lesser General Public
15 : * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 : *
17 : * Author: Vratislav Podzimek <vpodzime@redhat.com>
18 : */
19 :
20 : #include <glib.h>
21 : #include "exec.h"
22 : #include "extra_arg.h"
23 : #include "logging.h"
24 : #include <stdlib.h>
25 : #include <poll.h>
26 : #include <fcntl.h>
27 : #include <errno.h>
28 : #include <sys/types.h>
29 : #include <sys/wait.h>
30 : #include <unistd.h>
31 :
32 : #ifdef __clang__
33 : #define ZERO_INIT {}
34 : #else
35 : #define ZERO_INIT {0}
36 : #endif
37 :
38 :
39 : extern char **environ;
40 :
41 : static GMutex id_counter_lock;
42 : static guint64 id_counter = 0;
43 :
44 : static GMutex task_id_counter_lock;
45 : static guint64 task_id_counter = 0;
46 : static BDUtilsProgFunc prog_func = NULL;
47 : static __thread BDUtilsProgFunc thread_prog_func = NULL;
48 :
49 : /**
50 : * bd_utils_exec_error_quark: (skip)
51 : */
52 547 : GQuark bd_utils_exec_error_quark (void)
53 : {
54 547 : return g_quark_from_static_string ("g-bd-utils-exec-error-quark");
55 : }
56 :
57 : /**
58 : * bd_utils_get_next_task_id:
59 : */
60 2870 : guint64 bd_utils_get_next_task_id (void) {
61 2870 : guint64 task_id = 0;
62 :
63 2870 : g_mutex_lock (&id_counter_lock);
64 2870 : id_counter++;
65 2870 : task_id = id_counter;
66 2870 : g_mutex_unlock (&id_counter_lock);
67 :
68 2870 : return task_id;
69 : }
70 :
71 : /**
72 : * bd_utils_log_task_status:
73 : * @task_id: ID of the task the status of which is being logged
74 : * @msg: log message
75 : */
76 1331 : void bd_utils_log_task_status (guint64 task_id, const gchar *msg) {
77 1331 : gchar *log_msg = NULL;
78 :
79 1331 : log_msg = g_strdup_printf ("[%"G_GUINT64_FORMAT"] %s", task_id, msg);
80 1331 : bd_utils_log (BD_UTILS_LOG_INFO, log_msg);
81 1331 : g_free (log_msg);
82 1331 : }
83 :
84 : /**
85 : * log_running: (skip)
86 : *
87 : * Returns: id of the running task
88 : */
89 2429 : static guint64 log_running (const gchar **argv) {
90 2429 : guint64 task_id = 0;
91 2429 : gchar *str_argv = NULL;
92 2429 : gchar *log_msg = NULL;
93 :
94 2429 : task_id = bd_utils_get_next_task_id ();
95 :
96 2429 : str_argv = g_strjoinv (" ", (gchar **) argv);
97 2429 : log_msg = g_strdup_printf ("Running [%"G_GUINT64_FORMAT"] %s ...", task_id, str_argv);
98 2429 : bd_utils_log (BD_UTILS_LOG_INFO, log_msg);
99 2429 : g_free (str_argv);
100 2429 : g_free (log_msg);
101 :
102 2429 : return task_id;
103 : }
104 :
105 : /**
106 : * log_out: (skip)
107 : *
108 : */
109 2428 : static void log_out (guint64 task_id, const gchar *stdout, const gchar *stderr) {
110 2428 : gchar *log_msg = NULL;
111 :
112 2428 : log_msg = g_strdup_printf ("stdout[%"G_GUINT64_FORMAT"]: %s", task_id, stdout);
113 2428 : bd_utils_log (BD_UTILS_LOG_INFO, log_msg);
114 2428 : g_free (log_msg);
115 :
116 2428 : log_msg = g_strdup_printf ("stderr[%"G_GUINT64_FORMAT"]: %s", task_id, stderr);
117 2428 : bd_utils_log (BD_UTILS_LOG_INFO, log_msg);
118 2428 : g_free (log_msg);
119 :
120 2428 : return;
121 : }
122 :
123 : /**
124 : * log_done: (skip)
125 : *
126 : */
127 2428 : static void log_done (guint64 task_id, gint exit_code) {
128 2428 : gchar *log_msg = NULL;
129 :
130 2428 : log_msg = g_strdup_printf ("...done [%"G_GUINT64_FORMAT"] (exit code: %d)", task_id, exit_code);
131 2428 : bd_utils_log (BD_UTILS_LOG_INFO, log_msg);
132 2428 : g_free (log_msg);
133 :
134 :
135 2428 : return;
136 : }
137 :
138 2429 : static const gchar ** _append_extra_args (const gchar **argv, const BDExtraArg **extra) {
139 2429 : const gchar **args = NULL;
140 2429 : guint args_len = 0;
141 2429 : const BDExtraArg **extra_p = NULL;
142 2429 : const gchar **arg_p = NULL;
143 2429 : guint i = 0;
144 :
145 2429 : if (!extra)
146 2284 : return NULL;
147 :
148 145 : args_len = g_strv_length ((gchar **) argv);
149 284 : for (extra_p = extra; *extra_p; extra_p++) {
150 139 : if ((*extra_p)->opt && (g_strcmp0 ((*extra_p)->opt, "") != 0))
151 139 : args_len++;
152 139 : if ((*extra_p)->val && (g_strcmp0 ((*extra_p)->val, "") != 0))
153 31 : args_len++;
154 : }
155 :
156 145 : args = g_new0 (const gchar *, args_len + 1);
157 696 : for (arg_p = argv; *arg_p; arg_p++)
158 551 : args[i++] = *arg_p;
159 284 : for (extra_p = extra; *extra_p; extra_p++) {
160 139 : if ((*extra_p)->opt && (g_strcmp0 ((*extra_p)->opt, "") != 0))
161 139 : args[i++] = (*extra_p)->opt;
162 139 : if ((*extra_p)->val && (g_strcmp0 ((*extra_p)->val, "") != 0))
163 31 : args[i++] = (*extra_p)->val;
164 : }
165 :
166 145 : return args;
167 : }
168 :
169 : /**
170 : * bd_utils_exec_and_report_error:
171 : * @argv: (array zero-terminated=1): the argv array for the call
172 : * @extra: (nullable) (array zero-terminated=1): extra arguments
173 : * @error: (out) (optional): place to store error (if any)
174 : *
175 : * Returns: whether the @argv was successfully executed (no error and exit code 0) or not
176 : */
177 1428 : gboolean bd_utils_exec_and_report_error (const gchar **argv, const BDExtraArg **extra, GError **error) {
178 1428 : gint status = 0;
179 : /* just use the "stronger" function providing dumb progress reporting (just
180 : 'started' and 'finished') and throw away the returned status */
181 1428 : return bd_utils_exec_and_report_progress (argv, extra, NULL, &status, error);
182 : }
183 :
184 : /**
185 : * bd_utils_exec_and_report_error_no_progress:
186 : * @argv: (array zero-terminated=1): the argv array for the call
187 : * @extra: (nullable) (array zero-terminated=1): extra arguments
188 : * @error: (out) (optional): place to store error (if any)
189 : *
190 : * Returns: whether the @argv was successfully executed (no error and exit code 0) or not
191 : */
192 1 : gboolean bd_utils_exec_and_report_error_no_progress (const gchar **argv, const BDExtraArg **extra, GError **error) {
193 1 : gint status = 0;
194 : /* just use the "stronger" function and throw away the returned status */
195 1 : return bd_utils_exec_and_report_status_error (argv, extra, &status, error);
196 : }
197 :
198 : /**
199 : * bd_utils_exec_and_capture_output_no_progress:
200 : * @argv: (array zero-terminated=1): the argv array for the call
201 : * @extra: (nullable) (array zero-terminated=1): extra arguments
202 : * @output: (out) (optional): place to store stdout to
203 : * @stderr: (out) (optional): place to store stderr to
204 : * @status: (out): place to store the process return code
205 : * @error: (out) (optional): place to store error (if any)
206 : *
207 : * Returns: whether the @argv was successfully executed capturing the output or not
208 : */
209 157 : gboolean bd_utils_exec_and_capture_output_no_progress (const gchar **argv, const BDExtraArg **extra, gchar **output, gchar **stderr, gint *status, GError **error) {
210 157 : gboolean success = FALSE;
211 157 : gchar *stdout_data = NULL;
212 157 : gchar *stderr_data = NULL;
213 157 : guint64 task_id = 0;
214 157 : const gchar **args = NULL;
215 157 : gint exit_status = 0;
216 157 : gchar **old_env = NULL;
217 157 : gchar **new_env = NULL;
218 157 : GError *l_error = NULL;
219 :
220 157 : args = _append_extra_args (argv, extra);
221 :
222 157 : old_env = g_get_environ ();
223 157 : new_env = g_environ_setenv (old_env, "LC_ALL", "C.UTF-8", TRUE);
224 157 : new_env = g_environ_unsetenv (new_env, "LANGUAGE");
225 :
226 157 : task_id = log_running (args ? args : argv);
227 157 : success = g_spawn_sync (NULL, args ? (gchar **) args : (gchar **) argv, new_env, G_SPAWN_SEARCH_PATH,
228 : NULL, NULL, &stdout_data, &stderr_data, &exit_status, error);
229 157 : g_strfreev (new_env);
230 157 : if (!success) {
231 : /* error is already populated from the call */
232 0 : g_free (stdout_data);
233 0 : g_free (stderr_data);
234 0 : return FALSE;
235 : }
236 :
237 : /* g_spawn_sync set the status in the same way waitpid() does, we need
238 : to get the process exit code manually (this is similar to calling
239 : WEXITSTATUS but also sets the error for terminated processes */
240 :
241 : #if !GLIB_CHECK_VERSION(2, 69, 0)
242 : #define g_spawn_check_wait_status(x,y) (g_spawn_check_exit_status (x,y))
243 : #endif
244 :
245 157 : if (!g_spawn_check_wait_status (exit_status, &l_error)) {
246 88 : if (g_error_matches (l_error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED)) {
247 : /* process was terminated abnormally (e.g. using a signal) */
248 0 : g_free (stdout_data);
249 0 : g_free (stderr_data);
250 0 : g_propagate_error (error, l_error);
251 0 : return FALSE;
252 : }
253 :
254 88 : *status = l_error->code;
255 88 : g_clear_error (&l_error);
256 : } else
257 69 : *status = 0;
258 :
259 157 : log_out (task_id, stdout_data, stderr_data);
260 157 : log_done (task_id, *status);
261 :
262 157 : g_free (args);
263 157 : if (output)
264 157 : *output = stdout_data;
265 : else
266 0 : g_free (stdout_data);
267 157 : if (stderr)
268 157 : *stderr = stderr_data;
269 : else
270 0 : g_free (stderr_data);
271 :
272 157 : return TRUE;
273 : }
274 :
275 : /**
276 : * bd_utils_exec_and_report_status_error:
277 : * @argv: (array zero-terminated=1): the argv array for the call
278 : * @extra: (nullable) (array zero-terminated=1): extra arguments
279 : * @status: (out): place to store the status
280 : * @error: (out) (optional): place to store error (if any)
281 : *
282 : * Returns: whether the @argv was successfully executed (no error and exit code 0) or not
283 : */
284 31 : gboolean bd_utils_exec_and_report_status_error (const gchar **argv, const BDExtraArg **extra, gint *status, GError **error) {
285 : gboolean ret;
286 31 : gchar *stdout_data = NULL;
287 31 : gchar *stderr_data = NULL;
288 :
289 31 : ret = bd_utils_exec_and_capture_output_no_progress (argv, extra, &stdout_data, &stderr_data, status, error);
290 :
291 31 : if (ret && *status != 0) {
292 0 : if (stderr_data && (g_strcmp0 ("", stderr_data) != 0))
293 0 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
294 : "Process reported exit code %d: %s", *status, stderr_data);
295 : else
296 0 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
297 : "Process reported exit code %d: %s", *status, stdout_data);
298 0 : ret = FALSE;
299 : }
300 31 : g_free (stdout_data);
301 31 : g_free (stderr_data);
302 31 : return ret;
303 : }
304 :
305 : /* buffer size in bytes used to read from stdout and stderr */
306 : #define _EXEC_BUF_SIZE 64*1024
307 :
308 : /* similar to g_strstr_len() yet treats 'null' byte as @needle. */
309 7728897 : static gchar *bd_strchr_len_null (const gchar *haystack, gssize haystack_len, const gchar needle) {
310 : gchar *ret;
311 : gchar *ret_null;
312 :
313 7728897 : ret = memchr (haystack, needle, haystack_len);
314 7728897 : ret_null = memchr (haystack, 0, haystack_len);
315 7728897 : if (ret && ret_null)
316 820905 : return MIN (ret, ret_null);
317 : else
318 6907992 : return MAX (ret, ret_null);
319 : }
320 :
321 : static gboolean
322 9905514 : _process_fd_event (gint fd, struct pollfd *poll_fd, GString *read_buffer, GString *filtered_buffer, gsize *read_buffer_pos, gboolean *done,
323 : guint64 progress_id, guint8 *progress, BDUtilsProgExtract prog_extract, GError **error) {
324 9905514 : gchar buf[_EXEC_BUF_SIZE] = { 0 };
325 : ssize_t num_read;
326 : gchar *line;
327 : gchar *newline_pos;
328 : int errno_saved;
329 9905514 : gboolean eof = FALSE;
330 :
331 9905514 : if (! *done && (poll_fd->revents & POLLIN)) {
332 : /* read until we get EOF (0) or error (-1), expecting EAGAIN */
333 10986857 : while ((num_read = read (fd, buf, _EXEC_BUF_SIZE)) > 0)
334 : g_string_append_len (read_buffer, buf, num_read);
335 5311663 : errno_saved = errno;
336 :
337 : /* process the fresh data by lines */
338 5311663 : if (read_buffer->len > *read_buffer_pos) {
339 : gchar *buf_ptr;
340 : gsize buf_len;
341 :
342 13040560 : while ((buf_ptr = read_buffer->str + *read_buffer_pos,
343 7728897 : buf_len = read_buffer->len - *read_buffer_pos,
344 7728897 : newline_pos = bd_strchr_len_null (buf_ptr, buf_len, '\n'))) {
345 2417234 : line = g_strndup (buf_ptr, newline_pos - buf_ptr + 1);
346 2417234 : if (prog_extract && prog_extract (line, progress))
347 273 : bd_utils_report_progress (progress_id, *progress, NULL);
348 : else
349 : g_string_append (filtered_buffer, line);
350 2417234 : g_free (line);
351 2417234 : *read_buffer_pos = newline_pos - read_buffer->str + 1;
352 : }
353 : }
354 :
355 : /* read error */
356 5311663 : if (num_read < 0 && errno_saved != EAGAIN && errno_saved != EINTR) {
357 0 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
358 : "Error reading from pipe: %s", g_strerror (errno_saved));
359 0 : return FALSE;
360 : }
361 :
362 : /* EOF */
363 5311663 : if (num_read == 0)
364 6 : eof = TRUE;
365 : }
366 :
367 9905514 : if (poll_fd->revents & POLLHUP || poll_fd->revents & POLLERR || poll_fd->revents & POLLNVAL)
368 4538 : eof = TRUE;
369 :
370 9905514 : if (eof) {
371 4542 : *done = TRUE;
372 : /* process the remaining buffer */
373 4542 : line = read_buffer->str + *read_buffer_pos;
374 : /* GString guarantees the buffer is always NULL-terminated. */
375 4542 : if (strlen (line) > 0) {
376 10 : if (prog_extract && prog_extract (line, progress))
377 0 : bd_utils_report_progress (progress_id, *progress, NULL);
378 : else
379 : g_string_append (filtered_buffer, line);
380 : }
381 : }
382 :
383 9905514 : return TRUE;
384 : }
385 :
386 2272 : static gboolean _utils_exec_and_report_progress (const gchar **argv, const BDExtraArg **extra, BDUtilsProgExtract prog_extract, const gchar *input, gint *proc_status, gchar **stdout, gchar **stderr, GError **error) {
387 2272 : const gchar **args = NULL;
388 2272 : gchar *args_str = NULL;
389 2272 : guint64 task_id = 0;
390 2272 : guint64 progress_id = 0;
391 2272 : gchar *msg = NULL;
392 2272 : GPid pid = 0;
393 2272 : gint out_fd = 0;
394 2272 : gint err_fd = 0;
395 2272 : gint in_fd = 0;
396 2272 : gint child_ret = -1;
397 2272 : gint status = 0;
398 2272 : gboolean ret = FALSE;
399 2272 : gint poll_status = 0;
400 2272 : guint8 completion = 0;
401 2272 : struct pollfd fds[2] = { ZERO_INIT, ZERO_INIT };
402 : int flags;
403 2272 : gboolean out_done = FALSE;
404 2272 : gboolean err_done = FALSE;
405 : GString *stdout_data;
406 : GString *stdout_buffer;
407 : GString *stderr_data;
408 : GString *stderr_buffer;
409 2272 : gsize stdout_buffer_pos = 0;
410 2272 : gsize stderr_buffer_pos = 0;
411 2272 : gchar **old_env = NULL;
412 2272 : gchar **new_env = NULL;
413 2272 : gboolean success = TRUE;
414 2272 : GError *l_error = NULL;
415 :
416 2272 : args = _append_extra_args (argv, extra);
417 :
418 2272 : task_id = log_running (args ? args : argv);
419 :
420 2272 : old_env = g_get_environ ();
421 2272 : new_env = g_environ_setenv (old_env, "LC_ALL", "C.UTF-8", TRUE);
422 2272 : new_env = g_environ_unsetenv (new_env, "LANGUAGE");
423 :
424 2272 : ret = g_spawn_async_with_pipes (NULL, args ? (gchar**) args : (gchar**) argv, new_env,
425 : G_SPAWN_DEFAULT|G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD,
426 : NULL, NULL, &pid, input ? &in_fd : NULL, &out_fd, &err_fd, error);
427 :
428 2272 : g_strfreev (new_env);
429 :
430 2272 : if (!ret) {
431 : /* error is already populated */
432 0 : g_free (args);
433 0 : return FALSE;
434 : }
435 :
436 2272 : args_str = g_strjoinv (" ", args ? (gchar **) args : (gchar **) argv);
437 2272 : msg = g_strdup_printf ("Started '%s'", args_str);
438 2272 : progress_id = bd_utils_report_started (msg);
439 2272 : g_free (args_str);
440 2272 : g_free (args);
441 2272 : g_free (msg);
442 :
443 : /* set both fds for non-blocking read */
444 2272 : flags = fcntl (out_fd, F_GETFL, 0);
445 2272 : if (fcntl (out_fd, F_SETFL, flags | O_NONBLOCK))
446 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING,
447 : "_utils_exec_and_report_progress: Failed to set out_fd non-blocking: %m");
448 2272 : flags = fcntl (err_fd, F_GETFL, 0);
449 2272 : if (fcntl (err_fd, F_SETFL, flags | O_NONBLOCK))
450 0 : bd_utils_log_format (BD_UTILS_LOG_WARNING,
451 : "_utils_exec_and_report_progress: Failed to set err_fd non-blocking: %m");
452 :
453 2272 : if (input) {
454 7 : ssize_t num_written = 0;
455 7 : ssize_t num_written_total = 0;
456 7 : size_t len = strlen (input);
457 :
458 13 : while (len - num_written_total > 0 && (num_written = write (in_fd, input + num_written_total, len - num_written_total)) > 0)
459 6 : num_written_total += num_written;
460 :
461 7 : if (num_written < 0) {
462 1 : g_set_error (&l_error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
463 : "Failed to write to stdin of the process: %m");
464 1 : bd_utils_report_finished (progress_id, l_error->message);
465 1 : g_propagate_error (error, l_error);
466 : /* would overwrite errno, need to close as a last step */
467 1 : close (in_fd);
468 1 : return FALSE;
469 : }
470 6 : close (in_fd);
471 6 : g_warn_if_fail (len - num_written_total == 0);
472 : }
473 :
474 2271 : stdout_data = g_string_new (NULL);
475 2271 : stdout_buffer = g_string_new (NULL);
476 2271 : stderr_data = g_string_new (NULL);
477 2271 : stderr_buffer = g_string_new (NULL);
478 :
479 2271 : fds[0].fd = out_fd;
480 2271 : fds[1].fd = err_fd;
481 2271 : fds[0].events = POLLIN | POLLHUP | POLLERR;
482 2271 : fds[1].events = POLLIN | POLLHUP | POLLERR;
483 5010076 : while (! (out_done && err_done)) {
484 5007805 : poll_status = poll (fds, 2, -1 /* timeout */);
485 5007805 : g_warn_if_fail (poll_status != 0); /* no timeout specified, zero should never be returned */
486 5007805 : if (poll_status < 0) {
487 0 : if (errno == EAGAIN || errno == EINTR)
488 0 : continue;
489 0 : g_set_error (&l_error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
490 : "Failed to poll output FDs: %m");
491 0 : bd_utils_report_finished (progress_id, l_error->message);
492 0 : g_propagate_error (error, l_error);
493 0 : success = FALSE;
494 0 : break;
495 : }
496 :
497 5007805 : if (!out_done) {
498 5002845 : if (! _process_fd_event (out_fd, &fds[0], stdout_buffer, stdout_data, &stdout_buffer_pos, &out_done, progress_id, &completion, prog_extract, &l_error)) {
499 0 : bd_utils_report_finished (progress_id, l_error->message);
500 0 : g_propagate_error (error, l_error);
501 0 : success = FALSE;
502 0 : break;
503 : }
504 : }
505 :
506 5007805 : if (!err_done) {
507 4902669 : if (! _process_fd_event (err_fd, &fds[1], stderr_buffer, stderr_data, &stderr_buffer_pos, &err_done, progress_id, &completion, prog_extract, &l_error)) {
508 0 : bd_utils_report_finished (progress_id, l_error->message);
509 0 : g_propagate_error (error, l_error);
510 0 : success = FALSE;
511 0 : break;
512 : }
513 : }
514 : }
515 :
516 2271 : g_string_free (stdout_buffer, TRUE);
517 2271 : g_string_free (stderr_buffer, TRUE);
518 2271 : close (out_fd);
519 2271 : close (err_fd);
520 :
521 2271 : child_ret = waitpid (pid, &status, 0);
522 2271 : *proc_status = WEXITSTATUS (status);
523 2271 : if (success) {
524 2271 : if (child_ret > 0) {
525 2271 : if (*proc_status != 0) {
526 315 : msg = stderr_data->len > 0 ? stderr_data->str : stdout_data->str;
527 315 : g_set_error (&l_error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
528 : "Process reported exit code %d: %s", *proc_status, msg);
529 315 : bd_utils_report_finished (progress_id, l_error->message);
530 315 : g_propagate_error (error, l_error);
531 315 : success = FALSE;
532 1956 : } else if (WIFSIGNALED (status)) {
533 0 : g_set_error (&l_error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
534 : "Process killed with a signal");
535 0 : bd_utils_report_finished (progress_id, l_error->message);
536 0 : g_propagate_error (error, l_error);
537 0 : success = FALSE;
538 : }
539 0 : } else if (child_ret == -1) {
540 0 : if (errno != ECHILD) {
541 0 : errno = 0;
542 0 : g_set_error (&l_error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
543 : "Failed to wait for the process");
544 0 : bd_utils_report_finished (progress_id, l_error->message);
545 0 : g_propagate_error (error, l_error);
546 0 : success = FALSE;
547 : } else {
548 : /* no such process (the child exited before we tried to wait for it) */
549 0 : errno = 0;
550 : }
551 : }
552 2271 : if (success)
553 1956 : bd_utils_report_finished (progress_id, "Completed");
554 : }
555 2271 : log_out (task_id, stdout_data->str, stderr_data->str);
556 2271 : log_done (task_id, *proc_status);
557 :
558 2271 : if (success && stdout)
559 814 : *stdout = g_string_free (stdout_data, FALSE);
560 : else
561 1457 : g_string_free (stdout_data, TRUE);
562 2271 : if (success && stderr)
563 814 : *stderr = g_string_free (stderr_data, FALSE);
564 : else
565 1457 : g_string_free (stderr_data, TRUE);
566 :
567 2271 : return success;
568 : }
569 :
570 : /**
571 : * bd_utils_exec_and_report_progress:
572 : * @argv: (array zero-terminated=1): the argv array for the call
573 : * @extra: (nullable) (array zero-terminated=1): extra arguments
574 : * @prog_extract: (scope notified) (nullable): function for extracting progress information
575 : * @proc_status: (out): place to store the process exit status
576 : * @error: (out) (optional): place to store error (if any)
577 : *
578 : * Note that any NULL bytes read from standard output and standard error
579 : * output are treated as separators similar to newlines and @prog_extract
580 : * will be called with the respective chunk.
581 : *
582 : * Returns: whether the @argv was successfully executed (no error and exit code 0) or not
583 : */
584 1443 : gboolean bd_utils_exec_and_report_progress (const gchar **argv, const BDExtraArg **extra, BDUtilsProgExtract prog_extract, gint *proc_status, GError **error) {
585 1443 : return _utils_exec_and_report_progress (argv, extra, prog_extract, NULL, proc_status, NULL, NULL, error);
586 : }
587 :
588 : /**
589 : * bd_utils_exec_with_input:
590 : * @argv: (array zero-terminated=1): the argv array for the call
591 : * @input: (nullable): input for the executed program
592 : * @extra: (nullable) (array zero-terminated=1): extra arguments
593 : * @error: (out) (optional): place to store error (if any)
594 : *
595 : * Returns: whether the @argv was successfully executed (no error and exit code 0) or not
596 : */
597 7 : gboolean bd_utils_exec_with_input (const gchar **argv, const gchar *input, const BDExtraArg **extra, GError **error) {
598 7 : gint status = 0;
599 : /* just use the "stronger" function providing dumb progress reporting (just
600 : 'started' and 'finished') and throw away the returned status */
601 7 : return _utils_exec_and_report_progress (argv, extra, NULL, input, &status, NULL, NULL, error);
602 : }
603 :
604 : /**
605 : * bd_utils_exec_and_capture_output:
606 : * @argv: (array zero-terminated=1): the argv array for the call
607 : * @extra: (nullable) (array zero-terminated=1): extra arguments
608 : * @output: (out): variable to store output to
609 : * @error: (out) (optional): place to store error (if any)
610 : *
611 : * Note that any NULL bytes read from standard output and standard error
612 : * output will be discarded.
613 : *
614 : * Returns: whether the @argv was successfully executed capturing the output or not
615 : */
616 822 : gboolean bd_utils_exec_and_capture_output (const gchar **argv, const BDExtraArg **extra, gchar **output, GError **error) {
617 822 : gint status = 0;
618 822 : gchar *stdout = NULL;
619 822 : gchar *stderr = NULL;
620 822 : gboolean ret = FALSE;
621 :
622 822 : ret = _utils_exec_and_report_progress (argv, extra, NULL, NULL, &status, &stdout, &stderr, error);
623 822 : if (!ret)
624 8 : return ret;
625 :
626 814 : if ((status != 0) || (g_strcmp0 ("", stdout) == 0)) {
627 22 : if (status != 0)
628 0 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED,
629 : "Process reported exit code %d: %s%s", status,
630 0 : stdout ? stdout : "",
631 0 : stderr ? stderr : "");
632 : else
633 22 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_NOOUT,
634 : "Process didn't provide any data on standard output. "
635 22 : "Error output: %s", stderr ? stderr : "");
636 22 : g_free (stderr);
637 22 : g_free (stdout);
638 22 : return FALSE;
639 : } else {
640 792 : *output = stdout;
641 792 : g_free (stderr);
642 792 : return TRUE;
643 : }
644 : }
645 :
646 : /**
647 : * bd_utils_version_cmp:
648 : * @ver_string1: first version string
649 : * @ver_string2: second version string
650 : * @error: (out) (optional): place to store error (if any)
651 : *
652 : * Returns: -1, 0 or 1 if @ver_string1 is lower, the same or higher version as
653 : * @ver_string2 respectively. If an error occurs, returns -2 and @error
654 : * is set.
655 : *
656 : * **ONLY SUPPORTS VERSION STRINGS OF FORMAT `X[.Y[.Z[.Z2[.Z3...[-R]]]]]` where all components
657 : * are natural numbers!**
658 : */
659 130 : gint bd_utils_version_cmp (const gchar *ver_string1, const gchar *ver_string2, GError **error) {
660 130 : gchar **v1_fields = NULL;
661 130 : gchar **v2_fields = NULL;
662 130 : guint v1_fields_len = 0;
663 130 : guint v2_fields_len = 0;
664 130 : guint64 v1_value = 0;
665 130 : guint64 v2_value = 0;
666 130 : GRegex *regex = NULL;
667 130 : gboolean success = FALSE;
668 130 : gint ret = -2;
669 :
670 130 : regex = g_regex_new ("^(\\d+)(\\.\\d+)*(-\\d)?$", 0, 0, error);
671 130 : if (!regex) {
672 : /* error is already populated */
673 0 : return -2;
674 : }
675 :
676 130 : success = g_regex_match (regex, ver_string1, 0, NULL);
677 130 : if (!success) {
678 4 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_INVAL_VER,
679 : "Invalid or unsupported version (1) format: %s", ver_string1);
680 4 : return -2;
681 : }
682 126 : success = g_regex_match (regex, ver_string2, 0, NULL);
683 126 : if (!success) {
684 4 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_INVAL_VER,
685 : "Invalid or unsupported version (2) format: %s", ver_string2);
686 4 : return -2;
687 : }
688 122 : g_regex_unref (regex);
689 :
690 122 : v1_fields = g_strsplit_set (ver_string1, ".-", 0);
691 122 : v2_fields = g_strsplit_set (ver_string2, ".-", 0);
692 122 : v1_fields_len = g_strv_length (v1_fields);
693 122 : v2_fields_len = g_strv_length (v2_fields);
694 :
695 362 : for (guint i=0; (i < v1_fields_len) && (i < v2_fields_len) && ret == -2; i++) {
696 240 : v1_value = g_ascii_strtoull (v1_fields[i], NULL, 0);
697 240 : v2_value = g_ascii_strtoull (v2_fields[i], NULL, 0);
698 240 : if (v1_value < v2_value)
699 11 : ret = -1;
700 229 : else if (v1_value > v2_value)
701 91 : ret = 1;
702 : }
703 :
704 122 : if (ret == -2) {
705 20 : if (v1_fields_len < v2_fields_len)
706 3 : ret = -1;
707 17 : else if (v1_fields_len > v2_fields_len)
708 3 : ret = 1;
709 : else
710 14 : ret = 0;
711 : }
712 :
713 122 : g_strfreev (v1_fields);
714 122 : g_strfreev (v2_fields);
715 :
716 122 : return ret;
717 : }
718 :
719 : /**
720 : * bd_utils_check_util_version:
721 : * @util: name of the utility to check
722 : * @version: (nullable): minimum required version of the utility or %NULL
723 : * if no version is required
724 : * @version_arg: (nullable): argument to use with the @util to get version
725 : * info or %NULL to use "--version"
726 : * @version_regexp: (nullable): regexp to extract version from the version
727 : * info or %NULL if only version is printed by "$ @util @version_arg"
728 : * @error: (out) (optional): place to store error (if any)
729 : *
730 : * Note: Both supplied @version and parsed result using @version_regexp must be in format
731 : * `X[.Y[.Z[.Z2[.Z3...[-R]]]]]` where all components are natural numbers, see
732 : * %bd_utils_version_cmp for details.
733 : *
734 : * Returns: whether the @util is available in a version >= @version or not
735 : * (@error is set in such case).
736 : */
737 1858 : gboolean bd_utils_check_util_version (const gchar *util, const gchar *version, const gchar *version_arg, const gchar *version_regexp, GError **error) {
738 1858 : gchar *util_path = NULL;
739 1858 : const gchar *argv[] = {util, version_arg ? version_arg : "--version", NULL};
740 1858 : gchar *output = NULL;
741 1858 : gboolean succ = FALSE;
742 1858 : GRegex *regex = NULL;
743 1858 : GMatchInfo *match_info = NULL;
744 1858 : gchar *version_str = NULL;
745 1858 : GError *l_error = NULL;
746 :
747 1858 : util_path = g_find_program_in_path (util);
748 1858 : if (!util_path) {
749 84 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_UTIL_UNAVAILABLE,
750 : "The '%s' utility is not available", util);
751 84 : return FALSE;
752 : }
753 1774 : g_free (util_path);
754 :
755 1774 : if (!version)
756 : /* nothing more to do here */
757 1665 : return TRUE;
758 :
759 109 : succ = bd_utils_exec_and_capture_output (argv, NULL, &output, &l_error);
760 109 : if (!succ) {
761 : /* if we got nothing on STDOUT, try using STDERR data from error message */
762 15 : if (g_error_matches (l_error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_NOOUT)) {
763 12 : output = g_strdup (l_error->message);
764 12 : g_clear_error (&l_error);
765 3 : } else if (g_error_matches (l_error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_FAILED)) {
766 : /* exit status != 0, try using the output anyway */
767 3 : output = g_strdup (l_error->message);
768 3 : g_clear_error (&l_error);
769 : }
770 : }
771 :
772 109 : if (version_regexp) {
773 105 : regex = g_regex_new (version_regexp, 0, 0, error);
774 105 : if (!regex) {
775 0 : g_free (output);
776 : /* error is already populated */
777 0 : return FALSE;
778 : }
779 :
780 105 : succ = g_regex_match (regex, output, 0, &match_info);
781 105 : if (!succ) {
782 1 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_UTIL_UNKNOWN_VER,
783 : "Failed to determine %s's version from: %s", util, output);
784 1 : g_free (output);
785 1 : g_regex_unref (regex);
786 1 : g_match_info_free (match_info);
787 1 : return FALSE;
788 : }
789 104 : g_regex_unref (regex);
790 :
791 104 : version_str = g_match_info_fetch (match_info, 1);
792 104 : g_match_info_free (match_info);
793 : }
794 : else
795 8 : version_str = g_strstrip (g_strdup (output));
796 :
797 108 : if (!version_str || (g_strcmp0 (version_str, "") == 0)) {
798 0 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_UTIL_UNKNOWN_VER,
799 : "Failed to determine %s's version from: %s", util, output);
800 0 : g_free (version_str);
801 0 : g_free (output);
802 0 : return FALSE;
803 : }
804 :
805 108 : g_free (output);
806 :
807 108 : if (bd_utils_version_cmp (version_str, version, &l_error) < 0) {
808 : /* smaller version or error */
809 9 : if (!l_error)
810 8 : g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_UTIL_LOW_VER,
811 : "Too low version of %s: %s. At least %s required.",
812 : util, version_str, version);
813 : else
814 1 : g_propagate_error (error, l_error);
815 9 : g_free (version_str);
816 9 : return FALSE;
817 : }
818 :
819 99 : g_free (version_str);
820 99 : return TRUE;
821 : }
822 :
823 : /**
824 : * bd_utils_init_prog_reporting:
825 : * @new_prog_func: (nullable) (scope notified): progress reporting function to
826 : * use or %NULL to reset to default
827 : * @error: (out) (optional): place to store error (if any)
828 : *
829 : * Returns: whether progress reporting was successfully initialized or not
830 : */
831 6 : gboolean bd_utils_init_prog_reporting (BDUtilsProgFunc new_prog_func, GError **error G_GNUC_UNUSED) {
832 : /* XXX: the error attribute will likely be used in the future when this
833 : function gets more complicated */
834 :
835 6 : prog_func = new_prog_func;
836 :
837 6 : return TRUE;
838 : }
839 :
840 : /**
841 : * bd_utils_init_prog_reporting_thread:
842 : * @new_prog_func: (nullable) (scope notified): progress reporting function to
843 : * use on current thread or %NULL
844 : * to reset to default or global
845 : * @error: (out) (optional): place to store error (if any)
846 : *
847 : * Returns: whether progress reporting was successfully initialized or not
848 : */
849 1 : gboolean bd_utils_init_prog_reporting_thread (BDUtilsProgFunc new_prog_func, GError **error G_GNUC_UNUSED) {
850 : /* XXX: the error attribute will likely be used in the future when this
851 : function gets more complicated */
852 :
853 1 : thread_prog_func = new_prog_func;
854 :
855 1 : return TRUE;
856 : }
857 :
858 0 : static void thread_progress_muted (guint64 task_id G_GNUC_UNUSED, BDUtilsProgStatus status G_GNUC_UNUSED,
859 : guint8 completion G_GNUC_UNUSED, gchar *msg G_GNUC_UNUSED) {
860 : /* This function serves as a special value for the progress reporting
861 : * function to detect that nothing is done here. If clients use their own
862 : * empty function then bd_utils_prog_reporting_initialized will return TRUE
863 : * but with this function here it returns FALSE.
864 : */
865 0 : }
866 :
867 : /**
868 : * bd_utils_mute_prog_reporting_thread:
869 : * @error: (out) (optional): place to store error (if any)
870 : *
871 : * Returns: whether progress reporting for the current thread was successfully
872 : * muted (deinitialized even in presence of a global reporting function) or not
873 : */
874 1 : gboolean bd_utils_mute_prog_reporting_thread (GError **error G_GNUC_UNUSED) {
875 : /* XXX: the error attribute will likely be used in the future when this
876 : function gets more complicated */
877 :
878 1 : thread_prog_func = thread_progress_muted;
879 :
880 1 : return TRUE;
881 : }
882 :
883 : /**
884 : * bd_utils_prog_reporting_initialized:
885 : *
886 : * Returns: TRUE if progress reporting has been initialized, i.e. a reporting
887 : * function was set up with either bd_utils_init_prog_reporting or
888 : * bd_utils_init_prog_reporting_thread (takes precedence). FALSE if
889 : * bd_utils_mute_prog_reporting_thread was used to mute the thread.
890 : */
891 20 : gboolean bd_utils_prog_reporting_initialized (void) {
892 20 : return (thread_prog_func != NULL || prog_func != NULL) && thread_prog_func != thread_progress_muted;
893 : }
894 :
895 : /**
896 : * bd_utils_report_started:
897 : * @msg: message describing the started task/action
898 : *
899 : * Returns: ID of the started task/action
900 : */
901 3426 : guint64 bd_utils_report_started (const gchar *msg) {
902 3426 : guint64 task_id = 0;
903 : BDUtilsProgFunc current_prog_func;
904 :
905 3426 : current_prog_func = thread_prog_func != NULL ? thread_prog_func : prog_func;
906 :
907 3426 : g_mutex_lock (&task_id_counter_lock);
908 3426 : task_id_counter++;
909 3426 : task_id = task_id_counter;
910 3426 : g_mutex_unlock (&task_id_counter_lock);
911 :
912 3426 : if (current_prog_func)
913 6 : current_prog_func (task_id, BD_UTILS_PROG_STARTED, 0, (gchar *)msg);
914 3426 : return task_id;
915 : }
916 :
917 : /**
918 : * bd_utils_report_progress:
919 : * @task_id: ID of the task/action
920 : * @completion: percentage of completion
921 : * @msg: message describing the status of the task/action
922 : */
923 584 : void bd_utils_report_progress (guint64 task_id, guint64 completion, const gchar *msg) {
924 : BDUtilsProgFunc current_prog_func;
925 :
926 584 : current_prog_func = thread_prog_func != NULL ? thread_prog_func : prog_func;
927 584 : if (current_prog_func)
928 370 : current_prog_func (task_id, BD_UTILS_PROG_PROGRESS, completion, (gchar *)msg);
929 584 : }
930 :
931 : /**
932 : * bd_utils_report_finished:
933 : * @task_id: ID of the task/action
934 : * @msg: message describing the status of the task/action
935 : */
936 3434 : void bd_utils_report_finished (guint64 task_id, const gchar *msg) {
937 : BDUtilsProgFunc current_prog_func;
938 :
939 3434 : current_prog_func = thread_prog_func != NULL ? thread_prog_func : prog_func;
940 3434 : if (current_prog_func)
941 5 : current_prog_func (task_id, BD_UTILS_PROG_FINISHED, 100, (gchar *)msg);
942 3434 : }
943 :
944 : /**
945 : * bd_utils_echo_str_to_file:
946 : * @str: string to write to @file_path
947 : * @file_path: path to file
948 : * @error: (out) (optional): place to store error (if any)
949 : *
950 : * Returns: whether the @str was successfully written to @file_path
951 : * or not.
952 : */
953 1 : gboolean bd_utils_echo_str_to_file (const gchar *str, const gchar *file_path, GError **error) {
954 1 : GIOChannel *out_file = NULL;
955 1 : gsize bytes_written = 0;
956 :
957 1 : out_file = g_io_channel_new_file (file_path, "w", error);
958 1 : if (!out_file || g_io_channel_write_chars (out_file, str, -1, &bytes_written, error) != G_IO_STATUS_NORMAL) {
959 0 : g_prefix_error (error, "Failed to write '%s' to file '%s': ", str, file_path);
960 0 : return FALSE;
961 : }
962 1 : if (g_io_channel_shutdown (out_file, TRUE, error) != G_IO_STATUS_NORMAL) {
963 0 : g_prefix_error (error, "Failed to flush and close the file '%s': ", file_path);
964 0 : g_io_channel_unref (out_file);
965 0 : return FALSE;
966 : }
967 1 : g_io_channel_unref (out_file);
968 1 : return TRUE;
969 : }
|