Skip to content

Commit

Permalink
ovl: set i_ino to the value of st_ino for NFS export
Browse files Browse the repository at this point in the history
Eddie Horng reported that readdir of an overlayfs directory that
was exported via NFSv3 returns entries with d_type set to DT_UNKNOWN.
The reason is that while preparing the response for readdirplus, nfsd
checks inside encode_entryplus_baggage() that a child dentry's inode
number matches the value of d_ino returns by overlayfs readdir iterator.

Because the overlayfs inodes use arbitrary inode numbers that are not
correlated with the values of st_ino/d_ino, NFSv3 falls back to not
encoding d_type. Although this is an allowed behavior, we can fix it for
the case of all overlayfs layers on the same underlying filesystem.

When NFS export is enabled and d_ino is consistent with st_ino
(samefs), set the same value also to i_ino in ovl_fill_inode() for all
overlayfs inodes, nfsd readdirplus sanity checks will pass.
ovl_fill_inode() may be called from ovl_new_inode(), before real inode
was created with ino arg 0. In that case, i_ino will be updated to real
upper inode i_ino on ovl_inode_init() or ovl_inode_update().

Reported-by: Eddie Horng <[email protected]>
Tested-by: Eddie Horng <[email protected]>
Signed-off-by: Amir Goldstein <[email protected]>
Fixes: 8383f17 ("ovl: wire up NFS export operations")
Cc: <[email protected]> #v4.16
Signed-off-by: Miklos Szeredi <[email protected]>
  • Loading branch information
amir73il authored and Miklos Szeredi committed Apr 12, 2018
1 parent 3eb2ce8 commit 695b46e
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 5 deletions.
21 changes: 17 additions & 4 deletions fs/overlayfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -459,9 +459,20 @@ static inline void ovl_lockdep_annotate_inode_mutex_key(struct inode *inode)
#endif
}

static void ovl_fill_inode(struct inode *inode, umode_t mode, dev_t rdev)
static void ovl_fill_inode(struct inode *inode, umode_t mode, dev_t rdev,
unsigned long ino)
{
inode->i_ino = get_next_ino();
/*
* When NFS export is enabled and d_ino is consistent with st_ino
* (samefs), set the same value to i_ino, because nfsd readdirplus
* compares d_ino values to i_ino values of child entries. When called
* from ovl_new_inode(), ino arg is 0, so i_ino will be updated to real
* upper inode i_ino on ovl_inode_init() or ovl_inode_update().
*/
if (inode->i_sb->s_export_op && ovl_same_sb(inode->i_sb))
inode->i_ino = ino;
else
inode->i_ino = get_next_ino();
inode->i_mode = mode;
inode->i_flags |= S_NOCMTIME;
#ifdef CONFIG_FS_POSIX_ACL
Expand Down Expand Up @@ -597,7 +608,7 @@ struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev)

inode = new_inode(sb);
if (inode)
ovl_fill_inode(inode, mode, rdev);
ovl_fill_inode(inode, mode, rdev, 0);

return inode;
}
Expand Down Expand Up @@ -710,6 +721,7 @@ struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry,
struct inode *inode;
bool bylower = ovl_hash_bylower(sb, upperdentry, lowerdentry, index);
bool is_dir;
unsigned long ino = 0;

if (!realinode)
realinode = d_inode(lowerdentry);
Expand Down Expand Up @@ -748,13 +760,14 @@ struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry,
if (!is_dir)
nlink = ovl_get_nlink(lowerdentry, upperdentry, nlink);
set_nlink(inode, nlink);
ino = key->i_ino;
} else {
/* Lower hardlink that will be broken on copy up */
inode = new_inode(sb);
if (!inode)
goto out_nomem;
}
ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev);
ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev, ino);
ovl_inode_init(inode, upperdentry, lowerdentry);

if (upperdentry && ovl_is_impuredir(upperdentry))
Expand Down
8 changes: 7 additions & 1 deletion fs/overlayfs/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,16 @@ void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect)
void ovl_inode_init(struct inode *inode, struct dentry *upperdentry,
struct dentry *lowerdentry)
{
struct inode *realinode = d_inode(upperdentry ?: lowerdentry);

if (upperdentry)
OVL_I(inode)->__upperdentry = upperdentry;
if (lowerdentry)
OVL_I(inode)->lower = igrab(d_inode(lowerdentry));

ovl_copyattr(d_inode(upperdentry ?: lowerdentry), inode);
ovl_copyattr(realinode, inode);
if (!inode->i_ino)
inode->i_ino = realinode->i_ino;
}

void ovl_inode_update(struct inode *inode, struct dentry *upperdentry)
Expand All @@ -299,6 +303,8 @@ void ovl_inode_update(struct inode *inode, struct dentry *upperdentry)
smp_wmb();
OVL_I(inode)->__upperdentry = upperdentry;
if (inode_unhashed(inode)) {
if (!inode->i_ino)
inode->i_ino = upperinode->i_ino;
inode->i_private = upperinode;
__insert_inode_hash(inode, (unsigned long) upperinode);
}
Expand Down

0 comments on commit 695b46e

Please sign in to comment.