1    	/*
2    	 * shvar.c
3    	 *
4    	 * Implementation of non-destructively reading/writing files containing
5    	 * only shell variable declarations and full-line comments.
6    	 *
7    	 * Includes explicit inheritance mechanism intended for use with
8    	 * Red Hat Linux ifcfg-* files.  There is no protection against
9    	 * inheritance loops; they will generally cause stack overflows.
10   	 * Furthermore, they are only intended for one level of inheritance;
11   	 * the value setting algorithm assumes this.
12   	 *
13   	 * Copyright 1999,2000 Red Hat, Inc.
14   	 *
15   	 * This is free software; you can redistribute it and/or modify it
16   	 * under the terms of the GNU General Public License as published by
17   	 * the Free Software Foundation; either version 2 of the License, or
18   	 * (at your option) any later version.
19   	 *
20   	 * This program is distributed in the hope that it will be useful, but
21   	 * WITHOUT ANY WARRANTY; without even the implied warranty of
22   	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23   	 * General Public License for more details.
24   	 *
25   	 * You should have received a copy of the GNU General Public License along
26   	 * with this program; if not, write to the Free Software Foundation, Inc.,
27   	 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28   	 *
29   	 */
30   	
31   	#include <fcntl.h>
32   	#include <stdio.h>
33   	#include <stdlib.h>
34   	#include <string.h>
35   	#include <sys/types.h>
36   	#include <sys/stat.h>
37   	#include <unistd.h>
38   	
39   	#include "shvar.h"
40   	
41   	/* Open the file <name>, returning a shvarFile on success and NULL on failure.
42   	   Add a wrinkle to let the caller specify whether or not to create the file
43   	   (actually, return a structure anyway) if it doesn't exist. */
44   	static shvarFile *
45   	svOpenFile(const char *name, gboolean create)
46   	{
47   	    shvarFile *s = NULL;
48   	    int closefd = 0;
49   	
50   	    s = g_malloc0(sizeof(shvarFile));
51   	
52   	    s->fd = -1;
53   	    if (create)
54   		s->fd = open(name, O_RDWR); /* NOT O_CREAT */
55   	 
56   	    if (!create || s->fd == -1) {
57   		/* try read-only */
58   		s->fd = open(name, O_RDONLY); /* NOT O_CREAT */
59   		if (s->fd != -1) closefd = 1;
60   	    }
61   	    s->fileName = g_strdup(name);
62   	
63   	    if (s->fd != -1) {
64   		struct stat buf;
65   		char *p, *q;
66   	
67   		if (fstat(s->fd, &buf) < 0) goto bail;
68   		s->arena = g_malloc0(buf.st_size + 1);
69   	
70   		if (read(s->fd, s->arena, buf.st_size) < 0) goto bail;
71   	
72   		/* we'd use g_strsplit() here, but we want a list, not an array */
73   		for(p = s->arena; (q = strchr(p, '\n')) != NULL; p = q + 1) {
74   			s->lineList = g_list_append(s->lineList, g_strndup(p, q - p));
75   		}
76   	
77   		/* closefd is set if we opened the file read-only, so go ahead and
78   		   close it, because we can't write to it anyway */
79   		if (closefd) {
80   		    close(s->fd);
81   		    s->fd = -1;
82   		}
83   	
84   	        return s;
85   	    }
86   	
87   	    if (create) {
88   	        return s;
89   	    }
90   	
91   	bail:
92   	    if (s->fd != -1) close(s->fd);
93   	    g_free (s->arena);
94   	    g_free (s->fileName);
95   	    g_free (s);
96   	    return NULL;
97   	}
98   	
99   	/* Open the file <name>, return shvarFile on success, NULL on failure */
100  	shvarFile *
101  	svNewFile(const char *name)
102  	{
103  	    return svOpenFile(name, FALSE);
104  	}
105  	
106  	/* Create a new file structure, returning actual data if the file exists,
107  	 * and a suitable starting point if it doesn't. */
108  	shvarFile *
109  	svCreateFile(const char *name)
110  	{
111  	    return svOpenFile(name, TRUE);
112  	}
113  	
114  	/* remove escaped characters in place */
115  	void
116  	svUnescape(char *s) {
117  	    int len, i;
118  	
119  	    len = strlen(s);
120  	    if (len >= 2 && (s[0] == '"' || s[0] == '\'') && s[0] == s[len-1]) {
121  		i = len - 2;
122  		if (i == 0)
123  		  s[0] = '\0';
124  		else {
125  		  memmove(s, s+1, i);
126  		  s[i+1] = '\0';
127  		  len = i;
128  		}
129  	    }
130  	    for (i = 0; i < len; i++) {
131  		if (s[i] == '\\') {
132  		    memmove(s+i, s+i+1, len-(i+1));
133  		    len--;
134  		}
135  		s[len] = '\0';
136  	    }
137  	}
138  	
139  	
140  	/* create a new string with all necessary characters escaped.
141  	 * caller must free returned string
142  	 */
143  	static const char escapees[] = "\"'\\$~`";		/* must be escaped */
144  	static const char spaces[] = " \t|&;()<>";		/* only require "" */
145  	static const char newlines[] = "\n\r";			/* will be removed */
146  	char *
147  	svEscape(const char *s) {
148  	    char *new;
149  	    int i, j, mangle = 0, space = 0, newline = 0;
150  	    int newlen, slen;
151  	    static int esclen, splen;
152  	
153  	    if (!esclen) esclen = strlen(escapees);
154  	    if (!splen) splen = strlen(spaces);
155  	    slen = strlen(s);
156  	
157  	    for (i = 0; i < slen; i++) {
158  		if (strchr(escapees, s[i])) mangle++;
159  		if (strchr(spaces, s[i])) space++;
160  		if (strchr(newlines, s[i])) newline++;
161  	    }
162  	    if (!mangle && !space && !newline) return strdup(s);
163  	
164  	    newlen = slen + mangle - newline + 3;	/* 3 is extra ""\0 */
165  	    new = g_malloc0(newlen);
166  	
167  	    j = 0;
168  	    new[j++] = '"';
169  	    for (i = 0; i < slen; i++) {
170  		if (strchr(newlines, s[i]))
171  		    continue;
172  		if (strchr(escapees, s[i])) {
173  		    new[j++] = '\\';
174  		}
175  		new[j++] = s[i];
176  	    }
177  	    new[j++] = '"';
178  	    g_assert(j == slen + mangle - newline + 2); /* j is the index of the '\0' */
179  	
180  	    return new;
181  	}
182  	
183  	/* Get the value associated with the key, and leave the current pointer
184  	 * pointing at the line containing the value.  The char* returned MUST
185  	 * be freed by the caller.
186  	 */
187  	char *
188  	svGetValue(shvarFile *s, const char *key, gboolean verbatim)
189  	{
190  	    char *value = NULL;
191  	    char *line;
192  	    char *keyString;
193  	    int len;
194  	
195  	    g_assert(s);
196  	    g_assert(key);
197  	
198  	    keyString = g_malloc0(strlen(key) + 2);
199  	    strcpy(keyString, key);
200  	    keyString[strlen(key)] = '=';
201  	    len = strlen(keyString);
202  	
203  	    for (s->current = s->lineList; s->current; s->current = s->current->next) {
204  		line = s->current->data;
205  		if (!strncmp(keyString, line, len)) {
206  		    value = g_strdup(line + len);
207  		    if (!verbatim)
208  		      svUnescape(value);
209  		    break;
210  		}
211  	    }
212  	    g_free(keyString);
213  	
214  	    if (value) {
215  		if (value[0]) {
216  		    return value;
217  		} else {
218  		    g_free(value);
219  		    return NULL;
220  		}
221  	    }
222  	    if (s->parent) value = svGetValue(s->parent, key, verbatim);
223  	    return value;
224  	}
225  	
226  	/* return 1 if <key> resolves to any truth value (e.g. "yes", "y", "true")
227  	 * return 0 if <key> resolves to any non-truth value (e.g. "no", "n", "false")
228  	 * return <default> otherwise
229  	 */
230  	int
231  	svTrueValue(shvarFile *s, const char *key, int def)
232  	{
233  	    char *tmp;
234  	    int returnValue = def;
235  	
236  	    tmp = svGetValue(s, key, FALSE);
237  	    if (!tmp) return returnValue;
238  	
239  	    if ( (!strcasecmp("yes", tmp)) ||
240  		 (!strcasecmp("true", tmp)) ||
241  		 (!strcasecmp("t", tmp)) ||
242  		 (!strcasecmp("y", tmp)) ) returnValue = 1;
243  	    else
244  	    if ( (!strcasecmp("no", tmp)) ||
245  		 (!strcasecmp("false", tmp)) ||
246  		 (!strcasecmp("f", tmp)) ||
247  		 (!strcasecmp("n", tmp)) ) returnValue = 0;
248  	
249  	    g_free (tmp);
250  	    return returnValue;
251  	}
252  	
253  	
254  	/* Set the variable <key> equal to the value <value>.
255  	 * If <key> does not exist, and the <current> pointer is set, append
256  	 * the key=value pair after that line.  Otherwise, prepend the pair
257  	 * to the top of the file.  Here's the algorithm, as the C code
258  	 * seems to be rather dense:
259  	 *
260  	 * if (value == NULL), then:
261  	 *     if val2 (parent): change line to key= or append line key=
262  	 *     if val1 (this)  : delete line
263  	 *     else noop
264  	 * else use this table:
265  	 *                                val2
266  	 *             NULL              value               other
267  	 * v   NULL    append line       noop                append line
268  	 * a
269  	 * l   value   noop              noop                noop
270  	 * 1
271  	 *     other   change line       delete line         change line
272  	 *
273  	 * No changes are ever made to the parent config file, only to the
274  	 * specific file passed on the command line.
275  	 *
276  	 */
277  	void
278  	svSetValue(shvarFile *s, const char *key, const char *value, gboolean verbatim)
279  	{
280  	    char *newval = NULL, *val1 = NULL, *val2 = NULL;
281  	    char *keyValue;
282  	
283  	    g_assert(s);
284  	    g_assert(key);
285  	    /* value may be NULL */
286  	
287  	    if (value)
288  	        newval = verbatim ? g_strdup(value) : svEscape(value);
289  	    keyValue = g_strdup_printf("%s=%s", key, newval ? newval : "");
290  	
291  	    val1 = svGetValue(s, key, FALSE);
292  	    if (val1 && newval && !strcmp(val1, newval)) goto bail;
293  	    if (s->parent) val2 = svGetValue(s->parent, key, FALSE);
294  	
295  	    if (!newval || !newval[0]) {
296  		/* delete value somehow */
297  		if (val2) {
298  		    /* change/append line to get key= */
299  		    if (s->current) s->current->data = keyValue;
300  		    else s->lineList = g_list_append(s->lineList, keyValue);
301  		    s->modified = 1;
302  		    goto end;
303  		} else if (val1) {
304  		    /* delete line */
305  		    s->lineList = g_list_remove_link(s->lineList, s->current);
306  		    g_list_free_1(s->current);
307  		    s->modified = 1;
308  		}
309  		goto bail; /* do not need keyValue */
310  	    }
311  	
312  	    if (!val1) {
313  		if (val2 && !strcmp(val2, newval)) goto end;
314  		/* append line */
315  		s->lineList = g_list_append(s->lineList, keyValue);
316  		s->modified = 1;
317  		goto end;
318  	    }
319  	
320  	    /* deal with a whole line of noops */
321  	    if (val1 && !strcmp(val1, newval)) goto end;
322  	
323  	    /* At this point, val1 && val1 != value */
324  	    if (val2 && !strcmp(val2, newval)) {
325  		/* delete line */
326  		s->lineList = g_list_remove_link(s->lineList, s->current);
327  		g_list_free_1(s->current);
328  		s->modified = 1;
329  		goto bail; /* do not need keyValue */
330  	    } else {
331  		/* change line */
332  		if (s->current) s->current->data = keyValue;
333  		else s->lineList = g_list_append(s->lineList, keyValue);
334  		s->modified = 1;
335  	    }
336  	
337  	end:
338  	    g_free(newval);
339  	    g_free(val1);
340  	    g_free(val2);
341  	    return;
342  	
343  	bail:
344  	    g_free (keyValue);
345  	    goto end;
346  	}
347  	
348  	/* Write the current contents iff modified.  Returns -1 on error
349  	 * and 0 on success.  Do not write if no values have been modified.
350  	 * The mode argument is only used if creating the file, not if
351  	 * re-writing an existing file, and is passed unchanged to the
352  	 * open() syscall.
353  	 */
354  	int
355  	svWriteFile(shvarFile *s, int mode)
356  	{
357  	    FILE *f;
358  	    int tmpfd;
359  	
(1) Event cond_true: Condition "s->modified", taking true branch
360  	    if (s->modified) {
(2) Event cond_true: Condition "s->fd == -1", taking true branch
361  		if (s->fd == -1)
362  		    s->fd = open(s->fileName, O_WRONLY|O_CREAT, mode);
(3) Event cond_false: Condition "s->fd == -1", taking false branch
363  		if (s->fd == -1)
(4) Event if_end: End of if statement
364  		    return -1;
(5) Event cond_false: Condition "ftruncate(s->fd, 0) < 0", taking false branch
365  		if (ftruncate(s->fd, 0) < 0)
(6) Event if_end: End of if statement
366  		    return -1;
367  	
(7) Event negative_return_fn: Function "dup(s->fd)" returns a negative number.
(8) Event var_assign: Assigning: signed variable "tmpfd" = "dup(int)".
Also see events: [negative_returns]
368  		tmpfd = dup(s->fd);
(9) Event negative_returns: "tmpfd" is passed to a parameter that cannot be negative.
Also see events: [negative_return_fn][var_assign]
369  		f = fdopen(tmpfd, "w");
370  		fseek(f, 0, SEEK_SET);
371  		for (s->current = s->lineList; s->current; s->current = s->current->next) {
372  		    char *line = s->current->data;
373  		    fprintf(f, "%s\n", line);
374  		}
375  		fclose(f);
376  	    }
377  	
378  	    return 0;
379  	}
380  	
381  	 
382  	/* Close the file descriptor (if open) and delete the shvarFile.
383  	 * Returns -1 on error and 0 on success.
384  	 */
385  	int
386  	svCloseFile(shvarFile *s)
387  	{
388  	
389  	    g_assert(s);
390  	
391  	    if (s->fd != -1) close(s->fd);
392  	
393  	    g_free(s->arena);
394  	    g_free(s->fileName);
395  	    g_list_free_full (s->lineList, g_free); /* implicitly frees s->current */
396  	    g_free(s);
397  	    return 0;
398  	}
399