1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "running.h"
21 
22 #include <curses.h> /* FALSE curs_set() */
23 
24 #include <sys/stat.h> /* stat */
25 #ifndef _WIN32
26 #include <sys/wait.h> /* WEXITSTATUS() */
27 #else
28 #define WIN32_LEAN_AND_MEAN
29 #include <windows.h>
30 #include <shellapi.h>
31 #ifndef ERROR_ELEVATION_REQUIRED /* Windows Vista and later. */
32 #define ERROR_ELEVATION_REQUIRED 740L
33 #endif
34 #endif
35 #include <unistd.h> /* pid_t */
36 
37 #include <assert.h> /* assert() */
38 #include <errno.h> /* errno */
39 #include <stddef.h> /* NULL size_t */
40 #include <stdio.h> /* snprintf() */
41 #include <stdlib.h> /* EXIT_FAILURE EXIT_SUCCESS free() realloc() */
42 #include <string.h> /* strcmp() strerror() strrchr() strcat() strstr() strlen()
43                        strchr() strdup() strncmp() */
44 
45 #include "cfg/config.h"
46 #include "cfg/info.h"
47 #include "compat/fs_limits.h"
48 #include "compat/os.h"
49 #include "int/file_magic.h"
50 #include "int/fuse.h"
51 #include "int/path_env.h"
52 #include "int/vim.h"
53 #include "menus/users_menu.h"
54 #include "modes/dialogs/msg_dialog.h"
55 #include "modes/view.h"
56 #include "ui/statusbar.h"
57 #include "ui/quickview.h"
58 #include "ui/ui.h"
59 #include "utils/env.h"
60 #include "utils/fs.h"
61 #include "utils/log.h"
62 #include "utils/path.h"
63 #include "utils/str.h"
64 #include "utils/string_array.h"
65 #include "utils/utils.h"
66 #include "utils/utf8.h"
67 #include "background.h"
68 #include "filelist.h"
69 #include "filetype.h"
70 #include "flist_hist.h"
71 #include "flist_pos.h"
72 #include "flist_sel.h"
73 #include "macros.h"
74 #include "opt_handlers.h"
75 #include "status.h"
76 #include "types.h"
77 #include "vifm.h"
78 
79 /* Kinds of symbolic link file treatment on file handling. */
80 typedef enum
81 {
82 	FHL_NO_FOLLOW,  /* Don't follow (navigate to instead of navigation inside). */
83 	FHL_FOLLOW,     /* Follow (end up on the link target, not inside it). */
84 	FHL_FOLLOW_ALL, /* Follow all the way (end up on final link's target). */
85 }
86 FileHandleLink;
87 
88 static void handle_file(view_t *view, FileHandleExec exec,
89 		FileHandleLink follow);
90 static int is_runnable(view_t *view, const char full_path[], int type,
91 		int force_follow);
92 static int is_executable(const char full_path[], const dir_entry_t *curr,
93 		int dont_execute, int runnable);
94 #ifdef _WIN32
95 static void run_win_executable(char full_path[], int elevate);
96 static int run_win_executable_as_evaluated(const char full_path[]);
97 #endif
98 static int selection_is_consistent(view_t *view);
99 static void execute_file(const char full_path[], int elevate);
100 static void run_selection(view_t *view, int dont_execute);
101 static void run_with_defaults(view_t *view);
102 static void run_selection_separately(view_t *view, int dont_execute);
103 static int is_multi_run_compat(view_t *view, const char prog_cmd[]);
104 static void run_explicit_prog(const char prog_spec[], int pause, int force_bg);
105 static void run_implicit_prog(view_t *view, const char prog_spec[], int pause,
106 		int force_bg);
107 static void view_current_file(const view_t *view);
108 static void follow_link(view_t *view, int follow_dirs, int ultimate);
109 static void enter_dir(struct view_t *view);
110 static int cd_to_parent_dir(view_t *view);
111 static void extract_last_path_component(const char path[], char buf[]);
112 static void setup_shellout_env(void);
113 static void cleanup_shellout_env(void);
114 static char * gen_shell_cmd(const char cmd[], int pause,
115 		int use_term_multiplexer, ShellRequester *by);
116 static char * gen_term_multiplexer_cmd(const char cmd[], int pause,
117 		ShellRequester by);
118 static char * gen_term_multiplexer_title_arg(const char cmd[]);
119 static char * gen_normal_cmd(const char cmd[], int pause);
120 static void set_pwd_in_screen(const char path[]);
121 static int try_run_with_filetype(view_t *view, const assoc_records_t assocs,
122 		const char start[], int background);
123 static void output_to_statusbar(const char cmd[]);
124 static int output_to_preview(const char cmd[]);
125 static void output_to_nowhere(const char cmd[]);
126 static void run_in_split(const view_t *view, const char cmd[]);
127 static void path_handler(const char line[], void *arg);
128 static void line_handler(const char line[], void *arg);
129 
130 /* Name of environment variable used to communicate path to file used to
131  * initiate FUSE mounting of directory we're in. */
132 static const char *const FUSE_FILE_ENVVAR = "VIFM_FUSE_FILE";
133 
134 void
rn_open(view_t * view,FileHandleExec exec)135 rn_open(view_t *view, FileHandleExec exec)
136 {
137 	handle_file(view, exec, FHL_NO_FOLLOW);
138 }
139 
140 void
rn_follow(view_t * view,int ultimate)141 rn_follow(view_t *view, int ultimate)
142 {
143 	if(flist_custom_active(view))
144 	{
145 		const dir_entry_t *const curr = get_current_entry(view);
146 		if(!fentry_is_fake(curr))
147 		{
148 			/* Entry might be freed on navigation, so make sure name and origin will
149 			 * remain available for the call. */
150 			char *const name = strdup(curr->name);
151 			char *const origin = strdup(curr->origin);
152 			navigate_to_file(view, origin, name, 0);
153 			free(origin);
154 			free(name);
155 		}
156 		return;
157 	}
158 
159 	handle_file(view, FHE_RUN, ultimate ? FHL_FOLLOW_ALL : FHL_FOLLOW);
160 }
161 
162 static void
handle_file(view_t * view,FileHandleExec exec,FileHandleLink follow)163 handle_file(view_t *view, FileHandleExec exec, FileHandleLink follow)
164 {
165 	char full_path[PATH_MAX + 1];
166 	const dir_entry_t *const curr = get_current_entry(view);
167 
168 	int user_selection = !view->pending_marking;
169 	flist_set_marking(view, 1);
170 
171 	if(fentry_is_fake(curr))
172 	{
173 		return;
174 	}
175 
176 	get_full_path_of(curr, sizeof(full_path), full_path);
177 
178 	int could_enter_entry = (curr->type != FT_LINK || follow == FHL_NO_FOLLOW);
179 	int selected_entry = (curr->marked && (!user_selection || curr->selected));
180 	if(!selected_entry && could_enter_entry)
181 	{
182 		int dir_like_entry = (is_dir(full_path) || is_unc_root(view->curr_dir));
183 		if(dir_like_entry)
184 		{
185 			enter_dir(view);
186 			return;
187 		}
188 	}
189 
190 	int runnable = is_runnable(view, full_path, curr->type,
191 			follow != FHL_NO_FOLLOW);
192 	int executable = is_executable(full_path, curr, exec == FHE_NO_RUN, runnable);
193 
194 	if(stats_file_choose_action_set() && (executable || runnable))
195 	{
196 		/* Reuse marking second time. */
197 		view->pending_marking = 1;
198 		/* The call below does not return. */
199 		vifm_choose_files(view, 0, NULL);
200 	}
201 
202 	if(executable && !fentry_is_dir(curr))
203 	{
204 		execute_file(full_path, exec == FHE_ELEVATE_AND_RUN);
205 	}
206 	else if(runnable)
207 	{
208 		run_selection(view, exec == FHE_NO_RUN);
209 	}
210 	else if(curr->type == FT_LINK || is_shortcut(curr->name))
211 	{
212 		follow_link(view, follow != FHL_NO_FOLLOW, follow == FHL_FOLLOW_ALL);
213 	}
214 }
215 
216 /* Returns non-zero if file can be executed or it's a link to a directory (it
217  * can be entered), otherwise zero is returned. */
218 static int
is_runnable(view_t * view,const char full_path[],int type,int force_follow)219 is_runnable(view_t *view, const char full_path[], int type, int force_follow)
220 {
221 	int count = 0;
222 	dir_entry_t *entry = NULL;
223 	while(iter_marked_entries(view, &entry))
224 	{
225 		if(++count > 1)
226 		{
227 			return 1;
228 		}
229 	}
230 
231 	if(!force_follow && !cfg.follow_links && type == FT_LINK &&
232 			get_symlink_type(full_path) != SLT_DIR)
233 	{
234 		return 1;
235 	}
236 
237 	if(type == FT_REG)
238 	{
239 		return (!force_follow || !is_shortcut(full_path));
240 	}
241 
242 	return (type == FT_EXEC || type == FT_DIR);
243 }
244 
245 /* Returns non-zero if file can be executed, otherwise zero is returned. */
246 static int
is_executable(const char full_path[],const dir_entry_t * curr,int dont_execute,int runnable)247 is_executable(const char full_path[], const dir_entry_t *curr, int dont_execute,
248 		int runnable)
249 {
250 	int executable;
251 #ifndef _WIN32
252 	executable = curr->type == FT_EXEC ||
253 			(runnable && os_access(full_path, X_OK) == 0 && S_ISEXE(curr->mode));
254 #else
255 	executable = curr->type == FT_EXEC;
256 #endif
257 	return executable && !dont_execute && cfg.auto_execute;
258 }
259 
260 #ifdef _WIN32
261 
262 /* Runs a Windows executable handling errors and rights elevation. */
263 static void
run_win_executable(char full_path[],int elevate)264 run_win_executable(char full_path[], int elevate)
265 {
266 	int running_error = 0;
267 	int running_error_code = NO_ERROR;
268 	if(elevate && is_vista_and_above())
269 	{
270 		running_error = run_win_executable_as_evaluated(full_path);
271 	}
272 	else
273 	{
274 		int returned_exit_code;
275 		const int error = win_exec_cmd(full_path, &returned_exit_code);
276 		if(error != 0 && !returned_exit_code)
277 		{
278 			if(error == ERROR_ELEVATION_REQUIRED && is_vista_and_above())
279 			{
280 				const int user_response = prompt_msg("Program running error",
281 						"Executable requires rights elevation. Run with elevated rights?");
282 				if(user_response != 0)
283 				{
284 					running_error = run_win_executable_as_evaluated(full_path);
285 				}
286 			}
287 			else
288 			{
289 				running_error = 1;
290 				running_error_code = error;
291 			}
292 		}
293 		update_screen(UT_FULL);
294 	}
295 	if(running_error)
296 	{
297 		char err_msg[512];
298 		err_msg[0] = '\0';
299 		if(running_error_code != NO_ERROR && FormatMessageA(
300 				FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, NULL,
301 				running_error_code, 0, err_msg, sizeof(err_msg), NULL) == 0)
302 		{
303 			LOG_WERROR(GetLastError());
304 		}
305 
306 		show_error_msgf("Program running error", "Can't run an executable%s%s",
307 				(err_msg[0] == '\0') ? "." : ": ", err_msg);
308 	}
309 }
310 
311 /* Returns non-zero on error, otherwise zero is returned. */
312 static int
run_win_executable_as_evaluated(const char full_path[])313 run_win_executable_as_evaluated(const char full_path[])
314 {
315 	wchar_t *utf16_path;
316 	SHELLEXECUTEINFOW sei;
317 
318 	utf16_path = utf8_to_utf16(full_path);
319 
320 	memset(&sei, 0, sizeof(sei));
321 	sei.cbSize = sizeof(sei);
322 	sei.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
323 	sei.lpVerb = L"runas";
324 	sei.lpFile = utf16_path;
325 	sei.lpParameters = NULL;
326 	sei.nShow = SW_SHOWNORMAL;
327 
328 	if(!ShellExecuteExW(&sei))
329 	{
330 		const DWORD last_error = GetLastError();
331 		free(utf16_path);
332 		LOG_WERROR(last_error);
333 		return last_error != ERROR_CANCELLED;
334 	}
335 
336 	free(utf16_path);
337 	CloseHandle(sei.hProcess);
338 	return 0;
339 }
340 
341 #endif /* _WIN32 */
342 
343 /* Returns non-zero if selection doesn't mix files and directories, otherwise
344  * zero is returned. */
345 static int
selection_is_consistent(view_t * view)346 selection_is_consistent(view_t *view)
347 {
348 	int files = 0, dirs = 0;
349 	dir_entry_t *entry = NULL;
350 	while(iter_marked_entries(view, &entry))
351 	{
352 		if(fentry_is_dir(entry))
353 		{
354 			++dirs;
355 		}
356 		else
357 		{
358 			++files;
359 		}
360 	}
361 	return (dirs == 0 || files == 0);
362 }
363 
364 /* Executes file, specified by the full_path.  Changes type of slashes on
365  * Windows. */
366 static void
execute_file(const char full_path[],int elevate)367 execute_file(const char full_path[], int elevate)
368 {
369 #ifndef _WIN32
370 	char *const escaped = shell_like_escape(full_path, 0);
371 	rn_shell(escaped, PAUSE_ALWAYS, 1, SHELL_BY_APP);
372 	free(escaped);
373 #else
374 	char *const dquoted_full_path = strdup(enclose_in_dquotes(full_path));
375 
376 	internal_to_system_slashes(dquoted_full_path);
377 	run_win_executable(dquoted_full_path, elevate);
378 
379 	free(dquoted_full_path);
380 #endif
381 }
382 
383 /* Tries to run selection displaying error message on file type
384  * inconsistency. */
385 static void
run_selection(view_t * view,int dont_execute)386 run_selection(view_t *view, int dont_execute)
387 {
388 	if(!selection_is_consistent(view))
389 	{
390 		show_error_msg("Selection error",
391 				"Selection cannot contain files and directories at the same time");
392 		return;
393 	}
394 
395 	char *typed_fname = get_typed_entry_fpath(get_current_entry(view));
396 	const char *common_prog_cmd = ft_get_program(typed_fname);
397 	free(typed_fname);
398 
399 	int can_multi_run = (is_multi_run_compat(view, common_prog_cmd) != 0);
400 	int files_without_handler = (common_prog_cmd == NULL);
401 	int identical_handlers = 1;
402 	int nentries = 0;
403 
404 	dir_entry_t *entry = NULL;
405 	while(iter_marked_entries(view, &entry))
406 	{
407 		++nentries;
408 
409 		if(!path_exists_at(entry->origin, entry->name, DEREF))
410 		{
411 			show_error_msgf("Broken Link", "Destination of \"%s\" link doesn't exist",
412 					entry->name);
413 			return;
414 		}
415 
416 		char *typed_fname = get_typed_entry_fpath(entry);
417 		const char *entry_prog_cmd = ft_get_program(typed_fname);
418 		free(typed_fname);
419 
420 		if(entry_prog_cmd == NULL)
421 		{
422 			++files_without_handler;
423 			continue;
424 		}
425 
426 		can_multi_run &= (is_multi_run_compat(view, entry_prog_cmd) != 0);
427 		if(common_prog_cmd == NULL)
428 		{
429 			common_prog_cmd = entry_prog_cmd;
430 		}
431 		else if(strcmp(entry_prog_cmd, common_prog_cmd) != 0)
432 		{
433 			identical_handlers = 0;
434 		}
435 	}
436 
437 	can_multi_run &= (nentries > 1);
438 
439 	if(files_without_handler > 0)
440 	{
441 		run_with_defaults(view);
442 	}
443 	else if(can_multi_run)
444 	{
445 		run_selection_separately(view, dont_execute);
446 	}
447 	else if(identical_handlers)
448 	{
449 		rn_open_with(view, common_prog_cmd, dont_execute, 0);
450 	}
451 	else
452 	{
453 		show_error_msg("Run error", "Handlers of selected files are "
454 		                            "incompatible.");
455 	}
456 }
457 
458 /* Runs current file entry of the view in a generic way (entering directories
459  * and opening files in editors). */
460 static void
run_with_defaults(view_t * view)461 run_with_defaults(view_t *view)
462 {
463 	if(get_current_entry(view)->type == FT_DIR)
464 	{
465 		enter_dir(view);
466 		return;
467 	}
468 
469 	/* Reuse marking second time. */
470 	view->pending_marking = 1;
471 	if(vim_edit_marking() != 0)
472 	{
473 		show_error_msg("Running error", "Can't edit selection");
474 	}
475 }
476 
477 /* Runs each of selected file entries of the view individually. */
478 static void
run_selection_separately(view_t * view,int dont_execute)479 run_selection_separately(view_t *view, int dont_execute)
480 {
481 	const int pos = view->list_pos;
482 
483 	dir_entry_t *entry = NULL;
484 	while(iter_marked_entries(view, &entry))
485 	{
486 		char *typed_fname;
487 		const char *entry_prog_cmd;
488 
489 		typed_fname = get_typed_entry_fpath(entry);
490 		entry_prog_cmd = ft_get_program(typed_fname);
491 		free(typed_fname);
492 
493 		view->list_pos = entry_to_pos(view, entry);
494 		rn_open_with(view, entry_prog_cmd, dont_execute, 0);
495 	}
496 
497 	view->list_pos = pos;
498 }
499 
500 /* Checks whether command is compatible with firing multiple file handlers for a
501  * set of selected files.  Returns non-zero if so, otherwise zero is
502  * returned. */
503 static int
is_multi_run_compat(view_t * view,const char prog_cmd[])504 is_multi_run_compat(view_t *view, const char prog_cmd[])
505 {
506 	size_t len;
507 	if(prog_cmd == NULL)
508 		return 0;
509 	if((len = strlen(prog_cmd)) == 0)
510 		return 0;
511 	/* XXX: should the check be for " &" and not just "&"? */
512 	if(prog_cmd[len - 1] != '&')
513 		return 0;
514 	if(strstr(prog_cmd, "%f") != NULL || strstr(prog_cmd, "%F") != NULL)
515 		return 0;
516 	if(strstr(prog_cmd, "%c") == NULL && strstr(prog_cmd, "%C") == NULL)
517 		return 0;
518 	return 1;
519 }
520 
521 void
rn_open_with(view_t * view,const char prog_spec[],int dont_execute,int force_bg)522 rn_open_with(view_t *view, const char prog_spec[], int dont_execute,
523 		int force_bg)
524 {
525 	const dir_entry_t *const curr = get_current_entry(view);
526 	const int pause = skip_prefix(&prog_spec, "!!");
527 
528 	if(!path_exists_at(curr->origin, curr->name, DEREF))
529 	{
530 		show_error_msg("Access Error", "File doesn't exist.");
531 		return;
532 	}
533 
534 	if(fuse_is_mount_string(prog_spec))
535 	{
536 		if(dont_execute)
537 		{
538 			view_current_file(view);
539 		}
540 		else
541 		{
542 			fuse_try_mount(view, prog_spec);
543 		}
544 	}
545 	else if(strcmp(prog_spec, VIFM_PSEUDO_CMD) == 0)
546 	{
547 		enter_dir(view);
548 	}
549 	else if(strchr(prog_spec, '%') != NULL)
550 	{
551 		run_explicit_prog(prog_spec, pause, force_bg);
552 	}
553 	else
554 	{
555 		run_implicit_prog(view, prog_spec, pause, force_bg);
556 	}
557 }
558 
559 /* Executes current file of the current view by program specification that
560  * includes at least one macro. */
561 static void
run_explicit_prog(const char prog_spec[],int pause,int force_bg)562 run_explicit_prog(const char prog_spec[], int pause, int force_bg)
563 {
564 	int bg;
565 	MacroFlags flags;
566 	int save_msg;
567 	char *const cmd = ma_expand(prog_spec, NULL, &flags, 1);
568 
569 	bg = cut_suffix(cmd, " &");
570 	bg = !pause && (bg || force_bg);
571 
572 	save_msg = 0;
573 	if(rn_ext(cmd, prog_spec, flags, bg, &save_msg) != 0)
574 	{
575 		if(save_msg)
576 		{
577 			curr_stats.save_msg = 1;
578 		}
579 	}
580 	else if(bg)
581 	{
582 		assert(flags != MF_IGNORE && "This case is for rn_ext()");
583 		(void)bg_run_external(cmd, flags == MF_IGNORE, SHELL_BY_USER);
584 	}
585 	else
586 	{
587 		(void)rn_shell(cmd, pause ? PAUSE_ALWAYS : PAUSE_ON_ERROR,
588 				flags != MF_NO_TERM_MUX, SHELL_BY_USER);
589 	}
590 
591 	free(cmd);
592 }
593 
594 /* Executes current file of the view by program specification that does not
595  * include any macros (hence file name is appended implicitly. */
596 static void
run_implicit_prog(view_t * view,const char prog_spec[],int pause,int force_bg)597 run_implicit_prog(view_t *view, const char prog_spec[], int pause, int force_bg)
598 {
599 	int bg;
600 	char cmd[NAME_MAX + 1 + NAME_MAX + 1];
601 	const char *name_macro;
602 	char *file_name;
603 	char spec[strlen(prog_spec) + 1U];
604 
605 	strcpy(spec, prog_spec);
606 	bg = cut_suffix(spec, " &") || force_bg;
607 
608 	if(curr_stats.shell_type == ST_CMD)
609 	{
610 		name_macro = (view == curr_view) ? "%\"c" : "%\"C";
611 	}
612 	else
613 	{
614 		name_macro = (view == curr_view) ? "%c" : "%C";
615 	}
616 
617 	file_name = ma_expand(name_macro, NULL, NULL, 1);
618 	snprintf(cmd, sizeof(cmd), "%s %s", spec, file_name);
619 	free(file_name);
620 
621 	if(bg)
622 	{
623 		(void)bg_run_external(cmd, 0, SHELL_BY_USER);
624 	}
625 	else
626 	{
627 		(void)rn_shell(cmd, pause ? PAUSE_ALWAYS : PAUSE_ON_ERROR, 1,
628 				SHELL_BY_USER);
629 	}
630 }
631 
632 /* Opens file under the cursor in the viewer. */
633 static void
view_current_file(const view_t * view)634 view_current_file(const view_t *view)
635 {
636 	char full_path[PATH_MAX + 1];
637 	get_current_full_path(view, sizeof(full_path), full_path);
638 	(void)vim_view_file(full_path, -1, -1, 1);
639 }
640 
641 /* Resolves link target and either navigates inside directory the link points to
642  * or navigates to directory where target is located placing cursor at it (the
643  * follow_dirs flag controls the behaviour). */
644 static void
follow_link(view_t * view,int follow_dirs,int ultimate)645 follow_link(view_t *view, int follow_dirs, int ultimate)
646 {
647 	char *dir, *file;
648 	char linkto[PATH_MAX + NAME_MAX + 1];
649 	const dir_entry_t *const curr = get_current_entry(view);
650 
651 	char full_path[PATH_MAX + 1];
652 	get_full_path_of(curr, sizeof(full_path), full_path);
653 
654 	int resolve_one_level = !ultimate;
655 
656 	if(ultimate)
657 	{
658 		if(os_realpath(full_path, linkto) != linkto)
659 		{
660 			show_error_msg("Error", "Can't resolve the link.");
661 			return;
662 		}
663 
664 #ifdef _WIN32
665 		/* This might be a Windows shortcut which aren't resolved by
666 		 * os_realpath(). */
667 		resolve_one_level = paths_are_equal(full_path, linkto);
668 #endif
669 	}
670 
671 	if(resolve_one_level)
672 	{
673 		/* We resolve origin to a real path because relative symbolic links are
674 		 * relative to real location of the link.  Can't simply do realpath() on
675 		 * full path as it would resolve symbolic links recursively, while we want
676 		 * to follow only one level deep. */
677 
678 		char origin_real[PATH_MAX + 1];
679 		if(os_realpath(curr->origin, origin_real) != origin_real)
680 		{
681 			show_error_msg("Error", "Can't resolve origin of the link.");
682 			return;
683 		}
684 
685 		if(get_link_target_abs(full_path, origin_real, linkto, sizeof(linkto)) != 0)
686 		{
687 			show_error_msg("Error", "Can't read link.");
688 			return;
689 		}
690 	}
691 
692 	if(!path_exists(linkto, NODEREF))
693 	{
694 		show_error_msg("Broken Link",
695 				"Can't access link destination.  It might be broken.");
696 		return;
697 	}
698 
699 	chosp(linkto);
700 
701 	if(is_dir(linkto) && !follow_dirs)
702 	{
703 		dir = strdup(curr->name);
704 		file = NULL;
705 	}
706 	else
707 	{
708 		dir = strdup(linkto);
709 		remove_last_path_component(dir);
710 
711 		file = get_last_path_component(linkto);
712 	}
713 
714 	if(dir[0] != '\0' && file != NULL)
715 	{
716 		navigate_to_file(view, dir, file, 1);
717 	}
718 	else if(dir[0] != '\0')
719 	{
720 		navigate_to(view, dir);
721 	}
722 	else if(file != NULL)
723 	{
724 		fpos_ensure_selected(view, file);
725 	}
726 
727 	free(dir);
728 }
729 
730 /* Handles opening of current entry of the view as a directory. */
731 static void
enter_dir(view_t * view)732 enter_dir(view_t *view)
733 {
734 	dir_entry_t *const curr = get_current_entry(view);
735 
736 	if(is_parent_dir(curr->name) && curr->origin == view->curr_dir)
737 	{
738 		rn_leave(view, 1);
739 		return;
740 	}
741 
742 	char full_path[PATH_MAX + 1];
743 	get_full_path_of(curr, sizeof(full_path), full_path);
744 
745 	if(cd_is_possible(full_path))
746 	{
747 		curr_stats.ch_pos = (cfg_ch_pos_on(CHPOS_ENTER) ? 1 : 0);
748 		navigate_to(view, full_path);
749 		curr_stats.ch_pos = 1;
750 	}
751 }
752 
753 void
rn_leave(view_t * view,int levels)754 rn_leave(view_t *view, int levels)
755 {
756 	/* Do not save intermediate directories in directory history. */
757 	curr_stats.drop_new_dir_hist = 1;
758 
759 	while(levels-- > 0)
760 	{
761 		if(cd_to_parent_dir(view) != 0)
762 		{
763 			break;
764 		}
765 	}
766 
767 	curr_stats.drop_new_dir_hist = 0;
768 	flist_hist_save(view);
769 }
770 
771 /* Goes one directory up from current location.  Returns zero unless it won't
772  * make sense to continue going up (like on error or reaching root). */
773 static int
cd_to_parent_dir(view_t * view)774 cd_to_parent_dir(view_t *view)
775 {
776 	char dir_name[strlen(view->curr_dir) + 1];
777 	int ret;
778 
779 	/* Return to original directory from custom view. */
780 	if(flist_custom_active(view))
781 	{
782 		navigate_to(view, view->custom.orig_dir);
783 		return 0;
784 	}
785 
786 	/* Do nothing in root. */
787 	if(is_root_dir(view->curr_dir))
788 	{
789 		return 1;
790 	}
791 
792 	dir_name[0] = '\0';
793 	extract_last_path_component(view->curr_dir, dir_name);
794 
795 	ret = change_directory(view, "../");
796 	if(ret == -1)
797 	{
798 		return 1;
799 	}
800 
801 	if(ret == 0)
802 	{
803 		/* XXX: we put cursor at one position and then move it.  Ideally it would
804 		 *      be set where it should be right away. */
805 		load_dir_list(view, 0);
806 		fpos_set_pos(view, fpos_find_by_name(view, dir_name));
807 	}
808 	return 0;
809 }
810 
811 /* Extracts last part of the path into buf.  Assumes that size of the buf is
812  * large enough. */
813 static void
extract_last_path_component(const char path[],char buf[])814 extract_last_path_component(const char path[], char buf[])
815 {
816 	const char *const last = get_last_path_component(path);
817 	copy_str(buf, until_first(last, '/') - last + 1, last);
818 }
819 
820 int
rn_shell(const char command[],ShellPause pause,int use_term_multiplexer,ShellRequester by)821 rn_shell(const char command[], ShellPause pause, int use_term_multiplexer,
822 		ShellRequester by)
823 {
824 	char *cmd;
825 	int result;
826 	int ec;
827 
828 	/* Shutdown UI at this point, where $PATH isn't cleared. */
829 	ui_shutdown();
830 
831 	int shellout = (command == NULL);
832 	if(shellout)
833 	{
834 		command = env_get_def("SHELL", cfg.shell);
835 
836 		/* Run shell with clean $PATH environment variable. */
837 		load_clean_path_env();
838 	}
839 
840 	if(pause == PAUSE_ALWAYS && command != NULL && ends_with(command, "&"))
841 	{
842 		pause = PAUSE_ON_ERROR;
843 	}
844 
845 	setup_shellout_env();
846 
847 	cmd = gen_shell_cmd(command, pause == PAUSE_ALWAYS, use_term_multiplexer,
848 			&by);
849 
850 	ec = vifm_system(cmd, by);
851 	/* No WIFEXITED(ec) check here, since vifm_system(...) shouldn't return until
852 	 * subprocess exited. */
853 	result = WEXITSTATUS(ec);
854 
855 	cleanup_shellout_env();
856 
857 	if(result != 0 && pause == PAUSE_ON_ERROR)
858 	{
859 		LOG_ERROR_MSG("Subprocess (%s) exit code: %d (0x%x); status = 0x%x", cmd,
860 				result, result, ec);
861 		pause_shell();
862 	}
863 
864 	free(cmd);
865 
866 	/* Force updates of views that don't have associated watchers. */
867 	if(flist_custom_active(&lwin) && lwin.custom.type != CV_TREE)
868 	{
869 		ui_view_schedule_reload(&lwin);
870 	}
871 	if(flist_custom_active(&rwin) && rwin.custom.type != CV_TREE)
872 	{
873 		ui_view_schedule_reload(&rwin);
874 	}
875 
876 	recover_after_shellout();
877 
878 	if(!curr_stats.skip_shellout_redraw)
879 	{
880 		/* Redraw to handle resizing of terminal that we could have missed. */
881 		stats_redraw_later();
882 	}
883 
884 	if(curr_stats.load_stage > 0)
885 	{
886 		curs_set(0);
887 	}
888 
889 	if(shellout)
890 	{
891 		load_real_path_env();
892 	}
893 
894 	return result;
895 }
896 
897 /* Configures environment variables before shellout.  Should be used in pair
898  * with cleanup_shellout_env(). */
899 static void
setup_shellout_env(void)900 setup_shellout_env(void)
901 {
902 	const char *mount_file;
903 	const char *term_multiplexer_fmt;
904 	char *escaped_path;
905 	char *cmd;
906 
907 	/* Need to use internal value instead of getcwd() for a symlink directory. */
908 	env_set("PWD", curr_view->curr_dir);
909 
910 	mount_file = fuse_get_mount_file(curr_view->curr_dir);
911 	if(mount_file == NULL)
912 	{
913 		env_remove(FUSE_FILE_ENVVAR);
914 		return;
915 	}
916 
917 	env_set(FUSE_FILE_ENVVAR, mount_file);
918 
919 	switch(curr_stats.term_multiplexer)
920 	{
921 		case TM_TMUX:   term_multiplexer_fmt = "tmux set-environment %s %s"; break;
922 		case TM_SCREEN: term_multiplexer_fmt = "screen -X setenv %s %s"; break;
923 
924 		default:
925 			return;
926 	}
927 
928 	escaped_path = shell_like_escape(mount_file, 0);
929 	cmd = format_str(term_multiplexer_fmt, FUSE_FILE_ENVVAR, escaped_path);
930 	(void)vifm_system(cmd, SHELL_BY_APP);
931 	free(cmd);
932 	free(escaped_path);
933 }
934 
935 /* Cleans up some environment changes made by setup_shellout_env() after command
936  * execution ends. */
937 static void
cleanup_shellout_env(void)938 cleanup_shellout_env(void)
939 {
940 	const char *term_multiplexer_fmt;
941 	char *cmd;
942 
943 	switch(curr_stats.term_multiplexer)
944 	{
945 		case TM_TMUX:   term_multiplexer_fmt = "tmux set-environment -u %s"; break;
946 		case TM_SCREEN: term_multiplexer_fmt = "screen -X unsetenv %s"; break;
947 
948 		default:
949 			return;
950 	}
951 
952 	cmd = format_str(term_multiplexer_fmt, FUSE_FILE_ENVVAR);
953 	(void)vifm_system(cmd, SHELL_BY_APP);
954 	free(cmd);
955 }
956 
957 /* Composes shell command to run based on parameters for execution.  Returns a
958  * newly allocated string, which should be freed by the caller. */
959 static char *
gen_shell_cmd(const char cmd[],int pause,int use_term_multiplexer,ShellRequester * by)960 gen_shell_cmd(const char cmd[], int pause, int use_term_multiplexer,
961 		ShellRequester *by)
962 {
963 	char *shell_cmd;
964 
965 	if(use_term_multiplexer && curr_stats.term_multiplexer != TM_NONE)
966 	{
967 		shell_cmd = gen_term_multiplexer_cmd(cmd, pause, *by);
968 		/* User shell settings were taken into account in command for multiplexer,
969 		 * don't use them to invoke the multiplexer itself. */
970 		*by = SHELL_BY_APP;
971 	}
972 	else
973 	{
974 		shell_cmd = gen_normal_cmd(cmd, pause);
975 	}
976 
977 	return shell_cmd;
978 }
979 
980 /* Composes command to be run using terminal multiplexer.  Returns newly
981  * allocated string that should be freed by the caller. */
982 static char *
gen_term_multiplexer_cmd(const char cmd[],int pause,ShellRequester by)983 gen_term_multiplexer_cmd(const char cmd[], int pause, ShellRequester by)
984 {
985 	char *title_arg;
986 	char *raw_shell_cmd;
987 	char *escaped_shell_cmd;
988 	char *shell_cmd = NULL;
989 
990 	if(curr_stats.term_multiplexer != TM_TMUX &&
991 			curr_stats.term_multiplexer != TM_SCREEN)
992 	{
993 		assert(0 && "Unexpected active terminal multiplexer value.");
994 		return NULL;
995 	}
996 
997 	title_arg = gen_term_multiplexer_title_arg(cmd);
998 
999 	raw_shell_cmd = format_str("%s%s", cmd, pause ? PAUSE_STR : "");
1000 	escaped_shell_cmd = shell_like_escape(raw_shell_cmd, 0);
1001 
1002 	const char *sh_flag = (by == SHELL_BY_USER ? cfg.shell_cmd_flag : "-c");
1003 
1004 	if(curr_stats.term_multiplexer == TM_TMUX)
1005 	{
1006 		char *const arg = format_str("%s %s %s", cfg.shell, sh_flag,
1007 				escaped_shell_cmd);
1008 		char *const escaped_arg = shell_like_escape(arg, 0);
1009 
1010 		shell_cmd = format_str("tmux new-window %s %s", title_arg, escaped_arg);
1011 
1012 		free(escaped_arg);
1013 		free(arg);
1014 	}
1015 	else if(curr_stats.term_multiplexer == TM_SCREEN)
1016 	{
1017 		set_pwd_in_screen(flist_get_dir(curr_view));
1018 
1019 		shell_cmd = format_str("screen %s %s %s %s", title_arg, cfg.shell, sh_flag,
1020 				escaped_shell_cmd);
1021 	}
1022 	else
1023 	{
1024 		assert(0 && "Unsupported terminal multiplexer type.");
1025 	}
1026 
1027 	free(escaped_shell_cmd);
1028 	free(raw_shell_cmd);
1029 	free(title_arg);
1030 
1031 	return shell_cmd;
1032 }
1033 
1034 /* Composes title for window of a terminal multiplexer from a command.  Returns
1035  * newly allocated string that should be freed by the caller. */
1036 static char *
gen_term_multiplexer_title_arg(const char cmd[])1037 gen_term_multiplexer_title_arg(const char cmd[])
1038 {
1039 	int bg;
1040 	const char *const vicmd = cfg_get_vicmd(&bg);
1041 	const char *const visubcmd = strstr(cmd, vicmd);
1042 	char *command_name = NULL;
1043 	const char *title;
1044 	char *title_arg;
1045 
1046 	if(visubcmd != NULL)
1047 	{
1048 		title = skip_whitespace(visubcmd + strlen(vicmd) + 1);
1049 		if(cfg.short_term_mux_titles)
1050 		{
1051 			title = get_last_path_component(title);
1052 		}
1053 	}
1054 	else
1055 	{
1056 		char *const separator = strchr(cmd, ' ');
1057 		if(separator != NULL)
1058 		{
1059 			*separator = '\0';
1060 			command_name = strdup(cmd);
1061 			*separator = ' ';
1062 		}
1063 		title = command_name;
1064 	}
1065 
1066 	if(is_null_or_empty(title))
1067 	{
1068 		title_arg = strdup("");
1069 	}
1070 	else
1071 	{
1072 		const char opt_c = (curr_stats.term_multiplexer == TM_SCREEN) ? 't' : 'n';
1073 		char *const escaped_title = shell_like_escape(title, 0);
1074 		title_arg = format_str("-%c %s", opt_c, escaped_title);
1075 		free(escaped_title);
1076 	}
1077 
1078 	free(command_name);
1079 
1080 	return title_arg;
1081 }
1082 
1083 /* Composes command to be run without terminal multiplexer.  Returns a newly
1084  * allocated string, which should be freed by the caller. */
1085 static char *
gen_normal_cmd(const char cmd[],int pause)1086 gen_normal_cmd(const char cmd[], int pause)
1087 {
1088 	if(pause)
1089 	{
1090 		const char *cmd_with_pause_fmt;
1091 
1092 		if(curr_stats.shell_type == ST_CMD)
1093 		{
1094 			cmd_with_pause_fmt = "%s" PAUSE_STR;
1095 		}
1096 		else if(curr_stats.shell_type == ST_PS)
1097 		{
1098 			/* TODO: make pausing work. */
1099 			cmd_with_pause_fmt = "%s";
1100 		}
1101 		else
1102 		{
1103 			cmd_with_pause_fmt = "%s; " PAUSE_CMD;
1104 		}
1105 
1106 		return format_str(cmd_with_pause_fmt, cmd);
1107 	}
1108 	else
1109 	{
1110 		return strdup(cmd);
1111 	}
1112 }
1113 
1114 /* Changes $PWD in running GNU/screen session to the specified path.  Needed for
1115  * symlink directories and sshfs mounts. */
1116 static void
set_pwd_in_screen(const char path[])1117 set_pwd_in_screen(const char path[])
1118 {
1119 	char *const escaped_dir = shell_like_escape(path, 0);
1120 	char *const set_pwd = format_str("screen -X setenv PWD %s", escaped_dir);
1121 
1122 	(void)vifm_system(set_pwd, SHELL_BY_APP);
1123 
1124 	free(set_pwd);
1125 	free(escaped_dir);
1126 }
1127 
1128 int
rn_open_with_match(view_t * view,const char beginning[],int background)1129 rn_open_with_match(view_t *view, const char beginning[], int background)
1130 {
1131 	dir_entry_t *const curr = get_current_entry(view);
1132 	assoc_records_t ft, magic;
1133 	char *typed_fname;
1134 
1135 	if(fentry_is_fake(curr))
1136 	{
1137 		return 1;
1138 	}
1139 
1140 	typed_fname = get_typed_entry_fpath(curr);
1141 	ft = ft_get_all_programs(typed_fname);
1142 	magic = get_magic_handlers(typed_fname);
1143 	free(typed_fname);
1144 
1145 	if(try_run_with_filetype(view, ft, beginning, background))
1146 	{
1147 		ft_assoc_records_free(&ft);
1148 		return 0;
1149 	}
1150 
1151 	ft_assoc_records_free(&ft);
1152 
1153 	return !try_run_with_filetype(view, magic, beginning, background);
1154 }
1155 
1156 /* Returns non-zero on successful running. */
1157 static int
try_run_with_filetype(view_t * view,const assoc_records_t assocs,const char start[],int background)1158 try_run_with_filetype(view_t *view, const assoc_records_t assocs,
1159 		const char start[], int background)
1160 {
1161 	const size_t len = strlen(start);
1162 	int i;
1163 	for(i = 0; i < assocs.count; i++)
1164 	{
1165 		if(strncmp(assocs.list[i].command, start, len) == 0)
1166 		{
1167 			rn_open_with(view, assocs.list[i].command, 0, background);
1168 			return 1;
1169 		}
1170 	}
1171 	return 0;
1172 }
1173 
1174 int
rn_ext(const char cmd[],const char title[],MacroFlags flags,int bg,int * save_msg)1175 rn_ext(const char cmd[], const char title[], MacroFlags flags, int bg,
1176 		int *save_msg)
1177 {
1178 	if(bg && flags != MF_NONE && flags != MF_NO_TERM_MUX && flags != MF_IGNORE)
1179 	{
1180 		ui_sb_errf("\"%s\" macro can't be combined with \" &\"",
1181 				ma_flags_to_str(flags));
1182 		*save_msg = 1;
1183 		return -1;
1184 	}
1185 
1186 	if(flags == MF_STATUSBAR_OUTPUT)
1187 	{
1188 		output_to_statusbar(cmd);
1189 		*save_msg = 1;
1190 		return -1;
1191 	}
1192 	else if(flags == MF_PREVIEW_OUTPUT)
1193 	{
1194 		*save_msg = output_to_preview(cmd);
1195 		return -1;
1196 	}
1197 	else if(flags == MF_IGNORE)
1198 	{
1199 		*save_msg = 0;
1200 		if(bg)
1201 		{
1202 			int error;
1203 
1204 			setup_shellout_env();
1205 			error = (bg_run_external(cmd, 1, SHELL_BY_USER) != 0);
1206 			cleanup_shellout_env();
1207 
1208 			if(error)
1209 			{
1210 				ui_sb_errf("Failed to start in bg: %s", cmd);
1211 				*save_msg = 1;
1212 			}
1213 		}
1214 		else
1215 		{
1216 			output_to_nowhere(cmd);
1217 		}
1218 		return -1;
1219 	}
1220 	else if(flags == MF_MENU_OUTPUT || flags == MF_MENU_NAV_OUTPUT)
1221 	{
1222 		const int navigate = flags == MF_MENU_NAV_OUTPUT;
1223 		setup_shellout_env();
1224 		*save_msg = show_user_menu(curr_view, cmd, title, navigate) != 0;
1225 		cleanup_shellout_env();
1226 	}
1227 	else if(flags == MF_SPLIT && curr_stats.term_multiplexer != TM_NONE)
1228 	{
1229 		run_in_split(curr_view, cmd);
1230 	}
1231 	else if(ONE_OF(flags, MF_CUSTOMVIEW_OUTPUT, MF_VERYCUSTOMVIEW_OUTPUT,
1232 				MF_CUSTOMVIEW_IOUTPUT, MF_VERYCUSTOMVIEW_IOUTPUT))
1233 	{
1234 		const int very =
1235 			ONE_OF(flags, MF_VERYCUSTOMVIEW_OUTPUT, MF_VERYCUSTOMVIEW_IOUTPUT);
1236 		const int interactive =
1237 			ONE_OF(flags, MF_CUSTOMVIEW_IOUTPUT, MF_VERYCUSTOMVIEW_IOUTPUT);
1238 		rn_for_flist(curr_view, cmd, title, very, interactive);
1239 	}
1240 	else
1241 	{
1242 		return 0;
1243 	}
1244 	return 1;
1245 }
1246 
1247 /* Executes the cmd and displays its output on the status bar. */
1248 static void
output_to_statusbar(const char cmd[])1249 output_to_statusbar(const char cmd[])
1250 {
1251 	FILE *file, *err;
1252 	char buf[2048];
1253 	char *lines;
1254 	size_t len;
1255 	int error;
1256 
1257 	setup_shellout_env();
1258 	error = (bg_run_and_capture((char *)cmd, 1, &file, &err) == (pid_t)-1);
1259 	cleanup_shellout_env();
1260 	if(error)
1261 	{
1262 		show_error_msgf("Trouble running command", "Unable to run: %s", cmd);
1263 		return;
1264 	}
1265 
1266 	lines = NULL;
1267 	len = 0;
1268 	while(fgets(buf, sizeof(buf), file) == buf)
1269 	{
1270 		char *p;
1271 
1272 		chomp(buf);
1273 		p = realloc(lines, len + 1 + strlen(buf) + 1);
1274 		if(p != NULL)
1275 		{
1276 			lines = p;
1277 			len += sprintf(lines + len, "%s%s", (len == 0) ? "": "\n", buf);
1278 		}
1279 	}
1280 
1281 	fclose(file);
1282 	fclose(err);
1283 
1284 	ui_sb_msg((lines == NULL) ? "" : lines);
1285 	free(lines);
1286 }
1287 
1288 /* Runs the command and captures its output into detached preview.  Returns new
1289  * value for the save_msg flag. */
1290 static int
output_to_preview(const char cmd[])1291 output_to_preview(const char cmd[])
1292 {
1293 	if(qv_ensure_is_shown() != 0)
1294 	{
1295 		return 1;
1296 	}
1297 	modview_detached_make(other_view, cmd);
1298 	return 0;
1299 }
1300 
1301 /* Executes the cmd ignoring its output. */
1302 static void
output_to_nowhere(const char cmd[])1303 output_to_nowhere(const char cmd[])
1304 {
1305 	FILE *file, *err;
1306 	int error;
1307 
1308 	setup_shellout_env();
1309 	error = (bg_run_and_capture((char *)cmd, 1, &file, &err) == (pid_t)-1);
1310 	cleanup_shellout_env();
1311 	if(error)
1312 	{
1313 		show_error_msgf("Trouble running command", "Unable to run: %s", cmd);
1314 		return;
1315 	}
1316 
1317 	/* FIXME: better way of doing this would be to redirect these streams to
1318 	 *        /dev/null rather than closing them, but not sure about Windows (NUL
1319 	 *        device might work). */
1320 	fclose(file);
1321 	fclose(err);
1322 }
1323 
1324 /* Runs the cmd in a split window of terminal multiplexer.  Runs shell, if cmd
1325  * is NULL. */
1326 static void
run_in_split(const view_t * view,const char cmd[])1327 run_in_split(const view_t *view, const char cmd[])
1328 {
1329 	char *const escaped_cmd = (cmd == NULL)
1330 	                        ? strdup(cfg.shell)
1331 	                        : shell_like_escape(cmd, 0);
1332 
1333 	setup_shellout_env();
1334 
1335 	if(curr_stats.term_multiplexer == TM_TMUX)
1336 	{
1337 		char cmd[1024];
1338 		snprintf(cmd, sizeof(cmd), "tmux split-window %s", escaped_cmd);
1339 		(void)vifm_system(cmd, SHELL_BY_APP);
1340 	}
1341 	else if(curr_stats.term_multiplexer == TM_SCREEN)
1342 	{
1343 		char cmd[1024];
1344 
1345 		/* "eval" executes each argument as a separate argument, but escaping rules
1346 		 * are not exactly like in shell, so last command is run separately. */
1347 		char *const escaped_dir = shell_like_escape(flist_get_dir(view), 0);
1348 		snprintf(cmd, sizeof(cmd), "screen -X eval chdir\\ %s 'focus bottom' "
1349 				"split 'focus bottom'", escaped_dir);
1350 		free(escaped_dir);
1351 		(void)vifm_system(cmd, SHELL_BY_APP);
1352 
1353 		snprintf(cmd, sizeof(cmd), "screen -X screen vifm-screen-split %s",
1354 				escaped_cmd);
1355 		(void)vifm_system(cmd, SHELL_BY_APP);
1356 	}
1357 	else
1358 	{
1359 		assert(0 && "Unexpected active terminal multiplexer value.");
1360 	}
1361 
1362 	cleanup_shellout_env();
1363 
1364 	free(escaped_cmd);
1365 }
1366 
1367 int
rn_for_flist(struct view_t * view,const char cmd[],const char title[],int very,int interactive)1368 rn_for_flist(struct view_t *view, const char cmd[], const char title[],
1369 		int very, int interactive)
1370 {
1371 	enum { MAX_TITLE_WIDTH = 80 };
1372 
1373 	/* It makes sense to do escaping before adding ellipses to get a predictable
1374 	 * result. */
1375 	char *escaped_title = escape_unreadable(title);
1376 	char *final_title = right_ellipsis(escaped_title, MAX_TITLE_WIDTH,
1377 			curr_stats.ellipsis);
1378 	free(escaped_title);
1379 
1380 	flist_custom_start(view, final_title);
1381 	free(final_title);
1382 
1383 	if(interactive && curr_stats.load_stage != 0)
1384 	{
1385 		ui_shutdown();
1386 	}
1387 
1388 	setup_shellout_env();
1389 	int error = (process_cmd_output("Loading custom view", cmd, 1, interactive,
1390 				&path_handler, view) != 0);
1391 	cleanup_shellout_env();
1392 
1393 	if(error)
1394 	{
1395 		show_error_msgf("Trouble running command", "Unable to run: %s", cmd);
1396 		return 1;
1397 	}
1398 
1399 	flist_custom_end(view, very);
1400 	return 0;
1401 }
1402 
1403 /* Implements process_cmd_output() callback that loads paths into custom
1404  * view. */
1405 static void
path_handler(const char line[],void * arg)1406 path_handler(const char line[], void *arg)
1407 {
1408 	view_t *view = arg;
1409 	flist_custom_add_spec(view, line);
1410 }
1411 
1412 int
rn_for_lines(const char cmd[],char *** lines,int * nlines)1413 rn_for_lines(const char cmd[], char ***lines, int *nlines)
1414 {
1415 	int error;
1416 	strlist_t list = {};
1417 
1418 	setup_shellout_env();
1419 	error = (process_cmd_output("Loading list", cmd, 1, 0, &line_handler,
1420 				&list) != 0);
1421 	cleanup_shellout_env();
1422 
1423 	if(error)
1424 	{
1425 		free_string_array(list.items, list.nitems);
1426 		return 1;
1427 	}
1428 
1429 	*lines = list.items;
1430 	*nlines = list.nitems;
1431 	return 0;
1432 }
1433 
1434 /* Implements process_cmd_output() callback that collects lines into a list. */
1435 static void
line_handler(const char line[],void * arg)1436 line_handler(const char line[], void *arg)
1437 {
1438 	strlist_t *const list = arg;
1439 	list->nitems = add_to_string_array(&list->items, list->nitems, line);
1440 }
1441 
1442 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1443 /* vim: set cinoptions+=t0 filetype=c : */
1444