19821f1d3SAlan Somers /*-
24d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause
39821f1d3SAlan Somers *
49821f1d3SAlan Somers * Copyright (c) 2019 The FreeBSD Foundation
59821f1d3SAlan Somers *
69821f1d3SAlan Somers * This software was developed by BFF Storage Systems, LLC under sponsorship
79821f1d3SAlan Somers * from the FreeBSD Foundation.
89821f1d3SAlan Somers *
99821f1d3SAlan Somers * Redistribution and use in source and binary forms, with or without
109821f1d3SAlan Somers * modification, are permitted provided that the following conditions
119821f1d3SAlan Somers * are met:
129821f1d3SAlan Somers * 1. Redistributions of source code must retain the above copyright
139821f1d3SAlan Somers * notice, this list of conditions and the following disclaimer.
149821f1d3SAlan Somers * 2. Redistributions in binary form must reproduce the above copyright
159821f1d3SAlan Somers * notice, this list of conditions and the following disclaimer in the
169821f1d3SAlan Somers * documentation and/or other materials provided with the distribution.
179821f1d3SAlan Somers *
189821f1d3SAlan Somers * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
199821f1d3SAlan Somers * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
209821f1d3SAlan Somers * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
219821f1d3SAlan Somers * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
229821f1d3SAlan Somers * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
239821f1d3SAlan Somers * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
249821f1d3SAlan Somers * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
259821f1d3SAlan Somers * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
269821f1d3SAlan Somers * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
279821f1d3SAlan Somers * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
289821f1d3SAlan Somers * SUCH DAMAGE.
299821f1d3SAlan Somers */
309821f1d3SAlan Somers
31e7f73af1SAlan Somers extern "C" {
32e7f73af1SAlan Somers #include <sys/param.h>
3327537990SAlan Somers
3427537990SAlan Somers #include <semaphore.h>
35e7f73af1SAlan Somers }
36e7f73af1SAlan Somers
379821f1d3SAlan Somers #include "mockfs.hh"
389821f1d3SAlan Somers #include "utils.hh"
399821f1d3SAlan Somers
409821f1d3SAlan Somers using namespace testing;
419821f1d3SAlan Somers
42cad67791SAlan Somers class Getattr : public FuseTest {
43cad67791SAlan Somers public:
expect_lookup(const char * relpath,uint64_t ino,mode_t mode,uint64_t size,int times,uint64_t attr_valid,uint32_t attr_valid_nsec)44cad67791SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
45cad67791SAlan Somers uint64_t size, int times, uint64_t attr_valid, uint32_t attr_valid_nsec)
46cad67791SAlan Somers {
47a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
48cad67791SAlan Somers .Times(times)
4929edc611SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
50cad67791SAlan Somers SET_OUT_HEADER_LEN(out, entry);
5129edc611SAlan Somers out.body.entry.attr.mode = mode;
5229edc611SAlan Somers out.body.entry.nodeid = ino;
5329edc611SAlan Somers out.body.entry.attr.nlink = 1;
5429edc611SAlan Somers out.body.entry.attr_valid = attr_valid;
5529edc611SAlan Somers out.body.entry.attr_valid_nsec = attr_valid_nsec;
5629edc611SAlan Somers out.body.entry.attr.size = size;
5729edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX;
58cad67791SAlan Somers })));
59cad67791SAlan Somers }
60cad67791SAlan Somers };
61cad67791SAlan Somers
6216bd2d47SAlan Somers class Getattr_7_8: public FuseTest {
6316bd2d47SAlan Somers public:
SetUp()6416bd2d47SAlan Somers virtual void SetUp() {
6516bd2d47SAlan Somers m_kernel_minor_version = 8;
6616bd2d47SAlan Somers FuseTest::SetUp();
6716bd2d47SAlan Somers }
6816bd2d47SAlan Somers };
699821f1d3SAlan Somers
709821f1d3SAlan Somers /*
719821f1d3SAlan Somers * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
729821f1d3SAlan Somers * should use the cached attributes, rather than query the daemon
739821f1d3SAlan Somers */
TEST_F(Getattr,attr_cache)74cad67791SAlan Somers TEST_F(Getattr, attr_cache)
759821f1d3SAlan Somers {
769821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
779821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
789821f1d3SAlan Somers const uint64_t ino = 42;
799821f1d3SAlan Somers struct stat sb;
809821f1d3SAlan Somers
81a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
8229edc611SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
839821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry);
8429edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
8529edc611SAlan Somers out.body.entry.nodeid = ino;
8629edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX;
879821f1d3SAlan Somers })));
889821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
899821f1d3SAlan Somers ResultOf([](auto in) {
9029edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR &&
9129edc611SAlan Somers in.header.nodeid == ino);
929821f1d3SAlan Somers }, Eq(true)),
939821f1d3SAlan Somers _)
9429edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
959821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
9629edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX;
9729edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
9829edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0644;
999821f1d3SAlan Somers })));
1009821f1d3SAlan Somers EXPECT_EQ(0, stat(FULLPATH, &sb));
1019821f1d3SAlan Somers /* The second stat(2) should use cached attributes */
1029821f1d3SAlan Somers EXPECT_EQ(0, stat(FULLPATH, &sb));
1039821f1d3SAlan Somers }
1049821f1d3SAlan Somers
1059821f1d3SAlan Somers /*
1069821f1d3SAlan Somers * If getattr returns a finite but non-zero cache timeout, then we should
1079821f1d3SAlan Somers * discard the cached attributes and requery the daemon after the timeout
1089821f1d3SAlan Somers * period passes.
1099821f1d3SAlan Somers */
TEST_F(Getattr,attr_cache_timeout)1103f2c630cSAlan Somers TEST_F(Getattr, attr_cache_timeout)
1119821f1d3SAlan Somers {
1129821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
1139821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
1149821f1d3SAlan Somers const uint64_t ino = 42;
1159821f1d3SAlan Somers struct stat sb;
1169821f1d3SAlan Somers
1173f2c630cSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
1189821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
1199821f1d3SAlan Somers ResultOf([](auto in) {
12029edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR &&
12129edc611SAlan Somers in.header.nodeid == ino);
1229821f1d3SAlan Somers }, Eq(true)),
1239821f1d3SAlan Somers _)
1249821f1d3SAlan Somers ).Times(2)
12529edc611SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
1269821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
12729edc611SAlan Somers out.body.attr.attr_valid_nsec = NAP_NS / 2;
12829edc611SAlan Somers out.body.attr.attr_valid = 0;
12929edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
13029edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0644;
1319821f1d3SAlan Somers })));
1323f2c630cSAlan Somers
1339821f1d3SAlan Somers EXPECT_EQ(0, stat(FULLPATH, &sb));
134a87257acSAlan Somers nap();
1353f2c630cSAlan Somers /* Timeout has expired. stat(2) should requery the daemon */
1369821f1d3SAlan Somers EXPECT_EQ(0, stat(FULLPATH, &sb));
1379821f1d3SAlan Somers }
1389821f1d3SAlan Somers
139e7f73af1SAlan Somers /*
140e7f73af1SAlan Somers * If attr.blksize is zero, then the kernel should use a default value for
141e7f73af1SAlan Somers * st_blksize
142e7f73af1SAlan Somers */
TEST_F(Getattr,blksize_zero)143e7f73af1SAlan Somers TEST_F(Getattr, blksize_zero)
144e7f73af1SAlan Somers {
145e7f73af1SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
146e7f73af1SAlan Somers const char RELPATH[] = "some_file.txt";
147e7f73af1SAlan Somers const uint64_t ino = 42;
148e7f73af1SAlan Somers struct stat sb;
149e7f73af1SAlan Somers
150e7f73af1SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
151e7f73af1SAlan Somers EXPECT_CALL(*m_mock, process(
152e7f73af1SAlan Somers ResultOf([](auto in) {
15329edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR &&
15429edc611SAlan Somers in.header.nodeid == ino);
155e7f73af1SAlan Somers }, Eq(true)),
156e7f73af1SAlan Somers _)
15729edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
158e7f73af1SAlan Somers SET_OUT_HEADER_LEN(out, attr);
15929edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0644;
16029edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
16129edc611SAlan Somers out.body.attr.attr.blksize = 0;
162b0ecfb42SAlan Somers out.body.attr.attr.size = 1;
163e7f73af1SAlan Somers })));
164e7f73af1SAlan Somers
165e7f73af1SAlan Somers ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
166e7f73af1SAlan Somers EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize);
167e7f73af1SAlan Somers }
168e7f73af1SAlan Somers
TEST_F(Getattr,enoent)1699821f1d3SAlan Somers TEST_F(Getattr, enoent)
1709821f1d3SAlan Somers {
1719821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
1729821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
1739821f1d3SAlan Somers struct stat sb;
1749821f1d3SAlan Somers const uint64_t ino = 42;
17527537990SAlan Somers sem_t sem;
17627537990SAlan Somers
17727537990SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
1789821f1d3SAlan Somers
179cad67791SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
1809821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
1819821f1d3SAlan Somers ResultOf([](auto in) {
18229edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR &&
18329edc611SAlan Somers in.header.nodeid == ino);
1849821f1d3SAlan Somers }, Eq(true)),
1859821f1d3SAlan Somers _)
1869821f1d3SAlan Somers ).WillOnce(Invoke(ReturnErrno(ENOENT)));
18727537990SAlan Somers // Since FUSE_GETATTR returns ENOENT, the kernel will reclaim the vnode
18827537990SAlan Somers // and send a FUSE_FORGET
18927537990SAlan Somers expect_forget(ino, 1, &sem);
19027537990SAlan Somers
1919821f1d3SAlan Somers EXPECT_NE(0, stat(FULLPATH, &sb));
1929821f1d3SAlan Somers EXPECT_EQ(ENOENT, errno);
19327537990SAlan Somers
19427537990SAlan Somers sem_wait(&sem);
19527537990SAlan Somers sem_destroy(&sem);
1969821f1d3SAlan Somers }
1979821f1d3SAlan Somers
TEST_F(Getattr,ok)1989821f1d3SAlan Somers TEST_F(Getattr, ok)
1999821f1d3SAlan Somers {
2009821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
2019821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
2029821f1d3SAlan Somers const uint64_t ino = 42;
2039821f1d3SAlan Somers struct stat sb;
2049821f1d3SAlan Somers
205cad67791SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
2069821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
2079821f1d3SAlan Somers ResultOf([](auto in) {
20829edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR &&
209bf507497SAlan Somers in.body.getattr.getattr_flags == 0 &&
21029edc611SAlan Somers in.header.nodeid == ino);
2119821f1d3SAlan Somers }, Eq(true)),
2129821f1d3SAlan Somers _)
21329edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
2149821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
21529edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
21629edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0644;
21729edc611SAlan Somers out.body.attr.attr.size = 1;
21829edc611SAlan Somers out.body.attr.attr.blocks = 2;
21929edc611SAlan Somers out.body.attr.attr.atime = 3;
22029edc611SAlan Somers out.body.attr.attr.mtime = 4;
22129edc611SAlan Somers out.body.attr.attr.ctime = 5;
22229edc611SAlan Somers out.body.attr.attr.atimensec = 6;
22329edc611SAlan Somers out.body.attr.attr.mtimensec = 7;
22429edc611SAlan Somers out.body.attr.attr.ctimensec = 8;
22529edc611SAlan Somers out.body.attr.attr.nlink = 9;
22629edc611SAlan Somers out.body.attr.attr.uid = 10;
22729edc611SAlan Somers out.body.attr.attr.gid = 11;
22829edc611SAlan Somers out.body.attr.attr.rdev = 12;
22929edc611SAlan Somers out.body.attr.attr.blksize = 12345;
2309821f1d3SAlan Somers })));
2319821f1d3SAlan Somers
2329821f1d3SAlan Somers ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
2339821f1d3SAlan Somers EXPECT_EQ(1, sb.st_size);
2349821f1d3SAlan Somers EXPECT_EQ(2, sb.st_blocks);
2359821f1d3SAlan Somers EXPECT_EQ(3, sb.st_atim.tv_sec);
2369821f1d3SAlan Somers EXPECT_EQ(6, sb.st_atim.tv_nsec);
2379821f1d3SAlan Somers EXPECT_EQ(4, sb.st_mtim.tv_sec);
2389821f1d3SAlan Somers EXPECT_EQ(7, sb.st_mtim.tv_nsec);
2399821f1d3SAlan Somers EXPECT_EQ(5, sb.st_ctim.tv_sec);
2409821f1d3SAlan Somers EXPECT_EQ(8, sb.st_ctim.tv_nsec);
2419821f1d3SAlan Somers EXPECT_EQ(9ull, sb.st_nlink);
2429821f1d3SAlan Somers EXPECT_EQ(10ul, sb.st_uid);
2439821f1d3SAlan Somers EXPECT_EQ(11ul, sb.st_gid);
2449821f1d3SAlan Somers EXPECT_EQ(12ul, sb.st_rdev);
245e7f73af1SAlan Somers EXPECT_EQ((blksize_t)12345, sb.st_blksize);
2469821f1d3SAlan Somers EXPECT_EQ(ino, sb.st_ino);
2479821f1d3SAlan Somers EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
2489821f1d3SAlan Somers
249*55b80e2cSAlan Somers /*
250*55b80e2cSAlan Somers * st_birthtim and st_flags are not supported by the fuse protocol.
251*55b80e2cSAlan Somers * They're only supported as OS-specific extensions to OSX. For
252*55b80e2cSAlan Somers * birthtime, the convention for "not supported" is "negative one
253*55b80e2cSAlan Somers * second".
254*55b80e2cSAlan Somers */
255*55b80e2cSAlan Somers EXPECT_EQ(-1, sb.st_birthtim.tv_sec);
256*55b80e2cSAlan Somers EXPECT_EQ(0, sb.st_birthtim.tv_nsec);
257*55b80e2cSAlan Somers EXPECT_EQ(0u, sb.st_flags);
2589821f1d3SAlan Somers }
25916bd2d47SAlan Somers
26025927e06SAlan Somers /*
26125927e06SAlan Somers * FUSE_GETATTR returns a different file type, even though the entry cache
26225927e06SAlan Somers * hasn't expired. This is a server bug! It probably means that the server
26325927e06SAlan Somers * removed the file and recreated it with the same inode but a different vtyp.
26425927e06SAlan Somers * The best thing fusefs can do is return ENOENT to the caller. After all, the
26525927e06SAlan Somers * entry must not have existed recently.
26625927e06SAlan Somers */
TEST_F(Getattr,vtyp_conflict)26725927e06SAlan Somers TEST_F(Getattr, vtyp_conflict)
26825927e06SAlan Somers {
26925927e06SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
27025927e06SAlan Somers const char RELPATH[] = "some_file.txt";
27125927e06SAlan Somers const uint64_t ino = 42;
27225927e06SAlan Somers struct stat sb;
27325927e06SAlan Somers sem_t sem;
27425927e06SAlan Somers
27525927e06SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
27625927e06SAlan Somers
27725927e06SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
27825927e06SAlan Somers .WillOnce(Invoke(
27925927e06SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) {
28025927e06SAlan Somers SET_OUT_HEADER_LEN(out, entry);
28125927e06SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
28225927e06SAlan Somers out.body.entry.nodeid = ino;
28325927e06SAlan Somers out.body.entry.attr.nlink = 1;
28425927e06SAlan Somers out.body.entry.attr_valid = 0;
28525927e06SAlan Somers out.body.entry.entry_valid = UINT64_MAX;
28625927e06SAlan Somers })));
28725927e06SAlan Somers EXPECT_CALL(*m_mock, process(
28825927e06SAlan Somers ResultOf([](auto in) {
28925927e06SAlan Somers return (in.header.opcode == FUSE_GETATTR &&
29025927e06SAlan Somers in.body.getattr.getattr_flags == 0 &&
29125927e06SAlan Somers in.header.nodeid == ino);
29225927e06SAlan Somers }, Eq(true)),
29325927e06SAlan Somers _)
29425927e06SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
29525927e06SAlan Somers SET_OUT_HEADER_LEN(out, attr);
29625927e06SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
29725927e06SAlan Somers out.body.attr.attr.mode = S_IFDIR | 0755; // Changed!
29825927e06SAlan Somers out.body.attr.attr.nlink = 2;
29925927e06SAlan Somers })));
30025927e06SAlan Somers // We should reclaim stale vnodes
30125927e06SAlan Somers expect_forget(ino, 1, &sem);
30225927e06SAlan Somers
30325927e06SAlan Somers ASSERT_NE(0, stat(FULLPATH, &sb));
30425927e06SAlan Somers EXPECT_EQ(errno, ENOENT);
30525927e06SAlan Somers
30625927e06SAlan Somers sem_wait(&sem);
30725927e06SAlan Somers sem_destroy(&sem);
30825927e06SAlan Somers }
30925927e06SAlan Somers
TEST_F(Getattr_7_8,ok)31016bd2d47SAlan Somers TEST_F(Getattr_7_8, ok)
31116bd2d47SAlan Somers {
31216bd2d47SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
31316bd2d47SAlan Somers const char RELPATH[] = "some_file.txt";
31416bd2d47SAlan Somers const uint64_t ino = 42;
31516bd2d47SAlan Somers struct stat sb;
31616bd2d47SAlan Somers
317a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
31829edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
31916bd2d47SAlan Somers SET_OUT_HEADER_LEN(out, entry_7_8);
32029edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
32129edc611SAlan Somers out.body.entry.nodeid = ino;
32229edc611SAlan Somers out.body.entry.attr.nlink = 1;
32329edc611SAlan Somers out.body.entry.attr.size = 1;
32416bd2d47SAlan Somers })));
32516bd2d47SAlan Somers EXPECT_CALL(*m_mock, process(
32616bd2d47SAlan Somers ResultOf([](auto in) {
32729edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR &&
32829edc611SAlan Somers in.header.nodeid == ino);
32916bd2d47SAlan Somers }, Eq(true)),
33016bd2d47SAlan Somers _)
33129edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
33216bd2d47SAlan Somers SET_OUT_HEADER_LEN(out, attr_7_8);
33329edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
33429edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0644;
33529edc611SAlan Somers out.body.attr.attr.size = 1;
33629edc611SAlan Somers out.body.attr.attr.blocks = 2;
33729edc611SAlan Somers out.body.attr.attr.atime = 3;
33829edc611SAlan Somers out.body.attr.attr.mtime = 4;
33929edc611SAlan Somers out.body.attr.attr.ctime = 5;
34029edc611SAlan Somers out.body.attr.attr.atimensec = 6;
34129edc611SAlan Somers out.body.attr.attr.mtimensec = 7;
34229edc611SAlan Somers out.body.attr.attr.ctimensec = 8;
34329edc611SAlan Somers out.body.attr.attr.nlink = 9;
34429edc611SAlan Somers out.body.attr.attr.uid = 10;
34529edc611SAlan Somers out.body.attr.attr.gid = 11;
34629edc611SAlan Somers out.body.attr.attr.rdev = 12;
34716bd2d47SAlan Somers })));
34816bd2d47SAlan Somers
34916bd2d47SAlan Somers ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
35016bd2d47SAlan Somers EXPECT_EQ(1, sb.st_size);
35116bd2d47SAlan Somers EXPECT_EQ(2, sb.st_blocks);
35216bd2d47SAlan Somers EXPECT_EQ(3, sb.st_atim.tv_sec);
35316bd2d47SAlan Somers EXPECT_EQ(6, sb.st_atim.tv_nsec);
35416bd2d47SAlan Somers EXPECT_EQ(4, sb.st_mtim.tv_sec);
35516bd2d47SAlan Somers EXPECT_EQ(7, sb.st_mtim.tv_nsec);
35616bd2d47SAlan Somers EXPECT_EQ(5, sb.st_ctim.tv_sec);
35716bd2d47SAlan Somers EXPECT_EQ(8, sb.st_ctim.tv_nsec);
35816bd2d47SAlan Somers EXPECT_EQ(9ull, sb.st_nlink);
35916bd2d47SAlan Somers EXPECT_EQ(10ul, sb.st_uid);
36016bd2d47SAlan Somers EXPECT_EQ(11ul, sb.st_gid);
36116bd2d47SAlan Somers EXPECT_EQ(12ul, sb.st_rdev);
36216bd2d47SAlan Somers EXPECT_EQ(ino, sb.st_ino);
36316bd2d47SAlan Somers EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
36416bd2d47SAlan Somers
36516bd2d47SAlan Somers //st_birthtim and st_flags are not supported by protocol 7.8. They're
36616bd2d47SAlan Somers //only supported as OS-specific extensions to OSX.
36716bd2d47SAlan Somers }
368