dav/db.c

Fri, 12 Apr 2019 12:42:41 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 12 Apr 2019 12:42:41 +0200
changeset 567
b0ce8b27978b
parent 560
a816e805e5db
child 572
1eac93fcef77
permissions
-rw-r--r--

implement copy/move for dav-sync pull

/*
 * 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 <ucx/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 = ucx_map_new(2048);
    db->conflict = ucx_map_new(16);
    
    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 *name = xmlTextReaderConstName(reader);
        
        if(type == XML_READER_TYPE_ELEMENT) {
            if(xstreq(name, "resource")) {
                LocalResource *res = process_resource(reader);
                if(res) {
                    ucx_map_cstr_put(db->resources, res->path, res);
                } else {
                    error = 1;
                    break;
                }
            } else if(xstreq(name, "conflict")) {
                LocalResource *res = process_conflict(reader);
                if(res) {
                    ucx_map_cstr_put(db->conflict, 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) {
    UcxList *parts = NULL;
    
    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);
        
        int part = TRUE;
        if(type == XML_READER_TYPE_ELEMENT) {
            if(depth == 3 && xstreq(name, "part")) {
                current_part = calloc(1, sizeof(FilePart));
                current_part->block = count;
                parts = ucx_list_append(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) {
        ucx_list_free_content(parts, (ucx_destructor)filepart_free);
        ucx_list_free(parts);
        return;
    }
    
    FilePart *file_parts = calloc(count, sizeof(FilePart));
    size_t i = 0;
    UCX_FOREACH(elm, parts) {
        FilePart *p = elm->data;
        file_parts[i] = *p;
        free(p);
        i++;
    }
    
    ucx_list_free(parts);
    
    res->parts = file_parts;
    res->numparts = count;
}

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, "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(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;
                }
            }
        } 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
    UcxMapIterator i = ucx_map_iterator(db->resources);
    LocalResource *res;
    UCX_MAP_FOREACH(key, res, i) {
        // <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->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->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;
            }
        }
        
        // </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
    i = ucx_map_iterator(db->conflict);
    UCX_MAP_FOREACH(key, res, i) {
        // <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) {
    ucx_map_free_content(db->resources, (ucx_destructor)local_resource_free);
    ucx_map_free_content(db->conflict, (ucx_destructor)local_resource_free);
    ucx_map_free(db->resources);
    ucx_map_free(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) {
        ucx_buffer_free(res->cached_tags);
    }
    if(res->tags_hash) {
        free(res->tags_hash);
    }
    free(res);
}

static char* nullstrdup(const char *s) {
    return s ? strdup(s) : NULL;
}

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;
    
    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(sstr_t));
        for(int i=0;i<xattr->nattr;i++) {
            xattr->names[i] = strdup(src->xattr->names[i]);
            xattr->values[i] = sstrdup(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);
    
    if(src->parts) {
        newres->numparts = src->numparts;
        newres->parts = calloc(src->numparts, sizeof(FilePart));
        for(int i=0;i<newres->numparts;i++) {
            FilePart s = src->parts[i];
            FilePart p;
            p.block = s.block;
            p.hash = nullstrdup(s.hash);
            p.etag = nullstrdup(s.etag);
            newres->parts[i] = p;
        }
    }
    
    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);
}

UcxMap* create_hash_index(SyncDatabase *db) {
    UcxMap *hmap = ucx_map_new(db->resources->count + 64);
    
    UcxMapIterator i = ucx_map_iterator(db->resources);
    UcxKey key;
    LocalResource *res;
    UCX_MAP_FOREACH(key, res, i) {
        if(res->hash) {
            ucx_map_cstr_put(hmap, res->hash, res);
        }
    }
    
    return hmap;
}

mercurial