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