/* * libslack - https://libslack.org * * Copyright (C) 1999-2004, 2010, 2020-2023 raf * * This program 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, see . * * 20230824 raf */ /* =head1 NAME I - program properties file module =head1 SYNOPSIS #include #include const char *prop_get(const char *name); const char *prop_get_or(const char *name, const char *default_value); const char *prop_set(const char *name, const char *value); int prop_get_int(const char *name); int prop_get_int_or(const char *name, int default_value); int prop_set_int(const char *name, int value); double prop_get_double(const char *name); double prop_get_double_or(const char *name, double default_value); double prop_set_double(const char *name, double value); int prop_get_bool(const char *name); int prop_get_bool_or(const char *name, int default_value); int prop_set_bool(const char *name, int value); int prop_unset(const char *name); int prop_save(void); int prop_clear(void); int prop_locker(Locker *locker); =head1 DESCRIPTION This module provides support for system-wide and user-specific (generic and program-specific) properties in "well-known" locations: /etc/properties/app - system-wide, generic properties ~/.properties/app - user-defined, generic properties /etc/properties/app.progname - system-wide, program-specific properties ~/.properties/app.progname - user-defined, program-specific properties Note that, depending on how I was installed, the system-wide C directory might not be under C. It might be under another directory, such as C or C. If you aren't sure where it is expected, run C and look for the definition of C. When the client first requests, sets, or unsets a property, all properties relevant to the current program are loaded from these files in the order given above. This order ensures that program-specific properties override generic properties and that user-defined properties override system-wide properties. The client can change properties at runtime and save the current properties back to disk (to the user-defined, program-specific properties file). Program names (as returned by I) are converted into file name suffixes by replacing every occurrence of the file path separator (C<'/'>) with a C<'-'>. Properties files consist of one property per line. Each property is specified by its name, followed by C<'='> followed by its value. The name must not have a C<'='> in it unless it is quoted with a preceding C<'\'>. Special characters are expressed in I string literal notation (e.g. C<\a\b\f\n\r\t\v\x1b>). Spaces immediately before and after the C<'='> are stripped unless they are quoted with a preceding C<'\'>. The properties files may also contain blank lines and comments (C<'#'> until the end of the line). Boolean property values can be expressed as C<0> or C<1>, C or C, C or C, C or C (case insensitive). =over 4 =cut */ #ifndef _BSD_SOURCE #define _BSD_SOURCE /* For snprintf() on OpenBSD-4.7 */ #endif #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE /* New name for _BSD_SOURCE */ #endif #include "config.h" #include "std.h" #include #include #include "prog.h" #include "daemon.h" #include "map.h" #include "err.h" #include "lim.h" #include "mem.h" #include "prop.h" #include "str.h" #include "locker.h" #ifndef HAVE_SNPRINTF #include "snprintf.h" #endif #ifndef isodigit #define isodigit(c) (isdigit((int)(unsigned char)c) && (unsigned char)(c) < '8') #endif #ifndef is_space #define is_space(c) isspace((int)(unsigned char)(c)) #endif typedef struct Prop Prop; struct Prop { Map *map; Prop *defaults; }; #ifndef TEST static struct { int init; Prop *prop; char *home; int dirty; Locker *locker; } g = { 0, NULL, NULL, 0, NULL }; /* C Creates and returns a I containing C and C. On error, returns C with C set appropriately. The new I will destroy C and C when it is destroyed. */ static Prop *prop_create(Map *map, Prop *defaults) { Prop *prop; if (!(prop = mem_new(Prop))) return NULL; prop->map = map; prop->defaults = defaults; return prop; } /* C Releases (deallocates) C. */ static void prop_release(Prop *prop) { if (!prop) return; map_release(prop->map); prop_release(prop->defaults); mem_release(prop); } /* C Compares the two strings pointed to by C and C using I. */ static int key_cmp(const char **a, const char **b) { return strcmp(*a, *b); } static const char *special_code = "abfnrtv"; static const char *special_char = "\a\b\f\n\r\t\v"; static const char *eq = "="; /* C Replaces every occurrence in C of special characters (represented in I by C<"\a">, C<"\b">, C<"\f">, C<"\n">, C<"\r">, C<"\t">, C<"\v">) with their corresponding I representation. Other non-printable characters are replaced with their I codes in hexadecimal (i.e. "\xhh"). It is the caller's responsibility to deallocate the returned I with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. */ static String *quote_special(const char *src) { return encode(src, special_char, special_code, '\\', 1); } /* C Replaces every occurrence in C of C<"\a">, C<"\b">, C<"\f">, C<"\n">, C<"\r">, C<"\t"> or C<"\v"> with the corresponding special characters (as interpreted by I). Ascii codes in octal or hexadecimal (i.e. "\ooo" or "\xhh") are replaced with their corresponding I characters. It is the caller's responsibility to deallocate the returned I with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. */ static String *unquote_special(const char *src) { return decode(src, special_char, special_code, '\\', 1); } /* C Replaces every occurrence in C of C<"="> with C<"\=">. It is the caller's responsibility to deallocate the returned I with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. */ static String *quote_equals(const char *src) { return encode(src, eq, eq, '\\', 0); } /* C Replaces every occurrence in C of C<"\="> with C<"=">. It is the caller's responsibility to deallocate the returned I with I or I. It is strongly recommended to use I, because it also sets the pointer variable to C. */ static String *unquote_equals(const char *src) { return decode(src, eq, eq, '\\', 0); } /* C Returns the user's home directory (obtained from C). The return value is cached so that any subsequent calls will be faster. */ static char *user_home(void) { struct passwd *pwent; char *home = NULL; if (g.home) return g.home; if ((pwent = getpwuid(getuid()))) home = pwent->pw_dir; return g.home = (home && strlen(home)) ? mem_strdup(home) : NULL; } /* C Parses one line from a properties file. C is the path of the properties file. C is the text to parse. C is the current line number. The property parsed, if any, is added to C. Emits error messages when syntax errors occur. That's probably a mistake. To suppress the error messages, call I first and restore error messages afterwards with something like I. */ static void prop_parse(Map *map, const char *path, char *line, size_t lineno) { String *prop, *name; char *p, *eq, *value, *val, *key; /* Unquote any special characters in the line */ if (!(prop = unquote_special(line))) { error("prop: Out of memory"); return; } /* Find first unquoted '=' */ for (p = cstr(prop), eq = strchr(p, '='); eq; eq = strchr(eq + 1, '=')) if (eq == p || eq[-1] != '\\') break; if (!eq) { error("prop: %s line %d: Expected '='\n%s", path, lineno, line); str_release(prop); set_errno(EINVAL); return; } /* Identify and separate the name and value */ value = eq + 1; while (is_space(value[0])) ++value; while (eq > p && is_space(eq[-1]) && (eq == p + 1 || eq[-2] != '\\')) --eq; *eq = nul; /* Unquote any quoted trailing space in the key */ if (eq > p + 1 && is_space(eq[-1]) && eq[-2] == '\\') { eq[-2] = eq[-1]; eq[-1] = nul; } /* Unquote any quoted '=' in the name */ if (!(name = unquote_equals(p))) { error("prop: Out of memory"); str_release(prop); return; } key = cstr(name); /* Unquote any quoted leading space in the value */ if (*value == '\\') ++value; /* Add this property to the map */ if (!(val = mem_strdup(value))) { error("prop: Out of memory"); str_release(prop); str_release(name); return; } if (map_add(map, key, val) == -1) { error("prop: %s line %d: Property %s already defined\n%s", path, lineno, name, line); mem_release(val); } str_release(prop); str_release(name); } /* C Creates and returns a new I containing C and the properties obtained from the properties file specified by C. On error, returns C with C set appropriately. */ static Prop *prop_load(const char *path, Prop *defaults) { Prop *prop; Map *map; if (!(map = map_create((map_release_t *)free))) return NULL; if (!daemon_parse_config(path, map, (daemon_config_parser_t *)prop_parse)) { map_release(map); return NULL; } if (!(prop = prop_create(map, defaults))) { map_release(map); return NULL; } return prop; } /* C Initialises the I module. Loads properties from the following locations: /etc/properties/app - system-wide, generic properties ~/.properties/app - user-defined, generic properties /etc/properties/app.progname - system-wide, program-specific properties ~/.properties/app.progname - user-defined, program-specific properties Note that, depending on how I was installed, the system-wide C directory might not be under C. It might be under another directory, such as C or C. If you aren't sure where it is expected, run C and look for the definition of C. Properties from the first three files become (nested) defaults. Properties from the last file becomes the top-level I. If the last file doesn't exist, an empty I becomes the top-level I. Called at the start of the first get, set, or unset function called. */ static int prop_init(void) { char *path; Prop *prop = NULL; Prop *prop_next; char *home; int writable = 0; size_t path_len; path_len = limit_path(); if (!(path = mem_create(path_len, char))) return -1; /* System wide generic properties: /etc/properties/app */ snprintf(path, path_len, "%s%cproperties%capp", ETC_DIR, PATH_SEP, PATH_SEP); if ((prop_next = prop_load(path, prop))) prop = prop_next; /* User defined generic properties: ~/.properties/app */ if ((home = user_home())) { snprintf(path, path_len, "%s%c.properties%capp", home, PATH_SEP, PATH_SEP); if ((prop_next = prop_load(path, prop))) prop = prop_next; } if (prog_name()) { char *progname, *sep; if (!(progname = mem_strdup(prog_name()))) { mem_release(path); prop_release(prop); return -1; } for (sep = strchr(progname, PATH_SEP); sep; sep = strchr(sep, PATH_SEP)) *sep++ = '-'; /* System wide program specific properties: /etc/properties/app.progname */ snprintf(path, path_len, "%s%cproperties%capp.%s", ETC_DIR, PATH_SEP, PATH_SEP, progname); if ((prop_next = prop_load(path, prop))) prop = prop_next; /* User defined program specific properties: ~/.properties/app.progname */ if (home) { snprintf(path, path_len, "%s%c.properties%capp.%s", home, PATH_SEP, PATH_SEP, progname); if ((prop_next = prop_load(path, prop))) { prop = prop_next; writable = 1; } } free(progname); } /* Guarantee a user defined program specific property map for prop_set() */ if (!writable) { Map *map; if (!(map = map_create((map_release_t *)free))) { mem_release(path); prop_release(prop); return -1; } if (!(prop_next = prop_create(map, prop))) { mem_release(path); prop_release(prop); map_release(map); return -1; } prop = prop_next; } mem_release(path); g.prop = prop; g.init = 1; return 0; } /* =item C Returns the value of the property named C. Returns C if there is no such property. On error, returns C with C set appropriately. =cut */ const char *prop_get(const char *name) { Prop *p; const char *value = NULL; int err; if ((err = locker_wrlock(g.locker))) return set_errnull(err); if (!g.init && prop_init() == -1) { locker_unlock(g.locker); return NULL; } for (p = g.prop; p; p = p->defaults) if ((value = map_get(p->map, name))) break; if ((err = locker_unlock(g.locker))) return set_errnull(err); return value; } /* =item C Returns the value of the property named C. Returns C on error, or if there is no such property. =cut */ const char *prop_get_or(const char *name, const char *default_value) { const char *prop = prop_get(name); return prop ? prop : default_value; } /* =item C Sets the property named C to a dynamically allocated copy of C. If I is called after a call to this function, the new property will be saved to disk, and will be available the next time this program is executed. On success, returns the copy of C. On error, returns C with C set appropriately. =cut */ const char *prop_set(const char *name, const char *value) { char *val; int err; if ((err = locker_wrlock(g.locker))) return set_errnull(err); if (!g.init && prop_init() == -1) { locker_unlock(g.locker); return NULL; } if (!(val = mem_strdup(value))) { locker_unlock(g.locker); return NULL; } if (map_put(g.prop->map, name, val) == -1) { mem_release(val); locker_unlock(g.locker); return NULL; } g.dirty = 1; if ((err = locker_unlock(g.locker))) return set_errnull(err); return val; } /* =item C Returns the value of the property named C as an integer. Returns C<0> on error, or if there is no such property, or if it is not interpretable as a decimal integer. =cut */ int prop_get_int(const char *name) { return prop_get_int_or(name, 0); } /* =item C Returns the value of the property named C as an integer. Returns C on error, or if there is no such property, or if it is not interpretable as a decimal integer. =cut */ int prop_get_int_or(const char *name, int default_value) { const char *prop = prop_get(name); int val; return (prop && sscanf(prop, " %d ", &val)) ? val : default_value; } /* =item C Sets the property named C to C. If I is called after a call to this function, the new property will be saved to disk and will be available the next time this program is executed. On success, returns C. On error, returns C<0>. =cut */ int prop_set_int(const char *name, int value) { char buf[128]; snprintf(buf, sizeof(buf), "%d", value); return prop_set(name, buf) ? value : 0; } /* =item C Returns the value of the property named C as a double. Returns C<0.0> on error, or if there is no such property, or if it is not interpretable as a floating point number. =cut */ double prop_get_double(const char *name) { return prop_get_double_or(name, 0.0); } /* =item C Returns the value of the property named C as a double. Returns C on error, or if there is no such property, or if it is not interpretable as a floating point number. =cut */ double prop_get_double_or(const char *name, double default_value) { const char *prop = prop_get(name); double val; return (prop && sscanf(prop, "%lg", &val)) ? val : default_value; } /* =item C Sets the property named C to C. If I is called after a call to this function, the new property will be saved to disk and will be available the next time this program is executed. On success, returns C. On error, returns C<0.0>. =cut */ double prop_set_double(const char *name, double value) { char buf[128]; snprintf(buf, 128, "%g", value); return prop_set(name, buf) ? value : -1; } /* =item C Returns the value of the property named C as a boolean value. Returns C<0> on error or if there is no such property. The values: C<"true">, C<"yes">, C<"on"> and C<"1"> are all interpreted as C. All other values are interpreted as C. =cut */ int prop_get_bool(const char *name) { return prop_get_bool_or(name, 0); } /* =item C Returns the value of the property named C as a boolean value. Returns C on error, or if there is no such property, or if it is not interpretable as a boolean value. The values: C<"true">, C<"yes">, C<"on"> and C<"1"> are all interpreted as C. The values: C<"false">, C<"no">, C<"off"> and C<"0"> are all interpreted as C. All other values are disregarded and will cause C to be returned. =cut */ int prop_get_bool_or(const char *name, int default_value) { const char *prop = prop_get(name); char buf[128]; int val; if (!prop) return default_value; if (sscanf(prop, " %d ", &val)) return val; if (sscanf(prop, " %127s ", buf)) { if ((buf[0] == 't' || buf[0] == 'T') && (buf[1] == 'r' || buf[1] == 'R') && (buf[2] == 'u' || buf[2] == 'U') && (buf[3] == 'e' || buf[3] == 'E') && (buf[4] == nul)) return 1; if ((buf[0] == 'f' || buf[0] == 'F') && (buf[1] == 'a' || buf[1] == 'A') && (buf[2] == 'l' || buf[2] == 'L') && (buf[3] == 's' || buf[3] == 'S') && (buf[4] == 'e' || buf[4] == 'E') && (buf[5] == nul)) return 0; if ((buf[0] == 'y' || buf[0] == 'Y') && (buf[1] == 'e' || buf[1] == 'E') && (buf[2] == 's' || buf[2] == 'S') && (buf[3] == nul)) return 1; if ((buf[0] == 'n' || buf[0] == 'N') && (buf[1] == 'o' || buf[1] == 'O') && (buf[2] == nul)) return 0; if ((buf[0] == 'o' || buf[0] == 'O') && (buf[1] == 'n' || buf[1] == 'N') && (buf[2] == nul)) return 1; if ((buf[0] == 'o' || buf[0] == 'O') && (buf[1] == 'f' || buf[1] == 'F') && (buf[2] == 'f' || buf[2] == 'F') && (buf[3] == nul)) return 0; } return default_value; } /* =item C Sets the property named C to C. If I is called after a call to this function, the new property will be saved to disk and will be available the next time this program is executed. On success, returns C. On error, returns C<0>. =cut */ int prop_set_bool(const char *name, int value) { return prop_set_int(name, value); } /* =item C Removes the property named C. Property removal is only saved to disk when I is called, if the property existed only in the user-defined, program-specific properties file, or was created by the program at runtime. On success, returns C<0>. On error, returns C<-1> with C set appropriately. =cut */ int prop_unset(const char *name) { Prop *p; int err; if ((err = locker_wrlock(g.locker))) return set_errno(err); if (!g.init && prop_init() == -1) { locker_unlock(g.locker); return -1; } for (p = g.prop; p; p = p->defaults) map_remove(p->map, name); g.dirty = 1; if ((err = locker_unlock(g.locker))) return set_errno(err); return 0; } /* =item C Saves the program's properties to disk. If the property named C<"save"> is set to C<"false">, C<"no">, C<"off"> or C<"0">, nothing is written to disk. If no properties were added, removed or changed, nothing is written to disk. Only the user-defined, program-specific properties are saved. Generic and system-wide properties files must be edited by hand. Each program can only save its own properties. They are saved in the following file: ~/.properties/app.progname Where C is the name of the program after being translated as described at the top of the I section. The C<~/.properties> directory is created if necessary. On success, returns C<0>. On error, returns C<-1> with C set appropriately. =cut */ int prop_save(void) { char *path; size_t path_len; size_t len; char *home; char *progname, *sep; struct stat status[1]; List *keys; Lister *k; FILE *file; int err; if (!prop_get_bool_or("save", 1)) return 0; if ((err = locker_wrlock(g.locker))) return set_errno(err); if (!g.dirty) { if ((err = locker_unlock(g.locker))) return set_errno(err); return 0; } if (!prog_name()) { locker_unlock(g.locker); return set_errno(EINVAL); } if (!(home = user_home())) { locker_unlock(g.locker); return set_errno(EINVAL); } path_len = limit_path(); if (!(path = mem_create(path_len, char))) { locker_unlock(g.locker); return -1; } snprintf(path, path_len, "%s%c.properties", home, PATH_SEP); if (stat(path, status) == -1 && mkdir(path, S_IRWXU) == -1) { mem_release(path); locker_unlock(g.locker); return -1; } if (stat(path, status) == -1 || S_ISDIR(status->st_mode) == 0) { mem_release(path); locker_unlock(g.locker); return set_errno(EINVAL); } if (!(progname = mem_strdup(prog_name()))) { mem_release(path); locker_unlock(g.locker); return -1; } for (sep = strchr(progname, PATH_SEP); sep; sep = strchr(sep, PATH_SEP)) *sep++ = '-'; len = strlen(path); snprintf(path + len, path_len - len, "%capp.%s", PATH_SEP, progname); mem_release(progname); file = fopen(path, "w"); mem_release(path); if (!file) { locker_unlock(g.locker); return -1; } if (!(keys = map_keys(g.prop->map))) { locker_unlock(g.locker); return -1; } if (!list_sort(keys, (list_cmp_t *)key_cmp)) { list_release(keys); locker_unlock(g.locker); return -1; } if (!(k = lister_create(keys))) { list_release(keys); locker_unlock(g.locker); return -1; } while (lister_has_next(k) == 1) { const char *key = (const char *)lister_next(k); const char *value = map_get(g.prop->map, key); String *lhs, *rhs, *lhs2; /* Quote any '=' in the key */ if (!(lhs = quote_equals(key))) { fclose(file); lister_release(k); list_release(keys); locker_unlock(g.locker); return -1; } /* Quote any special chars in the key */ if (!(lhs2 = quote_special(cstr(lhs)))) { fclose(file); lister_release(k); list_release(keys); str_release(lhs); locker_unlock(g.locker); return -1; } str_release(lhs); lhs = lhs2; /* Quote any trailing space in the key */ if (str_length(lhs) && is_space(cstr(lhs)[str_length(lhs) - 1])) { if (!str_insert(lhs, str_length(lhs) - 1, "\\")) { fclose(file); lister_release(k); list_release(keys); str_release(lhs); locker_unlock(g.locker); return -1; } } /* Quote any special chars in the value */ if (!(rhs = quote_special(value))) { fclose(file); lister_release(k); list_release(keys); str_release(lhs); locker_unlock(g.locker); return -1; } /* Quote any leading space in the value */ if (is_space(cstr(rhs)[0])) { if (!str_insert(rhs, 0, "\\")) { fclose(file); lister_release(k); list_release(keys); str_release(lhs); locker_unlock(g.locker); return -1; } } fprintf(file, "%s=%s\n", cstr(lhs), cstr(rhs)); str_release(lhs); str_release(rhs); } fclose(file); lister_release(k); list_release(keys); g.dirty = 0; if ((err = locker_unlock(g.locker))) return set_errno(err); return 0; } /* =item C Clears the properties as though they were never initialised. On success, returns C<0>. On error, returns C<-1> with C set appropriately. =cut */ int prop_clear(void) { int err; if ((err = locker_wrlock(g.locker))) return set_errno(err); prop_release(g.prop); g.prop = NULL; g.init = 0; g.dirty = 0; if ((err = locker_unlock(g.locker))) return set_errno(err); return 0; } /* =item C Sets the locking strategy for the I module to C. This is only needed in multi-threaded programs. It must only be called once, from the main thread. On success, returns C<0>. On error, returns C<-1> with C set appropriately. =cut */ int prop_locker(Locker *locker) { if (g.locker) return set_errno(EINVAL); g.locker = locker; return 0; } /* =back =head1 ERRORS On error, C is set either by an underlying function, or as follows: =over 4 =item C When there is no prog_name or home directory, or when there is a parse error in a properties file, or if C<~/.properties> exists but is not a directory. I sets this when an attempt is made to change the locker after one has already been set. =back =head1 MT-Level I =head1 FILES /etc/properties/app ~/.properties/app /etc/properties/app.* ~/.properties/app.* =head1 EXAMPLE #include #include #include #include int main(int ac, char **av) { const char *s; int i; double d; int b; prog_init(); prog_set_name(*av); s = prop_get("string"); i = prop_get_int_or("int", 1); d = prop_get_double_or("double", 1.0); b = prop_get_bool("boolean"); msg("s = '%s'\ni = %d\nd = %g\nb = %s\n", s, i, d, (b) ? "true" : "false"); prop_set("string", "strung"); prop_set_int("int", i += 4); prop_set_double("double", d *= 1.75); prop_set_bool("boolean", !b); prop_save(); return EXIT_SUCCESS; } =head1 BUGS This only provides coarse-grained persistence. If multiple instances of the same program are setting properties, the last to exit wins. This can be overcome by calling I after setting any property and I before getting any property. =head1 SEE ALSO I, I =head1 AUTHOR 20230824 raf =cut */ #endif #ifdef TEST static const char *user_home() { struct passwd *pwent; char *home = NULL; if ((pwent = getpwuid(getuid()))) home = pwent->pw_dir; return (home && strlen(home)) ? home : NULL; } static int props_exist(void) { struct stat status[1]; const char *home; char *path; size_t path_len; if (!(home = user_home())) return 0; path_len = limit_path(); if (!(path = mem_create(path_len, char))) return -1; snprintf(path, path_len, "%s%c.properties", home, PATH_SEP); if (stat(path, status) == 0 && S_ISDIR(status->st_mode) == 1) { mem_release(path); return 1; } mem_release(path); return 0; } static void clean(int has_props) { const char *home; char *path; char *progname; char *sep; size_t path_len; size_t len; if (!(home = user_home())) return; path_len = limit_path(); if (!(path = mem_create(path_len, char))) return; if (!(progname = mem_strdup(prog_name()))) { mem_release(path); return; } for (sep = strchr(progname, PATH_SEP); sep; sep = strchr(sep, PATH_SEP)) *sep++ = '-'; snprintf(path, path_len, "%s%c.properties", home, PATH_SEP); len = strlen(path); snprintf(path + len, path_len - len, "%capp.%s", PATH_SEP, progname); unlink(path); path[len] = nul; if (!has_props) rmdir(path); mem_release(path); mem_release(progname); } int main(int ac, char **av) { struct { const char *key; const char *value; } data[] = { { "key", "value" }, { " key with spaces ", "value with spaces" }, { "key with = sign", " value with leading space" }, { "key with newline\n and = two = signs", "value with newline\n!" }, { "key with newline,\n = two = signs and an Escape\033!", "value with newline\n and two non printables\001!\002!" }, { "", "" }, { NULL, NULL } }; const char * const key = "key"; const char * const value = "value"; const char * const not_key = "not key"; int has_props = props_exist(); const char *val; int ival, i; int int_val; const char *sval; double double_val; int bool_val; int errors = 0; if (ac == 2 && !strcmp(av[1], "help")) { printf("usage: %s\n", *av); return EXIT_SUCCESS; } printf("Testing: %s\n", "prop"); prog_init(); val = prop_set(key, value); if (strcmp(val, value)) ++errors, printf("Test1: prop_set(key, value) failed (%s not %s)\n", val, value); val = prop_get(key); if (!val || strcmp(val, value)) ++errors, printf("Test2: prop_get(key) failed (%s not %s)\n", val ? val : "null", value); val = prop_get_or(key, NULL); if (strcmp(val, value)) ++errors, printf("Test3: prop_get_or(key, NULL) failed (%s not %s)\n", val, value); val = prop_get_or(not_key, value); if (strcmp(val, value)) ++errors, printf("Test4: prop_get_or(not_key, value) failed (%s not %s)\n", val, value); ival = prop_unset(key); if (ival != 0) ++errors, printf("Test5: prop_unset() failed (%d not %d)\n", ival, 0); val = prop_get(key); if (val != NULL) ++errors, printf("Test6: prop_get(key) (after unset) failed (%s not NULL)\n", val); val = prop_get_or(key, value); if (strcmp(val, value)) ++errors, printf("Test7: prop_get_or(key, value) (after unset) failed (%s not %s)\n", val, value); ival = prop_save(); if (ival != -1) ++errors, printf("Test8: prop_save() (without progname) failed (%d not -1)\n", ival); prog_set_name("prop.test"); prop_clear(); val = prop_get(not_key); if (val != NULL) ++errors, printf("Test9: prop_get(not_key) failed (%s not NULL)\n", val); val = prop_get_or(not_key, value); if (val != value) ++errors, printf("Test10: prop_get_or(not_key, value) failed (%s not %s)\n", val, value); val = prop_set(key, value); if (strcmp(val, value)) ++errors, printf("Test11: prop_set(key, value) failed (%s not %s)\n", val, value); ival = prop_save(); if (ival != 0) ++errors, printf("Test12: prop_save() (with progname) failed (%d not 0, errno = %s)\n", ival, strerror(errno)); clean(has_props); prop_clear(); for (i = 0; data[i].key; ++i) { val = prop_set(data[i].key, data[i].value); if (strcmp(val, data[i].value)) ++errors, printf("Test%d: prop_set('%s', '%s') failed ('%s' not '%s')\n", 13 + 2 * i, data[i].key, data[i].value, val, data[i].value); val = prop_get(data[i].key); if (!val || strcmp(val, data[i].value)) ++errors, printf("Test%d: prop_get('%s') failed ('%s' not '%s')\n", 14 + 2 * i, data[i].key, val ? val : "null", data[i].value); } ival = prop_save(); if (ival != 0) ++errors, printf("Test25: prop_save() (with progname) failed (%d not 0, errno = %s)\n", ival, strerror(errno)); prop_clear(); for (i = 0; data[i].key; ++i) { val = prop_get(data[i].key); if (!val || strcmp(val, data[i].value)) ++errors, printf("Test%d: prop_get('%s') failed ('%s' not '%s')\n", 26 + i, data[i].key, val ? val : "null", data[i].value); } clean(has_props); prop_clear(); if ((int_val = prop_set_int("i", 37)) != 37) ++errors, printf("Test32: prop_set_int() failed (%d not 37)\n", int_val); if ((int_val = prop_get_int("i")) != 37) ++errors, printf("Test33: prop_get_int() failed (%d not 37)\n", int_val); if ((int_val = prop_get_int_or("i", 13)) != 37) ++errors, printf("Test34: prop_get_int_or() failed (%d not 37)\n", int_val); if ((int_val = prop_get_int_or("j", 13)) != 13) ++errors, printf("Test35: prop_get_int_or() failed (%d not 13)\n", int_val); if ((double_val = prop_set_double("d", 37.0)) != 37.0) ++errors, printf("Test36: prop_set_double() failed (%g not 37.0)\n", double_val); if ((double_val = prop_get_double("d")) != 37) ++errors, printf("Test37: prop_get_double() failed (%g not 37.0)\n", double_val); if ((double_val = prop_get_double_or("d", 13.0)) != 37) ++errors, printf("Test38: prop_get_double_or() failed (%g not 37.0)\n", double_val); if ((double_val = prop_get_double_or("e", 13.0)) != 13.0) ++errors, printf("Test39: prop_get_double_or() failed (%g not 13.0)\n", double_val); if ((bool_val = prop_set_bool("b", 1)) != 1) ++errors, printf("Test40: prop_set_bool() failed (%d not 1)\n", bool_val); if ((bool_val = prop_get_bool("b")) != 1) ++errors, printf("Test41: prop_get_bool() failed (%d not 1)\n", bool_val); if ((bool_val = prop_get_bool_or("b", 0)) != 1) ++errors, printf("Test42: prop_get_bool_or() failed (%d not 1)\n", bool_val); if ((bool_val = prop_get_bool_or("c", 1)) != 1) ++errors, printf("Test43: prop_get_bool_or() failed (%d not 1)\n", bool_val); sval = prop_set("b", "true"); if (!sval) ++errors, printf("Test44: prop_set() failed (returned null)\n"); else if (strcmp(sval, "true")) ++errors, printf("Test45: prop_set() failed (returned \"%s\" not \"%s\")\n", sval, "true"); if ((bool_val = prop_get_bool_or("b", 0)) != 1) ++errors, printf("Test46: prop_get_bool_or() failed (%d not 1)\n", bool_val); sval = prop_set("b", "false"); if (!sval) ++errors, printf("Test47: prop_set() failed (returned null)\n"); else if (strcmp(sval, "false")) ++errors, printf("Test48: prop_set() failed (returned \"%s\" not \"%s\")\n", sval, "true"); if ((bool_val = prop_get_bool_or("b", 1)) != 0) ++errors, printf("Test49: prop_get_bool_or() failed (%d not 0)\n", bool_val); sval = prop_set("b", "yes"); if (!sval) ++errors, printf("Test50: prop_set() failed (returned null)\n"); else if (strcmp(sval, "yes")) ++errors, printf("Test51: prop_set() failed (returned \"%s\" not \"%s\")\n", sval, "yes"); if ((bool_val = prop_get_bool_or("b", 0)) != 1) ++errors, printf("Test52: prop_get_bool_or() failed (%d not 1)\n", bool_val); sval = prop_set("b", "no"); if (!sval) ++errors, printf("Test53: prop_set() failed (returned null)\n"); else if (strcmp(sval, "no")) ++errors, printf("Test54: prop_set() failed (returned \"%s\" not \"%s\")\n", sval, "no"); if ((bool_val = prop_get_bool_or("b", 1)) != 0) ++errors, printf("Test55: prop_get_bool_or() failed (%d not 0)\n", bool_val); sval = prop_set("b", "on"); if (!sval) ++errors, printf("Test56: prop_set() failed (returned null)\n"); else if (strcmp(sval, "on")) ++errors, printf("Test57: prop_set() failed (returned \"%s\" not \"%s\")\n", sval, "on"); if ((bool_val = prop_get_bool_or("b", 0)) != 1) ++errors, printf("Test58: prop_get_bool_or() failed (%d not 1)\n", bool_val); sval = prop_set("b", "off"); if (!sval) ++errors, printf("Test59: prop_set() failed (returned null)\n"); else if (strcmp(sval, "off")) ++errors, printf("Test60: prop_set() failed (returned \"%s\" not \"%s\")\n", sval, "off"); if ((bool_val = prop_get_bool_or("b", 1)) != 0) ++errors, printf("Test61: prop_get_bool_or() failed (%d not 0)\n", bool_val); sval = prop_set("b", "neither"); if (!sval) ++errors, printf("Test62: prop_set() failed (returned null)\n"); else if (strcmp(sval, "neither")) ++errors, printf("Test63: prop_set() failed (returned \"%s\" not \"%s\")\n", sval, "neither"); if ((bool_val = prop_get_bool_or("b", 1)) != 1) ++errors, printf("Test64: prop_get_bool_or() failed (%d not 1)\n", bool_val); if ((bool_val = prop_get_bool_or("b", 0)) != 0) ++errors, printf("Test65: prop_get_bool_or() failed (%d not 0)\n", bool_val); if (errors) printf("%d/65 tests failed\n", errors); else printf("All tests passed\n"); return (errors == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } #endif /* vi:set ts=4 sw=4: */