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