diff mbox

ext4: Add statx support

Message ID 148966413352.3279.15659219505999889147.stgit@warthog.procyon.org.uk
State Not Applicable, archived
Headers show

Commit Message

David Howells March 16, 2017, 11:35 a.m. UTC
Return enhanced file attributes from the Ext4 filesystem.  This includes
the following:

 (1) The inode creation time (i_crtime) as stx_btime, setting STATX_BTIME.

 (2) Certain FS_xxx_FL flags are mapped to stx_attribute flags.

This requires that all ext4 inodes have a getattr call, not just some of
them, so to this end, split the ext4_getattr() function and only call part
of it where appropriate.

Example output:

	[root@andromeda ~]# touch foo
	[root@andromeda ~]# chattr +ai foo
	[root@andromeda ~]# /tmp/test-statx foo
	statx(foo) = 0
	results=fff
	  Size: 0               Blocks: 0          IO Block: 4096    regular file
	Device: 08:12           Inode: 2101950     Links: 1
	Access: (0644/-rw-r--r--)  Uid:     0   Gid:     0
	Access: 2016-02-11 17:08:29.031795451+0000
	Modify: 2016-02-11 17:08:29.031795451+0000
	Change: 2016-02-11 17:11:11.987790114+0000
	 Birth: 2016-02-11 17:08:29.031795451+0000
	Attributes: 0000000000000030 (-------- -------- -------- -------- -------- -------- -------- --ai----)

Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/ext4/ext4.h    |    1 +
 fs/ext4/file.c    |    2 +-
 fs/ext4/inode.c   |   36 +++++++++++++++++++++++++++++++++---
 fs/ext4/namei.c   |    2 ++
 fs/ext4/symlink.c |    2 ++
 5 files changed, 39 insertions(+), 4 deletions(-)

Comments

Andreas Dilger March 16, 2017, 9:46 p.m. UTC | #1
On Mar 16, 2017, at 5:35 AM, David Howells <dhowells@redhat.com> wrote:
> 
> Return enhanced file attributes from the Ext4 filesystem.  This includes
> the following:
> 
> (1) The inode creation time (i_crtime) as stx_btime, setting STATX_BTIME.
> 
> (2) Certain FS_xxx_FL flags are mapped to stx_attribute flags.
> 
> This requires that all ext4 inodes have a getattr call, not just some of
> them, so to this end, split the ext4_getattr() function and only call part
> of it where appropriate.
> 
> Example output:
> 
> 	[root@andromeda ~]# touch foo
> 	[root@andromeda ~]# chattr +ai foo
> 	[root@andromeda ~]# /tmp/test-statx foo
> 	statx(foo) = 0
> 	results=fff
> 	  Size: 0               Blocks: 0          IO Block: 4096    regular file
> 	Device: 08:12           Inode: 2101950     Links: 1
> 	Access: (0644/-rw-r--r--)  Uid:     0   Gid:     0
> 	Access: 2016-02-11 17:08:29.031795451+0000
> 	Modify: 2016-02-11 17:08:29.031795451+0000
> 	Change: 2016-02-11 17:11:11.987790114+0000
> 	 Birth: 2016-02-11 17:08:29.031795451+0000
> 	Attributes: 0000000000000030 (-------- -------- -------- -------- -------- -------- -------- --ai----)
> 
> Signed-off-by: David Howells <dhowells@redhat.com>

Reviewed-by: Andreas Dilger <adilger@dilger.ca>

> ---
> 
> fs/ext4/ext4.h    |    1 +
> fs/ext4/file.c    |    2 +-
> fs/ext4/inode.c   |   36 +++++++++++++++++++++++++++++++++---
> fs/ext4/namei.c   |    2 ++
> fs/ext4/symlink.c |    2 ++
> 5 files changed, 39 insertions(+), 4 deletions(-)
> 
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index f493af666591..fb69ee2388db 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -2466,6 +2466,7 @@ extern int  ext4_setattr(struct dentry *, struct iattr *);
> extern int  ext4_getattr(const struct path *, struct kstat *, u32, unsigned int);
> extern void ext4_evict_inode(struct inode *);
> extern void ext4_clear_inode(struct inode *);
> +extern int  ext4_file_getattr(const struct path *, struct kstat *, u32, unsigned int);

