UNIXworkcode

/* * 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 "db.h" #include <cx/utils.h> #include <libidav/utils.h> #include <libxml/encoding.h> #include <libxml/xmlwriter.h> #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) #ifdef _WIN32 #define ENV_HOME getenv("USERPROFILE") #else #define ENV_HOME getenv("HOME") #endif /* _WIN32 */ SyncDatabase* load_db(char *name) { char *dav_dir = util_concat_path(ENV_HOME, ".dav"); char *db_file = util_concat_path(dav_dir, name); free(dav_dir); SyncDatabase *db = malloc(sizeof(SyncDatabase)); db->resources = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 2048); db->conflict = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); cxDefineDestructor(db->resources, local_resource_free); cxDefineDestructor(db->conflict, local_resource_free); xmlTextReaderPtr reader = xmlReaderForFile(db_file, NULL, 0); if(!reader) { xmlDoc *doc = doc = xmlNewDoc(BAD_CAST "1.0"); xmlNode *root = xmlNewNode(NULL, BAD_CAST "directory"); xmlDocSetRootElement(doc, root); if(xmlSaveFormatFileEnc(db_file, doc, "UTF-8", 1) == -1) { destroy_db(db); db = NULL; } xmlFreeDoc(doc); free(db_file); return db; } free(db_file); int error = 0; while(xmlTextReaderRead(reader)) { int type = xmlTextReaderNodeType(reader); const xmlChar *xmlName = xmlTextReaderConstName(reader); if(type == XML_READER_TYPE_ELEMENT) { if(xstreq(xmlName, "resource")) { LocalResource *res = process_resource(reader); if(res) { cxMapPut(db->resources, cx_hash_key_str(res->path), res); } else { error = 1; break; } } else if(xstreq(xmlName, "conflict")) { LocalResource *res = process_conflict(reader); if(res) { cxMapPut(db->conflict, cx_hash_key_str(res->path), res); } else { error = 1; break; } } } } xmlFreeTextReader(reader); if(error) { destroy_db(db); return NULL; } else { return db; } } void process_parts(xmlTextReaderPtr reader, LocalResource *res) { // TODO: rewrite using low level array CxList *parts = cxLinkedListCreateSimple(CX_STORE_POINTERS); FilePart *current_part = NULL; size_t count = 0; int field = -1; int err = 0; while(xmlTextReaderRead(reader)) { int type = xmlTextReaderNodeType(reader); const xmlChar *name = xmlTextReaderConstName(reader); int depth = xmlTextReaderDepth(reader); if(type == XML_READER_TYPE_ELEMENT) { if(depth == 3 && xstreq(name, "part")) { current_part = calloc(1, sizeof(FilePart)); current_part->block = count; cxListAdd(parts, current_part); count++; } else if(depth == 4) { if(xstreq(name, "hash")) { field = 0; } else if(xstreq(name, "etag")) { field = 1; } } } else if(type == XML_READER_TYPE_END_ELEMENT) { if(depth == 2) { // </parts> break; } else if(depth == 3) { if(current_part) { if(!current_part->hash || !current_part->etag) { err = 1; } } // </part> current_part = NULL; } field = -1; } else if(type == XML_READER_TYPE_TEXT && depth == 5 && current_part) { const char *text = (const char*)xmlTextReaderConstValue(reader); if(field == 0) { current_part->hash = strdup(text); } else if(field == 1) { current_part->etag = strdup(text); } } } if(!err) { FilePart *file_parts = calloc(count, sizeof(FilePart)); CxIterator iter = cxListIterator(parts); cx_foreach(FilePart*, p, iter) { file_parts[iter.index] = *p; free(p); } res->parts = file_parts; res->numparts = count; } cxListDestroy(parts); } LocalResource* process_resource(xmlTextReaderPtr reader) { LocalResource *res = calloc(1, sizeof(LocalResource)); int field = -1; while(xmlTextReaderRead(reader)) { int type = xmlTextReaderNodeType(reader); const xmlChar *name = xmlTextReaderConstName(reader); if(type == XML_READER_TYPE_ELEMENT) { if(xstreq(name, "path")) { field = 0; } else if(xstreq(name, "etag")) { field = 1; } else if(xstreq(name, "lastmodified")) { field = 2; } else if(xstreq(name, "size")) { field = 3; } else if(xstreq(name, "tags-hash")) { field = 4; } else if(xstreq(name, "mode")) { field = 5; } else if(xstreq(name, "uid")) { field = 6; } else if(xstreq(name, "gid")) { field = 7; } else if(xstreq(name, "xattr-hash")) { field = 8; } else if(xstreq(name, "remote-tags-hash")) { field = 9; } else if(xstreq(name, "blocksize")) { field = 10; } else if(xstreq(name, "hash")) { field = 11; } else if(xstreq(name, "link")) { field = 12; } else if(xstreq(name, "localpath")) { field = 13; } else if(xstreq(name, "versioncontrol")) { field = 14; } else if(xstreq(name, "skipped")) { res->skipped = TRUE; } else if(xstreq(name, "tags-updated")) { res->tags_updated = TRUE; } else if(xstreq(name, "parts")) { process_parts(reader, res); } else if(xstreq(name, "isdirectory")) { res->isdirectory = 1; } } else if(type == XML_READER_TYPE_TEXT) { const xmlChar *value = xmlTextReaderConstValue(reader); //int b = 0; switch(field) { case 0: { res->path = strdup((char*)value); break; } case 1: { res->etag = strdup((char*)value); break; } case 2: { //res->last_modified = util_parse_lastmodified((char*)value); //res->last_modified = atoi((char*)value); char *endptr = (char*)value; time_t t = strtoll((char*)value, &endptr, 10); if(endptr == (char*)value) { fprintf( stderr, "lastmodified does not contain a number: %s\n", value); } else { res->last_modified = t; } break; } case 3: { res->size = 0; int64_t filelen = 0; if(util_strtoint((char*)value, &filelen)) { if(filelen > 0) { res->size = (size_t)filelen; } } break; } case 4: { res->tags_hash = strdup((char*)value); break; } case 5: { char *end; errno = 0; long int mode = strtol((char*)value, &end, 8); if(errno == 0) { res->mode = (mode_t)mode; } break; } case 6: { uint64_t uid = 0; if(util_strtouint((char*)value, &uid)) { res->uid = (uid_t)uid; } break; } case 7: { uint64_t gid = 0; if(util_strtouint((char*)value, &gid)) { res->gid = (gid_t)gid; } break; } case 8: { res->xattr_hash = strdup((char*)value); break; } case 9: { res->remote_tags_hash = strdup((char*)value); break; } case 10: { int64_t blsz = 0; if(util_strtoint((char*)value, &blsz)) { if(blsz < -1) { blsz = -1; } if(blsz > 0 && blsz < 16) { blsz = 0; } res->blocksize = blsz; } break; } case 11: { res->hash = strdup((char*)value); break; } case 12: { res->link_target = strdup((char*)value); break; } case 13: { res->local_path = strdup((char*)value); } case 14: { res->versioncontrol = util_getboolean((char*)value); } } } else if(XML_READER_TYPE_END_ELEMENT) { if(xstreq(name, "resource")) { break; } else { field = -1; } } } if(!res->path) { // TODO: free res return NULL; } else { return res; } } LocalResource* process_conflict(xmlTextReaderPtr reader) { LocalResource *res = calloc(1, sizeof(LocalResource)); int field = 0; while(xmlTextReaderRead(reader)) { int type = xmlTextReaderNodeType(reader); const xmlChar *name = xmlTextReaderConstName(reader); if(type == XML_READER_TYPE_ELEMENT) { if(xstreq(name, "path")) { field = 1; } else if(xstreq(name, "source")) { field = 2; } } else if(type == XML_READER_TYPE_TEXT) { const xmlChar *value = xmlTextReaderConstValue(reader); switch(field) { case 1: { res->path = strdup((const char*)value); break; } case 2: { res->conflict_source = strdup((const char*)value); break; } } } else if(XML_READER_TYPE_END_ELEMENT) { if(xstreq(name, "conflict")) { break; } else { field = 0; } } } if(!res->path) { // TODO: free res return NULL; } else { return res; } } int store_db(SyncDatabase *db, char *name, uint32_t settings) { // open writer char *dav_dir = util_concat_path(ENV_HOME, ".dav"); char *db_file = util_concat_path(dav_dir, name); free(dav_dir); xmlTextWriterPtr writer = xmlNewTextWriterFilename(db_file, 0); if(!writer) { fprintf(stderr, "Cannot write db file: %s\n", db_file); free(db_file); return -1; } free(db_file); // start document int r = 0; r = xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL); if(r < 0) { xmlFreeTextWriter(writer); return -1; } xmlTextWriterStartElement(writer, BAD_CAST "directory"); // write all resource entries CxIterator iter = cxMapIteratorValues(db->resources); cx_foreach(LocalResource*, res, iter) { // <resource> xmlTextWriterStartElement(writer, BAD_CAST "resource"); r = xmlTextWriterWriteElement( writer, BAD_CAST "path", BAD_CAST res->path); if(r < 0) { fprintf(stderr, "Cannot write path: %s\n", res->path); xmlFreeTextWriter(writer); return -1; } if(res->isdirectory) { r = xmlTextWriterStartElement(writer, BAD_CAST "isdirectory"); r += xmlTextWriterEndElement(writer); if(r < 0) { fprintf(stderr, "Cannot write isdirectory\n"); xmlFreeTextWriter(writer); return -1; } } if(res->etag) { r = xmlTextWriterWriteElement( writer, BAD_CAST "etag", BAD_CAST res->etag); if(r < 0) { fprintf(stderr, "Cannot write etag: %s\n", res->etag); xmlFreeTextWriter(writer); return -1; } } if(res->hash) { r = xmlTextWriterWriteElement( writer, BAD_CAST "hash", BAD_CAST res->hash); if(r < 0) { fprintf(stderr, "Cannot write hash: %s\n", res->hash); xmlFreeTextWriter(writer); return -1; } } r = xmlTextWriterWriteFormatElement( writer, BAD_CAST "lastmodified", "%" PRId64, (int64_t)res->last_modified); if(r < 0) { fprintf(stderr, "Cannot write lastmodified\n"); xmlFreeTextWriter(writer); return -1; } if(res->blocksize != 0) { r = xmlTextWriterWriteFormatElement( writer, BAD_CAST "mode", "%" PRId64, res->blocksize); if(r < 0) { fprintf(stderr, "Cannot write blocksize\n"); xmlFreeTextWriter(writer); return -1; } } if((settings & DB_STORE_MODE) == DB_STORE_MODE) { r = xmlTextWriterWriteFormatElement( writer, BAD_CAST "mode", "%o", (int)res->mode); if(r < 0) { fprintf(stderr, "Cannot write mode\n"); xmlFreeTextWriter(writer); return -1; } } if((settings & DB_STORE_OWNER) == DB_STORE_OWNER) { r = xmlTextWriterWriteFormatElement( writer, BAD_CAST "uid", "%u", (unsigned int)res->uid); if(r < 0) { fprintf(stderr, "Cannot write uid\n"); xmlFreeTextWriter(writer); return -1; } r = xmlTextWriterWriteFormatElement( writer, BAD_CAST "gid", "%u", (unsigned int)res->gid); if(r < 0) { fprintf(stderr, "Cannot write gid\n"); xmlFreeTextWriter(writer); return -1; } } r = xmlTextWriterWriteFormatElement( writer, BAD_CAST "size", "%" PRId64, (int64_t)res->size); if(r < 0) { fprintf(stderr, "Cannot write size\n"); xmlFreeTextWriter(writer); return -1; } if(res->tags_hash) { r = xmlTextWriterWriteElement( writer, BAD_CAST "tags-hash", BAD_CAST res->tags_hash); if(r < 0) { fprintf(stderr, "Cannot write tags-hash: %s\n", res->tags_hash); xmlFreeTextWriter(writer); return -1; } } if(res->remote_tags_hash) { r = xmlTextWriterWriteElement( writer, BAD_CAST "remote-tags-hash", BAD_CAST res->remote_tags_hash); if(r < 0) { fprintf(stderr, "Cannot write remote-tags-hash: %s\n", res->remote_tags_hash); xmlFreeTextWriter(writer); return -1; } } if(res->link_target) { r = xmlTextWriterWriteElement( writer, BAD_CAST "link", BAD_CAST res->link_target); if(r < 0) { fprintf(stderr, "Cannot write link: %s\n", res->link_target); xmlFreeTextWriter(writer); return -1; } } if(res->local_path) { r = xmlTextWriterWriteElement( writer, BAD_CAST "localpath", BAD_CAST res->local_path); if(r < 0) { fprintf(stderr, "Cannot write localpath: %s\n", res->local_path); xmlFreeTextWriter(writer); return -1; } } if(res->xattr_hash) { r = xmlTextWriterWriteElement( writer, BAD_CAST "xattr-hash", BAD_CAST res->xattr_hash); if(r < 0) { fprintf(stderr, "Cannot write xattr-hash: %s\n", res->xattr_hash); xmlFreeTextWriter(writer); return -1; } } if(res->skipped) { r = xmlTextWriterStartElement(writer, BAD_CAST "skipped"); r += xmlTextWriterEndElement(writer); if(r < 0) { fprintf(stderr, "Cannot write skipped\n"); xmlFreeTextWriter(writer); return -1; } } if(res->tags_updated) { r = xmlTextWriterStartElement(writer, BAD_CAST "tags-updated"); r += xmlTextWriterEndElement(writer); if(r < 0) { fprintf(stderr, "Cannot write tags-updated\n"); xmlFreeTextWriter(writer); return -1; } } if(res->numparts > 0) { r = xmlTextWriterStartElement(writer, BAD_CAST "parts"); if(r < 0) { xmlFreeTextWriter(writer); return -1; } for(size_t i=0;i<res->numparts;i++) { FilePart p = res->parts[i]; r = xmlTextWriterStartElement(writer, BAD_CAST "part"); if(r < 0) { xmlFreeTextWriter(writer); return -1; } if(p.hash) { r = xmlTextWriterWriteElement(writer, BAD_CAST "hash", BAD_CAST p.hash); if(r < 0) { xmlFreeTextWriter(writer); return -1; } } if(p.etag) { r = xmlTextWriterWriteElement(writer, BAD_CAST "etag", BAD_CAST p.etag); if(r < 0) { xmlFreeTextWriter(writer); return -1; } } r = xmlTextWriterEndElement(writer); if(r < 0) { xmlFreeTextWriter(writer); return -1; } } r = xmlTextWriterEndElement(writer); if(r < 0) { xmlFreeTextWriter(writer); return -1; } } if(res->versioncontrol) { r = xmlTextWriterWriteElement( writer, BAD_CAST "versioncontrol", BAD_CAST "true"); if(r < 0) { fprintf(stderr, "Cannot write versioncontrol\n"); xmlFreeTextWriter(writer); return -1; } } // </resource> xmlTextWriterEndElement(writer); } // write all remove entries /* i = ucx_map_iterator(db->remove); UCX_MAP_FOREACH(key, res, i) { // <remove> xmlTextWriterStartElement(writer, BAD_CAST "remove"); xmlTextWriterWriteElement( writer, BAD_CAST "path", BAD_CAST res->path); // </remove> xmlTextWriterEndElement(writer); } */ // write all conflict entries iter = cxMapIteratorValues(db->conflict); cx_foreach(LocalResource*, res, iter) { // <conflict> xmlTextWriterStartElement(writer, BAD_CAST "conflict"); xmlTextWriterWriteElement( writer, BAD_CAST "path", BAD_CAST res->path); if(res->conflict_source) { xmlTextWriterWriteElement( writer, BAD_CAST "source", BAD_CAST res->conflict_source); } // </conflict> xmlTextWriterEndElement(writer); } // end xmlTextWriterEndElement(writer); r = xmlTextWriterEndDocument(writer); if(r < 0) { xmlFreeTextWriter(writer); return -1; } xmlFreeTextWriter(writer); return 0; } void destroy_db(SyncDatabase *db) { cxMapDestroy(db->resources); cxMapDestroy(db->conflict); free(db); } void local_resource_free(LocalResource *res) { if(!res) { return; } if(res->name) { free(res->name); } if(res->path) { free(res->path); } if(res->etag) { free(res->etag); } if(res->cached_tags) { cxBufferFree(res->cached_tags); } if(res->tags_hash) { free(res->tags_hash); } if(res->prev_hash) { free(res->prev_hash); } free(res); } static char* nullstrdup(const char *s) { return s ? strdup(s) : NULL; } void local_resource_copy_parts(LocalResource *from, LocalResource *to) { if(from->parts) { to->numparts = from->numparts; to->parts = calloc(from->numparts, sizeof(FilePart)); for(int i=0;i<to->numparts;i++) { FilePart s = from->parts[i]; FilePart p; p.block = s.block; p.hash = nullstrdup(s.hash); p.etag = nullstrdup(s.etag); to->parts[i] = p; } } } LocalResource* local_resource_copy(LocalResource *src, const char *new_path) { LocalResource *newres = calloc(1, sizeof(LocalResource)); newres->path = strdup(new_path); newres->etag = nullstrdup(src->etag); newres->hash = nullstrdup(src->hash); newres->last_modified = src->last_modified; newres->mode = src->mode; newres->uid = src->uid; newres->gid = src->gid; newres->size = src->size; newres->isdirectory = src->isdirectory; newres->skipped = src->skipped; newres->versioncontrol = src->versioncontrol; if(src->xattr) { XAttributes *xattr = calloc(1, sizeof(XAttributes)); xattr->hash = nullstrdup(src->xattr->hash); xattr->nattr = src->xattr->nattr; xattr->names = calloc(xattr->nattr, sizeof(char*)); xattr->values = calloc(xattr->nattr, sizeof(cxmutstr)); for(int i=0;i<xattr->nattr;i++) { xattr->names[i] = strdup(src->xattr->names[i]); xattr->values[i] = cx_strdup(cx_strcast(src->xattr->values[i])); } newres->xattr = xattr; } newres->tags_hash = nullstrdup(src->tags_hash); newres->xattr_hash = nullstrdup(src->xattr_hash); newres->remote_tags_hash = nullstrdup(src->remote_tags_hash); local_resource_copy_parts(src, newres); newres->blocksize = src->blocksize; newres->tags_updated = src->tags_updated; newres->finfo_updated = src->finfo_updated; newres->xattr_updated = src->xattr_updated; newres->metadata_updated = src->metadata_updated; return newres; } void filepart_free(FilePart *part) { if(part->etag) { free(part->etag); } if(part->hash) { free(part->hash); } free(part); } CxMap* create_hash_index(SyncDatabase *db) { CxMap *hmap = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, cxMapSize(db->resources) + 64); CxIterator i = cxMapIteratorValues(db->resources); cx_foreach(LocalResource*, res, i) { if(res->hash) { cxMapPut(hmap, cx_hash_key_str(res->hash), res); } } return hmap; }