1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33
34 #include "db.h"
35
36 #include <cx/utils.h>
37
38 #include <libidav/utils.h>
39
40 #include <libxml/encoding.h>
41 #include <libxml/xmlwriter.h>
42
43
44 #define xstreq(a,b) xmlStrEqual(
BAD_CAST a,
BAD_CAST b)
45
46 #ifdef _WIN32
47 #define ENV_HOME getenv(
"USERPROFILE")
48 #else
49 #define ENV_HOME getenv(
"HOME")
50 #endif
51
52 SyncDatabase* load_db(
char *name) {
53 char *dav_dir = util_concat_path(
ENV_HOME,
".dav");
54 char *db_file = util_concat_path(dav_dir, name);
55 free(dav_dir);
56
57 SyncDatabase *db = malloc(
sizeof(SyncDatabase));
58 db->resources = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
2048);
59 db->conflict = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
16);
60
61 cxDefineDestructor(db->resources, local_resource_free);
62 cxDefineDestructor(db->conflict, local_resource_free);
63
64 xmlTextReaderPtr reader = xmlReaderForFile(db_file,
NULL,
0);
65 if(!reader) {
66 xmlDoc *doc = doc = xmlNewDoc(
BAD_CAST "1.0");
67 xmlNode *root = xmlNewNode(
NULL,
BAD_CAST "directory");
68 xmlDocSetRootElement(doc, root);
69 if(xmlSaveFormatFileEnc(db_file, doc,
"UTF-8",
1) == -
1) {
70 destroy_db(db);
71 db =
NULL;
72 }
73 xmlFreeDoc(doc);
74 free(db_file);
75 return db;
76 }
77 free(db_file);
78
79 int error =
0;
80 while(xmlTextReaderRead(reader)) {
81 int type = xmlTextReaderNodeType(reader);
82 const xmlChar *xmlName = xmlTextReaderConstName(reader);
83
84 if(type ==
XML_READER_TYPE_ELEMENT) {
85 if(xstreq(xmlName,
"resource")) {
86 LocalResource *res = process_resource(reader);
87 if(res) {
88 cxMapPut(db->resources, cx_hash_key_str(res->path), res);
89 }
else {
90 error =
1;
91 break;
92 }
93 }
else if(xstreq(xmlName,
"conflict")) {
94 LocalResource *res = process_conflict(reader);
95 if(res) {
96 cxMapPut(db->conflict, cx_hash_key_str(res->path), res);
97 }
else {
98 error =
1;
99 break;
100 }
101 }
102 }
103 }
104
105 xmlFreeTextReader(reader);
106 if(error) {
107 destroy_db(db);
108 return NULL;
109 }
else {
110 return db;
111 }
112 }
113
114 void process_parts(xmlTextReaderPtr reader, LocalResource *res) {
115
116
117 CxList *parts = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
118
119 FilePart *current_part =
NULL;
120
121 size_t count =
0;
122 int field = -
1;
123 int err =
0;
124 while(xmlTextReaderRead(reader)) {
125 int type = xmlTextReaderNodeType(reader);
126 const xmlChar *name = xmlTextReaderConstName(reader);
127 int depth = xmlTextReaderDepth(reader);
128
129 if(type ==
XML_READER_TYPE_ELEMENT) {
130 if(depth ==
3 && xstreq(name,
"part")) {
131 current_part = calloc(
1,
sizeof(FilePart));
132 current_part->block = count;
133 cxListAdd(parts, current_part);
134 count++;
135 }
else if(depth ==
4) {
136 if(xstreq(name,
"hash")) {
137 field =
0;
138 }
else if(xstreq(name,
"etag")) {
139 field =
1;
140 }
141 }
142 }
else if(type ==
XML_READER_TYPE_END_ELEMENT) {
143 if(depth ==
2) {
144
145 break;
146 }
else if(depth ==
3) {
147 if(current_part) {
148 if(!current_part->hash || !current_part->etag) {
149 err =
1;
150 }
151 }
152
153 current_part =
NULL;
154 }
155 field = -
1;
156 }
else if(type ==
XML_READER_TYPE_TEXT && depth ==
5 && current_part) {
157 const char *text = (
const char*)xmlTextReaderConstValue(reader);
158 if(field ==
0) {
159 current_part->hash = strdup(text);
160 }
else if(field ==
1) {
161 current_part->etag = strdup(text);
162 }
163 }
164 }
165
166 if(!err) {
167 FilePart *file_parts = calloc(count,
sizeof(FilePart));
168 CxIterator iter = cxListIterator(parts);
169 cx_foreach(FilePart*, p, iter) {
170 file_parts[iter.index] = *p;
171 free(p);
172 }
173
174 res->parts = file_parts;
175 res->numparts = count;
176 }
177
178 cxListDestroy(parts);
179 }
180
181 LocalResource* process_resource(xmlTextReaderPtr reader) {
182 LocalResource *res = calloc(
1,
sizeof(LocalResource));
183
184 int field = -
1;
185 while(xmlTextReaderRead(reader)) {
186 int type = xmlTextReaderNodeType(reader);
187 const xmlChar *name = xmlTextReaderConstName(reader);
188
189 if(type ==
XML_READER_TYPE_ELEMENT) {
190 if(xstreq(name,
"path")) {
191 field =
0;
192 }
else if(xstreq(name,
"etag")) {
193 field =
1;
194 }
else if(xstreq(name,
"lastmodified")) {
195 field =
2;
196 }
else if(xstreq(name,
"size")) {
197 field =
3;
198 }
else if(xstreq(name,
"tags-hash")) {
199 field =
4;
200 }
else if(xstreq(name,
"mode")) {
201 field =
5;
202 }
else if(xstreq(name,
"uid")) {
203 field =
6;
204 }
else if(xstreq(name,
"gid")) {
205 field =
7;
206 }
else if(xstreq(name,
"xattr-hash")) {
207 field =
8;
208 }
else if(xstreq(name,
"remote-tags-hash")) {
209 field =
9;
210 }
else if(xstreq(name,
"blocksize")) {
211 field =
10;
212 }
else if(xstreq(name,
"hash")) {
213 field =
11;
214 }
else if(xstreq(name,
"link")) {
215 field =
12;
216 }
else if(xstreq(name,
"localpath")) {
217 field =
13;
218 }
else if(xstreq(name,
"versioncontrol")) {
219 field =
14;
220 }
else if(xstreq(name,
"skipped")) {
221 res->skipped =
TRUE;
222 }
else if(xstreq(name,
"tags-updated")) {
223 res->tags_updated =
TRUE;
224 }
else if(xstreq(name,
"parts")) {
225 process_parts(reader, res);
226 }
else if(xstreq(name,
"isdirectory")) {
227 res->isdirectory =
1;
228 }
229 }
else if(type ==
XML_READER_TYPE_TEXT) {
230 const xmlChar *value = xmlTextReaderConstValue(reader);
231
232 switch(field) {
233 case 0: {
234 res->path = strdup((
char*)value);
235 break;
236 }
237 case 1: {
238 res->etag = strdup((
char*)value);
239 break;
240 }
241 case 2: {
242
243
244 char *endptr = (
char*)value;
245 time_t t = strtoll((
char*)value, &endptr,
10);
246 if(endptr == (
char*)value) {
247 fprintf(
248 stderr,
249 "lastmodified does not contain a number: %s\n", value);
250 }
else {
251 res->last_modified = t;
252 }
253 break;
254 }
255 case 3: {
256 res->size =
0;
257 int64_t filelen =
0;
258 if(util_strtoint((
char*)value, &filelen)) {
259 if(filelen >
0) {
260 res->size = (
size_t)filelen;
261 }
262 }
263 break;
264 }
265 case 4: {
266 res->tags_hash = strdup((
char*)value);
267 break;
268 }
269 case 5: {
270 char *end;
271 errno =
0;
272 long int mode = strtol((
char*)value, &end,
8);
273 if(errno ==
0) {
274 res->mode = (
mode_t)mode;
275 }
276 break;
277 }
278 case 6: {
279 uint64_t uid =
0;
280 if(util_strtouint((
char*)value, &uid)) {
281 res->uid = (
uid_t)uid;
282 }
283 break;
284 }
285 case 7: {
286 uint64_t gid =
0;
287 if(util_strtouint((
char*)value, &gid)) {
288 res->gid = (
gid_t)gid;
289 }
290 break;
291 }
292 case 8: {
293 res->xattr_hash = strdup((
char*)value);
294 break;
295 }
296 case 9: {
297 res->remote_tags_hash = strdup((
char*)value);
298 break;
299 }
300 case 10: {
301 int64_t blsz =
0;
302 if(util_strtoint((
char*)value, &blsz)) {
303 if(blsz < -
1) {
304 blsz = -
1;
305 }
306 if(blsz >
0 && blsz <
16) {
307 blsz =
0;
308 }
309 res->blocksize = blsz;
310 }
311 break;
312 }
313 case 11: {
314 res->hash = strdup((
char*)value);
315 break;
316 }
317 case 12: {
318 res->link_target = strdup((
char*)value);
319 break;
320 }
321 case 13: {
322 res->local_path = strdup((
char*)value);
323 }
324 case 14: {
325 res->versioncontrol = util_getboolean((
char*)value);
326 }
327 }
328 }
else if(
XML_READER_TYPE_END_ELEMENT) {
329 if(xstreq(name,
"resource")) {
330 break;
331 }
else {
332 field = -
1;
333 }
334 }
335 }
336
337 if(!res->path) {
338
339 return NULL;
340 }
else {
341 return res;
342 }
343 }
344
345 LocalResource* process_conflict(xmlTextReaderPtr reader) {
346 LocalResource *res = calloc(
1,
sizeof(LocalResource));
347
348 int field =
0;
349 while(xmlTextReaderRead(reader)) {
350 int type = xmlTextReaderNodeType(reader);
351 const xmlChar *name = xmlTextReaderConstName(reader);
352
353 if(type ==
XML_READER_TYPE_ELEMENT) {
354 if(xstreq(name,
"path")) {
355 field =
1;
356 }
else if(xstreq(name,
"source")) {
357 field =
2;
358 }
359 }
else if(type ==
XML_READER_TYPE_TEXT) {
360 const xmlChar *value = xmlTextReaderConstValue(reader);
361 switch(field) {
362 case 1: {
363 res->path = strdup((
const char*)value);
364 break;
365 }
366 case 2: {
367 res->conflict_source = strdup((
const char*)value);
368 break;
369 }
370 }
371 }
else if(
XML_READER_TYPE_END_ELEMENT) {
372 if(xstreq(name,
"conflict")) {
373 break;
374 }
else {
375 field =
0;
376 }
377 }
378 }
379
380 if(!res->path) {
381
382 return NULL;
383 }
else {
384 return res;
385 }
386 }
387
388 int store_db(SyncDatabase *db,
char *name,
uint32_t settings) {
389
390 char *dav_dir = util_concat_path(
ENV_HOME,
".dav");
391 char *db_file = util_concat_path(dav_dir, name);
392 free(dav_dir);
393 xmlTextWriterPtr writer = xmlNewTextWriterFilename(db_file,
0);
394 if(!writer) {
395 fprintf(stderr,
"Cannot write db file: %s\n", db_file);
396 free(db_file);
397 return -
1;
398 }
399 free(db_file);
400
401
402 int r =
0;
403 r = xmlTextWriterStartDocument(writer,
NULL,
"UTF-8",
NULL);
404 if(r <
0) {
405 xmlFreeTextWriter(writer);
406 return -
1;
407 }
408 xmlTextWriterStartElement(writer,
BAD_CAST "directory");
409
410
411 CxIterator iter = cxMapIteratorValues(db->resources);
412 cx_foreach(LocalResource*, res, iter) {
413
414 xmlTextWriterStartElement(writer,
BAD_CAST "resource");
415
416 r = xmlTextWriterWriteElement(
417 writer,
418 BAD_CAST "path",
419 BAD_CAST res->path);
420 if(r <
0) {
421 fprintf(stderr,
"Cannot write path: %s\n", res->path);
422 xmlFreeTextWriter(writer);
423 return -
1;
424 }
425
426 if(res->isdirectory) {
427 r = xmlTextWriterStartElement(writer,
BAD_CAST "isdirectory");
428 r += xmlTextWriterEndElement(writer);
429 if(r <
0) {
430 fprintf(stderr,
"Cannot write isdirectory\n");
431 xmlFreeTextWriter(writer);
432 return -
1;
433 }
434 }
435
436 if(res->etag) {
437 r = xmlTextWriterWriteElement(
438 writer,
439 BAD_CAST "etag",
440 BAD_CAST res->etag);
441 if(r <
0) {
442 fprintf(stderr,
"Cannot write etag: %s\n", res->etag);
443 xmlFreeTextWriter(writer);
444 return -
1;
445 }
446 }
447
448 if(res->hash) {
449 r = xmlTextWriterWriteElement(
450 writer,
451 BAD_CAST "hash",
452 BAD_CAST res->hash);
453 if(r <
0) {
454 fprintf(stderr,
"Cannot write hash: %s\n", res->hash);
455 xmlFreeTextWriter(writer);
456 return -
1;
457 }
458 }
459
460 r = xmlTextWriterWriteFormatElement(
461 writer,
462 BAD_CAST "lastmodified",
463 "%" PRId64,
464 (
int64_t)res->last_modified);
465 if(r <
0) {
466 fprintf(stderr,
"Cannot write lastmodified\n");
467 xmlFreeTextWriter(writer);
468 return -
1;
469 }
470
471 if(res->blocksize !=
0) {
472 r = xmlTextWriterWriteFormatElement(
473 writer,
474 BAD_CAST "mode",
475 "%" PRId64,
476 res->blocksize);
477 if(r <
0) {
478 fprintf(stderr,
"Cannot write blocksize\n");
479 xmlFreeTextWriter(writer);
480 return -
1;
481 }
482 }
483
484 if((settings &
DB_STORE_MODE) ==
DB_STORE_MODE) {
485 r = xmlTextWriterWriteFormatElement(
486 writer,
487 BAD_CAST "mode",
488 "%o",
489 (
int)res->mode);
490 if(r <
0) {
491 fprintf(stderr,
"Cannot write mode\n");
492 xmlFreeTextWriter(writer);
493 return -
1;
494 }
495 }
496
497 if((settings &
DB_STORE_OWNER) ==
DB_STORE_OWNER) {
498 r = xmlTextWriterWriteFormatElement(
499 writer,
500 BAD_CAST "uid",
501 "%u",
502 (
unsigned int)res->uid);
503 if(r <
0) {
504 fprintf(stderr,
"Cannot write uid\n");
505 xmlFreeTextWriter(writer);
506 return -
1;
507 }
508 r = xmlTextWriterWriteFormatElement(
509 writer,
510 BAD_CAST "gid",
511 "%u",
512 (
unsigned int)res->gid);
513 if(r <
0) {
514 fprintf(stderr,
"Cannot write gid\n");
515 xmlFreeTextWriter(writer);
516 return -
1;
517 }
518 }
519
520 r = xmlTextWriterWriteFormatElement(
521 writer,
522 BAD_CAST "size",
523 "%" PRId64,
524 (
int64_t)res->size);
525 if(r <
0) {
526 fprintf(stderr,
"Cannot write size\n");
527 xmlFreeTextWriter(writer);
528 return -
1;
529 }
530
531 if(res->tags_hash) {
532 r = xmlTextWriterWriteElement(
533 writer,
534 BAD_CAST "tags-hash",
535 BAD_CAST res->tags_hash);
536 if(r <
0) {
537 fprintf(stderr,
"Cannot write tags-hash: %s\n", res->tags_hash);
538 xmlFreeTextWriter(writer);
539 return -
1;
540 }
541 }
542
543 if(res->remote_tags_hash) {
544 r = xmlTextWriterWriteElement(
545 writer,
546 BAD_CAST "remote-tags-hash",
547 BAD_CAST res->remote_tags_hash);
548 if(r <
0) {
549 fprintf(stderr,
"Cannot write remote-tags-hash: %s\n", res->remote_tags_hash);
550 xmlFreeTextWriter(writer);
551 return -
1;
552 }
553 }
554
555 if(res->link_target) {
556 r = xmlTextWriterWriteElement(
557 writer,
558 BAD_CAST "link",
559 BAD_CAST res->link_target);
560 if(r <
0) {
561 fprintf(stderr,
"Cannot write link: %s\n", res->link_target);
562 xmlFreeTextWriter(writer);
563 return -
1;
564 }
565 }
566
567 if(res->local_path) {
568 r = xmlTextWriterWriteElement(
569 writer,
570 BAD_CAST "localpath",
571 BAD_CAST res->local_path);
572 if(r <
0) {
573 fprintf(stderr,
"Cannot write localpath: %s\n", res->local_path);
574 xmlFreeTextWriter(writer);
575 return -
1;
576 }
577 }
578
579
580 if(res->xattr_hash) {
581 r = xmlTextWriterWriteElement(
582 writer,
583 BAD_CAST "xattr-hash",
584 BAD_CAST res->xattr_hash);
585 if(r <
0) {
586 fprintf(stderr,
"Cannot write xattr-hash: %s\n", res->xattr_hash);
587 xmlFreeTextWriter(writer);
588 return -
1;
589 }
590 }
591
592 if(res->skipped) {
593 r = xmlTextWriterStartElement(writer,
BAD_CAST "skipped");
594 r += xmlTextWriterEndElement(writer);
595 if(r <
0) {
596 fprintf(stderr,
"Cannot write skipped\n");
597 xmlFreeTextWriter(writer);
598 return -
1;
599 }
600 }
601
602 if(res->tags_updated) {
603 r = xmlTextWriterStartElement(writer,
BAD_CAST "tags-updated");
604 r += xmlTextWriterEndElement(writer);
605 if(r <
0) {
606 fprintf(stderr,
"Cannot write tags-updated\n");
607 xmlFreeTextWriter(writer);
608 return -
1;
609 }
610 }
611
612 if(res->numparts >
0) {
613 r = xmlTextWriterStartElement(writer,
BAD_CAST "parts");
614 if(r <
0) {
615 xmlFreeTextWriter(writer);
616 return -
1;
617 }
618 for(
size_t i=
0;i<res->numparts;i++) {
619 FilePart p = res->parts[i];
620 r = xmlTextWriterStartElement(writer,
BAD_CAST "part");
621 if(r <
0) {
622 xmlFreeTextWriter(writer);
623 return -
1;
624 }
625
626 if(p.hash) {
627 r = xmlTextWriterWriteElement(writer,
BAD_CAST "hash",
BAD_CAST p.hash);
628 if(r <
0) {
629 xmlFreeTextWriter(writer);
630 return -
1;
631 }
632 }
633 if(p.etag) {
634 r = xmlTextWriterWriteElement(writer,
BAD_CAST "etag",
BAD_CAST p.etag);
635 if(r <
0) {
636 xmlFreeTextWriter(writer);
637 return -
1;
638 }
639 }
640 r = xmlTextWriterEndElement(writer);
641 if(r <
0) {
642 xmlFreeTextWriter(writer);
643 return -
1;
644 }
645 }
646 r = xmlTextWriterEndElement(writer);
647 if(r <
0) {
648 xmlFreeTextWriter(writer);
649 return -
1;
650 }
651 }
652
653 if(res->versioncontrol) {
654 r = xmlTextWriterWriteElement(
655 writer,
656 BAD_CAST "versioncontrol",
657 BAD_CAST "true");
658 if(r <
0) {
659 fprintf(stderr,
"Cannot write versioncontrol\n");
660 xmlFreeTextWriter(writer);
661 return -
1;
662 }
663 }
664
665
666 xmlTextWriterEndElement(writer);
667 }
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687 iter = cxMapIteratorValues(db->conflict);
688 cx_foreach(LocalResource*, res, iter) {
689
690 xmlTextWriterStartElement(writer,
BAD_CAST "conflict");
691
692 xmlTextWriterWriteElement(
693 writer,
694 BAD_CAST "path",
695 BAD_CAST res->path);
696
697 if(res->conflict_source) {
698 xmlTextWriterWriteElement(
699 writer,
700 BAD_CAST "source",
701 BAD_CAST res->conflict_source);
702 }
703
704
705 xmlTextWriterEndElement(writer);
706 }
707
708
709 xmlTextWriterEndElement(writer);
710 r = xmlTextWriterEndDocument(writer);
711 if(r <
0) {
712 xmlFreeTextWriter(writer);
713 return -
1;
714 }
715 xmlFreeTextWriter(writer);
716 return 0;
717 }
718
719 void destroy_db(SyncDatabase *db) {
720 cxMapDestroy(db->resources);
721 cxMapDestroy(db->conflict);
722 free(db);
723 }
724
725 void local_resource_free(LocalResource *res) {
726 if(!res) {
727 return;
728 }
729 if(res->name) {
730 free(res->name);
731 }
732 if(res->path) {
733 free(res->path);
734 }
735 if(res->etag) {
736 free(res->etag);
737 }
738 if(res->cached_tags) {
739 cxBufferFree(res->cached_tags);
740 }
741 if(res->tags_hash) {
742 free(res->tags_hash);
743 }
744 if(res->prev_hash) {
745 free(res->prev_hash);
746 }
747 free(res);
748 }
749
750 static char* nullstrdup(
const char *s) {
751 return s ? strdup(s) :
NULL;
752 }
753
754 void local_resource_copy_parts(LocalResource *from, LocalResource *to) {
755 if(from->parts) {
756 to->numparts = from->numparts;
757 to->parts = calloc(from->numparts,
sizeof(FilePart));
758 for(
int i=
0;i<to->numparts;i++) {
759 FilePart s = from->parts[i];
760 FilePart p;
761 p.block = s.block;
762 p.hash = nullstrdup(s.hash);
763 p.etag = nullstrdup(s.etag);
764 to->parts[i] = p;
765 }
766 }
767 }
768
769 LocalResource* local_resource_copy(LocalResource *src,
const char *new_path) {
770 LocalResource *newres = calloc(
1,
sizeof(LocalResource));
771 newres->path = strdup(new_path);
772 newres->etag = nullstrdup(src->etag);
773 newres->hash = nullstrdup(src->hash);
774 newres->last_modified = src->last_modified;
775 newres->mode = src->mode;
776 newres->uid = src->uid;
777 newres->gid = src->gid;
778 newres->size = src->size;
779 newres->isdirectory = src->isdirectory;
780 newres->skipped = src->skipped;
781 newres->versioncontrol = src->versioncontrol;
782
783 if(src->xattr) {
784 XAttributes *xattr = calloc(
1,
sizeof(XAttributes));
785 xattr->hash = nullstrdup(src->xattr->hash);
786 xattr->nattr = src->xattr->nattr;
787 xattr->names = calloc(xattr->nattr,
sizeof(
char*));
788 xattr->values = calloc(xattr->nattr,
sizeof(cxmutstr));
789 for(
int i=
0;i<xattr->nattr;i++) {
790 xattr->names[i] = strdup(src->xattr->names[i]);
791 xattr->values[i] = cx_strdup(cx_strcast(src->xattr->values[i]));
792 }
793 newres->xattr = xattr;
794 }
795
796 newres->tags_hash = nullstrdup(src->tags_hash);
797 newres->xattr_hash = nullstrdup(src->xattr_hash);
798 newres->remote_tags_hash = nullstrdup(src->remote_tags_hash);
799
800 local_resource_copy_parts(src, newres);
801
802 newres->blocksize = src->blocksize;
803
804 newres->tags_updated = src->tags_updated;
805 newres->finfo_updated = src->finfo_updated;
806 newres->xattr_updated = src->xattr_updated;
807 newres->metadata_updated = src->metadata_updated;
808
809 return newres;
810
811 }
812
813 void filepart_free(FilePart *part) {
814 if(part->etag) {
815 free(part->etag);
816 }
817 if(part->hash) {
818 free(part->hash);
819 }
820 free(part);
821 }
822
823 CxMap* create_hash_index(SyncDatabase *db) {
824 CxMap *hmap = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, cxMapSize(db->resources) +
64);
825
826 CxIterator i = cxMapIteratorValues(db->resources);
827 cx_foreach(LocalResource*, res, i) {
828 if(res->hash) {
829 cxMapPut(hmap, cx_hash_key_str(res->hash), res);
830 }
831 }
832
833 return hmap;
834 }
835