(style) should this wrap at 80 columns?

> extern int  ext4_sync_inode(handle_t *, struct inode *);
> extern void ext4_dirty_inode(struct inode *, int);
> extern int ext4_change_inode_journal_flag(struct inode *, int);
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index 8210c1f43556..cefa9835f275 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -744,7 +744,7 @@ const struct file_operations ext4_file_operations = {
> 
> const struct inode_operations ext4_file_inode_operations = {
> 	.setattr	= ext4_setattr,
> -	.getattr	= ext4_getattr,
> +	.getattr	= ext4_file_getattr,
> 	.listxattr	= ext4_listxattr,
> 	.get_acl	= ext4_get_acl,
> 	.set_acl	= ext4_set_acl,
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index 7385e6a6b6cb..5450e1eade1d 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -5390,11 +5390,41 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr)
> int ext4_getattr(const struct path *path, struct kstat *stat,
> 		 u32 request_mask, unsigned int query_flags)
> {
> -	struct inode *inode;
> -	unsigned long long delalloc_blocks;
> +	struct inode *inode = d_inode(path->dentry);
> +	struct ext4_inode *raw_inode;
> +	struct ext4_inode_info *ei = EXT4_I(inode);
> +	unsigned int flags;
> +
> +	if (EXT4_FITS_IN_INODE(raw_inode, ei, i_crtime)) {
> +		stat->result_mask |= STATX_BTIME;
> +		stat->btime.tv_sec = ei->i_crtime.tv_sec;
> +		stat->btime.tv_nsec = ei->i_crtime.tv_nsec;
> +	}
> +
> +	ext4_get_inode_flags(ei);
> +	flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
> +	if (flags & EXT4_APPEND_FL)
> +		stat->attributes |= STATX_ATTR_APPEND;
> +	if (flags & EXT4_COMPR_FL)
> +		stat->attributes |= STATX_ATTR_COMPRESSED;
> +	if (flags & EXT4_ENCRYPT_FL)
> +		stat->attributes |= STATX_ATTR_ENCRYPTED;
> +	if (flags & EXT4_IMMUTABLE_FL)
> +		stat->attributes |= STATX_ATTR_IMMUTABLE;
> +	if (flags & EXT4_NODUMP_FL)
> +		stat->attributes |= STATX_ATTR_NODUMP;
> 
> -	inode = d_inode(path->dentry);
> 	generic_fillattr(inode, stat);
> +	return 0;
> +}
> +
> +int ext4_file_getattr(const struct path *path, struct kstat *stat,
> +		      u32 request_mask, unsigned int query_flags)
> +{
> +	struct inode *inode = d_inode(path->dentry);
> +	u64 delalloc_blocks;
> +
> +	ext4_getattr(path, stat, request_mask, query_flags);
> 
> 	/*
> 	 * If there is inline data in the inode, the inode will normally not
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index 6ad612c576fc..07e5e1405771 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
> @@ -3912,6 +3912,7 @@ const struct inode_operations ext4_dir_inode_operations = {
> 	.tmpfile	= ext4_tmpfile,
> 	.rename		= ext4_rename2,
> 	.setattr	= ext4_setattr,
> +	.getattr	= ext4_getattr,
> 	.listxattr	= ext4_listxattr,
> 	.get_acl	= ext4_get_acl,
> 	.set_acl	= ext4_set_acl,
> @@ -3920,6 +3921,7 @@ const struct inode_operations ext4_dir_inode_operations = {
> 
> const struct inode_operations ext4_special_inode_operations = {
> 	.setattr	= ext4_setattr,
> +	.getattr	= ext4_getattr,
> 	.listxattr	= ext4_listxattr,
> 	.get_acl	= ext4_get_acl,
> 	.set_acl	= ext4_set_acl,
> diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
> index 73b184d161fc..4c6b23a0603e 100644
> --- a/fs/ext4/symlink.c
> +++ b/fs/ext4/symlink.c
> @@ -91,11 +91,13 @@ const struct inode_operations ext4_encrypted_symlink_inode_operations = {
> const struct inode_operations ext4_symlink_inode_operations = {
> 	.get_link	= page_get_link,
> 	.setattr	= ext4_setattr,
> +	.getattr	= ext4_getattr,
> 	.listxattr	= ext4_listxattr,
> };
> 
> const struct inode_operations ext4_fast_symlink_inode_operations = {
> 	.get_link	= simple_get_link,
> 	.setattr	= ext4_setattr,
> +	.getattr	= ext4_getattr,
> 	.listxattr	= ext4_listxattr,
> };
> 


Cheers, Andreas
Eric Biggers March 17, 2017, 5:47 a.m. UTC | #2
Hi David,

On Thu, Mar 16, 2017 at 11:35:33AM +0000, David Howells wrote:
> +
> +	ext4_get_inode_flags(ei);
> +	flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
> +	if (flags & EXT4_APPEND_FL)
> +		stat->attributes |= STATX_ATTR_APPEND;
> +	if (flags & EXT4_COMPR_FL)
> +		stat->attributes |= STATX_ATTR_COMPRESSED;
> +	if (flags & EXT4_ENCRYPT_FL)
> +		stat->attributes |= STATX_ATTR_ENCRYPTED;
> +	if (flags & EXT4_IMMUTABLE_FL)
> +		stat->attributes |= STATX_ATTR_IMMUTABLE;
> +	if (flags & EXT4_NODUMP_FL)
> +		stat->attributes |= STATX_ATTR_NODUMP;
>  
> -	inode = d_inode(path->dentry);
>  	generic_fillattr(inode, stat);
> +	return 0;
> +}

I think it's the wrong approach to be calling ext4_get_inode_flags() here to
sync the generic inode flags (inode->i_flags) to the ext4-specific inode flags
(ei->i_flags).  I know the GETFLAGS and FSGETXATTR ioctls do it too, but I think
it's a mistake --- at least, when done so without holding the inode lock.  The
issue is that flag syncs can occur in both directions concurrently and cause an
update to be lost.  For example, with thread 1 doing a stat() and thread 2 doing
the SETFLAGS ioctl to set the APPEND flag:

	Thread 1, in ext4_get_inode_flags()	Thread 2, in ext4_ioctl_setflags()
	-----------------------------------    -----------------------------------

	Read inode->i_flags; S_APPEND is clear	
						Set EXT4_APPEND_FL in ei->i_flags
	Clear EXT4_APPEND_FL in ei->i_flags
						Read ei->i_flags; EXT4_APPEND_FL is clear
						Clear S_APPEND in inode->i_flags

So the flag set by SETFLAGS gets lost.  This bug probably hasn't really been
noticable with GETFLAGS and FSGETXATTR since they're rarely used, but stat() on
the other hand is very common, and I'm worried that with this change people
would start seeing this race more often.

Ultimately this needs to be addressed in ext4 more fully, but how about for
->getattr() just skipping the call to ext4_get_inode_flags() and instead
populating the generic attributes like STATX_ATTR_APPEND and
STATX_ATTR_IMMUTABLE from the generic inode flags, rather than from the
ext4-specific flags?  Actually, it could even be done in generic_fillattr(), so
that all filesystems benefit.

> diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
> index 73b184d161fc..4c6b23a0603e 100644
> --- a/fs/ext4/symlink.c
> +++ b/fs/ext4/symlink.c
> @@ -91,11 +91,13 @@ const struct inode_operations ext4_encrypted_symlink_inode_operations = {
>  const struct inode_operations ext4_symlink_inode_operations = {
>  	.get_link	= page_get_link,
>  	.setattr	= ext4_setattr,
> +	.getattr	= ext4_getattr,
>  	.listxattr	= ext4_listxattr,
>  };
>  
>  const struct inode_operations ext4_fast_symlink_inode_operations = {
>  	.get_link	= simple_get_link,
>  	.setattr	= ext4_setattr,
> +	.getattr	= ext4_getattr,
>  	.listxattr	= ext4_listxattr,
>  };
> 

getattr needs to be added to ext4_encrypted_symlink_inode_operations too.

- Eric
Andreas Dilger March 17, 2017, 8:19 p.m. UTC | #3
On Mar 16, 2017, at 11:47 PM, Eric Biggers <ebiggers3@gmail.com> wrote:
> On Thu, Mar 16, 2017 at 11:35:33AM +0000, David Howells wrote:
>> +
>> +	ext4_get_inode_flags(ei);
>> +	flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
>> +	if (flags & EXT4_APPEND_FL)
>> +		stat->attributes |= STATX_ATTR_APPEND;
>> +	if (flags & EXT4_COMPR_FL)
>> +		stat->attributes |= STATX_ATTR_COMPRESSED;
>> +	if (flags & EXT4_ENCRYPT_FL)
>> +		stat->attributes |= STATX_ATTR_ENCRYPTED;
>> +	if (flags & EXT4_IMMUTABLE_FL)
>> +		stat->attributes |= STATX_ATTR_IMMUTABLE;
>> +	if (flags & EXT4_NODUMP_FL)
>> +		stat->attributes |= STATX_ATTR_NODUMP;
>> 
>> -	inode = d_inode(path->dentry);
>> 	generic_fillattr(inode, stat);
>> +	return 0;
>> +}
> 
> I think it's the wrong approach to be calling ext4_get_inode_flags() here to
> sync the generic inode flags (inode->i_flags) to the ext4-specific inode flags
> (ei->i_flags).  I know the GETFLAGS and FSGETXATTR ioctls do it too, but I
> think it's a mistake --- at least, when done so without holding the inode lock.
> The issue is that flag syncs can occur in both directions concurrently and
> cause an update to be lost.  For example, with thread 1 doing a stat() and
> thread 2 doing the SETFLAGS ioctl to set the APPEND flag:
> 
> Thread 1, ext4_get_inode_flags()	Thread 2, ext4_ioctl_setflags()
> -----------------------------------	---------------------------
> Read inode->i_flags; S_APPEND clear
> 					Set EXT4_APPEND_FL in ei->i_flags
> Clear EXT4_APPEND_FL in ei->i_flags
> 					Read ei->i_flags; EXT4_APPEND_FL clear
> 					Clear S_APPEND in inode->i_flags
> 
> So the flag set by SETFLAGS gets lost.  This bug probably hasn't really been
> noticable with GETFLAGS and FSGETXATTR since they're rarely used, but stat() on
> the other hand is very common, and I'm worried that with this change people
> would start seeing this race more often.
> 
> Ultimately this needs to be addressed in ext4 more fully, but how about for
> ->getattr() just skipping the call to ext4_get_inode_flags() and instead
> populating the generic attributes like STATX_ATTR_APPEND and
> STATX_ATTR_IMMUTABLE from the generic inode flags, rather than from the
> ext4-specific flags?  Actually, it could even be done in generic_fillattr(), so
> that all filesystems benefit.

Wouldn't it make more sense to just extract the ext4 flags directly from
ei->i_flags?  I think ext4_get_inode_flags() is only really useful when
the VFS inode flags are changed and they need to be propagated to the ext4
inode.

I guess the other more significant question is when/where are the VFS inode
flags changed that they need to be propagated into the ext4 disk inode?
The main avenue for changing these attribute flags that I know about is via
EXT4_IOC_SETFLAGS (FS_IOC_SETFLAGS), and there is one place that I could
find in fs/nsfs.c that sets S_IMMUTABLE but I don't think that propagates
down to an ext4 disk inode.

It seems like the use of ext4_get_inode_flags() is largely superfluous.
The original commit ff9ddf7e847 indicates this was for quota inodes only.
I think it can be removed from EXT4_IOC_FSGETXATTR, and EXT4_IOC_GETFLAGS
and made conditional in ext4_do_update_inode():

#ifdef CONFIG_QUOTA
        for (i = 0; i < EXT4_MAXQUOTAS; i++) {
                if (unlikely(sb_dqopt(sb)->files[i] == inode))
                        ext4_get_inode_flags(EXT4_I(inode));
        }
#endif

Jan, what do you think?

Cheers, Andreas

> 
>> diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
>> index 73b184d161fc..4c6b23a0603e 100644
>> --- a/fs/ext4/symlink.c
>> +++ b/fs/ext4/symlink.c
>> @@ -91,11 +91,13 @@ const struct inode_operations ext4_encrypted_symlink_inode_operations = {
>> const struct inode_operations ext4_symlink_inode_operations = {
>> 	.get_link	= page_get_link,
>> 	.setattr	= ext4_setattr,
>> +	.getattr	= ext4_getattr,
>> 	.listxattr	= ext4_listxattr,
>> };
>> 
>> const struct inode_operations ext4_fast_symlink_inode_operations = {
>> 	.get_link	= simple_get_link,
>> 	.setattr	= ext4_setattr,
>> +	.getattr	= ext4_getattr,
>> 	.listxattr	= ext4_listxattr,
>> };
>> 
> 
> getattr needs to be added to ext4_encrypted_symlink_inode_operations too.
> 
> - Eric


Cheers, Andreas
Jan Kara March 19, 2017, 11:12 a.m. UTC | #4
On Fri 17-03-17 14:19:22, Andreas Dilger wrote:
> On Mar 16, 2017, at 11:47 PM, Eric Biggers <ebiggers3@gmail.com> wrote:
> > On Thu, Mar 16, 2017 at 11:35:33AM +0000, David Howells wrote:
> >> +
> >> +	ext4_get_inode_flags(ei);
> >> +	flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
> >> +	if (flags & EXT4_APPEND_FL)
> >> +		stat->attributes |= STATX_ATTR_APPEND;
> >> +	if (flags & EXT4_COMPR_FL)
> >> +		stat->attributes |= STATX_ATTR_COMPRESSED;
> >> +	if (flags & EXT4_ENCRYPT_FL)
> >> +		stat->attributes |= STATX_ATTR_ENCRYPTED;
> >> +	if (flags & EXT4_IMMUTABLE_FL)
> >> +		stat->attributes |= STATX_ATTR_IMMUTABLE;
> >> +	if (flags & EXT4_NODUMP_FL)
> >> +		stat->attributes |= STATX_ATTR_NODUMP;
> >> 
> >> -	inode = d_inode(path->dentry);
> >> 	generic_fillattr(inode, stat);
> >> +	return 0;
> >> +}
> > 
> > I think it's the wrong approach to be calling ext4_get_inode_flags() here to
> > sync the generic inode flags (inode->i_flags) to the ext4-specific inode flags
> > (ei->i_flags).  I know the GETFLAGS and FSGETXATTR ioctls do it too, but I
> > think it's a mistake --- at least, when done so without holding the inode lock.
> > The issue is that flag syncs can occur in both directions concurrently and
> > cause an update to be lost.  For example, with thread 1 doing a stat() and
> > thread 2 doing the SETFLAGS ioctl to set the APPEND flag:
> > 
> > Thread 1, ext4_get_inode_flags()	Thread 2, ext4_ioctl_setflags()
> > -----------------------------------	---------------------------
> > Read inode->i_flags; S_APPEND clear
> > 					Set EXT4_APPEND_FL in ei->i_flags
> > Clear EXT4_APPEND_FL in ei->i_flags
> > 					Read ei->i_flags; EXT4_APPEND_FL clear
> > 					Clear S_APPEND in inode->i_flags
> > 
> > So the flag set by SETFLAGS gets lost.  This bug probably hasn't really been
> > noticable with GETFLAGS and FSGETXATTR since they're rarely used, but stat() on
> > the other hand is very common, and I'm worried that with this change people
> > would start seeing this race more often.
> > 
> > Ultimately this needs to be addressed in ext4 more fully, but how about for
> > ->getattr() just skipping the call to ext4_get_inode_flags() and instead
> > populating the generic attributes like STATX_ATTR_APPEND and
> > STATX_ATTR_IMMUTABLE from the generic inode flags, rather than from the
> > ext4-specific flags?  Actually, it could even be done in generic_fillattr(), so
> > that all filesystems benefit.
> 
> Wouldn't it make more sense to just extract the ext4 flags directly from
> ei->i_flags?  I think ext4_get_inode_flags() is only really useful when
> the VFS inode flags are changed and they need to be propagated to the ext4
> inode.
> 
> I guess the other more significant question is when/where are the VFS inode
> flags changed that they need to be propagated into the ext4 disk inode?
> The main avenue for changing these attribute flags that I know about is via
> EXT4_IOC_SETFLAGS (FS_IOC_SETFLAGS), and there is one place that I could
> find in fs/nsfs.c that sets S_IMMUTABLE but I don't think that propagates
> down to an ext4 disk inode.

Yes, you seem to be right. And actually I have checked and XFS does not
bother to copy inode->i_flags to its on-disk flags so it seems generally we
are not expected to reflect inode->i_flags in on-disk state.

> It seems like the use of ext4_get_inode_flags() is largely superfluous.
> The original commit ff9ddf7e847 indicates this was for quota inodes only.
> I think it can be removed from EXT4_IOC_FSGETXATTR, and EXT4_IOC_GETFLAGS
> and made conditional in ext4_do_update_inode():
> 
> #ifdef CONFIG_QUOTA
>         for (i = 0; i < EXT4_MAXQUOTAS; i++) {
>                 if (unlikely(sb_dqopt(sb)->files[i] == inode))
>                         ext4_get_inode_flags(EXT4_I(inode));
>         }
> #endif
> 
> Jan, what do you think?

So I think even better would be to have ext4_quota_on() and
ext4_quota_off() just update the flags as needed and avoid doing it
anywhere else... I'll have a look into it.

								Honza
Christoph Hellwig March 20, 2017, 5:52 p.m. UTC | #5
On Thu, Mar 16, 2017 at 11:35:33AM +0000, David Howells wrote:
> Return enhanced file attributes from the Ext4 filesystem.  This includes
> the following:

Just as the comment to a similar patch from Darrick for XFS:  Please add
test cases that verify this information.

Especially in the light of the races mentioned in the discussion later.

If we can't get tests for statx before 4.11 is getting close to release
I fear we'll have to revert it - untested syscalls have a tendency to
be broken and create problems for the future.
David Howells March 31, 2017, 8:06 a.m. UTC | #6
Eric Biggers <ebiggers3@gmail.com> wrote:

> Ultimately this needs to be addressed in ext4 more fully, but how about for
> ->getattr() just skipping the call to ext4_get_inode_flags() and instead
> populating the generic attributes like STATX_ATTR_APPEND and
> STATX_ATTR_IMMUTABLE from the generic inode flags, rather than from the
> ext4-specific flags?  Actually, it could even be done in generic_fillattr(), so
> that all filesystems benefit.

For the moment, taking Andreas's comments into account, I'll just drop the
call to ext4_get_inode_flags() and just assume ei->i_flags is correct.

I'm not sure whether to push the APPEND and IMMUTABLE attribute transfers to
the generic code - it makes sense for all filesystems that support such flags
and not for those that don't:-/  And, true, the same could be said of the
AUTOMMOUNT attribute.

David
Jan Kara April 12, 2017, 7:34 a.m. UTC | #7
On Sun 19-03-17 12:12:37, Jan Kara wrote:
> On Fri 17-03-17 14:19:22, Andreas Dilger wrote:
> > On Mar 16, 2017, at 11:47 PM, Eric Biggers <ebiggers3@gmail.com> wrote:
> > > On Thu, Mar 16, 2017 at 11:35:33AM +0000, David Howells wrote:
> > >> +
> > >> +	ext4_get_inode_flags(ei);
> > >> +	flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
> > >> +	if (flags & EXT4_APPEND_FL)
> > >> +		stat->attributes |= STATX_ATTR_APPEND;
> > >> +	if (flags & EXT4_COMPR_FL)
> > >> +		stat->attributes |= STATX_ATTR_COMPRESSED;
> > >> +	if (flags & EXT4_ENCRYPT_FL)
> > >> +		stat->attributes |= STATX_ATTR_ENCRYPTED;
> > >> +	if (flags & EXT4_IMMUTABLE_FL)
> > >> +		stat->attributes |= STATX_ATTR_IMMUTABLE;
> > >> +	if (flags & EXT4_NODUMP_FL)
> > >> +		stat->attributes |= STATX_ATTR_NODUMP;
> > >> 
> > >> -	inode = d_inode(path->dentry);
> > >> 	generic_fillattr(inode, stat);
> > >> +	return 0;
> > >> +}
> > > 
> > > I think it's the wrong approach to be calling ext4_get_inode_flags() here to
> > > sync the generic inode flags (inode->i_flags) to the ext4-specific inode flags
> > > (ei->i_flags).  I know the GETFLAGS and FSGETXATTR ioctls do it too, but I
> > > think it's a mistake --- at least, when done so without holding the inode lock.
> > > The issue is that flag syncs can occur in both directions concurrently and
> > > cause an update to be lost.  For example, with thread 1 doing a stat() and
> > > thread 2 doing the SETFLAGS ioctl to set the APPEND flag:
> > > 
> > > Thread 1, ext4_get_inode_flags()	Thread 2, ext4_ioctl_setflags()
> > > -----------------------------------	---------------------------
> > > Read inode->i_flags; S_APPEND clear
> > > 					Set EXT4_APPEND_FL in ei->i_flags
> > > Clear EXT4_APPEND_FL in ei->i_flags
> > > 					Read ei->i_flags; EXT4_APPEND_FL clear
> > > 					Clear S_APPEND in inode->i_flags
> > > 
> > > So the flag set by SETFLAGS gets lost.  This bug probably hasn't really been
> > > noticable with GETFLAGS and FSGETXATTR since they're rarely used, but stat() on
> > > the other hand is very common, and I'm worried that with this change people
> > > would start seeing this race more often.
> > > 
> > > Ultimately this needs to be addressed in ext4 more fully, but how about for
> > > ->getattr() just skipping the call to ext4_get_inode_flags() and instead
> > > populating the generic attributes like STATX_ATTR_APPEND and
> > > STATX_ATTR_IMMUTABLE from the generic inode flags, rather than from the
> > > ext4-specific flags?  Actually, it could even be done in generic_fillattr(), so
> > > that all filesystems benefit.
> > 
> > Wouldn't it make more sense to just extract the ext4 flags directly from
> > ei->i_flags?  I think ext4_get_inode_flags() is only really useful when
> > the VFS inode flags are changed and they need to be propagated to the ext4
> > inode.
> > 
> > I guess the other more significant question is when/where are the VFS inode
> > flags changed that they need to be propagated into the ext4 disk inode?
> > The main avenue for changing these attribute flags that I know about is via
> > EXT4_IOC_SETFLAGS (FS_IOC_SETFLAGS), and there is one place that I could
> > find in fs/nsfs.c that sets S_IMMUTABLE but I don't think that propagates
> > down to an ext4 disk inode.
> 
> Yes, you seem to be right. And actually I have checked and XFS does not
> bother to copy inode->i_flags to its on-disk flags so it seems generally we
> are not expected to reflect inode->i_flags in on-disk state.
> 
> > It seems like the use of ext4_get_inode_flags() is largely superfluous.
> > The original commit ff9ddf7e847 indicates this was for quota inodes only.
> > I think it can be removed from EXT4_IOC_FSGETXATTR, and EXT4_IOC_GETFLAGS
> > and made conditional in ext4_do_update_inode():
> > 
> > #ifdef CONFIG_QUOTA
> >         for (i = 0; i < EXT4_MAXQUOTAS; i++) {
> >                 if (unlikely(sb_dqopt(sb)->files[i] == inode))
> >                         ext4_get_inode_flags(EXT4_I(inode));
> >         }
> > #endif
> > 
> > Jan, what do you think?
> 
> So I think even better would be to have ext4_quota_on() and
> ext4_quota_off() just update the flags as needed and avoid doing it
> anywhere else... I'll have a look into it.

FYI I have just sent out patches to do this...

								Honza
diff mbox

Patch

diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index f493af666591..fb69ee2388db 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -2466,6 +2466,7 @@  extern int  ext4_setattr(struct dentry *, struct iattr *);
 extern int  ext4_getattr(const struct path *, struct kstat *, u32, unsigned int);
 extern void ext4_evict_inode(struct inode *);
 extern void ext4_clear_inode(struct inode *);
+extern int  ext4_file_getattr(const struct path *, struct kstat *, u32, unsigned int);
 extern int  ext4_sync_inode(handle_t *, struct inode *);
 extern void ext4_dirty_inode(struct inode *, int);
 extern int ext4_change_inode_journal_flag(struct inode *, int);
diff --git a/fs/ext4/file.c b/fs/ext4/file.c
index 8210c1f43556..cefa9835f275 100644
--- a/fs/ext4/file.c
+++ b/fs/ext4/file.c
@@ -744,7 +744,7 @@  const struct file_operations ext4_file_operations = {
 
 const struct inode_operations ext4_file_inode_operations = {
 	.setattr	= ext4_setattr,
-	.getattr	= ext4_getattr,
+	.getattr	= ext4_file_getattr,
 	.listxattr	= ext4_listxattr,
 	.get_acl	= ext4_get_acl,
 	.set_acl	= ext4_set_acl,
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 7385e6a6b6cb..5450e1eade1d 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5390,11 +5390,41 @@  int ext4_setattr(struct dentry *dentry, struct iattr *attr)
 int ext4_getattr(const struct path *path, struct kstat *stat,
 		 u32 request_mask, unsigned int query_flags)
 {
-	struct inode *inode;
-	unsigned long long delalloc_blocks;
+	struct inode *inode = d_inode(path->dentry);
+	struct ext4_inode *raw_inode;
+	struct ext4_inode_info *ei = EXT4_I(inode);
+	unsigned int flags;
+
+	if (EXT4_FITS_IN_INODE(raw_inode, ei, i_crtime)) {
+		stat->result_mask |= STATX_BTIME;
+		stat->btime.tv_sec = ei->i_crtime.tv_sec;
+		stat->btime.tv_nsec = ei->i_crtime.tv_nsec;
+	}
+
+	ext4_get_inode_flags(ei);
+	flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
+	if (flags & EXT4_APPEND_FL)
+		stat->attributes |= STATX_ATTR_APPEND;
+	if (flags & EXT4_COMPR_FL)
+		stat->attributes |= STATX_ATTR_COMPRESSED;
+	if (flags & EXT4_ENCRYPT_FL)
+		stat->attributes |= STATX_ATTR_ENCRYPTED;
+	if (flags & EXT4_IMMUTABLE_FL)
+		stat->attributes |= STATX_ATTR_IMMUTABLE;
+	if (flags & EXT4_NODUMP_FL)
+		stat->attributes |= STATX_ATTR_NODUMP;
 
-	inode = d_inode(path->dentry);
 	generic_fillattr(inode, stat);
+	return 0;
+}
+
+int ext4_file_getattr(const struct path *path, struct kstat *stat,
+		      u32 request_mask, unsigned int query_flags)
+{
+	struct inode *inode = d_inode(path->dentry);
+	u64 delalloc_blocks;
+
+	ext4_getattr(path, stat, request_mask, query_flags);
 
 	/*
 	 * If there is inline data in the inode, the inode will normally not
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 6ad612c576fc..07e5e1405771 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3912,6 +3912,7 @@  const struct inode_operations ext4_dir_inode_operations = {
 	.tmpfile	= ext4_tmpfile,
 	.rename		= ext4_rename2,
 	.setattr	= ext4_setattr,
+	.getattr	= ext4_getattr,
 	.listxattr	= ext4_listxattr,
 	.get_acl	= ext4_get_acl,
 	.set_acl	= ext4_set_acl,
@@ -3920,6 +3921,7 @@  const struct inode_operations ext4_dir_inode_operations = {
 
 const struct inode_operations ext4_special_inode_operations = {
 	.setattr	= ext4_setattr,
+	.getattr	= ext4_getattr,
 	.listxattr	= ext4_listxattr,
 	.get_acl	= ext4_get_acl,
 	.set_acl	= ext4_set_acl,
diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
index 73b184d161fc..4c6b23a0603e 100644
--- a/fs/ext4/symlink.c
+++ b/fs/ext4/symlink.c
@@ -91,11 +91,13 @@  const struct inode_operations ext4_encrypted_symlink_inode_operations = {
 const struct inode_operations ext4_symlink_inode_operations = {
 	.get_link	= page_get_link,
 	.setattr	= ext4_setattr,
+	.getattr	= ext4_getattr,
 	.listxattr	= ext4_listxattr,
 };
 
 const struct inode_operations ext4_fast_symlink_inode_operations = {
 	.get_link	= simple_get_link,
 	.setattr	= ext4_setattr,
+	.getattr	= ext4_getattr,
 	.listxattr	= ext4_listxattr,
 };