davfs2/src/cache.c

3693 lines
100 KiB
C

/* 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 <dirent.h>
#endif
#include <errno.h>
#include <error.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <grp.h>
#ifdef HAVE_LIBINTL_H
#include <libintl.h>
#endif
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif
#include <time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_UTIME_H
#include <utime.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#include <sys/xattr.h>
#include <ne_alloc.h>
#include <ne_string.h>
#include <ne_xml.h>
#include "defaults.h"
#include "mount_davfs.h"
#include "webdav.h"
#include "cache.h"
#ifdef ENABLE_NLS
#define _(String) gettext(String)
#else
#define _(String) String
#endif
/* 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,
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 */
/*==========================*/
/* Number of nodes. */
static int nnodes;
/* 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;
/* 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;
/* 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;
/* 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;
/* Private function prototypes and inline functions */
/*==================================================*/
/* Node operations. */
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;
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 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) {
remove(node->cache_path);
free(node->cache_path);
node->cache_path = NULL;
node->dirty = 0;
if (is_reg(node))
cache_size -= node->size;
}
}
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);
static off_t
write_dir(dav_node *dir, int fd);
/* 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(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();
/* Public functions */
/*==================*/
void
dav_init_cache(const dav_args *args, const char *mpoint)
{
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;
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);
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);
backup->name = ne_strdup(args->backup_dir);
backup->mode = S_IFDIR | S_IRWXU;
clean_cache();
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) {
error(EXIT_FAILURE, 0, _("Mounting failed.\n%s"),
dav_get_webdav_error());
}
}
void
dav_close_cache(int got_sigterm)
{
if (debug)
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Closing cache");
write_dir_entry = &write_dir_entry_dummy;
flush = &flush_dummy;
clean_tree(root, !got_sigterm);
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,
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\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);
}
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) {
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG),
"tidy: %i of %i nodes changed", nchanged, nnodes);
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "cache-size: %llu MiBytes.",
(cache_size + 0x80000) / 0x100000);
}
if (cache_size > max_cache_size)
resize_cache();
static dav_node_list_item *item = NULL;
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)
return 0;
}
next_item = item->next;
dav_node *node = item->node;
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);
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,
&node->mime_type, set_execute);
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), "tidy: neon error");
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " %s",
dav_get_webdav_error());
}
if (ret == EACCES || ret == EINVAL || ret == ENOENT
|| ret == EPERM || ret == ENOSPC || ret == EEXIST) {
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);
}
item = next_item;
if (item)
return 1;
item = changed;
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);
} 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;
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);
}
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) {
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);
} 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;
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)
{
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 (!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_RDWR, 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, 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),
pid, pgid, uid);
}
} else if (!ret) {
ret = update_cache_file(node);
if (!ret)
ret = open_file(fd, node, flags & (O_ACCMODE | O_APPEND),
pid, pgid, uid);
}
if (!ret)
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 (!exists(node))
return ENOENT;
dav_handle *fh = get_file_handle(node, fd, 0, 0, 0);
if (!fh)
return EBADF;
if (fh->flags == O_WRONLY)
return EINVAL;
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", *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;
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;
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;
}
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 (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)
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;
}
dav_stat
dav_statfs(void)
{
dav_stat st;
st.blocks = 0x65B9AA;
st.bfree = 0x32DCD5;
st.bavail = 0x32DCD5;
st.files = nnodes;
st.ffree = 666666;
struct stat cache_st;
if (stat(cache_dir, &cache_st) == 0) {
st.bsize = cache_st.st_blksize;
} else {
st.bsize = 4096;
}
st.namelen = 256;
off_t used = 0;
if (dav_quota(root->path, &st.blocks, &used) == 0) {
st.blocks /= st.bsize;
st.bfree = st.blocks - (used / st.bsize) - 1;
st.bavail = st.bfree;
st.ffree = st.bfree;
}
return st;
}
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)
{
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;
*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;
}
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;
}
*chp = (dav_node_list_item *) malloc(sizeof(*chp));
if (!*chp)
abort();
(*chp)->node = node;
(*chp)->next = NULL;
(*chp)->save_at = 0;
nchanged++;
}
/* 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;
}
/* 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)
{
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;
int ret = dav_put(node->path, node->cache_path,
&node->remote_exists, &node->lock_expire,
&node->etag, &node->smtime,
&node->mime_type, set_execute);
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) {
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)
{
if (debug)
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "deleting node %p", node);
if (node->path)
free(node->path);
if (node->name)
free(node->name);
delete_cache_file(node);
if (node->etag)
free(node->etag);
if (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)
{
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)
{
if (dst && !is_dir(dst))
return ENOTDIR;
if (dst && is_busy(dst))
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);
}
if (dav_move(src->path, dst_path) != 0) {
free(dst_path);
return EIO;
}
if (dst)
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.
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_name, 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_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 (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 = 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) {
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->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;
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)
{
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--;
}
}
/* 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;
}
}
/* 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_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_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)
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 (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) && (!is_locked(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)
|| (node->etag && props->etag
&& strcmp(node->etag, props->etag) != 0)) {
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 {
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) {
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;
}
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);
}
}
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) {
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 (*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))) {
if (debug)
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " no update");
return 0;
}
int ret = 0;
if (is_dirty(node)) {
if (get_upload_time(node) >= time(NULL))
return 0;
ret = dav_put(node->path, node->cache_path, &node->remote_exists,
&node->lock_expire, &node->etag, &node->smtime,
&node->mime_type, -1);
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) {
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;
off_t old_size = node->size;
ret = dav_get_file(node->path, node->cache_path, &node->size,
&node->etag, &node->smtime, &node->mime_type,
&modified);
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;
}
} 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, &node->mime_type, NULL);
if (!ret) {
node->etag = etag;
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);
}
delete_cache_file(node);
}
}
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)
{
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, path, mpoint + 1, "+", pw->pw_name, NULL);
*(dir_name + strlen(host) + strlen(path) - 1) = '+';
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);
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 orphaned file in cache:"));
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), " %s", path);
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)
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, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>") == 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);
}
/* 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(void)
{
if (debug)
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG),
"resize cache: %llu of %llu MiBytes used.",
(cache_size + 0x80000) / 0x100000,
(max_cache_size + 0x80000) / 0x100000);
while (1) {
dav_node *least_recent = NULL;
cache_size = 0;
size_t i;
for (i = 0; i < table_size; i++) {
dav_node *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))
least_recent = node;
cache_size += node->size;
}
node = node->table_next;
}
}
if (cache_size < max_cache_size)
break;
if (!least_recent) {
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;
}
if (debug)
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG),
" %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) {
if (debug)
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "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) {
if (debug)
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "writing backup %p",
node);
if (fprintf(file, "%s<d:%s>\n", indent, type[BACKUP]) < 0)
return -1;
} else if (is_dir(node)) {
if (debug)
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "writing directory %p",
node);
if (fprintf(file, "%s<d:%s>\n", indent, type[DDIR]) < 0)
return -1;
} else {
if (debug)
syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "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><![CDATA[%s]]></d:%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<d:%s><![CDATA[%s]]></d:%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<d:%s><![CDATA[%s]]></d:%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<d:%s><![CDATA[%s]]></d:%s>\n", ind, type[ETAG], node->etag,
type[ETAG]) < 0)
return -1;
}
if (is_reg(node) && node->mime_type != NULL) {
if (fprintf(file, "%s<d:%s><![CDATA[%s]]></d:%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<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;
}
char t[64];
struct tm *lt = localtime(&node->atime);
strftime(t, 64, "(%FT%T%z)", lt);
if (fprintf(file, "%s<d:%s>%li%s</d:%s>\n", ind, type[ATIME], node->atime,
t, type[ATIME]) < 0)
return -1;
lt = localtime(&node->mtime);
strftime(t, 64, "(%FT%T%z)", lt);
if (fprintf(file, "%s<d:%s>%li%s</d:%s>\n", ind, type[MTIME], node->mtime,
t, type[MTIME]) < 0)
return -1;
lt = localtime(&node->ctime);
strftime(t, 64, "(%FT%T%z)", lt);
if (fprintf(file, "%s<d:%s>%li%s</d:%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<d:%s>%li%s</d:%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<d:%s>%i</d:%s>\n", ind, type[REMOTE_EXISTS],
node->remote_exists, type[REMOTE_EXISTS]) < 0)
return -1;
if (fprintf(file, "%s<d:%s>%i</d:%s>\n", ind, type[DIRTY], node->dirty,
type[DIRTY]) < 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;
}
/* 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) {
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])
free(n[j]);
if (s[j])
free(s[j]);
}
return align;
}