Tue, 09 Sep 2025 16:01:30 +0200
move resource_get_remote_change to a separate source file
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2025 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 "pull.h" #include "syncdir.h" #include <libidav/utils.h> #include <errno.h> #include <string.h> /* * strcmp version that works with NULL pointers */ static int nullstrcmp(const char *s1, const char *s2) { if(!s1 && s2) { return -1; } if(s1 && !s2) { return 1; } if(!s1 && !s2) { return 0; } return strcmp(s1, s2); } static char* nullstrdup(const char *s) { return s ? strdup(s) : NULL; } RemoteChangeType resource_get_remote_change( CmdArgs *a, DavResource *res, SyncDirectory *dir, SyncDatabase *db) { DavBool update_db = FALSE; char *etag = dav_get_string_property(res, "D:getetag"); if(!etag) { fprintf(stderr, "Error: resource %s has no etag\n", res->path); return REMOTE_NO_CHANGE; } char *hash = sync_get_content_hash(res); DavBool issplit = dav_get_property(res, "idav:split") ? TRUE : FALSE; if(issplit) { util_remove_trailing_pathseparator(res->path); } DavBool iscollection = res->iscollection && !issplit; RemoteChangeType type = cmd_getoption(a, "conflict") ? REMOTE_CHANGE_MODIFIED : REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; LocalResource *local = cxMapGet(db->resources, dav_resource_path_key(res)); char *local_path = syncdir_create_local_path(dir, res->path); char *link = SYNC_SYMLINK(dir) ? dav_get_string_property_ns(res, DAV_PROPS_NS, "link") : NULL; SYS_STAT s; DavBool exists = 1; if(sys_stat(local_path, &s)) { if(errno != ENOENT) { fprintf(stderr, "Cannot stat file: %s\n", local_path); free(local_path); return REMOTE_NO_CHANGE; } exists = 0; } RemoteChangeType ret = REMOTE_NO_CHANGE; if(iscollection) { if(!exists) { ret = REMOTE_CHANGE_MKDIR; } else if(local && S_ISDIR(s.st_mode)) { local->isdirectory = 1; // make sure isdirectory is set } else { // set change to REMOTE_CHANGE_MKDIR, which will fail later ret = REMOTE_CHANGE_MKDIR; } } else if(local) { DavBool nochange = FALSE; if(SYNC_SYMLINK(dir) && nullstrcmp(link, local->link_target)) { ret = REMOTE_CHANGE_LINK; nochange = TRUE; if(local->link_target) { LocalResource *local2 = local_resource_new(dir, db, local->path); if(type == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED && nullstrcmp(local->link_target, local2->link_target)) { ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; } local_resource_free(local2); if(!nullstrcmp(link, local->link_target)) { ret = REMOTE_NO_CHANGE; update_db = TRUE; } } } else if(issplit && local->hash && hash) { if(!strcmp(local->hash, hash)) { // resource is already up-to-date on the client nochange = TRUE; } } else if(local->etag) { cxstring e = cx_str(etag); if(cx_strprefix(e, CX_STR("W/"))) { e = cx_strsubs(e, 2); } if(!strcmp(e.ptr, local->etag)) { // resource is already up-to-date on the client nochange = TRUE; } } if(!nochange) { if(!(exists && s.st_mtime != local->last_modified)) { type = REMOTE_CHANGE_MODIFIED; } ret = type; } } else if(link) { // new file is a link ret = REMOTE_CHANGE_LINK; if(exists && type == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) { // a file with the same name already exists // if it is a link, compare the targets LocalResource *local2 = local_resource_new(dir, db, res->path); if(local2) { if(local2->link_target) { if(strcmp(link, local2->link_target)) { ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; } } else { ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; } local_resource_free(local2); } } } else if(exists) { ret = type; } else { ret = REMOTE_CHANGE_NEW; } // if hashing is enabled we can compare the hash of the remote file // with the local file to test if a file is really modified char *update_hash = NULL; if (!iscollection && !link && (ret == REMOTE_CHANGE_MODIFIED || ret == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) && exists && hash && !dir->pull_skip_hashing) { // because rehashing a file is slow, there is a config element for // disabling this (pull-skip-hashing) char *local_hash = util_file_hash(local_path); if(local_hash) { if(!strcmp(hash, local_hash)) { ret = REMOTE_NO_CHANGE; update_db = TRUE; update_hash = local_hash; // if local already exists, update the hash here // because it is possible that there are metadata updates // and in this case the db will updated later and needs // the current hash if(local) { if(local->hash) { free(local->hash); } local->hash = local_hash; } } else { free(local_hash); } } } // if a file is not modified, check if the metadata has changed while(ret == REMOTE_NO_CHANGE && local) { // check if tags have changed if(dir->tagconfig) { DavXmlNode *tagsprop = dav_get_property_ns(res, DAV_PROPS_NS, "tags"); CxList *remote_tags = NULL; if(tagsprop) { remote_tags = parse_dav_xml_taglist(tagsprop); } char *remote_hash = create_tags_hash(remote_tags); if(nullstrcmp(remote_hash, local->remote_tags_hash)) { ret = REMOTE_CHANGE_METADATA; } if(remote_hash) { free(remote_hash); } free_taglist(remote_tags); if(ret == REMOTE_CHANGE_METADATA) { break; } } // check if extended attributes have changed if((dir->metadata & FINFO_XATTR) == FINFO_XATTR) { DavXmlNode *xattr = dav_get_property_ns(res, DAV_PROPS_NS, "xattributes"); char *xattr_hash = get_xattr_hash(xattr); if(nullstrcmp(xattr_hash, local->xattr_hash)) { ret = REMOTE_CHANGE_METADATA; break; } } // check if finfo has changed DavXmlNode *finfo = dav_get_property_ns(res, DAV_PROPS_NS, "finfo"); if((dir->metadata & FINFO_MODE) == FINFO_MODE) { FileInfo f; finfo_get_values(finfo, &f); if(f.mode_set && f.mode != local->mode) { ret = REMOTE_CHANGE_METADATA; break; } } break; } // if update_db is set, a file was modified on the server, but we already // have the file content, but we need to update the db if(ret == REMOTE_NO_CHANGE && update_db) { if(!local) { local = calloc(1, sizeof(LocalResource)); local->path = strdup(res->path); cxMapPut(db->resources, cx_hash_key_str(local->path), local); } // update local res SYS_STAT statdata; if(!sys_stat(local_path, &statdata)) { sync_set_metadata_from_stat(local, &statdata); } else { fprintf(stderr, "stat failed for file: %s : %s", local_path, strerror(errno)); } local_resource_set_etag(local, etag); if(!local->hash) { local->hash = update_hash; } // else: hash already updated if(link) { free(local->link_target); local->link_target = link; } } free(local_path); return ret; }