]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/xattr.cc
MFC r361401:
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / xattr.cc
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_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 }