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