1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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 /* Tests for all things relating to extended attributes and FUSE */ 32 33 extern "C" { 34 #include <sys/types.h> 35 #include <sys/extattr.h> 36 #include <sys/wait.h> 37 #include <semaphore.h> 38 #include <signal.h> 39 #include <string.h> 40 } 41 42 #include "mockfs.hh" 43 #include "utils.hh" 44 45 using namespace testing; 46 47 const char FULLPATH[] = "mountpoint/some_file.txt"; 48 const char RELPATH[] = "some_file.txt"; 49 static sem_t killer_semaphore; 50 51 void* killer(void* target) { 52 pid_t pid = *(pid_t*)target; 53 sem_wait(&killer_semaphore); 54 if (verbosity > 1) 55 printf("Killing! pid %d\n", pid); 56 kill(pid, SIGINT); 57 58 return(NULL); 59 } 60 61 class Xattr: public FuseTest { 62 public: 63 void expect_listxattr(uint64_t ino, uint32_t size, ProcessMockerT r, 64 Sequence *seq = NULL) 65 { 66 if (seq == NULL) { 67 EXPECT_CALL(*m_mock, process( 68 ResultOf([=](auto in) { 69 return (in.header.opcode == FUSE_LISTXATTR && 70 in.header.nodeid == ino && 71 in.body.listxattr.size == size); 72 }, Eq(true)), 73 _) 74 ).WillOnce(Invoke(r)) 75 .RetiresOnSaturation(); 76 } else { 77 EXPECT_CALL(*m_mock, process( 78 ResultOf([=](auto in) { 79 return (in.header.opcode == FUSE_LISTXATTR && 80 in.header.nodeid == ino && 81 in.body.listxattr.size == size); 82 }, Eq(true)), 83 _) 84 ).InSequence(*seq) 85 .WillOnce(Invoke(r)) 86 .RetiresOnSaturation(); 87 } 88 } 89 90 void expect_removexattr(uint64_t ino, const char *attr, int error) 91 { 92 EXPECT_CALL(*m_mock, process( 93 ResultOf([=](auto in) { 94 const char *a = (const char*)in.body.bytes; 95 return (in.header.opcode == FUSE_REMOVEXATTR && 96 in.header.nodeid == ino && 97 0 == strcmp(attr, a)); 98 }, Eq(true)), 99 _) 100 ).WillOnce(Invoke(ReturnErrno(error))); 101 } 102 103 /* 104 * Expect a FUSE_SETXATTR request in the format used by protocol 7.33 and 105 * later, with the FUSE_SETXATTR_EXT bit set. 106 */ 107 void expect_setxattr_ext(uint64_t ino, const char *attr, const char *value, 108 ProcessMockerT r) 109 { 110 EXPECT_CALL(*m_mock, process( 111 ResultOf([=](auto in) { 112 const char *a = (const char*)in.body.bytes + 113 sizeof(fuse_setxattr_in); 114 const char *v = a + strlen(a) + 1; 115 return (in.header.opcode == FUSE_SETXATTR && 116 in.header.nodeid == ino && 117 in.body.setxattr.size == (strlen(value) + 1) && 118 in.body.setxattr.setxattr_flags == 0 && 119 0 == strcmp(attr, a) && 120 0 == strcmp(value, v)); 121 }, Eq(true)), 122 _) 123 ).WillOnce(Invoke(r)); 124 } 125 126 /* 127 * Expect a FUSE_SETXATTR request in the format used by protocol 7.32 and 128 * earlier. 129 */ 130 void expect_setxattr_7_32(uint64_t ino, const char *attr, const char *value, 131 ProcessMockerT r) 132 { 133 EXPECT_CALL(*m_mock, process( 134 ResultOf([=](auto in) { 135 const char *a = (const char *)in.body.bytes + 136 FUSE_COMPAT_SETXATTR_IN_SIZE; 137 const char *v = a + strlen(a) + 1; 138 return (in.header.opcode == FUSE_SETXATTR && 139 in.header.nodeid == ino && 140 in.body.setxattr.size == (strlen(value) + 1) && 141 0 == strcmp(attr, a) && 142 0 == strcmp(value, v)); 143 }, Eq(true)), 144 _) 145 ).WillOnce(Invoke(r)); 146 } 147 }; 148 149 class Xattr_7_32: public Xattr { 150 public: 151 virtual void SetUp() 152 { 153 m_kernel_minor_version = 32; 154 Xattr::SetUp(); 155 } 156 }; 157 158 class Getxattr: public Xattr {}; 159 160 class Listxattr: public Xattr {}; 161 162 /* Listxattr tests that need to use a signal */ 163 class ListxattrSig: public Listxattr { 164 public: 165 pthread_t m_killer_th; 166 pid_t m_child; 167 168 void SetUp() { 169 /* 170 * Mount with -o nointr so the mount can't get interrupted while 171 * waiting for a response from the server 172 */ 173 m_nointr = true; 174 FuseTest::SetUp(); 175 176 ASSERT_EQ(0, sem_init(&killer_semaphore, 0, 0)) << strerror(errno); 177 } 178 179 void TearDown() { 180 if (m_killer_th != NULL) { 181 pthread_join(m_killer_th, NULL); 182 } 183 184 sem_destroy(&killer_semaphore); 185 186 FuseTest::TearDown(); 187 } 188 }; 189 190 class Removexattr: public Xattr {}; 191 class Setxattr: public Xattr {}; 192 class SetxattrExt: public Setxattr { 193 public: 194 virtual void SetUp() { 195 m_init_flags |= FUSE_SETXATTR_EXT; 196 Setxattr::SetUp(); 197 } 198 }; 199 class Setxattr_7_32:public Xattr_7_32 {}; 200 class RofsXattr: public Xattr { 201 public: 202 virtual void SetUp() { 203 m_ro = true; 204 Xattr::SetUp(); 205 } 206 }; 207 208 /* 209 * If the extended attribute does not exist on this file, the daemon should 210 * return ENOATTR (ENODATA on Linux, but it's up to the daemon to choose the 211 * correct errror code) 212 */ 213 TEST_F(Getxattr, enoattr) 214 { 215 char data[80]; 216 uint64_t ino = 42; 217 int ns = EXTATTR_NAMESPACE_USER; 218 ssize_t r; 219 220 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 221 expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR)); 222 223 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 224 ASSERT_EQ(-1, r); 225 ASSERT_EQ(ENOATTR, errno); 226 } 227 228 /* 229 * If the filesystem returns ENOSYS, then it will be treated as a permanent 230 * failure and all future VOP_GETEXTATTR calls will fail with EOPNOTSUPP 231 * without querying the filesystem daemon 232 */ 233 TEST_F(Getxattr, enosys) 234 { 235 char data[80]; 236 uint64_t ino = 42; 237 int ns = EXTATTR_NAMESPACE_USER; 238 ssize_t r; 239 240 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); 241 expect_getxattr(ino, "user.foo", ReturnErrno(ENOSYS)); 242 243 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 244 ASSERT_EQ(-1, r); 245 EXPECT_EQ(EOPNOTSUPP, errno); 246 247 /* Subsequent attempts should not query the filesystem at all */ 248 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 249 ASSERT_EQ(-1, r); 250 EXPECT_EQ(EOPNOTSUPP, errno); 251 } 252 253 /* 254 * On FreeBSD, if the user passes an insufficiently large buffer then the 255 * filesystem is supposed to copy as much of the attribute's value as will fit. 256 * 257 * On Linux, however, the filesystem is supposed to return ERANGE. 258 * 259 * libfuse specifies the Linux behavior. However, that's probably an error. 260 * It would probably be correct for the filesystem to use platform-dependent 261 * behavior. 262 * 263 * This test case covers a filesystem that uses the Linux behavior 264 * TODO: require FreeBSD Behavior. 265 */ 266 TEST_F(Getxattr, erange) 267 { 268 char data[10]; 269 uint64_t ino = 42; 270 int ns = EXTATTR_NAMESPACE_USER; 271 ssize_t r; 272 273 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 274 expect_getxattr(ino, "user.foo", ReturnErrno(ERANGE)); 275 276 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 277 ASSERT_EQ(-1, r); 278 ASSERT_EQ(ERANGE, errno); 279 } 280 281 /* 282 * If the user passes a 0-length buffer, then the daemon should just return the 283 * size of the attribute 284 */ 285 TEST_F(Getxattr, size_only) 286 { 287 uint64_t ino = 42; 288 int ns = EXTATTR_NAMESPACE_USER; 289 290 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 291 expect_getxattr(ino, "user.foo", 292 ReturnImmediate([](auto in __unused, auto& out) { 293 SET_OUT_HEADER_LEN(out, getxattr); 294 out.body.getxattr.size = 99; 295 }) 296 ); 297 298 ASSERT_EQ(99, extattr_get_file(FULLPATH, ns, "foo", NULL, 0)) 299 << strerror(errno);; 300 } 301 302 /* 303 * Successfully get an attribute from the system namespace 304 */ 305 TEST_F(Getxattr, system) 306 { 307 uint64_t ino = 42; 308 char data[80]; 309 const char value[] = "whatever"; 310 ssize_t value_len = strlen(value) + 1; 311 int ns = EXTATTR_NAMESPACE_SYSTEM; 312 ssize_t r; 313 314 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 315 expect_getxattr(ino, "system.foo", 316 ReturnImmediate([&](auto in __unused, auto& out) { 317 memcpy((void*)out.body.bytes, value, value_len); 318 out.header.len = sizeof(out.header) + value_len; 319 }) 320 ); 321 322 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 323 ASSERT_EQ(value_len, r) << strerror(errno); 324 EXPECT_STREQ(value, data); 325 } 326 327 /* 328 * Successfully get an attribute from the user namespace 329 */ 330 TEST_F(Getxattr, user) 331 { 332 uint64_t ino = 42; 333 char data[80]; 334 const char value[] = "whatever"; 335 ssize_t value_len = strlen(value) + 1; 336 int ns = EXTATTR_NAMESPACE_USER; 337 ssize_t r; 338 339 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 340 expect_getxattr(ino, "user.foo", 341 ReturnImmediate([&](auto in __unused, auto& out) { 342 memcpy((void*)out.body.bytes, value, value_len); 343 out.header.len = sizeof(out.header) + value_len; 344 }) 345 ); 346 347 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 348 ASSERT_EQ(value_len, r) << strerror(errno); 349 EXPECT_STREQ(value, data); 350 } 351 352 /* 353 * If the filesystem returns ENOSYS, then it will be treated as a permanent 354 * failure and all future VOP_LISTEXTATTR calls will fail with EOPNOTSUPP 355 * without querying the filesystem daemon 356 */ 357 TEST_F(Listxattr, enosys) 358 { 359 uint64_t ino = 42; 360 int ns = EXTATTR_NAMESPACE_USER; 361 362 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); 363 expect_listxattr(ino, 0, ReturnErrno(ENOSYS)); 364 365 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 366 EXPECT_EQ(EOPNOTSUPP, errno); 367 368 /* Subsequent attempts should not query the filesystem at all */ 369 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 370 EXPECT_EQ(EOPNOTSUPP, errno); 371 } 372 373 /* 374 * Listing extended attributes failed because they aren't configured on this 375 * filesystem 376 */ 377 TEST_F(Listxattr, enotsup) 378 { 379 uint64_t ino = 42; 380 int ns = EXTATTR_NAMESPACE_USER; 381 382 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 383 expect_listxattr(ino, 0, ReturnErrno(ENOTSUP)); 384 385 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 386 ASSERT_EQ(ENOTSUP, errno); 387 } 388 389 /* 390 * On FreeBSD, if the user passes an insufficiently large buffer to 391 * extattr_list_file(2) or VOP_LISTEXTATTR(9), then the file system is supposed 392 * to copy as much of the attribute's value as will fit. 393 * 394 * On Linux, however, the file system is supposed to return ERANGE if an 395 * insufficiently large buffer is passed to listxattr(2). 396 * 397 * fusefs(4) must guarantee the usual FreeBSD behavior. 398 */ 399 TEST_F(Listxattr, erange) 400 { 401 uint64_t ino = 42; 402 int ns = EXTATTR_NAMESPACE_USER; 403 char attrs[9] = "user.foo"; 404 char expected[3] = {3, 'f', 'o'}; 405 char buf[3]; 406 407 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 408 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) 409 { 410 out.body.listxattr.size = sizeof(attrs); 411 SET_OUT_HEADER_LEN(out, listxattr); 412 })); 413 expect_listxattr(ino, sizeof(attrs), 414 ReturnImmediate([&](auto in __unused, auto& out) { 415 memcpy((void*)out.body.bytes, attrs, sizeof(attrs)); 416 out.header.len = sizeof(fuse_out_header) + sizeof(attrs); 417 })); 418 419 420 ASSERT_EQ(static_cast<ssize_t>(sizeof(buf)), 421 extattr_list_file(FULLPATH, ns, buf, sizeof(buf))); 422 ASSERT_EQ(0, memcmp(expected, buf, sizeof(buf))); 423 } 424 425 /* 426 * A buggy or malicious file system always returns ERANGE, even if we pass an 427 * appropriately sized buffer. That will send the kernel into an infinite 428 * loop. This test will ensure that the loop is interruptible by killing the 429 * blocked process with SIGINT. 430 */ 431 TEST_F(ListxattrSig, erange_forever) 432 { 433 uint64_t ino = 42; 434 uint32_t lie_size = 10; 435 int status; 436 437 fork(false, &status, [&] { 438 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 439 .WillRepeatedly(Invoke( 440 ReturnImmediate([=](auto in __unused, auto& out) { 441 SET_OUT_HEADER_LEN(out, entry); 442 out.body.entry.attr.mode = S_IFREG | 0644; 443 out.body.entry.nodeid = ino; 444 out.body.entry.attr.nlink = 1; 445 out.body.entry.attr_valid = UINT64_MAX; 446 out.body.entry.entry_valid = UINT64_MAX; 447 }))); 448 EXPECT_CALL(*m_mock, process( 449 ResultOf([=](auto in) { 450 return (in.header.opcode == FUSE_LISTXATTR && 451 in.header.nodeid == ino && 452 in.body.listxattr.size == 0); 453 }, Eq(true)), 454 _) 455 ).WillRepeatedly(ReturnImmediate([=](auto i __unused, auto& out) 456 { 457 /* The file system requests 10 bytes, but it's a lie */ 458 out.body.listxattr.size = lie_size; 459 SET_OUT_HEADER_LEN(out, listxattr); 460 /* 461 * We can send the signal any time after fusefs enters 462 * VOP_LISTEXTATTR 463 */ 464 sem_post(&killer_semaphore); 465 })); 466 /* 467 * Even though the kernel faithfully respects our size request, 468 * we'll return ERANGE anyway. 469 */ 470 EXPECT_CALL(*m_mock, process( 471 ResultOf([=](auto in) { 472 return (in.header.opcode == FUSE_LISTXATTR && 473 in.header.nodeid == ino && 474 in.body.listxattr.size == lie_size); 475 }, Eq(true)), 476 _) 477 ).WillRepeatedly(ReturnErrno(ERANGE)); 478 479 ASSERT_EQ(0, pthread_create(&m_killer_th, NULL, killer, 480 &m_mock->m_child_pid)) 481 << strerror(errno); 482 483 }, [] { 484 /* Child process will block until it gets signaled */ 485 int ns = EXTATTR_NAMESPACE_USER; 486 char buf[3]; 487 extattr_list_file(FULLPATH, ns, buf, sizeof(buf)); 488 return 0; 489 } 490 ); 491 492 ASSERT_TRUE(WIFSIGNALED(status)); 493 } 494 495 /* 496 * Get the size of the list that it would take to list no extended attributes 497 */ 498 TEST_F(Listxattr, size_only_empty) 499 { 500 uint64_t ino = 42; 501 int ns = EXTATTR_NAMESPACE_USER; 502 503 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 504 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) { 505 out.body.listxattr.size = 0; 506 SET_OUT_HEADER_LEN(out, listxattr); 507 })); 508 509 ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) 510 << strerror(errno); 511 } 512 513 /* 514 * Get the size of the list that it would take to list some extended 515 * attributes. Due to the format differences between a FreeBSD and a 516 * Linux/FUSE extended attribute list, fuse(4) will actually allocate a buffer 517 * and get the whole list, then convert it, just to figure out its size. 518 */ 519 TEST_F(Listxattr, size_only_nonempty) 520 { 521 uint64_t ino = 42; 522 int ns = EXTATTR_NAMESPACE_USER; 523 char attrs[9] = "user.foo"; 524 525 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 526 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) 527 { 528 out.body.listxattr.size = sizeof(attrs); 529 SET_OUT_HEADER_LEN(out, listxattr); 530 })); 531 532 expect_listxattr(ino, sizeof(attrs), 533 ReturnImmediate([=](auto in __unused, auto& out) { 534 size_t l = sizeof(attrs); 535 strlcpy((char*)out.body.bytes, attrs, l); 536 out.header.len = sizeof(fuse_out_header) + l; 537 }) 538 ); 539 540 ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0)) 541 << strerror(errno); 542 } 543 544 /* 545 * The list of extended attributes grows in between the server's two calls to 546 * FUSE_LISTXATTR. 547 */ 548 TEST_F(Listxattr, size_only_race_bigger) 549 { 550 uint64_t ino = 42; 551 int ns = EXTATTR_NAMESPACE_USER; 552 char attrs0[9] = "user.foo"; 553 char attrs1[18] = "user.foo\0user.bar"; 554 Sequence seq; 555 556 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 557 .WillRepeatedly(Invoke( 558 ReturnImmediate([=](auto in __unused, auto& out) { 559 SET_OUT_HEADER_LEN(out, entry); 560 out.body.entry.attr.mode = S_IFREG | 0644; 561 out.body.entry.nodeid = ino; 562 out.body.entry.attr.nlink = 1; 563 out.body.entry.attr_valid = UINT64_MAX; 564 out.body.entry.entry_valid = UINT64_MAX; 565 }))); 566 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) 567 { 568 out.body.listxattr.size = sizeof(attrs0); 569 SET_OUT_HEADER_LEN(out, listxattr); 570 }), &seq); 571 572 /* 573 * After the first FUSE_LISTXATTR the list grew, so the second 574 * operation returns ERANGE. 575 */ 576 expect_listxattr(ino, sizeof(attrs0), ReturnErrno(ERANGE), &seq); 577 578 /* And now the kernel retries */ 579 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) 580 { 581 out.body.listxattr.size = sizeof(attrs1); 582 SET_OUT_HEADER_LEN(out, listxattr); 583 }), &seq); 584 expect_listxattr(ino, sizeof(attrs1), 585 ReturnImmediate([&](auto in __unused, auto& out) { 586 memcpy((char*)out.body.bytes, attrs1, sizeof(attrs1)); 587 out.header.len = sizeof(fuse_out_header) + 588 sizeof(attrs1); 589 }), &seq 590 ); 591 592 /* Userspace should never know about the retry */ 593 ASSERT_EQ(8, extattr_list_file(FULLPATH, ns, NULL, 0)) 594 << strerror(errno); 595 } 596 597 /* 598 * The list of extended attributes shrinks in between the server's two calls to 599 * FUSE_LISTXATTR 600 */ 601 TEST_F(Listxattr, size_only_race_smaller) 602 { 603 uint64_t ino = 42; 604 int ns = EXTATTR_NAMESPACE_USER; 605 char attrs0[18] = "user.foo\0user.bar"; 606 char attrs1[9] = "user.foo"; 607 608 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 609 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) 610 { 611 out.body.listxattr.size = sizeof(attrs0); 612 SET_OUT_HEADER_LEN(out, listxattr); 613 })); 614 expect_listxattr(ino, sizeof(attrs0), 615 ReturnImmediate([&](auto in __unused, auto& out) { 616 memcpy((char*)out.body.bytes, attrs1, sizeof(attrs1)); 617 out.header.len = sizeof(fuse_out_header) + 618 sizeof(attrs1); 619 }) 620 ); 621 622 ASSERT_EQ(4, extattr_list_file(FULLPATH, ns, NULL, 0)) 623 << strerror(errno); 624 } 625 626 TEST_F(Listxattr, size_only_really_big) 627 { 628 uint64_t ino = 42; 629 int ns = EXTATTR_NAMESPACE_USER; 630 631 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 632 expect_listxattr(ino, 0, ReturnImmediate([](auto i __unused, auto& out) { 633 out.body.listxattr.size = 16000; 634 SET_OUT_HEADER_LEN(out, listxattr); 635 })); 636 637 expect_listxattr(ino, 16000, 638 ReturnImmediate([](auto in __unused, auto& out) { 639 const char l[16] = "user.foobarbang"; 640 for (int i=0; i < 1000; i++) { 641 memcpy(&out.body.bytes[16 * i], l, 16); 642 } 643 out.header.len = sizeof(fuse_out_header) + 16000; 644 }) 645 ); 646 647 ASSERT_EQ(11000, extattr_list_file(FULLPATH, ns, NULL, 0)) 648 << strerror(errno); 649 } 650 651 /* 652 * List all of the user attributes of a file which has both user and system 653 * attributes 654 */ 655 TEST_F(Listxattr, user) 656 { 657 uint64_t ino = 42; 658 int ns = EXTATTR_NAMESPACE_USER; 659 char data[80]; 660 char expected[9] = {3, 'f', 'o', 'o', 4, 'b', 'a', 'n', 'g'}; 661 char attrs[28] = "user.foo\0system.x\0user.bang"; 662 663 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 664 expect_listxattr(ino, 0, 665 ReturnImmediate([&](auto in __unused, auto& out) { 666 out.body.listxattr.size = sizeof(attrs); 667 SET_OUT_HEADER_LEN(out, listxattr); 668 }) 669 ); 670 671 expect_listxattr(ino, sizeof(attrs), 672 ReturnImmediate([&](auto in __unused, auto& out) { 673 memcpy((void*)out.body.bytes, attrs, sizeof(attrs)); 674 out.header.len = sizeof(fuse_out_header) + sizeof(attrs); 675 })); 676 677 ASSERT_EQ(static_cast<ssize_t>(sizeof(expected)), 678 extattr_list_file(FULLPATH, ns, data, sizeof(data))) 679 << strerror(errno); 680 ASSERT_EQ(0, memcmp(expected, data, sizeof(expected))); 681 } 682 683 /* 684 * List all of the system attributes of a file which has both user and system 685 * attributes 686 */ 687 TEST_F(Listxattr, system) 688 { 689 uint64_t ino = 42; 690 int ns = EXTATTR_NAMESPACE_SYSTEM; 691 char data[80]; 692 char expected[2] = {1, 'x'}; 693 char attrs[28] = "user.foo\0system.x\0user.bang"; 694 695 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 696 expect_listxattr(ino, 0, 697 ReturnImmediate([&](auto in __unused, auto& out) { 698 out.body.listxattr.size = sizeof(attrs); 699 SET_OUT_HEADER_LEN(out, listxattr); 700 }) 701 ); 702 703 expect_listxattr(ino, sizeof(attrs), 704 ReturnImmediate([&](auto in __unused, auto& out) { 705 memcpy((void*)out.body.bytes, attrs, sizeof(attrs)); 706 out.header.len = sizeof(fuse_out_header) + sizeof(attrs); 707 })); 708 709 ASSERT_EQ(static_cast<ssize_t>(sizeof(expected)), 710 extattr_list_file(FULLPATH, ns, data, sizeof(data))) 711 << strerror(errno); 712 ASSERT_EQ(0, memcmp(expected, data, sizeof(expected))); 713 } 714 715 /* Fail to remove a nonexistent attribute */ 716 TEST_F(Removexattr, enoattr) 717 { 718 uint64_t ino = 42; 719 int ns = EXTATTR_NAMESPACE_USER; 720 721 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 722 expect_removexattr(ino, "user.foo", ENOATTR); 723 724 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 725 ASSERT_EQ(ENOATTR, errno); 726 } 727 728 /* 729 * If the filesystem returns ENOSYS, then it will be treated as a permanent 730 * failure and all future VOP_DELETEEXTATTR calls will fail with EOPNOTSUPP 731 * without querying the filesystem daemon 732 */ 733 TEST_F(Removexattr, enosys) 734 { 735 uint64_t ino = 42; 736 int ns = EXTATTR_NAMESPACE_USER; 737 738 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); 739 expect_removexattr(ino, "user.foo", ENOSYS); 740 741 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 742 EXPECT_EQ(EOPNOTSUPP, errno); 743 744 /* Subsequent attempts should not query the filesystem at all */ 745 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 746 EXPECT_EQ(EOPNOTSUPP, errno); 747 } 748 749 /* Successfully remove a user xattr */ 750 TEST_F(Removexattr, user) 751 { 752 uint64_t ino = 42; 753 int ns = EXTATTR_NAMESPACE_USER; 754 755 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 756 expect_removexattr(ino, "user.foo", 0); 757 758 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 759 << strerror(errno); 760 } 761 762 /* Successfully remove a system xattr */ 763 TEST_F(Removexattr, system) 764 { 765 uint64_t ino = 42; 766 int ns = EXTATTR_NAMESPACE_SYSTEM; 767 768 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 769 expect_removexattr(ino, "system.foo", 0); 770 771 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 772 << strerror(errno); 773 } 774 775 776 /* 777 * If the filesystem returns ENOSYS, then it will be treated as a permanent 778 * failure and all future VOP_SETEXTATTR calls will fail with EOPNOTSUPP 779 * without querying the filesystem daemon 780 */ 781 TEST_F(Setxattr, enosys) 782 { 783 uint64_t ino = 42; 784 const char value[] = "whatever"; 785 ssize_t value_len = strlen(value) + 1; 786 int ns = EXTATTR_NAMESPACE_USER; 787 ssize_t r; 788 789 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); 790 expect_setxattr_7_32(ino, "user.foo", value, ReturnErrno(ENOSYS)); 791 792 r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 793 value_len); 794 ASSERT_EQ(-1, r); 795 EXPECT_EQ(EOPNOTSUPP, errno); 796 797 /* Subsequent attempts should not query the filesystem at all */ 798 r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 799 value_len); 800 ASSERT_EQ(-1, r); 801 EXPECT_EQ(EOPNOTSUPP, errno); 802 } 803 804 /* 805 * SETXATTR will return ENOTSUP if the namespace is invalid or the filesystem 806 * as currently configured doesn't support extended attributes. 807 */ 808 TEST_F(Setxattr, enotsup) 809 { 810 uint64_t ino = 42; 811 const char value[] = "whatever"; 812 ssize_t value_len = strlen(value) + 1; 813 int ns = EXTATTR_NAMESPACE_USER; 814 ssize_t r; 815 816 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 817 expect_setxattr_7_32(ino, "user.foo", value, ReturnErrno(ENOTSUP)); 818 819 r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 820 value_len); 821 ASSERT_EQ(-1, r); 822 EXPECT_EQ(ENOTSUP, errno); 823 } 824 825 /* 826 * Successfully set a user attribute. 827 */ 828 TEST_F(Setxattr, user) 829 { 830 uint64_t ino = 42; 831 const char value[] = "whatever"; 832 ssize_t value_len = strlen(value) + 1; 833 int ns = EXTATTR_NAMESPACE_USER; 834 ssize_t r; 835 836 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 837 expect_setxattr_7_32(ino, "user.foo", value, ReturnErrno(0)); 838 839 r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 840 value_len); 841 ASSERT_EQ(value_len, r) << strerror(errno); 842 } 843 844 /* 845 * Successfully set a system attribute. 846 */ 847 TEST_F(Setxattr, system) 848 { 849 uint64_t ino = 42; 850 const char value[] = "whatever"; 851 ssize_t value_len = strlen(value) + 1; 852 int ns = EXTATTR_NAMESPACE_SYSTEM; 853 ssize_t r; 854 855 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 856 expect_setxattr_7_32(ino, "system.foo", value, ReturnErrno(0)); 857 858 r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 859 value_len); 860 ASSERT_EQ(value_len, r) << strerror(errno); 861 } 862 863 864 /* 865 * For servers using protocol 7.32 and older, the kernel should use the older 866 * FUSE_SETXATTR format. 867 */ 868 TEST_F(Setxattr_7_32, ok) 869 { 870 uint64_t ino = 42; 871 const char value[] = "whatever"; 872 ssize_t value_len = strlen(value) + 1; 873 int ns = EXTATTR_NAMESPACE_USER; 874 ssize_t r; 875 876 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 877 expect_setxattr_7_32(ino, "user.foo", value, ReturnErrno(0)); 878 879 r = extattr_set_file(FULLPATH, ns, "foo", (const void *)value, 880 value_len); 881 ASSERT_EQ(value_len, r) << strerror(errno); 882 } 883 884 /* 885 * Successfully set a user attribute using the extended format 886 */ 887 TEST_F(SetxattrExt, user) 888 { 889 uint64_t ino = 42; 890 const char value[] = "whatever"; 891 ssize_t value_len = strlen(value) + 1; 892 int ns = EXTATTR_NAMESPACE_USER; 893 ssize_t r; 894 895 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 896 expect_setxattr_ext(ino, "user.foo", value, ReturnErrno(0)); 897 898 r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 899 value_len); 900 ASSERT_EQ(value_len, r) << strerror(errno); 901 } 902 903 TEST_F(RofsXattr, deleteextattr_erofs) 904 { 905 uint64_t ino = 42; 906 int ns = EXTATTR_NAMESPACE_USER; 907 908 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 909 910 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 911 ASSERT_EQ(EROFS, errno); 912 } 913 914 TEST_F(RofsXattr, setextattr_erofs) 915 { 916 uint64_t ino = 42; 917 const char value[] = "whatever"; 918 ssize_t value_len = strlen(value) + 1; 919 int ns = EXTATTR_NAMESPACE_USER; 920 ssize_t r; 921 922 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 923 924 r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 925 value_len); 926 ASSERT_EQ(-1, r); 927 EXPECT_EQ(EROFS, errno); 928 } 929