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, ¤t_mon) == 0 &&
221 cached != NULL)
222 {
223 if(filemon_equal(¤t_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, ¤t_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