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