LCOV - code coverage report
Current view: top level - utils - exec.c (source / functions) Coverage Total Hit
Test: libblockdev Coverage Report Lines: 83.9 % 454 381
Test Date: 2026-01-23 09:12:16 Functions: 85.7 % 28 24
Legend: Lines: hit not hit

            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          527 : GQuark bd_utils_exec_error_quark (void)
      53              : {
      54          527 :     return g_quark_from_static_string ("g-bd-utils-exec-error-quark");
      55              : }
      56              : 
      57              : /**
      58              :  * bd_utils_get_next_task_id:
      59              :  */
      60         2783 : guint64 bd_utils_get_next_task_id (void) {
      61         2783 :     guint64 task_id = 0;
      62              : 
      63         2783 :     g_mutex_lock (&id_counter_lock);
      64         2783 :     id_counter++;
      65         2783 :     task_id = id_counter;
      66         2783 :     g_mutex_unlock (&id_counter_lock);
      67              : 
      68         2783 :     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         1269 : void bd_utils_log_task_status (guint64 task_id, const gchar *msg) {
      77         1269 :     gchar *log_msg = NULL;
      78              : 
      79         1269 :     log_msg = g_strdup_printf ("[%"G_GUINT64_FORMAT"] %s", task_id, msg);
      80         1269 :     bd_utils_log (BD_UTILS_LOG_INFO, log_msg);
      81         1269 :     g_free (log_msg);
      82         1269 : }
      83              : 
      84              : /**
      85              :  * log_running: (skip)
      86              :  *
      87              :  * Returns: id of the running task
      88              :  */
      89         2362 : static guint64 log_running (const gchar **argv) {
      90         2362 :     guint64 task_id = 0;
      91         2362 :     gchar *str_argv = NULL;
      92         2362 :     gchar *log_msg = NULL;
      93              : 
      94         2362 :     task_id = bd_utils_get_next_task_id ();
      95              : 
      96         2362 :     str_argv = g_strjoinv (" ", (gchar **) argv);
      97         2362 :     log_msg = g_strdup_printf ("Running [%"G_GUINT64_FORMAT"] %s ...", task_id, str_argv);
      98         2362 :     bd_utils_log (BD_UTILS_LOG_INFO, log_msg);
      99         2362 :     g_free (str_argv);
     100         2362 :     g_free (log_msg);
     101              : 
     102         2362 :     return task_id;
     103              : }
     104              : 
     105              : /**
     106              :  * log_out: (skip)
     107              :  *
     108              :  */
     109         2361 : static void log_out (guint64 task_id, const gchar *stdout, const gchar *stderr) {
     110         2361 :     gchar *log_msg = NULL;
     111              : 
     112         2361 :     log_msg = g_strdup_printf ("stdout[%"G_GUINT64_FORMAT"]: %s", task_id, stdout);
     113         2361 :     bd_utils_log (BD_UTILS_LOG_INFO, log_msg);
     114         2361 :     g_free (log_msg);
     115              : 
     116         2361 :     log_msg = g_strdup_printf ("stderr[%"G_GUINT64_FORMAT"]: %s", task_id, stderr);
     117         2361 :     bd_utils_log (BD_UTILS_LOG_INFO, log_msg);
     118         2361 :     g_free (log_msg);
     119              : 
     120         2361 :     return;
     121              : }
     122              : 
     123              : /**
     124              :  * log_done: (skip)
     125              :  *
     126              :  */
     127         2361 : static void log_done (guint64 task_id, gint exit_code) {
     128         2361 :     gchar *log_msg = NULL;
     129              : 
     130         2361 :     log_msg = g_strdup_printf ("...done [%"G_GUINT64_FORMAT"] (exit code: %d)", task_id, exit_code);
     131         2361 :     bd_utils_log (BD_UTILS_LOG_INFO, log_msg);
     132         2361 :     g_free (log_msg);
     133              : 
     134              : 
     135         2361 :     return;
     136              : }
     137              : 
     138         2362 : static const gchar ** _append_extra_args (const gchar **argv, const BDExtraArg **extra) {
     139         2362 :     const gchar **args = NULL;
     140         2362 :     guint args_len = 0;
     141         2362 :     const BDExtraArg **extra_p = NULL;
     142         2362 :     const gchar **arg_p = NULL;
     143         2362 :     guint i = 0;
     144              : 
     145         2362 :     if (!extra)
     146         2218 :         return NULL;
     147              : 
     148          144 :     args_len = g_strv_length ((gchar **) argv);
     149          282 :     for (extra_p = extra; *extra_p; extra_p++) {
     150          138 :         if ((*extra_p)->opt && (g_strcmp0 ((*extra_p)->opt, "") != 0))
     151          138 :             args_len++;
     152          138 :         if ((*extra_p)->val && (g_strcmp0 ((*extra_p)->val, "") != 0))
     153           31 :             args_len++;
     154              :     }
     155              : 
     156          144 :     args = g_new0 (const gchar *, args_len + 1);
     157          693 :     for (arg_p = argv; *arg_p; arg_p++)
     158          549 :         args[i++] = *arg_p;
     159          282 :     for (extra_p = extra; *extra_p; extra_p++) {
     160          138 :         if ((*extra_p)->opt && (g_strcmp0 ((*extra_p)->opt, "") != 0))
     161          138 :             args[i++] = (*extra_p)->opt;
     162          138 :         if ((*extra_p)->val && (g_strcmp0 ((*extra_p)->val, "") != 0))
     163           31 :             args[i++] = (*extra_p)->val;
     164              :     }
     165              : 
     166          144 :     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         1397 : gboolean bd_utils_exec_and_report_error (const gchar **argv, const BDExtraArg **extra, GError **error) {
     178         1397 :     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         1397 :     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            0 : gboolean bd_utils_exec_and_report_error_no_progress (const gchar **argv, const BDExtraArg **extra, GError **error) {
     193            0 :     gint status = 0;
     194              :     /* just use the "stronger" function and throw away the returned status */
     195            0 :     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          156 : gboolean bd_utils_exec_and_capture_output_no_progress (const gchar **argv, const BDExtraArg **extra, gchar **output, gchar **stderr, gint *status, GError **error) {
     210          156 :     gboolean success = FALSE;
     211          156 :     gchar *stdout_data = NULL;
     212          156 :     gchar *stderr_data = NULL;
     213          156 :     guint64 task_id = 0;
     214          156 :     const gchar **args = NULL;
     215          156 :     gint exit_status = 0;
     216          156 :     gchar **old_env = NULL;
     217          156 :     gchar **new_env = NULL;
     218          156 :     GError *l_error = NULL;
     219              : 
     220          156 :     args = _append_extra_args (argv, extra);
     221              : 
     222          156 :     old_env = g_get_environ ();
     223          156 :     new_env = g_environ_setenv (old_env, "LC_ALL", "C.UTF-8", TRUE);
     224          156 :     new_env = g_environ_unsetenv (new_env, "LANGUAGE");
     225              : 
     226          156 :     task_id = log_running (args ? args : argv);
     227          156 :     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          156 :     g_strfreev (new_env);
     230          156 :     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          156 :     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           68 :         *status = 0;
     258              : 
     259          156 :     log_out (task_id, stdout_data, stderr_data);
     260          156 :     log_done (task_id, *status);
     261              : 
     262          156 :     g_free (args);
     263          156 :     if (output)
     264          156 :         *output = stdout_data;
     265              :     else
     266            0 :         g_free (stdout_data);
     267          156 :     if (stderr)
     268          156 :         *stderr = stderr_data;
     269              :     else
     270            0 :         g_free (stderr_data);
     271              : 
     272          156 :     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           30 : gboolean bd_utils_exec_and_report_status_error (const gchar **argv, const BDExtraArg **extra, gint *status, GError **error) {
     285              :     gboolean ret;
     286           30 :     gchar *stdout_data = NULL;
     287           30 :     gchar *stderr_data = NULL;
     288              : 
     289           30 :     ret = bd_utils_exec_and_capture_output_no_progress (argv, extra, &stdout_data, &stderr_data, status, error);
     290              : 
     291           30 :     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           30 :     g_free (stdout_data);
     301           30 :     g_free (stderr_data);
     302           30 :     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      7861192 : static gchar *bd_strchr_len_null (const gchar *haystack, gssize haystack_len, const gchar needle) {
     310              :     gchar *ret;
     311              :     gchar *ret_null;
     312              : 
     313      7861192 :     ret = memchr (haystack, needle, haystack_len);
     314      7861192 :     ret_null = memchr (haystack, 0, haystack_len);
     315      7861192 :     if (ret && ret_null)
     316       823139 :         return MIN (ret, ret_null);
     317              :     else
     318      7038053 :         return MAX (ret, ret_null);
     319              : }
     320              : 
     321              : static gboolean
     322     10317139 : _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     10317139 :     gchar buf[_EXEC_BUF_SIZE] = { 0 };
     325              :     ssize_t num_read;
     326              :     gchar *line;
     327              :     gchar *newline_pos;
     328              :     int errno_saved;
     329     10317139 :     gboolean eof = FALSE;
     330              : 
     331     10317139 :     if (! *done && (poll_fd->revents & POLLIN)) {
     332              :         /* read until we get EOF (0) or error (-1), expecting EAGAIN */
     333     11263443 :         while ((num_read = read (fd, buf, _EXEC_BUF_SIZE)) > 0)
     334              :             g_string_append_len (read_buffer, buf, num_read);
     335      5444337 :         errno_saved = errno;
     336              : 
     337              :         /* process the fresh data by lines */
     338      5444337 :         if (read_buffer->len > *read_buffer_pos) {
     339              :             gchar *buf_ptr;
     340              :             gsize buf_len;
     341              : 
     342     13305529 :             while ((buf_ptr = read_buffer->str + *read_buffer_pos,
     343      7861192 :                     buf_len = read_buffer->len - *read_buffer_pos,
     344      7861192 :                     newline_pos = bd_strchr_len_null (buf_ptr, buf_len, '\n'))) {
     345      2416855 :                 line = g_strndup (buf_ptr, newline_pos - buf_ptr + 1);
     346      2416855 :                 if (prog_extract && prog_extract (line, progress))
     347          272 :                     bd_utils_report_progress (progress_id, *progress, NULL);
     348              :                 else
     349              :                     g_string_append (filtered_buffer, line);
     350      2416855 :                 g_free (line);
     351      2416855 :                 *read_buffer_pos = newline_pos - read_buffer->str + 1;
     352              :             }
     353              :         }
     354              : 
     355              :         /* read error */
     356      5444337 :         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      5444337 :         if (num_read == 0)
     364            8 :             eof = TRUE;
     365              :     }
     366              : 
     367     10317139 :     if (poll_fd->revents & POLLHUP || poll_fd->revents & POLLERR || poll_fd->revents & POLLNVAL)
     368         4407 :         eof = TRUE;
     369              : 
     370     10317139 :     if (eof) {
     371         4410 :         *done = TRUE;
     372              :         /* process the remaining buffer */
     373         4410 :         line = read_buffer->str + *read_buffer_pos;
     374              :         /* GString guarantees the buffer is always NULL-terminated. */
     375         4410 :         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     10317139 :     return TRUE;
     384              : }
     385              : 
     386         2206 : 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         2206 :     const gchar **args = NULL;
     388         2206 :     gchar *args_str = NULL;
     389         2206 :     guint64 task_id = 0;
     390         2206 :     guint64 progress_id = 0;
     391         2206 :     gchar *msg = NULL;
     392         2206 :     GPid pid = 0;
     393         2206 :     gint out_fd = 0;
     394         2206 :     gint err_fd = 0;
     395         2206 :     gint in_fd = 0;
     396         2206 :     gint child_ret = -1;
     397         2206 :     gint status = 0;
     398         2206 :     gboolean ret = FALSE;
     399         2206 :     gint poll_status = 0;
     400         2206 :     guint8 completion = 0;
     401         2206 :     struct pollfd fds[2] = { ZERO_INIT, ZERO_INIT };
     402              :     int flags;
     403         2206 :     gboolean out_done = FALSE;
     404         2206 :     gboolean err_done = FALSE;
     405              :     GString *stdout_data;
     406              :     GString *stdout_buffer;
     407              :     GString *stderr_data;
     408              :     GString *stderr_buffer;
     409         2206 :     gsize stdout_buffer_pos = 0;
     410         2206 :     gsize stderr_buffer_pos = 0;
     411         2206 :     gchar **old_env = NULL;
     412         2206 :     gchar **new_env = NULL;
     413         2206 :     gboolean success = TRUE;
     414         2206 :     GError *l_error = NULL;
     415              : 
     416         2206 :     args = _append_extra_args (argv, extra);
     417              : 
     418         2206 :     task_id = log_running (args ? args : argv);
     419              : 
     420         2206 :     old_env = g_get_environ ();
     421         2206 :     new_env = g_environ_setenv (old_env, "LC_ALL", "C.UTF-8", TRUE);
     422         2206 :     new_env = g_environ_unsetenv (new_env, "LANGUAGE");
     423              : 
     424         2206 :     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         2206 :     g_strfreev (new_env);
     429              : 
     430         2206 :     if (!ret) {
     431              :         /* error is already populated */
     432            0 :         g_free (args);
     433            0 :         return FALSE;
     434              :     }
     435              : 
     436         2206 :     args_str = g_strjoinv (" ", args ? (gchar **) args : (gchar **) argv);
     437         2206 :     msg = g_strdup_printf ("Started '%s'", args_str);
     438         2206 :     progress_id = bd_utils_report_started (msg);
     439         2206 :     g_free (args_str);
     440         2206 :     g_free (args);
     441         2206 :     g_free (msg);
     442              : 
     443              :     /* set both fds for non-blocking read */
     444         2206 :     flags = fcntl (out_fd, F_GETFL, 0);
     445         2206 :     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         2206 :     flags = fcntl (err_fd, F_GETFL, 0);
     449         2206 :     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         2206 :     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         2205 :     stdout_data = g_string_new (NULL);
     475         2205 :     stdout_buffer = g_string_new (NULL);
     476         2205 :     stderr_data = g_string_new (NULL);
     477         2205 :     stderr_buffer = g_string_new (NULL);
     478              : 
     479         2205 :     fds[0].fd = out_fd;
     480         2205 :     fds[1].fd = err_fd;
     481         2205 :     fds[0].events = POLLIN | POLLHUP | POLLERR;
     482         2205 :     fds[1].events = POLLIN | POLLHUP | POLLERR;
     483      5238262 :     while (! (out_done && err_done)) {
     484      5236057 :         poll_status = poll (fds, 2, -1 /* timeout */);
     485      5236057 :         g_warn_if_fail (poll_status != 0);  /* no timeout specified, zero should never be returned */
     486      5236057 :         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      5236057 :         if (!out_done) {
     498      5231427 :             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      5236057 :         if (!err_done) {
     507      5085712 :             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         2205 :     g_string_free (stdout_buffer, TRUE);
     517         2205 :     g_string_free (stderr_buffer, TRUE);
     518         2205 :     close (out_fd);
     519         2205 :     close (err_fd);
     520              : 
     521         2205 :     child_ret = waitpid (pid, &status, 0);
     522         2205 :     *proc_status = WEXITSTATUS (status);
     523         2205 :     if (success) {
     524         2205 :         if (child_ret > 0) {
     525         2205 :             if (*proc_status != 0) {
     526          309 :                 msg = stderr_data->len > 0 ? stderr_data->str : stdout_data->str;
     527          309 :                 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          309 :                 bd_utils_report_finished (progress_id, l_error->message);
     530          309 :                 g_propagate_error (error, l_error);
     531          309 :                 success = FALSE;
     532         1896 :             } 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         2205 :         if (success)
     553         1896 :             bd_utils_report_finished (progress_id, "Completed");
     554              :     }
     555         2205 :     log_out (task_id, stdout_data->str, stderr_data->str);
     556         2205 :     log_done (task_id, *proc_status);
     557              : 
     558         2205 :     if (success && stdout)
     559          781 :         *stdout = g_string_free (stdout_data, FALSE);
     560              :     else
     561         1424 :         g_string_free (stdout_data, TRUE);
     562         2205 :     if (success && stderr)
     563          781 :         *stderr = g_string_free (stderr_data, FALSE);
     564              :     else
     565         1424 :         g_string_free (stderr_data, TRUE);
     566              : 
     567         2205 :     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         1411 : gboolean bd_utils_exec_and_report_progress (const gchar **argv, const BDExtraArg **extra, BDUtilsProgExtract prog_extract, gint *proc_status, GError **error) {
     585         1411 :     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          788 : gboolean bd_utils_exec_and_capture_output (const gchar **argv, const BDExtraArg **extra, gchar **output, GError **error) {
     617          788 :     gint status = 0;
     618          788 :     gchar *stdout = NULL;
     619          788 :     gchar *stderr = NULL;
     620          788 :     gboolean ret = FALSE;
     621              : 
     622          788 :     ret = _utils_exec_and_report_progress (argv, extra, NULL, NULL, &status, &stdout, &stderr, error);
     623          788 :     if (!ret)
     624            7 :         return ret;
     625              : 
     626          781 :     if ((status != 0) || (g_strcmp0 ("", stdout) == 0)) {
     627           21 :         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           21 :             g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_NOOUT,
     634              :                          "Process didn't provide any data on standard output. "
     635           21 :                          "Error output: %s", stderr ? stderr : "");
     636           21 :         g_free (stderr);
     637           21 :         g_free (stdout);
     638           21 :         return FALSE;
     639              :     } else {
     640          760 :         *output = stdout;
     641          760 :         g_free (stderr);
     642          760 :         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          127 : gint bd_utils_version_cmp (const gchar *ver_string1, const gchar *ver_string2, GError **error) {
     660          127 :     gchar **v1_fields = NULL;
     661          127 :     gchar **v2_fields = NULL;
     662          127 :     guint v1_fields_len = 0;
     663          127 :     guint v2_fields_len = 0;
     664          127 :     guint64 v1_value = 0;
     665          127 :     guint64 v2_value = 0;
     666          127 :     GRegex *regex = NULL;
     667          127 :     gboolean success = FALSE;
     668          127 :     gint ret = -2;
     669              : 
     670          127 :     regex = g_regex_new ("^(\\d+)(\\.\\d+)*(-\\d)?$", 0, 0, error);
     671          127 :     if (!regex) {
     672              :         /* error is already populated */
     673            0 :         return -2;
     674              :     }
     675              : 
     676          127 :     success = g_regex_match (regex, ver_string1, 0, NULL);
     677          127 :     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          123 :     success = g_regex_match (regex, ver_string2, 0, NULL);
     683          123 :     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          119 :     g_regex_unref (regex);
     689              : 
     690          119 :     v1_fields = g_strsplit_set (ver_string1, ".-", 0);
     691          119 :     v2_fields = g_strsplit_set (ver_string2, ".-", 0);
     692          119 :     v1_fields_len = g_strv_length (v1_fields);
     693          119 :     v2_fields_len = g_strv_length (v2_fields);
     694              : 
     695          353 :     for (guint i=0; (i < v1_fields_len) && (i < v2_fields_len) && ret == -2; i++) {
     696          234 :         v1_value = g_ascii_strtoull (v1_fields[i], NULL, 0);
     697          234 :         v2_value = g_ascii_strtoull (v2_fields[i], NULL, 0);
     698          234 :         if (v1_value < v2_value)
     699           11 :             ret = -1;
     700          223 :         else if (v1_value > v2_value)
     701           88 :             ret = 1;
     702              :     }
     703              : 
     704          119 :     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          119 :     g_strfreev (v1_fields);
     714          119 :     g_strfreev (v2_fields);
     715              : 
     716          119 :     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         1843 : gboolean bd_utils_check_util_version (const gchar *util, const gchar *version, const gchar *version_arg, const gchar *version_regexp, GError **error) {
     738         1843 :     gchar *util_path = NULL;
     739         1843 :     const gchar *argv[] = {util, version_arg ? version_arg : "--version", NULL};
     740         1843 :     gchar *output = NULL;
     741         1843 :     gboolean succ = FALSE;
     742         1843 :     GRegex *regex = NULL;
     743         1843 :     GMatchInfo *match_info = NULL;
     744         1843 :     gchar *version_str = NULL;
     745         1843 :     GError *l_error = NULL;
     746              : 
     747         1843 :     util_path = g_find_program_in_path (util);
     748         1843 :     if (!util_path) {
     749           78 :         g_set_error (error, BD_UTILS_EXEC_ERROR, BD_UTILS_EXEC_ERROR_UTIL_UNAVAILABLE,
     750              :                      "The '%s' utility is not available", util);
     751           78 :         return FALSE;
     752              :     }
     753         1765 :     g_free (util_path);
     754              : 
     755         1765 :     if (!version)
     756              :         /* nothing more to do here */
     757         1659 :         return TRUE;
     758              : 
     759          106 :     succ = bd_utils_exec_and_capture_output (argv, NULL, &output, &l_error);
     760          106 :     if (!succ) {
     761              :         /* if we got nothing on STDOUT, try using STDERR data from error message */
     762           14 :         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            2 :         } 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            2 :             output = g_strdup (l_error->message);
     768            2 :             g_clear_error (&l_error);
     769              :         }
     770              :     }
     771              : 
     772          106 :     if (version_regexp) {
     773          102 :         regex = g_regex_new (version_regexp, 0, 0, error);
     774          102 :         if (!regex) {
     775            0 :             g_free (output);
     776              :             /* error is already populated */
     777            0 :             return FALSE;
     778              :         }
     779              : 
     780          102 :         succ = g_regex_match (regex, output, 0, &match_info);
     781          102 :         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          101 :         g_regex_unref (regex);
     790              : 
     791          101 :         version_str = g_match_info_fetch (match_info, 1);
     792          101 :         g_match_info_free (match_info);
     793              :     }
     794              :     else
     795            8 :         version_str = g_strstrip (g_strdup (output));
     796              : 
     797          105 :     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          105 :     g_free (output);
     806              : 
     807          105 :     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           96 :     g_free (version_str);
     820           96 :     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            0 : 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            0 :     thread_prog_func = new_prog_func;
     854              : 
     855            0 :     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            0 : 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            0 :     thread_prog_func = thread_progress_muted;
     879              : 
     880            0 :     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         3328 : guint64 bd_utils_report_started (const gchar *msg) {
     902         3328 :     guint64 task_id = 0;
     903              :     BDUtilsProgFunc current_prog_func;
     904              : 
     905         3328 :     current_prog_func = thread_prog_func != NULL ? thread_prog_func : prog_func;
     906              : 
     907         3328 :     g_mutex_lock (&task_id_counter_lock);
     908         3328 :     task_id_counter++;
     909         3328 :     task_id = task_id_counter;
     910         3328 :     g_mutex_unlock (&task_id_counter_lock);
     911              : 
     912         3328 :     if (current_prog_func)
     913            6 :         current_prog_func (task_id, BD_UTILS_PROG_STARTED, 0, (gchar *)msg);
     914         3328 :     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          583 : void bd_utils_report_progress (guint64 task_id, guint64 completion, const gchar *msg) {
     924              :     BDUtilsProgFunc current_prog_func;
     925              : 
     926          583 :     current_prog_func = thread_prog_func != NULL ? thread_prog_func : prog_func;
     927          583 :     if (current_prog_func)
     928          370 :         current_prog_func (task_id, BD_UTILS_PROG_PROGRESS, completion, (gchar *)msg);
     929          583 : }
     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         3336 : void bd_utils_report_finished (guint64 task_id, const gchar *msg) {
     937              :     BDUtilsProgFunc current_prog_func;
     938              : 
     939         3336 :     current_prog_func = thread_prog_func != NULL ? thread_prog_func : prog_func;
     940         3336 :     if (current_prog_func)
     941            5 :         current_prog_func (task_id, BD_UTILS_PROG_FINISHED, 100, (gchar *)msg);
     942         3336 : }
     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              : }
        

Generated by: LCOV version 2.0-1