From cd851487708f68bcb130965275b2ed8c2a14526b Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 26 Feb 2019 03:28:14 +0000 Subject: [PATCH 01/41] Create a project branch for fuse(4) work. The branch will be used for a new test suite, bug fixes, and various other enhancements to fuse. Sponsored by: The FreeBSD Foundation --- sbin/ifconfig/af_inet.c | 8 ++++---- sbin/ifconfig/af_inet6.c | 34 +++++++++++++++++----------------- sbin/ifconfig/ifconfig.c | 2 +- sbin/ifconfig/ifgroup.c | 4 ++-- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/sbin/ifconfig/af_inet.c b/sbin/ifconfig/af_inet.c index d2a6dcb7714..3d44a4c0b99 100644 --- a/sbin/ifconfig/af_inet.c +++ b/sbin/ifconfig/af_inet.c @@ -107,16 +107,16 @@ in_status(int s __unused, const struct ifaddrs *ifa) if (cidr == 0) break; } - printf("/%d ", cidr); + printf("/%d", cidr); } else if (f_inet != NULL && strcmp(f_inet, "dotted") == 0) - printf(" netmask %s ", inet_ntoa(sin->sin_addr)); + printf(" netmask %s", inet_ntoa(sin->sin_addr)); else - printf(" netmask 0x%lx ", (unsigned long)ntohl(sin->sin_addr.s_addr)); + printf(" netmask 0x%lx", (unsigned long)ntohl(sin->sin_addr.s_addr)); if (ifa->ifa_flags & IFF_BROADCAST) { sin = (struct sockaddr_in *)ifa->ifa_broadaddr; if (sin != NULL && sin->sin_addr.s_addr != 0) - printf("broadcast %s ", inet_ntoa(sin->sin_addr)); + printf(" broadcast %s", inet_ntoa(sin->sin_addr)); } print_vhid(ifa, " "); diff --git a/sbin/ifconfig/af_inet6.c b/sbin/ifconfig/af_inet6.c index 217bfd52a98..6a556c15059 100644 --- a/sbin/ifconfig/af_inet6.c +++ b/sbin/ifconfig/af_inet6.c @@ -247,49 +247,49 @@ in6_status(int s __unused, const struct ifaddrs *ifa) if (sin == NULL) sin = &null_sin; if (f_inet6 != NULL && strcmp(f_inet6, "cidr") == 0) - printf("/%d ", prefix(&sin->sin6_addr, + printf("/%d", prefix(&sin->sin6_addr, sizeof(struct in6_addr))); else - printf(" prefixlen %d ", prefix(&sin->sin6_addr, + printf(" prefixlen %d", prefix(&sin->sin6_addr, sizeof(struct in6_addr))); if ((flags6 & IN6_IFF_ANYCAST) != 0) - printf("anycast "); + printf(" anycast"); if ((flags6 & IN6_IFF_TENTATIVE) != 0) - printf("tentative "); + printf(" tentative"); if ((flags6 & IN6_IFF_DUPLICATED) != 0) - printf("duplicated "); + printf(" duplicated"); if ((flags6 & IN6_IFF_DETACHED) != 0) - printf("detached "); + printf(" detached"); if ((flags6 & IN6_IFF_DEPRECATED) != 0) - printf("deprecated "); + printf(" deprecated"); if ((flags6 & IN6_IFF_AUTOCONF) != 0) - printf("autoconf "); + printf(" autoconf"); if ((flags6 & IN6_IFF_TEMPORARY) != 0) - printf("temporary "); + printf(" temporary"); if ((flags6 & IN6_IFF_PREFER_SOURCE) != 0) - printf("prefer_source "); + printf(" prefer_source"); if (((struct sockaddr_in6 *)(ifa->ifa_addr))->sin6_scope_id) - printf("scopeid 0x%x ", + printf(" scopeid 0x%x", ((struct sockaddr_in6 *)(ifa->ifa_addr))->sin6_scope_id); if (ip6lifetime && (lifetime.ia6t_preferred || lifetime.ia6t_expire)) { - printf("pltime "); + printf(" pltime"); if (lifetime.ia6t_preferred) { - printf("%s ", lifetime.ia6t_preferred < now.tv_sec + printf(" %s", lifetime.ia6t_preferred < now.tv_sec ? "0" : sec2str(lifetime.ia6t_preferred - now.tv_sec)); } else - printf("infty "); + printf(" infty"); - printf("vltime "); + printf(" vltime"); if (lifetime.ia6t_expire) { - printf("%s ", lifetime.ia6t_expire < now.tv_sec + printf(" %s", lifetime.ia6t_expire < now.tv_sec ? "0" : sec2str(lifetime.ia6t_expire - now.tv_sec)); } else - printf("infty "); + printf(" infty"); } print_vhid(ifa, " "); diff --git a/sbin/ifconfig/ifconfig.c b/sbin/ifconfig/ifconfig.c index 95ef3b065c8..e330cc91566 100644 --- a/sbin/ifconfig/ifconfig.c +++ b/sbin/ifconfig/ifconfig.c @@ -1405,7 +1405,7 @@ print_vhid(const struct ifaddrs *ifa, const char *s) if (ifd->ifi_vhid == 0) return; - printf("vhid %d ", ifd->ifi_vhid); + printf(" vhid %d", ifd->ifi_vhid); } void diff --git a/sbin/ifconfig/ifgroup.c b/sbin/ifconfig/ifgroup.c index 106d1260ef1..50d17ca6429 100644 --- a/sbin/ifconfig/ifgroup.c +++ b/sbin/ifconfig/ifgroup.c @@ -113,9 +113,9 @@ getifgroups(int s) len -= sizeof(struct ifg_req); if (strcmp(ifg->ifgrq_group, "all")) { if (cnt == 0) - printf("\tgroups: "); + printf("\tgroups:"); cnt++; - printf("%s ", ifg->ifgrq_group); + printf(" %s", ifg->ifgrq_group); } } if (cnt) From cf16949867b7af0ac2b53b1489fb428d9608c6bb Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Thu, 28 Feb 2019 19:27:54 +0000 Subject: [PATCH 02/41] fuse(4): convert debug printfs into dtrace probes fuse(4) was heavily instrumented with debug printf statements that could only be enabled with compile-time flags. They fell into three basic groups: 1) Totally redundant with dtrace FBT probes. These I deleted. 2) Print textual information, usually error messages. These I converted to SDT probes of the form fuse:fuse:FILE:trace. They work just like the old printf statements except they can be enabled at runtime with dtrace. They can be filtered by FILE and/or by priority. 3) More complicated probes that print detailed information. These I converted into ad-hoc SDT probes. Sponsored by: The FreeBSD Foundation --- sys/fs/fuse/fuse_debug.h | 81 ------------------------ sys/fs/fuse/fuse_device.c | 68 +++++++++++--------- sys/fs/fuse/fuse_file.c | 25 ++++---- sys/fs/fuse/fuse_internal.c | 29 ++++----- sys/fs/fuse/fuse_io.c | 86 +++++++++++++------------ sys/fs/fuse/fuse_ipc.c | 104 +++++++------------------------ sys/fs/fuse/fuse_main.c | 3 + sys/fs/fuse/fuse_node.c | 22 +++---- sys/fs/fuse/fuse_vfsops.c | 55 ++++++++-------- sys/fs/fuse/fuse_vnops.c | 121 +++++++++--------------------------- 10 files changed, 201 insertions(+), 393 deletions(-) delete mode 100644 sys/fs/fuse/fuse_debug.h diff --git a/sys/fs/fuse/fuse_debug.h b/sys/fs/fuse/fuse_debug.h deleted file mode 100644 index 7876f2ec2d6..00000000000 --- a/sys/fs/fuse/fuse_debug.h +++ /dev/null @@ -1,81 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 2007-2009 Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * Copyright (C) 2005 Csaba Henk. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * $FreeBSD$ - */ - -#include - -/* Debug related stuff */ - -#ifndef FUSE_DEBUG_MODULE -#error "FUSE_DEBUG_MODULE is not defined" -#else -#define FUSE_DEBUG_VAR __CONCAT(FUSE_DEBUG_,FUSE_DEBUG_MODULE) -#endif - -#define FS_DEBUG(fmt, ...) DEBUGX(FUSE_DEBUG_VAR >= 1, fmt, ## __VA_ARGS__) -#define FS_DEBUG2G(fmt, ...) DEBUGX(FUSE_DEBUG_VAR >= 2, fmt, ## __VA_ARGS__) - -#define debug_printf(fmt, ...) FS_DEBUG(fmt, ## __VA_ARGS__) -#define kdebug_printf(fmt, ...) FS_DEBUG(fmt, ## __VA_ARGS__) - -#define fuse_trace_printf(fmt, ...) \ - DEBUGX(FUSE_DEBUG_VAR && FUSE_TRACE, fmt, ## __VA_ARGS__) -#define fuse_trace_printf_func() \ - fuse_trace_printf("%s:%d\n", __FILE__, __LINE__) -#define fuse_trace_printf_vfsop() fuse_trace_printf_func() -#define fuse_trace_printf_vnop() fuse_trace_printf_func() diff --git a/sys/fs/fuse/fuse_device.c b/sys/fs/fuse/fuse_device.c index 304147b9f81..6539a88f45c 100644 --- a/sys/fs/fuse/fuse_device.c +++ b/sys/fs/fuse/fuse_device.c @@ -73,6 +73,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -82,8 +83,13 @@ __FBSDID("$FreeBSD$"); #include "fuse.h" #include "fuse_ipc.h" -#define FUSE_DEBUG_MODULE DEVICE -#include "fuse_debug.h" +SDT_PROVIDER_DECLARE(fuse); +/* + * Fuse trace probe: + * arg0: verbosity. Higher numbers give more verbose messages + * arg1: Textual message + */ +SDT_PROBE_DEFINE2(fuse, , device, trace, "int", "char*"); static struct cdev *fuse_dev; @@ -127,15 +133,14 @@ fuse_device_open(struct cdev *dev, int oflags, int devtype, struct thread *td) struct fuse_data *fdata; int error; - FS_DEBUG("device %p\n", dev); + SDT_PROBE2(fuse, , device, trace, 1, "device open"); fdata = fdata_alloc(dev, td->td_ucred); error = devfs_set_cdevpriv(fdata, fdata_dtor); if (error != 0) fdata_trydestroy(fdata); else - FS_DEBUG("%s: device opened by thread %d.\n", dev->si_name, - td->td_tid); + SDT_PROBE2(fuse, , device, trace, 1, "device open success"); return (error); } @@ -170,7 +175,7 @@ fuse_device_close(struct cdev *dev, int fflag, int devtype, struct thread *td) fuse_lck_mtx_unlock(data->aw_mtx); FUSE_UNLOCK(); - FS_DEBUG("%s: device closed by thread %d.\n", dev->si_name, td->td_tid); + SDT_PROBE2(fuse, , device, trace, 1, "device close"); return (0); } @@ -214,7 +219,7 @@ fuse_device_read(struct cdev *dev, struct uio *uio, int ioflag) int buflen[3]; int i; - FS_DEBUG("fuse device being read on thread %d\n", uio->uio_td->td_tid); + SDT_PROBE2(fuse, , device, trace, 1, "fuse device read"); err = devfs_get_cdevpriv((void **)&data); if (err != 0) @@ -223,7 +228,9 @@ fuse_device_read(struct cdev *dev, struct uio *uio, int ioflag) fuse_lck_mtx_lock(data->ms_mtx); again: if (fdata_get_dead(data)) { - FS_DEBUG2G("we know early on that reader should be kicked so we don't wait for news\n"); + SDT_PROBE2(fuse, , device, trace, 2, + "we know early on that reader should be kicked so we " + "don't wait for news"); fuse_lck_mtx_unlock(data->ms_mtx); return (ENODEV); } @@ -249,7 +256,7 @@ again: * -- and some other cases, too, tho not totally clear, when * (cv_signal/wakeup_one signals the whole process ?) */ - FS_DEBUG("no message on thread #%d\n", uio->uio_td->td_tid); + SDT_PROBE2(fuse, , device, trace, 1, "no message on thread"); goto again; } fuse_lck_mtx_unlock(data->ms_mtx); @@ -259,16 +266,18 @@ again: * somebody somewhere -- eg., umount routine -- * wants this liaison finished off */ - FS_DEBUG2G("reader is to be sacked\n"); + SDT_PROBE2(fuse, , device, trace, 2, "reader is to be sacked"); if (tick) { - FS_DEBUG2G("weird -- \"kick\" is set tho there is message\n"); + SDT_PROBE2(fuse, , device, trace, 2, "weird -- " + "\"kick\" is set tho there is message"); FUSE_ASSERT_MS_DONE(tick); fuse_ticket_drop(tick); } return (ENODEV); /* This should make the daemon get off * of us */ } - FS_DEBUG("message got on thread #%d\n", uio->uio_td->td_tid); + SDT_PROBE2(fuse, , device, trace, 1, + "fuse device read message successfully"); KASSERT(tick->tk_ms_bufdata || tick->tk_ms_bufsize == 0, ("non-null buf pointer with positive size")); @@ -302,7 +311,8 @@ again: */ if (uio->uio_resid < buflen[i]) { fdata_set_dead(data); - FS_DEBUG2G("daemon is stupid, kick it off...\n"); + SDT_PROBE2(fuse, , device, trace, 2, + "daemon is stupid, kick it off..."); err = ENODEV; break; } @@ -320,16 +330,14 @@ again: static inline int fuse_ohead_audit(struct fuse_out_header *ohead, struct uio *uio) { - FS_DEBUG("Out header -- len: %i, error: %i, unique: %llu; iovecs: %d\n", - ohead->len, ohead->error, (unsigned long long)ohead->unique, - uio->uio_iovcnt); - if (uio->uio_resid + sizeof(struct fuse_out_header) != ohead->len) { - FS_DEBUG("Format error: body size differs from size claimed by header\n"); + SDT_PROBE2(fuse, , device, trace, 1, "Format error: body size " + "differs from size claimed by header"); return (EINVAL); } if (uio->uio_resid && ohead->error) { - FS_DEBUG("Format error: non zero error but message had a body\n"); + SDT_PROBE2(fuse, , device, trace, 1, + "Format error: non zero error but message had a body"); return (EINVAL); } /* Sanitize the linuxism of negative errnos */ @@ -338,6 +346,8 @@ fuse_ohead_audit(struct fuse_out_header *ohead, struct uio *uio) return (0); } +SDT_PROBE_DEFINE1(fuse, , device, fuse_device_write_bumped_into_callback, + "uint64_t"); /* * fuse_device_write first reads the header sent by the daemon. * If that's OK, looks up ticket/callback node by the unique id seen in header. @@ -353,15 +363,13 @@ fuse_device_write(struct cdev *dev, struct uio *uio, int ioflag) struct fuse_ticket *tick, *x_tick; int found = 0; - FS_DEBUG("resid: %zd, iovcnt: %d, thread: %d\n", - uio->uio_resid, uio->uio_iovcnt, uio->uio_td->td_tid); - err = devfs_get_cdevpriv((void **)&data); if (err != 0) return (err); if (uio->uio_resid < sizeof(struct fuse_out_header)) { - FS_DEBUG("got less than a header!\n"); + SDT_PROBE2(fuse, , device, trace, 1, + "fuse_device_write got less than a header!"); fdata_set_dead(data); return (EINVAL); } @@ -385,8 +393,9 @@ fuse_device_write(struct cdev *dev, struct uio *uio, int ioflag) fuse_lck_mtx_lock(data->aw_mtx); TAILQ_FOREACH_SAFE(tick, &data->aw_head, tk_aw_link, x_tick) { - FS_DEBUG("bumped into callback #%llu\n", - (unsigned long long)tick->tk_unique); + SDT_PROBE1(fuse, , device, + fuse_device_write_bumped_into_callback, + tick->tk_unique); if (tick->tk_unique == ohead.unique) { found = 1; fuse_aw_remove(tick); @@ -405,12 +414,14 @@ fuse_device_write(struct cdev *dev, struct uio *uio, int ioflag) * via ticket_drop(), so no manual mucking * around...) */ - FS_DEBUG("pass ticket to a callback\n"); + SDT_PROBE2(fuse, , device, trace, 1, + "pass ticket to a callback"); memcpy(&tick->tk_aw_ohead, &ohead, sizeof(ohead)); err = tick->tk_aw_handler(tick, uio); } else { /* pretender doesn't wanna do anything with answer */ - FS_DEBUG("stuff devalidated, so we drop it\n"); + SDT_PROBE2(fuse, , device, trace, 1, + "stuff devalidated, so we drop it"); } /* @@ -421,7 +432,8 @@ fuse_device_write(struct cdev *dev, struct uio *uio, int ioflag) fuse_ticket_drop(tick); } else { /* no callback at all! */ - FS_DEBUG("erhm, no handler for this response\n"); + SDT_PROBE2(fuse, , device, trace, 1, + "erhm, no handler for this response"); err = EINVAL; } diff --git a/sys/fs/fuse/fuse_file.c b/sys/fs/fuse/fuse_file.c index 015b1353445..759ac7a6bd9 100644 --- a/sys/fs/fuse/fuse_file.c +++ b/sys/fs/fuse/fuse_file.c @@ -58,11 +58,10 @@ #include __FBSDID("$FreeBSD$"); -#include +#include #include #include #include -#include #include #include #include @@ -74,6 +73,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include "fuse.h" @@ -82,8 +82,13 @@ __FBSDID("$FreeBSD$"); #include "fuse_ipc.h" #include "fuse_node.h" -#define FUSE_DEBUG_MODULE FILE -#include "fuse_debug.h" +SDT_PROVIDER_DECLARE(fuse); +/* + * Fuse trace probe: + * arg0: verbosity. Higher numbers give more verbose messages + * arg1: Textual message + */ +SDT_PROBE_DEFINE2(fuse, , file, trace, "int", "char*"); static int fuse_fh_count = 0; @@ -102,9 +107,6 @@ fuse_filehandle_open(struct vnode *vp, fufh_type_t fufh_type, int oflags = 0; int op = FUSE_OPEN; - fuse_trace_printf("fuse_filehandle_open(vp=%p, fufh_type=%d)\n", - vp, fufh_type); - if (fuse_filehandle_valid(vp, fufh_type)) { panic("FUSE: filehandle_open called despite valid fufh (type=%d)", fufh_type); @@ -118,6 +120,8 @@ fuse_filehandle_open(struct vnode *vp, fufh_type_t fufh_type, if (vnode_isdir(vp)) { op = FUSE_OPENDIR; if (fufh_type != FUFH_RDONLY) { + SDT_PROBE2(fuse, , file, trace, 1, + "non-rdonly fh requested for a directory?"); printf("FUSE:non-rdonly fh requested for a directory?\n"); fufh_type = FUFH_RDONLY; } @@ -129,7 +133,8 @@ fuse_filehandle_open(struct vnode *vp, fufh_type_t fufh_type, foi->flags = oflags; if ((err = fdisp_wait_answ(&fdi))) { - debug_printf("OUCH ... daemon didn't give fh (err = %d)\n", err); + SDT_PROBE2(fuse, , file, trace, 1, + "OUCH ... daemon didn't give fh"); if (err == ENOENT) { fuse_internal_vnode_disappear(vp); } @@ -167,9 +172,6 @@ fuse_filehandle_close(struct vnode *vp, fufh_type_t fufh_type, int err = 0; int op = FUSE_RELEASE; - fuse_trace_printf("fuse_filehandle_put(vp=%p, fufh_type=%d)\n", - vp, fufh_type); - fufh = &(fvdat->fufh[fufh_type]); if (!FUFH_IS_VALID(fufh)) { panic("FUSE: filehandle_put called on invalid fufh (type=%d)", @@ -266,7 +268,6 @@ fuse_filehandle_init(struct vnode *vp, fufh_type_t fufh_type, struct fuse_vnode_data *fvdat = VTOFUD(vp); struct fuse_filehandle *fufh; - FS_DEBUG("id=%jd type=%d\n", (intmax_t)fh_id, fufh_type); fufh = &(fvdat->fufh[fufh_type]); MPASS(!FUFH_IS_VALID(fufh)); fufh->fh_id = fh_id; diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c index d556dc3fd96..2870410560a 100644 --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -58,11 +58,10 @@ #include __FBSDID("$FreeBSD$"); -#include +#include #include #include #include -#include #include #include #include @@ -70,6 +69,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -94,8 +94,13 @@ __FBSDID("$FreeBSD$"); #include "fuse_file.h" #include "fuse_param.h" -#define FUSE_DEBUG_MODULE INTERNAL -#include "fuse_debug.h" +SDT_PROVIDER_DECLARE(fuse); +/* + * Fuse trace probe: + * arg0: verbosity. Higher numbers give more verbose messages + * arg1: Textual message + */ +SDT_PROBE_DEFINE2(fuse, , internal, trace, "int", "char*"); #ifdef ZERO_PAD_INCOMPLETE_BUFS static int isbzero(void *buf, size_t len); @@ -127,8 +132,6 @@ fuse_internal_access(struct vnode *vp, */ /* return 0;*/ - fuse_trace_printf_func(); - mp = vnode_mount(vp); vtype = vnode_vtype(vp); @@ -209,8 +212,6 @@ fuse_internal_access(struct vnode *vp, int fuse_internal_fsync_callback(struct fuse_ticket *tick, struct uio *uio) { - fuse_trace_printf_func(); - if (tick->tk_aw_ohead.error == ENOSYS) { fsess_set_notimpl(tick->tk_data->mp, fticket_opcode(tick)); } @@ -227,8 +228,6 @@ fuse_internal_fsync(struct vnode *vp, struct fuse_fsync_in *ffsi; struct fuse_dispatcher fdi; - fuse_trace_printf_func(); - if (vnode_isdir(vp)) { op = FUSE_FSYNCDIR; } @@ -386,8 +385,6 @@ fuse_internal_remove(struct vnode *dvp, err = 0; fvdat = VTOFUD(vp); - debug_printf("dvp=%p, cnp=%p, op=%d\n", vp, cnp, op); - fdisp_init(&fdi, cnp->cn_namelen + 1); fdisp_make_vp(&fdi, op, dvp, cnp->cn_thread, cnp->cn_cred); @@ -442,8 +439,6 @@ fuse_internal_newentry_makerequest(struct mount *mp, size_t bufsize, struct fuse_dispatcher *fdip) { - debug_printf("fdip=%p\n", fdip); - fdip->iosize = bufsize + cnp->cn_namelen + 1; fdisp_make(fdip, op, mp, dnid, cnp->cn_thread, cnp->cn_cred); @@ -526,9 +521,6 @@ fuse_internal_forget_send(struct mount *mp, struct fuse_dispatcher fdi; struct fuse_forget_in *ffi; - debug_printf("mp=%p, nodeid=%ju, nlookup=%ju\n", - mp, (uintmax_t)nodeid, (uintmax_t)nlookup); - /* * KASSERT(nlookup > 0, ("zero-times forget for vp #%llu", * (long long unsigned) nodeid)); @@ -574,7 +566,8 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio) /* XXX: Do we want to check anything further besides this? */ if (fiio->major < 7) { - debug_printf("userpace version too low\n"); + SDT_PROBE2(fuse, , internal, trace, 1, + "userpace version too low"); err = EPROTONOSUPPORT; goto out; } diff --git a/sys/fs/fuse/fuse_io.c b/sys/fs/fuse/fuse_io.c index eb89db22177..1f28ccd9cdb 100644 --- a/sys/fs/fuse/fuse_io.c +++ b/sys/fs/fuse/fuse_io.c @@ -98,9 +98,13 @@ __FBSDID("$FreeBSD$"); #include "fuse_ipc.h" #include "fuse_io.h" -#define FUSE_DEBUG_MODULE IO -#include "fuse_debug.h" - +SDT_PROVIDER_DECLARE(fuse); +/* + * Fuse trace probe: + * arg0: verbosity. Higher numbers give more verbose messages + * arg1: Textual message + */ +SDT_PROBE_DEFINE2(fuse, , io, trace, "int", "char*"); static int fuse_read_directbackend(struct vnode *vp, struct uio *uio, @@ -115,6 +119,8 @@ static int fuse_write_biobackend(struct vnode *vp, struct uio *uio, struct ucred *cred, struct fuse_filehandle *fufh, int ioflag); +SDT_PROBE_DEFINE5(fuse, , io, io_dispatch, "struct vnode*", "struct uio*", + "int", "struct ucred*", "struct fuse_filehandle*"); int fuse_io_dispatch(struct vnode *vp, struct uio *uio, int ioflag, struct ucred *cred) @@ -130,6 +136,8 @@ fuse_io_dispatch(struct vnode *vp, struct uio *uio, int ioflag, printf("FUSE: io dispatch: filehandles are closed\n"); return err; } + SDT_PROBE5(fuse, , io, io_dispatch, vp, uio, ioflag, cred, fufh); + /* * Ideally, when the daemon asks for direct io at open time, the * standard file flag should be set according to this, so that would @@ -145,12 +153,12 @@ fuse_io_dispatch(struct vnode *vp, struct uio *uio, int ioflag, switch (uio->uio_rw) { case UIO_READ: if (directio) { - FS_DEBUG("direct read of vnode %ju via file handle %ju\n", - (uintmax_t)VTOILLU(vp), (uintmax_t)fufh->fh_id); + SDT_PROBE2(fuse, , io, trace, 1, + "direct read of vnode"); err = fuse_read_directbackend(vp, uio, cred, fufh); } else { - FS_DEBUG("buffered read of vnode %ju\n", - (uintmax_t)VTOILLU(vp)); + SDT_PROBE2(fuse, , io, trace, 1, + "buffered read of vnode"); err = fuse_read_biobackend(vp, uio, cred, fufh); } break; @@ -162,12 +170,12 @@ fuse_io_dispatch(struct vnode *vp, struct uio *uio, int ioflag, * cached. */ if (directio || fuse_data_cache_mode == FUSE_CACHE_WT) { - FS_DEBUG("direct write of vnode %ju via file handle %ju\n", - (uintmax_t)VTOILLU(vp), (uintmax_t)fufh->fh_id); + SDT_PROBE2(fuse, , io, trace, 1, + "direct write of vnode"); err = fuse_write_directbackend(vp, uio, cred, fufh, ioflag); } else { - FS_DEBUG("buffered write of vnode %ju\n", - (uintmax_t)VTOILLU(vp)); + SDT_PROBE2(fuse, , io, trace, 1, + "buffered write of vnode"); err = fuse_write_biobackend(vp, uio, cred, fufh, ioflag); } break; @@ -178,6 +186,9 @@ fuse_io_dispatch(struct vnode *vp, struct uio *uio, int ioflag, return (err); } +SDT_PROBE_DEFINE3(fuse, , io, read_bio_backend_start, "int", "int", "int"); +SDT_PROBE_DEFINE2(fuse, , io, read_bio_backend_feed, "int", "int"); +SDT_PROBE_DEFINE3(fuse, , io, read_bio_backend_end, "int", "ssize_t", "int"); static int fuse_read_biobackend(struct vnode *vp, struct uio *uio, struct ucred *cred, struct fuse_filehandle *fufh) @@ -190,9 +201,6 @@ fuse_read_biobackend(struct vnode *vp, struct uio *uio, const int biosize = fuse_iosize(vp); - FS_DEBUG("resid=%zx offset=%jx fsize=%jx\n", - uio->uio_resid, uio->uio_offset, VTOFUD(vp)->filesize); - if (uio->uio_resid == 0) return (0); if (uio->uio_offset < 0) @@ -209,7 +217,8 @@ fuse_read_biobackend(struct vnode *vp, struct uio *uio, lbn = uio->uio_offset / biosize; on = uio->uio_offset & (biosize - 1); - FS_DEBUG2G("biosize %d, lbn %d, on %d\n", biosize, (int)lbn, on); + SDT_PROBE3(fuse, , io, read_bio_backend_start, + biosize, (int)lbn, on); /* * Obtain the buffer cache block. Figure out the buffer size @@ -258,19 +267,22 @@ fuse_read_biobackend(struct vnode *vp, struct uio *uio, if (on < bcount) n = MIN((unsigned)(bcount - on), uio->uio_resid); if (n > 0) { - FS_DEBUG2G("feeding buffeater with %d bytes of buffer %p," - " saying %d was asked for\n", - n, bp->b_data + on, n + (int)bp->b_resid); + SDT_PROBE2(fuse, , io, read_bio_backend_feed, + n, n + (int)bp->b_resid); err = uiomove(bp->b_data + on, n, uio); } brelse(bp); - FS_DEBUG2G("end of turn, err %d, uio->uio_resid %zd, n %d\n", - err, uio->uio_resid, n); + SDT_PROBE3(fuse, , io, read_bio_backend_end, err, + uio->uio_resid, n); } while (err == 0 && uio->uio_resid > 0 && n > 0); return (err); } +SDT_PROBE_DEFINE1(fuse, , io, read_directbackend_start, "struct fuse_read_in*"); +SDT_PROBE_DEFINE2(fuse, , io, read_directbackend_complete, + "struct fuse_dispatcher*", "struct uio*"); + static int fuse_read_directbackend(struct vnode *vp, struct uio *uio, struct ucred *cred, struct fuse_filehandle *fufh) @@ -301,17 +313,13 @@ fuse_read_directbackend(struct vnode *vp, struct uio *uio, fri->size = MIN(uio->uio_resid, fuse_get_mpdata(vp->v_mount)->max_read); - FS_DEBUG2G("fri->fh %ju, fri->offset %ju, fri->size %ju\n", - (uintmax_t)fri->fh, (uintmax_t)fri->offset, - (uintmax_t)fri->size); + SDT_PROBE1(fuse, , io, read_directbackend_start, fri); if ((err = fdisp_wait_answ(&fdi))) goto out; - FS_DEBUG2G("complete: got iosize=%d, requested fri.size=%zd; " - "resid=%zd offset=%ju\n", - fri->size, fdi.iosize, uio->uio_resid, - (uintmax_t)uio->uio_offset); + SDT_PROBE2(fuse, , io, read_directbackend_complete, + fdi.iosize, uio); if ((err = uiomove(fdi.answ, MIN(fri->size, fdi.iosize), uio))) break; @@ -380,6 +388,10 @@ fuse_write_directbackend(struct vnode *vp, struct uio *uio, return (err); } +SDT_PROBE_DEFINE6(fuse, , io, write_biobackend_start, "int64_t", "int", "int", + "struct uio*", "int", "bool"); +SDT_PROBE_DEFINE2(fuse, , io, write_biobackend_append_race, "long", "int"); + static int fuse_write_biobackend(struct vnode *vp, struct uio *uio, struct ucred *cred, struct fuse_filehandle *fufh, int ioflag) @@ -393,8 +405,6 @@ fuse_write_biobackend(struct vnode *vp, struct uio *uio, const int biosize = fuse_iosize(vp); KASSERT(uio->uio_rw == UIO_WRITE, ("ncl_write mode")); - FS_DEBUG("resid=%zx offset=%jx fsize=%jx\n", - uio->uio_resid, uio->uio_offset, fvdat->filesize); if (vp->v_type != VREG) return (EIO); if (uio->uio_offset < 0) @@ -421,10 +431,6 @@ fuse_write_biobackend(struct vnode *vp, struct uio *uio, on = uio->uio_offset & (biosize - 1); n = MIN((unsigned)(biosize - on), uio->uio_resid); - FS_DEBUG2G("lbn %ju, on %d, n %d, uio offset %ju, uio resid %zd\n", - (uintmax_t)lbn, on, n, - (uintmax_t)uio->uio_offset, uio->uio_resid); - again: /* * Handle direct append and file extension cases, calculate @@ -438,7 +444,8 @@ again: * readers from reading garbage. */ bcount = on; - FS_DEBUG("getting block from OS, bcount %d\n", bcount); + SDT_PROBE6(fuse, , io, write_biobackend_start, + lbn, on, n, uio, bcount, true); bp = getblk(vp, lbn, bcount, PCATCH, 0, 0); if (bp != NULL) { @@ -468,7 +475,8 @@ again: bcount = fvdat->filesize - (off_t)lbn *biosize; } - FS_DEBUG("getting block from OS, bcount %d\n", bcount); + SDT_PROBE6(fuse, , io, write_biobackend_start, + lbn, on, n, uio, bcount, false); bp = getblk(vp, lbn, bcount, PCATCH, 0, 0); if (bp && uio->uio_offset + n > fvdat->filesize) { err = fuse_vnode_setsize(vp, cred, @@ -530,7 +538,7 @@ again: */ if (bp->b_dirtyend > bcount) { - FS_DEBUG("FUSE append race @%lx:%d\n", + SDT_PROBE2(fuse, , io, write_biobackend_append_race, (long)bp->b_blkno * biosize, bp->b_dirtyend - bcount); bp->b_dirtyend = bcount; @@ -626,9 +634,6 @@ fuse_io_strategy(struct vnode *vp, struct buf *bp) MPASS(vp->v_type == VREG || vp->v_type == VDIR); MPASS(bp->b_iocmd == BIO_READ || bp->b_iocmd == BIO_WRITE); - FS_DEBUG("inode=%ju offset=%jd resid=%ld\n", - (uintmax_t)VTOI(vp), (intmax_t)(((off_t)bp->b_blkno) * biosize), - bp->b_bcount); error = fuse_filehandle_getrw(vp, (bp->b_iocmd == BIO_READ) ? FUFH_RDONLY : FUFH_WRONLY, &fufh); @@ -701,7 +706,8 @@ fuse_io_strategy(struct vnode *vp, struct buf *bp) * If we only need to commit, try to commit */ if (bp->b_flags & B_NEEDCOMMIT) { - FS_DEBUG("write: B_NEEDCOMMIT flags set\n"); + SDT_PROBE2(fuse, , io, trace, 1, + "write: B_NEEDCOMMIT flags set"); } /* * Setup for actual write diff --git a/sys/fs/fuse/fuse_ipc.c b/sys/fs/fuse/fuse_ipc.c index 54d24f1bc34..6718b18d479 100644 --- a/sys/fs/fuse/fuse_ipc.c +++ b/sys/fs/fuse/fuse_ipc.c @@ -58,22 +58,24 @@ #include __FBSDID("$FreeBSD$"); -#include +#include #include #include #include -#include #include #include #include #include +#include #include #include #include #include #include #include +#include #include +#include #include #include #include @@ -84,8 +86,13 @@ __FBSDID("$FreeBSD$"); #include "fuse_ipc.h" #include "fuse_internal.h" -#define FUSE_DEBUG_MODULE IPC -#include "fuse_debug.h" +SDT_PROVIDER_DECLARE(fuse); +/* + * Fuse trace probe: + * arg0: verbosity. Higher numbers give more verbose messages + * arg1: Textual message + */ +SDT_PROBE_DEFINE2(fuse, , ipc, trace, "int", "char*"); static struct fuse_ticket *fticket_alloc(struct fuse_data *data); static void fticket_refresh(struct fuse_ticket *ftick); @@ -146,8 +153,6 @@ fiov_init(struct fuse_iov *fiov, size_t size) { uint32_t msize = FU_AT_LEAST(size); - debug_printf("fiov=%p, size=%zd\n", fiov, size); - fiov->len = 0; fiov->base = malloc(msize, M_FUSEMSG, M_WAITOK | M_ZERO); @@ -159,8 +164,6 @@ fiov_init(struct fuse_iov *fiov, size_t size) void fiov_teardown(struct fuse_iov *fiov) { - debug_printf("fiov=%p\n", fiov); - MPASS(fiov->base != NULL); free(fiov->base, M_FUSEMSG); } @@ -168,8 +171,6 @@ fiov_teardown(struct fuse_iov *fiov) void fiov_adjust(struct fuse_iov *fiov, size_t size) { - debug_printf("fiov=%p, size=%zd\n", fiov, size); - if (fiov->allocated_size < size || (fuse_iov_permanent_bufsize >= 0 && fiov->allocated_size - size > fuse_iov_permanent_bufsize && @@ -189,8 +190,6 @@ fiov_adjust(struct fuse_iov *fiov, size_t size) void fiov_refresh(struct fuse_iov *fiov) { - debug_printf("fiov=%p\n", fiov); - bzero(fiov->base, fiov->len); fiov_adjust(fiov, 0); } @@ -201,8 +200,6 @@ fticket_ctor(void *mem, int size, void *arg, int flags) struct fuse_ticket *ftick = mem; struct fuse_data *data = arg; - debug_printf("ftick=%p data=%p\n", ftick, data); - FUSE_ASSERT_MS_DONE(ftick); FUSE_ASSERT_AW_DONE(ftick); @@ -227,8 +224,6 @@ fticket_dtor(void *mem, int size, void *arg) { struct fuse_ticket *ftick = mem; - debug_printf("ftick=%p\n", ftick); - FUSE_ASSERT_MS_DONE(ftick); FUSE_ASSERT_AW_DONE(ftick); @@ -240,8 +235,6 @@ fticket_init(void *mem, int size, int flags) { struct fuse_ticket *ftick = mem; - FS_DEBUG("ftick=%p\n", ftick); - bzero(ftick, sizeof(struct fuse_ticket)); fiov_init(&ftick->tk_ms_fiov, sizeof(struct fuse_in_header)); @@ -259,8 +252,6 @@ fticket_fini(void *mem, int size) { struct fuse_ticket *ftick = mem; - FS_DEBUG("ftick=%p\n", ftick); - fiov_teardown(&ftick->tk_ms_fiov); fiov_teardown(&ftick->tk_aw_fiov); mtx_destroy(&ftick->tk_aw_mtx); @@ -282,8 +273,6 @@ static inline void fticket_refresh(struct fuse_ticket *ftick) { - debug_printf("ftick=%p\n", ftick); - FUSE_ASSERT_MS_DONE(ftick); FUSE_ASSERT_AW_DONE(ftick); @@ -310,9 +299,6 @@ fticket_wait_answer(struct fuse_ticket *ftick) int err = 0; struct fuse_data *data; - debug_printf("ftick=%p\n", ftick); - fuse_lck_mtx_lock(ftick->tk_aw_mtx); - if (fticket_answered(ftick)) { goto out; } @@ -338,7 +324,8 @@ fticket_wait_answer(struct fuse_ticket *ftick) } out: if (!(err || fticket_answered(ftick))) { - debug_printf("FUSE: requester was woken up but still no answer"); + SDT_PROBE2(fuse, , ipc, trace, 1, + "FUSE: requester was woken up but still no answer"); err = ENXIO; } fuse_lck_mtx_unlock(ftick->tk_aw_mtx); @@ -353,29 +340,16 @@ fticket_aw_pull_uio(struct fuse_ticket *ftick, struct uio *uio) int err = 0; size_t len = uio_resid(uio); - debug_printf("ftick=%p, uio=%p\n", ftick, uio); - if (len) { switch (ftick->tk_aw_type) { case FT_A_FIOV: fiov_adjust(fticket_resp(ftick), len); err = uiomove(fticket_resp(ftick)->base, len, uio); - if (err) { - debug_printf("FUSE: FT_A_FIOV: error is %d" - " (%p, %zd, %p)\n", - err, fticket_resp(ftick)->base, - len, uio); - } break; case FT_A_BUF: ftick->tk_aw_bufsize = len; err = uiomove(ftick->tk_aw_bufdata, len, uio); - if (err) { - debug_printf("FUSE: FT_A_BUF: error is %d" - " (%p, %zd, %p)\n", - err, ftick->tk_aw_bufdata, len, uio); - } break; default: @@ -390,8 +364,6 @@ fticket_pull(struct fuse_ticket *ftick, struct uio *uio) { int err = 0; - debug_printf("ftick=%p, uio=%p\n", ftick, uio); - if (ftick->tk_aw_ohead.error) { return 0; } @@ -407,8 +379,6 @@ fdata_alloc(struct cdev *fdev, struct ucred *cred) { struct fuse_data *data; - debug_printf("fdev=%p\n", fdev); - data = malloc(sizeof(struct fuse_data), M_FUSEMSG, M_WAITOK | M_ZERO); data->fdev = fdev; @@ -427,10 +397,6 @@ fdata_alloc(struct cdev *fdev, struct ucred *cred) void fdata_trydestroy(struct fuse_data *data) { - FS_DEBUG("data=%p data.mp=%p data.fdev=%p data.flags=%04x\n", - data, data->mp, data->fdev, data->dataflags); - - FS_DEBUG("destroy: data=%p\n", data); data->ref--; MPASS(data->ref >= 0); if (data->ref != 0) @@ -449,8 +415,6 @@ fdata_trydestroy(struct fuse_data *data) void fdata_set_dead(struct fuse_data *data) { - debug_printf("data=%p\n", data); - FUSE_LOCK(); if (fdata_get_dead(data)) { FUSE_UNLOCK(); @@ -471,8 +435,6 @@ fuse_ticket_fetch(struct fuse_data *data) int err = 0; struct fuse_ticket *ftick; - debug_printf("data=%p\n", data); - ftick = fticket_alloc(data); if (!(data->dataflags & FSESS_INITED)) { @@ -495,7 +457,6 @@ fuse_ticket_drop(struct fuse_ticket *ftick) int die; die = refcount_release(&ftick->tk_refcount); - debug_printf("ftick=%p refcount=%d\n", ftick, ftick->tk_refcount); if (die) fticket_destroy(ftick); @@ -505,9 +466,6 @@ fuse_ticket_drop(struct fuse_ticket *ftick) void fuse_insert_callback(struct fuse_ticket *ftick, fuse_handler_t * handler) { - debug_printf("ftick=%p, handler=%p data=%p\n", ftick, ftick->tk_data, - handler); - if (fdata_get_dead(ftick->tk_data)) { return; } @@ -521,8 +479,6 @@ fuse_insert_callback(struct fuse_ticket *ftick, fuse_handler_t * handler) void fuse_insert_message(struct fuse_ticket *ftick) { - debug_printf("ftick=%p\n", ftick); - if (ftick->tk_flag & FT_DIRTY) { panic("FUSE: ticket reused without being refreshed"); } @@ -544,8 +500,6 @@ fuse_body_audit(struct fuse_ticket *ftick, size_t blen) int err = 0; enum fuse_opcode opcode; - debug_printf("ftick=%p, blen = %zu\n", ftick, blen); - opcode = fticket_opcode(ftick); switch (opcode) { @@ -719,9 +673,6 @@ fuse_setup_ihead(struct fuse_in_header *ihead, struct fuse_ticket *ftick, ihead->nodeid = nid; ihead->opcode = op; - debug_printf("ihead=%p, ftick=%p, nid=%ju, op=%d, blen=%zu\n", - ihead, ftick, (uintmax_t)nid, op, blen); - ihead->pid = pid; ihead->uid = cred->cr_uid; ihead->gid = cred->cr_rgid; @@ -738,8 +689,6 @@ fuse_standard_handler(struct fuse_ticket *ftick, struct uio *uio) { int err = 0; - debug_printf("ftick=%p, uio=%p\n", ftick, uio); - err = fticket_pull(ftick, uio); fuse_lck_mtx_lock(ftick->tk_aw_mtx); @@ -760,9 +709,6 @@ fdisp_make_pid(struct fuse_dispatcher *fdip, enum fuse_opcode op, { struct fuse_data *data = fuse_get_mpdata(mp); - debug_printf("fdip=%p, op=%d, mp=%p, nid=%ju\n", - fdip, op, mp, (uintmax_t)nid); - if (fdip->tick) { fticket_refresh(fdip->tick); } else { @@ -788,12 +734,13 @@ void fdisp_make_vp(struct fuse_dispatcher *fdip, enum fuse_opcode op, struct vnode *vp, struct thread *td, struct ucred *cred) { - debug_printf("fdip=%p, op=%d, vp=%p\n", fdip, op, vp); RECTIFY_TDCR(td, cred); return fdisp_make_pid(fdip, op, vnode_mount(vp), VTOI(vp), td->td_proc->p_pid, cred); } +SDT_PROBE_DEFINE2(fuse, , ipc, fdisp_wait_answ_error, "char*", "int"); + int fdisp_wait_answ(struct fuse_dispatcher *fdip) { @@ -804,8 +751,6 @@ fdisp_wait_answ(struct fuse_dispatcher *fdip) fuse_insert_message(fdip->tick); if ((err = fticket_wait_answer(fdip->tick))) { - debug_printf("IPC: interrupted, err = %d\n", err); - fuse_lck_mtx_lock(fdip->tick->tk_aw_mtx); if (fticket_answered(fdip->tick)) { @@ -814,7 +759,8 @@ fdisp_wait_answ(struct fuse_dispatcher *fdip) * the standard handler has completed his job. * So we drop the ticket and exit as usual. */ - debug_printf("IPC: already answered\n"); + SDT_PROBE2(fuse, , ipc, fdisp_wait_answ_error, + "IPC: interrupted, already answered", err); fuse_lck_mtx_unlock(fdip->tick->tk_aw_mtx); goto out; } else { @@ -823,23 +769,23 @@ fdisp_wait_answ(struct fuse_dispatcher *fdip) * Then by setting the answered flag we get *him* * to drop the ticket. */ - debug_printf("IPC: setting to answered\n"); + SDT_PROBE2(fuse, , ipc, fdisp_wait_answ_error, + "IPC: interrupted, setting to answered", err); fticket_set_answered(fdip->tick); fuse_lck_mtx_unlock(fdip->tick->tk_aw_mtx); return err; } } - debug_printf("IPC: not interrupted, err = %d\n", err); if (fdip->tick->tk_aw_errno) { - debug_printf("IPC: explicit EIO-ing, tk_aw_errno = %d\n", - fdip->tick->tk_aw_errno); + SDT_PROBE2(fuse, , ipc, fdisp_wait_answ_error, + "IPC: explicit EIO-ing", fdip->tick->tk_aw_errno); err = EIO; goto out; } if ((err = fdip->tick->tk_aw_ohead.error)) { - debug_printf("IPC: setting status to %d\n", - fdip->tick->tk_aw_ohead.error); + SDT_PROBE2(fuse, , ipc, fdisp_wait_answ_error, + "IPC: setting status", fdip->tick->tk_aw_ohead.error); /* * This means a "proper" fuse syscall error. * We record this value so the caller will @@ -855,13 +801,9 @@ fdisp_wait_answ(struct fuse_dispatcher *fdip) fdip->answ = fticket_resp(fdip->tick)->base; fdip->iosize = fticket_resp(fdip->tick)->len; - debug_printf("IPC: all is well\n"); - return 0; out: - debug_printf("IPC: dropping ticket, err = %d\n", err); - return err; } diff --git a/sys/fs/fuse/fuse_main.c b/sys/fs/fuse/fuse_main.c index c43cf2bb9b1..459168a0f60 100644 --- a/sys/fs/fuse/fuse_main.c +++ b/sys/fs/fuse/fuse_main.c @@ -67,11 +67,13 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include #include #include +#include #include #include "fuse.h" @@ -98,6 +100,7 @@ SYSCTL_INT(_vfs_fuse, OID_AUTO, kernelabi_major, CTLFLAG_RD, SYSCTL_NULL_INT_PTR, FUSE_KERNEL_VERSION, "FUSE kernel abi major version"); SYSCTL_INT(_vfs_fuse, OID_AUTO, kernelabi_minor, CTLFLAG_RD, SYSCTL_NULL_INT_PTR, FUSE_KERNEL_MINOR_VERSION, "FUSE kernel abi minor version"); +SDT_PROVIDER_DEFINE(fuse); /****************************** * diff --git a/sys/fs/fuse/fuse_node.c b/sys/fs/fuse/fuse_node.c index caf8119d5df..5dfba665e9c 100644 --- a/sys/fs/fuse/fuse_node.c +++ b/sys/fs/fuse/fuse_node.c @@ -89,8 +89,13 @@ __FBSDID("$FreeBSD$"); #include "fuse_io.h" #include "fuse_ipc.h" -#define FUSE_DEBUG_MODULE VNOPS -#include "fuse_debug.h" +SDT_PROVIDER_DECLARE(fuse); +/* + * Fuse trace probe: + * arg0: verbosity. Higher numbers give more verbose messages + * arg1: Textual message + */ +SDT_PROBE_DEFINE2(fuse, , node, trace, "int", "char*"); MALLOC_DEFINE(M_FUSEVN, "fuse_vnode", "fuse vnode private data"); @@ -219,8 +224,6 @@ fuse_vnode_alloc(struct mount *mp, struct vnode *vp2; int err = 0; - FS_DEBUG("been asked for vno #%ju\n", (uintmax_t)nodeid); - if (vtyp == VNON) { return EINVAL; } @@ -232,7 +235,7 @@ fuse_vnode_alloc(struct mount *mp, if (*vpp) { MPASS((*vpp)->v_type == vtyp && (*vpp)->v_data != NULL); - FS_DEBUG("vnode taken from hash\n"); + SDT_PROBE2(fuse, , node, trace, 1, "vnode taken from hash"); return (0); } fvdat = malloc(sizeof(*fvdat), M_FUSEVN, M_WAITOK | M_ZERO); @@ -276,8 +279,6 @@ fuse_vnode_get(struct mount *mp, struct thread *td = (cnp != NULL ? cnp->cn_thread : curthread); int err = 0; - debug_printf("dvp=%p\n", dvp); - err = fuse_vnode_alloc(mp, td, nodeid, vtyp, vpp); if (err) { return err; @@ -354,8 +355,6 @@ fuse_vnode_savesize(struct vnode *vp, struct ucred *cred) struct fuse_setattr_in *fsai; int err = 0; - FS_DEBUG("inode=%ju size=%ju\n", (uintmax_t)VTOI(vp), - (uintmax_t)fvdat->filesize); ASSERT_VOP_ELOCKED(vp, "fuse_io_extend"); if (fuse_isdeadfs(vp)) { @@ -405,7 +404,7 @@ fuse_vnode_refreshsize(struct vnode *vp, struct ucred *cred) return; VOP_GETATTR(vp, &va, cred); - FS_DEBUG("refreshed file size: %jd\n", (intmax_t)VTOFUD(vp)->filesize); + SDT_PROBE2(fuse, , node, trace, 1, "refreshed file size"); } int @@ -415,9 +414,6 @@ fuse_vnode_setsize(struct vnode *vp, struct ucred *cred, off_t newsize) off_t oldsize; int err = 0; - FS_DEBUG("inode=%ju oldsize=%ju newsize=%ju\n", - (uintmax_t)VTOI(vp), (uintmax_t)fvdat->filesize, - (uintmax_t)newsize); ASSERT_VOP_ELOCKED(vp, "fuse_vnode_setsize"); oldsize = fvdat->filesize; diff --git a/sys/fs/fuse/fuse_vfsops.c b/sys/fs/fuse/fuse_vfsops.c index 8ae82cca3c6..92d1ae5e628 100644 --- a/sys/fs/fuse/fuse_vfsops.c +++ b/sys/fs/fuse/fuse_vfsops.c @@ -58,11 +58,10 @@ #include __FBSDID("$FreeBSD$"); -#include +#include #include #include #include -#include #include #include #include @@ -89,8 +88,13 @@ __FBSDID("$FreeBSD$"); #include #include -#define FUSE_DEBUG_MODULE VFSOPS -#include "fuse_debug.h" +SDT_PROVIDER_DECLARE(fuse); +/* + * Fuse trace probe: + * arg0: verbosity. Higher numbers give more verbose messages + * arg1: Textual message + */ +SDT_PROBE_DEFINE2(fuse, , vfsops, trace, "int", "char*"); /* This will do for privilege types for now */ #ifndef PRIV_VFS_FUSE_ALLOWOTHER @@ -203,6 +207,10 @@ fuse_getdevice(const char *fspec, struct thread *td, struct cdev **fdevp) vfs_flagopt(opts, "__" #fnam, &__mntopts, fval); \ } while (0) +SDT_PROBE_DEFINE1(fuse, , vfsops, mntopts, "uint64_t"); +SDT_PROBE_DEFINE4(fuse, , vfsops, mount_err, "char*", "struct fuse_data*", + "struct mount*", "int"); + static int fuse_vfsop_mount(struct mount *mp) { @@ -229,8 +237,6 @@ fuse_vfsop_mount(struct mount *mp) __mntopts = 0; td = curthread; - fuse_trace_printf_vfsop(); - if (mp->mnt_flag & MNT_UPDATE) return EOPNOTSUPP; @@ -286,11 +292,12 @@ fuse_vfsop_mount(struct mount *mp) } subtype = vfs_getopts(opts, "subtype=", &err); - FS_DEBUG2G("mntopts 0x%jx\n", (uintmax_t)mntopts); + SDT_PROBE1(fuse, , vfsops, mntopts, mntopts); err = fget(td, fd, &cap_read_rights, &fp); if (err != 0) { - FS_DEBUG("invalid or not opened device: data=%p\n", data); + SDT_PROBE2(fuse, , vfsops, trace, 1, + "invalid or not opened device"); goto out; } fptmp = td->td_fpop; @@ -300,15 +307,16 @@ fuse_vfsop_mount(struct mount *mp) fdrop(fp, td); FUSE_LOCK(); if (err != 0 || data == NULL || data->mp != NULL) { - FS_DEBUG("invalid or not opened device: data=%p data.mp=%p\n", - data, data != NULL ? data->mp : NULL); err = ENXIO; + SDT_PROBE4(fuse, , vfsops, mount_err, + "invalid or not opened device", data, mp, err); FUSE_UNLOCK(); goto out; } if (fdata_get_dead(data)) { - FS_DEBUG("device is dead during mount: data=%p\n", data); err = ENOTCONN; + SDT_PROBE4(fuse, , vfsops, mount_err, + "device is dead during mount", data, mp, err); FUSE_UNLOCK(); goto out; } @@ -345,7 +353,6 @@ fuse_vfsop_mount(struct mount *mp) } copystr(fspec, mp->mnt_stat.f_mntfromname, MNAMELEN - 1, &len); bzero(mp->mnt_stat.f_mntfromname + len, MNAMELEN - len); - FS_DEBUG2G("mp %p: %s\n", mp, mp->mnt_stat.f_mntfromname); /* Now handshaking with daemon */ fuse_internal_send_init(data, td); @@ -358,9 +365,8 @@ out: * Destroy device only if we acquired reference to * it */ - FS_DEBUG("mount failed, destroy device: data=%p mp=%p" - " err=%d\n", - data, mp, err); + SDT_PROBE4(fuse, , vfsops, mount_err, + "mount failed, destroy device", data, mp, err); data->mp = NULL; fdata_trydestroy(data); } @@ -381,8 +387,6 @@ fuse_vfsop_unmount(struct mount *mp, int mntflags) struct fuse_dispatcher fdi; struct thread *td = curthread; - fuse_trace_printf_vfsop(); - if (mntflags & MNT_FORCE) { flags |= FORCECLOSE; } @@ -402,7 +406,6 @@ fuse_vfsop_unmount(struct mount *mp, int mntflags) FUSE_UNLOCK(); err = vflush(mp, 0, flags, td); if (err) { - debug_printf("vflush failed"); return err; } if (fdata_get_dead(data)) { @@ -450,12 +453,14 @@ fuse_vfsop_root(struct mount *mp, int lkflags, struct vnode **vpp) FUSE_LOCK(); MPASS(data->vroot == NULL || data->vroot == *vpp); if (data->vroot == NULL) { - FS_DEBUG("new root vnode\n"); + SDT_PROBE2(fuse, , vfsops, trace, 1, + "new root vnode"); data->vroot = *vpp; FUSE_UNLOCK(); vref(*vpp); } else if (data->vroot != *vpp) { - FS_DEBUG("root vnode race\n"); + SDT_PROBE2(fuse, , vfsops, trace, 1, + "root vnode race"); FUSE_UNLOCK(); VOP_UNLOCK(*vpp, 0); vrele(*vpp); @@ -477,7 +482,6 @@ fuse_vfsop_statfs(struct mount *mp, struct statfs *sbp) struct fuse_statfs_out *fsfo; struct fuse_data *data; - FS_DEBUG2G("mp %p: %s\n", mp, mp->mnt_stat.f_mntfromname); data = fuse_get_mpdata(mp); if (!(data->dataflags & FSESS_INITED)) @@ -508,15 +512,6 @@ fuse_vfsop_statfs(struct mount *mp, struct statfs *sbp) sbp->f_namemax = fsfo->st.namelen; sbp->f_bsize = fsfo->st.frsize; /* cast from uint32_t to uint64_t */ - FS_DEBUG("fuse_statfs_out -- blocks: %llu, bfree: %llu, bavail: %llu, " - "fil es: %llu, ffree: %llu, bsize: %i, namelen: %i\n", - (unsigned long long)fsfo->st.blocks, - (unsigned long long)fsfo->st.bfree, - (unsigned long long)fsfo->st.bavail, - (unsigned long long)fsfo->st.files, - (unsigned long long)fsfo->st.ffree, fsfo->st.bsize, - fsfo->st.namelen); - fdisp_destroy(&fdi); return 0; diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c index d878a9f9982..00f2b0d6882 100644 --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -58,11 +58,10 @@ #include __FBSDID("$FreeBSD$"); -#include +#include #include #include #include -#include #include #include #include @@ -108,8 +107,13 @@ __FBSDID("$FreeBSD$"); #include -#define FUSE_DEBUG_MODULE VNOPS -#include "fuse_debug.h" +SDT_PROVIDER_DECLARE(fuse); +/* + * Fuse trace probe: + * arg0: verbosity. Higher numbers give more verbose messages + * arg1: Textual message + */ +SDT_PROBE_DEFINE2(fuse, , vnops, trace, "int", "char*"); /* vnode ops */ static vop_access_t fuse_vnop_access; @@ -232,8 +236,6 @@ fuse_vnop_access(struct vop_access_args *ap) int err; - FS_DEBUG2G("inode=%ju\n", (uintmax_t)VTOI(vp)); - if (fuse_isdeadfs(vp)) { if (vnode_isvroot(vp)) { return 0; @@ -255,7 +257,6 @@ fuse_vnop_access(struct vop_access_args *ap) bzero(&facp, sizeof(facp)); err = fuse_internal_access(vp, accmode, &facp, ap->a_td, ap->a_cred); - FS_DEBUG2G("err=%d accmode=0x%x\n", err, accmode); return err; } @@ -275,8 +276,6 @@ fuse_vnop_close(struct vop_close_args *ap) int fflag = ap->a_fflag; fufh_type_t fufh_type; - fuse_trace_printf_vnop(); - if (fuse_isdeadfs(vp)) { return 0; } @@ -339,8 +338,6 @@ fuse_vnop_create(struct vop_create_args *ap) uint64_t x_fh_id; uint32_t x_open_flags; - fuse_trace_printf_vnop(); - if (fuse_isdeadfs(dvp)) { return ENXIO; } @@ -352,12 +349,11 @@ fuse_vnop_create(struct vop_create_args *ap) vap->va_type); return (EINVAL); } - debug_printf("parent nid = %ju, mode = %x\n", (uintmax_t)parentnid, - mode); fdisp_init(fdip, sizeof(*foi) + cnp->cn_namelen + 1); if (!fsess_isimpl(mp, FUSE_CREATE)) { - debug_printf("eh, daemon doesn't implement create?\n"); + SDT_PROBE2(fuse, , vnops, trace, 1, + "eh, daemon doesn't implement create?"); return (EINVAL); } fdisp_make(fdip, FUSE_CREATE, vnode_mount(dvp), parentnid, td, cred); @@ -375,7 +371,6 @@ fuse_vnop_create(struct vop_create_args *ap) if (err) { if (err == ENOSYS) fsess_set_notimpl(mp, FUSE_CREATE); - debug_printf("create: got err=%d from daemon\n", err); goto out; } @@ -446,8 +441,6 @@ fuse_vnop_fsync(struct vop_fsync_args *ap) int type, err = 0; - fuse_trace_printf_vnop(); - if (fuse_isdeadfs(vp)) { return 0; } @@ -490,8 +483,6 @@ fuse_vnop_getattr(struct vop_getattr_args *ap) int dataflags; struct fuse_dispatcher fdi; - FS_DEBUG2G("inode=%ju\n", (uintmax_t)VTOI(vp)); - dataflags = fuse_get_mpdata(vnode_mount(vp))->dataflags; /* Note that we are not bailing out on a dead file system just yet. */ @@ -500,7 +491,6 @@ fuse_vnop_getattr(struct vop_getattr_args *ap) if (!vnode_isvroot(vp)) { fdata_set_dead(fuse_get_mpdata(vnode_mount(vp))); err = ENOTCONN; - debug_printf("fuse_getattr b: returning ENOTCONN\n"); return err; } else { goto fake; @@ -509,7 +499,7 @@ fuse_vnop_getattr(struct vop_getattr_args *ap) fdisp_init(&fdi, 0); if ((err = fdisp_simple_putget_vp(&fdi, FUSE_GETATTR, vp, td, cred))) { if ((err == ENOTCONN) && vnode_isvroot(vp)) { - /* see comment at similar place in fuse_statfs() */ + /* see comment in fuse_vfsop_statfs() */ fdisp_destroy(&fdi); goto fake; } @@ -541,7 +531,6 @@ fuse_vnop_getattr(struct vop_getattr_args *ap) fvdat->flag &= ~FN_SIZECHANGE; } } - debug_printf("fuse_getattr e: returning 0\n"); out: fdisp_destroy(&fdi); @@ -571,8 +560,6 @@ fuse_vnop_inactive(struct vop_inactive_args *ap) int type, need_flush = 1; - FS_DEBUG("inode=%ju\n", (uintmax_t)VTOI(vp)); - for (type = 0; type < FUFH_MAXTYPE; type++) { fufh = &(fvdat->fufh[type]); if (FUFH_IS_VALID(fufh)) { @@ -619,8 +606,6 @@ fuse_vnop_link(struct vop_link_args *ap) int err; - fuse_trace_printf_vnop(); - if (fuse_isdeadfs(vp)) { return ENXIO; } @@ -685,9 +670,6 @@ fuse_vnop_lookup(struct vop_lookup_args *ap) uint64_t nid; struct fuse_access_param facp; - FS_DEBUG2G("parent_inode=%ju - %*s\n", - (uintmax_t)VTOI(dvp), (int)cnp->cn_namelen, cnp->cn_nameptr); - if (fuse_isdeadfs(dvp)) { *vpp = NULL; return ENXIO; @@ -815,7 +797,8 @@ calldaemon: */ #if 0 if ((cnp->cn_flags & MAKEENTRY) != 0) { - FS_DEBUG("inserting NULL into cache\n"); + SDT_PROBE2(fuse, , vnops, trace, 1, + "inserting NULL into cache"); cache_enter(dvp, NULL, cnp); } #endif @@ -1088,7 +1071,9 @@ out: } if (err) { if (tmpvtype == VLNK) - FS_DEBUG("weird, permission error with a symlink?\n"); + SDT_PROBE2(fuse, , vnops, trace, + 1, "weird, permission " + "error with a symlink?"); vput(*vpp); *vpp = NULL; } @@ -1119,8 +1104,6 @@ fuse_vnop_mkdir(struct vop_mkdir_args *ap) struct fuse_mkdir_in fmdi; - fuse_trace_printf_vnop(); - if (fuse_isdeadfs(dvp)) { return ENXIO; } @@ -1169,8 +1152,6 @@ fuse_vnop_open(struct vop_open_args *ap) int error, isdir = 0; int32_t fuse_open_flags; - FS_DEBUG2G("inode=%ju mode=0x%x\n", (uintmax_t)VTOI(vp), mode); - if (fuse_isdeadfs(vp)) { return ENXIO; } @@ -1245,9 +1226,6 @@ fuse_vnop_read(struct vop_read_args *ap) int ioflag = ap->a_ioflag; struct ucred *cred = ap->a_cred; - FS_DEBUG2G("inode=%ju offset=%jd resid=%zd\n", - (uintmax_t)VTOI(vp), uio->uio_offset, uio->uio_resid); - if (fuse_isdeadfs(vp)) { return ENXIO; } @@ -1282,8 +1260,6 @@ fuse_vnop_readdir(struct vop_readdir_args *ap) int err = 0; int freefufh = 0; - FS_DEBUG2G("inode=%ju\n", (uintmax_t)VTOI(vp)); - if (fuse_isdeadfs(vp)) { return ENXIO; } @@ -1293,7 +1269,8 @@ fuse_vnop_readdir(struct vop_readdir_args *ap) } if (!fuse_filehandle_valid(vp, FUFH_RDONLY)) { - FS_DEBUG("calling readdir() before open()"); + SDT_PROBE2(fuse, , vnops, trace, 1, + "calling readdir() before open()"); err = fuse_filehandle_open(vp, FUFH_RDONLY, &fufh, NULL, cred); freefufh = 1; } else { @@ -1331,8 +1308,6 @@ fuse_vnop_readlink(struct vop_readlink_args *ap) struct fuse_dispatcher fdi; int err; - FS_DEBUG2G("inode=%ju\n", (uintmax_t)VTOI(vp)); - if (fuse_isdeadfs(vp)) { return ENXIO; } @@ -1378,8 +1353,6 @@ fuse_vnop_reclaim(struct vop_reclaim_args *ap) if (!fvdat) { panic("FUSE: no vnode data during recycling"); } - FS_DEBUG("inode=%ju\n", (uintmax_t)VTOI(vp)); - for (type = 0; type < FUFH_MAXTYPE; type++) { fufh = &(fvdat->fufh[type]); if (FUFH_IS_VALID(fufh)) { @@ -1418,9 +1391,6 @@ fuse_vnop_remove(struct vop_remove_args *ap) int err; - FS_DEBUG2G("inode=%ju name=%*s\n", - (uintmax_t)VTOI(vp), (int)cnp->cn_namelen, cnp->cn_nameptr); - if (fuse_isdeadfs(vp)) { return ENXIO; } @@ -1459,18 +1429,12 @@ fuse_vnop_rename(struct vop_rename_args *ap) int err = 0; - FS_DEBUG2G("from: inode=%ju name=%*s -> to: inode=%ju name=%*s\n", - (uintmax_t)VTOI(fvp), (int)fcnp->cn_namelen, fcnp->cn_nameptr, - (uintmax_t)(tvp == NULL ? -1 : VTOI(tvp)), - (int)tcnp->cn_namelen, tcnp->cn_nameptr); - if (fuse_isdeadfs(fdvp)) { return ENXIO; } if (fvp->v_mount != tdvp->v_mount || (tvp && fvp->v_mount != tvp->v_mount)) { - FS_DEBUG("cross-device rename: %s -> %s\n", - fcnp->cn_nameptr, (tcnp != NULL ? tcnp->cn_nameptr : "(NULL)")); + SDT_PROBE2(fuse, , vnops, trace, 1, "cross-device rename"); err = EXDEV; goto out; } @@ -1531,8 +1495,6 @@ fuse_vnop_rmdir(struct vop_rmdir_args *ap) int err; - FS_DEBUG2G("inode=%ju\n", (uintmax_t)VTOI(vp)); - if (fuse_isdeadfs(vp)) { return ENXIO; } @@ -1571,8 +1533,6 @@ fuse_vnop_setattr(struct vop_setattr_args *ap) int sizechanged = 0; uint64_t newsize = 0; - FS_DEBUG2G("inode=%ju\n", (uintmax_t)VTOI(vp)); - if (fuse_isdeadfs(vp)) { return ENXIO; } @@ -1658,14 +1618,16 @@ fuse_vnop_setattr(struct vop_setattr_args *ap) if (vnode_vtype(vp) != vtyp) { if (vnode_vtype(vp) == VNON && vtyp != VNON) { - debug_printf("FUSE: Dang! vnode_vtype is VNON and vtype isn't.\n"); + SDT_PROBE2(fuse, , vnops, trace, 1, "FUSE: Dang! " + "vnode_vtype is VNON and vtype isn't."); } else { /* * STALE vnode, ditch * - * The vnode has changed its type "behind our back". There's - * nothing really we can do, so let us just force an internal - * revocation and tell the caller to try again, if interested. + * The vnode has changed its type "behind our back". + * There's nothing really we can do, so let us just + * force an internal revocation and tell the caller to + * try again, if interested. */ fuse_internal_vnode_disappear(vp); err = EAGAIN; @@ -1695,8 +1657,6 @@ fuse_vnop_strategy(struct vop_strategy_args *ap) struct vnode *vp = ap->a_vp; struct buf *bp = ap->a_bp; - fuse_trace_printf_vnop(); - if (!vp || fuse_isdeadfs(vp)) { bp->b_ioflags |= BIO_ERROR; bp->b_error = ENXIO; @@ -1743,9 +1703,6 @@ fuse_vnop_symlink(struct vop_symlink_args *ap) int err; size_t len; - FS_DEBUG2G("inode=%ju name=%*s\n", - (uintmax_t)VTOI(dvp), (int)cnp->cn_namelen, cnp->cn_nameptr); - if (fuse_isdeadfs(dvp)) { return ENXIO; } @@ -1786,8 +1743,6 @@ fuse_vnop_write(struct vop_write_args *ap) int ioflag = ap->a_ioflag; struct ucred *cred = ap->a_cred; - fuse_trace_printf_vnop(); - if (fuse_isdeadfs(vp)) { return ENXIO; } @@ -1800,6 +1755,7 @@ fuse_vnop_write(struct vop_write_args *ap) return fuse_io_dispatch(vp, uio, ioflag, cred); } +SDT_PROBE_DEFINE1(fuse, , vnops, vnop_getpages_error, "int"); /* struct vnop_getpages_args { struct vnode *a_vp; @@ -1821,8 +1777,6 @@ fuse_vnop_getpages(struct vop_getpages_args *ap) struct ucred *cred; vm_page_t *pages; - FS_DEBUG2G("heh\n"); - vp = ap->a_vp; KASSERT(vp->v_object, ("objectless vp passed to getpages")); td = curthread; /* XXX */ @@ -1831,7 +1785,8 @@ fuse_vnop_getpages(struct vop_getpages_args *ap) npages = ap->a_count; if (!fsess_opt_mmap(vnode_mount(vp))) { - FS_DEBUG("called on non-cacheable vnode??\n"); + SDT_PROBE2(fuse, , vnops, trace, 1, + "called on non-cacheable vnode??\n"); return (VM_PAGER_ERROR); } @@ -1876,7 +1831,7 @@ fuse_vnop_getpages(struct vop_getpages_args *ap) uma_zfree(fuse_pbuf_zone, bp); if (error && (uio.uio_resid == count)) { - FS_DEBUG("error %d\n", error); + SDT_PROBE1(fuse, , vnops, vnop_getpages_error, error); return VM_PAGER_ERROR; } /* @@ -1954,8 +1909,6 @@ fuse_vnop_putpages(struct vop_putpages_args *ap) vm_page_t *pages; vm_ooffset_t fsize; - FS_DEBUG2G("heh\n"); - vp = ap->a_vp; KASSERT(vp->v_object, ("objectless vp passed to putpages")); fsize = vp->v_object->un_pager.vnp.vnp_size; @@ -1968,7 +1921,8 @@ fuse_vnop_putpages(struct vop_putpages_args *ap) offset = IDX_TO_OFF(pages[0]->pindex); if (!fsess_opt_mmap(vnode_mount(vp))) { - FS_DEBUG("called on non-cacheable vnode??\n"); + SDT_PROBE2(fuse, , vnops, trace, 1, + "called on non-cacheable vnode??\n"); } for (i = 0; i < npages; i++) rtvals[i] = VM_PAGER_AGAIN; @@ -2051,8 +2005,6 @@ fuse_vnop_getextattr(struct vop_getextattr_args *ap) size_t len; int err; - fuse_trace_printf_vnop(); - if (fuse_isdeadfs(vp)) return (ENXIO); @@ -2088,7 +2040,6 @@ fuse_vnop_getextattr(struct vop_getextattr_args *ap) if (err != 0) { if (err == ENOSYS) fsess_set_notimpl(mp, FUSE_GETXATTR); - debug_printf("getxattr: got err=%d from daemon\n", err); goto out; } @@ -2131,8 +2082,6 @@ fuse_vnop_setextattr(struct vop_setextattr_args *ap) char *attr_str; int err; - fuse_trace_printf_vnop(); - if (fuse_isdeadfs(vp)) return (ENXIO); @@ -2158,7 +2107,6 @@ fuse_vnop_setextattr(struct vop_setextattr_args *ap) err = uiomove((char *)fdi.indata + sizeof(*set_xattr_in) + len, uio->uio_resid, uio); if (err != 0) { - debug_printf("setxattr: got error %d from uiomove\n", err); goto out; } @@ -2167,7 +2115,6 @@ fuse_vnop_setextattr(struct vop_setextattr_args *ap) if (err != 0) { if (err == ENOSYS) fsess_set_notimpl(mp, FUSE_SETXATTR); - debug_printf("setxattr: got err=%d from daemon\n", err); goto out; } @@ -2262,8 +2209,6 @@ fuse_vnop_listextattr(struct vop_listextattr_args *ap) int linux_list_len; int err; - fuse_trace_printf_vnop(); - if (fuse_isdeadfs(vp)) return (ENXIO); @@ -2293,7 +2238,6 @@ fuse_vnop_listextattr(struct vop_listextattr_args *ap) if (err != 0) { if (err == ENOSYS) fsess_set_notimpl(mp, FUSE_LISTXATTR); - debug_printf("listextattr: got err=%d from daemon\n", err); goto out; } @@ -2368,8 +2312,6 @@ fuse_vnop_deleteextattr(struct vop_deleteextattr_args *ap) char *attr_str; int err; - fuse_trace_printf_vnop(); - if (fuse_isdeadfs(vp)) return (ENXIO); @@ -2393,7 +2335,6 @@ fuse_vnop_deleteextattr(struct vop_deleteextattr_args *ap) if (err != 0) { if (err == ENOSYS) fsess_set_notimpl(mp, FUSE_REMOVEXATTR); - debug_printf("removexattr: got err=%d from daemon\n", err); } fdisp_destroy(&fdi); From c02ccc7e448dcf6a16526fb8c3541d7f83608818 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 1 Mar 2019 15:49:11 +0000 Subject: [PATCH 03/41] Fix typos from r344664 Sponsored by: The FreeBSD Foundation --- sys/fs/fuse/fuse_ipc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sys/fs/fuse/fuse_ipc.c b/sys/fs/fuse/fuse_ipc.c index 6718b18d479..fd615709926 100644 --- a/sys/fs/fuse/fuse_ipc.c +++ b/sys/fs/fuse/fuse_ipc.c @@ -66,7 +66,6 @@ __FBSDID("$FreeBSD$"); #include #include #include -#include #include #include #include @@ -75,7 +74,6 @@ __FBSDID("$FreeBSD$"); #include #include #include -#include #include #include #include @@ -299,6 +297,8 @@ fticket_wait_answer(struct fuse_ticket *ftick) int err = 0; struct fuse_data *data; + fuse_lck_mtx_lock(ftick->tk_aw_mtx); + if (fticket_answered(ftick)) { goto out; } From 44154e682a358962de25a5001900953f21c2e107 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 1 Mar 2019 23:53:05 +0000 Subject: [PATCH 04/41] Begin a fuse(4) test suite It only tests the kernel portion of fuse, not the userspace portion (which comes from sysutils/fusefs-libs). The kernel-userspace interface is de-facto standardized, and this test suite seeks to validate FreeBSD's implementation. It uses GoogleMock to substitute for a userspace daemon and validate the kernel's behavior in response to filesystem access. GoogleMock is convenient because it can validate the order, number, and arguments of each operation, and return canned responses. But that also means that the test suite must use GoogleTest, since GoogleMock is incompatible with atf-c++ and atf.test.mk does not allow C++ programs to use atf-c. This commit adds the first 10 test cases out of an estimated 130 total. PR: 235775, 235773 Sponsored by: The FreeBSD Foundation --- etc/mtree/BSD.tests.dist | 2 + tests/sys/fs/Makefile | 1 + tests/sys/fs/fuse/Makefile | 47 +++++++ tests/sys/fs/fuse/getattr.cc | 241 ++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/lookup.cc | 258 +++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.cc | 251 ++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.hh | 131 ++++++++++++++++++ tests/sys/fs/fuse/utils.cc | 57 ++++++++ tests/sys/fs/fuse/utils.hh | 62 +++++++++ 9 files changed, 1050 insertions(+) create mode 100644 tests/sys/fs/fuse/Makefile create mode 100644 tests/sys/fs/fuse/getattr.cc create mode 100644 tests/sys/fs/fuse/lookup.cc create mode 100644 tests/sys/fs/fuse/mockfs.cc create mode 100644 tests/sys/fs/fuse/mockfs.hh create mode 100644 tests/sys/fs/fuse/utils.cc create mode 100644 tests/sys/fs/fuse/utils.hh diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist index 32037557c7b..75670bfaa79 100644 --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -701,6 +701,8 @@ file .. fs + fuse + .. tmpfs .. .. diff --git a/tests/sys/fs/Makefile b/tests/sys/fs/Makefile index c82ee143a78..ec7ebdfc449 100644 --- a/tests/sys/fs/Makefile +++ b/tests/sys/fs/Makefile @@ -7,6 +7,7 @@ TESTSDIR= ${TESTSBASE}/sys/fs TESTSRC= ${SRCTOP}/contrib/netbsd-tests/fs #TESTS_SUBDIRS+= nullfs # XXX: needs rump +TESTS_SUBDIRS+= fuse TESTS_SUBDIRS+= tmpfs ${PACKAGE}FILES+= h_funcs.subr diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile new file mode 100644 index 00000000000..9bf734130ca --- /dev/null +++ b/tests/sys/fs/fuse/Makefile @@ -0,0 +1,47 @@ +# $FreeBSD$ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/sys/fs/fuse + +ATF_TESTS_CXX+= getattr +ATF_TESTS_CXX+= lookup + +SRCS.getattr+= getattr.cc +SRCS.getattr+= getmntopts.c +SRCS.getattr+= mockfs.cc +SRCS.getattr+= utils.cc + +SRCS.lookup+= lookup.cc +SRCS.lookup+= getmntopts.c +SRCS.lookup+= mockfs.cc +SRCS.lookup+= utils.cc + +TEST_METADATA+= timeout=10 +TEST_METADATA+= required_user=root + +FUSEFS= ${.CURDIR:H:H:H:H}/sys/fs/fuse +MOUNT= ${.CURDIR:H:H:H:H}/sbin/mount +CFLAGS+= -I${.CURDIR:H:H:H} +CFLAGS+= -I${FUSEFS} +CFLAGS+= -I${MOUNT} +.PATH: ${MOUNT} + +LIBADD+= pthread +WARNS?= 6 +NO_WTHREAD_SAFETY= # GoogleTest fails Clang's thread safety check + +# Use googlemock from ports until after the import-googletest-1.8.1 branch +# merges to head. +CXXFLAGS+= -I/usr/local/include +CXXFLAGS+= -DGTEST_HAS_POSIX_RE=1 +CXXFLAGS+= -DGTEST_HAS_PTHREAD=1 +CXXFLAGS+= -DGTEST_HAS_STREAM_REDIRECTION=1 +CXXFLAGS+= -frtti +CXXFLAGS+= -std=c++14 +LDADD+= ${LOCALBASE}/lib/libgmock.a +LDADD+= ${LOCALBASE}/lib/libgtest.a +# Without -lpthread, gtest fails at _runtime_ with the error pthread_key_create(&key, &DeleteThreadLocalValue)failed with error 78 +LIBADD+= pthread + +.include diff --git a/tests/sys/fs/fuse/getattr.cc b/tests/sys/fs/fuse/getattr.cc new file mode 100644 index 00000000000..231d6c530f4 --- /dev/null +++ b/tests/sys/fs/fuse/getattr.cc @@ -0,0 +1,241 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Getattr : public FuseTest {}; + +/* + * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs + * should use the cached attributes, rather than query the daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ +TEST_F(Getattr, DISABLED_attr_cache) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + const uint64_t generation = 13; + struct stat sb; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.generation = generation; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr_valid = UINT64_MAX; + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + EXPECT_EQ(0, stat(FULLPATH, &sb)); + /* The second stat(2) should use cached attributes */ + EXPECT_EQ(0, stat(FULLPATH, &sb)); +} + +/* + * If getattr returns a finite but non-zero cache timeout, then we should + * discard the cached attributes and requery the daemon after the timeout + * period passes. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235773 */ +TEST_F(Getattr, attr_cache_timeout) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + const uint64_t generation = 13; + struct stat sb; + /* + * The timeout should be longer than the longest plausible time the + * daemon would take to complete a write(2) to /dev/fuse, but no longer. + */ + long timeout_ns = 250'000'000; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.entry_valid = UINT64_MAX; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.generation = generation; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).Times(2) + .WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr_valid_nsec = timeout_ns; + out->body.attr.attr_valid = UINT64_MAX; + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + EXPECT_EQ(0, stat(FULLPATH, &sb)); + usleep(2 * timeout_ns / 1000); + /* Timeout has expire. stat(2) should requery the daemon */ + EXPECT_EQ(0, stat(FULLPATH, &sb)); +} + +TEST_F(Getattr, enoent) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + struct stat sb; + const uint64_t ino = 42; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = 0100644; + out->body.entry.nodeid = ino; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, stat(FULLPATH, &sb)); + EXPECT_EQ(ENOENT, errno); +} + +TEST_F(Getattr, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + const uint64_t generation = 13; + struct stat sb; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.generation = generation; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.size = 1; + out->body.attr.attr.blocks = 2; + out->body.attr.attr.atime = 3; + out->body.attr.attr.mtime = 4; + out->body.attr.attr.ctime = 5; + out->body.attr.attr.atimensec = 6; + out->body.attr.attr.mtimensec = 7; + out->body.attr.attr.ctimensec = 8; + out->body.attr.attr.nlink = 9; + out->body.attr.attr.uid = 10; + out->body.attr.attr.gid = 11; + out->body.attr.attr.rdev = 12; + })); + + ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); + EXPECT_EQ(1, sb.st_size); + EXPECT_EQ(2, sb.st_blocks); + EXPECT_EQ(3, sb.st_atim.tv_sec); + EXPECT_EQ(6, sb.st_atim.tv_nsec); + EXPECT_EQ(4, sb.st_mtim.tv_sec); + EXPECT_EQ(7, sb.st_mtim.tv_nsec); + EXPECT_EQ(5, sb.st_ctim.tv_sec); + EXPECT_EQ(8, sb.st_ctim.tv_nsec); + EXPECT_EQ(9ull, sb.st_nlink); + EXPECT_EQ(10ul, sb.st_uid); + EXPECT_EQ(11ul, sb.st_gid); + EXPECT_EQ(12ul, sb.st_rdev); + EXPECT_EQ(ino, sb.st_ino); + EXPECT_EQ(S_IFREG | 0644, sb.st_mode); + + // fuse(4) does not _yet_ support inode generations + //EXPECT_EQ(generation, sb.st_gen); + + //st_birthtim and st_flags are not supported by protocol 7.8. They're + //only supported as OS-specific extensions to OSX. + //EXPECT_EQ(, sb.st_birthtim); + //EXPECT_EQ(, sb.st_flags); + + //FUSE can't set st_blksize until protocol 7.9 +} diff --git a/tests/sys/fs/fuse/lookup.cc b/tests/sys/fs/fuse/lookup.cc new file mode 100644 index 00000000000..6c24fdf500d --- /dev/null +++ b/tests/sys/fs/fuse/lookup.cc @@ -0,0 +1,258 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Lookup: public FuseTest {}; + +/* + * If lookup returns a non-zero cache timeout, then subsequent VOP_GETATTRs + * should use the cached attributes, rather than query the daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ +TEST_F(Lookup, DISABLED_attr_cache) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + struct stat sb; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + out->body.entry.attr.ino = ino; // Must match nodeid + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.attr.size = 1; + out->body.entry.attr.blocks = 2; + out->body.entry.attr.atime = 3; + out->body.entry.attr.mtime = 4; + out->body.entry.attr.ctime = 5; + out->body.entry.attr.atimensec = 6; + out->body.entry.attr.mtimensec = 7; + out->body.entry.attr.ctimensec = 8; + out->body.entry.attr.nlink = 9; + out->body.entry.attr.uid = 10; + out->body.entry.attr.gid = 11; + out->body.entry.attr.rdev = 12; + })); + /* stat(2) issues a VOP_LOOKUP followed by a VOP_GETATTR */ + ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); + EXPECT_EQ(1, sb.st_size); + EXPECT_EQ(2, sb.st_blocks); + EXPECT_EQ(3, sb.st_atim.tv_sec); + EXPECT_EQ(6, sb.st_atim.tv_nsec); + EXPECT_EQ(4, sb.st_mtim.tv_sec); + EXPECT_EQ(7, sb.st_mtim.tv_nsec); + EXPECT_EQ(5, sb.st_ctim.tv_sec); + EXPECT_EQ(8, sb.st_ctim.tv_nsec); + EXPECT_EQ(9ull, sb.st_nlink); + EXPECT_EQ(10ul, sb.st_uid); + EXPECT_EQ(11ul, sb.st_gid); + EXPECT_EQ(12ul, sb.st_rdev); + EXPECT_EQ(ino, sb.st_ino); + EXPECT_EQ(S_IFREG | 0644, sb.st_mode); + + // fuse(4) does not _yet_ support inode generations + //EXPECT_EQ(generation, sb.st_gen); + + //st_birthtim and st_flags are not supported by protocol 7.8. They're + //only supported as OS-specific extensions to OSX. + //EXPECT_EQ(, sb.st_birthtim); + //EXPECT_EQ(, sb.st_flags); + + //FUSE can't set st_blksize until protocol 7.9 +} + +/* + * If lookup returns a finite but non-zero cache timeout, then we should discard + * the cached attributes and requery the daemon. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235773 */ +TEST_F(Lookup, attr_cache_timeout) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + struct stat sb; + /* + * The timeout should be longer than the longest plausible time the + * daemon would take to complete a write(2) to /dev/fuse, but no longer. + */ + long timeout_ns = 250'000'000; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.nodeid = ino; + out->body.entry.attr_valid_nsec = timeout_ns; + out->body.entry.attr.ino = ino; // Must match nodeid + out->body.entry.attr.mode = S_IFREG | 0644; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + + /* access(2) will issue a VOP_LOOKUP but not a VOP_GETATTR */ + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); + usleep(2 * timeout_ns / 1000); + /* The cache has timed out; VOP_GETATTR should query the daemon*/ + ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); +} + +TEST_F(Lookup, enoent) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_LOOKUP); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); + EXPECT_EQ(ENOENT, errno); +} + +/* + * If lookup returns a non-zero entry timeout, then subsequent VOP_LOOKUPs + * should use the cached inode rather than requery the daemon + */ +TEST_F(Lookup, entry_cache) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.entry_valid = UINT64_MAX; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = 14; + })); + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); + /* The second access(2) should use the cache */ + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); +} + +/* + * If lookup returns a finite but non-zero entry cache timeout, then we should + * discard the cached inode and requery the daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235773 */ +TEST_F(Lookup, DISABLED_entry_cache_timeout) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + /* + * The timeout should be longer than the longest plausible time the + * daemon would take to complete a write(2) to /dev/fuse, but no longer. + */ + long timeout_ns = 250'000'000; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).Times(2) + .WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.entry_valid_nsec = timeout_ns; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = 14; + })); + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); + usleep(2 * timeout_ns / 1000); + /* The cache has timed out; VOP_LOOKUP should query the daemon*/ + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); +} + +TEST_F(Lookup, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = 14; + })); + /* + * access(2) is one of the few syscalls that will not (always) follow + * up a successful VOP_LOOKUP with another VOP. + */ + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc new file mode 100644 index 00000000000..cab79529d53 --- /dev/null +++ b/tests/sys/fs/fuse/mockfs.cc @@ -0,0 +1,251 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mntopts.h" // for build_iovec +} + +#include + +#include "mockfs.hh" + +using namespace testing; + +int verbosity = 0; +static sig_atomic_t quit = 0; + +const char* opcode2opname(uint32_t opcode) +{ + const int NUM_OPS = 39; + const char* table[NUM_OPS] = { + "Unknown (opcode 0)", + "FUSE_LOOKUP", + "FUSE_FORGET", + "FUSE_GETATTR", + "FUSE_SETATTR", + "FUSE_READLINK", + "FUSE_SYMLINK", + "Unknown (opcode 7)", + "FUSE_MKNOD", + "FUSE_MKDIR", + "FUSE_UNLINK", + "FUSE_RMDIR", + "FUSE_RENAME", + "FUSE_LINK", + "FUSE_OPEN", + "FUSE_READ", + "FUSE_WRITE", + "FUSE_STATFS", + "FUSE_RELEASE", + "Unknown (opcode 19)", + "FUSE_FSYNC", + "FUSE_SETXATTR", + "FUSE_GETXATTR", + "FUSE_LISTXATTR", + "FUSE_REMOVEXATTR", + "FUSE_FLUSH", + "FUSE_INIT", + "FUSE_OPENDIR", + "FUSE_READDIR", + "FUSE_RELEASEDIR", + "FUSE_FSYNCDIR", + "FUSE_GETLK", + "FUSE_SETLK", + "FUSE_SETLKW", + "FUSE_ACCESS", + "FUSE_CREATE", + "FUSE_INTERRUPT", + "FUSE_BMAP", + "FUSE_DESTROY" + }; + if (opcode >= NUM_OPS) + return ("Unknown (opcode > max)"); + else + return (table[opcode]); +} + +void sigint_handler(int __unused sig) { + quit = 1; +} + +MockFS::MockFS() { + struct iovec *iov = NULL; + int iovlen = 0; + char fdstr[15]; + + /* + * Kyua sets pwd to a testcase-unique tempdir; no need to use + * mkdtemp + */ + /* + * googletest doesn't allow ASSERT_ in constructors, so we must throw + * instead. + */ + if (mkdir("mountpoint" , 0644) && errno != EEXIST) + throw(std::system_error(errno, std::system_category(), + "Couldn't make mountpoint directory")); + + m_fuse_fd = open("/dev/fuse", O_RDWR); + if (m_fuse_fd < 0) + throw(std::system_error(errno, std::system_category(), + "Couldn't open /dev/fuse")); + sprintf(fdstr, "%d", m_fuse_fd); + + m_pid = getpid(); + + build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1); + build_iovec(&iov, &iovlen, "fspath", + __DECONST(void *, "mountpoint"), -1); + build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); + build_iovec(&iov, &iovlen, "fd", fdstr, -1); + if (nmount(iov, iovlen, 0)) + throw(std::system_error(errno, std::system_category(), + "Couldn't mount filesystem")); + + // Setup default handler + ON_CALL(*this, process(_, _)) + .WillByDefault(Invoke(this, &MockFS::process_default)); + + init(); + if (pthread_create(&m_thr, NULL, service, (void*)this)) + throw(std::system_error(errno, std::system_category(), + "Couldn't Couldn't start fuse thread")); +} + +MockFS::~MockFS() { + pthread_kill(m_daemon_id, SIGUSR1); + // Closing the /dev/fuse file descriptor first allows unmount to + // succeed even if the daemon doesn't correctly respond to commands + // during the unmount sequence. + close(m_fuse_fd); + pthread_join(m_daemon_id, NULL); + ::unmount("mountpoint", MNT_FORCE); + rmdir("mountpoint"); +} + +void MockFS::init() { + mockfs_buf_in *in; + mockfs_buf_out out; + + in = (mockfs_buf_in*) malloc(sizeof(*in)); + ASSERT_TRUE(in != NULL); + + read_request(in); + ASSERT_EQ(FUSE_INIT, in->header.opcode); + + memset(&out, 0, sizeof(out)); + out.header.unique = in->header.unique; + out.header.error = 0; + out.body.init.major = FUSE_KERNEL_VERSION; + out.body.init.minor = FUSE_KERNEL_MINOR_VERSION; + SET_OUT_HEADER_LEN(&out, init); + write(m_fuse_fd, &out, out.header.len); + + free(in); +} + +void MockFS::loop() { + mockfs_buf_in *in; + mockfs_buf_out out; + + in = (mockfs_buf_in*) malloc(sizeof(*in)); + ASSERT_TRUE(in != NULL); + while (!quit) { + bzero(in, sizeof(*in)); + bzero(&out, sizeof(out)); + read_request(in); + if (quit) + break; + if (verbosity > 0) { + printf("Got request %s\n", + opcode2opname(in->header.opcode)); + } + if ((pid_t)in->header.pid != m_pid) { + /* + * Reject any requests from unknown processes. Because + * we actually do mount a filesystem, plenty of + * unrelated system daemons may try to access it. + */ + process_default(in, &out); + } else { + process(in, &out); + } + if (in->header.opcode == FUSE_FORGET) { + /*Alone among the opcodes, FORGET expects no response*/ + continue; + } + ASSERT_TRUE(write(m_fuse_fd, &out, out.header.len) > 0 || + errno == EAGAIN) + << strerror(errno); + } + free(in); +} + +void MockFS::process_default(const mockfs_buf_in *in, mockfs_buf_out* out) { + out->header.unique = in->header.unique; + out->header.error = -EOPNOTSUPP; + out->header.len = sizeof(out->header); +} + +void MockFS::read_request(mockfs_buf_in *in) { + ssize_t res; + + res = read(m_fuse_fd, in, sizeof(*in)); + if (res < 0 && !quit) + perror("read"); + ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || quit); +} + +void* MockFS::service(void *pthr_data) { + MockFS *mock_fs = (MockFS*)pthr_data; + mock_fs->m_daemon_id = pthread_self(); + + quit = 0; + signal(SIGUSR1, sigint_handler); + + mock_fs->loop(); + + return (NULL); +} + +void MockFS::unmount() { + ::unmount("mountpoint", 0); +} diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh new file mode 100644 index 00000000000..649c09811ba --- /dev/null +++ b/tests/sys/fs/fuse/mockfs.hh @@ -0,0 +1,131 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include + +#include + +#include "fuse_kernel.h" +} + +#include + +#define SET_OUT_HEADER_LEN(out, variant) { \ + (out)->header.len = (sizeof((out)->header) + \ + sizeof((out)->body.variant)); \ +} + +extern int verbosity; + +union fuse_payloads_in { + fuse_forget_in forget; + fuse_init_in init; + char lookup[0]; + /* value is from fuse_kern_chan.c in fusefs-libs */ + uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; +}; + +struct mockfs_buf_in { + fuse_in_header header; + union fuse_payloads_in body; +}; + +union fuse_payloads_out { + fuse_init_out init; + fuse_entry_out entry; + fuse_attr_out attr; +}; + +struct mockfs_buf_out { + fuse_out_header header; + union fuse_payloads_out body; +}; + +/* + * Fake FUSE filesystem + * + * "Mounts" a filesystem to a temporary directory and services requests + * according to the programmed expectations. + * + * Operates directly on the fuse(4) kernel API, not the libfuse(3) user api. + */ +class MockFS { + public: + /* thread id of the fuse daemon thread */ + pthread_t m_daemon_id; + + private: + /* file descriptor of /dev/fuse control device */ + int m_fuse_fd; + + /* pid of the test process */ + pid_t m_pid; + + /* + * Thread that's running the mockfs daemon. + * + * It must run in a separate thread so it doesn't deadlock with the + * client test code. + */ + pthread_t m_thr; + + /* Initialize a session after mounting */ + void init(); + + /* Default request handler */ + void process_default(const mockfs_buf_in*, mockfs_buf_out*); + + /* Entry point for the daemon thread */ + static void* service(void*); + + /* Read, but do not process, a single request from the kernel */ + void read_request(mockfs_buf_in*); + + public: + /* Create a new mockfs and mount it to a tempdir */ + MockFS(); + virtual ~MockFS(); + + /* Process FUSE requests endlessly */ + void loop(); + + /* + * Request handler + * + * This method is expected to provide the response to each FUSE + * operation. Responses must be immediate (so this method can't be used + * for testing a daemon with queue depth > 1). Test cases must define + * each response using Googlemock expectations + */ + MOCK_METHOD2(process, void(const mockfs_buf_in*, mockfs_buf_out*)); + + /* Gracefully unmount */ + void unmount(); +}; diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc new file mode 100644 index 00000000000..19b85cd3fd2 --- /dev/null +++ b/tests/sys/fs/fuse/utils.cc @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include "mockfs.hh" + +static void usage(char* progname) { + fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname); + exit(2); +} + +int main(int argc, char **argv) { + int ch; + + ::testing::InitGoogleTest(&argc, argv); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + verbosity++; + break; + default: + usage(argv[0]); + break; + } + } + + return (RUN_ALL_TESTS()); +} diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh new file mode 100644 index 00000000000..6923a5c95b7 --- /dev/null +++ b/tests/sys/fs/fuse/utils.hh @@ -0,0 +1,62 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#define GTEST_REQUIRE_KERNEL_MODULE(_mod_name) do { \ + if (modfind(_mod_name) == -1) { \ + printf("module %s could not be resolved: %s\n", \ + _mod_name, strerror(errno)); \ + /* + * TODO: enable GTEST_SKIP once GoogleTest 1.8.2 merges + * GTEST_SKIP() + */ \ + FAIL() << "Module " << _mod_name << " could not be resolved\n";\ + } \ +} while(0) + +class FuseTest : public ::testing::Test { + protected: + MockFS *m_mock = NULL; + + public: + void SetUp() { + GTEST_REQUIRE_KERNEL_MODULE("fuse"); + try { + m_mock = new MockFS{}; + } catch (std::system_error err) { + FAIL() << err.what(); + } + } + + void TearDown() { + if (m_mock) + delete m_mock; + } +}; From 7716c35f770a1ccfe803f6f7c6fecaab36abc7b7 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sat, 2 Mar 2019 15:32:20 +0000 Subject: [PATCH 05/41] Add some fuse(4) tests for FUSE_SETATTR Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 7 + tests/sys/fs/fuse/mockfs.hh | 5 +- tests/sys/fs/fuse/setattr.cc | 386 +++++++++++++++++++++++++++++++++++ 3 files changed, 396 insertions(+), 2 deletions(-) create mode 100644 tests/sys/fs/fuse/setattr.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 9bf734130ca..28ff655c2ab 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -6,6 +6,7 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= lookup +ATF_TESTS_CXX+= setattr SRCS.getattr+= getattr.cc SRCS.getattr+= getmntopts.c @@ -17,6 +18,12 @@ SRCS.lookup+= getmntopts.c SRCS.lookup+= mockfs.cc SRCS.lookup+= utils.cc +SRCS.setattr+= setattr.cc +SRCS.setattr+= getmntopts.c +SRCS.setattr+= mockfs.cc +SRCS.setattr+= utils.cc + +# TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 TEST_METADATA+= required_user=root diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 649c09811ba..dfc95b1f3a0 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -45,11 +45,12 @@ extern "C" { extern int verbosity; union fuse_payloads_in { + /* value is from fuse_kern_chan.c in fusefs-libs */ + uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; fuse_forget_in forget; fuse_init_in init; char lookup[0]; - /* value is from fuse_kern_chan.c in fusefs-libs */ - uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; + fuse_setattr_in setattr; }; struct mockfs_buf_in { diff --git a/tests/sys/fs/fuse/setattr.cc b/tests/sys/fs/fuse/setattr.cc new file mode 100644 index 00000000000..7d48831969a --- /dev/null +++ b/tests/sys/fs/fuse/setattr.cc @@ -0,0 +1,386 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include + +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Setattr : public FuseTest {}; + + +/* Change the mode of a file */ +TEST_F(Setattr, chmod) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + const mode_t oldmode = 0755; + const mode_t newmode = 0644; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | oldmode; + out->body.entry.nodeid = ino; + out->body.entry.attr.mode = S_IFREG | oldmode; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + /* In protocol 7.23, ctime will be changed too */ + uint32_t valid = FATTR_MODE; + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino && + in->body.setattr.valid == valid && + in->body.setattr.mode == newmode); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | newmode; + })); + EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); +} + +/* Change the owner and group of a file */ +TEST_F(Setattr, chown) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + const gid_t oldgroup = 66; + const gid_t newgroup = 99; + const uid_t olduser = 33; + const uid_t newuser = 44; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr.gid = oldgroup; + out->body.entry.attr.uid = olduser; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + /* In protocol 7.23, ctime will be changed too */ + uint32_t valid = FATTR_GID | FATTR_UID; + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino && + in->body.setattr.valid == valid && + in->body.setattr.uid == newuser && + in->body.setattr.gid == newgroup); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.uid = newuser; + out->body.attr.attr.gid = newgroup; + })); + EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno); +} + + +/* Change the mode of an open file, by its file descriptor */ +//TODO TEST_F(Setattr, fchmod) {} + +/* + * FUSE daemons are allowed to check permissions however they like. If the + * daemon returns EPERM, even if the file permissions "should" grant access, + * then fuse(4) should return EPERM too. + */ +TEST_F(Setattr, eperm) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0777; + out->body.entry.nodeid = ino; + out->body.entry.attr.uid = in->header.uid; + out->body.entry.attr.gid = in->header.gid; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -EPERM; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, truncate(FULLPATH, 10)); + EXPECT_EQ(EPERM, errno); +} + +/* Change the size of the file */ +TEST_F(Setattr, truncate) { + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + const uint64_t oldsize = 100'000'000; + const uint64_t newsize = 20'000'000; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr.size = oldsize; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + /* In protocol 7.23, ctime will be changed too */ + uint32_t valid = FATTR_SIZE; + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino && + in->body.setattr.valid == valid && + in->body.setattr.size == newsize); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.size = newsize; + })); + EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno); +} + +/* Change a file's timestamps */ +TEST_F(Setattr, utimensat) { + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + const timespec oldtimes[2] = { + {.tv_sec = 1, .tv_nsec = 2}, + {.tv_sec = 3, .tv_nsec = 4}, + }; + const timespec newtimes[2] = { + {.tv_sec = 5, .tv_nsec = 6}, + {.tv_sec = 7, .tv_nsec = 8}, + }; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + out->body.entry.attr.atime = oldtimes[0].tv_sec; + out->body.entry.attr.atimensec = oldtimes[0].tv_nsec; + out->body.entry.attr.mtime = oldtimes[1].tv_sec; + out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec; + })); + + /* + * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR + * call + */ + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.atime = oldtimes[0].tv_sec; + out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; + out->body.attr.attr.mtime = oldtimes[1].tv_sec; + out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + /* In protocol 7.23, ctime will be changed too */ + uint32_t valid = FATTR_ATIME | FATTR_MTIME; + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino && + in->body.setattr.valid == valid && + in->body.setattr.atime == newtimes[0].tv_sec && + in->body.setattr.atimensec == + newtimes[0].tv_nsec && + in->body.setattr.mtime == newtimes[1].tv_sec && + in->body.setattr.mtimensec == + newtimes[1].tv_nsec); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.atime = newtimes[0].tv_sec; + out->body.attr.attr.atimensec = newtimes[0].tv_nsec; + out->body.attr.attr.mtime = newtimes[1].tv_sec; + out->body.attr.attr.mtimensec = newtimes[1].tv_nsec; + })); + EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) + << strerror(errno); +} + +/* Change a file mtime but not its atime */ +TEST_F(Setattr, utimensat_mtime_only) { + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + const timespec oldtimes[2] = { + {.tv_sec = 1, .tv_nsec = 2}, + {.tv_sec = 3, .tv_nsec = 4}, + }; + const timespec newtimes[2] = { + {.tv_sec = 5, .tv_nsec = UTIME_OMIT}, + {.tv_sec = 7, .tv_nsec = 8}, + }; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + out->body.entry.attr.atime = oldtimes[0].tv_sec; + out->body.entry.attr.atimensec = oldtimes[0].tv_nsec; + out->body.entry.attr.mtime = oldtimes[1].tv_sec; + out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec; + })); + + /* + * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR + * call + */ + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.atime = oldtimes[0].tv_sec; + out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; + out->body.attr.attr.mtime = oldtimes[1].tv_sec; + out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + /* In protocol 7.23, ctime will be changed too */ + uint32_t valid = FATTR_MTIME; + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino && + in->body.setattr.valid == valid && + in->body.setattr.mtime == newtimes[1].tv_sec && + in->body.setattr.mtimensec == + newtimes[1].tv_nsec); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.atime = oldtimes[0].tv_sec; + out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; + out->body.attr.attr.mtime = newtimes[1].tv_sec; + out->body.attr.attr.mtimensec = newtimes[1].tv_nsec; + })); + EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) + << strerror(errno); +} + +/* + * Writethrough cache: newly changed attributes should be automatically cached, + * if the filesystem allows it + */ +//TODO TEST_F(Setattr, writethrough_cache){} From 8eeb82e16950f01009280a220dd1544b91dfe45e Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sat, 2 Mar 2019 16:28:29 +0000 Subject: [PATCH 06/41] fuse(4) use a global environment check. This is marginally faster than using an environment check in each test case. Also, if the global check fails then all of the tests are skipped. Oddly, it's not possible to skip a test in any other way. Also, allow the test to run as a normal user if vfs.usermount=1 and /dev/fuse is accessible. Reported by: ngie Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 1 - tests/sys/fs/fuse/utils.cc | 35 +++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/utils.hh | 15 --------------- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 28ff655c2ab..845470684b1 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -25,7 +25,6 @@ SRCS.setattr+= utils.cc # TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 -TEST_METADATA+= required_user=root FUSEFS= ${.CURDIR:H:H:H:H}/sys/fs/fuse MOUNT= ${.CURDIR:H:H:H:H}/sbin/mount diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index 19b85cd3fd2..654b5082725 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -27,10 +27,43 @@ * SUCH DAMAGE. */ +#include +#include +#include + #include #include #include "mockfs.hh" +#include "utils.hh" + +class FuseEnv: public ::testing::Environment { + virtual void SetUp() { + const char *mod_name = "fuse"; + const char *devnode = "/dev/fuse"; + const char *usermount_node = "vfs.usermount"; + int usermount_val = 0; + size_t usermount_size = sizeof(usermount_val); + if (modfind(mod_name) == -1) { + FAIL() << "Module " << mod_name << + " could not be resolved"; + } + if (eaccess(devnode, R_OK | W_OK)) { + if (errno == ENOENT) { + FAIL() << devnode << " does not exist"; + } else if (errno == EACCES) { + FAIL() << devnode << + " is not accessible by the current user"; + } else { + FAIL() << strerror(errno); + } + } + sysctlbyname(usermount_node, &usermount_val, &usermount_size, + NULL, 0); + if (geteuid() != 0 && !usermount_val) + FAIL() << "current user is not allowed to mount"; + } +}; static void usage(char* progname) { fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname); @@ -39,8 +72,10 @@ static void usage(char* progname) { int main(int argc, char **argv) { int ch; + FuseEnv *fuse_env = new FuseEnv; ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(fuse_env); while ((ch = getopt(argc, argv, "v")) != -1) { switch (ch) { diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index 6923a5c95b7..0462af14fcb 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -27,27 +27,12 @@ * SUCH DAMAGE. */ -#include - -#define GTEST_REQUIRE_KERNEL_MODULE(_mod_name) do { \ - if (modfind(_mod_name) == -1) { \ - printf("module %s could not be resolved: %s\n", \ - _mod_name, strerror(errno)); \ - /* - * TODO: enable GTEST_SKIP once GoogleTest 1.8.2 merges - * GTEST_SKIP() - */ \ - FAIL() << "Module " << _mod_name << " could not be resolved\n";\ - } \ -} while(0) - class FuseTest : public ::testing::Test { protected: MockFS *m_mock = NULL; public: void SetUp() { - GTEST_REQUIRE_KERNEL_MODULE("fuse"); try { m_mock = new MockFS{}; } catch (std::system_error err) { From 56f07a9855de189d0da4c08da8e161d15ea685d3 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Mon, 4 Mar 2019 19:10:22 +0000 Subject: [PATCH 07/41] fuse(4): add tests for negative lookups PR: 236226 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/lookup.cc | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/sys/fs/fuse/lookup.cc b/tests/sys/fs/fuse/lookup.cc index 6c24fdf500d..81ccad98e3c 100644 --- a/tests/sys/fs/fuse/lookup.cc +++ b/tests/sys/fs/fuse/lookup.cc @@ -198,6 +198,65 @@ TEST_F(Lookup, entry_cache) ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); } +/* + * If the daemon returns an error of 0 and an inode of 0, that's a flag for + * "ENOENT and cache it" with the given entry_timeout + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */ +TEST_F(Lookup, DISABLED_entry_cache_negative) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_LOOKUP); + }, Eq(true)), + _) + ).Times(1) + .WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = 0; + out->body.entry.nodeid = 0; + out->body.entry.entry_valid = UINT64_MAX; + SET_OUT_HEADER_LEN(out, entry); + })); + EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); + EXPECT_EQ(ENOENT, errno); + EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); + EXPECT_EQ(ENOENT, errno); +} + +/* Negative entry caches should timeout, too */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */ +TEST_F(Lookup, DISABLED_entry_cache_negative_timeout) +{ + /* + * The timeout should be longer than the longest plausible time the + * daemon would take to complete a write(2) to /dev/fuse, but no longer. + */ + long timeout_ns = 250'000'000; + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_LOOKUP); + }, Eq(true)), + _) + ).Times(1) + .WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = 0; + out->body.entry.nodeid = 0; + out->body.entry.entry_valid_nsec = timeout_ns; + SET_OUT_HEADER_LEN(out, entry); + })); + EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); + EXPECT_EQ(ENOENT, errno); + + usleep(2 * timeout_ns / 1000); + + /* The cache has timed out; VOP_LOOKUP should requery the daemon*/ + EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); + EXPECT_EQ(ENOENT, errno); +} + /* * If lookup returns a finite but non-zero entry cache timeout, then we should * discard the cached inode and requery the daemon From 234331105203ee6bc745edb8868cfa2ed2a9e970 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Mon, 4 Mar 2019 22:03:09 +0000 Subject: [PATCH 08/41] fuse(4): fix the entry_cache_negative_timeout test I committed too soon in r344775; the test actually passes when I write it correctly. PR: 236226 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/lookup.cc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/sys/fs/fuse/lookup.cc b/tests/sys/fs/fuse/lookup.cc index 81ccad98e3c..a767d0c49df 100644 --- a/tests/sys/fs/fuse/lookup.cc +++ b/tests/sys/fs/fuse/lookup.cc @@ -225,8 +225,7 @@ TEST_F(Lookup, DISABLED_entry_cache_negative) } /* Negative entry caches should timeout, too */ -/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */ -TEST_F(Lookup, DISABLED_entry_cache_negative_timeout) +TEST_F(Lookup, entry_cache_negative_timeout) { /* * The timeout should be longer than the longest plausible time the @@ -239,8 +238,8 @@ TEST_F(Lookup, DISABLED_entry_cache_negative_timeout) return (in->header.opcode == FUSE_LOOKUP); }, Eq(true)), _) - ).Times(1) - .WillOnce(Invoke([=](auto in, auto out) { + ).Times(2) + .WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; out->body.entry.nodeid = 0; From 99fe8368c2e87753bedfcf9a70d9957d7db8fda9 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Mon, 4 Mar 2019 22:07:33 +0000 Subject: [PATCH 09/41] fuse(4): add tests for CREATE, OPEN, READLINK, SETATTR and SYMLINK The new SETATTR tests deal with already-open files. PR: 235775 PR: 236231 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 28 ++- tests/sys/fs/fuse/create.cc | 383 ++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.hh | 18 +- tests/sys/fs/fuse/open.cc | 168 +++++++++++++++ tests/sys/fs/fuse/readlink.cc | 115 ++++++++++ tests/sys/fs/fuse/setattr.cc | 153 +++++++++++++- tests/sys/fs/fuse/symlink.cc | 116 ++++++++++ 7 files changed, 975 insertions(+), 6 deletions(-) create mode 100644 tests/sys/fs/fuse/create.cc create mode 100644 tests/sys/fs/fuse/open.cc create mode 100644 tests/sys/fs/fuse/readlink.cc create mode 100644 tests/sys/fs/fuse/symlink.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 845470684b1..ac2f2e2f227 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -4,25 +4,49 @@ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/fs/fuse +ATF_TESTS_CXX+= create ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= lookup +ATF_TESTS_CXX+= open +ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= setattr +ATF_TESTS_CXX+= symlink + +SRCS.create+= create.cc +SRCS.create+= getmntopts.c +SRCS.create+= mockfs.cc +SRCS.create+= utils.cc SRCS.getattr+= getattr.cc SRCS.getattr+= getmntopts.c SRCS.getattr+= mockfs.cc SRCS.getattr+= utils.cc -SRCS.lookup+= lookup.cc SRCS.lookup+= getmntopts.c +SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc SRCS.lookup+= utils.cc -SRCS.setattr+= setattr.cc +SRCS.open+= getmntopts.c +SRCS.open+= mockfs.cc +SRCS.open+= open.cc +SRCS.open+= utils.cc + +SRCS.readlink+= getmntopts.c +SRCS.readlink+= mockfs.cc +SRCS.readlink+= readlink.cc +SRCS.readlink+= utils.cc + SRCS.setattr+= getmntopts.c SRCS.setattr+= mockfs.cc +SRCS.setattr+= setattr.cc SRCS.setattr+= utils.cc +SRCS.symlink+= getmntopts.c +SRCS.symlink+= mockfs.cc +SRCS.symlink+= symlink.cc +SRCS.symlink+= utils.cc + # TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc new file mode 100644 index 00000000000..50685039664 --- /dev/null +++ b/tests/sys/fs/fuse/create.cc @@ -0,0 +1,383 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Create: public FuseTest {}; + +/* + * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the + * attribute cache + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ +TEST_F(Create, DISABLED_attr_cache) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + uint64_t ino = 42; + int fd; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_open_in); + return (in->header.opcode == FUSE_CREATE && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, create); + out->body.create.entry.attr.mode = S_IFREG | mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.entry_valid = UINT64_MAX; + out->body.create.entry.attr_valid = UINT64_MAX; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).Times(0); + + fd = open(FULLPATH, O_CREAT | O_EXCL, mode); + EXPECT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * The fuse daemon fails the request with EEXIST. This usually indicates a + * race condition: some other FUSE client created the file in between when the + * kernel checked for it with lookup and tried to create it with create + */ +TEST_F(Create, eexist) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_open_in); + return (in->header.opcode == FUSE_CREATE && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -EEXIST; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); + EXPECT_EQ(EEXIST, errno); +} + +// TODO: enosys: kernel should fall back to mknod/open + +/* + * Creating a new file after FUSE_LOOKUP returned a negative cache entry + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ +TEST_F(Create, DISABLED_entry_cache_negative) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + uint64_t ino = 42; + int fd; + + /* create will first do a LOOKUP, adding a negative cache entry */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + /* nodeid means ENOENT and cache it */ + out->body.entry.nodeid = 0; + out->header.unique = in->header.unique; + out->header.error = 0; + /* + * Set entry_valid = 0 because this test isn't concerned with + * whether or not we actually cache negative entries, only with + * whether we interpret negative cache responses correctly. + */ + out->body.entry.entry_valid = 0; + SET_OUT_HEADER_LEN(out, entry); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_open_in); + return (in->header.opcode == FUSE_CREATE && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, create); + out->body.create.entry.attr.mode = S_IFREG | mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.entry_valid = UINT64_MAX; + out->body.create.entry.attr_valid = UINT64_MAX; + })); + + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + + fd = open(FULLPATH, O_CREAT | O_EXCL, mode); + ASSERT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * Creating a new file should purge any negative namecache entries + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ +TEST_F(Create, DISABLED_entry_cache_negative_purge) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + uint64_t ino = 42; + int fd; + + /* create will first do a LOOKUP, adding a negative cache entry */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).Times(1) + .WillOnce(Invoke([=](auto in, auto out) { + /* nodeid means ENOENT and cache it */ + out->body.entry.nodeid = 0; + out->header.unique = in->header.unique; + out->header.error = 0; + out->body.entry.entry_valid = UINT64_MAX; + SET_OUT_HEADER_LEN(out, entry); + })).RetiresOnSaturation(); + + /* Then the CREATE should purge the negative cache entry */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_open_in); + return (in->header.opcode == FUSE_CREATE && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, create); + out->body.create.entry.attr.mode = S_IFREG | mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.attr_valid = UINT64_MAX; + })); + + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + + fd = open(FULLPATH, O_CREAT | O_EXCL, mode); + ASSERT_LE(0, fd) << strerror(errno); + + /* Finally, a subsequent lookup should query the daemon */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).Times(1) + .WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = 0; + out->body.entry.nodeid = ino; + out->body.entry.attr.mode = S_IFREG | mode; + SET_OUT_HEADER_LEN(out, entry); + })); + + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * The daemon is responsible for checking file permissions (unless the + * default_permissions mount option was used) + */ +TEST_F(Create, eperm) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_open_in); + return (in->header.opcode == FUSE_CREATE && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -EPERM; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); + EXPECT_EQ(EPERM, errno); +} + +TEST_F(Create, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + uint64_t ino = 42; + int fd; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_open_in); + return (in->header.opcode == FUSE_CREATE && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, create); + out->body.create.entry.attr.mode = S_IFREG | mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.entry_valid = UINT64_MAX; + out->body.create.entry.attr_valid = UINT64_MAX; + })); + + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + + fd = open(FULLPATH, O_CREAT | O_EXCL, mode); + EXPECT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index dfc95b1f3a0..77e645951e8 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -44,12 +44,19 @@ extern "C" { extern int verbosity; +/* This struct isn't defined by fuse_kernel.h or libfuse, but it should be */ +struct fuse_create_out { + struct fuse_entry_out entry; + struct fuse_open_out open; +}; + union fuse_payloads_in { /* value is from fuse_kern_chan.c in fusefs-libs */ uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; fuse_forget_in forget; fuse_init_in init; char lookup[0]; + fuse_open_in open; fuse_setattr_in setattr; }; @@ -59,9 +66,16 @@ struct mockfs_buf_in { }; union fuse_payloads_out { - fuse_init_out init; - fuse_entry_out entry; fuse_attr_out attr; + fuse_create_out create; + fuse_entry_out entry; + fuse_init_out init; + fuse_open_out open; + /* + * The protocol places no limits on the length of the string. This is + * merely convenient for testing. + */ + char str[80]; }; struct mockfs_buf_out { diff --git a/tests/sys/fs/fuse/open.cc b/tests/sys/fs/fuse/open.cc new file mode 100644 index 00000000000..38d7c78d05c --- /dev/null +++ b/tests/sys/fs/fuse/open.cc @@ -0,0 +1,168 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Open: public FuseTest {}; + +/* + * The fuse daemon fails the request with enoent. This usually indicates a + * race condition: some other FUSE client removed the file in between when the + * kernel checked for it with lookup and tried to open it + */ +TEST_F(Open, enoent) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, open(FULLPATH, O_RDONLY)); + EXPECT_EQ(ENOENT, errno); +} + +/* + * The daemon is responsible for checking file permissions (unless the + * default_permissions mount option was used) + */ +TEST_F(Open, eperm) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -EPERM; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, open(FULLPATH, O_RDONLY)); + EXPECT_EQ(EPERM, errno); +} + +TEST_F(Open, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + })); + + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + + fd = open(FULLPATH, O_RDONLY); + EXPECT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} diff --git a/tests/sys/fs/fuse/readlink.cc b/tests/sys/fs/fuse/readlink.cc new file mode 100644 index 00000000000..fd7c8f3c44e --- /dev/null +++ b/tests/sys/fs/fuse/readlink.cc @@ -0,0 +1,115 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Readlink: public FuseTest {}; + +TEST_F(Readlink, eloop) +{ + const char FULLPATH[] = "mountpoint/src"; + const char RELPATH[] = "src"; + const uint64_t ino = 42; + char buf[80]; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFLNK | 0777; + out->body.entry.nodeid = ino; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READLINK && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ELOOP; + out->header.len = sizeof(out->header); + })); + + + EXPECT_EQ(-1, readlink(FULLPATH, buf, sizeof(buf))); + EXPECT_EQ(ELOOP, errno); +} + +TEST_F(Readlink, ok) +{ + const char FULLPATH[] = "mountpoint/src"; + const char RELPATH[] = "src"; + const char dst[] = "dst"; + const uint64_t ino = 42; + char buf[80]; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFLNK | 0777; + out->body.entry.nodeid = ino; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READLINK && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + strlcpy(out->body.str, dst, sizeof(out->body.str)); + out->header.len = sizeof(out->header) + strlen(dst) + 1; + })); + + + EXPECT_EQ((ssize_t)strlen(dst) + 1, + readlink(FULLPATH, buf, sizeof(buf))); + EXPECT_STREQ(dst, buf); +} diff --git a/tests/sys/fs/fuse/setattr.cc b/tests/sys/fs/fuse/setattr.cc index 7d48831969a..8fe4ba586c7 100644 --- a/tests/sys/fs/fuse/setattr.cc +++ b/tests/sys/fs/fuse/setattr.cc @@ -132,8 +132,6 @@ TEST_F(Setattr, chown) } -/* Change the mode of an open file, by its file descriptor */ -//TODO TEST_F(Setattr, fchmod) {} /* * FUSE daemons are allowed to check permissions however they like. If the @@ -176,6 +174,157 @@ TEST_F(Setattr, eperm) EXPECT_EQ(EPERM, errno); } +/* Change the mode of an open file, by its file descriptor */ +TEST_F(Setattr, fchmod) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + const mode_t oldmode = 0755; + const mode_t newmode = 0644; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | oldmode; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + })); + + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | oldmode; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + /* In protocol 7.23, ctime will be changed too */ + uint32_t valid = FATTR_MODE; + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino && + in->body.setattr.valid == valid && + in->body.setattr.mode == newmode); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | newmode; + })); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* Change the size of an open file, by its file descriptor */ +TEST_F(Setattr, ftruncate) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + uint64_t fh = 0xdeadbeef1a7ebabe; + const off_t oldsize = 99; + const off_t newsize = 12345; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + out->body.entry.attr.size = oldsize; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = fh; + })); + + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0755; + out->body.attr.attr.size = oldsize; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + /* In protocol 7.23, ctime will be changed too */ + uint32_t valid = FATTR_SIZE | FATTR_FH; + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino && + in->body.setattr.valid == valid && + in->body.setattr.fh == fh); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0755; + out->body.attr.attr.size = newsize; + })); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + /* Change the size of the file */ TEST_F(Setattr, truncate) { const char FULLPATH[] = "mountpoint/some_file.txt"; diff --git a/tests/sys/fs/fuse/symlink.cc b/tests/sys/fs/fuse/symlink.cc new file mode 100644 index 00000000000..69dd78cc536 --- /dev/null +++ b/tests/sys/fs/fuse/symlink.cc @@ -0,0 +1,116 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Symlink: public FuseTest {}; + +TEST_F(Symlink, enospc) +{ + const char FULLPATH[] = "mountpoint/lnk"; + const char RELPATH[] = "lnk"; + const char dst[] = "dst"; + //const uint64_t ino = 42; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes; + const char *linkname = name + strlen(name) + 1; + return (in->header.opcode == FUSE_SYMLINK && + (0 == strcmp(linkname, dst)) && + (0 == strcmp(name, RELPATH))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOSPC; + out->header.len = sizeof(out->header); + })); + + + EXPECT_EQ(-1, symlink(dst, FULLPATH)); + EXPECT_EQ(ENOSPC, errno); +} + +TEST_F(Symlink, ok) +{ + const char FULLPATH[] = "mountpoint/src"; + const char RELPATH[] = "src"; + const char dst[] = "dst"; + const uint64_t ino = 42; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes; + const char *linkname = name + strlen(name) + 1; + return (in->header.opcode == FUSE_SYMLINK && + (0 == strcmp(linkname, dst)) && + (0 == strcmp(name, RELPATH))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFLNK | 0777; + out->body.entry.nodeid = ino; + })); + + EXPECT_EQ(0, symlink(dst, FULLPATH)) << strerror(errno); +} From 4cbb4f8886c1cad55897418f089dad5b3a6e5f20 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 5 Mar 2019 00:27:54 +0000 Subject: [PATCH 10/41] fuse(4): add tests related to FUSE_MKNOD PR: 236236 Sponsored by: The FreeBSD Foundation --- sys/fs/fuse/fuse_kernel.h | 5 ++ tests/sys/fs/fuse/Makefile | 6 ++ tests/sys/fs/fuse/create.cc | 89 +++++++++++++++++++- tests/sys/fs/fuse/mknod.cc | 162 ++++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.hh | 1 + tests/sys/fs/fuse/utils.hh | 2 +- 6 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 tests/sys/fs/fuse/mknod.cc diff --git a/sys/fs/fuse/fuse_kernel.h b/sys/fs/fuse/fuse_kernel.h index 5ac9f21bf03..c77a4f32d81 100644 --- a/sys/fs/fuse/fuse_kernel.h +++ b/sys/fs/fuse/fuse_kernel.h @@ -194,6 +194,11 @@ struct fuse_attr_out { struct fuse_attr attr; }; +struct fuse_mknod_in { + __u32 mode; + __u32 rdev; +}; + struct fuse_mkdir_in { __u32 mode; __u32 padding; diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index ac2f2e2f227..2506c14e687 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -7,6 +7,7 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse ATF_TESTS_CXX+= create ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= lookup +ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= setattr @@ -27,6 +28,11 @@ SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc SRCS.lookup+= utils.cc +SRCS.mknod+= getmntopts.c +SRCS.mknod+= mockfs.cc +SRCS.mknod+= mknod.cc +SRCS.mknod+= utils.cc + SRCS.open+= getmntopts.c SRCS.open+= mockfs.cc SRCS.open+= open.cc diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index 50685039664..8315075e943 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -133,7 +133,94 @@ TEST_F(Create, eexist) EXPECT_EQ(EEXIST, errno); } -// TODO: enosys: kernel should fall back to mknod/open +/* + * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback + * to FUSE_MKNOD/FUSE_OPEN + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Create, DISABLED_Enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + uint64_t ino = 42; + int fd; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_open_in); + return (in->header.opcode == FUSE_CREATE && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOSYS; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_mknod_in); + return (in->header.opcode == FUSE_MKNOD && + in->body.mknod.mode == (S_IFREG | mode) && + in->body.mknod.rdev == 0 && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, create); + out->body.create.entry.attr.mode = S_IFREG | mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.entry_valid = UINT64_MAX; + out->body.create.entry.attr_valid = UINT64_MAX; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + })); + + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + + fd = open(FULLPATH, O_CREAT | O_EXCL, mode); + EXPECT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} /* * Creating a new file after FUSE_LOOKUP returned a negative cache entry diff --git a/tests/sys/fs/fuse/mknod.cc b/tests/sys/fs/fuse/mknod.cc new file mode 100644 index 00000000000..1b205ac6bae --- /dev/null +++ b/tests/sys/fs/fuse/mknod.cc @@ -0,0 +1,162 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Mknod: public FuseTest { + +public: + +virtual void SetUp() { + if (geteuid() != 0) { + // TODO: With GoogleTest 1.8.2, use SKIP instead + FAIL() << "Only root may use most mknod(2) variations"; + } + FuseTest::SetUp(); +} + +/* Test an OK creation of a file with the given mode and device number */ +void test_ok(mode_t mode, dev_t dev) { + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_mknod_in); + return (in->header.opcode == FUSE_MKNOD && + in->body.mknod.mode == mode && + in->body.mknod.rdev == dev && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, create); + out->body.create.entry.attr.mode = mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.entry_valid = UINT64_MAX; + out->body.create.entry.attr_valid = UINT64_MAX; + out->body.create.entry.attr.rdev = dev; + })); + EXPECT_EQ(0, mknod(FULLPATH, mode, dev)) << strerror(errno); +} + +}; + +/* + * mknod(2) should be able to create block devices on a FUSE filesystem. Even + * though FreeBSD doesn't use block devices, this is useful when copying media + * from or preparing media for other operating systems. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_blk) +{ + test_ok(S_IFBLK | 0755, 0xfe00); /* /dev/vda's device number on Linux */ +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_chr) +{ + test_ok(S_IFCHR | 0755, 0x64); /* /dev/fuse's device number */ +} + +/* + * The daemon is responsible for checking file permissions (unless the + * default_permissions mount option was used) + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_eperm) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = S_IFIFO | 0755; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_mknod_in); + return (in->header.opcode == FUSE_MKNOD && + in->body.mknod.mode == mode && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -EPERM; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, mknod(FULLPATH, mode, 0)); + EXPECT_EQ(EPERM, errno); +} + + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_fifo) +{ + test_ok(S_IFIFO | 0755, 0); +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_whiteout) +{ + test_ok(S_IFWHT | 0755, 0); +} diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 77e645951e8..bbcf14b4aaa 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -56,6 +56,7 @@ union fuse_payloads_in { fuse_forget_in forget; fuse_init_in init; char lookup[0]; + fuse_mknod_in mknod; fuse_open_in open; fuse_setattr_in setattr; }; diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index 0462af14fcb..d3edc44feba 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -32,7 +32,7 @@ class FuseTest : public ::testing::Test { MockFS *m_mock = NULL; public: - void SetUp() { + virtual void SetUp() { try { m_mock = new MockFS{}; } catch (std::system_error err) { From 76effb87dcc6223793c119ad45c2a334f8baf4ec Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 5 Mar 2019 03:27:32 +0000 Subject: [PATCH 11/41] fuse(4): combine some common code in the tests Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 6 ++ tests/sys/fs/fuse/create.cc | 102 ++++------------------------------ tests/sys/fs/fuse/getattr.cc | 38 ++----------- tests/sys/fs/fuse/lookup.cc | 76 ++++++------------------- tests/sys/fs/fuse/mknod.cc | 30 +--------- tests/sys/fs/fuse/mockfs.cc | 10 ++++ tests/sys/fs/fuse/mockfs.hh | 23 ++++++++ tests/sys/fs/fuse/open.cc | 36 ++---------- tests/sys/fs/fuse/readlink.cc | 23 +------- tests/sys/fs/fuse/setattr.cc | 70 +++-------------------- tests/sys/fs/fuse/symlink.cc | 25 +-------- 11 files changed, 93 insertions(+), 346 deletions(-) diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 2506c14e687..bc6aba32f04 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -7,6 +7,7 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse ATF_TESTS_CXX+= create ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= lookup +#ATF_TESTS_CXX+= mkdir ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink @@ -28,6 +29,11 @@ SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc SRCS.lookup+= utils.cc +#SRCS.mkdir+= getmntopts.c +#SRCS.mkdir+= mockfs.cc +#SRCS.mkdir+= mkdir.cc +#SRCS.mkdir+= utils.cc + SRCS.mknod+= getmntopts.c SRCS.mknod+= mockfs.cc SRCS.mknod+= mknod.cc diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index 8315075e943..a5a605a01f9 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -51,17 +51,7 @@ TEST_F(Create, DISABLED_attr_cache) uint64_t ino = 42; int fd; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -104,17 +94,7 @@ TEST_F(Create, eexist) const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -124,11 +104,7 @@ TEST_F(Create, eexist) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -EEXIST; - out->header.len = sizeof(out->header); - })); + ).WillOnce(Invoke(ReturnErrno(EEXIST))); EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); EXPECT_EQ(EEXIST, errno); } @@ -146,17 +122,7 @@ TEST_F(Create, DISABLED_Enosys) uint64_t ino = 42; int fd; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -166,11 +132,7 @@ TEST_F(Create, DISABLED_Enosys) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOSYS; - out->header.len = sizeof(out->header); - })); + ).WillOnce(Invoke(ReturnErrno(ENOSYS))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -235,13 +197,7 @@ TEST_F(Create, DISABLED_entry_cache_negative) int fd; /* create will first do a LOOKUP, adding a negative cache entry */ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; out->header.unique = in->header.unique; @@ -304,13 +260,7 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) int fd; /* create will first do a LOOKUP, adding a negative cache entry */ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).Times(1) + EXPECT_LOOKUP(RELPATH).Times(1) .WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; @@ -355,13 +305,7 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) ASSERT_LE(0, fd) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).Times(1) + EXPECT_LOOKUP(RELPATH).Times(1) .WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; @@ -384,17 +328,7 @@ TEST_F(Create, eperm) const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -404,11 +338,7 @@ TEST_F(Create, eperm) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -EPERM; - out->header.len = sizeof(out->header); - })); + ).WillOnce(Invoke(ReturnErrno(EPERM))); EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); EXPECT_EQ(EPERM, errno); } @@ -421,17 +351,7 @@ TEST_F(Create, ok) uint64_t ino = 42; int fd; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { diff --git a/tests/sys/fs/fuse/getattr.cc b/tests/sys/fs/fuse/getattr.cc index 231d6c530f4..c795ee820f7 100644 --- a/tests/sys/fs/fuse/getattr.cc +++ b/tests/sys/fs/fuse/getattr.cc @@ -47,13 +47,7 @@ TEST_F(Getattr, DISABLED_attr_cache) const uint64_t generation = 13; struct stat sb; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -97,13 +91,7 @@ TEST_F(Getattr, attr_cache_timeout) */ long timeout_ns = 250'000'000; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.entry_valid = UINT64_MAX; @@ -139,13 +127,7 @@ TEST_F(Getattr, enoent) struct stat sb; const uint64_t ino = 42; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = 0100644; @@ -158,11 +140,7 @@ TEST_F(Getattr, enoent) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + ).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_NE(0, stat(FULLPATH, &sb)); EXPECT_EQ(ENOENT, errno); } @@ -175,13 +153,7 @@ TEST_F(Getattr, ok) const uint64_t generation = 13; struct stat sb; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/lookup.cc b/tests/sys/fs/fuse/lookup.cc index a767d0c49df..7a623e1f873 100644 --- a/tests/sys/fs/fuse/lookup.cc +++ b/tests/sys/fs/fuse/lookup.cc @@ -50,13 +50,7 @@ TEST_F(Lookup, DISABLED_attr_cache) const uint64_t ino = 42; struct stat sb; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.nodeid = ino; @@ -121,13 +115,7 @@ TEST_F(Lookup, attr_cache_timeout) */ long timeout_ns = 250'000'000; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.nodeid = ino; @@ -157,17 +145,11 @@ TEST_F(Lookup, attr_cache_timeout) TEST_F(Lookup, enoent) { - EXPECT_CALL(*m_mock, process( - ResultOf([](auto in) { - return (in->header.opcode == FUSE_LOOKUP); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); - EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); + const char FULLPATH[] = "mountpoint/does_not_exist"; + const char RELPATH[] = "does_not_exist"; + + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_NE(0, access(FULLPATH, F_OK)); EXPECT_EQ(ENOENT, errno); } @@ -180,13 +162,7 @@ TEST_F(Lookup, entry_cache) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.entry_valid = UINT64_MAX; @@ -205,12 +181,7 @@ TEST_F(Lookup, entry_cache) /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */ TEST_F(Lookup, DISABLED_entry_cache_negative) { - EXPECT_CALL(*m_mock, process( - ResultOf([](auto in) { - return (in->header.opcode == FUSE_LOOKUP); - }, Eq(true)), - _) - ).Times(1) + EXPECT_LOOKUP("does_not_exist").Times(1) .WillOnce(Invoke([](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; @@ -227,18 +198,15 @@ TEST_F(Lookup, DISABLED_entry_cache_negative) /* Negative entry caches should timeout, too */ TEST_F(Lookup, entry_cache_negative_timeout) { + const char *RELPATH = "does_not_exist"; + const char *FULLPATH = "mountpoint/does_not_exist"; /* * The timeout should be longer than the longest plausible time the * daemon would take to complete a write(2) to /dev/fuse, but no longer. */ long timeout_ns = 250'000'000; - EXPECT_CALL(*m_mock, process( - ResultOf([](auto in) { - return (in->header.opcode == FUSE_LOOKUP); - }, Eq(true)), - _) - ).Times(2) + EXPECT_LOOKUP(RELPATH).Times(2) .WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; @@ -246,13 +214,13 @@ TEST_F(Lookup, entry_cache_negative_timeout) out->body.entry.entry_valid_nsec = timeout_ns; SET_OUT_HEADER_LEN(out, entry); })); - EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); + EXPECT_NE(0, access(FULLPATH, F_OK)); EXPECT_EQ(ENOENT, errno); usleep(2 * timeout_ns / 1000); /* The cache has timed out; VOP_LOOKUP should requery the daemon*/ - EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); + EXPECT_NE(0, access(FULLPATH, F_OK)); EXPECT_EQ(ENOENT, errno); } @@ -271,13 +239,7 @@ TEST_F(Lookup, DISABLED_entry_cache_timeout) */ long timeout_ns = 250'000'000; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).Times(2) + EXPECT_LOOKUP(RELPATH).Times(2) .WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); @@ -296,13 +258,7 @@ TEST_F(Lookup, ok) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/mknod.cc b/tests/sys/fs/fuse/mknod.cc index 1b205ac6bae..ca38985046a 100644 --- a/tests/sys/fs/fuse/mknod.cc +++ b/tests/sys/fs/fuse/mknod.cc @@ -54,17 +54,7 @@ void test_ok(mode_t mode, dev_t dev) { const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -118,17 +108,7 @@ TEST_F(Mknod, DISABLED_eperm) const char RELPATH[] = "some_file.txt"; mode_t mode = S_IFIFO | 0755; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -139,11 +119,7 @@ TEST_F(Mknod, DISABLED_eperm) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -EPERM; - out->header.len = sizeof(out->header); - })); + ).WillOnce(Invoke(ReturnErrno(EPERM))); EXPECT_NE(0, mknod(FULLPATH, mode, 0)); EXPECT_EQ(EPERM, errno); } diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index cab79529d53..c85bad13660 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -102,6 +102,16 @@ const char* opcode2opname(uint32_t opcode) return (table[opcode]); } +std::function +ReturnErrno(int error) +{ + return([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -error; + out->header.len = sizeof(out->header); + }); +} + void sigint_handler(int __unused sig) { quit = 1; } diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index bbcf14b4aaa..56853c5821c 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -42,6 +42,22 @@ extern "C" { sizeof((out)->body.variant)); \ } +/* + * Create an expectation on FUSE_LOOKUP and return it so the caller can set + * actions. + * + * This must be a macro instead of a method because EXPECT_CALL returns a type + * with a deleted constructor. + */ +#define EXPECT_LOOKUP(path) \ + EXPECT_CALL(*m_mock, process( \ + ResultOf([=](auto in) { \ + return (in->header.opcode == FUSE_LOOKUP && \ + strcmp(in->body.lookup, (path)) == 0); \ + }, Eq(true)), \ + _) \ + ) + extern int verbosity; /* This struct isn't defined by fuse_kernel.h or libfuse, but it should be */ @@ -84,6 +100,13 @@ struct mockfs_buf_out { union fuse_payloads_out body; }; +/* + * Helper function used for setting an error expectation for any fuse operation. + * The operation will return the supplied error + */ +std::function +ReturnErrno(int error); + /* * Fake FUSE filesystem * diff --git a/tests/sys/fs/fuse/open.cc b/tests/sys/fs/fuse/open.cc index 38d7c78d05c..9de47d7f2d5 100644 --- a/tests/sys/fs/fuse/open.cc +++ b/tests/sys/fs/fuse/open.cc @@ -49,13 +49,7 @@ TEST_F(Open, enoent) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -68,11 +62,7 @@ TEST_F(Open, enoent) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + ).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_NE(0, open(FULLPATH, O_RDONLY)); EXPECT_EQ(ENOENT, errno); } @@ -87,13 +77,7 @@ TEST_F(Open, eperm) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -106,11 +90,7 @@ TEST_F(Open, eperm) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -EPERM; - out->header.len = sizeof(out->header); - })); + ).WillOnce(Invoke(ReturnErrno(EPERM))); EXPECT_NE(0, open(FULLPATH, O_RDONLY)); EXPECT_EQ(EPERM, errno); } @@ -122,13 +102,7 @@ TEST_F(Open, ok) uint64_t ino = 42; int fd; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/readlink.cc b/tests/sys/fs/fuse/readlink.cc index fd7c8f3c44e..11d39a98350 100644 --- a/tests/sys/fs/fuse/readlink.cc +++ b/tests/sys/fs/fuse/readlink.cc @@ -45,13 +45,7 @@ TEST_F(Readlink, eloop) const uint64_t ino = 42; char buf[80]; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFLNK | 0777; @@ -64,12 +58,7 @@ TEST_F(Readlink, eloop) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ELOOP; - out->header.len = sizeof(out->header); - })); - + ).WillOnce(Invoke(ReturnErrno(ELOOP))); EXPECT_EQ(-1, readlink(FULLPATH, buf, sizeof(buf))); EXPECT_EQ(ELOOP, errno); @@ -83,13 +72,7 @@ TEST_F(Readlink, ok) const uint64_t ino = 42; char buf[80]; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFLNK | 0777; diff --git a/tests/sys/fs/fuse/setattr.cc b/tests/sys/fs/fuse/setattr.cc index 8fe4ba586c7..37582be1e4f 100644 --- a/tests/sys/fs/fuse/setattr.cc +++ b/tests/sys/fs/fuse/setattr.cc @@ -50,13 +50,7 @@ TEST_F(Setattr, chmod) const mode_t oldmode = 0755; const mode_t newmode = 0644; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | oldmode; @@ -94,13 +88,7 @@ TEST_F(Setattr, chown) const uid_t olduser = 33; const uid_t newuser = 44; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -144,13 +132,7 @@ TEST_F(Setattr, eperm) const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0777; @@ -165,11 +147,7 @@ TEST_F(Setattr, eperm) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -EPERM; - out->header.len = sizeof(out->header); - })); + ).WillOnce(Invoke(ReturnErrno(EPERM))); EXPECT_NE(0, truncate(FULLPATH, 10)); EXPECT_EQ(EPERM, errno); } @@ -184,13 +162,7 @@ TEST_F(Setattr, fchmod) const mode_t oldmode = 0755; const mode_t newmode = 0644; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | oldmode; @@ -258,13 +230,7 @@ TEST_F(Setattr, ftruncate) const off_t oldsize = 99; const off_t newsize = 12345; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0755; @@ -333,13 +299,7 @@ TEST_F(Setattr, truncate) { const uint64_t oldsize = 100'000'000; const uint64_t newsize = 20'000'000; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -381,13 +341,7 @@ TEST_F(Setattr, utimensat) { {.tv_sec = 7, .tv_nsec = 8}, }; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -463,13 +417,7 @@ TEST_F(Setattr, utimensat_mtime_only) { {.tv_sec = 7, .tv_nsec = 8}, }; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/symlink.cc b/tests/sys/fs/fuse/symlink.cc index 69dd78cc536..e853964b07c 100644 --- a/tests/sys/fs/fuse/symlink.cc +++ b/tests/sys/fs/fuse/symlink.cc @@ -43,19 +43,8 @@ TEST_F(Symlink, enospc) const char FULLPATH[] = "mountpoint/lnk"; const char RELPATH[] = "lnk"; const char dst[] = "dst"; - //const uint64_t ino = 42; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -84,17 +73,7 @@ TEST_F(Symlink, ok) const char dst[] = "dst"; const uint64_t ino = 42; - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_LOOKUP && - strcmp(in->body.lookup, RELPATH) == 0); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOENT; - out->header.len = sizeof(out->header); - })); + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { From 50deb1a8c27ce4baf86bf95c744fb14f725612db Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 5 Mar 2019 18:53:28 +0000 Subject: [PATCH 12/41] fuse(4): add tests for FUSE_MKDIR and FUSE_ACCESS PR: 236291 PR: 236231 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 16 ++- tests/sys/fs/fuse/access.cc | 99 ++++++++++++++++++ tests/sys/fs/fuse/mkdir.cc | 198 ++++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.hh | 2 + 4 files changed, 310 insertions(+), 5 deletions(-) create mode 100644 tests/sys/fs/fuse/access.cc create mode 100644 tests/sys/fs/fuse/mkdir.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index bc6aba32f04..5e7a931f110 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -4,16 +4,22 @@ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/fs/fuse +ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= lookup -#ATF_TESTS_CXX+= mkdir +ATF_TESTS_CXX+= mkdir ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= setattr ATF_TESTS_CXX+= symlink +SRCS.access+= access.cc +SRCS.access+= getmntopts.c +SRCS.access+= mockfs.cc +SRCS.access+= utils.cc + SRCS.create+= create.cc SRCS.create+= getmntopts.c SRCS.create+= mockfs.cc @@ -29,10 +35,10 @@ SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc SRCS.lookup+= utils.cc -#SRCS.mkdir+= getmntopts.c -#SRCS.mkdir+= mockfs.cc -#SRCS.mkdir+= mkdir.cc -#SRCS.mkdir+= utils.cc +SRCS.mkdir+= getmntopts.c +SRCS.mkdir+= mockfs.cc +SRCS.mkdir+= mkdir.cc +SRCS.mkdir+= utils.cc SRCS.mknod+= getmntopts.c SRCS.mknod+= mockfs.cc diff --git a/tests/sys/fs/fuse/access.cc b/tests/sys/fs/fuse/access.cc new file mode 100644 index 00000000000..3debb2fcd4c --- /dev/null +++ b/tests/sys/fs/fuse/access.cc @@ -0,0 +1,99 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +//#include +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Access: public FuseTest {}; + +/* TODO: test methods for the default_permissions mount option */ + +/* The error case of FUSE_ACCESS. */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236291 */ +TEST_F(Access, DISABLED_eaccess) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + mode_t access_mode = X_OK; + + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_ACCESS && + in->header.nodeid == ino && + in->body.access.mask == access_mode); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EACCES))); + + + ASSERT_NE(0, access(FULLPATH, access_mode)); + ASSERT_EQ(EACCES, errno); +} + +/* The successful case of FUSE_ACCESS. */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236291 */ +TEST_F(Access, DISABLED_ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + mode_t access_mode = R_OK; + + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_ACCESS && + in->header.nodeid == ino && + in->body.access.mask == access_mode); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/mkdir.cc b/tests/sys/fs/fuse/mkdir.cc new file mode 100644 index 00000000000..a0bcdde092c --- /dev/null +++ b/tests/sys/fs/fuse/mkdir.cc @@ -0,0 +1,198 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Mkdir: public FuseTest {}; + +/* + * EMLINK is possible on filesystems that limit the number of hard links to a + * single file, like early versions of BtrFS + */ +TEST_F(Mkdir, emlink) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + mode_t mode = 0755; + + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_mkdir_in); + return (in->header.opcode == FUSE_MKDIR && + in->body.mkdir.mode == (S_IFDIR | mode) && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EMLINK))); + + ASSERT_NE(1, mkdir(FULLPATH, mode)); + ASSERT_EQ(EMLINK, errno); +} + +/* + * Creating a new directory after FUSE_LOOKUP returned a negative cache entry + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ +TEST_F(Mkdir, DISABLED_entry_cache_negative) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + uint64_t ino = 42; + + /* mkdir will first do a LOOKUP, adding a negative cache entry */ + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + /* nodeid means ENOENT and cache it */ + out->body.entry.nodeid = 0; + out->header.unique = in->header.unique; + out->header.error = 0; + /* + * Set entry_valid = 0 because this test isn't concerned with + * whether or not we actually cache negative entries, only with + * whether we interpret negative cache responses correctly. + */ + out->body.entry.entry_valid = 0; + SET_OUT_HEADER_LEN(out, entry); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_open_in); + return (in->header.opcode == FUSE_MKDIR && + in->body.mkdir.mode == (S_IFDIR | mode) && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.create.entry.attr.mode = S_IFDIR | mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.entry_valid = UINT64_MAX; + out->body.create.entry.attr_valid = UINT64_MAX; + })); + + ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); +} + +/* + * Creating a new directory should purge any negative namecache entries + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ +TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + uint64_t ino = 42; + + /* mkdir will first do a LOOKUP, adding a negative cache entry */ + EXPECT_LOOKUP(RELPATH).Times(1) + .WillOnce(Invoke([=](auto in, auto out) { + /* nodeid means ENOENT and cache it */ + out->body.entry.nodeid = 0; + out->header.unique = in->header.unique; + out->header.error = 0; + out->body.entry.entry_valid = UINT64_MAX; + SET_OUT_HEADER_LEN(out, entry); + })).RetiresOnSaturation(); + + /* Then the MKDIR should purge the negative cache entry */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_open_in); + return (in->header.opcode == FUSE_MKDIR && + in->body.mkdir.mode == (S_IFDIR | mode) && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | mode; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); + + ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); + + /* Finally, a subsequent lookup should query the daemon */ + EXPECT_LOOKUP(RELPATH).Times(1) + .WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = 0; + out->body.entry.nodeid = ino; + out->body.entry.attr.mode = S_IFDIR | mode; + SET_OUT_HEADER_LEN(out, entry); + })); + + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); +} + +TEST_F(Mkdir, ok) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + mode_t mode = 0755; + uint64_t ino = 42; + + EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_mkdir_in); + return (in->header.opcode == FUSE_MKDIR && + in->body.mkdir.mode == (S_IFDIR | mode) && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.create.entry.attr.mode = S_IFDIR | mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.entry_valid = UINT64_MAX; + out->body.create.entry.attr_valid = UINT64_MAX; + })); + + ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 56853c5821c..a3d1f4975f4 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -67,11 +67,13 @@ struct fuse_create_out { }; union fuse_payloads_in { + fuse_access_in access; /* value is from fuse_kern_chan.c in fusefs-libs */ uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; fuse_forget_in forget; fuse_init_in init; char lookup[0]; + fuse_mkdir_in mkdir; fuse_mknod_in mknod; fuse_open_in open; fuse_setattr_in setattr; From 9b4318e553855dcedaf109abd8d4f9c0cde9fce5 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 5 Mar 2019 21:40:08 +0000 Subject: [PATCH 13/41] fuse(4): add test cases for FUSE_LINK and FUSE_RENAME Also, add a FUSE_LOOKUP test case for subdirectories, and improve debugging output. Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 12 +++ tests/sys/fs/fuse/access.cc | 4 +- tests/sys/fs/fuse/create.cc | 16 +-- tests/sys/fs/fuse/getattr.cc | 8 +- tests/sys/fs/fuse/link.cc | 106 +++++++++++++++++++ tests/sys/fs/fuse/lookup.cc | 46 +++++++-- tests/sys/fs/fuse/mkdir.cc | 10 +- tests/sys/fs/fuse/mknod.cc | 4 +- tests/sys/fs/fuse/mockfs.cc | 20 +++- tests/sys/fs/fuse/mockfs.hh | 5 +- tests/sys/fs/fuse/open.cc | 6 +- tests/sys/fs/fuse/readlink.cc | 4 +- tests/sys/fs/fuse/rename.cc | 188 ++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/setattr.cc | 16 +-- tests/sys/fs/fuse/symlink.cc | 11 +- tests/sys/fs/fuse/utils.hh | 2 +- 16 files changed, 402 insertions(+), 56 deletions(-) create mode 100644 tests/sys/fs/fuse/link.cc create mode 100644 tests/sys/fs/fuse/rename.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 5e7a931f110..44d12f47da2 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -7,11 +7,13 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create ATF_TESTS_CXX+= getattr +ATF_TESTS_CXX+= link ATF_TESTS_CXX+= lookup ATF_TESTS_CXX+= mkdir ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink +ATF_TESTS_CXX+= rename ATF_TESTS_CXX+= setattr ATF_TESTS_CXX+= symlink @@ -30,6 +32,11 @@ SRCS.getattr+= getmntopts.c SRCS.getattr+= mockfs.cc SRCS.getattr+= utils.cc +SRCS.link+= getmntopts.c +SRCS.link+= link.cc +SRCS.link+= mockfs.cc +SRCS.link+= utils.cc + SRCS.lookup+= getmntopts.c SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc @@ -55,6 +62,11 @@ SRCS.readlink+= mockfs.cc SRCS.readlink+= readlink.cc SRCS.readlink+= utils.cc +SRCS.rename+= getmntopts.c +SRCS.rename+= mockfs.cc +SRCS.rename+= rename.cc +SRCS.rename+= utils.cc + SRCS.setattr+= getmntopts.c SRCS.setattr+= mockfs.cc SRCS.setattr+= setattr.cc diff --git a/tests/sys/fs/fuse/access.cc b/tests/sys/fs/fuse/access.cc index 3debb2fcd4c..aea765002aa 100644 --- a/tests/sys/fs/fuse/access.cc +++ b/tests/sys/fs/fuse/access.cc @@ -51,7 +51,7 @@ TEST_F(Access, DISABLED_eaccess) uint64_t ino = 42; mode_t access_mode = X_OK; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -80,7 +80,7 @@ TEST_F(Access, DISABLED_ok) uint64_t ino = 42; mode_t access_mode = R_OK; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index a5a605a01f9..46a88e2c677 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -51,7 +51,7 @@ TEST_F(Create, DISABLED_attr_cache) uint64_t ino = 42; int fd; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -94,7 +94,7 @@ TEST_F(Create, eexist) const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -122,7 +122,7 @@ TEST_F(Create, DISABLED_Enosys) uint64_t ino = 42; int fd; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -197,7 +197,7 @@ TEST_F(Create, DISABLED_entry_cache_negative) int fd; /* create will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; out->header.unique = in->header.unique; @@ -260,7 +260,7 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) int fd; /* create will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(RELPATH).Times(1) + EXPECT_LOOKUP(1, RELPATH).Times(1) .WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; @@ -305,7 +305,7 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) ASSERT_LE(0, fd) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - EXPECT_LOOKUP(RELPATH).Times(1) + EXPECT_LOOKUP(1, RELPATH).Times(1) .WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; @@ -328,7 +328,7 @@ TEST_F(Create, eperm) const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -351,7 +351,7 @@ TEST_F(Create, ok) uint64_t ino = 42; int fd; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { diff --git a/tests/sys/fs/fuse/getattr.cc b/tests/sys/fs/fuse/getattr.cc index c795ee820f7..d9b5b36a640 100644 --- a/tests/sys/fs/fuse/getattr.cc +++ b/tests/sys/fs/fuse/getattr.cc @@ -47,7 +47,7 @@ TEST_F(Getattr, DISABLED_attr_cache) const uint64_t generation = 13; struct stat sb; - EXPECT_LOOKUP(RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -91,7 +91,7 @@ TEST_F(Getattr, attr_cache_timeout) */ long timeout_ns = 250'000'000; - EXPECT_LOOKUP(RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.entry_valid = UINT64_MAX; @@ -127,7 +127,7 @@ TEST_F(Getattr, enoent) struct stat sb; const uint64_t ino = 42; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = 0100644; @@ -153,7 +153,7 @@ TEST_F(Getattr, ok) const uint64_t generation = 13; struct stat sb; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/link.cc b/tests/sys/fs/fuse/link.cc new file mode 100644 index 00000000000..1436358f33b --- /dev/null +++ b/tests/sys/fs/fuse/link.cc @@ -0,0 +1,106 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Link: public FuseTest {}; + +TEST_F(Link, emlink) +{ + const char FULLPATH[] = "mountpoint/lnk"; + const char RELPATH[] = "lnk"; + const char FULLDST[] = "mountpoint/dst"; + const char RELDST[] = "dst"; + uint64_t dst_ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = dst_ino; + SET_OUT_HEADER_LEN(out, entry); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(struct fuse_link_in); + return (in->header.opcode == FUSE_LINK && + in->body.link.oldnodeid == dst_ino && + (0 == strcmp(name, RELPATH))); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EMLINK))); + + EXPECT_EQ(-1, link(FULLDST, FULLPATH)); + EXPECT_EQ(EMLINK, errno); +} + +TEST_F(Link, ok) +{ + const char FULLPATH[] = "mountpoint/src"; + const char RELPATH[] = "src"; + const char FULLDST[] = "mountpoint/dst"; + const char RELDST[] = "dst"; + uint64_t dst_ino = 42; + const uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = dst_ino; + SET_OUT_HEADER_LEN(out, entry); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(struct fuse_link_in); + return (in->header.opcode == FUSE_LINK && + in->body.link.oldnodeid == dst_ino && + (0 == strcmp(name, RELPATH))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + })); + + EXPECT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/lookup.cc b/tests/sys/fs/fuse/lookup.cc index 7a623e1f873..f3c8c0b892c 100644 --- a/tests/sys/fs/fuse/lookup.cc +++ b/tests/sys/fs/fuse/lookup.cc @@ -50,7 +50,7 @@ TEST_F(Lookup, DISABLED_attr_cache) const uint64_t ino = 42; struct stat sb; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.nodeid = ino; @@ -115,7 +115,7 @@ TEST_F(Lookup, attr_cache_timeout) */ long timeout_ns = 250'000'000; - EXPECT_LOOKUP(RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.nodeid = ino; @@ -148,7 +148,7 @@ TEST_F(Lookup, enoent) const char FULLPATH[] = "mountpoint/does_not_exist"; const char RELPATH[] = "does_not_exist"; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_NE(0, access(FULLPATH, F_OK)); EXPECT_EQ(ENOENT, errno); } @@ -162,7 +162,7 @@ TEST_F(Lookup, entry_cache) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.entry_valid = UINT64_MAX; @@ -181,7 +181,7 @@ TEST_F(Lookup, entry_cache) /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */ TEST_F(Lookup, DISABLED_entry_cache_negative) { - EXPECT_LOOKUP("does_not_exist").Times(1) + EXPECT_LOOKUP(1, "does_not_exist").Times(1) .WillOnce(Invoke([](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; @@ -206,7 +206,7 @@ TEST_F(Lookup, entry_cache_negative_timeout) */ long timeout_ns = 250'000'000; - EXPECT_LOOKUP(RELPATH).Times(2) + EXPECT_LOOKUP(1, RELPATH).Times(2) .WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; @@ -239,7 +239,7 @@ TEST_F(Lookup, DISABLED_entry_cache_timeout) */ long timeout_ns = 250'000'000; - EXPECT_LOOKUP(RELPATH).Times(2) + EXPECT_LOOKUP(1, RELPATH).Times(2) .WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); @@ -258,7 +258,7 @@ TEST_F(Lookup, ok) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -270,3 +270,33 @@ TEST_F(Lookup, ok) */ ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); } + +// Lookup in a subdirectory of the fuse mount +TEST_F(Lookup, subdir) +{ + const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; + const char DIRPATH[] = "some_dir"; + const char RELPATH[] = "some_file.txt"; + uint64_t dir_ino = 2; + uint64_t file_ino = 3; + + EXPECT_LOOKUP(1, DIRPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = dir_ino; + })); + EXPECT_LOOKUP(dir_ino, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = file_ino; + })); + /* + * access(2) is one of the few syscalls that will not (always) follow + * up a successful VOP_LOOKUP with another VOP. + */ + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); +} + + diff --git a/tests/sys/fs/fuse/mkdir.cc b/tests/sys/fs/fuse/mkdir.cc index a0bcdde092c..b04e3edbf74 100644 --- a/tests/sys/fs/fuse/mkdir.cc +++ b/tests/sys/fs/fuse/mkdir.cc @@ -48,7 +48,7 @@ TEST_F(Mkdir, emlink) const char RELPATH[] = "some_dir"; mode_t mode = 0755; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -77,7 +77,7 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative) uint64_t ino = 42; /* mkdir will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; out->header.unique = in->header.unique; @@ -124,7 +124,7 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) uint64_t ino = 42; /* mkdir will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(RELPATH).Times(1) + EXPECT_LOOKUP(1, RELPATH).Times(1) .WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; @@ -155,7 +155,7 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - EXPECT_LOOKUP(RELPATH).Times(1) + EXPECT_LOOKUP(1, RELPATH).Times(1) .WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; @@ -174,7 +174,7 @@ TEST_F(Mkdir, ok) mode_t mode = 0755; uint64_t ino = 42; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { diff --git a/tests/sys/fs/fuse/mknod.cc b/tests/sys/fs/fuse/mknod.cc index ca38985046a..6e11436c55d 100644 --- a/tests/sys/fs/fuse/mknod.cc +++ b/tests/sys/fs/fuse/mknod.cc @@ -54,7 +54,7 @@ void test_ok(mode_t mode, dev_t dev) { const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -108,7 +108,7 @@ TEST_F(Mknod, DISABLED_eperm) const char RELPATH[] = "some_file.txt"; mode_t mode = S_IFIFO | 0755; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index c85bad13660..1f9c75231f8 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -116,6 +116,20 @@ void sigint_handler(int __unused sig) { quit = 1; } +void debug_fuseop(const mockfs_buf_in *in) +{ + printf("%s ino=%lu", opcode2opname(in->header.opcode), + in->header.nodeid); + switch (in->header.opcode) { + case FUSE_LOOKUP: + printf(" %s", in->body.lookup); + break; + default: + break; + } + printf("\n"); +} + MockFS::MockFS() { struct iovec *iov = NULL; int iovlen = 0; @@ -204,10 +218,8 @@ void MockFS::loop() { read_request(in); if (quit) break; - if (verbosity > 0) { - printf("Got request %s\n", - opcode2opname(in->header.opcode)); - } + if (verbosity > 0) + debug_fuseop(in); if ((pid_t)in->header.pid != m_pid) { /* * Reject any requests from unknown processes. Because diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index a3d1f4975f4..bfd86e8bdec 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -49,10 +49,11 @@ extern "C" { * This must be a macro instead of a method because EXPECT_CALL returns a type * with a deleted constructor. */ -#define EXPECT_LOOKUP(path) \ +#define EXPECT_LOOKUP(parent, path) \ EXPECT_CALL(*m_mock, process( \ ResultOf([=](auto in) { \ return (in->header.opcode == FUSE_LOOKUP && \ + in->header.nodeid == (parent) && \ strcmp(in->body.lookup, (path)) == 0); \ }, Eq(true)), \ _) \ @@ -72,10 +73,12 @@ union fuse_payloads_in { uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; fuse_forget_in forget; fuse_init_in init; + fuse_link_in link; char lookup[0]; fuse_mkdir_in mkdir; fuse_mknod_in mknod; fuse_open_in open; + fuse_rename_in rename; fuse_setattr_in setattr; }; diff --git a/tests/sys/fs/fuse/open.cc b/tests/sys/fs/fuse/open.cc index 9de47d7f2d5..003f78e18e6 100644 --- a/tests/sys/fs/fuse/open.cc +++ b/tests/sys/fs/fuse/open.cc @@ -49,7 +49,7 @@ TEST_F(Open, enoent) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -77,7 +77,7 @@ TEST_F(Open, eperm) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -102,7 +102,7 @@ TEST_F(Open, ok) uint64_t ino = 42; int fd; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/readlink.cc b/tests/sys/fs/fuse/readlink.cc index 11d39a98350..6db5dcfad2a 100644 --- a/tests/sys/fs/fuse/readlink.cc +++ b/tests/sys/fs/fuse/readlink.cc @@ -45,7 +45,7 @@ TEST_F(Readlink, eloop) const uint64_t ino = 42; char buf[80]; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFLNK | 0777; @@ -72,7 +72,7 @@ TEST_F(Readlink, ok) const uint64_t ino = 42; char buf[80]; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFLNK | 0777; diff --git a/tests/sys/fs/fuse/rename.cc b/tests/sys/fs/fuse/rename.cc new file mode 100644 index 00000000000..94ec7d05246 --- /dev/null +++ b/tests/sys/fs/fuse/rename.cc @@ -0,0 +1,188 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Rename: public FuseTest { + public: + int tmpfd = -1; + char tmpfile[80] = "/tmp/fuse.rename.XXXXXX"; + + virtual void TearDown() { + if (tmpfd >= 0) { + close(tmpfd); + unlink(tmpfile); + } + + FuseTest::TearDown(); + } +}; + +// EINVAL, dst is subdir of src +TEST_F(Rename, einval) +{ + const char FULLDST[] = "mountpoint/src/dst"; + const char RELDST[] = "dst"; + const char FULLSRC[] = "mountpoint/src"; + const char RELSRC[] = "src"; + uint64_t src_ino = 42; + + EXPECT_LOOKUP(1, RELSRC).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = src_ino; + SET_OUT_HEADER_LEN(out, entry); + })); + EXPECT_LOOKUP(src_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); + + ASSERT_NE(0, rename(FULLSRC, FULLDST)); + ASSERT_EQ(EINVAL, errno); +} + +// source does not exist +TEST_F(Rename, enoent) +{ + const char FULLDST[] = "mountpoint/dst"; + const char FULLSRC[] = "mountpoint/src"; + const char RELSRC[] = "src"; + // FUSE hardcodes the mountpoint to inocde 1 + + EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke(ReturnErrno(ENOENT))); + + ASSERT_NE(0, rename(FULLSRC, FULLDST)); + ASSERT_EQ(ENOENT, errno); +} + +TEST_F(Rename, exdev) +{ + const char FULLB[] = "mountpoint/src"; + const char RELB[] = "src"; + // FUSE hardcodes the mountpoint to inocde 1 + uint64_t b_ino = 42; + + tmpfd = mkstemp(tmpfile); + ASSERT_LE(0, tmpfd) << strerror(errno); + + EXPECT_LOOKUP(1, RELB).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = b_ino; + SET_OUT_HEADER_LEN(out, entry); + })); + + ASSERT_NE(0, rename(tmpfile, FULLB)); + ASSERT_EQ(EXDEV, errno); + + ASSERT_NE(0, rename(FULLB, tmpfile)); + ASSERT_EQ(EXDEV, errno); +} + +TEST_F(Rename, ok) +{ + const char FULLDST[] = "mountpoint/dst"; + const char RELDST[] = "dst"; + const char FULLSRC[] = "mountpoint/src"; + const char RELSRC[] = "src"; + // FUSE hardcodes the mountpoint to inocde 1 + uint64_t dst_dir_ino = 1; + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + SET_OUT_HEADER_LEN(out, entry); + })); + EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *src = (const char*)in->body.bytes + + sizeof(fuse_rename_in); + const char *dst = src + strlen(src) + 1; + return (in->header.opcode == FUSE_RENAME && + in->body.rename.newdir == dst_dir_ino && + (0 == strcmp(RELDST, dst)) && + (0 == strcmp(RELSRC, src))); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); +} + +// Rename overwrites an existing destination file +TEST_F(Rename, overwrite) +{ + const char FULLDST[] = "mountpoint/dst"; + const char RELDST[] = "dst"; + const char FULLSRC[] = "mountpoint/src"; + const char RELSRC[] = "src"; + // The inode of the already-existing destination file + uint64_t dst_ino = 2; + // FUSE hardcodes the mountpoint to inocde 1 + uint64_t dst_dir_ino = 1; + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + SET_OUT_HEADER_LEN(out, entry); + })); + EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = dst_ino; + SET_OUT_HEADER_LEN(out, entry); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *src = (const char*)in->body.bytes + + sizeof(fuse_rename_in); + const char *dst = src + strlen(src) + 1; + return (in->header.opcode == FUSE_RENAME && + in->body.rename.newdir == dst_dir_ino && + (0 == strcmp(RELDST, dst)) && + (0 == strcmp(RELSRC, src))); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/setattr.cc b/tests/sys/fs/fuse/setattr.cc index 37582be1e4f..26c3db4c4a3 100644 --- a/tests/sys/fs/fuse/setattr.cc +++ b/tests/sys/fs/fuse/setattr.cc @@ -50,7 +50,7 @@ TEST_F(Setattr, chmod) const mode_t oldmode = 0755; const mode_t newmode = 0644; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | oldmode; @@ -88,7 +88,7 @@ TEST_F(Setattr, chown) const uid_t olduser = 33; const uid_t newuser = 44; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -132,7 +132,7 @@ TEST_F(Setattr, eperm) const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0777; @@ -162,7 +162,7 @@ TEST_F(Setattr, fchmod) const mode_t oldmode = 0755; const mode_t newmode = 0644; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | oldmode; @@ -230,7 +230,7 @@ TEST_F(Setattr, ftruncate) const off_t oldsize = 99; const off_t newsize = 12345; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0755; @@ -299,7 +299,7 @@ TEST_F(Setattr, truncate) { const uint64_t oldsize = 100'000'000; const uint64_t newsize = 20'000'000; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -341,7 +341,7 @@ TEST_F(Setattr, utimensat) { {.tv_sec = 7, .tv_nsec = 8}, }; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -417,7 +417,7 @@ TEST_F(Setattr, utimensat_mtime_only) { {.tv_sec = 7, .tv_nsec = 8}, }; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/symlink.cc b/tests/sys/fs/fuse/symlink.cc index e853964b07c..02c1e9c7f4c 100644 --- a/tests/sys/fs/fuse/symlink.cc +++ b/tests/sys/fs/fuse/symlink.cc @@ -44,7 +44,7 @@ TEST_F(Symlink, enospc) const char RELPATH[] = "lnk"; const char dst[] = "dst"; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -55,12 +55,7 @@ TEST_F(Symlink, enospc) (0 == strcmp(name, RELPATH))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -ENOSPC; - out->header.len = sizeof(out->header); - })); - + ).WillOnce(Invoke(ReturnErrno(ENOSPC))); EXPECT_EQ(-1, symlink(dst, FULLPATH)); EXPECT_EQ(ENOSPC, errno); @@ -73,7 +68,7 @@ TEST_F(Symlink, ok) const char dst[] = "dst"; const uint64_t ino = 42; - EXPECT_LOOKUP(RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index d3edc44feba..64a0745e62c 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -40,7 +40,7 @@ class FuseTest : public ::testing::Test { } } - void TearDown() { + virtual void TearDown() { if (m_mock) delete m_mock; } From c7c8f5905100430de352bc8955d03d40b5dd36ea Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 6 Mar 2019 00:38:10 +0000 Subject: [PATCH 14/41] fuse(4): add tests for unlink, rmdir, and statfs Also, combine some common code for sending cacheable negative lookup responses. Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 18 ++++++ tests/sys/fs/fuse/create.cc | 31 +++------- tests/sys/fs/fuse/lookup.cc | 24 +++----- tests/sys/fs/fuse/mkdir.cc | 31 +++------- tests/sys/fs/fuse/mockfs.cc | 44 ++++++++++---- tests/sys/fs/fuse/mockfs.hh | 27 ++++++--- tests/sys/fs/fuse/rename.cc | 105 ++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/rmdir.cc | 90 +++++++++++++++++++++++++++ tests/sys/fs/fuse/statfs.cc | 118 ++++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/unlink.cc | 90 +++++++++++++++++++++++++++ 10 files changed, 500 insertions(+), 78 deletions(-) create mode 100644 tests/sys/fs/fuse/rmdir.cc create mode 100644 tests/sys/fs/fuse/statfs.cc create mode 100644 tests/sys/fs/fuse/unlink.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 44d12f47da2..a2f156ca696 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -14,8 +14,11 @@ ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= rename +ATF_TESTS_CXX+= rmdir ATF_TESTS_CXX+= setattr +ATF_TESTS_CXX+= statfs ATF_TESTS_CXX+= symlink +ATF_TESTS_CXX+= unlink SRCS.access+= access.cc SRCS.access+= getmntopts.c @@ -67,16 +70,31 @@ SRCS.rename+= mockfs.cc SRCS.rename+= rename.cc SRCS.rename+= utils.cc +SRCS.rmdir+= getmntopts.c +SRCS.rmdir+= mockfs.cc +SRCS.rmdir+= rmdir.cc +SRCS.rmdir+= utils.cc + SRCS.setattr+= getmntopts.c SRCS.setattr+= mockfs.cc SRCS.setattr+= setattr.cc SRCS.setattr+= utils.cc +SRCS.statfs+= getmntopts.c +SRCS.statfs+= mockfs.cc +SRCS.statfs+= statfs.cc +SRCS.statfs+= utils.cc + SRCS.symlink+= getmntopts.c SRCS.symlink+= mockfs.cc SRCS.symlink+= symlink.cc SRCS.symlink+= utils.cc +SRCS.unlink+= getmntopts.c +SRCS.unlink+= mockfs.cc +SRCS.unlink+= unlink.cc +SRCS.unlink+= utils.cc + # TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index 46a88e2c677..9506a86ed8e 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -195,21 +195,15 @@ TEST_F(Create, DISABLED_entry_cache_negative) mode_t mode = 0755; uint64_t ino = 42; int fd; + /* + * Set entry_valid = 0 because this test isn't concerned with whether + * or not we actually cache negative entries, only with whether we + * interpret negative cache responses correctly. + */ + struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; /* create will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - /* nodeid means ENOENT and cache it */ - out->body.entry.nodeid = 0; - out->header.unique = in->header.unique; - out->header.error = 0; - /* - * Set entry_valid = 0 because this test isn't concerned with - * whether or not we actually cache negative entries, only with - * whether we interpret negative cache responses correctly. - */ - out->body.entry.entry_valid = 0; - SET_OUT_HEADER_LEN(out, entry); - })); + EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -258,17 +252,12 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) mode_t mode = 0755; uint64_t ino = 42; int fd; + struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; /* create will first do a LOOKUP, adding a negative cache entry */ EXPECT_LOOKUP(1, RELPATH).Times(1) - .WillOnce(Invoke([=](auto in, auto out) { - /* nodeid means ENOENT and cache it */ - out->body.entry.nodeid = 0; - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.entry_valid = UINT64_MAX; - SET_OUT_HEADER_LEN(out, entry); - })).RetiresOnSaturation(); + .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) + .RetiresOnSaturation(); /* Then the CREATE should purge the negative cache entry */ EXPECT_CALL(*m_mock, process( diff --git a/tests/sys/fs/fuse/lookup.cc b/tests/sys/fs/fuse/lookup.cc index f3c8c0b892c..9c31193552c 100644 --- a/tests/sys/fs/fuse/lookup.cc +++ b/tests/sys/fs/fuse/lookup.cc @@ -181,14 +181,11 @@ TEST_F(Lookup, entry_cache) /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */ TEST_F(Lookup, DISABLED_entry_cache_negative) { + struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; + EXPECT_LOOKUP(1, "does_not_exist").Times(1) - .WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.nodeid = 0; - out->body.entry.entry_valid = UINT64_MAX; - SET_OUT_HEADER_LEN(out, entry); - })); + .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))); + EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); EXPECT_EQ(ENOENT, errno); EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); @@ -204,20 +201,15 @@ TEST_F(Lookup, entry_cache_negative_timeout) * The timeout should be longer than the longest plausible time the * daemon would take to complete a write(2) to /dev/fuse, but no longer. */ - long timeout_ns = 250'000'000; + struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 250'000'000}; EXPECT_LOOKUP(1, RELPATH).Times(2) - .WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.nodeid = 0; - out->body.entry.entry_valid_nsec = timeout_ns; - SET_OUT_HEADER_LEN(out, entry); - })); + .WillRepeatedly(Invoke(ReturnNegativeCache(&entry_valid))); + EXPECT_NE(0, access(FULLPATH, F_OK)); EXPECT_EQ(ENOENT, errno); - usleep(2 * timeout_ns / 1000); + usleep(2 * entry_valid.tv_nsec / 1000); /* The cache has timed out; VOP_LOOKUP should requery the daemon*/ EXPECT_NE(0, access(FULLPATH, F_OK)); diff --git a/tests/sys/fs/fuse/mkdir.cc b/tests/sys/fs/fuse/mkdir.cc index b04e3edbf74..f652f11ef5f 100644 --- a/tests/sys/fs/fuse/mkdir.cc +++ b/tests/sys/fs/fuse/mkdir.cc @@ -75,21 +75,15 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative) const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; + /* + * Set entry_valid = 0 because this test isn't concerned with whether + * or not we actually cache negative entries, only with whether we + * interpret negative cache responses correctly. + */ + struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; /* mkdir will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - /* nodeid means ENOENT and cache it */ - out->body.entry.nodeid = 0; - out->header.unique = in->header.unique; - out->header.error = 0; - /* - * Set entry_valid = 0 because this test isn't concerned with - * whether or not we actually cache negative entries, only with - * whether we interpret negative cache responses correctly. - */ - out->body.entry.entry_valid = 0; - SET_OUT_HEADER_LEN(out, entry); - })); + EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -122,17 +116,12 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; + struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; /* mkdir will first do a LOOKUP, adding a negative cache entry */ EXPECT_LOOKUP(1, RELPATH).Times(1) - .WillOnce(Invoke([=](auto in, auto out) { - /* nodeid means ENOENT and cache it */ - out->body.entry.nodeid = 0; - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.entry_valid = UINT64_MAX; - SET_OUT_HEADER_LEN(out, entry); - })).RetiresOnSaturation(); + .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) + .RetiresOnSaturation(); /* Then the MKDIR should purge the negative cache entry */ EXPECT_CALL(*m_mock, process( diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index 1f9c75231f8..57cd58c35cb 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -112,6 +112,21 @@ ReturnErrno(int error) }); } +/* Helper function used for returning negative cache entries for LOOKUP */ +std::function +ReturnNegativeCache(const struct timespec *entry_valid) +{ + return([=](auto in, auto out) { + /* nodeid means ENOENT and cache it */ + out->body.entry.nodeid = 0; + out->header.unique = in->header.unique; + out->header.error = 0; + out->body.entry.entry_valid = entry_valid->tv_sec; + out->body.entry.entry_valid_nsec = entry_valid->tv_nsec; + SET_OUT_HEADER_LEN(out, entry); + }); +} + void sigint_handler(int __unused sig) { quit = 1; } @@ -135,6 +150,9 @@ MockFS::MockFS() { int iovlen = 0; char fdstr[15]; + m_daemon_id = NULL; + quit = 0; + /* * Kyua sets pwd to a testcase-unique tempdir; no need to use * mkdtemp @@ -169,18 +187,14 @@ MockFS::MockFS() { .WillByDefault(Invoke(this, &MockFS::process_default)); init(); - if (pthread_create(&m_thr, NULL, service, (void*)this)) + signal(SIGUSR1, sigint_handler); + if (pthread_create(&m_daemon_id, NULL, service, (void*)this)) throw(std::system_error(errno, std::system_category(), "Couldn't Couldn't start fuse thread")); } MockFS::~MockFS() { - pthread_kill(m_daemon_id, SIGUSR1); - // Closing the /dev/fuse file descriptor first allows unmount to - // succeed even if the daemon doesn't correctly respond to commands - // during the unmount sequence. - close(m_fuse_fd); - pthread_join(m_daemon_id, NULL); + kill_daemon(); ::unmount("mountpoint", MNT_FORCE); rmdir("mountpoint"); } @@ -206,6 +220,18 @@ void MockFS::init() { free(in); } +void MockFS::kill_daemon() { + if (m_daemon_id != NULL) { + pthread_kill(m_daemon_id, SIGUSR1); + // Closing the /dev/fuse file descriptor first allows unmount + // to succeed even if the daemon doesn't correctly respond to + // commands during the unmount sequence. + close(m_fuse_fd); + pthread_join(m_daemon_id, NULL); + m_daemon_id = NULL; + } +} + void MockFS::loop() { mockfs_buf_in *in; mockfs_buf_out out; @@ -258,10 +284,6 @@ void MockFS::read_request(mockfs_buf_in *in) { void* MockFS::service(void *pthr_data) { MockFS *mock_fs = (MockFS*)pthr_data; - mock_fs->m_daemon_id = pthread_self(); - - quit = 0; - signal(SIGUSR1, sigint_handler); mock_fs->loop(); diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index bfd86e8bdec..9950aebd35a 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -37,6 +37,8 @@ extern "C" { #include +#define TIME_T_MAX (std::numeric_limits::max()) + #define SET_OUT_HEADER_LEN(out, variant) { \ (out)->header.len = (sizeof((out)->header) + \ sizeof((out)->body.variant)); \ @@ -79,7 +81,9 @@ union fuse_payloads_in { fuse_mknod_in mknod; fuse_open_in open; fuse_rename_in rename; + char rmdir[0]; fuse_setattr_in setattr; + char unlink[0]; }; struct mockfs_buf_in { @@ -93,6 +97,7 @@ union fuse_payloads_out { fuse_entry_out entry; fuse_init_out init; fuse_open_out open; + fuse_statfs_out statfs; /* * The protocol places no limits on the length of the string. This is * merely convenient for testing. @@ -112,6 +117,10 @@ struct mockfs_buf_out { std::function ReturnErrno(int error); +/* Helper function used for returning negative cache entries for LOOKUP */ +std::function +ReturnNegativeCache(const struct timespec *entry_valid); + /* * Fake FUSE filesystem * @@ -122,7 +131,12 @@ ReturnErrno(int error); */ class MockFS { public: - /* thread id of the fuse daemon thread */ + /* + * thread id of the fuse daemon thread + * + * It must run in a separate thread so it doesn't deadlock with the + * client test code. + */ pthread_t m_daemon_id; private: @@ -132,14 +146,6 @@ class MockFS { /* pid of the test process */ pid_t m_pid; - /* - * Thread that's running the mockfs daemon. - * - * It must run in a separate thread so it doesn't deadlock with the - * client test code. - */ - pthread_t m_thr; - /* Initialize a session after mounting */ void init(); @@ -157,6 +163,9 @@ class MockFS { MockFS(); virtual ~MockFS(); + /* Kill the filesystem daemon without unmounting the filesystem */ + void kill_daemon(); + /* Process FUSE requests endlessly */ void loop(); diff --git a/tests/sys/fs/fuse/rename.cc b/tests/sys/fs/fuse/rename.cc index 94ec7d05246..d6e398db490 100644 --- a/tests/sys/fs/fuse/rename.cc +++ b/tests/sys/fs/fuse/rename.cc @@ -87,6 +87,111 @@ TEST_F(Rename, enoent) ASSERT_EQ(ENOENT, errno); } +/* + * Renaming a file after FUSE_LOOKUP returned a negative cache entry for dst + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ +TEST_F(Rename, DISABLED_entry_cache_negative) +{ + const char FULLDST[] = "mountpoint/dst"; + const char RELDST[] = "dst"; + const char FULLSRC[] = "mountpoint/src"; + const char RELSRC[] = "src"; + // FUSE hardcodes the mountpoint to inocde 1 + uint64_t dst_dir_ino = 1; + uint64_t ino = 42; + /* + * Set entry_valid = 0 because this test isn't concerned with whether + * or not we actually cache negative entries, only with whether we + * interpret negative cache responses correctly. + */ + struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; + + EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + SET_OUT_HEADER_LEN(out, entry); + })); + + /* LOOKUP returns a negative cache entry for dst */ + EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid)); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *src = (const char*)in->body.bytes + + sizeof(fuse_rename_in); + const char *dst = src + strlen(src) + 1; + return (in->header.opcode == FUSE_RENAME && + in->body.rename.newdir == dst_dir_ino && + (0 == strcmp(RELDST, dst)) && + (0 == strcmp(RELSRC, src))); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); +} + +/* + * Renaming a file should purge any negative namecache entries for the dst + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ +TEST_F(Rename, DISABLED_entry_cache_negative_purge) +{ + const char FULLDST[] = "mountpoint/dst"; + const char RELDST[] = "dst"; + const char FULLSRC[] = "mountpoint/src"; + const char RELSRC[] = "src"; + // FUSE hardcodes the mountpoint to inocde 1 + uint64_t dst_dir_ino = 1; + uint64_t ino = 42; + /* + * Set entry_valid = 0 because this test isn't concerned with whether + * or not we actually cache negative entries, only with whether we + * interpret negative cache responses correctly. + */ + struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; + + EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + SET_OUT_HEADER_LEN(out, entry); + })); + + /* LOOKUP returns a negative cache entry for dst */ + EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid)) + .RetiresOnSaturation(); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *src = (const char*)in->body.bytes + + sizeof(fuse_rename_in); + const char *dst = src + strlen(src) + 1; + return (in->header.opcode == FUSE_RENAME && + in->body.rename.newdir == dst_dir_ino && + (0 == strcmp(RELDST, dst)) && + (0 == strcmp(RELSRC, src))); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); + + /* Finally, a subsequent lookup should query the daemon */ + EXPECT_LOOKUP(1, RELDST).Times(1) + .WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = 0; + out->body.entry.nodeid = ino; + out->body.entry.attr.mode = S_IFREG | 0644; + SET_OUT_HEADER_LEN(out, entry); + })); + + ASSERT_EQ(0, access(FULLDST, F_OK)) << strerror(errno); +} + TEST_F(Rename, exdev) { const char FULLB[] = "mountpoint/src"; diff --git a/tests/sys/fs/fuse/rmdir.cc b/tests/sys/fs/fuse/rmdir.cc new file mode 100644 index 00000000000..837c0186271 --- /dev/null +++ b/tests/sys/fs/fuse/rmdir.cc @@ -0,0 +1,90 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Rmdir: public FuseTest {}; + +TEST_F(Rmdir, enotempty) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 2; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RMDIR && + 0 == strcmp(RELPATH, in->body.rmdir) && + in->header.nodeid == 1); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(ENOTEMPTY))); + + ASSERT_NE(0, rmdir(FULLPATH)); + ASSERT_EQ(ENOTEMPTY, errno); +} + +TEST_F(Rmdir, ok) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 2; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RMDIR && + 0 == strcmp(RELPATH, in->body.rmdir) && + in->header.nodeid == 1); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/statfs.cc b/tests/sys/fs/fuse/statfs.cc new file mode 100644 index 00000000000..bf622db39c8 --- /dev/null +++ b/tests/sys/fs/fuse/statfs.cc @@ -0,0 +1,118 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Statfs: public FuseTest {}; + +TEST_F(Statfs, eio) +{ + struct statfs statbuf; + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_STATFS); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EIO))); + + ASSERT_NE(0, statfs("mountpoint", &statbuf)); + ASSERT_EQ(EIO, errno); +} + +/* + * When the daemon is dead but the filesystem is still mounted, fuse(4) fakes + * the statfs(2) response, which is necessary for unmounting. + */ +TEST_F(Statfs, enotconn) +{ + struct statfs statbuf; + char mp[PATH_MAX]; + + m_mock->kill_daemon(); + + ASSERT_NE(NULL, getcwd(mp, PATH_MAX)) << strerror(errno); + strlcat(mp, "/mountpoint", PATH_MAX); + ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); + + EXPECT_EQ(getuid(), statbuf.f_owner); + EXPECT_EQ(0, strcmp("fusefs", statbuf.f_fstypename)); + EXPECT_EQ(0, strcmp("/dev/fuse", statbuf.f_mntfromname)); + EXPECT_EQ(0, strcmp(mp, statbuf.f_mntonname)); +} + +TEST_F(Statfs, ok) +{ + struct statfs statbuf; + char mp[PATH_MAX]; + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_STATFS); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, statfs); + out->body.statfs.st.blocks = 1000; + out->body.statfs.st.bfree = 100; + out->body.statfs.st.bavail = 200; + out->body.statfs.st.files = 5; + out->body.statfs.st.ffree = 6; + out->body.statfs.st.namelen = 128; + out->body.statfs.st.frsize = 1024; + })); + + ASSERT_NE(NULL, getcwd(mp, PATH_MAX)) << strerror(errno); + strlcat(mp, "/mountpoint", PATH_MAX); + ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); + EXPECT_EQ(1024ul, statbuf.f_bsize); + /* + * fuse(4) ignores the filesystem's reported optimal transfer size, and + * chooses a size that works well with the rest of the system instead + */ + EXPECT_EQ(1000ul, statbuf.f_blocks); + EXPECT_EQ(100ul, statbuf.f_bfree); + EXPECT_EQ(200l, statbuf.f_bavail); + EXPECT_EQ(5ul, statbuf.f_files); + EXPECT_EQ(6l, statbuf.f_ffree); + EXPECT_EQ(128u, statbuf.f_namemax); + EXPECT_EQ(getuid(), statbuf.f_owner); + EXPECT_EQ(0, strcmp("fusefs", statbuf.f_fstypename)); + EXPECT_EQ(0, strcmp("/dev/fuse", statbuf.f_mntfromname)); + EXPECT_EQ(0, strcmp(mp, statbuf.f_mntonname)); +} diff --git a/tests/sys/fs/fuse/unlink.cc b/tests/sys/fs/fuse/unlink.cc new file mode 100644 index 00000000000..f103f545d67 --- /dev/null +++ b/tests/sys/fs/fuse/unlink.cc @@ -0,0 +1,90 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Unlink: public FuseTest {}; + +TEST_F(Unlink, eperm) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 1; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_UNLINK && + 0 == strcmp(RELPATH, in->body.unlink) && + in->header.nodeid == 1); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EPERM))); + + ASSERT_NE(0, unlink(FULLPATH)); + ASSERT_EQ(EPERM, errno); +} + +TEST_F(Unlink, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 1; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_UNLINK && + 0 == strcmp(RELPATH, in->body.unlink) && + in->header.nodeid == 1); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); +} From c2e7dba7f8c61ab15a9ee6acea90b5db22e5a182 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Thu, 7 Mar 2019 18:12:34 +0000 Subject: [PATCH 15/41] fuse(4): add tests relating to open(2) flags Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/open.cc | 151 +++++++++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 44 deletions(-) diff --git a/tests/sys/fs/fuse/open.cc b/tests/sys/fs/fuse/open.cc index 003f78e18e6..4f750663a20 100644 --- a/tests/sys/fs/fuse/open.cc +++ b/tests/sys/fs/fuse/open.cc @@ -36,7 +36,58 @@ extern "C" { using namespace testing; -class Open: public FuseTest {}; +class Open: public FuseTest { + +public: + +/* Test an OK open of a file with the given flags */ +void test_ok(int os_flags, int fuse_flags) { + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->body.open.flags == (uint32_t)fuse_flags && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + })); + + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + + fd = open(FULLPATH, os_flags); + EXPECT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} +}; + /* * The fuse daemon fails the request with enoent. This usually indicates a @@ -95,48 +146,60 @@ TEST_F(Open, eperm) EXPECT_EQ(EPERM, errno); } -TEST_F(Open, ok) +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ +TEST_F(Open, DISABLED_o_append) { - const char FULLPATH[] = "mountpoint/some_file.txt"; - const char RELPATH[] = "some_file.txt"; - uint64_t ino = 42; - int fd; - - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); - - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPEN && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(out->header); - SET_OUT_HEADER_LEN(out, open); - })); - - /* Until the attr cache is working, we may send an additional GETATTR */ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_GETATTR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, attr); - out->body.attr.attr.ino = ino; // Must match nodeid - out->body.attr.attr.mode = S_IFREG | 0644; - })); - - fd = open(FULLPATH, O_RDONLY); - EXPECT_LE(0, fd) << strerror(errno); - /* Deliberately leak fd. close(2) will be tested in release.cc */ + test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND); } + +/* The kernel is supposed to filter out this flag */ +TEST_F(Open, o_creat) +{ + test_ok(O_WRONLY | O_CREAT, O_WRONLY); +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ +TEST_F(Open, DISABLED_o_direct) +{ + test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT); +} + +/* The kernel is supposed to filter out this flag */ +TEST_F(Open, o_excl) +{ + test_ok(O_WRONLY | O_EXCL, O_WRONLY); +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236329 */ +TEST_F(Open, DISABLED_o_exec) +{ + test_ok(O_EXEC, O_EXEC); +} + +/* The kernel is supposed to filter out this flag */ +TEST_F(Open, o_noctty) +{ + test_ok(O_WRONLY | O_NOCTTY, O_WRONLY); +} + +TEST_F(Open, o_rdonly) +{ + test_ok(O_RDONLY, O_RDONLY); +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ +TEST_F(Open, DISABLED_o_trunc) +{ + test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC); +} + +TEST_F(Open, o_wronly) +{ + test_ok(O_WRONLY, O_WRONLY); +} + +TEST_F(Open, o_rdwr) +{ + test_ok(O_RDWR, O_RDWR); +} + From 84c4fd1f4897bf26db720ef989935287738268b0 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 8 Mar 2019 02:00:49 +0000 Subject: [PATCH 16/41] fuse(4): add dtrace probe for illegal short writes Sponsored by: The FreeBSD Foundation --- sys/fs/fuse/fuse_io.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sys/fs/fuse/fuse_io.c b/sys/fs/fuse/fuse_io.c index 1f28ccd9cdb..4898cdf5c27 100644 --- a/sys/fs/fuse/fuse_io.c +++ b/sys/fs/fuse/fuse_io.c @@ -369,13 +369,24 @@ fuse_write_directbackend(struct vnode *vp, struct uio *uio, if ((err = fdisp_wait_answ(&fdi))) break; + /* Adjust the uio in the case of short writes */ diff = chunksize - ((struct fuse_write_out *)fdi.answ)->size; if (diff < 0) { err = EINVAL; break; + } else if (diff > 0 && !(ioflag & IO_DIRECT)) { + /* + * XXX We really should be directly checking whether + * the file was opened with FOPEN_DIRECT_IO, not + * IO_DIRECT. IO_DIRECT can be set in multiple ways. + */ + SDT_PROBE2(fuse, , io, trace, 1, + "misbehaving filesystem: short writes are only " + "allowed with direct_io"); } uio->uio_resid += diff; uio->uio_offset -= diff; + if (uio->uio_offset > fvdat->filesize && fuse_data_cache_mode != FUSE_CACHE_UC) { fuse_vnode_setsize(vp, cred, uio->uio_offset); From 1d882fd6a191e429488a2889316fc1d8b9bb717c Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 8 Mar 2019 19:01:31 +0000 Subject: [PATCH 17/41] fuse(4): add tests for FUSE_WRITE and FUSE_RELEASE And a few definitions needed for upcoming FUSE_READ tests Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 14 +- tests/sys/fs/fuse/mockfs.cc | 152 +++++--- tests/sys/fs/fuse/mockfs.hh | 22 +- tests/sys/fs/fuse/release.cc | 199 ++++++++++ tests/sys/fs/fuse/write.cc | 733 +++++++++++++++++++++++++++++++++++ 5 files changed, 1067 insertions(+), 53 deletions(-) create mode 100644 tests/sys/fs/fuse/release.cc create mode 100644 tests/sys/fs/fuse/write.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index a2f156ca696..2c70c5e506c 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -13,12 +13,14 @@ ATF_TESTS_CXX+= mkdir ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink +ATF_TESTS_CXX+= release ATF_TESTS_CXX+= rename ATF_TESTS_CXX+= rmdir ATF_TESTS_CXX+= setattr ATF_TESTS_CXX+= statfs ATF_TESTS_CXX+= symlink ATF_TESTS_CXX+= unlink +ATF_TESTS_CXX+= write SRCS.access+= access.cc SRCS.access+= getmntopts.c @@ -65,6 +67,11 @@ SRCS.readlink+= mockfs.cc SRCS.readlink+= readlink.cc SRCS.readlink+= utils.cc +SRCS.release+= getmntopts.c +SRCS.release+= mockfs.cc +SRCS.release+= release.cc +SRCS.release+= utils.cc + SRCS.rename+= getmntopts.c SRCS.rename+= mockfs.cc SRCS.rename+= rename.cc @@ -95,6 +102,11 @@ SRCS.unlink+= mockfs.cc SRCS.unlink+= unlink.cc SRCS.unlink+= utils.cc +SRCS.write+= getmntopts.c +SRCS.write+= mockfs.cc +SRCS.write+= write.cc +SRCS.write+= utils.cc + # TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 @@ -105,7 +117,7 @@ CFLAGS+= -I${FUSEFS} CFLAGS+= -I${MOUNT} .PATH: ${MOUNT} -LIBADD+= pthread +LIBADD+= util pthread WARNS?= 6 NO_WTHREAD_SAFETY= # GoogleTest fails Clang's thread safety check diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index 57cd58c35cb..a92c11dae3b 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -33,8 +33,10 @@ extern "C" { #include #include #include +#include #include +#include #include #include #include @@ -57,44 +59,44 @@ const char* opcode2opname(uint32_t opcode) const int NUM_OPS = 39; const char* table[NUM_OPS] = { "Unknown (opcode 0)", - "FUSE_LOOKUP", - "FUSE_FORGET", - "FUSE_GETATTR", - "FUSE_SETATTR", - "FUSE_READLINK", - "FUSE_SYMLINK", + "LOOKUP", + "FORGET", + "GETATTR", + "SETATTR", + "READLINK", + "SYMLINK", "Unknown (opcode 7)", - "FUSE_MKNOD", - "FUSE_MKDIR", - "FUSE_UNLINK", - "FUSE_RMDIR", - "FUSE_RENAME", - "FUSE_LINK", - "FUSE_OPEN", - "FUSE_READ", - "FUSE_WRITE", - "FUSE_STATFS", - "FUSE_RELEASE", + "MKNOD", + "MKDIR", + "UNLINK", + "RMDIR", + "RENAME", + "LINK", + "OPEN", + "READ", + "WRITE", + "STATFS", + "RELEASE", "Unknown (opcode 19)", - "FUSE_FSYNC", - "FUSE_SETXATTR", - "FUSE_GETXATTR", - "FUSE_LISTXATTR", - "FUSE_REMOVEXATTR", - "FUSE_FLUSH", - "FUSE_INIT", - "FUSE_OPENDIR", - "FUSE_READDIR", - "FUSE_RELEASEDIR", - "FUSE_FSYNCDIR", - "FUSE_GETLK", - "FUSE_SETLK", - "FUSE_SETLKW", - "FUSE_ACCESS", - "FUSE_CREATE", - "FUSE_INTERRUPT", - "FUSE_BMAP", - "FUSE_DESTROY" + "FSYNC", + "SETXATTR", + "GETXATTR", + "LISTXATTR", + "REMOVEXATTR", + "FLUSH", + "INIT", + "OPENDIR", + "READDIR", + "RELEASEDIR", + "FSYNCDIR", + "GETLK", + "SETLK", + "SETLKW", + "ACCESS", + "CREATE", + "INTERRUPT", + "BMAP", + "DESTROY" }; if (opcode >= NUM_OPS) return ("Unknown (opcode > max)"); @@ -133,12 +135,30 @@ void sigint_handler(int __unused sig) { void debug_fuseop(const mockfs_buf_in *in) { - printf("%s ino=%lu", opcode2opname(in->header.opcode), + printf("%-11s ino=%2lu", opcode2opname(in->header.opcode), in->header.nodeid); + if (verbosity > 1) { + printf(" uid=%5u gid=%5u pid=%5u unique=%lu len=%u", + in->header.uid, in->header.gid, in->header.pid, + in->header.unique, in->header.len); + } switch (in->header.opcode) { case FUSE_LOOKUP: printf(" %s", in->body.lookup); break; + case FUSE_OPEN: + printf(" flags=%#x mode=%#o", + in->body.open.flags, in->body.open.mode); + break; + case FUSE_READ: + printf(" offset=%lu size=%u", in->body.read.offset, + in->body.read.size); + break; + case FUSE_WRITE: + printf(" offset=%lu size=%u flags=%u", + in->body.write.offset, in->body.write.size, + in->body.write.write_flags); + break; default: break; } @@ -201,21 +221,36 @@ MockFS::~MockFS() { void MockFS::init() { mockfs_buf_in *in; - mockfs_buf_out out; + mockfs_buf_out *out; in = (mockfs_buf_in*) malloc(sizeof(*in)); ASSERT_TRUE(in != NULL); + out = (mockfs_buf_out*) malloc(sizeof(*out)); + ASSERT_TRUE(out != NULL); read_request(in); ASSERT_EQ(FUSE_INIT, in->header.opcode); - memset(&out, 0, sizeof(out)); - out.header.unique = in->header.unique; - out.header.error = 0; - out.body.init.major = FUSE_KERNEL_VERSION; - out.body.init.minor = FUSE_KERNEL_MINOR_VERSION; - SET_OUT_HEADER_LEN(&out, init); - write(m_fuse_fd, &out, out.header.len); + memset(out, 0, sizeof(*out)); + out->header.unique = in->header.unique; + out->header.error = 0; + out->body.init.major = FUSE_KERNEL_VERSION; + out->body.init.minor = FUSE_KERNEL_MINOR_VERSION; + + /* + * The default max_write is set to this formula in libfuse, though + * individual filesystems can lower it. The "- 4096" was added in + * commit 154ffe2, with the commit message "fix". + */ + uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096; + /* For testing purposes, it should be distinct from MAXPHYS */ + m_max_write = MIN(default_max_write, MAXPHYS / 2); + out->body.init.max_write = m_max_write; + + /* Default max_readahead is UINT_MAX, though it can be lowered */ + out->body.init.max_readahead = UINT_MAX; + SET_OUT_HEADER_LEN(out, init); + write(m_fuse_fd, out, out->header.len); free(in); } @@ -246,15 +281,15 @@ void MockFS::loop() { break; if (verbosity > 0) debug_fuseop(in); - if ((pid_t)in->header.pid != m_pid) { + if (pid_ok((pid_t)in->header.pid)) { + process(in, &out); + } else { /* * Reject any requests from unknown processes. Because * we actually do mount a filesystem, plenty of * unrelated system daemons may try to access it. */ process_default(in, &out); - } else { - process(in, &out); } if (in->header.opcode == FUSE_FORGET) { /*Alone among the opcodes, FORGET expects no response*/ @@ -267,6 +302,27 @@ void MockFS::loop() { free(in); } +bool MockFS::pid_ok(pid_t pid) { + if (pid == m_pid) { + return (true); + } else { + struct kinfo_proc *ki; + bool ok = false; + + ki = kinfo_getproc(pid); + if (ki == NULL) + return (false); + /* + * Allow access by the aio daemon processes so that our tests + * can use aio functions + */ + if (0 == strncmp("aiod", ki->ki_comm, 4)) + ok = true; + free(ki); + return (ok); + } +} + void MockFS::process_default(const mockfs_buf_in *in, mockfs_buf_out* out) { out->header.unique = in->header.unique; out->header.error = -EOPNOTSUPP; diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 9950aebd35a..2293ab2d401 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -80,10 +80,13 @@ union fuse_payloads_in { fuse_mkdir_in mkdir; fuse_mknod_in mknod; fuse_open_in open; + fuse_read_in read; + fuse_release_in release; fuse_rename_in rename; char rmdir[0]; fuse_setattr_in setattr; char unlink[0]; + fuse_write_in write; }; struct mockfs_buf_in { @@ -94,6 +97,8 @@ struct mockfs_buf_in { union fuse_payloads_out { fuse_attr_out attr; fuse_create_out create; + /* The protocol places no limits on the size of bytes */ + uint8_t bytes[0x2000]; fuse_entry_out entry; fuse_init_out init; fuse_open_out open; @@ -103,6 +108,7 @@ union fuse_payloads_out { * merely convenient for testing. */ char str[80]; + fuse_write_out write; }; struct mockfs_buf_out { @@ -110,12 +116,16 @@ struct mockfs_buf_out { union fuse_payloads_out body; }; +/* A function that can be invoked in place of MockFS::process */ +typedef std::function +ProcessMockerT; + /* * Helper function used for setting an error expectation for any fuse operation. * The operation will return the supplied error */ -std::function -ReturnErrno(int error); +ProcessMockerT ReturnErrno(int error); /* Helper function used for returning negative cache entries for LOOKUP */ std::function @@ -130,7 +140,6 @@ ReturnNegativeCache(const struct timespec *entry_valid); * Operates directly on the fuse(4) kernel API, not the libfuse(3) user api. */ class MockFS { - public: /* * thread id of the fuse daemon thread * @@ -139,7 +148,6 @@ class MockFS { */ pthread_t m_daemon_id; - private: /* file descriptor of /dev/fuse control device */ int m_fuse_fd; @@ -149,6 +157,9 @@ class MockFS { /* Initialize a session after mounting */ void init(); + /* Is pid from a process that might be involved in the test? */ + bool pid_ok(pid_t pid); + /* Default request handler */ void process_default(const mockfs_buf_in*, mockfs_buf_out*); @@ -159,6 +170,9 @@ class MockFS { void read_request(mockfs_buf_in*); public: + /* Maximum size of a FUSE_WRITE write */ + uint32_t m_max_write; + /* Create a new mockfs and mount it to a tempdir */ MockFS(); virtual ~MockFS(); diff --git a/tests/sys/fs/fuse/release.cc b/tests/sys/fs/fuse/release.cc new file mode 100644 index 00000000000..4b76d78058a --- /dev/null +++ b/tests/sys/fs/fuse/release.cc @@ -0,0 +1,199 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Release: public FuseTest { + +const static uint64_t FH = 0xdeadbeef1a7ebabe; + +public: +void expect_getattr(uint64_t ino) +{ + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + +} + +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void expect_open(uint64_t ino, int times) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = Release::FH; + })); + +} + +void expect_release(uint64_t ino, int times, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RELEASE && + in->header.nodeid == ino && + in->body.release.fh == Release::FH); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke(r)); +} +}; + +// TODO: lock owner stuff + +/* If a file descriptor is duplicated, only the last close causes RELEASE */ +TEST_F(Release, dup) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd, fd2; + + expect_lookup(RELPATH, ino); + expect_open(ino, 1); + expect_getattr(ino); + expect_release(ino, 1, ReturnErrno(0)); + + fd = open(FULLPATH, O_RDONLY); + EXPECT_LE(0, fd) << strerror(errno); + + fd2 = dup(fd); + + ASSERT_EQ(0, close(fd2)) << strerror(errno); + ASSERT_EQ(0, close(fd)) << strerror(errno); +} + +/* + * Some FUSE filesystem cache data internally and flush it on release. Such + * filesystems may generate errors during release. On Linux, these get + * returned by close(2). However, POSIX does not require close(2) to return + * this error. FreeBSD's fuse(4) should return EIO if it returns an error at + * all. + */ +/* http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html */ +TEST_F(Release, eio) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino, 1); + expect_getattr(ino); + expect_release(ino, 1, ReturnErrno(EIO)); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_TRUE(0 == close(fd) || errno == EIO) << strerror(errno); +} + +/* + * fuse(4) will issue multiple FUSE_OPEN operations for the same file if it's + * opened with different modes. Each FUSE_OPEN should get its own + * FUSE_RELEASE. + */ +TEST_F(Release, multiple_opens) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd, fd2; + + expect_lookup(RELPATH, ino); + expect_open(ino, 2); + expect_getattr(ino); + expect_release(ino, 2, ReturnErrno(0)); + + fd = open(FULLPATH, O_RDONLY); + EXPECT_LE(0, fd) << strerror(errno); + + fd2 = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd2) << strerror(errno); + + ASSERT_EQ(0, close(fd2)) << strerror(errno); + ASSERT_EQ(0, close(fd)) << strerror(errno); +} + +TEST_F(Release, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino, 1); + expect_getattr(ino); + expect_release(ino, 1, ReturnErrno(0)); + + fd = open(FULLPATH, O_RDONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(0, close(fd)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/write.cc b/tests/sys/fs/fuse/write.cc new file mode 100644 index 00000000000..e8845021a6e --- /dev/null +++ b/tests/sys/fs/fuse/write.cc @@ -0,0 +1,733 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +#include +#include +#include + +#include +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +/* + * TODO: remove FUSE_WRITE_CACHE definition when upgrading to protocol 7.9. + * This bit was actually part of kernel protocol version 7.2, but never + * documented until 7.9 + */ +#ifndef FUSE_WRITE_CACHE +#define FUSE_WRITE_CACHE 1 +#endif + +using namespace testing; + +class Write: public FuseTest { + +public: +int m_maxbcachebuf; + +virtual void SetUp() { + const char *node = "vfs.maxbcachebuf"; + int val = 0; + size_t size = sizeof(val); + + ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) + << strerror(errno); + m_maxbcachebuf = val; + + FuseTest::SetUp(); +} + +const static uint64_t FH = 0xdeadbeef1a7ebabe; +void expect_getattr(uint64_t ino, uint64_t size) +{ + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.size = size; + out->body.attr.attr_valid = UINT64_MAX; + })); + +} + +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void expect_open(uint64_t ino, uint32_t flags, int times) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = Write::FH; + out->body.open.open_flags = flags; + })); +} + +void expect_read(uint64_t ino, uint64_t offset, uint64_t size, + const void *contents) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == Write::FH && + in->body.read.offset == offset && + in->body.read.size == size); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(struct fuse_out_header) + size; + memmove(out->body.bytes, contents, size); + })).RetiresOnSaturation(); + +} + +void expect_release(uint64_t ino, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RELEASE && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke(r)); +} + +void expect_write(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, + uint32_t flags, const void *contents) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *buf = (const char*)in->body.bytes + + sizeof(struct fuse_write_in); + bool pid_ok; + + if (in->body.write.write_flags & FUSE_WRITE_CACHE) + pid_ok = true; + else + pid_ok = (pid_t)in->header.pid == getpid(); + + return (in->header.opcode == FUSE_WRITE && + in->header.nodeid == ino && + in->body.write.fh == Write::FH && + in->body.write.offset == offset && + in->body.write.size == isize && + pid_ok && + in->body.write.write_flags == flags && + 0 == bcmp(buf, contents, isize)); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, write); + out->body.write.size = osize; + })); +} + +}; + +class AioWrite: public Write { +virtual void SetUp() { + const char *node = "vfs.aio.enable_unsafe"; + int val = 0; + size_t size = sizeof(val); + + ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) + << strerror(errno); + // TODO: With GoogleTest 1.8.2, use SKIP instead + if (!val) + FAIL() << "vfs.aio.enable_unsafe must be set for this test"; + FuseTest::SetUp(); +} +}; + +/* Tests for the write-through cache mode */ +class WriteThrough: public Write { + +virtual void SetUp() { + const char *cache_mode_node = "vfs.fuse.data_cache_mode"; + const char *sync_resize_node = "vfs.fuse.sync_resize"; + int val = 0; + size_t size = sizeof(val); + + ASSERT_EQ(0, sysctlbyname(cache_mode_node, &val, &size, NULL, 0)) + << strerror(errno); + // TODO: With GoogleTest 1.8.2, use SKIP instead + if (val != 1) + FAIL() << "vfs.fuse.data_cache_mode must be set to 1 " + "(writethrough) for this test"; + + ASSERT_EQ(0, sysctlbyname(sync_resize_node, &val, &size, NULL, 0)) + << strerror(errno); + if (val != 0) + FAIL() << "vfs.fuse.sync_resize must be set to 0 for this test." + " That sysctl will probably be removed soon."; + + FuseTest::SetUp(); +} + +}; + +/* Tests for the writeback cache mode */ +class WriteBack: public Write { + +virtual void SetUp() { + const char *node = "vfs.fuse.data_cache_mode"; + int val = 0; + size_t size = sizeof(val); + + ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) + << strerror(errno); + // TODO: With GoogleTest 1.8.2, use SKIP instead + if (val != 2) + FAIL() << "vfs.fuse.data_cache_mode must be set to 2 " + "(writeback) for this test"; + FuseTest::SetUp(); +} + +}; + +/* AIO writes need to set the header's pid field correctly */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ +TEST_F(AioWrite, DISABLED_aio_write) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + uint64_t offset = 4096; + int fd; + ssize_t bufsize = strlen(CONTENTS); + struct aiocb iocb, *piocb; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + iocb.aio_nbytes = bufsize; + iocb.aio_fildes = fd; + iocb.aio_buf = (void *)CONTENTS; + iocb.aio_offset = offset; + iocb.aio_sigevent.sigev_notify = SIGEV_NONE; + ASSERT_EQ(0, aio_write(&iocb)) << strerror(errno); + ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * When a file is opened with O_APPEND, we should forward that flag to + * FUSE_OPEN (tested by Open.o_append) but still attempt to calculate the + * offset internally. That way we'll work both with filesystems that + * understand O_APPEND (and ignore the offset) and filesystems that don't (and + * simply use the offset). + * + * Note that verifying the O_APPEND flag in FUSE_OPEN is done in the + * Open.o_append test. + */ +TEST_F(Write, append) +{ + const ssize_t BUFSIZE = 9; + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char CONTENTS[BUFSIZE] = "abcdefgh"; + uint64_t ino = 42; + /* + * Set offset to a maxbcachebuf boundary so we don't need to RMW when + * using writeback caching + */ + uint64_t initial_offset = m_maxbcachebuf; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, initial_offset); + expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, 0, CONTENTS); + + /* Must open O_RDWR or fuse(4) implicitly sets direct_io */ + fd = open(FULLPATH, O_RDWR | O_APPEND); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +TEST_F(Write, append_direct_io) +{ + const ssize_t BUFSIZE = 9; + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char CONTENTS[BUFSIZE] = "abcdefgh"; + uint64_t ino = 42; + uint64_t initial_offset = 4096; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino, FOPEN_DIRECT_IO, 1); + expect_getattr(ino, initial_offset); + expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, 0, CONTENTS); + + fd = open(FULLPATH, O_WRONLY | O_APPEND); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(BUFSIZE, write(fd, CONTENTS, BUFSIZE)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* A direct write should evict any overlapping cached data */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235774 */ +TEST_F(Write, DISABLED_direct_io_evicts_cache) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char CONTENTS0[] = "abcdefgh"; + const char CONTENTS1[] = "ijklmnop"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS0) + 1; + char readbuf[bufsize]; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + expect_read(ino, 0, bufsize, CONTENTS0); + expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS1); + + fd = open(FULLPATH, O_RDWR); + EXPECT_LE(0, fd) << strerror(errno); + + // Prime cache + ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); + + // Write directly, evicting cache + ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno); + ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); + ASSERT_EQ(bufsize, write(fd, CONTENTS1, bufsize)) << strerror(errno); + + // Read again. Cache should be bypassed + expect_read(ino, 0, bufsize, CONTENTS1); + ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno); + ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); + ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); + ASSERT_STREQ(readbuf, CONTENTS1); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * When the direct_io option is used, filesystems are allowed to write less + * data than requested + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236381 */ +TEST_F(Write, DISABLED_direct_io_short_write) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefghijklmnop"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + ssize_t halfbufsize = bufsize / 2; + const char *halfcontents = CONTENTS + halfbufsize; + + expect_lookup(RELPATH, ino); + expect_open(ino, FOPEN_DIRECT_IO, 1); + expect_getattr(ino, 0); + expect_write(ino, 0, bufsize, halfbufsize, 0, CONTENTS); + expect_write(ino, halfbufsize, halfbufsize, halfbufsize, 0, + halfcontents); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * An insidious edge case: the filesystem returns a short write, and the + * difference between what we requested and what it actually wrote crosses an + * iov element boundary + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236381 */ +TEST_F(Write, DISABLED_direct_io_short_write_iov) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS0 = "abcdefgh"; + const char *CONTENTS1 = "ijklmnop"; + const char *EXPECTED0 = "abcdefghijklmnop"; + const char *EXPECTED1 = "hijklmnop"; + uint64_t ino = 42; + int fd; + ssize_t size0 = strlen(CONTENTS0) - 1; + ssize_t size1 = strlen(CONTENTS1) + 1; + ssize_t totalsize = size0 + size1; + struct iovec iov[2]; + + expect_lookup(RELPATH, ino); + expect_open(ino, FOPEN_DIRECT_IO, 1); + expect_getattr(ino, 0); + expect_write(ino, 0, totalsize, size0, 0, EXPECTED0); + expect_write(ino, size0, size1, size1, 0, EXPECTED1); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + iov[0].iov_base = (void*)CONTENTS0; + iov[0].iov_len = strlen(CONTENTS0); + iov[1].iov_base = (void*)CONTENTS1; + iov[1].iov_len = strlen(CONTENTS1); + ASSERT_EQ(totalsize, writev(fd, iov, 2)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * If the kernel cannot be sure which uid, gid, or pid was responsible for a + * write, then it must set the FUSE_WRITE_CACHE bit + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236378 */ +// TODO: check vfs.fuse.mmap_enable +TEST_F(Write, DISABLED_mmap) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + void *p; + uint64_t offset = 10; + size_t len; + void *zeros, *expected; + + len = getpagesize(); + + zeros = calloc(1, len); + ASSERT_NE(NULL, zeros); + expected = calloc(1, len); + ASSERT_NE(NULL, expected); + memmove((uint8_t*)expected + offset, CONTENTS, bufsize); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, len); + expect_read(ino, 0, len, zeros); + /* + * Writes from the pager may or may not be associated with the correct + * pid, so they must set FUSE_WRITE_CACHE + */ + expect_write(ino, 0, len, len, FUSE_WRITE_CACHE, expected); + expect_release(ino, ReturnErrno(0)); + + fd = open(FULLPATH, O_RDWR); + EXPECT_LE(0, fd) << strerror(errno); + + p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + ASSERT_NE(MAP_FAILED, p) << strerror(errno); + + memmove((uint8_t*)p + offset, CONTENTS, bufsize); + + ASSERT_EQ(0, munmap(p, len)) << strerror(errno); + close(fd); // Write mmap'd data on close + + free(expected); + free(zeros); +} + +TEST_F(Write, pwrite) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + uint64_t offset = 4096; + int fd; + ssize_t bufsize = strlen(CONTENTS); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, pwrite(fd, CONTENTS, bufsize, offset)) + << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +TEST_F(Write, write) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* fuse(4) should not issue writes of greater size than the daemon requests */ +TEST_F(Write, write_large) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + int *contents; + uint64_t ino = 42; + int fd; + ssize_t halfbufsize, bufsize; + + halfbufsize = m_mock->m_max_write; + bufsize = halfbufsize * 2; + contents = (int*)malloc(bufsize); + ASSERT_NE(NULL, contents); + for (int i = 0; i < (int)bufsize / (int)sizeof(i); i++) { + contents[i] = i; + } + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, 0, halfbufsize, halfbufsize, 0, contents); + expect_write(ino, halfbufsize, halfbufsize, halfbufsize, 0, + &contents[halfbufsize / sizeof(int)]); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, write(fd, contents, bufsize)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ + + free(contents); +} + +TEST_F(Write, write_nothing) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = ""; + uint64_t ino = 42; + int fd; + ssize_t bufsize = 0; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * Without direct_io, writes should be committed to cache + */ +TEST_F(WriteBack, writeback) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + char readbuf[bufsize]; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); + + fd = open(FULLPATH, O_RDWR); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + /* + * A subsequent read should be serviced by cache, without querying the + * filesystem daemon + */ + ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); + ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * With O_DIRECT, writes should be not committed to cache. Admittedly this is + * an odd test, because it would be unusual to use O_DIRECT for writes but not + * reads. + */ +TEST_F(WriteBack, o_direct) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + char readbuf[bufsize]; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); + expect_read(ino, 0, bufsize, CONTENTS); + + fd = open(FULLPATH, O_RDWR | O_DIRECT); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + /* A subsequent read must query the daemon because cache is empty */ + ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); + ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno); + ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * Without direct_io, writes should be committed to cache + */ +/* + * Disabled because we don't yet implement write-through caching. No bugzilla + * entry, because that's a feature request, not a bug. + */ +TEST_F(WriteThrough, DISABLED_writethrough) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + char readbuf[bufsize]; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); + + fd = open(FULLPATH, O_RDWR); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + /* + * A subsequent read should be serviced by cache, without querying the + * filesystem daemon + */ + ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* With writethrough caching, writes update the cached file size */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ +TEST_F(WriteThrough, DISABLED_update_file_size) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + struct stat sb; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).Times(2) + .WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.size = 0; + out->body.attr.attr_valid = UINT64_MAX; + })); + expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); + + fd = open(FULLPATH, O_RDWR); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + /* Get cached attributes */ + ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); + ASSERT_EQ(bufsize, sb.st_size); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} From e071c64b4cc4f5ca7bba3d7a9504d6df6feaad85 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 8 Mar 2019 23:07:51 +0000 Subject: [PATCH 18/41] fuse(4): Add some tests for FUSE_FLUSH PR: 236405 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 6 ++ tests/sys/fs/fuse/flush.cc | 193 ++++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.hh | 1 + 3 files changed, 200 insertions(+) create mode 100644 tests/sys/fs/fuse/flush.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 2c70c5e506c..f3993e8af94 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -6,6 +6,7 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create +ATF_TESTS_CXX+= flush ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= link ATF_TESTS_CXX+= lookup @@ -32,6 +33,11 @@ SRCS.create+= getmntopts.c SRCS.create+= mockfs.cc SRCS.create+= utils.cc +SRCS.flush+= flush.cc +SRCS.flush+= getmntopts.c +SRCS.flush+= mockfs.cc +SRCS.flush+= utils.cc + SRCS.getattr+= getattr.cc SRCS.getattr+= getmntopts.c SRCS.getattr+= mockfs.cc diff --git a/tests/sys/fs/fuse/flush.cc b/tests/sys/fs/fuse/flush.cc new file mode 100644 index 00000000000..9156da34b54 --- /dev/null +++ b/tests/sys/fs/fuse/flush.cc @@ -0,0 +1,193 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Flush: public FuseTest { + +const static uint64_t FH = 0xdeadbeef1a7ebabe; + +public: +void expect_flush(uint64_t ino, int times, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_FLUSH && + in->header.nodeid == ino && + in->body.flush.fh == Flush::FH); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke(r)); +} + +void expect_getattr(uint64_t ino) +{ + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + +} + +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void expect_open(uint64_t ino, int times) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = Flush::FH; + })); + +} + +/* + * When testing FUSE_FLUSH, the FUSE_RELEASE calls are uninteresting. This + * expectation will silence googlemock warnings + */ +void expect_release() +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RELEASE); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke(ReturnErrno(0))); +} +}; + +// TODO: lock_owner stuff + +/* If a file descriptor is duplicated, every close causes FLUSH */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ +TEST_F(Flush, DISABLED_dup) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd, fd2; + + expect_lookup(RELPATH, ino); + expect_open(ino, 1); + expect_getattr(ino); + expect_flush(ino, 2, ReturnErrno(0)); + expect_release(); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + fd2 = dup(fd); + + ASSERT_EQ(0, close(fd2)) << strerror(errno); + ASSERT_EQ(0, close(fd)) << strerror(errno); +} + +/* + * Some FUSE filesystem cache data internally and flush it on release. Such + * filesystems may generate errors during release. On Linux, these get + * returned by close(2). However, POSIX does not require close(2) to return + * this error. FreeBSD's fuse(4) should return EIO if it returns an error at + * all. + */ +/* http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ +TEST_F(Flush, DISABLED_eio) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino, 1); + expect_getattr(ino); + expect_flush(ino, 1, ReturnErrno(EIO)); + expect_release(); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_TRUE(0 == close(fd) || errno == EIO) << strerror(errno); +} + +/* A FUSE_FLUSH should be sent on close(2) */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ +TEST_F(Flush, DISABLED_flush) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino, 1); + expect_getattr(ino); + expect_flush(ino, 1, ReturnErrno(0)); + expect_release(); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + ASSERT_TRUE(0 == close(fd)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 2293ab2d401..ab9902bf8a4 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -73,6 +73,7 @@ union fuse_payloads_in { fuse_access_in access; /* value is from fuse_kern_chan.c in fusefs-libs */ uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; + fuse_flush_in flush; fuse_forget_in forget; fuse_init_in init; fuse_link_in link; From e825cfb7757f684edbbff424a11892bfe7a7eb79 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Mon, 11 Mar 2019 18:28:20 +0000 Subject: [PATCH 19/41] fuse(4): add tests for FUSE_READ PR: 236379 PR: 236466 PR: 236472 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 6 + tests/sys/fs/fuse/mockfs.cc | 6 +- tests/sys/fs/fuse/mockfs.hh | 7 +- tests/sys/fs/fuse/read.cc | 566 ++++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/utils.cc | 16 + tests/sys/fs/fuse/utils.hh | 18 +- tests/sys/fs/fuse/write.cc | 15 - 7 files changed, 607 insertions(+), 27 deletions(-) create mode 100644 tests/sys/fs/fuse/read.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index f3993e8af94..a0bf2878103 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -13,6 +13,7 @@ ATF_TESTS_CXX+= lookup ATF_TESTS_CXX+= mkdir ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open +ATF_TESTS_CXX+= read ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= release ATF_TESTS_CXX+= rename @@ -68,6 +69,11 @@ SRCS.open+= mockfs.cc SRCS.open+= open.cc SRCS.open+= utils.cc +SRCS.read+= getmntopts.c +SRCS.read+= mockfs.cc +SRCS.read+= read.cc +SRCS.read+= utils.cc + SRCS.readlink+= getmntopts.c SRCS.readlink+= mockfs.cc SRCS.readlink+= readlink.cc diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index a92c11dae3b..75046ad6671 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -165,12 +165,13 @@ void debug_fuseop(const mockfs_buf_in *in) printf("\n"); } -MockFS::MockFS() { +MockFS::MockFS(int max_readahead) { struct iovec *iov = NULL; int iovlen = 0; char fdstr[15]; m_daemon_id = NULL; + m_maxreadahead = max_readahead; quit = 0; /* @@ -247,8 +248,7 @@ void MockFS::init() { m_max_write = MIN(default_max_write, MAXPHYS / 2); out->body.init.max_write = m_max_write; - /* Default max_readahead is UINT_MAX, though it can be lowered */ - out->body.init.max_readahead = UINT_MAX; + out->body.init.max_readahead = m_maxreadahead; SET_OUT_HEADER_LEN(out, init); write(m_fuse_fd, out, out->header.len); diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index ab9902bf8a4..8a706eec4a4 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -99,7 +99,7 @@ union fuse_payloads_out { fuse_attr_out attr; fuse_create_out create; /* The protocol places no limits on the size of bytes */ - uint8_t bytes[0x2000]; + uint8_t bytes[0x20000]; fuse_entry_out entry; fuse_init_out init; fuse_open_out open; @@ -152,6 +152,9 @@ class MockFS { /* file descriptor of /dev/fuse control device */ int m_fuse_fd; + /* The max_readahead filesystem option */ + uint32_t m_maxreadahead; + /* pid of the test process */ pid_t m_pid; @@ -175,7 +178,7 @@ class MockFS { uint32_t m_max_write; /* Create a new mockfs and mount it to a tempdir */ - MockFS(); + MockFS(int max_readahead); virtual ~MockFS(); /* Kill the filesystem daemon without unmounting the filesystem */ diff --git a/tests/sys/fs/fuse/read.cc b/tests/sys/fs/fuse/read.cc new file mode 100644 index 00000000000..d4cb5059150 --- /dev/null +++ b/tests/sys/fs/fuse/read.cc @@ -0,0 +1,566 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +#include +#include +#include + +#include +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Read: public FuseTest { + +public: +const static uint64_t FH = 0xdeadbeef1a7ebabe; +void expect_getattr(uint64_t ino, uint64_t size) +{ + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.size = size; + })); + +} + +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void expect_open(uint64_t ino, uint32_t flags, int times) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = Read::FH; + out->body.open.open_flags = flags; + })); +} + +void expect_read(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, + const void *contents) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == Read::FH && + in->body.read.offset == offset && + in->body.read.size == isize); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(struct fuse_out_header) + osize; + memmove(out->body.bytes, contents, osize); + })).RetiresOnSaturation(); +} +}; + +class AioRead: public Read { +virtual void SetUp() { + const char *node = "vfs.aio.enable_unsafe"; + int val = 0; + size_t size = sizeof(val); + + ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) + << strerror(errno); + // TODO: With GoogleTest 1.8.2, use SKIP instead + if (!val) + FAIL() << "vfs.aio.enable_unsafe must be set for this test"; + FuseTest::SetUp(); +} +}; + +class ReadAhead: public Read, public WithParamInterface { + virtual void SetUp() { + m_maxreadahead = GetParam(); + Read::SetUp(); + } +}; + +/* AIO reads need to set the header's pid field correctly */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ +TEST_F(AioRead, aio_read) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + char buf[bufsize]; + struct aiocb iocb, *piocb; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + expect_read(ino, 0, bufsize, bufsize, CONTENTS); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + iocb.aio_nbytes = bufsize; + iocb.aio_fildes = fd; + iocb.aio_buf = buf; + iocb.aio_offset = 0; + iocb.aio_sigevent.sigev_notify = SIGEV_NONE; + ASSERT_EQ(0, aio_read(&iocb)) << strerror(errno); + ASSERT_EQ(bufsize, aio_waitcomplete(&piocb, NULL)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* 0-length reads shouldn't cause any confusion */ +TEST_F(Read, direct_io_read_nothing) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + uint64_t offset = 100; + char buf[80]; + + expect_lookup(RELPATH, ino); + expect_open(ino, FOPEN_DIRECT_IO, 1); + expect_getattr(ino, offset + 1000); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(0, pread(fd, buf, 0, offset)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * With direct_io, reads should not fill the cache. They should go straight to + * the daemon + */ +TEST_F(Read, direct_io_pread) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + uint64_t offset = 100; + ssize_t bufsize = strlen(CONTENTS); + char buf[bufsize]; + + expect_lookup(RELPATH, ino); + expect_open(ino, FOPEN_DIRECT_IO, 1); + expect_getattr(ino, offset + bufsize); + expect_read(ino, offset, bufsize, bufsize, CONTENTS); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * With direct_io, filesystems are allowed to return less data than is + * requested. fuse(4) should return a short read to userland. + */ +TEST_F(Read, direct_io_short_read) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefghijklmnop"; + uint64_t ino = 42; + int fd; + uint64_t offset = 100; + ssize_t bufsize = strlen(CONTENTS); + ssize_t halfbufsize = bufsize / 2; + char buf[bufsize]; + + expect_lookup(RELPATH, ino); + expect_open(ino, FOPEN_DIRECT_IO, 1); + expect_getattr(ino, offset + bufsize); + expect_read(ino, offset, bufsize, halfbufsize, CONTENTS); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(halfbufsize, pread(fd, buf, bufsize, offset)) + << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS, halfbufsize)); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +TEST_F(Read, eio) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + char buf[bufsize]; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EIO))); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(-1, read(fd, buf, bufsize)) << strerror(errno); + ASSERT_EQ(EIO, errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +TEST_F(Read, mmap) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t len; + ssize_t bufsize = strlen(CONTENTS); + void *p; + //char buf[bufsize]; + + len = getpagesize(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + /* mmap may legitimately try to read more data than is available */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == Read::FH && + in->body.read.offset == 0 && + in->body.read.size >= bufsize); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(struct fuse_out_header) + bufsize; + memmove(out->body.bytes, CONTENTS, bufsize); + })); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); + ASSERT_NE(MAP_FAILED, p) << strerror(errno); + + ASSERT_EQ(0, memcmp(p, CONTENTS, bufsize)); + + ASSERT_EQ(0, munmap(p, len)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass + * cache and to straight to the daemon + */ +TEST_F(Read, o_direct) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + char buf[bufsize]; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + expect_read(ino, 0, bufsize, bufsize, CONTENTS); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + // Fill the cache + ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); + + // Reads with o_direct should bypass the cache + expect_read(ino, 0, bufsize, bufsize, CONTENTS); + ASSERT_EQ(0, fcntl(fd, F_SETFL, O_DIRECT)) << strerror(errno); + ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); + ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +TEST_F(Read, pread) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + /* + * Set offset to a maxbcachebuf boundary so we'll be sure what offset + * to read from. Without this, the read might start at a lower offset. + */ + uint64_t offset = m_maxbcachebuf; + ssize_t bufsize = strlen(CONTENTS); + char buf[bufsize]; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, offset + bufsize); + expect_read(ino, offset, bufsize, bufsize, CONTENTS); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, pread(fd, buf, bufsize, offset)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +TEST_F(Read, read) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + char buf[bufsize]; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + expect_read(ino, 0, bufsize, bufsize, CONTENTS); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* If the filesystem allows it, the kernel should try to readahead */ +TEST_F(Read, default_readahead) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS0 = "abcdefghijklmnop"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = 8; + /* hard-coded in fuse_internal.c */ + size_t default_maxreadahead = 65536; + ssize_t filesize = default_maxreadahead * 2; + char *contents; + char buf[bufsize]; + const char *contents1 = CONTENTS0 + bufsize; + + contents = (char*)calloc(1, filesize); + ASSERT_NE(NULL, contents); + memmove(contents, CONTENTS0, strlen(CONTENTS0)); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, filesize); + expect_read(ino, 0, default_maxreadahead, default_maxreadahead, + contents); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize)); + + /* A subsequent read should be serviced by cache */ + ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, contents1, bufsize)); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* Reading with sendfile should work (though it obviously won't be 0-copy) */ +TEST_F(Read, sendfile) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + char buf[bufsize]; + int sp[2]; + off_t sbytes; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + /* Like mmap, sendfile may request more data than is available */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == Read::FH && + in->body.read.offset == 0 && + in->body.read.size >= bufsize); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(struct fuse_out_header) + bufsize; + memmove(out->body.bytes, CONTENTS, bufsize); + })); + + ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp)) + << strerror(errno); + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0)) + << strerror(errno); + ASSERT_EQ(bufsize, read(sp[0], buf, bufsize)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); + + close(sp[1]); + close(sp[0]); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* sendfile should fail gracefully if fuse declines the read */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236466 */ +TEST_F(Read, DISABLED_sendfile_eio) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + int sp[2]; + off_t sbytes; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EIO))); + + ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp)) + << strerror(errno); + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_NE(0, sendfile(fd, sp[1], 0, bufsize, NULL, &sbytes, 0)); + + close(sp[1]); + close(sp[0]); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* fuse(4) should honor the filesystem's requested m_readahead parameter */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236472 */ +TEST_P(ReadAhead, DISABLED_readahead) { + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS0 = "abcdefghijklmnop"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = 8; + ssize_t filesize = m_maxbcachebuf * 2; + char *contents; + char buf[bufsize]; + + ASSERT_TRUE(GetParam() < (uint32_t)m_maxbcachebuf) + << "Test assumes that max_readahead < maxbcachebuf"; + + contents = (char*)calloc(1, filesize); + ASSERT_NE(NULL, contents); + memmove(contents, CONTENTS0, strlen(CONTENTS0)); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, filesize); + /* fuse(4) should only read ahead the allowed amount */ + expect_read(ino, 0, GetParam(), GetParam(), contents); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS0, bufsize)); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +INSTANTIATE_TEST_CASE_P(RA, ReadAhead, ::testing::Values(0u, 2048u)); diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index 654b5082725..3f59710840b 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -65,6 +65,22 @@ class FuseEnv: public ::testing::Environment { } }; +void FuseTest::SetUp() { + const char *node = "vfs.maxbcachebuf"; + int val = 0; + size_t size = sizeof(val); + + ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) + << strerror(errno); + m_maxbcachebuf = val; + + try { + m_mock = new MockFS(m_maxreadahead); + } catch (std::system_error err) { + FAIL() << err.what(); + } +} + static void usage(char* progname) { fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname); exit(2); diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index 64a0745e62c..99650142bff 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -29,16 +29,20 @@ class FuseTest : public ::testing::Test { protected: + uint32_t m_maxreadahead; MockFS *m_mock = NULL; public: - virtual void SetUp() { - try { - m_mock = new MockFS{}; - } catch (std::system_error err) { - FAIL() << err.what(); - } - } + int m_maxbcachebuf; + + /* + * libfuse's default max_readahead is UINT_MAX, though it can be + * lowered + */ + FuseTest(): FuseTest(UINT_MAX) {} + FuseTest(uint32_t maxreadahead): m_maxreadahead(maxreadahead) {} + + virtual void SetUp(); virtual void TearDown() { if (m_mock) diff --git a/tests/sys/fs/fuse/write.cc b/tests/sys/fs/fuse/write.cc index e8845021a6e..4b452c9a152 100644 --- a/tests/sys/fs/fuse/write.cc +++ b/tests/sys/fs/fuse/write.cc @@ -56,20 +56,6 @@ using namespace testing; class Write: public FuseTest { public: -int m_maxbcachebuf; - -virtual void SetUp() { - const char *node = "vfs.maxbcachebuf"; - int val = 0; - size_t size = sizeof(val); - - ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0)) - << strerror(errno); - m_maxbcachebuf = val; - - FuseTest::SetUp(); -} - const static uint64_t FH = 0xdeadbeef1a7ebabe; void expect_getattr(uint64_t ino, uint64_t size) { @@ -137,7 +123,6 @@ void expect_read(uint64_t ino, uint64_t offset, uint64_t size, out->header.len = sizeof(struct fuse_out_header) + size; memmove(out->body.bytes, contents, size); })).RetiresOnSaturation(); - } void expect_release(uint64_t ino, ProcessMockerT r) From da1200c90f3580f960303a5029c937707fe661b2 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Mon, 11 Mar 2019 19:10:48 +0000 Subject: [PATCH 20/41] Update copyright info in fuse tests * Add SPDX tags * Remove "All Rights Reserved", with permission of emaste (FBSD Foundation) Reported by: emaste Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/access.cc | 3 ++- tests/sys/fs/fuse/create.cc | 3 ++- tests/sys/fs/fuse/flush.cc | 3 ++- tests/sys/fs/fuse/getattr.cc | 3 ++- tests/sys/fs/fuse/link.cc | 3 ++- tests/sys/fs/fuse/lookup.cc | 3 ++- tests/sys/fs/fuse/mkdir.cc | 3 ++- tests/sys/fs/fuse/mknod.cc | 3 ++- tests/sys/fs/fuse/mockfs.cc | 3 ++- tests/sys/fs/fuse/mockfs.hh | 3 ++- tests/sys/fs/fuse/open.cc | 3 ++- tests/sys/fs/fuse/read.cc | 2 ++ tests/sys/fs/fuse/readlink.cc | 3 ++- tests/sys/fs/fuse/release.cc | 3 ++- tests/sys/fs/fuse/rename.cc | 3 ++- tests/sys/fs/fuse/rmdir.cc | 3 ++- tests/sys/fs/fuse/setattr.cc | 3 ++- tests/sys/fs/fuse/statfs.cc | 3 ++- tests/sys/fs/fuse/symlink.cc | 3 ++- tests/sys/fs/fuse/utils.cc | 3 ++- tests/sys/fs/fuse/utils.hh | 3 ++- tests/sys/fs/fuse/write.cc | 3 ++- 22 files changed, 44 insertions(+), 21 deletions(-) diff --git a/tests/sys/fs/fuse/access.cc b/tests/sys/fs/fuse/access.cc index aea765002aa..9e8e896915c 100644 --- a/tests/sys/fs/fuse/access.cc +++ b/tests/sys/fs/fuse/access.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index 9506a86ed8e..e29fc8b6643 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/flush.cc b/tests/sys/fs/fuse/flush.cc index 9156da34b54..98ffc88bd3c 100644 --- a/tests/sys/fs/fuse/flush.cc +++ b/tests/sys/fs/fuse/flush.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/getattr.cc b/tests/sys/fs/fuse/getattr.cc index d9b5b36a640..630bdf55294 100644 --- a/tests/sys/fs/fuse/getattr.cc +++ b/tests/sys/fs/fuse/getattr.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/link.cc b/tests/sys/fs/fuse/link.cc index 1436358f33b..65acc272629 100644 --- a/tests/sys/fs/fuse/link.cc +++ b/tests/sys/fs/fuse/link.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/lookup.cc b/tests/sys/fs/fuse/lookup.cc index 9c31193552c..13fe5d370a6 100644 --- a/tests/sys/fs/fuse/lookup.cc +++ b/tests/sys/fs/fuse/lookup.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/mkdir.cc b/tests/sys/fs/fuse/mkdir.cc index f652f11ef5f..25c07421053 100644 --- a/tests/sys/fs/fuse/mkdir.cc +++ b/tests/sys/fs/fuse/mkdir.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/mknod.cc b/tests/sys/fs/fuse/mknod.cc index 6e11436c55d..078d67d03d2 100644 --- a/tests/sys/fs/fuse/mknod.cc +++ b/tests/sys/fs/fuse/mknod.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index 75046ad6671..b1f0c08a2f2 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 8a706eec4a4..66276a38c54 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/open.cc b/tests/sys/fs/fuse/open.cc index 4f750663a20..c3a289c7b32 100644 --- a/tests/sys/fs/fuse/open.cc +++ b/tests/sys/fs/fuse/open.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/read.cc b/tests/sys/fs/fuse/read.cc index d4cb5059150..d72f1fedb45 100644 --- a/tests/sys/fs/fuse/read.cc +++ b/tests/sys/fs/fuse/read.cc @@ -1,4 +1,6 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship diff --git a/tests/sys/fs/fuse/readlink.cc b/tests/sys/fs/fuse/readlink.cc index 6db5dcfad2a..8d94cab790f 100644 --- a/tests/sys/fs/fuse/readlink.cc +++ b/tests/sys/fs/fuse/readlink.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/release.cc b/tests/sys/fs/fuse/release.cc index 4b76d78058a..f53ef08e98d 100644 --- a/tests/sys/fs/fuse/release.cc +++ b/tests/sys/fs/fuse/release.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/rename.cc b/tests/sys/fs/fuse/rename.cc index d6e398db490..a15a9071874 100644 --- a/tests/sys/fs/fuse/rename.cc +++ b/tests/sys/fs/fuse/rename.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/rmdir.cc b/tests/sys/fs/fuse/rmdir.cc index 837c0186271..72ea9a7ce15 100644 --- a/tests/sys/fs/fuse/rmdir.cc +++ b/tests/sys/fs/fuse/rmdir.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/setattr.cc b/tests/sys/fs/fuse/setattr.cc index 26c3db4c4a3..b20a160572e 100644 --- a/tests/sys/fs/fuse/setattr.cc +++ b/tests/sys/fs/fuse/setattr.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/statfs.cc b/tests/sys/fs/fuse/statfs.cc index bf622db39c8..a7268abd655 100644 --- a/tests/sys/fs/fuse/statfs.cc +++ b/tests/sys/fs/fuse/statfs.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/symlink.cc b/tests/sys/fs/fuse/symlink.cc index 02c1e9c7f4c..2c9a0deab25 100644 --- a/tests/sys/fs/fuse/symlink.cc +++ b/tests/sys/fs/fuse/symlink.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index 3f59710840b..0f0ae9268cc 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index 99650142bff..33f3308f229 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. diff --git a/tests/sys/fs/fuse/write.cc b/tests/sys/fs/fuse/write.cc index 4b452c9a152..44e537d7026 100644 --- a/tests/sys/fs/fuse/write.cc +++ b/tests/sys/fs/fuse/write.cc @@ -1,6 +1,7 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2019 The FreeBSD Foundation - * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. From 4459896e18a9dad4ba40c699d46e8e89107e197b Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Mon, 11 Mar 2019 22:29:56 +0000 Subject: [PATCH 21/41] fuse(4): add tests for FUSE_OPENDIR, FUSE_FSYNC, and FUSE_FSYNCDIR And one more for FUSE_WRITE, too. PR: 236379 PR: 236473 PR: 236474 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 18 ++ tests/sys/fs/fuse/fsync.cc | 349 ++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/fsyncdir.cc | 186 ++++++++++++++++++ tests/sys/fs/fuse/mockfs.cc | 9 + tests/sys/fs/fuse/mockfs.hh | 2 + tests/sys/fs/fuse/opendir.cc | 123 ++++++++++++ tests/sys/fs/fuse/write.cc | 33 ++++ 7 files changed, 720 insertions(+) create mode 100644 tests/sys/fs/fuse/fsync.cc create mode 100644 tests/sys/fs/fuse/fsyncdir.cc create mode 100644 tests/sys/fs/fuse/opendir.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index a0bf2878103..0705486307d 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -7,12 +7,15 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create ATF_TESTS_CXX+= flush +ATF_TESTS_CXX+= fsync +ATF_TESTS_CXX+= fsyncdir ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= link ATF_TESTS_CXX+= lookup ATF_TESTS_CXX+= mkdir ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open +ATF_TESTS_CXX+= opendir ATF_TESTS_CXX+= read ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= release @@ -39,6 +42,16 @@ SRCS.flush+= getmntopts.c SRCS.flush+= mockfs.cc SRCS.flush+= utils.cc +SRCS.fsync+= fsync.cc +SRCS.fsync+= getmntopts.c +SRCS.fsync+= mockfs.cc +SRCS.fsync+= utils.cc + +SRCS.fsyncdir+= fsyncdir.cc +SRCS.fsyncdir+= getmntopts.c +SRCS.fsyncdir+= mockfs.cc +SRCS.fsyncdir+= utils.cc + SRCS.getattr+= getattr.cc SRCS.getattr+= getmntopts.c SRCS.getattr+= mockfs.cc @@ -69,6 +82,11 @@ SRCS.open+= mockfs.cc SRCS.open+= open.cc SRCS.open+= utils.cc +SRCS.opendir+= getmntopts.c +SRCS.opendir+= mockfs.cc +SRCS.opendir+= opendir.cc +SRCS.opendir+= utils.cc + SRCS.read+= getmntopts.c SRCS.read+= mockfs.cc SRCS.read+= read.cc diff --git a/tests/sys/fs/fuse/fsync.cc b/tests/sys/fs/fuse/fsync.cc new file mode 100644 index 00000000000..ec828b3d803 --- /dev/null +++ b/tests/sys/fs/fuse/fsync.cc @@ -0,0 +1,349 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +/* + * TODO: remove FUSE_FSYNC_FDATASYNC definition when upgrading to protocol 7.28. + * This bit was actually part of kernel protocol version 5.2, but never + * documented until after 7.28 + */ +#ifndef FUSE_FSYNC_FDATASYNC +#define FUSE_FSYNC_FDATASYNC 1 +#endif + +class Fsync: public FuseTest { +public: +const static uint64_t FH = 0xdeadbeef1a7ebabe; +void expect_fsync(uint64_t ino, uint32_t flags, int error) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_FSYNC && + in->header.nodeid == ino && + //(pid_t)in->header.pid == getpid() && + in->body.fsync.fh == FH && + in->body.fsync.fsync_flags == flags); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(error))); +} + +void expect_getattr(uint64_t ino) +{ + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr_valid = UINT64_MAX; + })); +} + +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void expect_open(uint64_t ino) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = FH; + })); +} + +void expect_release(uint64_t ino) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RELEASE && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); +} + +void expect_write(uint64_t ino, uint64_t size, const void *contents) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *buf = (const char*)in->body.bytes + + sizeof(struct fuse_write_in); + + return (in->header.opcode == FUSE_WRITE && + in->header.nodeid == ino && + 0 == bcmp(buf, contents, size)); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, write); + out->body.write.size = size; + })); +} + +}; + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +TEST_F(Fsync, DISABLED_aio_fsync) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + ssize_t bufsize = strlen(CONTENTS); + uint64_t ino = 42; + struct aiocb iocb, *piocb; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + expect_write(ino, bufsize, CONTENTS); + expect_fsync(ino, 0, 0); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + + bzero(&iocb, sizeof(iocb)); + iocb.aio_fildes = fd; + + ASSERT_EQ(0, aio_fsync(O_SYNC, &iocb)) << strerror(errno); + ASSERT_EQ(0, aio_waitcomplete(&piocb, NULL)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * fuse(4) should NOT fsync during VOP_RELEASE or VOP_INACTIVE + * + * This test only really make sense in writeback caching mode, but it should + * still pass in any cache mode. + */ +TEST_F(Fsync, close) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + ssize_t bufsize = strlen(CONTENTS); + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + expect_write(ino, bufsize, CONTENTS); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETATTR); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_FSYNC); + }, Eq(true)), + _) + ).Times(0); + expect_release(ino); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + close(fd); +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +TEST_F(Fsync, DISABLED_eio) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + ssize_t bufsize = strlen(CONTENTS); + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + expect_write(ino, bufsize, CONTENTS); + expect_fsync(ino, FUSE_FSYNC_FDATASYNC, EIO); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + ASSERT_NE(0, fdatasync(fd)); + ASSERT_EQ(EIO, errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +TEST_F(Fsync, DISABLED_fdatasync) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + ssize_t bufsize = strlen(CONTENTS); + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + expect_write(ino, bufsize, CONTENTS); + expect_fsync(ino, FUSE_FSYNC_FDATASYNC, 0); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + ASSERT_EQ(0, fdatasync(fd)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +TEST_F(Fsync, DISABLED_fsync) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + ssize_t bufsize = strlen(CONTENTS); + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + expect_write(ino, bufsize, CONTENTS); + expect_fsync(ino, 0, 0); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + ASSERT_EQ(0, fsync(fd)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* Fsync should sync a file with dirty metadata but clean data */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +TEST_F(Fsync, DISABLED_fsync_metadata_only) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + mode_t mode = 0755; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETATTR); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | mode; + })); + + expect_fsync(ino, 0, 0); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(0, fchmod(fd, mode)) << strerror(errno); + ASSERT_EQ(0, fsync(fd)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +// fsync()ing a file that isn't dirty should be a no-op +TEST_F(Fsync, nop) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(0, fsync(fd)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + + diff --git a/tests/sys/fs/fuse/fsyncdir.cc b/tests/sys/fs/fuse/fsyncdir.cc new file mode 100644 index 00000000000..8cf624cbe63 --- /dev/null +++ b/tests/sys/fs/fuse/fsyncdir.cc @@ -0,0 +1,186 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +/* + * TODO: remove FUSE_FSYNC_FDATASYNC definition when upgrading to protocol 7.28. + * This bit was actually part of kernel protocol version 5.2, but never + * documented until after 7.28 + */ +#ifndef FUSE_FSYNC_FDATASYNC +#define FUSE_FSYNC_FDATASYNC 1 +#endif + +class FsyncDir: public FuseTest { +public: +const static uint64_t FH = 0xdeadbeef1a7ebabe; +void expect_fsyncdir(uint64_t ino, uint32_t flags, int error) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_FSYNCDIR && + in->header.nodeid == ino && + //(pid_t)in->header.pid == getpid() && + in->body.fsyncdir.fh == FH && + in->body.fsyncdir.fsync_flags == flags); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(error))); +} + +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void expect_opendir(uint64_t ino) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPENDIR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = FH; + })); +} + +}; + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +TEST_F(FsyncDir, DISABLED_aio_fsync) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + struct aiocb iocb, *piocb; + int fd; + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + expect_fsyncdir(ino, 0, 0); + + fd = open(FULLPATH, O_DIRECTORY); + ASSERT_LE(0, fd) << strerror(errno); + + bzero(&iocb, sizeof(iocb)); + iocb.aio_fildes = fd; + + ASSERT_EQ(0, aio_fsync(O_SYNC, &iocb)) << strerror(errno); + ASSERT_EQ(0, aio_waitcomplete(&piocb, NULL)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +TEST_F(FsyncDir, DISABLED_eio) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + expect_fsyncdir(ino, 0, EIO); + + fd = open(FULLPATH, O_DIRECTORY); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_NE(0, fsync(fd)); + ASSERT_EQ(EIO, errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +TEST_F(FsyncDir, DISABLED_fsyncdata) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + expect_fsyncdir(ino, FUSE_FSYNC_FDATASYNC, 0); + + fd = open(FULLPATH, O_DIRECTORY); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(0, fdatasync(fd)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * Unlike regular files, the kernel doesn't know whether a directory is or + * isn't dirty, so fuse(4) should always send FUSE_FSYNCDIR on fsync(2) + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236473 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +TEST_F(FsyncDir, DISABLED_fsync) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + expect_fsyncdir(ino, 0, 0); + + fd = open(FULLPATH, O_DIRECTORY); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(0, fsync(fd)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index b1f0c08a2f2..eee9acd3b1b 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -144,6 +144,12 @@ void debug_fuseop(const mockfs_buf_in *in) in->header.unique, in->header.len); } switch (in->header.opcode) { + case FUSE_FSYNC: + printf(" flags=%#x", in->body.fsync.fsync_flags); + break; + case FUSE_FSYNCDIR: + printf(" flags=%#x", in->body.fsyncdir.fsync_flags); + break; case FUSE_LOOKUP: printf(" %s", in->body.lookup); break; @@ -155,6 +161,9 @@ void debug_fuseop(const mockfs_buf_in *in) printf(" offset=%lu size=%u", in->body.read.offset, in->body.read.size); break; + case FUSE_SETATTR: + printf(" valid=%#x", in->body.setattr.valid); + break; case FUSE_WRITE: printf(" offset=%lu size=%u flags=%u", in->body.write.offset, in->body.write.size, diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 66276a38c54..e9e7af93051 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -75,6 +75,8 @@ union fuse_payloads_in { /* value is from fuse_kern_chan.c in fusefs-libs */ uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; fuse_flush_in flush; + fuse_fsync_in fsync; + fuse_fsync_in fsyncdir; fuse_forget_in forget; fuse_init_in init; fuse_link_in link; diff --git a/tests/sys/fs/fuse/opendir.cc b/tests/sys/fs/fuse/opendir.cc new file mode 100644 index 00000000000..e312b39e8d8 --- /dev/null +++ b/tests/sys/fs/fuse/opendir.cc @@ -0,0 +1,123 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Opendir: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); +} +}; + + +/* + * The fuse daemon fails the request with enoent. This usually indicates a + * race condition: some other FUSE client removed the file in between when the + * kernel checked for it with lookup and tried to open it + */ +TEST_F(Opendir, enoent) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + + expect_lookup(RELPATH, ino); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPENDIR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(ENOENT))); + EXPECT_NE(0, open(FULLPATH, O_DIRECTORY)); + EXPECT_EQ(ENOENT, errno); +} + +/* + * The daemon is responsible for checking file permissions (unless the + * default_permissions mount option was used) + */ +TEST_F(Opendir, eperm) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + + expect_lookup(RELPATH, ino); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPENDIR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EPERM))); + + EXPECT_NE(0, open(FULLPATH, O_DIRECTORY)); + EXPECT_EQ(EPERM, errno); +} + +TEST_F(Opendir, ok) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + + expect_lookup(RELPATH, ino); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPENDIR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, open); + })); + + EXPECT_LE(0, open(FULLPATH, O_DIRECTORY)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/write.cc b/tests/sys/fs/fuse/write.cc index 44e537d7026..136f05f1c71 100644 --- a/tests/sys/fs/fuse/write.cc +++ b/tests/sys/fs/fuse/write.cc @@ -580,6 +580,39 @@ TEST_F(Write, write_nothing) /* Deliberately leak fd. close(2) will be tested in release.cc */ } +/* In writeback mode, dirty data should be written on close */ +TEST_F(WriteBack, close) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETATTR); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + })); + expect_release(ino, ReturnErrno(0)); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + close(fd); +} + /* * Without direct_io, writes should be committed to cache */ From 0f10547be1f3f5a0a62a3960d5a6de00f2a469c4 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 12 Mar 2019 22:25:59 +0000 Subject: [PATCH 22/41] fuse(4): add tests for opendir and readdir Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 6 + tests/sys/fs/fuse/mockfs.cc | 8 + tests/sys/fs/fuse/mockfs.hh | 2 + tests/sys/fs/fuse/opendir.cc | 35 +++- tests/sys/fs/fuse/readdir.cc | 334 +++++++++++++++++++++++++++++++++++ 5 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 tests/sys/fs/fuse/readdir.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 0705486307d..2fdccb33795 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -17,6 +17,7 @@ ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= opendir ATF_TESTS_CXX+= read +ATF_TESTS_CXX+= readdir ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= release ATF_TESTS_CXX+= rename @@ -92,6 +93,11 @@ SRCS.read+= mockfs.cc SRCS.read+= read.cc SRCS.read+= utils.cc +SRCS.readdir+= getmntopts.c +SRCS.readdir+= mockfs.cc +SRCS.readdir+= readdir.cc +SRCS.readdir+= utils.cc + SRCS.readlink+= getmntopts.c SRCS.readlink+= mockfs.cc SRCS.readlink+= readlink.cc diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index eee9acd3b1b..94097c9257f 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -157,10 +157,18 @@ void debug_fuseop(const mockfs_buf_in *in) printf(" flags=%#x mode=%#o", in->body.open.flags, in->body.open.mode); break; + case FUSE_OPENDIR: + printf(" flags=%#x mode=%#o", + in->body.opendir.flags, in->body.opendir.mode); + break; case FUSE_READ: printf(" offset=%lu size=%u", in->body.read.offset, in->body.read.size); break; + case FUSE_READDIR: + printf(" offset=%lu size=%u", in->body.readdir.offset, + in->body.readdir.size); + break; case FUSE_SETATTR: printf(" valid=%#x", in->body.setattr.valid); break; diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index e9e7af93051..556ab95d2e5 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -84,7 +84,9 @@ union fuse_payloads_in { fuse_mkdir_in mkdir; fuse_mknod_in mknod; fuse_open_in open; + fuse_open_in opendir; fuse_read_in read; + fuse_read_in readdir; fuse_release_in release; fuse_rename_in rename; char rmdir[0]; diff --git a/tests/sys/fs/fuse/opendir.cc b/tests/sys/fs/fuse/opendir.cc index e312b39e8d8..2c952426781 100644 --- a/tests/sys/fs/fuse/opendir.cc +++ b/tests/sys/fs/fuse/opendir.cc @@ -29,6 +29,7 @@ */ extern "C" { +#include #include } @@ -100,7 +101,7 @@ TEST_F(Opendir, eperm) EXPECT_EQ(EPERM, errno); } -TEST_F(Opendir, ok) +TEST_F(Opendir, open) { const char FULLPATH[] = "mountpoint/some_dir"; const char RELPATH[] = "some_dir"; @@ -121,3 +122,35 @@ TEST_F(Opendir, ok) EXPECT_LE(0, open(FULLPATH, O_DIRECTORY)) << strerror(errno); } + +TEST_F(Opendir, opendir) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + + expect_lookup(RELPATH, ino); + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_STATFS); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, statfs); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPENDIR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, open); + })); + + errno = 0; + EXPECT_NE(NULL, opendir(FULLPATH)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/readdir.cc b/tests/sys/fs/fuse/readdir.cc new file mode 100644 index 00000000000..3258947103f --- /dev/null +++ b/tests/sys/fs/fuse/readdir.cc @@ -0,0 +1,334 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; +using namespace std; + +class Readdir: public FuseTest { +const static uint64_t FH = 0xdeadbeef1a7ebabe; +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void expect_opendir(uint64_t ino) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_STATFS); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, statfs); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPENDIR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = FH; + })); +} + +void expect_readdir(uint64_t ino, uint64_t off, vector &ents) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READDIR && + in->header.nodeid == ino && + in->body.readdir.offset == off); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes; + int i = 0; + + out->header.unique = in->header.unique; + out->header.error = 0; + out->header.len = 0; + + for (const auto& it: ents) { + size_t entlen, entsize; + + fde->ino = it.d_fileno; + fde->off = it.d_off; + fde->type = it.d_type; + fde->namelen = it.d_namlen; + strncpy(fde->name, it.d_name, it.d_namlen); + entlen = FUSE_NAME_OFFSET + fde->namelen; + entsize = FUSE_DIRENT_SIZE(fde); + /* + * The FUSE protocol does not require zeroing out the + * unused portion of the name. But it's a good + * practice to prevent information disclosure to the + * FUSE client, even though the client is usually the + * kernel + */ + memset(fde->name + fde->namelen, 0, entsize - entlen); + if (out->header.len + entsize > in->body.read.size) { + printf("Overflow in readdir expectation: i=%d\n" + , i); + break; + } + out->header.len += entsize; + fde = (struct fuse_dirent*) + ((long*)fde + entsize / sizeof(long)); + i++; + } + out->header.len += sizeof(out->header); + })); + +} +}; + +/* FUSE_READDIR returns nothing but "." and ".." */ +TEST_F(Readdir, dots) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + DIR *dir; + struct dirent *de; + vector ents(2); + vector empty_ents(0); + const char *dot = "."; + const char *dotdot = ".."; + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + ents[0].d_fileno = 2; + ents[0].d_off = 2000; + ents[0].d_namlen = strlen(dotdot); + ents[0].d_type = DT_DIR; + strncpy(ents[0].d_name, dotdot, ents[0].d_namlen); + ents[1].d_fileno = 3; + ents[1].d_off = 3000; + ents[1].d_namlen = strlen(dot); + ents[1].d_type = DT_DIR; + strncpy(ents[1].d_name, dot, ents[1].d_namlen); + expect_readdir(ino, 0, ents); + expect_readdir(ino, 3000, empty_ents); + + errno = 0; + dir = opendir(FULLPATH); + ASSERT_NE(NULL, dir) << strerror(errno); + + errno = 0; + de = readdir(dir); + ASSERT_NE(NULL, de) << strerror(errno); + EXPECT_EQ(2ul, de->d_fileno); + /* + * fuse(4) doesn't actually set d_off, which is ok for now because + * nothing uses it. + */ + //EXPECT_EQ(2000, de->d_off); + EXPECT_EQ(DT_DIR, de->d_type); + EXPECT_EQ(2, de->d_namlen); + EXPECT_EQ(0, strcmp("..", de->d_name)); + + errno = 0; + de = readdir(dir); + ASSERT_NE(NULL, de) << strerror(errno); + EXPECT_EQ(3ul, de->d_fileno); + //EXPECT_EQ(3000, de->d_off); + EXPECT_EQ(DT_DIR, de->d_type); + EXPECT_EQ(1, de->d_namlen); + EXPECT_EQ(0, strcmp(".", de->d_name)); + + ASSERT_EQ(NULL, readdir(dir)); + ASSERT_EQ(0, errno); + + /* Deliberately leak dir. RELEASEDIR will be tested separately */ +} + +TEST_F(Readdir, eio) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + DIR *dir; + struct dirent *de; + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READDIR && + in->header.nodeid == ino && + in->body.readdir.offset == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EIO))); + + errno = 0; + dir = opendir(FULLPATH); + ASSERT_NE(NULL, dir) << strerror(errno); + + errno = 0; + de = readdir(dir); + ASSERT_EQ(NULL, de); + ASSERT_EQ(EIO, errno); + + /* Deliberately leak dir. RELEASEDIR will be tested separately */ +} + +/* + * FUSE_READDIR returns nothing, not even "." and "..". This is legal, though + * the filesystem obviously won't be fully functional. + */ +TEST_F(Readdir, nodots) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + DIR *dir; + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READDIR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = 0; + out->header.len = sizeof(out->header); + })); + + errno = 0; + dir = opendir(FULLPATH); + ASSERT_NE(NULL, dir) << strerror(errno); + errno = 0; + ASSERT_EQ(NULL, readdir(dir)); + ASSERT_EQ(0, errno); + + /* Deliberately leak dir. RELEASEDIR will be tested separately */ +} + +/* telldir(3) and seekdir(3) should work with fuse */ +TEST_F(Readdir, seekdir) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + DIR *dir; + struct dirent *de; + /* + * use enough entries to be > 4096 bytes, so getdirentries must be + * called + * multiple times. + */ + vector ents0(122), ents1(102), ents2(30); + long bookmark; + int i = 0; + + for (auto& it: ents0) { + snprintf(it.d_name, MAXNAMLEN, "file.%d", i); + it.d_fileno = 2 + i; + it.d_off = (2 + i) * 1000; + it.d_namlen = strlen(it.d_name); + it.d_type = DT_REG; + i++; + } + for (auto& it: ents1) { + snprintf(it.d_name, MAXNAMLEN, "file.%d", i); + it.d_fileno = 2 + i; + it.d_off = (2 + i) * 1000; + it.d_namlen = strlen(it.d_name); + it.d_type = DT_REG; + i++; + } + for (auto& it: ents2) { + snprintf(it.d_name, MAXNAMLEN, "file.%d", i); + it.d_fileno = 2 + i; + it.d_off = (2 + i) * 1000; + it.d_namlen = strlen(it.d_name); + it.d_type = DT_REG; + i++; + } + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + + expect_readdir(ino, 0, ents0); + expect_readdir(ino, 123000, ents1); + expect_readdir(ino, 225000, ents2); + + errno = 0; + dir = opendir(FULLPATH); + ASSERT_NE(NULL, dir) << strerror(errno); + + for (i=0; i < 128; i++) { + errno = 0; + de = readdir(dir); + ASSERT_NE(NULL, de) << strerror(errno); + EXPECT_EQ(2 + (ino_t)i, de->d_fileno); + } + bookmark = telldir(dir); + + for (; i < 232; i++) { + errno = 0; + de = readdir(dir); + ASSERT_NE(NULL, de) << strerror(errno); + EXPECT_EQ(2 + (ino_t)i, de->d_fileno); + } + + seekdir(dir, bookmark); + de = readdir(dir); + ASSERT_NE(NULL, de) << strerror(errno); + EXPECT_EQ(130ul, de->d_fileno); + + /* Deliberately leak dir. RELEASEDIR will be tested separately */ +} From 1bb6c550764cc9b7bc807bce04c051ca1a557623 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 13 Mar 2019 13:41:05 +0000 Subject: [PATCH 23/41] fues(4): add tests for FUSE_RELEASEDIR Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 6 ++ tests/sys/fs/fuse/mockfs.hh | 1 + tests/sys/fs/fuse/releasedir.cc | 144 ++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 tests/sys/fs/fuse/releasedir.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 2fdccb33795..a21ed815b17 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -20,6 +20,7 @@ ATF_TESTS_CXX+= read ATF_TESTS_CXX+= readdir ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= release +ATF_TESTS_CXX+= releasedir ATF_TESTS_CXX+= rename ATF_TESTS_CXX+= rmdir ATF_TESTS_CXX+= setattr @@ -108,6 +109,11 @@ SRCS.release+= mockfs.cc SRCS.release+= release.cc SRCS.release+= utils.cc +SRCS.releasedir+= getmntopts.c +SRCS.releasedir+= mockfs.cc +SRCS.releasedir+= releasedir.cc +SRCS.releasedir+= utils.cc + SRCS.rename+= getmntopts.c SRCS.rename+= mockfs.cc SRCS.rename+= rename.cc diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 556ab95d2e5..653cd9d1c63 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -88,6 +88,7 @@ union fuse_payloads_in { fuse_read_in read; fuse_read_in readdir; fuse_release_in release; + fuse_release_in releasedir; fuse_rename_in rename; char rmdir[0]; fuse_setattr_in setattr; diff --git a/tests/sys/fs/fuse/releasedir.cc b/tests/sys/fs/fuse/releasedir.cc new file mode 100644 index 00000000000..c66918a834c --- /dev/null +++ b/tests/sys/fs/fuse/releasedir.cc @@ -0,0 +1,144 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class ReleaseDir: public FuseTest { + +const static uint64_t FH = 0xdeadbeef1a7ebabe; + +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void expect_opendir(uint64_t ino) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_STATFS); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, statfs); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPENDIR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = FH; + })); +} + +void expect_releasedir(uint64_t ino, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RELEASEDIR && + in->header.nodeid == ino && + in->body.release.fh == FH); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} +}; + +/* If a file descriptor is duplicated, only the last close causes RELEASE */ +TEST_F(ReleaseDir, dup) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + DIR *dir, *dir2; + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READDIR && + in->header.nodeid == ino && + in->body.readdir.offset == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = 0; + out->header.len = sizeof(out->header); + })); + expect_releasedir(ino, ReturnErrno(0)); + + dir = opendir(FULLPATH); + ASSERT_NE(NULL, dir) << strerror(errno); + + dir2 = fdopendir(dup(dirfd(dir))); + ASSERT_NE(NULL, dir2) << strerror(errno); + + ASSERT_EQ(0, closedir(dir)) << strerror(errno); + ASSERT_EQ(0, closedir(dir2)) << strerror(errno); +} + +TEST_F(ReleaseDir, ok) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + DIR *dir; + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + expect_releasedir(ino, ReturnErrno(0)); + + dir = opendir(FULLPATH); + ASSERT_NE(NULL, dir) << strerror(errno); + + ASSERT_EQ(0, closedir(dir)) << strerror(errno); +} From ef61047a9b37e77e0dc4d5f8862e3dd241329263 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 13 Mar 2019 22:16:00 +0000 Subject: [PATCH 24/41] fuse(4): add tests for POSIX file locking operations PR: 234581 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 6 + tests/sys/fs/fuse/locks.cc | 475 ++++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.cc | 11 +- tests/sys/fs/fuse/mockfs.hh | 15 +- tests/sys/fs/fuse/utils.cc | 2 +- tests/sys/fs/fuse/utils.hh | 5 +- 6 files changed, 507 insertions(+), 7 deletions(-) create mode 100644 tests/sys/fs/fuse/locks.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index a21ed815b17..51ae166b227 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -11,6 +11,7 @@ ATF_TESTS_CXX+= fsync ATF_TESTS_CXX+= fsyncdir ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= link +ATF_TESTS_CXX+= locks ATF_TESTS_CXX+= lookup ATF_TESTS_CXX+= mkdir ATF_TESTS_CXX+= mknod @@ -64,6 +65,11 @@ SRCS.link+= link.cc SRCS.link+= mockfs.cc SRCS.link+= utils.cc +SRCS.locks+= locks.cc +SRCS.locks+= getmntopts.c +SRCS.locks+= mockfs.cc +SRCS.locks+= utils.cc + SRCS.lookup+= getmntopts.c SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc diff --git a/tests/sys/fs/fuse/locks.cc b/tests/sys/fs/fuse/locks.cc new file mode 100644 index 00000000000..f0cc1476a2e --- /dev/null +++ b/tests/sys/fs/fuse/locks.cc @@ -0,0 +1,475 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +/* This flag value should probably be defined in fuse_kernel.h */ +#define OFFSET_MAX 0x7fffffffffffffffLL + +using namespace testing; + +/* For testing filesystems without posix locking support */ +class Fallback: public FuseTest { +public: +const static uint64_t FH = 0xdeadbeef1a7ebabe; + +void expect_getattr(uint64_t ino) +{ + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr_valid = UINT64_MAX; + })); +} + +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void expect_open(uint64_t ino) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = FH; + })); +} + +}; + +/* For testing filesystems with posix locking support */ +class Locks: public Fallback { + virtual void SetUp() { + m_init_flags = FUSE_POSIX_LOCKS; + Fallback::SetUp(); + } +}; + +class GetlkFallback: public Fallback {}; +class Getlk: public Locks {}; +class SetlkFallback: public Fallback {}; +class Setlk: public Locks {}; +class SetlkwFallback: public Fallback {}; +class Setlkw: public Locks {}; + +/* + * If the fuse filesystem does not support posix file locks, then the kernel + * should fall back to local locks. + */ +TEST_F(GetlkFallback, local) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + struct flock fl; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 10; + fl.l_len = 1000; + fl.l_pid = getpid(); + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * If the filesystem has no locks that fit the description, the filesystem + * should return F_UNLCK + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ +TEST_F(Getlk, DISABLED_no_locks) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + struct flock fl; + int fd; + pid_t pid = 1234; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETLK && + in->header.nodeid == ino && + in->body.getlk.fh == FH && + in->body.getlk.owner == (uint32_t)pid && + in->body.getlk.lk.start == 10 && + in->body.getlk.lk.end == 1009 && + in->body.getlk.lk.type == F_RDLCK && + in->body.getlk.lk.pid == 10); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, getlk); + out->body.getlk.lk = in->body.getlk.lk; + out->body.getlk.lk.type = F_UNLCK; + })); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 10; + fl.l_len = 1000; + fl.l_pid = pid; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); + ASSERT_EQ(F_UNLCK, fl.l_type); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* A different pid does have a lock */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ +TEST_F(Getlk, DISABLED_lock_exists) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + struct flock fl; + int fd; + pid_t pid = 1234; + pid_t pid2 = 1234; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETLK && + in->header.nodeid == ino && + in->body.getlk.fh == FH && + in->body.getlk.owner == (uint32_t)pid && + in->body.getlk.lk.start == 10 && + in->body.getlk.lk.end == 1009 && + in->body.getlk.lk.type == F_RDLCK && + in->body.getlk.lk.pid == 10); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, getlk); + out->body.getlk.lk.start = 100; + out->body.getlk.lk.end = 199; + out->body.getlk.lk.type = F_WRLCK; + out->body.getlk.lk.pid = (uint32_t)pid2;; + })); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 10; + fl.l_len = 1000; + fl.l_pid = pid; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); + EXPECT_EQ(100, fl.l_start); + EXPECT_EQ(100, fl.l_len); + EXPECT_EQ(pid2, fl.l_pid); + EXPECT_EQ(F_WRLCK, fl.l_type); + EXPECT_EQ(SEEK_SET, fl.l_whence); + EXPECT_EQ(0, fl.l_sysid); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * If the fuse filesystem does not support posix file locks, then the kernel + * should fall back to local locks. + */ +TEST_F(SetlkFallback, local) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + struct flock fl; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 10; + fl.l_len = 1000; + fl.l_pid = getpid(); + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* Set a new lock with FUSE_SETLK */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ +TEST_F(Setlk, DISABLED_set) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + struct flock fl; + int fd; + pid_t pid = 1234; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETLK && + in->header.nodeid == ino && + in->body.getlk.fh == FH && + in->body.getlk.owner == (uint32_t)pid && + in->body.getlk.lk.start == 10 && + in->body.getlk.lk.end == 1009 && + in->body.getlk.lk.type == F_RDLCK && + in->body.getlk.lk.pid == 10); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, getlk); + out->body.getlk.lk = in->body.getlk.lk; + out->body.getlk.lk.type = F_UNLCK; + })); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 10; + fl.l_len = 1000; + fl.l_pid = pid; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* l_len = 0 is a flag value that means to lock until EOF */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ +TEST_F(Setlk, DISABLED_set_eof) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + struct flock fl; + int fd; + pid_t pid = 1234; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETLK && + in->header.nodeid == ino && + in->body.getlk.fh == FH && + in->body.getlk.owner == (uint32_t)pid && + in->body.getlk.lk.start == 10 && + in->body.getlk.lk.end == OFFSET_MAX && + in->body.getlk.lk.type == F_RDLCK && + in->body.getlk.lk.pid == 10); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, getlk); + out->body.getlk.lk = in->body.getlk.lk; + out->body.getlk.lk.type = F_UNLCK; + })); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 10; + fl.l_len = 0; + fl.l_pid = pid; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* Fail to set a new lock with FUSE_SETLK due to a conflict */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ +TEST_F(Setlk, DISABLED_eagain) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + struct flock fl; + int fd; + pid_t pid = 1234; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETLK && + in->header.nodeid == ino && + in->body.getlk.fh == FH && + in->body.getlk.owner == (uint32_t)pid && + in->body.getlk.lk.start == 10 && + in->body.getlk.lk.end == 1009 && + in->body.getlk.lk.type == F_RDLCK && + in->body.getlk.lk.pid == 10); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EAGAIN))); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 10; + fl.l_len = 1000; + fl.l_pid = pid; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_EQ(-1, fcntl(fd, F_SETLK, &fl)); + ASSERT_EQ(EAGAIN, errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * If the fuse filesystem does not support posix file locks, then the kernel + * should fall back to local locks. + */ +TEST_F(SetlkwFallback, local) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + struct flock fl; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 10; + fl.l_len = 1000; + fl.l_pid = getpid(); + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * Set a new lock with FUSE_SETLK. If the lock is not available, then the + * command should block. But to the kernel, that's the same as just being + * slow, so we don't need a separate test method + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ +TEST_F(Setlkw, DISABLED_set) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + struct flock fl; + int fd; + pid_t pid = 1234; + + expect_lookup(RELPATH, ino); + expect_open(ino); + expect_getattr(ino); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETLK && + in->header.nodeid == ino && + in->body.getlk.fh == FH && + in->body.getlk.owner == (uint32_t)pid && + in->body.getlk.lk.start == 10 && + in->body.getlk.lk.end == 1009 && + in->body.getlk.lk.type == F_RDLCK && + in->body.getlk.lk.pid == 10); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, getlk); + out->body.getlk.lk = in->body.getlk.lk; + out->body.getlk.lk.type = F_UNLCK; + })); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 10; + fl.l_len = 1000; + fl.l_pid = pid; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index 94097c9257f..5794f67e8bf 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -183,7 +183,7 @@ void debug_fuseop(const mockfs_buf_in *in) printf("\n"); } -MockFS::MockFS(int max_readahead) { +MockFS::MockFS(int max_readahead, uint32_t flags) { struct iovec *iov = NULL; int iovlen = 0; char fdstr[15]; @@ -225,7 +225,7 @@ MockFS::MockFS(int max_readahead) { ON_CALL(*this, process(_, _)) .WillByDefault(Invoke(this, &MockFS::process_default)); - init(); + init(flags); signal(SIGUSR1, sigint_handler); if (pthread_create(&m_daemon_id, NULL, service, (void*)this)) throw(std::system_error(errno, std::system_category(), @@ -238,7 +238,7 @@ MockFS::~MockFS() { rmdir("mountpoint"); } -void MockFS::init() { +void MockFS::init(uint32_t flags) { mockfs_buf_in *in; mockfs_buf_out *out; @@ -255,6 +255,7 @@ void MockFS::init() { out->header.error = 0; out->body.init.major = FUSE_KERNEL_VERSION; out->body.init.minor = FUSE_KERNEL_MINOR_VERSION; + out->body.init.flags = in->body.init.flags & flags; /* * The default max_write is set to this formula in libfuse, though @@ -313,6 +314,10 @@ void MockFS::loop() { /*Alone among the opcodes, FORGET expects no response*/ continue; } + if (out.header.error == FUSE_NORESPONSE) { + /* Used by tests of slow opcodes. No response ATM */ + continue; + } ASSERT_TRUE(write(m_fuse_fd, &out, out.header.len) > 0 || errno == EAGAIN) << strerror(errno); diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 653cd9d1c63..4ce66e28c8f 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -40,6 +40,12 @@ extern "C" { #define TIME_T_MAX (std::numeric_limits::max()) +/* + * A pseudo-fuse errno used indicate that a fuse operation should have no + * response, at least not immediately + */ +#define FUSE_NORESPONSE 9999 + #define SET_OUT_HEADER_LEN(out, variant) { \ (out)->header.len = (sizeof((out)->header) + \ sizeof((out)->body.variant)); \ @@ -78,6 +84,8 @@ union fuse_payloads_in { fuse_fsync_in fsync; fuse_fsync_in fsyncdir; fuse_forget_in forget; + fuse_interrupt_in interrupt; + fuse_lk_in getlk; fuse_init_in init; fuse_link_in link; char lookup[0]; @@ -92,6 +100,7 @@ union fuse_payloads_in { fuse_rename_in rename; char rmdir[0]; fuse_setattr_in setattr; + fuse_lk_in setlk; char unlink[0]; fuse_write_in write; }; @@ -107,8 +116,10 @@ union fuse_payloads_out { /* The protocol places no limits on the size of bytes */ uint8_t bytes[0x20000]; fuse_entry_out entry; + fuse_lk_out getlk; fuse_init_out init; fuse_open_out open; + fuse_lk_out setlk; fuse_statfs_out statfs; /* * The protocol places no limits on the length of the string. This is @@ -165,7 +176,7 @@ class MockFS { pid_t m_pid; /* Initialize a session after mounting */ - void init(); + void init(uint32_t flags); /* Is pid from a process that might be involved in the test? */ bool pid_ok(pid_t pid); @@ -184,7 +195,7 @@ class MockFS { uint32_t m_max_write; /* Create a new mockfs and mount it to a tempdir */ - MockFS(int max_readahead); + MockFS(int max_readahead, uint32_t flags); virtual ~MockFS(); /* Kill the filesystem daemon without unmounting the filesystem */ diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index 0f0ae9268cc..61512b69800 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -76,7 +76,7 @@ void FuseTest::SetUp() { m_maxbcachebuf = val; try { - m_mock = new MockFS(m_maxreadahead); + m_mock = new MockFS(m_maxreadahead, m_init_flags); } catch (std::system_error err) { FAIL() << err.what(); } diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index 33f3308f229..ba0a533e508 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -31,6 +31,7 @@ class FuseTest : public ::testing::Test { protected: uint32_t m_maxreadahead; + uint32_t m_init_flags; MockFS *m_mock = NULL; public: @@ -40,8 +41,10 @@ class FuseTest : public ::testing::Test { * libfuse's default max_readahead is UINT_MAX, though it can be * lowered */ - FuseTest(): FuseTest(UINT_MAX) {} + FuseTest(): FuseTest(UINT_MAX, 0) {} FuseTest(uint32_t maxreadahead): m_maxreadahead(maxreadahead) {} + FuseTest(uint32_t maxreadahead, uint32_t init_flags): + m_maxreadahead(maxreadahead), m_init_flags(init_flags) {} virtual void SetUp(); From 0e125f5ff8d17fc460dcc992f4ff59b7f0ed555a Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Thu, 14 Mar 2019 00:12:59 +0000 Subject: [PATCH 25/41] fuse(4): combine common code in the tests Combine a bunch of mostly similar expect_* methods into utils.cc, and only define FH in a single place. Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/access.cc | 22 ++--- tests/sys/fs/fuse/create.cc | 9 +- tests/sys/fs/fuse/flush.cc | 60 ++----------- tests/sys/fs/fuse/fsync.cc | 102 ++++------------------ tests/sys/fs/fuse/fsyncdir.cc | 25 +----- tests/sys/fs/fuse/getattr.cc | 32 +------ tests/sys/fs/fuse/link.cc | 22 ++--- tests/sys/fs/fuse/locks.cc | 79 ++++------------- tests/sys/fs/fuse/lookup.cc | 21 ++--- tests/sys/fs/fuse/mkdir.cc | 9 +- tests/sys/fs/fuse/open.cc | 25 +----- tests/sys/fs/fuse/opendir.cc | 8 +- tests/sys/fs/fuse/read.cc | 65 +------------- tests/sys/fs/fuse/readdir.cc | 35 +------- tests/sys/fs/fuse/readlink.cc | 22 ++--- tests/sys/fs/fuse/release.cc | 93 ++++---------------- tests/sys/fs/fuse/releasedir.cc | 36 +------- tests/sys/fs/fuse/rename.cc | 73 +++------------- tests/sys/fs/fuse/rmdir.cc | 30 +++---- tests/sys/fs/fuse/unlink.cc | 24 ++---- tests/sys/fs/fuse/utils.cc | 148 +++++++++++++++++++++++++++++++- tests/sys/fs/fuse/utils.hh | 60 +++++++++++++ tests/sys/fs/fuse/write.cc | 113 ++---------------------- 23 files changed, 360 insertions(+), 753 deletions(-) diff --git a/tests/sys/fs/fuse/access.cc b/tests/sys/fs/fuse/access.cc index 9e8e896915c..8ce01028924 100644 --- a/tests/sys/fs/fuse/access.cc +++ b/tests/sys/fs/fuse/access.cc @@ -39,7 +39,13 @@ extern "C" { using namespace testing; -class Access: public FuseTest {}; +class Access: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); +} +}; /* TODO: test methods for the default_permissions mount option */ @@ -52,12 +58,7 @@ TEST_F(Access, DISABLED_eaccess) uint64_t ino = 42; mode_t access_mode = X_OK; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_ACCESS && @@ -81,12 +82,7 @@ TEST_F(Access, DISABLED_ok) uint64_t ino = 42; mode_t access_mode = R_OK; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_ACCESS && diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index e29fc8b6643..0d02a4701e0 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -295,14 +295,7 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) ASSERT_LE(0, fd) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - EXPECT_LOOKUP(1, RELPATH).Times(1) - .WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.nodeid = ino; - out->body.entry.attr.mode = S_IFREG | mode; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELPATH, ino, S_IFREG | mode, 1); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ diff --git a/tests/sys/fs/fuse/flush.cc b/tests/sys/fs/fuse/flush.cc index 98ffc88bd3c..9f071b8a892 100644 --- a/tests/sys/fs/fuse/flush.cc +++ b/tests/sys/fs/fuse/flush.cc @@ -40,8 +40,6 @@ using namespace testing; class Flush: public FuseTest { -const static uint64_t FH = 0xdeadbeef1a7ebabe; - public: void expect_flush(uint64_t ino, int times, ProcessMockerT r) { @@ -49,58 +47,16 @@ void expect_flush(uint64_t ino, int times, ProcessMockerT r) ResultOf([=](auto in) { return (in->header.opcode == FUSE_FLUSH && in->header.nodeid == ino && - in->body.flush.fh == Flush::FH); + in->body.flush.fh == FH); }, Eq(true)), _) ).Times(times) .WillRepeatedly(Invoke(r)); } -void expect_getattr(uint64_t ino) -{ - /* Until the attr cache is working, we may send an additional GETATTR */ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_GETATTR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, attr); - out->body.attr.attr.ino = ino; // Must match nodeid - out->body.attr.attr.mode = S_IFREG | 0644; - })); - -} - void expect_lookup(const char *relpath, uint64_t ino) { - EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_open(uint64_t ino, int times) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPEN && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).Times(times) - .WillRepeatedly(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(out->header); - SET_OUT_HEADER_LEN(out, open); - out->body.open.fh = Flush::FH; - })); - + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } /* @@ -130,8 +86,8 @@ TEST_F(Flush, DISABLED_dup) int fd, fd2; expect_lookup(RELPATH, ino); - expect_open(ino, 1); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_flush(ino, 2, ReturnErrno(0)); expect_release(); @@ -161,8 +117,8 @@ TEST_F(Flush, DISABLED_eio) int fd; expect_lookup(RELPATH, ino); - expect_open(ino, 1); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_flush(ino, 1, ReturnErrno(EIO)); expect_release(); @@ -182,8 +138,8 @@ TEST_F(Flush, DISABLED_flush) int fd; expect_lookup(RELPATH, ino); - expect_open(ino, 1); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_flush(ino, 1, ReturnErrno(0)); expect_release(); diff --git a/tests/sys/fs/fuse/fsync.cc b/tests/sys/fs/fuse/fsync.cc index ec828b3d803..01b568acb52 100644 --- a/tests/sys/fs/fuse/fsync.cc +++ b/tests/sys/fs/fuse/fsync.cc @@ -50,7 +50,6 @@ using namespace testing; class Fsync: public FuseTest { public: -const static uint64_t FH = 0xdeadbeef1a7ebabe; void expect_fsync(uint64_t ino, uint32_t flags, int error) { EXPECT_CALL(*m_mock, process( @@ -65,79 +64,14 @@ void expect_fsync(uint64_t ino, uint32_t flags, int error) ).WillOnce(Invoke(ReturnErrno(error))); } -void expect_getattr(uint64_t ino) -{ - /* Until the attr cache is working, we may send an additional GETATTR */ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_GETATTR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, attr); - out->body.attr.attr.ino = ino; // Must match nodeid - out->body.attr.attr.mode = S_IFREG | 0644; - out->body.attr.attr_valid = UINT64_MAX; - })); -} - void expect_lookup(const char *relpath, uint64_t ino) { - EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_open(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPEN && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(out->header); - SET_OUT_HEADER_LEN(out, open); - out->body.open.fh = FH; - })); -} - -void expect_release(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_RELEASE && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnErrno(0))); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } void expect_write(uint64_t ino, uint64_t size, const void *contents) { - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - const char *buf = (const char*)in->body.bytes + - sizeof(struct fuse_write_in); - - return (in->header.opcode == FUSE_WRITE && - in->header.nodeid == ino && - 0 == bcmp(buf, contents, size)); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, write); - out->body.write.size = size; - })); + FuseTest::expect_write(ino, 0, size, size, 0, contents); } }; @@ -156,8 +90,8 @@ TEST_F(Fsync, DISABLED_aio_fsync) int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, 0, 0); @@ -190,8 +124,8 @@ TEST_F(Fsync, close) int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -209,7 +143,7 @@ TEST_F(Fsync, close) }, Eq(true)), _) ).Times(0); - expect_release(ino); + expect_release(ino, 1, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -228,8 +162,8 @@ TEST_F(Fsync, DISABLED_eio) int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, FUSE_FSYNC_FDATASYNC, EIO); @@ -253,8 +187,8 @@ TEST_F(Fsync, DISABLED_fdatasync) int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, FUSE_FSYNC_FDATASYNC, 0); @@ -278,8 +212,8 @@ TEST_F(Fsync, DISABLED_fsync) int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); expect_write(ino, bufsize, CONTENTS); expect_fsync(ino, 0, 0); @@ -303,8 +237,8 @@ TEST_F(Fsync, DISABLED_fsync_metadata_only) mode_t mode = 0755; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETATTR); @@ -335,8 +269,8 @@ TEST_F(Fsync, nop) int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); fd = open(FULLPATH, O_WRONLY); ASSERT_LE(0, fd) << strerror(errno); @@ -346,4 +280,4 @@ TEST_F(Fsync, nop) /* Deliberately leak fd. close(2) will be tested in release.cc */ } - +// TODO: ENOSYS test diff --git a/tests/sys/fs/fuse/fsyncdir.cc b/tests/sys/fs/fuse/fsyncdir.cc index 8cf624cbe63..bc736337731 100644 --- a/tests/sys/fs/fuse/fsyncdir.cc +++ b/tests/sys/fs/fuse/fsyncdir.cc @@ -50,7 +50,6 @@ using namespace testing; class FsyncDir: public FuseTest { public: -const static uint64_t FH = 0xdeadbeef1a7ebabe; void expect_fsyncdir(uint64_t ino, uint32_t flags, int error) { EXPECT_CALL(*m_mock, process( @@ -67,29 +66,7 @@ void expect_fsyncdir(uint64_t ino, uint32_t flags, int error) void expect_lookup(const char *relpath, uint64_t ino) { - EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_opendir(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPENDIR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(out->header); - SET_OUT_HEADER_LEN(out, open); - out->body.open.fh = FH; - })); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); } }; diff --git a/tests/sys/fs/fuse/getattr.cc b/tests/sys/fs/fuse/getattr.cc index 630bdf55294..c107c74bc87 100644 --- a/tests/sys/fs/fuse/getattr.cc +++ b/tests/sys/fs/fuse/getattr.cc @@ -45,7 +45,6 @@ TEST_F(Getattr, DISABLED_attr_cache) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; - const uint64_t generation = 13; struct stat sb; EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { @@ -53,7 +52,6 @@ TEST_F(Getattr, DISABLED_attr_cache) SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; - out->body.entry.generation = generation; })); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { @@ -84,7 +82,6 @@ TEST_F(Getattr, attr_cache_timeout) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; - const uint64_t generation = 13; struct stat sb; /* * The timeout should be longer than the longest plausible time the @@ -92,14 +89,7 @@ TEST_F(Getattr, attr_cache_timeout) */ long timeout_ns = 250'000'000; - EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.entry_valid = UINT64_MAX; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.generation = generation; - })); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 2); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && @@ -128,13 +118,7 @@ TEST_F(Getattr, enoent) struct stat sb; const uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = 0100644; - out->body.entry.nodeid = ino; - })); - + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && @@ -151,16 +135,9 @@ TEST_F(Getattr, ok) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; - const uint64_t generation = 13; struct stat sb; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.generation = generation; - })); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && @@ -202,9 +179,6 @@ TEST_F(Getattr, ok) EXPECT_EQ(ino, sb.st_ino); EXPECT_EQ(S_IFREG | 0644, sb.st_mode); - // fuse(4) does not _yet_ support inode generations - //EXPECT_EQ(generation, sb.st_gen); - //st_birthtim and st_flags are not supported by protocol 7.8. They're //only supported as OS-specific extensions to OSX. //EXPECT_EQ(, sb.st_birthtim); diff --git a/tests/sys/fs/fuse/link.cc b/tests/sys/fs/fuse/link.cc index 65acc272629..84944f42822 100644 --- a/tests/sys/fs/fuse/link.cc +++ b/tests/sys/fs/fuse/link.cc @@ -37,7 +37,13 @@ extern "C" { using namespace testing; -class Link: public FuseTest {}; +class Link: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); +} +}; TEST_F(Link, emlink) { @@ -48,12 +54,7 @@ TEST_F(Link, emlink) uint64_t dst_ino = 42; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); - EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = dst_ino; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELDST, dst_ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -80,12 +81,7 @@ TEST_F(Link, ok) const uint64_t ino = 42; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); - EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = dst_ino; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELDST, dst_ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { diff --git a/tests/sys/fs/fuse/locks.cc b/tests/sys/fs/fuse/locks.cc index f0cc1476a2e..6a6f8d8a304 100644 --- a/tests/sys/fs/fuse/locks.cc +++ b/tests/sys/fs/fuse/locks.cc @@ -43,51 +43,10 @@ using namespace testing; /* For testing filesystems without posix locking support */ class Fallback: public FuseTest { public: -const static uint64_t FH = 0xdeadbeef1a7ebabe; - -void expect_getattr(uint64_t ino) -{ - /* Until the attr cache is working, we may send an additional GETATTR */ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_GETATTR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, attr); - out->body.attr.attr.ino = ino; // Must match nodeid - out->body.attr.attr.mode = S_IFREG | 0644; - out->body.attr.attr_valid = UINT64_MAX; - })); -} void expect_lookup(const char *relpath, uint64_t ino) { - EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_open(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPEN && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(out->header); - SET_OUT_HEADER_LEN(out, open); - out->body.open.fh = FH; - })); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } }; @@ -120,8 +79,8 @@ TEST_F(GetlkFallback, local) int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -150,8 +109,8 @@ TEST_F(Getlk, DISABLED_no_locks) pid_t pid = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETLK && @@ -197,8 +156,8 @@ TEST_F(Getlk, DISABLED_lock_exists) pid_t pid2 = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETLK && @@ -251,8 +210,8 @@ TEST_F(SetlkFallback, local) int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -278,8 +237,8 @@ TEST_F(Setlk, DISABLED_set) pid_t pid = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && @@ -323,8 +282,8 @@ TEST_F(Setlk, DISABLED_set_eof) pid_t pid = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && @@ -368,8 +327,8 @@ TEST_F(Setlk, DISABLED_eagain) pid_t pid = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && @@ -410,8 +369,8 @@ TEST_F(SetlkwFallback, local) int fd; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -441,8 +400,8 @@ TEST_F(Setlkw, DISABLED_set) pid_t pid = 1234; expect_lookup(RELPATH, ino); - expect_open(ino); - expect_getattr(ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && diff --git a/tests/sys/fs/fuse/lookup.cc b/tests/sys/fs/fuse/lookup.cc index 13fe5d370a6..86ab5d7d308 100644 --- a/tests/sys/fs/fuse/lookup.cc +++ b/tests/sys/fs/fuse/lookup.cc @@ -49,6 +49,7 @@ TEST_F(Lookup, DISABLED_attr_cache) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; + const uint64_t generation = 13; struct stat sb; EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { @@ -70,6 +71,7 @@ TEST_F(Lookup, DISABLED_attr_cache) out->body.entry.attr.uid = 10; out->body.entry.attr.gid = 11; out->body.entry.attr.rdev = 12; + out->body.entry.generation = generation; })); /* stat(2) issues a VOP_LOOKUP followed by a VOP_GETATTR */ ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); @@ -124,18 +126,7 @@ TEST_F(Lookup, attr_cache_timeout) out->body.entry.attr.ino = ino; // Must match nodeid out->body.entry.attr.mode = S_IFREG | 0644; })); - EXPECT_CALL(*m_mock, process( - ResultOf([](auto in) { - return (in->header.opcode == FUSE_GETATTR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, attr); - out->body.attr.attr.ino = ino; // Must match nodeid - out->body.attr.attr.mode = S_IFREG | 0644; - })); + expect_getattr(ino, 0); /* access(2) will issue a VOP_LOOKUP but not a VOP_GETATTR */ ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); @@ -246,6 +237,12 @@ TEST_F(Lookup, DISABLED_entry_cache_timeout) ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); } +// TODO: export_support +// After upgrading the protocol to 7.10, check that the kernel will only +// attempt to lookup "." and ".." if the filesystem sets FUSE_EXPORT_SUPPORT in +// the init flags. If not, then all lookups for those entries will return +// ESTALE. + TEST_F(Lookup, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; diff --git a/tests/sys/fs/fuse/mkdir.cc b/tests/sys/fs/fuse/mkdir.cc index 25c07421053..2066c33eb2a 100644 --- a/tests/sys/fs/fuse/mkdir.cc +++ b/tests/sys/fs/fuse/mkdir.cc @@ -145,14 +145,7 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - EXPECT_LOOKUP(1, RELPATH).Times(1) - .WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.nodeid = ino; - out->body.entry.attr.mode = S_IFDIR | mode; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELPATH, ino, S_IFDIR | mode, 1); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); } diff --git a/tests/sys/fs/fuse/open.cc b/tests/sys/fs/fuse/open.cc index c3a289c7b32..1b1aa0d899a 100644 --- a/tests/sys/fs/fuse/open.cc +++ b/tests/sys/fs/fuse/open.cc @@ -48,14 +48,7 @@ void test_ok(int os_flags, int fuse_flags) { uint64_t ino = 42; int fd; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); - + FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && @@ -101,13 +94,7 @@ TEST_F(Open, enoent) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - })); - + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && @@ -129,13 +116,7 @@ TEST_F(Open, eperm) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - })); - + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && diff --git a/tests/sys/fs/fuse/opendir.cc b/tests/sys/fs/fuse/opendir.cc index 2c952426781..d15e5138654 100644 --- a/tests/sys/fs/fuse/opendir.cc +++ b/tests/sys/fs/fuse/opendir.cc @@ -42,13 +42,7 @@ class Opendir: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino) { - EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); } }; diff --git a/tests/sys/fs/fuse/read.cc b/tests/sys/fs/fuse/read.cc index d72f1fedb45..1a684270645 100644 --- a/tests/sys/fs/fuse/read.cc +++ b/tests/sys/fs/fuse/read.cc @@ -48,72 +48,9 @@ using namespace testing; class Read: public FuseTest { public: -const static uint64_t FH = 0xdeadbeef1a7ebabe; -void expect_getattr(uint64_t ino, uint64_t size) -{ - /* Until the attr cache is working, we may send an additional GETATTR */ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_GETATTR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, attr); - out->body.attr.attr.ino = ino; // Must match nodeid - out->body.attr.attr.mode = S_IFREG | 0644; - out->body.attr.attr.size = size; - })); - -} - void expect_lookup(const char *relpath, uint64_t ino) { - EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_open(uint64_t ino, uint32_t flags, int times) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPEN && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).Times(times) - .WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(out->header); - SET_OUT_HEADER_LEN(out, open); - out->body.open.fh = Read::FH; - out->body.open.open_flags = flags; - })); -} - -void expect_read(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, - const void *contents) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_READ && - in->header.nodeid == ino && - in->body.read.fh == Read::FH && - in->body.read.offset == offset && - in->body.read.size == isize); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(struct fuse_out_header) + osize; - memmove(out->body.bytes, contents, osize); - })).RetiresOnSaturation(); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } }; diff --git a/tests/sys/fs/fuse/readdir.cc b/tests/sys/fs/fuse/readdir.cc index 3258947103f..641a2ef19aa 100644 --- a/tests/sys/fs/fuse/readdir.cc +++ b/tests/sys/fs/fuse/readdir.cc @@ -40,43 +40,10 @@ using namespace testing; using namespace std; class Readdir: public FuseTest { -const static uint64_t FH = 0xdeadbeef1a7ebabe; public: void expect_lookup(const char *relpath, uint64_t ino) { - EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_opendir(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([](auto in) { - return (in->header.opcode == FUSE_STATFS); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, statfs); - })); - - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPENDIR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(out->header); - SET_OUT_HEADER_LEN(out, open); - out->body.open.fh = FH; - })); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); } void expect_readdir(uint64_t ino, uint64_t off, vector &ents) diff --git a/tests/sys/fs/fuse/readlink.cc b/tests/sys/fs/fuse/readlink.cc index 8d94cab790f..ff3490f4e1a 100644 --- a/tests/sys/fs/fuse/readlink.cc +++ b/tests/sys/fs/fuse/readlink.cc @@ -37,7 +37,13 @@ extern "C" { using namespace testing; -class Readlink: public FuseTest {}; +class Readlink: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + FuseTest::expect_lookup(relpath, ino, S_IFLNK | 0777, 1); +} +}; TEST_F(Readlink, eloop) { @@ -46,12 +52,7 @@ TEST_F(Readlink, eloop) const uint64_t ino = 42; char buf[80]; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFLNK | 0777; - out->body.entry.nodeid = ino; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -73,12 +74,7 @@ TEST_F(Readlink, ok) const uint64_t ino = 42; char buf[80]; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFLNK | 0777; - out->body.entry.nodeid = ino; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { diff --git a/tests/sys/fs/fuse/release.cc b/tests/sys/fs/fuse/release.cc index f53ef08e98d..71cf3392a53 100644 --- a/tests/sys/fs/fuse/release.cc +++ b/tests/sys/fs/fuse/release.cc @@ -40,67 +40,10 @@ using namespace testing; class Release: public FuseTest { -const static uint64_t FH = 0xdeadbeef1a7ebabe; - public: -void expect_getattr(uint64_t ino) +void expect_lookup(const char *relpath, uint64_t ino, int times) { - /* Until the attr cache is working, we may send an additional GETATTR */ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_GETATTR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, attr); - out->body.attr.attr.ino = ino; // Must match nodeid - out->body.attr.attr.mode = S_IFREG | 0644; - })); - -} - -void expect_lookup(const char *relpath, uint64_t ino) -{ - EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_open(uint64_t ino, int times) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPEN && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).Times(times) - .WillRepeatedly(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(out->header); - SET_OUT_HEADER_LEN(out, open); - out->body.open.fh = Release::FH; - })); - -} - -void expect_release(uint64_t ino, int times, ProcessMockerT r) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_RELEASE && - in->header.nodeid == ino && - in->body.release.fh == Release::FH); - }, Eq(true)), - _) - ).Times(times) - .WillRepeatedly(Invoke(r)); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, times); } }; @@ -114,10 +57,10 @@ TEST_F(Release, dup) uint64_t ino = 42; int fd, fd2; - expect_lookup(RELPATH, ino); - expect_open(ino, 1); - expect_getattr(ino); - expect_release(ino, 1, ReturnErrno(0)); + expect_lookup(RELPATH, ino, 1); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_release(ino, 1, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); @@ -143,10 +86,10 @@ TEST_F(Release, eio) uint64_t ino = 42; int fd; - expect_lookup(RELPATH, ino); - expect_open(ino, 1); - expect_getattr(ino); - expect_release(ino, 1, ReturnErrno(EIO)); + expect_lookup(RELPATH, ino, 1); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_release(ino, 1, EIO); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); @@ -166,10 +109,10 @@ TEST_F(Release, multiple_opens) uint64_t ino = 42; int fd, fd2; - expect_lookup(RELPATH, ino); - expect_open(ino, 2); - expect_getattr(ino); - expect_release(ino, 2, ReturnErrno(0)); + expect_lookup(RELPATH, ino, 2); + expect_open(ino, 0, 2); + expect_getattr(ino, 0); + expect_release(ino, 2, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); @@ -188,10 +131,10 @@ TEST_F(Release, ok) uint64_t ino = 42; int fd; - expect_lookup(RELPATH, ino); - expect_open(ino, 1); - expect_getattr(ino); - expect_release(ino, 1, ReturnErrno(0)); + expect_lookup(RELPATH, ino, 1); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_release(ino, 1, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); diff --git a/tests/sys/fs/fuse/releasedir.cc b/tests/sys/fs/fuse/releasedir.cc index c66918a834c..e2b10dd3e10 100644 --- a/tests/sys/fs/fuse/releasedir.cc +++ b/tests/sys/fs/fuse/releasedir.cc @@ -39,44 +39,10 @@ using namespace testing; class ReleaseDir: public FuseTest { -const static uint64_t FH = 0xdeadbeef1a7ebabe; - public: void expect_lookup(const char *relpath, uint64_t ino) { - EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_opendir(uint64_t ino) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([](auto in) { - return (in->header.opcode == FUSE_STATFS); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, statfs); - })); - - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPENDIR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(out->header); - SET_OUT_HEADER_LEN(out, open); - out->body.open.fh = FH; - })); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); } void expect_releasedir(uint64_t ino, ProcessMockerT r) diff --git a/tests/sys/fs/fuse/rename.cc b/tests/sys/fs/fuse/rename.cc index a15a9071874..ed37f3f8ba1 100644 --- a/tests/sys/fs/fuse/rename.cc +++ b/tests/sys/fs/fuse/rename.cc @@ -62,12 +62,7 @@ TEST_F(Rename, einval) const char RELSRC[] = "src"; uint64_t src_ino = 42; - EXPECT_LOOKUP(1, RELSRC).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFDIR | 0755; - out->body.entry.nodeid = src_ino; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELSRC, src_ino, S_IFDIR | 0755, 2); EXPECT_LOOKUP(src_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); ASSERT_NE(0, rename(FULLSRC, FULLDST)); @@ -80,7 +75,7 @@ TEST_F(Rename, enoent) const char FULLDST[] = "mountpoint/dst"; const char FULLSRC[] = "mountpoint/src"; const char RELSRC[] = "src"; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke(ReturnErrno(ENOENT))); @@ -98,7 +93,7 @@ TEST_F(Rename, DISABLED_entry_cache_negative) const char RELDST[] = "dst"; const char FULLSRC[] = "mountpoint/src"; const char RELSRC[] = "src"; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 uint64_t dst_dir_ino = 1; uint64_t ino = 42; /* @@ -108,13 +103,7 @@ TEST_F(Rename, DISABLED_entry_cache_negative) */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; - EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - SET_OUT_HEADER_LEN(out, entry); - })); - + expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); /* LOOKUP returns a negative cache entry for dst */ EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid)); @@ -144,7 +133,7 @@ TEST_F(Rename, DISABLED_entry_cache_negative_purge) const char RELDST[] = "dst"; const char FULLSRC[] = "mountpoint/src"; const char RELSRC[] = "src"; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 uint64_t dst_dir_ino = 1; uint64_t ino = 42; /* @@ -154,13 +143,7 @@ TEST_F(Rename, DISABLED_entry_cache_negative_purge) */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; - EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - SET_OUT_HEADER_LEN(out, entry); - })); - + expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); /* LOOKUP returns a negative cache entry for dst */ EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid)) .RetiresOnSaturation(); @@ -181,14 +164,7 @@ TEST_F(Rename, DISABLED_entry_cache_negative_purge) ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - EXPECT_LOOKUP(1, RELDST).Times(1) - .WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.nodeid = ino; - out->body.entry.attr.mode = S_IFREG | 0644; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); ASSERT_EQ(0, access(FULLDST, F_OK)) << strerror(errno); } @@ -197,18 +173,13 @@ TEST_F(Rename, exdev) { const char FULLB[] = "mountpoint/src"; const char RELB[] = "src"; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 uint64_t b_ino = 42; tmpfd = mkstemp(tmpfile); ASSERT_LE(0, tmpfd) << strerror(errno); - EXPECT_LOOKUP(1, RELB).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = b_ino; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELB, b_ino, S_IFREG | 0644, 2); ASSERT_NE(0, rename(tmpfile, FULLB)); ASSERT_EQ(EXDEV, errno); @@ -223,16 +194,11 @@ TEST_F(Rename, ok) const char RELDST[] = "dst"; const char FULLSRC[] = "mountpoint/src"; const char RELSRC[] = "src"; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 uint64_t dst_dir_ino = 1; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - SET_OUT_HEADER_LEN(out, entry); - })); + expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( @@ -260,23 +226,12 @@ TEST_F(Rename, overwrite) const char RELSRC[] = "src"; // The inode of the already-existing destination file uint64_t dst_ino = 2; - // FUSE hardcodes the mountpoint to inocde 1 + // FUSE hardcodes the mountpoint to inode 1 uint64_t dst_dir_ino = 1; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - SET_OUT_HEADER_LEN(out, entry); - })); - EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = dst_ino; - SET_OUT_HEADER_LEN(out, entry); - })); - + expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); + expect_lookup(RELDST, dst_ino, S_IFREG | 0644, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *src = (const char*)in->body.bytes + diff --git a/tests/sys/fs/fuse/rmdir.cc b/tests/sys/fs/fuse/rmdir.cc index 72ea9a7ce15..6f35c33dfd9 100644 --- a/tests/sys/fs/fuse/rmdir.cc +++ b/tests/sys/fs/fuse/rmdir.cc @@ -37,7 +37,19 @@ extern "C" { using namespace testing; -class Rmdir: public FuseTest {}; +class Rmdir: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + EXPECT_LOOKUP(1, relpath).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 2; + })); +} +}; TEST_F(Rmdir, enotempty) { @@ -45,13 +57,7 @@ TEST_F(Rmdir, enotempty) const char RELPATH[] = "some_dir"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr.nlink = 2; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_RMDIR && @@ -71,13 +77,7 @@ TEST_F(Rmdir, ok) const char RELPATH[] = "some_dir"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFDIR | 0755; - out->body.entry.nodeid = ino; - out->body.entry.attr.nlink = 2; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_RMDIR && diff --git a/tests/sys/fs/fuse/unlink.cc b/tests/sys/fs/fuse/unlink.cc index f103f545d67..0389d4c7172 100644 --- a/tests/sys/fs/fuse/unlink.cc +++ b/tests/sys/fs/fuse/unlink.cc @@ -36,7 +36,13 @@ extern "C" { using namespace testing; -class Unlink: public FuseTest {}; +class Unlink: public FuseTest { +public: +void expect_lookup(const char *relpath, uint64_t ino) +{ + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); +} +}; TEST_F(Unlink, eperm) { @@ -44,13 +50,7 @@ TEST_F(Unlink, eperm) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.attr.nlink = 1; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_UNLINK && @@ -70,13 +70,7 @@ TEST_F(Unlink, ok) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.attr.nlink = 1; - })); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_UNLINK && diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index 61512b69800..300f2daf8ad 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -38,7 +38,9 @@ #include "mockfs.hh" #include "utils.hh" -class FuseEnv: public ::testing::Environment { +using namespace testing; + +class FuseEnv: public Environment { virtual void SetUp() { const char *mod_name = "fuse"; const char *devnode = "/dev/fuse"; @@ -82,6 +84,146 @@ void FuseTest::SetUp() { } } +void FuseTest::expect_getattr(uint64_t ino, uint64_t size) +{ + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + out->body.attr.attr.size = size; + out->body.attr.attr_valid = UINT64_MAX; + })); +} + +void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, + int times) +{ + EXPECT_LOOKUP(1, relpath) + .Times(times) + .WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = mode; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 1; + out->body.entry.attr_valid = UINT64_MAX; + })); +} + +void FuseTest::expect_open(uint64_t ino, uint32_t flags, int times) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = FH; + out->body.open.open_flags = flags; + })); +} + +void FuseTest::expect_opendir(uint64_t ino) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_STATFS); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, statfs); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPENDIR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + out->body.open.fh = FH; + })); +} + +void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize, + uint64_t osize, const void *contents) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == FH && + in->body.read.offset == offset && + in->body.read.size == isize); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(struct fuse_out_header) + osize; + memmove(out->body.bytes, contents, osize); + })).RetiresOnSaturation(); +} + +void FuseTest::expect_release(uint64_t ino, int times, int error) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RELEASE && + in->header.nodeid == ino && + in->body.release.fh == FH); + }, Eq(true)), + _) + ).Times(times) + .WillRepeatedly(Invoke(ReturnErrno(error))); +} +void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize, + uint64_t osize, uint32_t flags, const void *contents) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *buf = (const char*)in->body.bytes + + sizeof(struct fuse_write_in); + bool pid_ok; + + if (in->body.write.write_flags & FUSE_WRITE_CACHE) + pid_ok = true; + else + pid_ok = (pid_t)in->header.pid == getpid(); + + return (in->header.opcode == FUSE_WRITE && + in->header.nodeid == ino && + in->body.write.fh == FH && + in->body.write.offset == offset && + in->body.write.size == isize && + pid_ok && + in->body.write.write_flags == flags && + 0 == bcmp(buf, contents, isize)); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, write); + out->body.write.size = osize; + })); +} + static void usage(char* progname) { fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname); exit(2); @@ -91,8 +233,8 @@ int main(int argc, char **argv) { int ch; FuseEnv *fuse_env = new FuseEnv; - ::testing::InitGoogleTest(&argc, argv); - ::testing::AddGlobalTestEnvironment(fuse_env); + InitGoogleTest(&argc, argv); + AddGlobalTestEnvironment(fuse_env); while ((ch = getopt(argc, argv, "v")) != -1) { switch (ch) { diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index ba0a533e508..de17d08f5ae 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -28,11 +28,21 @@ * SUCH DAMAGE. */ +/* + * TODO: remove FUSE_WRITE_CACHE definition when upgrading to protocol 7.9. + * This bit was actually part of kernel protocol version 7.2, but never + * documented until 7.9 + */ +#ifndef FUSE_WRITE_CACHE +#define FUSE_WRITE_CACHE 1 +#endif + class FuseTest : public ::testing::Test { protected: uint32_t m_maxreadahead; uint32_t m_init_flags; MockFS *m_mock = NULL; + const static uint64_t FH = 0xdeadbeef1a7ebabe; public: int m_maxbcachebuf; @@ -52,4 +62,54 @@ class FuseTest : public ::testing::Test { if (m_mock) delete m_mock; } + + /* + * Create an expectation that FUSE_GETATTR will be called for the given + * inode any number of times. It will respond with a few basic + * attributes, like the given size and the mode S_IFREG | 0644 + */ + void expect_getattr(uint64_t ino, uint64_t size); + + /* + * Create an expectation that FUSE_LOOKUP will be called for the given + * path exactly times times. It will respond with inode ino, mode + * mode, and cache validity forever. + */ + void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, + int times); + + /* + * Create an expectation that FUSE_GETATTR will be called for the given + * inode exactly times times. It will return with open_flags flags and + * file handle FH. + */ + void expect_open(uint64_t ino, uint32_t flags, int times); + + /* + * Create an expectation that FUSE_OPENDIR will be called exactly once + * for inode ino. + */ + void expect_opendir(uint64_t ino); + + /* + * Create an expectation that FUSE_READ will be called exactly once for + * the given inode, at offset offset and with size isize. It will + * return the first osize bytes from contents + */ + void expect_read(uint64_t ino, uint64_t offset, uint64_t isize, + uint64_t osize, const void *contents); + + /* + * Create an expectation that FUSE_RELEASE will be called times times + * for the given inode, returning error error + */ + void expect_release(uint64_t ino, int times, int error); + + /* + * Create an expectation that FUSE_WRITE will be called exactly once + * for the given inode, at offset offset, with write_flags flags, + * size isize and buffer contents. It will return osize + */ + void expect_write(uint64_t ino, uint64_t offset, uint64_t isize, + uint64_t osize, uint32_t flags, const void *contents); }; diff --git a/tests/sys/fs/fuse/write.cc b/tests/sys/fs/fuse/write.cc index 136f05f1c71..7bdd20f694d 100644 --- a/tests/sys/fs/fuse/write.cc +++ b/tests/sys/fs/fuse/write.cc @@ -43,87 +43,15 @@ extern "C" { #include "mockfs.hh" #include "utils.hh" -/* - * TODO: remove FUSE_WRITE_CACHE definition when upgrading to protocol 7.9. - * This bit was actually part of kernel protocol version 7.2, but never - * documented until 7.9 - */ -#ifndef FUSE_WRITE_CACHE -#define FUSE_WRITE_CACHE 1 -#endif - using namespace testing; class Write: public FuseTest { public: -const static uint64_t FH = 0xdeadbeef1a7ebabe; -void expect_getattr(uint64_t ino, uint64_t size) -{ - /* Until the attr cache is working, we may send an additional GETATTR */ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_GETATTR && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, attr); - out->body.attr.attr.ino = ino; // Must match nodeid - out->body.attr.attr.mode = S_IFREG | 0644; - out->body.attr.attr.size = size; - out->body.attr.attr_valid = UINT64_MAX; - })); - -} void expect_lookup(const char *relpath, uint64_t ino) { - EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, entry); - out->body.entry.attr.mode = S_IFREG | 0644; - out->body.entry.nodeid = ino; - out->body.entry.attr_valid = UINT64_MAX; - })); -} - -void expect_open(uint64_t ino, uint32_t flags, int times) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_OPEN && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).Times(times) - .WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(out->header); - SET_OUT_HEADER_LEN(out, open); - out->body.open.fh = Write::FH; - out->body.open.open_flags = flags; - })); -} - -void expect_read(uint64_t ino, uint64_t offset, uint64_t size, - const void *contents) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_READ && - in->header.nodeid == ino && - in->body.read.fh == Write::FH && - in->body.read.offset == offset && - in->body.read.size == size); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.len = sizeof(struct fuse_out_header) + size; - memmove(out->body.bytes, contents, size); - })).RetiresOnSaturation(); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); } void expect_release(uint64_t ino, ProcessMockerT r) @@ -137,37 +65,6 @@ void expect_release(uint64_t ino, ProcessMockerT r) ).WillRepeatedly(Invoke(r)); } -void expect_write(uint64_t ino, uint64_t offset, uint64_t isize, uint64_t osize, - uint32_t flags, const void *contents) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - const char *buf = (const char*)in->body.bytes + - sizeof(struct fuse_write_in); - bool pid_ok; - - if (in->body.write.write_flags & FUSE_WRITE_CACHE) - pid_ok = true; - else - pid_ok = (pid_t)in->header.pid == getpid(); - - return (in->header.opcode == FUSE_WRITE && - in->header.nodeid == ino && - in->body.write.fh == Write::FH && - in->body.write.offset == offset && - in->body.write.size == isize && - pid_ok && - in->body.write.write_flags == flags && - 0 == bcmp(buf, contents, isize)); - }, Eq(true)), - _) - ).WillOnce(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, write); - out->body.write.size = osize; - })); -} - }; class AioWrite: public Write { @@ -337,7 +234,7 @@ TEST_F(Write, DISABLED_direct_io_evicts_cache) expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); - expect_read(ino, 0, bufsize, CONTENTS0); + expect_read(ino, 0, bufsize, bufsize, CONTENTS0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS1); fd = open(FULLPATH, O_RDWR); @@ -352,7 +249,7 @@ TEST_F(Write, DISABLED_direct_io_evicts_cache) ASSERT_EQ(bufsize, write(fd, CONTENTS1, bufsize)) << strerror(errno); // Read again. Cache should be bypassed - expect_read(ino, 0, bufsize, CONTENTS1); + expect_read(ino, 0, bufsize, bufsize, CONTENTS1); ASSERT_EQ(0, fcntl(fd, F_SETFL, 0)) << strerror(errno); ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); ASSERT_EQ(bufsize, read(fd, readbuf, bufsize)) << strerror(errno); @@ -459,7 +356,7 @@ TEST_F(Write, DISABLED_mmap) expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, len); - expect_read(ino, 0, len, zeros); + expect_read(ino, 0, len, len, zeros); /* * Writes from the pager may or may not be associated with the correct * pid, so they must set FUSE_WRITE_CACHE @@ -663,7 +560,7 @@ TEST_F(WriteBack, o_direct) expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); - expect_read(ino, 0, bufsize, CONTENTS); + expect_read(ino, 0, bufsize, bufsize, CONTENTS); fd = open(FULLPATH, O_RDWR | O_DIRECT); EXPECT_LE(0, fd) << strerror(errno); From b6e5e8cf5bee3b99970170009272978b36cfb3ea Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Thu, 14 Mar 2019 14:59:59 +0000 Subject: [PATCH 26/41] fuse(4): skip the Write.append test unless vfs.fuse.sync_resize==0 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/write.cc | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/sys/fs/fuse/write.cc b/tests/sys/fs/fuse/write.cc index 7bdd20f694d..d671e40586a 100644 --- a/tests/sys/fs/fuse/write.cc +++ b/tests/sys/fs/fuse/write.cc @@ -65,6 +65,18 @@ void expect_release(uint64_t ino, ProcessMockerT r) ).WillRepeatedly(Invoke(r)); } +void require_sync_resize_0() { + const char *sync_resize_node = "vfs.fuse.sync_resize"; + int val = 0; + size_t size = sizeof(val); + + ASSERT_EQ(0, sysctlbyname(sync_resize_node, &val, &size, NULL, 0)) + << strerror(errno); + if (val != 0) + FAIL() << "vfs.fuse.sync_resize must be set to 0 for this test." + " That sysctl will probably be removed soon."; +} + }; class AioWrite: public Write { @@ -87,7 +99,6 @@ class WriteThrough: public Write { virtual void SetUp() { const char *cache_mode_node = "vfs.fuse.data_cache_mode"; - const char *sync_resize_node = "vfs.fuse.sync_resize"; int val = 0; size_t size = sizeof(val); @@ -98,12 +109,6 @@ virtual void SetUp() { FAIL() << "vfs.fuse.data_cache_mode must be set to 1 " "(writethrough) for this test"; - ASSERT_EQ(0, sysctlbyname(sync_resize_node, &val, &size, NULL, 0)) - << strerror(errno); - if (val != 0) - FAIL() << "vfs.fuse.sync_resize must be set to 0 for this test." - " That sysctl will probably be removed soon."; - FuseTest::SetUp(); } @@ -183,6 +188,8 @@ TEST_F(Write, append) uint64_t initial_offset = m_maxbcachebuf; int fd; + require_sync_resize_0(); + expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, initial_offset); From 3592c9fe120449391c42701174c33f3fa113f38b Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Thu, 14 Mar 2019 15:07:46 +0000 Subject: [PATCH 27/41] fuse(4) tests: minor tweaks * better debugging for FUSE_SETATTR * Move a big variable from stack to heap Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/mockfs.cc | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index 5794f67e8bf..8f67a45ecb9 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -170,7 +170,28 @@ void debug_fuseop(const mockfs_buf_in *in) in->body.readdir.size); break; case FUSE_SETATTR: - printf(" valid=%#x", in->body.setattr.valid); + if (verbosity <= 1) { + printf(" valid=%#x", in->body.setattr.valid); + break; + } + if (in->body.setattr.valid & FATTR_MODE) + printf(" mode=%#o", in->body.setattr.mode); + if (in->body.setattr.valid & FATTR_UID) + printf(" uid=%u", in->body.setattr.uid); + if (in->body.setattr.valid & FATTR_GID) + printf(" gid=%u", in->body.setattr.gid); + if (in->body.setattr.valid & FATTR_SIZE) + printf(" size=%zu", in->body.setattr.size); + if (in->body.setattr.valid & FATTR_ATIME) + printf(" atime=%zu.%u", + in->body.setattr.atime, + in->body.setattr.atimensec); + if (in->body.setattr.valid & FATTR_MTIME) + printf(" mtime=%zu.%u", + in->body.setattr.mtime, + in->body.setattr.mtimensec); + if (in->body.setattr.valid & FATTR_FH) + printf(" fh=%zu", in->body.setattr.fh); break; case FUSE_WRITE: printf(" offset=%lu size=%u flags=%u", @@ -288,40 +309,42 @@ void MockFS::kill_daemon() { void MockFS::loop() { mockfs_buf_in *in; - mockfs_buf_out out; + mockfs_buf_out *out; in = (mockfs_buf_in*) malloc(sizeof(*in)); + out = (mockfs_buf_out*) malloc(sizeof(*out)); ASSERT_TRUE(in != NULL); while (!quit) { bzero(in, sizeof(*in)); - bzero(&out, sizeof(out)); + bzero(out, sizeof(*out)); read_request(in); if (quit) break; if (verbosity > 0) debug_fuseop(in); if (pid_ok((pid_t)in->header.pid)) { - process(in, &out); + process(in, out); } else { /* * Reject any requests from unknown processes. Because * we actually do mount a filesystem, plenty of * unrelated system daemons may try to access it. */ - process_default(in, &out); + process_default(in, out); } if (in->header.opcode == FUSE_FORGET) { /*Alone among the opcodes, FORGET expects no response*/ continue; } - if (out.header.error == FUSE_NORESPONSE) { + if (out->header.error == FUSE_NORESPONSE) { /* Used by tests of slow opcodes. No response ATM */ continue; } - ASSERT_TRUE(write(m_fuse_fd, &out, out.header.len) > 0 || + ASSERT_TRUE(write(m_fuse_fd, out, out->header.len) > 0 || errno == EAGAIN) << strerror(errno); } + free(out); free(in); } From 94ef9d62ccd93f2e3ecb709ce38bcbe4c94ad67f Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Thu, 14 Mar 2019 17:20:24 +0000 Subject: [PATCH 28/41] fuse(4): add tests for FUSE_INTERRUPT This required changing the way that all operations are mocked. Previously MockFS::process had one input argument and one output argument. Now, it returns a vector of zero or more responses. This allows tests to simulate conditions where the filesystem daemon has a queue depth > 1. PR: 236530 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 9 + tests/sys/fs/fuse/create.cc | 40 ++-- tests/sys/fs/fuse/fsync.cc | 8 +- tests/sys/fs/fuse/getattr.cc | 16 +- tests/sys/fs/fuse/interrupt.cc | 312 ++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/link.cc | 4 +- tests/sys/fs/fuse/locks.cc | 20 +- tests/sys/fs/fuse/lookup.cc | 34 ++-- tests/sys/fs/fuse/mkdir.cc | 12 +- tests/sys/fs/fuse/mknod.cc | 4 +- tests/sys/fs/fuse/mockfs.cc | 74 ++++---- tests/sys/fs/fuse/mockfs.hh | 30 ++- tests/sys/fs/fuse/open.cc | 8 +- tests/sys/fs/fuse/opendir.cc | 12 +- tests/sys/fs/fuse/read.cc | 8 +- tests/sys/fs/fuse/readdir.cc | 8 +- tests/sys/fs/fuse/readlink.cc | 4 +- tests/sys/fs/fuse/releasedir.cc | 4 +- tests/sys/fs/fuse/rmdir.cc | 5 +- tests/sys/fs/fuse/setattr.cc | 89 ++++----- tests/sys/fs/fuse/statfs.cc | 4 +- tests/sys/fs/fuse/symlink.cc | 4 +- tests/sys/fs/fuse/utils.cc | 28 +-- tests/sys/fs/fuse/write.cc | 8 +- 24 files changed, 551 insertions(+), 194 deletions(-) create mode 100644 tests/sys/fs/fuse/interrupt.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 51ae166b227..05bdff6ecd1 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -4,12 +4,16 @@ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/fs/fuse +# We could simply link all of these files into a single executable. But since +# Kyua treats googletest programs as plain tests, it's better to separate them +# out, so we get more granular reporting. ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create ATF_TESTS_CXX+= flush ATF_TESTS_CXX+= fsync ATF_TESTS_CXX+= fsyncdir ATF_TESTS_CXX+= getattr +ATF_TESTS_CXX+= interrupt ATF_TESTS_CXX+= link ATF_TESTS_CXX+= locks ATF_TESTS_CXX+= lookup @@ -60,6 +64,11 @@ SRCS.getattr+= getmntopts.c SRCS.getattr+= mockfs.cc SRCS.getattr+= utils.cc +SRCS.interrupt+= interrupt.cc +SRCS.interrupt+= getmntopts.c +SRCS.interrupt+= mockfs.cc +SRCS.interrupt+= utils.cc + SRCS.link+= getmntopts.c SRCS.link+= link.cc SRCS.link+= mockfs.cc diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index 0d02a4701e0..df8b118f293 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -62,14 +62,14 @@ TEST_F(Create, DISABLED_attr_cache) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -145,14 +145,14 @@ TEST_F(Create, DISABLED_Enosys) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -160,11 +160,11 @@ TEST_F(Create, DISABLED_Enosys) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -173,12 +173,12 @@ TEST_F(Create, DISABLED_Enosys) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); EXPECT_LE(0, fd) << strerror(errno); @@ -214,14 +214,14 @@ TEST_F(Create, DISABLED_entry_cache_negative) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -230,12 +230,12 @@ TEST_F(Create, DISABLED_entry_cache_negative) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); ASSERT_LE(0, fd) << strerror(errno); @@ -269,13 +269,13 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -284,12 +284,12 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); ASSERT_LE(0, fd) << strerror(errno); @@ -344,14 +344,14 @@ TEST_F(Create, ok) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -360,12 +360,12 @@ TEST_F(Create, ok) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); EXPECT_LE(0, fd) << strerror(errno); diff --git a/tests/sys/fs/fuse/fsync.cc b/tests/sys/fs/fuse/fsync.cc index 01b568acb52..c92f1993934 100644 --- a/tests/sys/fs/fuse/fsync.cc +++ b/tests/sys/fs/fuse/fsync.cc @@ -132,11 +132,11 @@ TEST_F(Fsync, close) return (in->header.opcode == FUSE_SETATTR); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_FSYNC); @@ -244,12 +244,12 @@ TEST_F(Fsync, DISABLED_fsync_metadata_only) return (in->header.opcode == FUSE_SETATTR); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | mode; - })); + }))); expect_fsync(ino, 0, 0); diff --git a/tests/sys/fs/fuse/getattr.cc b/tests/sys/fs/fuse/getattr.cc index c107c74bc87..bee203e515f 100644 --- a/tests/sys/fs/fuse/getattr.cc +++ b/tests/sys/fs/fuse/getattr.cc @@ -47,25 +47,25 @@ TEST_F(Getattr, DISABLED_attr_cache) const uint64_t ino = 42; struct stat sb; - EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr_valid = UINT64_MAX; out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); EXPECT_EQ(0, stat(FULLPATH, &sb)); /* The second stat(2) should use cached attributes */ EXPECT_EQ(0, stat(FULLPATH, &sb)); @@ -97,14 +97,14 @@ TEST_F(Getattr, attr_cache_timeout) }, Eq(true)), _) ).Times(2) - .WillRepeatedly(Invoke([=](auto in, auto out) { + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr_valid_nsec = timeout_ns; out->body.attr.attr_valid = UINT64_MAX; out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); EXPECT_EQ(0, stat(FULLPATH, &sb)); usleep(2 * timeout_ns / 1000); /* Timeout has expire. stat(2) should requery the daemon */ @@ -144,7 +144,7 @@ TEST_F(Getattr, ok) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid @@ -161,7 +161,7 @@ TEST_F(Getattr, ok) out->body.attr.attr.uid = 10; out->body.attr.attr.gid = 11; out->body.attr.attr.rdev = 12; - })); + }))); ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); EXPECT_EQ(1, sb.st_size); diff --git a/tests/sys/fs/fuse/interrupt.cc b/tests/sys/fs/fuse/interrupt.cc new file mode 100644 index 00000000000..3c4767845e7 --- /dev/null +++ b/tests/sys/fs/fuse/interrupt.cc @@ -0,0 +1,312 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +/* Don't do anything; all we care about is that the syscall gets interrupted */ +void sigusr2_handler(int __unused sig) { + if (verbosity > 1) + printf("Signaled!\n"); +} + +void* killer(void* target) { + /* + * Sleep for awhile so we can be mostly confident that the main thread + * is already blocked in write(2) + */ + usleep(250'000); + if (verbosity > 1) + printf("Signalling!\n"); + pthread_kill(*(pthread_t*)target, SIGUSR2); + + return(NULL); +} + +class Interrupt: public FuseTest { +public: +pthread_t m_child; + +Interrupt(): m_child(NULL) {}; + +void expect_lookup(const char *relpath, uint64_t ino) +{ + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); +} + +/* + * Expect a FUSE_WRITE but don't reply. Instead, just record the unique value + * to the provided pointer + */ +void expect_write(uint64_t ino, uint64_t *write_unique) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_WRITE && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto &out __unused) { + *write_unique = in->header.unique; + })); +} + +void setup_interruptor(pthread_t self) +{ + ASSERT_EQ(0, signal(SIGUSR2, sigusr2_handler)) << strerror(errno); + ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)self)) + << strerror(errno); +} + +void TearDown() { + if (m_child != NULL) { + pthread_join(m_child, NULL); + } + + FuseTest::TearDown(); +} +}; + +/* + * An interrupt operation that gets received after the original command is + * complete should generate an EAGAIN response. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ +TEST_F(Interrupt, DISABLED_already_complete) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + pthread_t self; + uint64_t write_unique = 0; + + self = pthread_self(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, &write_unique); + EXPECT_CALL(*m_mock, process( + ResultOf([&](auto in) { + return (in->header.opcode == FUSE_INTERRUPT && + in->body.interrupt.unique == write_unique); + }, Eq(true)), + _) + ).WillOnce(Invoke([&](auto in, auto &out) { + // First complete the write request + auto out0 = new mockfs_buf_out; + out0->header.unique = write_unique; + SET_OUT_HEADER_LEN(out0, write); + out0->body.write.size = bufsize; + out.push_back(out0); + + // Then, respond EAGAIN to the interrupt request + auto out1 = new mockfs_buf_out; + out1->header.unique = in->header.unique; + out1->header.error = -EAGAIN; + out1->header.len = sizeof(out1->header); + out.push_back(out1); + })); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + + setup_interruptor(self); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and + * complete the original operation whenever it damn well pleases. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ +TEST_F(Interrupt, DISABLED_ignore) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + pthread_t self; + uint64_t write_unique; + + self = pthread_self(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, &write_unique); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_INTERRUPT && + in->body.interrupt.unique == write_unique); + }, Eq(true)), + _) + ).WillOnce(Invoke([&](auto in __unused, auto &out) { + // Ignore FUSE_INTERRUPT; respond to the FUSE_WRITE + auto out0 = new mockfs_buf_out; + out0->header.unique = write_unique; + SET_OUT_HEADER_LEN(out0, write); + out0->body.write.size = bufsize; + out.push_back(out0); + })); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + + setup_interruptor(self); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * A syscall that gets interrupted while blocking on FUSE I/O should send a + * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR + * in response to the _original_ operation. The kernel should ultimately + * return EINTR to userspace + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ +TEST_F(Interrupt, DISABLED_in_progress) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + pthread_t self; + uint64_t write_unique; + + self = pthread_self(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, &write_unique); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_INTERRUPT && + in->body.interrupt.unique == write_unique); + }, Eq(true)), + _) + ).WillOnce(Invoke([&](auto in __unused, auto &out) { + auto out0 = new mockfs_buf_out; + out0->header.error = -EINTR; + out0->header.unique = write_unique; + out0->header.len = sizeof(out0->header); + out.push_back(out0); + })); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + + setup_interruptor(self); + ASSERT_EQ(-1, write(fd, CONTENTS, bufsize)); + EXPECT_EQ(EINTR, errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * If the FUSE filesystem receives the FUSE_INTERRUPT operation before + * processing the original, then it should wait for "some timeout" for the + * original operation to arrive. If not, it should send EAGAIN to the + * INTERRUPT operation, and the kernel should requeue the INTERRUPT. + * + * In this test, we'll pretend that the INTERRUPT arrives too soon, gets + * EAGAINed, then the kernel requeues it, and the second time around it + * successfully interrupts the original + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ +TEST_F(Interrupt, DISABLED_too_soon) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + pthread_t self; + uint64_t write_unique; + + self = pthread_self(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, &write_unique); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_INTERRUPT && + in->body.interrupt.unique == write_unique); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EAGAIN))) + .RetiresOnSaturation(); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_INTERRUPT && + in->body.interrupt.unique == write_unique); + }, Eq(true)), + _) + ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { + auto out0 = new mockfs_buf_out; + out0->header.error = -EINTR; + out0->header.unique = write_unique; + out0->header.len = sizeof(out0->header); + out.push_back(out0); + })); + + fd = open(FULLPATH, O_WRONLY); + ASSERT_LE(0, fd) << strerror(errno); + + setup_interruptor(self); + ASSERT_EQ(-1, write(fd, CONTENTS, bufsize)); + EXPECT_EQ(EINTR, errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} diff --git a/tests/sys/fs/fuse/link.cc b/tests/sys/fs/fuse/link.cc index 84944f42822..6ba606691fc 100644 --- a/tests/sys/fs/fuse/link.cc +++ b/tests/sys/fs/fuse/link.cc @@ -92,12 +92,12 @@ TEST_F(Link, ok) (0 == strcmp(name, RELPATH))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; - })); + }))); EXPECT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno); } diff --git a/tests/sys/fs/fuse/locks.cc b/tests/sys/fs/fuse/locks.cc index 6a6f8d8a304..e6d2e57cbda 100644 --- a/tests/sys/fs/fuse/locks.cc +++ b/tests/sys/fs/fuse/locks.cc @@ -123,12 +123,12 @@ TEST_F(Getlk, DISABLED_no_locks) in->body.getlk.lk.pid == 10); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -170,14 +170,14 @@ TEST_F(Getlk, DISABLED_lock_exists) in->body.getlk.lk.pid == 10); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk.start = 100; out->body.getlk.lk.end = 199; out->body.getlk.lk.type = F_WRLCK; out->body.getlk.lk.pid = (uint32_t)pid2;; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -251,12 +251,12 @@ TEST_F(Setlk, DISABLED_set) in->body.getlk.lk.pid == 10); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -296,12 +296,12 @@ TEST_F(Setlk, DISABLED_set_eof) in->body.getlk.lk.pid == 10); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -414,12 +414,12 @@ TEST_F(Setlkw, DISABLED_set) in->body.getlk.lk.pid == 10); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); diff --git a/tests/sys/fs/fuse/lookup.cc b/tests/sys/fs/fuse/lookup.cc index 86ab5d7d308..e478be267b5 100644 --- a/tests/sys/fs/fuse/lookup.cc +++ b/tests/sys/fs/fuse/lookup.cc @@ -52,7 +52,8 @@ TEST_F(Lookup, DISABLED_attr_cache) const uint64_t generation = 13; struct stat sb; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.nodeid = ino; @@ -72,7 +73,7 @@ TEST_F(Lookup, DISABLED_attr_cache) out->body.entry.attr.gid = 11; out->body.entry.attr.rdev = 12; out->body.entry.generation = generation; - })); + }))); /* stat(2) issues a VOP_LOOKUP followed by a VOP_GETATTR */ ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); EXPECT_EQ(1, sb.st_size); @@ -118,14 +119,15 @@ TEST_F(Lookup, attr_cache_timeout) */ long timeout_ns = 250'000'000; - EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.nodeid = ino; out->body.entry.attr_valid_nsec = timeout_ns; out->body.entry.attr.ino = ino; // Must match nodeid out->body.entry.attr.mode = S_IFREG | 0644; - })); + }))); expect_getattr(ino, 0); /* access(2) will issue a VOP_LOOKUP but not a VOP_GETATTR */ @@ -154,13 +156,14 @@ TEST_F(Lookup, entry_cache) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.entry_valid = UINT64_MAX; out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = 14; - })); + }))); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); /* The second access(2) should use the cache */ ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); @@ -224,13 +227,13 @@ TEST_F(Lookup, DISABLED_entry_cache_timeout) long timeout_ns = 250'000'000; EXPECT_LOOKUP(1, RELPATH).Times(2) - .WillRepeatedly(Invoke([=](auto in, auto out) { + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.entry_valid_nsec = timeout_ns; out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = 14; - })); + }))); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); usleep(2 * timeout_ns / 1000); /* The cache has timed out; VOP_LOOKUP should query the daemon*/ @@ -248,12 +251,13 @@ TEST_F(Lookup, ok) const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = 14; - })); + }))); /* * access(2) is one of the few syscalls that will not (always) follow * up a successful VOP_LOOKUP with another VOP. @@ -270,18 +274,20 @@ TEST_F(Lookup, subdir) uint64_t dir_ino = 2; uint64_t file_ino = 3; - EXPECT_LOOKUP(1, DIRPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, DIRPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFDIR | 0755; out->body.entry.nodeid = dir_ino; - })); - EXPECT_LOOKUP(dir_ino, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + }))); + EXPECT_LOOKUP(dir_ino, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = file_ino; - })); + }))); /* * access(2) is one of the few syscalls that will not (always) follow * up a successful VOP_LOOKUP with another VOP. diff --git a/tests/sys/fs/fuse/mkdir.cc b/tests/sys/fs/fuse/mkdir.cc index 2066c33eb2a..ea148af5b94 100644 --- a/tests/sys/fs/fuse/mkdir.cc +++ b/tests/sys/fs/fuse/mkdir.cc @@ -95,14 +95,14 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.create.entry.attr.mode = S_IFDIR | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); } @@ -134,13 +134,13 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFDIR | mode; out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; - })); + }))); ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); @@ -168,14 +168,14 @@ TEST_F(Mkdir, ok) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.create.entry.attr.mode = S_IFDIR | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; - })); + }))); ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); } diff --git a/tests/sys/fs/fuse/mknod.cc b/tests/sys/fs/fuse/mknod.cc index 078d67d03d2..2101d10c7df 100644 --- a/tests/sys/fs/fuse/mknod.cc +++ b/tests/sys/fs/fuse/mknod.cc @@ -67,7 +67,7 @@ void test_ok(mode_t mode, dev_t dev) { (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = mode; @@ -75,7 +75,7 @@ void test_ok(mode_t mode, dev_t dev) { out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; out->body.create.entry.attr.rdev = dev; - })); + }))); EXPECT_EQ(0, mknod(FULLPATH, mode, dev)) << strerror(errno); } diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index 8f67a45ecb9..a098b336f00 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -105,28 +105,43 @@ const char* opcode2opname(uint32_t opcode) return (table[opcode]); } -std::function +ProcessMockerT ReturnErrno(int error) { - return([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = -error; - out->header.len = sizeof(out->header); + return([=](auto in, auto &out) { + auto out0 = new mockfs_buf_out; + out0->header.unique = in->header.unique; + out0->header.error = -error; + out0->header.len = sizeof(out0->header); + out.push_back(out0); }); } /* Helper function used for returning negative cache entries for LOOKUP */ -std::function +ProcessMockerT ReturnNegativeCache(const struct timespec *entry_valid) { - return([=](auto in, auto out) { + return([=](auto in, auto &out) { /* nodeid means ENOENT and cache it */ - out->body.entry.nodeid = 0; - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.entry_valid = entry_valid->tv_sec; - out->body.entry.entry_valid_nsec = entry_valid->tv_nsec; - SET_OUT_HEADER_LEN(out, entry); + auto out0 = new mockfs_buf_out; + out0->body.entry.nodeid = 0; + out0->header.unique = in->header.unique; + out0->header.error = 0; + out0->body.entry.entry_valid = entry_valid->tv_sec; + out0->body.entry.entry_valid_nsec = entry_valid->tv_nsec; + SET_OUT_HEADER_LEN(out0, entry); + out.push_back(out0); + }); +} + +ProcessMockerT +ReturnImmediate(std::function f) +{ + return([=](auto in, auto &out) { + auto out0 = new mockfs_buf_out; + f(in, out0); + out.push_back(out0); }); } @@ -309,14 +324,12 @@ void MockFS::kill_daemon() { void MockFS::loop() { mockfs_buf_in *in; - mockfs_buf_out *out; + std::vector out; in = (mockfs_buf_in*) malloc(sizeof(*in)); - out = (mockfs_buf_out*) malloc(sizeof(*out)); ASSERT_TRUE(in != NULL); while (!quit) { bzero(in, sizeof(*in)); - bzero(out, sizeof(*out)); read_request(in); if (quit) break; @@ -332,19 +345,14 @@ void MockFS::loop() { */ process_default(in, out); } - if (in->header.opcode == FUSE_FORGET) { - /*Alone among the opcodes, FORGET expects no response*/ - continue; + for (auto &it: out) { + ASSERT_TRUE(write(m_fuse_fd, it, it->header.len) > 0 || + errno == EAGAIN) + << strerror(errno); + delete it; } - if (out->header.error == FUSE_NORESPONSE) { - /* Used by tests of slow opcodes. No response ATM */ - continue; - } - ASSERT_TRUE(write(m_fuse_fd, out, out->header.len) > 0 || - errno == EAGAIN) - << strerror(errno); + out.clear(); } - free(out); free(in); } @@ -369,10 +377,14 @@ bool MockFS::pid_ok(pid_t pid) { } } -void MockFS::process_default(const mockfs_buf_in *in, mockfs_buf_out* out) { - out->header.unique = in->header.unique; - out->header.error = -EOPNOTSUPP; - out->header.len = sizeof(out->header); +void MockFS::process_default(const mockfs_buf_in *in, + std::vector &out) +{ + auto out0 = new mockfs_buf_out; + out0->header.unique = in->header.unique; + out0->header.error = -EOPNOTSUPP; + out0->header.len = sizeof(out0->header); + out.push_back(out0); } void MockFS::read_request(mockfs_buf_in *in) { diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 4ce66e28c8f..dbf217ba0f2 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -132,11 +132,16 @@ union fuse_payloads_out { struct mockfs_buf_out { fuse_out_header header; union fuse_payloads_out body; + + /* Default constructor: zero everything */ + mockfs_buf_out() { + memset(this, 0, sizeof(*this)); + } }; /* A function that can be invoked in place of MockFS::process */ typedef std::function + std::vector &out)> ProcessMockerT; /* @@ -146,8 +151,12 @@ ProcessMockerT; ProcessMockerT ReturnErrno(int error); /* Helper function used for returning negative cache entries for LOOKUP */ -std::function -ReturnNegativeCache(const struct timespec *entry_valid); +ProcessMockerT ReturnNegativeCache(const struct timespec *entry_valid); + +/* Helper function used for returning a single immediate response */ +ProcessMockerT ReturnImmediate( + std::function f); /* * Fake FUSE filesystem @@ -182,7 +191,8 @@ class MockFS { bool pid_ok(pid_t pid); /* Default request handler */ - void process_default(const mockfs_buf_in*, mockfs_buf_out*); + void process_default(const mockfs_buf_in*, + std::vector&); /* Entry point for the daemon thread */ static void* service(void*); @@ -207,12 +217,14 @@ class MockFS { /* * Request handler * - * This method is expected to provide the response to each FUSE - * operation. Responses must be immediate (so this method can't be used - * for testing a daemon with queue depth > 1). Test cases must define - * each response using Googlemock expectations + * This method is expected to provide the responses to each FUSE + * operation. For an immediate response, push one buffer into out. + * For a delayed response, push nothing. For an immediate response + * plus a delayed response to an earlier operation, push two bufs. + * Test cases must define each response using Googlemock expectations */ - MOCK_METHOD2(process, void(const mockfs_buf_in*, mockfs_buf_out*)); + MOCK_METHOD2(process, void(const mockfs_buf_in*, + std::vector&)); /* Gracefully unmount */ void unmount(); diff --git a/tests/sys/fs/fuse/open.cc b/tests/sys/fs/fuse/open.cc index 1b1aa0d899a..19d6ddbf24e 100644 --- a/tests/sys/fs/fuse/open.cc +++ b/tests/sys/fs/fuse/open.cc @@ -56,11 +56,11 @@ void test_ok(int os_flags, int fuse_flags) { in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -69,12 +69,12 @@ void test_ok(int os_flags, int fuse_flags) { in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; - })); + }))); fd = open(FULLPATH, os_flags); EXPECT_LE(0, fd) << strerror(errno); diff --git a/tests/sys/fs/fuse/opendir.cc b/tests/sys/fs/fuse/opendir.cc index d15e5138654..948bfd27d72 100644 --- a/tests/sys/fs/fuse/opendir.cc +++ b/tests/sys/fs/fuse/opendir.cc @@ -109,10 +109,10 @@ TEST_F(Opendir, open) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, open); - })); + }))); EXPECT_LE(0, open(FULLPATH, O_DIRECTORY)) << strerror(errno); } @@ -129,10 +129,10 @@ TEST_F(Opendir, opendir) return (in->header.opcode == FUSE_STATFS); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, statfs); - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -140,10 +140,10 @@ TEST_F(Opendir, opendir) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, open); - })); + }))); errno = 0; EXPECT_NE(NULL, opendir(FULLPATH)) << strerror(errno); diff --git a/tests/sys/fs/fuse/read.cc b/tests/sys/fs/fuse/read.cc index 1a684270645..a41301b4c67 100644 --- a/tests/sys/fs/fuse/read.cc +++ b/tests/sys/fs/fuse/read.cc @@ -242,11 +242,11 @@ TEST_F(Read, mmap) in->body.read.size >= bufsize); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(struct fuse_out_header) + bufsize; memmove(out->body.bytes, CONTENTS, bufsize); - })); + }))); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); @@ -411,11 +411,11 @@ TEST_F(Read, sendfile) in->body.read.size >= bufsize); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(struct fuse_out_header) + bufsize; memmove(out->body.bytes, CONTENTS, bufsize); - })); + }))); ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_STREAM, 0, sp)) << strerror(errno); diff --git a/tests/sys/fs/fuse/readdir.cc b/tests/sys/fs/fuse/readdir.cc index 641a2ef19aa..ee9556984a0 100644 --- a/tests/sys/fs/fuse/readdir.cc +++ b/tests/sys/fs/fuse/readdir.cc @@ -55,7 +55,7 @@ void expect_readdir(uint64_t ino, uint64_t off, vector &ents) in->body.readdir.offset == off); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes; int i = 0; @@ -92,7 +92,7 @@ void expect_readdir(uint64_t ino, uint64_t off, vector &ents) i++; } out->header.len += sizeof(out->header); - })); + }))); } }; @@ -208,11 +208,11 @@ TEST_F(Readdir, nodots) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; out->header.len = sizeof(out->header); - })); + }))); errno = 0; dir = opendir(FULLPATH); diff --git a/tests/sys/fs/fuse/readlink.cc b/tests/sys/fs/fuse/readlink.cc index ff3490f4e1a..203b21149c2 100644 --- a/tests/sys/fs/fuse/readlink.cc +++ b/tests/sys/fs/fuse/readlink.cc @@ -82,11 +82,11 @@ TEST_F(Readlink, ok) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; strlcpy(out->body.str, dst, sizeof(out->body.str)); out->header.len = sizeof(out->header) + strlen(dst) + 1; - })); + }))); EXPECT_EQ((ssize_t)strlen(dst) + 1, diff --git a/tests/sys/fs/fuse/releasedir.cc b/tests/sys/fs/fuse/releasedir.cc index e2b10dd3e10..fc4f15a263c 100644 --- a/tests/sys/fs/fuse/releasedir.cc +++ b/tests/sys/fs/fuse/releasedir.cc @@ -75,11 +75,11 @@ TEST_F(ReleaseDir, dup) in->body.readdir.offset == 0); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; out->header.len = sizeof(out->header); - })); + }))); expect_releasedir(ino, ReturnErrno(0)); dir = opendir(FULLPATH); diff --git a/tests/sys/fs/fuse/rmdir.cc b/tests/sys/fs/fuse/rmdir.cc index 6f35c33dfd9..5ab958563d4 100644 --- a/tests/sys/fs/fuse/rmdir.cc +++ b/tests/sys/fs/fuse/rmdir.cc @@ -41,13 +41,14 @@ class Rmdir: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino) { - EXPECT_LOOKUP(1, relpath).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, relpath) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFDIR | 0755; out->body.entry.nodeid = ino; out->body.entry.attr.nlink = 2; - })); + }))); } }; diff --git a/tests/sys/fs/fuse/setattr.cc b/tests/sys/fs/fuse/setattr.cc index b20a160572e..5e0202fc49a 100644 --- a/tests/sys/fs/fuse/setattr.cc +++ b/tests/sys/fs/fuse/setattr.cc @@ -51,13 +51,14 @@ TEST_F(Setattr, chmod) const mode_t oldmode = 0755; const mode_t newmode = 0644; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | oldmode; out->body.entry.nodeid = ino; out->body.entry.attr.mode = S_IFREG | oldmode; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { @@ -69,12 +70,12 @@ TEST_F(Setattr, chmod) in->body.setattr.mode == newmode); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | newmode; - })); + }))); EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); } @@ -89,14 +90,15 @@ TEST_F(Setattr, chown) const uid_t olduser = 33; const uid_t newuser = 44; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.attr.gid = oldgroup; out->body.entry.attr.uid = olduser; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { @@ -109,14 +111,14 @@ TEST_F(Setattr, chown) in->body.setattr.gid == newgroup); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; out->body.attr.attr.uid = newuser; out->body.attr.attr.gid = newgroup; - })); + }))); EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno); } @@ -133,14 +135,15 @@ TEST_F(Setattr, eperm) const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0777; out->body.entry.nodeid = ino; out->body.entry.attr.uid = in->header.uid; out->body.entry.attr.gid = in->header.gid; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { @@ -163,13 +166,14 @@ TEST_F(Setattr, fchmod) const mode_t oldmode = 0755; const mode_t newmode = 0644; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | oldmode; out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -177,11 +181,11 @@ TEST_F(Setattr, fchmod) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -190,12 +194,12 @@ TEST_F(Setattr, fchmod) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | oldmode; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -207,12 +211,12 @@ TEST_F(Setattr, fchmod) in->body.setattr.mode == newmode); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | newmode; - })); + }))); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); @@ -231,14 +235,15 @@ TEST_F(Setattr, ftruncate) const off_t oldsize = 99; const off_t newsize = 12345; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0755; out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; out->body.entry.attr.size = oldsize; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -246,12 +251,12 @@ TEST_F(Setattr, ftruncate) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); out->body.open.fh = fh; - })); + }))); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( @@ -260,13 +265,13 @@ TEST_F(Setattr, ftruncate) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0755; out->body.attr.attr.size = oldsize; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -278,13 +283,13 @@ TEST_F(Setattr, ftruncate) in->body.setattr.fh == fh); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0755; out->body.attr.attr.size = newsize; - })); + }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); @@ -300,13 +305,13 @@ TEST_F(Setattr, truncate) { const uint64_t oldsize = 100'000'000; const uint64_t newsize = 20'000'000; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.attr.size = oldsize; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { @@ -318,13 +323,13 @@ TEST_F(Setattr, truncate) { in->body.setattr.size == newsize); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; out->body.attr.attr.size = newsize; - })); + }))); EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno); } @@ -342,7 +347,7 @@ TEST_F(Setattr, utimensat) { {.tv_sec = 7, .tv_nsec = 8}, }; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -352,7 +357,7 @@ TEST_F(Setattr, utimensat) { out->body.entry.attr.atimensec = oldtimes[0].tv_nsec; out->body.entry.attr.mtime = oldtimes[1].tv_sec; out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec; - })); + }))); /* * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR @@ -364,7 +369,7 @@ TEST_F(Setattr, utimensat) { in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid @@ -373,7 +378,7 @@ TEST_F(Setattr, utimensat) { out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; out->body.attr.attr.mtime = oldtimes[1].tv_sec; out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -390,7 +395,7 @@ TEST_F(Setattr, utimensat) { newtimes[1].tv_nsec); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid @@ -399,7 +404,7 @@ TEST_F(Setattr, utimensat) { out->body.attr.attr.atimensec = newtimes[0].tv_nsec; out->body.attr.attr.mtime = newtimes[1].tv_sec; out->body.attr.attr.mtimensec = newtimes[1].tv_nsec; - })); + }))); EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) << strerror(errno); } @@ -418,7 +423,7 @@ TEST_F(Setattr, utimensat_mtime_only) { {.tv_sec = 7, .tv_nsec = 8}, }; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -428,7 +433,7 @@ TEST_F(Setattr, utimensat_mtime_only) { out->body.entry.attr.atimensec = oldtimes[0].tv_nsec; out->body.entry.attr.mtime = oldtimes[1].tv_sec; out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec; - })); + }))); /* * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR @@ -440,7 +445,7 @@ TEST_F(Setattr, utimensat_mtime_only) { in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid @@ -449,7 +454,7 @@ TEST_F(Setattr, utimensat_mtime_only) { out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; out->body.attr.attr.mtime = oldtimes[1].tv_sec; out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec; - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -463,7 +468,7 @@ TEST_F(Setattr, utimensat_mtime_only) { newtimes[1].tv_nsec); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid @@ -472,7 +477,7 @@ TEST_F(Setattr, utimensat_mtime_only) { out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; out->body.attr.attr.mtime = newtimes[1].tv_sec; out->body.attr.attr.mtimensec = newtimes[1].tv_nsec; - })); + }))); EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) << strerror(errno); } diff --git a/tests/sys/fs/fuse/statfs.cc b/tests/sys/fs/fuse/statfs.cc index a7268abd655..e4bf6cdd0e1 100644 --- a/tests/sys/fs/fuse/statfs.cc +++ b/tests/sys/fs/fuse/statfs.cc @@ -86,7 +86,7 @@ TEST_F(Statfs, ok) return (in->header.opcode == FUSE_STATFS); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, statfs); out->body.statfs.st.blocks = 1000; @@ -96,7 +96,7 @@ TEST_F(Statfs, ok) out->body.statfs.st.ffree = 6; out->body.statfs.st.namelen = 128; out->body.statfs.st.frsize = 1024; - })); + }))); ASSERT_NE(NULL, getcwd(mp, PATH_MAX)) << strerror(errno); strlcat(mp, "/mountpoint", PATH_MAX); diff --git a/tests/sys/fs/fuse/symlink.cc b/tests/sys/fs/fuse/symlink.cc index 2c9a0deab25..3515c3f18cf 100644 --- a/tests/sys/fs/fuse/symlink.cc +++ b/tests/sys/fs/fuse/symlink.cc @@ -80,12 +80,12 @@ TEST_F(Symlink, ok) (0 == strcmp(name, RELPATH))); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFLNK | 0777; out->body.entry.nodeid = ino; - })); + }))); EXPECT_EQ(0, symlink(dst, FULLPATH)) << strerror(errno); } diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index 300f2daf8ad..93f5c547fd1 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -93,14 +93,14 @@ void FuseTest::expect_getattr(uint64_t ino, uint64_t size) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; out->body.attr.attr.size = size; out->body.attr.attr_valid = UINT64_MAX; - })); + }))); } void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, @@ -108,14 +108,14 @@ void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, { EXPECT_LOOKUP(1, relpath) .Times(times) - .WillRepeatedly(Invoke([=](auto in, auto out) { + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = mode; out->body.entry.nodeid = ino; out->body.entry.attr.nlink = 1; out->body.entry.attr_valid = UINT64_MAX; - })); + }))); } void FuseTest::expect_open(uint64_t ino, uint32_t flags, int times) @@ -127,13 +127,13 @@ void FuseTest::expect_open(uint64_t ino, uint32_t flags, int times) }, Eq(true)), _) ).Times(times) - .WillRepeatedly(Invoke([=](auto in, auto out) { + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); out->body.open.fh = FH; out->body.open.open_flags = flags; - })); + }))); } void FuseTest::expect_opendir(uint64_t ino) @@ -143,10 +143,10 @@ void FuseTest::expect_opendir(uint64_t ino) return (in->header.opcode == FUSE_STATFS); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, statfs); - })); + }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -154,12 +154,12 @@ void FuseTest::expect_opendir(uint64_t ino) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); out->body.open.fh = FH; - })); + }))); } void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize, @@ -174,11 +174,11 @@ void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize, in->body.read.size == isize); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.len = sizeof(struct fuse_out_header) + osize; memmove(out->body.bytes, contents, osize); - })).RetiresOnSaturation(); + }))).RetiresOnSaturation(); } void FuseTest::expect_release(uint64_t ino, int times, int error) @@ -217,11 +217,11 @@ void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize, 0 == bcmp(buf, contents, isize)); }, Eq(true)), _) - ).WillOnce(Invoke([=](auto in, auto out) { + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, write); out->body.write.size = osize; - })); + }))); } static void usage(char* progname) { diff --git a/tests/sys/fs/fuse/write.cc b/tests/sys/fs/fuse/write.cc index d671e40586a..6d8dd1793d4 100644 --- a/tests/sys/fs/fuse/write.cc +++ b/tests/sys/fs/fuse/write.cc @@ -503,11 +503,11 @@ TEST_F(WriteBack, close) return (in->header.opcode == FUSE_SETATTR); }, Eq(true)), _) - ).WillRepeatedly(Invoke([=](auto in, auto out) { + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid - })); + }))); expect_release(ino, ReturnErrno(0)); fd = open(FULLPATH, O_RDWR); @@ -635,14 +635,14 @@ TEST_F(WriteThrough, DISABLED_update_file_size) }, Eq(true)), _) ).Times(2) - .WillRepeatedly(Invoke([=](auto in, auto out) { + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; out->body.attr.attr.size = 0; out->body.attr.attr_valid = UINT64_MAX; - })); + }))); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); fd = open(FULLPATH, O_RDWR); From 0b6ee94ad534f41b423c0797fbd6b7e4378388e3 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Thu, 14 Mar 2019 23:05:59 +0000 Subject: [PATCH 29/41] fuse(4): add tests for extended attributes Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 6 + tests/sys/fs/fuse/mockfs.cc | 12 + tests/sys/fs/fuse/mockfs.hh | 5 + tests/sys/fs/fuse/xattr.cc | 509 ++++++++++++++++++++++++++++++++++++ 4 files changed, 532 insertions(+) create mode 100644 tests/sys/fs/fuse/xattr.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 05bdff6ecd1..894eaf7ba82 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -33,6 +33,7 @@ ATF_TESTS_CXX+= statfs ATF_TESTS_CXX+= symlink ATF_TESTS_CXX+= unlink ATF_TESTS_CXX+= write +ATF_TESTS_CXX+= xattr SRCS.access+= access.cc SRCS.access+= getmntopts.c @@ -164,6 +165,11 @@ SRCS.write+= mockfs.cc SRCS.write+= write.cc SRCS.write+= utils.cc +SRCS.xattr+= getmntopts.c +SRCS.xattr+= mockfs.cc +SRCS.xattr+= xattr.cc +SRCS.xattr+= utils.cc + # TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index a098b336f00..24bdbc35b01 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -208,6 +208,18 @@ void debug_fuseop(const mockfs_buf_in *in) if (in->body.setattr.valid & FATTR_FH) printf(" fh=%zu", in->body.setattr.fh); break; + case FUSE_SETXATTR: + /* + * In theory neither the xattr name and value need be + * ASCII, but in this test suite they always are. + */ + { + const char *attr = (const char*)in->body.bytes + + sizeof(fuse_setxattr_in); + const char *v = attr + strlen(attr) + 1; + printf(" %s=%s", attr, v); + } + break; case FUSE_WRITE: printf(" offset=%lu size=%u flags=%u", in->body.write.offset, in->body.write.size, diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index dbf217ba0f2..d8ee9fd5d9c 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -86,8 +86,10 @@ union fuse_payloads_in { fuse_forget_in forget; fuse_interrupt_in interrupt; fuse_lk_in getlk; + fuse_getxattr_in getxattr; fuse_init_in init; fuse_link_in link; + fuse_listxattr_in listxattr; char lookup[0]; fuse_mkdir_in mkdir; fuse_mknod_in mknod; @@ -100,6 +102,7 @@ union fuse_payloads_in { fuse_rename_in rename; char rmdir[0]; fuse_setattr_in setattr; + fuse_setxattr_in setxattr; fuse_lk_in setlk; char unlink[0]; fuse_write_in write; @@ -117,7 +120,9 @@ union fuse_payloads_out { uint8_t bytes[0x20000]; fuse_entry_out entry; fuse_lk_out getlk; + fuse_getxattr_out getxattr; fuse_init_out init; + fuse_listxattr_out listxattr; fuse_open_out open; fuse_lk_out setlk; fuse_statfs_out statfs; diff --git a/tests/sys/fs/fuse/xattr.cc b/tests/sys/fs/fuse/xattr.cc new file mode 100644 index 00000000000..bc916297f4e --- /dev/null +++ b/tests/sys/fs/fuse/xattr.cc @@ -0,0 +1,509 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +const char FULLPATH[] = "mountpoint/some_file.txt"; +const char RELPATH[] = "some_file.txt"; + +/* For testing filesystems without posix locking support */ +class Xattr: public FuseTest { +public: +void expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *a = (const char*)in->body.bytes + + sizeof(fuse_getxattr_in); + return (in->header.opcode == FUSE_GETXATTR && + in->header.nodeid == ino && + 0 == strcmp(attr, a)); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} + +void expect_listxattr(uint64_t ino, uint32_t size, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LISTXATTR && + in->header.nodeid == ino && + in->body.listxattr.size == size); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)) + .RetiresOnSaturation(); +} + +void expect_removexattr(uint64_t ino, const char *attr, int error) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *a = (const char*)in->body.bytes; + return (in->header.opcode == FUSE_REMOVEXATTR && + in->header.nodeid == ino && + 0 == strcmp(attr, a)); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(error))); +} + +void expect_setxattr(uint64_t ino, const char *attr, const char *value, + ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *a = (const char*)in->body.bytes + + sizeof(fuse_setxattr_in); + const char *v = a + strlen(a) + 1; + return (in->header.opcode == FUSE_SETXATTR && + in->header.nodeid == ino && + 0 == strcmp(attr, a) && + 0 == strcmp(value, v)); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} + +}; + +class Getxattr: public Xattr {}; +class Listxattr: public Xattr {}; +class Removexattr: public Xattr {}; +class Setxattr: public Xattr {}; + +/* + * If the extended attribute does not exist on this file, the daemon should + * return ENOATTR (ENODATA on Linux, but it's up to the daemon to choose the + * correct errror code) + */ +TEST_F(Getxattr, enoattr) +{ + char data[80]; + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR)); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(-1, r); + ASSERT_EQ(ENOATTR, errno); +} + +/* + * On FreeBSD, if the user passes an insufficiently large buffer then the + * filesystem is supposed to copy as much of the attribute's value as will fit. + * + * On Linux, however, the filesystem is supposed to return ERANGE. + * + * libfuse specifies the Linux behavior. However, that's probably an error. + * It would probably be correct for the filesystem to use platform-dependent + * behavior. + * + * This test case covers a filesystem that uses the Linux behavior + */ +TEST_F(Getxattr, erange) +{ + char data[10]; + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "user.foo", ReturnErrno(ERANGE)); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(-1, r); + ASSERT_EQ(ERANGE, errno); +} + +/* + * If the user passes a 0-length buffer, then the daemon should just return the + * size of the attribute + */ +TEST_F(Getxattr, size_only) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "user.foo", ReturnImmediate([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, getxattr); + out->body.getxattr.size = 99; + })); + + ASSERT_EQ(99, extattr_get_file(FULLPATH, ns, "foo", NULL, 0)) + << strerror(errno);; +} + +/* + * Successfully get an attribute from the system namespace + */ +TEST_F(Getxattr, system) +{ + uint64_t ino = 42; + char data[80]; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_SYSTEM; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "system.foo", + ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + memcpy((void*)out->body.bytes, value, value_len); + out->header.len = sizeof(out->header) + value_len; + }) + ); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(value_len, r) << strerror(errno); + EXPECT_STREQ(value, data); +} + +/* + * Successfully get an attribute from the user namespace + */ +TEST_F(Getxattr, user) +{ + uint64_t ino = 42; + char data[80]; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "user.foo", + ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + memcpy((void*)out->body.bytes, value, value_len); + out->header.len = sizeof(out->header) + value_len; + }) + ); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(value_len, r) << strerror(errno); + EXPECT_STREQ(value, data); +} + +/* + * Listing extended attributes failed because they aren't configured on this + * filesystem + */ +TEST_F(Listxattr, enotsup) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnErrno(ENOTSUP)); + + ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); + ASSERT_EQ(ENOTSUP, errno); +} + +/* + * On FreeBSD, if the user passes an insufficiently large buffer then the + * filesystem is supposed to copy as much of the attribute's value as will fit. + * + * On Linux, however, the filesystem is supposed to return ERANGE. + * + * libfuse specifies the Linux behavior. However, that's probably an error. + * It would probably be correct for the filesystem to use platform-dependent + * behavior. + * + * This test case covers a filesystem that uses the Linux behavior + */ +TEST_F(Listxattr, erange) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnErrno(ERANGE)); + + ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); + ASSERT_EQ(ERANGE, errno); +} + +/* + * Get the size of the list that it would take to list no extended attributes + */ +TEST_F(Listxattr, size_only_empty) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnImmediate([](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.listxattr.size = 0; + SET_OUT_HEADER_LEN(out, listxattr); + })); + + ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) + << strerror(errno); +} + +/* + * Get the size of the list that it would take to list some extended + * attributes. Due to the format differences between a FreeBSD and a + * Linux/FUSE extended attribute list, fuse(4) will actually allocate a buffer + * and get the whole list, then convert it, just to figure out its size. + */ +TEST_F(Listxattr, size_only_nonempty) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnImmediate([](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.listxattr.size = 45; + SET_OUT_HEADER_LEN(out, listxattr); + })); + + // TODO: fix the expected size after fixing the size calculation bug in + // fuse_vnop_listextattr. It should be exactly 45. + expect_listxattr(ino, 53, ReturnImmediate([](auto in, auto out) { + const char l[] = "user.foo"; + out->header.unique = in->header.unique; + strlcpy((char*)out->body.bytes, l, sizeof(out->body.bytes)); + out->header.len = sizeof(fuse_out_header) + sizeof(l); + })); + + ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0)) + << strerror(errno); +} + +TEST_F(Listxattr, size_only_really_big) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnImmediate([](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.listxattr.size = 16000; + SET_OUT_HEADER_LEN(out, listxattr); + })); + + // TODO: fix the expected size after fixing the size calculation bug in + // fuse_vnop_listextattr. It should be exactly 16000. + expect_listxattr(ino, 16008, ReturnImmediate([](auto in, auto out) { + const char l[16] = "user.foobarbang"; + out->header.unique = in->header.unique; + for (int i=0; i < 1000; i++) { + memcpy(&out->body.bytes[16 * i], l, 16); + } + out->header.len = sizeof(fuse_out_header) + 16000; + })); + + ASSERT_EQ(11000, extattr_list_file(FULLPATH, ns, NULL, 0)) + << strerror(errno); +} + +/* + * List all of the user attributes of a file which has both user and system + * attributes + */ +TEST_F(Listxattr, user) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + char data[80]; + char expected[9] = {3, 'f', 'o', 'o', 4, 'b', 'a', 'n', 'g'}; + char attrs[28] = "user.foo\0system.x\0user.bang"; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.listxattr.size = sizeof(attrs); + SET_OUT_HEADER_LEN(out, listxattr); + })); + + // TODO: fix the expected size after fixing the size calculation bug in + // fuse_vnop_listextattr. + expect_listxattr(ino, sizeof(attrs) + 8, + ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + memcpy((void*)out->body.bytes, attrs, sizeof(attrs)); + out->header.len = sizeof(fuse_out_header) + sizeof(attrs); + })); + + ASSERT_EQ((ssize_t)sizeof(expected), + extattr_list_file(FULLPATH, ns, data, sizeof(data))) + << strerror(errno); + ASSERT_EQ(0, memcmp(expected, data, sizeof(expected))); +} + +/* + * List all of the system attributes of a file which has both user and system + * attributes + */ +TEST_F(Listxattr, system) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_SYSTEM; + char data[80]; + char expected[2] = {1, 'x'}; + char attrs[28] = "user.foo\0system.x\0user.bang"; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.listxattr.size = sizeof(attrs); + SET_OUT_HEADER_LEN(out, listxattr); + })); + + // TODO: fix the expected size after fixing the size calculation bug in + // fuse_vnop_listextattr. + expect_listxattr(ino, sizeof(attrs) + 8, + ReturnImmediate([&](auto in, auto out) { + out->header.unique = in->header.unique; + memcpy((void*)out->body.bytes, attrs, sizeof(attrs)); + out->header.len = sizeof(fuse_out_header) + sizeof(attrs); + })); + + ASSERT_EQ((ssize_t)sizeof(expected), + extattr_list_file(FULLPATH, ns, data, sizeof(data))) + << strerror(errno); + ASSERT_EQ(0, memcmp(expected, data, sizeof(expected))); +} + +/* Fail to remove a nonexistent attribute */ +TEST_F(Removexattr, enoattr) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_removexattr(ino, "user.foo", ENOATTR); + + ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); + ASSERT_EQ(ENOATTR, errno); +} + +/* Successfully remove a user xattr */ +TEST_F(Removexattr, user) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_removexattr(ino, "user.foo", 0); + + ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) + << strerror(errno); +} + +/* Successfully remove a system xattr */ +TEST_F(Removexattr, system) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_SYSTEM; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_removexattr(ino, "system.foo", 0); + + ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) + << strerror(errno); +} + +/* + * SETXATTR will return ENOTSUP if the namespace is invalid or the filesystem + * as currently configured doesn't support extended attributes. + */ +TEST_F(Setxattr, enotsup) +{ + uint64_t ino = 42; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOTSUP)); + + r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); + ASSERT_EQ(-1, r); + EXPECT_EQ(ENOTSUP, errno); +} + +/* + * Successfully set a user attribute. + */ +TEST_F(Setxattr, user) +{ + uint64_t ino = 42; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_setxattr(ino, "user.foo", value, ReturnErrno(0)); + + r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); + ASSERT_EQ(value_len, r) << strerror(errno); +} + +/* + * Successfully set a system attribute. + */ +TEST_F(Setxattr, system) +{ + uint64_t ino = 42; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_SYSTEM; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_setxattr(ino, "system.foo", value, ReturnErrno(0)); + + r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); + ASSERT_EQ(value_len, r) << strerror(errno); +} From 4da6e8cef16641e7bde4a1618085576d6f7f9407 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 15 Mar 2019 14:49:27 +0000 Subject: [PATCH 30/41] fuse(4): add tests for FUSE_DESTROY, FUSE_FORGET, and unlinking open files Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 6 +++ tests/sys/fs/fuse/destroy.cc | 92 ++++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.cc | 3 ++ tests/sys/fs/fuse/unlink.cc | 58 +++++++++++++++-------- 4 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 tests/sys/fs/fuse/destroy.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 894eaf7ba82..ec01b0c3476 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -9,6 +9,7 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse # out, so we get more granular reporting. ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create +ATF_TESTS_CXX+= destroy ATF_TESTS_CXX+= flush ATF_TESTS_CXX+= fsync ATF_TESTS_CXX+= fsyncdir @@ -45,6 +46,11 @@ SRCS.create+= getmntopts.c SRCS.create+= mockfs.cc SRCS.create+= utils.cc +SRCS.destroy+= destroy.cc +SRCS.destroy+= getmntopts.c +SRCS.destroy+= mockfs.cc +SRCS.destroy+= utils.cc + SRCS.flush+= flush.cc SRCS.flush+= getmntopts.c SRCS.flush+= mockfs.cc diff --git a/tests/sys/fs/fuse/destroy.cc b/tests/sys/fs/fuse/destroy.cc new file mode 100644 index 00000000000..e63e37df600 --- /dev/null +++ b/tests/sys/fs/fuse/destroy.cc @@ -0,0 +1,92 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Destroy: public FuseTest { +public: +void expect_destroy(int error) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_DESTROY); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(error))); +} + +void expect_forget(uint64_t ino, uint64_t nlookup) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_FORGET && + in->header.nodeid == ino && + in->body.forget.nlookup == nlookup); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + /* FUSE_FORGET has no response! */ + })); +} +}; + +/* + * On unmount the kernel should send a FUSE_DESTROY operation. It should also + * send FUSE_FORGET operations for all inodes with lookup_count > 0. It's hard + * to trigger FUSE_FORGET in way except by unmounting, so this is the only + * testing that FUSE_FORGET gets. + */ +TEST_F(Destroy, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 2); + expect_forget(1, 1); + expect_forget(ino, 2); + expect_destroy(0); + + /* + * access(2) the file to force a lookup. Access it twice to double its + * lookup count. + */ + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); + ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); + + /* + * Unmount, triggering a FUSE_DESTROY and also causing a VOP_RECLAIM + * for every vnode on this mp, triggering FUSE_FORGET for each of them. + */ + m_mock->unmount(); +} diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index 24bdbc35b01..49467b4ca56 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -159,6 +159,9 @@ void debug_fuseop(const mockfs_buf_in *in) in->header.unique, in->header.len); } switch (in->header.opcode) { + case FUSE_FORGET: + printf(" nlookup=%lu", in->body.forget.nlookup); + break; case FUSE_FSYNC: printf(" flags=%#x", in->body.fsync.fsync_flags); break; diff --git a/tests/sys/fs/fuse/unlink.cc b/tests/sys/fs/fuse/unlink.cc index 0389d4c7172..d2ab0cb9bfe 100644 --- a/tests/sys/fs/fuse/unlink.cc +++ b/tests/sys/fs/fuse/unlink.cc @@ -38,10 +38,23 @@ using namespace testing; class Unlink: public FuseTest { public: -void expect_lookup(const char *relpath, uint64_t ino) +void expect_lookup(const char *relpath, uint64_t ino, int times) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, times); } + +void expect_unlink(uint64_t parent, const char *path, int error) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_UNLINK && + 0 == strcmp(path, in->body.unlink) && + in->header.nodeid == parent); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(error))); +} + }; TEST_F(Unlink, eperm) @@ -50,15 +63,8 @@ TEST_F(Unlink, eperm) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - expect_lookup(RELPATH, ino); - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_UNLINK && - 0 == strcmp(RELPATH, in->body.unlink) && - in->header.nodeid == 1); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnErrno(EPERM))); + expect_lookup(RELPATH, ino, 1); + expect_unlink(1, RELPATH, EPERM); ASSERT_NE(0, unlink(FULLPATH)); ASSERT_EQ(EPERM, errno); @@ -70,15 +76,27 @@ TEST_F(Unlink, ok) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - expect_lookup(RELPATH, ino); - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_UNLINK && - 0 == strcmp(RELPATH, in->body.unlink) && - in->header.nodeid == 1); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnErrno(0))); + expect_lookup(RELPATH, ino, 1); + expect_unlink(1, RELPATH, 0); ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); } + +/* Unlink an open file */ +TEST_F(Unlink, open_but_deleted) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino, 2); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_unlink(1, RELPATH, 0); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} From 9ae9282e95b090cf66ce9c7f666984bea238d146 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 15 Mar 2019 16:16:50 +0000 Subject: [PATCH 31/41] fuse(4): add some miscellaneous test cases that I had overlooked * Test that FUSE_FLUSH and FUSE_RELEASE release POSIX file locks * Test that FUSE_SETATTR's attr caching feature works * Fix some minor mistakes in the posix file lock tests Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/flush.cc | 63 +++++++++++++++++++++++++++++--- tests/sys/fs/fuse/fsync.cc | 2 +- tests/sys/fs/fuse/locks.cc | 69 +++++++++++++++++------------------- tests/sys/fs/fuse/mockfs.cc | 8 +++++ tests/sys/fs/fuse/mockfs.hh | 2 ++ tests/sys/fs/fuse/release.cc | 57 ++++++++++++++++++++++++++--- tests/sys/fs/fuse/setattr.cc | 64 ++++++++++++++++++++++++++++----- tests/sys/fs/fuse/utils.cc | 4 ++- tests/sys/fs/fuse/utils.hh | 3 +- 9 files changed, 214 insertions(+), 58 deletions(-) diff --git a/tests/sys/fs/fuse/flush.cc b/tests/sys/fs/fuse/flush.cc index 9f071b8a892..a721dc08f4e 100644 --- a/tests/sys/fs/fuse/flush.cc +++ b/tests/sys/fs/fuse/flush.cc @@ -41,12 +41,13 @@ using namespace testing; class Flush: public FuseTest { public: -void expect_flush(uint64_t ino, int times, ProcessMockerT r) +void expect_flush(uint64_t ino, int times, pid_t lo, ProcessMockerT r) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_FLUSH && in->header.nodeid == ino && + in->body.flush.lock_owner == (uint64_t)lo && in->body.flush.fh == FH); }, Eq(true)), _) @@ -74,7 +75,12 @@ void expect_release() } }; -// TODO: lock_owner stuff +class FlushWithLocks: public Flush { + virtual void SetUp() { + m_init_flags = FUSE_POSIX_LOCKS; + Flush::SetUp(); + } +}; /* If a file descriptor is duplicated, every close causes FLUSH */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ @@ -88,7 +94,7 @@ TEST_F(Flush, DISABLED_dup) expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_flush(ino, 2, ReturnErrno(0)); + expect_flush(ino, 2, 0, ReturnErrno(0)); expect_release(); fd = open(FULLPATH, O_WRONLY); @@ -119,7 +125,7 @@ TEST_F(Flush, DISABLED_eio) expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_flush(ino, 1, ReturnErrno(EIO)); + expect_flush(ino, 1, 0, ReturnErrno(EIO)); expect_release(); fd = open(FULLPATH, O_WRONLY); @@ -140,7 +146,7 @@ TEST_F(Flush, DISABLED_flush) expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_flush(ino, 1, ReturnErrno(0)); + expect_flush(ino, 1, 0, ReturnErrno(0)); expect_release(); fd = open(FULLPATH, O_WRONLY); @@ -148,3 +154,50 @@ TEST_F(Flush, DISABLED_flush) ASSERT_TRUE(0 == close(fd)) << strerror(errno); } + +/* + * When closing a file with a POSIX file lock, flush should release the lock, + * _even_if_ it's not the process's last file descriptor for this file. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ +TEST_F(FlushWithLocks, DISABLED_unlock_on_close) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd, fd2; + struct flock fl; + pid_t pid = getpid(); + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETLK && + in->header.nodeid == ino && + in->body.setlk.fh == FH); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, setlk); + out->body.setlk.lk = in->body.setlk.lk; + }))); + expect_flush(ino, 1, pid, ReturnErrno(0)); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 0; + fl.l_len = 0; + fl.l_pid = pid; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); + + fd2 = dup(fd); + ASSERT_EQ(0, close(fd2)) << strerror(errno); + /* Deliberately leak fd */ +} diff --git a/tests/sys/fs/fuse/fsync.cc b/tests/sys/fs/fuse/fsync.cc index c92f1993934..1673367557e 100644 --- a/tests/sys/fs/fuse/fsync.cc +++ b/tests/sys/fs/fuse/fsync.cc @@ -143,7 +143,7 @@ TEST_F(Fsync, close) }, Eq(true)), _) ).Times(0); - expect_release(ino, 1, 0); + expect_release(ino, 1, 0, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); diff --git a/tests/sys/fs/fuse/locks.cc b/tests/sys/fs/fuse/locks.cc index e6d2e57cbda..e4bb50d4acf 100644 --- a/tests/sys/fs/fuse/locks.cc +++ b/tests/sys/fs/fuse/locks.cc @@ -120,7 +120,7 @@ TEST_F(Getlk, DISABLED_no_locks) in->body.getlk.lk.start == 10 && in->body.getlk.lk.end == 1009 && in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.getlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { @@ -153,7 +153,7 @@ TEST_F(Getlk, DISABLED_lock_exists) struct flock fl; int fd; pid_t pid = 1234; - pid_t pid2 = 1234; + pid_t pid2 = 1235; expect_lookup(RELPATH, ino); expect_open(ino, 0, 1); @@ -167,7 +167,7 @@ TEST_F(Getlk, DISABLED_lock_exists) in->body.getlk.lk.start == 10 && in->body.getlk.lk.end == 1009 && in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.getlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { @@ -243,19 +243,18 @@ TEST_F(Setlk, DISABLED_set) ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && - in->body.getlk.fh == FH && - in->body.getlk.owner == (uint32_t)pid && - in->body.getlk.lk.start == 10 && - in->body.getlk.lk.end == 1009 && - in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.setlk.fh == FH && + in->body.setlk.owner == (uint32_t)pid && + in->body.setlk.lk.start == 10 && + in->body.setlk.lk.end == 1009 && + in->body.setlk.lk.type == F_RDLCK && + in->body.setlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, getlk); - out->body.getlk.lk = in->body.getlk.lk; - out->body.getlk.lk.type = F_UNLCK; + SET_OUT_HEADER_LEN(out, setlk); + out->body.setlk.lk = in->body.setlk.lk; }))); fd = open(FULLPATH, O_RDWR); @@ -288,19 +287,18 @@ TEST_F(Setlk, DISABLED_set_eof) ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && - in->body.getlk.fh == FH && - in->body.getlk.owner == (uint32_t)pid && - in->body.getlk.lk.start == 10 && - in->body.getlk.lk.end == OFFSET_MAX && - in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.setlk.fh == FH && + in->body.setlk.owner == (uint32_t)pid && + in->body.setlk.lk.start == 10 && + in->body.setlk.lk.end == OFFSET_MAX && + in->body.setlk.lk.type == F_RDLCK && + in->body.setlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, getlk); - out->body.getlk.lk = in->body.getlk.lk; - out->body.getlk.lk.type = F_UNLCK; + SET_OUT_HEADER_LEN(out, setlk); + out->body.setlk.lk = in->body.setlk.lk; }))); fd = open(FULLPATH, O_RDWR); @@ -333,12 +331,12 @@ TEST_F(Setlk, DISABLED_eagain) ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && - in->body.getlk.fh == FH && - in->body.getlk.owner == (uint32_t)pid && - in->body.getlk.lk.start == 10 && - in->body.getlk.lk.end == 1009 && - in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.setlk.fh == FH && + in->body.setlk.owner == (uint32_t)pid && + in->body.setlk.lk.start == 10 && + in->body.setlk.lk.end == 1009 && + in->body.setlk.lk.type == F_RDLCK && + in->body.setlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnErrno(EAGAIN))); @@ -406,19 +404,18 @@ TEST_F(Setlkw, DISABLED_set) ResultOf([=](auto in) { return (in->header.opcode == FUSE_SETLK && in->header.nodeid == ino && - in->body.getlk.fh == FH && - in->body.getlk.owner == (uint32_t)pid && - in->body.getlk.lk.start == 10 && - in->body.getlk.lk.end == 1009 && - in->body.getlk.lk.type == F_RDLCK && - in->body.getlk.lk.pid == 10); + in->body.setlkw.fh == FH && + in->body.setlkw.owner == (uint32_t)pid && + in->body.setlkw.lk.start == 10 && + in->body.setlkw.lk.end == 1009 && + in->body.setlkw.lk.type == F_RDLCK && + in->body.setlkw.lk.pid == (uint64_t)pid); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, getlk); - out->body.getlk.lk = in->body.getlk.lk; - out->body.getlk.lk.type = F_UNLCK; + SET_OUT_HEADER_LEN(out, setlkw); + out->body.setlkw.lk = in->body.setlkw.lk; }))); fd = open(FULLPATH, O_RDWR); diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index 49467b4ca56..41e8b826aa8 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -159,6 +159,9 @@ void debug_fuseop(const mockfs_buf_in *in) in->header.unique, in->header.len); } switch (in->header.opcode) { + case FUSE_FLUSH: + printf(" lock_owner=%lu", in->body.flush.lock_owner); + break; case FUSE_FORGET: printf(" nlookup=%lu", in->body.forget.nlookup); break; @@ -187,6 +190,11 @@ void debug_fuseop(const mockfs_buf_in *in) printf(" offset=%lu size=%u", in->body.readdir.offset, in->body.readdir.size); break; + case FUSE_RELEASE: + printf(" flags=%#x lock_owner=%lu", + in->body.release.flags, + in->body.release.lock_owner); + break; case FUSE_SETATTR: if (verbosity <= 1) { printf(" valid=%#x", in->body.setattr.valid); diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index d8ee9fd5d9c..b836d18d308 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -104,6 +104,7 @@ union fuse_payloads_in { fuse_setattr_in setattr; fuse_setxattr_in setxattr; fuse_lk_in setlk; + fuse_lk_in setlkw; char unlink[0]; fuse_write_in write; }; @@ -125,6 +126,7 @@ union fuse_payloads_out { fuse_listxattr_out listxattr; fuse_open_out open; fuse_lk_out setlk; + fuse_lk_out setlkw; fuse_statfs_out statfs; /* * The protocol places no limits on the length of the string. This is diff --git a/tests/sys/fs/fuse/release.cc b/tests/sys/fs/fuse/release.cc index 71cf3392a53..f3f5e26dd5a 100644 --- a/tests/sys/fs/fuse/release.cc +++ b/tests/sys/fs/fuse/release.cc @@ -47,7 +47,13 @@ void expect_lookup(const char *relpath, uint64_t ino, int times) } }; -// TODO: lock owner stuff +class ReleaseWithLocks: public Release { + virtual void SetUp() { + m_init_flags = FUSE_POSIX_LOCKS; + Release::SetUp(); + } +}; + /* If a file descriptor is duplicated, only the last close causes RELEASE */ TEST_F(Release, dup) @@ -60,7 +66,7 @@ TEST_F(Release, dup) expect_lookup(RELPATH, ino, 1); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_release(ino, 1, 0); + expect_release(ino, 1, 0, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); @@ -89,7 +95,7 @@ TEST_F(Release, eio) expect_lookup(RELPATH, ino, 1); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_release(ino, 1, EIO); + expect_release(ino, 1, 0, EIO); fd = open(FULLPATH, O_WRONLY); EXPECT_LE(0, fd) << strerror(errno); @@ -112,7 +118,7 @@ TEST_F(Release, multiple_opens) expect_lookup(RELPATH, ino, 2); expect_open(ino, 0, 2); expect_getattr(ino, 0); - expect_release(ino, 2, 0); + expect_release(ino, 2, 0, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); @@ -134,10 +140,51 @@ TEST_F(Release, ok) expect_lookup(RELPATH, ino, 1); expect_open(ino, 0, 1); expect_getattr(ino, 0); - expect_release(ino, 1, 0); + expect_release(ino, 1, 0, 0); fd = open(FULLPATH, O_RDONLY); EXPECT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, close(fd)) << strerror(errno); } + +/* When closing a file with a POSIX file lock, release should release the lock*/ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234581 */ +TEST_F(ReleaseWithLocks, DISABLED_unlock_on_close) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + struct flock fl; + pid_t pid = getpid(); + + expect_lookup(RELPATH, ino, 1); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_SETLK && + in->header.nodeid == ino && + in->body.setlk.fh == FH); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, setlk); + out->body.setlk.lk = in->body.setlk.lk; + }))); + expect_release(ino, 1, (uint64_t)pid, 0); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + fl.l_start = 0; + fl.l_len = 0; + fl.l_pid = pid; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_sysid = 0; + ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); + + ASSERT_EQ(0, close(fd)) << strerror(errno); +} diff --git a/tests/sys/fs/fuse/setattr.cc b/tests/sys/fs/fuse/setattr.cc index 5e0202fc49a..ec8de916412 100644 --- a/tests/sys/fs/fuse/setattr.cc +++ b/tests/sys/fs/fuse/setattr.cc @@ -42,6 +42,55 @@ using namespace testing; class Setattr : public FuseTest {}; +/* + * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs + * should use the cached attributes, rather than query the daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ +TEST_F(Setattr, DISABLED_attr_cache) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + struct stat sb; + const mode_t newmode = 0644; + + EXPECT_LOOKUP(1, RELPATH) + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + }))); + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + /* In protocol 7.23, ctime will be changed too */ + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | newmode; + }))); + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_GETATTR); + }, Eq(true)), + _) + ).Times(0); + + /* Set an attribute with SETATTR */ + ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); + + /* The stat(2) should use cached attributes */ + ASSERT_EQ(0, stat(FULLPATH, &sb)); + EXPECT_EQ(S_IFREG | newmode, sb.st_mode); +} + /* Change the mode of a file */ TEST_F(Setattr, chmod) { @@ -305,7 +354,8 @@ TEST_F(Setattr, truncate) { const uint64_t oldsize = 100'000'000; const uint64_t newsize = 20'000'000; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -347,7 +397,8 @@ TEST_F(Setattr, utimensat) { {.tv_sec = 7, .tv_nsec = 8}, }; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -423,7 +474,8 @@ TEST_F(Setattr, utimensat_mtime_only) { {.tv_sec = 7, .tv_nsec = 8}, }; - EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { + EXPECT_LOOKUP(1, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; @@ -481,9 +533,3 @@ TEST_F(Setattr, utimensat_mtime_only) { EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) << strerror(errno); } - -/* - * Writethrough cache: newly changed attributes should be automatically cached, - * if the filesystem allows it - */ -//TODO TEST_F(Setattr, writethrough_cache){} diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index 93f5c547fd1..29671299820 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -181,12 +181,14 @@ void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize, }))).RetiresOnSaturation(); } -void FuseTest::expect_release(uint64_t ino, int times, int error) +void FuseTest::expect_release(uint64_t ino, int times, uint64_t lock_owner, + int error) { EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_RELEASE && in->header.nodeid == ino && + in->body.release.lock_owner == lock_owner && in->body.release.fh == FH); }, Eq(true)), _) diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index de17d08f5ae..51b43ee7ba3 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -103,7 +103,8 @@ class FuseTest : public ::testing::Test { * Create an expectation that FUSE_RELEASE will be called times times * for the given inode, returning error error */ - void expect_release(uint64_t ino, int times, int error); + void expect_release(uint64_t ino, int times, uint64_t lock_owner, + int error); /* * Create an expectation that FUSE_WRITE will be called exactly once From 9038479127db2e7f32b19712b5b8f29b8c03b917 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 15 Mar 2019 17:04:33 +0000 Subject: [PATCH 32/41] fuse(4): combine common code in the tests Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/create.cc | 30 ++++-------- tests/sys/fs/fuse/flush.cc | 1 - tests/sys/fs/fuse/fsync.cc | 6 +-- tests/sys/fs/fuse/getattr.cc | 13 ++--- tests/sys/fs/fuse/link.cc | 3 +- tests/sys/fs/fuse/locks.cc | 7 +-- tests/sys/fs/fuse/lookup.cc | 21 +++----- tests/sys/fs/fuse/mkdir.cc | 9 ++-- tests/sys/fs/fuse/mknod.cc | 3 +- tests/sys/fs/fuse/mockfs.cc | 1 + tests/sys/fs/fuse/open.cc | 6 +-- tests/sys/fs/fuse/opendir.cc | 9 ++-- tests/sys/fs/fuse/read.cc | 6 +-- tests/sys/fs/fuse/readdir.cc | 4 +- tests/sys/fs/fuse/readlink.cc | 3 +- tests/sys/fs/fuse/release.cc | 1 - tests/sys/fs/fuse/releasedir.cc | 3 +- tests/sys/fs/fuse/rmdir.cc | 3 +- tests/sys/fs/fuse/setattr.cc | 67 +++++++++----------------- tests/sys/fs/fuse/statfs.cc | 3 +- tests/sys/fs/fuse/symlink.cc | 3 +- tests/sys/fs/fuse/utils.cc | 21 +++----- tests/sys/fs/fuse/write.cc | 6 +-- tests/sys/fs/fuse/xattr.cc | 85 ++++++++++++++++----------------- 24 files changed, 117 insertions(+), 197 deletions(-) diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index df8b118f293..2aed78b1268 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -62,8 +62,7 @@ TEST_F(Create, DISABLED_attr_cache) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; @@ -145,8 +144,7 @@ TEST_F(Create, DISABLED_Enosys) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; @@ -160,8 +158,7 @@ TEST_F(Create, DISABLED_Enosys) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); }))); @@ -173,8 +170,7 @@ TEST_F(Create, DISABLED_Enosys) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; @@ -214,8 +210,7 @@ TEST_F(Create, DISABLED_entry_cache_negative) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; @@ -230,8 +225,7 @@ TEST_F(Create, DISABLED_entry_cache_negative) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; @@ -269,8 +263,7 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; @@ -284,8 +277,7 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; @@ -344,8 +336,7 @@ TEST_F(Create, ok) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; @@ -360,8 +351,7 @@ TEST_F(Create, ok) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/flush.cc b/tests/sys/fs/fuse/flush.cc index a721dc08f4e..5edc7301f8d 100644 --- a/tests/sys/fs/fuse/flush.cc +++ b/tests/sys/fs/fuse/flush.cc @@ -181,7 +181,6 @@ TEST_F(FlushWithLocks, DISABLED_unlock_on_close) }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, setlk); out->body.setlk.lk = in->body.setlk.lk; }))); diff --git a/tests/sys/fs/fuse/fsync.cc b/tests/sys/fs/fuse/fsync.cc index 1673367557e..5be75444960 100644 --- a/tests/sys/fs/fuse/fsync.cc +++ b/tests/sys/fs/fuse/fsync.cc @@ -132,8 +132,7 @@ TEST_F(Fsync, close) return (in->header.opcode == FUSE_SETATTR); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid }))); @@ -244,8 +243,7 @@ TEST_F(Fsync, DISABLED_fsync_metadata_only) return (in->header.opcode == FUSE_SETATTR); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | mode; diff --git a/tests/sys/fs/fuse/getattr.cc b/tests/sys/fs/fuse/getattr.cc index bee203e515f..fc024be692f 100644 --- a/tests/sys/fs/fuse/getattr.cc +++ b/tests/sys/fs/fuse/getattr.cc @@ -47,8 +47,8 @@ TEST_F(Getattr, DISABLED_attr_cache) const uint64_t ino = 42; struct stat sb; - EXPECT_LOOKUP(1, RELPATH).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + EXPECT_LOOKUP(1, RELPATH) + .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; @@ -59,8 +59,7 @@ TEST_F(Getattr, DISABLED_attr_cache) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr_valid = UINT64_MAX; out->body.attr.attr.ino = ino; // Must match nodeid @@ -97,8 +96,7 @@ TEST_F(Getattr, attr_cache_timeout) }, Eq(true)), _) ).Times(2) - .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr_valid_nsec = timeout_ns; out->body.attr.attr_valid = UINT64_MAX; @@ -144,8 +142,7 @@ TEST_F(Getattr, ok) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/link.cc b/tests/sys/fs/fuse/link.cc index 6ba606691fc..feca5254e8b 100644 --- a/tests/sys/fs/fuse/link.cc +++ b/tests/sys/fs/fuse/link.cc @@ -92,8 +92,7 @@ TEST_F(Link, ok) (0 == strcmp(name, RELPATH))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; diff --git a/tests/sys/fs/fuse/locks.cc b/tests/sys/fs/fuse/locks.cc index e4bb50d4acf..90ba5e34db1 100644 --- a/tests/sys/fs/fuse/locks.cc +++ b/tests/sys/fs/fuse/locks.cc @@ -124,7 +124,6 @@ TEST_F(Getlk, DISABLED_no_locks) }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk = in->body.getlk.lk; out->body.getlk.lk.type = F_UNLCK; @@ -170,8 +169,7 @@ TEST_F(Getlk, DISABLED_lock_exists) in->body.getlk.lk.pid == (uint64_t)pid); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, getlk); out->body.getlk.lk.start = 100; out->body.getlk.lk.end = 199; @@ -252,7 +250,6 @@ TEST_F(Setlk, DISABLED_set) }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, setlk); out->body.setlk.lk = in->body.setlk.lk; }))); @@ -296,7 +293,6 @@ TEST_F(Setlk, DISABLED_set_eof) }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, setlk); out->body.setlk.lk = in->body.setlk.lk; }))); @@ -413,7 +409,6 @@ TEST_F(Setlkw, DISABLED_set) }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, setlkw); out->body.setlkw.lk = in->body.setlkw.lk; }))); diff --git a/tests/sys/fs/fuse/lookup.cc b/tests/sys/fs/fuse/lookup.cc index e478be267b5..b4cc21a70ed 100644 --- a/tests/sys/fs/fuse/lookup.cc +++ b/tests/sys/fs/fuse/lookup.cc @@ -53,8 +53,7 @@ TEST_F(Lookup, DISABLED_attr_cache) struct stat sb; EXPECT_LOOKUP(1, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; @@ -120,8 +119,7 @@ TEST_F(Lookup, attr_cache_timeout) long timeout_ns = 250'000'000; EXPECT_LOOKUP(1, RELPATH) - .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.nodeid = ino; out->body.entry.attr_valid_nsec = timeout_ns; @@ -157,8 +155,7 @@ TEST_F(Lookup, entry_cache) const char RELPATH[] = "some_file.txt"; EXPECT_LOOKUP(1, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.entry_valid = UINT64_MAX; out->body.entry.attr.mode = S_IFREG | 0644; @@ -227,8 +224,7 @@ TEST_F(Lookup, DISABLED_entry_cache_timeout) long timeout_ns = 250'000'000; EXPECT_LOOKUP(1, RELPATH).Times(2) - .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.entry_valid_nsec = timeout_ns; out->body.entry.attr.mode = S_IFREG | 0644; @@ -252,8 +248,7 @@ TEST_F(Lookup, ok) const char RELPATH[] = "some_file.txt"; EXPECT_LOOKUP(1, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = 14; @@ -275,15 +270,13 @@ TEST_F(Lookup, subdir) uint64_t file_ino = 3; EXPECT_LOOKUP(1, DIRPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFDIR | 0755; out->body.entry.nodeid = dir_ino; }))); EXPECT_LOOKUP(dir_ino, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = file_ino; diff --git a/tests/sys/fs/fuse/mkdir.cc b/tests/sys/fs/fuse/mkdir.cc index ea148af5b94..bff635f12a9 100644 --- a/tests/sys/fs/fuse/mkdir.cc +++ b/tests/sys/fs/fuse/mkdir.cc @@ -95,8 +95,7 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.create.entry.attr.mode = S_IFDIR | mode; out->body.create.entry.nodeid = ino; @@ -134,8 +133,7 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFDIR | mode; out->body.entry.nodeid = ino; @@ -168,8 +166,7 @@ TEST_F(Mkdir, ok) (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.create.entry.attr.mode = S_IFDIR | mode; out->body.create.entry.nodeid = ino; diff --git a/tests/sys/fs/fuse/mknod.cc b/tests/sys/fs/fuse/mknod.cc index 2101d10c7df..4d456b48a0c 100644 --- a/tests/sys/fs/fuse/mknod.cc +++ b/tests/sys/fs/fuse/mknod.cc @@ -67,8 +67,7 @@ void test_ok(mode_t mode, dev_t dev) { (0 == strcmp(RELPATH, name))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = mode; out->body.create.entry.nodeid = ino; diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index 41e8b826aa8..bb5e7f6b24e 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -140,6 +140,7 @@ ReturnImmediate(std::functionheader.unique = in->header.unique; f(in, out0); out.push_back(out0); }); diff --git a/tests/sys/fs/fuse/open.cc b/tests/sys/fs/fuse/open.cc index 19d6ddbf24e..136f0441177 100644 --- a/tests/sys/fs/fuse/open.cc +++ b/tests/sys/fs/fuse/open.cc @@ -56,8 +56,7 @@ void test_ok(int os_flags, int fuse_flags) { in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); }))); @@ -69,8 +68,7 @@ void test_ok(int os_flags, int fuse_flags) { in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/opendir.cc b/tests/sys/fs/fuse/opendir.cc index 948bfd27d72..0b3bd162ec4 100644 --- a/tests/sys/fs/fuse/opendir.cc +++ b/tests/sys/fs/fuse/opendir.cc @@ -109,8 +109,7 @@ TEST_F(Opendir, open) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, open); }))); @@ -129,8 +128,7 @@ TEST_F(Opendir, opendir) return (in->header.opcode == FUSE_STATFS); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, statfs); }))); @@ -140,8 +138,7 @@ TEST_F(Opendir, opendir) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, open); }))); diff --git a/tests/sys/fs/fuse/read.cc b/tests/sys/fs/fuse/read.cc index a41301b4c67..61a160aeffb 100644 --- a/tests/sys/fs/fuse/read.cc +++ b/tests/sys/fs/fuse/read.cc @@ -242,8 +242,7 @@ TEST_F(Read, mmap) in->body.read.size >= bufsize); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { out->header.len = sizeof(struct fuse_out_header) + bufsize; memmove(out->body.bytes, CONTENTS, bufsize); }))); @@ -411,8 +410,7 @@ TEST_F(Read, sendfile) in->body.read.size >= bufsize); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { out->header.len = sizeof(struct fuse_out_header) + bufsize; memmove(out->body.bytes, CONTENTS, bufsize); }))); diff --git a/tests/sys/fs/fuse/readdir.cc b/tests/sys/fs/fuse/readdir.cc index ee9556984a0..a828559d800 100644 --- a/tests/sys/fs/fuse/readdir.cc +++ b/tests/sys/fs/fuse/readdir.cc @@ -59,7 +59,6 @@ void expect_readdir(uint64_t ino, uint64_t off, vector &ents) struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes; int i = 0; - out->header.unique = in->header.unique; out->header.error = 0; out->header.len = 0; @@ -208,8 +207,7 @@ TEST_F(Readdir, nodots) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { out->header.error = 0; out->header.len = sizeof(out->header); }))); diff --git a/tests/sys/fs/fuse/readlink.cc b/tests/sys/fs/fuse/readlink.cc index 203b21149c2..5ca85c2bfa5 100644 --- a/tests/sys/fs/fuse/readlink.cc +++ b/tests/sys/fs/fuse/readlink.cc @@ -82,8 +82,7 @@ TEST_F(Readlink, ok) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { strlcpy(out->body.str, dst, sizeof(out->body.str)); out->header.len = sizeof(out->header) + strlen(dst) + 1; }))); diff --git a/tests/sys/fs/fuse/release.cc b/tests/sys/fs/fuse/release.cc index f3f5e26dd5a..71221104375 100644 --- a/tests/sys/fs/fuse/release.cc +++ b/tests/sys/fs/fuse/release.cc @@ -170,7 +170,6 @@ TEST_F(ReleaseWithLocks, DISABLED_unlock_on_close) }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, setlk); out->body.setlk.lk = in->body.setlk.lk; }))); diff --git a/tests/sys/fs/fuse/releasedir.cc b/tests/sys/fs/fuse/releasedir.cc index fc4f15a263c..72eabf74fcc 100644 --- a/tests/sys/fs/fuse/releasedir.cc +++ b/tests/sys/fs/fuse/releasedir.cc @@ -75,8 +75,7 @@ TEST_F(ReleaseDir, dup) in->body.readdir.offset == 0); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { out->header.error = 0; out->header.len = sizeof(out->header); }))); diff --git a/tests/sys/fs/fuse/rmdir.cc b/tests/sys/fs/fuse/rmdir.cc index 5ab958563d4..d85101e195d 100644 --- a/tests/sys/fs/fuse/rmdir.cc +++ b/tests/sys/fs/fuse/rmdir.cc @@ -42,8 +42,7 @@ public: void expect_lookup(const char *relpath, uint64_t ino) { EXPECT_LOOKUP(1, relpath) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFDIR | 0755; out->body.entry.nodeid = ino; diff --git a/tests/sys/fs/fuse/setattr.cc b/tests/sys/fs/fuse/setattr.cc index ec8de916412..d0be7ea85b4 100644 --- a/tests/sys/fs/fuse/setattr.cc +++ b/tests/sys/fs/fuse/setattr.cc @@ -56,8 +56,7 @@ TEST_F(Setattr, DISABLED_attr_cache) const mode_t newmode = 0644; EXPECT_LOOKUP(1, RELPATH) - .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; @@ -70,8 +69,7 @@ TEST_F(Setattr, DISABLED_attr_cache) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | newmode; @@ -101,8 +99,7 @@ TEST_F(Setattr, chmod) const mode_t newmode = 0644; EXPECT_LOOKUP(1, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | oldmode; out->body.entry.nodeid = ino; @@ -119,8 +116,7 @@ TEST_F(Setattr, chmod) in->body.setattr.mode == newmode); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | newmode; @@ -140,8 +136,7 @@ TEST_F(Setattr, chown) const uid_t newuser = 44; EXPECT_LOOKUP(1, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; @@ -160,8 +155,7 @@ TEST_F(Setattr, chown) in->body.setattr.gid == newgroup); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; @@ -186,7 +180,6 @@ TEST_F(Setattr, eperm) EXPECT_LOOKUP(1, RELPATH) .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0777; out->body.entry.nodeid = ino; @@ -216,8 +209,7 @@ TEST_F(Setattr, fchmod) const mode_t newmode = 0644; EXPECT_LOOKUP(1, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | oldmode; out->body.entry.nodeid = ino; @@ -230,8 +222,7 @@ TEST_F(Setattr, fchmod) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); }))); @@ -243,8 +234,7 @@ TEST_F(Setattr, fchmod) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | oldmode; @@ -260,8 +250,7 @@ TEST_F(Setattr, fchmod) in->body.setattr.mode == newmode); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | newmode; @@ -285,8 +274,7 @@ TEST_F(Setattr, ftruncate) const off_t newsize = 12345; EXPECT_LOOKUP(1, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0755; out->body.entry.nodeid = ino; @@ -300,8 +288,7 @@ TEST_F(Setattr, ftruncate) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); out->body.open.fh = fh; @@ -314,8 +301,7 @@ TEST_F(Setattr, ftruncate) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0755; @@ -332,8 +318,7 @@ TEST_F(Setattr, ftruncate) in->body.setattr.fh == fh); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0755; @@ -355,8 +340,7 @@ TEST_F(Setattr, truncate) { const uint64_t newsize = 20'000'000; EXPECT_LOOKUP(1, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; @@ -373,8 +357,7 @@ TEST_F(Setattr, truncate) { in->body.setattr.size == newsize); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; @@ -398,8 +381,7 @@ TEST_F(Setattr, utimensat) { }; EXPECT_LOOKUP(1, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; @@ -420,8 +402,7 @@ TEST_F(Setattr, utimensat) { in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; @@ -446,8 +427,7 @@ TEST_F(Setattr, utimensat) { newtimes[1].tv_nsec); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; @@ -475,8 +455,7 @@ TEST_F(Setattr, utimensat_mtime_only) { }; EXPECT_LOOKUP(1, RELPATH) - .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; @@ -497,8 +476,7 @@ TEST_F(Setattr, utimensat_mtime_only) { in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; @@ -520,8 +498,7 @@ TEST_F(Setattr, utimensat_mtime_only) { newtimes[1].tv_nsec); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/statfs.cc b/tests/sys/fs/fuse/statfs.cc index e4bf6cdd0e1..d3ec22bc2ad 100644 --- a/tests/sys/fs/fuse/statfs.cc +++ b/tests/sys/fs/fuse/statfs.cc @@ -86,8 +86,7 @@ TEST_F(Statfs, ok) return (in->header.opcode == FUSE_STATFS); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, statfs); out->body.statfs.st.blocks = 1000; out->body.statfs.st.bfree = 100; diff --git a/tests/sys/fs/fuse/symlink.cc b/tests/sys/fs/fuse/symlink.cc index 3515c3f18cf..d1bfa8d56e2 100644 --- a/tests/sys/fs/fuse/symlink.cc +++ b/tests/sys/fs/fuse/symlink.cc @@ -80,8 +80,7 @@ TEST_F(Symlink, ok) (0 == strcmp(name, RELPATH))); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFLNK | 0777; out->body.entry.nodeid = ino; diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index 29671299820..f7bb4a30dfa 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -93,8 +93,7 @@ void FuseTest::expect_getattr(uint64_t ino, uint64_t size) in->header.nodeid == ino); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; @@ -108,8 +107,7 @@ void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, { EXPECT_LOOKUP(1, relpath) .Times(times) - .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = mode; out->body.entry.nodeid = ino; @@ -127,8 +125,7 @@ void FuseTest::expect_open(uint64_t ino, uint32_t flags, int times) }, Eq(true)), _) ).Times(times) - .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) { out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); out->body.open.fh = FH; @@ -143,8 +140,7 @@ void FuseTest::expect_opendir(uint64_t ino) return (in->header.opcode == FUSE_STATFS); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, statfs); }))); @@ -154,8 +150,7 @@ void FuseTest::expect_opendir(uint64_t ino) in->header.nodeid == ino); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { out->header.len = sizeof(out->header); SET_OUT_HEADER_LEN(out, open); out->body.open.fh = FH; @@ -174,8 +169,7 @@ void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize, in->body.read.size == isize); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { out->header.len = sizeof(struct fuse_out_header) + osize; memmove(out->body.bytes, contents, osize); }))).RetiresOnSaturation(); @@ -219,8 +213,7 @@ void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize, 0 == bcmp(buf, contents, isize)); }, Eq(true)), _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, write); out->body.write.size = osize; }))); diff --git a/tests/sys/fs/fuse/write.cc b/tests/sys/fs/fuse/write.cc index 6d8dd1793d4..67af1e38bb3 100644 --- a/tests/sys/fs/fuse/write.cc +++ b/tests/sys/fs/fuse/write.cc @@ -503,8 +503,7 @@ TEST_F(WriteBack, close) return (in->header.opcode == FUSE_SETATTR); }, Eq(true)), _) - ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid }))); @@ -635,8 +634,7 @@ TEST_F(WriteThrough, DISABLED_update_file_size) }, Eq(true)), _) ).Times(2) - .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) { - out->header.unique = in->header.unique; + .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; diff --git a/tests/sys/fs/fuse/xattr.cc b/tests/sys/fs/fuse/xattr.cc index bc916297f4e..57148571033 100644 --- a/tests/sys/fs/fuse/xattr.cc +++ b/tests/sys/fs/fuse/xattr.cc @@ -166,11 +166,12 @@ TEST_F(Getxattr, size_only) int ns = EXTATTR_NAMESPACE_USER; expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); - expect_getxattr(ino, "user.foo", ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; - SET_OUT_HEADER_LEN(out, getxattr); - out->body.getxattr.size = 99; - })); + expect_getxattr(ino, "user.foo", + ReturnImmediate([](auto in __unused, auto out) { + SET_OUT_HEADER_LEN(out, getxattr); + out->body.getxattr.size = 99; + }) + ); ASSERT_EQ(99, extattr_get_file(FULLPATH, ns, "foo", NULL, 0)) << strerror(errno);; @@ -190,8 +191,7 @@ TEST_F(Getxattr, system) expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); expect_getxattr(ino, "system.foo", - ReturnImmediate([&](auto in, auto out) { - out->header.unique = in->header.unique; + ReturnImmediate([&](auto in __unused, auto out) { memcpy((void*)out->body.bytes, value, value_len); out->header.len = sizeof(out->header) + value_len; }) @@ -216,8 +216,7 @@ TEST_F(Getxattr, user) expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); expect_getxattr(ino, "user.foo", - ReturnImmediate([&](auto in, auto out) { - out->header.unique = in->header.unique; + ReturnImmediate([&](auto in __unused, auto out) { memcpy((void*)out->body.bytes, value, value_len); out->header.len = sizeof(out->header) + value_len; }) @@ -277,8 +276,7 @@ TEST_F(Listxattr, size_only_empty) int ns = EXTATTR_NAMESPACE_USER; expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); - expect_listxattr(ino, 0, ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) { out->body.listxattr.size = 0; SET_OUT_HEADER_LEN(out, listxattr); })); @@ -299,20 +297,21 @@ TEST_F(Listxattr, size_only_nonempty) int ns = EXTATTR_NAMESPACE_USER; expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); - expect_listxattr(ino, 0, ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) { out->body.listxattr.size = 45; SET_OUT_HEADER_LEN(out, listxattr); })); // TODO: fix the expected size after fixing the size calculation bug in // fuse_vnop_listextattr. It should be exactly 45. - expect_listxattr(ino, 53, ReturnImmediate([](auto in, auto out) { - const char l[] = "user.foo"; - out->header.unique = in->header.unique; - strlcpy((char*)out->body.bytes, l, sizeof(out->body.bytes)); - out->header.len = sizeof(fuse_out_header) + sizeof(l); - })); + expect_listxattr(ino, 53, + ReturnImmediate([](auto in __unused, auto out) { + const char l[] = "user.foo"; + strlcpy((char*)out->body.bytes, l, + sizeof(out->body.bytes)); + out->header.len = sizeof(fuse_out_header) + sizeof(l); + }) + ); ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0)) << strerror(errno); @@ -324,22 +323,22 @@ TEST_F(Listxattr, size_only_really_big) int ns = EXTATTR_NAMESPACE_USER; expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); - expect_listxattr(ino, 0, ReturnImmediate([](auto in, auto out) { - out->header.unique = in->header.unique; + expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) { out->body.listxattr.size = 16000; SET_OUT_HEADER_LEN(out, listxattr); })); // TODO: fix the expected size after fixing the size calculation bug in // fuse_vnop_listextattr. It should be exactly 16000. - expect_listxattr(ino, 16008, ReturnImmediate([](auto in, auto out) { - const char l[16] = "user.foobarbang"; - out->header.unique = in->header.unique; - for (int i=0; i < 1000; i++) { - memcpy(&out->body.bytes[16 * i], l, 16); - } - out->header.len = sizeof(fuse_out_header) + 16000; - })); + expect_listxattr(ino, 16008, + ReturnImmediate([](auto in __unused, auto out) { + const char l[16] = "user.foobarbang"; + for (int i=0; i < 1000; i++) { + memcpy(&out->body.bytes[16 * i], l, 16); + } + out->header.len = sizeof(fuse_out_header) + 16000; + }) + ); ASSERT_EQ(11000, extattr_list_file(FULLPATH, ns, NULL, 0)) << strerror(errno); @@ -358,17 +357,17 @@ TEST_F(Listxattr, user) char attrs[28] = "user.foo\0system.x\0user.bang"; expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); - expect_listxattr(ino, 0, ReturnImmediate([&](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.listxattr.size = sizeof(attrs); - SET_OUT_HEADER_LEN(out, listxattr); - })); + expect_listxattr(ino, 0, + ReturnImmediate([&](auto in __unused, auto out) { + out->body.listxattr.size = sizeof(attrs); + SET_OUT_HEADER_LEN(out, listxattr); + }) + ); // TODO: fix the expected size after fixing the size calculation bug in // fuse_vnop_listextattr. expect_listxattr(ino, sizeof(attrs) + 8, - ReturnImmediate([&](auto in, auto out) { - out->header.unique = in->header.unique; + ReturnImmediate([&](auto in __unused, auto out) { memcpy((void*)out->body.bytes, attrs, sizeof(attrs)); out->header.len = sizeof(fuse_out_header) + sizeof(attrs); })); @@ -392,17 +391,17 @@ TEST_F(Listxattr, system) char attrs[28] = "user.foo\0system.x\0user.bang"; expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); - expect_listxattr(ino, 0, ReturnImmediate([&](auto in, auto out) { - out->header.unique = in->header.unique; - out->body.listxattr.size = sizeof(attrs); - SET_OUT_HEADER_LEN(out, listxattr); - })); + expect_listxattr(ino, 0, + ReturnImmediate([&](auto in __unused, auto out) { + out->body.listxattr.size = sizeof(attrs); + SET_OUT_HEADER_LEN(out, listxattr); + }) + ); // TODO: fix the expected size after fixing the size calculation bug in // fuse_vnop_listextattr. expect_listxattr(ino, sizeof(attrs) + 8, - ReturnImmediate([&](auto in, auto out) { - out->header.unique = in->header.unique; + ReturnImmediate([&](auto in __unused, auto out) { memcpy((void*)out->body.bytes, attrs, sizeof(attrs)); out->header.len = sizeof(fuse_out_header) + sizeof(attrs); })); From 71885041ce3a8b943ccf349936794ea743793efc Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 15 Mar 2019 18:06:51 +0000 Subject: [PATCH 33/41] fuse(4): add tests for ENOSYS special cases PR: 236557 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/access.cc | 52 +++++++++++++------ tests/sys/fs/fuse/create.cc | 1 + tests/sys/fs/fuse/flush.cc | 29 +++++++++++ tests/sys/fs/fuse/fsync.cc | 34 +++++++++++- tests/sys/fs/fuse/fsyncdir.cc | 28 ++++++++++ tests/sys/fs/fuse/xattr.cc | 97 +++++++++++++++++++++++++++++++++++ 6 files changed, 223 insertions(+), 18 deletions(-) diff --git a/tests/sys/fs/fuse/access.cc b/tests/sys/fs/fuse/access.cc index 8ce01028924..4f932675e33 100644 --- a/tests/sys/fs/fuse/access.cc +++ b/tests/sys/fs/fuse/access.cc @@ -41,6 +41,18 @@ using namespace testing; class Access: public FuseTest { public: +void expect_access(uint64_t ino, mode_t access_mode, int error) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_ACCESS && + in->header.nodeid == ino && + in->body.access.mask == access_mode); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(error))); +} + void expect_lookup(const char *relpath, uint64_t ino) { FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); @@ -59,20 +71,33 @@ TEST_F(Access, DISABLED_eaccess) mode_t access_mode = X_OK; expect_lookup(RELPATH, ino); - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_ACCESS && - in->header.nodeid == ino && - in->body.access.mask == access_mode); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnErrno(EACCES))); - + expect_access(ino, access_mode, EACCES); ASSERT_NE(0, access(FULLPATH, access_mode)); ASSERT_EQ(EACCES, errno); } +/* + * If the filesystem returns ENOSYS, then it is treated as a permanent success, + * and subsequent VOP_ACCESS calls will succeed automatically without querying + * the daemon. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236291 */ +TEST_F(Access, DISABLED_enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + mode_t access_mode = R_OK; + + expect_lookup(RELPATH, ino); + expect_access(ino, access_mode, ENOSYS); + + ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); + ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); +} + /* The successful case of FUSE_ACCESS. */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236291 */ TEST_F(Access, DISABLED_ok) @@ -83,14 +108,7 @@ TEST_F(Access, DISABLED_ok) mode_t access_mode = R_OK; expect_lookup(RELPATH, ino); - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_ACCESS && - in->header.nodeid == ino && - in->body.access.mask == access_mode); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnErrno(0))); + expect_access(ino, access_mode, 0); ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); } diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index 2aed78b1268..99b7d3d85fa 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -114,6 +114,7 @@ TEST_F(Create, eexist) * to FUSE_MKNOD/FUSE_OPEN */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */ TEST_F(Create, DISABLED_Enosys) { const char FULLPATH[] = "mountpoint/some_file.txt"; diff --git a/tests/sys/fs/fuse/flush.cc b/tests/sys/fs/fuse/flush.cc index 5edc7301f8d..887a0462d71 100644 --- a/tests/sys/fs/fuse/flush.cc +++ b/tests/sys/fs/fuse/flush.cc @@ -134,6 +134,35 @@ TEST_F(Flush, DISABLED_eio) ASSERT_TRUE(0 == close(fd) || errno == EIO) << strerror(errno); } +/* + * If the filesystem returns ENOSYS, it will be treated as success and + * no more FUSE_FLUSH operations will be sent to the daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */ +TEST_F(Flush, DISABLED_enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd, fd2; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + /* On the 2nd close, FUSE_FLUSH won't be sent at all */ + expect_flush(ino, 1, 0, ReturnErrno(ENOSYS)); + expect_release(); + + fd = open(FULLPATH, O_WRONLY); + EXPECT_LE(0, fd) << strerror(errno); + + fd2 = dup(fd); + + EXPECT_EQ(0, close(fd2)) << strerror(errno); + EXPECT_EQ(0, close(fd)) << strerror(errno); +} + /* A FUSE_FLUSH should be sent on close(2) */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236405 */ TEST_F(Flush, DISABLED_flush) diff --git a/tests/sys/fs/fuse/fsync.cc b/tests/sys/fs/fuse/fsync.cc index 5be75444960..c2f808a51b6 100644 --- a/tests/sys/fs/fuse/fsync.cc +++ b/tests/sys/fs/fuse/fsync.cc @@ -56,7 +56,6 @@ void expect_fsync(uint64_t ino, uint32_t flags, int error) ResultOf([=](auto in) { return (in->header.opcode == FUSE_FSYNC && in->header.nodeid == ino && - //(pid_t)in->header.pid == getpid() && in->body.fsync.fh == FH && in->body.fsync.fsync_flags == flags); }, Eq(true)), @@ -175,6 +174,39 @@ TEST_F(Fsync, DISABLED_eio) /* Deliberately leak fd. close(2) will be tested in release.cc */ } +/* + * If the filesystem returns ENOSYS, it will be treated as success and + * subsequent calls to VOP_FSYNC will succeed automatically without being sent + * to the filesystem daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */ +TEST_F(Fsync, DISABLED_enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + ssize_t bufsize = strlen(CONTENTS); + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, 0); + expect_write(ino, bufsize, CONTENTS); + expect_fsync(ino, FUSE_FSYNC_FDATASYNC, ENOSYS); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + EXPECT_EQ(0, fdatasync(fd)); + + /* Subsequent calls shouldn't query the daemon*/ + EXPECT_EQ(0, fdatasync(fd)); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + + /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(Fsync, DISABLED_fdatasync) { diff --git a/tests/sys/fs/fuse/fsyncdir.cc b/tests/sys/fs/fuse/fsyncdir.cc index bc736337731..c600185d140 100644 --- a/tests/sys/fs/fuse/fsyncdir.cc +++ b/tests/sys/fs/fuse/fsyncdir.cc @@ -119,6 +119,34 @@ TEST_F(FsyncDir, DISABLED_eio) /* Deliberately leak fd. close(2) will be tested in release.cc */ } +/* + * If the filesystem returns ENOSYS, it will be treated as success and + * subsequent calls to VOP_FSYNC will succeed automatically without being sent + * to the filesystem daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */ +TEST_F(FsyncDir, DISABLED_enosys) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino); + expect_opendir(ino); + expect_fsyncdir(ino, FUSE_FSYNC_FDATASYNC, ENOSYS); + + fd = open(FULLPATH, O_DIRECTORY); + ASSERT_LE(0, fd) << strerror(errno); + EXPECT_EQ(0, fsync(fd)) << strerror(errno); + + /* Subsequent calls shouldn't query the daemon*/ + EXPECT_EQ(0, fsync(fd)) << strerror(errno); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236474 */ TEST_F(FsyncDir, DISABLED_fsyncdata) { diff --git a/tests/sys/fs/fuse/xattr.cc b/tests/sys/fs/fuse/xattr.cc index 57148571033..3cd51e2d3e9 100644 --- a/tests/sys/fs/fuse/xattr.cc +++ b/tests/sys/fs/fuse/xattr.cc @@ -129,6 +129,32 @@ TEST_F(Getxattr, enoattr) ASSERT_EQ(ENOATTR, errno); } +/* + * If the filesystem returns ENOSYS, then it will be treated as a permanent + * failure and all future VOP_GETEXTATTR calls will fail with EOPNOTSUPP + * without querying the filesystem daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */ +TEST_F(Getxattr, DISABLED_enosys) +{ + char data[80]; + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_getxattr(ino, "user.foo", ReturnErrno(ENOSYS)); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(-1, r); + EXPECT_EQ(EOPNOTSUPP, errno); + + /* Subsequent attempts should not query the filesystem at all */ + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(-1, r); + EXPECT_EQ(EOPNOTSUPP, errno); +} + /* * On FreeBSD, if the user passes an insufficiently large buffer then the * filesystem is supposed to copy as much of the attribute's value as will fit. @@ -227,6 +253,28 @@ TEST_F(Getxattr, user) EXPECT_STREQ(value, data); } +/* + * If the filesystem returns ENOSYS, then it will be treated as a permanent + * failure and all future VOP_LISTEXTATTR calls will fail with EOPNOTSUPP + * without querying the filesystem daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */ +TEST_F(Listxattr, DISABLED_enosys) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_listxattr(ino, 0, ReturnErrno(ENOSYS)); + + ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); + EXPECT_EQ(EOPNOTSUPP, errno); + + /* Subsequent attempts should not query the filesystem at all */ + ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); + EXPECT_EQ(EOPNOTSUPP, errno); +} + /* * Listing extended attributes failed because they aren't configured on this * filesystem @@ -425,6 +473,28 @@ TEST_F(Removexattr, enoattr) ASSERT_EQ(ENOATTR, errno); } +/* + * If the filesystem returns ENOSYS, then it will be treated as a permanent + * failure and all future VOP_DELETEEXTATTR calls will fail with EOPNOTSUPP + * without querying the filesystem daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */ +TEST_F(Removexattr, DISABLED_enosys) +{ + uint64_t ino = 42; + int ns = EXTATTR_NAMESPACE_USER; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_removexattr(ino, "user.foo", ENOSYS); + + ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); + EXPECT_EQ(EOPNOTSUPP, errno); + + /* Subsequent attempts should not query the filesystem at all */ + ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); + EXPECT_EQ(EOPNOTSUPP, errno); +} + /* Successfully remove a user xattr */ TEST_F(Removexattr, user) { @@ -451,6 +521,33 @@ TEST_F(Removexattr, system) << strerror(errno); } +/* + * If the filesystem returns ENOSYS, then it will be treated as a permanent + * failure and all future VOP_SETEXTATTR calls will fail with EOPNOTSUPP + * without querying the filesystem daemon + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */ +TEST_F(Setxattr, DISABLED_enosys) +{ + uint64_t ino = 42; + const char value[] = "whatever"; + ssize_t value_len = strlen(value) + 1; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOSYS)); + + r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); + ASSERT_EQ(-1, r); + EXPECT_EQ(EOPNOTSUPP, errno); + + /* Subsequent attempts should not query the filesystem at all */ + r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); + ASSERT_EQ(-1, r); + EXPECT_EQ(EOPNOTSUPP, errno); +} + /* * SETXATTR will return ENOTSUP if the namespace is invalid or the filesystem * as currently configured doesn't support extended attributes. From bce2c9bbd84c2de1570e053a7f34823fd7994b2c Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 15 Mar 2019 18:50:36 +0000 Subject: [PATCH 34/41] VOP_INACTIVE(9): clarify wording Reviewed by: kib, 0mp MFC after: 2 weeks Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D19596 --- share/man/man9/VOP_INACTIVE.9 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/share/man/man9/VOP_INACTIVE.9 b/share/man/man9/VOP_INACTIVE.9 index 6c71d03659c..05a7f2cc477 100644 --- a/share/man/man9/VOP_INACTIVE.9 +++ b/share/man/man9/VOP_INACTIVE.9 @@ -28,7 +28,7 @@ .\" .\" $FreeBSD$ .\" -.Dd July 27, 2014 +.Dd March 15, 2019 .Dt VOP_INACTIVE 9 .Os .Sh NAME @@ -50,13 +50,16 @@ The vnode being reclaimed. .El .Pp .Fn VOP_INACTIVE -is called when the kernel is no longer using the vnode. +is usually called when the kernel is no longer using the vnode. +However, there is no guarantee that it will be called at all, for example if +the last reference was dropped while the vnode lock could not be upgraded +to exclusive without sleeping. This may be because the reference count reaches zero or it may be that the file system is being forcibly unmounted while there are open files. -It can be used to reclaim space for +It can be used to reclaim space on the last close of an .Sq open but deleted -files. +file. .Pp .Fn VOP_RECLAIM is called when a vnode is being reused for a different file system. From 48f58d58cf17586490b9b4801fb6358543e1b0ed Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 15 Mar 2019 20:16:35 +0000 Subject: [PATCH 35/41] fuse(4): add tests for the FUSE_ASYNC_READ option Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/read.cc | 161 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/tests/sys/fs/fuse/read.cc b/tests/sys/fs/fuse/read.cc index 61a160aeffb..42072f8193f 100644 --- a/tests/sys/fs/fuse/read.cc +++ b/tests/sys/fs/fuse/read.cc @@ -55,6 +55,7 @@ void expect_lookup(const char *relpath, uint64_t ino) }; class AioRead: public Read { +public: virtual void SetUp() { const char *node = "vfs.aio.enable_unsafe"; int val = 0; @@ -69,6 +70,13 @@ virtual void SetUp() { } }; +class AsyncRead: public AioRead { + virtual void SetUp() { + m_init_flags = FUSE_ASYNC_READ; + AioRead::SetUp(); + } +}; + class ReadAhead: public Read, public WithParamInterface { virtual void SetUp() { m_maxreadahead = GetParam(); @@ -108,6 +116,159 @@ TEST_F(AioRead, aio_read) /* Deliberately leak fd. close(2) will be tested in release.cc */ } +/* + * Without the FUSE_ASYNC_READ mount option, fuse(4) should ensure that there + * is at most one outstanding read operation per file handle + */ +TEST_F(AioRead, async_read_disabled) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = 50; + char buf0[bufsize], buf1[bufsize]; + off_t off0 = 0; + off_t off1 = 4096; + struct aiocb iocb0, iocb1; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == FH && + in->body.read.offset == (uint64_t)off0 && + in->body.read.size == bufsize); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + /* Filesystem is slow to respond */ + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == FH && + in->body.read.offset == (uint64_t)off1 && + in->body.read.size == bufsize); + }, Eq(true)), + _) + ).Times(0); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + /* + * Submit two AIO read requests, and respond to neither. If the + * filesystem ever gets the second read request, then we failed to + * limit outstanding reads. + */ + iocb0.aio_nbytes = bufsize; + iocb0.aio_fildes = fd; + iocb0.aio_buf = buf0; + iocb0.aio_offset = off0; + iocb0.aio_sigevent.sigev_notify = SIGEV_NONE; + ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno); + + iocb1.aio_nbytes = bufsize; + iocb1.aio_fildes = fd; + iocb1.aio_buf = buf1; + iocb1.aio_offset = off1; + iocb1.aio_sigevent.sigev_notify = SIGEV_NONE; + ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno); + + /* + * Sleep for awhile to make sure the kernel has had a chance to issue + * the second read, even though the first has not yet returned + */ + usleep(250'000); + + /* Deliberately leak iocbs */ + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * With the FUSE_ASYNC_READ mount option, fuse(4) may issue multiple + * simultaneous read requests on the same file handle. + */ +/* + * Disabled because we don't yet implement FUSE_ASYNC_READ. No bugzilla + * entry, because that's a feature request, not a bug. + */ +TEST_F(AsyncRead, DISABLED_async_read) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = 50; + char buf0[bufsize], buf1[bufsize]; + off_t off0 = 0; + off_t off1 = 4096; + struct aiocb iocb0, iocb1; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == FH && + in->body.read.offset == (uint64_t)off0 && + in->body.read.size == bufsize); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + /* Filesystem is slow to respond */ + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == FH && + in->body.read.offset == (uint64_t)off1 && + in->body.read.size == bufsize); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + /* Filesystem is slow to respond */ + })); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + /* + * Submit two AIO read requests, but respond to neither. Ensure that + * we received both. + */ + iocb0.aio_nbytes = bufsize; + iocb0.aio_fildes = fd; + iocb0.aio_buf = buf0; + iocb0.aio_offset = off0; + iocb0.aio_sigevent.sigev_notify = SIGEV_NONE; + ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno); + + iocb1.aio_nbytes = bufsize; + iocb1.aio_fildes = fd; + iocb1.aio_buf = buf1; + iocb1.aio_offset = off1; + iocb1.aio_sigevent.sigev_notify = SIGEV_NONE; + ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno); + + /* + * Sleep for awhile to make sure the kernel has had a chance to issue + * both reads. + */ + usleep(250'000); + + /* Deliberately leak iocbs */ + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + /* 0-length reads shouldn't cause any confusion */ TEST_F(Read, direct_io_read_nothing) { From 51786f270d248455c652927b23c7f303d2b73e20 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 15 Mar 2019 22:47:20 +0000 Subject: [PATCH 36/41] fuse(4): add tests for the FOPEN_KEEP_CACHE option PR: 236560 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/access.cc | 2 +- tests/sys/fs/fuse/create.cc | 2 +- tests/sys/fs/fuse/destroy.cc | 2 +- tests/sys/fs/fuse/flush.cc | 2 +- tests/sys/fs/fuse/fsync.cc | 2 +- tests/sys/fs/fuse/fsyncdir.cc | 2 +- tests/sys/fs/fuse/getattr.cc | 6 +- tests/sys/fs/fuse/interrupt.cc | 2 +- tests/sys/fs/fuse/link.cc | 2 +- tests/sys/fs/fuse/locks.cc | 2 +- tests/sys/fs/fuse/mkdir.cc | 2 +- tests/sys/fs/fuse/open.cc | 6 +- tests/sys/fs/fuse/opendir.cc | 2 +- tests/sys/fs/fuse/read.cc | 108 +++++++++++++++++++++++++++----- tests/sys/fs/fuse/readdir.cc | 2 +- tests/sys/fs/fuse/readlink.cc | 2 +- tests/sys/fs/fuse/release.cc | 2 +- tests/sys/fs/fuse/releasedir.cc | 2 +- tests/sys/fs/fuse/rename.cc | 16 ++--- tests/sys/fs/fuse/unlink.cc | 2 +- tests/sys/fs/fuse/utils.cc | 3 +- tests/sys/fs/fuse/utils.hh | 4 +- tests/sys/fs/fuse/write.cc | 36 +++++------ tests/sys/fs/fuse/xattr.cc | 44 ++++++------- 24 files changed, 165 insertions(+), 90 deletions(-) diff --git a/tests/sys/fs/fuse/access.cc b/tests/sys/fs/fuse/access.cc index 4f932675e33..5ceaf567a67 100644 --- a/tests/sys/fs/fuse/access.cc +++ b/tests/sys/fs/fuse/access.cc @@ -55,7 +55,7 @@ void expect_access(uint64_t ino, mode_t access_mode, int error) void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); } }; diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index 99b7d3d85fa..088569cb2e4 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -288,7 +288,7 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) ASSERT_LE(0, fd) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - expect_lookup(RELPATH, ino, S_IFREG | mode, 1); + expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ diff --git a/tests/sys/fs/fuse/destroy.cc b/tests/sys/fs/fuse/destroy.cc index e63e37df600..da3647ffff6 100644 --- a/tests/sys/fs/fuse/destroy.cc +++ b/tests/sys/fs/fuse/destroy.cc @@ -72,7 +72,7 @@ TEST_F(Destroy, ok) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 2); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); expect_forget(1, 1); expect_forget(ino, 2); expect_destroy(0); diff --git a/tests/sys/fs/fuse/flush.cc b/tests/sys/fs/fuse/flush.cc index 887a0462d71..d7fd02ad420 100644 --- a/tests/sys/fs/fuse/flush.cc +++ b/tests/sys/fs/fuse/flush.cc @@ -57,7 +57,7 @@ void expect_flush(uint64_t ino, int times, pid_t lo, ProcessMockerT r) void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); } /* diff --git a/tests/sys/fs/fuse/fsync.cc b/tests/sys/fs/fuse/fsync.cc index c2f808a51b6..9894a3d2888 100644 --- a/tests/sys/fs/fuse/fsync.cc +++ b/tests/sys/fs/fuse/fsync.cc @@ -65,7 +65,7 @@ void expect_fsync(uint64_t ino, uint32_t flags, int error) void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); } void expect_write(uint64_t ino, uint64_t size, const void *contents) diff --git a/tests/sys/fs/fuse/fsyncdir.cc b/tests/sys/fs/fuse/fsyncdir.cc index c600185d140..a36a53a304b 100644 --- a/tests/sys/fs/fuse/fsyncdir.cc +++ b/tests/sys/fs/fuse/fsyncdir.cc @@ -66,7 +66,7 @@ void expect_fsyncdir(uint64_t ino, uint32_t flags, int error) void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1); } }; diff --git a/tests/sys/fs/fuse/getattr.cc b/tests/sys/fs/fuse/getattr.cc index fc024be692f..9e81b3b46ab 100644 --- a/tests/sys/fs/fuse/getattr.cc +++ b/tests/sys/fs/fuse/getattr.cc @@ -88,7 +88,7 @@ TEST_F(Getattr, attr_cache_timeout) */ long timeout_ns = 250'000'000; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 2); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && @@ -116,7 +116,7 @@ TEST_F(Getattr, enoent) struct stat sb; const uint64_t ino = 42; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && @@ -135,7 +135,7 @@ TEST_F(Getattr, ok) const uint64_t ino = 42; struct stat sb; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && diff --git a/tests/sys/fs/fuse/interrupt.cc b/tests/sys/fs/fuse/interrupt.cc index 3c4767845e7..86135162587 100644 --- a/tests/sys/fs/fuse/interrupt.cc +++ b/tests/sys/fs/fuse/interrupt.cc @@ -66,7 +66,7 @@ Interrupt(): m_child(NULL) {}; void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); } /* diff --git a/tests/sys/fs/fuse/link.cc b/tests/sys/fs/fuse/link.cc index feca5254e8b..55a1785e342 100644 --- a/tests/sys/fs/fuse/link.cc +++ b/tests/sys/fs/fuse/link.cc @@ -41,7 +41,7 @@ class Link: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); } }; diff --git a/tests/sys/fs/fuse/locks.cc b/tests/sys/fs/fuse/locks.cc index 90ba5e34db1..a1d37357ffb 100644 --- a/tests/sys/fs/fuse/locks.cc +++ b/tests/sys/fs/fuse/locks.cc @@ -46,7 +46,7 @@ public: void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); } }; diff --git a/tests/sys/fs/fuse/mkdir.cc b/tests/sys/fs/fuse/mkdir.cc index bff635f12a9..c6b0b0945a3 100644 --- a/tests/sys/fs/fuse/mkdir.cc +++ b/tests/sys/fs/fuse/mkdir.cc @@ -143,7 +143,7 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - expect_lookup(RELPATH, ino, S_IFDIR | mode, 1); + expect_lookup(RELPATH, ino, S_IFDIR | mode, 0, 1); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); } diff --git a/tests/sys/fs/fuse/open.cc b/tests/sys/fs/fuse/open.cc index 136f0441177..220f2a79818 100644 --- a/tests/sys/fs/fuse/open.cc +++ b/tests/sys/fs/fuse/open.cc @@ -48,7 +48,7 @@ void test_ok(int os_flags, int fuse_flags) { uint64_t ino = 42; int fd; - FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && @@ -92,7 +92,7 @@ TEST_F(Open, enoent) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && @@ -114,7 +114,7 @@ TEST_F(Open, eperm) const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_OPEN && diff --git a/tests/sys/fs/fuse/opendir.cc b/tests/sys/fs/fuse/opendir.cc index 0b3bd162ec4..94ceb69555e 100644 --- a/tests/sys/fs/fuse/opendir.cc +++ b/tests/sys/fs/fuse/opendir.cc @@ -42,7 +42,7 @@ class Opendir: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1); } }; diff --git a/tests/sys/fs/fuse/read.cc b/tests/sys/fs/fuse/read.cc index 42072f8193f..15d73aa919a 100644 --- a/tests/sys/fs/fuse/read.cc +++ b/tests/sys/fs/fuse/read.cc @@ -48,9 +48,9 @@ using namespace testing; class Read: public FuseTest { public: -void expect_lookup(const char *relpath, uint64_t ino) +void expect_lookup(const char *relpath, uint64_t ino, uint64_t size) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1); } }; @@ -97,7 +97,7 @@ TEST_F(AioRead, aio_read) char buf[bufsize]; struct aiocb iocb, *piocb; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, bufsize); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); expect_read(ino, 0, bufsize, bufsize, CONTENTS); @@ -132,7 +132,7 @@ TEST_F(AioRead, async_read_disabled) off_t off1 = 4096; struct aiocb iocb0, iocb1; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, bufsize); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); EXPECT_CALL(*m_mock, process( @@ -210,7 +210,7 @@ TEST_F(AsyncRead, DISABLED_async_read) off_t off1 = 4096; struct aiocb iocb0, iocb1; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, bufsize); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); EXPECT_CALL(*m_mock, process( @@ -279,7 +279,7 @@ TEST_F(Read, direct_io_read_nothing) uint64_t offset = 100; char buf[80]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, offset + 1000); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, offset + 1000); @@ -305,7 +305,7 @@ TEST_F(Read, direct_io_pread) ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, offset + bufsize); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, offset + bufsize); expect_read(ino, offset, bufsize, bufsize, CONTENTS); @@ -334,7 +334,7 @@ TEST_F(Read, direct_io_short_read) ssize_t halfbufsize = bufsize / 2; char buf[bufsize]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, offset + bufsize); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, offset + bufsize); expect_read(ino, offset, bufsize, halfbufsize, CONTENTS); @@ -358,7 +358,7 @@ TEST_F(Read, eio) ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, bufsize); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); EXPECT_CALL(*m_mock, process( @@ -376,6 +376,79 @@ TEST_F(Read, eio) /* Deliberately leak fd. close(2) will be tested in release.cc */ } +/* + * With the keep_cache option, the kernel may keep its read cache across + * multiple open(2)s. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236560 */ +TEST_F(Read, DISABLED_keep_cache) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd0, fd1; + ssize_t bufsize = strlen(CONTENTS); + char buf[bufsize]; + + FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2); + expect_open(ino, FOPEN_KEEP_CACHE, 1); + expect_getattr(ino, bufsize); + expect_read(ino, 0, bufsize, bufsize, CONTENTS); + + fd0 = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd0) << strerror(errno); + ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno); + + fd1 = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd1) << strerror(errno); + + /* + * This read should be serviced by cache, even though it's on the other + * file descriptor + */ + ASSERT_EQ(bufsize, read(fd1, buf, bufsize)) << strerror(errno); + + /* Deliberately leak fd0 and fd1. */ +} + +/* + * Without the keep_cache option, the kernel should drop its read caches on + * every open + */ +TEST_F(Read, keep_cache_disabled) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd0, fd1; + ssize_t bufsize = strlen(CONTENTS); + char buf[bufsize]; + + FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, bufsize, 2); + expect_open(ino, FOPEN_KEEP_CACHE, 1); + expect_getattr(ino, bufsize); + expect_read(ino, 0, bufsize, bufsize, CONTENTS); + + fd0 = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd0) << strerror(errno); + ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno); + + fd1 = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd1) << strerror(errno); + + /* + * This read should not be serviced by cache, even though it's on the + * original file descriptor + */ + expect_read(ino, 0, bufsize, bufsize, CONTENTS); + ASSERT_EQ(0, lseek(fd0, 0, SEEK_SET)) << strerror(errno); + ASSERT_EQ(bufsize, read(fd0, buf, bufsize)) << strerror(errno); + + /* Deliberately leak fd0 and fd1. */ +} + TEST_F(Read, mmap) { const char FULLPATH[] = "mountpoint/some_file.txt"; @@ -390,7 +463,7 @@ TEST_F(Read, mmap) len = getpagesize(); - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, bufsize); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); /* mmap may legitimately try to read more data than is available */ @@ -434,7 +507,7 @@ TEST_F(Read, o_direct) ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, bufsize); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); expect_read(ino, 0, bufsize, bufsize, CONTENTS); @@ -471,7 +544,7 @@ TEST_F(Read, pread) ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, offset + bufsize); expect_open(ino, 0, 1); expect_getattr(ino, offset + bufsize); expect_read(ino, offset, bufsize, bufsize, CONTENTS); @@ -494,7 +567,7 @@ TEST_F(Read, read) ssize_t bufsize = strlen(CONTENTS); char buf[bufsize]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, bufsize); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); expect_read(ino, 0, bufsize, bufsize, CONTENTS); @@ -504,6 +577,7 @@ TEST_F(Read, read) ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); + /* Deliberately leak fd. close(2) will be tested in release.cc */ } @@ -527,7 +601,7 @@ TEST_F(Read, default_readahead) ASSERT_NE(NULL, contents); memmove(contents, CONTENTS0, strlen(CONTENTS0)); - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, filesize); expect_open(ino, 0, 1); expect_getattr(ino, filesize); expect_read(ino, 0, default_maxreadahead, default_maxreadahead, @@ -558,7 +632,7 @@ TEST_F(Read, sendfile) int sp[2]; off_t sbytes; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, bufsize); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); /* Like mmap, sendfile may request more data than is available */ @@ -604,7 +678,7 @@ TEST_F(Read, DISABLED_sendfile_eio) int sp[2]; off_t sbytes; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, bufsize); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); EXPECT_CALL(*m_mock, process( @@ -646,7 +720,7 @@ TEST_P(ReadAhead, DISABLED_readahead) { ASSERT_NE(NULL, contents); memmove(contents, CONTENTS0, strlen(CONTENTS0)); - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, filesize); expect_open(ino, 0, 1); expect_getattr(ino, filesize); /* fuse(4) should only read ahead the allowed amount */ diff --git a/tests/sys/fs/fuse/readdir.cc b/tests/sys/fs/fuse/readdir.cc index a828559d800..13387bf8d56 100644 --- a/tests/sys/fs/fuse/readdir.cc +++ b/tests/sys/fs/fuse/readdir.cc @@ -43,7 +43,7 @@ class Readdir: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1); } void expect_readdir(uint64_t ino, uint64_t off, vector &ents) diff --git a/tests/sys/fs/fuse/readlink.cc b/tests/sys/fs/fuse/readlink.cc index 5ca85c2bfa5..918bd885660 100644 --- a/tests/sys/fs/fuse/readlink.cc +++ b/tests/sys/fs/fuse/readlink.cc @@ -41,7 +41,7 @@ class Readlink: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFLNK | 0777, 1); + FuseTest::expect_lookup(relpath, ino, S_IFLNK | 0777, 0, 1); } }; diff --git a/tests/sys/fs/fuse/release.cc b/tests/sys/fs/fuse/release.cc index 71221104375..88edb45a080 100644 --- a/tests/sys/fs/fuse/release.cc +++ b/tests/sys/fs/fuse/release.cc @@ -43,7 +43,7 @@ class Release: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino, int times) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, times); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, times); } }; diff --git a/tests/sys/fs/fuse/releasedir.cc b/tests/sys/fs/fuse/releasedir.cc index 72eabf74fcc..0976cdd268d 100644 --- a/tests/sys/fs/fuse/releasedir.cc +++ b/tests/sys/fs/fuse/releasedir.cc @@ -42,7 +42,7 @@ class ReleaseDir: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino) { - FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 1); + FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1); } void expect_releasedir(uint64_t ino, ProcessMockerT r) diff --git a/tests/sys/fs/fuse/rename.cc b/tests/sys/fs/fuse/rename.cc index ed37f3f8ba1..81c039add5b 100644 --- a/tests/sys/fs/fuse/rename.cc +++ b/tests/sys/fs/fuse/rename.cc @@ -62,7 +62,7 @@ TEST_F(Rename, einval) const char RELSRC[] = "src"; uint64_t src_ino = 42; - expect_lookup(RELSRC, src_ino, S_IFDIR | 0755, 2); + expect_lookup(RELSRC, src_ino, S_IFDIR | 0755, 0, 2); EXPECT_LOOKUP(src_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); ASSERT_NE(0, rename(FULLSRC, FULLDST)); @@ -103,7 +103,7 @@ TEST_F(Rename, DISABLED_entry_cache_negative) */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; - expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); + expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); /* LOOKUP returns a negative cache entry for dst */ EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid)); @@ -143,7 +143,7 @@ TEST_F(Rename, DISABLED_entry_cache_negative_purge) */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; - expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); + expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); /* LOOKUP returns a negative cache entry for dst */ EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid)) .RetiresOnSaturation(); @@ -164,7 +164,7 @@ TEST_F(Rename, DISABLED_entry_cache_negative_purge) ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ - expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); + expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); ASSERT_EQ(0, access(FULLDST, F_OK)) << strerror(errno); } @@ -179,7 +179,7 @@ TEST_F(Rename, exdev) tmpfd = mkstemp(tmpfile); ASSERT_LE(0, tmpfd) << strerror(errno); - expect_lookup(RELB, b_ino, S_IFREG | 0644, 2); + expect_lookup(RELB, b_ino, S_IFREG | 0644, 0, 2); ASSERT_NE(0, rename(tmpfile, FULLB)); ASSERT_EQ(EXDEV, errno); @@ -198,7 +198,7 @@ TEST_F(Rename, ok) uint64_t dst_dir_ino = 1; uint64_t ino = 42; - expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); + expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); EXPECT_CALL(*m_mock, process( @@ -230,8 +230,8 @@ TEST_F(Rename, overwrite) uint64_t dst_dir_ino = 1; uint64_t ino = 42; - expect_lookup(RELSRC, ino, S_IFREG | 0644, 1); - expect_lookup(RELDST, dst_ino, S_IFREG | 0644, 1); + expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); + expect_lookup(RELDST, dst_ino, S_IFREG | 0644, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *src = (const char*)in->body.bytes + diff --git a/tests/sys/fs/fuse/unlink.cc b/tests/sys/fs/fuse/unlink.cc index d2ab0cb9bfe..bdcdf4b8e15 100644 --- a/tests/sys/fs/fuse/unlink.cc +++ b/tests/sys/fs/fuse/unlink.cc @@ -40,7 +40,7 @@ class Unlink: public FuseTest { public: void expect_lookup(const char *relpath, uint64_t ino, int times) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, times); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, times); } void expect_unlink(uint64_t parent, const char *path, int error) diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index f7bb4a30dfa..abf5e378149 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -103,7 +103,7 @@ void FuseTest::expect_getattr(uint64_t ino, uint64_t size) } void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, - int times) + uint64_t size, int times) { EXPECT_LOOKUP(1, relpath) .Times(times) @@ -113,6 +113,7 @@ void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, out->body.entry.nodeid = ino; out->body.entry.attr.nlink = 1; out->body.entry.attr_valid = UINT64_MAX; + out->body.entry.attr.size = size; }))); } diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index 51b43ee7ba3..adaaebb4010 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -73,10 +73,10 @@ class FuseTest : public ::testing::Test { /* * Create an expectation that FUSE_LOOKUP will be called for the given * path exactly times times. It will respond with inode ino, mode - * mode, and cache validity forever. + * mode, filesize size, and cache validity forever. */ void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, - int times); + uint64_t size, int times); /* * Create an expectation that FUSE_GETATTR will be called for the given diff --git a/tests/sys/fs/fuse/write.cc b/tests/sys/fs/fuse/write.cc index 67af1e38bb3..a57a7e32249 100644 --- a/tests/sys/fs/fuse/write.cc +++ b/tests/sys/fs/fuse/write.cc @@ -49,9 +49,9 @@ class Write: public FuseTest { public: -void expect_lookup(const char *relpath, uint64_t ino) +void expect_lookup(const char *relpath, uint64_t ino, uint64_t size) { - FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1); + FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1); } void expect_release(uint64_t ino, ProcessMockerT r) @@ -146,7 +146,7 @@ TEST_F(AioWrite, DISABLED_aio_write) ssize_t bufsize = strlen(CONTENTS); struct aiocb iocb, *piocb; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS); @@ -190,7 +190,7 @@ TEST_F(Write, append) require_sync_resize_0(); - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, initial_offset); expect_open(ino, 0, 1); expect_getattr(ino, initial_offset); expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, 0, CONTENTS); @@ -213,7 +213,7 @@ TEST_F(Write, append_direct_io) uint64_t initial_offset = 4096; int fd; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, initial_offset); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, initial_offset); expect_write(ino, initial_offset, BUFSIZE, BUFSIZE, 0, CONTENTS); @@ -238,7 +238,7 @@ TEST_F(Write, DISABLED_direct_io_evicts_cache) ssize_t bufsize = strlen(CONTENTS0) + 1; char readbuf[bufsize]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, bufsize); expect_open(ino, 0, 1); expect_getattr(ino, bufsize); expect_read(ino, 0, bufsize, bufsize, CONTENTS0); @@ -281,7 +281,7 @@ TEST_F(Write, DISABLED_direct_io_short_write) ssize_t halfbufsize = bufsize / 2; const char *halfcontents = CONTENTS + halfbufsize; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, halfbufsize, 0, CONTENTS); @@ -316,7 +316,7 @@ TEST_F(Write, DISABLED_direct_io_short_write_iov) ssize_t totalsize = size0 + size1; struct iovec iov[2]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, FOPEN_DIRECT_IO, 1); expect_getattr(ino, 0); expect_write(ino, 0, totalsize, size0, 0, EXPECTED0); @@ -360,7 +360,7 @@ TEST_F(Write, DISABLED_mmap) ASSERT_NE(NULL, expected); memmove((uint8_t*)expected + offset, CONTENTS, bufsize); - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, len); expect_open(ino, 0, 1); expect_getattr(ino, len); expect_read(ino, 0, len, len, zeros); @@ -396,7 +396,7 @@ TEST_F(Write, pwrite) int fd; ssize_t bufsize = strlen(CONTENTS); - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, offset, bufsize, bufsize, 0, CONTENTS); @@ -418,7 +418,7 @@ TEST_F(Write, write) int fd; ssize_t bufsize = strlen(CONTENTS); - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); @@ -448,7 +448,7 @@ TEST_F(Write, write_large) contents[i] = i; } - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, halfbufsize, halfbufsize, 0, contents); @@ -473,7 +473,7 @@ TEST_F(Write, write_nothing) int fd; ssize_t bufsize = 0; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); expect_getattr(ino, 0); @@ -494,7 +494,7 @@ TEST_F(WriteBack, close) int fd; ssize_t bufsize = strlen(CONTENTS); - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); @@ -529,7 +529,7 @@ TEST_F(WriteBack, writeback) ssize_t bufsize = strlen(CONTENTS); char readbuf[bufsize]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); @@ -562,7 +562,7 @@ TEST_F(WriteBack, o_direct) ssize_t bufsize = strlen(CONTENTS); char readbuf[bufsize]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); @@ -596,7 +596,7 @@ TEST_F(WriteThrough, DISABLED_writethrough) ssize_t bufsize = strlen(CONTENTS); char readbuf[bufsize]; - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); expect_getattr(ino, 0); expect_write(ino, 0, bufsize, bufsize, 0, CONTENTS); @@ -625,7 +625,7 @@ TEST_F(WriteThrough, DISABLED_update_file_size) int fd; ssize_t bufsize = strlen(CONTENTS); - expect_lookup(RELPATH, ino); + expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { diff --git a/tests/sys/fs/fuse/xattr.cc b/tests/sys/fs/fuse/xattr.cc index 3cd51e2d3e9..6cd5263d7ec 100644 --- a/tests/sys/fs/fuse/xattr.cc +++ b/tests/sys/fs/fuse/xattr.cc @@ -121,7 +121,7 @@ TEST_F(Getxattr, enoattr) int ns = EXTATTR_NAMESPACE_USER; ssize_t r; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR)); r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); @@ -142,7 +142,7 @@ TEST_F(Getxattr, DISABLED_enosys) int ns = EXTATTR_NAMESPACE_USER; ssize_t r; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_getxattr(ino, "user.foo", ReturnErrno(ENOSYS)); r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); @@ -174,7 +174,7 @@ TEST_F(Getxattr, erange) int ns = EXTATTR_NAMESPACE_USER; ssize_t r; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_getxattr(ino, "user.foo", ReturnErrno(ERANGE)); r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); @@ -191,7 +191,7 @@ TEST_F(Getxattr, size_only) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_USER; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_getxattr(ino, "user.foo", ReturnImmediate([](auto in __unused, auto out) { SET_OUT_HEADER_LEN(out, getxattr); @@ -215,7 +215,7 @@ TEST_F(Getxattr, system) int ns = EXTATTR_NAMESPACE_SYSTEM; ssize_t r; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_getxattr(ino, "system.foo", ReturnImmediate([&](auto in __unused, auto out) { memcpy((void*)out->body.bytes, value, value_len); @@ -240,7 +240,7 @@ TEST_F(Getxattr, user) int ns = EXTATTR_NAMESPACE_USER; ssize_t r; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_getxattr(ino, "user.foo", ReturnImmediate([&](auto in __unused, auto out) { memcpy((void*)out->body.bytes, value, value_len); @@ -264,7 +264,7 @@ TEST_F(Listxattr, DISABLED_enosys) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_USER; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_listxattr(ino, 0, ReturnErrno(ENOSYS)); ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); @@ -284,7 +284,7 @@ TEST_F(Listxattr, enotsup) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_USER; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_listxattr(ino, 0, ReturnErrno(ENOTSUP)); ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); @@ -308,7 +308,7 @@ TEST_F(Listxattr, erange) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_USER; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_listxattr(ino, 0, ReturnErrno(ERANGE)); ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); @@ -323,7 +323,7 @@ TEST_F(Listxattr, size_only_empty) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_USER; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) { out->body.listxattr.size = 0; SET_OUT_HEADER_LEN(out, listxattr); @@ -344,7 +344,7 @@ TEST_F(Listxattr, size_only_nonempty) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_USER; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) { out->body.listxattr.size = 45; SET_OUT_HEADER_LEN(out, listxattr); @@ -370,7 +370,7 @@ TEST_F(Listxattr, size_only_really_big) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_USER; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto out) { out->body.listxattr.size = 16000; SET_OUT_HEADER_LEN(out, listxattr); @@ -404,7 +404,7 @@ TEST_F(Listxattr, user) char expected[9] = {3, 'f', 'o', 'o', 4, 'b', 'a', 'n', 'g'}; char attrs[28] = "user.foo\0system.x\0user.bang"; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_listxattr(ino, 0, ReturnImmediate([&](auto in __unused, auto out) { out->body.listxattr.size = sizeof(attrs); @@ -438,7 +438,7 @@ TEST_F(Listxattr, system) char expected[2] = {1, 'x'}; char attrs[28] = "user.foo\0system.x\0user.bang"; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_listxattr(ino, 0, ReturnImmediate([&](auto in __unused, auto out) { out->body.listxattr.size = sizeof(attrs); @@ -466,7 +466,7 @@ TEST_F(Removexattr, enoattr) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_USER; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_removexattr(ino, "user.foo", ENOATTR); ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); @@ -484,7 +484,7 @@ TEST_F(Removexattr, DISABLED_enosys) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_USER; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_removexattr(ino, "user.foo", ENOSYS); ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); @@ -501,7 +501,7 @@ TEST_F(Removexattr, user) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_USER; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_removexattr(ino, "user.foo", 0); ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) @@ -514,7 +514,7 @@ TEST_F(Removexattr, system) uint64_t ino = 42; int ns = EXTATTR_NAMESPACE_SYSTEM; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_removexattr(ino, "system.foo", 0); ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) @@ -535,7 +535,7 @@ TEST_F(Setxattr, DISABLED_enosys) int ns = EXTATTR_NAMESPACE_USER; ssize_t r; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOSYS)); r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); @@ -560,7 +560,7 @@ TEST_F(Setxattr, enotsup) int ns = EXTATTR_NAMESPACE_USER; ssize_t r; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOTSUP)); r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); @@ -579,7 +579,7 @@ TEST_F(Setxattr, user) int ns = EXTATTR_NAMESPACE_USER; ssize_t r; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_setxattr(ino, "user.foo", value, ReturnErrno(0)); r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); @@ -597,7 +597,7 @@ TEST_F(Setxattr, system) int ns = EXTATTR_NAMESPACE_SYSTEM; ssize_t r; - expect_lookup(RELPATH, ino, S_IFREG | 0644, 1); + expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_setxattr(ino, "system.foo", value, ReturnErrno(0)); r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); From 93d9f5818a030465801a30af7f0f40e2b9ed0d00 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Mon, 18 Mar 2019 18:05:19 +0000 Subject: [PATCH 37/41] fuse(4): add tests for some mount options. This commit adds tests for the default_permissions and push_symlinks_in mount options. It doesn't add tests for allow_other, because I'm not sure how that will interact with Kyua (the test will need to drop privileges). All of the other mount options are undocumented. PR: 216391 Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 6 + tests/sys/fs/fuse/access.cc | 3 - tests/sys/fs/fuse/default_permissions.cc | 136 +++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.cc | 14 ++- tests/sys/fs/fuse/mockfs.hh | 3 +- tests/sys/fs/fuse/readlink.cc | 64 ++++++++--- tests/sys/fs/fuse/utils.cc | 3 +- tests/sys/fs/fuse/utils.hh | 20 ++-- 8 files changed, 217 insertions(+), 32 deletions(-) create mode 100644 tests/sys/fs/fuse/default_permissions.cc diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index ec01b0c3476..64e606cc005 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -9,6 +9,7 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse # out, so we get more granular reporting. ATF_TESTS_CXX+= access ATF_TESTS_CXX+= create +ATF_TESTS_CXX+= default_permissions ATF_TESTS_CXX+= destroy ATF_TESTS_CXX+= flush ATF_TESTS_CXX+= fsync @@ -46,6 +47,11 @@ SRCS.create+= getmntopts.c SRCS.create+= mockfs.cc SRCS.create+= utils.cc +SRCS.default_permissions+= default_permissions.cc +SRCS.default_permissions+= getmntopts.c +SRCS.default_permissions+= mockfs.cc +SRCS.default_permissions+= utils.cc + SRCS.destroy+= destroy.cc SRCS.destroy+= getmntopts.c SRCS.destroy+= mockfs.cc diff --git a/tests/sys/fs/fuse/access.cc b/tests/sys/fs/fuse/access.cc index 5ceaf567a67..094809be646 100644 --- a/tests/sys/fs/fuse/access.cc +++ b/tests/sys/fs/fuse/access.cc @@ -29,7 +29,6 @@ */ extern "C" { -//#include #include #include } @@ -59,8 +58,6 @@ void expect_lookup(const char *relpath, uint64_t ino) } }; -/* TODO: test methods for the default_permissions mount option */ - /* The error case of FUSE_ACCESS. */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236291 */ TEST_F(Access, DISABLED_eaccess) diff --git a/tests/sys/fs/fuse/default_permissions.cc b/tests/sys/fs/fuse/default_permissions.cc new file mode 100644 index 00000000000..ffc56bb6cdc --- /dev/null +++ b/tests/sys/fs/fuse/default_permissions.cc @@ -0,0 +1,136 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 The FreeBSD Foundation + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Tests for the "default_permissions" mount option. They must be in their own + * file so they can be run as an unprivileged user + */ + +extern "C" { +#include +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class DefaultPermissions: public FuseTest { + +virtual void SetUp() { + if (geteuid() == 0) { + FAIL() << "This test requires an unprivileged user"; + } + m_default_permissions = true; + FuseTest::SetUp(); +} + +public: +void expect_lookup(const char *relpath, uint64_t ino, mode_t mode) +{ + FuseTest::expect_lookup(relpath, ino, S_IFREG | mode, 0, 1); +} + +}; + +class Access: public DefaultPermissions {}; +class Open: public DefaultPermissions {}; + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=216391 */ +TEST_F(Access, DISABLED_eaccess) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + mode_t access_mode = X_OK; + + expect_lookup(RELPATH, ino, S_IFREG | 0644); + /* + * Once default_permissions is properly implemented, there might be + * another FUSE_GETATTR or something in here. But there should not be + * a FUSE_ACCESS + */ + + ASSERT_NE(0, access(FULLPATH, access_mode)); + ASSERT_EQ(EACCES, errno); +} + +TEST_F(Access, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + mode_t access_mode = R_OK; + + expect_lookup(RELPATH, ino, S_IFREG | 0644); + /* + * Once default_permissions is properly implemented, there might be + * another FUSE_GETATTR or something in here. But there should not be + * a FUSE_ACCESS + */ + + ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); +} + +TEST_F(Open, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + expect_lookup(RELPATH, ino, S_IFREG | 0644); + expect_open(ino, 0, 1); + /* Until the attr cache is working, we may send an additional GETATTR */ + expect_getattr(ino, 0); + + fd = open(FULLPATH, O_RDONLY); + EXPECT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=216391 */ +TEST_F(Open, DISABLED_eperm) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + expect_lookup(RELPATH, ino, S_IFREG | 0644); + /* + * Once default_permissions is properly implemented, there might be + * another FUSE_GETATTR or something in here. But there should not be + * a FUSE_ACCESS + */ + + EXPECT_NE(0, open(FULLPATH, O_RDWR)); + EXPECT_EQ(EPERM, errno); +} diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index bb5e7f6b24e..a5a7e9130e4 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -243,7 +243,9 @@ void debug_fuseop(const mockfs_buf_in *in) printf("\n"); } -MockFS::MockFS(int max_readahead, uint32_t flags) { +MockFS::MockFS(int max_readahead, bool push_symlinks_in, + bool default_permissions, uint32_t flags) +{ struct iovec *iov = NULL; int iovlen = 0; char fdstr[15]; @@ -277,6 +279,16 @@ MockFS::MockFS(int max_readahead, uint32_t flags) { __DECONST(void *, "mountpoint"), -1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); build_iovec(&iov, &iovlen, "fd", fdstr, -1); + if (push_symlinks_in) { + const bool trueval = true; + build_iovec(&iov, &iovlen, "push_symlinks_in", + __DECONST(void*, &trueval), sizeof(bool)); + } + if (default_permissions) { + const bool trueval = true; + build_iovec(&iov, &iovlen, "default_permissions", + __DECONST(void*, &trueval), sizeof(bool)); + } if (nmount(iov, iovlen, 0)) throw(std::system_error(errno, std::system_category(), "Couldn't mount filesystem")); diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index b836d18d308..5061fd51524 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -212,7 +212,8 @@ class MockFS { uint32_t m_max_write; /* Create a new mockfs and mount it to a tempdir */ - MockFS(int max_readahead, uint32_t flags); + MockFS(int max_readahead, bool push_symlinks_in, + bool default_permissions, uint32_t flags); virtual ~MockFS(); /* Kill the filesystem daemon without unmounting the filesystem */ diff --git a/tests/sys/fs/fuse/readlink.cc b/tests/sys/fs/fuse/readlink.cc index 918bd885660..de2828dd877 100644 --- a/tests/sys/fs/fuse/readlink.cc +++ b/tests/sys/fs/fuse/readlink.cc @@ -29,6 +29,8 @@ */ extern "C" { +#include + #include } @@ -43,6 +45,24 @@ void expect_lookup(const char *relpath, uint64_t ino) { FuseTest::expect_lookup(relpath, ino, S_IFLNK | 0777, 0, 1); } +void expect_readlink(uint64_t ino, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READLINK && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} + +}; + +class PushSymlinksIn: public Readlink { + virtual void SetUp() { + m_push_symlinks_in = true; + Readlink::SetUp(); + } }; TEST_F(Readlink, eloop) @@ -53,14 +73,7 @@ TEST_F(Readlink, eloop) char buf[80]; expect_lookup(RELPATH, ino); - - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_READLINK && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnErrno(ELOOP))); + expect_readlink(ino, ReturnErrno(ELOOP)); EXPECT_EQ(-1, readlink(FULLPATH, buf, sizeof(buf))); EXPECT_EQ(ELOOP, errno); @@ -75,20 +88,35 @@ TEST_F(Readlink, ok) char buf[80]; expect_lookup(RELPATH, ino); - - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_READLINK && - in->header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { + expect_readlink(ino, ReturnImmediate([=](auto in __unused, auto out) { strlcpy(out->body.str, dst, sizeof(out->body.str)); out->header.len = sizeof(out->header) + strlen(dst) + 1; - }))); - + })); EXPECT_EQ((ssize_t)strlen(dst) + 1, readlink(FULLPATH, buf, sizeof(buf))); EXPECT_STREQ(dst, buf); } + +TEST_F(PushSymlinksIn, readlink) +{ + const char FULLPATH[] = "mountpoint/src"; + const char RELPATH[] = "src"; + const char dst[] = "/dst"; + const uint64_t ino = 42; + char buf[MAXPATHLEN], wd[MAXPATHLEN], want[MAXPATHLEN]; + int len; + + expect_lookup(RELPATH, ino); + expect_readlink(ino, ReturnImmediate([=](auto in __unused, auto out) { + strlcpy(out->body.str, dst, sizeof(out->body.str)); + out->header.len = sizeof(out->header) + strlen(dst) + 1; + })); + + ASSERT_NE(NULL, getcwd(wd, sizeof(wd))) << strerror(errno); + len = snprintf(want, sizeof(want), "%s/mountpoint%s", wd, dst); + ASSERT_LE(0, len) << strerror(errno); + + EXPECT_EQ((ssize_t)len + 1, readlink(FULLPATH, buf, sizeof(buf))); + EXPECT_STREQ(want, buf); +} diff --git a/tests/sys/fs/fuse/utils.cc b/tests/sys/fs/fuse/utils.cc index abf5e378149..a08daf36a8c 100644 --- a/tests/sys/fs/fuse/utils.cc +++ b/tests/sys/fs/fuse/utils.cc @@ -78,7 +78,8 @@ void FuseTest::SetUp() { m_maxbcachebuf = val; try { - m_mock = new MockFS(m_maxreadahead, m_init_flags); + m_mock = new MockFS(m_maxreadahead, m_push_symlinks_in, + m_default_permissions, m_init_flags); } catch (std::system_error err) { FAIL() << err.what(); } diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index adaaebb4010..7791c4e0ef2 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -41,20 +41,24 @@ class FuseTest : public ::testing::Test { protected: uint32_t m_maxreadahead; uint32_t m_init_flags; + bool m_default_permissions; + bool m_push_symlinks_in; MockFS *m_mock = NULL; const static uint64_t FH = 0xdeadbeef1a7ebabe; public: int m_maxbcachebuf; - /* - * libfuse's default max_readahead is UINT_MAX, though it can be - * lowered - */ - FuseTest(): FuseTest(UINT_MAX, 0) {} - FuseTest(uint32_t maxreadahead): m_maxreadahead(maxreadahead) {} - FuseTest(uint32_t maxreadahead, uint32_t init_flags): - m_maxreadahead(maxreadahead), m_init_flags(init_flags) {} + FuseTest(): + /* + * libfuse's default max_readahead is UINT_MAX, though it can + * be lowered + */ + m_maxreadahead(UINT_MAX), + m_init_flags(0), + m_default_permissions(false), + m_push_symlinks_in(false) + {} virtual void SetUp(); From b2e95f1ce5f66b667678b7ff1054a1c3750b4556 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 19 Mar 2019 03:10:13 +0000 Subject: [PATCH 38/41] fuse(4): build the tests with the new googletest in base Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/Makefile | 82 ++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index 64e606cc005..c98020dc196 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -7,35 +7,35 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse # We could simply link all of these files into a single executable. But since # Kyua treats googletest programs as plain tests, it's better to separate them # out, so we get more granular reporting. -ATF_TESTS_CXX+= access -ATF_TESTS_CXX+= create -ATF_TESTS_CXX+= default_permissions -ATF_TESTS_CXX+= destroy -ATF_TESTS_CXX+= flush -ATF_TESTS_CXX+= fsync -ATF_TESTS_CXX+= fsyncdir -ATF_TESTS_CXX+= getattr -ATF_TESTS_CXX+= interrupt -ATF_TESTS_CXX+= link -ATF_TESTS_CXX+= locks -ATF_TESTS_CXX+= lookup -ATF_TESTS_CXX+= mkdir -ATF_TESTS_CXX+= mknod -ATF_TESTS_CXX+= open -ATF_TESTS_CXX+= opendir -ATF_TESTS_CXX+= read -ATF_TESTS_CXX+= readdir -ATF_TESTS_CXX+= readlink -ATF_TESTS_CXX+= release -ATF_TESTS_CXX+= releasedir -ATF_TESTS_CXX+= rename -ATF_TESTS_CXX+= rmdir -ATF_TESTS_CXX+= setattr -ATF_TESTS_CXX+= statfs -ATF_TESTS_CXX+= symlink -ATF_TESTS_CXX+= unlink -ATF_TESTS_CXX+= write -ATF_TESTS_CXX+= xattr +GTESTS+= access +GTESTS+= create +GTESTS+= default_permissions +GTESTS+= destroy +GTESTS+= flush +GTESTS+= fsync +GTESTS+= fsyncdir +GTESTS+= getattr +GTESTS+= interrupt +GTESTS+= link +GTESTS+= locks +GTESTS+= lookup +GTESTS+= mkdir +GTESTS+= mknod +GTESTS+= open +GTESTS+= opendir +GTESTS+= read +GTESTS+= readdir +GTESTS+= readlink +GTESTS+= release +GTESTS+= releasedir +GTESTS+= rename +GTESTS+= rmdir +GTESTS+= setattr +GTESTS+= statfs +GTESTS+= symlink +GTESTS+= unlink +GTESTS+= write +GTESTS+= xattr SRCS.access+= access.cc SRCS.access+= getmntopts.c @@ -51,6 +51,7 @@ SRCS.default_permissions+= default_permissions.cc SRCS.default_permissions+= getmntopts.c SRCS.default_permissions+= mockfs.cc SRCS.default_permissions+= utils.cc +TEST_METADATA.default_permissions+= required_user="unprivileged" SRCS.destroy+= destroy.cc SRCS.destroy+= getmntopts.c @@ -106,6 +107,7 @@ SRCS.mknod+= getmntopts.c SRCS.mknod+= mockfs.cc SRCS.mknod+= mknod.cc SRCS.mknod+= utils.cc +TEST_METADATA.mknod+= required_user="root" SRCS.open+= getmntopts.c SRCS.open+= mockfs.cc @@ -191,22 +193,16 @@ CFLAGS+= -I${.CURDIR:H:H:H} CFLAGS+= -I${FUSEFS} CFLAGS+= -I${MOUNT} .PATH: ${MOUNT} +CXXSTD= c++14 -LIBADD+= util pthread -WARNS?= 6 -NO_WTHREAD_SAFETY= # GoogleTest fails Clang's thread safety check +# XXX Setting CXXFLAGS globally seems to be necessary to get mockfs.cc and +# utils.cc to build correctly. +CXXFLAGS+= ${GTESTS_CXXFLAGS} -# Use googlemock from ports until after the import-googletest-1.8.1 branch -# merges to head. -CXXFLAGS+= -I/usr/local/include -CXXFLAGS+= -DGTEST_HAS_POSIX_RE=1 -CXXFLAGS+= -DGTEST_HAS_PTHREAD=1 -CXXFLAGS+= -DGTEST_HAS_STREAM_REDIRECTION=1 -CXXFLAGS+= -frtti -CXXFLAGS+= -std=c++14 -LDADD+= ${LOCALBASE}/lib/libgmock.a -LDADD+= ${LOCALBASE}/lib/libgtest.a -# Without -lpthread, gtest fails at _runtime_ with the error pthread_key_create(&key, &DeleteThreadLocalValue)failed with error 78 LIBADD+= pthread +LIBADD+= gmock gtest +LIBADD+= util + +WARNS?= 6 .include From 7e4844f7d9546ba65202fbe834d1c06aa72a37b8 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 19 Mar 2019 17:49:15 +0000 Subject: [PATCH 39/41] fuse(4): remove more debugging printfs I missed these in r344664. They're basically useless because they can only be controlled at compile-time. Also, de-inline fuse_internal_cache_attrs. It's big enough to be a regular function, and this way it gets a dtrace FBT probe. Sponsored by: The FreeBSD Foundation --- sys/fs/fuse/fuse.h | 61 +--------------------------- sys/fs/fuse/fuse_internal.c | 63 ++++++++++++++++++++++++++++- sys/fs/fuse/fuse_internal.h | 79 +------------------------------------ sys/fs/fuse/fuse_ipc.h | 16 -------- sys/fs/fuse/fuse_vnops.c | 26 ++++++++---- 5 files changed, 85 insertions(+), 160 deletions(-) diff --git a/sys/fs/fuse/fuse.h b/sys/fs/fuse/fuse.h index 07e2759af96..b2d452667d9 100644 --- a/sys/fs/fuse/fuse.h +++ b/sys/fs/fuse/fuse.h @@ -159,65 +159,8 @@ do { \ (cred) = (td)->td_ucred; \ } while (0) -/* Debug related stuff */ - -#ifndef FUSE_DEBUG_DEVICE -#define FUSE_DEBUG_DEVICE 0 -#endif - -#ifndef FUSE_DEBUG_FILE -#define FUSE_DEBUG_FILE 0 -#endif - -#ifndef FUSE_DEBUG_INTERNAL -#define FUSE_DEBUG_INTERNAL 0 -#endif - -#ifndef FUSE_DEBUG_IO -#define FUSE_DEBUG_IO 0 -#endif - -#ifndef FUSE_DEBUG_IPC -#define FUSE_DEBUG_IPC 0 -#endif - -#ifndef FUSE_DEBUG_LOCK -#define FUSE_DEBUG_LOCK 0 -#endif - -#ifndef FUSE_DEBUG_VFSOPS -#define FUSE_DEBUG_VFSOPS 0 -#endif - -#ifndef FUSE_DEBUG_VNOPS -#define FUSE_DEBUG_VNOPS 0 -#endif - -#ifndef FUSE_TRACE -#define FUSE_TRACE 0 -#endif - -#define DEBUGX(cond, fmt, ...) do { \ - if (((cond))) { \ - printf("%s: " fmt, __func__, ## __VA_ARGS__); \ - } \ -} while (0) - -#define fuse_lck_mtx_lock(mtx) do { \ - DEBUGX(FUSE_DEBUG_LOCK, "0: lock(%s): %s@%d by %d\n", \ - __STRING(mtx), __func__, __LINE__, curthread->td_proc->p_pid); \ - mtx_lock(&(mtx)); \ - DEBUGX(FUSE_DEBUG_LOCK, "1: lock(%s): %s@%d by %d\n", \ - __STRING(mtx), __func__, __LINE__, curthread->td_proc->p_pid); \ -} while (0) - -#define fuse_lck_mtx_unlock(mtx) do { \ - DEBUGX(FUSE_DEBUG_LOCK, "0: unlock(%s): %s@%d by %d\n", \ - __STRING(mtx), __func__, __LINE__, curthread->td_proc->p_pid); \ - mtx_unlock(&(mtx)); \ - DEBUGX(FUSE_DEBUG_LOCK, "1: unlock(%s): %s@%d by %d\n", \ - __STRING(mtx), __func__, __LINE__, curthread->td_proc->p_pid); \ -} while (0) +#define fuse_lck_mtx_lock(mtx) mtx_lock(&(mtx)) +#define fuse_lck_mtx_unlock(mtx) mtx_unlock(&(mtx)) void fuse_ipc_init(void); void fuse_ipc_destroy(void); diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c index 2870410560a..0c33c0cbe51 100644 --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -207,6 +207,66 @@ fuse_internal_access(struct vnode *vp, return err; } +/* + * Cache FUSE attributes from feo, in attr cache associated with vnode 'vp'. + * Optionally, if argument 'vap' is not NULL, store a copy of the converted + * attributes there as well. + * + * If the nominal attribute cache TTL is zero, do not cache on the 'vp' (but do + * return the result to the caller). + */ +void +fuse_internal_cache_attrs(struct vnode *vp, struct fuse_attr *attr, + uint64_t attr_valid, uint32_t attr_valid_nsec, struct vattr *vap) +{ + struct mount *mp; + struct fuse_vnode_data *fvdat; + struct vattr *vp_cache_at; + + mp = vnode_mount(vp); + fvdat = VTOFUD(vp); + + /* Honor explicit do-not-cache requests from user filesystems. */ + if (attr_valid == 0 && attr_valid_nsec == 0) + fvdat->valid_attr_cache = false; + else + fvdat->valid_attr_cache = true; + + vp_cache_at = VTOVA(vp); + + if (vap == NULL && vp_cache_at == NULL) + return; + + if (vap == NULL) + vap = vp_cache_at; + + vattr_null(vap); + + vap->va_fsid = mp->mnt_stat.f_fsid.val[0]; + vap->va_fileid = attr->ino; + vap->va_mode = attr->mode & ~S_IFMT; + vap->va_nlink = attr->nlink; + vap->va_uid = attr->uid; + vap->va_gid = attr->gid; + vap->va_rdev = attr->rdev; + vap->va_size = attr->size; + /* XXX on i386, seconds are truncated to 32 bits */ + vap->va_atime.tv_sec = attr->atime; + vap->va_atime.tv_nsec = attr->atimensec; + vap->va_mtime.tv_sec = attr->mtime; + vap->va_mtime.tv_nsec = attr->mtimensec; + vap->va_ctime.tv_sec = attr->ctime; + vap->va_ctime.tv_nsec = attr->ctimensec; + vap->va_blocksize = PAGE_SIZE; + vap->va_type = IFTOVT(attr->mode); + vap->va_bytes = attr->blocks * S_BLKSIZE; + vap->va_flags = 0; + + if (vap != vp_cache_at && vp_cache_at != NULL) + memcpy(vp_cache_at, vap, sizeof(*vap)); +} + + /* fsync */ int @@ -472,7 +532,8 @@ fuse_internal_newentry_core(struct vnode *dvp, feo->nodeid, 1); return err; } - cache_attrs(*vpp, feo, NULL); + fuse_internal_cache_attrs(*vpp, &feo->attr, feo->attr_valid, + feo->attr_valid_nsec, NULL); return err; } diff --git a/sys/fs/fuse/fuse_internal.h b/sys/fs/fuse/fuse_internal.h index 057c2888313..d809a526258 100644 --- a/sys/fs/fuse/fuse_internal.h +++ b/sys/fs/fuse/fuse_internal.h @@ -193,74 +193,8 @@ int fuse_internal_access(struct vnode *vp, mode_t mode, struct fuse_access_param *facp, struct thread *td, struct ucred *cred); /* attributes */ - -/* - * Cache FUSE attributes 'fat', with nominal expiration - * 'attr_valid'.'attr_valid_nsec', in attr cache associated with vnode 'vp'. - * Optionally, if argument 'vap' is not NULL, store a copy of the converted - * attributes there as well. - * - * If the nominal attribute cache TTL is zero, do not cache on the 'vp' (but do - * return the result to the caller). - */ -static inline void -fuse_internal_attr_fat2vat(struct vnode *vp, struct fuse_attr *fat, - uint64_t attr_valid, uint32_t attr_valid_nsec, struct vattr *vap) -{ - struct mount *mp; - struct fuse_vnode_data *fvdat; - struct vattr *vp_cache_at; - - mp = vnode_mount(vp); - fvdat = VTOFUD(vp); - - DEBUGX(FUSE_DEBUG_INTERNAL, "node #%ju, mode 0%o\n", - (uintmax_t)fat->ino, fat->mode); - - /* Honor explicit do-not-cache requests from user filesystems. */ - if (attr_valid == 0 && attr_valid_nsec == 0) - fvdat->valid_attr_cache = false; - else - fvdat->valid_attr_cache = true; - - vp_cache_at = VTOVA(vp); - - if (vap == NULL && vp_cache_at == NULL) - return; - - if (vap == NULL) - vap = vp_cache_at; - - vattr_null(vap); - - vap->va_fsid = mp->mnt_stat.f_fsid.val[0]; - vap->va_fileid = fat->ino; - vap->va_mode = fat->mode & ~S_IFMT; - vap->va_nlink = fat->nlink; - vap->va_uid = fat->uid; - vap->va_gid = fat->gid; - vap->va_rdev = fat->rdev; - vap->va_size = fat->size; - /* XXX on i386, seconds are truncated to 32 bits */ - vap->va_atime.tv_sec = fat->atime; - vap->va_atime.tv_nsec = fat->atimensec; - vap->va_mtime.tv_sec = fat->mtime; - vap->va_mtime.tv_nsec = fat->mtimensec; - vap->va_ctime.tv_sec = fat->ctime; - vap->va_ctime.tv_nsec = fat->ctimensec; - vap->va_blocksize = PAGE_SIZE; - vap->va_type = IFTOVT(fat->mode); - vap->va_bytes = fat->blocks * S_BLKSIZE; - vap->va_flags = 0; - - if (vap != vp_cache_at && vp_cache_at != NULL) - memcpy(vp_cache_at, vap, sizeof(*vap)); -} - - -#define cache_attrs(vp, fuse_out, vap_out) \ - fuse_internal_attr_fat2vat((vp), &(fuse_out)->attr, \ - (fuse_out)->attr_valid, (fuse_out)->attr_valid_nsec, (vap_out)) +void fuse_internal_cache_attrs(struct vnode *vp, struct fuse_attr *attr, + uint64_t attr_valid, uint32_t attr_valid_nsec, struct vattr *vap); /* fsync */ @@ -300,24 +234,15 @@ void fuse_internal_vnode_disappear(struct vnode *vp); static inline int fuse_internal_checkentry(struct fuse_entry_out *feo, enum vtype vtyp) { - DEBUGX(FUSE_DEBUG_INTERNAL, - "feo=%p, vtype=%d\n", feo, vtyp); - if (vtyp != IFTOVT(feo->attr.mode)) { - DEBUGX(FUSE_DEBUG_INTERNAL, - "EINVAL -- %x != %x\n", vtyp, IFTOVT(feo->attr.mode)); return (EINVAL); } if (feo->nodeid == FUSE_NULL_ID) { - DEBUGX(FUSE_DEBUG_INTERNAL, - "EINVAL -- feo->nodeid is NULL\n"); return (EINVAL); } if (feo->nodeid == FUSE_ROOT_ID) { - DEBUGX(FUSE_DEBUG_INTERNAL, - "EINVAL -- feo->nodeid is FUSE_ROOT_ID\n"); return (EINVAL); } diff --git a/sys/fs/fuse/fuse_ipc.h b/sys/fs/fuse/fuse_ipc.h index 4c3dd35384c..c7b6f33f0aa 100644 --- a/sys/fs/fuse/fuse_ipc.h +++ b/sys/fs/fuse/fuse_ipc.h @@ -136,7 +136,6 @@ fticket_resp(struct fuse_ticket *ftick) static inline bool fticket_answered(struct fuse_ticket *ftick) { - DEBUGX(FUSE_DEBUG_IPC, "-> ftick=%p\n", ftick); mtx_assert(&ftick->tk_aw_mtx, MA_OWNED); return (ftick->tk_flag & FT_ANSW); } @@ -144,7 +143,6 @@ fticket_answered(struct fuse_ticket *ftick) static inline void fticket_set_answered(struct fuse_ticket *ftick) { - DEBUGX(FUSE_DEBUG_IPC, "-> ftick=%p\n", ftick); mtx_assert(&ftick->tk_aw_mtx, MA_OWNED); ftick->tk_flag |= FT_ANSW; } @@ -152,7 +150,6 @@ fticket_set_answered(struct fuse_ticket *ftick) static inline enum fuse_opcode fticket_opcode(struct fuse_ticket *ftick) { - DEBUGX(FUSE_DEBUG_IPC, "-> ftick=%p\n", ftick); return (((struct fuse_in_header *)(ftick->tk_ms_fiov.base))->opcode); } @@ -273,8 +270,6 @@ fsess_opt_brokenio(struct mount *mp) static inline void fuse_ms_push(struct fuse_ticket *ftick) { - DEBUGX(FUSE_DEBUG_IPC, "ftick=%p refcount=%d\n", ftick, - ftick->tk_refcount + 1); mtx_assert(&ftick->tk_data->ms_mtx, MA_OWNED); refcount_acquire(&ftick->tk_refcount); STAILQ_INSERT_TAIL(&ftick->tk_data->ms_head, ftick, tk_ms_link); @@ -293,8 +288,6 @@ fuse_ms_pop(struct fuse_data *data) ftick->tk_ms_link.stqe_next = NULL; #endif } - DEBUGX(FUSE_DEBUG_IPC, "ftick=%p refcount=%d\n", ftick, - ftick ? ftick->tk_refcount : -1); return (ftick); } @@ -302,8 +295,6 @@ fuse_ms_pop(struct fuse_data *data) static inline void fuse_aw_push(struct fuse_ticket *ftick) { - DEBUGX(FUSE_DEBUG_IPC, "ftick=%p refcount=%d\n", ftick, - ftick->tk_refcount + 1); mtx_assert(&ftick->tk_data->aw_mtx, MA_OWNED); refcount_acquire(&ftick->tk_refcount); TAILQ_INSERT_TAIL(&ftick->tk_data->aw_head, ftick, tk_aw_link); @@ -312,8 +303,6 @@ fuse_aw_push(struct fuse_ticket *ftick) static inline void fuse_aw_remove(struct fuse_ticket *ftick) { - DEBUGX(FUSE_DEBUG_IPC, "ftick=%p refcount=%d\n", - ftick, ftick->tk_refcount); mtx_assert(&ftick->tk_data->aw_mtx, MA_OWNED); TAILQ_REMOVE(&ftick->tk_data->aw_head, ftick, tk_aw_link); #ifdef INVARIANTS @@ -331,8 +320,6 @@ fuse_aw_pop(struct fuse_data *data) if ((ftick = TAILQ_FIRST(&data->aw_head)) != NULL) fuse_aw_remove(ftick); - DEBUGX(FUSE_DEBUG_IPC, "ftick=%p refcount=%d\n", ftick, - ftick ? ftick->tk_refcount : -1); return (ftick); } @@ -374,7 +361,6 @@ struct fuse_dispatcher { static inline void fdisp_init(struct fuse_dispatcher *fdisp, size_t iosize) { - DEBUGX(FUSE_DEBUG_IPC, "-> fdisp=%p, iosize=%zx\n", fdisp, iosize); fdisp->iosize = iosize; fdisp->tick = NULL; } @@ -382,7 +368,6 @@ fdisp_init(struct fuse_dispatcher *fdisp, size_t iosize) static inline void fdisp_destroy(struct fuse_dispatcher *fdisp) { - DEBUGX(FUSE_DEBUG_IPC, "-> fdisp=%p, ftick=%p\n", fdisp, fdisp->tick); fuse_ticket_drop(fdisp->tick); #ifdef INVARIANTS fdisp->tick = NULL; @@ -404,7 +389,6 @@ static inline int fdisp_simple_putget_vp(struct fuse_dispatcher *fdip, enum fuse_opcode op, struct vnode *vp, struct thread *td, struct ucred *cred) { - DEBUGX(FUSE_DEBUG_IPC, "-> fdip=%p, opcode=%d, vp=%p\n", fdip, op, vp); fdisp_make_vp(fdip, op, vp, td, cred); return (fdisp_wait_answ(fdip)); } diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c index fbf0237789a..f9661f0f2f8 100644 --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -478,6 +478,7 @@ fuse_vnop_getattr(struct vop_getattr_args *ap) struct ucred *cred = ap->a_cred; struct thread *td = curthread; struct fuse_vnode_data *fvdat = VTOFUD(vp); + struct fuse_attr_out *fao; int err = 0; int dataflags; @@ -509,7 +510,9 @@ fuse_vnop_getattr(struct vop_getattr_args *ap) goto out; } - cache_attrs(vp, (struct fuse_attr_out *)fdi.answ, vap); + fao = (struct fuse_attr_out *)fdi.answ; + fuse_internal_cache_attrs(vp, &fao->attr, fao->attr_valid, + fao->attr_valid_nsec, vap); if (vap->va_type != vnode_vtype(vp)) { fuse_internal_vnode_disappear(vp); err = ENOENT; @@ -975,11 +978,17 @@ calldaemon: } if (op == FUSE_GETATTR) { - cache_attrs(*vpp, (struct fuse_attr_out *)fdi.answ, - NULL); + struct fuse_attr_out *fao = + (struct fuse_attr_out*)fdi.answ; + fuse_internal_cache_attrs(*vpp, + &fao->attr, fao->attr_valid, + fao->attr_valid_nsec, NULL); } else { - cache_attrs(*vpp, (struct fuse_entry_out *)fdi.answ, - NULL); + struct fuse_entry_out *feo = + (struct fuse_entry_out*)fdi.answ; + fuse_internal_cache_attrs(*vpp, + &feo->attr, feo->attr_valid, + feo->attr_valid_nsec, NULL); } /* Insert name into cache if appropriate. */ @@ -1636,8 +1645,11 @@ fuse_vnop_setattr(struct vop_setattr_args *ap) err = EAGAIN; } } - if (err == 0) - cache_attrs(vp, (struct fuse_attr_out *)fdi.answ, NULL); + if (err == 0) { + struct fuse_attr_out *fao = (struct fuse_attr_out*)fdi.answ; + fuse_internal_cache_attrs(vp, &fao->attr, fao->attr_valid, + fao->attr_valid_nsec, NULL); + } out: fdisp_destroy(&fdi); From 197f8aac0060068fb7ed52d08c7c45f9283aebea Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 20 Mar 2019 16:08:07 +0000 Subject: [PATCH 40/41] fuse(4): fix a race condition in the tests Sometimes the fuse daemon doesn't die as soon as its /dev/fuse file descriptor is closed; it needs to be unmounted first. Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/mockfs.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/sys/fs/fuse/mockfs.cc b/tests/sys/fs/fuse/mockfs.cc index a5a7e9130e4..7812c6dd225 100644 --- a/tests/sys/fs/fuse/mockfs.cc +++ b/tests/sys/fs/fuse/mockfs.cc @@ -307,6 +307,10 @@ MockFS::MockFS(int max_readahead, bool push_symlinks_in, MockFS::~MockFS() { kill_daemon(); ::unmount("mountpoint", MNT_FORCE); + if (m_daemon_id != NULL) { + pthread_join(m_daemon_id, NULL); + m_daemon_id = NULL; + } rmdir("mountpoint"); } @@ -353,8 +357,6 @@ void MockFS::kill_daemon() { // to succeed even if the daemon doesn't correctly respond to // commands during the unmount sequence. close(m_fuse_fd); - pthread_join(m_daemon_id, NULL); - m_daemon_id = NULL; } } From da1a9eb08c680465ca7d0d798a0f9ed4fff3b62c Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 20 Mar 2019 17:11:25 +0000 Subject: [PATCH 41/41] googletest: backport GTEST_SKIP to googletest 1.8.1 This commit backports revisions 00938b2b228f3b70d3d9e51f29a1505bdad43f1e and 59f90a338bce2376b540ee239cf4e269bf6d68ad from googletest's master branch to our included version of googletest, which is based on 1.8.1. It adds the GTEST_SKIP feature, which is very useful for a project like FreeBSD where some tests depend on particular system configurations. Obtained from: github.com/google/googletest Sponsored by: The FreeBSD Foundation --- contrib/googletest/googletest/CMakeLists.txt | 1 + .../include/gtest/gtest-test-part.h | 12 ++- .../googletest/include/gtest/gtest.h | 24 ++++- .../include/gtest/internal/gtest-internal.h | 5 +- .../googletest/src/gtest-internal-inl.h | 3 + .../googletest/src/gtest-test-part.cc | 16 +-- contrib/googletest/googletest/src/gtest.cc | 100 +++++++++++++++--- .../test/googletest-test-part-test.cc | 31 +++++- .../googletest/test/gtest_all_test.cc | 7 +- .../googletest/test/gtest_skip_test.cc | 55 ++++++++++ 10 files changed, 224 insertions(+), 30 deletions(-) create mode 100644 contrib/googletest/googletest/test/gtest_skip_test.cc diff --git a/contrib/googletest/googletest/CMakeLists.txt b/contrib/googletest/googletest/CMakeLists.txt index 9ee79408c27..e4e99e836fc 100644 --- a/contrib/googletest/googletest/CMakeLists.txt +++ b/contrib/googletest/googletest/CMakeLists.txt @@ -217,6 +217,7 @@ if (gtest_build_tests) test/gtest-typed-test2_test.cc) cxx_test(gtest_unittest gtest_main) cxx_test(gtest-unittest-api_test gtest) + cxx_test(gtest_skip_test gtest_main) ############################################################ # C++ tests built with non-standard compiler flags. diff --git a/contrib/googletest/googletest/include/gtest/gtest-test-part.h b/contrib/googletest/googletest/include/gtest/gtest-test-part.h index 1c7b89e0879..7b30aff67a3 100644 --- a/contrib/googletest/googletest/include/gtest/gtest-test-part.h +++ b/contrib/googletest/googletest/include/gtest/gtest-test-part.h @@ -53,7 +53,8 @@ class GTEST_API_ TestPartResult { enum Type { kSuccess, // Succeeded. kNonFatalFailure, // Failed but the test can continue. - kFatalFailure // Failed and the test should be terminated. + kFatalFailure, // Failed and the test should be terminated. + kSkip // Skipped. }; // C'tor. TestPartResult does NOT have a default constructor. @@ -89,18 +90,21 @@ class GTEST_API_ TestPartResult { // Gets the message associated with the test part. const char* message() const { return message_.c_str(); } + // Returns true iff the test part was skipped. + bool skipped() const { return type_ == kSkip; } + // Returns true iff the test part passed. bool passed() const { return type_ == kSuccess; } - // Returns true iff the test part failed. - bool failed() const { return type_ != kSuccess; } - // Returns true iff the test part non-fatally failed. bool nonfatally_failed() const { return type_ == kNonFatalFailure; } // Returns true iff the test part fatally failed. bool fatally_failed() const { return type_ == kFatalFailure; } + // Returns true iff the test part failed. + bool failed() const { return fatally_failed() || nonfatally_failed(); } + private: Type type_; diff --git a/contrib/googletest/googletest/include/gtest/gtest.h b/contrib/googletest/googletest/include/gtest/gtest.h index 5df4b0a3a71..89214183d25 100644 --- a/contrib/googletest/googletest/include/gtest/gtest.h +++ b/contrib/googletest/googletest/include/gtest/gtest.h @@ -440,6 +440,9 @@ class GTEST_API_ Test { // Returns true iff the current test has a non-fatal failure. static bool HasNonfatalFailure(); + // Returns true iff the current test was skipped. + static bool IsSkipped(); + // Returns true iff the current test has a (either fatal or // non-fatal) failure. static bool HasFailure() { return HasFatalFailure() || HasNonfatalFailure(); } @@ -574,7 +577,10 @@ class GTEST_API_ TestResult { int test_property_count() const; // Returns true iff the test passed (i.e. no test part failed). - bool Passed() const { return !Failed(); } + bool Passed() const { return !Skipped() && !Failed(); } + + // Returns true iff the test was skipped. + bool Skipped() const; // Returns true iff the test failed. bool Failed() const; @@ -854,6 +860,9 @@ class GTEST_API_ TestCase { // Gets the number of successful tests in this test case. int successful_test_count() const; + // Gets the number of skipped tests in this test case. + int skipped_test_count() const; + // Gets the number of failed tests in this test case. int failed_test_count() const; @@ -936,6 +945,11 @@ class GTEST_API_ TestCase { return test_info->should_run() && test_info->result()->Passed(); } + // Returns true iff test skipped. + static bool TestSkipped(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Skipped(); + } + // Returns true iff test failed. static bool TestFailed(const TestInfo* test_info) { return test_info->should_run() && test_info->result()->Failed(); @@ -1258,6 +1272,9 @@ class GTEST_API_ UnitTest { // Gets the number of successful tests. int successful_test_count() const; + // Gets the number of skipped tests. + int skipped_test_count() const; + // Gets the number of failed tests. int failed_test_count() const; @@ -1835,6 +1852,11 @@ class TestWithParam : public Test, public WithParamInterface { // Macros for indicating success/failure in test code. +// Skips test in runtime. +// Skipping test aborts current function. +// Skipped tests are neither successful nor failed. +#define GTEST_SKIP() GTEST_SKIP_("Skipped") + // ADD_FAILURE unconditionally adds a failure to the current test. // SUCCEED generates a success - it doesn't automatically make the // current test successful, as a test is only successful when it has diff --git a/contrib/googletest/googletest/include/gtest/internal/gtest-internal.h b/contrib/googletest/googletest/include/gtest/internal/gtest-internal.h index b762f61fc53..380a11c4515 100644 --- a/contrib/googletest/googletest/include/gtest/internal/gtest-internal.h +++ b/contrib/googletest/googletest/include/gtest/internal/gtest-internal.h @@ -1208,7 +1208,10 @@ class NativeArray { #define GTEST_SUCCESS_(message) \ GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess) -// Suppress MSVC warning 4702 (unreachable code) for the code following +#define GTEST_SKIP_(message) \ + return GTEST_MESSAGE_(message, ::testing::TestPartResult::kSkip) + +// Suppress MSVC warning 4072 (unreachable code) for the code following // statement if it returns or throws (or doesn't return or throw in some // situations). #define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \ diff --git a/contrib/googletest/googletest/src/gtest-internal-inl.h b/contrib/googletest/googletest/src/gtest-internal-inl.h index 479004149b4..f79b1ad0420 100644 --- a/contrib/googletest/googletest/src/gtest-internal-inl.h +++ b/contrib/googletest/googletest/src/gtest-internal-inl.h @@ -544,6 +544,9 @@ class GTEST_API_ UnitTestImpl { // Gets the number of successful tests. int successful_test_count() const; + // Gets the number of skipped tests. + int skipped_test_count() const; + // Gets the number of failed tests. int failed_test_count() const; diff --git a/contrib/googletest/googletest/src/gtest-test-part.cc b/contrib/googletest/googletest/src/gtest-test-part.cc index c88860d9238..2855e3e95e6 100644 --- a/contrib/googletest/googletest/src/gtest-test-part.cc +++ b/contrib/googletest/googletest/src/gtest-test-part.cc @@ -47,12 +47,16 @@ std::string TestPartResult::ExtractSummary(const char* message) { // Prints a TestPartResult object. std::ostream& operator<<(std::ostream& os, const TestPartResult& result) { - return os - << result.file_name() << ":" << result.line_number() << ": " - << (result.type() == TestPartResult::kSuccess ? "Success" : - result.type() == TestPartResult::kFatalFailure ? "Fatal failure" : - "Non-fatal failure") << ":\n" - << result.message() << std::endl; + return os << result.file_name() << ":" << result.line_number() << ": " + << (result.type() == TestPartResult::kSuccess + ? "Success" + : result.type() == TestPartResult::kSkip + ? "Skipped" + : result.type() == TestPartResult::kFatalFailure + ? "Fatal failure" + : "Non-fatal failure") + << ":\n" + << result.message() << std::endl; } // Appends a TestPartResult to the array. diff --git a/contrib/googletest/googletest/src/gtest.cc b/contrib/googletest/googletest/src/gtest.cc index 96b07c68abb..e8864d53c20 100644 --- a/contrib/googletest/googletest/src/gtest.cc +++ b/contrib/googletest/googletest/src/gtest.cc @@ -796,6 +796,11 @@ int UnitTestImpl::successful_test_count() const { return SumOverTestCaseList(test_cases_, &TestCase::successful_test_count); } +// Gets the number of skipped tests. +int UnitTestImpl::skipped_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::skipped_test_count); +} + // Gets the number of failed tests. int UnitTestImpl::failed_test_count() const { return SumOverTestCaseList(test_cases_, &TestCase::failed_test_count); @@ -2207,6 +2212,16 @@ void TestResult::Clear() { elapsed_time_ = 0; } +// Returns true off the test part was skipped. +static bool TestPartSkipped(const TestPartResult& result) { + return result.skipped(); +} + +// Returns true iff the test was skipped. +bool TestResult::Skipped() const { + return !Failed() && CountIf(test_part_results_, TestPartSkipped) > 0; +} + // Returns true iff the test failed. bool TestResult::Failed() const { for (int i = 0; i < total_part_count(); ++i) { @@ -2511,8 +2526,9 @@ void Test::Run() { internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); impl->os_stack_trace_getter()->UponLeavingGTest(); internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()"); - // We will run the test only if SetUp() was successful. - if (!HasFatalFailure()) { + // We will run the test only if SetUp() was successful and didn't call + // GTEST_SKIP(). + if (!HasFatalFailure() && !IsSkipped()) { impl->os_stack_trace_getter()->UponLeavingGTest(); internal::HandleExceptionsInMethodIfSupported( this, &Test::TestBody, "the test body"); @@ -2537,6 +2553,11 @@ bool Test::HasNonfatalFailure() { HasNonfatalFailure(); } +// Returns true iff the current test was skipped. +bool Test::IsSkipped() { + return internal::GetUnitTestImpl()->current_test_result()->Skipped(); +} + // class TestInfo // Constructs a TestInfo object. It assumes ownership of the test factory @@ -2685,9 +2706,10 @@ void TestInfo::Run() { factory_, &internal::TestFactoryBase::CreateTest, "the test fixture's constructor"); - // Runs the test if the constructor didn't generate a fatal failure. + // Runs the test if the constructor didn't generate a fatal failure or invoke + // GTEST_SKIP(). // Note that the object will not be null - if (!Test::HasFatalFailure()) { + if (!Test::HasFatalFailure() && !Test::IsSkipped()) { // This doesn't throw as all user code that can throw are wrapped into // exception handling code. test->Run(); @@ -2715,6 +2737,11 @@ int TestCase::successful_test_count() const { return CountIf(test_info_list_, TestPassed); } +// Gets the number of successful tests in this test case. +int TestCase::skipped_test_count() const { + return CountIf(test_info_list_, TestSkipped); +} + // Gets the number of failed tests in this test case. int TestCase::failed_test_count() const { return CountIf(test_info_list_, TestFailed); @@ -2866,6 +2893,8 @@ static std::string FormatTestCaseCount(int test_case_count) { // between the two when viewing the test result. static const char * TestPartResultTypeToString(TestPartResult::Type type) { switch (type) { + case TestPartResult::kSkip: + return "Skipped"; case TestPartResult::kSuccess: return "Success"; @@ -3119,6 +3148,7 @@ class PrettyUnitTestResultPrinter : public TestEventListener { private: static void PrintFailedTests(const UnitTest& unit_test); + static void PrintSkippedTests(const UnitTest& unit_test); }; // Fired before each iteration of tests starts. @@ -3187,18 +3217,25 @@ void PrettyUnitTestResultPrinter::OnTestStart(const TestInfo& test_info) { // Called after an assertion failure. void PrettyUnitTestResultPrinter::OnTestPartResult( const TestPartResult& result) { - // If the test part succeeded, we don't need to do anything. - if (result.type() == TestPartResult::kSuccess) - return; - - // Print failure message from the assertion (e.g. expected this and got that). - PrintTestPartResult(result); - fflush(stdout); + switch (result.type()) { + // If the test part succeeded, or was skipped, + // we don't need to do anything. + case TestPartResult::kSkip: + case TestPartResult::kSuccess: + return; + default: + // Print failure message from the assertion + // (e.g. expected this and got that). + PrintTestPartResult(result); + fflush(stdout); + } } void PrettyUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) { if (test_info.result()->Passed()) { ColoredPrintf(COLOR_GREEN, "[ OK ] "); + } else if (test_info.result()->Skipped()) { + ColoredPrintf(COLOR_GREEN, "[ SKIPPED ] "); } else { ColoredPrintf(COLOR_RED, "[ FAILED ] "); } @@ -3248,7 +3285,7 @@ void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) { } for (int j = 0; j < test_case.total_test_count(); ++j) { const TestInfo& test_info = *test_case.GetTestInfo(j); - if (!test_info.should_run() || test_info.result()->Passed()) { + if (!test_info.should_run() || !test_info.result()->Failed()) { continue; } ColoredPrintf(COLOR_RED, "[ FAILED ] "); @@ -3259,6 +3296,30 @@ void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) { } } +// Internal helper for printing the list of skipped tests. +void PrettyUnitTestResultPrinter::PrintSkippedTests(const UnitTest& unit_test) { + const int skipped_test_count = unit_test.skipped_test_count(); + if (skipped_test_count == 0) { + return; + } + + for (int i = 0; i < unit_test.total_test_case_count(); ++i) { + const TestCase& test_case = *unit_test.GetTestCase(i); + if (!test_case.should_run() || (test_case.skipped_test_count() == 0)) { + continue; + } + for (int j = 0; j < test_case.total_test_count(); ++j) { + const TestInfo& test_info = *test_case.GetTestInfo(j); + if (!test_info.should_run() || !test_info.result()->Skipped()) { + continue; + } + ColoredPrintf(COLOR_GREEN, "[ SKIPPED ] "); + printf("%s.%s", test_case.name(), test_info.name()); + printf("\n"); + } + } +} + void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, int /*iteration*/) { ColoredPrintf(COLOR_GREEN, "[==========] "); @@ -3273,6 +3334,13 @@ void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, ColoredPrintf(COLOR_GREEN, "[ PASSED ] "); printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str()); + const int skipped_test_count = unit_test.skipped_test_count(); + if (skipped_test_count > 0) { + ColoredPrintf(COLOR_GREEN, "[ SKIPPED ] "); + printf("%s, listed below:\n", FormatTestCount(skipped_test_count).c_str()); + PrintSkippedTests(unit_test); + } + int num_failures = unit_test.failed_test_count(); if (!unit_test.Passed()) { const int failed_test_count = unit_test.failed_test_count(); @@ -4540,6 +4608,11 @@ int UnitTest::successful_test_count() const { return impl()->successful_test_count(); } +// Gets the number of skipped tests. +int UnitTest::skipped_test_count() const { + return impl()->skipped_test_count(); +} + // Gets the number of failed tests. int UnitTest::failed_test_count() const { return impl()->failed_test_count(); } @@ -4660,7 +4733,8 @@ void UnitTest::AddTestPartResult( impl_->GetTestPartResultReporterForCurrentThread()-> ReportTestPartResult(result); - if (result_type != TestPartResult::kSuccess) { + if (result_type != TestPartResult::kSuccess && + result_type != TestPartResult::kSkip) { // gtest_break_on_failure takes precedence over // gtest_throw_on_failure. This allows a user to set the latter // in the code (perhaps in order to use Google Test assertions diff --git a/contrib/googletest/googletest/test/googletest-test-part-test.cc b/contrib/googletest/googletest/test/googletest-test-part-test.cc index cd2d6f9e858..8c9459e18aa 100644 --- a/contrib/googletest/googletest/test/googletest-test-part-test.cc +++ b/contrib/googletest/googletest/test/googletest-test-part-test.cc @@ -46,9 +46,10 @@ class TestPartResultTest : public Test { TestPartResultTest() : r1_(TestPartResult::kSuccess, "foo/bar.cc", 10, "Success!"), r2_(TestPartResult::kNonFatalFailure, "foo/bar.cc", -1, "Failure!"), - r3_(TestPartResult::kFatalFailure, NULL, -1, "Failure!") {} + r3_(TestPartResult::kFatalFailure, nullptr, -1, "Failure!"), + r4_(TestPartResult::kSkip, "foo/bar.cc", 2, "Skipped!") {} - TestPartResult r1_, r2_, r3_; + TestPartResult r1_, r2_, r3_, r4_; }; @@ -79,6 +80,7 @@ TEST_F(TestPartResultTest, ResultAccessorsWork) { EXPECT_FALSE(success.failed()); EXPECT_FALSE(success.nonfatally_failed()); EXPECT_FALSE(success.fatally_failed()); + EXPECT_FALSE(success.skipped()); const TestPartResult nonfatal_failure(TestPartResult::kNonFatalFailure, "file.cc", @@ -88,6 +90,7 @@ TEST_F(TestPartResultTest, ResultAccessorsWork) { EXPECT_TRUE(nonfatal_failure.failed()); EXPECT_TRUE(nonfatal_failure.nonfatally_failed()); EXPECT_FALSE(nonfatal_failure.fatally_failed()); + EXPECT_FALSE(nonfatal_failure.skipped()); const TestPartResult fatal_failure(TestPartResult::kFatalFailure, "file.cc", @@ -97,6 +100,14 @@ TEST_F(TestPartResultTest, ResultAccessorsWork) { EXPECT_TRUE(fatal_failure.failed()); EXPECT_FALSE(fatal_failure.nonfatally_failed()); EXPECT_TRUE(fatal_failure.fatally_failed()); + EXPECT_FALSE(fatal_failure.skipped()); + + const TestPartResult skip(TestPartResult::kSkip, "file.cc", 42, "message"); + EXPECT_FALSE(skip.passed()); + EXPECT_FALSE(skip.failed()); + EXPECT_FALSE(skip.nonfatally_failed()); + EXPECT_FALSE(skip.fatally_failed()); + EXPECT_TRUE(skip.skipped()); } // Tests TestPartResult::type(). @@ -104,23 +115,27 @@ TEST_F(TestPartResultTest, type) { EXPECT_EQ(TestPartResult::kSuccess, r1_.type()); EXPECT_EQ(TestPartResult::kNonFatalFailure, r2_.type()); EXPECT_EQ(TestPartResult::kFatalFailure, r3_.type()); + EXPECT_EQ(TestPartResult::kSkip, r4_.type()); } // Tests TestPartResult::file_name(). TEST_F(TestPartResultTest, file_name) { EXPECT_STREQ("foo/bar.cc", r1_.file_name()); EXPECT_STREQ(NULL, r3_.file_name()); + EXPECT_STREQ("foo/bar.cc", r4_.file_name()); } // Tests TestPartResult::line_number(). TEST_F(TestPartResultTest, line_number) { EXPECT_EQ(10, r1_.line_number()); EXPECT_EQ(-1, r2_.line_number()); + EXPECT_EQ(2, r4_.line_number()); } // Tests TestPartResult::message(). TEST_F(TestPartResultTest, message) { EXPECT_STREQ("Success!", r1_.message()); + EXPECT_STREQ("Skipped!", r4_.message()); } // Tests TestPartResult::passed(). @@ -128,6 +143,7 @@ TEST_F(TestPartResultTest, Passed) { EXPECT_TRUE(r1_.passed()); EXPECT_FALSE(r2_.passed()); EXPECT_FALSE(r3_.passed()); + EXPECT_FALSE(r4_.passed()); } // Tests TestPartResult::failed(). @@ -135,6 +151,15 @@ TEST_F(TestPartResultTest, Failed) { EXPECT_FALSE(r1_.failed()); EXPECT_TRUE(r2_.failed()); EXPECT_TRUE(r3_.failed()); + EXPECT_FALSE(r4_.failed()); +} + +// Tests TestPartResult::failed(). +TEST_F(TestPartResultTest, Skipped) { + EXPECT_FALSE(r1_.skipped()); + EXPECT_FALSE(r2_.skipped()); + EXPECT_FALSE(r3_.skipped()); + EXPECT_TRUE(r4_.skipped()); } // Tests TestPartResult::fatally_failed(). @@ -142,6 +167,7 @@ TEST_F(TestPartResultTest, FatallyFailed) { EXPECT_FALSE(r1_.fatally_failed()); EXPECT_FALSE(r2_.fatally_failed()); EXPECT_TRUE(r3_.fatally_failed()); + EXPECT_FALSE(r4_.fatally_failed()); } // Tests TestPartResult::nonfatally_failed(). @@ -149,6 +175,7 @@ TEST_F(TestPartResultTest, NonfatallyFailed) { EXPECT_FALSE(r1_.nonfatally_failed()); EXPECT_TRUE(r2_.nonfatally_failed()); EXPECT_FALSE(r3_.nonfatally_failed()); + EXPECT_FALSE(r4_.nonfatally_failed()); } // Tests the TestPartResultArray class. diff --git a/contrib/googletest/googletest/test/gtest_all_test.cc b/contrib/googletest/googletest/test/gtest_all_test.cc index e61e36b1dfe..f948a104ff0 100644 --- a/contrib/googletest/googletest/test/gtest_all_test.cc +++ b/contrib/googletest/googletest/test/gtest_all_test.cc @@ -37,10 +37,11 @@ #include "test/googletest-message-test.cc" #include "test/googletest-options-test.cc" #include "test/googletest-port-test.cc" +#include "test/googletest-test-part-test.cc" +#include "test/gtest-typed-test2_test.cc" +#include "test/gtest-typed-test_test.cc" #include "test/gtest_pred_impl_unittest.cc" #include "test/gtest_prod_test.cc" -#include "test/googletest-test-part-test.cc" -#include "test/gtest-typed-test_test.cc" -#include "test/gtest-typed-test2_test.cc" +#include "test/gtest_skip_test.cc" #include "test/gtest_unittest.cc" #include "test/production.cc" diff --git a/contrib/googletest/googletest/test/gtest_skip_test.cc b/contrib/googletest/googletest/test/gtest_skip_test.cc new file mode 100644 index 00000000000..717e105e2e0 --- /dev/null +++ b/contrib/googletest/googletest/test/gtest_skip_test.cc @@ -0,0 +1,55 @@ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: arseny.aprelev@gmail.com (Arseny Aprelev) +// + +#include "gtest/gtest.h" + +using ::testing::Test; + +TEST(SkipTest, DoesSkip) { + GTEST_SKIP(); + EXPECT_EQ(0, 1); +} + +class Fixture : public Test { + protected: + void SetUp() override { + GTEST_SKIP() << "skipping all tests for this fixture"; + } +}; + +TEST_F(Fixture, SkipsOneTest) { + EXPECT_EQ(5, 7); +} + +TEST_F(Fixture, SkipsAnotherTest) { + EXPECT_EQ(99, 100); +}