/* dav_fuse7.c: interface to the fuse kernel module FUSE_KERNEL_VERSION 7. 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" #include #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_LIBINTL_H #include #endif #ifdef HAVE_STDDEF_H #include #endif #ifdef HAVE_STDINT_H #include #endif #include #ifdef HAVE_SYSLOG_H #include #endif #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #include #include #include "defaults.h" #include "mount_davfs.h" #include "cache.h" #include "kernel_interface.h" #include "linux/fuse.h" #include "webdav.h" #include "provide.h" #ifdef ENABLE_NLS #define _(String) gettext(String) #else #define _(String) String #endif /* Data Types */ /*============*/ /* There is no struct fuse_create_out in fuse_kernel.h. */ struct create_out { struct fuse_entry_out entry; struct fuse_open_out open; }; /* Private global variables */ /*==========================*/ /* Size of the buffer to communicate with fuse. */ static size_t buf_size; /* fuse wants the nodeid of the root node to be 1, so we have to translate between the real nodeid and what fuse wants. */ static uint64_t root; /* Send debug messages to syslog if != 0. */ int debug; int fuse_device; //Read- and write-lock for all important actions. extern pthread_mutex_t* mutex_allops; //suspended read-call struct read_job { struct read_job *next; //next read-call unsigned int exec_cnt; //count of executions size_t buf_len; //buffer length char* buf; //fuse buffer }; //Queue for read-calls, wich are suspended for a while, //because the download of the reading file is just in progress //and not enough data is available to execute the complete //real-call. struct read_jobs{ struct read_job *job_list; int job_count; }; static struct read_jobs read_job_queue={0, 0}; /* Private function prototypes */ /*=============================*/ /* Functions to handle upcalls fromthe kernel module. */ static uint32_t fuse_access(char* buf); static uint32_t fuse_create(char* buf); static uint32_t fuse_forget(char* buf); static uint32_t fuse_getattr(char* buf); static uint32_t fuse_getxattr(char* buf); static uint32_t fuse_init(char* buf); static uint32_t fuse_lookup(char* buf); static uint32_t fuse_mkdir(char* buf); static uint32_t fuse_mknod(char* buf); static uint32_t fuse_open(char* buf); static uint32_t fuse_read(char* buf); static uint32_t fuse_readdir(char* buf); static uint32_t fuse_release(char* buf); static uint32_t fuse_rename(char* buf); static uint32_t fuse_setattr(char* buf); static uint32_t fuse_stat(char* buf); static uint32_t fuse_write(char* buf); static uint32_t fuse_sync(char* buf); static uint32_t fuse_rmdir(char* buf); static uint32_t fuse_unlink(char* buf); static uint32_t fuse_listxattr(char* buf); static void dav_check_cache(void* arg); static int fuse_execute(char* buf); static void check_read_queue(void); static void read_job_dequeue(void); static int read_job_enqueue(char* buf, size_t buf_len); static void read_queue_clean(void); static int fuse_try_read(struct read_job* job); /* Auxiliary functions. */ static off_t write_dir_entry(int fd, off_t off, const dav_node *node, const char *name); static void set_attr(struct fuse_attr *attr, const dav_node *node); /* Public functions */ /*==================*/ void dav_fuse7_loop(int device, size_t bufsize, time_t idle_time, dav_is_mounted_fn is_mounted, volatile int *keep_on_running, int dbg) { debug = dbg; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "fuse kernel version 7"); buf_size = bufsize; fuse_device = device; dav_register_kernel_interface(&write_dir_entry, NULL, NULL); pthread_t dav_check_cache_thread_id=0; if(pthread_create(&dav_check_cache_thread_id, NULL, (void*) dav_check_cache, (void*) keep_on_running)){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "starting thread for dav_check_cache() failed"); return; } while (*keep_on_running) { mutex_lock(mutex_allops); dav_provide_update(); mutex_unlock(mutex_allops); struct timeval tv; if(read_job_queue.job_count>0){ //minimum one read-call is suspended, //check the read-queue more frequently tv.tv_sec = 0; tv.tv_usec = 100000; } else{ tv.tv_sec = 1; tv.tv_usec = 0; } fd_set fds; FD_ZERO(&fds); FD_SET(fuse_device, &fds); int ret = select(fuse_device + 1, &fds, NULL, NULL, &tv); /* Buffer used for communication with the kernel module (in and out). */ char *buf = malloc(buf_size); if (!buf) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), _("can't allocate message buffer")); return; } ssize_t bytes_read = 0; if (ret > 0) { //Lock access to the fuse device. bytes_read = read(fuse_device, buf, buf_size); if (bytes_read <= 0) { if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "READ: %s", strerror(errno)); free(buf); buf=0; if (bytes_read == 0 || errno == EINTR || errno == EAGAIN || errno == ENOENT){ check_read_queue(); continue; } else break; } } else if (ret == 0) { free(buf); buf=0; if (!is_mounted()) break; else { check_read_queue(); continue; } } else { free(buf); buf=0; break; } if ((size_t) bytes_read < sizeof(struct fuse_in_header)) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "not enough bytes read from fuse device: %u", bytes_read); free(buf); buf=0; check_read_queue(); continue; } struct fuse_in_header *ih = (struct fuse_in_header *) buf; if (ih->nodeid == 1) ih->nodeid = root; if(fuse_execute(buf)==-1){ //read-call failed, enqueue the job if(read_job_enqueue(buf, bytes_read)==-2){ //to many pending jobs - abort this one syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "aborting fuse-read"); struct fuse_out_header *oh = (struct fuse_out_header *) buf; oh->error = -EIO; oh->len = sizeof(struct fuse_out_header); ssize_t n = 0; ssize_t w = 0; while (n < oh->len && w >= 0) { w = write(fuse_device, buf + n, oh->len - n); n += w; } free(buf); buf=0; } } check_read_queue(); } if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "leaving dav_fuse7_loop"); read_queue_clean(); } /* Try to execute suspended read-calls. */ static void check_read_queue(void) { struct read_job* cached_read_job = read_job_queue.job_list; if(cached_read_job){ struct fuse_in_header *ih = (struct fuse_in_header *) cached_read_job->buf; if(ih->opcode != FUSE_READ){ //WTF?! syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "unknown fuse call in read queue: %d", ih->opcode); read_job_dequeue(); return; } //try this call, may be enough data is now available if(!fuse_try_read(cached_read_job)){ //read-call executed successfully or aborted, try next read read_job_dequeue(); } } } /* Private functions */ /*===================*/ /* Try to execute a suspended read-call. */ static int fuse_try_read(struct read_job* job) { struct fuse_out_header *oh = (struct fuse_out_header *) job->buf; ssize_t n = 0; ssize_t w = 0; mutex_lock(mutex_allops); __u32 read_ret = fuse_read(job->buf); if(read_ret == -ENODATA){ //read failed again, missing data job->exec_cnt++; if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "fuse-read from queue failed - must wait for incoming data (%d)", job->exec_cnt); if(job->exec_cnt<720){ //still wait for incoming data mutex_unlock(mutex_allops); return -1; } else{ //approximately 4 read attempts per second //abort this read-call after ~3 minutes syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "aborting fuse-read from queue"); oh->error = -EINTR; //signal interrupt oh->len = sizeof(struct fuse_out_header); } } else oh->len = read_ret; if (debug && oh->len) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "RET: %s", strerror(-oh->error)); while (n < oh->len && w >= 0) { w = write(fuse_device, job->buf + n, oh->len - n); n += w; } mutex_unlock(mutex_allops); return 0; } //Execute fuse operation in the main thread. static int fuse_execute(char* buf) { struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_in_header *ih = (struct fuse_in_header *) buf; int is_read = 0; __u32 read_ret = 0; mutex_lock(mutex_allops); switch (ih->opcode) { case FUSE_LOOKUP: oh->len = fuse_lookup(buf); break; case FUSE_FORGET: oh->len = fuse_forget(buf); oh->error = 0; break; case FUSE_GETATTR: oh->len = fuse_getattr(buf); break; case FUSE_SETATTR: oh->len = fuse_setattr(buf); break; case FUSE_READLINK: if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_READLINK:"); oh->error = -ENOSYS; oh->len = sizeof(struct fuse_out_header); break; case FUSE_SYMLINK: if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_SYMLINK:"); oh->error = -ENOSYS; oh->len = sizeof(struct fuse_out_header); break; case FUSE_MKNOD: oh->len = fuse_mknod(buf); break; case FUSE_MKDIR: oh->len = fuse_mkdir(buf); break; case FUSE_UNLINK: oh->len = fuse_unlink(buf); break; case FUSE_RMDIR: oh->len = fuse_rmdir(buf); break; case FUSE_RENAME: oh->len = fuse_rename(buf); break; case FUSE_LINK: if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_LINK:"); oh->error = -ENOSYS; oh->len = sizeof(struct fuse_out_header); break; case FUSE_OPEN: oh->len = fuse_open(buf); break; case FUSE_READ: read_ret = fuse_read(buf); is_read = 1; break; case FUSE_WRITE: oh->len = fuse_write(buf); break; case FUSE_STATFS: oh->len = fuse_stat(buf); break; case FUSE_RELEASE: oh->len = fuse_release(buf); break; case FUSE_FSYNC: oh->len = fuse_sync(buf); break; case FUSE_SETXATTR: if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_GETXATTR:"); oh->error = -ENOSYS; oh->len = sizeof(struct fuse_out_header); break; case FUSE_GETXATTR: oh->len = fuse_getxattr(buf); break; case FUSE_LISTXATTR: oh->len = fuse_listxattr(buf); break; case FUSE_REMOVEXATTR: if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_REMOVEXATTR:"); oh->error = -ENOSYS; oh->len = sizeof(struct fuse_out_header); break; case FUSE_FLUSH: if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_FLUSH: ignored"); oh->error = 0; oh->len = sizeof(struct fuse_out_header); break; case FUSE_INIT: oh->len = fuse_init(buf); break; case FUSE_OPENDIR: oh->len = fuse_open(buf); break; case FUSE_READDIR: oh->len = fuse_readdir(buf); break; case FUSE_RELEASEDIR: oh->len = fuse_release(buf); break; case FUSE_FSYNCDIR: if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_FSYNCDIR:"); oh->error = -ENOSYS; oh->len = sizeof(struct fuse_out_header); break; case FUSE_ACCESS: oh->len = fuse_access(buf); break; case FUSE_CREATE: oh->len = fuse_create(buf); break; default: if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "UNKNOWN FUSE CALL %i", ih->opcode); oh->error = -ENOSYS; oh->len = sizeof(struct fuse_out_header); break; } mutex_unlock(mutex_allops); if(is_read){ if(read_ret == -ENODATA){ //read-call failed, insufficient data. //Enqueue call in the read-queue and try again later. if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "fuse-read failed - must wait for incoming data"); return -1; } else oh->len = read_ret; } if (debug && oh->len) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "RET: %s", strerror(-oh->error)); ssize_t n = 0; ssize_t w = 0; while (n < oh->len && w >= 0) { w = write(fuse_device, buf + n, oh->len - n); n += w; } free(buf); buf=0; return 0; } static int read_job_enqueue(char *buf, size_t buf_len) { struct read_job *p, **pp; if (!buf) return -1; if(read_job_queue.job_count>10){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "queueing fuse-read failed - too many pending calls"); return -2; } p = (struct read_job*)calloc(1, sizeof(struct read_job)); if (!p) return -3; p->buf = buf; p->buf_len = buf_len; p->exec_cnt = 0; for (pp = &read_job_queue.job_list; *pp; pp = &((*pp)->next)); *pp = p; read_job_queue.job_count++; return 0; } static void read_job_dequeue(void) { struct read_job *p2; struct read_job *p1; p1 = read_job_queue.job_list; if(p1){ p2 = p1->next; free(p1->buf); p1->buf = 0; free(p1); read_job_queue.job_list = p2; read_job_queue.job_count--; } } static void read_queue_clean(void) { struct read_job *p2; struct read_job *p1; for(p1 = read_job_queue.job_list; p1;){ p2 = p1->next; free(p1->buf); p1->buf = 0; free(p1); p1 = p2; } read_job_queue.job_count = 0; } /* Execute the function dav_tidy_cache in an endless loop for checking the cache. */ static void dav_check_cache(void* arg) { volatile int *keep_on_running = (volatile int *) arg; while(*keep_on_running){ dav_tidy_cache(); sleep(3); } dav_close_webdav(0); } /* Functions to handle upcalls from the kernel module. The cache module only uses data types from the C-library. For file access, mode and the like it only uses symbolic constants defined in the C-library. So the main purpose of this functions is to translate from kernel specific types and constants to types and constants from the C-library, and back. All of this functions return the amount of data in buf that is to be send to the kernel module. */ static uint32_t fuse_unlink(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_out_header *oh = (struct fuse_out_header *) buf; if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_UNLINK:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " p 0x%llx, %s", ih->nodeid, (char *) (buf + sizeof(struct fuse_in_header))); } oh->error = dav_remove((dav_node *) ((size_t) ih->nodeid), (char *) (buf + sizeof(struct fuse_in_header)), ih->uid); if (oh->error != 0) oh->error *= -1; return sizeof(struct fuse_out_header); } static uint32_t fuse_access(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_access_in *in = (struct fuse_access_in *) (buf + sizeof(struct fuse_in_header)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_ACCESS:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, f 0%o", ih->nodeid, in->mask); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " uid %i", ih->uid); } oh->error = dav_access((dav_node *) ((size_t) ih->nodeid), ih->uid, in->mask); if (oh->error) oh->error *= -1; return sizeof(struct fuse_out_header); } static uint32_t fuse_create(char* buf) { struct fuse_in_header *ih= (struct fuse_in_header *) buf; #if FUSE_KERNEL_MINOR_VERSION < 13 struct fuse_open_in *in = (struct fuse_open_in *) (buf + sizeof(struct fuse_in_header)); char *name = buf + sizeof(struct fuse_in_header) + sizeof(struct fuse_open_in); #else struct fuse_create_in *in = (struct fuse_create_in *) (buf + sizeof(struct fuse_in_header)); char *name = buf + sizeof(struct fuse_in_header) + sizeof(struct fuse_create_in); #endif struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct create_out *out = (struct create_out *) (buf + sizeof(struct fuse_out_header)); if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_CREATE:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " name %s", name); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, f 0%o", ih->nodeid, in->flags); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " pid %i, mode 0%o", ih->pid, in->mode); } int created = 0; dav_node *node = NULL; oh->error = dav_lookup(&node, (dav_node *) ((size_t) ih->nodeid), name, ih->uid); if (!oh->error) { if (!node) { oh->error = -EIO; return sizeof(struct fuse_out_header); } else if (in->flags & O_EXCL) { oh->error = -EEXIST; return sizeof(struct fuse_out_header); } } else if (oh->error == ENOENT) { oh->error = dav_create(&node, (dav_node *) ((size_t) ih->nodeid), name, ih->uid, in->mode & DAV_A_MASK); if (oh->error || !node) { if (!oh->error) oh->error = EIO; oh->error *= -1; return sizeof(struct fuse_out_header); } created = 1; } else { oh->error *= -1; return sizeof(struct fuse_out_header); } int fd = 0; oh->error = dav_open(&fd, node, in->flags & ~(O_EXCL | O_CREAT), ih->pid, 0, ih->uid, 1); if (oh->error || !fd) { if (created) dav_remove((dav_node *) ((size_t) ih->nodeid), name, ih->uid); if (!oh->error) oh->error = EIO; oh->error *= -1; return sizeof(struct fuse_out_header); } out->entry.nodeid = (size_t) node; out->entry.generation = out->entry.nodeid; out->entry.entry_valid = 1; out->entry.attr_valid = 1; out->entry.entry_valid_nsec = 0; out->entry.attr_valid_nsec = 0; set_attr(&out->entry.attr, node); out->open.open_flags = in->flags & (O_ACCMODE | O_APPEND); out->open.fh = fd; out->open.padding = 0; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " fd %i", fd); return sizeof(struct fuse_out_header) + sizeof(struct create_out); } static uint32_t fuse_forget(char* buf) { //struct fuse_in_header *ih = (struct fuse_in_header *) buf; //struct fuse_forget_in *in = (struct fuse_forget_in *) // (buf + sizeof(struct fuse_in_header)); if (debug) { //dav_node* node = (dav_node *) ((size_t) ih->nodeid); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_FORGET:"); //syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, %s", ih->nodeid, node->path); //syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " l 0x%llx", in->nlookup); //the number of lookups to forget } return 0; } static uint32_t fuse_getattr(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_attr_out *out = (struct fuse_attr_out *) (buf + sizeof(struct fuse_out_header)); if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_GETATTR:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx", ih->nodeid); } oh->error = dav_getattr((dav_node *) ((size_t) ih->nodeid), ih->uid); if (oh->error) { oh->error *= -1; return sizeof(struct fuse_out_header); } set_attr(&out->attr, (dav_node *) ((size_t) ih->nodeid)); out->attr_valid = 1; out->attr_valid_nsec = 0; out->dummy = 0; return sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out); } static uint32_t fuse_getxattr(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_getxattr_in *in = (struct fuse_getxattr_in *) (buf + sizeof(struct fuse_in_header)); char *name = (char *) (buf + sizeof(struct fuse_in_header) + sizeof(struct fuse_getxattr_in)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_getxattr_out *out = (struct fuse_getxattr_out *) (buf + sizeof(struct fuse_out_header)); char *value = (char *) (buf + sizeof(struct fuse_out_header)); if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_GETXATTR:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, %s, %i", ih->nodeid, name, in->size); } size_t size = in->size; if (size == 0) { oh->error = dav_getxattr((dav_node *) ((size_t) ih->nodeid), name, value, &size, ih->uid); if (oh->error) { oh->error *= -1; return sizeof(struct fuse_out_header); } out->size = size; out->padding = 0; return sizeof(struct fuse_out_header) + sizeof(struct fuse_getxattr_out); } else { if (size > (buf_size - sizeof(struct fuse_out_header))) size = buf_size - sizeof(struct fuse_out_header); oh->error = dav_getxattr((dav_node *) ((size_t) ih->nodeid), name, value, &size, ih->uid); if (oh->error) { oh->error *= -1; return sizeof(struct fuse_out_header); } return sizeof(struct fuse_out_header) + size; } } static uint32_t fuse_init(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_init_in *in = (struct fuse_init_in *) (buf + sizeof(struct fuse_in_header)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_init_out *out = (struct fuse_init_out *) (buf + sizeof(struct fuse_out_header)); if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_INIT:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " version %i.%i", in->major, in->minor); } dav_node *node; oh->error = dav_root(&node, ih->uid); if (oh->error || !node) { if (!oh->error) oh->error = EIO; oh->error *= -1; return sizeof(struct fuse_out_header); } root = (size_t) node; out->major = FUSE_KERNEL_VERSION; out->minor = FUSE_KERNEL_MINOR_VERSION; //Set the maximum number of bytes to read-ahead. //The default is determined by the kernel. out->max_readahead = 0; out->flags = 0; #if FUSE_KERNEL_MINOR_VERSION < 13 out->unused = 0; #else //Maximum number of outstanding background requests //Default: 12 out->max_background = 0; //Congestion starts at 75% of maximum out->congestion_threshold = 0; #endif out->max_write = buf_size - sizeof(struct fuse_in_header) - sizeof(struct fuse_write_in) - 4095; return sizeof(struct fuse_out_header) + sizeof(struct fuse_init_out); } static uint32_t fuse_listxattr(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_getxattr_in *in = (struct fuse_getxattr_in *) (buf + sizeof(struct fuse_in_header)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_getxattr_out *out = (struct fuse_getxattr_out *) (buf + sizeof(struct fuse_out_header)); char *value = (char *) (buf + sizeof(struct fuse_out_header)); if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_LISTXATTR:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, %i", ih->nodeid, in->size); } size_t size = in->size; if (size == 0) { oh->error = dav_listxattr((dav_node *) ((size_t) ih->nodeid), value, &size, ih->uid); if (oh->error) { oh->error *= -1; return sizeof(struct fuse_out_header); } out->size = size; out->padding = 0; return sizeof(struct fuse_out_header) + sizeof(struct fuse_getxattr_out); } else { if (size > (buf_size - sizeof(struct fuse_out_header))) size = buf_size - sizeof(struct fuse_out_header); oh->error = dav_listxattr((dav_node *) ((size_t) ih->nodeid), value, &size, ih->uid); if (oh->error) { oh->error *= -1; return sizeof(struct fuse_out_header); } return sizeof(struct fuse_out_header) + size; } } static uint32_t fuse_lookup(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; char * name = (char *) (buf + sizeof(struct fuse_in_header)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_entry_out *out = (struct fuse_entry_out *) (buf + sizeof(struct fuse_out_header)); if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_LOOKUP:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " p 0x%llx, %s", ih->nodeid, name); } dav_node *node = NULL; oh->error = dav_lookup(&node, (dav_node *) ((size_t) ih->nodeid), name, ih->uid); if (oh->error || !node) { if (!oh->error) oh->error = EIO; oh->error *= -1; return sizeof(struct fuse_out_header); } out->nodeid = (size_t) node; out->generation = out->nodeid; out->entry_valid = 1; out->attr_valid = 1; out->entry_valid_nsec = 0; out->attr_valid_nsec = 0; set_attr(&out->attr, node); return sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out); } static uint32_t fuse_mkdir(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_mkdir_in *in = (struct fuse_mkdir_in *) (buf + sizeof(struct fuse_in_header)); char *name = (char *) (buf + sizeof(struct fuse_in_header) + sizeof(struct fuse_mkdir_in)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_entry_out *out = (struct fuse_entry_out *) (buf + sizeof(struct fuse_out_header)); if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_MKDIR:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " p 0x%llx, %s", ih->nodeid, name); } dav_node *node = NULL; oh->error = dav_mkdir(&node, (dav_node *) ((size_t) ih->nodeid), name, ih->uid, in->mode & DAV_A_MASK); if (oh->error || !node) { if (!oh->error) oh->error = EIO; oh->error *= -1; return sizeof(struct fuse_out_header); } out->nodeid = (size_t) node; out->generation = out->nodeid; out->entry_valid = 1; out->attr_valid = 1; out->entry_valid_nsec = 0; out->attr_valid_nsec = 0; set_attr(&out->attr, node); return sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out); } static uint32_t fuse_mknod(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_mknod_in *in = (struct fuse_mknod_in *) (buf + sizeof(struct fuse_in_header)); char *name = (char *) (buf + sizeof(struct fuse_in_header) + sizeof(struct fuse_mknod_in)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_entry_out *out = (struct fuse_entry_out *) (buf + sizeof(struct fuse_out_header)); if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_MKNOD:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " p 0x%llx, m 0%o", ih->nodeid, in->mode); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " %s", name); } if (!S_ISREG(in->mode)) { oh->error = -ENOTSUP; return sizeof(struct fuse_out_header); } dav_node *node = NULL; oh->error = dav_create(&node, (dav_node *) ((size_t) ih->nodeid), name, ih->uid, in->mode & DAV_A_MASK); if (oh->error || !node) { if (!oh->error) oh->error = EIO; oh->error *= -1; return sizeof(struct fuse_out_header); } out->nodeid = (size_t) node; out->generation = out->nodeid; out->entry_valid = 1; out->attr_valid = 1; out->entry_valid_nsec = 0; out->attr_valid_nsec = 0; set_attr(&out->attr, node); return sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out); } static uint32_t fuse_open(char* buf) { struct fuse_in_header *ih= (struct fuse_in_header *) buf; struct fuse_open_in *in = (struct fuse_open_in *) (buf + sizeof(struct fuse_in_header)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_open_out *out = (struct fuse_open_out *) (buf + sizeof(struct fuse_out_header)); if (debug) { if (ih->opcode == FUSE_OPENDIR) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_OPENDIR:"); } else { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_OPEN:"); } syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, f 0%o", ih->nodeid, in->flags); #if FUSE_KERNEL_MINOR_VERSION < 13 syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " pid %i, mode 0%o", ih->pid, in->mode); #else syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " pid %i", ih->pid); #endif } int fd = 0; oh->error = dav_open(&fd, (dav_node *) ((size_t) ih->nodeid), in->flags, ih->pid, 0, ih->uid, 0); if (oh->error || !fd) { if (!oh->error) oh->error = EIO; oh->error *= -1; return sizeof(struct fuse_out_header); } out->open_flags = in->flags & (O_ACCMODE | O_APPEND); out->fh = fd; out->padding = 0; if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " fd %i", fd); return sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out); } static uint32_t fuse_read(char* buf) { struct fuse_in_header *ih= (struct fuse_in_header *) buf; struct fuse_read_in *in = (struct fuse_read_in *) (buf + sizeof(struct fuse_in_header)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_READ:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, fd %llu", ih->nodeid, in->fh); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " pid %i", ih->pid); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " size %u, off %llu", in->size, in->offset); } if (in->size > (buf_size - sizeof(struct fuse_out_header))) { oh->error = -EINVAL; return sizeof(struct fuse_out_header); } ssize_t len = 0; __s32 read_error = dav_read(&len, (dav_node *) ((size_t) ih->nodeid), in->fh, buf + sizeof(struct fuse_out_header), in->size, in->offset); if(read_error == ENODATA) return -ENODATA; oh->error = read_error; if (oh->error) oh->error *= -1; return len + sizeof(struct fuse_out_header); } static uint32_t fuse_readdir(char* buf) { struct fuse_in_header *ih= (struct fuse_in_header *) buf; struct fuse_read_in *in = (struct fuse_read_in *) (buf + sizeof(struct fuse_in_header)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_READDIR:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, fd %llu", ih->nodeid, in->fh); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " pid %i", ih->pid); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " size %u, off %llu", in->size, in->offset); } if (in->size > (buf_size - sizeof(struct fuse_out_header))) { oh->error = -EINVAL; return sizeof(struct fuse_out_header); } ssize_t len = 0; oh->error = dav_read(&len, (dav_node *) ((size_t) ih->nodeid), in->fh, buf + sizeof(struct fuse_out_header), in->size, in->offset); if (oh->error) oh->error *= -1; return len + sizeof(struct fuse_out_header); } static uint32_t fuse_release(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_release_in *in = (struct fuse_release_in *) (buf + sizeof(struct fuse_in_header)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; if (debug) { if (ih->opcode == FUSE_RELEASEDIR) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_RELEASEDIR:"); } else { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_RELEASE:"); } syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, f 0%o", ih->nodeid, in->flags); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " pid %i, fd %llu", ih->pid, in->fh); } oh->error = dav_close((dav_node *) ((size_t) ih->nodeid), in->fh, in->flags, ih->pid, 0); if (oh->error) oh->error *= -1; return sizeof(struct fuse_out_header); } static uint32_t fuse_rmdir(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_out_header *oh = (struct fuse_out_header *) buf; if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_RMDIR:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " p 0x%llx, %s", ih->nodeid, (char *) (buf + sizeof(struct fuse_in_header))); } oh->error = dav_rmdir((dav_node *) ((size_t) ih->nodeid), (char *) (buf + sizeof(struct fuse_in_header)), ih->uid); if (oh->error != 0) oh->error *= -1; return sizeof(struct fuse_out_header); } static uint32_t fuse_sync(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_out_header *oh = (struct fuse_out_header *) buf; if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_FSYNC:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx", ih->nodeid); } oh->error = dav_sync((dav_node *) ((size_t) ih->nodeid)); if (oh->error != 0) oh->error *= -1; return sizeof(struct fuse_out_header); } static uint32_t fuse_rename(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_rename_in *in = (struct fuse_rename_in *) (buf + sizeof(struct fuse_in_header)); char *old = (char *) (buf + sizeof(struct fuse_in_header) + sizeof(struct fuse_rename_in)); char *new = old + strlen(old) + 1; struct fuse_out_header *oh = (struct fuse_out_header *) buf; if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_RENAME:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " sp 0x%llx, %s", ih->nodeid, old); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " dp 0x%llx, %s", in->newdir, new); } if (in->newdir == 1) in->newdir = root; oh->error = dav_rename((dav_node *) ((size_t) ih->nodeid), old, (dav_node *) ((size_t) in->newdir), new, ih->uid); if (oh->error) oh->error *= -1; return sizeof(struct fuse_out_header); } static uint32_t fuse_setattr(char* buf) { struct fuse_in_header *ih = (struct fuse_in_header *) buf; struct fuse_setattr_in *in = (struct fuse_setattr_in *) (buf + sizeof(struct fuse_in_header)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_attr_out *out = (struct fuse_attr_out *) (buf + sizeof(struct fuse_out_header)); if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_SETATTR:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, m 0%o", ih->nodeid, in->mode); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " uid %i, gid %i", in->uid, in->gid); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " sz %llu, at %llu,", in->size, in->atime); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " mt %llu", in->mtime); } oh->error = dav_setattr((dav_node *) ((size_t) ih->nodeid), ih->uid, in->valid & FATTR_MODE, in->mode, in->valid & FATTR_UID, in->uid, in->valid & FATTR_GID, in->gid, in->valid & FATTR_ATIME, in->atime, in->valid & FATTR_MTIME, in->mtime, in->valid & FATTR_SIZE, in->size); if (oh->error) { oh->error *= -1; return sizeof(struct fuse_out_header); } set_attr(&out->attr, (dav_node *) ((size_t) ih->nodeid)); out->attr_valid = 1; out->attr_valid_nsec = 0; out->dummy = 0; return sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out); } static uint32_t fuse_stat(char* buf) { struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_statfs_out *out = (struct fuse_statfs_out *) (buf + sizeof(struct fuse_out_header)); if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_STATFS:"); __uint64_t block_size = (buf_size - sizeof(struct fuse_in_header) - sizeof(struct fuse_write_in) - 4095) & ~4095; dav_stat *st = calloc(1, sizeof(dav_stat)); if(!dav_statfs(st, block_size)){ out->st.blocks = st->blocks; out->st.bfree = st->bfree; out->st.bavail = st->bavail; out->st.files = st->files; out->st.ffree = st->ffree; out->st.bsize = block_size; out->st.namelen = st->namelen; out->st.frsize = 0; out->st.padding = 0; if(debug){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " btotal: %llu", out->st.blocks); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " bfree: %llu", out->st.bfree); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " files: %llu", out->st.files); } int i; for (i = 0; i < 6; i++) out->st.spare[i] = 0; free(st); oh->error = 0; return sizeof(struct fuse_out_header) + sizeof(struct fuse_statfs_out); } else{ free(st); oh->error = -ENOSYS; //The file system does not support this call. return sizeof(struct fuse_out_header); } } static uint32_t fuse_write(char* buf) { struct fuse_in_header *ih= (struct fuse_in_header *) buf; struct fuse_write_in *in = (struct fuse_write_in *) (buf + sizeof(struct fuse_in_header)); struct fuse_out_header *oh = (struct fuse_out_header *) buf; struct fuse_write_out *out = (struct fuse_write_out *) (buf + sizeof(struct fuse_out_header)); if (debug) { syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "FUSE_WRITE:"); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " n 0x%llx, fd %llu", ih->nodeid, in->fh); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " pid %i, flags 0%o", ih->pid, in->write_flags); syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), " size %u, off %llu", in->size, in->offset); } if (in->size > (buf_size - sizeof(struct fuse_in_header) - sizeof(struct fuse_write_in))) { oh->error = -EINVAL; return sizeof(struct fuse_out_header); } size_t size=0; oh->error = dav_write(&size, (dav_node *) ((size_t) ih->nodeid), in->fh, buf + sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in), in->size, in->offset); if(oh->error == ENOSPC || oh->error == EDQUOT) { //cannot write file, not enough space //remove the cache file and the corresponding node dav_node* node = (dav_node *) ((size_t) ih->nodeid); if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "not enough space or quota reached (%i). removing file: %s", oh->error, node->cache_path/*node->name*/); int ret = dav_remove(node->parent, node->name, ih->uid); if(!ret) { if(oh->error == ENOSPC) avm_event_log(AVM_EVENT_NO_SPACE, node->path); } else syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "removing file failed: %s", strerror(ret)); oh->error = ENOSPC; } if (oh->error) { oh->error *= -1; return sizeof(struct fuse_out_header); } out->size = size; out->padding = 0; return sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out); } /* Auxiliary functions. */ /* Writes a struct fuse_dirent to file with file descriptor fd. fd : An open file descriptor to write to. off : The current file size. name : File name; if NULL, the last, empty entry is written. return value : New size of the file. -1 in case of an error. */ static off_t write_dir_entry(int fd, off_t off, const dav_node *node, const char *name) { if (!name) return off; struct fuse_dirent entry; size_t head = offsetof(struct fuse_dirent, name); size_t reclen = (head + strlen(name) + sizeof(uint64_t) -1) & ~(sizeof(uint64_t) - 1); entry.ino = (((size_t) node) == root) ? 1 : (size_t) node; entry.off = off + reclen; entry.namelen = strlen(name); entry.type = (node->mode & S_IFMT) >> 12; size_t size = 0; ssize_t ret = 0; while (ret >= 0 && size < head) { ret = write(fd, (char *) &entry + size, head - size); if (ret < 0) { int err_code = errno; syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "writing dir entry failed: %s", strerror(err_code)); if(err_code == ENOSPC && resize_cache()){ //Write failed, because no space is left. //Free space may be now available, because the cache was resized. //Try the write call a 2nd time. ret = write(fd, (char *) &entry + size, head - size); if(ret < 0){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "2nd attempt to write dir entry failed: %s", strerror(errno)); return -1; } else{ if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "2nd attempt to write dir entry succeeded"); } } else return -1; } size += ret; } if (size != head) return -1; ret = 0; while (ret >= 0 && size < (head + entry.namelen)) { ret = write(fd, name + size - head, entry.namelen - size + head); if (ret < 0) { int err_code = errno; syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "writing dir entry failed: %s", strerror(err_code)); if(err_code == ENOSPC && resize_cache()){ //Write failed, because no space is left. //Free space may be now available, because the cache was resized. //Try the write call a 2nd time. ret = write(fd, name + size - head, entry.namelen - size + head); if(ret < 0){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "2nd attempt to write dir entry failed: %s", strerror(errno)); return -1; } else{ if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "2nd attempt to write dir entry succeeded"); } } else return -1; } size += ret; } if (size != (head + entry.namelen)) return -1; ret = 0; while (ret >= 0 && size < reclen) { ret = write(fd, "\0", 1); if (ret < 0) { int err_code = errno; syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "writing dir entry failed: %s", strerror(err_code)); if(err_code == ENOSPC && resize_cache()){ //Write failed, because no space is left. //Free space may be now available, because the cache was resized. //Try the write call a 2nd time. ret = write(fd, "\0", 1); if(ret < 0){ syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "2nd attempt to write dir entry failed: %s", strerror(errno)); return -1; } else{ if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "2nd attempt to write dir entry succeeded"); } } else return -1; } size += ret; } if (size != reclen) return -1; return off + reclen; } static void set_attr(struct fuse_attr *attr, const dav_node *node) { attr->ino = (((size_t) node) == root) ? 1 : (size_t) node; attr->size = node->size; attr->blocks = (node->size + 511) / 512; attr->atime = node->atime; attr->mtime = node->mtime; attr->ctime = node->ctime; attr->atimensec = 0; attr->mtimensec = 0; attr->ctimensec = 0; attr->mode = node->mode; if (S_ISDIR(node->mode)) { attr->nlink = node->nref; } else { attr->nlink = 1; } attr->uid = node->uid; attr->gid = node->gid; attr->rdev = 0; #if FUSE_KERNEL_MINOR_VERSION >= 10 //Allow the userspace filesystem to supply a blksize value to be //returned by stat() and friends. //If the field is zero, it defaults to the old PAGE_CACHE_SIZE value. attr->blksize = 0; attr->padding = 0; #endif }