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 "variables.h"
20
21 #include <assert.h> /* assert() */
22 #include <ctype.h>
23 #include <stddef.h> /* NULL size_t */
24 #include <stdio.h>
25 #include <stdlib.h> /* free() realloc() */
26 #include <string.h> /* strcmp() */
27
28 #include "../compat/reallocarray.h"
29 #include "../utils/env.h"
30 #include "../utils/macros.h"
31 #include "../utils/str.h"
32 #include "../utils/string_array.h"
33 #include "private/options.h"
34 #include "completion.h"
35 #include "parsing.h"
36 #include "text_buffer.h"
37 #include "var.h"
38
39 #define VAR_NAME_MAX 64
40 #define VAL_LEN_MAX 2048
41
42 /* Types of supported variables. */
43 typedef enum
44 {
45 VT_ENVVAR, /* Environment variable. */
46 VT_ANY_OPTION, /* Global and local options (if local exists). */
47 VT_GLOBAL_OPTION, /* Global option. */
48 VT_LOCAL_OPTION, /* Local option. */
49 }
50 VariableType;
51
52 /* Supported operations. */
53 typedef enum
54 {
55 VO_ASSIGN, /* Assigning a variable (=). */
56 VO_APPEND, /* Appending to a string (.=). */
57 VO_ADD, /* Adding to numbers or composite values (+=). */
58 VO_SUB, /* Subtracting from numbers or removing from composites (-=). */
59 }
60 VariableOperation;
61
62 /* Description of a builtin variable. */
63 typedef struct
64 {
65 char *name; /* Name of the variable (including "v:" prefix). */
66 var_t val; /* Current value of the variable. */
67 }
68 builtinvar_t;
69
70 typedef struct
71 {
72 char *name;
73 char *val;
74 char *initial;
75 int from_parent;
76 int removed;
77 }
78 envvar_t;
79
80 const char ENV_VAR_NAME_FIRST_CHAR[] = "abcdefghijklmnopqrstuvwxyz"
81 "ABCDEFGHIJKLMNOPQRSTUVWXYZ_";
82 const char ENV_VAR_NAME_CHARS[] = "abcdefghijklmnopqrstuvwxyz"
83 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
84
85 static void init_var(const char name[], const char value[]);
86 static void clear_builtinvars(void);
87 static int extract_name(const char **in, VariableType *type, size_t buf_len,
88 char buf[]);
89 static int extract_op(const char **in, VariableOperation *vo);
90 static int parse_name(const char **in, const char first[], const char other[],
91 size_t buf_len, char buf[]);
92 static int is_valid_op(const char name[], VariableType vt,
93 VariableOperation vo);
94 static int perform_op(const char name[], VariableType vt,
95 VariableOperation vo, const char value[]);
96 static void append_envvar(const char *name, const char *val);
97 static void set_envvar(const char *name, const char *val);
98 static int perform_opt_op(const char name[], VariableType vt,
99 VariableOperation vo, const char value[]);
100 static envvar_t * get_record(const char *name);
101 static char * skip_non_whitespace(const char str[]);
102 static envvar_t * find_record(const char *name);
103 static void free_record(envvar_t *record);
104 static void clear_record(envvar_t *record);
105 static void complete_envvars(const char var[], const char **start);
106 static void complete_builtinvars(const char var[], const char **start);
107
108 static int initialized;
109 static envvar_t *vars;
110 static size_t nvars;
111 /* List of builtin variables. */
112 static builtinvar_t *builtin_vars;
113 /* Number of builtin variables. */
114 static size_t nbuiltins;
115
116 void
init_variables(void)117 init_variables(void)
118 {
119 int env_count;
120 char **env_lst;
121
122 if(nvars > 0)
123 {
124 clear_envvars();
125 }
126
127 env_lst = env_list(&env_count);
128 if(env_count > 0)
129 {
130 int i;
131
132 /* Allocate memory for environment variables. */
133 vars = reallocarray(NULL, env_count, sizeof(*vars));
134 assert(vars != NULL && "Failed to allocate memory for env vars.");
135
136 /* Initialize variable list. */
137 for(i = 0; i < env_count; ++i)
138 {
139 init_var(env_lst[i], env_get(env_lst[i]));
140 }
141 }
142
143 free_string_array(env_lst, env_count);
144
145 init_parser(&local_getenv);
146
147 initialized = 1;
148 }
149
150 const char *
local_getenv(const char envname[])151 local_getenv(const char envname[])
152 {
153 envvar_t *record = find_record(envname);
154 return (record == NULL || record->removed) ? "" : record->val;
155 }
156
157 /* Initializes environment variable inherited from parent process. */
158 static void
init_var(const char name[],const char value[])159 init_var(const char name[], const char value[])
160 {
161 envvar_t *const record = get_record(name);
162 if(record == NULL)
163 {
164 return;
165 }
166
167 record->from_parent = 1;
168
169 (void)replace_string(&record->initial, value);
170 (void)replace_string(&record->val, value);
171 if(record->initial == NULL || record->val == NULL)
172 {
173 free_record(record);
174 }
175 }
176
177 void
clear_variables(void)178 clear_variables(void)
179 {
180 assert(initialized);
181
182 clear_builtinvars();
183 clear_envvars();
184 }
185
186 /* Clears the list of builtin variables. */
187 static void
clear_builtinvars(void)188 clear_builtinvars(void)
189 {
190 size_t i;
191 for(i = 0U; i < nbuiltins; ++i)
192 {
193 free(builtin_vars[i].name);
194 var_free(builtin_vars[i].val);
195 }
196 nbuiltins = 0U;
197 free(builtin_vars);
198 builtin_vars = NULL;
199 }
200
201 void
clear_envvars(void)202 clear_envvars(void)
203 {
204 size_t i;
205 assert(initialized);
206
207 /* Free memory. */
208 for(i = 0U; i < nvars; ++i)
209 {
210 if(vars[i].name == NULL)
211 continue;
212
213 if(vars[i].from_parent)
214 {
215 set_envvar(vars[i].name, vars[i].initial);
216 }
217 else
218 {
219 env_remove(vars[i].name);
220 }
221 free_record(&vars[i]);
222 }
223
224 nvars = 0;
225 free(vars);
226 vars = NULL;
227 }
228
229 int
let_variables(const char cmd[])230 let_variables(const char cmd[])
231 {
232 char name[VAR_NAME_MAX + 1];
233 int error;
234 var_t res_var;
235 char *str_val;
236 ParsingErrors parsing_error;
237 VariableType type;
238 VariableOperation op;
239
240 assert(initialized);
241
242 if(extract_name(&cmd, &type, sizeof(name), name) != 0)
243 {
244 return 1;
245 }
246
247 cmd = skip_whitespace(cmd);
248
249 if(extract_op(&cmd, &op) != 0)
250 {
251 return -1;
252 }
253
254 cmd = skip_whitespace(cmd);
255
256 parsing_error = parse(cmd, 1, &res_var);
257 if(parsing_error != PE_NO_ERROR)
258 {
259 report_parsing_error(parsing_error);
260 return -1;
261 }
262
263 if(get_last_position() != NULL && *get_last_position() != '\0')
264 {
265 vle_tb_append_linef(vle_err, "%s: %s", "Incorrect :let statement",
266 "trailing characters");
267 return -1;
268 }
269
270 if(!is_valid_op(name, type, op))
271 {
272 vle_tb_append_linef(vle_err, "Wrong variable type for this operation");
273 return -1;
274 }
275
276 str_val = var_to_str(res_var);
277
278 error = perform_op(name, type, op, str_val);
279
280 free(str_val);
281 var_free(res_var);
282
283 return error;
284 }
285
286 /* Extracts name from the string. Returns zero on success, otherwise non-zero
287 * is returned. */
288 static int
extract_name(const char ** in,VariableType * type,size_t buf_len,char buf[])289 extract_name(const char **in, VariableType *type, size_t buf_len, char buf[])
290 {
291 int error;
292
293 /* Copy variable name. */
294 if(**in == '$')
295 {
296 ++*in;
297 error = (parse_name(in, ENV_VAR_NAME_FIRST_CHAR, ENV_VAR_NAME_CHARS,
298 buf_len, buf) != 0);
299 *type = VT_ENVVAR;
300 }
301 else if(**in == '&')
302 {
303 ++*in;
304
305 *type = VT_ANY_OPTION;
306 if(((*in)[0] == 'l' || (*in)[0] == 'g') && (*in)[1] == ':')
307 {
308 *type = (*in[0] == 'l') ? VT_LOCAL_OPTION : VT_GLOBAL_OPTION;
309 *in += 2;
310 }
311
312 error = (parse_name(in, OPT_NAME_FIRST_CHAR, OPT_NAME_CHARS, buf_len,
313 buf) != 0);
314 }
315 else
316 {
317 /* Currently we support only environment variables and options. */
318 vle_tb_append_line(vle_err, "Incorrect variable type");
319 return -1;
320 }
321 if(error)
322 {
323 vle_tb_append_line(vle_err, "Incorrect variable name");
324 return -1;
325 }
326
327 /* Test for empty variable name. */
328 if(buf[0] == '\0')
329 {
330 vle_tb_append_linef(vle_err, "%s: %s", "Unsupported variable name",
331 "empty name");
332 return -1;
333 }
334
335 return 0;
336 }
337
338 /* Extracts operation from the string. Sets operation advancing the
339 * pointer if needed. Returns zero on success, otherwise non-zero returned. */
340 static int
extract_op(const char ** in,VariableOperation * vo)341 extract_op(const char **in, VariableOperation *vo)
342 {
343 /* Check first operator char and skip it. */
344 if(**in == '.')
345 {
346 ++*in;
347 *vo = VO_APPEND;
348 }
349 else if(**in == '+')
350 {
351 *vo = VO_ADD;
352 ++*in;
353 }
354 else if(**in == '-')
355 {
356 *vo = VO_SUB;
357 ++*in;
358 }
359 else
360 {
361 *vo = VO_ASSIGN;
362 }
363
364 /* Check for equal sign and skip it. */
365 if(**in != '=')
366 {
367 vle_tb_append_linef(vle_err, "%s: '=' expected at %s",
368 "Incorrect :let statement", *in);
369 return 1;
370 }
371 ++*in;
372
373 return 0;
374 }
375
376 /* Parses name of the form `first { other }`. Returns zero on success,
377 * otherwise non-zero is returned. */
378 static int
parse_name(const char ** in,const char first[],const char other[],size_t buf_len,char buf[])379 parse_name(const char **in, const char first[], const char other[],
380 size_t buf_len, char buf[])
381 {
382 if(buf_len == 0UL || !char_is_one_of(first, **in))
383 {
384 return 1;
385 }
386
387 buf[0] = '\0';
388
389 do
390 {
391 strcatch(buf, **in);
392 ++*in;
393 }
394 while(--buf_len > 1UL && char_is_one_of(other, **in));
395
396 return 0;
397 }
398
399 /* Validates operation on a specific variable type. Returns non-zero for valid
400 * operation, otherwise zero is returned. */
401 static int
is_valid_op(const char name[],VariableType vt,VariableOperation vo)402 is_valid_op(const char name[], VariableType vt, VariableOperation vo)
403 {
404 opt_t *opt;
405
406 if(vt == VT_ENVVAR)
407 {
408 return (vo == VO_ASSIGN || vo == VO_APPEND);
409 }
410
411 opt = find_option(name, OPT_GLOBAL);
412 if(opt == NULL)
413 {
414 /* Let this error be handled somewhere else. */
415 return 1;
416 }
417
418 if(opt->type == OPT_BOOL)
419 {
420 return 0;
421 }
422
423 if(opt->type == OPT_STR)
424 {
425 return (vo == VO_ASSIGN || vo == VO_APPEND);
426 }
427
428 return (vo == VO_ASSIGN || vo == VO_ADD || vo == VO_SUB);
429 }
430
431 /* Performs operation on a value. Returns zero on success, otherwise non-zero
432 * is returned. */
433 static int
perform_op(const char name[],VariableType vt,VariableOperation vo,const char value[])434 perform_op(const char name[], VariableType vt, VariableOperation vo,
435 const char value[])
436 {
437 if(vt == VT_ENVVAR)
438 {
439 /* Update environment variable. */
440 if(vo == VO_APPEND)
441 {
442 append_envvar(name, value);
443 }
444 else
445 {
446 set_envvar(name, value);
447 }
448 return 0;
449 }
450
451 /* Update an option. */
452
453 if(vt == VT_ANY_OPTION || vt == VT_LOCAL_OPTION)
454 {
455 if(perform_opt_op(name, vt, vo, value) != 0)
456 {
457 return 1;
458 }
459 }
460
461 if(vt == VT_ANY_OPTION || vt == VT_GLOBAL_OPTION)
462 {
463 if(perform_opt_op(name, VT_GLOBAL_OPTION, vo, value) != 0)
464 {
465 return 1;
466 }
467 }
468
469 return 0;
470 }
471
472 static void
append_envvar(const char * name,const char * val)473 append_envvar(const char *name, const char *val)
474 {
475 envvar_t *record;
476 char *p;
477
478 record = find_record(name);
479 if(record == NULL)
480 {
481 set_envvar(name, val);
482 return;
483 }
484
485 p = realloc(record->val, strlen(record->val) + strlen(val) + 1);
486 if(p == NULL)
487 {
488 vle_tb_append_line(vle_err, "Not enough memory");
489 return;
490 }
491 record->val = p;
492
493 strcat(record->val, val);
494 env_set(name, record->val);
495 }
496
497 static void
set_envvar(const char * name,const char * val)498 set_envvar(const char *name, const char *val)
499 {
500 envvar_t *record;
501 char *p;
502
503 record = get_record(name);
504 if(record == NULL)
505 {
506 vle_tb_append_line(vle_err, "Not enough memory");
507 return;
508 }
509
510 p = strdup(val);
511 if(p == NULL)
512 {
513 vle_tb_append_line(vle_err, "Not enough memory");
514 return;
515 }
516 free(record->val);
517 record->val = p;
518 env_set(name, val);
519 }
520
521 /* Performs operation on an option. Returns zero on success, otherwise non-zero
522 * is returned. */
523 static int
perform_opt_op(const char name[],VariableType vt,VariableOperation vo,const char value[])524 perform_opt_op(const char name[], VariableType vt, VariableOperation vo,
525 const char value[])
526 {
527 OPT_SCOPE scope = (vt == VT_ANY_OPTION || vt == VT_LOCAL_OPTION)
528 ? OPT_LOCAL
529 : OPT_GLOBAL;
530
531 opt_t *const opt = find_option(name, scope);
532 if(opt == NULL)
533 {
534 if(vt == VT_ANY_OPTION)
535 {
536 return 0;
537 }
538
539 vle_tb_append_linef(vle_err, "Unknown %s option name: %s",
540 (scope == OPT_LOCAL) ? "local" : "global", name);
541 return 1;
542 }
543
544 switch(vo)
545 {
546 case VO_ASSIGN:
547 if(set_set(opt, value) != 0)
548 {
549 return 1;
550 }
551 break;
552 case VO_ADD:
553 case VO_APPEND:
554 if(set_add(opt, value) != 0)
555 {
556 return 1;
557 }
558 break;
559 case VO_SUB:
560 if(set_remove(opt, value) != 0)
561 {
562 return 1;
563 }
564 break;
565 }
566 return 0;
567 }
568
569 /* Searches for variable and creates new record if it didn't existed. */
570 static envvar_t *
get_record(const char * name)571 get_record(const char *name)
572 {
573 envvar_t *p = NULL;
574 size_t i;
575
576 /* Search for existing variable. */
577 for(i = 0U; i < nvars; ++i)
578 {
579 if(vars[i].name == NULL)
580 p = &vars[i];
581 else if(strcmp(vars[i].name, name) == 0)
582 return &vars[i];
583 }
584
585 if(p == NULL)
586 {
587 /* Try to reallocate list of variables. */
588 p = realloc(vars, sizeof(*vars)*(nvars + 1U));
589 if(p == NULL)
590 return NULL;
591 vars = p;
592 p = &vars[nvars];
593 ++nvars;
594 }
595
596 /* Initialize new record. */
597 p->initial = strdup("");
598 p->name = strdup(name);
599 p->val = strdup("");
600 p->from_parent = 0;
601 p->removed = 0;
602 if(p->initial == NULL || p->name == NULL || p->val == NULL)
603 {
604 free_record(p);
605 return NULL;
606 }
607 return p;
608 }
609
610 int
unlet_variables(const char cmd[])611 unlet_variables(const char cmd[])
612 {
613 int error = 0;
614 assert(initialized);
615
616 while(*cmd != '\0')
617 {
618 envvar_t *record;
619
620 char name[VAR_NAME_MAX + 1];
621 char *p;
622 int envvar = 1;
623
624 /* Check if it's environment variable. */
625 if(*cmd != '$')
626 envvar = 0;
627 else
628 cmd++;
629
630 /* Copy variable name. */
631 p = name;
632 while(*cmd != '\0' && char_is_one_of(ENV_VAR_NAME_CHARS, *cmd) &&
633 (size_t)(p - name) < sizeof(name) - 1)
634 {
635 *p++ = *cmd++;
636 }
637 *p = '\0';
638
639 if(*cmd != '\0' && !isspace(*cmd))
640 {
641 vle_tb_append_line(vle_err, "Trailing characters");
642 error++;
643 break;
644 }
645
646 cmd = skip_whitespace(cmd);
647
648 /* Currently we support only environment variables. */
649 if(!envvar)
650 {
651 vle_tb_append_linef(vle_err, "%s: %s", "Unsupported variable type", name);
652
653 cmd = skip_non_whitespace(cmd);
654 error++;
655 continue;
656 }
657
658 /* Test for empty variable name. */
659 if(name[0] == '\0')
660 {
661 vle_tb_append_linef(vle_err, "%s: %s", "Unsupported variable name",
662 "empty name");
663 error++;
664 continue;
665 }
666
667 record = find_record(name);
668 if(record == NULL || record->removed)
669 {
670 vle_tb_append_linef(vle_err, "%s: %s", "No such variable", name);
671 error++;
672 continue;
673 }
674
675 if(record->from_parent)
676 record->removed = 1;
677 else
678 free_record(record);
679 env_remove(name);
680 }
681
682 return error;
683 }
684
685 /* Skips consecutive non-whitespace characters. Returns pointer to the next
686 * character in the str. */
687 static char *
skip_non_whitespace(const char str[])688 skip_non_whitespace(const char str[])
689 {
690 while(!isspace(*str) && *str != '\0')
691 {
692 ++str;
693 }
694 return (char *)str;
695 }
696
697 /* searches for existent variable */
698 static envvar_t *
find_record(const char * name)699 find_record(const char *name)
700 {
701 size_t i;
702 for(i = 0U; i < nvars; ++i)
703 {
704 if(vars[i].name != NULL && stroscmp(vars[i].name, name) == 0)
705 return &vars[i];
706 }
707 return NULL;
708 }
709
710 static void
free_record(envvar_t * record)711 free_record(envvar_t *record)
712 {
713 free(record->initial);
714 free(record->name);
715 free(record->val);
716
717 clear_record(record);
718 }
719
720 static void
clear_record(envvar_t * record)721 clear_record(envvar_t *record)
722 {
723 record->initial = NULL;
724 record->name = NULL;
725 record->val = NULL;
726 }
727
728 void
complete_variables(const char var[],const char ** start)729 complete_variables(const char var[], const char **start)
730 {
731 assert(initialized && "Variables unit is not initialized.");
732
733 if(var[0] == '$')
734 {
735 complete_envvars(var, start);
736 }
737 else if(var[0] == 'v' && var[1] == ':')
738 {
739 complete_builtinvars(var, start);
740 }
741 else
742 {
743 *start = var;
744 vle_compl_add_match(var, "");
745 }
746 }
747
748 /* Completes environment variable name. var should point to "$...". *start is
749 * set to completion insertion position in var. */
750 static void
complete_envvars(const char var[],const char ** start)751 complete_envvars(const char var[], const char **start)
752 {
753 size_t i;
754 size_t len;
755
756 ++var;
757 *start = var;
758
759 /* Add all variables that start with given beginning. */
760 len = strlen(var);
761 for(i = 0U; i < nvars; ++i)
762 {
763 if(vars[i].name == NULL)
764 continue;
765 if(vars[i].removed)
766 continue;
767 if(strnoscmp(vars[i].name, var, len) == 0)
768 vle_compl_add_match(vars[i].name, vars[i].val);
769 }
770 vle_compl_finish_group();
771 vle_compl_add_last_match(var);
772 }
773
774 /* Completes builtin variable name. var should point to "v:...". *start is
775 * set to completion insertion position in var. */
776 static void
complete_builtinvars(const char var[],const char ** start)777 complete_builtinvars(const char var[], const char **start)
778 {
779 size_t i;
780 size_t len;
781
782 *start = var;
783
784 /* Add all variables that start with given beginning. */
785 len = strlen(var);
786 for(i = 0U; i < nbuiltins; ++i)
787 {
788 if(strncmp(builtin_vars[i].name, var, len) == 0)
789 {
790 char *const str_val = var_to_str(builtin_vars[i].val);
791 vle_compl_add_match(builtin_vars[i].name, str_val);
792 free(str_val);
793 }
794 }
795 vle_compl_finish_group();
796 vle_compl_add_last_match(var);
797 }
798
799 var_t
getvar(const char varname[])800 getvar(const char varname[])
801 {
802 size_t i;
803 for(i = 0U; i < nbuiltins; ++i)
804 {
805 if(strcmp(builtin_vars[i].name, varname) == 0)
806 {
807 return builtin_vars[i].val;
808 }
809 }
810
811 return var_error();
812 }
813
814 int
setvar(const char varname[],var_t val)815 setvar(const char varname[], var_t val)
816 {
817 builtinvar_t new_var;
818 size_t i;
819 void *p;
820
821 if(!starts_with_lit(varname, "v:"))
822 {
823 return 1;
824 }
825
826 /* Initialize new variable before doing anything else. */
827 new_var.name = strdup(varname);
828 new_var.val = var_clone(val);
829 if(new_var.name == NULL || new_var.val.type == VTYPE_ERROR)
830 {
831 free(new_var.name);
832 var_free(new_var.val);
833 return 1;
834 }
835
836 /* Search for existing variable. */
837 for(i = 0U; i < nbuiltins; ++i)
838 {
839 if(strcmp(builtin_vars[i].name, varname) == 0)
840 {
841 free(builtin_vars[i].name);
842 var_free(builtin_vars[i].val);
843 builtin_vars[i] = new_var;
844 return 0;
845 }
846 }
847
848 /* Try to reallocate list of variables. */
849 p = realloc(builtin_vars, sizeof(*builtin_vars)*(nbuiltins + 1U));
850 if(p == NULL)
851 {
852 free(new_var.name);
853 var_free(new_var.val);
854 return 1;
855 }
856 builtin_vars = p;
857 builtin_vars[nbuiltins] = new_var;
858 ++nbuiltins;
859
860 return 0;
861 }
862
863 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
864 /* vim: set cinoptions+=t0 filetype=c : */
865