diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 5 | ||||
-rw-r--r-- | lib/fuse.c | 246 | ||||
-rw-r--r-- | lib/ulockmgr.c | 402 |
3 files changed, 609 insertions, 44 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 5206493..87e323a 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,7 +1,7 @@ ## Process this file with automake to produce Makefile.in AM_CPPFLAGS = -I$(top_srcdir)/include -DFUSERMOUNT_DIR=\"$(bindir)\" -lib_LTLIBRARIES = libfuse.la +lib_LTLIBRARIES = libfuse.la libulockmgr.la if BSD mount_source = mount_bsd.c @@ -27,4 +27,7 @@ libfuse_la_SOURCES = \ libfuse_la_LDFLAGS = $(libfuse_libs) -version-number 2:6:0 \ -Wl,--version-script,$(srcdir)/fuse_versionscript +libulockmgr_la_SOURCES = ulockmgr.c +libulockmgr_la_LDFLAGS = -version-number 1:0:0 + EXTRA_DIST = fuse_versionscript @@ -34,6 +34,7 @@ #define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1 #define FUSE_UNKNOWN_INO 0xffffffff +#define OFFSET_MAX 0x7fffffffffffffffLL struct fuse_config { unsigned int uid; @@ -76,6 +77,15 @@ struct fuse { int intr_installed; }; +struct lock { + int type; + off_t start; + off_t end; + pid_t pid; + uint64_t owner; + struct lock *next; +}; + struct node { struct node *name_next; struct node *id_next; @@ -91,6 +101,7 @@ struct node { struct timespec mtime; off_t size; int cache_valid; + struct lock *locks; }; struct fuse_dirhandle { @@ -1604,43 +1615,6 @@ static void fuse_write(fuse_req_t req, fuse_ino_t ino, const char *buf, reply_err(req, res); } -static void fuse_flush(fuse_req_t req, fuse_ino_t ino, - struct fuse_file_info *fi, uint64_t owner) -{ - struct fuse *f = req_fuse_prepare(req); - char *path; - int err; - - err = -ENOENT; - pthread_rwlock_rdlock(&f->tree_lock); - path = get_path(f, ino); - if (path != NULL) { - if (f->conf.debug) { - printf("FLUSH[%llu]\n", (unsigned long long) fi->fh); - fflush(stdout); - } - err = -ENOSYS; - if (f->op.flush) { - struct fuse_intr_data d; - fuse_prepare_interrupt(f, req, &d); - err = f->op.flush(path, fi); - fuse_finish_interrupt(f, req, &d); - } - free(path); - } - if (f->op.lock) { - struct flock lock; - memset(&lock, 0, sizeof(lock)); - lock.l_type = F_UNLCK; - lock.l_whence = SEEK_SET; - fuse_do_lock(f, req, path, fi, F_SETLK, &lock, owner); - if (err == -ENOSYS) - err = 0; - } - pthread_rwlock_unlock(&f->tree_lock); - reply_err(req, err); -} - static void fuse_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { @@ -2162,6 +2136,167 @@ static void fuse_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) reply_err(req, err); } +static struct lock *locks_conflict(struct node *node, const struct lock *lock) +{ + struct lock *l; + + for (l = node->locks; l; l = l->next) + if (l->owner != lock->owner && + lock->start <= l->end && l->start <= lock->end && + (l->type == F_WRLCK || lock->type == F_WRLCK)) + break; + + return l; +} + +static void delete_lock(struct lock **lockp) +{ + struct lock *l = *lockp; + *lockp = l->next; + free(l); +} + +static void insert_lock(struct lock **pos, struct lock *lock) +{ + lock->next = *pos; + *pos = lock; +} + +static int locks_insert(struct node *node, struct lock *lock) +{ + struct lock **lp; + struct lock *newl1 = NULL; + struct lock *newl2 = NULL; + + if (lock->type != F_UNLCK || lock->start != 0 || lock->end != OFFSET_MAX) { + newl1 = malloc(sizeof(struct lock)); + newl2 = malloc(sizeof(struct lock)); + + if (!newl1 || !newl2) { + free(newl1); + free(newl2); + return -ENOLCK; + } + } + + for (lp = &node->locks; *lp;) { + struct lock *l = *lp; + if (l->owner != lock->owner) + goto skip; + + if (lock->type == l->type) { + if (l->end < lock->start - 1) + goto skip; + if (lock->end < l->start - 1) + break; + if (l->start <= lock->start && lock->end <= l->end) + goto out; + if (l->start < lock->start) + lock->start = l->start; + if (lock->end < l->end) + lock->end = l->end; + goto delete; + } else { + if (l->end < lock->start) + goto skip; + if (lock->end < l->start) + break; + if (lock->start <= l->start && l->end <= lock->end) + goto delete; + if (l->end <= lock->end) { + l->end = lock->start - 1; + goto skip; + } + if (lock->start <= l->start) { + l->start = lock->end + 1; + break; + } + *newl2 = *l; + newl2->start = lock->end + 1; + l->end = lock->start - 1; + insert_lock(&l->next, newl2); + newl2 = NULL; + } + skip: + lp = &l->next; + continue; + + delete: + delete_lock(lp); + } + if (lock->type != F_UNLCK) { + *newl1 = *lock; + insert_lock(lp, newl1); + newl1 = NULL; + } +out: + free(newl1); + free(newl2); + return 0; +} + +static void flock_to_lock(struct flock *flock, struct lock *lock) +{ + memset(lock, 0, sizeof(struct lock)); + lock->type = flock->l_type; + lock->start = flock->l_start; + lock->end = flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX; + lock->pid = flock->l_pid; +} + +static void lock_to_flock(struct lock *lock, struct flock *flock) +{ + flock->l_type = lock->type; + flock->l_start = lock->start; + flock->l_len = (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1; + flock->l_pid = lock->pid; +} + +static void fuse_flush(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, uint64_t owner) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, ino); + if (path != NULL) { + if (f->conf.debug) { + printf("FLUSH[%llu]\n", (unsigned long long) fi->fh); + fflush(stdout); + } + err = -ENOSYS; + if (f->op.flush) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = f->op.flush(path, fi); + fuse_finish_interrupt(f, req, &d); + } + free(path); + } + if (f->op.lock) { + struct flock lock; + struct lock l; + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + fuse_do_lock(f, req, path, fi, F_SETLK, &lock, owner); + flock_to_lock(&lock, &l); + l.owner = owner; + pthread_mutex_lock(&f->lock); + locks_insert(get_node(f, ino), &l); + pthread_mutex_unlock(&f->lock); + + /* if op.lock() is defined FLUSH is needed regardless of op.flush() */ + if (err == -ENOSYS) + err = 0; + } + pthread_rwlock_unlock(&f->tree_lock); + reply_err(req, err); +} + static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, uint64_t owner, int cmd) @@ -2174,7 +2309,7 @@ static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino, pthread_rwlock_rdlock(&f->tree_lock); path = get_path(f, ino); if (path != NULL) { - fuse_do_lock(f, req, path, fi, cmd, lock, owner); + err = fuse_do_lock(f, req, path, fi, cmd, lock, owner); free(path); } pthread_rwlock_unlock(&f->tree_lock); @@ -2185,7 +2320,23 @@ static void fuse_getlk(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, uint64_t owner) { - int err = fuse_lock_common(req, ino, fi, lock, owner, F_GETLK); + int err; + struct lock l; + struct lock *conflict; + struct fuse *f = req_fuse(req); + + flock_to_lock(lock, &l); + l.owner = owner; + pthread_mutex_lock(&f->lock); + conflict = locks_conflict(get_node(f, ino), &l); + if (conflict) + lock_to_flock(conflict, lock); + pthread_mutex_unlock(&f->lock); + if (!conflict) + err = fuse_lock_common(req, ino, fi, lock, owner, F_GETLK); + else + err = 0; + if (!err) fuse_reply_lock(req, lock); else @@ -2196,8 +2347,18 @@ static void fuse_setlk(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, uint64_t owner, int sleep) { - reply_err(req, fuse_lock_common(req, ino, fi, lock, owner, - sleep ? F_SETLKW : F_SETLK)); + int err = fuse_lock_common(req, ino, fi, lock, owner, + sleep ? F_SETLKW : F_SETLK); + if (!err) { + struct fuse *f = req_fuse(req); + struct lock l; + flock_to_lock(lock, &l); + l.owner = owner; + pthread_mutex_lock(&f->lock); + locks_insert(get_node(f, ino), &l); + pthread_mutex_unlock(&f->lock); + } + reply_err(req, err); } static struct fuse_lowlevel_ops fuse_path_ops = { @@ -2418,8 +2579,7 @@ static int fuse_init_intr_signal(int signum, int *installed) memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = fuse_intr_sighandler; - sigemptyset(&(sa.sa_mask)); - sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); if (sigaction(signum, &sa, NULL) == -1) { perror("fuse: cannot set interrupt signal handler"); diff --git a/lib/ulockmgr.c b/lib/ulockmgr.c new file mode 100644 index 0000000..795fc41 --- /dev/null +++ b/lib/ulockmgr.c @@ -0,0 +1,402 @@ +/* + libulockmgr: Userspace Lock Manager Library + Copyright (C) 2006 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPL. + See the file COPYING.LIB +*/ + +/* #define DEBUG 1 */ + +#include "ulockmgr.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pthread.h> +#include <errno.h> +#include <assert.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/wait.h> + +struct message { + int intr; + pthread_t thr; + int cmd; + int fd; + struct flock lock; + int error; +}; + +struct fd_store { + struct fd_store *next; + int fd; + int finished; +}; + +struct owner { + struct owner *next; + struct owner *prev; + struct fd_store *fds; + void *id; + size_t id_len; + int cfd; +}; + +static pthread_mutex_t ulockmgr_lock; +static int ulockmgr_cfd = -1; +static struct owner owner_list = { .next = &owner_list, .prev = &owner_list }; + +#define MAX_SEND_FDS 2 + +static void list_del_owner(struct owner *owner) +{ + struct owner *prev = owner->prev; + struct owner *next = owner->next; + prev->next = next; + next->prev = prev; +} + +static void list_add_owner(struct owner *owner, struct owner *next) +{ + struct owner *prev = next->prev; + owner->next = next; + owner->prev = prev; + prev->next = owner; + next->prev = owner; +} + +static int ulockmgr_send_message(int sock, void *buf, size_t buflen, + int *fdp, int numfds) +{ + struct msghdr msg; + struct cmsghdr *p_cmsg; + struct iovec vec; + char cmsgbuf[CMSG_SPACE(sizeof(int) * MAX_SEND_FDS)]; + int res; + + assert(numfds <= MAX_SEND_FDS); + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + p_cmsg = CMSG_FIRSTHDR(&msg); + p_cmsg->cmsg_level = SOL_SOCKET; + p_cmsg->cmsg_type = SCM_RIGHTS; + p_cmsg->cmsg_len = CMSG_LEN(sizeof(int) * numfds); + memcpy(CMSG_DATA(p_cmsg), fdp, sizeof(int) * numfds); + msg.msg_controllen = p_cmsg->cmsg_len; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + vec.iov_base = buf; + vec.iov_len = buflen; + res = sendmsg(sock, &msg, MSG_NOSIGNAL); + if (res == -1) { + perror("libulockmgr: sendmsg"); + return -1; + } + if ((size_t) res != buflen) { + fprintf(stderr, "libulockmgr: sendmsg short\n"); + return -1; + } + return 0; +} + +static int ulockmgr_start_daemon(void) +{ + int sv[2]; + int res; + char tmp[64]; + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + if (res == -1) { + perror("libulockmgr: socketpair"); + return -1; + } + snprintf(tmp, sizeof(tmp), "exec ulockmgr_server %i", sv[0]); + res = system(tmp); + close(sv[0]); + if (res == -1 || !WIFEXITED(res) || WEXITSTATUS(res) != 0) { + close(sv[1]); + return -1; + } + ulockmgr_cfd = sv[1]; + return 0; +} + +static struct owner *ulockmgr_new_owner(const void *id, size_t id_len) +{ + int sv[2]; + int res; + char c = 'm'; + struct owner *o; + + if (ulockmgr_cfd == -1 && ulockmgr_start_daemon() == -1) + return NULL; + + o = calloc(1, sizeof(struct owner) + id_len); + if (!o) { + fprintf(stderr, "libulockmgr: failed to allocate memory\n"); + return NULL; + } + o->id = o + 1; + o->id_len = id_len; + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + if (res == -1) { + perror("libulockmgr: socketpair"); + goto out_free; + } + res = ulockmgr_send_message(ulockmgr_cfd, &c, sizeof(c), &sv[0], 1); + close(sv[0]); + if (res == -1) { + close(ulockmgr_cfd); + ulockmgr_cfd = -1; + goto out_close; + } + + o->cfd = sv[1]; + memcpy(o->id, id, id_len); + list_add_owner(o, &owner_list); + + return o; + + out_close: + close(sv[1]); + out_free: + free(o); + return NULL; +} + +static int ulockmgr_send_request(struct message *msg, const void *id, + size_t id_len) +{ + int sv[2]; + int cfd; + struct owner *o; + struct fd_store *f; + int fd = msg->fd; + int cmd = msg->cmd; + int res; + int unlockall = (cmd == F_SETLK && msg->lock.l_type == F_UNLCK && + msg->lock.l_start == 0 && msg->lock.l_len == 0); + + for (o = owner_list.next; o != &owner_list; o = o->next) + if (o->id_len == id_len && memcmp(o->id, id, id_len) == 0) + break; + + if (o == &owner_list) + o = NULL; + + if (!o && cmd != F_GETLK && msg->lock.l_type != F_UNLCK) + o = ulockmgr_new_owner(id, id_len); + + if (!o) { + if (cmd == F_GETLK) { + res = fcntl(msg->fd, F_GETLK, &msg->lock); + return (res == -1) ? -errno : 0; + } else if (msg->lock.l_type == F_UNLCK) + return 0; + else + return -ENOLCK; + } + + f = calloc(1, sizeof(struct fd_store)); + if (!f) { + fprintf(stderr, "libulockmgr: failed to allocate memory\n"); + return -ENOLCK; + } + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + if (res == -1) { + perror("libulockmgr: socketpair"); + free(f); + return -ENOLCK; + } + + cfd = sv[1]; + sv[1] = msg->fd; + res = ulockmgr_send_message(o->cfd, msg, sizeof(struct message), sv, 2); + close(sv[0]); + if (res == -1) { + free(f); + close(cfd); + return -EIO; + } + + f->fd = msg->fd; + f->next = o->fds; + o->fds = f; + + res = recv(cfd, msg, sizeof(struct message), MSG_WAITALL); + if (res == -1) { + perror("libulockmgr: recv"); + msg->error = EIO; + } else if (res != sizeof(struct message)) { + fprintf(stderr, "libulockmgr: recv short\n"); + msg->error = EIO; + } else if (cmd == F_SETLKW && msg->error == EAGAIN) { + pthread_mutex_unlock(&ulockmgr_lock); + while (1) { + sigset_t old; + sigset_t unblock; + int errno_save; + + sigemptyset(&unblock); + sigaddset(&unblock, SIGUSR1); + pthread_sigmask(SIG_UNBLOCK, &unblock, &old); + res = recv(cfd, msg, sizeof(struct message), MSG_WAITALL); + errno_save = errno; + pthread_sigmask(SIG_SETMASK, &old, NULL); + if (res == sizeof(struct message)) + break; + else if (res >= 0) { + fprintf(stderr, "libulockmgr: recv short\n"); + msg->error = EIO; + break; + } else if (errno_save != EINTR) { + errno = errno_save; + perror("libulockmgr: recv"); + msg->error = EIO; + break; + } + msg->intr = 1; + res = send(o->cfd, msg, sizeof(struct message), MSG_NOSIGNAL); + if (res == -1) { + perror("libulockmgr: send"); + msg->error = EIO; + break; + } + if (res != sizeof(struct message)) { + fprintf(stderr, "libulockmgr: send short\n"); + msg->error = EIO; + break; + } + } + pthread_mutex_lock(&ulockmgr_lock); + + } + + f->finished = 1; + close(cfd); + if (unlockall) { + struct fd_store **fp; + + for (fp = &o->fds; *fp;) { + f = *fp; + if (f->fd == fd && f->finished) { + *fp = f->next; + free(f); + } else + fp = &f->next; + } + if (!o->fds) { + list_del_owner(o); + close(o->cfd); + free(o); + } + /* Force OK on unlock-all, since it _will_ succeed once the + owner is deleted */ + msg->error = 0; + } + + return -msg->error; +} + +#ifdef DEBUG +static uint32_t owner_hash(const unsigned char *id, size_t id_len) +{ + uint32_t h = 0; + size_t i; + for (i = 0; i < id_len; i++) + h = ((h << 8) | (h >> 24)) ^ id[i]; + + return h; +} +#endif + +static int ulockmgr_canonicalize(int fd, struct flock *lock) +{ + off_t offset; + if (lock->l_whence == SEEK_CUR) { + offset = lseek(fd, 0, SEEK_CUR); + if (offset == (off_t) -1) + return -errno; + } else if (lock->l_whence == SEEK_END) { + struct stat stbuf; + int res = fstat(fd, &stbuf); + if (res == -1) + return -errno; + + offset = stbuf.st_size; + } else + offset = 0; + + lock->l_whence = SEEK_SET; + lock->l_start += offset; + + if (lock->l_start < 0) + return -EINVAL; + + if (lock->l_len < 0) { + lock->l_start += lock->l_len; + if (lock->l_start < 0) + return -EINVAL; + lock->l_len = -lock->l_len; + } + if (lock->l_len && lock->l_start + lock->l_len - 1 < 0) + return -EINVAL; + + return 0; +} + +int ulockmgr_op(int fd, int cmd, struct flock *lock, const void *owner, + size_t owner_len) +{ + int err; + struct message msg; + sigset_t old; + sigset_t block; + + if (cmd != F_GETLK && cmd != F_SETLK && cmd != F_SETLKW) + return -EINVAL; + + if (lock->l_whence != SEEK_SET && lock->l_whence != SEEK_CUR && + lock->l_whence != SEEK_END) + return -EINVAL; + +#ifdef DEBUG + fprintf(stderr, "libulockmgr: %i %i %i %lli %lli own: 0x%08x\n", + cmd, lock->l_type, lock->l_whence, lock->l_start, lock->l_len, + owner_hash(owner, owner_len)); +#endif + + /* Unlock should never block anyway */ + if (cmd == F_SETLKW && lock->l_type == F_UNLCK) + cmd = F_SETLK; + + memset(&msg, 0, sizeof(struct message)); + msg.cmd = cmd; + msg.fd = fd; + msg.lock = *lock; + err = ulockmgr_canonicalize(fd, &msg.lock); + if (err) + return err; + + sigemptyset(&block); + sigaddset(&block, SIGUSR1); + pthread_sigmask(SIG_BLOCK, &block, &old); + pthread_mutex_lock(&ulockmgr_lock); + err = ulockmgr_send_request(&msg, owner, owner_len); + pthread_mutex_unlock(&ulockmgr_lock); + pthread_sigmask(SIG_SETMASK, &old, NULL); + if (!err && cmd == F_GETLK) { + if (msg.lock.l_type == F_UNLCK) + lock->l_type = F_UNLCK; + else + *lock = msg.lock; + } + + return err; +} |