On Mon, Mar 23, 2020 at 9:50 AM Phasip <phasip@xxxxxxxxx> wrote: > > Hello! > > I have stumbled upon two ways of producing kernel warnings when using the overlayfs, both seem to be results of the same issue. > > The issue seems to be related to handling of hard links that are created directly in the upperdir. > Below is my system details and then two samples with a list of commands to reproduce and the corresponding kernel warning Hi, Thanks for the report. The problem is that i_nlink is not kept in sync with changes to underlying layers. That would not in itself be an issue, since modification of the underlying layers may result in undefined/unexpected behavior. The problem is that this manifests itself as a kernel warning. Since unlink/rename is synchronized on the victim inode (the one that is getting removed) it is possible to detect this condition and prevent drop_nlink() from being called. Attached patch fixes both of your testcases. We'll need an xfstests case for this as well. Thanks, Miklos
--- fs/overlayfs/dir.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -819,6 +819,20 @@ static bool ovl_pure_upper(struct dentry !ovl_test_flag(OVL_WHITEOUTS, d_inode(dentry)); } +static void ovl_drop_nlink(struct inode *inode) +{ + struct dentry *alias = d_find_alias(inode); + + dput(alias); + /* + * Changes to underlying layers may cause i_nlink to lose sync with + * reality. In this case prevent the link count from going to zero + * prematurely. + */ + if (inode->i_nlink > !!alias) + drop_nlink(inode); +} + static int ovl_do_remove(struct dentry *dentry, bool is_dir) { int err; @@ -856,7 +870,7 @@ static int ovl_do_remove(struct dentry * if (is_dir) clear_nlink(dentry->d_inode); else - drop_nlink(dentry->d_inode); + ovl_drop_nlink(dentry->d_inode); } ovl_nlink_end(dentry); @@ -1201,7 +1215,7 @@ static int ovl_rename(struct inode *oldd if (new_is_dir) clear_nlink(d_inode(new)); else - drop_nlink(d_inode(new)); + ovl_drop_nlink(d_inode(new)); } ovl_dir_modified(old->d_parent, ovl_type_origin(old) ||