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