/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2019 The FreeBSD Foundation4*5* This software was developed by BFF Storage Systems, LLC under sponsorship6* from the FreeBSD Foundation.7*8* Redistribution and use in source and binary forms, with or without9* modification, are permitted provided that the following conditions10* are met:11* 1. Redistributions of source code must retain the above copyright12* notice, this list of conditions and the following disclaimer.13* 2. Redistributions in binary form must reproduce the above copyright14* notice, this list of conditions and the following disclaimer in the15* documentation and/or other materials provided with the distribution.16*17* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND18* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE19* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE20* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE21* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL22* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS23* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)24* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT25* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY26* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF27* SUCH DAMAGE.28*/2930extern "C" {31#include <sys/param.h>32#include <sys/mount.h>3334#include <fcntl.h>35#include <pthread.h>36#include <semaphore.h>37}3839#include "mockfs.hh"40#include "utils.hh"4142using namespace testing;4344/* Tests for orderly unmounts */45class Destroy: public FuseTest {};4647/* Tests for unexpected deaths of the server */48class Death: public FuseTest{};4950/* Tests for the auto_unmount mount option*/51class AutoUnmount: public FuseTest {52virtual void SetUp() {53m_auto_unmount = true;54FuseTest::SetUp();55}5657protected:58/* Unmounting fusefs might be asynchronous with close, so use a retry loop */59void assert_unmounted() {60struct statfs statbuf;6162for (int retry = 100; retry > 0; retry--) {63ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);64if (strcmp("fusefs", statbuf.f_fstypename) != 0 &&65strcmp("/dev/fuse", statbuf.f_mntfromname) != 0)66return;67nap();68}69FAIL() << "fusefs is still mounted";70}7172};7374static void* open_th(void* arg) {75int fd;76const char *path = (const char*)arg;7778fd = open(path, O_RDONLY);79EXPECT_EQ(-1, fd);80EXPECT_EQ(ENOTCONN, errno);81return 0;82}8384/*85* With the auto_unmount mount option, the kernel will automatically unmount86* the file system when the server dies.87*/88TEST_F(AutoUnmount, auto_unmount)89{90/* Kill the daemon */91m_mock->kill_daemon();9293/* Use statfs to check that the file system is no longer mounted */94assert_unmounted();95}9697/*98* When -o auto_unmount is used, the kernel should _not_ unmount the file99* system when any /dev/fuse file descriptor is closed, but only for the last100* one.101*/102TEST_F(AutoUnmount, dup)103{104struct statfs statbuf;105int fuse2;106107EXPECT_CALL(*m_mock, process(108ResultOf([](auto in) {109return (in.header.opcode == FUSE_STATFS);110}, Eq(true)),111_)112).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {113SET_OUT_HEADER_LEN(out, statfs);114})));115116fuse2 = dup_dev_fuse();117118/*119* Close one of the /dev/fuse file descriptors. Close the duplicate120* first so the daemon thread doesn't freak out when it gets a bunch of121* EBADF errors.122*/123close(fuse2);124125/* Use statfs to check that the file system is still mounted */126ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);127EXPECT_EQ(0, strcmp("fusefs", statbuf.f_fstypename));128EXPECT_EQ(0, strcmp("/dev/fuse", statbuf.f_mntfromname));129130/*131* Close the original file descriptor too. Now the file system should be132* unmounted at last.133*/134m_mock->kill_daemon();135assert_unmounted();136}137138/*139* The server dies with unsent operations still on the message queue.140* Check for any memory leaks like this:141* 1) kldunload fusefs, if necessary142* 2) kldload fusefs143* 3) ./destroy --gtest_filter=Death.unsent_operations144* 4) kldunload fusefs145* 5) check /var/log/messages for anything like this:146Freed UMA keg (fuse_ticket) was not empty (31 items). Lost 2 pages of memory.147Warning: memory type fuse_msgbuf leaked memory on destroy (68 allocations, 428800 bytes leaked).148*/149TEST_F(Death, unsent_operations)150{151const char FULLPATH0[] = "mountpoint/some_file.txt";152const char FULLPATH1[] = "mountpoint/other_file.txt";153const char RELPATH0[] = "some_file.txt";154const char RELPATH1[] = "other_file.txt";155pthread_t th0, th1;156ino_t ino0 = 42, ino1 = 43;157sem_t sem;158mode_t mode = S_IFREG | 0644;159160sem_init(&sem, 0, 0);161162EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0)163.WillRepeatedly(Invoke(164ReturnImmediate([=](auto in __unused, auto& out) {165SET_OUT_HEADER_LEN(out, entry);166out.body.entry.attr.mode = mode;167out.body.entry.nodeid = ino0;168out.body.entry.attr.nlink = 1;169})));170EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH1)171.WillRepeatedly(Invoke(172ReturnImmediate([=](auto in __unused, auto& out) {173SET_OUT_HEADER_LEN(out, entry);174out.body.entry.attr.mode = mode;175out.body.entry.nodeid = ino1;176out.body.entry.attr.nlink = 1;177})));178179EXPECT_CALL(*m_mock, process(180ResultOf([&](auto in) {181return (in.header.opcode == FUSE_OPEN);182}, Eq(true)),183_)184).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {185sem_post(&sem);186pause();187}));188189/*190* One thread's operation will be sent to the daemon and block, and the191* other's will be stuck in the message queue.192*/193ASSERT_EQ(0, pthread_create(&th0, NULL, open_th,194__DECONST(void*, FULLPATH0))) << strerror(errno);195ASSERT_EQ(0, pthread_create(&th1, NULL, open_th,196__DECONST(void*, FULLPATH1))) << strerror(errno);197198/* Wait for the first thread to block */199sem_wait(&sem);200/* Give the second thread time to block */201nap();202203m_mock->kill_daemon();204205pthread_join(th0, NULL);206pthread_join(th1, NULL);207208sem_destroy(&sem);209}210211/*212* On unmount the kernel should send a FUSE_DESTROY operation. It should also213* send FUSE_FORGET operations for all inodes with lookup_count > 0.214*/215TEST_F(Destroy, ok)216{217const char FULLPATH[] = "mountpoint/some_file.txt";218const char RELPATH[] = "some_file.txt";219uint64_t ino = 42;220221expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);222expect_forget(ino, 2);223expect_destroy(0);224225/*226* access(2) the file to force a lookup. Access it twice to double its227* lookup count.228*/229ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);230ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);231232/*233* Unmount, triggering a FUSE_DESTROY and also causing a VOP_RECLAIM234* for every vnode on this mp, triggering FUSE_FORGET for each of them.235*/236m_mock->unmount();237}238239240