Index: sys/fs/fuse/fuse_internal.c =================================================================== --- sys/fs/fuse/fuse_internal.c +++ sys/fs/fuse/fuse_internal.c @@ -242,6 +242,14 @@ vp_cache_at->va_mtime.tv_nsec = attr->mtimensec; vp_cache_at->va_ctime.tv_sec = attr->ctime; vp_cache_at->va_ctime.tv_nsec = attr->ctimensec; + if (fuse_libabi_geq(data, 7, 9)) { + vp_cache_at->va_birthtime.tv_sec = attr->crtime; + vp_cache_at->va_birthtime.tv_nsec = attr->crtimensec; + } else { + /* {-1, 0} means "no value" */ + vp_cache_at->va_birthtime.tv_sec = -1; + vp_cache_at->va_birthtime.tv_nsec = 0; + } if (fuse_libabi_geq(data, 7, 9) && attr->blksize > 0) vp_cache_at->va_blocksize = attr->blksize; else @@ -855,7 +863,10 @@ data = fuse_get_mpdata(mp); dataflags = data->dataflags; - fdisp_init(&fdi, sizeof(*fsai)); + if (fuse_libabi_geq(data, 7, 9)) + fdisp_init(&fdi, sizeof(*fsai)); + else + fdisp_init(&fdi, FUSE_COMPAT_SETATTR_IN_SIZE); fdisp_make_vp(&fdi, FUSE_SETATTR, vp, td, cred); if (!cred) { fdi.finh->uid = 0; @@ -901,6 +912,12 @@ fsai->valid |= FATTR_MTIME; if (vap->va_vaflags & VA_UTIMES_NULL) fsai->valid |= FATTR_MTIME_NOW; + } + if (fuse_libabi_geq(data, 7, 9) && vap->va_birthtime.tv_sec != VNOVAL) { + fsai->crtime = vap->va_birthtime.tv_sec; + fsai->crtimensec = vap->va_birthtime.tv_nsec; + fsai->valid |= FATTR_CRTIME; + /* birthtime can never be set to UTIME_NOW */ } if (vap->va_mode != (mode_t)VNOVAL) { fsai->mode = vap->va_mode & ALLPERMS; Index: sys/fs/fuse/fuse_kernel.h =================================================================== --- sys/fs/fuse/fuse_kernel.h +++ sys/fs/fuse/fuse_kernel.h @@ -45,6 +45,9 @@ * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in * - add blksize field to fuse_attr * - add file flags field to fuse_read_in and fuse_write_in +#if defined(__FreeBSD__) + * - add birthtime field fo fuse_attr (FreeBSD only) +#endif */ #ifndef _FUSE_FUSE_KERNEL_H @@ -85,14 +88,27 @@ __u64 atime; __u64 mtime; __u64 ctime; +#ifdef __APPLE__ + __u64 crtime; +#endif __u32 atimensec; __u32 mtimensec; __u32 ctimensec; +#ifdef __APPLE__ + __u32 crtimensec; +#endif __u32 mode; __u32 nlink; __u32 uid; __u32 gid; __u32 rdev; +#if defined(__APPLE__) || defined(__FreeBSD__) + __u32 flags; /* file flags; see chflags(2) */ +#endif +#ifdef __FreeBSD__ + __u64 crtime; + __u32 crtimensec; +#endif __u32 blksize; __u32 padding; }; @@ -130,6 +146,16 @@ #define FATTR_ATIME_NOW (1 << 7) #define FATTR_MTIME_NOW (1 << 8) #define FATTR_LOCKOWNER (1 << 9) +#if defined(__APPLE__) || defined(__FreeBSD__) +#define FATTR_CRTIME (1 << 28) +#endif /* defined(__APPLE__) || defined(__FreeBSD__) */ +#ifdef __APPLE__ +#define FATTR_CHGTIME (1 << 29) +#define FATTR_BKUPTIME (1 << 30) +#endif /* __APPLE__ */ +#if defined(__APPLE__) || defined(__FreeBSD__) +#define FATTR_FLAGS (1 << 31) +#endif /* defined(__APPLE__) || defined(__FreeBSD__) */ /** * Flags returned by the OPEN request @@ -223,7 +249,11 @@ /* The read buffer is required to be at least 8k, but may be much larger */ #define FUSE_MIN_READ_BUFFER 8192 -#define FUSE_COMPAT_ENTRY_OUT_SIZE 120 +#ifdef __APPLE__ +# define FUSE_COMPAT_ENTRY_OUT_SIZE 136 +#else +# define FUSE_COMPAT_ENTRY_OUT_SIZE 120 +#endif struct fuse_entry_out { __u64 nodeid; /* Inode ID */ @@ -246,7 +276,11 @@ __u64 fh; }; -#define FUSE_COMPAT_ATTR_OUT_SIZE 96 +#ifdef __APPLE__ +# define FUSE_COMPAT_ATTR_OUT_SIZE 112 +#else +# define FUSE_COMPAT_ATTR_OUT_SIZE 96 +#endif struct fuse_attr_out { __u64 attr_valid; /* Cache timeout for the attributes */ @@ -273,6 +307,9 @@ __u64 oldnodeid; }; +/* size of fuse_setattr_in before the addition of crtime and flags in 7.9 */ +#define FUSE_COMPAT_SETATTR_IN_SIZE 88 + struct fuse_setattr_in { __u32 valid; __u32 padding; @@ -290,6 +327,21 @@ __u32 uid; __u32 gid; __u32 unused5; +#ifdef __APPLE__ + __u64 bkuptime; + __u64 chgtime; +#endif /* __APPLE__ */ +#if defined(__APPLE__) || defined(__FreeBSD__) + __u64 crtime; +#endif /* defined(__APPLE__) || defined(__FreeBSD__) */ +#ifdef __APPLE__ + __u32 bkuptimensec; + __u32 chgtimensec; +#endif /* __APPLE__ */ +#if defined(__APPLE__) || defined(__FreeBSD__) + __u32 crtimensec; + __u32 flags; /* file flags; see chflags(2) */ +#endif /* defined(__APPLE__) || defined(__FreeBSD__) */ }; struct fuse_open_in { Index: sys/fs/fuse/fuse_vnops.c =================================================================== --- sys/fs/fuse/fuse_vnops.c +++ sys/fs/fuse/fuse_vnops.c @@ -1700,7 +1700,9 @@ } /* Don't set accmode. Permission to trunc is checked upstack */ } - if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) { + if (vap->va_atime.tv_sec != VNOVAL || + vap->va_mtime.tv_sec != VNOVAL || + vap->va_birthtime.tv_sec != VNOVAL) { if (vap->va_vaflags & VA_UTIMES_NULL) accmode |= VWRITE; else Index: tests/sys/fs/fusefs/getattr.cc =================================================================== --- tests/sys/fs/fusefs/getattr.cc +++ tests/sys/fs/fusefs/getattr.cc @@ -212,6 +212,8 @@ out.body.attr.attr.uid = 10; out.body.attr.attr.gid = 11; out.body.attr.attr.rdev = 12; + out.body.attr.attr.crtime = 13; + out.body.attr.attr.crtimensec = 14; out.body.attr.attr.blksize = 12345; }))); @@ -228,16 +230,11 @@ EXPECT_EQ(10ul, sb.st_uid); EXPECT_EQ(11ul, sb.st_gid); EXPECT_EQ(12ul, sb.st_rdev); + EXPECT_EQ(13, sb.st_birthtim.tv_sec); + EXPECT_EQ(14, sb.st_birthtim.tv_nsec); EXPECT_EQ((blksize_t)12345, sb.st_blksize); EXPECT_EQ(ino, sb.st_ino); EXPECT_EQ(S_IFREG | 0644, sb.st_mode); - - //st_birthtim and st_flags are not supported by protocol 7.8. They're - //only supported as OS-specific extensions to OSX. - //EXPECT_EQ(, sb.st_birthtim); - //EXPECT_EQ(, sb.st_flags); - - //FUSE can't set st_blksize until protocol 7.9 } TEST_F(Getattr_7_8, ok) @@ -292,9 +289,9 @@ EXPECT_EQ(10ul, sb.st_uid); EXPECT_EQ(11ul, sb.st_gid); EXPECT_EQ(12ul, sb.st_rdev); + /* fusefs should leave birthtime uninitialized */ + EXPECT_EQ(-1, sb.st_birthtim.tv_sec); + EXPECT_EQ(0, sb.st_birthtim.tv_nsec); EXPECT_EQ(ino, sb.st_ino); EXPECT_EQ(S_IFREG | 0644, sb.st_mode); - - //st_birthtim and st_flags are not supported by protocol 7.8. They're - //only supported as OS-specific extensions to OSX. } Index: tests/sys/fs/fusefs/mockfs.cc =================================================================== --- tests/sys/fs/fusefs/mockfs.cc +++ tests/sys/fs/fusefs/mockfs.cc @@ -253,6 +253,10 @@ printf(" mtime=%" PRIu64 ".%u", in.body.setattr.mtime, in.body.setattr.mtimensec); + if (in.body.setattr.valid & FATTR_CRTIME) + printf(" crtime=%" PRIu64 ".%u", + in.body.setattr.crtime, + in.body.setattr.crtimensec); if (in.body.setattr.valid & FATTR_FH) printf(" fh=%" PRIu64 "", in.body.setattr.fh); break; Index: tests/sys/fs/fusefs/setattr.cc =================================================================== --- tests/sys/fs/fusefs/setattr.cc +++ tests/sys/fs/fusefs/setattr.cc @@ -565,13 +565,14 @@ const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; - const timespec oldtimes[2] = { + const timespec newtimes[2] = { {.tv_sec = 1, .tv_nsec = 2}, {.tv_sec = 3, .tv_nsec = 4}, }; - const timespec newtimes[2] = { + const timespec oldtimes[3] = { {.tv_sec = 5, .tv_nsec = 6}, {.tv_sec = 7, .tv_nsec = 8}, + {.tv_sec = 9, .tv_nsec = 10}, }; EXPECT_LOOKUP(1, RELPATH) @@ -584,12 +585,15 @@ out.body.entry.attr.atimensec = oldtimes[0].tv_nsec; out.body.entry.attr.mtime = oldtimes[1].tv_sec; out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec; + out.body.entry.attr.crtime = oldtimes[2].tv_sec; + out.body.entry.attr.crtimensec = oldtimes[2].tv_nsec; }))); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { /* In protocol 7.23, ctime will be changed too */ - uint32_t valid = FATTR_ATIME | FATTR_MTIME; + uint32_t valid = FATTR_ATIME | FATTR_MTIME | + FATTR_CRTIME; return (in.header.opcode == FUSE_SETATTR && in.header.nodeid == ino && in.body.setattr.valid == valid && @@ -598,6 +602,14 @@ newtimes[0].tv_nsec && in.body.setattr.mtime == newtimes[1].tv_sec && in.body.setattr.mtimensec == + newtimes[1].tv_nsec && + /* + * birthtime can't be set directly. Instead, + * it's set to the older of the old birthtime + * and the new mtime + */ + in.body.setattr.crtime == newtimes[1].tv_sec && + in.body.setattr.crtimensec == newtimes[1].tv_nsec); }, Eq(true)), _) @@ -609,19 +621,28 @@ out.body.attr.attr.atimensec = newtimes[0].tv_nsec; out.body.attr.attr.mtime = newtimes[1].tv_sec; out.body.attr.attr.mtimensec = newtimes[1].tv_nsec; + out.body.attr.attr.crtime = newtimes[1].tv_sec; + out.body.attr.attr.crtimensec = newtimes[1].tv_nsec; }))); + + //struct stat sb; + //EXPECT_EQ(0, stat(FULLPATH, &sb)); + //printf("atime=%ld.%ld\t", sb.st_atim.tv_sec, sb.st_atim.tv_nsec); + //printf("mtime=%ld.%ld\t", sb.st_mtim.tv_sec, sb.st_mtim.tv_nsec); + //printf("birthtime=%ld.%ld\n", sb.st_birthtim.tv_sec, sb.st_birthtim.tv_nsec); EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) << strerror(errno); } -/* Change a file mtime but not its atime */ +/* Change a file's mtime but not its atime */ TEST_F(Setattr, utimensat_mtime_only) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; - const timespec oldtimes[2] = { + const timespec oldtimes[3] = { {.tv_sec = 1, .tv_nsec = 2}, {.tv_sec = 3, .tv_nsec = 4}, + {.tv_sec = 1, .tv_nsec = 1}, }; const timespec newtimes[2] = { {.tv_sec = 5, .tv_nsec = UTIME_OMIT}, @@ -638,6 +659,8 @@ out.body.entry.attr.atimensec = oldtimes[0].tv_nsec; out.body.entry.attr.mtime = oldtimes[1].tv_sec; out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec; + out.body.entry.attr.crtime = oldtimes[2].tv_sec; + out.body.entry.attr.crtimensec = oldtimes[2].tv_nsec; }))); EXPECT_CALL(*m_mock, process( @@ -675,9 +698,10 @@ const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; - const timespec oldtimes[2] = { + const timespec oldtimes[3] = { {.tv_sec = 1, .tv_nsec = 2}, {.tv_sec = 3, .tv_nsec = 4}, + {.tv_sec = 5, .tv_nsec = 6}, }; const timespec newtimes[2] = { {.tv_sec = 0, .tv_nsec = UTIME_NOW}, @@ -701,6 +725,8 @@ out.body.entry.attr.atimensec = oldtimes[0].tv_nsec; out.body.entry.attr.mtime = oldtimes[1].tv_sec; out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec; + out.body.entry.attr.crtime = oldtimes[2].tv_sec; + out.body.entry.attr.crtimensec = oldtimes[2].tv_nsec; }))); EXPECT_CALL(*m_mock, process(