paul@181 | 1 | /* |
paul@181 | 2 | * profile.c -- A simple configuration file parsing "library in a file" |
paul@181 | 3 | * |
paul@181 | 4 | * The profile library was originally written by Theodore Ts'o in 1995 |
paul@181 | 5 | * for use in the MIT Kerberos v5 library. It has been |
paul@181 | 6 | * modified/enhanced/bug-fixed over time by other members of the MIT |
paul@181 | 7 | * Kerberos team. This version was originally taken from the Kerberos |
paul@181 | 8 | * v5 distribution, version 1.4.2, and radically simplified for use in |
paul@181 | 9 | * e2fsprogs. (Support for locking for multi-threaded operations, |
paul@181 | 10 | * being able to modify and update the configuration file |
paul@181 | 11 | * programmatically, and Mac/Windows portability have been removed. |
paul@181 | 12 | * It has been folded into a single C source file to make it easier to |
paul@181 | 13 | * fold into an application program.) |
paul@181 | 14 | * |
paul@181 | 15 | * Copyright (C) 2005, 2006 by Theodore Ts'o. |
paul@181 | 16 | * |
paul@181 | 17 | * %Begin-Header% |
paul@181 | 18 | * This file may be redistributed under the terms of the GNU Public |
paul@181 | 19 | * License. |
paul@181 | 20 | * %End-Header% |
paul@181 | 21 | * |
paul@181 | 22 | * Copyright (C) 1985-2005 by the Massachusetts Institute of Technology. |
paul@181 | 23 | * |
paul@181 | 24 | * All rights reserved. |
paul@181 | 25 | * |
paul@181 | 26 | * Export of this software from the United States of America may require |
paul@181 | 27 | * a specific license from the United States Government. It is the |
paul@181 | 28 | * responsibility of any person or organization contemplating export to |
paul@181 | 29 | * obtain such a license before exporting. |
paul@181 | 30 | * |
paul@181 | 31 | * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and |
paul@181 | 32 | * distribute this software and its documentation for any purpose and |
paul@181 | 33 | * without fee is hereby granted, provided that the above copyright |
paul@181 | 34 | * notice appear in all copies and that both that copyright notice and |
paul@181 | 35 | * this permission notice appear in supporting documentation, and that |
paul@181 | 36 | * the name of M.I.T. not be used in advertising or publicity pertaining |
paul@181 | 37 | * to distribution of the software without specific, written prior |
paul@181 | 38 | * permission. Furthermore if you modify this software you must label |
paul@181 | 39 | * your software as modified software and not distribute it in such a |
paul@181 | 40 | * fashion that it might be confused with the original MIT software. |
paul@181 | 41 | * M.I.T. makes no representations about the suitability of this software |
paul@181 | 42 | * for any purpose. It is provided "as is" without express or implied |
paul@181 | 43 | * warranty. |
paul@181 | 44 | * |
paul@181 | 45 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR |
paul@181 | 46 | * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED |
paul@181 | 47 | * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
paul@181 | 48 | * |
paul@181 | 49 | */ |
paul@181 | 50 | |
paul@181 | 51 | #include "config.h" |
paul@181 | 52 | #ifdef HAVE_UNISTD_H |
paul@181 | 53 | #include <unistd.h> |
paul@181 | 54 | #endif |
paul@181 | 55 | #include <stdio.h> |
paul@181 | 56 | #ifdef HAVE_STDLIB_H |
paul@181 | 57 | #include <stdlib.h> |
paul@181 | 58 | #endif |
paul@181 | 59 | #include <time.h> |
paul@181 | 60 | #include <string.h> |
paul@181 | 61 | #include <strings.h> |
paul@181 | 62 | #include <errno.h> |
paul@181 | 63 | #include <ctype.h> |
paul@181 | 64 | #include <limits.h> |
paul@181 | 65 | #include <stddef.h> |
paul@181 | 66 | #include <sys/types.h> |
paul@181 | 67 | #include <sys/stat.h> |
paul@181 | 68 | #include <dirent.h> |
paul@181 | 69 | #ifdef HAVE_PWD_H |
paul@181 | 70 | #include <pwd.h> |
paul@181 | 71 | #endif |
paul@181 | 72 | |
paul@181 | 73 | #include <et/com_err.h> |
paul@181 | 74 | #include "profile.h" |
paul@181 | 75 | #include "prof_err.h" |
paul@181 | 76 | |
paul@181 | 77 | #undef STAT_ONCE_PER_SECOND |
paul@181 | 78 | #undef HAVE_STAT |
paul@181 | 79 | |
paul@181 | 80 | /* |
paul@181 | 81 | * prof_int.h |
paul@181 | 82 | */ |
paul@181 | 83 | |
paul@181 | 84 | typedef long prf_magic_t; |
paul@181 | 85 | |
paul@181 | 86 | /* |
paul@181 | 87 | * This is the structure which stores the profile information for a |
paul@181 | 88 | * particular configuration file. |
paul@181 | 89 | */ |
paul@181 | 90 | struct _prf_file_t { |
paul@181 | 91 | prf_magic_t magic; |
paul@181 | 92 | char *filespec; |
paul@181 | 93 | #ifdef STAT_ONCE_PER_SECOND |
paul@181 | 94 | time_t last_stat; |
paul@181 | 95 | #endif |
paul@181 | 96 | time_t timestamp; /* time tree was last updated from file */ |
paul@181 | 97 | int flags; /* r/w, dirty */ |
paul@181 | 98 | int upd_serial; /* incremented when data changes */ |
paul@181 | 99 | struct profile_node *root; |
paul@181 | 100 | struct _prf_file_t *next; |
paul@181 | 101 | }; |
paul@181 | 102 | |
paul@181 | 103 | typedef struct _prf_file_t *prf_file_t; |
paul@181 | 104 | |
paul@181 | 105 | /* |
paul@181 | 106 | * The profile flags |
paul@181 | 107 | */ |
paul@181 | 108 | #define PROFILE_FILE_RW 0x0001 |
paul@181 | 109 | #define PROFILE_FILE_DIRTY 0x0002 |
paul@181 | 110 | #define PROFILE_FILE_NO_RELOAD 0x0004 |
paul@181 | 111 | |
paul@181 | 112 | /* |
paul@181 | 113 | * This structure defines the high-level, user visible profile_t |
paul@181 | 114 | * object, which is used as a handle by users who need to query some |
paul@181 | 115 | * configuration file(s) |
paul@181 | 116 | */ |
paul@181 | 117 | struct _profile_t { |
paul@181 | 118 | prf_magic_t magic; |
paul@181 | 119 | prf_file_t first_file; |
paul@181 | 120 | }; |
paul@181 | 121 | |
paul@181 | 122 | /* |
paul@181 | 123 | * Used by the profile iterator in prof_get.c |
paul@181 | 124 | */ |
paul@181 | 125 | #define PROFILE_ITER_LIST_SECTION 0x0001 |
paul@181 | 126 | #define PROFILE_ITER_SECTIONS_ONLY 0x0002 |
paul@181 | 127 | #define PROFILE_ITER_RELATIONS_ONLY 0x0004 |
paul@181 | 128 | |
paul@181 | 129 | #define PROFILE_ITER_FINAL_SEEN 0x0100 |
paul@181 | 130 | |
paul@181 | 131 | /* |
paul@181 | 132 | * Check if a filespec is last in a list (NULL on UNIX, invalid FSSpec on MacOS |
paul@181 | 133 | */ |
paul@181 | 134 | |
paul@181 | 135 | #define PROFILE_LAST_FILESPEC(x) (((x) == NULL) || ((x)[0] == '\0')) |
paul@181 | 136 | |
paul@181 | 137 | struct profile_node { |
paul@181 | 138 | errcode_t magic; |
paul@181 | 139 | char *name; |
paul@181 | 140 | char *value; |
paul@181 | 141 | int group_level; |
paul@181 | 142 | unsigned int final:1; /* Indicate don't search next file */ |
paul@181 | 143 | unsigned int deleted:1; |
paul@181 | 144 | struct profile_node *first_child; |
paul@181 | 145 | struct profile_node *parent; |
paul@181 | 146 | struct profile_node *next, *prev; |
paul@181 | 147 | }; |
paul@181 | 148 | |
paul@181 | 149 | #define CHECK_MAGIC(node) \ |
paul@181 | 150 | if ((node)->magic != PROF_MAGIC_NODE) \ |
paul@181 | 151 | return PROF_MAGIC_NODE; |
paul@181 | 152 | |
paul@181 | 153 | /* profile parser declarations */ |
paul@181 | 154 | struct parse_state { |
paul@181 | 155 | int state; |
paul@181 | 156 | int group_level; |
paul@181 | 157 | int line_num; |
paul@181 | 158 | struct profile_node *root_section; |
paul@181 | 159 | struct profile_node *current_section; |
paul@181 | 160 | }; |
paul@181 | 161 | |
paul@181 | 162 | static const char *default_filename = "<default>"; |
paul@181 | 163 | |
paul@181 | 164 | static profile_syntax_err_cb_t syntax_err_cb; |
paul@181 | 165 | |
paul@181 | 166 | static errcode_t parse_line(char *line, struct parse_state *state); |
paul@181 | 167 | |
paul@181 | 168 | #ifdef DEBUG_PROGRAM |
paul@181 | 169 | static errcode_t profile_write_tree_file |
paul@181 | 170 | (struct profile_node *root, FILE *dstfile); |
paul@181 | 171 | |
paul@181 | 172 | static errcode_t profile_write_tree_to_buffer |
paul@181 | 173 | (struct profile_node *root, char **buf); |
paul@181 | 174 | #endif |
paul@181 | 175 | |
paul@181 | 176 | |
paul@181 | 177 | static void profile_free_node |
paul@181 | 178 | (struct profile_node *relation); |
paul@181 | 179 | |
paul@181 | 180 | static errcode_t profile_create_node |
paul@181 | 181 | (const char *name, const char *value, |
paul@181 | 182 | struct profile_node **ret_node); |
paul@181 | 183 | |
paul@181 | 184 | #ifdef DEBUG_PROGRAM |
paul@181 | 185 | static errcode_t profile_verify_node |
paul@181 | 186 | (struct profile_node *node); |
paul@181 | 187 | #endif |
paul@181 | 188 | |
paul@181 | 189 | static errcode_t profile_add_node |
paul@181 | 190 | (struct profile_node *section, |
paul@181 | 191 | const char *name, const char *value, |
paul@181 | 192 | struct profile_node **ret_node); |
paul@181 | 193 | |
paul@181 | 194 | static errcode_t profile_find_node |
paul@181 | 195 | (struct profile_node *section, |
paul@181 | 196 | const char *name, const char *value, |
paul@181 | 197 | int section_flag, void **state, |
paul@181 | 198 | struct profile_node **node); |
paul@181 | 199 | |
paul@181 | 200 | static errcode_t profile_node_iterator |
paul@181 | 201 | (void **iter_p, struct profile_node **ret_node, |
paul@181 | 202 | char **ret_name, char **ret_value); |
paul@181 | 203 | |
paul@181 | 204 | static errcode_t profile_open_file |
paul@181 | 205 | (const char * file, prf_file_t *ret_prof); |
paul@181 | 206 | |
paul@181 | 207 | static errcode_t profile_update_file |
paul@181 | 208 | (prf_file_t prf); |
paul@181 | 209 | |
paul@181 | 210 | static void profile_free_file |
paul@181 | 211 | (prf_file_t profile); |
paul@181 | 212 | |
paul@181 | 213 | static errcode_t profile_get_value(profile_t profile, const char *name, |
paul@181 | 214 | const char *subname, const char *subsubname, |
paul@181 | 215 | const char **ret_value); |
paul@181 | 216 | |
paul@181 | 217 | |
paul@181 | 218 | /* |
paul@181 | 219 | * prof_init.c --- routines that manipulate the user-visible profile_t |
paul@181 | 220 | * object. |
paul@181 | 221 | */ |
paul@181 | 222 | |
paul@181 | 223 | static int compstr(const void *m1, const void *m2) |
paul@181 | 224 | { |
paul@181 | 225 | const char *s1 = *((const char * const *) m1); |
paul@181 | 226 | const char *s2 = *((const char * const *) m2); |
paul@181 | 227 | |
paul@181 | 228 | return strcmp(s1, s2); |
paul@181 | 229 | } |
paul@181 | 230 | |
paul@181 | 231 | static void free_list(char **list) |
paul@181 | 232 | { |
paul@181 | 233 | char **cp; |
paul@181 | 234 | |
paul@181 | 235 | if (list == 0) |
paul@181 | 236 | return; |
paul@181 | 237 | |
paul@181 | 238 | for (cp = list; *cp; cp++) |
paul@181 | 239 | free(*cp); |
paul@181 | 240 | free(list); |
paul@181 | 241 | } |
paul@181 | 242 | |
paul@181 | 243 | static errcode_t get_dirlist(const char *dirname, char***ret_array) |
paul@181 | 244 | { |
paul@181 | 245 | DIR *dir; |
paul@181 | 246 | struct dirent *de; |
paul@181 | 247 | struct stat st; |
paul@181 | 248 | errcode_t retval; |
paul@181 | 249 | char *fn, *cp; |
paul@181 | 250 | char **array = 0, **new_array; |
paul@181 | 251 | int max = 0, num = 0; |
paul@181 | 252 | |
paul@181 | 253 | dir = opendir(dirname); |
paul@181 | 254 | if (!dir) |
paul@181 | 255 | return errno; |
paul@181 | 256 | |
paul@181 | 257 | while ((de = readdir(dir)) != NULL) { |
paul@181 | 258 | for (cp = de->d_name; *cp; cp++) { |
paul@181 | 259 | if (!isalnum(*cp) && |
paul@181 | 260 | (*cp != '-') && |
paul@181 | 261 | (*cp != '_')) |
paul@181 | 262 | break; |
paul@181 | 263 | } |
paul@181 | 264 | if (*cp) |
paul@181 | 265 | continue; |
paul@181 | 266 | fn = malloc(strlen(dirname) + strlen(de->d_name) + 2); |
paul@181 | 267 | if (!fn) { |
paul@181 | 268 | retval = ENOMEM; |
paul@181 | 269 | goto errout; |
paul@181 | 270 | } |
paul@181 | 271 | sprintf(fn, "%s/%s", dirname, de->d_name); |
paul@181 | 272 | if ((stat(fn, &st) < 0) || !S_ISREG(st.st_mode)) { |
paul@181 | 273 | free(fn); |
paul@181 | 274 | continue; |
paul@181 | 275 | } |
paul@181 | 276 | if (num >= max) { |
paul@181 | 277 | max += 10; |
paul@181 | 278 | new_array = realloc(array, sizeof(char *) * (max+1)); |
paul@181 | 279 | if (!new_array) { |
paul@181 | 280 | retval = ENOMEM; |
paul@181 | 281 | free(fn); |
paul@181 | 282 | goto errout; |
paul@181 | 283 | } |
paul@181 | 284 | array = new_array; |
paul@181 | 285 | } |
paul@181 | 286 | array[num++] = fn; |
paul@181 | 287 | } |
paul@181 | 288 | if (array) { |
paul@181 | 289 | qsort(array, num, sizeof(char *), compstr); |
paul@181 | 290 | array[num++] = 0; |
paul@181 | 291 | } |
paul@181 | 292 | *ret_array = array; |
paul@181 | 293 | closedir(dir); |
paul@181 | 294 | return 0; |
paul@181 | 295 | errout: |
paul@181 | 296 | if (array) |
paul@181 | 297 | array[num] = 0; |
paul@181 | 298 | closedir(dir); |
paul@181 | 299 | free_list(array); |
paul@181 | 300 | return retval; |
paul@181 | 301 | } |
paul@181 | 302 | |
paul@181 | 303 | errcode_t |
paul@181 | 304 | profile_init(const char * const *files, profile_t *ret_profile) |
paul@181 | 305 | { |
paul@181 | 306 | const char * const *fs; |
paul@181 | 307 | profile_t profile; |
paul@181 | 308 | prf_file_t new_file, *last; |
paul@181 | 309 | errcode_t retval = 0; |
paul@181 | 310 | char **cpp, *cp, **array = 0; |
paul@181 | 311 | |
paul@181 | 312 | profile = malloc(sizeof(struct _profile_t)); |
paul@181 | 313 | if (!profile) |
paul@181 | 314 | return ENOMEM; |
paul@181 | 315 | memset(profile, 0, sizeof(struct _profile_t)); |
paul@181 | 316 | profile->magic = PROF_MAGIC_PROFILE; |
paul@181 | 317 | last = &profile->first_file; |
paul@181 | 318 | |
paul@181 | 319 | /* if the filenames list is not specified return an empty profile */ |
paul@181 | 320 | if ( files ) { |
paul@181 | 321 | for (fs = files; !PROFILE_LAST_FILESPEC(*fs); fs++) { |
paul@181 | 322 | if (array) |
paul@181 | 323 | free_list(array); |
paul@181 | 324 | array = NULL; |
paul@181 | 325 | retval = get_dirlist(*fs, &array); |
paul@181 | 326 | if (retval == 0) { |
paul@181 | 327 | if (!array) |
paul@181 | 328 | continue; |
paul@181 | 329 | for (cpp = array; (cp = *cpp); cpp++) { |
paul@181 | 330 | retval = profile_open_file(cp, &new_file); |
paul@181 | 331 | if (retval == EACCES) |
paul@181 | 332 | continue; |
paul@181 | 333 | if (retval) |
paul@181 | 334 | goto errout; |
paul@181 | 335 | *last = new_file; |
paul@181 | 336 | last = &new_file->next; |
paul@181 | 337 | } |
paul@181 | 338 | } else if ((retval != ENOTDIR) && |
paul@181 | 339 | strcmp(*fs, default_filename)) |
paul@181 | 340 | goto errout; |
paul@181 | 341 | |
paul@181 | 342 | retval = profile_open_file(*fs, &new_file); |
paul@181 | 343 | /* if this file is missing, skip to the next */ |
paul@181 | 344 | if (retval == ENOENT || retval == EACCES) { |
paul@181 | 345 | continue; |
paul@181 | 346 | } |
paul@181 | 347 | if (retval) |
paul@181 | 348 | goto errout; |
paul@181 | 349 | *last = new_file; |
paul@181 | 350 | last = &new_file->next; |
paul@181 | 351 | } |
paul@181 | 352 | /* |
paul@181 | 353 | * If all the files were not found, return the appropriate error. |
paul@181 | 354 | */ |
paul@181 | 355 | if (!profile->first_file) { |
paul@181 | 356 | retval = ENOENT; |
paul@181 | 357 | goto errout; |
paul@181 | 358 | } |
paul@181 | 359 | } |
paul@181 | 360 | |
paul@181 | 361 | free_list(array); |
paul@181 | 362 | *ret_profile = profile; |
paul@181 | 363 | return 0; |
paul@181 | 364 | errout: |
paul@181 | 365 | free_list(array); |
paul@181 | 366 | profile_release(profile); |
paul@181 | 367 | return retval; |
paul@181 | 368 | } |
paul@181 | 369 | |
paul@181 | 370 | void |
paul@181 | 371 | profile_release(profile_t profile) |
paul@181 | 372 | { |
paul@181 | 373 | prf_file_t p, next; |
paul@181 | 374 | |
paul@181 | 375 | if (!profile || profile->magic != PROF_MAGIC_PROFILE) |
paul@181 | 376 | return; |
paul@181 | 377 | |
paul@181 | 378 | for (p = profile->first_file; p; p = next) { |
paul@181 | 379 | next = p->next; |
paul@181 | 380 | profile_free_file(p); |
paul@181 | 381 | } |
paul@181 | 382 | profile->magic = 0; |
paul@181 | 383 | free(profile); |
paul@181 | 384 | } |
paul@181 | 385 | |
paul@181 | 386 | /* |
paul@181 | 387 | * This function sets the value of the pseudo file "<default>". If |
paul@181 | 388 | * the file "<default>" had previously been passed to profile_init(), |
paul@181 | 389 | * then def_string parameter will be parsed and used as the profile |
paul@181 | 390 | * information for the "<default>" file. |
paul@181 | 391 | */ |
paul@181 | 392 | errcode_t profile_set_default(profile_t profile, const char *def_string) |
paul@181 | 393 | { |
paul@181 | 394 | struct parse_state state; |
paul@181 | 395 | prf_file_t prf; |
paul@181 | 396 | errcode_t retval; |
paul@181 | 397 | const char *in; |
paul@181 | 398 | char *line, *p, *end; |
paul@181 | 399 | int line_size, len; |
paul@181 | 400 | |
paul@181 | 401 | if (!def_string || !profile || profile->magic != PROF_MAGIC_PROFILE) |
paul@181 | 402 | return PROF_MAGIC_PROFILE; |
paul@181 | 403 | |
paul@181 | 404 | for (prf = profile->first_file; prf; prf = prf->next) { |
paul@181 | 405 | if (strcmp(prf->filespec, default_filename) == 0) |
paul@181 | 406 | break; |
paul@181 | 407 | } |
paul@181 | 408 | if (!prf) |
paul@181 | 409 | return 0; |
paul@181 | 410 | |
paul@181 | 411 | if (prf->root) { |
paul@181 | 412 | profile_free_node(prf->root); |
paul@181 | 413 | prf->root = 0; |
paul@181 | 414 | } |
paul@181 | 415 | |
paul@181 | 416 | memset(&state, 0, sizeof(struct parse_state)); |
paul@181 | 417 | retval = profile_create_node("(root)", 0, &state.root_section); |
paul@181 | 418 | if (retval) |
paul@181 | 419 | return retval; |
paul@181 | 420 | |
paul@181 | 421 | line = 0; |
paul@181 | 422 | line_size = 0; |
paul@181 | 423 | in = def_string; |
paul@181 | 424 | while (*in) { |
paul@181 | 425 | end = strchr(in, '\n'); |
paul@181 | 426 | len = end ? (end - in) : (int) strlen(in); |
paul@181 | 427 | if (len >= line_size) { |
paul@181 | 428 | line_size = len+1; |
paul@181 | 429 | p = realloc(line, line_size); |
paul@181 | 430 | if (!p) { |
paul@181 | 431 | retval = ENOMEM; |
paul@181 | 432 | goto errout; |
paul@181 | 433 | } |
paul@181 | 434 | line = p; |
paul@181 | 435 | } |
paul@181 | 436 | memcpy(line, in, len); |
paul@181 | 437 | line[len] = 0; |
paul@181 | 438 | retval = parse_line(line, &state); |
paul@181 | 439 | if (retval) { |
paul@181 | 440 | errout: |
paul@181 | 441 | if (syntax_err_cb) |
paul@181 | 442 | (syntax_err_cb)(prf->filespec, retval, |
paul@181 | 443 | state.line_num); |
paul@181 | 444 | free(line); |
paul@181 | 445 | if (prf->root) |
paul@181 | 446 | profile_free_node(prf->root); |
paul@181 | 447 | return retval; |
paul@181 | 448 | } |
paul@181 | 449 | if (!end) |
paul@181 | 450 | break; |
paul@181 | 451 | in = end+1; |
paul@181 | 452 | } |
paul@181 | 453 | prf->root = state.root_section; |
paul@181 | 454 | free(line); |
paul@181 | 455 | |
paul@181 | 456 | return 0; |
paul@181 | 457 | } |
paul@181 | 458 | |
paul@181 | 459 | /* |
paul@181 | 460 | * prof_file.c ---- routines that manipulate an individual profile file. |
paul@181 | 461 | */ |
paul@181 | 462 | |
paul@181 | 463 | errcode_t profile_open_file(const char * filespec, |
paul@181 | 464 | prf_file_t *ret_prof) |
paul@181 | 465 | { |
paul@181 | 466 | prf_file_t prf; |
paul@181 | 467 | errcode_t retval; |
paul@181 | 468 | char *home_env = 0; |
paul@181 | 469 | unsigned int len; |
paul@181 | 470 | char *expanded_filename; |
paul@181 | 471 | |
paul@181 | 472 | prf = malloc(sizeof(struct _prf_file_t)); |
paul@181 | 473 | if (!prf) |
paul@181 | 474 | return ENOMEM; |
paul@181 | 475 | memset(prf, 0, sizeof(struct _prf_file_t)); |
paul@181 | 476 | prf->magic = PROF_MAGIC_FILE; |
paul@181 | 477 | |
paul@181 | 478 | len = strlen(filespec)+1; |
paul@181 | 479 | if (filespec[0] == '~' && filespec[1] == '/') { |
paul@181 | 480 | home_env = getenv("HOME"); |
paul@181 | 481 | #ifdef HAVE_PWD_H |
paul@181 | 482 | if (home_env == NULL) { |
paul@181 | 483 | #ifdef HAVE_GETWUID_R |
paul@181 | 484 | struct passwd *pw, pwx; |
paul@181 | 485 | uid_t uid; |
paul@181 | 486 | char pwbuf[BUFSIZ]; |
paul@181 | 487 | |
paul@181 | 488 | uid = getuid(); |
paul@181 | 489 | if (!getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw) |
paul@181 | 490 | && pw != NULL && pw->pw_dir[0] != 0) |
paul@181 | 491 | home_env = pw->pw_dir; |
paul@181 | 492 | #else |
paul@181 | 493 | struct passwd *pw; |
paul@181 | 494 | |
paul@181 | 495 | pw = getpwuid(getuid()); |
paul@181 | 496 | home_env = pw->pw_dir; |
paul@181 | 497 | #endif |
paul@181 | 498 | } |
paul@181 | 499 | #endif |
paul@181 | 500 | if (home_env) |
paul@181 | 501 | len += strlen(home_env); |
paul@181 | 502 | } |
paul@181 | 503 | expanded_filename = malloc(len); |
paul@181 | 504 | if (expanded_filename == 0) { |
paul@181 | 505 | profile_free_file(prf); |
paul@181 | 506 | return errno; |
paul@181 | 507 | } |
paul@181 | 508 | if (home_env) { |
paul@181 | 509 | strcpy(expanded_filename, home_env); |
paul@181 | 510 | strcat(expanded_filename, filespec+1); |
paul@181 | 511 | } else |
paul@181 | 512 | memcpy(expanded_filename, filespec, len); |
paul@181 | 513 | |
paul@181 | 514 | prf->filespec = expanded_filename; |
paul@181 | 515 | |
paul@181 | 516 | if (strcmp(prf->filespec, default_filename) != 0) { |
paul@181 | 517 | retval = profile_update_file(prf); |
paul@181 | 518 | if (retval) { |
paul@181 | 519 | profile_free_file(prf); |
paul@181 | 520 | return retval; |
paul@181 | 521 | } |
paul@181 | 522 | } |
paul@181 | 523 | |
paul@181 | 524 | *ret_prof = prf; |
paul@181 | 525 | return 0; |
paul@181 | 526 | } |
paul@181 | 527 | |
paul@181 | 528 | errcode_t profile_update_file(prf_file_t prf) |
paul@181 | 529 | { |
paul@181 | 530 | errcode_t retval; |
paul@181 | 531 | #ifdef HAVE_STAT |
paul@181 | 532 | struct stat st; |
paul@181 | 533 | #ifdef STAT_ONCE_PER_SECOND |
paul@181 | 534 | time_t now; |
paul@181 | 535 | #endif |
paul@181 | 536 | #endif |
paul@181 | 537 | FILE *f; |
paul@181 | 538 | char buf[2048]; |
paul@181 | 539 | struct parse_state state; |
paul@181 | 540 | |
paul@181 | 541 | if (prf->flags & PROFILE_FILE_NO_RELOAD) |
paul@181 | 542 | return 0; |
paul@181 | 543 | |
paul@181 | 544 | #ifdef HAVE_STAT |
paul@181 | 545 | #ifdef STAT_ONCE_PER_SECOND |
paul@181 | 546 | now = time(0); |
paul@181 | 547 | if (now == prf->last_stat && prf->root != NULL) { |
paul@181 | 548 | return 0; |
paul@181 | 549 | } |
paul@181 | 550 | #endif |
paul@181 | 551 | if (stat(prf->filespec, &st)) { |
paul@181 | 552 | retval = errno; |
paul@181 | 553 | return retval; |
paul@181 | 554 | } |
paul@181 | 555 | #ifdef STAT_ONCE_PER_SECOND |
paul@181 | 556 | prf->last_stat = now; |
paul@181 | 557 | #endif |
paul@181 | 558 | if (st.st_mtime == prf->timestamp && prf->root != NULL) { |
paul@181 | 559 | return 0; |
paul@181 | 560 | } |
paul@181 | 561 | if (prf->root) { |
paul@181 | 562 | profile_free_node(prf->root); |
paul@181 | 563 | prf->root = 0; |
paul@181 | 564 | } |
paul@181 | 565 | #else |
paul@181 | 566 | /* |
paul@181 | 567 | * If we don't have the stat() call, assume that our in-core |
paul@181 | 568 | * memory image is correct. That is, we won't reread the |
paul@181 | 569 | * profile file if it changes. |
paul@181 | 570 | */ |
paul@181 | 571 | if (prf->root) { |
paul@181 | 572 | return 0; |
paul@181 | 573 | } |
paul@181 | 574 | #endif |
paul@181 | 575 | memset(&state, 0, sizeof(struct parse_state)); |
paul@181 | 576 | retval = profile_create_node("(root)", 0, &state.root_section); |
paul@181 | 577 | if (retval) |
paul@181 | 578 | return retval; |
paul@181 | 579 | errno = 0; |
paul@181 | 580 | f = fopen(prf->filespec, "r"); |
paul@181 | 581 | if (f == NULL) { |
paul@181 | 582 | retval = errno; |
paul@181 | 583 | if (retval == 0) |
paul@181 | 584 | retval = ENOENT; |
paul@181 | 585 | return retval; |
paul@181 | 586 | } |
paul@181 | 587 | prf->upd_serial++; |
paul@181 | 588 | while (!feof(f)) { |
paul@181 | 589 | if (fgets(buf, sizeof(buf), f) == NULL) |
paul@181 | 590 | break; |
paul@181 | 591 | retval = parse_line(buf, &state); |
paul@181 | 592 | if (retval) { |
paul@181 | 593 | if (syntax_err_cb) |
paul@181 | 594 | (syntax_err_cb)(prf->filespec, retval, |
paul@181 | 595 | state.line_num); |
paul@181 | 596 | fclose(f); |
paul@181 | 597 | return retval; |
paul@181 | 598 | } |
paul@181 | 599 | } |
paul@181 | 600 | prf->root = state.root_section; |
paul@181 | 601 | |
paul@181 | 602 | fclose(f); |
paul@181 | 603 | |
paul@181 | 604 | #ifdef HAVE_STAT |
paul@181 | 605 | prf->timestamp = st.st_mtime; |
paul@181 | 606 | #endif |
paul@181 | 607 | return 0; |
paul@181 | 608 | } |
paul@181 | 609 | |
paul@181 | 610 | void profile_free_file(prf_file_t prf) |
paul@181 | 611 | { |
paul@181 | 612 | if (prf->root) |
paul@181 | 613 | profile_free_node(prf->root); |
paul@181 | 614 | free(prf->filespec); |
paul@181 | 615 | free(prf); |
paul@181 | 616 | } |
paul@181 | 617 | |
paul@181 | 618 | /* Begin the profile parser */ |
paul@181 | 619 | |
paul@181 | 620 | profile_syntax_err_cb_t profile_set_syntax_err_cb(profile_syntax_err_cb_t hook) |
paul@181 | 621 | { |
paul@181 | 622 | profile_syntax_err_cb_t old; |
paul@181 | 623 | |
paul@181 | 624 | old = syntax_err_cb; |
paul@181 | 625 | syntax_err_cb = hook; |
paul@181 | 626 | return(old); |
paul@181 | 627 | } |
paul@181 | 628 | |
paul@181 | 629 | #define STATE_INIT_COMMENT 0 |
paul@181 | 630 | #define STATE_STD_LINE 1 |
paul@181 | 631 | #define STATE_GET_OBRACE 2 |
paul@181 | 632 | |
paul@181 | 633 | static char *skip_over_blanks(char *cp) |
paul@181 | 634 | { |
paul@181 | 635 | while (*cp && isspace((int) (*cp))) |
paul@181 | 636 | cp++; |
paul@181 | 637 | return cp; |
paul@181 | 638 | } |
paul@181 | 639 | |
paul@181 | 640 | static int end_or_comment(char ch) |
paul@181 | 641 | { |
paul@181 | 642 | return (ch == 0 || ch == '#' || ch == ';'); |
paul@181 | 643 | } |
paul@181 | 644 | |
paul@181 | 645 | static char *skip_over_nonblanks(char *cp) |
paul@181 | 646 | { |
paul@181 | 647 | while (!end_or_comment(*cp) && !isspace(*cp)) |
paul@181 | 648 | cp++; |
paul@181 | 649 | return cp; |
paul@181 | 650 | } |
paul@181 | 651 | |
paul@181 | 652 | static void strip_line(char *line) |
paul@181 | 653 | { |
paul@181 | 654 | char *p = line + strlen(line); |
paul@181 | 655 | while (p > line && (p[-1] == '\n' || p[-1] == '\r')) |
paul@181 | 656 | *p-- = 0; |
paul@181 | 657 | } |
paul@181 | 658 | |
paul@181 | 659 | static void parse_quoted_string(char *str) |
paul@181 | 660 | { |
paul@181 | 661 | char *to, *from; |
paul@181 | 662 | |
paul@181 | 663 | to = from = str; |
paul@181 | 664 | |
paul@181 | 665 | for (to = from = str; *from && *from != '"'; to++, from++) { |
paul@181 | 666 | if (*from == '\\') { |
paul@181 | 667 | from++; |
paul@181 | 668 | switch (*from) { |
paul@181 | 669 | case 'n': |
paul@181 | 670 | *to = '\n'; |
paul@181 | 671 | break; |
paul@181 | 672 | case 't': |
paul@181 | 673 | *to = '\t'; |
paul@181 | 674 | break; |
paul@181 | 675 | case 'b': |
paul@181 | 676 | *to = '\b'; |
paul@181 | 677 | break; |
paul@181 | 678 | default: |
paul@181 | 679 | *to = *from; |
paul@181 | 680 | } |
paul@181 | 681 | continue; |
paul@181 | 682 | } |
paul@181 | 683 | *to = *from; |
paul@181 | 684 | } |
paul@181 | 685 | *to = '\0'; |
paul@181 | 686 | } |
paul@181 | 687 | |
paul@181 | 688 | static errcode_t parse_line(char *line, struct parse_state *state) |
paul@181 | 689 | { |
paul@181 | 690 | char *cp, ch, *tag, *value; |
paul@181 | 691 | char *p; |
paul@181 | 692 | errcode_t retval; |
paul@181 | 693 | struct profile_node *node; |
paul@181 | 694 | int do_subsection = 0; |
paul@181 | 695 | void *iter = 0; |
paul@181 | 696 | |
paul@181 | 697 | state->line_num++; |
paul@181 | 698 | if (state->state == STATE_GET_OBRACE) { |
paul@181 | 699 | cp = skip_over_blanks(line); |
paul@181 | 700 | if (*cp != '{') |
paul@181 | 701 | return PROF_MISSING_OBRACE; |
paul@181 | 702 | state->state = STATE_STD_LINE; |
paul@181 | 703 | return 0; |
paul@181 | 704 | } |
paul@181 | 705 | if (state->state == STATE_INIT_COMMENT) { |
paul@181 | 706 | if (line[0] != '[') |
paul@181 | 707 | return 0; |
paul@181 | 708 | state->state = STATE_STD_LINE; |
paul@181 | 709 | } |
paul@181 | 710 | |
paul@181 | 711 | if (*line == 0) |
paul@181 | 712 | return 0; |
paul@181 | 713 | strip_line(line); |
paul@181 | 714 | cp = skip_over_blanks(line); |
paul@181 | 715 | ch = *cp; |
paul@181 | 716 | if (end_or_comment(ch)) |
paul@181 | 717 | return 0; |
paul@181 | 718 | if (ch == '[') { |
paul@181 | 719 | if (state->group_level > 0) |
paul@181 | 720 | return PROF_SECTION_NOTOP; |
paul@181 | 721 | cp++; |
paul@181 | 722 | cp = skip_over_blanks(cp); |
paul@181 | 723 | p = strchr(cp, ']'); |
paul@181 | 724 | if (p == NULL) |
paul@181 | 725 | return PROF_SECTION_SYNTAX; |
paul@181 | 726 | if (*cp == '"') { |
paul@181 | 727 | cp++; |
paul@181 | 728 | parse_quoted_string(cp); |
paul@181 | 729 | } else { |
paul@181 | 730 | *p-- = '\0'; |
paul@181 | 731 | while (isspace(*p) && (p > cp)) |
paul@181 | 732 | *p-- = '\0'; |
paul@181 | 733 | if (*cp == 0) |
paul@181 | 734 | return PROF_SECTION_SYNTAX; |
paul@181 | 735 | } |
paul@181 | 736 | retval = profile_find_node(state->root_section, cp, 0, 1, |
paul@181 | 737 | &iter, &state->current_section); |
paul@181 | 738 | if (retval == PROF_NO_SECTION) { |
paul@181 | 739 | retval = profile_add_node(state->root_section, |
paul@181 | 740 | cp, 0, |
paul@181 | 741 | &state->current_section); |
paul@181 | 742 | if (retval) |
paul@181 | 743 | return retval; |
paul@181 | 744 | } else if (retval) |
paul@181 | 745 | return retval; |
paul@181 | 746 | |
paul@181 | 747 | /* |
paul@181 | 748 | * Finish off the rest of the line. |
paul@181 | 749 | */ |
paul@181 | 750 | cp = p+1; |
paul@181 | 751 | if (*cp == '*') { |
paul@181 | 752 | state->current_section->final = 1; |
paul@181 | 753 | cp++; |
paul@181 | 754 | } |
paul@181 | 755 | /* |
paul@181 | 756 | * Spaces or comments after ']' should not be fatal |
paul@181 | 757 | */ |
paul@181 | 758 | cp = skip_over_blanks(cp); |
paul@181 | 759 | if (!end_or_comment(*cp)) |
paul@181 | 760 | return PROF_SECTION_SYNTAX; |
paul@181 | 761 | return 0; |
paul@181 | 762 | } |
paul@181 | 763 | if (ch == '}') { |
paul@181 | 764 | if (state->group_level == 0) |
paul@181 | 765 | return PROF_EXTRA_CBRACE; |
paul@181 | 766 | if (*(cp+1) == '*') |
paul@181 | 767 | state->current_section->final = 1; |
paul@181 | 768 | state->current_section = state->current_section->parent; |
paul@181 | 769 | state->group_level--; |
paul@181 | 770 | return 0; |
paul@181 | 771 | } |
paul@181 | 772 | /* |
paul@181 | 773 | * Parse the relations |
paul@181 | 774 | */ |
paul@181 | 775 | tag = cp; |
paul@181 | 776 | cp = strchr(cp, '='); |
paul@181 | 777 | if (!cp) |
paul@181 | 778 | return PROF_RELATION_SYNTAX; |
paul@181 | 779 | if (cp == tag) |
paul@181 | 780 | return PROF_RELATION_SYNTAX; |
paul@181 | 781 | *cp = '\0'; |
paul@181 | 782 | if (*tag == '"') { |
paul@181 | 783 | tag++; |
paul@181 | 784 | parse_quoted_string(tag); |
paul@181 | 785 | } else { |
paul@181 | 786 | /* Look for whitespace on left-hand side. */ |
paul@181 | 787 | p = skip_over_nonblanks(tag); |
paul@181 | 788 | if (*p) |
paul@181 | 789 | *p++ = 0; |
paul@181 | 790 | p = skip_over_blanks(p); |
paul@181 | 791 | /* If we have more non-whitespace, it's an error. */ |
paul@181 | 792 | if (*p) |
paul@181 | 793 | return PROF_RELATION_SYNTAX; |
paul@181 | 794 | } |
paul@181 | 795 | |
paul@181 | 796 | cp = skip_over_blanks(cp+1); |
paul@181 | 797 | value = cp; |
paul@181 | 798 | ch = value[0]; |
paul@181 | 799 | if (ch == '"') { |
paul@181 | 800 | value++; |
paul@181 | 801 | parse_quoted_string(value); |
paul@181 | 802 | } else if (end_or_comment(ch)) { |
paul@181 | 803 | do_subsection++; |
paul@181 | 804 | state->state = STATE_GET_OBRACE; |
paul@181 | 805 | } else if (value[0] == '{') { |
paul@181 | 806 | cp = skip_over_blanks(value+1); |
paul@181 | 807 | ch = *cp; |
paul@181 | 808 | if (end_or_comment(ch)) |
paul@181 | 809 | do_subsection++; |
paul@181 | 810 | else |
paul@181 | 811 | return PROF_RELATION_SYNTAX; |
paul@181 | 812 | } else { |
paul@181 | 813 | cp = skip_over_nonblanks(value); |
paul@181 | 814 | p = skip_over_blanks(cp); |
paul@181 | 815 | ch = *p; |
paul@181 | 816 | *cp = 0; |
paul@181 | 817 | if (!end_or_comment(ch)) |
paul@181 | 818 | return PROF_RELATION_SYNTAX; |
paul@181 | 819 | } |
paul@181 | 820 | if (do_subsection) { |
paul@181 | 821 | p = strchr(tag, '*'); |
paul@181 | 822 | if (p) |
paul@181 | 823 | *p = '\0'; |
paul@181 | 824 | retval = profile_add_node(state->current_section, |
paul@181 | 825 | tag, 0, &state->current_section); |
paul@181 | 826 | if (retval) |
paul@181 | 827 | return retval; |
paul@181 | 828 | if (p) |
paul@181 | 829 | state->current_section->final = 1; |
paul@181 | 830 | state->group_level++; |
paul@181 | 831 | return 0; |
paul@181 | 832 | } |
paul@181 | 833 | p = strchr(tag, '*'); |
paul@181 | 834 | if (p) |
paul@181 | 835 | *p = '\0'; |
paul@181 | 836 | profile_add_node(state->current_section, tag, value, &node); |
paul@181 | 837 | if (p) |
paul@181 | 838 | node->final = 1; |
paul@181 | 839 | return 0; |
paul@181 | 840 | } |
paul@181 | 841 | |
paul@181 | 842 | #ifdef DEBUG_PROGRAM |
paul@181 | 843 | /* |
paul@181 | 844 | * Return TRUE if the string begins or ends with whitespace |
paul@181 | 845 | */ |
paul@181 | 846 | static int need_double_quotes(char *str) |
paul@181 | 847 | { |
paul@181 | 848 | if (!str || !*str) |
paul@181 | 849 | return 0; |
paul@181 | 850 | if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1)))) |
paul@181 | 851 | return 1; |
paul@181 | 852 | if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b') || |
paul@181 | 853 | strchr(str, ' ') || strchr(str, '#') || strchr(str, ';')) |
paul@181 | 854 | return 1; |
paul@181 | 855 | return 0; |
paul@181 | 856 | } |
paul@181 | 857 | |
paul@181 | 858 | /* |
paul@181 | 859 | * Output a string with double quotes, doing appropriate backquoting |
paul@181 | 860 | * of characters as necessary. |
paul@181 | 861 | */ |
paul@181 | 862 | static void output_quoted_string(char *str, void (*cb)(const char *,void *), |
paul@181 | 863 | void *data) |
paul@181 | 864 | { |
paul@181 | 865 | char ch; |
paul@181 | 866 | char buf[2]; |
paul@181 | 867 | |
paul@181 | 868 | cb("\"", data); |
paul@181 | 869 | if (!str) { |
paul@181 | 870 | cb("\"", data); |
paul@181 | 871 | return; |
paul@181 | 872 | } |
paul@181 | 873 | buf[1] = 0; |
paul@181 | 874 | while ((ch = *str++)) { |
paul@181 | 875 | switch (ch) { |
paul@181 | 876 | case '\\': |
paul@181 | 877 | cb("\\\\", data); |
paul@181 | 878 | break; |
paul@181 | 879 | case '\n': |
paul@181 | 880 | cb("\\n", data); |
paul@181 | 881 | break; |
paul@181 | 882 | case '\t': |
paul@181 | 883 | cb("\\t", data); |
paul@181 | 884 | break; |
paul@181 | 885 | case '\b': |
paul@181 | 886 | cb("\\b", data); |
paul@181 | 887 | break; |
paul@181 | 888 | default: |
paul@181 | 889 | /* This would be a lot faster if we scanned |
paul@181 | 890 | forward for the next "interesting" |
paul@181 | 891 | character. */ |
paul@181 | 892 | buf[0] = ch; |
paul@181 | 893 | cb(buf, data); |
paul@181 | 894 | break; |
paul@181 | 895 | } |
paul@181 | 896 | } |
paul@181 | 897 | cb("\"", data); |
paul@181 | 898 | } |
paul@181 | 899 | |
paul@181 | 900 | #ifndef EOL |
paul@181 | 901 | #define EOL "\n" |
paul@181 | 902 | #endif |
paul@181 | 903 | |
paul@181 | 904 | /* Errors should be returned, not ignored! */ |
paul@181 | 905 | static void dump_profile(struct profile_node *root, int level, |
paul@181 | 906 | void (*cb)(const char *, void *), void *data) |
paul@181 | 907 | { |
paul@181 | 908 | int i; |
paul@181 | 909 | struct profile_node *p; |
paul@181 | 910 | void *iter; |
paul@181 | 911 | long retval; |
paul@181 | 912 | |
paul@181 | 913 | iter = 0; |
paul@181 | 914 | do { |
paul@181 | 915 | retval = profile_find_node(root, 0, 0, 0, &iter, &p); |
paul@181 | 916 | if (retval) |
paul@181 | 917 | break; |
paul@181 | 918 | for (i=0; i < level; i++) |
paul@181 | 919 | cb("\t", data); |
paul@181 | 920 | if (need_double_quotes(p->name)) |
paul@181 | 921 | output_quoted_string(p->name, cb, data); |
paul@181 | 922 | else |
paul@181 | 923 | cb(p->name, data); |
paul@181 | 924 | cb(" = ", data); |
paul@181 | 925 | if (need_double_quotes(p->value)) |
paul@181 | 926 | output_quoted_string(p->value, cb, data); |
paul@181 | 927 | else |
paul@181 | 928 | cb(p->value, data); |
paul@181 | 929 | cb(EOL, data); |
paul@181 | 930 | } while (iter != 0); |
paul@181 | 931 | |
paul@181 | 932 | iter = 0; |
paul@181 | 933 | do { |
paul@181 | 934 | retval = profile_find_node(root, 0, 0, 1, &iter, &p); |
paul@181 | 935 | if (retval) |
paul@181 | 936 | break; |
paul@181 | 937 | if (level == 0) { /* [xxx] */ |
paul@181 | 938 | cb("[", data); |
paul@181 | 939 | if (need_double_quotes(p->name)) |
paul@181 | 940 | output_quoted_string(p->name, cb, data); |
paul@181 | 941 | else |
paul@181 | 942 | cb(p->name, data); |
paul@181 | 943 | cb("]", data); |
paul@181 | 944 | cb(p->final ? "*" : "", data); |
paul@181 | 945 | cb(EOL, data); |
paul@181 | 946 | dump_profile(p, level+1, cb, data); |
paul@181 | 947 | cb(EOL, data); |
paul@181 | 948 | } else { /* xxx = { ... } */ |
paul@181 | 949 | for (i=0; i < level; i++) |
paul@181 | 950 | cb("\t", data); |
paul@181 | 951 | if (need_double_quotes(p->name)) |
paul@181 | 952 | output_quoted_string(p->name, cb, data); |
paul@181 | 953 | else |
paul@181 | 954 | cb(p->name, data); |
paul@181 | 955 | cb(" = {", data); |
paul@181 | 956 | cb(EOL, data); |
paul@181 | 957 | dump_profile(p, level+1, cb, data); |
paul@181 | 958 | for (i=0; i < level; i++) |
paul@181 | 959 | cb("\t", data); |
paul@181 | 960 | cb("}", data); |
paul@181 | 961 | cb(p->final ? "*" : "", data); |
paul@181 | 962 | cb(EOL, data); |
paul@181 | 963 | } |
paul@181 | 964 | } while (iter != 0); |
paul@181 | 965 | } |
paul@181 | 966 | |
paul@181 | 967 | static void dump_profile_to_file_cb(const char *str, void *data) |
paul@181 | 968 | { |
paul@181 | 969 | fputs(str, data); |
paul@181 | 970 | } |
paul@181 | 971 | |
paul@181 | 972 | errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile) |
paul@181 | 973 | { |
paul@181 | 974 | dump_profile(root, 0, dump_profile_to_file_cb, dstfile); |
paul@181 | 975 | return 0; |
paul@181 | 976 | } |
paul@181 | 977 | |
paul@181 | 978 | struct prof_buf { |
paul@181 | 979 | char *base; |
paul@181 | 980 | size_t cur, max; |
paul@181 | 981 | int err; |
paul@181 | 982 | }; |
paul@181 | 983 | |
paul@181 | 984 | static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len) |
paul@181 | 985 | { |
paul@181 | 986 | if (b->err) |
paul@181 | 987 | return; |
paul@181 | 988 | if (b->max - b->cur < len) { |
paul@181 | 989 | size_t newsize; |
paul@181 | 990 | char *newptr; |
paul@181 | 991 | |
paul@181 | 992 | newsize = b->max + (b->max >> 1) + len + 1024; |
paul@181 | 993 | newptr = realloc(b->base, newsize); |
paul@181 | 994 | if (newptr == NULL) { |
paul@181 | 995 | b->err = 1; |
paul@181 | 996 | return; |
paul@181 | 997 | } |
paul@181 | 998 | b->base = newptr; |
paul@181 | 999 | b->max = newsize; |
paul@181 | 1000 | } |
paul@181 | 1001 | memcpy(b->base + b->cur, d, len); |
paul@181 | 1002 | b->cur += len; /* ignore overflow */ |
paul@181 | 1003 | } |
paul@181 | 1004 | |
paul@181 | 1005 | static void dump_profile_to_buffer_cb(const char *str, void *data) |
paul@181 | 1006 | { |
paul@181 | 1007 | add_data_to_buffer((struct prof_buf *)data, str, strlen(str)); |
paul@181 | 1008 | } |
paul@181 | 1009 | |
paul@181 | 1010 | errcode_t profile_write_tree_to_buffer(struct profile_node *root, |
paul@181 | 1011 | char **buf) |
paul@181 | 1012 | { |
paul@181 | 1013 | struct prof_buf prof_buf = { 0, 0, 0, 0 }; |
paul@181 | 1014 | |
paul@181 | 1015 | dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf); |
paul@181 | 1016 | if (prof_buf.err) { |
paul@181 | 1017 | *buf = NULL; |
paul@181 | 1018 | return ENOMEM; |
paul@181 | 1019 | } |
paul@181 | 1020 | add_data_to_buffer(&prof_buf, "", 1); /* append nul */ |
paul@181 | 1021 | if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) { |
paul@181 | 1022 | char *newptr = realloc(prof_buf.base, prof_buf.cur); |
paul@181 | 1023 | if (newptr) |
paul@181 | 1024 | prof_buf.base = newptr; |
paul@181 | 1025 | } |
paul@181 | 1026 | *buf = prof_buf.base; |
paul@181 | 1027 | return 0; |
paul@181 | 1028 | } |
paul@181 | 1029 | #endif |
paul@181 | 1030 | |
paul@181 | 1031 | /* |
paul@181 | 1032 | * prof_tree.c --- these routines maintain the parse tree of the |
paul@181 | 1033 | * config file. |
paul@181 | 1034 | * |
paul@181 | 1035 | * All of the details of how the tree is stored is abstracted away in |
paul@181 | 1036 | * this file; all of the other profile routines build, access, and |
paul@181 | 1037 | * modify the tree via the accessor functions found in this file. |
paul@181 | 1038 | * |
paul@181 | 1039 | * Each node may represent either a relation or a section header. |
paul@181 | 1040 | * |
paul@181 | 1041 | * A section header must have its value field set to 0, and may a one |
paul@181 | 1042 | * or more child nodes, pointed to by first_child. |
paul@181 | 1043 | * |
paul@181 | 1044 | * A relation has as its value a pointer to allocated memory |
paul@181 | 1045 | * containing a string. Its first_child pointer must be null. |
paul@181 | 1046 | * |
paul@181 | 1047 | */ |
paul@181 | 1048 | |
paul@181 | 1049 | /* |
paul@181 | 1050 | * Free a node, and any children |
paul@181 | 1051 | */ |
paul@181 | 1052 | void profile_free_node(struct profile_node *node) |
paul@181 | 1053 | { |
paul@181 | 1054 | struct profile_node *child, *next; |
paul@181 | 1055 | |
paul@181 | 1056 | if (node->magic != PROF_MAGIC_NODE) |
paul@181 | 1057 | return; |
paul@181 | 1058 | |
paul@181 | 1059 | free(node->name); |
paul@181 | 1060 | free(node->value); |
paul@181 | 1061 | |
paul@181 | 1062 | for (child=node->first_child; child; child = next) { |
paul@181 | 1063 | next = child->next; |
paul@181 | 1064 | profile_free_node(child); |
paul@181 | 1065 | } |
paul@181 | 1066 | node->magic = 0; |
paul@181 | 1067 | |
paul@181 | 1068 | free(node); |
paul@181 | 1069 | } |
paul@181 | 1070 | |
paul@181 | 1071 | #ifndef HAVE_STRDUP |
paul@181 | 1072 | #undef strdup |
paul@181 | 1073 | #define strdup MYstrdup |
paul@181 | 1074 | static char *MYstrdup (const char *s) |
paul@181 | 1075 | { |
paul@181 | 1076 | size_t sz = strlen(s) + 1; |
paul@181 | 1077 | char *p = malloc(sz); |
paul@181 | 1078 | if (p != 0) |
paul@181 | 1079 | memcpy(p, s, sz); |
paul@181 | 1080 | return p; |
paul@181 | 1081 | } |
paul@181 | 1082 | #endif |
paul@181 | 1083 | |
paul@181 | 1084 | /* |
paul@181 | 1085 | * Create a node |
paul@181 | 1086 | */ |
paul@181 | 1087 | errcode_t profile_create_node(const char *name, const char *value, |
paul@181 | 1088 | struct profile_node **ret_node) |
paul@181 | 1089 | { |
paul@181 | 1090 | struct profile_node *new; |
paul@181 | 1091 | |
paul@181 | 1092 | new = malloc(sizeof(struct profile_node)); |
paul@181 | 1093 | if (!new) |
paul@181 | 1094 | return ENOMEM; |
paul@181 | 1095 | memset(new, 0, sizeof(struct profile_node)); |
paul@181 | 1096 | new->name = strdup(name); |
paul@181 | 1097 | if (new->name == 0) { |
paul@181 | 1098 | profile_free_node(new); |
paul@181 | 1099 | return ENOMEM; |
paul@181 | 1100 | } |
paul@181 | 1101 | if (value) { |
paul@181 | 1102 | new->value = strdup(value); |
paul@181 | 1103 | if (new->value == 0) { |
paul@181 | 1104 | profile_free_node(new); |
paul@181 | 1105 | return ENOMEM; |
paul@181 | 1106 | } |
paul@181 | 1107 | } |
paul@181 | 1108 | new->magic = PROF_MAGIC_NODE; |
paul@181 | 1109 | |
paul@181 | 1110 | *ret_node = new; |
paul@181 | 1111 | return 0; |
paul@181 | 1112 | } |
paul@181 | 1113 | |
paul@181 | 1114 | /* |
paul@181 | 1115 | * This function verifies that all of the representation invariants of |
paul@181 | 1116 | * the profile are true. If not, we have a programming bug somewhere, |
paul@181 | 1117 | * probably in this file. |
paul@181 | 1118 | */ |
paul@181 | 1119 | #ifdef DEBUG_PROGRAM |
paul@181 | 1120 | errcode_t profile_verify_node(struct profile_node *node) |
paul@181 | 1121 | { |
paul@181 | 1122 | struct profile_node *p, *last; |
paul@181 | 1123 | errcode_t retval; |
paul@181 | 1124 | |
paul@181 | 1125 | CHECK_MAGIC(node); |
paul@181 | 1126 | |
paul@181 | 1127 | if (node->value && node->first_child) |
paul@181 | 1128 | return PROF_SECTION_WITH_VALUE; |
paul@181 | 1129 | |
paul@181 | 1130 | last = 0; |
paul@181 | 1131 | for (p = node->first_child; p; last = p, p = p->next) { |
paul@181 | 1132 | if (p->prev != last) |
paul@181 | 1133 | return PROF_BAD_LINK_LIST; |
paul@181 | 1134 | if (last && (last->next != p)) |
paul@181 | 1135 | return PROF_BAD_LINK_LIST; |
paul@181 | 1136 | if (node->group_level+1 != p->group_level) |
paul@181 | 1137 | return PROF_BAD_GROUP_LVL; |
paul@181 | 1138 | if (p->parent != node) |
paul@181 | 1139 | return PROF_BAD_PARENT_PTR; |
paul@181 | 1140 | retval = profile_verify_node(p); |
paul@181 | 1141 | if (retval) |
paul@181 | 1142 | return retval; |
paul@181 | 1143 | } |
paul@181 | 1144 | return 0; |
paul@181 | 1145 | } |
paul@181 | 1146 | #endif |
paul@181 | 1147 | |
paul@181 | 1148 | /* |
paul@181 | 1149 | * Add a node to a particular section |
paul@181 | 1150 | */ |
paul@181 | 1151 | errcode_t profile_add_node(struct profile_node *section, const char *name, |
paul@181 | 1152 | const char *value, struct profile_node **ret_node) |
paul@181 | 1153 | { |
paul@181 | 1154 | errcode_t retval; |
paul@181 | 1155 | struct profile_node *p, *last, *new; |
paul@181 | 1156 | |
paul@181 | 1157 | CHECK_MAGIC(section); |
paul@181 | 1158 | |
paul@181 | 1159 | if (section->value) |
paul@181 | 1160 | return PROF_ADD_NOT_SECTION; |
paul@181 | 1161 | |
paul@181 | 1162 | /* |
paul@181 | 1163 | * Find the place to insert the new node. We look for the |
paul@181 | 1164 | * place *after* the last match of the node name, since |
paul@181 | 1165 | * order matters. |
paul@181 | 1166 | */ |
paul@181 | 1167 | for (p=section->first_child, last = 0; p; last = p, p = p->next) { |
paul@181 | 1168 | int cmp; |
paul@181 | 1169 | cmp = strcmp(p->name, name); |
paul@181 | 1170 | if (cmp > 0) |
paul@181 | 1171 | break; |
paul@181 | 1172 | } |
paul@181 | 1173 | retval = profile_create_node(name, value, &new); |
paul@181 | 1174 | if (retval) |
paul@181 | 1175 | return retval; |
paul@181 | 1176 | new->group_level = section->group_level+1; |
paul@181 | 1177 | new->deleted = 0; |
paul@181 | 1178 | new->parent = section; |
paul@181 | 1179 | new->prev = last; |
paul@181 | 1180 | new->next = p; |
paul@181 | 1181 | if (p) |
paul@181 | 1182 | p->prev = new; |
paul@181 | 1183 | if (last) |
paul@181 | 1184 | last->next = new; |
paul@181 | 1185 | else |
paul@181 | 1186 | section->first_child = new; |
paul@181 | 1187 | if (ret_node) |
paul@181 | 1188 | *ret_node = new; |
paul@181 | 1189 | return 0; |
paul@181 | 1190 | } |
paul@181 | 1191 | |
paul@181 | 1192 | /* |
paul@181 | 1193 | * Iterate through the section, returning the nodes which match |
paul@181 | 1194 | * the given name. If name is NULL, then interate through all the |
paul@181 | 1195 | * nodes in the section. If section_flag is non-zero, only return the |
paul@181 | 1196 | * section which matches the name; don't return relations. If value |
paul@181 | 1197 | * is non-NULL, then only return relations which match the requested |
paul@181 | 1198 | * value. (The value argument is ignored if section_flag is non-zero.) |
paul@181 | 1199 | * |
paul@181 | 1200 | * The first time this routine is called, the state pointer must be |
paul@181 | 1201 | * null. When this profile_find_node_relation() returns, if the state |
paul@181 | 1202 | * pointer is non-NULL, then this routine should be called again. |
paul@181 | 1203 | * (This won't happen if section_flag is non-zero, obviously.) |
paul@181 | 1204 | * |
paul@181 | 1205 | */ |
paul@181 | 1206 | errcode_t profile_find_node(struct profile_node *section, const char *name, |
paul@181 | 1207 | const char *value, int section_flag, void **state, |
paul@181 | 1208 | struct profile_node **node) |
paul@181 | 1209 | { |
paul@181 | 1210 | struct profile_node *p; |
paul@181 | 1211 | |
paul@181 | 1212 | CHECK_MAGIC(section); |
paul@181 | 1213 | p = *state; |
paul@181 | 1214 | if (p) { |
paul@181 | 1215 | CHECK_MAGIC(p); |
paul@181 | 1216 | } else |
paul@181 | 1217 | p = section->first_child; |
paul@181 | 1218 | |
paul@181 | 1219 | for (; p; p = p->next) { |
paul@181 | 1220 | if (name && (strcmp(p->name, name))) |
paul@181 | 1221 | continue; |
paul@181 | 1222 | if (section_flag) { |
paul@181 | 1223 | if (p->value) |
paul@181 | 1224 | continue; |
paul@181 | 1225 | } else { |
paul@181 | 1226 | if (!p->value) |
paul@181 | 1227 | continue; |
paul@181 | 1228 | if (value && (strcmp(p->value, value))) |
paul@181 | 1229 | continue; |
paul@181 | 1230 | } |
paul@181 | 1231 | if (p->deleted) |
paul@181 | 1232 | continue; |
paul@181 | 1233 | /* A match! */ |
paul@181 | 1234 | if (node) |
paul@181 | 1235 | *node = p; |
paul@181 | 1236 | break; |
paul@181 | 1237 | } |
paul@181 | 1238 | if (p == 0) { |
paul@181 | 1239 | *state = 0; |
paul@181 | 1240 | return section_flag ? PROF_NO_SECTION : PROF_NO_RELATION; |
paul@181 | 1241 | } |
paul@181 | 1242 | /* |
paul@181 | 1243 | * OK, we've found one match; now let's try to find another |
paul@181 | 1244 | * one. This way, if we return a non-zero state pointer, |
paul@181 | 1245 | * there's guaranteed to be another match that's returned. |
paul@181 | 1246 | */ |
paul@181 | 1247 | for (p = p->next; p; p = p->next) { |
paul@181 | 1248 | if (name && (strcmp(p->name, name))) |
paul@181 | 1249 | continue; |
paul@181 | 1250 | if (section_flag) { |
paul@181 | 1251 | if (p->value) |
paul@181 | 1252 | continue; |
paul@181 | 1253 | } else { |
paul@181 | 1254 | if (!p->value) |
paul@181 | 1255 | continue; |
paul@181 | 1256 | if (value && (strcmp(p->value, value))) |
paul@181 | 1257 | continue; |
paul@181 | 1258 | } |
paul@181 | 1259 | /* A match! */ |
paul@181 | 1260 | break; |
paul@181 | 1261 | } |
paul@181 | 1262 | *state = p; |
paul@181 | 1263 | return 0; |
paul@181 | 1264 | } |
paul@181 | 1265 | |
paul@181 | 1266 | /* |
paul@181 | 1267 | * This is a general-purpose iterator for returning all nodes that |
paul@181 | 1268 | * match the specified name array. |
paul@181 | 1269 | */ |
paul@181 | 1270 | struct profile_iterator { |
paul@181 | 1271 | prf_magic_t magic; |
paul@181 | 1272 | profile_t profile; |
paul@181 | 1273 | int flags; |
paul@181 | 1274 | const char *const *names; |
paul@181 | 1275 | const char *name; |
paul@181 | 1276 | prf_file_t file; |
paul@181 | 1277 | int file_serial; |
paul@181 | 1278 | int done_idx; |
paul@181 | 1279 | struct profile_node *node; |
paul@181 | 1280 | int num; |
paul@181 | 1281 | }; |
paul@181 | 1282 | |
paul@181 | 1283 | errcode_t |
paul@181 | 1284 | profile_iterator_create(profile_t profile, const char *const *names, int flags, |
paul@181 | 1285 | void **ret_iter) |
paul@181 | 1286 | { |
paul@181 | 1287 | struct profile_iterator *iter; |
paul@181 | 1288 | int done_idx = 0; |
paul@181 | 1289 | |
paul@181 | 1290 | if (profile == 0) |
paul@181 | 1291 | return PROF_NO_PROFILE; |
paul@181 | 1292 | if (profile->magic != PROF_MAGIC_PROFILE) |
paul@181 | 1293 | return PROF_MAGIC_PROFILE; |
paul@181 | 1294 | if (!names) |
paul@181 | 1295 | return PROF_BAD_NAMESET; |
paul@181 | 1296 | if (!(flags & PROFILE_ITER_LIST_SECTION)) { |
paul@181 | 1297 | if (!names[0]) |
paul@181 | 1298 | return PROF_BAD_NAMESET; |
paul@181 | 1299 | done_idx = 1; |
paul@181 | 1300 | } |
paul@181 | 1301 | |
paul@181 | 1302 | if ((iter = malloc(sizeof(struct profile_iterator))) == NULL) |
paul@181 | 1303 | return ENOMEM; |
paul@181 | 1304 | |
paul@181 | 1305 | iter->magic = PROF_MAGIC_ITERATOR; |
paul@181 | 1306 | iter->profile = profile; |
paul@181 | 1307 | iter->names = names; |
paul@181 | 1308 | iter->flags = flags; |
paul@181 | 1309 | iter->file = profile->first_file; |
paul@181 | 1310 | iter->done_idx = done_idx; |
paul@181 | 1311 | iter->node = 0; |
paul@181 | 1312 | iter->num = 0; |
paul@181 | 1313 | *ret_iter = iter; |
paul@181 | 1314 | return 0; |
paul@181 | 1315 | } |
paul@181 | 1316 | |
paul@181 | 1317 | void profile_iterator_free(void **iter_p) |
paul@181 | 1318 | { |
paul@181 | 1319 | struct profile_iterator *iter; |
paul@181 | 1320 | |
paul@181 | 1321 | if (!iter_p) |
paul@181 | 1322 | return; |
paul@181 | 1323 | iter = *iter_p; |
paul@181 | 1324 | if (!iter || iter->magic != PROF_MAGIC_ITERATOR) |
paul@181 | 1325 | return; |
paul@181 | 1326 | free(iter); |
paul@181 | 1327 | *iter_p = 0; |
paul@181 | 1328 | } |
paul@181 | 1329 | |
paul@181 | 1330 | /* |
paul@181 | 1331 | * Note: the returned character strings in ret_name and ret_value |
paul@181 | 1332 | * points to the stored character string in the parse string. Before |
paul@181 | 1333 | * this string value is returned to a calling application |
paul@181 | 1334 | * (profile_node_iterator is not an exported interface), it should be |
paul@181 | 1335 | * strdup()'ed. |
paul@181 | 1336 | */ |
paul@181 | 1337 | errcode_t profile_node_iterator(void **iter_p, struct profile_node **ret_node, |
paul@181 | 1338 | char **ret_name, char **ret_value) |
paul@181 | 1339 | { |
paul@181 | 1340 | struct profile_iterator *iter = *iter_p; |
paul@181 | 1341 | struct profile_node *section, *p; |
paul@181 | 1342 | const char *const *cpp; |
paul@181 | 1343 | errcode_t retval; |
paul@181 | 1344 | int skip_num = 0; |
paul@181 | 1345 | |
paul@181 | 1346 | if (!iter || iter->magic != PROF_MAGIC_ITERATOR) |
paul@181 | 1347 | return PROF_MAGIC_ITERATOR; |
paul@181 | 1348 | if (iter->file && iter->file->magic != PROF_MAGIC_FILE) |
paul@181 | 1349 | return PROF_MAGIC_FILE; |
paul@181 | 1350 | /* |
paul@181 | 1351 | * If the file has changed, then the node pointer is invalid, |
paul@181 | 1352 | * so we'll have search the file again looking for it. |
paul@181 | 1353 | */ |
paul@181 | 1354 | if (iter->node && (iter->file && |
paul@181 | 1355 | iter->file->upd_serial != iter->file_serial)) { |
paul@181 | 1356 | iter->flags &= ~PROFILE_ITER_FINAL_SEEN; |
paul@181 | 1357 | skip_num = iter->num; |
paul@181 | 1358 | iter->node = 0; |
paul@181 | 1359 | } |
paul@181 | 1360 | if (iter->node && iter->node->magic != PROF_MAGIC_NODE) { |
paul@181 | 1361 | return PROF_MAGIC_NODE; |
paul@181 | 1362 | } |
paul@181 | 1363 | get_new_file: |
paul@181 | 1364 | if (iter->node == 0) { |
paul@181 | 1365 | if (iter->file == NULL || |
paul@181 | 1366 | (iter->flags & PROFILE_ITER_FINAL_SEEN)) { |
paul@181 | 1367 | profile_iterator_free(iter_p); |
paul@181 | 1368 | if (ret_node) |
paul@181 | 1369 | *ret_node = 0; |
paul@181 | 1370 | if (ret_name) |
paul@181 | 1371 | *ret_name = 0; |
paul@181 | 1372 | if (ret_value) |
paul@181 | 1373 | *ret_value =0; |
paul@181 | 1374 | return 0; |
paul@181 | 1375 | } |
paul@181 | 1376 | if ((retval = profile_update_file(iter->file))) { |
paul@181 | 1377 | if (retval == ENOENT || retval == EACCES) { |
paul@181 | 1378 | /* XXX memory leak? */ |
paul@181 | 1379 | if (iter->file) |
paul@181 | 1380 | iter->file = iter->file->next; |
paul@181 | 1381 | skip_num = 0; |
paul@181 | 1382 | retval = 0; |
paul@181 | 1383 | goto get_new_file; |
paul@181 | 1384 | } else { |
paul@181 | 1385 | profile_iterator_free(iter_p); |
paul@181 | 1386 | return retval; |
paul@181 | 1387 | } |
paul@181 | 1388 | } |
paul@181 | 1389 | iter->file_serial = iter->file->upd_serial; |
paul@181 | 1390 | /* |
paul@181 | 1391 | * Find the section to list if we are a LIST_SECTION, |
paul@181 | 1392 | * or find the containing section if not. |
paul@181 | 1393 | */ |
paul@181 | 1394 | section = iter->file->root; |
paul@181 | 1395 | for (cpp = iter->names; cpp[iter->done_idx]; cpp++) { |
paul@181 | 1396 | for (p=section->first_child; p; p = p->next) { |
paul@181 | 1397 | if (!strcmp(p->name, *cpp) && !p->value) |
paul@181 | 1398 | break; |
paul@181 | 1399 | } |
paul@181 | 1400 | if (!p) { |
paul@181 | 1401 | section = 0; |
paul@181 | 1402 | break; |
paul@181 | 1403 | } |
paul@181 | 1404 | section = p; |
paul@181 | 1405 | if (p->final) |
paul@181 | 1406 | iter->flags |= PROFILE_ITER_FINAL_SEEN; |
paul@181 | 1407 | } |
paul@181 | 1408 | if (!section) { |
paul@181 | 1409 | if (iter->file) |
paul@181 | 1410 | iter->file = iter->file->next; |
paul@181 | 1411 | skip_num = 0; |
paul@181 | 1412 | goto get_new_file; |
paul@181 | 1413 | } |
paul@181 | 1414 | iter->name = *cpp; |
paul@181 | 1415 | iter->node = section->first_child; |
paul@181 | 1416 | } |
paul@181 | 1417 | /* |
paul@181 | 1418 | * OK, now we know iter->node is set up correctly. Let's do |
paul@181 | 1419 | * the search. |
paul@181 | 1420 | */ |
paul@181 | 1421 | for (p = iter->node; p; p = p->next) { |
paul@181 | 1422 | if (iter->name && strcmp(p->name, iter->name)) |
paul@181 | 1423 | continue; |
paul@181 | 1424 | if ((iter->flags & PROFILE_ITER_SECTIONS_ONLY) && |
paul@181 | 1425 | p->value) |
paul@181 | 1426 | continue; |
paul@181 | 1427 | if ((iter->flags & PROFILE_ITER_RELATIONS_ONLY) && |
paul@181 | 1428 | !p->value) |
paul@181 | 1429 | continue; |
paul@181 | 1430 | if (skip_num > 0) { |
paul@181 | 1431 | skip_num--; |
paul@181 | 1432 | continue; |
paul@181 | 1433 | } |
paul@181 | 1434 | if (p->deleted) |
paul@181 | 1435 | continue; |
paul@181 | 1436 | break; |
paul@181 | 1437 | } |
paul@181 | 1438 | iter->num++; |
paul@181 | 1439 | if (!p) { |
paul@181 | 1440 | if (iter->file) |
paul@181 | 1441 | iter->file = iter->file->next; |
paul@181 | 1442 | iter->node = 0; |
paul@181 | 1443 | skip_num = 0; |
paul@181 | 1444 | goto get_new_file; |
paul@181 | 1445 | } |
paul@181 | 1446 | if ((iter->node = p->next) == NULL) |
paul@181 | 1447 | if (iter->file) |
paul@181 | 1448 | iter->file = iter->file->next; |
paul@181 | 1449 | if (ret_node) |
paul@181 | 1450 | *ret_node = p; |
paul@181 | 1451 | if (ret_name) |
paul@181 | 1452 | *ret_name = p->name; |
paul@181 | 1453 | if (ret_value) |
paul@181 | 1454 | *ret_value = p->value; |
paul@181 | 1455 | return 0; |
paul@181 | 1456 | } |
paul@181 | 1457 | |
paul@181 | 1458 | |
paul@181 | 1459 | /* |
paul@181 | 1460 | * prof_get.c --- routines that expose the public interfaces for |
paul@181 | 1461 | * querying items from the profile. |
paul@181 | 1462 | * |
paul@181 | 1463 | */ |
paul@181 | 1464 | |
paul@181 | 1465 | /* |
paul@181 | 1466 | * This function only gets the first value from the file; it is a |
paul@181 | 1467 | * helper function for profile_get_string, profile_get_integer, etc. |
paul@181 | 1468 | */ |
paul@181 | 1469 | errcode_t profile_get_value(profile_t profile, const char *name, |
paul@181 | 1470 | const char *subname, const char *subsubname, |
paul@181 | 1471 | const char **ret_value) |
paul@181 | 1472 | { |
paul@181 | 1473 | errcode_t retval; |
paul@181 | 1474 | void *state; |
paul@181 | 1475 | char *value; |
paul@181 | 1476 | const char *names[4]; |
paul@181 | 1477 | |
paul@181 | 1478 | names[0] = name; |
paul@181 | 1479 | names[1] = subname; |
paul@181 | 1480 | names[2] = subsubname; |
paul@181 | 1481 | names[3] = 0; |
paul@181 | 1482 | |
paul@181 | 1483 | if ((retval = profile_iterator_create(profile, names, |
paul@181 | 1484 | PROFILE_ITER_RELATIONS_ONLY, |
paul@181 | 1485 | &state))) |
paul@181 | 1486 | return retval; |
paul@181 | 1487 | |
paul@181 | 1488 | if ((retval = profile_node_iterator(&state, 0, 0, &value))) |
paul@181 | 1489 | goto cleanup; |
paul@181 | 1490 | |
paul@181 | 1491 | if (value) |
paul@181 | 1492 | *ret_value = value; |
paul@181 | 1493 | else |
paul@181 | 1494 | retval = PROF_NO_RELATION; |
paul@181 | 1495 | |
paul@181 | 1496 | cleanup: |
paul@181 | 1497 | profile_iterator_free(&state); |
paul@181 | 1498 | return retval; |
paul@181 | 1499 | } |
paul@181 | 1500 | |
paul@181 | 1501 | errcode_t |
paul@181 | 1502 | profile_get_string(profile_t profile, const char *name, const char *subname, |
paul@181 | 1503 | const char *subsubname, const char *def_val, |
paul@181 | 1504 | char **ret_string) |
paul@181 | 1505 | { |
paul@181 | 1506 | const char *value; |
paul@181 | 1507 | errcode_t retval; |
paul@181 | 1508 | |
paul@181 | 1509 | if (profile) { |
paul@181 | 1510 | retval = profile_get_value(profile, name, subname, |
paul@181 | 1511 | subsubname, &value); |
paul@181 | 1512 | if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) |
paul@181 | 1513 | value = def_val; |
paul@181 | 1514 | else if (retval) |
paul@181 | 1515 | return retval; |
paul@181 | 1516 | } else |
paul@181 | 1517 | value = def_val; |
paul@181 | 1518 | |
paul@181 | 1519 | if (value) { |
paul@181 | 1520 | *ret_string = malloc(strlen(value)+1); |
paul@181 | 1521 | if (*ret_string == 0) |
paul@181 | 1522 | return ENOMEM; |
paul@181 | 1523 | strcpy(*ret_string, value); |
paul@181 | 1524 | } else |
paul@181 | 1525 | *ret_string = 0; |
paul@181 | 1526 | return 0; |
paul@181 | 1527 | } |
paul@181 | 1528 | |
paul@181 | 1529 | errcode_t |
paul@181 | 1530 | profile_get_integer(profile_t profile, const char *name, const char *subname, |
paul@181 | 1531 | const char *subsubname, int def_val, int *ret_int) |
paul@181 | 1532 | { |
paul@181 | 1533 | const char *value; |
paul@181 | 1534 | errcode_t retval; |
paul@181 | 1535 | char *end_value; |
paul@181 | 1536 | long ret_long; |
paul@181 | 1537 | |
paul@181 | 1538 | *ret_int = def_val; |
paul@181 | 1539 | if (profile == 0) |
paul@181 | 1540 | return 0; |
paul@181 | 1541 | |
paul@181 | 1542 | retval = profile_get_value(profile, name, subname, subsubname, &value); |
paul@181 | 1543 | if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) { |
paul@181 | 1544 | *ret_int = def_val; |
paul@181 | 1545 | return 0; |
paul@181 | 1546 | } else if (retval) |
paul@181 | 1547 | return retval; |
paul@181 | 1548 | |
paul@181 | 1549 | if (value[0] == 0) |
paul@181 | 1550 | /* Empty string is no good. */ |
paul@181 | 1551 | return PROF_BAD_INTEGER; |
paul@181 | 1552 | errno = 0; |
paul@181 | 1553 | ret_long = strtol(value, &end_value, 0); |
paul@181 | 1554 | |
paul@181 | 1555 | /* Overflow or underflow. */ |
paul@181 | 1556 | if ((ret_long == LONG_MIN || ret_long == LONG_MAX) && errno != 0) |
paul@181 | 1557 | return PROF_BAD_INTEGER; |
paul@181 | 1558 | /* Value outside "int" range. */ |
paul@181 | 1559 | if ((long) (int) ret_long != ret_long) |
paul@181 | 1560 | return PROF_BAD_INTEGER; |
paul@181 | 1561 | /* Garbage in string. */ |
paul@181 | 1562 | if (end_value != value + strlen (value)) |
paul@181 | 1563 | return PROF_BAD_INTEGER; |
paul@181 | 1564 | |
paul@181 | 1565 | |
paul@181 | 1566 | *ret_int = ret_long; |
paul@181 | 1567 | return 0; |
paul@181 | 1568 | } |
paul@181 | 1569 | |
paul@181 | 1570 | errcode_t |
paul@181 | 1571 | profile_get_uint(profile_t profile, const char *name, const char *subname, |
paul@181 | 1572 | const char *subsubname, unsigned int def_val, |
paul@181 | 1573 | unsigned int *ret_int) |
paul@181 | 1574 | { |
paul@181 | 1575 | const char *value; |
paul@181 | 1576 | errcode_t retval; |
paul@181 | 1577 | char *end_value; |
paul@181 | 1578 | unsigned long ret_long; |
paul@181 | 1579 | |
paul@181 | 1580 | *ret_int = def_val; |
paul@181 | 1581 | if (profile == 0) |
paul@181 | 1582 | return 0; |
paul@181 | 1583 | |
paul@181 | 1584 | retval = profile_get_value(profile, name, subname, subsubname, &value); |
paul@181 | 1585 | if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) { |
paul@181 | 1586 | *ret_int = def_val; |
paul@181 | 1587 | return 0; |
paul@181 | 1588 | } else if (retval) |
paul@181 | 1589 | return retval; |
paul@181 | 1590 | |
paul@181 | 1591 | if (value[0] == 0) |
paul@181 | 1592 | /* Empty string is no good. */ |
paul@181 | 1593 | return PROF_BAD_INTEGER; |
paul@181 | 1594 | errno = 0; |
paul@181 | 1595 | ret_long = strtoul(value, &end_value, 0); |
paul@181 | 1596 | |
paul@181 | 1597 | /* Overflow or underflow. */ |
paul@181 | 1598 | if ((ret_long == ULONG_MAX) && errno != 0) |
paul@181 | 1599 | return PROF_BAD_INTEGER; |
paul@181 | 1600 | /* Value outside "int" range. */ |
paul@181 | 1601 | if ((unsigned long) (unsigned int) ret_long != ret_long) |
paul@181 | 1602 | return PROF_BAD_INTEGER; |
paul@181 | 1603 | /* Garbage in string. */ |
paul@181 | 1604 | if (end_value != value + strlen (value)) |
paul@181 | 1605 | return PROF_BAD_INTEGER; |
paul@181 | 1606 | |
paul@181 | 1607 | *ret_int = ret_long; |
paul@181 | 1608 | return 0; |
paul@181 | 1609 | } |
paul@181 | 1610 | |
paul@181 | 1611 | errcode_t |
paul@181 | 1612 | profile_get_double(profile_t profile, const char *name, const char *subname, |
paul@181 | 1613 | const char *subsubname, double def_val, double *ret_double) |
paul@181 | 1614 | { |
paul@181 | 1615 | const char *value; |
paul@181 | 1616 | errcode_t retval; |
paul@181 | 1617 | char *end_value; |
paul@181 | 1618 | double double_val; |
paul@181 | 1619 | |
paul@181 | 1620 | *ret_double = def_val; |
paul@181 | 1621 | if (profile == 0) |
paul@181 | 1622 | return 0; |
paul@181 | 1623 | |
paul@181 | 1624 | retval = profile_get_value(profile, name, subname, subsubname, &value); |
paul@181 | 1625 | if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) { |
paul@181 | 1626 | *ret_double = def_val; |
paul@181 | 1627 | return 0; |
paul@181 | 1628 | } else if (retval) |
paul@181 | 1629 | return retval; |
paul@181 | 1630 | |
paul@181 | 1631 | if (value[0] == 0) |
paul@181 | 1632 | /* Empty string is no good. */ |
paul@181 | 1633 | return PROF_BAD_INTEGER; |
paul@181 | 1634 | errno = 0; |
paul@181 | 1635 | double_val = strtod(value, &end_value); |
paul@181 | 1636 | |
paul@181 | 1637 | /* Overflow or underflow. */ |
paul@181 | 1638 | if (errno != 0) |
paul@181 | 1639 | return PROF_BAD_INTEGER; |
paul@181 | 1640 | /* Garbage in string. */ |
paul@181 | 1641 | if (end_value != value + strlen(value)) |
paul@181 | 1642 | return PROF_BAD_INTEGER; |
paul@181 | 1643 | |
paul@181 | 1644 | *ret_double = double_val; |
paul@181 | 1645 | return 0; |
paul@181 | 1646 | } |
paul@181 | 1647 | |
paul@181 | 1648 | static const char *const conf_yes[] = { |
paul@181 | 1649 | "y", "yes", "true", "t", "1", "on", |
paul@181 | 1650 | 0, |
paul@181 | 1651 | }; |
paul@181 | 1652 | |
paul@181 | 1653 | static const char *const conf_no[] = { |
paul@181 | 1654 | "n", "no", "false", "nil", "0", "off", |
paul@181 | 1655 | 0, |
paul@181 | 1656 | }; |
paul@181 | 1657 | |
paul@181 | 1658 | static errcode_t |
paul@181 | 1659 | profile_parse_boolean(const char *s, int *ret_boolean) |
paul@181 | 1660 | { |
paul@181 | 1661 | const char *const *p; |
paul@181 | 1662 | |
paul@181 | 1663 | if (ret_boolean == NULL) |
paul@181 | 1664 | return PROF_EINVAL; |
paul@181 | 1665 | |
paul@181 | 1666 | for(p=conf_yes; *p; p++) { |
paul@181 | 1667 | if (!strcasecmp(*p,s)) { |
paul@181 | 1668 | *ret_boolean = 1; |
paul@181 | 1669 | return 0; |
paul@181 | 1670 | } |
paul@181 | 1671 | } |
paul@181 | 1672 | |
paul@181 | 1673 | for(p=conf_no; *p; p++) { |
paul@181 | 1674 | if (!strcasecmp(*p,s)) { |
paul@181 | 1675 | *ret_boolean = 0; |
paul@181 | 1676 | return 0; |
paul@181 | 1677 | } |
paul@181 | 1678 | } |
paul@181 | 1679 | |
paul@181 | 1680 | return PROF_BAD_BOOLEAN; |
paul@181 | 1681 | } |
paul@181 | 1682 | |
paul@181 | 1683 | errcode_t |
paul@181 | 1684 | profile_get_boolean(profile_t profile, const char *name, const char *subname, |
paul@181 | 1685 | const char *subsubname, int def_val, int *ret_boolean) |
paul@181 | 1686 | { |
paul@181 | 1687 | const char *value; |
paul@181 | 1688 | errcode_t retval; |
paul@181 | 1689 | |
paul@181 | 1690 | if (profile == 0) { |
paul@181 | 1691 | *ret_boolean = def_val; |
paul@181 | 1692 | return 0; |
paul@181 | 1693 | } |
paul@181 | 1694 | |
paul@181 | 1695 | retval = profile_get_value(profile, name, subname, subsubname, &value); |
paul@181 | 1696 | if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) { |
paul@181 | 1697 | *ret_boolean = def_val; |
paul@181 | 1698 | return 0; |
paul@181 | 1699 | } else if (retval) |
paul@181 | 1700 | return retval; |
paul@181 | 1701 | |
paul@181 | 1702 | return profile_parse_boolean (value, ret_boolean); |
paul@181 | 1703 | } |
paul@181 | 1704 | |
paul@181 | 1705 | errcode_t |
paul@181 | 1706 | profile_iterator(void **iter_p, char **ret_name, char **ret_value) |
paul@181 | 1707 | { |
paul@181 | 1708 | char *name, *value; |
paul@181 | 1709 | errcode_t retval; |
paul@181 | 1710 | |
paul@181 | 1711 | retval = profile_node_iterator(iter_p, 0, &name, &value); |
paul@181 | 1712 | if (retval) |
paul@181 | 1713 | return retval; |
paul@181 | 1714 | |
paul@181 | 1715 | if (ret_name) { |
paul@181 | 1716 | if (name) { |
paul@181 | 1717 | *ret_name = malloc(strlen(name)+1); |
paul@181 | 1718 | if (!*ret_name) |
paul@181 | 1719 | return ENOMEM; |
paul@181 | 1720 | strcpy(*ret_name, name); |
paul@181 | 1721 | } else |
paul@181 | 1722 | *ret_name = 0; |
paul@181 | 1723 | } |
paul@181 | 1724 | if (ret_value) { |
paul@181 | 1725 | if (value) { |
paul@181 | 1726 | *ret_value = malloc(strlen(value)+1); |
paul@181 | 1727 | if (!*ret_value) { |
paul@181 | 1728 | if (ret_name) { |
paul@181 | 1729 | free(*ret_name); |
paul@181 | 1730 | *ret_name = 0; |
paul@181 | 1731 | } |
paul@181 | 1732 | return ENOMEM; |
paul@181 | 1733 | } |
paul@181 | 1734 | strcpy(*ret_value, value); |
paul@181 | 1735 | } else |
paul@181 | 1736 | *ret_value = 0; |
paul@181 | 1737 | } |
paul@181 | 1738 | return 0; |
paul@181 | 1739 | } |
paul@181 | 1740 | |
paul@181 | 1741 | #ifdef DEBUG_PROGRAM |
paul@181 | 1742 | |
paul@181 | 1743 | /* |
paul@181 | 1744 | * test_profile.c --- testing program for the profile routine |
paul@181 | 1745 | */ |
paul@181 | 1746 | |
paul@181 | 1747 | #include "argv_parse.h" |
paul@181 | 1748 | #include "profile_helpers.h" |
paul@181 | 1749 | |
paul@181 | 1750 | const char *program_name = "test_profile"; |
paul@181 | 1751 | |
paul@181 | 1752 | #define PRINT_VALUE 1 |
paul@181 | 1753 | #define PRINT_VALUES 2 |
paul@181 | 1754 | |
paul@181 | 1755 | static void do_cmd(profile_t profile, char **argv) |
paul@181 | 1756 | { |
paul@181 | 1757 | errcode_t retval; |
paul@181 | 1758 | const char **names, *value; |
paul@181 | 1759 | char **values, **cpp; |
paul@181 | 1760 | char *cmd; |
paul@181 | 1761 | int print_status; |
paul@181 | 1762 | |
paul@181 | 1763 | cmd = *(argv); |
paul@181 | 1764 | names = (const char **) argv + 1; |
paul@181 | 1765 | print_status = 0; |
paul@181 | 1766 | retval = 0; |
paul@181 | 1767 | if (cmd == 0) |
paul@181 | 1768 | return; |
paul@181 | 1769 | if (!strcmp(cmd, "query")) { |
paul@181 | 1770 | retval = profile_get_values(profile, names, &values); |
paul@181 | 1771 | print_status = PRINT_VALUES; |
paul@181 | 1772 | } else if (!strcmp(cmd, "query1")) { |
paul@181 | 1773 | const char *name = 0; |
paul@181 | 1774 | const char *subname = 0; |
paul@181 | 1775 | const char *subsubname = 0; |
paul@181 | 1776 | |
paul@181 | 1777 | name = names[0]; |
paul@181 | 1778 | if (name) |
paul@181 | 1779 | subname = names[1]; |
paul@181 | 1780 | if (subname) |
paul@181 | 1781 | subsubname = names[2]; |
paul@181 | 1782 | if (subsubname && names[3]) { |
paul@181 | 1783 | fprintf(stderr, |
paul@181 | 1784 | "Only 3 levels are allowed with query1\n"); |
paul@181 | 1785 | retval = EINVAL; |
paul@181 | 1786 | } else |
paul@181 | 1787 | retval = profile_get_value(profile, name, subname, |
paul@181 | 1788 | subsubname, &value); |
paul@181 | 1789 | print_status = PRINT_VALUE; |
paul@181 | 1790 | } else if (!strcmp(cmd, "list_sections")) { |
paul@181 | 1791 | retval = profile_get_subsection_names(profile, names, |
paul@181 | 1792 | &values); |
paul@181 | 1793 | print_status = PRINT_VALUES; |
paul@181 | 1794 | } else if (!strcmp(cmd, "list_relations")) { |
paul@181 | 1795 | retval = profile_get_relation_names(profile, names, |
paul@181 | 1796 | &values); |
paul@181 | 1797 | print_status = PRINT_VALUES; |
paul@181 | 1798 | } else if (!strcmp(cmd, "dump")) { |
paul@181 | 1799 | retval = profile_write_tree_file |
paul@181 | 1800 | (profile->first_file->root, stdout); |
paul@181 | 1801 | #if 0 |
paul@181 | 1802 | } else if (!strcmp(cmd, "clear")) { |
paul@181 | 1803 | retval = profile_clear_relation(profile, names); |
paul@181 | 1804 | } else if (!strcmp(cmd, "update")) { |
paul@181 | 1805 | retval = profile_update_relation(profile, names+2, |
paul@181 | 1806 | *names, *(names+1)); |
paul@181 | 1807 | #endif |
paul@181 | 1808 | } else if (!strcmp(cmd, "verify")) { |
paul@181 | 1809 | retval = profile_verify_node |
paul@181 | 1810 | (profile->first_file->root); |
paul@181 | 1811 | #if 0 |
paul@181 | 1812 | } else if (!strcmp(cmd, "rename_section")) { |
paul@181 | 1813 | retval = profile_rename_section(profile, names+1, *names); |
paul@181 | 1814 | } else if (!strcmp(cmd, "add")) { |
paul@181 | 1815 | value = *names; |
paul@181 | 1816 | if (strcmp(value, "NULL") == 0) |
paul@181 | 1817 | value = NULL; |
paul@181 | 1818 | retval = profile_add_relation(profile, names+1, value); |
paul@181 | 1819 | } else if (!strcmp(cmd, "flush")) { |
paul@181 | 1820 | retval = profile_flush(profile); |
paul@181 | 1821 | #endif |
paul@181 | 1822 | } else { |
paul@181 | 1823 | printf("Invalid command.\n"); |
paul@181 | 1824 | } |
paul@181 | 1825 | if (retval) { |
paul@181 | 1826 | com_err(cmd, retval, ""); |
paul@181 | 1827 | print_status = 0; |
paul@181 | 1828 | } |
paul@181 | 1829 | switch (print_status) { |
paul@181 | 1830 | case PRINT_VALUE: |
paul@181 | 1831 | printf("%s\n", value); |
paul@181 | 1832 | break; |
paul@181 | 1833 | case PRINT_VALUES: |
paul@181 | 1834 | for (cpp = values; *cpp; cpp++) |
paul@181 | 1835 | printf("%s\n", *cpp); |
paul@181 | 1836 | profile_free_list(values); |
paul@181 | 1837 | break; |
paul@181 | 1838 | } |
paul@181 | 1839 | } |
paul@181 | 1840 | |
paul@181 | 1841 | static void do_batchmode(profile_t profile) |
paul@181 | 1842 | { |
paul@181 | 1843 | int argc, ret; |
paul@181 | 1844 | char **argv; |
paul@181 | 1845 | char buf[256]; |
paul@181 | 1846 | |
paul@181 | 1847 | while (!feof(stdin)) { |
paul@181 | 1848 | if (fgets(buf, sizeof(buf), stdin) == NULL) |
paul@181 | 1849 | break; |
paul@181 | 1850 | printf(">%s", buf); |
paul@181 | 1851 | ret = argv_parse(buf, &argc, &argv); |
paul@181 | 1852 | if (ret != 0) { |
paul@181 | 1853 | printf("Argv_parse returned %d!\n", ret); |
paul@181 | 1854 | continue; |
paul@181 | 1855 | } |
paul@181 | 1856 | do_cmd(profile, argv); |
paul@181 | 1857 | printf("\n"); |
paul@181 | 1858 | argv_free(argv); |
paul@181 | 1859 | } |
paul@181 | 1860 | profile_release(profile); |
paul@181 | 1861 | exit(0); |
paul@181 | 1862 | |
paul@181 | 1863 | } |
paul@181 | 1864 | |
paul@181 | 1865 | void syntax_err_report(const char *filename, long err, int line_num) |
paul@181 | 1866 | { |
paul@181 | 1867 | fprintf(stderr, "Syntax error in %s, line number %d: %s\n", |
paul@181 | 1868 | filename, line_num, error_message(err)); |
paul@181 | 1869 | exit(1); |
paul@181 | 1870 | } |
paul@181 | 1871 | |
paul@181 | 1872 | const char *default_str = "[foo]\n\tbar=quux\n\tsub = {\n\t\twin = true\n}\n"; |
paul@181 | 1873 | |
paul@181 | 1874 | int main(int argc, char **argv) |
paul@181 | 1875 | { |
paul@181 | 1876 | profile_t profile; |
paul@181 | 1877 | long retval; |
paul@181 | 1878 | char *cmd; |
paul@181 | 1879 | |
paul@181 | 1880 | if (argc < 2) { |
paul@181 | 1881 | fprintf(stderr, "Usage: %s filename [cmd argset]\n", program_name); |
paul@181 | 1882 | exit(1); |
paul@181 | 1883 | } |
paul@181 | 1884 | |
paul@181 | 1885 | initialize_prof_error_table(); |
paul@181 | 1886 | |
paul@181 | 1887 | profile_set_syntax_err_cb(syntax_err_report); |
paul@181 | 1888 | |
paul@181 | 1889 | retval = profile_init_path(argv[1], &profile); |
paul@181 | 1890 | if (retval) { |
paul@181 | 1891 | com_err(program_name, retval, "while initializing profile"); |
paul@181 | 1892 | exit(1); |
paul@181 | 1893 | } |
paul@181 | 1894 | retval = profile_set_default(profile, default_str); |
paul@181 | 1895 | if (retval) { |
paul@181 | 1896 | com_err(program_name, retval, "while setting default"); |
paul@181 | 1897 | exit(1); |
paul@181 | 1898 | } |
paul@181 | 1899 | |
paul@181 | 1900 | cmd = *(argv+2); |
paul@181 | 1901 | if (!cmd || !strcmp(cmd, "batch")) |
paul@181 | 1902 | do_batchmode(profile); |
paul@181 | 1903 | else |
paul@181 | 1904 | do_cmd(profile, argv+2); |
paul@181 | 1905 | profile_release(profile); |
paul@181 | 1906 | |
paul@181 | 1907 | return 0; |
paul@181 | 1908 | } |
paul@181 | 1909 | |
paul@181 | 1910 | #endif |