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