1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2019 The FreeBSD Foundation 5 * 6 * This software was developed by BFF Storage Systems, LLC under sponsorship 7 * from the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 extern "C" { 32 #include <fcntl.h> 33 } 34 35 #include "mockfs.hh" 36 #include "utils.hh" 37 38 using namespace testing; 39 40 class Open: public FuseTest { 41 42 public: 43 44 /* Test an OK open of a file with the given flags */ 45 void test_ok(int os_flags, int fuse_flags) { 46 const char FULLPATH[] = "mountpoint/some_file.txt"; 47 const char RELPATH[] = "some_file.txt"; 48 uint64_t ino = 42; 49 int fd; 50 51 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 52 EXPECT_CALL(*m_mock, process( 53 ResultOf([=](auto in) { 54 return (in->header.opcode == FUSE_OPEN && 55 in->body.open.flags == (uint32_t)fuse_flags && 56 in->header.nodeid == ino); 57 }, Eq(true)), 58 _) 59 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 60 out->header.len = sizeof(out->header); 61 SET_OUT_HEADER_LEN(out, open); 62 }))); 63 64 /* Until the attr cache is working, we may send an additional GETATTR */ 65 EXPECT_CALL(*m_mock, process( 66 ResultOf([=](auto in) { 67 return (in->header.opcode == FUSE_GETATTR && 68 in->header.nodeid == ino); 69 }, Eq(true)), 70 _) 71 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 72 SET_OUT_HEADER_LEN(out, attr); 73 out->body.attr.attr.ino = ino; // Must match nodeid 74 out->body.attr.attr.mode = S_IFREG | 0644; 75 }))); 76 77 fd = open(FULLPATH, os_flags); 78 EXPECT_LE(0, fd) << strerror(errno); 79 /* Deliberately leak fd. close(2) will be tested in release.cc */ 80 } 81 }; 82 83 84 /* 85 * fusefs(5) does not support I/O on device nodes (neither does UFS). But it 86 * shouldn't crash 87 */ 88 TEST_F(Open, chr) 89 { 90 const char FULLPATH[] = "mountpoint/zero"; 91 const char RELPATH[] = "zero"; 92 uint64_t ino = 42; 93 94 EXPECT_LOOKUP(1, RELPATH) 95 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 96 SET_OUT_HEADER_LEN(out, entry); 97 out->body.entry.attr.mode = S_IFCHR | 0644; 98 out->body.entry.nodeid = ino; 99 out->body.entry.attr.nlink = 1; 100 out->body.entry.attr_valid = UINT64_MAX; 101 out->body.entry.attr.rdev = 44; /* /dev/zero's rdev */ 102 }))); 103 104 ASSERT_EQ(-1, open(FULLPATH, O_RDONLY)); 105 EXPECT_EQ(EOPNOTSUPP, errno); 106 } 107 108 /* 109 * The fuse daemon fails the request with enoent. This usually indicates a 110 * race condition: some other FUSE client removed the file in between when the 111 * kernel checked for it with lookup and tried to open it 112 */ 113 TEST_F(Open, enoent) 114 { 115 const char FULLPATH[] = "mountpoint/some_file.txt"; 116 const char RELPATH[] = "some_file.txt"; 117 uint64_t ino = 42; 118 119 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 120 EXPECT_CALL(*m_mock, process( 121 ResultOf([=](auto in) { 122 return (in->header.opcode == FUSE_OPEN && 123 in->header.nodeid == ino); 124 }, Eq(true)), 125 _) 126 ).WillOnce(Invoke(ReturnErrno(ENOENT))); 127 EXPECT_NE(0, open(FULLPATH, O_RDONLY)); 128 EXPECT_EQ(ENOENT, errno); 129 } 130 131 /* 132 * The daemon is responsible for checking file permissions (unless the 133 * default_permissions mount option was used) 134 */ 135 TEST_F(Open, eperm) 136 { 137 const char FULLPATH[] = "mountpoint/some_file.txt"; 138 const char RELPATH[] = "some_file.txt"; 139 uint64_t ino = 42; 140 141 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 142 EXPECT_CALL(*m_mock, process( 143 ResultOf([=](auto in) { 144 return (in->header.opcode == FUSE_OPEN && 145 in->header.nodeid == ino); 146 }, Eq(true)), 147 _) 148 ).WillOnce(Invoke(ReturnErrno(EPERM))); 149 EXPECT_NE(0, open(FULLPATH, O_RDONLY)); 150 EXPECT_EQ(EPERM, errno); 151 } 152 153 /* fusefs(5) does not yet support I/O on fifos. But it shouldn't crash. */ 154 TEST_F(Open, fifo) 155 { 156 const char FULLPATH[] = "mountpoint/zero"; 157 const char RELPATH[] = "zero"; 158 uint64_t ino = 42; 159 160 EXPECT_LOOKUP(1, RELPATH) 161 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 162 SET_OUT_HEADER_LEN(out, entry); 163 out->body.entry.attr.mode = S_IFIFO | 0644; 164 out->body.entry.nodeid = ino; 165 out->body.entry.attr.nlink = 1; 166 out->body.entry.attr_valid = UINT64_MAX; 167 }))); 168 169 ASSERT_EQ(-1, open(FULLPATH, O_RDONLY)); 170 EXPECT_EQ(EOPNOTSUPP, errno); 171 } 172 173 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 174 TEST_F(Open, DISABLED_o_append) 175 { 176 test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND); 177 } 178 179 /* The kernel is supposed to filter out this flag */ 180 TEST_F(Open, o_creat) 181 { 182 test_ok(O_WRONLY | O_CREAT, O_WRONLY); 183 } 184 185 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 186 TEST_F(Open, DISABLED_o_direct) 187 { 188 test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT); 189 } 190 191 /* The kernel is supposed to filter out this flag */ 192 TEST_F(Open, o_excl) 193 { 194 test_ok(O_WRONLY | O_EXCL, O_WRONLY); 195 } 196 197 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236329 */ 198 TEST_F(Open, DISABLED_o_exec) 199 { 200 test_ok(O_EXEC, O_EXEC); 201 } 202 203 /* The kernel is supposed to filter out this flag */ 204 TEST_F(Open, o_noctty) 205 { 206 test_ok(O_WRONLY | O_NOCTTY, O_WRONLY); 207 } 208 209 TEST_F(Open, o_rdonly) 210 { 211 test_ok(O_RDONLY, O_RDONLY); 212 } 213 214 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 215 TEST_F(Open, DISABLED_o_trunc) 216 { 217 test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC); 218 } 219 220 TEST_F(Open, o_wronly) 221 { 222 test_ok(O_WRONLY, O_WRONLY); 223 } 224 225 TEST_F(Open, o_rdwr) 226 { 227 test_ok(O_RDWR, O_RDWR); 228 } 229 230