From nobody Mon Jun 8 07:26:16 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.168]) (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 292CF392C36 for ; Sun, 31 May 2026 18:19:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.168 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251595; cv=none; b=L8gR/itMYMq1RoS92Xs+r6xl3SrNluxv9fiBL24bkmfb1lz0Lr7K/hx5COoVo7XtULraVisK1kaFDQZUNVGtrlYEa/avC2jU9ssTomEa6z+niWW/ZSJlRqP3kUWdJlLoobA4w3ZOQSNR2NEIFw9ZFtota9H5iQv5/5t4U0PanQE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251595; c=relaxed/simple; bh=+7BJ1ogU0JFbnVIXoaKvjjkF0neuKWvB0SAPcIB/oq8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MeAv4FrIEf4g2LvikYIMFWV9AmwKXWLA4IpYxa6KjJAkK612irUxhJg9zAVaTSpeBQTU0JAe6FTvKuu5GGAFdicuL5bx4+Gq15gmi+s6DHunYHl4JOR3fuWkrh1l8IDTm0/17gFYcmTtpc4SrrkzMrjbd1vfnyUuvM7qFGObTXo= 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=VyWV1Hkh; arc=none smtp.client-ip=195.121.94.168 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="VyWV1Hkh" X-KPN-MessageId: 4d461328-5d1d-11f1-92b3-005056aba152 Received: from smtp.kpnmail.nl (unknown [10.31.155.39]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id 4d461328-5d1d-11f1-92b3-005056aba152; Sun, 31 May 2026 20:19:45 +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=T4Ix4vnXQIcxzLjeDEVNR9mQKii3/QrnaG7EOxqFwgQ=; b=VyWV1HkhrN/72F2wK22gb0rfmDAx/wNgwTzFMJ5M/tvll0qGXYA69eL/1THoiT1iL055ktg9pWIDW B6uU5DLDACPJps4EsoT6gfsknwo4Cgux9UkPMG2EA1KvRw+7qSycCPRfzFr8D7QRACpHvP8ClOzXDJ zce+YEwr69V4TFyIr2+69lrt74r5vYwzOdsnbIiLV8edsvuhiEaziaaMckUiknKslXpMCHFPLz1d9k DWwyaHp0NlCbtXNW2xuSKTKGDshDkbwyfATl+x1XCqdBKqmOSde9Q96k9U3lhQVbG9n+IfQKkop5B0 5r6Xoi10ml7VaKSFZqgPf9VjFuV7UPA== X-KPN-MID: 33|weWFwkD64ApSqR2Hw8ZJ3Varu/pn5rwxVau+S/jJx3kkrqcxWcD+W3yex8LZCTP xaQe55rxwX4O2xsXRHNL3+N2b3dE7RrFTI4t4W0l9JfE= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|X5cdvvVLCicq1nayklEUPbm2J6R1vS39VHuS2PZyAUhfqxDXRZDgi/GKoQmOQUk xlCO7AVn7/FuB7jKXHuGHXQ== Received: from daedalus.home (unknown [178.227.141.146]) by smtp.xs4all.nl (Halon) with ESMTPSA id 4d10d6df-5d1d-11f1-8011-005056ab7447; Sun, 31 May 2026 20:19:45 +0200 (CEST) From: Jori Koolstra To: NeilBrown , Al Viro , Aleksa Sarai , Christian Brauner Cc: Jori Koolstra , Jan Kara , Jeff Layton , Mateusz Guzik , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Charles Mirabile Subject: [RFC PATCH v6 1/9] fs/namei.c: make lookup_last() use trailing_slashes() Date: Sun, 31 May 2026 20:20:37 +0200 Message-ID: <20260531182045.171711-2-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260531182045.171711-1-jkoolstra@xs4all.nl> References: <20260531182045.171711-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" Small cleanup to use the existing trailing_slashes() function in lookup_last(). Signed-off-by: Jori Koolstra --- fs/namei.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index c7fac83c9a85..137d9fffbe80 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2777,9 +2777,14 @@ static const char *path_init(struct nameidata *nd, u= nsigned flags) return s; } =20 +static inline bool trailing_slashes(struct nameidata *nd) +{ + return (bool)nd->last.name[nd->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)) nd->flags |=3D LOOKUP_FOLLOW | LOOKUP_DIRECTORY; =20 return walk_component(nd, WALK_TRAILING); @@ -4524,11 +4529,6 @@ 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; base-commit: 174914ea551314c52a61713b9c4bde9e42d48073 --=20 2.54.0 From nobody Mon Jun 8 07:26:16 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.170]) (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 A44FE394EA7 for ; Sun, 31 May 2026 18:20:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251656; cv=none; b=K3mCawZE1Tikb2hmmeREAGsILf0d+7IRo8XKUkKNfmX5W2bKWki2ex30Ywxs5lxLN4nx1J6v7p7//eGUyFlsBl9brq/pL/5GBRk+0GKm913qw2hngUeu+z2WhY9BAkvsEGRPeMdD8DiPvW8yL8OAEE04t5AQ8iqKfHH3t274o2A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251656; c=relaxed/simple; bh=ITbap9DPpzUHkoWoO5kTldCq5lcmwN4rGgz2nAF/sIU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pK5pMvemrDe4LUhuVDA/2O6jOwDWgi9RKvawO5XlMt0HfFmPJ46Wt/edSjFTMfK4v/KTsIfCmx0MEkTdFvNjDt3vnkY4RTNrDLUR5nURN5aDYCcJ5ekxZ2YlnPB+UteR9QSwHHSDBth9N/kow1qIfXQUU8Pq03YrRqoTg5YBxXM= 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=QuacYR24; arc=none smtp.client-ip=195.121.94.170 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="QuacYR24" X-KPN-MessageId: 4df95bf0-5d1d-11f1-8aa3-005056ab378f Received: from smtp.kpnmail.nl (unknown [10.31.155.39]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id 4df95bf0-5d1d-11f1-8aa3-005056ab378f; Sun, 31 May 2026 20:19:46 +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=DHKIaJvVawa9lI2EeU3t15iWZNFx9FU5KWyPNAo1oS4=; b=QuacYR24m6W+h4ijrivJRokP4hJ/XUJvq9cEd8MIAa0ZHSRov9ZsDdh0G8slC9bRzppbYfhKkuWtt bNN4ddSnIdS58kQ2Qb1vZ+SF+LVeepQAcNz2/8CoLm0MjBJkGcRbYbegGp0ylBeyPecIJI+yg7q2BC j5zuO0JgF+LXJxfrqTrjyO5bo/R0zlXFHgrvN4AKPGX5in1qVLRLdYDe3Ff7ALuuwxp7znaH9TJo6d zv75gSJKNWr3V60WqjQCR1gKcwkf4sWfqf7oRiQXdfVjaK9Q5prL+njSCf3sG0eyoBMdZYvDibb/tw TfThiGhqN/JQdSPd+gD2+WLGaeqv73Q== X-KPN-MID: 33|YAQHyWQ7JD6veSExEtykrt3oABei5IsTnABbCFH1PeXFt/yCsvhiMrd96/n7wM9 H88YrIToODmKYzI/3rt412RaGKIyb0SElFQj3ZGOmhUY= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|HxZLdPCzo2c+vmAoM2bqOsiTtT+IrmfZ/M6QLdVzvfT6hJuEflQk7sNmfojlFdL zPfOxWy4c7P5wS21oRyTjjA== Received: from daedalus.home (unknown [178.227.141.146]) by smtp.xs4all.nl (Halon) with ESMTPSA id 4db295d5-5d1d-11f1-8011-005056ab7447; Sun, 31 May 2026 20:19:46 +0200 (CEST) From: Jori Koolstra To: NeilBrown , Al Viro , Aleksa Sarai , Christian Brauner Cc: Jori Koolstra , Jan Kara , Jeff Layton , Mateusz Guzik , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Charles Mirabile Subject: [RFC PATCH v6 2/9] fs/namei.c: move create error && negative dentry case in lookup_open up Date: Sun, 31 May 2026 20:20:38 +0200 Message-ID: <20260531182045.171711-3-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260531182045.171711-1-jkoolstra@xs4all.nl> References: <20260531182045.171711-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" O_CREAT is stripped when create_error is set in lookup_open(), so when lookup does not return an inode, the case if (!dentry->d_inode && (open_flag & O_CREAT)) is always skipped. We can get rid of this cognitive step by handling the error case first. Signed-off-by: Jori Koolstra Reviewed-by: NeilBrown --- fs/namei.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 137d9fffbe80..4cd132fe3981 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4499,6 +4499,11 @@ static struct dentry *lookup_open(struct nameidata *= nd, struct file *file, } } =20 + if (unlikely(create_error) && !dentry->d_inode) { + error =3D create_error; + goto out_dput; + } + /* Negative dentry, just create the file */ if (!dentry->d_inode && (open_flag & O_CREAT)) { /* but break the directory lease first! */ @@ -4518,10 +4523,7 @@ static struct dentry *lookup_open(struct nameidata *= nd, struct file *file, if (error) goto out_dput; } - if (unlikely(create_error) && !dentry->d_inode) { - error =3D create_error; - goto out_dput; - } + return dentry; =20 out_dput: --=20 2.54.0 From nobody Mon Jun 8 07:26:16 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.169]) (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 2FA83233932 for ; Sun, 31 May 2026 18:19:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251593; cv=none; b=Bo25J4HngcpVZJ5OCzFMaAwTvU9T7pYA6lx+u80z8J6skzLeehGLBBO7j3j5/VhBJhEFy1Jz5Gh/wionZJet5N900E3gOeTfhGs9k1Ub407cXlHjwB5qTY7dWxtmS9QWEAZv+MGKnDHk2Z6YZsDT6//OQgL5ruedw6/Zuxw+2Y0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251593; c=relaxed/simple; bh=O3RfnIu+MAK/92YGT+49j2Q1Ah5PouvdP6nZzhBMXyk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mhgHcmvktpPVESoMVDXDJjIKVJC1diIN/8R5r6ihiuUB8k6BAnVW6t+dX78Z4SlW/MaX5YxNVHToKEOxnc6uiNoEO75895glbxVR7eRcsgRKw7zUsdKQDiXs+Per9Q/uiTfU2w0s2eokJdapnH3zLSVspiivvMCHlpbKI0Rl3ko= 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=Vm9+Jbma; arc=none smtp.client-ip=195.121.94.169 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="Vm9+Jbma" X-KPN-MessageId: 4ea49758-5d1d-11f1-b189-005056abad63 Received: from smtp.kpnmail.nl (unknown [10.31.155.39]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id 4ea49758-5d1d-11f1-b189-005056abad63; Sun, 31 May 2026 20:19:48 +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=QZTROv6wENpAHg79+FWzAw49rtJn58bwTufg2QrAj9o=; b=Vm9+JbmaB9032opZQ8fxc89auVA33LQXjN/w83P30m27yIAJQRPYDWPMmQjA7x+1JyNumKilFqoWY K4EQ7YXJAWMXVo7dV+Ykb2nx2TIjFHViUENHDIdru2/rONzZIMu5BBg7gX/eeGcz6fK07I5YBonTeC ea6ggJ8yWv3Z9vGJ60/FwgRcDfnakKuB7rQJ857EucMvKxHAdzsO5LFo+dl0eOYjJF+78DOnHCIUv/ XLrWOUYiPKSqOlClm0maRnGmd4m4R62oL4RqemTBtdb7XozGj6XPF6G/JlYSLBGHIvmIm1OOUKBCn4 PhNMrVocBDc/8xTbYoyQ8D2vOEc7vug== X-KPN-MID: 33|IpB8AipPg22g/Ae43XIiwdTtuvghkeSzE03S9wl9MVswdMMsziGdUzKDTKr3wCZ abjE7BRWy1pCYO2FGMP9GEZNU3i3W/yN2l/FvFutw/w0= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|zJYwD7DKxca0ADcKxYjnR6ltSgP7vdEmZCrVAl+TqKSm6TsYJhYZTiTWNeuFMDf O4ICRlXsmpk/N7ILpZ8IQew== Received: from daedalus.home (unknown [178.227.141.146]) by smtp.xs4all.nl (Halon) with ESMTPSA id 4e5b0a4f-5d1d-11f1-8011-005056ab7447; Sun, 31 May 2026 20:19:47 +0200 (CEST) From: Jori Koolstra To: NeilBrown , Al Viro , Aleksa Sarai , Christian Brauner Cc: Jori Koolstra , Jan Kara , Jeff Layton , Mateusz Guzik , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Charles Mirabile Subject: [RFC PATCH v6 3/9] vfs: prepare vfs_creat|mkdir_no_perm for reuse in lookup_open() Date: Sun, 31 May 2026 20:20:39 +0200 Message-ID: <20260531182045.171711-4-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260531182045.171711-1-jkoolstra@xs4all.nl> References: <20260531182045.171711-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(). Signed-off-by: Jori Koolstra Reviewed-by: NeilBrown --- fs/namei.c | 105 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 4cd132fe3981..2a35dd72ee96 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, bool excl) +{ + struct inode *dir =3D d_inode(dentry->d_parent); + int error; + + error =3D try_break_deleg(dir, di); + if (error) + return error; + + error =3D dir->i_op->create(idmap, dir, dentry, mode, excl); + 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, 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, /*excl=3D*/ true); } 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 @@ -4522,6 +4542,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); } =20 return dentry; @@ -4610,13 +4632,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 @@ -5051,13 +5069,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 { @@ -5209,6 +5220,39 @@ 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; + + error =3D -EMLINK; + if (max_links && dir->i_nlink >=3D max_links) + goto err; + + error =3D try_break_deleg(dir, di); + if (error) + goto err; + + de =3D dir->i_op->mkdir(idmap, dir, dentry, mode); + if (IS_ERR(de)) { + error =3D PTR_ERR(de); + goto err; + } + if (de) { + dput(dentry); + dentry =3D de; + } + fsnotify_mkdir(dir, dentry); + return dentry; + +err: + return ERR_PTR(error); +} + /** * vfs_mkdir - create directory returning correct dentry if possible * @idmap: idmap of the mount the inode was found from @@ -5236,7 +5280,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); @@ -5252,24 +5295,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) + de =3D vfs_mkdir_no_perm(idmap, dir, dentry, mode, delegated_inode); + if (IS_ERR(de)) { + error =3D PTR_ERR(de); goto err; - - error =3D try_break_deleg(dir, 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)) - goto err; - if (de) { - dput(dentry); - dentry =3D de; } - fsnotify_mkdir(dir, dentry); - return dentry; + return de; =20 err: end_creating(dentry); --=20 2.54.0 From nobody Mon Jun 8 07:26:16 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.168]) (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 F2406395AD3 for ; Sun, 31 May 2026 18:19:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.168 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251597; cv=none; b=hPAWh+KUNzyK+JinWRH4JtBk0N5sb8tviZ6faQFxVW1+64FOgmsaqCp/826tP1xOLTN6M5XNWIEYER8Qvb2Rb5R27w1N2SyGT07kl7mjM+7jPUliaRezLK83parRFYFGDb9hO9d9jsH55UsGVW7hfbmV/RW/h5hdxcRwS/8hjco= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251597; c=relaxed/simple; bh=p61vhvva4xdYOwHX26DLHiTY4b88UTDKWJk0s61v/Jk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Tl8F7m+BsjmBltyYcP3iJSnBE11QPJlJyhU1ipLAqGcZQLeyOAF6tM6pzxFy2WCnY0IPj7BeKPP/AC4/ceHgCbbSR84e0h734Exuzqmrfy1MWpi2Mp969vRsZPuL09GdkrePVzQOYFEmoRvIHbDtvtuaDXu7NenMqYKlA1fdrfg= 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=HrJqpDq/; arc=none smtp.client-ip=195.121.94.168 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="HrJqpDq/" X-KPN-MessageId: 4f23a6f1-5d1d-11f1-92b3-005056aba152 Received: from smtp.kpnmail.nl (unknown [10.31.155.39]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id 4f23a6f1-5d1d-11f1-92b3-005056aba152; Sun, 31 May 2026 20:19:48 +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=S/PcftfCfUTrWV5yABNt7y5wuIj0V4tgVvHpnpjZmfQ=; b=HrJqpDq/NHV0+0I5p3cepi3J8aXxEkG2/9ygCMhHzFCP7ESFr6GKpW5rhyA1c8GwXZBAOJlkLftnp K4Re0YNYX+UiQK0OoSlHK5ScxmRTudBqMEP9EkM4ba/xaParw7P2pYNYJHEiowcyX9aouoTAFKrxlK vJ/Z/gOQPQjBK8EypuKtm6ZW/gG7jCP7duqzR8ONKDNa4KasfdRrbJUps5Bajo92PloU/2A1WtoKLd 0xpsVFVd4avz1cA1KTntyeE0SS56uEfuKEpdjYcO9dq/cCogQFJ9vMXmiAjgY4+yxznPWMFG7a4CoY LRZSV694mjD/+URDTcmfv+9golEnzJQ== X-KPN-MID: 33|bU+JIveaSCmmjpRRMoCqXc9Rn2dhqiG9B2rXcuvvN6wJ2UsS/l3yfTZhqhspHbe HMKfTpiLzVmNMlX+Wuuf4YrN1TfXEowccHCETAYVgRlw= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|yfs3/GKz6NOsjg7nvEzO/EiSaWixgvnlg2tBc4L/CT1FSI+zF7yvqCyUcoiV7iA TZ1ccTeFSfwuTpbk/ekDhxw== Received: from daedalus.home (unknown [178.227.141.146]) by smtp.xs4all.nl (Halon) with ESMTPSA id 4ef12f7d-5d1d-11f1-8011-005056ab7447; Sun, 31 May 2026 20:19:48 +0200 (CEST) From: Jori Koolstra To: NeilBrown , Al Viro , Aleksa Sarai , Christian Brauner Cc: Jori Koolstra , Jan Kara , Jeff Layton , Mateusz Guzik , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Charles Mirabile Subject: [RFC PATCH v6 4/9] vfs: lookup_open(): move setting FMODE_CREATED up when calling create() Date: Sun, 31 May 2026 20:20:40 +0200 Message-ID: <20260531182045.171711-5-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260531182045.171711-1-jkoolstra@xs4all.nl> References: <20260531182045.171711-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. Signed-off-by: Jori Koolstra Reviewed-by: NeilBrown --- fs/namei.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/namei.c b/fs/namei.c index 2a35dd72ee96..16dcf537db87 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4531,7 +4531,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; @@ -4544,6 +4543,7 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, goto out_dput; =20 fsnotify_create(dir_inode, dentry); + file->f_mode |=3D FMODE_CREATED; } =20 return dentry; --=20 2.54.0 From nobody Mon Jun 8 07:26:16 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.170]) (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 A6D65395AD8 for ; Sun, 31 May 2026 18:20:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251660; cv=none; b=otMHRycPGr1SV35Kg8s9bqqt9wl7dna0W24HMfqDEnV7welf+Q0PwtWHRqGkQGa547sH7C8vgWT0Pf35NufRUdwGQDnylJtbGN2tC2mTmnV9HbwyqauGSKV7cyezhRJdc8qtcExfwc/70v05v9YN8axFGi4oiBDS/GV+zccFE5U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251660; c=relaxed/simple; bh=LofID8F4aeEP2NL0yaU8KzdnQikWu1PqIr89V0ExjAo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=TfH0TTRS+GuffaNYxh1l1JWFVH+7Mqwf3bigfdJDxXqM0hKFMfzkbgnLvTzbOymFcWw0vAfsP/3KSk20DJesMnEWZhgeVZoyKlpYhUJFMjYPl764Ulr94oBUuCLpvThV+MzlL8qcpvNEadqANXwgNyklyZ3q42xhHIg8zoVlEDs= 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=FKUJ7owK; arc=none smtp.client-ip=195.121.94.170 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="FKUJ7owK" X-KPN-MessageId: 4fcef7d3-5d1d-11f1-8aa3-005056ab378f Received: from smtp.kpnmail.nl (unknown [10.31.155.39]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id 4fcef7d3-5d1d-11f1-8aa3-005056ab378f; Sun, 31 May 2026 20:19:49 +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=jAqQXpNHogqs9fFU91Con1KstcX0pVy6+8pBtPvuNSI=; b=FKUJ7owKCLkG7rXEiBj1BOZyWOdtucG8t+us2U7k9J3bQPGflq1DaQnQET0WFTEPrtKCJ2ElCK5hb VWBtXSQAENkODxrxRyFKMGbBtqp3u20cLSuHvYmZCBas8lzrRJsHN5zveMBhmjeFbESMuXE5uhPdyR q79mQraGanOyTaaRofVNe2oup5oVYfIabaL1qjftenqNtrruZifmwl7qCCL2U6UmO4TLdVu/UYkVPY ueRjWoaPEc1dRTfIsPIREZq4iYU5htz+bHsnktdO4Iu5WsISoOYEjzctwUhoivUfP6rbmnbOt3PjMF bqi026e77hBoJlgIxwJn74j8gLTQKpw== X-KPN-MID: 33|jtdY/8S3y1tmkQ/r3Cn+C0sYUN/ZhPyGWf7w86oLMQ/TAmtpP4X2iwHwEznTW3L K5JgVlf+FV8hF63nVu4zcr9x3WEZEZqC//vpkSkqCGuc= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|j5Q6Jaakw7n4qKrREEATSrRBGDN7n1DofkSF5tWf8f79p3YAAR45JsRwvGTSkIM rCXp6tlVMsNEO8XBxEcyFxg== Received: from daedalus.home (unknown [178.227.141.146]) by smtp.xs4all.nl (Halon) with ESMTPSA id 4f826e69-5d1d-11f1-8011-005056ab7447; Sun, 31 May 2026 20:19:49 +0200 (CEST) From: Jori Koolstra To: NeilBrown , Al Viro , Aleksa Sarai , Christian Brauner Cc: Jori Koolstra , Jan Kara , Jeff Layton , Mateusz Guzik , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Charles Mirabile Subject: [RFC PATCH v6 5/9] vfs: lookup_open(): move audit_inode_child() and i_op->create check to before try_break_deleg() Date: Sun, 31 May 2026 20:20:41 +0200 Message-ID: <20260531182045.171711-6-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260531182045.171711-1-jkoolstra@xs4all.nl> References: <20260531182045.171711-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" Calling audit_inode_child() and the i_op->create check in lookup_open() take 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() may_create_dentry() audit_inode_child(dir, child, AUDIT_TYPE_CHILD_CREATE) i_op->create check try_break_deleg() In particular, audit_inode_child() is called regardless of whether try_break_deleg() fails. Move move audit_inode_child() and the i_op->create check to before try_break_deleg() in lookup_open(). Signed-off-by: Jori Koolstra --- fs/namei.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 16dcf537db87..293592a4d011 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4526,17 +4526,18 @@ 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)) { - /* but break the directory lease first! */ - error =3D try_break_deleg(dir_inode, delegated_inode); - if (error) - goto out_dput; - audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); + if (!dir_inode->i_op->create) { error =3D -EACCES; goto out_dput; } =20 + /* but break the directory lease first! */ + error =3D try_break_deleg(dir_inode, delegated_inode); + if (error) + goto out_dput; + error =3D dir_inode->i_op->create(idmap, dir_inode, dentry, mode, open_flag & O_EXCL); if (error) --=20 2.54.0 From nobody Mon Jun 8 07:26:16 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.167]) (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 A46153932FD for ; Sun, 31 May 2026 18:20:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.167 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251660; cv=none; b=FxplPvFWsHr6ivAFNtHi+qxJqqY5Siq3bXaSl2W2LI35rTYvWthFlVH7Pbs5rwvdpfwpdjvOXxoENSk05a1aM57gBKSf7aGcr4NgThVMF4NprQkTlFS3QEJq9sd/gs1ZZxTpO9jHvmt0FQ7P8V4bW+VQx99x/LvjbJcc0u1Ht1c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251660; c=relaxed/simple; bh=ft+9yq72KJdD/sjuEoB+DhVJkKsT41kLGUfS9mNA/ng=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=oV3oECH+ckjtaahuAWKgaBou3VMheWquLCRRXt3FQ1wJYsniYHwX66Y47uT0zt/asVZGLKL+1FnYkcDReUNao4zcWXXyD9xeKDVXu6bC4RbaD7MSDOOLHus2ZIu2adYCqhC2UhyJ76BxS5/QX3v4Fm1nB38LqBhPM9pHGQtk24s= 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=hfYQEFqI; arc=none smtp.client-ip=195.121.94.167 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="hfYQEFqI" X-KPN-MessageId: 507b806a-5d1d-11f1-96a9-005056abbe64 Received: from smtp.kpnmail.nl (unknown [10.31.155.39]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id 507b806a-5d1d-11f1-96a9-005056abbe64; Sun, 31 May 2026 20:19:51 +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=L1JI9rT8lcqw7yTG/hZa/LYzcpZYPHY91GUQ2FxpbBw=; b=hfYQEFqIDy4epi5eefEHXkuuKFooBodLagZIw9+yP/yaYpBBAErjLyfDUgw3KbXsCOCsK9jjZFIEq h+DJm1AFNJog6l8n4kDKYIlnI1ThcKf9zSXaldilesniaK4FAmqXIp3nlshrGzKIInOJL+XobxJiSB n6Ct7anY7DilKjGOtzX4b4gyl0XkktatQ9aTDUqhLs50ooA52pDc4hvIhZBvtspWqPsPil/LZeYjUt sSBQ3BntGclkxo6oUgf0BTjdIZYeXIzOa6RJR4BClEvIue7h2R462HptzGO+O2SVqwBojqnrpbiCiZ CK7MuMVo1YZcNdlThmG+DQXdgzAYu7w== X-KPN-MID: 33|1V+7mnPA12pc+J4sY5I1PKRJWaUYYa/u5H9dehUrX8NcOqufRTfi0GubfB7ABP/ Hp7WafaNj5eRL3mVHDc1IWjBjK83yK9EKp+G7j5lXwA8= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|42GZKuzmEJbp6p3rqqF/zylRA4b9REWOhWDBS5djRNI7Hj4AUJMQL3IX9GesYae ya1q/MKjbjcMN+Iz3cLfg7g== Received: from daedalus.home (unknown [178.227.141.146]) by smtp.xs4all.nl (Halon) with ESMTPSA id 50319ac2-5d1d-11f1-8011-005056ab7447; Sun, 31 May 2026 20:19:50 +0200 (CEST) From: Jori Koolstra To: NeilBrown , Al Viro , Aleksa Sarai , Christian Brauner Cc: Jori Koolstra , Jan Kara , Jeff Layton , Mateusz Guzik , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Charles Mirabile Subject: [RFC PATCH v6 6/9] vfs: lookup_open(): use vfs_create_no_perm() Date: Sun, 31 May 2026 20:20:42 +0200 Message-ID: <20260531182045.171711-7-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260531182045.171711-1-jkoolstra@xs4all.nl> References: <20260531182045.171711-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. Signed-off-by: Jori Koolstra --- fs/namei.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 293592a4d011..a8c247e0bb65 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4533,17 +4533,11 @@ 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, delegated_inode); + error =3D vfs_create_no_perm(idmap, dentry, mode, delegated_inode, + open_flag & O_EXCL); if (error) goto out_dput; =20 - error =3D dir_inode->i_op->create(idmap, dir_inode, dentry, - mode, open_flag & O_EXCL); - if (error) - goto out_dput; - - fsnotify_create(dir_inode, dentry); file->f_mode |=3D FMODE_CREATED; } =20 --=20 2.54.0 From nobody Mon Jun 8 07:26:16 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.167]) (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 C06483939DB for ; Sun, 31 May 2026 18:19:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.167 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251595; cv=none; b=OgIiQv4eQvcPr3DHvm0ASBXzHkzSOfx6lBSmRR1/sR1gp3dpLH8hiYb0x6MYY54m5HqJVQth1VIk0S+upc7H8KiVibfUyG+nHOH3cj7IaMHBtIX/VGMQUKcyrlUohxFgb8/bM58lt9+npc6YNvt9GuR+vdypgywg0efvNme1WKQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251595; c=relaxed/simple; bh=prDA/ZkaoWHimPaEgbVbLBCP/9Hr7JDKeCC5c4dQrQk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=om7X5rXGwO6/NwW1UbpeqctDMfJ0klHT+GKmxujZq3DLa9Jn4oAfvqdsdhtDAOy9vPSlmWGUHTePyTNJLqrIvBpg2+8/fVgOMQlkiAg93xf4iHE/q/qzIN9cX5Uis35BxhaOec8kpG9PZxUskafs/L9CJlEr/nIJanoHGvvpmZw= 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=V+2aFr0C; arc=none smtp.client-ip=195.121.94.167 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="V+2aFr0C" X-KPN-MessageId: 510ab670-5d1d-11f1-96a9-005056abbe64 Received: from smtp.kpnmail.nl (unknown [10.31.155.39]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id 510ab670-5d1d-11f1-96a9-005056abbe64; Sun, 31 May 2026 20:19:52 +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=r1sFqTXedlMyWqPjwiEDsNgQki9tQsfI/pvCFGzINbw=; b=V+2aFr0CHhCwpgdYAn6ml00wzBnkNaTUdtsExHQTzBrXS7KCHW5z5E0o9T9dh4iBX7nE01gXdvrhq +sVdHLWOa1EjyF2SpNcSYiYqa328UJhikdcX1GB034BNkbMZHF72zyDDkkTu6V6pGGYd34gfLThE7E /l9ItgWxCOcfOEIBvJJnlw5XsVbzMa1aH/qntdrueX9WX6uCgwvP3dLMpmdehfrGQb5gX0ERUg1kjO xdVJTx07NZ9viJxobkLGRThQINnD5JvgZn4is9aan+TYJFVxL6CBXRrjExJL0eSvP5G6cA8GfWPvuU GacmpDMqvWW1GiXbkTUIHzCzLGFZ9Aw== X-KPN-MID: 33|55OZhV/AWwqg4unodZAngDYuFy8rFnoiiLqzTjbBQoDxYRkNoL5wSdhTnvg7FtP AkGWuGHuQPrqYK5FSSlC2utoGR5pfDvXVxmbb62bkcEc= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|VEzpcJK2vGJHUS8yPxs5TGYT5eqYm20yfC/OJ+FEc8NisZd57EzlYaO6u4fMZUM PNjKREEVYpHXZ3bSGmSP34w== Received: from daedalus.home (unknown [178.227.141.146]) by smtp.xs4all.nl (Halon) with ESMTPSA id 50bfd774-5d1d-11f1-8011-005056ab7447; Sun, 31 May 2026 20:19:51 +0200 (CEST) From: Jori Koolstra To: NeilBrown , Al Viro , Aleksa Sarai , Christian Brauner Cc: Jori Koolstra , Jan Kara , Jeff Layton , Mateusz Guzik , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Charles Mirabile Subject: [RFC PATCH v6 7/9] vfs: add O_CREAT|O_DIRECTORY to open*(2) Date: Sun, 31 May 2026 20:20:43 +0200 Message-ID: <20260531182045.171711-8-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260531182045.171711-1-jkoolstra@xs4all.nl> References: <20260531182045.171711-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 proper APIs is possible (by immediately fstat()ing what was opened, to verify that it has the right inode type), but difficult to get right. Hence, adding support for a new flag combo O_CREAT|O_DIRECTORY to open*(2) that creates a directory (if it does not exist already) and returns an O_DIRECTORY fd is very useful. 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 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 year 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 blocked in atomic_open() until individual filesytems that support i_op->atomic_open() are given tailored support. 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 | 94 +++++++++++++++++++++++++++++++++---------- fs/open.c | 25 +++++++----- include/linux/fcntl.h | 7 ++++ 3 files changed, 94 insertions(+), 32 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index a8c247e0bb65..724b9de6831a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1377,13 +1377,12 @@ 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 * @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 @@ -1411,6 +1410,12 @@ 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 suprise 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 @@ -1442,6 +1447,12 @@ static int may_create_in_sticky(struct mnt_idmap *id= map, struct nameidata *nd, "sticky_create_regular"); return -EACCES; } + + if (sysctl_protected_regular >=3D 2 && S_ISDIR(inode->i_mode)) { + audit_log_path_denied(AUDIT_ANOM_CREAT, + "sticky_create_dir"); + return -EACCES; + } } =20 return 0; @@ -4215,7 +4226,7 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry= *dentry, umode_t mode, return error; =20 if (!dir->i_op->create) - return -EACCES; /* shouldn't it be ENOSYS? */ + return -EACCES; /* shouldn't it be ENOSYS? */ =20 mode =3D vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG); error =3D security_inode_create(dir, dentry, mode); @@ -4339,21 +4350,40 @@ 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) + umode_t mode, bool create_dir) { - int error =3D security_path_mknod(dir, dentry, mode, 0); + struct inode *dir_inode =3D dir->dentry->d_inode; + 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, umode_t mode, bool create_dir) +{ + if (create_dir) + 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,8 +4409,12 @@ 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); + + if (O_IS_MKDIR(open_flag)) + error =3D EINVAL; + else + error =3D dir->i_op->atomic_open(dir, dentry, file, + open_to_namei_flags(open_flag), mode); d_lookup_done(dentry); if (!error) { if (file->f_mode & FMODE_OPENED) { @@ -4413,6 +4447,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,6 +4477,7 @@ static struct dentry *lookup_open(struct nameidata *n= d, 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); DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); =20 if (unlikely(IS_DEADDIR(dir_inode))) @@ -4487,10 +4526,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, mode, create_dir); if (likely(got_write)) create_error =3D may_o_create(idmap, &nd->path, - dentry, mode); + dentry, mode, create_dir); else create_error =3D -EROFS; } @@ -4527,14 +4566,26 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, /* Negative dentry, just create the file */ if (!dentry->d_inode && (open_flag & O_CREAT)) { audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); - - 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 - error =3D vfs_create_no_perm(idmap, dentry, mode, delegated_inode, - open_flag & O_EXCL); + if (create_dir) { + struct dentry *res =3D vfs_mkdir_no_perm(idmap, dir_inode, dentry, + mode, delegated_inode); + if (IS_ERR(res)) { + error =3D PTR_ERR(res); + } else { + error =3D 0; + dentry =3D res; + } + } else { + error =3D vfs_create_no_perm(idmap, dentry, mode, delegated_inode, + open_flag & O_EXCL); + + } if (error) goto out_dput; =20 @@ -4553,7 +4604,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)) + if (trailing_slashes(nd) && !(open_flag & O_DIRECTORY)) return ERR_PTR(-EISDIR); =20 /* Don't bother on an O_EXCL create */ @@ -4624,7 +4675,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 @@ -4687,8 +4738,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)) @@ -5054,7 +5106,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, mode, /*create_dir=3D= */ false); if (create_error) flags &=3D ~O_CREAT; =20 diff --git a/fs/open.c b/fs/open.c index 681d405bc61e..5cf8ada58483 100644 --- a/fs/open.c +++ b/fs/open.c @@ -1209,29 +1209,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; @@ -1268,6 +1269,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 a332e79b3207..79843e8add6e 100644 --- a/include/linux/fcntl.h +++ b/include/linux/fcntl.h @@ -12,6 +12,13 @@ FASYNC | O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | \ O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE) =20 + +#define O_MKDIR_MASK (O_CREAT | O_DIRECTORY) +static inline bool O_IS_MKDIR(unsigned 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.54.0 From nobody Mon Jun 8 07:26:16 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.168]) (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 8DD473955C3 for ; Sun, 31 May 2026 18:19:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.168 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251596; cv=none; b=nRdNSyT4y+UJQkkNEBJjBEtvTJb9BruON+FOUo8SYv1i/b7FSMiiJ4mzpo30FHecx1k9+CwW5cxR6yw4tuBWrzUB+OxPQCIi1MLJSmFzk2Ev5K5KrwznsmrMOSdi5jMKnvusdMUo9Kq6BeHK8uHh8gqqNHGb2ArP1ArAnhxKY7k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251596; c=relaxed/simple; bh=C/2LWVWDngvB9ucVHgKIjqvDr719sDGqQ92tKG9eZNk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=X65s2lPxhhDc/HFc6vhoD5IMzx3das4Juh51J/3B8tQpknmbxEElomTS/Y5/VveoFm5cY+LJfLoQDvxnZwxpnIJVQPvsifDZK6uWXj3yJXO7O5Dz72GUsRsRGxWT0uPzvGQ9m32mATrgS2T1mt1uCo+OKBly0CdxPdSssg77yc4= 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=iBQ/NaXv; arc=none smtp.client-ip=195.121.94.168 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="iBQ/NaXv" X-KPN-MessageId: 51922b33-5d1d-11f1-92b3-005056aba152 Received: from smtp.kpnmail.nl (unknown [10.31.155.39]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id 51922b33-5d1d-11f1-92b3-005056aba152; Sun, 31 May 2026 20:19:52 +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=q8nMU3gs3aSiVabh8ieTDz3owJmShMgi82h5KVQxaAQ=; b=iBQ/NaXvNzYjDpYS3wpgrkYczEJakbtTN4S7yKVswXLGurypOHS5ZoQQLABrAC9YV/dZGWJxjKejR 4Ra6csYsWNdrBCbVeFfq7D0QOh9+j5vwcgRj5c7KC1WyI60Pt75hQlWA2NNP39s3vgrsFjd3ndv80P JwfUo1ukshgpfmkCeHBmYq596QEz35OLe5fwAXSc8Ro+9FOPpcod4gGuYtmfuRaGyWmQzBLxa0qbKX 1CJ7b0jQTImvMG8bfTaDKu2VivzpCGNwZRJ8jTWvERDcMT1M5iVJZdVAgcY83euws1Ic5QuVeQdM9Q +yzLRcFhFsjo2LiudlDpMwnKk2Zpu7Q== X-KPN-MID: 33|QXzer0TfTPNfASE3eGVtH3EScMNbQdhVNt0F+jfVlzIk4OmvA24AS5PNODewAyG tpG3IKBi8v7s1FGG3D9xwZw== X-KPN-VerifiedSender: Yes X-CMASSUN: 33|kvmDwG1kiFptKUtH8LNqUurFdyfTd6tSUMlMpon3U08ikYlcwaV976dVAnKL2f/ tzEmpxBEWzdgrdflLXDCOKw== Received: from daedalus.home (unknown [178.227.141.146]) by smtp.xs4all.nl (Halon) with ESMTPSA id 515c609c-5d1d-11f1-8011-005056ab7447; Sun, 31 May 2026 20:19:52 +0200 (CEST) From: Jori Koolstra To: NeilBrown , Al Viro , Aleksa Sarai , Christian Brauner Cc: Jori Koolstra , Jan Kara , Jeff Layton , Mateusz Guzik , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Charles Mirabile Subject: [RFC PATCH v6 8/9] vfs: move O_IS_MKDIR check out atomic_open() to individual filesystems Date: Sun, 31 May 2026 20:20:44 +0200 Message-ID: <20260531182045.171711-9-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260531182045.171711-1-jkoolstra@xs4all.nl> References: <20260531182045.171711-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 need to get the chance to implement O_CREAT|O_DIRECTORY or not, rather than decide this at the VFS level in atomic_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 | 8 ++------ fs/nfs/dir.c | 3 +++ fs/nfs/file.c | 3 +++ fs/smb/client/dir.c | 3 +++ fs/vboxsf/dir.c | 3 +++ 10 files changed, 29 insertions(+), 6 deletions(-) diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index f468acb8ee7d..8eff9320aa8a 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -771,6 +771,9 @@ v9fs_vfs_atomic_open(struct inode *dir, struct dentry *= dentry, struct inode *inode; int p9_omode; =20 + if (O_IS_MKDIR(flags)) + return -EINVAL; + 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 141fb54db65d..9a63ae0f3b58 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)) + return -EINVAL; + 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 d54d71669176..a82a711a86e6 100644 --- a/fs/ceph/file.c +++ b/fs/ceph/file.c @@ -813,6 +813,9 @@ int ceph_atomic_open(struct inode *dir, struct dentry *= dentry, if (dentry->d_name.len > NAME_MAX) return -ENAMETOOLONG; =20 + if (O_IS_MKDIR(flags)) + return -EINVAL; + err =3D ceph_wait_on_conflict_unlink(dentry); if (err) return err; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index b658b6baf72f..58f3310f828f 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -940,6 +940,9 @@ static int fuse_atomic_open(struct inode *dir, struct d= entry *entry, if (fuse_is_bad(dir)) return -EIO; =20 + if (O_IS_MKDIR(flags)) + return -EINVAL; + if (d_in_lookup(entry)) { struct dentry *res =3D fuse_lookup(dir, entry, 0); if (res || d_really_is_positive(entry)) diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index e9bf4879c07f..df66ac2d0c15 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1384,6 +1384,9 @@ static int gfs2_atomic_open(struct inode *dir, struct= dentry *dentry, { bool excl =3D !!(flags & O_EXCL); =20 + if (O_IS_MKDIR(flags)) + return -EINVAL; + 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 724b9de6831a..6cc3d42dc1a5 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4409,12 +4409,8 @@ 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; - - if (O_IS_MKDIR(open_flag)) - error =3D EINVAL; - else - error =3D dir->i_op->atomic_open(dir, dentry, file, - open_to_namei_flags(open_flag), mode); + error =3D dir->i_op->atomic_open(dir, dentry, file, + open_to_namei_flags(open_flag), mode); d_lookup_done(dentry); if (!error) { if (file->f_mode & FMODE_OPENED) { diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index e9ce1883288c..cbbc61788d0e 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2314,6 +2314,9 @@ int nfs_atomic_open_v23(struct inode *dir, struct den= try *dentry, if (dentry->d_name.len > NFS_SERVER(dir)->namelen) return -ENAMETOOLONG; =20 + if (O_IS_MKDIR(open_flags)) + return -EINVAL; + if (open_flags & O_CREAT) { error =3D nfs_do_create(dir, dentry, mode, open_flags); if (!error) { diff --git a/fs/nfs/file.c b/fs/nfs/file.c index 25048a3c2364..b885d8facaf5 100644 --- a/fs/nfs/file.c +++ b/fs/nfs/file.c @@ -52,6 +52,9 @@ int nfs_check_flags(int flags) if ((flags & (O_APPEND | O_DIRECT)) =3D=3D (O_APPEND | O_DIRECT)) return -EINVAL; =20 + if (O_IS_MKDIR(flags)) + return -EINVAL; + return 0; } EXPORT_SYMBOL_GPL(nfs_check_flags); diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index e4295a5b55b3..b282753713d6 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -526,6 +526,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)) + return -EINVAL; + /* * 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 42bedc4ec7af..cc999c1ab7cf 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)) + return -EINVAL; + if (d_in_lookup(dentry)) { struct dentry *res =3D vboxsf_dir_lookup(parent, dentry, 0); if (res || d_really_is_positive(dentry)) --=20 2.54.0 From nobody Mon Jun 8 07:26:16 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.167]) (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 7DAC23955F0 for ; Sun, 31 May 2026 18:19:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.167 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251597; cv=none; b=ZhJ3oxWAU/PEL8BEu3xKM2QFv/HmRfP/HC/n3YLkJDBKMF7MRhZ4Nj5krzth2CEwwhGZvA2myX/oR1yVlwMgk4NRqbAxznrZYIip+xExZHYPSG3EV+ww2P4oZ2yjiDjlsGHWjCxuoY5Wtdnm572zfRBX6huJrKxmmtkoIo/2Hjc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780251597; c=relaxed/simple; bh=dn6SihIUPLWOfN26dYAPACiqQ49/1rU1058g0P6VrIs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FHy2JtSa2IOwGeLDyIM6AKf87L4n65kPn8r5x8NjiyP98bu6Req7mfqNi6wLOrk+yXJ7blmlIhSIMMQadL9mvQR/sy1EnKdICxehq+E3V0t6U91TTEYQqBL3lrFZcOdncsPbARB87C2Lzxy6O443S/oje6Qyj7EGskSaNTm1164= 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=tv8U5sNk; arc=none smtp.client-ip=195.121.94.167 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="tv8U5sNk" X-KPN-MessageId: 522ef40e-5d1d-11f1-96a9-005056abbe64 Received: from smtp.kpnmail.nl (unknown [10.31.155.39]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id 522ef40e-5d1d-11f1-96a9-005056abbe64; Sun, 31 May 2026 20:19: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=34ZFfcPo/wCV7rUgvNIeW6wK60MzzQzVDVItofBMs5I=; b=tv8U5sNkmzBqbX23gMiQVx7fCYlWNHyjxavmxUEhzzTrChy8DUHsvmsDEET+X+92TJt+L0eK5ZU/a 1nmG9yQqNJKFO2O18sByOu6cJVVSlaz6j1nPC9dcfvllTtT6xPx795fNVgomySjpbI6m3g38wBVdqK ecGXa5ZvCIE6zdbK0lSFej9+02tgtu56CyjWb5cMT736AWcHr2Z/crs2zIsjvXsZC4WfDxbsvjCy9q nMWo3Y1VjJLvttoseNKgqgQOUlMrxjk/wFYbokDKwDhMPc2XWdZkaBaGWoCuZ+fiU7nuJLXpaPDDcb SaLpFAoH4QitArcXngtNNMDRNfnBNCw== X-KPN-MID: 33|XUUscqeZMaFhRDs6XEX9lcT0Y2925Cq6T96N11s36aNkMDoGAMIYaeHUWPPMVb6 y9FEXHHkYzYi71jP/fTylGpWPXupkqhAy4NrIhhyPenk= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|DVxjmziSlgRNEjvYt7L0foOk9lYvMa516mh447HZbWbjh8iIteDixRInhUgN1hf h1QClAsHqnlFFk+VCm8pqDg== Received: from daedalus.home (unknown [178.227.141.146]) by smtp.xs4all.nl (Halon) with ESMTPSA id 51e4cedb-5d1d-11f1-8011-005056ab7447; Sun, 31 May 2026 20:19:53 +0200 (CEST) From: Jori Koolstra To: NeilBrown , Al Viro , Aleksa Sarai , Christian Brauner Cc: Jori Koolstra , Jan Kara , Jeff Layton , Mateusz Guzik , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Charles Mirabile Subject: [RFC PATCH v6 9/9] selftest: add tests for open*(O_CREAT|O_DIRECTORY) Date: Sun, 31 May 2026 20:20:45 +0200 Message-ID: <20260531182045.171711-10-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260531182045.171711-1-jkoolstra@xs4all.nl> References: <20260531182045.171711-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 | 4 +- tools/testing/selftests/filesystems/fclog.c | 1 + .../filesystems/open_o_creat_o_dir.c | 197 ++++++++++++++++++ 4 files changed, 201 insertions(+), 2 deletions(-) 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 64ac0dfa46b7..f257b3ddb479 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 85427d7f19b9..ec7f93b700d2 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 +CFLAGS +=3D $(KHDR_INCLUDES) $(TOOLS_INCLUDES) +TEST_GEN_PROGS :=3D open_o_creat_o_dir devpts_pts file_stressor anon_inode= _test kernfs_test fclog TEST_GEN_PROGS_EXTENDED :=3D dnotify_test =20 include ../lib.mk diff --git a/tools/testing/selftests/filesystems/fclog.c b/tools/testing/se= lftests/filesystems/fclog.c index 551c4a0f395a..33ed59286a2d 100644 --- a/tools/testing/selftests/filesystems/fclog.c +++ b/tools/testing/selftests/filesystems/fclog.c @@ -4,6 +4,7 @@ * Copyright (C) 2025 SUSE LLC. */ =20 +#include #include #include #include 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..03b5edcffeef --- /dev/null +++ b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +#include "kselftest_harness.h" + +static inline int open_o_creat_o_dir(int dfd, const char *pathname, + mode_t mode, unsigned int flags) +{ + return syscall(__NR_openat, dfd, pathname, + flags | O_DIRECTORY | O_CREAT, mode); +} + +#define open_o_creat_o_dir_checked_flags(dfd, pathname, flags) ({ \ + struct stat __st; \ + int __fd =3D open_o_creat_o_dir(dfd, pathname, S_IRWXU, flags); \ + ASSERT_GE(__fd, 0); \ + ASSERT_EQ(fstat(__fd, &__st), 0); \ + EXPECT_TRUE(S_ISDIR(__st.st_mode)); \ + __fd; \ +}) + +#define open_o_creat_o_dir_checked(dfd, pathname) \ + open_o_creat_o_dir_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 open_o_creat_o_dir_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 open_o_creat_o_dir_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(open_o_creat_o_dir(self->dfd, "nonexistent/child", S_IRWXU, 0),= -1); + EXPECT_EQ(errno, ENOENT); +} + +/* An invalid dfd must fail with EBADF. */ +TEST_F(open_o_creat_o_dir, ebadf) +{ + EXPECT_EQ(open_o_creat_o_dir(-42, "badfdir", S_IRWXU, 0), -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(open_o_creat_o_dir(file_fd, "subdir", S_IRWXU, 0), -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 must fail with EEXIST when + * the target directory already exists. + */ +TEST_F(open_o_creat_o_dir, o_excl_eexist) +{ + int fd; + + fd =3D open_o_creat_o_dir_checked_flags(self->dfd, "excldir", O_EXCL); + EXPECT_EQ(close(fd), 0); + + EXPECT_EQ(open_o_creat_o_dir(self->dfd, "excldir", S_IRWXU, O_EXCL), -1); + EXPECT_EQ(errno, EEXIST); + + 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(open_o_creat_o_dir(self->dfd, "regfile", S_IRWXU, 0), -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(open_o_creat_o_dir(self->dfd, "rdwrdir", S_IRWXU, O_RDWR), -1); + EXPECT_EQ(errno, EISDIR); + /* Clean up if the kernel created the directory anyway. */ + unlinkat(self->dfd, "rdwrdir", AT_REMOVEDIR); +} + +/* + * 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 open_o_creat_o_dir_checked(self->dfd, "parent"); + child_fd =3D open_o_creat_o_dir_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 --=20 2.54.0