diff options
-rw-r--r-- | .dir-locals.el | 26 | ||||
-rw-r--r-- | .travis.yml | 16 | ||||
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | README.md | 25 | ||||
-rw-r--r-- | configure.ac | 13 | ||||
-rwxr-xr-x | example/fioclient.c | 102 | ||||
-rwxr-xr-x | example/fselclient.c | 4 | ||||
-rw-r--r-- | include/cuse_lowlevel.h | 6 | ||||
-rw-r--r-- | include/fuse.h | 10 | ||||
-rw-r--r-- | include/fuse_common.h | 12 | ||||
-rw-r--r-- | include/fuse_lowlevel.h | 32 | ||||
-rw-r--r-- | include/fuse_opt.h | 6 | ||||
-rw-r--r-- | lib/cuse_lowlevel.c | 16 | ||||
-rw-r--r-- | lib/fuse.c | 9 | ||||
-rwxr-xr-x | lib/fuse_loop_mt.c | 2 | ||||
-rwxr-xr-x | lib/fuse_lowlevel.c | 157 | ||||
-rw-r--r-- | lib/fuse_session.c | 5 | ||||
-rwxr-xr-x | lib/fuse_signals.c | 1 | ||||
-rw-r--r-- | lib/mount.c | 14 | ||||
-rw-r--r-- | lib/mount_bsd.c | 2 | ||||
-rw-r--r-- | test/.gitignore | 2 | ||||
-rw-r--r-- | test/Makefile | 7 | ||||
-rw-r--r-- | test/Makefile.am | 2 | ||||
-rw-r--r-- | test/conftest.py | 85 | ||||
-rw-r--r-- | test/pytest.ini | 2 | ||||
-rw-r--r-- | test/test.c | 2 | ||||
-rwxr-xr-x | test/test_examples.py | 345 | ||||
-rwxr-xr-x | test/test_fuse.py | 32 | ||||
-rw-r--r-- | test/util.py | 38 |
29 files changed, 717 insertions, 260 deletions
diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..c70a23a --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,26 @@ +((nil . ((eval . (add-hook 'before-save-hook + 'whitespace-cleanup nil t)))) + (python-mode . ((indent-tabs-mode . nil))) + (c-mode . ((c-file-style . "stroustrup") + (indent-tabs-mode . t) + (tab-width . 8) + (c-basic-offset . 8) + (c-file-offsets . + ((block-close . 0) + (brace-list-close . 0) + (brace-list-entry . 0) + (brace-list-intro . +) + (case-label . 0) + (class-close . 0) + (defun-block-intro . +) + (defun-close . 0) + (defun-open . 0) + (else-clause . 0) + (inclass . +) + (label . 0) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + (topmost-intro . 0)))))) diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..565bf5e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +sudo: required +language: + - python + - c +python: "3.5" +compiler: gcc +install: + - sudo apt-get update -qq + - sudo apt-get install -qq doxygen libtool automake autoconf + - sudo python -m pip install pytest +script: + - ./makeconf.sh + - ./configure + - make -j4 + - doxygen doc/Doxyfile + - python -m pytest test/ diff --git a/Makefile.am b/Makefile.am index 971d5fe..6cb802c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,3 +12,7 @@ pkgconfigdir = @pkgconfigdir@ pkgconfig_DATA = fuse3.pc $(pkgconfig_DATA): config.status + +.PHONY: test +test: all + python3 -m pytest test/ @@ -5,18 +5,20 @@ Warning: unresolved security issue ---------------------------------- Be aware that FUSE has an unresolved security bug -([bug #15](https://github.com/libfuse/libfuse/issues/15)): the -permission check for accessing a cached directory is only done once -when the directory entry is first loaded into the cache. Subsequent -accesses will re-use the results of the first check, even if the -directory permissions have since changed, and even if the subsequent -access is made by a different user. +([bug #15](https://github.com/libfuse/libfuse/issues/15)): if the +`default_permissions` mount option is not used, the results of the +first permission check performed by the file system for a directory +entry will be re-used for subsequent accesses as long as the inode of +the accessed entry is present in the kernel cache - even if the +permissions have since changed, and even if the subsequent access is +made by a different user. This bug needs to be fixed in the Linux kernel and has been known since 2006 but unfortunately no fix has been applied yet. If you depend on correct permission handling for FUSE file systems, the only -workaround is to completely disable caching of directory -entries. Alternatively, the severity of the bug can be somewhat +workaround is to use `default_permissions` (which does not currently +support ACLs), or to completely disable caching of directory entry +attributes. Alternatively, the severity of the bug can be somewhat reduced by not using the `allow_other` mount option. @@ -54,6 +56,12 @@ tarball, build and install with make -j8 make install +To run some self tests, you need a Python 3 environment with the +[py.test](http://www.pytest.org/) module installed. To run the tests, +execute + + python3 -m pytest test/ + You may also need to add `/usr/local/lib` to `/etc/ld.so.conf` and/or run *ldconfig*. If you're building from the git repository (instead of using a release tarball), you also need to run `./makeconf.sh` to @@ -109,4 +117,3 @@ https://lists.sourceforge.net/lists/listinfo/fuse-devel). Please report any bugs on the GitHub issue tracker at https://github.com/libfuse/libfuse/issues. - diff --git a/configure.ac b/configure.ac index 29a281d..7ca9f4f 100644 --- a/configure.ac +++ b/configure.ac @@ -29,13 +29,15 @@ AC_ARG_ENABLE(util, [ --enable-util Compile with util ]) AC_ARG_ENABLE(example, [ --enable-example Compile with examples ]) +AC_ARG_ENABLE(test, + [ --enable-test Compile with tests ]) AC_ARG_ENABLE(mtab, [ --disable-mtab Disable and ignore usage of /etc/mtab ]) AC_ARG_WITH(pkgconfigdir, - [ --with-pkgconfigdir=DIR pkgconfig file in DIR @<:@LIBDIR/pkgconfig@:>@], - [pkgconfigdir=$withval], - [pkgconfigdir='${libdir}/pkgconfig']) + [ --with-pkgconfigdir=DIR pkgconfig file in DIR @<:@LIBDIR/pkgconfig@:>@], + [pkgconfigdir=$withval], + [pkgconfigdir='${libdir}/pkgconfig']) AC_SUBST(pkgconfigdir) subdirs2="include" @@ -49,6 +51,9 @@ fi if test "$enable_example" != "no"; then subdirs2="$subdirs2 example"; fi +if test "$enable_test" != "no"; then + subdirs2="$subdirs2 test"; +fi if test "$enable_mtab" = "no"; then AC_DEFINE(IGNORE_MTAB, 1, [Don't update /etc/mtab]) fi @@ -120,7 +125,7 @@ if test "$arch" = linux -a "$cross_compiling" != "yes"; then fi fi -AC_CONFIG_FILES([fuse3.pc Makefile lib/Makefile util/Makefile example/Makefile include/Makefile doc/Makefile]) +AC_CONFIG_FILES([fuse3.pc Makefile lib/Makefile util/Makefile example/Makefile include/Makefile doc/Makefile test/Makefile]) AC_OUTPUT if test "$util_linux_ok" = no; then diff --git a/example/fioclient.c b/example/fioclient.c index a7c0dbe..704f24b 100755 --- a/example/fioclient.c +++ b/example/fioclient.c @@ -34,56 +34,20 @@ #include "fioc.h" const char *usage = -"Usage: fioclient FIOC_FILE COMMAND\n" +"Usage: fioclient FIOC_FILE [size]\n" "\n" -"COMMANDS\n" -" s [SIZE] : get size if SIZE is omitted, set size otherwise\n" -" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n" -" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n" +"Get size if <size> is omitted, set size otherwise\n" "\n"; -static int do_rw(int fd, int is_read, size_t size, off_t offset, - size_t *prev_size, size_t *new_size) -{ - struct fioc_rw_arg arg = { .offset = offset }; - ssize_t ret; - - arg.buf = calloc(1, size); - if (!arg.buf) { - fprintf(stderr, "failed to allocated %zu bytes\n", size); - return -1; - } - - if (is_read) { - arg.size = size; - ret = ioctl(fd, FIOC_READ, &arg); - if (ret >= 0) - fwrite(arg.buf, 1, ret, stdout); - } else { - arg.size = fread(arg.buf, 1, size, stdin); - fprintf(stderr, "Writing %zu bytes\n", arg.size); - ret = ioctl(fd, FIOC_WRITE, &arg); - } - - if (ret >= 0) { - *prev_size = arg.prev_size; - *new_size = arg.new_size; - } else - perror("ioctl"); - - free(arg.buf); - return ret; -} - int main(int argc, char **argv) { - size_t param[2] = { }; - size_t size, prev_size = 0, new_size = 0; - char cmd; - int fd, i, rc; + size_t size; + int fd; - if (argc < 3) - goto usage; + if (argc < 2) { + fprintf(stderr, "%s", usage); + return 1; + } fd = open(argv[1], O_RDWR); if (fd < 0) { @@ -91,46 +55,18 @@ int main(int argc, char **argv) return 1; } - cmd = tolower(argv[2][0]); - argc -= 3; - argv += 3; - - for (i = 0; i < argc; i++) { - char *endp; - param[i] = strtoul(argv[i], &endp, 0); - if (endp == argv[i] || *endp != '\0') - goto usage; - } - - switch (cmd) { - case 's': - if (!argc) { - if (ioctl(fd, FIOC_GET_SIZE, &size)) { - perror("ioctl"); - return 1; - } - printf("%zu\n", size); - } else { - size = param[0]; - if (ioctl(fd, FIOC_SET_SIZE, &size)) { - perror("ioctl"); - return 1; - } + if (argc == 2) { + if (ioctl(fd, FIOC_GET_SIZE, &size)) { + perror("ioctl"); + return 1; } - return 0; - - case 'r': - case 'w': - rc = do_rw(fd, cmd == 'r', param[0], param[1], - &prev_size, &new_size); - if (rc < 0) + printf("%zu\n", size); + } else { + size = strtoul(argv[2], NULL, 0); + if (ioctl(fd, FIOC_SET_SIZE, &size)) { + perror("ioctl"); return 1; - fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n", - rc, prev_size, new_size); - return 0; + } } - - usage: - fprintf(stderr, "%s", usage); - return 1; + return 0; } diff --git a/example/fselclient.c b/example/fselclient.c index ac8b7b0..637cb07 100755 --- a/example/fselclient.c +++ b/example/fselclient.c @@ -40,7 +40,7 @@ int main(void) { static const char hex_map[FSEL_FILES] = "0123456789ABCDEF"; int fds[FSEL_FILES]; - int i, nfds; + int i, nfds, tries; for (i = 0; i < FSEL_FILES; i++) { char name[] = { hex_map[i], '\0' }; @@ -52,7 +52,7 @@ int main(void) } nfds = fds[FSEL_FILES - 1] + 1; - while (1) { + for(tries=0; tries < 16; tries++) { static char buf[4096]; fd_set rfds; int rc; diff --git a/include/cuse_lowlevel.h b/include/cuse_lowlevel.h index e147fa2..80476c2 100644 --- a/include/cuse_lowlevel.h +++ b/include/cuse_lowlevel.h @@ -9,8 +9,8 @@ Read example/cusexmp.c for usages. */ -#ifndef _CUSE_LOWLEVEL_H_ -#define _CUSE_LOWLEVEL_H_ +#ifndef CUSE_LOWLEVEL_H_ +#define CUSE_LOWLEVEL_H_ #ifndef FUSE_USE_VERSION #define FUSE_USE_VERSION 29 @@ -84,4 +84,4 @@ int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, } #endif -#endif /* _CUSE_LOWLEVEL_H_ */ +#endif /* CUSE_LOWLEVEL_H_ */ diff --git a/include/fuse.h b/include/fuse.h index e16104c..7f5daa8 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -6,8 +6,8 @@ See the file COPYING.LIB. */ -#ifndef _FUSE_H_ -#define _FUSE_H_ +#ifndef FUSE_H_ +#define FUSE_H_ /** @file * @@ -222,7 +222,7 @@ struct fuse_operations { /** Get file system statistics * - * The 'f_frsize', 'f_favail', 'f_fsid' and 'f_flag' fields are ignored + * The 'f_favail', 'f_fsid' and 'f_flag' fields are ignored * * Replaced 'struct statfs' parameter with 'struct statvfs' in * version 2.5 @@ -705,7 +705,7 @@ void fuse_exit(struct fuse *f); * If you are using multiple threads, you can enjoy all the parallel execution * and interactive response benefits of threads, and you get to enjoy all the * benefits of race conditions and locking bugs, too. Ensure that any code used - * in the callback funtion of fuse_operations is also thread-safe. + * in the callback function of fuse_operations is also thread-safe. * * @param f the FUSE handle * @return 0 if no error occurred, -1 otherwise @@ -936,4 +936,4 @@ struct fuse_session *fuse_get_session(struct fuse *f); } #endif -#endif /* _FUSE_H_ */ +#endif /* FUSE_H_ */ diff --git a/include/fuse_common.h b/include/fuse_common.h index beb44c7..2fa9962 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -7,12 +7,12 @@ /** @file */ -#if !defined(_FUSE_H_) && !defined(_FUSE_LOWLEVEL_H_) +#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_) #error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead." #endif -#ifndef _FUSE_COMMON_H_ -#define _FUSE_COMMON_H_ +#ifndef FUSE_COMMON_H_ +#define FUSE_COMMON_H_ #include "fuse_opt.h" #include <stdint.h> @@ -114,7 +114,7 @@ struct fuse_file_info { #define FUSE_CAP_SPLICE_READ (1 << 9) #define FUSE_CAP_FLOCK_LOCKS (1 << 10) #define FUSE_CAP_IOCTL_DIR (1 << 11) -#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12) +#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12) #define FUSE_CAP_READDIRPLUS (1 << 13) #define FUSE_CAP_READDIRPLUS_AUTO (1 << 14) #define FUSE_CAP_ASYNC_DIO (1 << 15) @@ -429,7 +429,7 @@ struct fuse_bufvec { }; /* Initialize bufvec with a single buffer of given size */ -#define FUSE_BUFVEC_INIT(size__) \ +#define FUSE_BUFVEC_INIT(size__) \ ((struct fuse_bufvec) { \ /* .count= */ 1, \ /* .idx = */ 0, \ @@ -523,4 +523,4 @@ struct _fuse_off_t_must_be_64bit_dummy_struct \ { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); }; #endif -#endif /* _FUSE_COMMON_H_ */ +#endif /* FUSE_COMMON_H_ */ diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 32908cb..a01dbf6 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -6,8 +6,8 @@ See the file COPYING.LIB. */ -#ifndef _FUSE_LOWLEVEL_H_ -#define _FUSE_LOWLEVEL_H_ +#ifndef FUSE_LOWLEVEL_H_ +#define FUSE_LOWLEVEL_H_ /** @file * @@ -602,7 +602,7 @@ struct fuse_lowlevel_ops { * * fi->fh will contain the value set by the opendir method, or * will be undefined if the opendir method didn't set any value. - * + * * Returning a directory entry from readdir() does not affect * its lookup count. * @@ -932,11 +932,11 @@ struct fuse_lowlevel_ops { * kernel supports splicing from the fuse device, then the * data will be made available in pipe for supporting zero * copy data transfer. - * - * buf->count is guaranteed to be one (and thus buf->idx is - * always zero). The write_buf handler must ensure that - * bufv->off is correctly updated (reflecting the number of - * bytes read from bufv->buf[0]). + * + * buf->count is guaranteed to be one (and thus buf->idx is + * always zero). The write_buf handler must ensure that + * bufv->off is correctly updated (reflecting the number of + * bytes read from bufv->buf[0]). * * Introduced in version 2.9 * @@ -1036,7 +1036,7 @@ struct fuse_lowlevel_ops { * * fi->fh will contain the value set by the opendir method, or * will be undefined if the opendir method didn't set any value. - * + * * In contrast to readdir() (which does not affect the lookup counts), * the lookup count of every entry returned by readdirplus(), except "." * and "..", is incremented by one. @@ -1402,7 +1402,7 @@ int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph); * @return zero for success, -errno for failure */ int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino, - off_t off, off_t len); + off_t off, off_t len); /** * Notify to invalidate parent attributes and the dentry matching @@ -1419,7 +1419,7 @@ int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino, * @return zero for success, -errno for failure */ int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, fuse_ino_t parent, - const char *name, size_t namelen); + const char *name, size_t namelen); /** * Notify to invalidate parent attributes and delete the dentry matching @@ -1702,14 +1702,6 @@ int fuse_session_loop_mt(struct fuse_session *se); * ----------------------------------------------------------- */ /** - * Query the file descriptor of the channel - * - * @param ch the channel - * @return the file descriptor passed to fuse_chan_new() - */ -int fuse_chan_fd(struct fuse_chan *ch); - -/** * Obtain counted reference to the channel * * @param ch the channel @@ -1728,4 +1720,4 @@ void fuse_chan_put(struct fuse_chan *ch); } #endif -#endif /* _FUSE_LOWLEVEL_H_ */ +#endif /* FUSE_LOWLEVEL_H_ */ diff --git a/include/fuse_opt.h b/include/fuse_opt.h index 20653b1..d8573e7 100644 --- a/include/fuse_opt.h +++ b/include/fuse_opt.h @@ -6,8 +6,8 @@ See the file COPYING.LIB. */ -#ifndef _FUSE_OPT_H_ -#define _FUSE_OPT_H_ +#ifndef FUSE_OPT_H_ +#define FUSE_OPT_H_ /** @file * @@ -268,4 +268,4 @@ int fuse_opt_match(const struct fuse_opt opts[], const char *opt); } #endif -#endif /* _FUSE_OPT_H_ */ +#endif /* FUSE_OPT_H_ */ diff --git a/lib/cuse_lowlevel.c b/lib/cuse_lowlevel.c index fbaa873..cef14c6 100644 --- a/lib/cuse_lowlevel.c +++ b/lib/cuse_lowlevel.c @@ -281,12 +281,16 @@ struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], int res; res = fuse_parse_cmdline(&args, NULL, multithreaded, &foreground); - if (res == -1) - goto err_args; + if (res == -1) { + fuse_opt_free_args(&args); + return NULL; + } res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL); - if (res == -1) - goto err_args; + if (res == -1) { + fuse_opt_free_args(&args); + return NULL; + } /* * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos @@ -301,7 +305,7 @@ struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], se = cuse_lowlevel_new(&args, ci, clop, userdata); fuse_opt_free_args(&args); if (se == NULL) - goto err_args; + return NULL; fd = open(devname, O_RDWR); if (fd == -1) { @@ -335,8 +339,6 @@ err_sig: fuse_remove_signal_handlers(se); err_se: fuse_session_destroy(se); -err_args: - fuse_opt_free_args(&args); return NULL; } @@ -118,11 +118,11 @@ struct node_table { }; #define container_of(ptr, type, member) ({ \ - const typeof( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - offsetof(type,member) );}) + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) #define list_entry(ptr, type, member) \ - container_of(ptr, type, member) + container_of(ptr, type, member) struct list_head { struct list_head *next; @@ -4320,7 +4320,7 @@ static int fuse_session_loop_remember(struct fuse *f) time_t next_clean; struct fuse_chan *ch = fuse_session_chan(se); struct pollfd fds = { - .fd = fuse_chan_fd(ch), + .fd = ch->fd, .events = POLLIN }; struct fuse_buf fbuf = { @@ -4831,4 +4831,3 @@ void fuse_destroy(struct fuse *f) free(f); fuse_delete_context_key(); } - diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index 6d7f051..036f75c 100755 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -193,7 +193,7 @@ static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt) } fcntl(clonefd, F_SETFD, FD_CLOEXEC); - masterfd = fuse_chan_fd(mt->prevch); + masterfd = mt->prevch->fd; res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd); if (res == -1) { fprintf(stderr, "fuse: failed to clone device fd: %s\n", diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index eff820f..a28ff47 100755 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -158,80 +158,11 @@ static struct fuse_req *fuse_ll_alloc_req(struct fuse_ll *f) return req; } -static int fuse_chan_recv(struct fuse_session *se, struct fuse_buf *buf, - struct fuse_chan *ch) -{ - struct fuse_ll *f = se->f; - int err; - ssize_t res; - - if (!buf->mem) { - buf->mem = malloc(f->bufsize); - if (!buf->mem) { - fprintf(stderr, - "fuse: failed to allocate read buffer\n"); - return -ENOMEM; - } - } - -restart: - res = read(fuse_chan_fd(ch), buf->mem, f->bufsize); - err = errno; - - if (fuse_session_exited(se)) - return 0; - if (res == -1) { - /* ENOENT means the operation was interrupted, it's safe - to restart */ - if (err == ENOENT) - goto restart; - - if (err == ENODEV) { - fuse_session_exit(se); - return 0; - } - /* Errors occurring during normal operation: EINTR (read - interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem - umounted) */ - if (err != EINTR && err != EAGAIN) - perror("fuse: reading device"); - return -err; - } - if ((size_t) res < sizeof(struct fuse_in_header)) { - fprintf(stderr, "short read on fuse device\n"); - return -EIO; - } - - buf->size = res; - - return res; -} - -static int fuse_chan_send(struct fuse_chan *ch, const struct iovec iov[], - size_t count) -{ - ssize_t res = writev(fuse_chan_fd(ch), iov, count); - int err = errno; - - if (res == -1) { - struct fuse_session *se = fuse_chan_session(ch); - - assert(se != NULL); - - /* ENOENT means the operation was interrupted */ - if (!fuse_session_exited(se) && err != ENOENT) - perror("fuse: writing device"); - return -err; - } - - return 0; -} - void fuse_chan_close(struct fuse_chan *ch) { - int fd = fuse_chan_fd(ch); + int fd = ch->fd; if (fd != -1) - close(fd); + close(fd); } @@ -257,9 +188,24 @@ static int fuse_send_msg(struct fuse_ll *f, struct fuse_chan *ch, } } - return fuse_chan_send(ch, iov, count); + ssize_t res = writev(ch->fd, iov, count); + int err = errno; + + if (res == -1) { + struct fuse_session *se = fuse_chan_session(ch); + + assert(se != NULL); + + /* ENOENT means the operation was interrupted */ + if (!fuse_session_exited(se) && err != ENOENT) + perror("fuse: writing device"); + return -err; + } + + return 0; } + int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, int count) { @@ -868,7 +814,7 @@ static int fuse_send_data_iov(struct fuse_ll *f, struct fuse_chan *ch, splice_flags |= SPLICE_F_MOVE; res = splice(llp->pipe[0], NULL, - fuse_chan_fd(ch), NULL, out->len, splice_flags); + ch->fd, NULL, out->len, splice_flags); if (res == -1) { res = -errno; perror("fuse: splice from pipe"); @@ -2088,7 +2034,7 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fprintf(stderr, " max_background=%i\n", outarg.max_background); fprintf(stderr, " congestion_threshold=%i\n", - outarg.congestion_threshold); + outarg.congestion_threshold); fprintf(stderr, " time_gran=%u\n", outarg.time_gran); } @@ -2193,7 +2139,7 @@ int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph) } int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino, - off_t off, off_t len) + off_t off, off_t len) { struct fuse_notify_inval_inode_out outarg; struct fuse_ll *f; @@ -2217,7 +2163,7 @@ int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino, } int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, fuse_ino_t parent, - const char *name, size_t namelen) + const char *name, size_t namelen) { struct fuse_notify_inval_entry_out outarg; struct fuse_ll *f; @@ -2723,7 +2669,7 @@ static void fuse_ll_help(void) " -o big_writes enable larger than 4kB writes\n" " -o no_remote_lock disable remote file locking\n" " -o no_remote_flock disable remote file locking (BSD)\n" -" -o no_remote_posix_lock disable remove file locking (POSIX)\n" +" -o no_remote_posix_lock disable remote file locking (POSIX)\n" " -o [no_]splice_write use splice to write to the fuse device\n" " -o [no_]splice_move move data while splicing to the fuse device\n" " -o [no_]splice_read use splice to read from the fuse device\n" @@ -2788,16 +2734,16 @@ static void fuse_ll_pipe_destructor(void *data) fuse_ll_pipe_free(llp); } -#ifdef HAVE_SPLICE int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf, struct fuse_chan *ch) { struct fuse_ll *f = se->f; + int err; + ssize_t res; +#ifdef HAVE_SPLICE size_t bufsize = f->bufsize; struct fuse_ll_pipe *llp; struct fuse_buf tmpbuf; - int err; - int res; if (f->conn.proto_minor < 14 || !(f->conn.want & FUSE_CAP_SPLICE_READ)) goto fallback; @@ -2819,7 +2765,7 @@ int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf, goto fallback; } - res = splice(fuse_chan_fd(ch), NULL, llp->pipe[1], NULL, bufsize, 0); + res = splice(ch->fd, NULL, llp->pipe[1], NULL, bufsize, 0); err = errno; if (fuse_session_exited(se)) @@ -2892,15 +2838,48 @@ int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf, return res; fallback: - return fuse_chan_recv(se, buf, ch); -} -#else -int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf, - struct fuse_chan *ch) -{ - return fuse_chan_recv(se, buf, ch); -} #endif + if (!buf->mem) { + buf->mem = malloc(f->bufsize); + if (!buf->mem) { + fprintf(stderr, + "fuse: failed to allocate read buffer\n"); + return -ENOMEM; + } + } + +restart: + res = read(ch->fd, buf->mem, f->bufsize); + err = errno; + + if (fuse_session_exited(se)) + return 0; + if (res == -1) { + /* ENOENT means the operation was interrupted, it's safe + to restart */ + if (err == ENOENT) + goto restart; + + if (err == ENODEV) { + fuse_session_exit(se); + return 0; + } + /* Errors occurring during normal operation: EINTR (read + interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem + umounted) */ + if (err != EINTR && err != EAGAIN) + perror("fuse: reading device"); + return -err; + } + if ((size_t) res < sizeof(struct fuse_in_header)) { + fprintf(stderr, "short read on fuse device\n"); + return -EIO; + } + + buf->size = res; + + return res; +} #define MIN_BUFSIZE 0x21000 diff --git a/lib/fuse_session.c b/lib/fuse_session.c index 42fe5c3..6b54e43 100644 --- a/lib/fuse_session.c +++ b/lib/fuse_session.c @@ -90,11 +90,6 @@ struct fuse_chan *fuse_chan_new(int fd) return ch; } -int fuse_chan_fd(struct fuse_chan *ch) -{ - return ch->fd; -} - struct fuse_session *fuse_chan_session(struct fuse_chan *ch) { return ch->se; diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c index b992b71..9fa787c 100755 --- a/lib/fuse_signals.c +++ b/lib/fuse_signals.c @@ -72,4 +72,3 @@ void fuse_remove_signal_handlers(struct fuse_session *se) set_one_signal_handler(SIGTERM, exit_handler, 1); set_one_signal_handler(SIGPIPE, SIG_IGN, 1); } - diff --git a/lib/mount.c b/lib/mount.c index de4ae74..7be7b25 100644 --- a/lib/mount.c +++ b/lib/mount.c @@ -28,12 +28,12 @@ #ifdef __NetBSD__ #include <perfuse.h> -#define MS_RDONLY MNT_RDONLY -#define MS_NOSUID MNT_NOSUID -#define MS_NODEV MNT_NODEV -#define MS_NOEXEC MNT_NOEXEC -#define MS_SYNCHRONOUS MNT_SYNCHRONOUS -#define MS_NOATIME MNT_NOATIME +#define MS_RDONLY MNT_RDONLY +#define MS_NOSUID MNT_NOSUID +#define MS_NODEV MNT_NODEV +#define MS_NOEXEC MNT_NOEXEC +#define MS_SYNCHRONOUS MNT_SYNCHRONOUS +#define MS_NOATIME MNT_NOATIME #define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0) #endif @@ -398,7 +398,7 @@ static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, rv = receive_fd(fds[1]); if (!mo->auto_unmount) { - /* with auto_unmount option fusermount will not exit until + /* with auto_unmount option fusermount will not exit until this socket is closed */ close(fds[1]); waitpid(pid, NULL, 0); /* bury zombie */ diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c index 8f3acf0..6a7c958 100644 --- a/lib/mount_bsd.c +++ b/lib/mount_bsd.c @@ -43,7 +43,7 @@ struct mount_opts { char *kernel_opts; }; -#define FUSE_DUAL_OPT_KEY(templ, key) \ +#define FUSE_DUAL_OPT_KEY(templ, key) \ FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key) static const struct fuse_opt fuse_mount_opts[] = { diff --git a/test/.gitignore b/test/.gitignore index 7b95378..b7041be 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,2 +1,2 @@ -!Makefile test +__pycache__/ diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index 738dd53..0000000 --- a/test/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -CC=gcc -CFLAGS=-Wall -W - -all: test - -clean: - rm -f *.o test diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..55d6950 --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,2 @@ +bin_PROGRAMS = test +test_SOURCES = test.c diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..0da2f4b --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,85 @@ +import sys +import pytest +import time +import re + +# If a test fails, wait a moment before retrieving the captured +# stdout/stderr. When using a server process, this makes sure that we capture +# any potential output of the server that comes *after* a test has failed. For +# example, if a request handler raises an exception, the server first signals an +# error to FUSE (causing the test to fail), and then logs the exception. Without +# the extra delay, the exception will go into nowhere. +@pytest.mark.hookwrapper +def pytest_pyfunc_call(pyfuncitem): + outcome = yield + failed = outcome.excinfo is not None + if failed: + time.sleep(1) + +@pytest.fixture() +def pass_capfd(request, capfd): + '''Provide capfd object to UnitTest instances''' + request.instance.capfd = capfd + +def check_test_output(capfd): + (stdout, stderr) = capfd.readouterr() + + # Write back what we've read (so that it will still be printed. + sys.stdout.write(stdout) + sys.stderr.write(stderr) + + # Strip out false positives + for (pattern, flags, count) in capfd.false_positives: + cp = re.compile(pattern, flags) + (stdout, cnt) = cp.subn('', stdout, count=count) + if count == 0 or count - cnt > 0: + stderr = cp.sub('', stderr, count=count - cnt) + + for pattern in ('exception', 'error', 'warning', 'fatal', 'traceback', + 'fault', 'crash(?:ed)?', 'abort(?:ed)'): + cp = re.compile(r'\b{}\b'.format(pattern), re.IGNORECASE | re.MULTILINE) + hit = cp.search(stderr) + if hit: + raise AssertionError('Suspicious output to stderr (matched "%s")' % hit.group(0)) + hit = cp.search(stdout) + if hit: + raise AssertionError('Suspicious output to stdout (matched "%s")' % hit.group(0)) + +def register_output(self, pattern, count=1, flags=re.MULTILINE): + '''Register *pattern* as false positive for output checking + + This prevents the test from failing because the output otherwise + appears suspicious. + ''' + + self.false_positives.append((pattern, flags, count)) + +# This is a terrible hack that allows us to access the fixtures from the +# pytest_runtest_call hook. Among a lot of other hidden assumptions, it probably +# relies on tests running sequential (i.e., don't dare to use e.g. the xdist +# plugin) +current_capfd = None +@pytest.yield_fixture(autouse=True) +def save_cap_fixtures(request, capfd): + global current_capfd + capfd.false_positives = [] + + # Monkeypatch in a function to register false positives + type(capfd).register_output = register_output + + if request.config.getoption('capture') == 'no': + capfd = None + current_capfd = capfd + bak = current_capfd + yield + + # Try to catch problems with this hack (e.g. when running tests + # simultaneously) + assert bak is current_capfd + current_capfd = None + +@pytest.hookimpl(trylast=True) +def pytest_runtest_call(item): + capfd = current_capfd + if capfd is not None: + check_test_output(capfd) diff --git a/test/pytest.ini b/test/pytest.ini new file mode 100644 index 0000000..bc4af36 --- /dev/null +++ b/test/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --verbose --assert=rewrite --tb=native -x diff --git a/test/test.c b/test/test.c index 5d5750d..2b38bb2 100644 --- a/test/test.c +++ b/test/test.c @@ -993,7 +993,7 @@ static int do_test_open_acc(int flags, const char *flags_str, int mode, int err) int res; int fd; - start_test("open_acc(%s) mode: 0%03o error: '%s'", flags_str, mode, + start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode, strerror(err)); unlink(testfile); res = create_file(testfile, data, datalen); diff --git a/test/test_examples.py b/test/test_examples.py new file mode 100755 index 0000000..31d2fae --- /dev/null +++ b/test/test_examples.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python3 + +if __name__ == '__main__': + import pytest + import sys + sys.exit(pytest.main([__file__] + sys.argv[1:])) + +import subprocess +import os +import sys +import pytest +import stat +import shutil +import filecmp +import errno +from tempfile import NamedTemporaryFile +from util import wait_for_mount, umount, cleanup + +basename = os.path.join(os.path.dirname(__file__), '..') +TEST_FILE = __file__ + +with open(TEST_FILE, 'rb') as fh: + TEST_DATA = fh.read() + +def name_generator(__ctr=[0]): + __ctr[0] += 1 + return 'testfile_%d' % __ctr[0] + +@pytest.mark.parametrize("name", ('hello', 'hello_ll')) +def test_hello(tmpdir, name): + mnt_dir = str(tmpdir) + cmdline = [os.path.join(basename, 'example', name), + '-f', mnt_dir ] + mount_process = subprocess.Popen(cmdline) + try: + wait_for_mount(mount_process, mnt_dir) + assert os.listdir(mnt_dir) == [ 'hello' ] + filename = os.path.join(mnt_dir, 'hello') + with open(filename, 'r') as fh: + assert fh.read() == 'Hello World!\n' + with pytest.raises(IOError) as exc_info: + open(filename, 'r+') + assert exc_info.value.errno == errno.EACCES + with pytest.raises(IOError) as exc_info: + open(filename + 'does-not-exist', 'r+') + assert exc_info.value.errno == errno.ENOENT + except: + cleanup(mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +@pytest.mark.parametrize("name", ('fusexmp', 'fusexmp_fh')) +def test_fusexmp_fh(tmpdir, name): + mnt_dir = str(tmpdir.mkdir('mnt')) + src_dir = str(tmpdir.mkdir('src')) + + cmdline = [os.path.join(basename, 'example', name), + '-f', '-o' , 'use_ino,readdir_ino,kernel_cache', + mnt_dir ] + mount_process = subprocess.Popen(cmdline) + try: + wait_for_mount(mount_process, mnt_dir) + work_dir = os.path.join(mnt_dir, src_dir) + tst_write(work_dir) + tst_mkdir(work_dir) + tst_symlink(work_dir) + tst_mknod(work_dir) + if os.getuid() == 0: + tst_chown(work_dir) + # Underlying fs may not have full nanosecond resolution + tst_utimens(work_dir, ns_tol=1000) + tst_link(work_dir) + tst_readdir(work_dir) + tst_statvfs(work_dir) + tst_truncate_path(work_dir) + tst_truncate_fd(work_dir) + tst_unlink(work_dir) + tst_passthrough(src_dir, work_dir) + except: + cleanup(mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +def test_fioc(tmpdir): + mnt_dir = str(tmpdir) + testfile = os.path.join(mnt_dir, 'fioc') + cmdline = [os.path.join(basename, 'example', 'fioc'), + '-f', mnt_dir ] + mount_process = subprocess.Popen(cmdline) + try: + wait_for_mount(mount_process, mnt_dir) + + base_cmd = [ os.path.join(basename, 'example', 'fioclient'), + testfile ] + assert subprocess.check_output(base_cmd) == b'0\n' + with open(testfile, 'wb') as fh: + fh.write(b'foobar') + assert subprocess.check_output(base_cmd) == b'6\n' + subprocess.check_call(base_cmd + [ '3' ]) + with open(testfile, 'rb') as fh: + assert fh.read()== b'foo' + except: + cleanup(mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +def test_fsel(tmpdir): + mnt_dir = str(tmpdir) + cmdline = [os.path.join(basename, 'example', 'fsel'), + '-f', mnt_dir ] + mount_process = subprocess.Popen(cmdline) + try: + wait_for_mount(mount_process, mnt_dir) + cmdline = [ os.path.join(basename, 'example', 'fselclient') ] + subprocess.check_call(cmdline, cwd=mnt_dir) + except: + cleanup(mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +def checked_unlink(filename, path, isdir=False): + fullname = os.path.join(path, filename) + if isdir: + os.rmdir(fullname) + else: + os.unlink(fullname) + with pytest.raises(OSError) as exc_info: + os.stat(fullname) + assert exc_info.value.errno == errno.ENOENT + assert filename not in os.listdir(path) + +def tst_mkdir(mnt_dir): + dirname = name_generator() + fullname = mnt_dir + "/" + dirname + os.mkdir(fullname) + fstat = os.stat(fullname) + assert stat.S_ISDIR(fstat.st_mode) + assert os.listdir(fullname) == [] + assert fstat.st_nlink in (1,2) + assert dirname in os.listdir(mnt_dir) + checked_unlink(dirname, mnt_dir, isdir=True) + +def tst_symlink(mnt_dir): + linkname = name_generator() + fullname = mnt_dir + "/" + linkname + os.symlink("/imaginary/dest", fullname) + fstat = os.lstat(fullname) + assert stat.S_ISLNK(fstat.st_mode) + assert os.readlink(fullname) == "/imaginary/dest" + assert fstat.st_nlink == 1 + assert linkname in os.listdir(mnt_dir) + checked_unlink(linkname, mnt_dir) + +def tst_mknod(mnt_dir): + filename = os.path.join(mnt_dir, name_generator()) + shutil.copyfile(TEST_FILE, filename) + fstat = os.lstat(filename) + assert stat.S_ISREG(fstat.st_mode) + assert fstat.st_nlink == 1 + assert os.path.basename(filename) in os.listdir(mnt_dir) + assert filecmp.cmp(TEST_FILE, filename, False) + checked_unlink(filename, mnt_dir) + +def tst_chown(mnt_dir): + filename = os.path.join(mnt_dir, name_generator()) + os.mkdir(filename) + fstat = os.lstat(filename) + uid = fstat.st_uid + gid = fstat.st_gid + + uid_new = uid + 1 + os.chown(filename, uid_new, -1) + fstat = os.lstat(filename) + assert fstat.st_uid == uid_new + assert fstat.st_gid == gid + + gid_new = gid + 1 + os.chown(filename, -1, gid_new) + fstat = os.lstat(filename) + assert fstat.st_uid == uid_new + assert fstat.st_gid == gid_new + + checked_unlink(filename, mnt_dir, isdir=True) + +def tst_write(mnt_dir): + name = os.path.join(mnt_dir, name_generator()) + shutil.copyfile(TEST_FILE, name) + assert filecmp.cmp(name, TEST_FILE, False) + checked_unlink(name, mnt_dir) + +def tst_unlink(mnt_dir): + name = os.path.join(mnt_dir, name_generator()) + data1 = b'foo' + data2 = b'bar' + + with open(os.path.join(mnt_dir, name), 'wb+', buffering=0) as fh: + fh.write(data1) + checked_unlink(name, mnt_dir) + fh.write(data2) + fh.seek(0) + assert fh.read() == data1+data2 + +def tst_statvfs(mnt_dir): + os.statvfs(mnt_dir) + +def tst_link(mnt_dir): + name1 = os.path.join(mnt_dir, name_generator()) + name2 = os.path.join(mnt_dir, name_generator()) + shutil.copyfile(TEST_FILE, name1) + assert filecmp.cmp(name1, TEST_FILE, False) + os.link(name1, name2) + + fstat1 = os.lstat(name1) + fstat2 = os.lstat(name2) + + assert fstat1 == fstat2 + assert fstat1.st_nlink == 2 + + assert os.path.basename(name2) in os.listdir(mnt_dir) + assert filecmp.cmp(name1, name2, False) + os.unlink(name2) + fstat1 = os.lstat(name1) + assert fstat1.st_nlink == 1 + os.unlink(name1) + +def tst_readdir(mnt_dir): + dir_ = os.path.join(mnt_dir, name_generator()) + file_ = dir_ + "/" + name_generator() + subdir = dir_ + "/" + name_generator() + subfile = subdir + "/" + name_generator() + + os.mkdir(dir_) + shutil.copyfile(TEST_FILE, file_) + os.mkdir(subdir) + shutil.copyfile(TEST_FILE, subfile) + + listdir_is = os.listdir(dir_) + listdir_is.sort() + listdir_should = [ os.path.basename(file_), os.path.basename(subdir) ] + listdir_should.sort() + assert listdir_is == listdir_should + + os.unlink(file_) + os.unlink(subfile) + os.rmdir(subdir) + os.rmdir(dir_) + +def tst_truncate_path(mnt_dir): + assert len(TEST_DATA) > 1024 + + filename = os.path.join(mnt_dir, name_generator()) + with open(filename, 'wb') as fh: + fh.write(TEST_DATA) + + fstat = os.stat(filename) + size = fstat.st_size + assert size == len(TEST_DATA) + + # Add zeros at the end + os.truncate(filename, size + 1024) + assert os.stat(filename).st_size == size + 1024 + with open(filename, 'rb') as fh: + assert fh.read(size) == TEST_DATA + assert fh.read(1025) == b'\0' * 1024 + + # Truncate data + os.truncate(filename, size - 1024) + assert os.stat(filename).st_size == size - 1024 + with open(filename, 'rb') as fh: + assert fh.read(size) == TEST_DATA[:size-1024] + + os.unlink(filename) + +def tst_truncate_fd(mnt_dir): + assert len(TEST_DATA) > 1024 + with NamedTemporaryFile('w+b', 0, dir=mnt_dir) as fh: + fd = fh.fileno() + fh.write(TEST_DATA) + fstat = os.fstat(fd) + size = fstat.st_size + assert size == len(TEST_DATA) + + # Add zeros at the end + os.ftruncate(fd, size + 1024) + assert os.fstat(fd).st_size == size + 1024 + fh.seek(0) + assert fh.read(size) == TEST_DATA + assert fh.read(1025) == b'\0' * 1024 + + # Truncate data + os.ftruncate(fd, size - 1024) + assert os.fstat(fd).st_size == size - 1024 + fh.seek(0) + assert fh.read(size) == TEST_DATA[:size-1024] + +def tst_utimens(mnt_dir, ns_tol=0): + filename = os.path.join(mnt_dir, name_generator()) + os.mkdir(filename) + fstat = os.lstat(filename) + + atime = fstat.st_atime + 42.28 + mtime = fstat.st_mtime - 42.23 + if sys.version_info < (3,3): + os.utime(filename, (atime, mtime)) + else: + atime_ns = fstat.st_atime_ns + int(42.28*1e9) + mtime_ns = fstat.st_mtime_ns - int(42.23*1e9) + os.utime(filename, None, ns=(atime_ns, mtime_ns)) + + fstat = os.lstat(filename) + + assert abs(fstat.st_atime - atime) < 1e-3 + assert abs(fstat.st_mtime - mtime) < 1e-3 + if sys.version_info >= (3,3): + assert abs(fstat.st_atime_ns - atime_ns) <= ns_tol + assert abs(fstat.st_mtime_ns - mtime_ns) <= ns_tol + + checked_unlink(filename, mnt_dir, isdir=True) + +def tst_passthrough(src_dir, mnt_dir): + name = name_generator() + src_name = os.path.join(src_dir, name) + mnt_name = os.path.join(src_dir, name) + assert name not in os.listdir(src_dir) + assert name not in os.listdir(mnt_dir) + with open(src_name, 'w') as fh: + fh.write('Hello, world') + assert name in os.listdir(src_dir) + assert name in os.listdir(mnt_dir) + assert os.stat(src_name) == os.stat(mnt_name) + + name = name_generator() + src_name = os.path.join(src_dir, name) + mnt_name = os.path.join(src_dir, name) + assert name not in os.listdir(src_dir) + assert name not in os.listdir(mnt_dir) + with open(mnt_name, 'w') as fh: + fh.write('Hello, world') + assert name in os.listdir(src_dir) + assert name in os.listdir(mnt_dir) + assert os.stat(src_name) == os.stat(mnt_name) diff --git a/test/test_fuse.py b/test/test_fuse.py new file mode 100755 index 0000000..bbba6e0 --- /dev/null +++ b/test/test_fuse.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +import pytest +import sys + +if __name__ == '__main__': + sys.exit(pytest.main([__file__] + sys.argv[1:])) + +import subprocess +import os +from util import wait_for_mount, umount, cleanup + +basename = os.path.join(os.path.dirname(__file__), '..') + +def test_fuse(tmpdir): + mnt_dir = str(tmpdir.mkdir('mnt')) + src_dir = str(tmpdir.mkdir('src')) + + cmdline = [ os.path.join(basename, 'example', 'fusexmp_fh'), + '-f', '-o' , 'use_ino,readdir_ino,kernel_cache', + mnt_dir ] + mount_process = subprocess.Popen(cmdline) + try: + wait_for_mount(mount_process, mnt_dir) + cmdline = [ os.path.join(basename, 'test', 'test'), + os.path.join(mnt_dir, src_dir), + ':' + src_dir ] + subprocess.check_call(cmdline) + except: + cleanup(mnt_dir) + raise + else: + umount(mount_process, mnt_dir) diff --git a/test/util.py b/test/util.py new file mode 100644 index 0000000..48ec995 --- /dev/null +++ b/test/util.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import subprocess +import pytest +import os +import time + +def wait_for_mount(mount_process, mnt_dir): + elapsed = 0 + while elapsed < 30: + if os.path.ismount(mnt_dir): + return True + if mount_process.poll() is not None: + pytest.fail('file system process terminated prematurely') + time.sleep(0.1) + elapsed += 0.1 + pytest.fail("mountpoint failed to come up") + +def cleanup(mnt_dir): + subprocess.call(['fusermount', '-z', '-u', mnt_dir], + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT) + +def umount(mount_process, mnt_dir): + subprocess.check_call(['fusermount', '-z', '-u', mnt_dir]) + assert not os.path.ismount(mnt_dir) + + # Give mount process a little while to terminate. Popen.wait(timeout) + # was only added in 3.3... + elapsed = 0 + while elapsed < 30: + code = mount_process.poll() + if code is not None: + if code == 0: + return + pytest.fail('file system process terminated with code %s' % (code,)) + time.sleep(0.1) + elapsed += 0.1 + pytest.fail('mount process did not terminate') |