Logo Search packages:      
Sourcecode: davfs2 version File versions  Download package

cache.c

/* 
** DAV directory chache
 */

/*
 *  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 2 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"

#include <argz.h>
#include <dirent.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <grp.h>
#include <libintl.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>

#ifdef ENABLE_NLS
#   include <libintl.h>
#   define _(String) gettext(String)
#else
#   define _(String) String
#endif

#include <sys/stat.h>

#include <ne_alloc.h>
#include <ne_dates.h>
#include <ne_string.h>
#include <ne_uri.h>
#include <ne_xml.h>

#include "dav_debug.h"
#include "defaults.h"
#include "webdav.h"
#include "cache.h"


/* Private constants */
/*===================*/

/* 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,
    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",
    [END] = NULL
};


/* Private global variables */
/*==========================*/

/* Root node of the directory cache. */
static dav_node *root;

/* Directory for local buckups. */
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;

/* How long results of PROPFIND for directories are considered valid. */
static time_t dir_refresh;

/* Wait that much seconds from last file access before doing a new
   GET If-Modiefied request. */
static time_t file_refresh;

/* 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;

/* 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;


/* Private function prototypes and inline functions */
/*==================================================*/

/* Node operations. */

static void add_node(dav_node *parent, dav_props *props);

static inline void attr_from_cache_file(dav_node *node) {

    struct stat st;
    if (node->cache_path == NULL || stat(node->cache_path, &st) != 0)
        return;
    node->size = st.st_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 void backup_node(dav_node *orig);

static void clean_tree(dav_node *node);

static void delete_node(dav_node *node);

static void delete_tree(dav_node *node);

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_table(dav_node *node);

static void remove_from_tree(dav_node *node);

static void remove_node(dav_node *node);

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 != NULL && 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 != NULL);
}

static inline int is_created(const dav_node *node) {

    return (S_ISREG(node->mode) && node->cache_path != NULL
            && !node->remote_exists && node->parent != NULL
            && 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 != 0);
}

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 != NULL) {
        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 != NULL) {
        remove(node->cache_path);
        free(node->cache_path);
        node->cache_path = NULL;
        node->dirty = 0;
    }
}

static inline void set_cache_file_times(dav_node *node) {

    if (node->cache_path == NULL)
        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);

/* Permanent cache maintenance. */

static void check_cache_dir(const char *dir, const char *host,
                            const char *path, const char *mpoint);

static void clean_cache(void);
static void parse_index(void);

static void resize_cache(unsigned long long cache_size);

static int write_node(dav_node *node, FILE *file, const char *indent);

static int xml_cdata_decimal(void *userdata, int state, const char *cdata,
                             size_t len);

static int xml_cdata_mode(void *userdata, int state, const char *cdata,
                          size_t len);

static int xml_cdata_size(void *userdata, int state, const char *cdata,
                             size_t len);

