Sat, 20 Oct 2018 11:03:38 +0200
fixes wrong integer type
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2018 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <libidav/utils.h> #include <ucx/map.h> #include <ucx/utils.h> #include "scfg.h" #include "config.h" #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) #define print_error(lineno, ...) \ do {\ fprintf(stderr, "Error (sync.xml line %u): ", lineno); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "Abort.\n"); \ } while(0); #define print_warning(lineno, ...) \ do {\ fprintf(stderr, "Warning (sync.xml line %u): ", lineno); \ fprintf(stderr, __VA_ARGS__); \ } while(0); #ifdef _WIN32 #define ENV_HOME getenv("USERPROFILE") #else #define ENV_HOME getenv("HOME") #endif /* _WIN32 */ static UcxMap *directories; UcxMapIterator scfg_directory_iterator() { return ucx_map_iterator(directories); } static int create_default_sync_config(char *file) { FILE *out = fopen(file, "w"); if(!out) { perror("Cannot create config file"); return -1; } fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", out); fputs("<configuration>\n", out); fputs("</configuration>\n", out); fclose(out); return 0; } static UcxList* add_regex_pattern(UcxList *list, char *value, unsigned short xmlline) { regex_t *regex = malloc(sizeof(regex_t)); if (regcomp(regex, value, REG_EXTENDED|REG_NOSUB)) { print_warning(xmlline, "Invalid regular expression (%s) ... skipped\n", value); free(regex); return list; } else { return ucx_list_append(list, regex); } } static int scfg_load_filter( xmlNode *node, UcxList **include, UcxList **exclude, UcxList **tags) { node = node->children; while(node) { if(node->type == XML_ELEMENT_NODE) { char *value = util_xml_get_text(node); if(xstreq(node->name, "include")) { if(value) { *include = add_regex_pattern(*include, value, node->line); } } else if(xstreq(node->name, "exclude")) { if(value) { *exclude = add_regex_pattern(*exclude, value, node->line); } } else if(xstreq(node->name, "tags")) { if(value) { SyncTagFilter *tagfilter = parse_tagfilter_string( value, DAV_SYNC_TAGFILTER_SCOPE_RESOURCE); if(!tagfilter) { print_error( node->line, "malformed tag filter: %s\n", value); return 1; } else { // get scope xmlChar *scope = xmlGetNoNsProp(node, BAD_CAST "scope"); if(scope) { if(xstreq(scope, "resource")) { tagfilter->scope = DAV_SYNC_TAGFILTER_SCOPE_RESOURCE; } else if(xstreq(scope, "collection")) { tagfilter->scope = DAV_SYNC_TAGFILTER_SCOPE_COLLECTION; } else if(xstreq(scope, "all")) { tagfilter->scope = DAV_SYNC_TAGFILTER_SCOPE_RESOURCE | DAV_SYNC_TAGFILTER_SCOPE_COLLECTION; } else { tagfilter->scope = DAV_SYNC_TAGFILTER_SCOPE_RESOURCE; } } xmlFree(scope); *tags = ucx_list_append(*tags, tagfilter); } } } else { print_error(node->line, "unknown filter config element: %s\n", node->name); return 1; } if(!value) { print_error(node->line, "missing value for filter: %s\n", node->name); return 1; } } node = node->next; } return 0; } static TagFormat str2tagformat(const char *str) { if(!strcmp(str, "text")) { return TAG_FORMAT_TEXT; } else if(!strcmp(str, "csv")) { return TAG_FORMAT_CSV; } else if(!strcmp(str, "xml")) { return TAG_FORMAT_XML; } else if(!strcmp(str, "macos")) { return TAG_FORMAT_MACOS; } return TAG_FORMAT_UNKNOWN; } static TagConfig* parse_tagconfig(xmlNode *node) { TagConfig conf; conf.store = TAG_STORE_XATTR; conf.local_format = TAG_FORMAT_TEXT; conf.server_format = TAG_FORMAT_XML; conf.xattr_name = NULL; conf.detect_changes = false; conf.conflict = TAG_NO_CONFLICT; xmlNode *c = node->children; // TODO: error handling while(c) { if(c->type == XML_ELEMENT_NODE) { char *value = util_xml_get_text(c); if(xstreq(c->name, "local-store")) { if(!value) { return NULL; } else if(xstreq(value, "xattr")) { conf.store = TAG_STORE_XATTR; } else { return NULL; } xmlAttr *attr = c->properties; xmlChar *format = xmlGetNoNsProp(node, BAD_CAST "format"); if(format) { conf.local_format = str2tagformat((char*)format); xmlFree(format); } } else if(xstreq(c->name, "detect-changes")) { if(!value) { return NULL; } conf.detect_changes = util_getboolean(value); } else if(xstreq(c->name, "xattr-name")) { if(!value) { return NULL; } conf.xattr_name = strdup(value); } else if(xstreq(c->name, "on-conflict")) { if(!value) { return NULL; } if(xstreq(value, "no_conflict")) { conf.conflict = TAG_NO_CONFLICT; } else if(xstreq(value, "keep_local")) { conf.conflict = TAG_KEEP_LOCAL; } else if(xstreq(value, "keep_remote")) { conf.conflict = TAG_KEEP_REMOTE; } else if(xstreq(value, "merge")) { conf.conflict = TAG_MERGE; } else { fprintf(stderr, "on-conflict: unknown value: %s\n", value); return NULL; } } } c = c->next; } if(conf.store == TAG_STORE_XATTR && !conf.xattr_name) { switch(conf.local_format) { default: case TAG_FORMAT_TEXT: case TAG_FORMAT_CSV: case TAG_FORMAT_XML: conf.xattr_name = strdup(DEFAULT_TAG_XATTR); break; case TAG_FORMAT_MACOS: conf.xattr_name = strdup(MACOS_TAG_XATTR); break; } } TagConfig *tagconfig = malloc(sizeof(TagConfig)); *tagconfig = conf; return tagconfig; } static int scfg_load_directory(xmlNode *node) { char *name = NULL; char *path = NULL; char *trash = NULL; char *collection = NULL; char *repository = NULL; char *database = NULL; TagConfig *tagconfig = NULL; UcxList *include = NULL; UcxList *exclude = NULL; UcxList *tagfilter = NULL; int max_retry = 0; int allow_cmd = SYNC_CMD_PULL | SYNC_CMD_PUSH | SYNC_CMD_ARCHIVE; bool backuppull = false; bool lockpull = false; bool lockpush = false; time_t lock_timeout = 0; unsigned short parentlineno = node->line; node = node->children; while(node) { if(node->type == XML_ELEMENT_NODE) { char *value = util_xml_get_text(node); /* every key needs a value */ if(!value) { /* TODO: maybe this should only be reported, if the key is valid * But this makes the code very ugly. */ print_error(node->line, "missing value for directory element: %s\n", node->name); return 1; } if(xstreq(node->name, "name")) { name = value; } else if(xstreq(node->name, "path")) { path = value; } else if(xstreq(node->name, "trash")) { trash = value; } else if(xstreq(node->name, "collection")) { collection = value; } else if(xstreq(node->name, "repository")) { repository = value; } else if(xstreq(node->name, "filter")) { if(scfg_load_filter(node, &include, &exclude, &tagfilter)) { return 1; } } else if(xstreq(node->name, "database")) { database = value; } else if(xstreq(node->name, "tagconfig")) { tagconfig = parse_tagconfig(node); } else if(xstreq(node->name, "max-retry")) { int64_t i; if(util_strtoint(value, &i) && i >= 0) { max_retry = (int)i; } else { print_warning(node->line, "unsigned integer value " "expected in <max-retry> element\n"); } } else if(xstreq(node->name, "allow-cmd")) { int cmds = 0; const char *delims = " ,\r\n"; char *cmdstr = strdup(value); char *cmd = strtok(cmdstr, delims); while(cmd) { if(!strcmp(cmd, "pull")) { cmds |= SYNC_CMD_PULL; } else if(!strcmp(cmd, "push")) { cmds |= SYNC_CMD_PUSH; } else if(!strcmp(cmd, "archive")) { cmds |= SYNC_CMD_ARCHIVE; } cmd = strtok(NULL, delims); } free(cmdstr); allow_cmd = cmds; } else if(xstreq(node->name, "backup-on-pull")) { backuppull = util_getboolean(value); } else if(xstreq(node->name, "lock-pull")) { lockpull = util_getboolean(value); } else if(xstreq(node->name, "lock-push")) { lockpush = util_getboolean(value); } else if(xstreq(node->name, "lock-timeout")) { int64_t t = 0; if(util_strtoint(value, &t)) { lock_timeout = (time_t)t; } else { print_warning(node->line, "integer value " "expected in <lock-timeout> element\n"); } } else { print_error(node->line, "unknown directory config element: %s\n", node->name); return 1; } } node = node->next; } if(!name) { print_error(parentlineno, "missing name element for directory\n"); return 1; } if(!path) { print_error(parentlineno, "missing path element for directory %s\n", name); return 1; } if(!repository) { print_error(parentlineno, "missing repository element for directory %s\n", name); return 1; } if(!database) { print_error(parentlineno, "missing database element for directory %s\n", name); return 1; } SyncDirectory *dir = malloc(sizeof(SyncDirectory)); dir->name = strdup(name); dir->path = scfg_create_path(path); dir->collection = collection ? strdup(collection) : NULL; dir->repository = strdup(repository); dir->database = strdup(database); dir->tagconfig = tagconfig; dir->max_retry = max_retry; dir->allow_cmd = allow_cmd; dir->backuppull = backuppull; dir->lockpull = lockpull; dir->lockpush = lockpush; dir->lock_timeout = lock_timeout; if (include) { dir->include = include; } else { regex_t *matchall = malloc(sizeof(regex_t)); regcomp(matchall, ".*", REG_NOSUB); dir->include = ucx_list_append(NULL, matchall); } if (exclude) { dir->exclude = exclude; } else { regex_t *matchnothing = malloc(sizeof(regex_t)); regcomp(matchnothing, "///", REG_NOSUB); dir->exclude = ucx_list_append(NULL, matchnothing); } dir->tagfilter = tagfilter; if (trash && sstrtrim(sstr(trash)).length > 0) { if (trash[0] == '/' || trash[0] == '$') { dir->trash = scfg_create_path(trash); } else { char *t = util_concat_path(dir->path, trash); dir->trash = util_concat_path(t, "/"); free(t); } if(dir->trash[strlen(dir->trash)-1] != '/') { char *t = dir->trash; dir->trash = util_concat_path(t, "/"); free(t); } } else { dir->trash = NULL; } ucx_map_cstr_put(directories, name, dir); return 0; } int load_sync_config() { directories = ucx_map_new(8); if(check_config_dir()) { fprintf(stderr, "Cannot create .dav directory\n"); return 1; } char *file = util_concat_path(ENV_HOME, ".dav/sync.xml"); struct stat s; if(stat(file, &s)) { switch(errno) { case ENOENT: { if(create_default_sync_config(file)) { return 1; } break; } default: { perror("Cannot load sync.xml"); } } free(file); return 0; } xmlDoc *doc = xmlReadFile(file, NULL, 0); if(!doc) { fprintf(stderr, "Cannot load sync.xml\n"); free(file); return -1; } int ret = 0; xmlNode *node = xmlDocGetRootElement(doc)->children; while(node && !ret) { if(node->type == XML_ELEMENT_NODE) { if(xstreq(node->name, "directory")) { ret = scfg_load_directory(node); } else { print_error(node->line, "unknown config element: %s\n", node->name); ret = 1; } } node = node->next; } xmlFreeDoc(doc); free(file); return ret; } SyncDirectory* scfg_get_dir(char *name) { return ucx_map_cstr_get(directories, name); } int scfg_check_dir(SyncDirectory *dir) { struct stat s; if(stat(dir->path, &s)) { int err = errno; if(err == ENOENT) { fprintf(stderr, "directory %s does not exist.\n", dir->path); } else { fprintf(stderr, "Cannot stat directory %s.\n", dir->path); perror(NULL); } fprintf(stderr, "Abort.\n"); return -1; } if(dir->trash) { // create trash directory mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; if (util_mkdir(dir->trash, mode)) { if (errno != EEXIST) { fprintf(stderr, "Cannot create trash directory: %s\nAbort.\n", dir->trash); return -1; } } } return 0; } char* scfg_create_path(char *cfg) { if(!cfg) { return NULL; } if(cfg[0] != '$') { return strdup(cfg); } sstr_t s = sstr(cfg); sstr_t path = sstrchr(sstr(cfg), '/'); char *localpath = NULL; if(path.length > 0) { // path = $var/path/ sstr_t var = sstrsubsl(s, 1, path.ptr - s.ptr - 1); if(var.length > 0) { char *env = sstrdup(var).ptr; char *envval = getenv(env); free(env); if(envval) { localpath = util_concat_path(envval, path.ptr); } else { fprintf( stderr, "Environment variable %.*s not set.\nAbort.\n", (int)var.length, var.ptr); exit(-1); } } else { localpath = sstrdup(path).ptr; } } else { // path = $var char *envval = getenv(cfg + 1); if(envval) { localpath = strdup(envval); } else { fprintf( stderr, "Environment variable %s not set.\nAbort.\n", cfg); exit(-1); } } return localpath; } int add_directory(SyncDirectory *dir) { char *file = util_concat_path(ENV_HOME, ".dav/sync.xml"); xmlDoc *doc = xmlReadFile(file, NULL, 0); if(!doc) { free(file); fprintf(stderr, "Cannot load config.xml\n"); return 1; } xmlNode *root = xmlDocGetRootElement(doc); xmlNode *dirNode = xmlNewNode(NULL, BAD_CAST "directory"); xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); xmlNewTextChild(dirNode, NULL, BAD_CAST "name", BAD_CAST dir->name); xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); xmlNewTextChild(dirNode, NULL, BAD_CAST "path", BAD_CAST dir->path); xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); xmlNewTextChild(dirNode, NULL, BAD_CAST "repository", BAD_CAST dir->repository); xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); xmlNewTextChild(dirNode, NULL, BAD_CAST "collection", BAD_CAST dir->collection); xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); if(dir->trash) { xmlNewTextChild(dirNode, NULL, BAD_CAST "trash", BAD_CAST dir->trash); xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); } xmlNewTextChild(dirNode, NULL, BAD_CAST "database", BAD_CAST dir->database); xmlNodeAddContent(dirNode, BAD_CAST "\n\t"); xmlNodeAddContent(root, BAD_CAST "\n\t"); xmlAddChild(root, dirNode); xmlNodeAddContent(root, BAD_CAST "\n"); int ret = 0; if(xmlSaveFormatFileEnc(file, doc, "UTF-8", 1) == -1) { ret = 1; } xmlFreeDoc(doc); free(file); return ret; } char* generate_db_name(char *basename) { char *dbname = NULL; int count = -1; while(!dbname) { sstr_t name = count < 0 ? ucx_sprintf("%s-db.xml", basename) : ucx_sprintf("%s%d-db.xml", basename, count); count++; UcxMapIterator i = ucx_map_iterator(directories); SyncDirectory *dir; bool unique = true; UCX_MAP_FOREACH(key, dir, i) { if(!sstrcmp(name, sstr(dir->database))) { unique = false; break; } } if(unique) { dbname = name.ptr; break; } } return dbname; } void free_sync_config() { if(directories) { UcxMapIterator i = ucx_map_iterator(directories); SyncDirectory *dir; UCX_MAP_FOREACH(elm, dir, i) { free(dir->name); free(dir->path); free(dir->repository); free(dir->database); if(dir->collection) { free(dir->collection); } if(dir->trash) { free(dir->trash); } UCX_FOREACH(elm, dir->include) { regfree(elm->data); free(elm->data); } ucx_list_free(dir->include); UCX_FOREACH(elm, dir->exclude) { regfree(elm->data); free(elm->data); } ucx_list_free(dir->exclude); free(dir); } ucx_map_free(directories); } }