/* cache.c: directory and file cache. Copyright (C) 2006, 2007, 2008, 2009 Werner Baumann This file is part of davfs2. davfs2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. davfs2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with davfs2; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #ifdef HAVE_DIRENT_H #include #endif #include #include #ifdef HAVE_FCNTL_H #include #endif #include #ifdef HAVE_LIBINTL_H #include #endif #include #include #include #ifdef HAVE_SYSLOG_H #include #endif #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_UTIME_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #include #include #include #include #include #include "defaults.h" #include "mount_davfs.h" #include "webdav.h" #include "cache.h" #include "provide.h" #ifdef ENABLE_NLS #define _(String) gettext(String) #else #define _(String) String #endif /* Private constants */ /*===================*/ #define MIN_FREE_SPACE 5000000 /* Constants describing different types of XML elements and attributes in cache index file. */ enum { ROOT = 1, BACKUP, DDIR, REG, PATH, NAME, CACHE_PATH, SIZE, MODE, UID, GID, ATIME, MTIME, CTIME, SMTIME, ETAG, MIME, DIRTY, REMOTE_EXISTS, END }; static const char* const type[] = { [ROOT] = "root", [BACKUP] = "backup", [DDIR] = "dir", [REG] = "reg", [PATH] = "path", [NAME] = "name", [CACHE_PATH] = "cache_path", [SIZE] = "size", [MODE] = "mode", [UID] = "uid", [GID] = "gid", [ATIME] = "atime", [MTIME] = "mtime", [CTIME] = "ctime", [SMTIME] = "smtime", [ETAG] = "etag", [MIME] = "mime", [DIRTY] = "dirty", [REMOTE_EXISTS] = "remote_exists", [END] = NULL }; /* Private global variables */ /*==========================*/ /* we don't want to flood the event log screen */ static int event_sent_up_quota_reached = 0; static int event_sent_down_quota_reached = 0; static int event_sent_max_files_per_folder_reached = 0; /* Number of nodes. */ static int nnodes; /* Root node of the directory cache. */ static dav_node *root; /* Directory for local backups. */ static dav_node *backup; /* A hash table to store the nodes. The hash is computed from the pointer to the node, which is also the node number. */ static dav_node **table; /* Size of the hash table. */ static size_t table_size; /* Number of files in list changed. */ static int nchanged; /* List of nodes, that have been changed and the changes must be saved back to the server. */ static dav_node_list_item *changed; /* List of nodes, which must be uploaded again. */ static dav_backup_list_item *backuped; /* Maximum count of upload attempts for every backuped node. */ #define BACKUP_MAX_UPLOADS 5 /* Minimum duration between uploads of a backuped node. */ #define BACKUP_MIN_INTERVAL 60 /* How long results of PROPFIND for directories are considered valid. */ static time_t dir_refresh; /* Wait that many seconds from last file access before doing a new GET If-Modiefied request. */ static time_t file_refresh; /* Upload file that many seconds after closing. */ static time_t delay_upload; /* Optimize file updates for graphical user interfaces = use PROPFIND to get the Last-Modified-dates for the whole directory instead of GET If-Modified-Since for single files. */ static int gui_optimize; /* Time interval to wait, before a directory is updated. Usually equal to dir_refresh, but will be varied in case the connection timed out.*/ static time_t retry; /* Minimum retry time. */ static time_t min_retry; /* Maximum retry time. */ static time_t max_retry; /* Refresh locks this much seconds before they time out. */ static time_t lock_refresh; /* Defaults for file ownership and mode. */ static uid_t default_uid; static gid_t default_gid; static mode_t default_file_mode; static mode_t default_dir_mode; static mode_t file_umask; static mode_t dir_umask; /* Directory for cached files and directories. */ static char *cache_dir; /* Maximum cache size. If open files require more space, this will be ignored. */ unsigned long long max_cache_size; /* Actual cache size. */ unsigned long long cache_size; /* AVM: Maximum files in the cache. With open files / files to sync the limit can be exceeded. */ int max_cache_files = DAV_CACHE_FILES; /* AVM: Actual cache file counter. */ int cache_file_count = 0; /* Alignment boundary of dav_node in byte. Used to compute a hash value and file numbers from node pointers. */ static size_t alignment; /* Pointers that will be set by dav_register_kernel_interface(), which must be called by the kernel-interface. Initialized to point to dummies as long as dav_register_kernel_interface() has not been called. */ /* A call back functions, that writes one direntry. The dummy returns EIO. Registering a working function is essential for mount.davfs. */ static off_t write_dir_entry_dummy(int fd, off_t off, const dav_node *node, const char *name) { return -1; } static dav_write_dir_entry_fn write_dir_entry = write_dir_entry_dummy; /* Points to a flag in the kernel interface module. If set to 1, at the end of the upcall the kernel dentries will be flushed. */ static int flush_dummy; static int *flush = &flush_dummy; /* Whether to create debug messages. */ static int debug; /* Buffer for xml_cdata callback. */ static char *xml_data = NULL; //Lock for all important operations, like executing fuse calls and //uploading files to the webdav server. pthread_mutex_t* mutex_allops = NULL; //Do not update storage infos within 30 seconds. time_t prov_update_time = 0; #define PROVIDE_UPDATE_TIMEOUT 30 // Hostname (AVM FIX T-Online) static char *host = NULL; /* Private function prototypes and inline functions */ /*==================================================*/ /* Node operations. */ static void upload_finished_of_backup(dav_node* node); static dav_node* get_backup_node(void); static void upload_failed_of_backup(dav_node* node); static void remove_node_from_backuped(dav_node* node, const char* node_path); static void add_node(dav_node *parent, dav_props *props); static void add_to_changed(dav_node *node); static inline void attr_from_cache_file(dav_node *node) { struct stat st; if (!node->cache_path || stat(node->cache_path, &st) != 0) return; if(is_filetranfer_inprogress(node->path)==1){ //Do not update node-size while download is in progress. //st_size only contains the current filesize, which is not //up-to-date after a few moments. if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "attr_from_cache_file - file transfer in progress: %s", node->path); } else{ off_t old_size = node->size; node->size = st.st_size; cache_size += node->size - old_size; } node->atime = (st.st_atime > node->atime) ? st.st_atime : node->atime; node->mtime = (st.st_mtime > node->mtime) ? st.st_mtime : node->mtime; } static off_t write_dir(dav_node *dir, int fd); static void backup_node(dav_node *orig); static void clean_tree(dav_node *node, int upload); static void delete_node(dav_node *node); static void delete_tree(dav_node *node); static inline time_t get_upload_time(dav_node *node) { dav_node_list_item *item = changed; while (item && item->node != node) item = item->next; if (!item) return 0; return item->save_at; } static int move_dir(dav_node *src, dav_node *dst, dav_node *dst_parent, const char *dst_name); static int move_dirty(dav_node *src, dav_node *dst, dav_node *dst_parent, const char *dst_name); static int move_reg(dav_node *src, dav_node *dst, dav_node *dst_parent, const char *dst_name); static dav_node * new_node(dav_node *parent, mode_t mode); static void remove_from_changed(dav_node *node); static void remove_from_table(dav_node *node); static void remove_from_tree(dav_node *node); static void remove_node(dav_node *node); static inline void set_upload_time(dav_node *node) { dav_node_list_item *item = changed; while (item && item->node != node) item = item->next; if (item) item->save_at = time(NULL) + delay_upload; } static int update_directory(dav_node *dir, time_t refresh); static int update_node(dav_node *node, dav_props *props); static void update_path(dav_node *node, const char *src_path, const char *dst_path); /* Get information about node. */ static int exists(const dav_node *node); static inline dav_node * get_child(const dav_node *parent, const char *name) { dav_node *node = parent->childs; while (node && strcmp(name, node->name) != 0) node = node->next; return node; } static dav_handle * get_file_handle(dav_node * node, int fd, int accmode, pid_t pid, pid_t pgid); static int has_permission(const dav_node *node, uid_t uid, int how); static inline int is_backup(const dav_node *node) { return (node == backup || node->parent == backup); } static int is_busy(const dav_node *node); static inline int is_cached(dav_node *node) { return (S_ISREG(node->mode) && node->cache_path); } static inline int is_created(const dav_node *node) { return (S_ISREG(node->mode) && node->cache_path && !node->remote_exists && node->parent && node->parent != backup); } static inline int is_dir(const dav_node *node) { return S_ISDIR(node->mode); } static inline int is_dirty(const dav_node *node) { return (S_ISREG(node->mode) && node->dirty); } static inline int is_locked(const dav_node *node) { return (S_ISREG(node->mode) && node->lock_expire); } static inline int is_open(const dav_node *node) { return (node->handles != NULL); } static inline int is_open_write(const dav_node *node) { dav_handle *fh = node->handles; while (fh) { if (fh->flags == O_RDWR || fh->flags == O_WRONLY) return 1; fh = fh->next; } return 0; } static inline int is_reg(const dav_node *node) { return S_ISREG(node->mode); } static int is_valid(const dav_node *node); /* Cache file functions. */ static void close_fh(dav_node *node, dav_handle *fh); static int create_cache_file(dav_node *node); static int create_dir_cache_file(dav_node *node); static inline void delete_cache_file(dav_node *node) { if (node->cache_path) { if(is_reg(node) && debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "deleting cache file: %s", node->path); remove(node->cache_path); free(node->cache_path); node->cache_path = NULL; node->dirty = 0; if (is_reg(node)) { cache_size -= node->size; if (cache_file_count > 0) cache_file_count--; // AVM } } } static inline void set_cache_file_times(dav_node *node) { if (!node->cache_path) return; struct utimbuf t; t.actime = node->atime; t.modtime = node->mtime; utime(node->cache_path, &t); } static int open_file(int *fd, dav_node *node, int flags, pid_t pid, pid_t pgid, uid_t uid); static int update_cache_file(dav_node *node); int count_cache_files(void); // AVM /* Permanent cache maintenance. */ static void check_cache_dir(const char *dir, const char *host, const char *path, const char *mpoint, const char *username); static void clean_cache(void); static void parse_index(void); static int write_node(dav_node *node, FILE *file, const char *indent); static int xml_cdata(void *userdata, int state, const char *cdata, size_t len); static int xml_end_backup(void *userdata, int state, const char *nspace, const char *name); static int xml_end_date(void *userdata, int state, const char *nspace, const char *name); static int xml_end_decimal(void *userdata, int state, const char *nspace, const char *name); static int xml_end_dir(void *userdata, int state, const char *nspace, const char *name); static int xml_end_mode(void *userdata, int state, const char *nspace, const char *name); static int xml_end_reg(void *userdata, int state, const char *nspace, const char *name); static int xml_end_root(void *userdata, int state, const char *nspace, const char *name); static int xml_end_size(void *userdata, int state, const char *nspace, const char *name); static int xml_end_string(void *userdata, int state, const char *nspace, const char *name); static int xml_end_string_old(void *userdata, int state, const char *nspace, const char *name); static int xml_start_backup(void *userdata, int parent, const char *nspace, const char *name, const char **atts); static int xml_start_date(void *userdata, int parent, const char *nspace, const char *name, const char **atts); static int xml_start_decimal(void *userdata, int parent, const char *nspace, const char *name, const char **atts); static int xml_start_dir(void *userdata, int parent, const char *nspace, const char *name, const char **atts); static int xml_start_mode(void *userdata, int parent, const char *nspace, const char *name, const char **atts); static int xml_start_reg(void *userdata, int parent, const char *nspace, const char *name, const char **atts); static int xml_start_root(void *userdata, int parent, const char *nspace, const char *name, const char **atts); static int xml_start_size(void *userdata, int parent, const char *nspace, const char *name, const char **atts); static int xml_start_string(void *userdata, int parent, const char *nspace, const char *name, const char **atts); /* Auxiliary. */ static size_t test_alignment(void); #define AVM_QUOTA_TOLERANCE 200000 #define AVM_FILESIZE_UNKNOWN 1 #define AVM_MAX_SIZE 0xffffffffffffffffULL #define AVM_PARTIAL_GET_TOLERANCE_MIN 128000 #define AVM_PARTIAL_GET_TOLERANCE_MID 512000 #define AVM_PARTIAL_GET_TOLERANCE_MAX 1024000 void avm_event_log(int event_id, const char* str) { size_t cmd_len = 48 + strlen(str); char* event_cmd = calloc(1, cmd_len); if(!event_cmd) return; snprintf(event_cmd, cmd_len, "/sbin/eventadd %d \"%s\"", event_id, str); system(event_cmd); free(event_cmd); } static int download_quota_ok(const quota_context *quota, size_t filesize) { __uint64_t download_quota_left = AVM_MAX_SIZE; __uint64_t down_quota_avail = (quota->download_avail > AVM_QUOTA_TOLERANCE) ? (quota->download_avail - AVM_QUOTA_TOLERANCE) : 0; __uint64_t tr_quota_avail = (quota->traffic_avail > AVM_QUOTA_TOLERANCE) ? (quota->traffic_avail - AVM_QUOTA_TOLERANCE) : 0; if(quota->download_avail > 0) { /* server delivers a download quota */ if(down_quota_avail > quota->download_used) download_quota_left = down_quota_avail - quota->download_used; else download_quota_left = 0; } else if(quota->traffic_avail > 0) { /* server delivers a traffic quota */ if(tr_quota_avail > quota->traffic_used) download_quota_left = tr_quota_avail - quota->traffic_used; else download_quota_left = 0; } return (filesize <= download_quota_left); } static int upload_quota_ok(const quota_context *quota, size_t filesize) { __uint64_t upload_quota_left = AVM_MAX_SIZE; __uint64_t storage_quota_left = AVM_MAX_SIZE; __uint64_t up_quota_avail = (quota->upload_avail > AVM_QUOTA_TOLERANCE) ? (quota->upload_avail - AVM_QUOTA_TOLERANCE) : 0; __uint64_t tr_quota_avail = (quota->traffic_avail > AVM_QUOTA_TOLERANCE) ? (quota->traffic_avail - AVM_QUOTA_TOLERANCE) : 0; __uint64_t st_quota_avail = (quota->storage_available > AVM_QUOTA_TOLERANCE) ? (quota->storage_available - AVM_QUOTA_TOLERANCE) : 0; __uint64_t max_filesize = (quota->max_filesize > AVM_QUOTA_TOLERANCE) ? (quota->max_filesize - AVM_QUOTA_TOLERANCE) : 0; if(quota->upload_avail > 0) { /* server delivers an upload quota */ if(up_quota_avail > quota->upload_used) upload_quota_left = up_quota_avail - quota->upload_used; else upload_quota_left = 0; } else if(quota->traffic_avail > 0) { /* server delivers a traffic quota */ if(tr_quota_avail > quota->traffic_used) upload_quota_left = tr_quota_avail - quota->traffic_used; else upload_quota_left = 0; } if(quota->storage_available > 0) { /* server delivers a storage quota */ if(st_quota_avail > quota->storage_used) storage_quota_left = st_quota_avail - quota->storage_used; else storage_quota_left = 0; } return (filesize <= upload_quota_left) && (filesize <= storage_quota_left) && ((quota->max_filecount == 0) || quota->storage_filecount < quota->max_filecount) && ((quota->max_filesize == 0) || filesize <= max_filesize); } /* Public functions */ /*==================*/ void dav_test_connection(const dav_args *args, const char *mpoint) { debug = args->debug & DAV_DBG_CACHE; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "testing webdav connection"); int ret = dav_test_webdav_connection(); if (ret == EAGAIN) { error(0, 0, _("server temporarily unreachable;\n" "mounting anyway")); } else if (ret) { provide_set_string(CONNECTION_STATE, dav_get_webdav_error()); provide_destroy(); const char* mount_err = dav_get_webdav_error(); dav_close_webdav(1); if(mount_err && strcmp(mount_err, "200 OK")) error(EXIT_FAILURE, 0, _("%s"), mount_err); else if(ret == EINVAL) error(EXIT_FAILURE, 0, _("%s"), "the server does not support WebDAV"); else error(EXIT_FAILURE, 0, _("%s"), "Unbekannt"); } else { // AVM check T-ONLINE authentication not done in the upper request if (args->host && strstr(args->host, "t-online.de")) { ret = dav_test_webdav_propfind_quota(dav_conv_to_server_enc(args->path)); if (ret == EAGAIN) { error(0, 0, _("server temporarily unreachable;\n" "mounting anyway")); } else if (ret) { // Error provide_set_string(CONNECTION_STATE, dav_get_webdav_error()); provide_destroy(); const char* mount_err = dav_get_webdav_error(); dav_close_webdav(1); if(mount_err && strcmp(mount_err, "200 OK")) error(EXIT_FAILURE, 0, _("%s"), mount_err); else if(ret == EINVAL) error(EXIT_FAILURE, 0, _("%s"), "the server does not support WebDAV"); else error(EXIT_FAILURE, 0, _("%s"), "Unbekannt"); } } // AVM check T-ONLINE provide_set_string(CONNECTION_STATE, "connected"); } } void dav_init_cache(const dav_args *args, const char *mpoint) { mutex_allops = calloc(1, sizeof(pthread_mutex_t)); pthread_mutex_init(mutex_allops, NULL); debug = args->debug & DAV_DBG_CACHE; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Initializing cache"); alignment = test_alignment(); if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Alignment of dav_node: %i", alignment); default_uid = args->uid; default_gid = args->gid; default_file_mode = args->file_mode; default_dir_mode = args->dir_mode; file_umask = args->file_umask; dir_umask = args->dir_umask; table_size = args->table_size; table = ne_calloc(sizeof(*table) * table_size); dir_refresh = args->dir_refresh; file_refresh = args->file_refresh; delay_upload = args->delay_upload; gui_optimize = args->gui_optimize; retry = dir_refresh; min_retry = args->retry; max_retry = args->max_retry; lock_refresh = args->lock_refresh; max_cache_files = args->cache_files; if (host) // AVM ne_free(host); host = ne_strdup(args->host); if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Checking cache directory"); max_cache_size = args->cache_size * 0x100000; check_cache_dir(args->cache_dir, args->host, args->path, mpoint, args->username); if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " %s", cache_dir); root = new_node(NULL, default_dir_mode); if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Reading stored cache data"); parse_index(); root->name = ne_strdup(""); root->path = dav_conv_to_server_enc(args->path); root->mode = default_dir_mode; if (!backup) backup = new_node(root, S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); backup->name = ne_strdup(args->backup_dir); backup->mode = S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; clean_cache(); count_cache_files(); // AVM int ret = update_directory(root, 0); if (ret == EAGAIN) { root->utime = 0; ret = update_directory(root, 0); } if (ret == EAGAIN) { error(0, 0, _("connection timed out two times;\n" "trying one last time")); root->utime = 0; ret = update_directory(root, 0); if (!ret) printf(_("Last try succeeded.\n")); } if (ret == EAGAIN) { error(0, 0, _("server temporarily unreachable;\n" "mounting anyway")); } else if (ret) { provide_set_string(CONNECTION_STATE, dav_get_webdav_error()); provide_destroy(); const char* mount_err = dav_get_webdav_error(); dav_close_webdav(1); if(mount_err && strcmp(mount_err, "200 OK")) error(EXIT_FAILURE, 0, _("%s"), mount_err); else if(ret == EINVAL) error(EXIT_FAILURE, 0, _("%s"), "the server does not support WebDAV"); else error(EXIT_FAILURE, 0, _("%s"), "Unbekannt"); } else{ provide_set_string(CONNECTION_STATE, "connected"); provide_set_uint64(CACHE_STORAGE_AVAIL, get_avail_cache_space(), 0); //get storage quota from server, to provide them with a shared memory dav_quota(root->path, NULL, NULL); } } void dav_close_cache(int got_sigterm) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Closing cache"); mutex_lock(mutex_allops); write_dir_entry = &write_dir_entry_dummy; flush = &flush_dummy; //clean_tree(root, !got_sigterm); clean_tree(root, 0); //AVM char *new_index = ne_concat(cache_dir, "/", DAV_INDEX, ".new", NULL); if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Creating index %s.", new_index); FILE *new_file = fopen(new_index, "w"); if (new_file) { int ret = fprintf(new_file, "\n"); if (ret >= 0) ret = write_node(root, new_file, ""); fclose(new_file); if (ret >= 0) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Replacing old index"); char *old_index = ne_concat(cache_dir, "/", DAV_INDEX, NULL); if (rename(new_index, old_index) != 0) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("can't replace %s with %s"), old_index, new_index); free(old_index); } else { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("error writing new index file %s"), new_index); } } else { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("can't create new index file for %s"), cache_dir); } free(new_index); mutex_unlock(mutex_allops); if (host) { // AVM ne_free(host); host = NULL; } } size_t dav_register_kernel_interface(dav_write_dir_entry_fn write_fn, int *flush_flag, unsigned int *blksize) { if (write_fn) write_dir_entry = write_fn; if (flush_flag) flush = flush_flag; if (blksize) { struct stat st; if (stat(cache_dir, &st) == 0) { *blksize = st.st_blksize; } else { *blksize = 4096; } } return alignment; } int dav_tidy_cache(void) { if (debug && nchanged) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "tidy: %i of %i nodes changed", nchanged, nnodes); } mutex_lock(mutex_allops); resize_cache(); static dav_node_list_item *item = NULL; dav_node *node = NULL; char* node_path = 0; quota_context quotas; dav_node_list_item *next_item = changed; while (next_item && next_item != item) next_item = next_item->next; if(!next_item) item = changed; if(item){ next_item = item->next; node = item->node; } if(node){ if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "path: %s, size: %llu", node->path, node->size); node_path = strdup(node->path); if ((is_dirty(node) || is_created(node)) && !is_open_write(node) && !is_backup(node) && item->save_at && item->save_at <= time(NULL)) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "tidy: %s", node->path); provide_get_quota("as); if(!upload_quota_ok("as, node->size)) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "insufficient quota for tidying cache: filesize: %llu, st_av: %llu, st_us: %llu, " "up_av: %llu, up_us: %llu, dw_av: %llu, dw_us: %llu, tr_av: %llu, tr_us: %llu", node->size, quotas.storage_available, quotas.storage_used, quotas.upload_avail, quotas.upload_used, quotas.download_avail, quotas.download_used, quotas.traffic_avail, quotas.traffic_used); if(!event_sent_up_quota_reached) { avm_event_log(AVM_EVENT_UPLOAD_QUOTA_REACHED, node->path); event_sent_up_quota_reached = 1; } delete_cache_file(node->parent); node->parent->utime = 0; remove_node(node); *flush = 1; free(node_path); mutex_unlock(mutex_allops); return EDQUOT; } int set_execute = -1; if (is_created(node) && node->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) set_execute = 1; //Unlock node list while uploading file to webdav server. int ret = dav_put(node->path, node->cache_path, &node->remote_exists, &node->lock_expire, &node->etag, &node->smtime, &node->mime_type, set_execute, node->size/*AVM*/); //After locking the node list, check if the node is still valid. if(ret == ENODELOST || !node || !exists(node) || !node->parent || !node->path){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "dav_tidy_cache: uploaded node is lost: %s", node_path); //Uploaded node is lost, try to remove it from the server. time_t lock_expire = 0; dav_delete(node_path, &lock_expire); free(node_path); mutex_unlock(mutex_allops); return ENOENT; } if (!ret) { node->utime = time(NULL); node->dirty = 0; if (dav_unlock(node->path, &node->lock_expire) == 0) remove_from_changed(node); } else { if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_tidy_cache: file upload failed (%d): %s", ret, node_path); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " %s", dav_get_webdav_error()); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " EACCES=%d, EINVAL=%d, ENOENT=%d, EPERM=%d, ENOSPC=%d, EEXIST=%d, EIO=%d, ENODELOST=%d" ,EACCES, EINVAL, ENOENT, EPERM, ENOSPC, EEXIST, EIO, ENODELOST); // AVM Help } if (ret == EACCES || ret == EINVAL || ret == ENOENT || ret == EPERM || ret == ENOSPC || ret == EEXIST || ret == EIO) { //EIO added, because an upload can fail with //"EIO: 500 InternalServer Error" and davfs2 will try //the upload again and again ... dav_unlock(node->path, &node->lock_expire); delete_cache_file(node->parent); node->parent->utime = 0; remove_node(node); *flush = 1; } } } else if (is_locked(node) && !is_dirty(node) && !is_created(node) && !is_open_write(node)) { if (dav_unlock(node->path, &node->lock_expire) == 0) remove_from_changed(node); } else if (is_locked(node) && node->lock_expire < (time(NULL) + lock_refresh)) { dav_lock_refresh(node->path, &node->lock_expire); } if(node_path) free(node_path); item = next_item; if (item){ mutex_unlock(mutex_allops); return 1; } item = changed; } node = get_backup_node(); if(node){ provide_get_quota("as); if(!upload_quota_ok("as, node->size)) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "insufficient quota for uploading backuped node: filesize: %llu, st_av: %llu, st_us: %llu, " "up_av: %llu, up_us: %llu, dw_av: %llu, dw_us: %llu, tr_av: %llu, tr_us: %llu", node->size, quotas.storage_available, quotas.storage_used, quotas.upload_avail, quotas.upload_used, quotas.download_avail, quotas.download_used, quotas.traffic_avail, quotas.traffic_used); if(!event_sent_up_quota_reached) { avm_event_log(AVM_EVENT_UPLOAD_QUOTA_REACHED, node->path); event_sent_up_quota_reached = 1; } upload_failed_of_backup(node); mutex_unlock(mutex_allops); return EDQUOT; } node_path = strdup(node->path); int set_execute = -1; if (is_created(node) && node->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) set_execute = 1; //Unlock node list while uploading file to webdav server. int ret = dav_put(node->path, node->cache_path, &node->remote_exists, &node->lock_expire, &node->etag, &node->smtime, &node->mime_type, set_execute, node->size/*AVM*/); //After locking the node list, check if the node is still valid. if(ret == ENODELOST || !node || !exists(node) || !node->parent || !node->path){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "dav_tidy_cache: uploaded node is lost: %s", node_path); remove_node_from_backuped(node, node_path); free(node_path); mutex_unlock(mutex_allops); return ENOENT; } dav_unlock(node->path, &node->lock_expire); if (!ret) { upload_finished_of_backup(node); avm_event_log(3353, node_path); } else { if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_tidy_cache: file upload failed: %s, ret=%d", node_path, ret); /*AVM*/ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " %s", dav_get_webdav_error()); } upload_failed_of_backup(node); } free(node_path); } mutex_unlock(mutex_allops); return 0; } /* Upcalls from the kernel. */ int dav_access(dav_node *node, uid_t uid, int how) { if (!is_valid(node)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "access %s", node->path); if (!has_permission(node, uid, how)) return EACCES; return 0; } int dav_close(dav_node *node, int fd, int flags, pid_t pid, pid_t pgid) { if (!exists(node)){ return ENOENT; } if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " close %s", node->path); dav_handle *fh = get_file_handle(node, fd, flags & O_ACCMODE, pid, pgid); if (!fh) return EBADF; close_fh(node, fh); if (!node->parent && node != root && !is_open(node)) { remove_from_table(node); delete_node(node); *flush = 1; return 0; } if (is_dir(node)) { node->atime = time(NULL); delete_cache_file(node); } else{ attr_from_cache_file(node); set_upload_time(node); } return 0; } int dav_create(dav_node **nodep, dav_node *parent, const char *name, uid_t uid, mode_t mode) { if (!is_valid(parent)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "create %s%s", parent->path, name); if (!is_dir(parent)) return ENOTDIR; if ((parent == root && strcmp(name, backup->name) == 0) || parent == backup) return EINVAL; if (!has_permission(parent, uid, X_OK | W_OK)) return EACCES; if (get_child(parent, name)) return EEXIST; struct passwd *pw = getpwuid(uid); if (!pw) return EINVAL; /* check upload quota - if there's no quota left for upload we don't need to create file in cache */ quota_context quotas; provide_get_quota("as); /* check filename length */ if((quotas.max_filenamelength != 0 && ((strlen(name) + strlen(parent->path)) > quotas.max_filenamelength))) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "creating file failed, filename too long"); return ENAMETOOLONG; /* EACCES; */ } /* check max number files in folder */ if(quotas.max_filesperfolder && ((parent->filecount + parent->nref) > quotas.max_filesperfolder)) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "creating file failed, too much nodes in folder (nodecount: %i, max_nodes: %i).", parent->filecount + parent->nref, quotas.max_filesperfolder); if(!event_sent_max_files_per_folder_reached) { avm_event_log(AVM_EVENT_MAX_FILES_PER_FOLDER_REACHED, parent->path); event_sent_max_files_per_folder_reached = 1; } return EACCES; } /* check upload quota */ if(!upload_quota_ok("as, AVM_FILESIZE_UNKNOWN)) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "insufficient quota for upload (3): st_av: %llu, st_us: %llu, " "up_av: %llu, up_us: %llu, dw_av: %llu, dw_us: %llu, tr_av: %llu, tr_us: %llu", quotas.storage_available, quotas.storage_used, quotas.upload_avail, quotas.upload_used, quotas.download_avail, quotas.download_used, quotas.traffic_avail, quotas.traffic_used); if(!event_sent_up_quota_reached) { avm_event_log(AVM_EVENT_UPLOAD_QUOTA_REACHED, name); event_sent_up_quota_reached = 1; } return EACCES; } char *name_conv = dav_conv_to_server_enc(name); char *path = ne_concat(parent->path, name_conv, NULL); free(name_conv); *nodep = new_node(parent, mode | S_IFREG); (*nodep)->path = path; (*nodep)->name = ne_strdup(name); (*nodep)->uid = uid; (*nodep)->gid = pw->pw_gid; int ret = create_cache_file(*nodep); if (!ret) ret = dav_lock(path, &(*nodep)->lock_expire, &(*nodep)->remote_exists); if (!ret) { (*nodep)->smtime = (*nodep)->mtime; if (!is_created(*nodep)) dav_head((*nodep)->path, &(*nodep)->etag, &(*nodep)->smtime, NULL, &(*nodep)->mime_type); (*nodep)->utime = (*nodep)->smtime; delete_cache_file(parent); *flush = 1; parent->mtime = (*nodep)->mtime; parent->ctime = (*nodep)->mtime; add_to_changed(*nodep); } else { remove_from_tree(*nodep); remove_from_table(*nodep); delete_node(*nodep); } return ret; } int dav_getattr(dav_node *node, uid_t uid) { if (!is_valid(node)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "getattr %s", node->path); if (node->parent && !has_permission(node->parent, uid, X_OK | R_OK)) return EACCES; if (is_dir(node)) { if (!node->utime) update_directory(node, retry); if (!node->cache_path) { if (create_dir_cache_file(node) != 0) return EIO; delete_cache_file(node); } } else if (is_open(node)) { attr_from_cache_file(node); } if (debug){ struct tm a_tmtime; localtime_r(&node->atime, &a_tmtime); struct tm m_tmtime; localtime_r(&node->mtime, &m_tmtime); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " size: %llu", node->size); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " atime: %02d.%02d.%04d %02d:%02d:%02d", a_tmtime.tm_mday, a_tmtime.tm_mon+1, a_tmtime.tm_year+1900, a_tmtime.tm_hour, a_tmtime.tm_min, a_tmtime.tm_sec); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " mtime: %02d.%02d.%04d %02d:%02d:%02d", m_tmtime.tm_mday, m_tmtime.tm_mon+1, m_tmtime.tm_year+1900, m_tmtime.tm_hour, m_tmtime.tm_min, m_tmtime.tm_sec); } return 0; } int dav_getxattr(dav_node *node, const char *name, char *buf, size_t *size, uid_t uid) { if (!is_valid(node)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "getxattr %s", node->path); if (node->parent != NULL && !has_permission(node->parent, uid, X_OK | R_OK)) return EACCES; if (strcmp(name, "user.mime_type") != 0 || !node->mime_type) return ENOTSUP; if (*size == 0) { *size = strlen(node->mime_type); } else if (strlen(node->mime_type) > *size) { return ERANGE; } else { *size = strlen(node->mime_type); strncpy(buf, node->mime_type, *size); } return 0; } int dav_listxattr(dav_node *node, char *buf, size_t *size, uid_t uid) { if (!is_valid(node)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "listxattr %s", node->path); if (node->parent != NULL && !has_permission(node->parent, uid, X_OK | R_OK)) return EACCES; if (!node->mime_type) return ENOTSUP; if (*size == 0) { *size = strlen("user.mime_type") + 1; } else if (strlen("user.mime_type") > *size) { return ERANGE; } else { *size = strlen("user.mime_type") + 1; strncpy(buf, "user.mime_type", *size - 1); *(buf + *size - 1) = '\0'; } return 0; } int dav_lookup(dav_node **nodep, dav_node *parent, const char *name, uid_t uid) { if (!is_valid(parent)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "lookup %s%s", parent->path, name); if (!is_dir(parent)) return ENOTDIR; if (!has_permission(parent, uid, X_OK | R_OK)) return EACCES; update_directory(parent, retry); *nodep = get_child(parent, name); if (*nodep == NULL){ update_directory(parent, file_refresh); *nodep = get_child(parent, name); } if (!*nodep) return ENOENT; if (is_dir(*nodep)) { if (!(*nodep)->utime) update_directory(*nodep, retry); //if (create_dir_cache_file(*nodep) != 0) // return EIO; } else if (is_open(*nodep)) { attr_from_cache_file(*nodep); } return 0; } int dav_mkdir(dav_node **nodep, dav_node *parent, const char *name, uid_t uid, mode_t mode) { if (!is_valid(parent)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "mkdir %s%s", parent->path, name); if (!is_dir(parent)) return ENOTDIR; if (parent == backup) return EINVAL; if (!has_permission(parent, uid, X_OK | W_OK)) return EACCES; quota_context quotas; provide_get_quota("as); /* check max number files in folder */ if(quotas.max_filesperfolder && ((parent->filecount + parent->nref) > quotas.max_filesperfolder)) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "creating dir failed, too much nodes in folder (nodecount: %i, max_nodes: %i).", parent->filecount + parent->nref, quotas.max_filesperfolder); if(!event_sent_max_files_per_folder_reached) { avm_event_log(AVM_EVENT_MAX_FILES_PER_FOLDER_REACHED, parent->path); event_sent_max_files_per_folder_reached = 1; } return EACCES; } update_directory(parent, retry); if (get_child(parent, name)) return EEXIST; struct passwd *pw = getpwuid(uid); if (!pw) return EINVAL; char *name_conv = dav_conv_to_server_enc(name); char *path = ne_concat(parent->path, name_conv, "/", NULL); free(name_conv); int ret = dav_make_collection(path); if (!ret) { *nodep = new_node(parent, mode | S_IFDIR); (*nodep)->path = path; (*nodep)->name = ne_strdup(name); (*nodep)->uid = uid; (*nodep)->gid = pw->pw_gid; (*nodep)->smtime = (*nodep)->mtime; (*nodep)->utime = (*nodep)->mtime; delete_cache_file(parent); *flush = 1; parent->mtime = (*nodep)->mtime; parent->ctime = (*nodep)->mtime; } else { free(path); } return ret; } int dav_open(int *fd, dav_node *node, int flags, pid_t pid, pid_t pgid, uid_t uid, int open_create) { if (!is_valid(node)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "open %s", node->path); if (flags & (O_EXCL | O_CREAT)) return EINVAL; int how; if ((O_ACCMODE & flags) == O_WRONLY) { how = W_OK; } else if ((O_ACCMODE & flags) == O_RDONLY) { how = R_OK; } else { how = R_OK | W_OK; } if (!open_create && !has_permission(node, uid, how)) return EACCES; if (is_dir(node)) { if ((how & W_OK) || (flags & O_TRUNC)) return EINVAL; update_directory(node, file_refresh); if (create_dir_cache_file(node) != 0) return EIO; node->atime = time(NULL); return open_file(fd, node, O_RDONLY, pid, pgid, uid); } int ret = 0; if ((O_ACCMODE & flags) == O_RDONLY) { ret = update_cache_file(node); if (!ret) ret = open_file(fd, node, flags & (O_ACCMODE | O_NONBLOCK), pid, pgid, uid); } else { if (!is_locked(node) && !is_backup(node)) ret = dav_lock(node->path, &node->lock_expire, &node->remote_exists); if (!ret && (flags & O_TRUNC)) { ret = create_cache_file(node); if (!ret) { ret = open_file(fd, node, flags & (O_ACCMODE | O_TRUNC | O_APPEND | O_NONBLOCK), pid, pgid, uid); } } else if (!ret) { ret = update_cache_file(node); if (!ret) ret = open_file(fd, node, flags & (O_ACCMODE | O_APPEND | O_NONBLOCK), pid, pgid, uid); } /*if (!ret) Samba frequently opens files read-/writeable without writing to them. Avoid uploading unchanged files. add_to_changed(node); */ } return ret; } int dav_read(ssize_t *len, dav_node * node, int fd, char *buf, size_t size, off_t offset) { if (!node || !exists(node) || (!node->parent && node != root) || !node->cache_path){ //Node is not or no more available. It can happen, if the //fuse call was paused in the read queue because of insufficient data. return ENOENT; } dav_handle *fh = get_file_handle(node, fd, 0, 0, 0); if (!fh) return EBADF; if (fh->flags == O_WRONLY) return EINVAL; unsigned int count = 0; struct stat st; if (stat(node->cache_path, &st) < 0) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "dav_read - stat failed on path: %s", node->cache_path); return ENODATA; } if((fh->flags & O_NONBLOCK) && debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_read: O_NONBLOCK is set"); //Try to get the part of file via the partial get method if it isn't in the cache right now. __uint64_t partial_get_offset = 0; if(size + offset > AVM_PARTIAL_GET_TOLERANCE_MID) partial_get_offset = size+offset-AVM_PARTIAL_GET_TOLERANCE_MID; if(!is_dir(node) && (st.st_size < partial_get_offset) && (st.st_size < node->size)) { int ret; if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "downloading part of file %s (requested offset+size: %llu, cache filesize: %llu, server filesize: %llu)", node->name, offset+size, st.st_size, node->size); size_t new_size = size; if(offset+size > node->size){ new_size = size-(offset+size-node->size); if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "using new bufsize: %d", new_size); } if((ret = dav_get_partial_file(node->path, buf, new_size, offset, len)) == 0) { // etag check here ? if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "received bytes: %d", *len); if (*len < size){ if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "memset bytes: %d", size-*len); memset(buf + *len, '\0', size - *len); } return 0; } if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "downloading part of file failed (ret: %i)", ret); if(fh->flags & O_NONBLOCK){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "cancel reading file, partial-get failed and O_NONBLOCK is set"); return EAGAIN; } } if(!is_dir(node) && (st.st_size < size+offset) && (st.st_size < node->size) && (fh->flags & O_NONBLOCK)) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "cancel reading file, insufficient data and O_NONBLOCK is set"); return EAGAIN; } //Try reading data from file. May be its download is still in progress and //not enough bytes are available. Still wait a little bit and try again. while(!is_dir(node) && (st.st_size < size+offset) && (st.st_size < node->size)) { // check download quota quota_context quotas; provide_get_quota("as); if(!download_quota_ok("as, node->size)) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "insufficient quota for download (1): filesize: %llu," " st_av: %llu, st_us: %llu, up_av: %llu, up_us: %llu, dw_av: %llu, dw_us: %llu, tr_av: %llu, tr_us: %llu", node->size, quotas.storage_available, quotas.storage_used, quotas.upload_avail, quotas.upload_used, quotas.download_avail, quotas.download_used, quotas.traffic_avail, quotas.traffic_used); if(!event_sent_down_quota_reached) { avm_event_log(AVM_EVENT_DOWNLOAD_QUOTA_REACHED, node->path); event_sent_down_quota_reached = 1; } return EACCES; } if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "cannot read file: insufficient data. " "file: %llu/%llu - read: %llu-%llu", st.st_size, node->size, offset, size+offset); // AVM FIX: do not check for new data so often => reduces the download speed (TCP receive windows runs full) //if(count>3){ if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "reading file suspend - insufficient data"); //insufficient data, reading from file will be suspended for a while return ENODATA; //} //else{ // //wait for incoming data // count++; // usleep(50000); //0.05 seconds // if (stat(node->cache_path, &st) < 0) // syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "dav_read - stat failed on path: %s", node->cache_path); //} } if (offset == 0 && is_dir(node) && !node->cache_path) { if (ftruncate(fh->fd, 0) != 0) return EIO; off_t sz = write_dir(node, fh->fd); if (sz <= 0) return EIO; node->size = sz; } *len = pread(fd, buf, size, offset); if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "read %i - leftover %d", *len, size - *len); if (*len < 0) return errno; if (*len < size) memset(buf + *len, '\0', size - *len); return 0; } int dav_remove(dav_node *parent, const char *name, uid_t uid) { if (!is_valid(parent)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "remove %s%s", parent->path, name); if (!is_dir(parent)) return ENOTDIR; if (!has_permission(parent, uid, X_OK | W_OK)) return EACCES; update_directory(parent, retry); dav_node *node = get_child(parent, name); if (!node) { delete_cache_file(parent); parent->utime = 0; *flush = 1; return ENOENT; } if (is_dir(node)) return EISDIR; if(is_filetranfer_inprogress(node->path)==1){ //Up- or Download of this file is in progress. if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "WARNING: file transfer in progress"); } int ret = 0; if (is_created(node)) { if (is_locked(node)) ret = dav_unlock(node->path, &node->lock_expire); } else if (!is_backup(node)) { ret = dav_delete(node->path, &node->lock_expire); if (ret == ENOENT) ret = 0; } if (ret) return ret; remove_from_tree(node); remove_from_changed(node); if (is_open(node)) { node->parent = NULL; } else { remove_from_table(node); delete_node(node); } delete_cache_file(parent); parent->mtime = time(NULL); parent->ctime = parent->mtime; *flush = 1; return 0; } int dav_rename(dav_node *src_parent, const char *src_name, dav_node *dst_parent, const char *dst_name, uid_t uid) { if (!is_valid(src_parent) || !is_valid(dst_parent)) return ENOENT; if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "rename %s%s", src_parent->path, src_name); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " into %s%s", dst_parent->path, dst_name); } if (!is_dir(src_parent) || !is_dir(dst_parent)) return ENOTDIR; if (is_backup(dst_parent)) return EINVAL; if (!has_permission(src_parent, uid, X_OK | W_OK) || !has_permission(dst_parent, uid, X_OK | W_OK)) return EACCES; update_directory(src_parent, retry); dav_node *src = get_child(src_parent, src_name); dav_node *dst = get_child(dst_parent, dst_name); if (!src) { delete_cache_file(src_parent); src_parent->utime = 0; *flush = 1; return ENOENT; } if (src == backup || (dst && is_backup(dst))) return EINVAL; if (dst) { // AVM Fix: Wenn das Zielverzeichnis schon existiert => Abbruch (sonst wird es ueberschrieben) return EEXIST; } if(is_filetranfer_inprogress(src->path)){ //Renaming of a uploading file or one of it's parent directories will cause a //uploading failure on the server -> HTTP/1.1 409 Conflict. if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "renaming aborted - file transfer in progress"); return EBUSY; } int ret; if (is_dir(src)) { ret = move_dir(src, dst, dst_parent, dst_name); } else { if (is_created(src) || is_backup(src)) { ret = move_dirty(src, dst, dst_parent, dst_name); } else { ret = move_reg(src, dst, dst_parent, dst_name); } } if (!ret) { if (src_parent != dst_parent) { remove_from_tree(src); delete_cache_file(src_parent); src_parent->mtime = time(NULL); src_parent->ctime = src_parent->mtime; src->parent = dst_parent; src->next = dst_parent->childs; dst_parent->childs = src; if (is_dir(src)) ++src->parent->nref; else if (is_reg(src)) { ++src->parent->filecount; } } delete_cache_file(dst_parent); dst_parent->mtime = time(NULL); dst_parent->ctime = dst_parent->mtime; *flush = 1; } return ret; } int dav_rmdir(dav_node *parent, const char *name, uid_t uid) { if (!is_valid(parent)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "rmdir %s%s", parent->path, name); if (!is_dir(parent)) return ENOTDIR; if (!has_permission(parent, uid, X_OK | W_OK)) return EACCES; update_directory(parent, retry); dav_node *node = get_child(parent, name); if (!node) { delete_cache_file(parent); parent->utime = 0; *flush = 1; return ENOENT; } if (node == backup) return EINVAL; if (!is_dir(node)) return ENOTDIR; if (node->childs) return ENOTEMPTY; int ret = dav_delete_dir(node->path); if (!ret) { remove_node(node); delete_cache_file(parent); parent->mtime = time(NULL); parent->ctime = parent->mtime; *flush = 1; } return ret; } int dav_root(dav_node **nodep, uid_t uid) { if (uid != 0) return EPERM; *nodep = root; return 0; } int dav_setattr(dav_node *node, uid_t uid, int sm, mode_t mode, int so, uid_t owner, int sg, gid_t gid, int sat, time_t atime, int smt, time_t mtime, int ssz, off_t size) { if (!is_valid(node)) return ENOENT; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "setattr %s", node->path); if (node->parent != NULL && !has_permission(node->parent, uid, X_OK)) return EACCES; if(is_filetranfer_inprogress(node->path)==1){ //Modifying a uploading file is not allowed. if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "setting file attributes aborted - file transfer in progress"); return EBUSY; } if (so) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " set owner to %i", owner); if (node == backup) return EINVAL; if (uid != 0 && (uid != owner || uid != node->uid)) return EPERM; if (!getpwuid(owner)) return EINVAL; } if (sg) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " set group to %i", gid); if (node == backup) return EINVAL; if (uid != node->uid && uid != 0) return EPERM; if (uid != 0) { struct passwd *pw = getpwuid(uid); if (!pw) return EPERM; if (pw->pw_gid != gid) { struct group *gr = getgrgid(gid); if (!gr) return EPERM; char **member = gr->gr_mem; while (*member != NULL && strcmp(*member, pw->pw_name) != 0) ++member; if (*member == NULL) return EPERM; } } if (!getgrgid(gid)) return EINVAL; } if (sm) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " set mode to %o", mode); if (node == backup) return EINVAL; if (uid != node->uid && uid != 0) return EPERM; if (is_dir(node) && (mode & dir_umask)) return EINVAL; if (is_reg(node) && (mode & file_umask)) return EINVAL; } if (sat || smt) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " set times"); if (uid != node->uid && uid != 0 && !has_permission(node, uid, W_OK)) return EPERM; } if (ssz) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " set size"); if (is_dir(node)) return EINVAL; if (uid != node->uid && uid != 0 && !has_permission(node, uid, W_OK)) return EPERM; int ret = 0; if (!is_locked(node) && !is_backup(node)) ret = dav_lock(node->path, &node->lock_expire, &node->remote_exists); if (!ret && size == 0) { ret = create_cache_file(node); } else if (!ret) { ret = update_cache_file(node); } if (!ret) ret = truncate(node->cache_path, size); if (ret) return ret; attr_from_cache_file(node); node->dirty = 1; add_to_changed(node); set_upload_time(node); } if (so) node->uid = owner; if (sg) node->gid = gid; if (sm) { if (!is_backup(node) && !is_created(node)) { int set_execute = -1; if ((node->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) && !(mode & (S_IXUSR | S_IXGRP | S_IXOTH))) set_execute = 0; if (!(node->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) set_execute = 1; if (set_execute != -1) { if (is_dirty(node) && !is_locked(node)) { int err = 0; time_t smtime = 0; char *etag = NULL; dav_head(node->path, &etag, &smtime, NULL, NULL); if (etag && node->etag && strcmp(etag, node->etag) != 0) err = EIO; if (smtime && smtime > node->smtime+1) err = EIO; if (etag) free(etag); if (err) return EIO; } dav_set_execute(node->path, set_execute); if (is_dirty(node)) dav_head(node->path, &node->etag, &node->smtime, NULL, &node->mime_type); } } node->mode = (node->mode & ~DAV_A_MASK) | mode; } if (sat) node->atime = atime; if (smt) node->mtime = mtime; if (sat || smt) set_cache_file_times(node); node->ctime = time(NULL); return 0; } __uint64_t get_free_cache_space(void) { struct statfs stat_buf; if(statfs(cache_dir, &stat_buf) == -1){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "statfs failed on cache partition: %s", strerror(errno)); return 0; } return 1.0 * stat_buf.f_bavail * stat_buf.f_bsize; } __uint64_t get_used_cache_space(void) { struct statfs stat_buf; if(statfs(cache_dir, &stat_buf) == -1){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "statfs failed on cache partition: %s", strerror(errno)); return 0; } return 1.0 * (stat_buf.f_blocks - stat_buf.f_bavail) * stat_buf.f_bsize; } __uint64_t get_avail_cache_space(void) { struct statfs stat_buf; if(statfs(cache_dir, &stat_buf) == -1){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "statfs failed on cache partition: %s", strerror(errno)); return 0; } return 1.0 * stat_buf.f_blocks * stat_buf.f_bsize; } void dav_provide_update(void) { if((prov_update_time > 0) && (time(NULL) <= prov_update_time + PROVIDE_UPDATE_TIMEOUT )) return; provide_set_uint64(CACHE_STORAGE_USED, get_used_cache_space(), 0); provide_update_avg_speed(); provide_update_alivetime(); prov_update_time = time(NULL); } int dav_statfs(dav_stat* infos, __uint64_t block_size) { __uint64_t used_bytes = 0; __uint64_t total_bytes = 0; infos->files = nnodes; infos->namelen = 256; if (dav_quota(root->path, &total_bytes, &used_bytes)) { //missing quota infos of webdav server //use storage infos from the cache partition infos->blocks = get_avail_cache_space() / block_size; infos->bfree = get_free_cache_space() / block_size; } else{ if(total_bytes < used_bytes) total_bytes = used_bytes; infos->blocks = total_bytes / block_size; if(infos->blocks - (used_bytes / block_size) < 1) infos->bfree = 0; else infos->bfree = infos->blocks - (used_bytes / block_size) - 1; } infos->bavail = infos->bfree; infos->ffree = infos->bfree; //dav_statfs() never fails, because Samba needs valid partition infos //for uploading files. return 0; } int dav_sync(dav_node *node) { if (!exists(node)) return ENOENT; dav_handle *fh = node->handles; while (fh) { if (fh->flags != O_RDONLY) fsync(fh->fd); fh = fh->next; } return 0; } int dav_write(size_t *written, dav_node * node, int fd, char *buf, size_t size, off_t offset) { int ret; struct stat statbuf; __uint64_t filesize = 0; if (!exists(node)) return ENOENT; dav_handle *fh = get_file_handle(node, fd, 0, 0, 0); if (!fh) return EBADF; if (fh->flags == O_RDONLY) return EINVAL; /* check upload quota */ quota_context quotas; provide_get_quota("as); if((offset > 0) && ((ret = stat(node->cache_path, &statbuf)) == 0)) filesize = statbuf.st_size; if(!upload_quota_ok("as, filesize + size)) { *written = 0; if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "insufficient quota for upload (1): filesize: %llu, st_av: %llu, st_us: %llu, " "up_av: %llu, up_us: %llu, dw_av: %llu, dw_us: %llu, tr_av: %llu, tr_us: %llu", filesize+size, quotas.storage_available, quotas.storage_used, quotas.upload_avail, quotas.upload_used, quotas.download_avail, quotas.download_used, quotas.traffic_avail, quotas.traffic_used); if(!event_sent_up_quota_reached) { avm_event_log(AVM_EVENT_UPLOAD_QUOTA_REACHED, node->path); event_sent_up_quota_reached = 1; } return EDQUOT; } *written = 0; ssize_t n = 0; while (*written < size && n >= 0) { n = pwrite(fd, buf + *written, size - *written, offset + *written); if (n < 0) { int err_code = errno; syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "writing file failed: %s", strerror(err_code)); if(err_code == ENOSPC && resize_cache()){ //Write failed, because no space is left. //Free space may be now available, because the cache was resized. //Try the write call a 2nd time. n = pwrite(fd, buf + *written, size - *written, offset + *written); if(n < 0){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "2nd attempt to write file failed: %s", strerror(errno)); return errno; } else{ if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "2nd attempt to write file succeeded"); } } else return err_code; } *written += n; } node->dirty = 1; add_to_changed(node); if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " written %i", *written); return 0; } /* Private functions */ /*===================*/ /* Node maintenance. */ /* Creates a new node taking properties from props and adds it to the child list of parent and to the hash table. If props references directory backup, no node will be created. parent : The parent directory node for the new node. props : Properties retrieved from the server. Will be freed. */ static void add_node(dav_node *parent, dav_props *props) { if (parent == root && strcmp(props->name, backup->name) == 0) { dav_delete_props(props); return; } dav_node *node; if (props->is_dir) { node = new_node(parent, default_dir_mode); } else { node = new_node(parent, default_file_mode); node->size = props->size; node->remote_exists = 1; if (props->is_exec == 1) { node->mode |= (node->mode & S_IRUSR) ? S_IXUSR : 0; node->mode |= (node->mode & S_IRGRP) ? S_IXGRP : 0; node->mode |= (node->mode & S_IROTH) ? S_IXOTH : 0; } else if (props->is_exec == 0) { node->mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); } node->mode &= ~file_umask; } parent->mtime = node->mtime; parent->ctime = node->mtime; node->path = props->path; node->name = props->name; node->etag = props->etag; node->smtime = props->mtime; if (node->smtime > 0) node->mtime = node->smtime; if (props->ctime > 0) { node->ctime = props->ctime; } else { node->ctime = node->mtime; } free(props); if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "added %s", node->path); } /* Checks whether node is allready in the list of changed nodes. If not it will be appended at the end of the list. */ static void add_to_changed(dav_node *node) { dav_node_list_item **chp = &changed; while (*chp) { if ((*chp)->node == node) return; chp = &(*chp)->next; } if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "add node to changed-list: %s", node->name); *chp = (dav_node_list_item *) malloc(sizeof(dav_node_list_item)); if (!*chp) abort(); (*chp)->node = node; (*chp)->next = NULL; (*chp)->save_at = 0; nchanged++; if(nchanged==1) //clear upload and download counter if //nothing happens in the meantime provide_clear_transfervalues(); provide_set_uint(DIRTY_FILES, nchanged, 0); } /* Print complete list of backuped nodes. */ static void print_backups(void) { if(debug){ dav_backup_list_item *chp; for(chp=backuped;chp;chp=chp->next) { struct tm add_tmtime; localtime_r(&(chp->add_at), &add_tmtime); struct tm next_tmtime; localtime_r(&(chp->next_attempt), &next_tmtime); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " orig_name: %s", chp->orig_name); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " name: %s", chp->node->name); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " path: %s", chp->node->path); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " add_at: %02d:%02d:%02d", add_tmtime.tm_hour, add_tmtime.tm_min, add_tmtime.tm_sec); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " attempt_cnt: %u", chp->attempt_cnt); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " next_attemp: %02d:%02d:%02d", next_tmtime.tm_hour, next_tmtime.tm_min, next_tmtime.tm_sec); } } } /* Remove given node from backup list. */ static void remove_node_from_backuped(dav_node* node, const char* node_path) { dav_backup_list_item **chp = &backuped; if(node){ while (*chp && (*chp)->node != node) chp = &(*chp)->next; if(*chp){ dav_backup_list_item *tofree = *chp; *chp = (*chp)->next; if(tofree->orig_name) free(tofree->orig_name); free(tofree); print_backups(); return; } } if(node_path){ while (*chp && ((*chp)->node->path && strcmp((*chp)->node->path, node_path))) chp = &(*chp)->next; if(*chp){ dav_backup_list_item *tofree = *chp; *chp = (*chp)->next; if(tofree->orig_name) free(tofree->orig_name); free(tofree); print_backups(); return; } } } /* Upload of given node failed again. * Increment upload counter or ignore this node * in the future, if to many uploads of the corresponding file * have failed. */ static void upload_failed_of_backup(dav_node* node) { dav_backup_list_item *chp = 0; for(chp=backuped;chp;chp=chp->next) { if(chp->node == node){ if(chp->attempt_cnt > BACKUP_MAX_UPLOADS){ remove_node_from_backuped(chp->node, NULL); } else{ chp->attempt_cnt++; chp->next_attempt = time(NULL) + (chp->attempt_cnt * BACKUP_MIN_INTERVAL); } break; } } print_backups(); } /* Upload of given node finished successfully. * The node will be moved from the backup directory * to the original directory. */ static void upload_finished_of_backup(dav_node* node) { dav_backup_list_item **chp = &backuped; while (*chp && (*chp)->node != node) chp = &(*chp)->next; if (!*chp){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "node not found in backup list: %s", node->name); return; } dav_backup_list_item *item = *chp; *chp = (*chp)->next; dav_node *src = item->node; dav_node *dst_parent = item->orig_parent; //use original parent directory free(src->name); src->name = item->orig_name; //use original node name if(!is_valid(dst_parent)){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "parent of backuped node is invalid: %s", node->name); return; } dav_node *dst = get_child(dst_parent, src->name); char *dst_path; if (!dst) { char *dst_conv = dav_conv_to_server_enc(src->name); dst_path = ne_concat(dst_parent->path, dst_conv, NULL); free(dst_conv); } else { dst_path = ne_strdup(dst->path); } update_path(src, src->path, dst_path); remove_from_tree(src); delete_cache_file(backup); backup->mtime = time(NULL); backup->ctime = backup->mtime; src->parent = dst_parent; src->next = dst_parent->childs; dav_node *np = dst_parent->childs; for (;np;np=np->next){ if(np->path && src->path && !strcmp(np->path, src->path)){ //The node is still available in the child-list of the //new parent directory, may be added with update_directory() //after finishing the file upload. remove_from_table(src); delete_node(src); item->orig_name = NULL; free(item); free(dst_path); *flush = 1; return; } } //add node to new parent directory dst_parent->childs = src; ++src->parent->filecount; delete_cache_file(dst_parent); dst_parent->mtime = time(NULL); dst_parent->ctime = dst_parent->mtime; item->orig_name = NULL; free(item); free(dst_path); *flush = 1; } /* Returns an entry of the backup-list with the oldest timestamp for * the last file upload. */ static dav_node* get_backup_node(void) { print_backups(); dav_backup_list_item *chp = 0; dav_backup_list_item *last = 0; time_t last_time = 0; for(chp=backuped;chp;chp=chp->next) { if((last_time == 0 || chp->next_attempt < last_time) && (chp->next_attempt < time(NULL))){ last_time = chp->next_attempt; last = chp; } } if(last){ if(debug){ struct tm add_tmtime; localtime_r(&(last->add_at), &add_tmtime); struct tm next_tmtime; localtime_r(&(last->next_attempt), &next_tmtime); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "--"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " orig_name: %s", last->orig_name); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " name: %s", last->node->name); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " path: %s", last->node->path); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " attempt_cnt: %u", last->attempt_cnt); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " next_attemp: %02d:%02d:%02d\n", next_tmtime.tm_hour, next_tmtime.tm_min, next_tmtime.tm_sec); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " add_at: %02d:%02d:%02d\n", add_tmtime.tm_hour, add_tmtime.tm_min, add_tmtime.tm_sec); } return last->node; } return 0; } /* Add given node to the backup-list if the first upload of the corresponding file failed. */ static void add_to_backuped(dav_node *node, dav_node *orig_parent, const char* orig_name) { dav_backup_list_item **chp = &backuped; while (*chp) { if ((*chp)->node == node) return; chp = &(*chp)->next; } if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "add node to backup-list: %s", node->name); *chp = (dav_backup_list_item *) malloc(sizeof(dav_backup_list_item)); if (!*chp) abort(); (*chp)->node = node; (*chp)->orig_parent = orig_parent; (*chp)->orig_name = ne_strdup(orig_name); (*chp)->next = NULL; (*chp)->add_at = time(NULL); (*chp)->attempt_cnt = 1; (*chp)->next_attempt = time(NULL) + ((*chp)->attempt_cnt * BACKUP_MIN_INTERVAL); print_backups(); } /* Creates a new file in directory backup. The name will be the name of the cache file of orig and attributes will be taken from orig. The cache file will be moved from orig to the new node. Open file descriptors will stay with orig. orig : the node to be backed up. */ static void backup_node(dav_node *orig) { if (!orig->cache_path) return; dav_node *node = new_node(backup, orig->mode); node->name = ne_strdup(orig->cache_path + strlen(cache_dir) +1); node->cache_path = orig->cache_path; orig->cache_path = NULL; node->mime_type = orig->mime_type; orig->mime_type = NULL; orig->dirty = 0; node->size = orig->size; node->uid = default_uid; node->gid = default_gid; if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "created backup of %p", orig); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " %s", orig->path); } delete_cache_file(backup); backup->mtime = time(NULL); backup->ctime = backup->mtime; *flush = 1; node->remote_exists = orig->remote_exists; node->smtime = orig->smtime; node->path = orig->path ? ne_strdup(orig->path) : NULL; node->etag = orig->etag ? ne_strdup(orig->etag) : NULL; add_to_backuped(node, orig->parent, orig->name); } /* Scans the directory tree starting from node and - if upload == 1: saves dirty files to the server and unlocks them. If it can not be safed, a local backup is created and the node is deleted. - removes any file nodes without cached file - removes all dir nodes that have not at least one file node below. Short: removes everthing that is not necessary to correctly reference the cached files. Node node itself will be removed and deleted if possible. Directory backup and root will never be removed. Kernel will *not* be notified about changes. Member nref of directories will be adjusted. */ static void clean_tree(dav_node *node, int upload) { char* node_path = 0; dav_provide_update(); if (node == backup) { delete_cache_file(backup); return; } if (is_dir(node)) { dav_node *child = node->childs; while (child) { dav_node *next = child->next; clean_tree(child, upload); child = next; } if (!node->childs && node != root && node != backup) { remove_from_tree(node); remove_from_table(node); delete_node(node); } else { delete_cache_file(node); } } else if (!is_cached(node) || access(node->cache_path, F_OK) != 0) { if (is_locked(node) && upload) dav_unlock(node->path, &node->lock_expire); remove_from_tree(node); remove_from_table(node); delete_node(node); } else if ((is_dirty(node) || is_created(node)) && upload) { int set_execute = -1; if (is_created(node) && node->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) set_execute = 1; node_path = strdup(node->path); quota_context quotas; provide_get_quota("as); if(!upload_quota_ok("as, node->size)) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "insufficient quota for cleaning tree: filesize: %llu, st_av: %llu, st_us: %llu, " "up_av: %llu, up_us: %llu, dw_av: %llu, dw_us: %llu, tr_av: %llu, tr_us: %llu", node->size, quotas.storage_available, quotas.storage_used, quotas.upload_avail, quotas.upload_used, quotas.download_avail, quotas.download_used, quotas.traffic_avail, quotas.traffic_used); if(!event_sent_up_quota_reached) { avm_event_log(AVM_EVENT_UPLOAD_QUOTA_REACHED, node->path); event_sent_up_quota_reached = 1; } backup_node(node); remove_from_tree(node); remove_from_table(node); delete_node(node); free(node_path); return; } int ret = dav_put(node->path, node->cache_path, &node->remote_exists, &node->lock_expire, &node->etag, &node->smtime, &node->mime_type, set_execute, 0 /*AVM funktion called when webdav is stopping, so we do not need a conflict upload here (node->size)*/); if(ret == ENODELOST || !node || !exists(node) || !node->parent || !node->path){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "clean_tree: uploaded node is lost: %s", node_path); //Uploaded node is lost, try to remove it from the server. time_t lock_expire = 0; dav_delete(node_path, &lock_expire); free(node_path); return; } if (is_locked(node)) dav_unlock(node->path, &node->lock_expire); if (!ret) { node->mtime = node->smtime; if (node->mtime > node->atime) node->atime = node->mtime; set_cache_file_times(node); node->dirty = 0; } else if (ret == EACCES || ret == EINVAL || ret == ENOENT || ret == EPERM || ret == ENOSPC || ret == EEXIST || ret == EIO) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "clean_tree: file upload failed (%d): %s", ret, node_path); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), " %s", dav_get_webdav_error()); backup_node(node); remove_from_tree(node); remove_from_table(node); delete_node(node); } } else { node->mtime = node->smtime; if (node->mtime > node->atime) node->atime = node->mtime; set_cache_file_times(node); } if(node_path) free(node_path); } /* Frees any resources held by node and finally frees node. If there are open file descriptors, this will be closed. */ static void delete_node(dav_node *node) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "deleting node %p", node); if (node->path){ memset(node->path,'\0',strlen(node->path)); free(node->path); } if (node->name){ memset(node->name,'\0',strlen(node->name)); free(node->name); } delete_cache_file(node); if (node->etag){ memset(node->etag,'\0',strlen(node->etag)); free(node->etag); } if (node->mime_type){ memset(node->mime_type,'\0',strlen(node->mime_type)); free(node->mime_type); } while (node->handles) { dav_handle *tofree = node->handles; node->handles = node->handles->next; close(tofree->fd); free(tofree); } free(node); nnodes--; } /* Deletes the tree starting at and including node. The tree is freed uncontionally, no checks for lost update problem and the like are done, also backup will be deleted if in tree. Exeption: the root node will not be deleted. */ static void delete_tree(dav_node *node) { dav_provide_update(); while (node->childs) delete_tree(node->childs); if (node != root) { remove_from_tree(node); remove_from_table(node); delete_node(node); if (node == backup) backup = NULL; } } /* Moves directory src to dst using WebDAV method MOVE. */ static int move_dir(dav_node *src, dav_node *dst, dav_node *dst_parent, const char *dst_name) { int fix_tonline = 0; int dst_path_len = 0; if (dst && !is_dir(dst)) return ENOTDIR; if (src && is_busy(src)) // AVM Fix: Ziel sollte nicht existieren, drum hier jetzt src pruefen return EBUSY; char *dst_path; if (!dst) { char *dst_conv = dav_conv_to_server_enc(dst_name); dst_path = ne_concat(dst_parent->path, dst_conv, "/", NULL); free(dst_conv); } else { dst_path = ne_strdup(dst->path); } // AVM FIX T-ONLINE, remove '/' at the end => error 409 (Feb. 2012) if (host && strstr(host, "t-online.de")) { // if last char is '/' then we will remove it dst_path_len = strlen(dst_path); if (dst_path_len && dst_path[dst_path_len-1] == '/') { dst_path[dst_path_len-1] = 0; fix_tonline = 1; } //syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "move_dir t-online removed / at the end: %s", dst_path); } if (dav_move(src->path, dst_path) != 0) { free(dst_path); return EIO; } if (dst) remove_node(dst); // AVM FIX T-ONLINE, insert '/' at the end , now the dst_path will be stored in the node if (fix_tonline) { dst_path[dst_path_len-1] = '/'; } free(src->name); src->name = ne_strdup(dst_name); update_path(src, src->path, dst_path); free(dst_path); return 0; } /* As the most recent version of file src is local, it deletes src and dst on the server and locks dst. CLEAN-UP-NEEDED: no longer used with dirty files. Remote must not exist. */ static int move_dirty(dav_node *src, dav_node *dst, dav_node *dst_parent, const char *dst_name) { if (dst && is_dir(dst)) return EISDIR; char *dst_path; if (!dst) { char *dst_conv = dav_conv_to_server_enc(dst_name); dst_path = ne_concat(dst_parent->path, dst_conv, NULL); free(dst_conv); } else { dst_path = ne_strdup(dst->path); } if (dst) { int ret = 0; if (is_created(dst)) { if (is_locked(dst)) ret = dav_unlock(dst_path, &dst->lock_expire); } else { ret = dav_delete(dst_path, &dst->lock_expire); } if (ret == ENOENT) ret = 0; if (ret) { free(dst_path); return EIO; } remove_from_tree(dst); remove_from_changed(dst); if (is_open(dst)) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "invalidating node %p", dst); dst->parent = NULL; } else { remove_from_table(dst); delete_node(dst); } } if (is_created(src) && is_locked(src)) dav_unlock(src->path, &src->lock_expire); src->remote_exists = 0; dav_lock(dst_path, &src->lock_expire, &src->remote_exists); if (!is_created(src) && (src->mode & (S_IXUSR | S_IXGRP | S_IXOTH))) dav_set_execute(dst_path, 1); free(src->name); src->name = ne_strdup(dst_name); free(src->path); src->path = dst_path; if (src->etag) { free(src->etag); src->etag = NULL; } src->smtime = time(NULL); if (!is_created(src)) dav_head(src->path, &src->etag, &src->smtime, NULL, &src->mime_type); src->utime = time(NULL); return 0; } /* Moves file src to dst using WebDAV method MOVE. */ static int move_reg(dav_node *src, dav_node *dst, dav_node *dst_parent, const char *dst_name) { if (dst && is_dir(dst)) return EISDIR; char *dst_path; if (!dst) { char *dst_conv = dav_conv_to_server_enc(dst_name); dst_path = ne_concat(dst_parent->path, dst_conv, NULL); free(dst_conv); } else { dst_path = ne_strdup(dst->path); } if (dav_move(src->path, dst_path) != 0) { free(dst_path); return EIO; } if (is_locked(src)) { src->lock_expire = 0; if (is_dirty(src)) dav_lock(dst_path, &src->lock_expire, &src->remote_exists); } if (is_cached(src)) dav_head(dst_path, &src->etag, &src->smtime, NULL, &src->mime_type); if (dst) { remove_from_tree(dst); remove_from_changed(dst); if (is_open(dst)) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "invalidating node %p", dst); dst->parent = NULL; } else { remove_from_table(dst); delete_node(dst); } } free(src->name); src->name = ne_strdup(dst_name); free(src->path); src->path = dst_path; src->utime = time(NULL); return 0; } /* Creates a new node. mode must have the I_ISDIR or I_ISREG bit set. node->mode is set to mode, but checked against the umask. All other members are set to reasonable defaults. The new node will be inserted into the child list of parent and the hash table. Member nref of the parent will be updated. parent : The parent of the new node, may be NULL. mode : Tthe mode of the new node. return value : A pointer to the new node. */ static dav_node * new_node(dav_node *parent, mode_t mode) { dav_node *node = (dav_node *) ne_malloc(sizeof(dav_node)); node->parent = parent; node->childs = NULL; if (parent) { if (S_ISDIR(mode)) ++parent->nref; else if (S_ISREG(mode)) { ++parent->filecount; } node->next = parent->childs; parent->childs = node; } else { node->next = NULL; } size_t i = ((size_t) node / alignment) % table_size; node->table_next = table[i]; table[i] = node; node->path = NULL; node->name = NULL; node->cache_path = NULL; node->etag = NULL; node->mime_type = NULL; node->handles = NULL; node->size = 0; node->atime = time(NULL); node->mtime = node->atime; node->ctime = node->atime; node->utime = 0; node->smtime = 0; node->lock_expire = 0; node->filecount = 0; if (S_ISDIR(mode)) { node->mode = mode & ~dir_umask; node->nref = 2; } else { node->mode = mode & ~file_umask; node->nref = 1; } node->remote_exists = 0; node->dirty = 0; node->uid = default_uid; node->gid = default_gid; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "new node: %p->%p", node->parent, node); nnodes++; return node; } /* Removes a node from the list of changed nodes. */ static void remove_from_changed(dav_node *node) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "remove node from changed-list: %s", node->name); dav_node_list_item **chp = &changed; while (*chp && (*chp)->node != node) chp = &(*chp)->next; if (*chp) { dav_node_list_item *tofree = *chp; *chp = (*chp)->next; free(tofree); nchanged--; provide_set_uint(DIRTY_FILES, nchanged, 0); } } /* Removes a node from the hash table. The root node can not be removed. */ static void remove_from_table(dav_node *node) { if (node == root) return; size_t i = ((size_t) node / alignment) % table_size; dav_node **np = &table[i]; while (*np && *np != node) np = &(*np)->table_next; if (*np) *np = (*np)->table_next; } /* Removes a node from the directory tree. The root node can not be removed. If node is a directory, member nref of the parent will be decremented. But no other attributes of the parent directory will be changed. */ static void remove_from_tree(dav_node *node) { if (node == root) return; dav_node **np = &node->parent->childs; while (*np && *np != node) np = &(*np)->next; if (*np) { *np = node->next; if (is_dir(node)) --node->parent->nref; else if(is_reg(node)) { --node->parent->filecount; } } } /* Frees locks, removes the node from the tree and from the hash table, and deletes it. Depending on the kind of node and its state additional action will be taken: - For directories the complete tree below is removed too. - If a regular file is dirty, open for writing or created, a backup in driectory backup will be created, that holds the cached local copy of the file. - If a file is open, it will not be removed from the hash table to allow proper closing of open file descriptors. */ static void remove_node(dav_node *node) { remove_from_tree(node); if (is_dir(node)) { while (node->childs != NULL) remove_node(node->childs); remove_from_table(node); delete_node(node); } else { remove_from_changed(node); if (is_locked(node)) dav_unlock(node->path, &node->lock_expire); if (is_dirty(node) || is_open_write(node) || is_created(node)) backup_node(node); if (is_open(node)) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "invalidating node %p", node); node->parent = NULL; node->dirty = 0; node->remote_exists = 0; } else { remove_from_table(node); delete_node(node); } } } /* Gets a property list from the server and updates node dir and its childs accordingly. If there are inconsistencies between the information from the server and the locally stored state, the local information is updated. Backups are created if necessary. This will only be done if the utime of dear is reached, otherwise this function will do nothing. utime and retry will be updated. If the contents or the mtime of the dir has changed, the dir-cache-file will be deleted and the flush flag will be set to force new lookups by the kernel. */ static int update_directory(dav_node *dir, time_t refresh) { if (dir == backup || time(NULL) <= (dir->utime + refresh)) return 0; dav_props *props = NULL; int ret = dav_get_collection(dir->path, &props); dir->utime = time(NULL); if (ret) { if (retry == dir_refresh) { retry = min_retry; } else { retry *= 2; retry = (retry > max_retry) ? max_retry : retry; retry = (retry == dir_refresh) ? (retry + 1) : retry; } return ret; } else { retry = dir_refresh; } int changed = 0; dav_node *child = dir->childs; while (child) { dav_provide_update(); dav_node *next = child->next; if (!is_backup(child)) { dav_props **pp = &props; while (*pp && strcmp((*pp)->path, child->path) != 0) pp = &(*pp)->next; if (*pp) { dav_props *p = *pp; *pp = p->next; changed |= update_node(child, p); } else if (!is_created(child)) { remove_node(child); changed = 1; } } child = next; } while (props) { dav_provide_update(); dav_props *next = props->next; if (strlen(props->name) > 0) { add_node(dir, props); changed = 1; } else { dir->mtime = props->mtime; if (props->ctime > dir->ctime) dir->ctime = props->ctime; if (dir->mtime > dir->ctime) dir->ctime = dir->mtime; if (dir->etag) free(dir->etag); dir->etag = props->etag; props->etag = NULL; dav_delete_props(props); } props = next; } if (changed) { delete_cache_file(dir); *flush = 1; } if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "directory updated: %p->%p", dir->parent, dir); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " %s", dir->path); } return 0; } /* Updates the properties of node according to props and frees props. If props is incompatibel with node or indicates a lost update problem, a new node is created from props and the old node is deleted, creating a local back up if necessary. If nodes are removed or created, flag flush is set, to force new lookups by the kernel. node : The node to be updated. It must not be the root node and have a valid parent. props : The properties retrieved from the server. They will be freed. return value: Value 1 indicates that the contents of the parent directory has changed and therefore the parents dir-cache-file has to be updated; 0 otherwise. NOTE: node may be removed and the pointer node may become invalid. */ static int update_node(dav_node *node, dav_props *props) { if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "updating node: %p->%p", node->parent, node); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " %s", node->path); } if (!node->parent) { dav_delete_props(props); // AVM FIX memory leak return 0; } int ret = 0; if(is_filetranfer_inprogress(node->path)==1){ //While changing attributes of a node, it can be replaced by a new one. //This will cause problems while uploading the corresponding file. if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "updating aborted - file transfer in progress"); dav_delete_props(props); return 0; } if ((is_dir(node) && !props->is_dir) || (!is_dir(node) && props->is_dir)) { add_node(node->parent, props); remove_node(node); *flush = 1; return 1; } if (strcmp(node->name, props->name) != 0) { free(node->name); node->name = ne_strdup(props->name); ret = 1; *flush = 1; } if (is_created(node)) { if (!is_open(node) && (props->size > 0)) { add_node(node->parent, props); remove_node(node); *flush = 1; return 1; } else { dav_delete_props(props); return ret; } } if (is_cached(node)) { if ((!node->etag && props->mtime > node->smtime+1) || (node->etag && props->etag && strcmp(node->etag, props->etag) != 0)) { //AVM: add (+1) for ignoring time differences in server responses //it can be a second for an unmodified file (e.g. 1244210315 - 1244210316) if (is_open(node)) { node->utime = 0; dav_delete_props(props); return ret; } else if (is_dirty(node)) { add_node(node->parent, props); remove_node(node); *flush = 1; return 1; } else { if(debug && node->etag && props->etag && strcmp(node->etag, props->etag) != 0) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "etag of cached file obsolete: %s - %s", node->etag, props->etag); else if(debug && !node->etag && props->mtime > node->smtime+1) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "smtime of cached file obsolete: %lu - %lu", node->smtime, props->mtime); delete_cache_file(node); *flush = 1; } } else { node->utime = time(NULL); dav_delete_props(props); return ret; } } if (props->mtime > node->atime) node->atime = props->mtime; if (props->mtime > node->smtime+1) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "smtime of cached file obsolete: %lu - %lu", node->smtime, props->mtime); node->mtime = props->mtime; node->smtime = props->mtime; node->utime = 0; delete_cache_file(node); *flush = 1; } if (props->ctime > node->ctime) node->ctime = props->ctime; if (node->etag) free(node->etag); node->etag = props->etag; props->etag = NULL; if (is_reg(node)) { if (props->is_exec == 1 && !(node->mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { node->mode |= (node->mode & S_IWUSR) ? S_IXUSR : 0; node->mode |= (node->mode & S_IWGRP) ? S_IXGRP : 0; node->mode |= (node->mode & S_IWOTH) ? S_IXOTH : 0; node->mode &= ~file_umask; *flush = 1; } else if (props->is_exec == 0 && (node->mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { node->mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); *flush = 1; } if (props->size && props->size != node->size) { node->size = props->size; *flush = 1; } } dav_delete_props(props); return ret; } /* For node and all nodes in the tree below nodethe compinent src_path in its path will be replaced by dst_path. If the path of a node does not start with src_path the node will *not* be removed, but its parent directory will be invalidated, so an update is forced. */ static void update_path(dav_node *node, const char *src_path, const char *dst_path) { dav_node *n = node->childs; while (n) { update_path(n, src_path, dst_path); n = n->next; } if (!node->path || strstr(node->path, src_path) != node->path) { delete_cache_file(node->parent); node->parent->utime = 0; *flush = 1; return; } char *path = ne_concat(dst_path, node->path + strlen(src_path), NULL); free(node->path); node->path = path; } /* Get information about node. */ static int exists(const dav_node *node) { size_t i = ((size_t) node / alignment) % table_size; dav_node *n = table[i]; while (n && n != node) n = n->table_next; if (n) { return 1; } else { *flush = 1; return 0; } } static dav_handle * get_file_handle(dav_node * node, int fd, int accmode, pid_t pid, pid_t pgid) { dav_handle *fh = node->handles; if (fd) { while (fh && fh->fd != fd) fh = fh->next; } else { while (fh && (fh->flags != accmode || fh->pid != pid)) fh = fh->next; if (!fh) { fh = node->handles; while (fh && (fh->flags != accmode || fh->pgid != pgid)) fh = fh->next; } } return fh; } /* Checks whether user uid has access to node according to how. In any case the user must have execute permission for the parent of node and all of its parents up to the root node. int how : How to acces the node. May be any combination of R_OK, W_OK, X_OK and F_OK. return value: 1 access is allowed. 0 access is denied. */ static int has_permission(const dav_node *node, uid_t uid, int how) { if (uid == 0) return 1; if (node->parent && !has_permission(node->parent, uid, X_OK)) return 0; mode_t a_mode = (how & R_OK) ? (S_IRUSR | S_IRGRP | S_IROTH) : 0; a_mode |= (how & W_OK) ? (S_IWUSR | S_IWGRP | S_IWOTH) : 0; a_mode |= (how & X_OK) ? (S_IXUSR | S_IXGRP | S_IXOTH) : 0; if (!(~node->mode & S_IRWXO & a_mode)) return 1; if (node->uid == uid && !(~node->mode & S_IRWXU & a_mode)) return 1; struct passwd *pw = getpwuid(uid); if (!pw) return 0; if (pw->pw_gid != node->gid) { struct group *grp = getgrgid(node->gid); if (!grp) return 0; char **members = grp->gr_mem; while (*members && strcmp(*members, pw->pw_name) == 0) members++; if (!*members) return 0; } if (!(~node->mode & S_IRWXG & a_mode)) return 1; return 0; } /* A node is considered busy if it is open for writing or, in case of a directory, if in the tree below the node there is any file open for write. return value : 1 if busy, 0 if not. */ static int is_busy(const dav_node *node) { dav_node *child = node->childs; while (child) { if (is_busy(child)) return 1; child = child->next; // AVM Fix: Dauerschleife behoben } return (is_reg(node) && is_open_write(node)); } /* Checks whether node exists and is valid. The parent directory is updated if necessary. */ static int is_valid(const dav_node *node) { if (!exists(node) || (!node->parent && node != root)) return 0; if (node == root || node == backup) return 1; update_directory(node->parent, retry); if (!exists(node) || (!node->parent && node != root)) return 0; return 1; } /* Cache file functions. */ static void close_fh(dav_node *node, dav_handle *fh) { close(fh->fd); dav_handle **fhp = &node->handles; while (*fhp && *fhp != fh) fhp = &(*fhp)->next; if (*fhp) *fhp = (*fhp)->next; free(fh); } /* It creates a new empty cache file for node. If a cache file already exists, it does nothing. return value : 0 on success, EIO if no cache file could be created. */ static int create_cache_file(dav_node *node) { if (node->cache_path) { if (access(node->cache_path, F_OK) == 0) { return 0; } else { free(node->cache_path); } } char* ext = strrchr(node->name, '.'); int fd = 0; if(ext && ext+1){ char* file_ext = ne_strdup(ext+1); char* file_name = ne_strdup(node->name); *strrchr(file_name, '.') = '\0'; char* file_path = ne_concat(cache_dir, "/", file_name, "-XXXXXX", NULL); fd = mkstemp(file_path); if(fd <= 0){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("can't create cache file %s"), file_path); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), strerror(errno)); node->cache_path = NULL; free(file_ext); free(file_name); free(file_path); return EIO; } node->cache_path = ne_concat(file_path, ".", file_ext, NULL); close(fd); remove(file_path); fd = open(node->cache_path, O_RDWR|O_CREAT, 0600); free(file_ext); free(file_name); free(file_path); } else{ node->cache_path = ne_concat(cache_dir, "/", node->name, "-XXXXXX", NULL); fd = mkstemp(node->cache_path); } if (fd <= 0) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("can't create cache file %s"), node->cache_path); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), strerror(errno)); free(node->cache_path); node->cache_path = NULL; return EIO; } if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "creating cache file: %s", node->cache_path); close(fd); cache_file_count++; // AVM return 0; } /* Creates a file in the cache that holds dir-entries of directory dir. dir will be updated if necessary. If this file already exists, it does nothing. To write the dir-entries it calls the write_dir_entry function of the kernel interface. return value : 0 on success, EIO if no cache file could be created. */ static int create_dir_cache_file(dav_node *dir) { if (dir->cache_path) { if (access(dir->cache_path, F_OK) == 0) { return 0; } else { free(dir->cache_path); } } dir->cache_path = ne_concat(cache_dir, "/dir-", dir->name, "-XXXXXX", NULL); int fd = mkstemp(dir->cache_path); if (fd <= 0) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("can't create cache file %s"), dir->cache_path); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), strerror(errno)); free(dir->cache_path); dir->cache_path = NULL; return EIO; } off_t size = write_dir(dir, fd); close(fd); if (size > 0) { dir->size = size; return 0; } else { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("error writing directory %s"), dir->cache_path); remove(dir->cache_path); free(dir->cache_path); dir->cache_path = NULL; return EIO; } } /* Opens the cache file of node using flags and stores the file descriptor in fd. A new structure dav_handle is created and added to the list of handles. return value : 0 on success, EIO if the file could not be opend. */ static int open_file(int *fd, dav_node *node, int flags, pid_t pid, pid_t pgid, uid_t uid) { *fd = open(node->cache_path, flags, node->mode); if((flags & O_NONBLOCK) && debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "open_file: O_NONBLOCK is set"); if (*fd <= 0) return EIO; dav_handle *fh = (dav_handle *) ne_malloc(sizeof(dav_handle)); fh->fd = *fd; if(is_dir(node)) fh->flags = O_ACCMODE & flags; else fh->flags = (O_ACCMODE | O_NONBLOCK) & flags; fh->pid = pid; fh->pgid = pgid; fh->uid = uid; fh->next = node->handles; node->handles = fh; /*if ((O_ACCMODE & flags) == O_WRONLY || (O_ACCMODE & flags) == O_RDWR) Samba frequently opens files read-/writeable without writing to them. Avoid uploading unchanged files. node->dirty = 1; */ return 0; } /* Download file from server, which was cached. */ static int dav_get_cachedfile_from_server(dav_node *node) { int ret = 0; char* node_path = strdup(node->path); int modified = 0; off_t old_size = node->size; if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_get_cachedfile_from_server: %s", node->path); ret = dav_get_file(node->path, node->cache_path, &node->size, &node->etag, &node->smtime, &node->mime_type, &modified); if(ret == ENODELOST || !node || !exists(node) || !node->parent || !node->path){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "dav_get_cachedfile_from_server: downloaded node is lost: %s", node_path); //Downloaded node is lost. time_t lock_expire = 0; dav_delete(node_path, &lock_expire); free(node_path); return ENOENT; } free(node_path); if (!ret) { if (modified) { node->mtime = node->smtime; node->atime = node->smtime; set_cache_file_times(node); } node->utime = time(NULL); cache_size += node->size - old_size; } return 0; } /* Download file from server without blocking the fuse-thread. * Use a new thread for the download. */ static void dav_get_cachedfile_from_server_async(void* arg) { int ret = 0; dav_node *node = (dav_node *) arg; if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_get_cachedfile_from_server_async: %s", node->path); mutex_lock(mutex_allops); ret = dav_get_cachedfile_from_server(node); mutex_unlock(mutex_allops); //before killing thread, close connection to server dav_close_webdav(0); } /* Download file from server. */ static int dav_get_file_from_server(dav_node *node) { int ret = 0; char* node_path = strdup(node->path); time_t smtime = 0; char *etag = NULL; if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_get_file_from_server: %s", node->path); ret = dav_get_file(node->path, node->cache_path, &node->size, &etag, &smtime, &node->mime_type, NULL); if(ret == ENODELOST || !node || !exists(node) || !node->parent || !node->path){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "update_cache_file: downloaded node is lost: %s", node_path); //Downloaded node is lost. time_t lock_expire = 0; dav_delete(node_path, &lock_expire); free(node_path); if(etag) free(etag); return ENOENT; } free(node_path); if (!ret) { node->etag = etag; if (smtime > 0) { node->atime = smtime; node->mtime = smtime; node->smtime = smtime; } node->utime = time(NULL); set_cache_file_times(node); cache_size += node->size; } else { if (ret == ENOENT) { delete_cache_file(node->parent); node->parent->utime = 0; *flush = 1; remove_node(node); } if(etag) free(etag); delete_cache_file(node); } return ret; } /* Download file from server by using a new thread. */ static void dav_get_file_from_server_async(void* arg) { int ret = 0; dav_node *node = (dav_node *) arg; if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_get_file_from_server_async: %s", node->path); mutex_lock(mutex_allops); ret = dav_get_file_from_server(node); mutex_unlock(mutex_allops); dav_close_webdav(0); } /* Upload modified file to server. */ static int dav_upload_dirty_file(dav_node *node) { int ret = 0; char* node_path = strdup(node->path); if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_upload_dirty_file: %s", node->path); ret = dav_put(node->path, node->cache_path, &node->remote_exists, &node->lock_expire, &node->etag, &node->smtime, &node->mime_type, -1, 0 /* AVM size only used for conflict / interupted uploads (node->size)*/); if(ret == ENODELOST || !node || !exists(node) || !node->parent || !node->path){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "dav_upload_dirty_file: uploaded node is lost: %s", node_path); //Uploaded node is lost, remove it from the server and the cache. time_t lock_expire = 0; dav_delete(node_path, &lock_expire); free(node_path); return ret; } if (!ret) { node->utime = time(NULL); node->dirty = 0; remove_from_changed(node); } else if (ret == EACCES || ret == EINVAL || ret == ENOENT || ret == EPERM || ret == ENOSPC || ret == EIO) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "dav_upload_dirty_file: file upload failed: %s", node_path); delete_cache_file(node->parent); node->parent->utime = 0; *flush = 1; remove_node(node); ret = EIO; } free(node_path); return ret; } /* Upload modified file to server in a separate thread. */ static void dav_upload_dirty_file_async(void* arg) { int ret = 0; dav_node *node = (dav_node *) arg; if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_upload_dirty_file_async: %s", node->path); mutex_lock(mutex_allops); ret = dav_upload_dirty_file(node); mutex_unlock(mutex_allops); dav_close_webdav(0); } /* Updates the cached file from the server if necessary and possible, or retrieves one from the server if no cache file exists. It is not necessary or possible if - node is in directory backup - node is created (does not yet exist on the server) - node is open for writing - it has been updated within the last second - node is dirty. If the node is dirty but not open for write, it will be stored back on the server. */ static int update_cache_file(dav_node *node) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "update_cache_file: %s", node->path); if (is_backup(node) || is_created(node) || is_open_write(node) || (is_dirty(node) && is_locked(node))) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " update not necessary"); return 0; } int ret = 0; if (is_dirty(node)) { if (get_upload_time(node) >= time(NULL)){ if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " upload time not reached"); return 0; } if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " update necessary: dirty node"); struct stat st; if (stat(node->cache_path, &st) < 0) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "update_cache_file - stat failed on node: %s", node->cache_path); return EIO; } if(is_filetranfer_inprogress(node->path)==1){ if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "upload not necessary - file transfer in progress"); return 0; } /* check upload quota */ quota_context quotas; provide_get_quota("as); if(!upload_quota_ok("as, st.st_size)) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "insufficient quota for upload (2): filesize: %llu, st_av: %llu, st_us: %llu, " "up_av: %llu, up_us: %llu, dw_av: %llu, dw_us: %llu, tr_av: %llu, tr_us: %llu", st.st_size, quotas.storage_available, quotas.storage_used, quotas.upload_avail, quotas.upload_used, quotas.download_avail, quotas.download_used, quotas.traffic_avail, quotas.traffic_used); if(!event_sent_up_quota_reached) { avm_event_log(AVM_EVENT_UPLOAD_QUOTA_REACHED, node->path); event_sent_up_quota_reached = 1; } return EACCES; } if(st.st_size < 200000){ ret = dav_upload_dirty_file(node); } else{ pthread_t dav_upload_file_thread_id = 0; if(pthread_create(&dav_upload_file_thread_id, NULL, (void*) dav_upload_dirty_file_async, node)){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "starting thread for dav_upload_dirty_file_async() failed"); return EIO; } } return ret; } if (gui_optimize && is_cached(node) && time(NULL) > (node->utime +file_refresh)) { update_directory(node->parent, file_refresh); if (!exists(node) || node->parent == NULL) return ENOENT; } if (is_cached(node) && access(node->cache_path, F_OK) == 0) { if (time(NULL) > (node->utime + file_refresh)){ if (debug){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " update necessary: cached node %lu > %lu+%lu", time(NULL), node->utime, file_refresh); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " updating directory"); } update_directory(node->parent, 0); //AVM if(!node){ //node is lost syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " node is lost"); return EIO; } } if (time(NULL) <= (node->utime + file_refresh)){ if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " update not necessary: cached node %lu <= %lu+%lu", time(NULL), node->utime, file_refresh); return 0; } else{ if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " update necessary: cached node %lu > %lu+%lu", time(NULL), node->utime, file_refresh); if(is_filetranfer_inprogress(node->path)==1){ if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "download not necessary - file transfer in progress"); return 0; } /* check download quota */ quota_context quotas; provide_get_quota("as); __uint64_t free_space = get_free_cache_space(); if(free_space < node->size + 10*1024){ if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "download not possible, not enough cache space available"); avm_event_log(3352, node->path); return EACCES; } if(!download_quota_ok("as, node->size)) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "insufficient quota for download (1): filesize: %llu," " st_av: %llu, st_us: %llu, up_av: %llu, up_us: %llu, dw_av: %llu, dw_us: %llu, tr_av: %llu, tr_us: %llu", node->size, quotas.storage_available, quotas.storage_used, quotas.upload_avail, quotas.upload_used, quotas.download_avail, quotas.download_used, quotas.traffic_avail, quotas.traffic_used); if(!event_sent_down_quota_reached) { avm_event_log(AVM_EVENT_DOWNLOAD_QUOTA_REACHED, node->path); event_sent_down_quota_reached = 1; } return EACCES; } if(node->size < 200000){ return dav_get_cachedfile_from_server(node); } else{ pthread_t get_file_thread_id = 0; if (pthread_create(&get_file_thread_id, NULL, (void*) dav_get_cachedfile_from_server_async, node)){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "starting thread for dav_get_cachedfile_from_server_async() failed"); return EIO; } } } } else { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " update necessary: node not cached"); /* check download quota */ quota_context quotas; provide_get_quota("as); __uint64_t free_space = get_free_cache_space(); if(free_space < node->size + 10*1024){ if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "download not possible, not enough cache space available"); avm_event_log(3352, node->path); return EACCES; } if(!download_quota_ok("as, node->size)) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "insufficient quota for download (2): filesize: %llu, st_av: %llu," " st_us: %llu, up_av: %llu, up_us: %llu, dw_av: %llu, dw_us: %llu, tr_av: %llu, tr_us: %llu", node->size, quotas.storage_available, quotas.storage_used, quotas.upload_avail, quotas.upload_used, quotas.download_avail, quotas.download_used, quotas.traffic_avail, quotas.traffic_used); if(!event_sent_down_quota_reached) { avm_event_log(AVM_EVENT_DOWNLOAD_QUOTA_REACHED, node->path); event_sent_down_quota_reached = 1; } return EACCES; } if (create_cache_file(node) != 0){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "create_cache_file failed"); return EIO; } if(is_filetranfer_inprogress(node->path)==1){ if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "download not necessary - file transfer in progress"); return 0; } if(node->size < 200000){ return dav_get_file_from_server(node); } else{ pthread_t get_file_thread_id = 0; if (pthread_create(&get_file_thread_id, NULL, (void*) dav_get_file_from_server_async, node)){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "starting thread for dav_get_file_from_server_async() failed"); return EIO; } } } return ret; } static off_t write_dir(dav_node *dir, int fd) { off_t size = write_dir_entry(fd, 0, dir, "."); if (size > 0 && dir->parent != NULL) size = write_dir_entry(fd, size, dir->parent, ".."); dav_node *node = dir->childs; while (size > 0 && node) { size = write_dir_entry(fd, size, node, node->name); node = node->next; } if (size > 0) size = write_dir_entry(fd, size, NULL, NULL); return size; } /* Permanent cache maintenance. */ /* Checks whether there is an cache directory for the server host, path path, mountpoint mpoint and the default_user in the top level cache directory dir. If not it will create one. In case of an error it will print an error message and terminate the program. dir : The top level cache directory. host : Domain name of the server. path : Path of the resource onthe server. mpoint : Mount point. */ static void check_cache_dir(const char *dir, const char *host, const char *path, const char *mpoint, const char *username) { struct passwd *pw = getpwuid(default_uid); if (!pw || !pw->pw_name) error(EXIT_FAILURE, 0, _("can't read user data base")); char *dir_name = ne_concat(host, "+", username ? username : "unknown", NULL); char *pos = strchr(dir_name, '/'); while (pos) { *pos = '-'; pos = strchr(pos, '/'); } DIR *tl_dir = opendir(dir); if (!tl_dir) error(EXIT_FAILURE, 0, _("can't open cache directory %s"), dir); struct dirent *de = readdir(tl_dir); while (de && !cache_dir) { if (strcmp(de->d_name, dir_name) == 0) { cache_dir = ne_concat(dir, "/", de->d_name, NULL); } de = readdir(tl_dir); } closedir(tl_dir); if (!cache_dir) { cache_dir = ne_concat(dir, "/", dir_name, NULL); if (mkdir(cache_dir, S_IRWXU) != 0) error(EXIT_FAILURE, 0, _("can't create cache directory %s"), cache_dir); } free(dir_name); struct stat st; if (stat(cache_dir, &st) != 0) error(EXIT_FAILURE, 0, _("can't access cache directory %s"), cache_dir); if (st.st_uid != geteuid()) error(EXIT_FAILURE, 0, _("wrong owner of cache directory %s"), cache_dir); /* AVM if ((DAV_A_MASK & st.st_mode) != S_IRWXU) error(EXIT_FAILURE, 0, _("wrong permissions set for cache directory %s"), cache_dir); */ } /* Searches cache for orphaned files and puts them into backup. */ static void clean_cache(void) { DIR *dir = opendir(cache_dir); if (!dir) return; struct dirent *de = readdir(dir); while (de) { if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0 && strcmp(de->d_name, DAV_INDEX) != 0) { char *path = ne_concat(cache_dir, "/", de->d_name, NULL); int i = 0; dav_node *node = NULL; while (!node && i < table_size) { node = table[i]; while (node && (!is_reg(node) || !node->cache_path || strcmp(path, node->cache_path) != 0)) { node = node->table_next; } i++; } if (!node) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("found and delete orphaned file in cache:")); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), " %s", path); remove(path); /*AVM*/ free(path); /*AVM*/ /* AVM old code => insert file in backup dav_node *found = new_node(backup, default_file_mode); found->mode &= ~(S_IRWXG | S_IRWXO); found->cache_path = path; found->name = ne_strdup(de->d_name); attr_from_cache_file(found); backup->mtime = time(NULL); backup->ctime = backup->mtime; */ } else { free(path); } } de = readdir(dir); } closedir(dir); } /* Reads the index file of the cache and creates a tree of nodes from the XML data in the index file. Will be called when the cache module is initialized. The root node must already exist. If an error occurs all nodes created up to this will be deleted. */ static void parse_index(void) { char *index = ne_concat(cache_dir, "/", DAV_INDEX, NULL); FILE *idx = fopen(index, "r"); if (!idx) { free(index); return; } char *buf = ne_malloc(DAV_XML_BUF_SIZE); size_t len = fread(buf, 1, DAV_XML_BUF_SIZE, idx); if (len <= 0) { free(buf); fclose(idx); free(index); return; } dav_node *node = root; ne_xml_parser *parser = ne_xml_create(); ne_xml_push_handler(parser, xml_start_root, NULL, xml_end_root, &node); ne_xml_push_handler(parser, xml_start_backup, NULL, xml_end_backup, &node); ne_xml_push_handler(parser, xml_start_dir, NULL, xml_end_dir, &node); ne_xml_push_handler(parser, xml_start_reg, NULL, xml_end_reg, &node); ne_xml_push_handler(parser, xml_start_date, xml_cdata, xml_end_date, &node); ne_xml_push_handler(parser, xml_start_decimal, xml_cdata, xml_end_decimal, &node); ne_xml_push_handler(parser, xml_start_mode, xml_cdata, xml_end_mode, &node); ne_xml_push_handler(parser, xml_start_size, xml_cdata, xml_end_size, &node); if (strstr(buf, "") == buf) { ne_xml_push_handler(parser, xml_start_string, xml_cdata, xml_end_string, &node); } else { ne_xml_push_handler(parser, xml_start_string, xml_cdata, xml_end_string_old, &node); } int ret = 0; while (len > 0 && !ret) { ret = ne_xml_parse(parser, buf, len); len = fread(buf, 1, DAV_XML_BUF_SIZE, idx); } ret = ne_xml_parse(parser, buf, 0); free(buf); if (ret) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("error parsing %s"), index); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _(" at line %i"), ne_xml_currentline(parser)); delete_tree(root); } ne_xml_destroy(parser); fclose(idx); free(index); } int resize_cache(void) { int resizing_done = 0; __uint64_t free_space = 0; // AVM optimize dav_node *node2 = 0; // AVM for old node check static time_t timelastcheck = 0; // AVM time_t act_time = time(NULL); size_t i; dav_node *node = NULL; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "resizing cache: start => cache file count %d (max %d) nnodes %d", cache_file_count, max_cache_files, nnodes); while ( (cache_file_count > max_cache_files) || (free_space = get_free_cache_space()) < MIN_FREE_SPACE) { //max files reached or less free space available deleting cache files necessary if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "resizing cache: %.3f MBytes used - %.3f MBytes available, cache file count %d", (cache_size + 80000) / 1024.0 / 1024.0, free_space ? (free_space / 1024.0 / 1024.0) : 0, cache_file_count ); dav_node *least_recent = NULL; cache_size = 0; cache_file_count = 0; for (i = 0; i < table_size; i++) { node = table[i]; while (node) { if (is_cached(node)) { if (!is_open(node) && !is_dirty(node) && !is_created(node) && !is_backup(node) && (!least_recent || node->atime < least_recent->atime)) //find oldest file access least_recent = node; cache_size += node->size; cache_file_count ++; // AVM } node = node->table_next; } } if (!least_recent) { if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "resizing cache not possible - no node found"); break; } //node found, delete the corresponding cache file delete_cache_file(least_recent); if(cache_size >= least_recent->size) cache_size -= least_recent->size; cache_file_count --; // AVM resizing_done = 1; } if (debug && resizing_done) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " result: %.3f MBytes used - %.3f MBytes available, cache file count %d", (cache_size + 80000) / 1024.0 / 1024.0, free_space ? (free_space / 1024.0 / 1024.0) : 0 , cache_file_count); // AVM test for old nodes, check every minute if ( timelastcheck+60 <= act_time) { timelastcheck = time(0); for (i = 0; i < table_size; i++) { node = table[i]; while (node) { node2 = node; node = node->table_next; if (node2->atime && act_time > (node2->atime + file_refresh )) { // access older than file_refresh (10 minutes) if (!is_dir(node2) && node2 != root && !is_backup(node2) && !is_cached(node2)) { if(is_filetranfer_inprogress(node2->path) == 0) { // file node is old => delete it (free memory) node2->parent->utime = 0; // refresh on next access remove_node(node2); } } }// if } // while } // for } // if return resizing_done; } /* new AVM: wenn init Cache, count the cache files (cache_size is also updated) */ int count_cache_files(void) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "count_cache_files: start => cache file count %d (max %d) %.3f MBytes used", cache_file_count, max_cache_files, (cache_size + 80000) / 1024.0 / 1024.0); cache_file_count = 0; cache_size = 0; size_t i; for (i = 0; i < table_size; i++) { dav_node *node = table[i]; while (node) { if (is_cached(node)) { cache_size += node->size; cache_file_count ++; } node = node->table_next; } } if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " count_cache_files: %.3f MBytes used; cache file count %d", (cache_size + 80000) / 1024.0 / 1024.0, cache_file_count); return 0; } /* Creates an entry for node in the index file file, removes the node from the tree and the hash table and deletes the node. The entry will be indented by indent to get proper alignment of nested entries. node : the node. file : the index file for this cache directory. indent : a string of spaces to indent the entry. return value : 0 on success, -1 if an error occurs. */ static int write_node(dav_node *node, FILE *file, const char *indent) { dav_provide_update(); if (node == root) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "writing root"); if (fprintf(file, "\n", type[ROOT], DAV_XML_NS) < 0) return -1; } else if (node == backup) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "writing backup %s", node->path ? node->path : node->name); if (fprintf(file, "%s\n", indent, type[BACKUP]) < 0) return -1; } else if (is_dir(node)) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "writing directory %s", node->path ? node->path : node->name); if (fprintf(file, "%s\n", indent, type[DDIR]) < 0) return -1; } else { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "writing %sfile %s", node->dirty ? "dirty-" : "", node->path ? node->path : node->name); if (fprintf(file, "%s\n", indent, type[REG]) < 0) return -1; } char *ind = ne_concat(indent, " ", NULL); if (node != root && !is_backup(node)) { if (fprintf(file, "%s\n", ind, type[PATH], node->path, type[PATH]) < 0) return -1; } if (node != root && node != backup) { char *name = dav_conv_to_utf_8(node->name); int ret = fprintf(file, "%s\n", ind, type[NAME], name, type[NAME]); free(name); if (ret < 0) return -1; } if (is_reg(node) && node->cache_path != NULL) { char *path = dav_conv_to_utf_8(node->cache_path); int ret = fprintf(file, "%s\n", ind, type[CACHE_PATH], path, type[CACHE_PATH]); free(path); if (ret < 0) return -1; } if (is_reg(node) && !is_backup(node) && node->etag != NULL) { if (fprintf(file, "%s\n", ind, type[ETAG], node->etag, type[ETAG]) < 0) return -1; } if (is_reg(node) && node->mime_type != NULL) { if (fprintf(file, "%s\n", ind, type[MIME], node->mime_type, type[MIME]) < 0) return -1; } if (is_reg(node)) { #if _FILE_OFFSET_BITS == 64 if (fprintf(file, "%s%lli\n", ind, type[SIZE], node->size, type[SIZE]) < 0) #else if (fprintf(file, "%s%li\n", ind, type[SIZE], node->size, type[SIZE]) < 0) #endif return -1; } char t[64]; struct tm *lt = localtime(&node->atime); if(!lt) return -1; strftime(t, 64, "(%FT%T%z)", lt); if (fprintf(file, "%s%li%s\n", ind, type[ATIME], node->atime, t, type[ATIME]) < 0) return -1; lt = localtime(&node->mtime); if(!lt) return -1; strftime(t, 64, "(%FT%T%z)", lt); if (fprintf(file, "%s%li%s\n", ind, type[MTIME], node->mtime, t, type[MTIME]) < 0) return -1; lt = localtime(&node->ctime); if(!lt) return -1; strftime(t, 64, "(%FT%T%z)", lt); if (fprintf(file, "%s%li%s\n", ind, type[CTIME], node->ctime, t, type[CTIME]) < 0) return -1; if (is_reg(node) && !is_backup(node)) { lt = localtime(&node->smtime); strftime(t, 64, "(%FT%T%z)", lt); if (fprintf(file, "%s%li%s\n", ind, type[SMTIME], node->smtime, t, type[SMTIME]) < 0) return -1; } if (is_reg(node) && !is_backup(node) && (is_dirty(node) || is_created(node))) { if (fprintf(file, "%s%i\n", ind, type[REMOTE_EXISTS], node->remote_exists, type[REMOTE_EXISTS]) < 0) return -1; if (fprintf(file, "%s%i\n", ind, type[DIRTY], node->dirty, type[DIRTY]) < 0) return -1; } if (node != backup) { if (fprintf(file, "%s%o\n", ind, type[MODE], node->mode, type[MODE]) < 0) return -1; } if (node != root && node != backup) { if (fprintf(file, "%s%i\n", ind, type[UID], node->uid, type[UID]) < 0) return -1; if (fprintf(file, "%s%i\n", ind, type[GID], node->gid, type[GID]) < 0) return -1; } dav_node *child = node->childs; while (child != NULL) { if (write_node(child, file, ind) < 0) return -1; child = child->next; } if (node == root) { if (fprintf(file, "%s\n", indent, type[ROOT]) < 0) return -1; } else if (node == backup) { if (fprintf(file, "%s\n", indent, type[BACKUP]) < 0) return -1; } else if (is_dir(node)) { if (fprintf(file, "%s\n", indent, type[DDIR]) < 0) return -1; } else { if (fprintf(file, "%s\n", indent, type[REG]) < 0) return -1; } free(ind); return 0; } /* Concatenate data from subsequent callbacks into xml_data. */ static int xml_cdata(void *userdata, int state, const char *cdata, size_t len) { if (!xml_data) { xml_data = ne_strndup(cdata, len); } else { char *add = ne_strndup(cdata, len); char *new = ne_concat(xml_data, add, NULL); free(add); free(xml_data); xml_data = new; } return 0; } /* Finishes the creation of directory backup. userdata is set to the parent of backup. return value : allways 0. */ static int xml_end_backup(void *userdata, int state, const char *nspace, const char *name) { dav_node *dir = *((dav_node **) userdata); *((dav_node **) userdata) = dir->parent; if (dir->path) { free(dir->path); dir->path = NULL; } if (dir->name) { free(dir->name); dir->name = NULL; } delete_cache_file(dir); if (dir->etag) { free(dir->etag); dir->etag = NULL; } dir->smtime = 0; return 0; } /* xml_data must be a string representing a value as a decimal number. Its value is assigned to the appropriate member of node userdata. state indacates the member of node. return value : 0 on success, -1 if an error occurs. */ static int xml_end_date(void *userdata, int state, const char *nspace, const char *name) { if (!xml_data) return -1; char *tail; time_t t = strtol(xml_data, &tail, 10); if (*tail != '\0' && *tail != '(') { free(xml_data); xml_data = NULL; return -1; } free(xml_data); xml_data = NULL; switch (state) { case ATIME: (*((dav_node **) userdata))->atime = t; break; case MTIME: (*((dav_node **) userdata))->mtime = t; break; case CTIME: (*((dav_node **) userdata))->ctime = t; break; case SMTIME: (*((dav_node **) userdata))->smtime = t; break; default: return -1; } return 0; } /* xml_dat must be a string representation of a decimal number. Its value is assigned to the appropriate member of node userdata. state indacates the member of node. return value : 0 on success, -1 if an error occurs. */ static int xml_end_decimal(void *userdata, int state, const char *nspace, const char *name) { if (!xml_data) return -1; char *tail; long int n = strtol(xml_data, &tail, 10); if (*tail != '\0') { free(xml_data); xml_data = NULL; return -1; } free(xml_data); xml_data = NULL; switch (state) { case UID: (*((dav_node **) userdata))->uid = n; break; case GID: (*((dav_node **) userdata))->gid = n; break; case DIRTY: (*((dav_node **) userdata))->dirty = n; break; case REMOTE_EXISTS: (*((dav_node **) userdata))->remote_exists = n; break; default: return -1; } return 0; } /* Finishes the creation of a directory. Members name and path of the not must not be NULL, or the direcotry tree will be deleted. userdata is set to the parent of the directory. return value : allways 0. */ static int xml_end_dir(void *userdata, int state, const char *nspace, const char *name) { dav_node *dir = *((dav_node **) userdata); *((dav_node **) userdata) = dir->parent; if (!dir->name || !dir->path) { delete_tree(dir); return 0; } delete_cache_file(dir); if (dir->etag) { free(dir->etag); dir->etag = NULL; } dir->size = 0; dir->smtime = 0; return 0; } /* xml_data must be a string representation of an octal number. Its value is assigned to member mode of node userdata. return value : 0 on success, -1 if an error occurs. */ static int xml_end_mode(void *userdata, int state, const char *nspace, const char *name) { if (!xml_data) return -1; char *tail; (*((dav_node **) userdata))->mode = strtol(xml_data, &tail, 8); if (*tail != '\0') { free(xml_data); xml_data = NULL; return -1; } free(xml_data); xml_data = NULL; return 0; } /* Finishes the creation of a node of a regular file. Members path, name and cach_path must not be NULL and the cache file must exist, or the node will be deleted. If the file is in directory backup, member path may be NULL. userdata is set to the parent of the file node. return value : allways 0. */ static int xml_end_reg(void *userdata, int state, const char *nspace, const char *name) { dav_node *reg = *((dav_node **) userdata); *((dav_node **) userdata) = reg->parent; struct stat st; if (!reg->name || !reg->cache_path || stat(reg->cache_path, &st) != 0 || reg->size != st.st_size || (!reg->path && !is_backup(reg))) { if (reg->cache_path) { remove(reg->cache_path); free(reg->cache_path); reg->cache_path = NULL; } delete_tree(reg); return 0; } cache_size += reg->size; if (is_backup(reg)) { if (reg->path) { free(reg->path); reg->path = NULL; } if (reg->etag) { free(reg->etag); reg->etag = NULL; } reg->smtime = 0; } else if (is_dirty(reg) || is_created(reg)) { add_to_changed(reg); set_upload_time(reg); } return 0; } /* Finishes the creation of the root directory. userdata must be equal to root, or the complete tree will be deleted. Members path, name, cache_path,etag and mime_type will be NULL. return value : allways 0. */ static int xml_end_root(void *userdata, int state, const char *nspace, const char *name) { dav_node *dir = *((dav_node **) userdata); if (dir != root) delete_tree(dir); if (dir->path) { free(dir->path); dir->path = NULL; } if (dir->name) { free(dir->name); dir->name = NULL; } delete_cache_file(dir); if (dir->etag) { free(dir->etag); dir->etag = NULL; } if (dir->mime_type) { free(dir->mime_type); dir->mime_type = NULL; } dir->size = 0; dir->smtime = 0; return 0; } /* xml_data must be a string representation of a decimal number representing a file size. Its value is assigned to member size of the node. return value : 0 on success, -1 if an error occurs. */ static int xml_end_size(void *userdata, int state, const char *nspace, const char *name) { if (!xml_data) return -1; char *tail; #if _FILE_OFFSET_BITS == 64 (*((dav_node **) userdata))->size = strtoll(xml_data, &tail, 10); #else (*((dav_node **) userdata))->size = strtol(xml_data, &tail, 10); #endif if (*tail != '\0') { free(xml_data); xml_data = NULL; return -1; } free(xml_data); xml_data = NULL; return 0; } /* Stores xml_data in the appropriate member of node userdata. state indicates the member of node. return value : 0 on success, -1 if an error occurs. */ static int xml_end_string(void *userdata, int state, const char *nspace, const char *name) { if (!xml_data) return -1; switch (state) { case PATH: (*((dav_node **) userdata))->path = xml_data; break; case NAME: (*((dav_node **) userdata))->name = dav_conv_from_utf_8(xml_data); free(xml_data); break; case CACHE_PATH: (*((dav_node **) userdata))->cache_path = dav_conv_from_utf_8(xml_data); free(xml_data); break; case ETAG: (*((dav_node **) userdata))->etag = xml_data; break; case MIME: (*((dav_node **) userdata))->mime_type = xml_data; break; default: free(xml_data); xml_data = NULL; return -1; } xml_data = NULL; return 0; } /* Stores xml_data in the appropriate member of node userdata. state indicates the member of node. return value : 0 on success, -1 if an error occurs. */ static int xml_end_string_old(void *userdata, int state, const char *nspace, const char *name) { if (!xml_data) return -1; switch (state) { case PATH: (*((dav_node **) userdata))->path = xml_data; break; case NAME: (*((dav_node **) userdata))->name = xml_data; break; case CACHE_PATH: (*((dav_node **) userdata))->cache_path = xml_data; break; case ETAG: (*((dav_node **) userdata))->etag = xml_data; break; case MIME: (*((dav_node **) userdata))->mime_type = xml_data; break; default: free(xml_data); xml_data = NULL; return -1; } xml_data = NULL; return 0; } /* Will be called when the start tag of a XML-element is found, and tests wheather it is a BACKUP elemt. In this case parent must be ROOT. userdata will be set to the newly created node backup and also the global variable backup will be set. return value : 0 not responsible for this kind of element. BACKUP if it is the backup element -1 XML error, parent is not root. */ static int xml_start_backup(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { if (strcmp(name, type[BACKUP]) != 0) return 0; if (parent != ROOT) return -1; dav_node *dir = new_node(*((dav_node **) userdata), S_IFDIR | S_IRWXU); backup = dir; *((dav_node **) userdata) = dir; return BACKUP; } /* Will be called when the start tag of a XML-element is found, and tests whether it is an element that contains a date. In this case the parent must be either a directory (including root and backup) or a file node. return value : 0 not responsible for this kind of element. A value that indicates which member of a node the decimal value must be assigned to. -1 XML error, parent must not contain this property. */ static int xml_start_date(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { int ret; if (strcmp(name, type[ATIME]) == 0) { ret = ATIME; } else if (strcmp(name, type[MTIME]) == 0) { ret = MTIME; } else if (strcmp(name, type[CTIME]) == 0) { ret = CTIME; } else if (strcmp(name, type[SMTIME]) == 0) { ret = SMTIME; } else { return 0; } if (parent != DDIR && parent != REG && parent != BACKUP && parent != ROOT) return -1; return ret; } /* Will be called when the start tag of a XML-element is found, and tests whether it is an element that contains a decimal value. In this case the parent must be either a directory (including root and backup) or a file node. If parent is ROOT or BACKUP the element type must not be UID or GID. DIRTY and REMOTE_EXISTS are only allowed for parent REG. return value : 0 not responsible for this kind of element. A value that indicates which member of a node the decimal value must be assigned to. -1 XML error, parent must not contain this property. */ static int xml_start_decimal(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { int ret; if (strcmp(name, type[UID]) == 0) { ret = UID; } else if (strcmp(name, type[GID]) == 0) { ret = GID; } else if (strcmp(name, type[DIRTY]) == 0) { ret = DIRTY; } else if (strcmp(name, type[REMOTE_EXISTS]) == 0) { ret = REMOTE_EXISTS; } else { return 0; } if (parent != DDIR && parent != REG && parent != BACKUP && parent != ROOT) return -1; if ((parent == BACKUP || parent == ROOT) && (ret == UID || ret == GID)) return -1; if (parent != REG && (ret == DIRTY || ret == REMOTE_EXISTS)) return -1; return ret; } /* Will be called when the start tag of a XML-element is found, and tests whether it is an directory element. Inthis case parent must be a directory (including root but nur backup). userdata will be set to the newly created directory node. return value : 0 not responsible for this kind of element. DDIR if it is a directory element. -1 XML error, parent must not contain this property. */ static int xml_start_dir(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { if (strcmp(name, type[DDIR]) != 0) return 0; if (parent != DDIR && parent != ROOT) return -1; dav_node *dir = new_node(*((dav_node **) userdata), default_dir_mode); *((dav_node **) userdata) = dir; return DDIR; } /* Will be called when the start tag of a XML-element is found, and tests whether it is an mode element. In this case parent must be a regular node or a directory (including root but not backup). return value : 0 not responsible for this kind of element. MODE if it is a mode element. -1 XML error, parent must not contain this property. */ static int xml_start_mode(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { if (strcmp(name, type[MODE]) != 0) return 0; if (parent != DDIR && parent != REG && parent != ROOT) return -1; return MODE; } /* Will be called when the start tag of a XML-element is found, and tests whether it is an element that represents a file node. In this case parent must be a directory (including root and backup). userdata will be set to the newly created file node. return value : 0 not responsible for this kind of element. REG if the element represents a file node. -1 XML error, parent must not contain this property. */ static int xml_start_reg(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { if (strcmp(name, type[REG]) != 0) return 0; if (parent != DDIR && parent != BACKUP && parent != ROOT) return -1; dav_node *reg = new_node(*((dav_node **) userdata), default_file_mode); *((dav_node **) userdata) = reg; if (parent != BACKUP) reg->remote_exists = 1; return REG; } /* Will be called when the start tag of a XML-element is found, and tests whether the element represents the root node. In this case parent must be 0. return value : 0 not responsible for this kind of element. ROOT if it is the root node element. -1 XML error, root property with parent not equal 0. */ static int xml_start_root(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { if (strcmp(name, type[ROOT]) != 0) return 0; if (parent != 0) return -1; return ROOT; } /* Will be called when the start tag of a XML-element is found, and tests whether it is an element that contains the file size. In this case the parent must be a file node. return value : 0 not responsible for this kind of element. SIZE if the element represents the file size. value must be assigned to. -1 XML error, parent must not contain this property. */ static int xml_start_size(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { if (strcmp(name, type[SIZE]) != 0) { return 0; } if (parent != REG) return -1; return SIZE; } /* Will be called when the start tag of a XML-element is found, and tests whether it is an element that rcontains a string. In this case parent must be a directory or a file node. return value : 0 not responsible for this kind of element. A value that indicates which member of a node the string must be assigned to. -1 XML error, parent must not contain this property. */ static int xml_start_string(void *userdata, int parent, const char *nspace, const char *name, const char **atts) { int ret; if (strcmp(name, type[PATH]) == 0) { ret = PATH; } else if (strcmp(name, type[NAME]) == 0) { ret = NAME; } else if (strcmp(name, type[CACHE_PATH]) == 0) { ret = CACHE_PATH; } else if (strcmp(name, type[ETAG]) == 0) { ret = ETAG; } else if (strcmp(name, type[MIME]) == 0) { ret = MIME; } else { return 0; } if (parent != DDIR && parent != REG) return -1; return ret; } /* Auxiliary. */ /* Tries to evaluate the alignment of structure dav_node. It allocates dav_node structures and random length strings alternatively and inspects the address. return value : the alignment (e.g. alignment = 4 means addresses are always multiples of 4 */ static size_t test_alignment() { srand(time(0)); size_t align = 64; size_t trials = 100; char *s[trials]; dav_node *n[trials]; size_t j = 0; while (align > 0 && j < trials) { size_t rand_val = (rand() / (RAND_MAX / 1024)) % (4 *align); if(rand_val < 1){ //do not call malloc with a value of 0 s[j] = NULL; n[j] = NULL; } else{ s[j] = (char *) ne_malloc(rand_val); n[j] = (dav_node *) ne_malloc(sizeof(dav_node)); while (align > 0 && ((size_t) n[j] % align) > 0) align /= 2; } ++j; } for (j = 0; j < trials; j++) { if (n[j]) free(n[j]); if (s[j]) free(s[j]); } return align; }