static int xml_cdata_string(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_dir(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_start_backup(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();


/* Public functions */
/*==================*/

void dav_init_cache(uid_t uid, gid_t gid, mode_t file_mode, mode_t dir_mode,
                    mode_t f_umask, mode_t d_umask, size_t hash_table_sz,
                    time_t dir_rf, time_t file_rf, int gui_opt, time_t dir_rt,
                    time_t dir_max_rt, time_t lock_rf, size_t cache_size,
                    const char *tl_cache_dir, const char *backup_dir,
                    const char *host, const char *path, const char *mpoint) {

    alignment = test_alignment();
    DBG1("  Alignment of dav_node: %i", alignment);

    default_uid = uid;
    default_gid = gid;

    default_file_mode = file_mode;
    default_dir_mode = dir_mode;
    file_umask = f_umask;
    dir_umask = d_umask;

    table_size = hash_table_sz;
    table = ne_calloc(sizeof(*table) * table_size);

    dir_refresh = dir_rf;
    file_refresh = file_rf;
    gui_optimize = gui_opt;
    retry = dir_refresh;
    min_retry = dir_rt;
    max_retry = dir_max_rt;
    lock_refresh = lock_rf;

    DBG0("  Checking cache directory.");
    max_cache_size = cache_size * 0x100000;
    check_cache_dir(tl_cache_dir, host, path, mpoint);
    DBG1("  %s", cache_dir);

    root = new_node(NULL, default_dir_mode);
    DBG0("  Reading stored cache data.");
    parse_index();
    root->name = ne_strdup("");
    root->path = dav_cconvert(path);
    root->mode = default_dir_mode;

    clean_cache();

    if (backup == NULL)
        backup = new_node(root, S_IFDIR | S_IRWXU);
    backup->name = ne_strdup(backup_dir);
    backup->mode = S_IFDIR | S_IRWXU;

    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 == 0)
            printf(_("Last try succeeded.\n"));
    }
    if (ret == EAGAIN) {
        error(0, 0, _("server temporarily unreachable;\n"
                      "mounting anyway"));
    } else if (ret != 0) {
        error(EXIT_FAILURE, 0, _("Mounting failed.\n%s"),
              dav_get_webdav_error());
    }
}


void dav_close_cache(void) {

    write_dir_entry = &write_dir_entry_dummy;
    flush = &flush_dummy;

    clean_tree(root);

    char *new_index = ne_concat(cache_dir, "/", DAV_INDEX, ".new", NULL);
    DBG1("  Creating index %s.", new_index);
    FILE *new_file = fopen(new_index, "w");
    if (new_file != NULL) {

        int ret = fprintf(new_file, "<?xml version=\"1.0\" ?>\n");
        if (ret >= 0)
            ret = write_node(root, new_file, "");

        fclose(new_file);

        if (ret >= 0) {
            DBG0("  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);
}


size_t dav_register_kernel_interface(dav_write_dir_entry_fn write_fn,
                                     int *flush_flag, unsigned int *blksize) {

    if (write_fn != NULL)
        write_dir_entry = write_fn;

    if (flush_flag != NULL)
        flush = flush_flag;

    if (blksize != NULL) {
        struct stat st;
        if (stat(cache_dir, &st) == 0) {
            *blksize = st.st_blksize;
        } else {
            *blksize = 4096;
        }
    }

    return alignment;
}


int dav_tidy_cache(void) {

    static size_t tidy_next = 0;
    static unsigned long long cache_size = 0;
    DBG1("TIDY_CACHE: from index %i ", tidy_next);

    int n_requests = 0;
    time_t lock_expire = time(NULL) + lock_refresh;

    while (tidy_next < table_size && n_requests < 4) {

        dav_node *node = table[tidy_next];
        while (node != NULL) {

            if ((is_dirty(node) || is_created(node)) && !is_open_write(node)
                    && !is_backup(node)) {

                int set_execute = -1;
                if (is_created(node)
                        && node->mode & (S_IXUSR | S_IXGRP | S_IXOTH))
                    set_execute = 1;
                int ret = dav_put(node->path, node->cache_path,
                                  &node->remote_exists, &node->lock_expire,
                                  &node->etag, &node->smtime, set_execute);
                if (ret == 0) {
                    node->utime = time(NULL);
                    node->dirty = 0;
                } else if (ret == EACCES || ret == EINVAL || ret == ENOENT
                           || ret == EPERM || ret == ENOSPC || ret == EEXIST) {
                    delete_cache_file(node->parent);
                    node->parent->utime = 0;
                    remove_node(node);
                    *flush = 1;
                    node = NULL;
                }
                n_requests += 3;
            }

            if (node != NULL && is_locked(node) && !is_dirty(node)
                    && !is_created(node) && !is_open_write(node)) {
                dav_unlock(node->path, &node->lock_expire);
                n_requests += 1;
            }

            if (node != NULL && is_locked(node)
                    && node->lock_expire < lock_expire) {
                dav_lock_refresh(node->path, &node->lock_expire);
                n_requests += 1;
            }

            if (node != NULL && is_cached(node))
                cache_size += node->size;

            if (node != NULL) {
                node = node->table_next;
            } else {
                node = table[tidy_next];
            }
        }
        tidy_next++;

    }
    DBG1("              to index %i ", tidy_next -1);

    if (tidy_next >= table_size) {
        if (cache_size > max_cache_size)
            resize_cache(cache_size);
        tidy_next = 0;
        cache_size = 0;
        return 0;
    } else {
        return 1;
    }
}


/* Upcalls from the kernel. */

int dav_access(dav_node *node, uid_t uid, int how) {

    if (!is_valid(node))
        return ENOENT;
    DBG1("  \"%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;
    DBG1("  \"%s\"", node->path);

    dav_handle *fh = get_file_handle(node, fd, flags & O_ACCMODE, pid, pgid);
    if (fh == 0)
        return EBADF;

    close_fh(node, fh);

    if (node->parent == NULL && node != root && !is_open(node)) {
        remove_from_table(node);
        delete_node(node);
        *flush = 1;
        return 0;
    }

    if (is_dir(node)) {
        node->atime = time(NULL);
    } else {
        attr_from_cache_file(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;
    DBG2("  \"%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) != NULL)
        return EEXIST;

    struct passwd *pw = getpwuid(uid);
    if (pw == NULL)
        return EINVAL;

    char *name_conv = dav_cconvert(name);
    char *path = ne_concat(parent->path, name_conv, NULL);
    free(name_conv);
    if (dav_head(path, NULL, NULL) == 0) {
        free(path);
        return EEXIST;
    }

    *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 == 0)
        ret = dav_lock(path, &(*nodep)->lock_expire, &(*nodep)->remote_exists);
    if (ret == 0) {
        (*nodep)->smtime = (*nodep)->mtime;
        if (!is_created(*nodep))
            dav_head((*nodep)->path, &(*nodep)->etag, &(*nodep)->smtime);
        (*nodep)->utime = (*nodep)->smtime;
        delete_cache_file(parent);
        *flush = 1;
        parent->mtime = (*nodep)->mtime;
        parent->ctime = (*nodep)->mtime;
    } 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;
    DBG1("  \"%s\"", node->path);
    if (node->parent != NULL && !has_permission(node->parent, uid, X_OK | R_OK))
        return EACCES;

    if (is_dir(node)) {
        if (node->utime == 0)
            update_directory(node, retry);
        if (create_dir_cache_file(node) != 0)
            return EIO;
    } else if (is_open(node)) {
        attr_from_cache_file(node);
    }

    return 0;
}


int dav_lookup(dav_node **nodep, dav_node *parent, const char *name,
               uid_t uid) {

    if (!is_valid(parent))
        return ENOENT;
    DBG2("  \"%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)
        return ENOENT;

    if (is_dir(*nodep)) {
        if ((*nodep)->utime == 0)
            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;
    DBG2("  \"%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;

    update_directory(parent, retry);
    if (get_child(parent, name) != NULL)
        return EEXIST;

    struct passwd *pw = getpwuid(uid);
    if (pw == NULL)
        return EINVAL;

    char *name_conv = dav_cconvert(name);
    char *path = ne_concat(parent->path, name_conv, "/", NULL);
    free(name_conv);
    int ret = dav_make_collection(path);

    if (ret == 0) {
        *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) {

    if (!is_valid(node))
        return ENOENT;
    DBG1("  \"%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 (!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, flags & O_ACCMODE, pid, pgid, uid);
    }

    int ret = 0;
    if ((O_ACCMODE & flags) == O_RDONLY) {

        ret = update_cache_file(node);
        if (ret == 0) 
            ret = open_file(fd, node, flags & O_ACCMODE, pid, pgid, uid);

    } else {

        if (!is_locked(node) && !is_backup(node))
            ret = dav_lock(node->path, &node->lock_expire,
                           &node->remote_exists);

        if (ret == 0 && (flags & O_TRUNC)) {
            ret = create_cache_file(node);
            if (ret == 0) {
                ret = open_file(fd, node,
                                flags & (O_ACCMODE | O_TRUNC | O_APPEND),
                                pid, pgid, uid);
            }
        } else if (ret == 0) {
            ret = update_cache_file(node);
            if (ret == 0)
                ret = open_file(fd, node, flags & (O_ACCMODE | O_APPEND),
                                pid, pgid, uid);
        }
    }

    return ret;
}


int dav_read(ssize_t *len, dav_node * node, int fd, char *buf, size_t size,
             off_t offset) {

    if (!exists(node))
        return ENOENT;

    dav_handle *fh = get_file_handle(node, fd, 0, 0, 0);
    if (fh == NULL)
        return EBADF;
    if (fh->flags == O_WRONLY)
        return EINVAL;

    *len = pread(fd, buf, size, offset);
    DBG1("  read %i", *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;
    DBG2("  \"%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 == NULL) {
        delete_cache_file(parent);
        parent->utime = 0;
        *flush = 1;
        return ENOENT;
    }
    if (is_dir(node))
        return EISDIR;

    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 != 0)
        return ret;

    remove_from_tree(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;
    DBG2("  \"%s%s\"", src_parent->path, src_name);
    DBG2("  \"%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 == NULL) {
        delete_cache_file(src_parent);
        src_parent->utime = 0;
        *flush = 1;
        return ENOENT;
    }
    if (src == backup || (dst != NULL && is_backup(dst)))
        return EINVAL;

    int ret;
    if (is_dir(src)) {
        ret = move_dir(src, dst, dst_parent, dst_name);
    } else {
        if (is_created(src) || is_backup(src) || is_dirty(src)) {
            ret = move_dirty(src, dst, dst_parent, dst_name);
        } else {
            ret = move_reg(src, dst, dst_parent, dst_name);
        }
    }

    if (ret == 0) {
        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;
        }
        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;
    DBG2("  \"%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 == NULL) {
        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 != 0)
        return ENOTEMPTY;

    int ret = dav_delete_dir(node->path);
    if (ret == 0) {
        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;
    DBG1("  \"%s\"", node->path);
    if (node->parent != NULL && !has_permission(node->parent, uid, X_OK))
        return EACCES;

    if (so) {
        DBG1("  set owner to %i", owner);
        if (node == backup)
            return EINVAL;
        if (uid != 0 && (uid != owner || uid != node->uid))
            return EPERM;
        if (getpwuid(owner) == NULL)
            return EINVAL;
    }

    if (sg) {
        DBG1("  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) == NULL)
            return EINVAL;
    }

    if (sm) {
        DBG1("  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) {
        DBG0("  set times");
        if (uid != node->uid && uid != 0 && !has_permission(node, uid, W_OK))
            return EPERM;
    }

    if (ssz) {
        DBG0("  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 == 0 && size == 0) {
            ret = create_cache_file(node);
        } else if (ret == 0) {
            ret = update_cache_file(node);
        }
        if (ret == 0)
            ret = truncate(node->cache_path, size);
        if (ret != 0)
            return ret;
        attr_from_cache_file(node);
        node->dirty = 1;
    }

    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);
                    if (etag != NULL && node->etag != NULL
                            && strcmp(etag, node->etag) != 0)
                        err = EIO;
                    if (smtime != 0 && smtime > node->smtime)
                        err = EIO;
                    if (etag != NULL)
                        free(etag);
                    if (err != 0)
                        return EIO;
                }
                dav_set_execute(node->path, set_execute);
                if (is_dirty(node))
                    dav_head(node->path, &node->etag, &node->smtime);
            }
        }
        node->mode = (node->mode & ~DAV_A_MASK) | mode;
    }

    if (sat)
        node->atime = atime;

    if (smt)
        node->mtime = mtime;

    node->ctime = time(NULL);
    return 0;
}


dav_stat dav_statfs(void) {

    dav_stat st;
    st.blocks = 0x65B9AA;
    st.bfree = 0x32DCD5;
    st.bavail = 0x32DCD5;
    st.files = 0x196E6A;
    st.ffree = 0x196E6B;
    struct stat cache_st;
    if (stat(cache_dir, &cache_st) == 0) {
        st.bsize = cache_st.st_blksize;
    } else {
        st.bsize = 4096;
    }
    st.namelen = 256;

    return st;
}


int dav_sync(dav_node *node) {

    if (!exists(node))
        return ENOENT;

    dav_handle *fh = node->handles;
    while (fh != NULL) {
        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) {

    if (!exists(node))
        return ENOENT;

    dav_handle *fh = get_file_handle(node, fd, 0, 0, 0);
    if (fh == NULL)
        return EBADF;
    if (fh->flags == O_RDONLY)
        return EINVAL;

    *written = 0;
    ssize_t n = 0;
    while (*written < size && n >= 0) {
        n = pwrite(fd, buf + *written, size - *written, offset + *written);
        if (n < 0)
            return errno;
        *written += n;
    }

    DBG1("  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);
    DBG1("    \"%s\"", node->path);
}


/* 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 == NULL)
        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;
    orig->dirty = 0;
    node->size = orig->size;
    node->uid = default_uid;
    node->gid = default_gid;
    DBG1("  Created backup of %p", orig);
    DBG1("    \"%s\"", orig->path);
    delete_cache_file(backup);
    backup->mtime = time(NULL);
    backup->ctime = backup->mtime;
    *flush = 1;
}


/* Scans the directory tree starting from node and
   - 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) {

    if (node == backup)
        return;

    if (is_dir(node)) {

        dav_node *child = node->childs;
        while (child != NULL) {
            dav_node *next = child->next;
            clean_tree(child);
            child = next;
        }
        if (node->childs == NULL && 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))
            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)) {

        int set_execute = -1;
        if (is_created(node)
                && node->mode & (S_IXUSR | S_IXGRP | S_IXOTH))
            set_execute = 1;
        int ret = dav_put(node->path, node->cache_path,
                          &node->remote_exists, &node->lock_expire,
                          &node->etag, &node->smtime, set_execute);
        if (is_locked(node))
            dav_unlock(node->path, &node->lock_expire);
        if (ret == 0) {
            node->mtime = node->smtime;
            if (node->mtime > node->atime)
                node->atime = node->mtime;
            set_cache_file_times(node);
        } else {
            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);
    }
}


/* 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) {

    DBG1("  Deleting node %p", node);
    if (node->path != NULL)
        free(node->path);
    if (node->name != NULL)
        free(node->name);
    delete_cache_file(node);
    if (node->etag != NULL)
        free(node->etag);
    while (node->handles != NULL) {
        dav_handle *tofree = node->handles;
        node->handles = node->handles->next;
        close(tofree->fd);
        free(tofree);
    }
    free(node);
}


/* 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) {

    while (node->childs != NULL)
        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) {

    if (dst != NULL && !is_dir(dst))
        return ENOTDIR;
    if (dst != NULL && is_busy(dst))
        return EBUSY;

    char *dst_path;
    if (dst == NULL) {
        char *dst_conv = dav_cconvert(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 (dst != NULL)
        remove_node(dst);

    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. */
static int move_dirty(dav_node *src, dav_node *dst, dav_node *dst_parent,
                      const char *dst_name) {

    if (dst != NULL && is_dir(dst))
        return EISDIR;

    char *dst_path;
    if (dst == NULL) {
        char *dst_conv = dav_cconvert(dst_name);
        dst_path = ne_concat(dst_parent->path, dst_name, NULL);
        free(dst_conv);
    } else {
        dst_path = ne_strdup(dst->path);
    }

    if (dst != NULL) {
        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 != 0) {
            free(dst_path);
            return EIO;
        }
        remove_from_tree(dst);
        if (is_open(dst)) {
            DBG1("  Invalidating node %p", dst);
            dst->parent = NULL;
        } else {
            remove_from_table(dst);
            delete_node(dst);
        }
    }

    if (is_created(src)) {
        if (is_locked(src))
            dav_unlock(src->path, &src->lock_expire);
    } else {
        dav_delete(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 != NULL) {
        free(src->etag);
        src->etag = NULL;
    }
    src->smtime = time(NULL);

    if (!is_created(src))
        dav_head(src->path, &src->etag, &src->smtime);
    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 != NULL && is_dir(dst))
        return EISDIR;

    char *dst_path;
    if (dst == NULL) {
        char *dst_conv = dav_cconvert(dst_name);
        dst_path = ne_concat(dst_parent->path, dst_name, 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 (dst != NULL) {
        remove_from_tree(dst);
        if (is_open(dst)) {
            DBG1("  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 = 0;

    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 != NULL) {
        if (S_ISDIR(mode))
            ++parent->nref;
        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->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;

    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;

    DBG2("  New node: %p->%p", node->parent, node);
    return node;
}


/* 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 != NULL && *np != node)
        np = &(*np)->table_next;
    if (*np != NULL)
        *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 != NULL && *np != node)
        np = &(*np)->next;
    if (*np != NULL) {
        *np = node->next;
        if (is_dir(node))
            --node->parent->nref;
    }
}


/* 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 {

        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)) {
            DBG1("  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 != 0) {
        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 != NULL) {
        dav_node *next = child->next;
        if (!is_backup(child)) {
            dav_props **pp = &props;
            while (*pp != NULL && strcmp((*pp)->path, child->path) != 0)
                pp = &(*pp)->next;
            if (*pp != NULL) {
                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 != NULL) {
        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 != NULL)
                free(dir->etag);
            dir->etag = props->etag;
            props->etag = NULL;
            dav_delete_props(props);
        }
        props = next;
    }

    if (changed) {
        delete_cache_file(dir);
        *flush = 1;
    }

    DBG2("  Directory updated: %p->%p", dir->parent, dir);
    DBG1("  \"%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 (node->parent == NULL)
        return 0;
    int ret = 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 (is_created(node)) {
        if (!is_open(node) && (!is_locked(node) || props->size > 0)) {
            add_node(node->parent, props);
            remove_node(node);
            *flush = 1;
            return 1;
        } else {
            dav_delete_props(props);
            return 0;
        }
    }

    if (is_cached(node)) {
        if ((node->etag == NULL && props->mtime > node->smtime)
                || (node->etag != NULL && props->etag != NULL
                    && strcmp(node->etag, props->etag) != 0)) {
            if (is_open(node)) {
                node->utime = 0;
                dav_delete_props(props);
                return 0;
            } else {
                add_node(node->parent, props);
                remove_node(node);
                *flush = 1;
                return 1;
            }
        } else {
            node->utime = time(NULL);
            dav_delete_props(props);
            return 0;
        }
    }

    if (strcmp(node->name, props->name) != 0) {
        free(node->name);
        node->name = props->name;
        props->name = NULL;
        ret = 1;
        *flush = 1;
    }

    if (props->mtime > node->atime)
        node->atime = props->mtime;
    if (props->mtime > node->smtime) {
        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 != NULL)
        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)) == 0) {
            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)) != 0) {
            node->mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
            *flush = 1;
        }
        if (props->size != 0 && props->size != node->size) {
            node->size = props->size;
            *flush = 1;
        }
    }

    dav_delete_props(props);

    DBG2("  Node updated: %p->%p", node->parent, node);
    DBG1("    \"%s\"", node->path);
    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 != NULL) {
        update_path(n, src_path, dst_path);
        n = n->next;
    }

    if (node->path == NULL || 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 != NULL && n != node)
        n = n->table_next;

    if (n != NULL) {
        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 != 0) {
        while (fh != NULL && fh->fd != fd)
            fh = fh->next;
    } else {
        while (fh != NULL
                && (fh->flags != accmode || fh->pid != pid))
            fh = fh->next;
        if (fh == NULL) {
            fh = node->handles;
            while (fh != NULL
                    && (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 != NULL && !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 == NULL)
        return 0;
    if (pw->pw_gid != node->gid) {
        struct group *grp = getgrgid(node->gid);
        if (grp == NULL)
            return 0;
        char **members = grp->gr_mem;
        while (*members != NULL && strcmp(*members, pw->pw_name) == 0)
            members++;
        if (*members == NULL)
            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 != NULL) {
        if (is_busy(child))
            return 1;
    }

    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 == NULL && node != root))
        return 0;

    if (node == root || node == backup)
        return 1;

    update_directory(node->parent, retry);
    if (!exists(node) || (node->parent == NULL && 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) != NULL && (*fhp) != fh)
        fhp = &(*fhp)->next;
    if (*fhp != NULL)
        *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 != NULL) {
        if (access(node->cache_path, F_OK) == 0) {
            return 0;
        } else {
            free(node->cache_path);
        }
    }

    node->cache_path = ne_concat(cache_dir, "/", node->name, "-XXXXXX", NULL);

    int 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;
    }

    close(fd);
    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 != NULL) {
        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_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 != NULL) {
        size = write_dir_entry(fd, size, node, node->name);
        node = node->next;
    }

    if (size > 0)
        size = write_dir_entry(fd, size, NULL, NULL);
    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);
    if (*fd <= 0)
        return EIO;
    dav_handle *fh = (dav_handle *) ne_malloc(sizeof(dav_handle));
    fh->fd = *fd;
    fh->flags = O_ACCMODE & 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)
            node->dirty = 1;

    return 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 (is_backup(node) || is_created(node) || is_open_write(node)
            || (is_dirty(node) && is_locked(node))) {
        DBG0("  no update");
        return 0;
    }

    int ret = 0;

    if (is_dirty(node)) {
        ret = dav_put(node->path, node->cache_path, &node->remote_exists,
                      &node->lock_expire, &node->etag, &node->smtime, -1);
        if (ret == 0) {
            node->utime = time(NULL);
            node->dirty = 0;
        } else if (ret == EACCES || ret == EINVAL || ret == ENOENT
                   || ret == EPERM || ret == ENOSPC) {
            delete_cache_file(node->parent);
            node->parent->utime = 0;
            *flush = 1;
            remove_node(node);
            ret = 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))
            return 0;
        int modified = 0;
        ret = dav_get_file(node->path, node->cache_path, &node->size,
                           &node->etag, &node->smtime, &modified);
        if (ret == 0) {
            if (modified) {
                node->mtime = node->smtime;
                node->atime = node->smtime;
                set_cache_file_times(node);
            }
            node->utime = time(NULL);
        }
    } else {
        if (create_cache_file(node) != 0)
            return EIO;
        time_t smtime = 0;
        char *etag = NULL;
        ret = dav_get_file(node->path, node->cache_path, &node->size, &etag,
                           &smtime, NULL);
        if (ret == 0) {
            node->etag = etag;
            node->atime = smtime;
            node->mtime = smtime;
            node->smtime = smtime;
            node->utime = time(NULL);
            set_cache_file_times(node);
        } else {
            if (ret == ENOENT) {
                delete_cache_file(node->parent);
                node->parent->utime = 0;
                *flush = 1;
                remove_node(node);
            }
            delete_cache_file(node);
        }
    }

    return ret;
}


/* 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) {

    struct passwd *pw = getpwuid(default_uid);
    if (pw == NULL || pw->pw_name == NULL)
        error(EXIT_FAILURE, 0, _("can't read user data base"));
    char *dir_name = ne_concat(host, path, mpoint + 1, "+", pw->pw_name, NULL);
    *(dir_name + strlen(host) + strlen(path) - 1) = '+';
    char *pos = strchr(dir_name, '/');
    while (pos != NULL) {
        *pos = '-';
        pos = strchr(pos, '/');
    }

    DIR *tl_dir = opendir(dir);
    if (tl_dir == NULL)
        error(EXIT_FAILURE, 0, _("can't open cache directory %s"), dir);

    struct dirent *de = readdir(tl_dir);
    while (de != NULL && cache_dir == NULL) {
        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 == NULL) {
        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);
    if ((DAV_A_MASK & st.st_mode) != S_IRWXU)
        error(EXIT_FAILURE, 0,
              _("wrong permissions set for cache directory %s"), cache_dir);
}


/* Deletes all files in the cache directory that are not reverenced by a
   file node. */
static void clean_cache(void) {

    DIR *dir = opendir(cache_dir);
    if (dir == NULL)
        return;

    struct dirent *de = readdir(dir);
    while (de != NULL) {

        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 == NULL && i < table_size) {
                node = table[i];
                while (node != NULL
                        && (!is_reg(node) || node->cache_path == NULL
                            || strcmp(path, node->cache_path) != 0)) {
                    node = node->next;
                }
                i++;
            }
            if (node == NULL)
                remove(path);
            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. */
#if NE_VERSION_MINOR == 24

static void parse_index(void) {

    char *index = ne_concat(cache_dir, "/", DAV_INDEX, NULL);
    FILE *idx = fopen(index, "r");
    if (idx == NULL)
        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_mode, xml_cdata_mode, NULL, &node);
    ne_xml_push_handler(parser, xml_start_decimal, xml_cdata_decimal, NULL,
                        &node);
    ne_xml_push_handler(parser, xml_start_size, xml_cdata_size, NULL, &node);
    ne_xml_push_handler(parser, xml_start_string, xml_cdata_string, NULL,
                        &node);

    char *buf = ne_malloc(DAV_XML_BUF_SIZE);
    size_t len = fread(buf, 1, DAV_XML_BUF_SIZE, idx);
    while (len > 0 && ne_xml_valid(parser)) {
        ne_xml_parse(parser, buf, len);
        len = fread(buf, 1, DAV_XML_BUF_SIZE, idx);
    }
    free(buf);

    if (ne_xml_valid(parser) == 0) {
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("error parsing %s"), index);
        delete_tree(root);
    }

    ne_xml_destroy(parser);
    fclose(idx);
    free(index);
}

#else     /* NE_VERSION_MINOR */

static void parse_index(void) {

    char *index = ne_concat(cache_dir, "/", DAV_INDEX, NULL);
    FILE *idx = fopen(index, "r");
    if (idx == NULL)
        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_mode, xml_cdata_mode, NULL, &node);
    ne_xml_push_handler(parser, xml_start_decimal, xml_cdata_decimal, NULL,
                        &node);
    ne_xml_push_handler(parser, xml_start_size, xml_cdata_size, NULL, &node);
    ne_xml_push_handler(parser, xml_start_string, xml_cdata_string, NULL,
                        &node);

    char *buf = ne_malloc(DAV_XML_BUF_SIZE);
    size_t len = fread(buf, 1, DAV_XML_BUF_SIZE, idx);
    int ret = 0;
    while (len > 0 && ret == 0) {
        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 != 0) {
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("error parsing %s"), index);
        delete_tree(root);
    }

    ne_xml_destroy(parser);
    fclose(idx);
    free(index);
}

#endif    /* NE_VERSION_MINOR */


/* Removes the files with lowest access time from cache until the cache size
   is smaller than max_cache_size or there are no more files that may be
   removed. */
static void resize_cache(unsigned long long cache_size) {

    DBG2("RESIZE_CACHE: %llu of %llu MiBytes used.",
         (cache_size + 0x80000) / 0x100000,
         (max_cache_size + 0x80000) / 0x100000);

    while (cache_size > max_cache_size) {
        dav_node *least_recent = NULL;
        size_t i;
        for (i = 0; i < table_size; i++) {
            dav_node *node = table[i];
            while (node != NULL) {
                if (is_cached(node) && !is_open(node) && !is_dirty(node)
                        && !is_created(node) && !is_backup(node)
                        && (least_recent == NULL
                            || node->atime < least_recent->atime))
                    least_recent = node;
                node = node->next;
            }
        }
        if (least_recent == NULL) {
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR),
                   _("open files exceed max cache size by %llu MiBytes"),
                   (cache_size - max_cache_size + 0x80000) / 0x100000);
            break;
        }
        delete_cache_file(least_recent);
        cache_size -= least_recent->size;
    }

    DBG2("              %llu of %llu MiBytes used.",
         (cache_size + 0x80000) / 0x100000,
         (max_cache_size + 0x80000) / 0x100000);
    return;
}


/* 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) {

    if (node == root) {
        DBG1("  Writing root %p.", node);
        if (fprintf(file, "<d:%s xmlns:d=\"%s\">\n", type[ROOT],
                    DAV_XML_NS) < 0)
            return -1;
    } else if (node == backup) {
        DBG1("  Writing backup %p.", node);
        if (fprintf(file, "%s<d:%s>\n", indent, type[BACKUP]) < 0)
            return -1;
    } else if (is_dir(node)) {
        DBG1("  Writing directory %p.", node);
        if (fprintf(file, "%s<d:%s>\n", indent, type[DDIR]) < 0)
            return -1;
    } else {
        DBG1("  Writing file %p.", node);
        if (fprintf(file, "%s<d:%s>\n", indent, type[REG]) < 0)
            return -1;
    }

    char *ind = ne_concat(indent, "  ", NULL);

    if (node != root && !is_backup(node)) {
        if (fprintf(file, "%s<d:%s>%s</d:%s>\n", ind, type[PATH], node->path,
                    type[PATH]) < 0)
            return -1;
    }

    if (node != root && node != backup) {
        if (fprintf(file, "%s<d:%s>%s</d:%s>\n", ind, type[NAME], node->name,
                    type[NAME]) < 0)
            return -1;
    }

    if (is_reg(node) && node->cache_path != NULL) {
        if (fprintf(file, "%s<d:%s>%s</d:%s>\n", ind, type[CACHE_PATH],
                    node->cache_path, type[CACHE_PATH]) < 0)
            return -1;
    }

    if (is_reg(node) && !is_backup(node) && node->etag != NULL) {
        if (fprintf(file, "%s<d:%s>%s</d:%s>\n", ind, type[ETAG], node->etag,
                    type[ETAG]) < 0)
        return -1;
    }

    if (is_reg(node)) {
#if _FILE_OFFSET_BITS == 64
        if (fprintf(file, "%s<d:%s>%lli</d:%s>\n", ind, type[SIZE], node->size,
                    type[SIZE]) < 0)
#else
        if (fprintf(file, "%s<d:%s>%li</d:%s>\n", ind, type[SIZE], node->size,
                    type[SIZE]) < 0)
#endif
            return -1;
    }

    if (fprintf(file, "%s<d:%s>%li</d:%s>\n", ind, type[ATIME], node->atime,
                type[ATIME]) < 0)
        return -1;
    if (fprintf(file, "%s<d:%s>%li</d:%s>\n", ind, type[MTIME], node->mtime,
                type[MTIME]) < 0)
        return -1;
    if (fprintf(file, "%s<d:%s>%li</d:%s>\n", ind, type[CTIME], node->ctime,
                type[CTIME]) < 0)
        return -1;

    if (is_reg(node) && !is_backup(node)) {
        if (fprintf(file, "%s<d:%s>%li</d:%s>\n", ind, type[SMTIME],
                    node->smtime, type[SMTIME]) < 0)
            return -1;
    }

    if (node != backup) {
        if (fprintf(file, "%s<d:%s>%o</d:%s>\n", ind, type[MODE], node->mode,
                    type[MODE]) < 0)
            return -1;
    }

    if (node != root && node != backup) {
        if (fprintf(file, "%s<d:%s>%i</d:%s>\n", ind, type[UID], node->uid,
                    type[UID]) < 0)
            return -1;
        if (fprintf(file, "%s<d:%s>%i</d:%s>\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</d:%s>\n", indent, type[ROOT]) < 0)
            return -1;
    } else if (node == backup) {
        if (fprintf(file, "%s</d:%s>\n", indent, type[BACKUP]) < 0)
            return -1;
    } else if (is_dir(node)) {
        if (fprintf(file, "%s</d:%s>\n", indent, type[DDIR]) < 0)
            return -1;
    } else {
        if (fprintf(file, "%s</d:%s>\n", indent, type[REG]) < 0)
            return -1;
    }

    free(ind);
    return 0;
}


/* cdata 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_cdata_decimal(void *userdata, int state, const char *cdata,
                             size_t len) {

    char *s = ne_strndup(cdata, len);
    char *tail;

    long int n = strtol(s, &tail, 10);
    if (*tail != '\0') {
        free(s);
        return -1;
    }
    free(s);

    switch (state) {
    case UID:
        (*((dav_node **) userdata))->uid = n;
        break;
    case GID:
        (*((dav_node **) userdata))->gid = n;
        break;
    case ATIME:
        (*((dav_node **) userdata))->atime = n;
        break;
    case MTIME:
        (*((dav_node **) userdata))->mtime = n;
        break;
    case CTIME:
        (*((dav_node **) userdata))->ctime = n;
        break;
    case SMTIME:
        (*((dav_node **) userdata))->smtime = n;
        break;
    default:
        return -1;
    }

    return 0;
}


/* cdata 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_cdata_mode(void *userdata, int state, const char *cdata,
                          size_t len) {

    char *s = ne_strndup(cdata, len);
    char *tail;
    (*((dav_node **) userdata))->mode = strtol(s, &tail, 8);
    if (*tail != '\0') {
        free(s);
        return -1;
    }
    free(s);

    return 0;
}


/* cdata 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_cdata_size(void *userdata, int state, const char *cdata,
                             size_t len) {

    char *s = ne_strndup(cdata, len);
    char *tail;

#if _FILE_OFFSET_BITS == 64
    (*((dav_node **) userdata))->size = strtoll(s, &tail, 10);
#else
    (*((dav_node **) userdata))->size = strtol(s, &tail, 10);
#endif

    if (*tail != '\0') {
        free(s);
        return -1;
    }
    free(s);

    return 0;
}


/* Stores a copy of cdata 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_cdata_string(void *userdata, int state, const char *cdata,
                            size_t len) {

    switch (state) {
    case PATH:
        (*((dav_node **) userdata))->path = ne_strndup(cdata, len);
        break;
    case NAME:
        (*((dav_node **) userdata))->name = ne_strndup(cdata, len);
        break;
    case CACHE_PATH:
        (*((dav_node **) userdata))->cache_path = ne_strndup(cdata, len);
        break;
    case ETAG:
        (*((dav_node **) userdata))->etag = ne_strndup(cdata, len);
        break;
    default:
        return -1;
    }

    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 != NULL) {
        free(dir->path);
        dir->path = NULL;
    }
    if (dir->name != NULL) {
        free(dir->name);
        dir->name = NULL;
    }
    delete_cache_file(dir);
    if (dir->etag != NULL) {
        free(dir->etag);
        dir->etag = NULL;
    }
    dir->smtime = 0;

    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 == NULL || dir->path == NULL) {
        delete_tree(dir);
        return 0;
    }

    delete_cache_file(dir);
    if (dir->etag != NULL) {
        free(dir->etag);
        dir->etag = NULL;
    }
    dir->size = 0;
    dir->smtime = 0;

    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;

    if (reg->name == NULL || reg->cache_path == NULL
            || access(reg->cache_path, F_OK) != 0
            || (reg->path == NULL && !is_backup(reg))) {
        delete_tree(reg);
        return 0;
    }

    if (is_backup(reg)) {
        if (reg->path != NULL) {
            free(reg->path);
            reg->path = NULL;
        }
        if (reg->etag != NULL) {
            free(reg->etag);
            reg->etag = NULL;
        }
        reg->smtime = 0;
    } else {
        reg->remote_exists = 1;
    }

    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 and etag 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 != NULL) {
        free(dir->path);
        dir->path = NULL;
    }
    if (dir->name != NULL) {
        free(dir->name);
        dir->name = NULL;
    }
    delete_cache_file(dir);
    if (dir->etag != NULL) {
        free(dir->etag);
        dir->etag = NULL;
    }
    dir->size = 0;
    dir->smtime = 0;

    return 0;
}


/* Will be called when the start tag of a XML-element is foud, 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 foud, and tests
   wheather it is an element that contains a dezimal 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.
   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[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;

    if ((parent == BACKUP || parent == ROOT) && (ret == UID || ret == GID))
        return -1;

    return ret;
}


/* Will be called when the start tag of a XML-element is foud, and tests
   wheather 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 foud, and tests
   wheather 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 foud, and tests
   wheather 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;

    return REG;
}


/* Will be called when the start tag of a XML-element is foud, and tests
   wheather 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 foud, and tests
   wheather 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 foud, and tests
   wheather 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 {
        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) {
        s[j] = (char *) ne_malloc((rand() / (RAND_MAX / 1024)) % (4 *align));
        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] != NULL)
            free(n[j]);
        if (s[j] != NULL)
            free(s[j]);
    }
    return align;
}


Generated by  Doxygen 1.6.0   Back to index