From 0c59ebfc9b811c60fcf808a5de33320eeeb394af Mon Sep 17 00:00:00 2001 From: Miklos Szeredi <miklos@szeredi.hu> Date: Sun, 10 Sep 2006 20:53:36 +0000 Subject: [PATCH] ulockmgr --- ChangeLog | 13 ++ configure.in | 2 +- example/Makefile.am | 6 +- example/fusexmp_fh.c | 10 + include/Makefile.am | 2 +- include/ulockmgr.h | 23 +++ kernel/configure.ac | 2 +- kernel/file.c | 25 ++- kernel/inode.c | 7 + lib/Makefile.am | 5 +- lib/fuse.c | 246 ++++++++++++++++++++----- lib/ulockmgr.c | 402 +++++++++++++++++++++++++++++++++++++++++ util/.cvsignore | 1 + util/Makefile.am | 5 +- util/ulockmgr_server.c | 347 +++++++++++++++++++++++++++++++++++ 15 files changed, 1042 insertions(+), 54 deletions(-) create mode 100644 include/ulockmgr.h create mode 100644 lib/ulockmgr.c create mode 100644 util/ulockmgr_server.c diff --git a/ChangeLog b/ChangeLog index 744ab42..ad21e97 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2006-09-10 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.6.0-rc1 + +2006-09-10 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: Fix unlock on close for kernels < 2.6.18 + + * Add ulockmgr library & server. This can be used for handling + file locking requests either directly from libfuse or over a + network, etc. This first version is not optimized and the number + of file descriptors it uses may get out of hand + 2006-09-07 Miklos Szeredi <miklos@szeredi.hu> * lib: Add interrupt support to high level library, which may be diff --git a/configure.in b/configure.in index 063f68f..f21326e 100644 --- a/configure.in +++ b/configure.in @@ -1,4 +1,4 @@ -AC_INIT(fuse, 2.6.0-pre3) +AC_INIT(fuse, 2.6.0-rc1) AC_CANONICAL_TARGET AM_INIT_AUTOMAKE AM_CONFIG_HEADER(include/config.h) diff --git a/example/Makefile.am b/example/Makefile.am index 1f31cfc..9fe35e8 100644 --- a/example/Makefile.am +++ b/example/Makefile.am @@ -3,9 +3,5 @@ AM_CPPFLAGS = -I$(top_srcdir)/include noinst_PROGRAMS = fusexmp fusexmp_fh null hello hello_ll -fusexmp_SOURCES = fusexmp.c -fusexmp_fh_SOURCES = fusexmp_fh.c -null_SOURCES = null.c -hello_SOURCES = hello.c - LDADD = ../lib/libfuse.la -lpthread +fusexmp_fh_LDADD = ../lib/libfuse.la ../lib/libulockmgr.la -lpthread diff --git a/example/fusexmp_fh.c b/example/fusexmp_fh.c index 5ede6db..047ad09 100644 --- a/example/fusexmp_fh.c +++ b/example/fusexmp_fh.c @@ -11,6 +11,7 @@ #define _GNU_SOURCE #include <fuse.h> +#include <ulockmgr.h> #include <stdio.h> #include <string.h> #include <unistd.h> @@ -400,6 +401,14 @@ static int xmp_removexattr(const char *path, const char *name) } #endif /* HAVE_SETXATTR */ +static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock, uint64_t owner) +{ + (void) path; + + return ulockmgr_op(fi->fh, cmd, lock, &owner, sizeof(owner)); +} + static struct fuse_operations xmp_oper = { .getattr = xmp_getattr, .fgetattr = xmp_fgetattr, @@ -434,6 +443,7 @@ static struct fuse_operations xmp_oper = { .listxattr = xmp_listxattr, .removexattr= xmp_removexattr, #endif + .lock = xmp_lock, }; int main(int argc, char *argv[]) diff --git a/include/Makefile.am b/include/Makefile.am index c786a0d..be2c592 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -11,6 +11,6 @@ fuseinclude_HEADERS = \ fuse_lowlevel_compat.h \ fuse_opt.h -include_HEADERS = old/fuse.h +include_HEADERS = old/fuse.h ulockmgr.h noinst_HEADERS = fuse_kernel.h diff --git a/include/ulockmgr.h b/include/ulockmgr.h new file mode 100644 index 0000000..4cd7b1c --- /dev/null +++ b/include/ulockmgr.h @@ -0,0 +1,23 @@ +/* + 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. +*/ + +#include <stdint.h> +#include <fcntl.h> + +/** + * Perform POSIX locking operation + * + * @param fd the file descriptor + * @param cmd the locking command (F_GETFL, F_SETLK or F_SETLKW) + * @param lock the lock parameters + * @param owner the lock owner ID cookie + * @param owner_len length of the lock owner ID cookie + * @return 0 on success -errno on error + */ +int ulockmgr_op(int fd, int cmd, struct flock *lock, const void *owner, + size_t owner_len); diff --git a/kernel/configure.ac b/kernel/configure.ac index c3c1f3f..910e125 100644 --- a/kernel/configure.ac +++ b/kernel/configure.ac @@ -1,4 +1,4 @@ -AC_INIT(fuse-kernel, 2.6.0-pre3) +AC_INIT(fuse-kernel, 2.6.0-rc1) AC_CONFIG_HEADERS([config.h]) AC_PROG_INSTALL diff --git a/kernel/file.c b/kernel/file.c index 61eba56..e3a42c0 100644 --- a/kernel/file.c +++ b/kernel/file.c @@ -867,9 +867,32 @@ static int fuse_setlk(struct file *file, struct file_lock *fl) /* Unlock on close is handled by the flush method */ if (fl->fl_flags & FL_CLOSE) return 0; -#endif req = fuse_get_req(fc); +#else + /* If it's (possibly) unlock on close, don't fail the allocation */ + if (fl->fl_type == F_UNLCK && fl->fl_start == 0 && + fl->fl_end == OFFSET_MAX) + req = fuse_get_req_nofail(fc, file); + else { + /* Hack: add dummy lock, otherwise unlock on close is + optimized away */ + struct file_lock **flp; + for (flp = &inode->i_flock; + *flp && !((*flp)->fl_flags & FL_POSIX); + flp = &(*flp)->fl_next); + if (!*flp) { + struct file_lock *dummy = + kmalloc(sizeof(struct file_lock), GFP_KERNEL); + if (!dummy) + return -ENOLCK; + locks_init_lock(dummy); + dummy->fl_flags |= FL_POSIX; + *flp = dummy; + } + req = fuse_get_req(fc); + } +#endif if (IS_ERR(req)) return PTR_ERR(req); diff --git a/kernel/inode.c b/kernel/inode.c index 0145045..475513b 100644 --- a/kernel/inode.c +++ b/kernel/inode.c @@ -83,6 +83,13 @@ static void fuse_destroy_inode(struct inode *inode) struct fuse_inode *fi = get_fuse_inode(inode); if (fi->forget_req) fuse_request_free(fi->forget_req); +#ifndef KERNEL_2_6_18_PLUS + if (inode->i_flock) { + WARN_ON(inode->i_flock->fl_next); + kfree(inode->i_flock); + inode->i_flock = NULL; + } +#endif kmem_cache_free(fuse_inode_cachep, inode); } 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 diff --git a/lib/fuse.c b/lib/fuse.c index a2f6bb1..92f21db 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -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; +} diff --git a/util/.cvsignore b/util/.cvsignore index 87461c8..d7a4699 100644 --- a/util/.cvsignore +++ b/util/.cvsignore @@ -3,4 +3,5 @@ Makefile .deps .libs fusermount +ulockmgr_server fuse_ioslave diff --git a/util/Makefile.am b/util/Makefile.am index c07440a..8e8db2a 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -1,9 +1,12 @@ ## Process this file with automake to produce Makefile.in -bin_PROGRAMS = fusermount +bin_PROGRAMS = fusermount ulockmgr_server fusermount_SOURCES = fusermount.c +ulockmgr_server_SOURCES = ulockmgr_server.c +ulockmgr_server_LDFLAGS = -pthread + install-exec-hook: -chown root $(DESTDIR)$(bindir)/fusermount -chmod u+s $(DESTDIR)$(bindir)/fusermount diff --git a/util/ulockmgr_server.c b/util/ulockmgr_server.c new file mode 100644 index 0000000..a2d6863 --- /dev/null +++ b/util/ulockmgr_server.c @@ -0,0 +1,347 @@ +/* + ulockmgr_server: Userspace Lock Manager Server + Copyright (C) 2006 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +/* #define DEBUG 1 */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <pthread.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.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 origfd; + int finished; +}; + +struct owner { + struct fd_store *fds; + pthread_mutex_t lock; +}; + +struct req_data { + struct owner *o; + int cfd; + struct fd_store *f; + struct message msg; +}; + +#define MAX_SEND_FDS 2 + +static int receive_message(int sock, void *buf, size_t buflen, int *fdp, + int numfds) +{ + struct msghdr msg; + struct iovec iov; + char ccmsg[CMSG_SPACE(sizeof(int)) * MAX_SEND_FDS]; + struct cmsghdr *cmsg; + int res; + + assert(numfds <= MAX_SEND_FDS); + iov.iov_base = buf; + iov.iov_len = buflen; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof(ccmsg); + + res = recvmsg(sock, &msg, MSG_WAITALL); + if (!res) + return 0; + if (res == -1) { + perror("ulockmgr_server: recvmsg"); + return -1; + } + if ((size_t) res != buflen) { + fprintf(stderr, "ulockmgr_server: short message received\n"); + return -1; + } + + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg) { + if (!cmsg->cmsg_type == SCM_RIGHTS) { + fprintf(stderr, "ulockmgr_server: unknown control message %d\n", + cmsg->cmsg_type); + return -1; + } + memcpy(fdp, CMSG_DATA(cmsg), sizeof(int) * numfds); + } + return res; +} + +static int closefrom(int minfd) +{ + DIR *dir = opendir("/proc/self/fd"); + if (dir) { + int dfd = dirfd(dir); + struct dirent *ent; + while ((ent = readdir(dir))) { + char *end; + int fd = strtol(ent->d_name, &end, 10); + if (ent->d_name[0] && !end[0] && fd >= minfd && fd != dfd) + close(fd); + } + closedir(dir); + } + return 0; +} + +static void send_reply(int cfd, struct message *msg) +{ + int res = send(cfd, msg, sizeof(struct message), MSG_NOSIGNAL); + if (res == -1) + perror("ulockmgr_server: sending reply"); +#ifdef DEBUG + fprintf(stderr, "ulockmgr_server: error: %i\n", msg->error); +#endif +} + +static void *process_request(void *d_) +{ + struct req_data *d = d_; + int res; + + assert(d->msg.cmd == F_SETLKW); + res = fcntl(d->f->fd, F_SETLK, &d->msg.lock); + if (res == -1 && errno == EAGAIN) { + d->msg.error = EAGAIN; + d->msg.thr = pthread_self(); + send_reply(d->cfd, &d->msg); + res = fcntl(d->f->fd, F_SETLKW, &d->msg.lock); + } + d->msg.error = (res == -1) ? errno : 0; + pthread_mutex_lock(&d->o->lock); + d->f->finished = 1; + pthread_mutex_unlock(&d->o->lock); + send_reply(d->cfd, &d->msg); + close(d->cfd); + free(d); + + return NULL; +} + +static void process_message(struct owner *o, struct message *msg, int cfd, + int fd) +{ + struct fd_store *f; + struct req_data *d; + pthread_t tid; + int res; + +#ifdef DEBUG + fprintf(stderr, "ulockmgr_server: %i %i %i %lli %lli\n", + msg->cmd, msg->lock.l_type, msg->lock.l_whence, msg->lock.l_start, + msg->lock.l_len); +#endif + + if (msg->cmd == F_SETLK && msg->lock.l_type == F_UNLCK && + msg->lock.l_start == 0 && msg->lock.l_len == 0) { + struct fd_store **fp; + + for (fp = &o->fds; *fp;) { + struct fd_store *f = *fp; + if (f->origfd == msg->fd && f->finished) { + close(f->fd); + *fp = f->next; + free(f); + } else + fp = &f->next; + } + close(fd); + + msg->error = 0; + send_reply(cfd, msg); + close(cfd); + return; + } + + f = malloc(sizeof(struct fd_store)); + if (!f) { + msg->error = ENOLCK; + send_reply(cfd, msg); + close(cfd); + return; + } + + f->fd = fd; + f->origfd = msg->fd; + + if (msg->cmd == F_GETLK || msg->cmd == F_SETLK || + msg->lock.l_type == F_UNLCK) { + res = fcntl(f->fd, msg->cmd, &msg->lock); + msg->error = (res == -1) ? errno : 0; + send_reply(cfd, msg); + close(cfd); + f->next = o->fds; + o->fds = f; + f->finished = 1; + return; + } + + d = malloc(sizeof(struct req_data)); + if (!d) { + msg->error = ENOLCK; + send_reply(cfd, msg); + close(cfd); + free(f); + return; + } + + d->o = o; + d->cfd = cfd; + d->f = f; + d->msg = *msg; + res = pthread_create(&tid, NULL, process_request, d); + if (res) { + msg->error = ENOLCK; + send_reply(cfd, msg); + close(cfd); + free(d); + free(f); + return; + } + + f->next = o->fds; + o->fds = f; + pthread_detach(tid); +} + +static void sigusr1_handler(int sig) +{ + (void) sig; + /* Nothing to do */ +} + +static void process_owner(int cfd) +{ + struct owner o; + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = sigusr1_handler; + sigemptyset(&sa.sa_mask); + + if (sigaction(SIGUSR1, &sa, NULL) == -1) { + perror("ulockmgr_server: cannot set sigusr1 signal handler"); + exit(1); + } + + memset(&o, 0, sizeof(struct owner)); + pthread_mutex_init(&o.lock, NULL); + while (1) { + struct message msg; + int rfds[2]; + int res; + + res = receive_message(cfd, &msg, sizeof(msg), rfds, 2); + if (!res) + break; + if (res == -1) + exit(1); + + if (msg.intr) + pthread_kill(msg.thr, SIGUSR1); + else { + pthread_mutex_lock(&o.lock); + process_message(&o, &msg, rfds[0], rfds[1]); + pthread_mutex_unlock(&o.lock); + } + } + if (o.fds) + fprintf(stderr, "ulockmgr_server: open file descriptors on exit\n"); +} + +int main(int argc, char *argv[]) +{ + int nullfd; + char *end; + int cfd; + sigset_t empty; + + if (argc != 2 || !argv[1][0]) + goto out_inval; + + cfd = strtol(argv[1], &end, 10); + if (*end) + goto out_inval; + + if (daemon(0, 1) == -1) { + perror("ulockmgr_server: daemon"); + exit(1); + } + + sigemptyset(&empty); + sigprocmask(SIG_SETMASK, &empty, NULL); + + if (dup2(cfd, 4) == -1) { + perror("ulockmgr_server: dup2"); + exit(1); + } + cfd = 4; + nullfd = open("/dev/null", O_RDWR); + dup2(nullfd, 0); + dup2(nullfd, 1); + close(3); + closefrom(5); + while (1) { + char c; + int sock; + int pid; + int res = receive_message(cfd, &c, sizeof(c), &sock, 1); + if (!res) + break; + if (res == -1) + exit(1); + + pid = fork(); + if (pid == -1) { + perror("ulockmgr_server: fork"); + close(sock); + continue; + } + if (pid == 0) { + close(cfd); + pid = fork(); + if (pid == -1) { + perror("ulockmgr_server: fork"); + _exit(1); + } + if (pid == 0) + process_owner(sock); + _exit(0); + } + waitpid(pid, NULL, 0); + close(sock); + } + return 0; + + out_inval: + fprintf(stderr, "%s should be started by libulockmgr\n", argv[0]); + return 1; +} -- 2.30.2