UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2019 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <errno.h> 33 #include <libidav/utils.h> 34 #include <cx/hash_map.h> 35 #include <cx/utils.h> 36 #include <cx/linked_list.h> 37 #include <cx/printf.h> 38 39 #include "scfg.h" 40 #include "config.h" 41 42 #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) 43 44 45 #define print_error(lineno, ...) \ 46 do {\ 47 fprintf(stderr, "Error (sync.xml line %u): ", lineno); \ 48 fprintf(stderr, __VA_ARGS__); \ 49 fprintf(stderr, "Abort.\n"); \ 50 } while(0); 51 #define print_warning(lineno, ...) \ 52 do {\ 53 fprintf(stderr, "Warning (sync.xml line %u): ", lineno); \ 54 fprintf(stderr, __VA_ARGS__); \ 55 } while(0); 56 57 #ifdef _WIN32 58 #define ENV_HOME getenv("USERPROFILE") 59 #else 60 #define ENV_HOME getenv("HOME") 61 #endif /* _WIN32 */ 62 63 static CxMap *directories; 64 65 CxIterator scfg_directory_iterator() { 66 return cxMapIteratorValues(directories); 67 } 68 69 static int create_default_sync_config(char *file) { 70 FILE *out = fopen(file, "w"); 71 if(!out) { 72 perror("Cannot create config file"); 73 return -1; 74 } 75 76 fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", out); 77 fputs("<configuration>\n", out); 78 fputs("</configuration>\n", out); 79 fclose(out); 80 81 return 0; 82 } 83 84 static void add_regex_pattern(CxList *list, char *value, 85 unsigned short xmlline) { 86 regex_t regex; 87 if (regcomp(&regex, value, REG_EXTENDED|REG_NOSUB)) { 88 print_warning(xmlline, 89 "Invalid regular expression (%s) ... skipped\n", value); 90 } else { 91 cxListAdd(list, &regex); 92 } 93 } 94 95 static int scfg_load_filter( 96 xmlNode *node, 97 CxList *include, 98 CxList *exclude, 99 CxList *tags) 100 { 101 node = node->children; 102 103 while(node) { 104 if(node->type == XML_ELEMENT_NODE) { 105 char *value = util_xml_get_text(node); 106 if(xstreq(node->name, "include")) { 107 if(value) { 108 add_regex_pattern(include, value, node->line); 109 } 110 } else if(xstreq(node->name, "exclude")) { 111 if(value) { 112 add_regex_pattern(exclude, value, node->line); 113 } 114 } else if(xstreq(node->name, "tags")) { 115 if(value) { 116 SyncTagFilter *tagfilter = parse_tagfilter_string( 117 value, DAV_SYNC_TAGFILTER_SCOPE_RESOURCE); 118 if(!tagfilter) { 119 print_error( 120 node->line, 121 "malformed tag filter: %s\n", 122 value); 123 return 1; 124 } else { 125 // get scope 126 xmlChar *scope = xmlGetNoNsProp(node, BAD_CAST "scope"); 127 if(scope) { 128 if(xstreq(scope, "resource")) 129 { 130 tagfilter->scope = 131 DAV_SYNC_TAGFILTER_SCOPE_RESOURCE; 132 } else if(xstreq(scope, "collection")) { 133 tagfilter->scope = 134 DAV_SYNC_TAGFILTER_SCOPE_COLLECTION; 135 } else if(xstreq(scope, "all")) { 136 tagfilter->scope = 137 DAV_SYNC_TAGFILTER_SCOPE_RESOURCE 138 | DAV_SYNC_TAGFILTER_SCOPE_COLLECTION; 139 } else { 140 tagfilter->scope = 141 DAV_SYNC_TAGFILTER_SCOPE_RESOURCE; 142 } 143 } 144 xmlFree(scope); 145 146 cxListAdd(tags, tagfilter); 147 } 148 } 149 } else { 150 print_error(node->line, 151 "unknown filter config element: %s\n", node->name); 152 return 1; 153 } 154 if(!value) { 155 print_error(node->line, 156 "missing value for filter: %s\n", node->name); 157 return 1; 158 } 159 } 160 161 node = node->next; 162 } 163 164 return 0; 165 } 166 167 Filter* parse_filter(xmlNode *node) { 168 CxList *include = cxLinkedListCreate(cxDefaultAllocator, NULL, sizeof(regex_t)); 169 CxList *exclude = cxLinkedListCreate(cxDefaultAllocator, NULL, sizeof(regex_t)); 170 CxList *tags = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); 171 172 cxDefineDestructor(include, regfree); 173 cxDefineDestructor(exclude, regfree); 174 // TODO: set tags destructor 175 176 if(scfg_load_filter(node, include, exclude, tags)) { 177 return NULL; 178 } 179 180 Filter *filter = malloc(sizeof(Filter)); 181 filter->include = include; 182 filter->exclude = exclude; 183 filter->tags = tags; 184 return filter; 185 } 186 187 void init_default_filter(Filter *filter) { 188 if(cxListSize(filter->include) == 0) { 189 regex_t matchall; 190 regcomp(&matchall, ".*", REG_NOSUB); 191 cxListAdd(filter->include, &matchall); 192 } 193 /* 194 if(!filter->exclude) { 195 regex_t *matchnothing = malloc(sizeof(regex_t)); 196 regcomp(matchnothing, "///", REG_NOSUB); 197 filter->exclude = ucx_list_append(NULL, matchnothing); 198 } 199 */ 200 } 201 202 static TagFormat str2tagformat(const char *str) { 203 if(!strcmp(str, "text")) { 204 return TAG_FORMAT_TEXT; 205 } else if(!strcmp(str, "csv")) { 206 return TAG_FORMAT_CSV; 207 } else if(!strcmp(str, "xml")) { 208 return TAG_FORMAT_XML; 209 } else if(!strcmp(str, "macos")) { 210 return TAG_FORMAT_MACOS; 211 } 212 return TAG_FORMAT_UNKNOWN; 213 } 214 215 #define CHECK_VALUE_RET_NULL(element, value) if(!(value)) \ 216 {print_error(element->line, "missing value element: %s\n", element->name); return NULL;} 217 218 static TagConfig* parse_tagconfig(xmlNode *node) { 219 TagConfig conf; 220 conf.store = TAG_STORE_XATTR; 221 conf.local_format = TAG_FORMAT_TEXT; 222 conf.server_format = TAG_FORMAT_XML; 223 conf.xattr_name = NULL; 224 conf.detect_changes = false; 225 conf.conflict = TAG_NO_CONFLICT; 226 xmlNode *c = node->children; 227 228 // TODO: error handling 229 while(c) { 230 if(c->type == XML_ELEMENT_NODE) { 231 char *value = util_xml_get_text(c); 232 if(xstreq(c->name, "local-store")) { 233 CHECK_VALUE_RET_NULL(c, value); 234 if(xstreq(value, "xattr")) { 235 conf.store = TAG_STORE_XATTR; 236 } else { 237 return NULL; 238 } 239 240 xmlChar *format = xmlGetNoNsProp(node, BAD_CAST "format"); 241 if(format) { 242 conf.local_format = str2tagformat((char*)format); 243 xmlFree(format); 244 } 245 } else if(xstreq(c->name, "detect-changes")) { 246 CHECK_VALUE_RET_NULL(c, value); 247 conf.detect_changes = util_getboolean(value); 248 } else if(xstreq(c->name, "xattr-name")) { 249 if(!value) { 250 return NULL; 251 } 252 conf.xattr_name = strdup(value); 253 } else if(xstreq(c->name, "on-conflict")) { 254 CHECK_VALUE_RET_NULL(c, value); 255 if(xstreq(value, "no_conflict")) { 256 conf.conflict = TAG_NO_CONFLICT; 257 } else if(xstreq(value, "keep_local")) { 258 conf.conflict = TAG_KEEP_LOCAL; 259 } else if(xstreq(value, "keep_remote")) { 260 conf.conflict = TAG_KEEP_REMOTE; 261 } else if(xstreq(value, "merge")) { 262 conf.conflict = TAG_MERGE; 263 } else { 264 fprintf(stderr, "on-conflict: unknown value: %s\n", value); 265 return NULL; 266 } 267 } 268 } 269 c = c->next; 270 } 271 272 if(conf.store == TAG_STORE_XATTR && !conf.xattr_name) { 273 switch(conf.local_format) { 274 default: 275 case TAG_FORMAT_TEXT: 276 case TAG_FORMAT_CSV: 277 case TAG_FORMAT_XML: conf.xattr_name = strdup(DEFAULT_TAG_XATTR); break; 278 case TAG_FORMAT_MACOS: conf.xattr_name = strdup(MACOS_TAG_XATTR); break; 279 } 280 } 281 282 TagConfig *tagconfig = malloc(sizeof(TagConfig)); 283 *tagconfig = conf; 284 return tagconfig; 285 } 286 287 static SplitConfig* parse_split(xmlNode *node) { 288 Filter *filter = NULL; 289 char *minsize = NULL; 290 char *blocksize = NULL; 291 292 xmlNode *c = node->children; 293 while(c) { 294 if(xstreq(c->name, "filter")) { 295 filter = parse_filter(node); 296 if(filter->tags) { 297 fprintf(stderr, "splitconfig: tag filter not supported\n"); 298 free_filter(*filter); 299 free(filter); 300 return NULL; 301 } 302 } else if(xstreq(c->name, "minsize")) { 303 minsize = util_xml_get_text(c); 304 } else if(xstreq(c->name, "blocksize")) { 305 blocksize = util_xml_get_text(c); 306 } 307 c = c->next; 308 } 309 310 uint64_t sz = 0; 311 if(!blocksize) { 312 fprintf(stderr, "splitconfig: no blocksize specified\n"); 313 return NULL; 314 } 315 size_t bsz_len = strlen(blocksize); 316 if(bsz_len < 2) { 317 fprintf(stderr, "splitconfig: blocksize too small\n"); 318 return NULL; 319 } 320 321 if(!util_szstrtouint(blocksize, &sz)) { 322 fprintf(stderr, "splitconfig: blocksize is not a number\n"); 323 return NULL; 324 } 325 326 if(!filter && !minsize) { 327 fprintf(stderr, "splitconfig: filter or minsize must be specified\n"); 328 return NULL; 329 } 330 331 int64_t minsz = -1; 332 if(minsize) { 333 uint64_t m; 334 if(!util_szstrtouint(minsize, &m)) { 335 fprintf(stderr, "splitconfig: minsize is not a number\n"); 336 return NULL; 337 } 338 minsz = (int64_t)m; 339 } 340 341 SplitConfig *sc = calloc(1, sizeof(SplitConfig)); 342 if(filter) { 343 init_default_filter(filter); 344 } 345 sc->minsize = minsz; 346 sc->blocksize = (size_t)sz; 347 return sc; 348 } 349 350 static CxList* parse_splitconfig(xmlNode *node, int *error) { 351 CxList *splitconfig = cxLinkedListCreateSimple(CX_STORE_POINTERS); 352 int err = 0; 353 xmlNode *c = node->children; 354 while(c) { 355 if(c->type == XML_ELEMENT_NODE && xstreq(c->name, "split")) { 356 SplitConfig *sc = parse_split(c); 357 if(sc) { 358 cxListAdd(splitconfig, sc); 359 } else { 360 err = 1; 361 break; 362 } 363 } 364 c = c->next; 365 } 366 367 if(error) { 368 *error = err; 369 } 370 return splitconfig; 371 } 372 373 static Versioning* parse_versioning_config(xmlNode *node) { 374 Versioning v; 375 v.always = FALSE; 376 v.type = VERSIONING_SIMPLE; 377 v.collection = VERSIONING_DEFAULT_PATH; 378 379 int err = 0; 380 381 xmlChar *attr_type = xmlGetNoNsProp(node, BAD_CAST "type"); 382 xmlChar *attr_always = xmlGetNoNsProp(node, BAD_CAST "always"); 383 384 if(attr_type) { 385 if(xstreq(attr_type, "simple")) { 386 v.type = VERSIONING_SIMPLE; 387 } else if(xstreq(attr_type, "deltav")) { 388 v.type = VERSIONING_DELTAV; 389 } else { 390 print_error(node->line, 391 "type attribute: unknown value: %s\n", 392 attr_type); 393 err = 1; 394 } 395 xmlFree(attr_type); 396 } 397 if(attr_always) { 398 v.always = util_getboolean((const char*)attr_always); 399 xmlFree(attr_always); 400 } 401 402 if(err) { 403 return NULL; 404 } 405 406 xmlNode *c = node->children; 407 while(c) { 408 if(c->type == XML_ELEMENT_NODE) { 409 char *value = util_xml_get_text(c); 410 if(xstreq(c->name, "history")) { 411 CHECK_VALUE_RET_NULL(c, value); 412 v.collection = value; 413 } 414 } 415 c = c->next; 416 } 417 418 v.collection = strdup(v.collection); 419 420 Versioning *versioning = malloc(sizeof(Versioning)); 421 *versioning = v; 422 return versioning; 423 } 424 425 static int scfg_load_directory(xmlNode *node) { 426 char *name = NULL; 427 char *path = NULL; 428 char *trash = NULL; 429 char *collection = NULL; 430 char *repository = NULL; 431 char *database = NULL; 432 char *logfile = NULL; 433 TagConfig *tagconfig = NULL; 434 Versioning *versioning = NULL; 435 CxList *include = cxLinkedListCreateSimple(sizeof(regex_t)); 436 CxList *exclude = cxLinkedListCreateSimple(sizeof(regex_t)); 437 CxList *tagfilter = cxLinkedListCreateSimple(CX_STORE_POINTERS); 438 CxList *splitconfig = NULL; 439 int max_retry = 0; 440 int allow_cmd = SYNC_CMD_PULL | SYNC_CMD_PUSH 441 | SYNC_CMD_ARCHIVE | SYNC_CMD_RESTORE; 442 bool backuppull = false; 443 bool lockpull = false; 444 bool lockpush = false; 445 bool hashing = false; 446 bool store_hash = false; 447 bool pull_skip_hashing = false; 448 //bool detect_copy = false; 449 time_t lock_timeout = 0; 450 uint32_t metadata = 0; 451 uint32_t symlink = 0; 452 PushStrategy pushstrat = PUSH_STRATEGY_METADATA; 453 454 unsigned short parentlineno = node->line; 455 node = node->children; 456 while(node) { 457 if(node->type == XML_ELEMENT_NODE) { 458 char *value = util_xml_get_text(node); 459 /* every key needs a value */ 460 if(!value && !xstreq(node->name, "versioning")) { 461 // TODO: only report if value is required 462 print_error(node->line, 463 "missing value for directory element: %s\n", 464 node->name); 465 return 1; 466 } 467 if(xstreq(node->name, "name")) { 468 name = value; 469 } else if(xstreq(node->name, "path")) { 470 path = value; 471 } else if(xstreq(node->name, "trash")) { 472 trash = value; 473 } else if(xstreq(node->name, "collection")) { 474 collection = value; 475 } else if(xstreq(node->name, "repository")) { 476 repository = value; 477 } else if(xstreq(node->name, "filter")) { 478 if(scfg_load_filter(node, include, exclude, tagfilter)) { 479 return 1; 480 } 481 } else if(xstreq(node->name, "database")) { 482 database = value; 483 } else if(xstreq(node->name, "logfile")) { 484 logfile = value; 485 } else if(xstreq(node->name, "tagconfig")) { 486 tagconfig = parse_tagconfig(node); 487 } else if(xstreq(node->name, "splitconfig")) { 488 int err = 0; 489 splitconfig = parse_splitconfig(node, &err); 490 if(err) { 491 return 1; 492 } 493 } else if(xstreq(node->name, "metadata")) { 494 uint32_t md = 0; 495 496 const char *delims = " ,\t\r\n"; 497 char *metadatastr = strdup(value); 498 char *m = strtok(metadatastr, delims); 499 while(m) { 500 if(!strcmp(m, "mtime")) { 501 md |= FINFO_MTIME; 502 } else if(!strcmp(m, "mode")) { 503 md |= FINFO_MODE; 504 } else if(!strcmp(m, "owner")) { 505 md |= FINFO_OWNER; 506 } else if(!strcmp(m, "xattr")) { 507 md |= FINFO_XATTR; 508 } else if(!strcmp(m, "all")) { 509 md |= FINFO_MTIME | FINFO_MODE | FINFO_OWNER | FINFO_XATTR; 510 } 511 m = strtok(NULL, delims); 512 } 513 free(metadatastr); 514 515 metadata = md; 516 } else if(xstreq(node->name, "versioning")) { 517 versioning = parse_versioning_config(node); 518 } else if(xstreq(node->name, "max-retry")) { 519 int64_t i; 520 if(util_strtoint(value, &i) && i >= 0) { 521 max_retry = (int)i; 522 } else { 523 print_warning(node->line, "unsigned integer value " 524 "expected in <max-retry> element\n"); 525 } 526 } else if(xstreq(node->name, "allow-cmd")) { 527 int cmds = 0; 528 const char *delims = " ,\t\r\n"; 529 char *cmdstr = strdup(value); 530 char *cmd = strtok(cmdstr, delims); 531 while(cmd) { 532 if(!strcmp(cmd, "pull")) { 533 cmds |= SYNC_CMD_PULL; 534 } else if(!strcmp(cmd, "push")) { 535 cmds |= SYNC_CMD_PUSH; 536 } else if(!strcmp(cmd, "archive")) { 537 cmds |= SYNC_CMD_ARCHIVE; 538 } else if(!strcmp(cmd, "restore")) { 539 cmds |= SYNC_CMD_RESTORE; 540 } 541 cmd = strtok(NULL, delims); 542 } 543 free(cmdstr); 544 allow_cmd = cmds; 545 546 } else if(xstreq(node->name, "backup-on-pull")) { 547 backuppull = util_getboolean(value); 548 } else if(xstreq(node->name, "lock-pull")) { 549 lockpull = util_getboolean(value); 550 } else if(xstreq(node->name, "lock-push")) { 551 lockpush = util_getboolean(value); 552 } else if(xstreq(node->name, "lock-timeout")) { 553 int64_t t = 0; 554 if(util_strtoint(value, &t)) { 555 lock_timeout = (time_t)t; 556 } else { 557 print_warning(node->line, "integer value " 558 "expected in <lock-timeout> element\n"); 559 } 560 } else if(xstreq(node->name, "hashing")) { 561 hashing = util_getboolean(value); 562 store_hash = hashing; 563 } else if(xstreq(node->name, "push-strategy")) { 564 if(value) { 565 if(xstreq(value, "metadata")) { 566 pushstrat = PUSH_STRATEGY_METADATA; 567 } else if(xstreq(value, "hash")) { 568 pushstrat = PUSH_STRATEGY_HASH; 569 } 570 } 571 } else if(xstreq(node->name, "symlink-intern")) { 572 if(!value) { 573 print_error(node->line, "missing value"); 574 } else if(xstreq(value, "sync")) { 575 symlink |= SYNC_SYMLINK_SYNC; 576 } else if(xstreq(value, "follow")) { 577 // nothing, default 578 } else if(xstreq(value, "ignore")) { 579 symlink |= SYNC_SYMLINK_IGNORE_INTERN; 580 } else { 581 print_error(node->line, 582 "unknown value: %s\n", value); 583 } 584 } else if(xstreq(node->name, "symlink-extern")) { 585 if(!value) { 586 print_error(node->line, "missing value"); 587 } else if(xstreq(value, "follow")) { 588 // nothing, default 589 } else if(xstreq(value, "ignore")) { 590 symlink |= SYNC_SYMLINK_IGNORE_EXTERN; 591 } else { 592 print_error(node->line, 593 "unknown value: %s\n", value); 594 } 595 } else { 596 print_error(node->line, 597 "unknown directory config element: %s\n", node->name); 598 return 1; 599 } 600 } 601 node = node->next; 602 } 603 604 if(!name) { 605 print_error(parentlineno, "missing name element for directory\n"); 606 return 1; 607 } 608 if(!path) { 609 print_error(parentlineno, 610 "missing path element for directory %s\n", name); 611 return 1; 612 } 613 if(!repository) { 614 print_error(parentlineno, 615 "missing repository element for directory %s\n", name); 616 return 1; 617 } 618 if(!database) { 619 print_error(parentlineno, 620 "missing database element for directory %s\n", name); 621 return 1; 622 } 623 624 SyncDirectory *dir = calloc(1, sizeof(SyncDirectory)); 625 dir->name = strdup(name); 626 dir->path = scfg_create_path(path); 627 dir->collection = collection ? strdup(collection) : NULL; 628 dir->repository = strdup(repository); 629 dir->database = strdup(database); 630 dir->logfile = logfile ? strdup(logfile) : NULL; 631 dir->tagconfig = tagconfig; 632 dir->versioning = versioning; 633 dir->max_retry = max_retry; 634 dir->allow_cmd = allow_cmd; 635 dir->backuppull = backuppull; 636 dir->lockpull = lockpull; 637 dir->lockpush = lockpush; 638 dir->hashing = hashing; 639 dir->store_hash = store_hash; 640 dir->pull_skip_hashing = pull_skip_hashing; 641 dir->lock_timeout = lock_timeout; 642 dir->metadata = metadata; 643 dir->splitconfig = splitconfig; 644 dir->symlink = symlink; 645 dir->push_strategy = pushstrat; 646 if((metadata & FINFO_MODE) == FINFO_MODE) { 647 dir->db_settings = DB_STORE_MODE; 648 } 649 if((metadata & FINFO_OWNER) == FINFO_OWNER) { 650 dir->db_settings |= DB_STORE_OWNER; 651 } 652 653 dir->filter.include = include; 654 dir->filter.exclude = exclude; 655 dir->filter.tags = tagfilter; 656 init_default_filter(&dir->filter); 657 658 if (trash && cx_strtrim(cx_str(trash)).length > 0) { 659 if (trash[0] == '/' || trash[0] == '$') { 660 dir->trash = scfg_create_path(trash); 661 } else { 662 char *t = util_concat_path(dir->path, trash); 663 dir->trash = util_concat_path(t, "/"); 664 free(t); 665 } 666 667 if(dir->trash[strlen(dir->trash)-1] != '/') { 668 char *t = dir->trash; 669 dir->trash = util_concat_path(t, "/"); 670 free(t); 671 } 672 } else { 673 dir->trash = NULL; 674 } 675 676 cxMapPut(directories, cx_hash_key_str(name), dir); 677 678 return 0; 679 } 680 681 int load_sync_config() { 682 directories = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8); 683 684 if(check_config_dir()) { 685 fprintf(stderr, "Cannot create .dav directory\n"); 686 return 1; 687 } 688 689 char *file = util_concat_path(ENV_HOME, ".dav/sync.xml"); 690 691 struct stat s; 692 if(stat(file, &s)) { 693 switch(errno) { 694 case ENOENT: { 695 if(create_default_sync_config(file)) { 696 return 1; 697 } 698 break; 699 } 700 default: { 701 perror("Cannot load sync.xml"); 702 } 703 } 704 free(file); 705 return 0; 706 } 707 708 xmlDoc *doc = xmlReadFile(file, NULL, 0); 709 if(!doc) { 710 fprintf(stderr, "Cannot load sync.xml\n"); 711 free(file); 712 return -1; 713 } 714 715 int ret = 0; 716 xmlNode *node = xmlDocGetRootElement(doc)->children; 717 while(node && !ret) { 718 if(node->type == XML_ELEMENT_NODE) { 719 if(xstreq(node->name, "directory")) { 720 ret = scfg_load_directory(node); 721 } else { 722 print_error(node->line, 723 "unknown config element: %s\n", node->name); 724 ret = 1; 725 } 726 } 727 node = node->next; 728 } 729 730 xmlFreeDoc(doc); 731 free(file); 732 return ret; 733 } 734 735 SyncDirectory* scfg_get_dir(const char *name) { 736 return cxMapGet(directories, cx_hash_key_str(name)); 737 } 738 739 int scfg_check_dir(SyncDirectory *dir) { 740 struct stat s; 741 if(stat(dir->path, &s)) { 742 int err = errno; 743 if(err == ENOENT) { 744 fprintf(stderr, "directory %s does not exist.\n", dir->path); 745 } else { 746 fprintf(stderr, "Cannot stat directory %s.\n", dir->path); 747 perror(NULL); 748 } 749 fprintf(stderr, "Abort.\n"); 750 return -1; 751 } 752 753 if(dir->trash) { 754 // create trash directory 755 mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; 756 if (util_mkdir(dir->trash, mode)) { 757 if (errno != EEXIST) { 758 fprintf(stderr, "Cannot create trash directory: %s\nAbort.\n", dir->trash); 759 return -1; 760 } 761 } 762 } 763 764 return 0; 765 } 766 767 char* scfg_create_path(const char *cfg) { 768 if(!cfg) { 769 return NULL; 770 } 771 if(cfg[0] != '$') { 772 return strdup(cfg); 773 } 774 775 cxstring s = cx_str(cfg); 776 cxstring path = cx_strchr(cx_str(cfg), '/'); 777 char *localpath = NULL; 778 if(path.length > 0) { 779 // path = $var/path/ 780 781 cxstring var = cx_strsubsl(s, 1, path.ptr - s.ptr - 1); 782 if(var.length > 0) { 783 char *env = cx_strdup(var).ptr; 784 char *envval = getenv(env); 785 free(env); 786 if(envval) { 787 localpath = util_concat_path(envval, path.ptr); 788 } else { 789 fprintf( 790 stderr, 791 "Environment variable %.*s not set.\nAbort.\n", 792 (int)var.length, 793 var.ptr); 794 exit(-1); 795 } 796 } else { 797 localpath = cx_strdup(path).ptr; 798 } 799 } else { 800 // path = $var 801 char *envval = getenv(cfg + 1); 802 if(envval) { 803 localpath = strdup(envval); 804 } else { 805 fprintf( 806 stderr, 807 "Environment variable %s not set.\nAbort.\n", 808 cfg); 809 exit(-1); 810 } 811 } 812 return localpath; 813 } 814 815 816 int add_directory(SyncDirectory *dir) { 817 char *file = util_concat_path(ENV_HOME, ".dav/sync.xml"); 818 xmlDoc *doc = xmlReadFile(file, NULL, 0); 819 if(!doc) { 820 free(file); 821 fprintf(stderr, "Cannot load config.xml\n"); 822 return 1; 823 } 824 825 xmlNode *root = xmlDocGetRootElement(doc); 826 827 xmlNode *dirNode = xmlNewNode(NULL, BAD_CAST "directory"); 828 xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); 829 830 xmlNewTextChild(dirNode, NULL, BAD_CAST "name", BAD_CAST dir->name); 831 xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); 832 833 xmlNewTextChild(dirNode, NULL, BAD_CAST "path", BAD_CAST dir->path); 834 xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); 835 836 xmlNewTextChild(dirNode, NULL, BAD_CAST "repository", BAD_CAST dir->repository); 837 xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); 838 839 xmlNewTextChild(dirNode, NULL, BAD_CAST "collection", BAD_CAST dir->collection); 840 xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); 841 842 if(dir->trash) { 843 xmlNewTextChild(dirNode, NULL, BAD_CAST "trash", BAD_CAST dir->trash); 844 xmlNodeAddContent(dirNode, BAD_CAST "\n\t\t"); 845 } 846 xmlNewTextChild(dirNode, NULL, BAD_CAST "database", BAD_CAST dir->database); 847 xmlNodeAddContent(dirNode, BAD_CAST "\n\t"); 848 849 xmlNodeAddContent(root, BAD_CAST "\n\t"); 850 xmlAddChild(root, dirNode); 851 xmlNodeAddContent(root, BAD_CAST "\n"); 852 853 int ret = 0; 854 if(xmlSaveFormatFileEnc(file, doc, "UTF-8", 1) == -1) { 855 ret = 1; 856 } 857 xmlFreeDoc(doc); 858 free(file); 859 860 return ret; 861 } 862 863 char* generate_db_name(const char *basename) { 864 char *dbname = NULL; 865 int count = -1; 866 while(!dbname) { 867 cxmutstr name = count < 0 ? 868 cx_asprintf("%s-db.xml", basename) : 869 cx_asprintf("%s%d-db.xml", basename, count); 870 count++; 871 872 CxIterator i = cxMapIteratorValues(directories); 873 bool unique = true; 874 cx_foreach(SyncDirectory *, dir, i) { 875 if(!cx_strcmp(cx_strcast(name), cx_str(dir->database))) { 876 unique = false; 877 break; 878 } 879 } 880 if(unique) { 881 dbname = name.ptr; 882 break; 883 } 884 } 885 return dbname; 886 } 887 888 void free_filter(Filter filter) { 889 cxListDestroy(filter.include); 890 cxListDestroy(filter.exclude); 891 cxListDestroy(filter.tags); 892 } 893 894 void free_sync_config() { 895 if(directories) { 896 CxIterator i = cxMapIteratorValues(directories); 897 cx_foreach(SyncDirectory *, dir, i) { 898 free(dir->name); 899 free(dir->path); 900 free(dir->repository); 901 free(dir->database); 902 903 if(dir->collection) { 904 free(dir->collection); 905 } 906 if(dir->trash) { 907 free(dir->trash); 908 } 909 910 free_filter(dir->filter); 911 912 free(dir); 913 } 914 915 cxMapDestroy(directories); 916 } 917 } 918