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