1 /*
2    Execution routines for GNU Midnight Commander
3 
4    Copyright (C) 2003-2021
5    Free Software Foundation, Inc.
6 
7    Written by:
8    Slava Zanko <slavazanko@gmail.com>, 2013
9 
10    This file is part of the Midnight Commander.
11 
12    The Midnight Commander is free software: you can redistribute it
13    and/or modify it under the terms of the GNU General Public License as
14    published by the Free Software Foundation, either version 3 of the License,
15    or (at your option) any later version.
16 
17    The Midnight Commander is distributed in the hope that it will be useful,
18    but WITHOUT ANY WARRANTY; without even the implied warranty of
19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20    GNU General Public License for more details.
21 
22    You should have received a copy of the GNU General Public License
23    along with this program.  If not, see <http://www.gnu.org/licenses/>.
24  */
25 
26 /** \file  execute.c
27  *  \brief Source: execution routines
28  */
29 
30 #include <config.h>
31 
32 #include <signal.h>
33 #include <string.h>
34 #include <sys/stat.h>
35 #include <sys/time.h>
36 
37 #include "lib/global.h"
38 
39 #include "lib/tty/tty.h"
40 #include "lib/tty/key.h"
41 #include "lib/tty/win.h"
42 #include "lib/vfs/vfs.h"
43 #include "lib/mcconfig.h"
44 #include "lib/util.h"
45 #include "lib/strutil.h"        /* str_replace_all_substrings() */
46 #include "lib/widget.h"
47 
48 #include "filemanager/filemanager.h"
49 #include "filemanager/layout.h" /* use_dash() */
50 #include "consaver/cons.saver.h"
51 #ifdef ENABLE_SUBSHELL
52 #include "subshell/subshell.h"
53 #endif
54 #include "setup.h"              /* clear_before_exec */
55 
56 #include "execute.h"
57 
58 /*** global variables ****************************************************************************/
59 
60 int pause_after_run = pause_on_dumb_terminals;
61 
62 /*** file scope macro definitions ****************************************************************/
63 
64 /*** file scope type declarations ****************************************************************/
65 
66 /*** file scope variables ************************************************************************/
67 
68 /*** file scope functions ************************************************************************/
69 
70 void do_execute (const char *shell, const char *command, int flags);
71 void do_executev (const char *shell, int flags, char *const argv[]);
72 char *execute_get_external_cmd_opts_from_config (const char *command,
73                                                  const vfs_path_t * filename_vpath,
74                                                  long start_line);
75 
76 /* --------------------------------------------------------------------------------------------- */
77 
78 static void
edition_post_exec(void)79 edition_post_exec (void)
80 {
81     tty_enter_ca_mode ();
82 
83     /* FIXME: Missing on slang endwin? */
84     tty_reset_prog_mode ();
85     tty_flush_input ();
86 
87     tty_keypad (TRUE);
88     tty_raw_mode ();
89     channels_up ();
90     enable_mouse ();
91     enable_bracketed_paste ();
92     if (mc_global.tty.alternate_plus_minus)
93         application_keypad_mode ();
94 }
95 
96 /* --------------------------------------------------------------------------------------------- */
97 
98 static void
edition_pre_exec(void)99 edition_pre_exec (void)
100 {
101     if (clear_before_exec)
102         tty_clear_screen ();
103     else
104     {
105         if (!(mc_global.tty.console_flag != '\0' || mc_global.tty.xterm_flag))
106             printf ("\n\n");
107     }
108 
109     channels_down ();
110     disable_mouse ();
111     disable_bracketed_paste ();
112 
113     tty_reset_shell_mode ();
114     tty_keypad (FALSE);
115     tty_reset_screen ();
116 
117     numeric_keypad_mode ();
118 
119     /* on xterms: maybe endwin did not leave the terminal on the shell
120      * screen page: do it now.
121      *
122      * Do not move this before endwin: in some systems rmcup includes
123      * a call to clear screen, so it will end up clearing the shell screen.
124      */
125     tty_exit_ca_mode ();
126 }
127 
128 /* --------------------------------------------------------------------------------------------- */
129 
130 #ifdef ENABLE_SUBSHELL
131 static void
do_possible_cd(const vfs_path_t * new_dir_vpath)132 do_possible_cd (const vfs_path_t * new_dir_vpath)
133 {
134     if (!panel_cd (current_panel, new_dir_vpath, cd_exact))
135         message (D_ERROR, _("Warning"), "%s",
136                  _("The Commander can't change to the directory that\n"
137                    "the subshell claims you are in. Perhaps you have\n"
138                    "deleted your working directory, or given yourself\n"
139                    "extra access permissions with the \"su\" command?"));
140 }
141 #endif /* ENABLE_SUBSHELL */
142 
143 /* --------------------------------------------------------------------------------------------- */
144 
145 static void
do_suspend_cmd(void)146 do_suspend_cmd (void)
147 {
148     pre_exec ();
149 
150     if (mc_global.tty.console_flag != '\0' && !mc_global.tty.use_subshell)
151         handle_console (CONSOLE_RESTORE);
152 
153 #ifdef SIGTSTP
154     {
155         struct sigaction sigtstp_action;
156 
157         memset (&sigtstp_action, 0, sizeof (sigtstp_action));
158         /* Make sure that the SIGTSTP below will suspend us directly,
159            without calling ncurses' SIGTSTP handler; we *don't* want
160            ncurses to redraw the screen immediately after the SIGCONT */
161         sigaction (SIGTSTP, &startup_handler, &sigtstp_action);
162 
163         kill (getpid (), SIGTSTP);
164 
165         /* Restore previous SIGTSTP action */
166         sigaction (SIGTSTP, &sigtstp_action, NULL);
167     }
168 #endif /* SIGTSTP */
169 
170     if (mc_global.tty.console_flag != '\0' && !mc_global.tty.use_subshell)
171         handle_console (CONSOLE_SAVE);
172 
173     edition_post_exec ();
174 }
175 
176 /* --------------------------------------------------------------------------------------------- */
177 
178 static gboolean
execute_prepare_with_vfs_arg(const vfs_path_t * filename_vpath,vfs_path_t ** localcopy_vpath,time_t * mtime)179 execute_prepare_with_vfs_arg (const vfs_path_t * filename_vpath, vfs_path_t ** localcopy_vpath,
180                               time_t * mtime)
181 {
182     struct stat st;
183 
184     /* Simplest case, this file is local */
185     if ((filename_vpath == NULL && vfs_file_is_local (vfs_get_raw_current_dir ()))
186         || vfs_file_is_local (filename_vpath))
187         return TRUE;
188 
189     /* FIXME: Creation of new files on VFS is not supported */
190     if (filename_vpath == NULL)
191         return FALSE;
192 
193     *localcopy_vpath = mc_getlocalcopy (filename_vpath);
194     if (*localcopy_vpath == NULL)
195     {
196         message (D_ERROR, MSG_ERROR, _("Cannot fetch a local copy of %s"),
197                  vfs_path_as_str (filename_vpath));
198         return FALSE;
199     }
200 
201     mc_stat (*localcopy_vpath, &st);
202     *mtime = st.st_mtime;
203     return TRUE;
204 }
205 
206 /* --------------------------------------------------------------------------------------------- */
207 
208 static void
execute_cleanup_with_vfs_arg(const vfs_path_t * filename_vpath,vfs_path_t ** localcopy_vpath,time_t * mtime)209 execute_cleanup_with_vfs_arg (const vfs_path_t * filename_vpath, vfs_path_t ** localcopy_vpath,
210                               time_t * mtime)
211 {
212     if (*localcopy_vpath != NULL)
213     {
214         struct stat st;
215 
216         /*
217          * filename can be an entry on panel, it can be changed by executing
218          * the command, so make a copy.  Smarter VFS code would make the code
219          * below unnecessary.
220          */
221         mc_stat (*localcopy_vpath, &st);
222         mc_ungetlocalcopy (filename_vpath, *localcopy_vpath, *mtime != st.st_mtime);
223         vfs_path_free (*localcopy_vpath, TRUE);
224         *localcopy_vpath = NULL;
225     }
226 }
227 
228 /* --------------------------------------------------------------------------------------------- */
229 
230 static char *
execute_get_opts_from_cfg(const char * command,const char * default_str)231 execute_get_opts_from_cfg (const char *command, const char *default_str)
232 {
233     char *str_from_config;
234 
235     str_from_config =
236         mc_config_get_string_raw (mc_global.main_config, CONFIG_EXT_EDITOR_VIEWER_SECTION, command,
237                                   NULL);
238 
239     if (str_from_config == NULL)
240     {
241         mc_config_t *cfg;
242 
243         cfg = mc_config_init (global_profile_name, TRUE);
244         if (cfg == NULL)
245             return g_strdup (default_str);
246 
247         str_from_config =
248             mc_config_get_string_raw (cfg, CONFIG_EXT_EDITOR_VIEWER_SECTION, command, default_str);
249 
250         mc_config_deinit (cfg);
251     }
252 
253     return str_from_config;
254 }
255 
256 /* --------------------------------------------------------------------------------------------- */
257 /*** public functions ****************************************************************************/
258 /* --------------------------------------------------------------------------------------------- */
259 
260 char *
execute_get_external_cmd_opts_from_config(const char * command,const vfs_path_t * filename_vpath,long start_line)261 execute_get_external_cmd_opts_from_config (const char *command, const vfs_path_t * filename_vpath,
262                                            long start_line)
263 {
264     char *str_from_config, *return_str;
265     char *parameter;
266 
267     if (filename_vpath == NULL)
268         return g_strdup ("");
269 
270     parameter = g_shell_quote (vfs_path_get_last_path_str (filename_vpath));
271 
272     if (start_line <= 0)
273         return parameter;
274 
275     str_from_config = execute_get_opts_from_cfg (command, "%filename");
276 
277     return_str = str_replace_all (str_from_config, "%filename", parameter);
278     g_free (parameter);
279     g_free (str_from_config);
280     str_from_config = return_str;
281 
282     parameter = g_strdup_printf ("%ld", start_line);
283     return_str = str_replace_all (str_from_config, "%lineno", parameter);
284     g_free (parameter);
285     g_free (str_from_config);
286 
287     return return_str;
288 }
289 
290 /* --------------------------------------------------------------------------------------------- */
291 
292 void
do_executev(const char * shell,int flags,char * const argv[])293 do_executev (const char *shell, int flags, char *const argv[])
294 {
295 #ifdef ENABLE_SUBSHELL
296     vfs_path_t *new_dir_vpath = NULL;
297 #endif /* ENABLE_SUBSHELL */
298 
299     vfs_path_t *old_vfs_dir_vpath = NULL;
300 
301     if (!vfs_current_is_local ())
302         old_vfs_dir_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
303 
304     if (mc_global.mc_run_mode == MC_RUN_FULL)
305         save_cwds_stat ();
306     pre_exec ();
307     if (mc_global.tty.console_flag != '\0')
308         handle_console (CONSOLE_RESTORE);
309 
310     if (!mc_global.tty.use_subshell && *argv != NULL && (flags & EXECUTE_INTERNAL) == 0)
311     {
312         printf ("%s%s\n", mc_prompt, *argv);
313         fflush (stdout);
314     }
315 #ifdef ENABLE_SUBSHELL
316     if (mc_global.tty.use_subshell && (flags & EXECUTE_INTERNAL) == 0)
317     {
318         do_update_prompt ();
319 
320         /* We don't care if it died, higher level takes care of this */
321         invoke_subshell (*argv, VISIBLY, old_vfs_dir_vpath != NULL ? NULL : &new_dir_vpath);
322     }
323     else
324 #endif /* ENABLE_SUBSHELL */
325         my_systemv_flags (flags, shell, argv);
326 
327     if ((flags & EXECUTE_INTERNAL) == 0)
328     {
329         if ((pause_after_run == pause_always
330              || (pause_after_run == pause_on_dumb_terminals && !mc_global.tty.xterm_flag
331                  && mc_global.tty.console_flag == '\0')) && quit == 0
332 #ifdef ENABLE_SUBSHELL
333             && subshell_state != RUNNING_COMMAND
334 #endif /* ENABLE_SUBSHELL */
335             )
336         {
337             printf ("%s", _("Press any key to continue..."));
338             fflush (stdout);
339             tty_raw_mode ();
340             get_key_code (0);
341             printf ("\r\n");
342             fflush (stdout);
343         }
344         if (mc_global.tty.console_flag != '\0' && output_lines != 0 && mc_global.keybar_visible)
345         {
346             putchar ('\n');
347             fflush (stdout);
348         }
349     }
350 
351     if (mc_global.tty.console_flag != '\0')
352         handle_console (CONSOLE_SAVE);
353     edition_post_exec ();
354 
355 #ifdef ENABLE_SUBSHELL
356     if (new_dir_vpath != NULL)
357     {
358         do_possible_cd (new_dir_vpath);
359         vfs_path_free (new_dir_vpath, TRUE);
360     }
361 
362 #endif /* ENABLE_SUBSHELL */
363 
364     if (old_vfs_dir_vpath != NULL)
365     {
366         mc_chdir (old_vfs_dir_vpath);
367         vfs_path_free (old_vfs_dir_vpath, TRUE);
368     }
369 
370     if (mc_global.mc_run_mode == MC_RUN_FULL)
371     {
372         update_panels (UP_OPTIMIZE, UP_KEEPSEL);
373         update_xterm_title_path ();
374     }
375 
376     do_refresh ();
377     use_dash (TRUE);
378 }
379 
380 /* --------------------------------------------------------------------------------------------- */
381 
382 void
do_execute(const char * shell,const char * command,int flags)383 do_execute (const char *shell, const char *command, int flags)
384 {
385     GPtrArray *args_array;
386 
387     args_array = g_ptr_array_new ();
388     g_ptr_array_add (args_array, (char *) command);
389     g_ptr_array_add (args_array, NULL);
390 
391     do_executev (shell, flags, (char *const *) args_array->pdata);
392 
393     g_ptr_array_free (args_array, TRUE);
394 }
395 
396 /* --------------------------------------------------------------------------------------------- */
397 
398 /** Set up the terminal before executing a program */
399 
400 void
pre_exec(void)401 pre_exec (void)
402 {
403     use_dash (FALSE);
404     edition_pre_exec ();
405 }
406 
407 /* --------------------------------------------------------------------------------------------- */
408 /** Hide the terminal after executing a program */
409 void
post_exec(void)410 post_exec (void)
411 {
412     edition_post_exec ();
413     use_dash (TRUE);
414     repaint_screen ();
415 }
416 
417 /* --------------------------------------------------------------------------------------------- */
418 /* Executes a command */
419 
420 void
shell_execute(const char * command,int flags)421 shell_execute (const char *command, int flags)
422 {
423     char *cmd = NULL;
424 
425     if (flags & EXECUTE_HIDE)
426     {
427         cmd = g_strconcat (" ", command, (char *) NULL);
428         flags ^= EXECUTE_HIDE;
429     }
430 
431 #ifdef ENABLE_SUBSHELL
432     if (mc_global.tty.use_subshell)
433     {
434         if (subshell_state == INACTIVE)
435             do_execute (mc_global.shell->path, cmd ? cmd : command, flags | EXECUTE_AS_SHELL);
436         else
437             message (D_ERROR, MSG_ERROR, "%s", _("The shell is already running a command"));
438     }
439     else
440 #endif /* ENABLE_SUBSHELL */
441         do_execute (mc_global.shell->path, cmd ? cmd : command, flags | EXECUTE_AS_SHELL);
442 
443     g_free (cmd);
444 }
445 
446 /* --------------------------------------------------------------------------------------------- */
447 
448 void
toggle_subshell(void)449 toggle_subshell (void)
450 {
451     static gboolean message_flag = TRUE;
452 
453 #ifdef ENABLE_SUBSHELL
454     vfs_path_t *new_dir_vpath = NULL;
455 #endif /* ENABLE_SUBSHELL */
456 
457     SIG_ATOMIC_VOLATILE_T was_sigwinch = 0;
458 
459     if (!(mc_global.tty.xterm_flag || mc_global.tty.console_flag != '\0'
460           || mc_global.tty.use_subshell || output_starts_shell))
461     {
462         if (message_flag)
463             message (D_ERROR, MSG_ERROR,
464                      _("Not an xterm or Linux console;\nthe subshell cannot be toggled."));
465         message_flag = FALSE;
466         return;
467     }
468 
469     channels_down ();
470     disable_mouse ();
471     disable_bracketed_paste ();
472     if (clear_before_exec)
473         tty_clear_screen ();
474     if (mc_global.tty.alternate_plus_minus)
475         numeric_keypad_mode ();
476 #ifndef HAVE_SLANG
477     /* With slang we don't want any of this, since there
478      * is no raw_mode supported
479      */
480     tty_reset_shell_mode ();
481 #endif /* !HAVE_SLANG */
482     tty_noecho ();
483     tty_keypad (FALSE);
484     tty_reset_screen ();
485     tty_exit_ca_mode ();
486     tty_raw_mode ();
487     if (mc_global.tty.console_flag != '\0')
488         handle_console (CONSOLE_RESTORE);
489 
490 #ifdef ENABLE_SUBSHELL
491     if (mc_global.tty.use_subshell)
492     {
493         vfs_path_t **new_dir_p;
494 
495         new_dir_p = vfs_current_is_local ()? &new_dir_vpath : NULL;
496         invoke_subshell (NULL, VISIBLY, new_dir_p);
497     }
498     else
499 #endif /* ENABLE_SUBSHELL */
500     {
501         if (output_starts_shell)
502         {
503             fputs (_("Type 'exit' to return to the Midnight Commander"), stderr);
504             fputs ("\n\r\n\r", stderr);
505 
506             my_system (EXECUTE_INTERNAL, mc_global.shell->path, NULL);
507         }
508         else
509             get_key_code (0);
510     }
511 
512     if (mc_global.tty.console_flag != '\0')
513         handle_console (CONSOLE_SAVE);
514 
515     tty_enter_ca_mode ();
516 
517     tty_reset_prog_mode ();
518     tty_keypad (TRUE);
519 
520     /* Prevent screen flash when user did 'exit' or 'logout' within
521        subshell */
522     if ((quit & SUBSHELL_EXIT) != 0)
523     {
524         /* User did 'exit' or 'logout': quit MC */
525         if (quiet_quit_cmd ())
526             return;
527 
528         quit = 0;
529 #ifdef ENABLE_SUBSHELL
530         /* restart subshell */
531         if (mc_global.tty.use_subshell)
532             init_subshell ();
533 #endif /* ENABLE_SUBSHELL */
534     }
535 
536     enable_mouse ();
537     enable_bracketed_paste ();
538     channels_up ();
539     if (mc_global.tty.alternate_plus_minus)
540         application_keypad_mode ();
541 
542     /* HACK:
543      * Save sigwinch flag that will be reset in mc_refresh() called via update_panels().
544      * There is some problem with screen redraw in ncurses-based mc in this situation.
545      */
546     was_sigwinch = tty_got_winch ();
547     tty_flush_winch ();
548 
549 #ifdef ENABLE_SUBSHELL
550     if (mc_global.tty.use_subshell)
551     {
552         if (mc_global.mc_run_mode == MC_RUN_FULL)
553         {
554             if (new_dir_vpath != NULL)
555                 do_possible_cd (new_dir_vpath);
556         }
557         else if (new_dir_vpath != NULL && mc_chdir (new_dir_vpath) != -1)
558             vfs_setup_cwd ();
559     }
560 
561     vfs_path_free (new_dir_vpath, TRUE);
562 #endif /* ENABLE_SUBSHELL */
563 
564     if (mc_global.mc_run_mode == MC_RUN_FULL)
565     {
566         update_panels (UP_OPTIMIZE, UP_KEEPSEL);
567         update_xterm_title_path ();
568     }
569 
570     if (was_sigwinch != 0 || tty_got_winch ())
571         dialog_change_screen_size ();
572     else
573         repaint_screen ();
574 }
575 
576 /* --------------------------------------------------------------------------------------------- */
577 
578 /* event callback */
579 gboolean
execute_suspend(const gchar * event_group_name,const gchar * event_name,gpointer init_data,gpointer data)580 execute_suspend (const gchar * event_group_name, const gchar * event_name,
581                  gpointer init_data, gpointer data)
582 {
583     (void) event_group_name;
584     (void) event_name;
585     (void) init_data;
586     (void) data;
587 
588     if (mc_global.mc_run_mode == MC_RUN_FULL)
589         save_cwds_stat ();
590     do_suspend_cmd ();
591     if (mc_global.mc_run_mode == MC_RUN_FULL)
592         update_panels (UP_OPTIMIZE, UP_KEEPSEL);
593     do_refresh ();
594 
595     return TRUE;
596 }
597 
598 /* --------------------------------------------------------------------------------------------- */
599 
600 /**
601  * Execute command on a filename that can be on VFS.
602  * Errors are reported to the user.
603  */
604 
605 void
execute_with_vfs_arg(const char * command,const vfs_path_t * filename_vpath)606 execute_with_vfs_arg (const char *command, const vfs_path_t * filename_vpath)
607 {
608     vfs_path_t *localcopy_vpath = NULL;
609     const vfs_path_t *do_execute_vpath;
610     time_t mtime;
611 
612     if (!execute_prepare_with_vfs_arg (filename_vpath, &localcopy_vpath, &mtime))
613         return;
614 
615     do_execute_vpath = (localcopy_vpath == NULL) ? filename_vpath : localcopy_vpath;
616 
617     do_execute (command, vfs_path_get_last_path_str (do_execute_vpath), EXECUTE_INTERNAL);
618 
619     execute_cleanup_with_vfs_arg (filename_vpath, &localcopy_vpath, &mtime);
620 }
621 
622 /* --------------------------------------------------------------------------------------------- */
623 /**
624  * Execute external editor or viewer.
625  *
626  * @param command editor/viewer to run
627  * @param filename_vpath path for edit/view
628  * @param start_line cursor will be placed at the 'start_line' position after opening file
629  *        if start_line is 0 or negative, no start line will be passed to editor/viewer
630  */
631 
632 void
execute_external_editor_or_viewer(const char * command,const vfs_path_t * filename_vpath,long start_line)633 execute_external_editor_or_viewer (const char *command, const vfs_path_t * filename_vpath,
634                                    long start_line)
635 {
636     vfs_path_t *localcopy_vpath = NULL;
637     const vfs_path_t *do_execute_vpath;
638     char *extern_cmd_options;
639     time_t mtime = 0;
640 
641     if (!execute_prepare_with_vfs_arg (filename_vpath, &localcopy_vpath, &mtime))
642         return;
643 
644     do_execute_vpath = (localcopy_vpath == NULL) ? filename_vpath : localcopy_vpath;
645 
646     extern_cmd_options =
647         execute_get_external_cmd_opts_from_config (command, do_execute_vpath, start_line);
648 
649     if (extern_cmd_options != NULL)
650     {
651         char **argv_cmd_options;
652         int argv_count;
653 
654         if (g_shell_parse_argv (extern_cmd_options, &argv_count, &argv_cmd_options, NULL))
655         {
656             do_executev (command, EXECUTE_INTERNAL, argv_cmd_options);
657             g_strfreev (argv_cmd_options);
658         }
659         else
660             do_executev (command, EXECUTE_INTERNAL, NULL);
661 
662         g_free (extern_cmd_options);
663     }
664 
665     execute_cleanup_with_vfs_arg (filename_vpath, &localcopy_vpath, &mtime);
666 }
667 
668 /* --------------------------------------------------------------------------------------------- */
669