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
(1) Event cond_true: |
Condition "s", taking true branch |
(2) Event if_fallthrough: |
Falling through to end of if statement |
(3) Event if_end: |
End of if statement |
(4) Event cond_true: |
Condition "({...})", taking true branch |
(5) Event if_fallthrough: |
Falling through to end of if statement |
(6) Event if_end: |
End of if statement |
195 g_assert(s);
(7) Event cond_true: |
Condition "key", taking true branch |
(8) Event if_fallthrough: |
Falling through to end of if statement |
(9) Event if_end: |
End of if statement |
(10) Event cond_true: |
Condition "({...})", taking true branch |
(11) Event if_fallthrough: |
Falling through to end of if statement |
(12) Event if_end: |
End of if statement |
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
(13) Event cond_true: |
Condition "s->current", taking true branch |
203 for (s->current = s->lineList; s->current; s->current = s->current->next) {
204 line = s->current->data;
(14) Event cond_true: |
Condition "!__coverity_strncmp(keyString, line, len)", taking true branch |
205 if (!strncmp(keyString, line, len)) {
206 value = g_strdup(line + len);
(15) Event cond_true: |
Condition "!verbatim", taking true branch |
207 if (!verbatim)
208 svUnescape(value);
(16) Event break: |
Breaking from loop |
209 break;
210 }
(17) Event loop_end: |
Reached end of loop |
211 }
212 g_free(keyString);
213
(18) Event cond_true: |
Condition "value", taking true branch |
214 if (value) {
(19) Event cond_false: |
Condition "value[0]", taking false branch |
215 if (value[0]) {
216 return value;
(20) Event else_branch: |
Reached else branch |
217 } else {
218 g_free(value);
(21) Event return_null: |
Explicitly returning null. |
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
360 if (s->modified) {
361 if (s->fd == -1)
362 s->fd = open(s->fileName, O_WRONLY|O_CREAT, mode);
363 if (s->fd == -1)
364 return -1;
365 if (ftruncate(s->fd, 0) < 0)
366 return -1;
367
368 tmpfd = dup(s->fd);
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