From nobody Sat Jul 25 22:33:15 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.186]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CE6B83403EE for ; Sun, 12 Jul 2026 17:54:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.186 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878900; cv=none; b=D3c0JjXgwkq4Y3J1u6xkNNw/rF9T5DsKnwtDKKOBOQSyQpx6QcPDNw4bFwI+X5DpIvGq3QV7Al3tA3CiGbRzfw8RfEUtkANnmL4FvwXcSQvlefiJyDUhdro4IPK8YeNkjanEBfJ8FvNcUn7wMjRc4GZxXmhtGf6LY+F2p0pmTyo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878900; c=relaxed/simple; bh=QNix6x/aQRh9lHJssv2ffhUtsBhRUkfQnO7F7MOscPY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=TbHe8ApyXVoK8xSQbFcIFLwlsEG0bLOORa9GrvHTo+FWcFFTOtGYWbD0JzJ/uWjoeo6gAPA3BDwG1UWRs5UR7TrZQuPvz7nJun1wFbkYE1bql05CAef5tvwsog6uTsnLKE3nzAjejlmE5kFzx7PKUUyKGY1SlFnNedCsKy0unqE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=nkPQW3yA; arc=none smtp.client-ip=195.121.94.186 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="nkPQW3yA" X-KPN-MessageId: c9007a25-7e1a-11f1-bfba-00505699b430 Received: from smtp.kpnmail.nl (unknown [10.31.155.5]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id c9007a25-7e1a-11f1-bfba-00505699b430; Sun, 12 Jul 2026 19:54:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=xrGpWzjKvcPVKevloUJ26DMlZ0X9lAKe5tg9ZytSdII=; b=nkPQW3yAZc6AS97TkvBcB4KZgtLGYrLb/itcl8P6Ub3hVN+dfBdWEATxYwE711KlUDA+o2HvMBfn9 QqV9ZzL1p4uGvNB7dkmkOHVqnASs5OneM23rphMiZcgr82kNcXgAHEYmBMcJ+0RV0UiGtf5ckzkeKa qZRK9rrEMa3cT7nYqiCftmRBMYfZSiSfQZM+uN8mfP7VaLUzrgaIxsv33qjT+UMsx/wYT4M5oj02UF fu2KiOy6v5MIRydOX3tA4MpzmcGjUX/lDdPImPSQxGVA7T5FWEM+KDN9Zqww/iyeO+EtlMT8EjoKtC fCwmsU8zG2ayvdtWxHRRM2EilsyVzjw== X-KPN-MID: 33|iELg2BFSGX7rwGyk0hxQWuzea0zBycv+QKlWqk3UBd5nTdAff/XzsJAkoq5revs Bg7d1Ijou3jB1LVeSer1sMW3Ax+iM8BIpTOpe7kZM8Ek= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|xF9sWC18cYw1wRXNzooeY1jNerzWcqf+rvaaoZad2v1GgBuo5dcn1t9JiqQsyip 3RWaFxKU94qbhiJRcDpREWQ== Received: from daedalus.home (unknown [178.230.226.124]) by smtp.xs4all.nl (Halon) with ESMTPSA id c8c0fa6d-7e1a-11f1-87f7-00505699b758; Sun, 12 Jul 2026 19:54:53 +0200 (CEST) From: Jori Koolstra To: Alexander Viro , Christian Brauner , Aleksa Sarai , NeilBrown Cc: Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH 01/10] fs/namei.c: use trailing_slashes() Date: Sun, 12 Jul 2026 19:55:30 +0200 Message-ID: <20260712175539.1565444-2-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260712175539.1565444-1-jkoolstra@xs4all.nl> References: <20260712175539.1565444-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" There are several places in fs/namei.c that can use the trailing_slashes() function to improve context. To allow this broader use its signature is changed to take a struct qstr instead of a struct nameidata. Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 19ce43c9a6e6..93e7d3e9bcb4 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2781,9 +2781,16 @@ static const char *path_init(struct nameidata *nd, u= nsigned flags) return s; } =20 +static inline bool trailing_slashes(const struct qstr *last) +{ + /* last->len is set by hash_name() to the length of the current + * component ->name, terminating with '/' or a NUL character. */ + return (bool)last->name[last->len]; +} + static inline const char *lookup_last(struct nameidata *nd) { - if (nd->last_type =3D=3D LAST_NORM && nd->last.name[nd->last.len]) + if (nd->last_type =3D=3D LAST_NORM && trailing_slashes(&nd->last)) nd->flags |=3D LOOKUP_FOLLOW | LOOKUP_DIRECTORY; =20 return walk_component(nd, WALK_TRAILING); @@ -4521,17 +4528,12 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, return ERR_PTR(error); } =20 -static inline bool trailing_slashes(struct nameidata *nd) -{ - return (bool)nd->last.name[nd->last.len]; -} - static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_= flag) { struct dentry *dentry; =20 if (open_flag & O_CREAT) { - if (trailing_slashes(nd)) + if (trailing_slashes(&nd->last)) return ERR_PTR(-EISDIR); =20 /* Don't bother on an O_EXCL create */ @@ -4539,7 +4541,7 @@ static struct dentry *lookup_fast_for_open(struct nam= eidata *nd, int open_flag) return NULL; } =20 - if (trailing_slashes(nd)) + if (trailing_slashes(&nd->last)) nd->flags |=3D LOOKUP_FOLLOW | LOOKUP_DIRECTORY; =20 dentry =3D lookup_fast(nd); @@ -4950,7 +4952,7 @@ static struct dentry *filename_create(int dfd, struct= filename *name, * Do the final lookup. Suppress 'create' if there is a trailing * '/', and a directory wasn't requested. */ - if (last.name[last.len] && !want_dir) + if (trailing_slashes(&last) && !want_dir) create_flags &=3D ~LOOKUP_CREATE; dentry =3D start_dirop(path->dentry, &last, reval_flag | create_flags); if (IS_ERR(dentry)) @@ -5569,7 +5571,7 @@ int filename_unlinkat(int dfd, struct filename *name) goto exit_drop_write; =20 /* Why not before? Because we want correct error value */ - if (unlikely(last.name[last.len])) { + if (unlikely(trailing_slashes(&last))) { if (d_is_dir(dentry)) error =3D -EISDIR; else @@ -6171,16 +6173,16 @@ int filename_renameat2(int olddfd, struct filename = *from, if (flags & RENAME_EXCHANGE) { if (!d_is_dir(rd.new_dentry)) { error =3D -ENOTDIR; - if (new_last.name[new_last.len]) + if (trailing_slashes(&new_last)) goto exit_unlock; } } /* unless the source is a directory trailing slashes give -ENOTDIR */ if (!d_is_dir(rd.old_dentry)) { error =3D -ENOTDIR; - if (old_last.name[old_last.len]) + if (trailing_slashes(&old_last)) goto exit_unlock; - if (!(flags & RENAME_EXCHANGE) && new_last.name[new_last.len]) + if (!(flags & RENAME_EXCHANGE) && trailing_slashes(&new_last)) goto exit_unlock; } =20 --=20 2.55.0 From nobody Sat Jul 25 22:33:15 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.186]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4BF2A34388A for ; Sun, 12 Jul 2026 17:54:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.186 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878898; cv=none; b=gHSRK42q2ZV+FQEx2gGY3A2Q4yn4QthZplX1KeiRADoOvW7bTi3dMFEBxEEkI200r+TADMkBifPmdj7JkOkDQGpZ2otVMQI8wqLF/zgzVLtdwWcpRTX5DWJnHN+F/VRdtPcp/qo5Yi6pxEDnOOOK5l00El8cqnmc2RM1za+VeDk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878898; c=relaxed/simple; bh=ywlBunsgX+iq4/GlUwnveotgrhWkekFoPW3HR5bN0ns=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=TatblecBOwprY9r1xRDWV5L2AugBUD9zcLo5rx4bgKUCDgRprkisjCbE3jzrETJUHA4+e+3sm2skXzLK4fH/kz9OqHNKXJ0t593PixauE6OJkh/cP46fJHHRZY/3Z5h4kUzsgmz1w/3PbKBaRKlVmXXstEttWv7mJAPmmDAwE2w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=B+ZBaa1t; arc=none smtp.client-ip=195.121.94.186 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="B+ZBaa1t" X-KPN-MessageId: c9b86f7b-7e1a-11f1-bfba-00505699b430 Received: from smtp.kpnmail.nl (unknown [10.31.155.5]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id c9b86f7b-7e1a-11f1-bfba-00505699b430; Sun, 12 Jul 2026 19:54:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=HmMELR7y+8WfVNUp4IjaZbzIhY4UvARZ+epXBazBtCc=; b=B+ZBaa1tIUHX+QFKrPKxjdDdJIRWwKWWuS5XiKy4CEb6mSmF8P0VNCn1XtYenMVxkADVVpactCPoZ c1mZLV0C6/9BhP2xSMkBp6eKszxQ2ReMqwQbybRLH7WO79YdOJ7ZNgvHSlvxQLT0j/JOl2OhzIFW87 pDPRHbcUaTNGvcS3VXr1laCNR9T9I1oy0dJLFd1Rhxft0i33ml54/tW6xRoK93S9ZAP1vqTJU9YMV5 s64jTtBvO7FHNVvFg74In8K2MfvKo5XC/q/Z62PtnZWRq90OVfXb6RY0RL/s+A6AZnsfbtQ54lB3yg Gm0cThY3mt+ZJKw4quk0jr92PkQTKwQ== X-KPN-MID: 33|8hJ7XCrr6mxQG8CaZJ6OOH/MScrlejVEqdgZ8R4Vk6pNvFfcJ45UWJrXqOLKDQ2 +YFUhizO3V4Ozcro2sxOZaV4PAvTxQMffl6SSsqdT1Wc= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|Tu/15hcSAs80jMqYjnmRNPI++gDKX4Yr3I20qrQSvZJa9ZjiRaAju86tHIz/RNt Cyj+qf6hUyIhuSG1WiCRqAA== Received: from daedalus.home (unknown [178.230.226.124]) by smtp.xs4all.nl (Halon) with ESMTPSA id c9875da0-7e1a-11f1-87f7-00505699b758; Sun, 12 Jul 2026 19:54:54 +0200 (CEST) From: Jori Koolstra To: Alexander Viro , Christian Brauner , Aleksa Sarai , NeilBrown Cc: Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH 02/10] vfs: prepare vfs_creat|mkdir_no_perm for reuse in lookup_open() Date: Sun, 12 Jul 2026 19:55:31 +0200 Message-ID: <20260712175539.1565444-3-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260712175539.1565444-1-jkoolstra@xs4all.nl> References: <20260712175539.1565444-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" To implement O_CREAT|O_DIRECTORY we will have to repeat some of the logic that is now in vfs_mkdir() (e.g. do error checks in the same order). Separate this out in vfs_mkdir_no_perm(), which does all the non-permission related work of vfs_mkdir(). Permission checking for the lookup_open() path is timed differently because we may just be doing an open and no create. Similar considerations give rise to vfs_create_no_perm(). Moving the fsnotify_* calls also allows us to deal with this in one place for each type of operation. This does mean that we also need to move the fsnotify_* calls into atomic_open() for the atomic open case, but this actually reduces duplicate code in open_last_lookups() and dentry_create(). Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 99 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 62 insertions(+), 37 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 93e7d3e9bcb4..48ceac6e8848 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4171,6 +4171,24 @@ static inline umode_t vfs_prepare_mode(struct mnt_id= map *idmap, return mode; } =20 +static inline +int vfs_create_no_perm(struct mnt_idmap *idmap, struct dentry *dentry, + umode_t mode, struct delegated_inode *di) +{ + struct inode *dir =3D d_inode(dentry->d_parent); + int error; + + error =3D try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); + if (error) + return error; + + error =3D dir->i_op->create(idmap, dir, dentry, mode, true); + if (!error) + fsnotify_create(dir, dentry); + + return error; +} + /** * vfs_create - create new file * @idmap: idmap of the mount the inode was found from @@ -4203,13 +4221,8 @@ int vfs_create(struct mnt_idmap *idmap, struct dentr= y *dentry, umode_t mode, error =3D security_inode_create(dir, dentry, mode); if (error) return error; - error =3D try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); - if (error) - return error; - error =3D dir->i_op->create(idmap, dir, dentry, mode, true); - if (!error) - fsnotify_create(dir, dentry); - return error; + + return vfs_create_no_perm(idmap, dentry, mode, di); } EXPORT_SYMBOL(vfs_create); =20 @@ -4386,10 +4399,17 @@ static struct dentry *atomic_open(const struct path= *path, struct dentry *dentry error =3D -ENOENT; } } + if (error) { dput(dentry); dentry =3D ERR_PTR(error); + } else { + if (file->f_mode & FMODE_CREATED) + fsnotify_create(dir, dentry); + if (file->f_mode & FMODE_OPENED) + fsnotify_open(file); } + return dentry; } =20 @@ -4516,6 +4536,8 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, mode, open_flag & O_EXCL); if (error) goto out_dput; + + fsnotify_create(dir_inode, dentry); } if (unlikely(create_error) && !dentry->d_inode) { error =3D create_error; @@ -4607,13 +4629,9 @@ static const char *open_last_lookups(struct nameidat= a *nd, inode_lock(dir->d_inode); else inode_lock_shared(dir->d_inode); + dentry =3D lookup_open(nd, file, op, got_write, &delegated_inode); - if (!IS_ERR(dentry)) { - if (file->f_mode & FMODE_CREATED) - fsnotify_create(dir->d_inode, dentry); - if (file->f_mode & FMODE_OPENED) - fsnotify_open(file); - } + if (open_flag & O_CREAT) inode_unlock(dir->d_inode); else @@ -5066,13 +5084,6 @@ struct file *dentry_create(struct path *path, int fl= ags, umode_t mode, if (unlikely(create_error) && error =3D=3D -ENOENT) error =3D create_error; =20 - if (!error) { - if (file->f_mode & FMODE_CREATED) - fsnotify_create(dir->d_inode, dentry); - if (file->f_mode & FMODE_OPENED) - fsnotify_open(file); - } - path->dentry =3D dentry; =20 } else { @@ -5224,6 +5235,33 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename= , umode_t, mode, unsigned, d return filename_mknodat(AT_FDCWD, name, mode, dev); } =20 +static inline +struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *idmap, struct inode *di= r, + struct dentry *dentry, umode_t mode, + struct delegated_inode *di) +{ + int error; + struct dentry *de; + unsigned max_links =3D dir->i_sb->s_max_links; + + if (max_links && dir->i_nlink >=3D max_links) + return ERR_PTR(-EMLINK); + + error =3D try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); + if (error) + return ERR_PTR(error); + + de =3D dir->i_op->mkdir(idmap, dir, dentry, mode); + if (IS_ERR(de)) + return de; + if (de) { + dput(dentry); + dentry =3D de; + } + fsnotify_mkdir(dir, dentry); + return dentry; +} + /** * vfs_mkdir - create directory returning correct dentry if possible * @idmap: idmap of the mount the inode was found from @@ -5251,7 +5289,6 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, str= uct inode *dir, struct delegated_inode *delegated_inode) { int error; - unsigned max_links =3D dir->i_sb->s_max_links; struct dentry *de; =20 error =3D may_create_dentry(idmap, dir, dentry); @@ -5267,24 +5304,12 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, s= truct inode *dir, if (error) goto err; =20 - error =3D -EMLINK; - if (max_links && dir->i_nlink >=3D max_links) - goto err; - - error =3D try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, delegated_inode); - if (error) - goto err; - - de =3D dir->i_op->mkdir(idmap, dir, dentry, mode); - error =3D PTR_ERR(de); - if (IS_ERR(de)) + de =3D vfs_mkdir_no_perm(idmap, dir, dentry, mode, delegated_inode); + if (IS_ERR(de)) { + error =3D PTR_ERR(de); goto err; - if (de) { - dput(dentry); - dentry =3D de; } - fsnotify_mkdir(dir, dentry); - return dentry; + return de; =20 err: end_creating(dentry); --=20 2.55.0 From nobody Sat Jul 25 22:33:15 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.186]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A7F0835E930 for ; Sun, 12 Jul 2026 17:54:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.186 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878899; cv=none; b=guZW9VKak4U0UwnLbiWbXs7T4a8kL024Uip/4XnQcbzzfOy+W3rbnZ3nANfBm7sibWPUzd+Dt3eu73YTrdoFybt8pxbOpYFYwi2sF/4h80oneCdJYdHqMciKUKpnMZnPl1ZL4fGIpDK+FVOh2Yrzr4Ltm3sf1r9fD27K5eiMnRY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878899; c=relaxed/simple; bh=u1sAIzE3h5PUzpTIElcpRCWWZ35nIPz4LbFFTOiI0Zw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Tm0up1CB45MMjbQRR7b/N33JFM+B8V0oWwK6qXzDcdOtGrVnWfhAaQH4axqex2F6Y9dSGkvk6tB+9w1pUqVub34SCYfsXmNhpegI5Fhg9XWxbnbgCPHqNRSXiYcU9x+R9J9WYrmQAPZyqYBEJgVSV2F5gE7RFvfAOjpVCnKEdZo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=e+P5PPnV; arc=none smtp.client-ip=195.121.94.186 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="e+P5PPnV" X-KPN-MessageId: ca5380bb-7e1a-11f1-bfba-00505699b430 Received: from smtp.kpnmail.nl (unknown [10.31.155.5]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id ca5380bb-7e1a-11f1-bfba-00505699b430; Sun, 12 Jul 2026 19:54:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=fj65mfPONwi6t8foTt9z20F0U/WrZ8/1TJPyfzE1ABI=; b=e+P5PPnV4JALj5a3M4UjASFdcS9dUnAMcNgCWYueDv2e1jxvvCB+CikPyzKBLWApL9QiPAgWVfW3n SoN7l5hV8Lzai4YxTzfeo3UGedA7Mkr+ZJ6+CTR0lWPbmkfdnjYI9mXLdpHTdMQ9WOatcqbBGe8iRM V7waLD1gQPKb/RfnzR3qcsW7f8tg/hWhEN1aMMwBLMcze9NQ5GRQn4TrjiKvgKwl6UHBTWLkSoSaSs ANFZwoaZ2fgVhEsVazVG34S1ZkdzAijhFIRnCvP6LfDk79AFU6pGNBYQ+CwEXvqzA/CKOfFkoJvfFj nnC6/RpxzetS4o/fR1k2eCTsjl6Ecxw== X-KPN-MID: 33|LyvFpbhfi1v5vz2XnhumPU3FSM/IeDFYQhkXJ6Vpdw5ConZBZLD53GzXk6Rnzi/ WM2zo3QFFOQKBUmGPAI6AVKvab45pKOrLChDxr3HUuKQ= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|3K4Py3rfcoKEJY202Gyeo26/dkWbX/yq6WSLqwkx6KLZWXS8OeDdJBPzynsa5X3 t2mBtU4J0fNTZD2JFROlEzw== Received: from daedalus.home (unknown [178.230.226.124]) by smtp.xs4all.nl (Halon) with ESMTPSA id ca243084-7e1a-11f1-87f7-00505699b758; Sun, 12 Jul 2026 19:54:55 +0200 (CEST) From: Jori Koolstra To: Alexander Viro , Christian Brauner , Aleksa Sarai , NeilBrown Cc: Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH 03/10] vfs: lookup_open(): move setting FMODE_CREATED down Date: Sun, 12 Jul 2026 19:55:32 +0200 Message-ID: <20260712175539.1565444-4-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260712175539.1565444-1-jkoolstra@xs4all.nl> References: <20260712175539.1565444-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" In preparation for using vfs_create_no_perm() in lookup_open() we need to move setting FMODE_CREATED on the file mode to either before or after that call, as currently it is in the middle. If try_break_deleg() fails it is currently not set, but vfs_create_no_perm() includes a try_break_deleg(). Going up the call chain of lookup_open() we see that it is only used in open_last_lookups() if no error is returned from lookup_open(), so we can safely move it to after the filesystem create() call. This also makes more sense when reading the code as you don't have to wonder what the implications are of setting FMODE_CREATED before the create() call. Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/namei.c b/fs/namei.c index 48ceac6e8848..a43e64717d11 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4525,7 +4525,6 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, if (error) goto out_dput; =20 - file->f_mode |=3D FMODE_CREATED; audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); if (!dir_inode->i_op->create) { error =3D -EACCES; @@ -4538,11 +4537,13 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, goto out_dput; =20 fsnotify_create(dir_inode, dentry); + file->f_mode |=3D FMODE_CREATED; } if (unlikely(create_error) && !dentry->d_inode) { error =3D create_error; goto out_dput; } + return dentry; =20 out_dput: --=20 2.55.0 From nobody Sat Jul 25 22:33:15 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.184]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 95D56381E9C for ; Sun, 12 Jul 2026 17:55:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.184 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878907; cv=none; b=Tr7bGFJRzub/5s1ZLI/ie0Tuw0kt3tRUWLoDW2SUrfK9el6cFtgsidKzAfMN83dH/rcNxKbfSs3sRcx5ZdjbdeBG6W7F3ScDhTUkQtE39SiasrEnZCZE4pEbfIO2mBvbrLab45DLQLIpb5/1bk07s0b3H1zjnmDdjnGbT44572U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878907; c=relaxed/simple; bh=qpVwvxXPy6jPCVFeD++9EdIwgB3C6GozoZRoeWP0CxA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FAgfdou7KJo+2S5O0foqMoscWqzTA5tpc9K5dIFH/Gx7G2EyVc1vLhPAoEH3BO8dnonim64z9HFdO88VUBq7uFEUwGEjlIkLp1AcB9oC6LddztwB4n8Z/TLAxokARK7OfUznNhs9M6KSq3CR2TRx96wRtVKClAq//NNs7EWB2ik= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=doNCId+J; arc=none smtp.client-ip=195.121.94.184 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="doNCId+J" X-KPN-MessageId: cb2c9998-7e1a-11f1-a59f-005056994fde Received: from smtp.kpnmail.nl (unknown [10.31.155.5]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id cb2c9998-7e1a-11f1-a59f-005056994fde; Sun, 12 Jul 2026 19:54:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=5j6W/FDwkpFm4i9Np0yyQ66L9ewGj65MEO11VqU5R5g=; b=doNCId+Jf/zzBzRRIyluLYlmFqVupHeCwBhmTTDhKsJd6x8VbuvIohQB3yfMmHRh7M6drsh5bI9KN WbwykIbhQon08jh92LSqhX3a40GH+apFRRO0OW5aLXH56Eszp9et0drLeZkyClikErjAFOsFGukBsX m/FOLoULLoBVMgvJOv7NQnYn/XcxXJa5CNHT3EVeDJb2rpcNiacPVzIBUO5Q2/sZkfA+gcxo8NJ6wI Jb24QcX9hXWD1TzUxF8DeeLRVEUNV45ZvyL3d51Ry+1fZpvJwOHAvJLur7dTazmpBHRnSvPHNuf76Y opB5qfX1oHRp4n7I/85TDzdFcDgmtuQ== X-KPN-MID: 33|IJ1Z28X5cYnUlcwPQzY8Ik14QFxmk4xuDghCFUYP7CUBcSMcJwoFX2qahHy8144 yDpYx3lqSerWAvqDQcnvpgg== X-KPN-VerifiedSender: Yes X-CMASSUN: 33|0CQ93ndg2dr/ur5RV+3vAJVQGG5k/wvuUn9dqNMgpRN+xyqpMeoSBd5/ea1q2NK XWkaWza5OnOPn7EBC3/dV6g== Received: from daedalus.home (unknown [178.230.226.124]) by smtp.xs4all.nl (Halon) with ESMTPSA id cafab80e-7e1a-11f1-87f7-00505699b758; Sun, 12 Jul 2026 19:54:56 +0200 (CEST) From: Jori Koolstra To: Alexander Viro , Christian Brauner , Aleksa Sarai , NeilBrown Cc: Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH 04/10] vfs: move ->create check in lookup_open() to before try_break_deleg() Date: Sun, 12 Jul 2026 19:55:33 +0200 Message-ID: <20260712175539.1565444-5-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260712175539.1565444-1-jkoolstra@xs4all.nl> References: <20260712175539.1565444-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The i_op->create check in lookup_open() takes place after the try_break_deleg() call. This does not match the order when doing a regular file create via mknod(2). There the call order is: filename_mknodat() vfs_create() i_op->create check try_break_deleg() Move the i_op->create check to before try_break_deleg() in lookup_open(). Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index a43e64717d11..aaba03954cd5 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4520,16 +4520,17 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, =20 /* Negative dentry, just create the file */ if (!dentry->d_inode && (open_flag & O_CREAT)) { + if (!dir_inode->i_op->create) { + error =3D -EACCES; + goto out_dput; + } + /* but break the directory lease first! */ error =3D try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, delegated_i= node); if (error) goto out_dput; =20 audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); - if (!dir_inode->i_op->create) { - error =3D -EACCES; - goto out_dput; - } =20 error =3D dir_inode->i_op->create(idmap, dir_inode, dentry, mode, open_flag & O_EXCL); --=20 2.55.0 From nobody Sat Jul 25 22:33:15 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.186]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3D926368D41 for ; Sun, 12 Jul 2026 17:55:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.186 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878901; cv=none; b=RKSw43uaOyF9qlY3B6qY9rX5bsMyBkTVtfqNGg8gygije9KA8+/NMrQUpVQvx49VJaiCEtyMrPMssbgjLNJ4+565p30qbYhDq9SifKpj/HPzlgLpEYpUNWmF+k7+TwqqRWhV/g4OeaY4R52yz2OCW75iBiu1CE7J36DNQLyC1nw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878901; c=relaxed/simple; bh=35MZFXyUa1mf577Kk2bx5Uoab2PSK5f05XHYZdgt0lk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mIv6tiH4Bi7qGUCTmu3O+F0WFZz6aI9WwVQqCJfxD5+n1FrLnCWDxh3ya266oQS5/VaodGD92HFU/UDb5rAQIix+R4P9qdomJ/8NrXvYhH0i9soOO21o2gcUyLfE615zz+8L1rYx5vop0W43Z2GDkjUdJhpYz0X7hXPy3wKg8Iw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=LdiZy/W4; arc=none smtp.client-ip=195.121.94.186 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="LdiZy/W4" X-KPN-MessageId: cc0883ae-7e1a-11f1-bfba-00505699b430 Received: from smtp.kpnmail.nl (unknown [10.31.155.5]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id cc0883ae-7e1a-11f1-bfba-00505699b430; Sun, 12 Jul 2026 19:54:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=unngHQ7XuHKiLSloKaFmMnBXhX63cPMZXAC31ja3FN0=; b=LdiZy/W4LMS4LVBsY1yzPAQHNIZa0XgkEW0h7nsx2Ufr+vxymI8UBX0NEOv66PHBd59yJHuilOXUR hr9eRF2wgbJoa4DVOmgTxiFU3JlEjHJzxyA+sdln4AtoIj3hDv6VkcbD8PomH76J5Gj0k67fVnCqJJ 6EQ3pdb2iAZ+BKNdeHpeR5R9PcNGj4LH7lXos4dTSAOCqmGWZtJgTecJqn/VGNdxMcPAUu0kiW+uXl TK0QmfdAcZ3x3syagyUMeUYWImoZJhaHvT9qhDApKwY5Gn7OK8JebaN2WaVwEY+DbQikn+qSd/umvp CbjYmYTGKn8W5mLNBAuaT8GZFaipXvQ== X-KPN-MID: 33|GTayxM4A7UTrpLKVC8UJLWwVQt0hUKEmWsF8CR9rVWOGVL9KEcOgxLRgSlbhdJe fvo+obN+GshS59I+NTHPuI7dfE6yMOtWRTQNnsTYzWtE= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|YwIA+sILYb8w1+F+v4lrybepxO3Tz6IX+mNRxrpFqgRRnds0CeG/QZUDggHPOnC H/Etp1nJrFGdiBGHhOU6nEw== Received: from daedalus.home (unknown [178.230.226.124]) by smtp.xs4all.nl (Halon) with ESMTPSA id cbdbaa3f-7e1a-11f1-87f7-00505699b758; Sun, 12 Jul 2026 19:54:58 +0200 (CEST) From: Jori Koolstra To: Alexander Viro , Christian Brauner , Aleksa Sarai , NeilBrown Cc: Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH 05/10] vfs: lookup_open(): use vfs_create_no_perm() Date: Sun, 12 Jul 2026 19:55:34 +0200 Message-ID: <20260712175539.1565444-6-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260712175539.1565444-1-jkoolstra@xs4all.nl> References: <20260712175539.1565444-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" We can replace the code in the no create_error/negative dentry found from lookup case in lookup_open() with the vfs_create_no_perm() helper. It is safe here to always pass excl=3Dtrue to ->create via vfs_create_no_perm(). Local filesystems always ignore the excl flag, and cluster filesystems implement ->atomic_open, so that the ->create call is never reached in lookup_open(). Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index aaba03954cd5..91e01c9794af 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4525,19 +4525,12 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, goto out_dput; } =20 - /* but break the directory lease first! */ - error =3D try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, delegated_i= node); - if (error) - goto out_dput; - audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); =20 - error =3D dir_inode->i_op->create(idmap, dir_inode, dentry, - mode, open_flag & O_EXCL); + error =3D vfs_create_no_perm(idmap, dentry, mode, delegated_inode); if (error) goto out_dput; =20 - fsnotify_create(dir_inode, dentry); file->f_mode |=3D FMODE_CREATED; } if (unlikely(create_error) && !dentry->d_inode) { --=20 2.55.0 From nobody Sat Jul 25 22:33:15 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.184]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EF297396B76 for ; Sun, 12 Jul 2026 17:55:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.184 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878908; cv=none; b=P+iWgZfffyggkO+ug2YXDJVzT1LxC3hgW8p7uzjnlb5l/gtNlDfHr1h/yRzcGesV9EJSaMs8c7Ipe2u7ntStd7eNVv2hX+zDAU/UHf4zOSRJ0X5gc2k4uOtLRM6AKb8GiAIZszTYNhxyZD+Hm1Xd4upIWJrMscQBCCta2iTOha0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878908; c=relaxed/simple; bh=R3wACWx1MK62zHo+jTeU/PBufQ9mBNB/Epyz0g4uCWc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ST9gdno1XdIyw+efSCzm5SAYyc6fG4vN1lU7FP1Lj0ZfeLbVodihuOtLDaEbBG9JQU3xN8sXfVNsrg3Ogb7b165smK8zGcVGkUlnsrwpfwz32h4yNyvC/In9w/rLrM9jgk+/RiDqCE4A4inAfdKYlPnSErJ/1zfC3ebvZVJbXSI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=JqXlkn/s; arc=none smtp.client-ip=195.121.94.184 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="JqXlkn/s" X-KPN-MessageId: ccbbb3a0-7e1a-11f1-a59f-005056994fde Received: from smtp.kpnmail.nl (unknown [10.31.155.5]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id ccbbb3a0-7e1a-11f1-a59f-005056994fde; Sun, 12 Jul 2026 19:54:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=Sg3NbKg5Xrfwx2Q9t6ZGx1L5LkBZr94jAE4mLoaAu8A=; b=JqXlkn/shquQ9raoLFlM+w4nWId6PASGm7sWExYslg1ThD7kohQTatZeqBjnP+8otEgOiBkl1ZU/I +Y9L83rgA0yGqrx2ES/kO70k4b2w4UMaN+flPwUs16w9fktuq/AQOqdpX1fBBXzkwR9uv8utWKqYMV W0tNXzUYphkoPZGffXho/s/N55QLiZkeJWiF6VUCsCgs1+Vxigi4efOOwVg4/Nn/tuZCqQ2qgxP8l9 HQxkx8Lm4poXbGmzJY2nua44JtkbH9OrZxrPSLW7drfpn8sdzjdh/eGVN6yZs+NoGWf2x2ChxXQV2W 8O915vlebGO7FXeQiOkP21tSbk3onVg== X-KPN-MID: 33|Fr1syQOh/p9FJopKiKp0keOwBJOyfzRGmoJNNINCXxjHJzhstnQAPz+zC443BZF z4yDJ7fw8ioAe8kiXl6XoDSDJC6s60F9Ok1qN1NUf39k= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|31e3WAvXxk3V1KH62+OQpl3LCqpRcOjuu1MN/0CFDt1tUyv2aFHfDt4lE9cwqol zuUiFQ/9C+uuCTFMUy/JBrQ== Received: from daedalus.home (unknown [178.230.226.124]) by smtp.xs4all.nl (Halon) with ESMTPSA id cc81621f-7e1a-11f1-87f7-00505699b758; Sun, 12 Jul 2026 19:54:59 +0200 (CEST) From: Jori Koolstra To: Alexander Viro , Christian Brauner , Aleksa Sarai , NeilBrown Cc: Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH 06/10] vfs: add O_CREAT|O_DIRECTORY to open*(2) Date: Sun, 12 Jul 2026 19:55:35 +0200 Message-ID: <20260712175539.1565444-7-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260712175539.1565444-1-jkoolstra@xs4all.nl> References: <20260712175539.1565444-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Currently there is no way to race-freely create and open a directory. For regular files we have open(O_CREAT) for creating a new file inode, and returning a pinning fd to it. The lack of such functionality for directories means that when populating a directory tree there's always a race involved: the inodes first need to be created, and then opened to adjust their permissions/ownership/labels/timestamps/acls/xattrs/..., but in the time window between the creation and the opening they might be replaced by something else. Addressing this race without a proper API is only partially possible: the caller can immediately fstat() what was opened to verify that it has the expected inode type, owner and mode. But besides being easy to get wrong, this cannot establish who created the directory: a directory created by another process with identical credentials is indistinguishable from one the caller created itself, so the caller cannot tell whether the directory is its own to manage. Historically, the O_CREAT|O_DIRECTORY behaviour was to return ENOTDIR if a regular file exists at the open path; EISDIR if a directory exists at the path; and to create a regular file if no file exists at the path. This behaviour changed accidentally with 973d4b73fbaf ("do_last(): rejoin the common path even earlier in FMODE_{OPENED,CREATED} case") causing ENOTDIR to return in the last case while still creating the file. As this change was not detected for a long time, Brauner proposed to adopt the more consistent NetBSD behaviour, i.e. to return EINVAL on the O_CREAT|O_DIRECTORY combination. This change was applied in 43b450632676 ("open: return EINVAL for O_DIRECTORY | O_CREAT") in March, 2023. As the EINVAL behaviour has been in the kernel for about 3 years now, no rollback is expected as a result of userspace reliance on old behaviour, leaving us free to reassign the O_CREAT|O_DIRECTORY semantics. O_CREAT|O_DIRECTORY is made to reduce to a lookup on ->atomic_open() filesystems. These filesystems currenly cannot handle O_CREAT|O_DIRECTORY without protocol extensions and therefore are forced into a fallback mode by stripping the O_CREAT bit. This causes existing directories to be succesfully opened, while for targets that should have been created, -ENOENT is returned. The other option of simply returning -EINVAL leads to inconsistent behaviour: before ->atomic_open() is called in lookup_open(), the dcache is queried. So returning -EINVAL there would make O_CREAT|O_DIRECTORY dependent on the cache state of the dentry. There is no separate sysctl for directory creation implemented currently. Therefore, for the S_ISDIR case, disabling sysctl_protected_regular is not enough to allow creating a directory in a sticky folder, because that may surprise users not expecting that O_CREAT|O_DIRECTORY is possible on newer kernels. This feature idea (and some of its description) is taken from the UAPI group: https://github.com/uapi-group/kernel-features?tab=3Dreadme-ov-file#race-fre= e-creation-and-opening-of-non-file-inodes Signed-off-by: Jori Koolstra --- fs/namei.c | 86 +++++++++++++++++++++++++++++++++++-------- fs/open.c | 25 +++++++------ include/linux/fcntl.h | 6 +++ 3 files changed, 90 insertions(+), 27 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 91e01c9794af..b37d2b0826b3 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1382,13 +1382,13 @@ int may_linkat(struct mnt_idmap *idmap, const struc= t path *link) =20 /** * may_create_in_sticky - Check whether an O_CREAT open in a sticky direct= ory - * should be allowed, or not, on files that already - * exist. + * should be allowed, or not, on files/directories that + * already exist. * @idmap: idmap of the mount the inode was found from * @nd: nameidata pathwalk data * @inode: the inode of the file to open * - * Block an O_CREAT open of a FIFO (or a regular file) when: + * Block an O_CREAT open of a FIFO (or a regular file/directory) when: * - sysctl_protected_fifos (or sysctl_protected_regular) is enabled * - the file already exists * - we are in a sticky directory @@ -1416,6 +1416,14 @@ static int may_create_in_sticky(struct mnt_idmap *id= map, struct nameidata *nd, if (likely(!(dir_mode & S_ISVTX))) return 0; =20 + /* + * There is no separate sysctl for directory creation in sticky + * folders. Therefore, for the S_ISDIR case, disabling + * sysctl_protected_regular is not enough to allow creating a + * directory in a sticky folder, because that may surprise users + * not expecting that O_CREAT|O_DIRECTORY is possible on newer + * kernels. + */ if (S_ISREG(inode->i_mode) && !sysctl_protected_regular) return 0; =20 @@ -1447,6 +1455,12 @@ static int may_create_in_sticky(struct mnt_idmap *id= map, struct nameidata *nd, "sticky_create_regular"); return -EACCES; } + + if (S_ISDIR(inode->i_mode)) { + audit_log_path_denied(AUDIT_ANOM_CREAT, + "sticky_create_dir"); + return -EACCES; + } } =20 return 0; @@ -4339,21 +4353,41 @@ static inline int open_to_namei_flags(int flag) =20 static int may_o_create(struct mnt_idmap *idmap, const struct path *dir, struct dentry *dentry, - umode_t mode) + int open_flag, umode_t mode) { - int error =3D security_path_mknod(dir, dentry, mode, 0); + struct inode *dir_inode =3D dir->dentry->d_inode; + bool create_dir =3D O_IS_MKDIR(open_flag); + int error; + + if (create_dir) + error =3D security_path_mkdir(dir, dentry, mode); + else + error =3D security_path_mknod(dir, dentry, mode, 0); if (error) return error; =20 if (!fsuidgid_has_mapping(dir->dentry->d_sb, idmap)) return -EOVERFLOW; =20 - error =3D inode_permission(idmap, dir->dentry->d_inode, - MAY_WRITE | MAY_EXEC); + error =3D inode_permission(idmap, dir_inode, MAY_WRITE | MAY_EXEC); if (error) return error; =20 - return security_inode_create(dir->dentry->d_inode, dentry, mode); + if (create_dir) + error =3D security_inode_mkdir(dir_inode, dentry, mode); + else + error =3D security_inode_create(dir_inode, dentry, mode); + + return error; +} + +static inline umode_t o_create_mode(struct mnt_idmap *idmap, + const struct inode *dir, int open_flag, umode_t mode) +{ + if (O_IS_MKDIR(open_flag)) + return vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, 0); + else + return vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG); } =20 /* @@ -4379,6 +4413,7 @@ static struct dentry *atomic_open(const struct path *= path, struct dentry *dentry =20 file->__f_path.dentry =3D DENTRY_NOT_SET; file->__f_path.mnt =3D path->mnt; + error =3D dir->i_op->atomic_open(dir, dentry, file, open_to_namei_flags(open_flag), mode); d_lookup_done(dentry); @@ -4413,6 +4448,10 @@ static struct dentry *atomic_open(const struct path = *path, struct dentry *dentry return dentry; } =20 +static inline +struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *, struct inode *, + struct dentry *, umode_t, + struct delegated_inode *); /* * Look up and maybe create and open the last component. * @@ -4439,10 +4478,14 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, struct dentry *dentry; int error, create_error =3D 0; umode_t mode =3D op->mode; + bool create_dir =3D O_IS_MKDIR(open_flag); =20 if (unlikely(IS_DEADDIR(dir_inode))) return ERR_PTR(-ENOENT); =20 + if (create_dir && dir_inode->i_op->atomic_open) + open_flag &=3D ~O_CREAT; + file->f_mode &=3D ~FMODE_CREATED; dentry =3D d_lookup(dir, &nd->last); for (;;) { @@ -4486,10 +4529,10 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, if (open_flag & O_CREAT) { if (open_flag & O_EXCL) open_flag &=3D ~O_TRUNC; - mode =3D vfs_prepare_mode(idmap, dir->d_inode, mode, mode, mode); + mode =3D o_create_mode(idmap, dir_inode, open_flag, mode); if (likely(got_write)) create_error =3D may_o_create(idmap, &nd->path, - dentry, mode); + dentry, open_flag, mode); else create_error =3D -EROFS; } @@ -4520,14 +4563,23 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, =20 /* Negative dentry, just create the file */ if (!dentry->d_inode && (open_flag & O_CREAT)) { - if (!dir_inode->i_op->create) { + if ((create_dir && !dir_inode->i_op->mkdir) + || (!create_dir && !dir_inode->i_op->create)) { error =3D -EACCES; goto out_dput; } =20 audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); =20 - error =3D vfs_create_no_perm(idmap, dentry, mode, delegated_inode); + if (create_dir) { + struct dentry *res =3D vfs_mkdir_no_perm(idmap, dir_inode, dentry, + mode, delegated_inode); + error =3D PTR_ERR_OR_ZERO(res); + if (!error) + dentry =3D res; + } else { + error =3D vfs_create_no_perm(idmap, dentry, mode, delegated_inode); + } if (error) goto out_dput; =20 @@ -4550,7 +4602,7 @@ static struct dentry *lookup_fast_for_open(struct nam= eidata *nd, int open_flag) struct dentry *dentry; =20 if (open_flag & O_CREAT) { - if (trailing_slashes(&nd->last)) + if (trailing_slashes(&nd->last) && !(open_flag & O_DIRECTORY)) return ERR_PTR(-EISDIR); =20 /* Don't bother on an O_EXCL create */ @@ -4621,7 +4673,7 @@ static const char *open_last_lookups(struct nameidata= *nd, */ } if (open_flag & O_CREAT) - inode_lock(dir->d_inode); + inode_lock_nested(dir->d_inode, I_MUTEX_PARENT); else inode_lock_shared(dir->d_inode); =20 @@ -4684,8 +4736,9 @@ static int do_open(struct nameidata *nd, if (open_flag & O_CREAT) { if ((open_flag & O_EXCL) && !(file->f_mode & FMODE_CREATED)) return -EEXIST; - if (d_is_dir(nd->path.dentry)) + if (!(open_flag & O_DIRECTORY) && d_is_dir(nd->path.dentry)) return -EISDIR; + error =3D may_create_in_sticky(idmap, nd, d_backing_inode(nd->path.dentry)); if (unlikely(error)) @@ -5060,7 +5113,7 @@ struct file *dentry_create(struct path *path, int fla= gs, umode_t mode, path->dentry =3D dir; mode =3D vfs_prepare_mode(idmap, dir_inode, mode, S_IALLUGO, S_IFREG); =20 - create_error =3D may_o_create(idmap, path, dentry, mode); + create_error =3D may_o_create(idmap, path, dentry, flags, mode); if (create_error) flags &=3D ~O_CREAT; =20 @@ -5230,6 +5283,7 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename,= umode_t, mode, unsigned, d return filename_mknodat(AT_FDCWD, name, mode, dev); } =20 +/* Returns the dentry to use (not NULL) or -E on error */ static inline struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *idmap, struct inode *di= r, struct dentry *dentry, umode_t mode, diff --git a/fs/open.c b/fs/open.c index 408925d7bd0b..9121aece78bc 100644 --- a/fs/open.c +++ b/fs/open.c @@ -1190,29 +1190,30 @@ inline int build_open_flags(const struct open_how *= how, struct open_flags *op) if (WILL_CREATE(flags)) { if (how->mode & ~S_IALLUGO) return -EINVAL; - op->mode =3D how->mode | S_IFREG; + if (O_IS_MKDIR(flags)) + op->mode =3D how->mode | S_IFDIR; + else + op->mode =3D how->mode | S_IFREG; } else { if (how->mode !=3D 0) return -EINVAL; op->mode =3D 0; } =20 - /* - * Block bugs where O_DIRECTORY | O_CREAT created regular files. - * Note, that blocking O_DIRECTORY | O_CREAT here also protects - * O_TMPFILE below which requires O_DIRECTORY being raised. - */ - if ((flags & (O_DIRECTORY | O_CREAT)) =3D=3D (O_DIRECTORY | O_CREAT)) - return -EINVAL; - /* Now handle the creative implementation of O_TMPFILE. */ if (flags & __O_TMPFILE) { /* * In order to ensure programs get explicit errors when trying * to use O_TMPFILE on old kernels we enforce that O_DIRECTORY - * is raised alongside __O_TMPFILE. + * is raised alongside __O_TMPFILE, but without O_CREAT. The + * reason for disallowing O_CREAT|O_TMPFILE is that + * O_DIRECTORY|O_CREAT used to work and created a regular file + * if nothing existed at the open path. Hence, allowing the + * combination would have caused O_CREAT|O_TMPFILE to create a + * regular (non-temporary) file on old kernels, while the caller + * would believe they created an actual O_TMPFILE. */ - if (!(flags & O_DIRECTORY)) + if (!(flags & O_DIRECTORY) || (flags & O_CREAT)) return -EINVAL; if (!(acc_mode & MAY_WRITE)) return -EINVAL; @@ -1270,6 +1271,8 @@ inline int build_open_flags(const struct open_how *ho= w, struct open_flags *op) op->intent =3D flags & O_PATH ? 0 : LOOKUP_OPEN; =20 if (flags & O_CREAT) { + if ((flags & O_DIRECTORY) && (acc_mode & MAY_WRITE)) + return -EISDIR; op->intent |=3D LOOKUP_CREATE; if (flags & O_EXCL) { op->intent |=3D LOOKUP_EXCL; diff --git a/include/linux/fcntl.h b/include/linux/fcntl.h index 6ad6b9e7a226..204e16bbe263 100644 --- a/include/linux/fcntl.h +++ b/include/linux/fcntl.h @@ -30,6 +30,12 @@ */ #define __O_REGULAR (1 << 30) =20 +#define O_MKDIR_MASK (O_CREAT | O_DIRECTORY) +static inline bool O_IS_MKDIR(unsigned int flags) +{ + return (flags & O_MKDIR_MASK) =3D=3D O_MKDIR_MASK; +} + /* List of all valid flags for the how->resolve argument: */ #define VALID_RESOLVE_FLAGS \ (RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | \ --=20 2.55.0 From nobody Sat Jul 25 22:33:15 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.184]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1FA63399356 for ; Sun, 12 Jul 2026 17:55:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.184 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878909; cv=none; b=GdmileqwdgcxzGjL+rJuZlnMvu3dxwVn9b278m0I1rndCTxHTJVuVk2iENuTjRdOuDtPKEfo8f7RZ0bFpuTmPoRE19JcBLcp6etoTGp9J2UT5HNqXXFFZLOnz1lUu1/xDkXEA8lF/h1+7mljElwBPkxPO9JHNmceA8mh1DuEejI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878909; c=relaxed/simple; bh=E4Pb6p53miVEFMWyKU+KSkiVVIR56GuI60a8mVAEwK4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Iuf+bv9xt903yJeCapWK0UF0Armxu2BPCVSmUu1c4GlhDMlB7xm7+Fp7eacVbjIEwPd70+OURpCdcmv0gZOUF0+mKgVh2a9OE8Ura3jTwW+FY7qtc3C6YbnIlXy1e8uS1EC81Fa8fceTjngj4ugNlcTbKxtmewHvfMP1X5Gijg4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=cQ28XVFv; arc=none smtp.client-ip=195.121.94.184 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="cQ28XVFv" X-KPN-MessageId: ccfaf910-7e1a-11f1-a59f-005056994fde Received: from smtp.kpnmail.nl (unknown [10.31.155.5]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id ccfaf910-7e1a-11f1-a59f-005056994fde; Sun, 12 Jul 2026 19:54:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=hN03yIEJ2j1fBq9AKp5W2RNSMHZOTRTubd4pHVxaK1E=; b=cQ28XVFvptX9KTlMwqeS9diS6lFKFMuuJd8A4Cwe86XThklUSkGfpvSGaGMPtEHTtuvO8Vw80QazD EKBv1CNEjIT1EDel/Sp6s6zQCandnyvWCfnNmCdsyJTcIGhYSOsWEUk/h8muh6NmuxJyUP7gxmE6c8 xPPXXtLd8uZzKFweAbVU8S793J2koB+qWbz+JwEPbkL/r1RLf2rxwn7WSIWXb4+hvx2VqKk/HjDfs3 T0YEHfEuc34eCWj0mWWAzgm2QJ1AP1N3859PXNJ3LXqqS4ALKSKNn2CB5nvQzBV1wNiWUwTpVslaDT s/O5ZBw035Z5x6qsTJurVnQyt2Hmhwg== X-KPN-MID: 33|BbU/Iet9AWXEmYEYS5qmNIX8vYwc0lVtvWqeWuZwRwnnFgM+sKidN0FM4sek7Xs Iytqt8FKSCg8FqwHLkzngQ1aruFxt19rGXY+fSqJ07cE= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|PK6+2z7+UE388LuI1NxigM2Em/c+H6BXWHto72qif/CvJSFxYT4At26Lm9WMSPn yK6KUhFYfoGEPIIngecxzvw== Received: from daedalus.home (unknown [178.230.226.124]) by smtp.xs4all.nl (Halon) with ESMTPSA id cccb8e77-7e1a-11f1-87f7-00505699b758; Sun, 12 Jul 2026 19:54:59 +0200 (CEST) From: Jori Koolstra To: Alexander Viro , Christian Brauner , Aleksa Sarai , NeilBrown Cc: Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH 07/10] vfs: move O_IS_MKDIR check from lookup_open() into individual filesystems Date: Sun, 12 Jul 2026 19:55:36 +0200 Message-ID: <20260712175539.1565444-8-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260712175539.1565444-1-jkoolstra@xs4all.nl> References: <20260712175539.1565444-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Individual filesystems that implement ->atomic_open() need to get the chance to implement O_CREAT|O_DIRECTORY or not, rather than decide this at the VFS level in lookup_open(). Signed-off-by: Jori Koolstra --- fs/9p/vfs_inode.c | 3 +++ fs/9p/vfs_inode_dotl.c | 3 +++ fs/ceph/file.c | 3 +++ fs/fuse/dir.c | 3 +++ fs/gfs2/inode.c | 3 +++ fs/namei.c | 3 --- fs/nfs/dir.c | 6 ++++++ fs/smb/client/dir.c | 3 +++ fs/vboxsf/dir.c | 3 +++ 9 files changed, 27 insertions(+), 3 deletions(-) diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 5783d0336f96..bb555e5ba261 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -777,6 +777,9 @@ v9fs_vfs_atomic_open(struct inode *dir, struct dentry *= dentry, struct inode *inode; int p9_omode; =20 + if (O_IS_MKDIR(flags)) + flags &=3D ~O_CREAT; + if (d_in_lookup(dentry)) { struct dentry *res =3D v9fs_vfs_lookup(dir, dentry, 0); if (res || d_really_is_positive(dentry)) diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index f7396d20cb6c..ff3facb420c2 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -239,6 +239,9 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct den= try *dentry, struct v9fs_session_info *v9ses; struct posix_acl *pacl =3D NULL, *dacl =3D NULL; =20 + if (O_IS_MKDIR(flags)) + flags &=3D ~O_CREAT; + if (d_in_lookup(dentry)) { struct dentry *res =3D v9fs_vfs_lookup(dir, dentry, 0); if (res || d_really_is_positive(dentry)) diff --git a/fs/ceph/file.c b/fs/ceph/file.c index 71161f2b2151..e79abdbd9f61 100644 --- a/fs/ceph/file.c +++ b/fs/ceph/file.c @@ -811,6 +811,9 @@ int ceph_atomic_open(struct inode *dir, struct dentry *= dentry, dir, ceph_vinop(dir), dentry, dentry, d_unhashed(dentry) ? "unhashed" : "hashed", flags, mode); =20 + if (O_IS_MKDIR(flags)) + flags &=3D ~O_CREAT; + if (dentry->d_name.len > NAME_MAX) return -ENAMETOOLONG; =20 diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 0e2a1039fa43..e01869565d5e 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -935,6 +935,9 @@ static int fuse_atomic_open(struct inode *dir, struct d= entry *entry, struct mnt_idmap *idmap =3D file_mnt_idmap(file); struct fuse_conn *fc =3D get_fuse_conn(dir); =20 + if (O_IS_MKDIR(flags)) + flags &=3D ~O_CREAT; + if (fuse_is_bad(dir)) return -EIO; =20 diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 8a77794bbd4a..9859ae0341a9 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1387,6 +1387,9 @@ static int gfs2_atomic_open(struct inode *dir, struct= dentry *dentry, { bool excl =3D !!(flags & O_EXCL); =20 + if (O_IS_MKDIR(flags)) + flags &=3D ~O_CREAT; + if (d_in_lookup(dentry)) { struct dentry *d =3D __gfs2_lookup(dir, dentry, file); if (file->f_mode & FMODE_OPENED) { diff --git a/fs/namei.c b/fs/namei.c index b37d2b0826b3..ffc63a6960f9 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4483,9 +4483,6 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, if (unlikely(IS_DEADDIR(dir_inode))) return ERR_PTR(-ENOENT); =20 - if (create_dir && dir_inode->i_op->atomic_open) - open_flag &=3D ~O_CREAT; - file->f_mode &=3D ~FMODE_CREATED; dentry =3D d_lookup(dir, &nd->last); for (;;) { diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index c7b723c18620..c815d9aa9b69 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2121,6 +2121,9 @@ int nfs_atomic_open(struct inode *dir, struct dentry = *dentry, dfprintk(VFS, "NFS: atomic_open(%s/%llu), %pd\n", dir->i_sb->s_id, dir->i_ino, dentry); =20 + if (O_IS_MKDIR(open_flags)) + open_flags &=3D ~O_CREAT; + err =3D nfs_check_flags(open_flags); if (err) return err; @@ -2313,6 +2316,9 @@ int nfs_atomic_open_v23(struct inode *dir, struct den= try *dentry, */ int error =3D 0; =20 + if (O_IS_MKDIR(open_flags)) + open_flags &=3D ~O_CREAT; + if (dentry->d_name.len > NFS_SERVER(dir)->namelen) return -ENAMETOOLONG; =20 diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 88a4a1787ff0..75bf86cc0612 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -538,6 +538,9 @@ int cifs_atomic_open(struct inode *dir, struct dentry *= direntry, if (unlikely(cifs_forced_shutdown(cifs_sb))) return smb_EIO(smb_eio_trace_forced_shutdown); =20 + if (O_IS_MKDIR(oflags)) + oflags &=3D ~O_CREAT; + /* * Posix open is only called (at lookup time) for file create now. For * opens (rather than creates), because we do not know if it is a file diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c index c5bd3271aa96..77ae2f696fd4 100644 --- a/fs/vboxsf/dir.c +++ b/fs/vboxsf/dir.c @@ -318,6 +318,9 @@ static int vboxsf_dir_atomic_open(struct inode *parent,= struct dentry *dentry, u64 handle; int err; =20 + if (O_IS_MKDIR(flags)) + flags &=3D ~O_CREAT; + if (d_in_lookup(dentry)) { struct dentry *res =3D vboxsf_dir_lookup(parent, dentry, 0); if (res || d_really_is_positive(dentry)) --=20 2.55.0 From nobody Sat Jul 25 22:33:15 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.186]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 51B2C370D6B for ; Sun, 12 Jul 2026 17:55:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.186 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878904; cv=none; b=ult7HaE4U6I961E1fGxKwOJQQTLaNX0tOM+3eMnuC1WvPgQOhM/WplCIx8dwtEGjAHwmZyLrLMR6I87vEMTE5feM9IOutrdZb9upWTqcRoASTtHr4zMIgH4Yw5PcvPCtYSYxqMdptzBOkE10dNwi+cPakaXNXM44yUN+/vNmn3c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878904; c=relaxed/simple; bh=B/ssqFivUuScnRyl1antmB5qUnloF8uykezthK1XEAA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OxrrB5wmAVyub0DNutMVJ0EdrpUm5QyPTa2PY0AFBuskHIvXGZVy4WNi2GjggN4WSW56PutZuZsT+Q6cjgfyLFElvHTaWX8K/fUJdidXMvQ58qphIrKS0qER8sfmWM5OIs79ylDgxtnBExzvoqiKSkRlAaJEpObfAUqISGgW/uQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=WnW9PB9E; arc=none smtp.client-ip=195.121.94.186 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="WnW9PB9E" X-KPN-MessageId: cdb89ecb-7e1a-11f1-bfba-00505699b430 Received: from smtp.kpnmail.nl (unknown [10.31.155.5]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id cdb89ecb-7e1a-11f1-bfba-00505699b430; Sun, 12 Jul 2026 19:55:01 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=6ER9B9mqi/tHGjhbZgfWDC1U1E3LU23q26LPDnjE9Ss=; b=WnW9PB9EDBVIbd0jz+N+CVleUzBOWuylHPaO+daMTMKF4YHfJP9O2A6bPWPpZNOTatG9eFlLubmZz +BO3Qh4x/YFvLbTxXvD+pjj3MOxvACiba6CdOtPxDMnqx6fOxLxl1/W5guE+xKzKFFBArm2qN9KAlf 3mNFSRA42ZdlIYshHvPw2Cp/v91zn9RFw8mybjUQ/7cPCjy06FlXmUK9j4ikJk/+ZUwYxhEqzzLP7c MbWub036ZastQ6scXIpP6ylvCg+CcMlvLYk6KNaDz0t+N9m+wUsn3bqRmlr0FvrvIUWZff64wcWcxX FDoz4liQYuauFu6yf1oVK8ln56QIieQ== X-KPN-MID: 33|FVK+vc0Vy30CdWdPkqReXYKRZDzzaQU+1iBFgEnrVsexU6wcf1CZDxXGcYsO9ly jlm3SkTBz9ATMuihmFZ5vg5Li1jId9TNpyEQYocc5elY= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|feovoX9SMsYrLzaiXyATpJF5uijiqWRS/9kBbAHKbTwaUYkiNBnEOysFLJ8xOQ2 SDkve/DefN3c0Pq/C04Dqow== Received: from daedalus.home (unknown [178.230.226.124]) by smtp.xs4all.nl (Halon) with ESMTPSA id cd81035e-7e1a-11f1-87f7-00505699b758; Sun, 12 Jul 2026 19:55:00 +0200 (CEST) From: Jori Koolstra To: Alexander Viro , Christian Brauner , Aleksa Sarai , NeilBrown Cc: Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH 08/10] vfs: refuse O_CREAT for directories through a dangling symlink Date: Sun, 12 Jul 2026 19:55:37 +0200 Message-ID: <20260712175539.1565444-9-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260712175539.1565444-1-jkoolstra@xs4all.nl> References: <20260712175539.1565444-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" open(O_CREAT) without O_EXCL follows a trailing symlink and, when the symlink target does not exist, creates it. Refuse to create through a dangling symlink for directories. In lookup_open() a negative target reached with nd->depth > 0 was arrived at by following a trailing symlink; since the dentry is negative the symlink is dangling. Set create_error to -EEXIST in that case (matching the errno returned by mkdir(2).) Reusing the existing create_error path strips O_CREAT for both the generic and ->atomic_open create paths and only reports the error when the target is actually negative. Thus opening an existing target through a symlink, interior symlinks, and O_EXCL (which never follows the trailing link) are all unaffected. Suggested-by: Christian Brauner (Amutable) Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/namei.c b/fs/namei.c index ffc63a6960f9..7d2f49b59b5c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4532,6 +4532,11 @@ static struct dentry *lookup_open(struct nameidata *= nd, struct file *file, dentry, open_flag, mode); else create_error =3D -EROFS; + /* Refuse to create a directory through a dangling (trailing) + * symlink. For regular files this has been allowed historically + * on O_CREAT without O_EXCL. */ + if (unlikely(nd->depth) && create_dir && !create_error) + create_error =3D -EEXIST; } if (create_error) open_flag &=3D ~O_CREAT; --=20 2.55.0 From nobody Sat Jul 25 22:33:15 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.183]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0F51D3AA195 for ; Sun, 12 Jul 2026 17:55:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.183 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878911; cv=none; b=k85UwOTrlz2RjehBRv1AKz8jhAl08F2stZq80TpwnqMWTx7t9GdcAxaDPV8EVUeYYpbo8bIUyOGXlJiAmxaW8nJanHLvZoIK28ajrN1BZGAgG5F6pq0rEeJAzK2VWgOf4I5gUEYEieo4THobLmjSkiVOhd81jZl+RTnRvGROVX8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878911; c=relaxed/simple; bh=0yOpKf6tauTKVrZ3MskO8jxoL/B/foDdZEWjYmHc/co=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=uF3vd1Y7uPwpAqbliqvZbuQIOZ9HN5XjYm1zQh3BZoKk0iyTv/MnQK2eIALUP41XxxhsKR/RugRHQX1sM2cSAjdN+7rDn0rWuQJ39GJgbMvb8WpuyQHOVp/qqmp1cGr4xotuxXdZ6pFLciYt6i7M89AoGEULUkmcJVr0G+RHQXQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=BtMhTPfs; arc=none smtp.client-ip=195.121.94.183 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="BtMhTPfs" X-KPN-MessageId: ce83ac83-7e1a-11f1-8f54-005056992ed3 Received: from smtp.kpnmail.nl (unknown [10.31.155.5]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id ce83ac83-7e1a-11f1-8f54-005056992ed3; Sun, 12 Jul 2026 19:55:02 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=0dXj08FzOLdO3c5dt6t8xpXist1W3wr1WmEpW5zQyPg=; b=BtMhTPfsB2KP2tLX5IJ1PaTxgiFRLx6Y+xtFGrISdc3LBfUVy9SSTGoQDgvYYSXoM+BcMzQmAICTv V5K07OxKCIdHMyVHIHu1IVZL3jtib99trnkhKd5gGyk5Ec7nqrQ8cqEW5MadSCHTFZiHccUImB/Aqy yNYOzXtQQmHFgAPlIr0tcrqIczaRDZxWbyY8nCUzrtWH6c9Z8huFBbQjxD4W/ExQdWQUX9uOndzWfQ rUJjytVyLYICpqW4YMkIcw+bXjj4P4T/io9qkfPQth1It3zCvUlX6dKPLHIclRkcNzkvj/JbvLkYHk JpQfzUNGsh27K4dVMosOw+I6DuV7ggw== X-KPN-MID: 33|x1L+9/1Div3P7pMGcwlQkZwmoewgAYxtrY919685D/O4Kbaq/nXJhKt2KSYhIL1 X41IdAC9tAWA/avlkJ0ZJ6ts8Ga4CnTAfFXTaH2EYAKM= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|FZa9YyVh1LsFcCg/hzzoJICzqs3c9cOZZTY1Z5/y1Xr5mHZ+aBe/UONFgwPdsUF E5QVwHdN8U1cr2s5oYPIIpw== Received: from daedalus.home (unknown [178.230.226.124]) by smtp.xs4all.nl (Halon) with ESMTPSA id ce57a63f-7e1a-11f1-87f7-00505699b758; Sun, 12 Jul 2026 19:55:02 +0200 (CEST) From: Jori Koolstra To: Alexander Viro , Christian Brauner , Aleksa Sarai , NeilBrown Cc: Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH 09/10] vfs: short-circuit MAY_WRITE access for O_DIRECTORY opens Date: Sun, 12 Jul 2026 19:55:38 +0200 Message-ID: <20260712175539.1565444-10-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260712175539.1565444-1-jkoolstra@xs4all.nl> References: <20260712175539.1565444-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Requesting write access on a directory can never succeed. Rather than performing a path-walk to determine whether the target is actually a directory (-EISDIR) or not (-ENOTDIR), or does not exist (-ENOENT), etc., we short-circuit to -ENOTDIR. Currently O_WRONLY for directories is only blocked in may_open(), which happens after we have the inode for the target, so after any create via O_CREAT|O_DIRECTORY. The advantage of short-circuiting is that we don't have to add even more logic to lookup_open() to differentiate -EISDIR/-ENOTDIR. Also, for filesystems that define atomic_open(), handling this cannot even be done at the VFS level, as we can't know ahead of calling ->atomic_open() what the result of the lookup is. Suggested-by: Christian Brauner (Amutable) Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/open.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/fs/open.c b/fs/open.c index 9121aece78bc..87e8864d9480 100644 --- a/fs/open.c +++ b/fs/open.c @@ -1270,9 +1270,16 @@ inline int build_open_flags(const struct open_how *h= ow, struct open_flags *op) =20 op->intent =3D flags & O_PATH ? 0 : LOOKUP_OPEN; =20 + /* + * Requesting write access on a directory can never succeed. Rather + * than performing a path-walk to determine whether the target is + * actually a directory (-EISDIR) or not (-ENOTDIR), we short-circuit + * to -ENOTDIR. + */ + if ((flags & O_DIRECTORY) && !(flags & __O_TMPFILE) && (acc_mode & MAY_WR= ITE)) + return -ENOTDIR; + if (flags & O_CREAT) { - if ((flags & O_DIRECTORY) && (acc_mode & MAY_WRITE)) - return -EISDIR; op->intent |=3D LOOKUP_CREATE; if (flags & O_EXCL) { op->intent |=3D LOOKUP_EXCL; --=20 2.55.0 From nobody Sat Jul 25 22:33:15 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.185]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9EDCF385D69 for ; Sun, 12 Jul 2026 17:55:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.185 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878907; cv=none; b=PybpOjYJzszDnwS7XKf47p/QMXLVj2M+3vW8L/Rn03wxCTAP0LTtHhOhPNsA/cy1xXQRhlAeRfa65WzPApP/MOoFKDbY4Ps8S9BRgM//DyrphRwnvgX4YHVsnSezDWv8+oiwlgPyzfGx0+D81AKnCyedRsO+VpYxB2beYo+Y/fs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783878907; c=relaxed/simple; bh=PjI0OhJSl/5PB11eJGelAcZelEuXgtMIOFIvqC8qWJU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=cnkpEVmH674k1UHIkX2bN2x3NwjboYLNPEW0Qs7lFyGgPtY3FW0OQ0B9hV0oiq/L+DflmjnJ523DvCbv4MGLm4ZKgoyIFvlyStuyWx0AQtLXXqgao1fN9Opr8wuPfQFZwVw8f3oBh9JMosMok9Yb+WVIvN7VKiPcfvKqGHHvY4o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=qN8iUToH; arc=none smtp.client-ip=195.121.94.185 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="qN8iUToH" X-KPN-MessageId: cf3c0400-7e1a-11f1-9e8e-005056999439 Received: from smtp.kpnmail.nl (unknown [10.31.155.5]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id cf3c0400-7e1a-11f1-9e8e-005056999439; Sun, 12 Jul 2026 19:55:03 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=tXVBxS6hQiHr6/o2NcEDkVQ17EXP1uVe9M4uweG+T1o=; b=qN8iUToHjjyrw+K/KuWTUM+LNcuWCLDLBxYQnR+gut7X+YeDmXHoSgcHmsERJN60dAyh6qThwIht0 Kk+Qij4b3L6pxu0QURWJRMwGFmXnmBclBQq9pv9z7MJcvwXPZPudyu01IVopWJTYKXWy4kj2bftVyi QOU7RYX4ovTbOMHuRd9CVs+2N2cPTPNqhnUuzI9W+fUhUbXfVhi+nMuwCl94jmCjQgUC5Z3W+Vl18I 4OO1Aj2nXQE1PtHgc6GddFMI02Eem3PtDqMZ26DeyLiJ8VgRY6DY3SzFgfJqZ4bC0GTzz9+Ltnhlxw Q5fS+YRDpVXWKfr4KjTXehmvbar8l+w== X-KPN-MID: 33|fVPvvCTUWInFwMEfjgqJhDWztQI1se/6UUqp/n1vkJ/dhi/EdfZV6tdjq3APKYu CwbF7BpvAn330OdYfs+T5JbvktTNOLed7D286B/8g99s= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|DO70rQERG5wK1DcR/LTPDYYFY3+Ju/qshoAK7SIJY/MUr55HOGRet8v1s0pGvCQ HcoexFzFJ7MeRyIg8mUNQdg== Received: from daedalus.home (unknown [178.230.226.124]) by smtp.xs4all.nl (Halon) with ESMTPSA id cf109cb7-7e1a-11f1-87f7-00505699b758; Sun, 12 Jul 2026 19:55:03 +0200 (CEST) From: Jori Koolstra To: Alexander Viro , Christian Brauner , Aleksa Sarai , NeilBrown Cc: Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Jori Koolstra Subject: [PATCH 10/10] selftest: add tests for open*(O_CREAT|O_DIRECTORY) Date: Sun, 12 Jul 2026 19:55:39 +0200 Message-ID: <20260712175539.1565444-11-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260712175539.1565444-1-jkoolstra@xs4all.nl> References: <20260712175539.1565444-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add some tests for the new valid O_CREAT|O_DIRECTORY flag combination for open*(2) to test compliance and to showcase its behaviour. Signed-off-by: Jori Koolstra --- .../testing/selftests/filesystems/.gitignore | 1 + tools/testing/selftests/filesystems/Makefile | 2 +- .../filesystems/open_o_creat_o_dir.c | 201 ++++++++++++++++++ .../testing/selftests/filesystems/wrappers.h | 11 + 4 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/open_o_creat_o_dir.c diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing= /selftests/filesystems/.gitignore index a78f894157de..d779a7945cf8 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +open_o_creat_o_dir dnotify_test devpts_pts fclog diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/s= elftests/filesystems/Makefile index a7ec2ba2dd83..b60950f8b15c 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 =20 CFLAGS +=3D $(KHDR_INCLUDES) -TEST_GEN_PROGS :=3D devpts_pts file_stressor anon_inode_test kernfs_test f= clog +TEST_GEN_PROGS :=3D open_o_creat_o_dir devpts_pts file_stressor anon_inode= _test kernfs_test fclog TEST_GEN_PROGS +=3D idmapped_tmpfile TEST_GEN_PROGS_EXTENDED :=3D dnotify_test =20 diff --git a/tools/testing/selftests/filesystems/open_o_creat_o_dir.c b/too= ls/testing/selftests/filesystems/open_o_creat_o_dir.c new file mode 100644 index 000000000000..0705205c1f92 --- /dev/null +++ b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include + +#include "kselftest_harness.h" +#include "wrappers.h" + +#define openat_o_mkdir_checked_flags(dfd, pathname, flags) ({ \ + struct stat __st; \ + int __fd =3D openat_o_mkdir(dfd, pathname, flags, S_IRWXU); \ + ASSERT_GE(__fd, 0); \ + ASSERT_EQ(fstat(__fd, &__st), 0); \ + EXPECT_TRUE(S_ISDIR(__st.st_mode)); \ + __fd; \ +}) + +#define openat_o_mkdir_checked(dfd, pathname) \ + openat_o_mkdir_checked_flags(dfd, pathname, 0) + +FIXTURE(open_o_creat_o_dir) { + char dirpath[PATH_MAX]; + int dfd; +}; + +FIXTURE_SETUP(open_o_creat_o_dir) +{ + strcpy(self->dirpath, "/tmp/open_o_creat_o_dir_test.XXXXXX"); + ASSERT_NE(mkdtemp(self->dirpath), NULL); + self->dfd =3D open(self->dirpath, O_DIRECTORY); + ASSERT_GE(self->dfd, 0); +} + +FIXTURE_TEARDOWN(open_o_creat_o_dir) +{ + close(self->dfd); + rmdir(self->dirpath); +} + +/* Does open_o_creat_o_dir return a fd at all? */ +TEST_F(open_o_creat_o_dir, returns_fd) +{ + int fd =3D openat_o_mkdir_checked(self->dfd, "newdir"); + EXPECT_EQ(close(fd), 0); + EXPECT_EQ(unlinkat(self->dfd, "newdir", AT_REMOVEDIR), 0); +} + +/* The fd must refer to the directory that was just created. */ +TEST_F(open_o_creat_o_dir, fd_is_created_dir) +{ + int fd; + struct stat st_via_fd, st_via_path; + char path[PATH_MAX]; + + fd =3D openat_o_mkdir_checked(self->dfd, "checkdir"); + + ASSERT_EQ(fstat(fd, &st_via_fd), 0); + + snprintf(path, sizeof(path), "%s/checkdir", self->dirpath); + ASSERT_EQ(stat(path, &st_via_path), 0); + + EXPECT_EQ(st_via_fd.st_ino, st_via_path.st_ino); + EXPECT_EQ(st_via_fd.st_dev, st_via_path.st_dev); + + EXPECT_EQ(close(fd), 0); + EXPECT_EQ(rmdir(path), 0); +} + +/* Missing parent component must fail with ENOENT. */ +TEST_F(open_o_creat_o_dir, enoent_missing_parent) +{ + EXPECT_EQ(openat_o_mkdir(self->dfd, "nonexistent/child", 0, S_IRWXU), -1); + EXPECT_EQ(errno, ENOENT); +} + +/* An invalid dfd must fail with EBADF. */ +TEST_F(open_o_creat_o_dir, ebadf) +{ + EXPECT_EQ(openat_o_mkdir(FD_INVALID, "badfdir", 0, S_IRWXU), -1); + EXPECT_EQ(errno, EBADF); +} + +/* A dfd that points to a file (not a directory) must fail with ENOTDIR. */ +TEST_F(open_o_creat_o_dir, enotdir_dfd) +{ + int file_fd; + + file_fd =3D openat(self->dfd, "file", + O_CREAT | O_WRONLY, S_IRWXU); + ASSERT_GE(file_fd, 0); + + EXPECT_EQ(openat_o_mkdir(file_fd, "subdir", 0, S_IRWXU), -1); + EXPECT_EQ(errno, ENOTDIR); + + EXPECT_EQ(close(file_fd), 0); + EXPECT_EQ(unlinkat(self->dfd, "file", 0), 0); +} + +/* + * O_EXCL together with O_CREAT|O_DIRECTORY should succeed if the target + * directory does not yet exist. After directory creation, repeating this + * call must fail with EEXIST. + */ +TEST_F(open_o_creat_o_dir, o_excl_eexist) +{ + int excldir_fd; + + excldir_fd =3D openat_o_mkdir_checked_flags(self->dfd, "excldir", O_EXCL); + + EXPECT_EQ(openat_o_mkdir(excldir_fd, ".", O_EXCL, S_IRWXU), -1); + EXPECT_EQ(errno, EEXIST); + + EXPECT_EQ(close(excldir_fd), 0); + EXPECT_EQ(unlinkat(self->dfd, "excldir", AT_REMOVEDIR), 0); +} + +/* + * O_CREAT|O_DIRECTORY on a path that already exists as a regular file + * must fail with ENOTDIR. + */ +TEST_F(open_o_creat_o_dir, existing_file_enotdir) +{ + int file_fd; + + file_fd =3D openat(self->dfd, "regfile", + O_CREAT | O_WRONLY, S_IRWXU); + ASSERT_GE(file_fd, 0); + EXPECT_EQ(close(file_fd), 0); + + EXPECT_EQ(openat_o_mkdir(self->dfd, "regfile", 0, S_IRWXU), -1); + EXPECT_EQ(errno, ENOTDIR); + + EXPECT_EQ(unlinkat(self->dfd, "regfile", 0), 0); +} + +/* + * O_CREAT|O_DIRECTORY combined with a writable access mode must be + * rejected: a directory cannot be opened for writing. + */ +TEST_F(open_o_creat_o_dir, rejects_writable_acc_mode) +{ + EXPECT_EQ(openat_o_mkdir(self->dfd, "rdwrdir", O_RDWR, S_IRWXU), -1); + EXPECT_EQ(errno, ENOTDIR); + /* Clean up if the kernel created the directory anyway. */ + unlinkat(self->dfd, "rdwrdir", AT_REMOVEDIR); +} + +/* + * openat(O_CREAT|O_DIRECTORY) with a trailing slash should work. + */ +TEST_F(open_o_creat_o_dir, trailing_slash) +{ + int fd =3D openat_o_mkdir_checked(self->dfd, "newdir/"); + EXPECT_EQ(close(fd), 0); + EXPECT_EQ(unlinkat(self->dfd, "newdir", AT_REMOVEDIR), 0); +} + +/* + * openat(O_CREAT) with a trailing slash but without O_DIRECTORY + * must fail with EISDIR and must not create anything at the path. + */ +TEST_F(open_o_creat_o_dir, trailing_slash_no_o_dir) +{ + int fd; + struct stat st; + + fd =3D openat(self->dfd, "trailing/", O_CREAT | O_WRONLY, S_IRWXU); + EXPECT_EQ(fd, -1); + EXPECT_EQ(errno, EISDIR); + + EXPECT_EQ(fstatat(self->dfd, "trailing", &st, 0), -1); + EXPECT_EQ(errno, ENOENT); + + /* Best-effort cleanup in case the kernel left a file behind. */ + if (fd >=3D 0) + close(fd); + unlinkat(self->dfd, "trailing", 0); +} + +/* + * The returned fd must be usable as a dfd for further *at() calls. + */ +TEST_F(open_o_creat_o_dir, fd_usable_as_dfd) +{ + int parent_fd, child_fd; + char path[PATH_MAX]; + + parent_fd =3D openat_o_mkdir_checked(self->dfd, "parent"); + child_fd =3D openat_o_mkdir_checked(parent_fd, "child"); + + EXPECT_EQ(close(child_fd), 0); + EXPECT_EQ(close(parent_fd), 0); + + snprintf(path, sizeof(path), "%s/parent/child", self->dirpath); + EXPECT_EQ(rmdir(path), 0); + snprintf(path, sizeof(path), "%s/parent", self->dirpath); + EXPECT_EQ(rmdir(path), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/wrappers.h b/tools/testing= /selftests/filesystems/wrappers.h index 420ae4f908cf..abe5b85cebdc 100644 --- a/tools/testing/selftests/filesystems/wrappers.h +++ b/tools/testing/selftests/filesystems/wrappers.h @@ -13,6 +13,10 @@ #define STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id = */ #endif =20 +#ifndef FD_INVALID +#define FD_INVALID -10009 +#endif + static inline int sys_fsopen(const char *fsname, unsigned int flags) { return syscall(__NR_fsopen, fsname, flags); @@ -105,4 +109,11 @@ static inline int sys_open_tree(int dfd, const char *f= ilename, unsigned int flag return syscall(__NR_open_tree, dfd, filename, flags); } =20 +static inline int openat_o_mkdir(int dfd, const char *pathname, + unsigned int flags, mode_t mode) +{ + return syscall(__NR_openat, dfd, pathname, + flags | O_DIRECTORY | O_CREAT, mode); +} + #endif --=20 2.55.0