/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * File-Roller * * Copyright (C) 2001, 2003, 2008, 2012 Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "file-utils.h" #include "fr-process.h" #include "glib-utils.h" #define REFRESH_RATE 20 #define BUFFER_SIZE 16384 /* -- FrCommandInfo -- */ typedef struct { GList *args; /* command to execute */ char *dir; /* working directory */ guint sticky : 1; /* whether the command must be * executed even if a previous * command has failed. */ guint ignore_error : 1; /* whether to continue to execute * other commands if this command * fails. */ ContinueFunc continue_func; gpointer continue_data; ProcFunc begin_func; gpointer begin_data; ProcFunc end_func; gpointer end_data; } FrCommandInfo; static FrCommandInfo * fr_command_info_new (void) { FrCommandInfo *info; info = g_new0 (FrCommandInfo, 1); info->args = NULL; info->dir = NULL; info->sticky = FALSE; info->ignore_error = FALSE; return info; } static void fr_command_info_free (FrCommandInfo *info) { if (info == NULL) return; if (info->args != NULL) { g_list_foreach (info->args, (GFunc) g_free, NULL); g_list_free (info->args); info->args = NULL; } if (info->dir != NULL) { g_free (info->dir); info->dir = NULL; } g_free (info); } /* -- FrChannelData -- */ static void fr_channel_data_init (FrChannelData *channel) { channel->source = NULL; channel->raw = NULL; channel->status = G_IO_STATUS_NORMAL; channel->error = NULL; } static void fr_channel_data_close_source (FrChannelData *channel) { if (channel->source != NULL) { g_io_channel_shutdown (channel->source, FALSE, NULL); g_io_channel_unref (channel->source); channel->source = NULL; } } static GIOStatus fr_channel_data_read (FrChannelData *channel) { char *line; gsize length; gsize terminator_pos; channel->status = G_IO_STATUS_NORMAL; g_clear_error (&channel->error); while ((channel->status = g_io_channel_read_line (channel->source, &line, &length, &terminator_pos, &channel->error)) == G_IO_STATUS_NORMAL) { line[terminator_pos] = 0; channel->raw = g_list_prepend (channel->raw, line); if (channel->line_func != NULL) (*channel->line_func) (line, channel->line_data); } return channel->status; } static GIOStatus fr_channel_data_flush (FrChannelData *channel) { GIOStatus status; while (((status = fr_channel_data_read (channel)) != G_IO_STATUS_ERROR) && (status != G_IO_STATUS_EOF)) /* void */; fr_channel_data_close_source (channel); return status; } static void fr_channel_data_reset (FrChannelData *channel) { fr_channel_data_close_source (channel); if (channel->raw != NULL) { g_list_foreach (channel->raw, (GFunc) g_free, NULL); g_list_free (channel->raw); channel->raw = NULL; } } static void fr_channel_data_free (FrChannelData *channel) { fr_channel_data_reset (channel); } static void fr_channel_data_set_fd (FrChannelData *channel, int fd, const char *charset) { fr_channel_data_reset (channel); channel->source = g_io_channel_unix_new (fd); g_io_channel_set_flags (channel->source, G_IO_FLAG_NONBLOCK, NULL); g_io_channel_set_buffer_size (channel->source, BUFFER_SIZE); if (charset != NULL) g_io_channel_set_encoding (channel->source, charset, NULL); } /* -- ExecData -- */ typedef struct { FrProcess *process; GCancellable *cancellable; GSimpleAsyncResult *result; gulong cancel_id; int error_command; /* command that caused an error. */ FrError *error; FrError *first_error; GList *first_error_stdout; GList *first_error_stderr; } ExecuteData; static void execute_data_free (ExecuteData *exec_data) { if (exec_data == NULL) return; if (exec_data->cancel_id != 0) g_cancellable_disconnect (exec_data->cancellable, exec_data->cancel_id); _g_object_unref (exec_data->process); _g_object_unref (exec_data->cancellable); _g_object_unref (exec_data->result); fr_error_free (exec_data->error); fr_error_free (exec_data->first_error); _g_string_list_free (exec_data->first_error_stdout); _g_string_list_free (exec_data->first_error_stderr); g_free (exec_data); } /* -- FrProcess -- */ enum { STICKY_ONLY, LAST_SIGNAL }; static guint fr_process_signals[LAST_SIGNAL] = { 0 }; static const char *try_charsets[] = { "UTF-8", "ISO-8859-1", "WINDOWS-1252" }; static int n_charsets = G_N_ELEMENTS (try_charsets); struct _FrProcessPrivate { GPtrArray *comm; /* FrCommandInfo elements. */ gint n_comm; /* total number of commands */ gint current_comm; /* currently editing command. */ GPid command_pid; guint check_timeout; gboolean running; gboolean stopping; gint current_command; gboolean use_standard_locale; gboolean sticky_only; /* whether to execute only sticky * commands. */ int current_charset; ExecuteData *exec_data; }; G_DEFINE_TYPE_WITH_PRIVATE (FrProcess, fr_process, G_TYPE_OBJECT) static void fr_process_finalize (GObject *object) { FrProcess *process; g_return_if_fail (object != NULL); g_return_if_fail (FR_IS_PROCESS (object)); process = FR_PROCESS (object); execute_data_free (process->priv->exec_data); fr_process_clear (process); g_ptr_array_free (process->priv->comm, FALSE); fr_channel_data_free (&process->out); fr_channel_data_free (&process->err); if (G_OBJECT_CLASS (fr_process_parent_class)->finalize) G_OBJECT_CLASS (fr_process_parent_class)->finalize (object); } static void fr_process_class_init (FrProcessClass *klass) { GObjectClass *gobject_class; fr_process_parent_class = g_type_class_peek_parent (klass); fr_process_signals[STICKY_ONLY] = g_signal_new ("sticky_only", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FrProcessClass, sticky_only), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = fr_process_finalize; } static void fr_process_init (FrProcess *process) { process->priv = fr_process_get_instance_private (process); process->priv->comm = g_ptr_array_new (); process->priv->n_comm = -1; process->priv->current_comm = -1; process->priv->command_pid = 0; fr_channel_data_init (&process->out); fr_channel_data_init (&process->err); process->priv->check_timeout = 0; process->priv->running = FALSE; process->priv->stopping = FALSE; process->restart = FALSE; process->priv->current_charset = -1; process->priv->use_standard_locale = FALSE; process->priv->exec_data = NULL; } FrProcess * fr_process_new (void) { return FR_PROCESS (g_object_new (FR_TYPE_PROCESS, NULL)); } void fr_process_clear (FrProcess *process) { gint i; g_return_if_fail (process != NULL); for (i = 0; i <= process->priv->n_comm; i++) { FrCommandInfo *info; info = g_ptr_array_index (process->priv->comm, i); fr_command_info_free (info); g_ptr_array_index (process->priv->comm, i) = NULL; } for (i = 0; i <= process->priv->n_comm; i++) g_ptr_array_remove_index_fast (process->priv->comm, 0); process->priv->n_comm = -1; process->priv->current_comm = -1; } void fr_process_begin_command (FrProcess *process, const char *arg) { FrCommandInfo *info; g_return_if_fail (process != NULL); info = fr_command_info_new (); info->args = g_list_prepend (NULL, g_strdup (arg)); g_ptr_array_add (process->priv->comm, info); process->priv->n_comm++; process->priv->current_comm = process->priv->n_comm; } void fr_process_begin_command_at (FrProcess *process, const char *arg, int index) { FrCommandInfo *info, *old_c_info; g_return_if_fail (process != NULL); g_return_if_fail (index >= 0 && index <= process->priv->n_comm); process->priv->current_comm = index; old_c_info = g_ptr_array_index (process->priv->comm, index); if (old_c_info != NULL) fr_command_info_free (old_c_info); info = fr_command_info_new (); info->args = g_list_prepend (NULL, g_strdup (arg)); g_ptr_array_index (process->priv->comm, index) = info; } void fr_process_set_working_dir (FrProcess *process, const char *dir) { FrCommandInfo *info; g_return_if_fail (process != NULL); g_return_if_fail (process->priv->current_comm >= 0); info = g_ptr_array_index (process->priv->comm, process->priv->current_comm); if (info->dir != NULL) g_free (info->dir); info->dir = g_strdup (dir); } void fr_process_set_working_dir_file (FrProcess *process, GFile *folder) { char *path; path = g_file_get_path (folder); fr_process_set_working_dir (process, path); g_free (path); } void fr_process_set_sticky (FrProcess *process, gboolean sticky) { FrCommandInfo *info; g_return_if_fail (process != NULL); g_return_if_fail (process->priv->current_comm >= 0); info = g_ptr_array_index (process->priv->comm, process->priv->current_comm); info->sticky = sticky; } void fr_process_set_ignore_error (FrProcess *process, gboolean ignore_error) { FrCommandInfo *info; g_return_if_fail (process != NULL); g_return_if_fail (process->priv->current_comm >= 0); info = g_ptr_array_index (process->priv->comm, process->priv->current_comm); info->ignore_error = ignore_error; } void fr_process_add_arg (FrProcess *process, const char *arg) { FrCommandInfo *info; g_return_if_fail (process != NULL); g_return_if_fail (process->priv->current_comm >= 0); info = g_ptr_array_index (process->priv->comm, process->priv->current_comm); info->args = g_list_prepend (info->args, g_strdup (arg)); } void fr_process_add_arg_concat (FrProcess *process, const char *arg1, ...) { GString *arg; va_list args; char *s; arg = g_string_new (arg1); va_start (args, arg1); while ((s = va_arg (args, char*)) != NULL) g_string_append (arg, s); va_end (args); fr_process_add_arg (process, arg->str); g_string_free (arg, TRUE); } void fr_process_add_arg_printf (FrProcess *fr_proc, const char *format, ...) { va_list args; char *arg; va_start (args, format); arg = g_strdup_vprintf (format, args); va_end (args); fr_process_add_arg (fr_proc, arg); g_free (arg); } void fr_process_add_arg_file (FrProcess *process, GFile *file) { char *path; path = g_file_get_path (file); fr_process_add_arg (process, path); g_free (path); } void fr_process_set_arg_at (FrProcess *process, int n_comm, int n_arg, const char *arg_value) { FrCommandInfo *info; GList *arg; g_return_if_fail (process != NULL); info = g_ptr_array_index (process->priv->comm, n_comm); arg = g_list_nth (info->args, n_arg); g_return_if_fail (arg != NULL); g_free (arg->data); arg->data = g_strdup (arg_value); } void fr_process_set_begin_func (FrProcess *process, ProcFunc func, gpointer func_data) { FrCommandInfo *info; g_return_if_fail (process != NULL); info = g_ptr_array_index (process->priv->comm, process->priv->current_comm); info->begin_func = func; info->begin_data = func_data; } void fr_process_set_end_func (FrProcess *process, ProcFunc func, gpointer func_data) { FrCommandInfo *info; g_return_if_fail (process != NULL); info = g_ptr_array_index (process->priv->comm, process->priv->current_comm); info->end_func = func; info->end_data = func_data; } void fr_process_set_continue_func (FrProcess *process, ContinueFunc func, gpointer func_data) { FrCommandInfo *info; g_return_if_fail (process != NULL); if (process->priv->current_comm < 0) return; info = g_ptr_array_index (process->priv->comm, process->priv->current_comm); info->continue_func = func; info->continue_data = func_data; } void fr_process_end_command (FrProcess *process) { FrCommandInfo *info; g_return_if_fail (process != NULL); info = g_ptr_array_index (process->priv->comm, process->priv->current_comm); info->args = g_list_reverse (info->args); } void fr_process_use_standard_locale (FrProcess *process, gboolean use_stand_locale) { g_return_if_fail (process != NULL); process->priv->use_standard_locale = use_stand_locale; } void fr_process_set_out_line_func (FrProcess *process, LineFunc func, gpointer data) { g_return_if_fail (process != NULL); process->out.line_func = func; process->out.line_data = data; } void fr_process_set_err_line_func (FrProcess *process, LineFunc func, gpointer data) { g_return_if_fail (process != NULL); process->err.line_func = func; process->err.line_data = data; } /* fr_process_execute */ static gboolean command_is_sticky (FrProcess *process, int i) { FrCommandInfo *info; info = g_ptr_array_index (process->priv->comm, i); return info->sticky; } static void allow_sticky_processes_only (ExecuteData *exec_data) { FrProcess *process = exec_data->process; if (! process->priv->sticky_only) { /* Remember the first error. */ exec_data->error_command = process->priv->current_command; exec_data->first_error = fr_error_copy (exec_data->error); exec_data->first_error_stdout = g_list_reverse (_g_string_list_dup (process->out.raw)); exec_data->first_error_stderr = g_list_reverse (_g_string_list_dup (process->err.raw)); } process->priv->sticky_only = TRUE; if (! process->priv->stopping) g_signal_emit (G_OBJECT (process), fr_process_signals[STICKY_ONLY], 0); } static void execute_cancelled_cb (GCancellable *cancellable, gpointer user_data) { ExecuteData *exec_data = user_data; FrProcess *process = exec_data->process; if (! process->priv->running) return; if (process->priv->stopping) return; process->priv->stopping = TRUE; exec_data->error = fr_error_new (FR_ERROR_STOPPED, 0, NULL); if (command_is_sticky (process, process->priv->current_command)) allow_sticky_processes_only (exec_data); else if (process->priv->command_pid > 0) killpg (process->priv->command_pid, SIGTERM); else { if (process->priv->check_timeout != 0) { g_source_remove (process->priv->check_timeout); process->priv->check_timeout = 0; } process->priv->command_pid = 0; fr_channel_data_close_source (&process->out); fr_channel_data_close_source (&process->err); process->priv->running = FALSE; if (exec_data->cancel_id != 0) { g_signal_handler_disconnect (exec_data->cancellable, exec_data->cancel_id); exec_data->cancel_id = 0; } g_simple_async_result_complete_in_idle (exec_data->result); } } static void _fr_process_start (ExecuteData *exec_data); static void _fr_process_restart (ExecuteData *exec_data) { exec_data->process->restart = TRUE; _fr_process_start (exec_data); } static void execute_current_command (ExecuteData *exec_data); static void child_setup (gpointer user_data) { FrProcess *process = user_data; if (process->priv->use_standard_locale) putenv ("LC_MESSAGES=C"); /* detach from the tty */ setsid (); /* create a process group to kill all the child processes when * canceling the operation. */ setpgid (0, 0); } static const char * _fr_process_get_charset (FrProcess *process) { const char *charset = NULL; if (process->priv->current_charset >= 0) charset = try_charsets[process->priv->current_charset]; else if (g_get_charset (&charset)) charset = NULL; return charset; } static void _fr_process_execute_complete_in_idle (ExecuteData *exec_data) { if (exec_data->cancel_id != 0) { g_cancellable_disconnect (exec_data->cancellable, exec_data->cancel_id); exec_data->cancel_id = 0; } g_simple_async_result_complete_in_idle (exec_data->result); } static gint check_child (gpointer data) { ExecuteData *exec_data = data; FrProcess *process = exec_data->process; FrCommandInfo *info; pid_t pid; int status; gboolean continue_process; info = g_ptr_array_index (process->priv->comm, process->priv->current_command); /* Remove check. */ g_source_remove (process->priv->check_timeout); process->priv->check_timeout = 0; if (fr_channel_data_read (&process->out) == G_IO_STATUS_ERROR) { exec_data->error = fr_error_new (FR_ERROR_IO_CHANNEL, 0, process->out.error); } else if (fr_channel_data_read (&process->err) == G_IO_STATUS_ERROR) { exec_data->error = fr_error_new (FR_ERROR_IO_CHANNEL, 0, process->err.error); } else { pid = waitpid (process->priv->command_pid, &status, WNOHANG); if (pid != process->priv->command_pid) { /* Add check again. */ process->priv->check_timeout = g_timeout_add (REFRESH_RATE, check_child, exec_data); return FALSE; } } if (info->ignore_error && (exec_data->error != NULL)) { #ifdef DEBUG { GList *scan; g_print ("** ERROR **\n"); g_print ("%s\n", exec_data->error->gerror->message); for (scan = process->err.raw; scan; scan = scan->next) g_print ("%s\n", (char *)scan->data); } #endif fr_clear_error (&exec_data->error); debug (DEBUG_INFO, "[error ignored]\n"); } else if (exec_data->error == NULL) { if (WIFEXITED (status)) { if (WEXITSTATUS (status) == 255) { exec_data->error = fr_error_new (FR_ERROR_COMMAND_NOT_FOUND, 0, NULL); } else if (WEXITSTATUS (status) != 0) { exec_data->error = fr_error_new (FR_ERROR_COMMAND_ERROR, WEXITSTATUS (status), NULL); } } else { exec_data->error = fr_error_new (FR_ERROR_EXITED_ABNORMALLY, 255, NULL); } } process->priv->command_pid = 0; if (exec_data->error == NULL) { if (fr_channel_data_flush (&process->out) == G_IO_STATUS_ERROR) exec_data->error = fr_error_new (FR_ERROR_IO_CHANNEL, 0, process->out.error); else if (fr_channel_data_flush (&process->err) == G_IO_STATUS_ERROR) exec_data->error = fr_error_new (FR_ERROR_IO_CHANNEL, 0, process->err.error); } if (info->end_func != NULL) (*info->end_func) (info->end_data); /**/ if ((exec_data->error != NULL) && (exec_data->error->type == FR_ERROR_IO_CHANNEL) && g_error_matches (exec_data->error->gerror, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE)) { if (process->priv->current_charset < n_charsets - 1) { /* try with another charset */ process->priv->current_charset++; _fr_process_restart (exec_data); return FALSE; } fr_error_free (exec_data->error); exec_data->error = fr_error_new (FR_ERROR_BAD_CHARSET, 0, exec_data->error->gerror); } /* Check whether to continue or stop the process */ continue_process = TRUE; if (info->continue_func != NULL) continue_process = (*info->continue_func) (&exec_data->error, info->continue_data); /* Execute the next command. */ if (continue_process) { if (exec_data->error != NULL) { allow_sticky_processes_only (exec_data); #ifdef DEBUG { GList *scan; g_print ("** ERROR **\n"); for (scan = process->err.raw; scan; scan = scan->next) g_print ("%s\n", (char *)scan->data); } #endif } if (process->priv->sticky_only) { do { process->priv->current_command++; } while ((process->priv->current_command <= process->priv->n_comm) && ! command_is_sticky (process, process->priv->current_command)); } else process->priv->current_command++; if (process->priv->current_command <= process->priv->n_comm) { execute_current_command (exec_data); return FALSE; } } /* Done */ process->priv->current_command = -1; process->priv->use_standard_locale = FALSE; if (process->out.raw != NULL) process->out.raw = g_list_reverse (process->out.raw); if (process->err.raw != NULL) process->err.raw = g_list_reverse (process->err.raw); process->priv->running = FALSE; process->priv->stopping = FALSE; if (process->priv->sticky_only) { /* Restore the first error. */ fr_error_free (exec_data->error); exec_data->error = fr_error_copy (exec_data->first_error); /* Restore the first error output as well. */ _g_string_list_free (process->out.raw); process->out.raw = exec_data->first_error_stdout; exec_data->first_error_stdout = NULL; _g_string_list_free (process->err.raw); process->err.raw = exec_data->first_error_stderr; exec_data->first_error_stderr = NULL; } _fr_process_execute_complete_in_idle (exec_data); return FALSE; } static void execute_current_command (ExecuteData *exec_data) { FrProcess *process = exec_data->process; FrCommandInfo *info; GList *scan; char **argv; int out_fd, err_fd; int i = 0; GError *error = NULL; debug (DEBUG_INFO, "%d/%d) ", process->priv->current_command, process->priv->n_comm); info = g_ptr_array_index (process->priv->comm, process->priv->current_command); argv = g_new (char *, g_list_length (info->args) + 1); for (scan = info->args; scan; scan = scan->next) argv[i++] = scan->data; argv[i] = NULL; #ifdef DEBUG { int j; if (process->priv->use_standard_locale) g_print ("\tLC_MESSAGES=C\n"); if (info->dir != NULL) g_print ("\tcd %s\n", info->dir); if (info->ignore_error) g_print ("\t[ignore error]\n"); g_print ("\t"); for (j = 0; j < i; j++) g_print ("%s ", argv[j]); g_print ("\n"); } #endif if (info->begin_func != NULL) (*info->begin_func) (info->begin_data); if (! g_spawn_async_with_pipes (info->dir, argv, NULL, (G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD), child_setup, process, &process->priv->command_pid, NULL, &out_fd, &err_fd, &error)) { exec_data->error = fr_error_new (FR_ERROR_SPAWN, 0, error); _fr_process_execute_complete_in_idle (exec_data); g_error_free (error); g_free (argv); return; } g_free (argv); fr_channel_data_set_fd (&process->out, out_fd, _fr_process_get_charset (process)); fr_channel_data_set_fd (&process->err, err_fd, _fr_process_get_charset (process)); process->priv->check_timeout = g_timeout_add (REFRESH_RATE, check_child, exec_data); } static void _fr_process_start (ExecuteData *exec_data) { FrProcess *process = exec_data->process; _g_string_list_free (exec_data->first_error_stdout); exec_data->first_error_stdout = NULL; _g_string_list_free (exec_data->first_error_stderr); exec_data->first_error_stderr = NULL; fr_error_free (exec_data->error); exec_data->error = NULL; fr_channel_data_reset (&process->out); fr_channel_data_reset (&process->err); process->priv->sticky_only = FALSE; process->priv->current_command = 0; process->priv->stopping = FALSE; if (process->priv->n_comm == -1) { process->priv->running = FALSE; _fr_process_execute_complete_in_idle (exec_data); } else { process->priv->running = TRUE; execute_current_command (exec_data); } } void fr_process_execute (FrProcess *process, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { ExecuteData *exec_data; g_return_if_fail (! process->priv->running); execute_data_free (process->priv->exec_data); process->priv->exec_data = exec_data = g_new0 (ExecuteData, 1); exec_data->process = g_object_ref (process); exec_data->cancellable = _g_object_ref (cancellable); exec_data->cancel_id = 0; exec_data->result = g_simple_async_result_new (G_OBJECT (process), callback, user_data, fr_process_execute); g_simple_async_result_set_op_res_gpointer (exec_data->result, exec_data, NULL); if (! process->restart) process->priv->current_charset = -1; if (cancellable != NULL) { GError *error = NULL; if (g_cancellable_set_error_if_cancelled (cancellable, &error)) { exec_data->error = fr_error_new (FR_ERROR_STOPPED, 0, error); _fr_process_execute_complete_in_idle (exec_data); g_error_free (error); return; } exec_data->cancel_id = g_cancellable_connect (cancellable, G_CALLBACK (execute_cancelled_cb), exec_data, NULL); } _fr_process_start (exec_data); } gboolean fr_process_execute_finish (FrProcess *process, GAsyncResult *result, FrError **error) { ExecuteData *exec_data; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (process), fr_process_execute), FALSE); exec_data = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)); if (exec_data->error == NULL) return TRUE; if (error != NULL) { if (exec_data->error->gerror == NULL) exec_data->error->gerror = g_error_new_literal (FR_ERROR, exec_data->error->type, ""); *error = fr_error_copy (exec_data->error); } return FALSE; } void fr_process_restart (FrProcess *process) { if (process->priv->exec_data != NULL) _fr_process_start (process->priv->exec_data); } void fr_process_cancel (FrProcess *process) { if (! process->priv->running) return; if (process->priv->exec_data == NULL) return; if (process->priv->exec_data->cancellable == NULL) return; g_cancellable_cancel (process->priv->exec_data->cancellable); }