]> TLD Linux GIT Repositories - rc-scripts.git/blob - src/shvar.c
- from PLD
[rc-scripts.git] / src / shvar.c
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
26  * along with this program; if not, write to the Free Software
27  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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 = open(name, O_RDWR); /* NOT O_CREAT */
53     if (s->fd == -1) {
54         /* try read-only */
55         s->fd = open(name, O_RDONLY); /* NOT O_CREAT */
56         if (s->fd != -1) closefd = 1;
57     }
58     s->fileName = g_strdup(name);
59
60     if (s->fd != -1) {
61         struct stat buf;
62         char *p, *q;
63
64         if (fstat(s->fd, &buf) < 0) goto bail;
65         s->arena = g_malloc0(buf.st_size + 1);
66
67         if (read(s->fd, s->arena, buf.st_size) < 0) goto bail;
68
69         /* we'd use g_strsplit() here, but we want a list, not an array */
70         for(p = s->arena; (q = strchr(p, '\n')) != NULL; p = q + 1) {
71                 s->lineList = g_list_append(s->lineList, g_strndup(p, q - p));
72         }
73
74         /* closefd is set if we opened the file read-only, so go ahead and
75            close it, because we can't write to it anyway */
76         if (closefd) {
77             close(s->fd);
78             s->fd = -1;
79         }
80
81         return s;
82     }
83
84     if (create) {
85         return s;
86     }
87
88 bail:
89     if (s->fd != -1) close(s->fd);
90     if (s->arena) g_free (s->arena);
91     if (s->fileName) g_free (s->fileName);
92     g_free (s);
93     return NULL;
94 }
95
96 /* Open the file <name>, return shvarFile on success, NULL on failure */
97 shvarFile *
98 svNewFile(const char *name)
99 {
100     return svOpenFile(name, FALSE);
101 }
102
103 /* Create a new file structure, returning actual data if the file exists,
104  * and a suitable starting point if it doesn't. */
105 shvarFile *
106 svCreateFile(const char *name)
107 {
108     return svOpenFile(name, TRUE);
109 }
110
111 /* remove escaped characters in place */
112 static void
113 unescape(char *s) {
114     int len, i;
115
116     len = strlen(s);
117     if ((s[0] == '"' || s[0] == '\'') && s[0] == s[len-1]) {
118         i = len - 2;
119         memmove(s, s+1, i);
120         s[i+1] = '\0';
121         len = i;
122     }
123     for (i = 0; i < len; i++) {
124         if (s[i] == '\\') {
125             memmove(s+i, s+i+1, len-(i+1));
126             len--;
127         }
128         s[len] = '\0';
129     }
130 }
131
132
133 /* create a new string with all necessary characters escaped.
134  * caller must free returned string
135  */
136 static const char escapees[] = "\"'\\$~`";              /* must be escaped */
137 static const char spaces[] = " \t|&;()<>";              /* only require "" */
138 static char *
139 escape(const char *s) {
140     char *new;
141     int i, j, mangle = 0, space = 0;
142     int newlen, slen;
143     static int esclen, splen;
144
145     if (!esclen) esclen = strlen(escapees);
146     if (!splen) splen = strlen(spaces);
147     slen = strlen(s);
148
149     for (i = 0; i < slen; i++) {
150         if (strchr(escapees, s[i])) mangle++;
151         if (strchr(spaces, s[i])) space++;
152     }
153     if (!mangle && !space) return strdup(s);
154
155     newlen = slen + mangle + 3; /* 3 is extra ""\0 */
156     new = g_malloc0(newlen);
157     if (!new) return NULL;
158
159     j = 0;
160     new[j++] = '"';
161     for (i = 0; i < slen; i++) {
162         if (strchr(escapees, s[i])) {
163             new[j++] = '\\';
164         }
165         new[j++] = s[i];
166     }
167     new[j++] = '"';
168     g_assert(j == slen + mangle + 2); /* j is the index of the '\0' */
169
170     return new;
171 }
172
173 /* Get the value associated with the key, and leave the current pointer
174  * pointing at the line containing the value.  The char* returned MUST
175  * be freed by the caller.
176  */
177 char *
178 svGetValue(shvarFile *s, const char *key)
179 {
180     char *value = NULL;
181     char *line;
182     char *keyString;
183     int len;
184
185     g_assert(s);
186     g_assert(key);
187
188     keyString = g_malloc0(strlen(key) + 2);
189     strcpy(keyString, key);
190     keyString[strlen(key)] = '=';
191     len = strlen(keyString);
192
193     for (s->current = s->lineList; s->current; s->current = s->current->next) {
194         line = s->current->data;
195         if (!strncmp(keyString, line, len)) {
196             value = g_strdup(line + len);
197             unescape(value);
198             break;
199         }
200     }
201     g_free(keyString);
202
203     if (value) {
204         if (value[0]) {
205             return value;
206         } else {
207             g_free(value);
208             return NULL;
209         }
210     }
211     if (s->parent) value = svGetValue(s->parent, key);
212     return value;
213 }
214
215 /* return 1 if <key> resolves to any truth value (e.g. "yes", "y", "true")
216  * return 0 if <key> resolves to any non-truth value (e.g. "no", "n", "false")
217  * return <default> otherwise
218  */
219 int
220 svTrueValue(shvarFile *s, const char *key, int def)
221 {
222     char *tmp;
223     int returnValue = def;
224
225     tmp = svGetValue(s, key);
226     if (!tmp) return returnValue;
227
228     if ( (!strcasecmp("yes", tmp)) ||
229          (!strcasecmp("true", tmp)) ||
230          (!strcasecmp("t", tmp)) ||
231          (!strcasecmp("y", tmp)) ) returnValue = 1;
232     else
233     if ( (!strcasecmp("no", tmp)) ||
234          (!strcasecmp("false", tmp)) ||
235          (!strcasecmp("f", tmp)) ||
236          (!strcasecmp("n", tmp)) ) returnValue = 0;
237
238     g_free (tmp);
239     return returnValue;
240 }
241
242
243 /* Set the variable <key> equal to the value <value>.
244  * If <key> does not exist, and the <current> pointer is set, append
245  * the key=value pair after that line.  Otherwise, prepend the pair
246  * to the top of the file.  Here's the algorithm, as the C code
247  * seems to be rather dense:
248  *
249  * if (value == NULL), then:
250  *     if val2 (parent): change line to key= or append line key=
251  *     if val1 (this)  : delete line
252  *     else noop
253  * else use this table:
254  *                                val2
255  *             NULL              value               other
256  * v   NULL    append line       noop                append line
257  * a
258  * l   value   noop              noop                noop
259  * 1
260  *     other   change line       delete line         change line
261  *
262  * No changes are ever made to the parent config file, only to the
263  * specific file passed on the command line.
264  *
265  */
266 void
267 svSetValue(shvarFile *s, const char *key, const char *value)
268 {
269     char *newval = NULL, *val1 = NULL, *val2 = NULL;
270     char *keyValue;
271
272     g_assert(s);
273     g_assert(key);
274     /* value may be NULL */
275
276     if (value) newval = escape(value);
277     keyValue = g_strdup_printf("%s=%s", key, newval ? newval : "");
278
279     val1 = svGetValue(s, key);
280     if (val1 && newval && !strcmp(val1, newval)) goto bail;
281     if (s->parent) val2 = svGetValue(s->parent, key);
282
283     if (!newval || !newval[0]) {
284         /* delete value somehow */
285         if (val2) {
286             /* change/append line to get key= */
287             if (s->current) s->current->data = keyValue;
288             else s->lineList = g_list_append(s->lineList, keyValue);
289             s->freeList = g_list_append(s->freeList, keyValue);
290             s->modified = 1;
291         } else if (val1) {
292             /* delete line */
293             s->lineList = g_list_remove_link(s->lineList, s->current);
294             g_list_free_1(s->current);
295             s->modified = 1;
296             goto bail; /* do not need keyValue */
297         }
298         goto end;
299     }
300
301     if (!val1) {
302         if (val2 && !strcmp(val2, newval)) goto end;
303         /* append line */
304         s->lineList = g_list_append(s->lineList, keyValue);
305         s->freeList = g_list_append(s->freeList, keyValue);
306         s->modified = 1;
307         goto end;
308     }
309
310     /* deal with a whole line of noops */
311     if (val1 && !strcmp(val1, newval)) goto end;
312
313     /* At this point, val1 && val1 != value */
314     if (val2 && !strcmp(val2, newval)) {
315         /* delete line */
316         s->lineList = g_list_remove_link(s->lineList, s->current);
317         g_list_free_1(s->current);
318         s->modified = 1;
319         goto bail; /* do not need keyValue */
320     } else {
321         /* change line */
322         if (s->current) s->current->data = keyValue;
323         else s->lineList = g_list_append(s->lineList, keyValue);
324         s->freeList = g_list_append(s->freeList, keyValue);
325         s->modified = 1;
326     }
327
328 end:
329     if (newval) free(newval);
330     if (val1) free(val1);
331     if (val2) free(val2);
332     return;
333
334 bail:
335     if (keyValue) free (keyValue);
336     goto end;
337 }
338
339 /* Write the current contents iff modified.  Returns -1 on error
340  * and 0 on success.  Do not write if no values have been modified.
341  * The mode argument is only used if creating the file, not if
342  * re-writing an existing file, and is passed unchanged to the
343  * open() syscall.
344  */
345 int
346 svWriteFile(shvarFile *s, int mode)
347 {
348     FILE *f;
349     int tmpfd;
350
351     if (s->modified) {
352         if (s->fd == -1)
353             s->fd = open(s->fileName, O_WRONLY|O_CREAT, mode);
354         if (s->fd == -1)
355             return -1;
356         if (ftruncate(s->fd, 0) < 0)
357             return -1;
358
359         tmpfd = dup(s->fd);
360         f = fdopen(tmpfd, "w");
361         fseek(f, 0, SEEK_SET);
362         for (s->current = s->lineList; s->current; s->current = s->current->next) {
363             char *line = s->current->data;
364             fprintf(f, "%s\n", line);
365         }
366         fclose(f);
367     }
368
369     return 0;
370 }
371
372  
373 /* Close the file descriptor (if open) and delete the shvarFile.
374  * Returns -1 on error and 0 on success.
375  */
376 int
377 svCloseFile(shvarFile *s)
378 {
379
380     g_assert(s);
381
382     if (s->fd != -1) close(s->fd);
383
384     g_free(s->arena);
385     for (s->current = s->freeList; s->current; s->current = s->current->next) {
386         g_free(s->current->data);
387     }
388     g_free(s->fileName);
389     g_list_free(s->freeList);
390     g_list_free(s->lineList); /* implicitly frees s->current */
391     g_free(s);
392     return 0;
393 }