/* copied from rp3 -- DO NOT EDIT HERE, ONLY COPY FROM rp3 */ /* * shvar.c * * Implementation of non-destructively reading/writing files containing * only shell variable declarations and full-line comments. * * Includes explicit inheritance mechanism intended for use with * Red Hat Linux ifcfg-* files. There is no protection against * inheritance loops; they will generally cause stack overflows. * Furthermore, they are only intended for one level of inheritance; * the value setting algorithm assumes this. * * Copyright 1999 Red Hat, Inc. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include #include #include #include #include #include #include "shvar.h" /* Open the file , return shvarFile on success, NULL on failure */ shvarFile * svNewFile(char *name) { shvarFile *s = NULL; int closefd = 0; s = calloc(sizeof(shvarFile), 1); if (!s) return NULL; s->fd = open(name, O_RDWR); /* NOT O_CREAT */ if (s->fd == -1) { /* try read-only */ s->fd = open(name, O_RDONLY); /* NOT O_CREAT */ if (s->fd) closefd = 1; } s->fileName = strdup(name); if (s->fd != -1) { struct stat buf; char *tmp; if (fstat(s->fd, &buf) < 0) goto bail; s->arena = calloc(buf.st_size, 1); if (!s->arena) goto bail; if (read(s->fd, s->arena, buf.st_size) < 0) goto bail; /* Yes, I know that strtok is evil, except that this is * precisely what it was intended for in the first place... */ tmp = strtok(s->arena, "\n"); while (tmp) { s->lineList = g_list_append(s->lineList, tmp); tmp = strtok(NULL, "\n"); } if (closefd) { close(s->fd); s->fd = -1; } } return s; bail: if (s->fd != -1) close(s->fd); if (s->arena) free (s->arena); if (s->fileName) free (s->fileName); free (s); return NULL; } /* remove escaped characters in place */ static void unescape(char *s) { int len, i; len = strlen(s); if ((s[0] == '"' || s[0] == '\'') && s[0] == s[len-1]) { i = len - 2; memmove(s, s+1, i); s[i+1] = '\0'; len = i; } for (i = 0; i < len; i++) { if (s[i] == '\\') { memmove(s+i, s+i+1, len-(i+1)); len--; } s[len] = '\0'; } } /* create a new string with all necessary characters escaped. * caller must free returned string */ static const char escapees[] = "\"'\\$~`"; /* must be escaped */ static const char spaces[] = " \t"; /* only require "" */ static char * escape(char *s) { char *new; int i, j, mangle = 0, space = 0; int newlen, slen; static int esclen, splen; if (!esclen) esclen = strlen(escapees); if (!splen) splen = strlen(spaces); for (i = 0; i < esclen; i++) { if (strchr(s, escapees[i])) mangle++; } for (i = 0; i < splen; i++) { if (strchr(s, spaces[i])) space++; } if (!mangle && !space) return strdup(s); slen = strlen(s); newlen = slen + mangle + 3; /* 3 is extra ""\0 */ new = calloc(newlen, 1); if (!new) return NULL; new[0] = '"'; for (i = 0, j = 1; i < slen; i++, j++) { if (strchr(escapees, s[i])) { new[j++] = '\\'; } new[j] = s[i]; } new[j] = '"'; return new; } /* Get the value associated with the key, and leave the current pointer * pointing at the line containing the value. The char* returned MUST * be freed by the caller. */ char * svGetValue(shvarFile *s, char *key) { char *value = NULL; char *line; char *keyString; int len; assert(s); assert(key); keyString = calloc (strlen(key) + 2, 1); if (!keyString) return NULL; strcpy(keyString, key); keyString[strlen(key)] = '='; len = strlen(keyString); for (s->current = s->lineList; s->current; s->current = s->current->next) { line = s->current->data; if (!strncmp(keyString, line, len)) { value = strdup(line + len); unescape(value); break; } } free(keyString); if (value) { if (value[0]) { return value; } else { free (value); return NULL; } } if (s->parent) value = svGetValue(s->parent, key); return value; } /* return 1 if resolves to any truth value (e.g. "yes", "y", "true") * return 0 if resolves to any non-truth value (e.g. "no", "n", "false") * return otherwise */ int svTrueValue(shvarFile *s, char *key, int def) { char *tmp; int returnValue = def; tmp = svGetValue(s, key); if (!tmp) return returnValue; if ( (!strcasecmp("yes", tmp)) || (!strcasecmp("true", tmp)) || (!strcasecmp("t", tmp)) || (!strcasecmp("y", tmp)) ) returnValue = 1; else if ( (!strcasecmp("no", tmp)) || (!strcasecmp("false", tmp)) || (!strcasecmp("f", tmp)) || (!strcasecmp("n", tmp)) ) returnValue = 0; free (tmp); return returnValue; } /* Set the variable equal to the value . * If does not exist, and the pointer is set, append * the key=value pair after that line. Otherwise, prepend the pair * to the top of the file. Here's the algorithm, as the C code * seems to be rather dense: * * if (value == NULL), then: * if val2 (parent): change line to key= or append line key= * if val1 (this) : delete line * else noop * else use this table: * val2 * NULL value other * v NULL append line noop append line * a * l value noop noop noop * 1 * other change line delete line change line * * No changes are ever made to the parent config file, only to the * specific file passed on the command line. * */ void svSetValue(shvarFile *s, char *key, char *value) { char *val1 = NULL, *val2 = NULL; char *keyValue; assert(s); assert(key); /* value may be NULL */ if (value) value = escape(value); keyValue = malloc (strlen(key) + (value?strlen(value):0) + 2); if (!keyValue) return; sprintf(keyValue, "%s=%s", key, value?value:""); val1 = svGetValue(s, key); if (val1 && value && !strcmp(val1, value)) goto bail; if (s->parent) val2 = svGetValue(s->parent, key); if (!value) { /* delete value somehow */ if (val2) { /* change/append line to get key= */ if (s->current) s->current->data = keyValue; else s->lineList = g_list_append(s->lineList, keyValue); s->freeList = g_list_append(s->freeList, keyValue); s->modified = 1; } else if (val1) { /* delete line */ s->lineList = g_list_remove_link(s->lineList, s->current); g_list_free_1(s->current); s->modified = 1; goto bail; /* do not need keyValue */ } goto end; } if (!val1) { if (val2 && !strcmp(val2, value)) goto end; /* append line */ s->lineList = g_list_append(s->lineList, keyValue); s->freeList = g_list_append(s->freeList, keyValue); s->modified = 1; goto end; } /* deal with a whole line of noops */ if (val1 && !strcmp(val1, value)) goto end; /* At this point, val1 && val1 != value */ if (val2 && !strcmp(val2, value)) { /* delete line */ s->lineList = g_list_remove_link(s->lineList, s->current); g_list_free_1(s->current); s->modified = 1; goto bail; /* do not need keyValue */ } else { /* change line */ if (s->current) s->current->data = keyValue; else s->lineList = g_list_append(s->lineList, keyValue); s->freeList = g_list_append(s->freeList, keyValue); s->modified = 1; } end: if (value) free(value); if (val1) free(val1); if (val2) free(val2); return; bail: if (keyValue) free (keyValue); goto end; } /* Write the current contents iff modified. Returns -1 on error * and 0 on success. Do not write if no values have been modified. * The mode argument is only used if creating the file, not if * re-writing an existing file, and is passed unchanged to the * open() syscall. */ int svWriteFile(shvarFile *s, int mode) { FILE *f; int tmpfd; if (s->modified) { if (s->fd == -1) s->fd = open(s->fileName, O_WRONLY|O_CREAT, mode); if (s->fd == -1) return -1; if (ftruncate(s->fd, 0) < 0) return -1; tmpfd = dup(s->fd); f = fdopen(tmpfd, "w"); fseek(f, 0, SEEK_SET); for (s->current = s->lineList; s->current; s->current = s->current->next) { char *line = s->current->data; fprintf(f, "%s\n", line); } fclose(f); } return 0; } /* Close the file descriptor (if open) and delete the shvarFile. * Returns -1 on error and 0 on success. */ int svCloseFile(shvarFile *s) { assert(s); if (s->fd != -1) close(s->fd); free(s->arena); for (s->current = s->freeList; s->current; s->current = s->current->next) { free(s->current->data); } free(s->fileName); g_list_free(s->freeList); g_list_free(s->lineList); /* implicitly frees s->current */ free(s); return 0; }