1 /* vifm
2  * Copyright (C) 2012 xaizek.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include "builtin_functions.h"
20 
21 #include <sys/types.h> /* mode_t */
22 
23 #include <assert.h> /* assert() */
24 #include <stddef.h> /* NULL size_t */
25 #include <stdlib.h> /* free() */
26 #include <string.h> /* strcmp() strdup() strpbrk() */
27 
28 #include "compat/fs_limits.h"
29 #include "compat/os.h"
30 #include "engine/functions.h"
31 #include "engine/text_buffer.h"
32 #include "engine/var.h"
33 #include "ui/cancellation.h"
34 #include "ui/tabs.h"
35 #include "ui/ui.h"
36 #include "utils/filemon.h"
37 #include "utils/fs.h"
38 #include "utils/fsddata.h"
39 #include "utils/macros.h"
40 #include "utils/path.h"
41 #include "utils/str.h"
42 #include "utils/string_array.h"
43 #include "utils/test_helpers.h"
44 #include "utils/trie.h"
45 #include "utils/utils.h"
46 #include "filelist.h"
47 #include "macros.h"
48 #include "types.h"
49 
50 /* A single entry of cache of external commands. */
51 typedef struct
52 {
53 	fsddata_t *caches; /* A set of named caches. */
54 	filemon_t mon;     /* File monitor. */
55 }
56 extcache_t;
57 
58 static var_t chooseopt_builtin(const call_info_t *call_info);
59 static var_t executable_builtin(const call_info_t *call_info);
60 static var_t expand_builtin(const call_info_t *call_info);
61 static var_t extcached_builtin(const call_info_t *call_info);
62 TSTATIC void set_extcached_monitor_type(FileMonType type);
63 static var_t filetype_builtin(const call_info_t *call_info);
64 static int get_fnum(var_t fnum);
65 static const char * type_of_link_target(const dir_entry_t *entry);
66 static var_t fnameescape_builtin(const call_info_t *call_info);
67 static var_t getpanetype_builtin(const call_info_t *call_info);
68 static var_t has_builtin(const call_info_t *call_info);
69 static var_t layoutis_builtin(const call_info_t *call_info);
70 static var_t paneisat_builtin(const call_info_t *call_info);
71 static var_t system_builtin(const call_info_t *call_info);
72 static var_t tabpagenr_builtin(const call_info_t *call_info);
73 static var_t term_builtin(const call_info_t *call_info);
74 static var_t execute_cmd(var_t cmd_arg, int interactive, int preserve_stdin);
75 
76 /* Function descriptions. */
77 static const function_t functions[] = {
78 	/* Name          Description                    Args   Handler  */
79 	{ "chooseopt",   "query choose options",       {1,1}, &chooseopt_builtin },
80 	{ "executable",  "check for executable file",  {1,1}, &executable_builtin },
81 	{ "expand",      "expand macros in a string",  {1,1}, &expand_builtin },
82 	{ "extcached",   "caches result of a command", {3,3}, &extcached_builtin },
83 	{ "filetype",    "retrieve type of a file",    {1,2}, &filetype_builtin },
84 	{ "fnameescape", "escapes string for a :cmd",  {1,1}, &fnameescape_builtin },
85 	{ "getpanetype", "retrieve type of file list", {0,0}, &getpanetype_builtin},
86 	{ "has",         "check for specific ability", {1,1}, &has_builtin },
87 	{ "layoutis",    "query current layout",       {1,1}, &layoutis_builtin },
88 	{ "paneisat",    "query pane location",        {1,1}, &paneisat_builtin },
89 	{ "system",      "execute external command",   {1,1}, &system_builtin },
90 	{ "tabpagenr",   "number of current/last tab", {0,1}, &tabpagenr_builtin },
91 	{ "term",        "run interactive command",    {1,1}, &term_builtin },
92 };
93 
94 /* Kind of monitor used by the extcached(). */
95 static FileMonType extcached_mon_type = FMT_CHANGED;
96 
97 void
init_builtin_functions(void)98 init_builtin_functions(void)
99 {
100 	size_t i;
101 	for(i = 0; i < ARRAY_LEN(functions); ++i)
102 	{
103 		int result = function_register(&functions[i]);
104 		assert(result == 0 && "Builtin function registration error");
105 		(void)result;
106 	}
107 }
108 
109 /* Retrieves values of options related to file choosing as a string.  On unknown
110  * arguments empty string is returned. */
111 static var_t
chooseopt_builtin(const call_info_t * call_info)112 chooseopt_builtin(const call_info_t *call_info)
113 {
114 	const char *result = NULL;
115 	char *type;
116 
117 	type = var_to_str(call_info->argv[0]);
118 	if(strcmp(type, "files") == 0)
119 	{
120 		result = curr_stats.chosen_files_out;
121 	}
122 	else if(strcmp(type, "dir") == 0)
123 	{
124 		result = curr_stats.chosen_dir_out;
125 	}
126 	else if(strcmp(type, "cmd") == 0)
127 	{
128 		result = curr_stats.on_choose;
129 	}
130 	else if(strcmp(type, "delimiter") == 0)
131 	{
132 		result = curr_stats.output_delimiter;
133 	}
134 	free(type);
135 
136 	return var_from_str(result == NULL ? "" : result);
137 }
138 
139 /* Checks whether executable exists at absolute path or in directories listed in
140  * $PATH when path isn't absolute.  Checks for various executable extensions on
141  * Windows.  Returns boolean value describing result of the check. */
142 static var_t
executable_builtin(const call_info_t * call_info)143 executable_builtin(const call_info_t *call_info)
144 {
145 	int exists;
146 	char *str_val;
147 
148 	str_val = var_to_str(call_info->argv[0]);
149 
150 	if(strpbrk(str_val, PATH_SEPARATORS) != NULL)
151 	{
152 		exists = executable_exists(str_val);
153 	}
154 	else
155 	{
156 		exists = (find_cmd_in_path(str_val, 0UL, NULL) == 0);
157 	}
158 
159 	free(str_val);
160 
161 	return var_from_bool(exists);
162 }
163 
164 /* Returns string after expanding expression. */
165 static var_t
expand_builtin(const call_info_t * call_info)166 expand_builtin(const call_info_t *call_info)
167 {
168 	var_t result;
169 	char *result_str;
170 	char *str_val;
171 	char *env_expanded_str_val;
172 
173 	str_val = var_to_str(call_info->argv[0]);
174 	env_expanded_str_val = expand_envvars(str_val, 0);
175 	result_str = ma_expand(env_expanded_str_val, NULL, NULL, 0);
176 	free(env_expanded_str_val);
177 	free(str_val);
178 
179 	result = var_from_str(result_str);
180 	free(result_str);
181 
182 	return result;
183 }
184 
185 /* Returns cached value of an external command.  Cache validity is bound to a
186  * file. */
187 static var_t
extcached_builtin(const call_info_t * call_info)188 extcached_builtin(const call_info_t *call_info)
189 {
190 	static trie_t *cache;
191 	if(cache == NULL)
192 	{
193 		cache = trie_create();
194 	}
195 
196 	char *cache_name = var_to_str(call_info->argv[0]);
197 	if(cache_name == NULL)
198 	{
199 		return var_error();
200 	}
201 
202 	char *path = var_to_str(call_info->argv[1]);
203 	if(path == NULL)
204 	{
205 		return var_error();
206 	}
207 
208 	char canonic[PATH_MAX + 1];
209 	to_canonic_path(path, flist_get_dir(curr_view), canonic, sizeof(canonic));
210 	replace_string(&path, canonic);
211 
212 	extcache_t *cached = NULL;
213 	void *data;
214 	if(trie_get(cache, path, &data) == 0)
215 	{
216 		cached = data;
217 	}
218 
219 	filemon_t current_mon = {};
220 	if(filemon_from_file(path, extcached_mon_type, &current_mon) == 0 &&
221 			cached != NULL)
222 	{
223 		if(filemon_equal(&current_mon, &cached->mon))
224 		{
225 			void *cached_output;
226 			if(fsddata_get(cached->caches, cache_name, &cached_output) == 0)
227 			{
228 				free(cache_name);
229 				free(path);
230 				return var_from_str(cached_output);
231 			}
232 		}
233 		else
234 		{
235 			fsddata_free(cached->caches);
236 			cached->caches = NULL;
237 		}
238 	}
239 
240 	if(cached == NULL)
241 	{
242 		cached = malloc(sizeof(*cached));
243 		if(cached == NULL)
244 		{
245 			free(cache_name);
246 			free(path);
247 			return var_error();
248 		}
249 
250 		cached->caches = NULL;
251 
252 		if(trie_set(cache, path, cached) < 0)
253 		{
254 			free(cached);
255 			free(cache_name);
256 			free(path);
257 			return var_error();
258 		}
259 	}
260 
261 	if(cached->caches == NULL)
262 	{
263 		cached->caches = fsddata_create(0, 0);
264 	}
265 
266 	filemon_assign(&cached->mon, &current_mon);
267 	free(path);
268 
269 	var_t output = execute_cmd(call_info->argv[2], call_info->interactive, 0);
270 	(void)fsddata_set(cached->caches, cache_name, var_to_str(output));
271 	free(cache_name);
272 
273 	return output;
274 }
275 
276 /* Modifies kind of monitor used by the extcached(). */
277 TSTATIC void
set_extcached_monitor_type(FileMonType type)278 set_extcached_monitor_type(FileMonType type)
279 {
280 	extcached_mon_type = type;
281 }
282 
283 /* Gets string representation of file type.  Returns the string. */
284 static var_t
filetype_builtin(const call_info_t * call_info)285 filetype_builtin(const call_info_t *call_info)
286 {
287 	const int fnum = get_fnum(call_info->argv[0]);
288 	int resolve_links = (call_info->argc > 1 && var_to_bool(call_info->argv[1]));
289 
290 	const char *result_str = "";
291 	if(fnum >= 0)
292 	{
293 		const dir_entry_t *entry = &curr_view->dir_entry[fnum];
294 		if(entry->type == FT_LINK && resolve_links)
295 		{
296 			result_str = type_of_link_target(entry);
297 		}
298 		else
299 		{
300 			result_str = get_type_str(entry->type);
301 		}
302 	}
303 	return var_from_str(result_str);
304 }
305 
306 /* Turns {fnum} into file position.  Returns the position or -1 if the argument
307  * is wrong. */
308 static int
get_fnum(var_t fnum)309 get_fnum(var_t fnum)
310 {
311 	char *str_val = var_to_str(fnum);
312 
313 	int pos = -1;
314 	if(strcmp(str_val, ".") == 0)
315 	{
316 		pos = curr_view->list_pos;
317 	}
318 	else
319 	{
320 		int int_val = var_to_int(fnum);
321 		if(int_val > 0 && int_val <= curr_view->list_rows)
322 		{
323 			pos = int_val - 1;
324 		}
325 	}
326 
327 	free(str_val);
328 	return pos;
329 }
330 
331 /* Resoves file type of link target.  The entry parameter is expected to point
332  * at symbolic link.  Returns the type as a string. */
333 static const char *
type_of_link_target(const dir_entry_t * entry)334 type_of_link_target(const dir_entry_t *entry)
335 {
336 	char path[PATH_MAX + 1];
337 	struct stat s;
338 
339 	get_full_path_of(entry, sizeof(path), path);
340 	if(get_link_target_abs(path, entry->origin, path, sizeof(path)) != 0 ||
341 			os_stat(path, &s) != 0)
342 	{
343 		return "broken";
344 	}
345 	return get_type_str(get_type_from_mode(s.st_mode));
346 }
347 
348 /* Escapes argument to make it suitable for use as an argument in :commands. */
349 static var_t
fnameescape_builtin(const call_info_t * call_info)350 fnameescape_builtin(const call_info_t *call_info)
351 {
352 	var_t result;
353 
354 	char *const str_val = var_to_str(call_info->argv[0]);
355 	char *const escaped = shell_like_escape(str_val, 1);
356 	free(str_val);
357 
358 	result = var_from_str(escaped);
359 	free(escaped);
360 	return result;
361 }
362 
363 /* Retrieves type of current pane as a string. */
364 static var_t
getpanetype_builtin(const call_info_t * call_info)365 getpanetype_builtin(const call_info_t *call_info)
366 {
367 	if(!flist_custom_active(curr_view))
368 	{
369 		return var_from_str("regular");
370 	}
371 
372 	switch(curr_view->custom.type)
373 	{
374 		case CV_REGULAR:
375 			return var_from_str("custom");
376 
377 		case CV_VERY:
378 			return var_from_str("very-custom");
379 
380 		case CV_CUSTOM_TREE:
381 		case CV_TREE:
382 			return var_from_str("tree");
383 
384 		case CV_DIFF:
385 		case CV_COMPARE:
386 			return var_from_str("compare");
387 	}
388 	return var_from_str("UNKNOWN");
389 }
390 
391 /* Checks current layout configuration.  Returns boolean value that reflects
392  * state of specified layout type. */
393 static var_t
layoutis_builtin(const call_info_t * call_info)394 layoutis_builtin(const call_info_t *call_info)
395 {
396 	char *type;
397 	int result;
398 
399 	type = var_to_str(call_info->argv[0]);
400 	if(strcmp(type, "only") == 0)
401 	{
402 		result = (curr_stats.number_of_windows == 1);
403 	}
404 	else if(strcmp(type, "split") == 0)
405 	{
406 		result = (curr_stats.number_of_windows == 2);
407 	}
408 	else if(strcmp(type, "vsplit") == 0)
409 	{
410 		result = (curr_stats.number_of_windows == 2 && curr_stats.split == VSPLIT);
411 	}
412 	else if(strcmp(type, "hsplit") == 0)
413 	{
414 		result = (curr_stats.number_of_windows == 2 && curr_stats.split == HSPLIT);
415 	}
416 	else
417 	{
418 		result = 0;
419 	}
420 	free(type);
421 
422 	return var_from_bool(result);
423 }
424 
425 /* Allows examining internal parameters from scripts to e.g. figure out
426  * environment in which application is running. */
427 static var_t
has_builtin(const call_info_t * call_info)428 has_builtin(const call_info_t *call_info)
429 {
430 	var_t result;
431 
432 	char *const str_val = var_to_str(call_info->argv[0]);
433 
434 	if(strcmp(str_val, "unix") == 0)
435 	{
436 		result = var_from_bool(get_env_type() == ET_UNIX);
437 	}
438 	else if(strcmp(str_val, "win") == 0)
439 	{
440 		result = var_from_bool(get_env_type() == ET_WIN);
441 	}
442 	else
443 	{
444 		result = var_false();
445 	}
446 
447 	free(str_val);
448 
449 	return result;
450 }
451 
452 /* Checks for relative position of current pane.  Returns boolean value that
453  * reflects state of specified location. */
454 static var_t
paneisat_builtin(const call_info_t * call_info)455 paneisat_builtin(const call_info_t *call_info)
456 {
457 	char *loc;
458 	int result;
459 
460 	const int only = (curr_stats.number_of_windows == 1);
461 	const int vsplit = (curr_stats.split == VSPLIT);
462 	const int first = (curr_view == &lwin);
463 
464 	loc = var_to_str(call_info->argv[0]);
465 	if(strcmp(loc, "top") == 0)
466 	{
467 		result = (only || vsplit || first);
468 	}
469 	else if(strcmp(loc, "bottom") == 0)
470 	{
471 		result = (only || vsplit || !first);
472 	}
473 	else if(strcmp(loc, "left") == 0)
474 	{
475 		result = (only || !vsplit || first);
476 	}
477 	else if(strcmp(loc, "right") == 0)
478 	{
479 		result = (only || !vsplit || !first);
480 	}
481 	else
482 	{
483 		result = 0;
484 	}
485 	free(loc);
486 
487 	return var_from_bool(result);
488 }
489 
490 /* Runs the command in a shell and returns its output (joined standard output
491  * and standard error streams).  All trailing newline characters are stripped to
492  * allow easy appending to command output.  Returns the output. */
493 static var_t
system_builtin(const call_info_t * call_info)494 system_builtin(const call_info_t *call_info)
495 {
496 	return execute_cmd(call_info->argv[0], call_info->interactive, 0);
497 }
498 
499 /* Retrieves number of current or last tab page.  Returns integer value with the
500  * number base one. */
501 static var_t
tabpagenr_builtin(const call_info_t * call_info)502 tabpagenr_builtin(const call_info_t *call_info)
503 {
504 	int first = 1;
505 
506 	if(call_info->argc != 0)
507 	{
508 		char *const type = var_to_str(call_info->argv[0]);
509 		if(strcmp(type, "$") != 0)
510 		{
511 			vle_tb_append_linef(vle_err, "Invalid argument (expected \"$\"): %s",
512 					type);
513 			free(type);
514 			return var_error();
515 		}
516 		free(type);
517 		first = 0;
518 	}
519 
520 	return var_from_int(first ? tabs_current(curr_view) + 1
521 	                          : tabs_count(curr_view));
522 }
523 
524 /* Runs interactive command in a shell and returns its output (joined standard
525  * output and standard error streams).  All trailing newline characters are
526  * stripped to allow easy appending to command output.  Returns the output. */
527 static var_t
term_builtin(const call_info_t * call_info)528 term_builtin(const call_info_t *call_info)
529 {
530 	ui_shutdown();
531 	return execute_cmd(call_info->argv[0], call_info->interactive, 1);
532 }
533 
534 /* Runs interactive command in a shell and returns its output (joined standard
535  * output and standard error streams).  All trailing newline characters are
536  * stripped to allow easy appending to command output.  Returns the output. */
537 static var_t
execute_cmd(var_t cmd_arg,int interactive,int preserve_stdin)538 execute_cmd(var_t cmd_arg, int interactive, int preserve_stdin)
539 {
540 	var_t result;
541 	char *cmd;
542 	FILE *cmd_stream;
543 	size_t cmd_out_len;
544 	char *result_str;
545 
546 	cmd = var_to_str(cmd_arg);
547 	cmd_stream = read_cmd_output(cmd, preserve_stdin);
548 	free(cmd);
549 
550 	if(interactive)
551 	{
552 		ui_cancellation_push_on();
553 	}
554 	else
555 	{
556 		ui_cancellation_push_off();
557 	}
558 
559 	result_str = read_nonseekable_stream(cmd_stream, &cmd_out_len, NULL, NULL);
560 	fclose(cmd_stream);
561 
562 	ui_cancellation_pop();
563 
564 	if(result_str == NULL)
565 	{
566 		return var_from_str("");
567 	}
568 
569 	/* Remove trailing new line characters. */
570 	while(cmd_out_len != 0U && result_str[cmd_out_len - 1] == '\n')
571 	{
572 		result_str[cmd_out_len - 1] = '\0';
573 		--cmd_out_len;
574 	}
575 
576 	result = var_from_str(result_str);
577 	free(result_str);
578 	return result;
579 }
580 
581 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
582 /* vim: set cinoptions+=t0 filetype=c : */
583