From nobody Thu Sep 24 13:41:54 2026 Received: from m16.mail.163.com (m16.mail.163.com [117.135.210.4]) (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 0BF5B43DEB3 for ; Wed, 23 Sep 2026 06:03:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=117.135.210.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143430; cv=none; b=XvBfY2+yaxtAusHV6r3ITEcnlH+418+l4bo13PLlDGAhP2hrvFTj5lycs+AhIU7Onzc0Mle8VQh8kDLxnEY4P25YrA1jnAl4fh9BSSMWnwXbc0mJxMgaanuJBi6hw+cJRghUN3moNgNjMRZqkLVUPFiVsgxQwf8bdDFvA8Ziifk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143430; c=relaxed/simple; bh=z9FjYaAWb4GTLQIi4odkLF+16Gv2WFoNNnH9R88QkxA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jUz0FD1xcWGV1xJYidbYGEA+BR4+y/voOL9ngsl+INyagrKmMuKW8mJoYUXb6m5gTGgFXDEyDcORexbwq3KgOO8h8UGFHYxsk6MOYmN6lCcDQtihev+T5NZVktkA70JjYDjEOlFKWYxeOX+tvnt42V0YaX5dWVWFTz+wPaKf4GE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=Ox/3Lfls; arc=none smtp.client-ip=117.135.210.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="Ox/3Lfls" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=zE uiFnhdmn5p2+jZmwYejIT0454wV9ELKGT/9ac1Qwc=; b=Ox/3LflsJFQfOAWl+i p+uBMj9VkW5BdpYaaDlTgfl7oMegCSGdd3CXV4D63IfgBS37w5vCNzC1aVmHfayN ZVJzLUpuujxighGE3s5V/od7+yxpa2fNMtXfAXJojPgkUb3z8M83u34nM7+SxjRN diyloUEmBK+omrpl/QGDNsIDU= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgCH_E+Ia7NqXdW_Aw--.58316S3; Wed, 23 Sep 2026 14:02:54 +0800 (CST) From: Baolin Liu To: linkinjeon@kernel.org, hyc.lee@gmail.com Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org, chenxiaosong@chenxiaosong.com, Baolin Liu Subject: [PATCH v4 01/10] ntfs: keep the EA presence flag after creating the first entry Date: Wed, 23 Sep 2026 14:02:37 +0800 Message-ID: <20260923060246.2499192-2-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260923060246.2499192-1-liubaolin12138@163.com> References: <20260923060246.2499192-1-liubaolin12138@163.com> 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 X-CM-TRANSID: PygvCgCH_E+Ia7NqXdW_Aw--.58316S3 X-Coremail-Antispam: 1Uf129KBjvdXoW7GrWrXr15WF15CF4xAFyrZwb_yoWftFg_Wa 4xZr1kKw4Utrn3J3Z7GryY9r4SqF4kKr1qq3s3trs7XayDtws8KrWkuws5uF4Du3yktrZ8 urykWa4UXrs5WjkaLaAFLSUrUUUUjb8apTn2vfkv8UJUUUU8Yxn0WfASr-VFAUDa7-sFnT 9fnUUvcSsGvfC2KfnxnUUI43ZEXa7IU0038UUUUUU== X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6Q6FUGqza47GIwAA3r Content-Type: text/plain; charset="utf-8" From: Baolin Liu The successful EA update path tests ea_info_qsize when updating NInoHasEA, but this size excludes the appended entry. Creating the first EA, or replacing the only entry, can therefore clear the cached presence flag despite a successful update. Reads then return ENODATA until reload. Include the appended entry in the size used for the presence test. Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee --- fs/ntfs/ea.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index b4fcfbe2da4c..bb97df3045a0 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -402,6 +402,7 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, =20 if (packed_ea_size) *packed_ea_size =3D p_ea_info->ea_length; + ea_info_qsize +=3D new_ea_size; mark_mft_record_dirty(ni); out: if (!err) { --=20 2.51.0 From nobody Thu Sep 24 13:41:54 2026 Received: from m16.mail.163.com (m16.mail.163.com [117.135.210.5]) (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 4CBD443B4BD for ; Wed, 23 Sep 2026 06:03:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=117.135.210.5 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143420; cv=none; b=FXZs3Ra+/VuaBlejeHmwXxHal56K2V/nUiergvmfk/vNPMIjD60YeS1zKi8jkdCFI06xyMR5Zl1SHXC2On7exJSWumFtwHRhQpqNeaSaK2yzda1fY+BQ6LhLjTQnLZkO+cGTeG+0eZaEAEVOOJbxsTyBR/aOV+BMAZteYdFYuKo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143420; c=relaxed/simple; bh=XxLdUmBno4bd5Qj5g1RgFgK0RqMw0Dv2kgbnplqeGkw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hcsULHVwh/T75akMcP/b++r/HHNWyFU22yRp+PSML8byYLxiASRrl+QAQwytWaUEfuUbsDM2N+aU9c7zxlRWAieee7Sta7nX0quqeeFmA6OU00gkWtqZduQpB/CFG7pzfHCIEDCHs2GPth/dVFR86VsQZZR7JLcBfxF5ySLiQZs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=mMxIHG0T; arc=none smtp.client-ip=117.135.210.5 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="mMxIHG0T" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=ye bFd7WMTqJxaSa+0EPIQ3YSpYYSMXtcX0gRL2DMfBE=; b=mMxIHG0TBsR1+IGJt2 n+x/gtx0c0qODQpDIfoADyluDgExD1Ukg0LmCUDtcTxXKcgJHZ0X4cHZFdA0FBDG WAtR/QN6y3a32iW5aLKcDZ1csr5XiAlbX/08igH2n8PGGXBmqPmQGbDS8IUeSVj1 RMaSS0FQTVNfgEj1V5lX8z+yg= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgCH_E+Ia7NqXdW_Aw--.58316S4; Wed, 23 Sep 2026 14:02:56 +0800 (CST) From: Baolin Liu To: linkinjeon@kernel.org, hyc.lee@gmail.com Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org, chenxiaosong@chenxiaosong.com, Baolin Liu Subject: [PATCH v4 02/10] ntfs: validate EA chains and link zero-terminated tails Date: Wed, 23 Sep 2026 14:02:38 +0800 Message-ID: <20260923060246.2499192-3-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260923060246.2499192-1-liubaolin12138@163.com> References: <20260923060246.2499192-1-liubaolin12138@163.com> 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 X-CM-TRANSID: PygvCgCH_E+Ia7NqXdW_Aw--.58316S4 X-Coremail-Antispam: 1Uf129KBjvJXoWxtrWDuw1DWw1fWw4DZryxXwb_yoW7uF4UpF 9Iy3s8tr4rJrnrGFnakw4DXayF9397WryUK34xKw1Ikr1qqr15tas09F9a9ayFy3ykAr1r tFZ8tFs5uF1UAFJanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07jgJ5rUUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6hCGUWqza5ARwAAA3e Content-Type: text/plain; charset="utf-8" From: Baolin Liu A zero next-entry offset terminates an EA list. Appending an entry without linking the old tail makes the new entry unreachable. Invalid entries are also treated as missing names, allowing setters to proceed on corrupt metadata. Validate the entire chain before modifying it and connect a zero tail to the append position. Check name terminators and retain the full tail span when removing an entry. Return EUCLEAN for malformed EA structures and inconsistent information lengths, preserving that error through the get, set and list paths. Keep ENOENT for a missing name and translate it to ENODATA only at the xattr read boundary. Preserve lower-level read errors. Reuse the same full-chain validation in ntfs_listxattr(), including size-only queries, before copying names. Reject malformed lists with EUCLEAN instead of returning an empty or partial list. Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee --- fs/ntfs/ea.c | 81 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index bb97df3045a0..2ee700f8227e 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -53,10 +53,13 @@ static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size= , const char *name, loff_t offset, p_ea_size; unsigned int next; =20 + if (!ea_buf_size) + return -ENOENT; + offset =3D 0; do { if (ea_buf_size - offset < sizeof(struct ea_attr)) - break; + return -EUCLEAN; =20 p_ea =3D (const struct ea_attr *)&ea_buf[offset]; next =3D le32_to_cpu(p_ea->next_entry_offset); @@ -64,31 +67,31 @@ static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size= , const char *name, =20 if (p_ea_size < sizeof(struct ea_attr) || offset + p_ea_size > ea_buf_size) - break; + return -EUCLEAN; =20 if ((s64)p_ea->ea_name_length + 1 > p_ea_size - offsetof(struct ea_attr, ea_name)) - break; + return -EUCLEAN; =20 actual_size =3D ALIGN(struct_size(p_ea, ea_name, 1 + p_ea->ea_name_lengt= h + le16_to_cpu(p_ea->ea_value_length)), 4); - if (actual_size > p_ea_size) - break; + if (actual_size > p_ea_size || + p_ea->ea_name[p_ea->ea_name_length]) + return -EUCLEAN; =20 - if (p_ea->ea_name_length =3D=3D name_len && + if (name && p_ea->ea_name_length =3D=3D name_len && !memcmp(p_ea->ea_name, name, name_len)) { *ea_offset =3D offset; - *ea_size =3D next ? next : actual_size; + *ea_size =3D p_ea_size; =20 if (ea_buf_size < *ea_offset + *ea_size) - goto out; + return -EUCLEAN; =20 return 0; } offset +=3D next; } while (next > 0 && offset < ea_buf_size); =20 -out: return -ENOENT; } =20 @@ -126,7 +129,7 @@ static int ntfs_get_ea(struct inode *inode, const char = *name, size_t name_len, return PTR_ERR(p_ea_info); if (ea_info_size !=3D sizeof(struct ea_information)) { kvfree(p_ea_info); - return -EIO; + return -EUCLEAN; } =20 ea_info_qlen =3D le32_to_cpu(p_ea_info->ea_query_length); @@ -137,7 +140,7 @@ static int ntfs_get_ea(struct inode *inode, const char = *name, size_t name_len, return PTR_ERR(ea_buf); =20 if (ea_info_qlen > all_ea_size) { - err =3D -EIO; + err =3D -EUCLEAN; goto free_ea_buf; } =20 @@ -162,7 +165,8 @@ static int ntfs_get_ea(struct inode *inode, const char = *name, size_t name_len, return ea_value_len; } =20 - err =3D -ENODATA; + if (err =3D=3D -ENOENT) + err =3D -ENODATA; free_ea_buf: kvfree(ea_buf); return err; @@ -216,7 +220,7 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, goto out; } if (ea_info_size !=3D sizeof(struct ea_information)) { - err =3D -EIO; + err =3D -EUCLEAN; goto out; } =20 @@ -255,12 +259,29 @@ static int ntfs_set_ea(struct inode *inode, const cha= r *name, size_t name_len, } =20 if (ea_info_qsize > all_ea_size) { - err =3D -EIO; + err =3D -EUCLEAN; + goto out; + } + + /* Validate the whole chain before modifying it, including its tail. */ + err =3D ntfs_ea_lookup(ea_buf, ea_info_qsize, NULL, 0, &ea_off, + &ea_size); + if (err !=3D -ENOENT) goto out; + /* A zero tail offset must be linked before appending another EA. */ + for (ea_off =3D 0; ea_off < ea_info_qsize; ea_off +=3D ea_size) { + p_ea =3D (struct ea_attr *)(ea_buf + ea_off); + ea_size =3D le32_to_cpu(p_ea->next_entry_offset); + if (!ea_size) { + ea_size =3D ea_info_qsize - ea_off; + p_ea->next_entry_offset =3D cpu_to_le32(ea_size); + } } =20 err =3D ntfs_ea_lookup(ea_buf, ea_info_qsize, name, name_len, &ea_off, &ea_size); + if (err && err !=3D -ENOENT) + goto out; if (ea_info_qsize && !err) { if (flags & XATTR_CREATE) { err =3D -EEXIST; @@ -537,7 +558,7 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buf= fer, size_t size) goto out; } if (ea_info_size !=3D sizeof(struct ea_information)) { - err =3D -EIO; + err =3D -EUCLEAN; goto out; } =20 @@ -550,32 +571,26 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *b= uffer, size_t size) goto out; } =20 - if (ea_info_qsize > ea_buf_size || ea_info_qsize =3D=3D 0) + if (ea_info_qsize > ea_buf_size) { + err =3D -EUCLEAN; + goto out; + } + + /* Validate the entire chain, including size-only queries. */ + err =3D ntfs_ea_lookup(ea_buf, ea_info_qsize, NULL, 0, &offset, + &ea_size); + if (err !=3D -ENOENT) + goto out; + err =3D 0; + if (!ea_info_qsize) goto out; =20 offset =3D 0; do { - if (ea_info_qsize - offset < sizeof(struct ea_attr)) { - err =3D -EIO; - goto out; - } - p_ea =3D (const struct ea_attr *)&ea_buf[offset]; next =3D le32_to_cpu(p_ea->next_entry_offset); ea_size =3D next ? next : (ea_info_qsize - offset); =20 - if (ea_size < sizeof(struct ea_attr) || - offset + ea_size > ea_info_qsize) { - err =3D -EIO; - goto out; - } - - if ((int)p_ea->ea_name_length + 1 > - ea_size - offsetof(struct ea_attr, ea_name)) { - err =3D -EIO; - goto out; - } - if (buffer) { if (ret + p_ea->ea_name_length + 1 > size) { err =3D -ERANGE; --=20 2.51.0 From nobody Thu Sep 24 13:41:54 2026 Received: from m16.mail.163.com (m16.mail.163.com [117.135.210.5]) (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 4CE8043B6E7 for ; Wed, 23 Sep 2026 06:03:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=117.135.210.5 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143421; cv=none; b=EdDfhndA5RyOQCCeiZTTVnjivFJ4DLhRhVwGBnQzb3g+9BKHovF2L2tp3LP6ftjAA/1b81Xsj4RI1c+tD5A9BIJqPRzQX/ZR5wj0+IyePPGHODXlYtSARk8/NfX7smB4DZ1hiZRTzUxKICRB7k3Sf31Cu0KHKMcAVlkcmbBi0R4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143421; c=relaxed/simple; bh=dJEHobYU4RAHvYMcm55K9ocTqTIw/eTQ1MP9+/bomEI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QHZ9mWsNpkxHXqm+oDtS04GyEcdoHq2Mb89d//mw4F6NXOqamOap5uVzoZ7aONLIeb7TllzqS9qdjDsZqZz8QyLE5XUcEN0XHl+R6vJkaOl4y8bTP9UPG6WbQy2ABw6t85NJJ58fJL2zLzPEGAXwStpMrqoe4STV5q/JA8gZUBw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=JXqywgaU; arc=none smtp.client-ip=117.135.210.5 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="JXqywgaU" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=d2 V/uhA5Nr+uRfziR+eTgIF69MwF6vW6j3mSPlkQWts=; b=JXqywgaUFeeClJZSXx mSqZu428DXcW0wPcCMvc6/B6tfeekiBQw4g6fCxpwkP50+hfWNV0D7Bm4dEmbmgR x9NHEZb6/oYCm5gcyvFkzLiYRitoJsFqlDvPMCHK+2/XPdKr3EiGAEuorN7rN82q 4U5HkDY+1n37qEXrYdr5PjI3g= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgCH_E+Ia7NqXdW_Aw--.58316S5; Wed, 23 Sep 2026 14:02:57 +0800 (CST) From: Baolin Liu To: linkinjeon@kernel.org, hyc.lee@gmail.com Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org, chenxiaosong@chenxiaosong.com, Baolin Liu Subject: [PATCH v4 03/10] ntfs: propagate EA write and truncate errors Date: Wed, 23 Sep 2026 14:02:39 +0800 Message-ID: <20260923060246.2499192-4-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260923060246.2499192-1-liubaolin12138@163.com> References: <20260923060246.2499192-1-liubaolin12138@163.com> 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 X-CM-TRANSID: PygvCgCH_E+Ia7NqXdW_Aw--.58316S5 X-Coremail-Antispam: 1Uf129KBjvdXoW7GFyfWFW7ZF4DXF17XF17Awb_yoWkZwc_X3 WxZF1kKw4qqF1kGanrArnI9r1qqa18ZF1qvwn5K3Wvy34Dtryqyrn8Wrs5KFsF9a1vkr98 Gwn3WF13AF13GjkaLaAFLSUrUUUUjb8apTn2vfkv8UJUUUU8Yxn0WfASr-VFAUDa7-sFnT 9fnUUvcSsGvfC2KfnxnUUI43ZEXa7IUnEtC3UUUUU== X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6hKGUWqza5IR4wAA39 Content-Type: text/plain; charset="utf-8" From: Baolin Liu ntfs_write_ea() replaces negative write errors with EIO and ignores errors when shrinking the attribute after writing a shorter list. This can report success even though the final resize failed. Preserve negative write errors, use EIO for short writes, and return truncate failures to the caller. Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee --- fs/ntfs/ea.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 2ee700f8227e..98ca9e4f8d25 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -32,12 +32,12 @@ static int ntfs_write_ea(struct ntfs_inode *ni, __le32 = type, char *value, s64 ea =20 written =3D ntfs_inode_attr_pwrite(ea_vi, ea_off, ea_size, value, false); if (written !=3D ea_size) - err =3D -EIO; + err =3D written < 0 ? written : -EIO; else { struct ntfs_inode *ea_ni =3D NTFS_I(ea_vi); =20 if (need_truncate && ea_ni->data_size > ea_off + ea_size) - ntfs_attr_truncate(ea_ni, ea_off + ea_size); + err =3D ntfs_attr_truncate(ea_ni, ea_off + ea_size); mark_mft_record_dirty(ni); } =20 --=20 2.51.0 From nobody Thu Sep 24 13:41:54 2026 Received: from m16.mail.163.com (m16.mail.163.com [117.135.210.4]) (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 690AA415F35 for ; Wed, 23 Sep 2026 06:03:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=117.135.210.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143418; cv=none; b=pO1A+veReTG+n8CanRFtBfJq9BZyilqKU7h6CHIKNSl55nC910toxTiqizqlZTOVqCyKTNUj1B3UWaOUBzN0QN6xPGPhMwqAR0hkVlOmFNv9+zyrNELsASBCy+EXHNTSbF5L5+AaIc9uSQC6amj6yeqPQnmtd/7OS3dQ1E+5+mc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143418; c=relaxed/simple; bh=4Bxu09lDN3sGYhPbkWjN2XGPea3D6GgYqM6hQhRqWHs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Kjtr7vyg5r49iS2twZeKRNIx+y4709hZoPJ+h/ohvRB59bMnYJXHOApzf/vr81WivMRbU/FJhIxHUR96E5yNX6RNhH0Q0bXU8b5Q86NtS4eQP0wW6IxikUSnXygOP7FAlUtHQam8XRYsrQ3KeIYDeEAwd8Sf708YAa7LEv1LQtU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=LsCz8QFC; arc=none smtp.client-ip=117.135.210.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="LsCz8QFC" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=5H pexEt4aloWm+EqmZ+/bxGP1VV1dZYIoQw3Ij9JXjc=; b=LsCz8QFCI6SGBJdQve C/FWbmr9GxWUCY1TQwqErywH2rKJbluEplha0V36xpcl8GxymbKMFtrf8glPciw7 tlrWgG7N0hA+7PHCl/hpkqdpNKzxDy/PbWC8BbdwG8d/KoPz3jiyVhzaQ2h0RMIC BuV+2q5gaimYKUqDe1vD11MVo= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgCH_E+Ia7NqXdW_Aw--.58316S6; Wed, 23 Sep 2026 14:02:59 +0800 (CST) From: Baolin Liu To: linkinjeon@kernel.org, hyc.lee@gmail.com Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org, chenxiaosong@chenxiaosong.com, Baolin Liu Subject: [PATCH v4 04/10] ntfs: preserve EA attribute lookup errors Date: Wed, 23 Sep 2026 14:02:40 +0800 Message-ID: <20260923060246.2499192-5-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260923060246.2499192-1-liubaolin12138@163.com> References: <20260923060246.2499192-1-liubaolin12138@163.com> 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 X-CM-TRANSID: PygvCgCH_E+Ia7NqXdW_Aw--.58316S6 X-Coremail-Antispam: 1Uf129KBjvJXoWxuryxurWxtr4UXr13Aw4fXwb_yoW5KrW7pF yfGry3twsxX3ZxKFn7tr1DX3WYvas7GryUKr9rK3Z2kr1rt34Dtw15KF9Y9FyFyrWrA340 gF4UKay7Cr47Xr7anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07jjBTOUUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbCwROGUWqza5O9NgAA3v Content-Type: text/plain; charset="utf-8" From: Baolin Liu ntfs_attr_exist() treats lookup failures as absence. Using it while updating EAs can select creation or removal paths after an allocation or I/O failure. Use an EA-local lookup helper that preserves errors from mapping the MFT record, allocating the search context and looking up the attribute. Check both EA attributes before modifying either and return EUCLEAN if only one exists. Keep the known presence state for the final update. Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee --- fs/ntfs/ea.c | 51 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 98ca9e4f8d25..6f7273f754ae 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -181,6 +181,29 @@ static inline int ea_packed_size(const struct ea_attr = *p_ea) return 5 + p_ea->ea_name_length + le16_to_cpu(p_ea->ea_value_length); } =20 +/* Unlike ntfs_attr_exist(), preserve lookup errors. */ +static int ntfs_ea_attr_lookup(struct ntfs_inode *ni, __le32 type) +{ + struct ntfs_attr_search_ctx *ctx; + struct mft_record *mrec; + int err; + + mrec =3D map_mft_record(ni); + if (IS_ERR(mrec)) + return PTR_ERR(mrec); + ctx =3D ntfs_attr_get_search_ctx(ni, mrec); + if (!ctx) { + err =3D -ENOMEM; + goto out; + } + err =3D ntfs_attr_lookup(type, AT_UNNAMED, 0, CASE_SENSITIVE, + 0, NULL, 0, ctx); + ntfs_attr_put_search_ctx(ctx); +out: + unmap_mft_record(ni); + return err; +} + /* * Set a new EA, and set EA_INFORMATION accordingly * @@ -198,7 +221,8 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, { struct ntfs_inode *ni =3D NTFS_I(inode); struct ea_information *p_ea_info =3D NULL; - int ea_packed, err =3D 0; + int ea_packed, err =3D 0, ea_err; + bool has_ea; struct ea_attr *p_ea; u32 ea_info_qsize =3D 0; char *ea_buf =3D NULL; @@ -211,7 +235,16 @@ static int ntfs_set_ea(struct inode *inode, const char= *name, size_t name_len, if (name_len > 255) return -ENAMETOOLONG; =20 - if (ntfs_attr_exist(ni, AT_EA_INFORMATION, AT_UNNAMED, 0)) { + err =3D ntfs_ea_attr_lookup(ni, AT_EA_INFORMATION); + if (err && err !=3D -ENOENT) + return err; + ea_err =3D ntfs_ea_attr_lookup(ni, AT_EA); + if (ea_err && ea_err !=3D -ENOENT) + return ea_err; + if (!err !=3D !ea_err) + return -EUCLEAN; + has_ea =3D !err; + if (has_ea) { p_ea_info =3D ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0, &ea_info_size); if (IS_ERR(p_ea_info)) { @@ -230,15 +263,9 @@ static int ntfs_set_ea(struct inode *inode, const char= *name, size_t name_len, ea_buf =3D NULL; goto out; } - if (!ea_buf) { - ea_info_qsize =3D 0; - kvfree(p_ea_info); - goto create_ea_info; - } =20 ea_info_qsize =3D le32_to_cpu(p_ea_info->ea_query_length); } else { -create_ea_info: p_ea_info =3D kzalloc_obj(struct ea_information, GFP_NOFS); if (!p_ea_info) return -ENOMEM; @@ -249,12 +276,6 @@ static int ntfs_set_ea(struct inode *inode, const char= *name, size_t name_len, if (err) goto out; =20 - if (ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) { - err =3D ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0); - if (err) - goto out; - } - goto alloc_new_ea; } =20 @@ -404,7 +425,7 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, /* * no EA or EA_INFORMATION : add them */ - if (!ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) { + if (!has_ea) { err =3D ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, ea_buf, ea_info_qsize + new_ea_size); if (err) --=20 2.51.0 From nobody Thu Sep 24 13:41:54 2026 Received: from m16.mail.163.com (m16.mail.163.com [220.197.31.3]) (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 CD09C4156EB for ; Wed, 23 Sep 2026 06:03:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=220.197.31.3 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143423; cv=none; b=r+Ys9FhT4EuRYGADfxUYj4MydBwLloLY+kTwJHboAj7Ch5aVHko8YmhsmjnvSa8PwocAoy3jeb4NWLfcNFXPosRjFfysq1+Lj0SQg3SFObqDzsEHmZvvUa2jpYUrrfr0RN9PqXwRaHaQ5eBLpEebT2HxOA9n7VsbX6DC2ck6k3I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143423; c=relaxed/simple; bh=sBOlCUJEiFsTGPin5Ftx4LyMsLxa/4JQEqaw1fbH5tE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hswCAQreLeI5qnY62RRJtIQSWpCHlnfgnLd6d0bKEpZ9Vpm+pgWoDUvc4ijRuQ2SSBXYJAR9bhAtT6kaTZ/5hMN/73ybLBVAoTOuvLEyHhX5ulsmkT11DZcLTE5YiAFbl1aYqeT2V6N60YqqQPWXsezfdiqdmt533Tid5JuwcIo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=YsZx6SRz; arc=none smtp.client-ip=220.197.31.3 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="YsZx6SRz" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=Cn LdFu/70ahvxUxf+KGnrcWNDnhJLnBlLFh9tP9Tuko=; b=YsZx6SRz04u9CaxaOs 1aUmf/Ktl+LG6dnODXslWev5zgQnGFCDclOH+7TnlkGJ/UNftSuSqVEAhOl/HZ6A LR/3DQODowcB531sw9e+ZGMkDFEQKoIMGObwsbopQu08KXBZ9jO5xmFpqJCZ8BHj 5Oc4M/pLU0qV5CHMUGseu9KK4= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgCH_E+Ia7NqXdW_Aw--.58316S7; Wed, 23 Sep 2026 14:03:01 +0800 (CST) From: Baolin Liu To: linkinjeon@kernel.org, hyc.lee@gmail.com Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org, chenxiaosong@chenxiaosong.com, Baolin Liu Subject: [PATCH v4 05/10] ntfs: distinguish empty EA values from removal Date: Wed, 23 Sep 2026 14:02:41 +0800 Message-ID: <20260923060246.2499192-6-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260923060246.2499192-1-liubaolin12138@163.com> References: <20260923060246.2499192-1-liubaolin12138@163.com> 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 X-CM-TRANSID: PygvCgCH_E+Ia7NqXdW_Aw--.58316S7 X-Coremail-Antispam: 1Uf129KBjvJXoWxCr4UKrW5JF4UCw17AF4DXFb_yoW5WryDpF W3CrW5tF1rXFyDGr1vyw1kW3W5Zry8Kry8K3s7K3Wfurnaqw1Dt3Z5KFyagF4SyFykJ3W0 ganYgws5Xa17JFJanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07jjyIUUUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6hWHUmqza5USSAAA3X Content-Type: text/plain; charset="utf-8" From: Baolin Liu The EA setter identifies removal using a zero value length together with XATTR_REPLACE. A replacement with a valid empty value is therefore mistaken for deletion. Use a NULL value to identify removal and keep zero-length values valid. Reject removal or XATTR_REPLACE for an absent entry before creating metadata, including when the file has no EA attributes. Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee --- fs/ntfs/ea.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 6f7273f754ae..03117749728b 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -244,6 +244,8 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, if (!err !=3D !ea_err) return -EUCLEAN; has_ea =3D !err; + if (!has_ea && (!value || (flags & XATTR_REPLACE))) + return -ENODATA; if (has_ea) { p_ea_info =3D ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0, &ea_info_size); @@ -308,7 +310,7 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, err =3D -EEXIST; goto out; } - if ((flags & XATTR_REPLACE) && !val_size) { + if (!value) { old_ea_info =3D *p_ea_info; old_ea_buf =3D kvmemdup(ea_buf, all_ea_size, GFP_NOFS); if (!old_ea_buf) { @@ -318,7 +320,7 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, } =20 /* Check the final $EA size before removing the old entry. */ - if (val_size && + if (value && ntfs_attr_size_bounds_check(ni->vol, AT_EA, ea_info_qsize - ea_size + new_ea_size)) { err =3D -EFBIG; @@ -327,7 +329,7 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, =20 p_ea =3D (struct ea_attr *)(ea_buf + ea_off); =20 - if (val_size && + if (value && le16_to_cpu(p_ea->ea_value_length) =3D=3D val_size && !memcmp(p_ea->ea_name + p_ea->ea_name_length + 1, value, val_size)) @@ -342,7 +344,7 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, ea_info_qsize -=3D ea_size; p_ea_info->ea_query_length =3D cpu_to_le32(ea_info_qsize); =20 - if ((flags & XATTR_REPLACE) && !val_size && !ea_info_qsize) { + if (!value && !ea_info_qsize) { err =3D ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0); if (err) goto out; @@ -357,7 +359,7 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, goto out; } =20 - if ((flags & XATTR_REPLACE) && !val_size) { + if (!value) { err =3D ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, true); if (err) { @@ -378,7 +380,7 @@ static int ntfs_set_ea(struct inode *inode, const char = *name, size_t name_len, goto out; } } else { - if (flags & XATTR_REPLACE) { + if (!value || (flags & XATTR_REPLACE)) { err =3D -ENODATA; goto out; } --=20 2.51.0 From nobody Thu Sep 24 13:41:54 2026 Received: from m16.mail.163.com (m16.mail.163.com [220.197.31.3]) (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 CB18B3E0C55 for ; Wed, 23 Sep 2026 06:03:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=220.197.31.3 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143425; cv=none; b=r0jy/q6wR6zt2anGuQ5qry7ERsrfAvt3oKDeFz5kqJvqpJukYoOIldx2F7MvIGm4iiFYD7H0YrOQYoS/Q8935EqqJUB1ZfLdGR0sorGuuz2/SXE5hXFBx2XRxOtOoItrq1UCDPykNV6z8BUC/nk19Eu71COrH8Oq90ZdmWU+BpY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143425; c=relaxed/simple; bh=MqzwoeFZEE+TllkMXFEbT0heP9+AL5eawgbPWbGNxpA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UHZJb55qPFT5NMuYJka6nQExhX1Ybx+R1BLj5a4RHtc6nx5hbJe9AejZ1p0sL7+Zf2qTTKsOrl/2t9ypx/K+tbPMMAsp2iONFWhuTqE2GhLlIvsbmtd54KXJBLbuNsmakMnTZT9rNIeZ4NBI+Se0in4aLyq0+jlKmsnF9tGRF70= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=FBI7NAd2; arc=none smtp.client-ip=220.197.31.3 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="FBI7NAd2" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=+L kJBGVlVooo7JK7wOQ3HZSobplKnMQj45yigZbQwvo=; b=FBI7NAd2lC1DJWNe3/ JeeeoGFZ79ctYGRGXmOloG1Oos83ONWP1rlPMwlghquauNPVIQSwSByU5ssYDCF8 uQsdB65tr+5b779V9t0QTv07whzy9XL3ZJWlMLeUAYlxLE8oKPTO7PdEo2WEZ/2B Ge2+7GQKpP1W00OBwNPmZnfo4= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgCH_E+Ia7NqXdW_Aw--.58316S8; Wed, 23 Sep 2026 14:03:03 +0800 (CST) From: Baolin Liu To: linkinjeon@kernel.org, hyc.lee@gmail.com Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org, chenxiaosong@chenxiaosong.com, Baolin Liu Subject: [PATCH v4 06/10] ntfs: restore EA attributes when an update fails Date: Wed, 23 Sep 2026 14:02:42 +0800 Message-ID: <20260923060246.2499192-7-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260923060246.2499192-1-liubaolin12138@163.com> References: <20260923060246.2499192-1-liubaolin12138@163.com> 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 X-CM-TRANSID: PygvCgCH_E+Ia7NqXdW_Aw--.58316S8 X-Coremail-Antispam: 1Uf129KBjvAXoW3tFWkurW3uFyUAw13uryfZwb_yoW8JryfZo Wa9r45Gw1kuryjkw17Kw4qyr9xXF95K3yUJw4YkanFkF4j9w1Dtwnxt3WFgaySg3WrKF4D Ww1Sqwn7CF18Jryxn29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvjxUxNBMDUUUU X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6heHUmqza5cSbgAA3x Content-Type: text/plain; charset="utf-8" From: Baolin Liu Creating or replacing an EA can leave EA data and information out of sync if a later update fails. Existing deletion rollback ignores its own errors, and creation changes metadata before preparing the new list. Prepare the complete new list while retaining both original attributes. Update EA data and information only after preparation succeeds, restore both on failure, and publish the cached presence state only on success. Keep structure errors as EUCLEAN and preserve operation errors. Mark the volume erroneous and shut it down if either restoration fails. This handles synchronous failures; it does not make the two updates atomic across power loss or guarantee restoration on a failing device. Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee --- fs/ntfs/ea.c | 349 ++++++++++++++++++++------------------------------- 1 file changed, 138 insertions(+), 211 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 03117749728b..8eafc7b53aab 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -204,262 +204,189 @@ static int ntfs_ea_attr_lookup(struct ntfs_inode *n= i, __le32 type) return err; } =20 +/* A NULL value means that the attribute should not exist. */ +static int ntfs_ea_update_attr(struct ntfs_inode *ni, __le32 type, + char *value, s64 size) +{ + int err =3D ntfs_ea_attr_lookup(ni, type); + + if (err && err !=3D -ENOENT) + return err; + if (!value) + return err =3D=3D -ENOENT ? 0 : + ntfs_attr_remove(ni, type, AT_UNNAMED, 0); + if (err =3D=3D -ENOENT) + return ntfs_attr_add(ni, type, AT_UNNAMED, 0, value, size); + return ntfs_write_ea(ni, type, value, 0, size, true); +} + /* - * Set a new EA, and set EA_INFORMATION accordingly - * - * This is roughly the same as ZwSetEaFile() on Windows, however - * the "offset to next" of the last EA should not be cleared. - * - * Consistency of the new EA is first checked. - * - * EA_INFORMATION is set first, and it is restored to its former - * state if setting EA fails. + * Prepare the new EA list before changing either NTFS attribute. Keep the + * original values until both updates succeed, so a failed update can be + * rolled back. This does not provide atomicity across a crash or power lo= ss. */ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_= len, const void *value, size_t val_size, int flags, __le16 *packed_ea_size) { struct ntfs_inode *ni =3D NTFS_I(inode); - struct ea_information *p_ea_info =3D NULL; - int ea_packed, err =3D 0, ea_err; - bool has_ea; - struct ea_attr *p_ea; - u32 ea_info_qsize =3D 0; - char *ea_buf =3D NULL; - char *new_ea_buf; - char *old_ea_buf =3D NULL; - struct ea_information old_ea_info; - size_t new_ea_size =3D ALIGN(struct_size(p_ea, ea_name, 1 + name_len + va= l_size), 4); - s64 ea_off, ea_info_size, all_ea_size, ea_size; - - if (name_len > 255) + struct ea_information *old_info =3D NULL, info =3D {}; + char *old_ea =3D NULL, *ea =3D NULL; + struct ea_attr *entry; + s64 old_size =3D 0, info_size, offset =3D 0, entry_size =3D 0; + u32 size =3D 0, new_size, next; + size_t add_size; + int err, info_err, ea_err, packed =3D 0, needed =3D 0; + bool exists, remove =3D !value; + + if (name_len > U8_MAX) return -ENAMETOOLONG; + if (val_size > U16_MAX) + return -E2BIG; =20 err =3D ntfs_ea_attr_lookup(ni, AT_EA_INFORMATION); if (err && err !=3D -ENOENT) return err; - ea_err =3D ntfs_ea_attr_lookup(ni, AT_EA); - if (ea_err && ea_err !=3D -ENOENT) - return ea_err; - if (!err !=3D !ea_err) + exists =3D !err; + err =3D ntfs_ea_attr_lookup(ni, AT_EA); + if (err && err !=3D -ENOENT) + return err; + /* Do not silently discard an orphaned attribute. */ + if (exists !=3D !err) return -EUCLEAN; - has_ea =3D !err; - if (!has_ea && (!value || (flags & XATTR_REPLACE))) - return -ENODATA; - if (has_ea) { - p_ea_info =3D ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0, - &ea_info_size); - if (IS_ERR(p_ea_info)) { - err =3D PTR_ERR(p_ea_info); - p_ea_info =3D NULL; - goto out; - } - if (ea_info_size !=3D sizeof(struct ea_information)) { + + if (exists) { + old_info =3D ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0, + &info_size); + if (IS_ERR(old_info)) + return PTR_ERR(old_info); + if (info_size !=3D sizeof(*old_info)) { err =3D -EUCLEAN; goto out; } - - ea_buf =3D ntfs_attr_readall(ni, AT_EA, NULL, 0, &all_ea_size); - if (IS_ERR(ea_buf)) { - err =3D PTR_ERR(ea_buf); - ea_buf =3D NULL; + old_ea =3D ntfs_attr_readall(ni, AT_EA, NULL, 0, &old_size); + if (IS_ERR(old_ea)) { + err =3D PTR_ERR(old_ea); + old_ea =3D NULL; goto out; } - - ea_info_qsize =3D le32_to_cpu(p_ea_info->ea_query_length); - } else { - p_ea_info =3D kzalloc_obj(struct ea_information, GFP_NOFS); - if (!p_ea_info) - return -ENOMEM; - - ea_info_qsize =3D 0; - err =3D ntfs_attr_add(ni, AT_EA_INFORMATION, AT_UNNAMED, 0, - (char *)p_ea_info, sizeof(struct ea_information)); - if (err) + size =3D le32_to_cpu(old_info->ea_query_length); + if (size > old_size) { + err =3D -EUCLEAN; goto out; - - goto alloc_new_ea; - } - - if (ea_info_qsize > all_ea_size) { - err =3D -EUCLEAN; - goto out; + } } =20 - /* Validate the whole chain before modifying it, including its tail. */ - err =3D ntfs_ea_lookup(ea_buf, ea_info_qsize, NULL, 0, &ea_off, - &ea_size); + /* Validate even the entries following the one being replaced. */ + err =3D ntfs_ea_lookup(old_ea, size, NULL, 0, &offset, &entry_size); if (err !=3D -ENOENT) goto out; - /* A zero tail offset must be linked before appending another EA. */ - for (ea_off =3D 0; ea_off < ea_info_qsize; ea_off +=3D ea_size) { - p_ea =3D (struct ea_attr *)(ea_buf + ea_off); - ea_size =3D le32_to_cpu(p_ea->next_entry_offset); - if (!ea_size) { - ea_size =3D ea_info_qsize - ea_off; - p_ea->next_entry_offset =3D cpu_to_le32(ea_size); - } - } - - err =3D ntfs_ea_lookup(ea_buf, ea_info_qsize, name, name_len, &ea_off, - &ea_size); + err =3D ntfs_ea_lookup(old_ea, size, name, name_len, &offset, &entry_size= ); if (err && err !=3D -ENOENT) goto out; - if (ea_info_qsize && !err) { + if (!err) { if (flags & XATTR_CREATE) { err =3D -EEXIST; goto out; } - if (!value) { - old_ea_info =3D *p_ea_info; - old_ea_buf =3D kvmemdup(ea_buf, all_ea_size, GFP_NOFS); - if (!old_ea_buf) { - err =3D -ENOMEM; - goto out; - } - } - - /* Check the final $EA size before removing the old entry. */ - if (value && - ntfs_attr_size_bounds_check(ni->vol, AT_EA, - ea_info_qsize - ea_size + new_ea_size)) { - err =3D -EFBIG; - goto out; - } - - p_ea =3D (struct ea_attr *)(ea_buf + ea_off); - - if (value && - le16_to_cpu(p_ea->ea_value_length) =3D=3D val_size && - !memcmp(p_ea->ea_name + p_ea->ea_name_length + 1, value, - val_size)) - goto out; - - le16_add_cpu(&p_ea_info->ea_length, 0 - ea_packed_size(p_ea)); - - if (p_ea->flags & NEED_EA) - le16_add_cpu(&p_ea_info->need_ea_count, -1); - - memmove((char *)p_ea, (char *)p_ea + ea_size, ea_info_qsize - (ea_off + = ea_size)); - ea_info_qsize -=3D ea_size; - p_ea_info->ea_query_length =3D cpu_to_le32(ea_info_qsize); - - if (!value && !ea_info_qsize) { - err =3D ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0); - if (err) - goto out; - - err =3D ntfs_attr_remove(ni, AT_EA_INFORMATION, AT_UNNAMED, 0); - if (err) { - /* Restore the original $EA if $EA_INFORMATION removal failed. */ - ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, old_ea_buf, - all_ea_size); - ea_info_qsize =3D le32_to_cpu(old_ea_info.ea_query_length); - } - goto out; - } - - if (!value) { - err =3D ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, - true); - if (err) { - ntfs_write_ea(ni, AT_EA, old_ea_buf, 0, - all_ea_size, false); - goto out; - } - - err =3D ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, - 0, sizeof(struct ea_information), false); - if (err) { - ntfs_write_ea(ni, AT_EA, old_ea_buf, 0, - all_ea_size, false); - ntfs_write_ea(ni, AT_EA_INFORMATION, - (char *)&old_ea_info, 0, - sizeof(old_ea_info), false); - } - goto out; + entry =3D (struct ea_attr *)(old_ea + offset); + if (!remove && le16_to_cpu(entry->ea_value_length) =3D=3D val_size && + !memcmp(entry->ea_name + name_len + 1, value, val_size)) { + info =3D *old_info; + goto done; } } else { - if (!value || (flags & XATTR_REPLACE)) { + if (remove || (flags & XATTR_REPLACE)) { err =3D -ENODATA; goto out; } + offset =3D size; + entry_size =3D 0; + } =20 - if (ntfs_attr_size_bounds_check(ni->vol, AT_EA, - ea_info_qsize + new_ea_size)) { - err =3D -EFBIG; + add_size =3D remove ? 0 : ALIGN(sizeof(*entry) + name_len + 1 + val_size,= 4); + new_size =3D size - entry_size + add_size; + if (new_size && ntfs_attr_size_bounds_check(ni->vol, AT_EA, new_size)) { + err =3D -EFBIG; + goto out; + } + if (new_size) { + ea =3D kvzalloc(new_size, GFP_NOFS); + if (!ea) { + err =3D -ENOMEM; goto out; } + if (offset) + memcpy(ea, old_ea, offset); + if (size > offset + entry_size) + memcpy(ea + offset, old_ea + offset + entry_size, + size - offset - entry_size); } -alloc_new_ea: - new_ea_buf =3D kvzalloc(ea_info_qsize + new_ea_size, GFP_NOFS); - if (!new_ea_buf) { - err =3D -ENOMEM; - goto out; + size -=3D entry_size; + + /* Link a zero-terminated old tail before appending the new entry. */ + for (offset =3D 0; offset < size; offset +=3D next) { + entry =3D (struct ea_attr *)(ea + offset); + next =3D le32_to_cpu(entry->next_entry_offset); + if (!next) { + next =3D size - offset; + entry->next_entry_offset =3D cpu_to_le32(next); + } + packed +=3D ea_packed_size(entry); + if (entry->flags & NEED_EA) + needed++; } - if (ea_info_qsize) - memcpy(new_ea_buf, ea_buf, ea_info_qsize); - kvfree(ea_buf); - ea_buf =3D new_ea_buf; - p_ea =3D (struct ea_attr *)(ea_buf + ea_info_qsize); - - /* - * EA and REPARSE_POINT compatibility not checked any more, - * required by Windows 10, but having both may lead to - * problems with earlier versions. - */ - memcpy(p_ea->ea_name, name, name_len); - p_ea->ea_name_length =3D name_len; - p_ea->ea_name[name_len] =3D 0; - memcpy(p_ea->ea_name + name_len + 1, value, val_size); - p_ea->ea_value_length =3D cpu_to_le16(val_size); - p_ea->next_entry_offset =3D cpu_to_le32(new_ea_size); - - ea_packed =3D le16_to_cpu(p_ea_info->ea_length) + ea_packed_size(p_ea); - p_ea_info->ea_length =3D cpu_to_le16(ea_packed); - p_ea_info->ea_query_length =3D cpu_to_le32(ea_info_qsize + new_ea_size); - - if (ea_packed > 0xffff) { + if (!remove) { + entry =3D (struct ea_attr *)(ea + size); + entry->next_entry_offset =3D cpu_to_le32(add_size); + entry->ea_name_length =3D name_len; + entry->ea_value_length =3D cpu_to_le16(val_size); + memcpy(entry->ea_name, name, name_len); + memcpy(entry->ea_name + name_len + 1, value, val_size); + packed +=3D ea_packed_size(entry); + } + if (packed > U16_MAX) { err =3D -EFBIG; goto out; } - - /* - * no EA or EA_INFORMATION : add them - */ - if (!has_ea) { - err =3D ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, ea_buf, - ea_info_qsize + new_ea_size); - if (err) - goto out; - } else { - err =3D ntfs_write_ea(ni, AT_EA, ea_buf, 0, - ea_info_qsize + new_ea_size, true); - if (err) - goto out; - } - - err =3D ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0, - sizeof(struct ea_information), false); - if (err) + info.ea_length =3D cpu_to_le16(packed); + info.need_ea_count =3D cpu_to_le16(needed); + info.ea_query_length =3D cpu_to_le32(new_size); + + /* All preparation is complete; errors from here require rollback. */ + err =3D ntfs_ea_update_attr(ni, AT_EA, ea, new_size); + if (!err) + err =3D ntfs_ea_update_attr(ni, AT_EA_INFORMATION, + new_size ? (char *)&info : NULL, + sizeof(info)); + if (err) { + /* Try both restorations even if the first one fails. */ + ea_err =3D ntfs_ea_update_attr(ni, AT_EA, old_ea, old_size); + info_err =3D ntfs_ea_update_attr(ni, AT_EA_INFORMATION, + (char *)old_info, sizeof(info)); + if (ea_err || info_err) { + NVolSetErrors(ni->vol); + NVolSetShutdown(ni->vol); + ntfs_error(inode->i_sb, + "Failed to restore EAs of inode 0x%llx (%d, %d); shutting down.", + ni->mft_no, ea_err, info_err); + err =3D -EIO; + } goto out; - - if (packed_ea_size) - *packed_ea_size =3D p_ea_info->ea_length; - ea_info_qsize +=3D new_ea_size; + } + size =3D new_size; mark_mft_record_dirty(ni); +done: + if (packed_ea_size) + *packed_ea_size =3D info.ea_length; + if (size) + NInoSetHasEA(ni); + else + NInoClearHasEA(ni); out: - if (!err) { - if (ea_info_qsize > 0) - NInoSetHasEA(ni); - else - NInoClearHasEA(ni); - } - - kvfree(ea_buf); - kvfree(old_ea_buf); - kvfree(p_ea_info); - + kvfree(ea); + kvfree(old_ea); + kvfree(old_info); return err; } =20 --=20 2.51.0 From nobody Thu Sep 24 13:41:54 2026 Received: from m16.mail.163.com (m16.mail.163.com [220.197.31.4]) (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 A933B3F65E0 for ; Wed, 23 Sep 2026 06:03:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=220.197.31.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143424; cv=none; b=t4SYd6c478UKe5ttwQT4md2Z7hTQgpJTIniGUGAeTR6V/6TMSna3xgDag24fWTBBXOADeqDem4l9wMaSFrb2rYtLlpNsju5iQnrvqnVf28OxQkDWGCBRLgzK7yEjr2qHPAYNs6nolI0gbmGZjhyS3Uaa2Q2+TsYQGjixaads61E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143424; c=relaxed/simple; bh=SLvIRF/BQQYJeKvS9wwLImD5kOoExEE25bLIsUlvVXc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hKyf9vazBuxQdFMi3VIaDBrtGgToGsDuhF+JlFUEoFe60H1tGj1mPbRmkolj5V1nCSGT4Eg/gVzaNl7SR9YsBjZs3KCwFvgvVz9/zlFV/s7WcGLntexEx7eQoQWeuQ5jCblDWlYvQM+ZFdIbYNUKunEmnWqEsS4A+E+875o70Ik= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=MK6Tc/0C; arc=none smtp.client-ip=220.197.31.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="MK6Tc/0C" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=by VHPD1vO1LiJiOEAeBdTEB0gw669olCIOtfkHmfpt4=; b=MK6Tc/0CIdhpY/3Vsx YjRCCRnmGxZkOjCj7K9qnYLHjDmDdlAfnzgUW2F02qZMzo59hrnM7vY5+smigl3F j0AmRgSoyXekm/8QdYpjFiuLVAPmmjBlZFJ52wFIllu0kmMlIGlMa9oPjpGGvqJv f6I0cr1cOkyBYA+8s0XBDbNX4= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgCH_E+Ia7NqXdW_Aw--.58316S9; Wed, 23 Sep 2026 14:03:04 +0800 (CST) From: Baolin Liu To: linkinjeon@kernel.org, hyc.lee@gmail.com Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org, chenxiaosong@chenxiaosong.com, Baolin Liu Subject: [PATCH v4 07/10] ntfs: add fileattr query support Date: Wed, 23 Sep 2026 14:02:43 +0800 Message-ID: <20260923060246.2499192-8-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260923060246.2499192-1-liubaolin12138@163.com> References: <20260923060246.2499192-1-liubaolin12138@163.com> 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 X-CM-TRANSID: PygvCgCH_E+Ia7NqXdW_Aw--.58316S9 X-Coremail-Antispam: 1Uf129KBjvJXoWxAry8Zr4DZw43uF48GFWxZwb_yoWrZw43pF Z3CFyfKa1rXFy2g392kFWjv3WY9ryxWw4UXryxX34IkFnrtr93KF1fK3WruFn5KrZ5A3yU XF1YqrWUGr45ZwUanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07j4Q6LUUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6RmIU2qza5nHOgAA39 Content-Type: text/plain; charset="utf-8" From: Baolin Liu Add fileattr_get operations for files and directories, as well as the existing symlink and special inode operation tables. Report compression, encryption, immutable and append-only from cached inode state, and FS_CASEFOLD_FL from the mount-wide case sensitivity policy. This introduces querying only. It neither reads a private flag EA nor allows userspace to change the reported flags. Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee --- Documentation/filesystems/ntfs.rst | 8 ++++++++ fs/ntfs/file.c | 33 ++++++++++++++++++++++++++++++ fs/ntfs/namei.c | 1 + fs/ntfs/ntfs.h | 2 ++ 4 files changed, 44 insertions(+) diff --git a/Documentation/filesystems/ntfs.rst b/Documentation/filesystems= /ntfs.rst index 4bfa392daec6..0a4c43d6c890 100644 --- a/Documentation/filesystems/ntfs.rst +++ b/Documentation/filesystems/ntfs.rst @@ -9,6 +9,7 @@ The Linux NTFS filesystem driver =20 - Overview - Utilities support + - Linux file attributes - Supported mount options =20 =20 @@ -34,6 +35,13 @@ The project is available at: https://github.com/ntfsprogs-plus/ntfsprogs-plus =20 =20 +Linux file attributes +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The driver supports querying compression, encryption, immutable, append-on= ly +and mount-wide case folding through lsattr(1). Case folding is reported +according to the mount options. + Supported mount options =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 99a2c7a5cf81..e5846bf498b0 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -15,6 +15,7 @@ #include #include #include +#include =20 #include "lcnalloc.h" #include "ntfs.h" @@ -136,6 +137,35 @@ static int ntfs_file_release(struct inode *vi, struct = file *filp) return 0; } =20 +/* + * ntfs_fileattr_get - inode_operations::fileattr_get + * @dentry: dentry to report the flags of + * @fa: filled in with the flags of @dentry + */ +int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) +{ + struct inode *vi =3D d_inode(dentry); + struct ntfs_inode *ni =3D NTFS_I(vi); + u32 flags =3D 0; + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (NInoCompressed(ni) || NInoWofCompressed(ni)) + flags |=3D FS_COMPR_FL; + if (NInoEncrypted(ni)) + flags |=3D FS_ENCRYPT_FL; + if (vi->i_flags & S_IMMUTABLE) + flags |=3D FS_IMMUTABLE_FL; + if (vi->i_flags & S_APPEND) + flags |=3D FS_APPEND_FL; + if (!NVolCaseSensitive(ni->vol)) + flags |=3D FS_CASEFOLD_FL; + + fileattr_fill_flags(fa, flags); + return 0; +} + /* * ntfs_file_fsync - sync a file to disk * @filp: file to be synced @@ -1234,6 +1264,7 @@ const struct file_operations ntfs_file_ops =3D { }; =20 const struct inode_operations ntfs_file_inode_ops =3D { + .fileattr_get =3D ntfs_fileattr_get, .setattr =3D ntfs_setattr, .getattr =3D ntfs_getattr, .listxattr =3D ntfs_listxattr, @@ -1243,12 +1274,14 @@ const struct inode_operations ntfs_file_inode_ops = =3D { }; =20 const struct inode_operations ntfs_symlink_inode_operations =3D { + .fileattr_get =3D ntfs_fileattr_get, .get_link =3D ntfs_get_link, .setattr =3D ntfs_setattr, .listxattr =3D ntfs_listxattr, }; =20 const struct inode_operations ntfs_special_inode_operations =3D { + .fileattr_get =3D ntfs_fileattr_get, .setattr =3D ntfs_setattr, .getattr =3D ntfs_getattr, .listxattr =3D ntfs_listxattr, diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 3e0adb9a0ea4..ee451700b766 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -1548,6 +1548,7 @@ static int ntfs_link(struct dentry *old_dentry, struc= t inode *dir, * Inode operations for directories. */ const struct inode_operations ntfs_dir_inode_ops =3D { + .fileattr_get =3D ntfs_fileattr_get, .lookup =3D ntfs_lookup, /* VFS: Lookup directory. */ .create =3D ntfs_create, .unlink =3D ntfs_unlink, diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h index dd56894762a9..a5c5c18671d2 100644 --- a/fs/ntfs/ntfs.h +++ b/fs/ntfs/ntfs.h @@ -183,6 +183,8 @@ extern const struct inode_operations ntfs_dir_inode_ops; extern const struct file_operations ntfs_empty_file_ops; extern const struct inode_operations ntfs_empty_inode_ops; =20 +int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); + extern const struct export_operations ntfs_export_ops; =20 /* --=20 2.51.0 From nobody Thu Sep 24 13:41:54 2026 Received: from m16.mail.163.com (m16.mail.163.com [220.197.31.4]) (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 5341D34EF0C for ; Wed, 23 Sep 2026 06:03:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=220.197.31.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143422; cv=none; b=oV++D9mCWje+qtwCqy3c50WirRzssn2la0LDr3mA6CCW9pRZoNzouSsdHN0lxx68L6u9dfoJgBL/2QFgTMAHU5BLcIB/a+1vGg4+U34+1sPntHXxMdSwQOGo4uJmHtK2y0z3vIsmpNJ2aDXNW2U6oh9BCMhfi5mbnSr0UIGIhzU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143422; c=relaxed/simple; bh=15DA8yMdbjM7OxjcYoVjhAcxAeHnFqKhKTszkfbMUS4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Q4nJhVtr2Op1aAoW0/kZNkzZ7KPDMUrFnQocVeVxar7aBHtKcuco7N26m/9ECX1+pxWKAzgUTIpbHgh0ZlLa1/fEJC/oVav8Qvbzf2UIVGR2apv93h/5kLSPg0K/YW3kWaqByIU4aJ0q/8KUEwKMX+O+rXPJ1jB8YdSOy/47ImI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=I+HsXE+8; arc=none smtp.client-ip=220.197.31.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="I+HsXE+8" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=8d dyUdS2m+i5tYUvvE2C4S+itWIORgCVuUU9h54hzLM=; b=I+HsXE+8l3eCSEFG5D AoCykIMFpyTi8FUUwWI8T700/yCcfdYYvfD+JPv8qy9dFjYdLes5n2fU78DjTP07 YkV51NxyCpnwciC0ERyiTGTGSLKKoFgaC7BlXKYf4WeGfQQTz0uXK/UVax3VKQ64 PGL1CAUNY1/hm7VD9Ajm2Z8U4= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgCH_E+Ia7NqXdW_Aw--.58316S10; Wed, 23 Sep 2026 14:03:06 +0800 (CST) From: Baolin Liu To: linkinjeon@kernel.org, hyc.lee@gmail.com Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org, chenxiaosong@chenxiaosong.com, Baolin Liu Subject: [PATCH v4 08/10] ntfs: persist immutable in the $LINUXFLAGS EA Date: Wed, 23 Sep 2026 14:02:44 +0800 Message-ID: <20260923060246.2499192-9-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260923060246.2499192-1-liubaolin12138@163.com> References: <20260923060246.2499192-1-liubaolin12138@163.com> 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 X-CM-TRANSID: PygvCgCH_E+Ia7NqXdW_Aw--.58316S10 X-Coremail-Antispam: 1Uf129KBjvAXoW3urW8KFykAw1fWF4fCrW5GFg_yoW8JF4Uto WS9w13uw4kCry5A3y0k3WxJ3yDWayUCrWxJrW5C3yDCFnIqw1Du3y2gws7uF47Kw15WF45 J34xXw4rJrW8XF98n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvjxUxMa0DUUUU X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6RqIU2qza5rHZwAA3g Content-Type: text/plain; charset="utf-8" From: Baolin Liu Add a fileattr setter for immutable and store its state in bit 0 of a little-endian u32 in the private $LINUXFLAGS EA. Reuse the standard NTFS EA attributes and restore the flag when loading an inode, so eviction and unmount do not discard the setting. Use $LINUXFLAGS to avoid the WSL-owned $LX namespace. Preserve unknown bits and delete the entry only when the entire value is zero. Reject malformed values with EUCLEAN. Reject direct xattr writes and removal to avoid bypassing fileattr permissions or stale cached state. Update the cached flags only after the EA update succeeds. Reject clearing immutable on system metadata files and files protected by sys_immutable, following inode-load policy. Do not persist a bit derived solely from this policy. Preserve append state without allowing it to be changed at this stage. Compression, encryption and mount-wide case folding remain read-only. Document the format and its Linux-only enforcement. Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee --- Documentation/filesystems/ntfs.rst | 18 +++++- fs/ntfs/ea.c | 49 ++++++++++++++++ fs/ntfs/ea.h | 6 ++ fs/ntfs/file.c | 90 +++++++++++++++++++++++++++++- fs/ntfs/inode.c | 12 ++-- fs/ntfs/inode.h | 2 + fs/ntfs/namei.c | 3 +- fs/ntfs/ntfs.h | 2 + 8 files changed, 171 insertions(+), 11 deletions(-) diff --git a/Documentation/filesystems/ntfs.rst b/Documentation/filesystems= /ntfs.rst index 0a4c43d6c890..173af66ae73f 100644 --- a/Documentation/filesystems/ntfs.rst +++ b/Documentation/filesystems/ntfs.rst @@ -38,9 +38,21 @@ The project is available at: Linux file attributes =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 -The driver supports querying compression, encryption, immutable, append-on= ly -and mount-wide case folding through lsattr(1). Case folding is reported -according to the mount options. +The driver supports lsattr(1) and setting immutable with chattr(1). +Immutable is stored in bit 0 of a private ``$LINUXFLAGS`` extended attribu= te +within the standard NTFS ``$EA``/``$EA_INFORMATION`` attributes. Its value +is a 32-bit little-endian bitmask. Unknown bits are preserved; the entry +is removed when the entire value becomes zero. The setting survives +inode eviction, unmount and reboot. Direct writes or removal through +xattr interfaces are rejected; use chattr(1) instead. + +System metadata files and files protected by ``sys_immutable`` cannot +have their immutable protection cleared. Protection derived solely from +that policy is not stored in the EA. Compression, encryption, append-only +and mount-wide case folding are reported but cannot be changed through +chattr(1). + +Windows does not enforce the Linux immutable flag stored in this EA. =20 Supported mount options =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 8eafc7b53aab..daa3b430ca76 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -482,6 +482,51 @@ int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t r= dev, __le16 *ea_size, return err; } =20 +int ntfs_ea_get_linuxflags(struct inode *inode) +{ + struct ntfs_inode *ni =3D NTFS_I(inode); + __le32 value; + int err; + + err =3D ntfs_get_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1, + &value, sizeof(value)); + if (err =3D=3D -ENODATA) + return 0; + if (err < 0) + return err =3D=3D -ERANGE ? -EUCLEAN : err; + if (err !=3D sizeof(value)) + return -EUCLEAN; + + ni->linuxflags =3D le32_to_cpu(value); + if (ni->linuxflags & NTFS_LINUXFLAGS_IMMUTABLE) + inode->i_flags |=3D S_IMMUTABLE; + return 0; +} + +int ntfs_ea_set_linuxflags(struct inode *inode, u32 linuxflags) +{ + struct ntfs_inode *ni =3D NTFS_I(inode); + __le32 value; + int err; + + if (linuxflags) { + value =3D cpu_to_le32(linuxflags); + err =3D ntfs_set_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1, + &value, sizeof(value), 0, NULL); + } else if (NInoHasEA(ni)) { + err =3D ntfs_set_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1, + NULL, 0, XATTR_REPLACE, NULL); + if (err =3D=3D -ENODATA) + err =3D 0; + } else { + err =3D 0; + } + + if (!err) + ni->linuxflags =3D linuxflags; + return err; +} + ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size) { struct inode *inode =3D d_inode(dentry); @@ -852,6 +897,10 @@ static int ntfs_setxattr(const struct xattr_handler *h= andler, if (NVolShutdown(ni->vol)) return -EIO; =20 + /* Only fileattr_set may change the flags and their cached state. */ + if (!strcmp(name, "$LINUXFLAGS")) + return -EPERM; + if (ntfs_is_reserved_lxattr(name) && !capable(CAP_SYS_ADMIN)) return -EPERM; =20 diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h index acb39c2a6fbc..5ec118e29425 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -7,6 +7,10 @@ #define NTFS_EA_GID BIT(2) #define NTFS_EA_MODE BIT(3) =20 +/* $LINUXFLAGS stores these bits in a single little-endian 32-bit value. */ +#define NTFS_LINUXFLAGS_IMMUTABLE BIT(0) +#define NTFS_LINUXFLAGS_MASK NTFS_LINUXFLAGS_IMMUTABLE + extern const struct xattr_handler *const ntfs_xattr_handlers[]; =20 int ntfs_ea_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode, dev_t = dev); @@ -14,6 +18,8 @@ int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rde= vp, unsigned int flags, bool *has_lxmod); int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size, unsigned int flags); +int ntfs_ea_get_linuxflags(struct inode *inode); +int ntfs_ea_set_linuxflags(struct inode *inode, u32 linuxflags); ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size); =20 #ifdef CONFIG_NTFS_FS_POSIX_ACL diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index e5846bf498b0..135e5be69e77 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -166,6 +166,87 @@ int ntfs_fileattr_get(struct dentry *dentry, struct fi= le_kattr *fa) return 0; } =20 +static bool ntfs_is_forced_immutable(struct inode *vi) +{ + struct ntfs_inode *ni =3D NTFS_I(vi); + + if (ni->mft_no < FILE_first_user && S_ISREG(vi->i_mode)) + return true; + + return NVolSysImmutable(ni->vol) && + (ni->flags & FILE_ATTR_SYSTEM) && + !S_ISFIFO(vi->i_mode) && !S_ISSOCK(vi->i_mode) && + !S_ISLNK(vi->i_mode); +} + +/* + * ntfs_fileattr_set - inode_operations::fileattr_set + * @idmap: idmap of the mount @dentry was found from + * @dentry: dentry to set the flags of + * @fa: flags to set + */ +int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, + struct file_kattr *fa) +{ + struct inode *vi =3D d_inode(dentry); + struct ntfs_inode *ni =3D NTFS_I(vi); + u32 allowed =3D FS_IMMUTABLE_FL; + u32 readonly =3D 0; + u32 linuxflags =3D ni->linuxflags & ~NTFS_LINUXFLAGS_MASK; + unsigned int new_fl =3D 0; + bool forced_immutable; + int err; + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (fileattr_has_fsx(fa)) + return -EOPNOTSUPP; + + if ((fa->flags & FS_APPEND_FL) !=3D + (IS_APPEND(vi) ? FS_APPEND_FL : 0)) + return -EOPNOTSUPP; + allowed |=3D fa->flags & FS_APPEND_FL; + + /* chattr passes the unchanged read-only flags back to us too. */ + if (NInoCompressed(ni) || NInoWofCompressed(ni)) + readonly |=3D FS_COMPR_FL; + if (NInoEncrypted(ni)) + readonly |=3D FS_ENCRYPT_FL; + if ((fa->flags ^ readonly) & (FS_COMPR_FL | FS_ENCRYPT_FL)) + return -EOPNOTSUPP; + allowed |=3D readonly; + + /* Case folding is a read-only, mount-wide property. */ + if (!NVolCaseSensitive(ni->vol)) + allowed |=3D FS_CASEFOLD_FL; + if (fa->flags & ~allowed) + return -EOPNOTSUPP; + + forced_immutable =3D ntfs_is_forced_immutable(vi); + if (!(fa->flags & FS_IMMUTABLE_FL) && forced_immutable) + return -EPERM; + + if (fa->flags & FS_IMMUTABLE_FL) { + new_fl |=3D S_IMMUTABLE; + /* Do not persist an immutable bit derived from the mount. */ + if (!forced_immutable || + (ni->linuxflags & NTFS_LINUXFLAGS_IMMUTABLE)) + linuxflags |=3D NTFS_LINUXFLAGS_IMMUTABLE; + } + + mutex_lock(&ni->mrec_lock); + err =3D ntfs_ea_set_linuxflags(vi, linuxflags); + mutex_unlock(&ni->mrec_lock); + if (err) + return err; + + inode_set_flags(vi, new_fl, S_IMMUTABLE); + inode_set_ctime_current(vi); + mark_inode_dirty(vi); + return 0; +} + /* * ntfs_file_fsync - sync a file to disk * @filp: file to be synced @@ -1264,9 +1345,10 @@ const struct file_operations ntfs_file_ops =3D { }; =20 const struct inode_operations ntfs_file_inode_ops =3D { - .fileattr_get =3D ntfs_fileattr_get, .setattr =3D ntfs_setattr, .getattr =3D ntfs_getattr, + .fileattr_get =3D ntfs_fileattr_get, + .fileattr_set =3D ntfs_fileattr_set, .listxattr =3D ntfs_listxattr, .get_acl =3D ntfs_get_acl, .set_acl =3D ntfs_set_acl, @@ -1274,17 +1356,19 @@ const struct inode_operations ntfs_file_inode_ops = =3D { }; =20 const struct inode_operations ntfs_symlink_inode_operations =3D { - .fileattr_get =3D ntfs_fileattr_get, .get_link =3D ntfs_get_link, .setattr =3D ntfs_setattr, .listxattr =3D ntfs_listxattr, + .fileattr_get =3D ntfs_fileattr_get, + .fileattr_set =3D ntfs_fileattr_set, }; =20 const struct inode_operations ntfs_special_inode_operations =3D { - .fileattr_get =3D ntfs_fileattr_get, .setattr =3D ntfs_setattr, .getattr =3D ntfs_getattr, .listxattr =3D ntfs_listxattr, + .fileattr_get =3D ntfs_fileattr_get, + .fileattr_set =3D ntfs_fileattr_set, .get_acl =3D ntfs_get_acl, .set_acl =3D ntfs_set_acl, }; diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 4375ad477809..9240f92b9279 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -498,6 +498,7 @@ void __ntfs_init_inode(struct super_block *sb, struct n= tfs_inode *ni) ni->reparse_tag =3D 0; ni->reparse_flags =3D 0; ni->target =3D NULL; + ni->linuxflags =3D 0; ni->i_dealloc_clusters =3D 0; } =20 @@ -669,10 +670,8 @@ void ntfs_set_vfs_operations(struct inode *inode, mode= _t mode, dev_t dev) * Q: What locks are held when the function is called? * A: i_state has I_NEW set, hence the inode is locked, also * i_count is set to 1, so it is not going to go away - * i_flags is set to 0 and we have no business touching it. Only an io= ctl() - * is allowed to write to them. We should of course be honouring them b= ut - * we need to do that using the IS_* macros defined in include/linux/fs= .h. - * In any case ntfs_read_locked_inode() has nothing to do with i_flags. + * i_flags is initialized from the persisted Linux file attributes and + * the driver's system-file protection policy before publishing the ino= de. * * Return 0 on success and -errno on error. */ @@ -871,6 +870,11 @@ static int ntfs_read_locked_inode(struct inode *vi) if (!err) { NInoSetHasEA(ni); ntfs_ea_get_wsl_inode(vi, &dev, flags, &has_lxmod); + err =3D ntfs_ea_get_linuxflags(vi); + if (err) + goto unm_err_out; + } else if (err !=3D -ENOENT) { + goto unm_err_out; } =20 if (ni->flags & FILE_ATTR_REPARSE_POINT) { diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h index ff61bd402df0..d9168c7eb701 100644 --- a/fs/ntfs/inode.h +++ b/fs/ntfs/inode.h @@ -95,6 +95,7 @@ enum ntfs_inode_mutex_lock_class { * belongs. * @i_dealloc_clusters: delayed allocated clusters. * @target: symlink buffer. + * @linuxflags: Linux-specific file attributes loaded from the $LINUXFLAGS= EA. */ struct ntfs_inode { rwlock_t size_lock; @@ -145,6 +146,7 @@ struct ntfs_inode { __le32 reparse_tag; __le32 reparse_flags; char *target; + u32 linuxflags; }; =20 /* diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index ee451700b766..de326886e046 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -1548,7 +1548,6 @@ static int ntfs_link(struct dentry *old_dentry, struc= t inode *dir, * Inode operations for directories. */ const struct inode_operations ntfs_dir_inode_ops =3D { - .fileattr_get =3D ntfs_fileattr_get, .lookup =3D ntfs_lookup, /* VFS: Lookup directory. */ .create =3D ntfs_create, .unlink =3D ntfs_unlink, @@ -1560,6 +1559,8 @@ const struct inode_operations ntfs_dir_inode_ops =3D { .listxattr =3D ntfs_listxattr, .setattr =3D ntfs_setattr, .getattr =3D ntfs_getattr, + .fileattr_get =3D ntfs_fileattr_get, + .fileattr_set =3D ntfs_fileattr_set, .symlink =3D ntfs_symlink, .mknod =3D ntfs_mknod, .link =3D ntfs_link, diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h index a5c5c18671d2..489bf031d1eb 100644 --- a/fs/ntfs/ntfs.h +++ b/fs/ntfs/ntfs.h @@ -184,6 +184,8 @@ extern const struct file_operations ntfs_empty_file_op= s; extern const struct inode_operations ntfs_empty_inode_ops; =20 int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); +int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, + struct file_kattr *fa); =20 extern const struct export_operations ntfs_export_ops; =20 --=20 2.51.0 From nobody Thu Sep 24 13:41:54 2026 Received: from m16.mail.163.com (m16.mail.163.com [117.135.210.4]) (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 04CEB414419 for ; Wed, 23 Sep 2026 06:03:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=117.135.210.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143424; cv=none; b=DvULcOvVhhUCKNrdGwp3E8yv/Cg0mkV5H8ObNinzjfVbM7hKGIpx7xxRGOGK4EUxllYKK8iwEHQS0+SbwvU8FQ9Ne5JZTmW0DCvK1ugtI8VSDfSDraPZwz2hnFeN3//aR2I/yb+0VMCD+DINFOndclZSq1jPXW8KbLajgGWz/aA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143424; c=relaxed/simple; bh=NBhoAf2O5JIjgNEdkMTlfN2KwkGjIFkeMJAxZaJ1vlg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Q4+5drantkvrL6TWAryNUCaV3sSLrzqXxG5J4XDuGfJ03CszfBmnw2cOcj9YA1kUkNQLgI4n2dXMIUF1s/aqo2cxsPqwcHU+x0pXYOg4qVq/cJIbryZqBOwnvugTh9Tb3G+zC4XYEn+UHv+ulYzcLGjX6uHxLCUZf5QTn/KQ7rY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=nWCtjaGA; arc=none smtp.client-ip=117.135.210.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="nWCtjaGA" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=V0 qO9ft2lJyJhqr2pcv48scKfPx+JP0jGXG9uoDqQv8=; b=nWCtjaGAI0GxEz62A8 8UBDEz2MocrF3YVoqAcceun+hBQNSXWkwwbtMICYQW+U0fu2sDJNHeSVxszm6/gV f7+0xoTDS1/8kFKHh+jS2M8iOuLDYyc0JbwRCublCRXRs3ix9oTGBLGLm8zdHUtY pusrlmAoZ51GQnCHoOfnHGBgU= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgCH_E+Ia7NqXdW_Aw--.58316S11; Wed, 23 Sep 2026 14:03:08 +0800 (CST) From: Baolin Liu To: linkinjeon@kernel.org, hyc.lee@gmail.com Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org, chenxiaosong@chenxiaosong.com, Baolin Liu Subject: [PATCH v4 09/10] ntfs: persist append-only in the $LINUXFLAGS EA Date: Wed, 23 Sep 2026 14:02:45 +0800 Message-ID: <20260923060246.2499192-10-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260923060246.2499192-1-liubaolin12138@163.com> References: <20260923060246.2499192-1-liubaolin12138@163.com> 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 X-CM-TRANSID: PygvCgCH_E+Ia7NqXdW_Aw--.58316S11 X-Coremail-Antispam: 1Uf129KBjvJXoW3Jr1Dtr15uF1UuFy8WFykZrb_yoW7Gw18pa 93JFyIgw4rW342grZ2kF4UW3WFvwn3XFWUArWvgw18AasxAw1SqF4ftF4rW3W8GFyfCry2 ga15trWUXrW8Z3DanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07jjq2_UUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbCwhyJVGqza5zHwwAA3p Content-Type: text/plain; charset="utf-8" From: Baolin Liu Extend the persistent fileattr interface to append-only using bit 1 of LINUXFLAGS. Restore S_APPEND on inode load and update it after successful EA writes. Include append in the known-bit mask while retaining all other unknown bits. Reuse the existing VFS capability checks and EA safeguards. Document append-only alongside immutable. Both flags now survive inode reclaim and remount without changing the NTFS attribute format. Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee --- Documentation/filesystems/ntfs.rst | 10 +++++----- fs/ntfs/ea.c | 2 ++ fs/ntfs/ea.h | 4 +++- fs/ntfs/file.c | 13 ++++++------- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Documentation/filesystems/ntfs.rst b/Documentation/filesystems= /ntfs.rst index 173af66ae73f..fb9c89b9b493 100644 --- a/Documentation/filesystems/ntfs.rst +++ b/Documentation/filesystems/ntfs.rst @@ -38,21 +38,21 @@ The project is available at: Linux file attributes =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 -The driver supports lsattr(1) and setting immutable with chattr(1). -Immutable is stored in bit 0 of a private ``$LINUXFLAGS`` extended attribu= te +The driver supports lsattr(1) and setting immutable and append-only with c= hattr(1). +Immutable is stored in bit 0 and append-only in bit 1 of a private ``$LINU= XFLAGS`` extended attribute within the standard NTFS ``$EA``/``$EA_INFORMATION`` attributes. Its value is a 32-bit little-endian bitmask. Unknown bits are preserved; the entry -is removed when the entire value becomes zero. The setting survives +is removed when the entire value becomes zero. These settings survive inode eviction, unmount and reboot. Direct writes or removal through xattr interfaces are rejected; use chattr(1) instead. =20 System metadata files and files protected by ``sys_immutable`` cannot have their immutable protection cleared. Protection derived solely from -that policy is not stored in the EA. Compression, encryption, append-only +that policy is not stored in the EA. Compression, encryption and mount-wide case folding are reported but cannot be changed through chattr(1). =20 -Windows does not enforce the Linux immutable flag stored in this EA. +Windows does not enforce the Linux immutable and append-only flags stored = in this EA. =20 Supported mount options =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index daa3b430ca76..4c99743c4dee 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -500,6 +500,8 @@ int ntfs_ea_get_linuxflags(struct inode *inode) ni->linuxflags =3D le32_to_cpu(value); if (ni->linuxflags & NTFS_LINUXFLAGS_IMMUTABLE) inode->i_flags |=3D S_IMMUTABLE; + if (ni->linuxflags & NTFS_LINUXFLAGS_APPEND) + inode->i_flags |=3D S_APPEND; return 0; } =20 diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h index 5ec118e29425..b988b39f5f00 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -9,7 +9,9 @@ =20 /* $LINUXFLAGS stores these bits in a single little-endian 32-bit value. */ #define NTFS_LINUXFLAGS_IMMUTABLE BIT(0) -#define NTFS_LINUXFLAGS_MASK NTFS_LINUXFLAGS_IMMUTABLE +#define NTFS_LINUXFLAGS_APPEND BIT(1) +#define NTFS_LINUXFLAGS_MASK (NTFS_LINUXFLAGS_IMMUTABLE | \ + NTFS_LINUXFLAGS_APPEND) =20 extern const struct xattr_handler *const ntfs_xattr_handlers[]; =20 diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 135e5be69e77..cda8d02c130e 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -190,7 +190,7 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct d= entry *dentry, { struct inode *vi =3D d_inode(dentry); struct ntfs_inode *ni =3D NTFS_I(vi); - u32 allowed =3D FS_IMMUTABLE_FL; + u32 allowed =3D FS_IMMUTABLE_FL | FS_APPEND_FL; u32 readonly =3D 0; u32 linuxflags =3D ni->linuxflags & ~NTFS_LINUXFLAGS_MASK; unsigned int new_fl =3D 0; @@ -203,11 +203,6 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct = dentry *dentry, if (fileattr_has_fsx(fa)) return -EOPNOTSUPP; =20 - if ((fa->flags & FS_APPEND_FL) !=3D - (IS_APPEND(vi) ? FS_APPEND_FL : 0)) - return -EOPNOTSUPP; - allowed |=3D fa->flags & FS_APPEND_FL; - /* chattr passes the unchanged read-only flags back to us too. */ if (NInoCompressed(ni) || NInoWofCompressed(ni)) readonly |=3D FS_COMPR_FL; @@ -234,6 +229,10 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct = dentry *dentry, (ni->linuxflags & NTFS_LINUXFLAGS_IMMUTABLE)) linuxflags |=3D NTFS_LINUXFLAGS_IMMUTABLE; } + if (fa->flags & FS_APPEND_FL) { + new_fl |=3D S_APPEND; + linuxflags |=3D NTFS_LINUXFLAGS_APPEND; + } =20 mutex_lock(&ni->mrec_lock); err =3D ntfs_ea_set_linuxflags(vi, linuxflags); @@ -241,7 +240,7 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct d= entry *dentry, if (err) return err; =20 - inode_set_flags(vi, new_fl, S_IMMUTABLE); + inode_set_flags(vi, new_fl, S_IMMUTABLE | S_APPEND); inode_set_ctime_current(vi); mark_inode_dirty(vi); return 0; --=20 2.51.0 From nobody Thu Sep 24 13:41:54 2026 Received: from m16.mail.163.com (m16.mail.163.com [117.135.210.2]) (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 00A403A1A23 for ; Wed, 23 Sep 2026 06:03:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=117.135.210.2 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143426; cv=none; b=qcreYSaCxvSUCZWCwkSLQCdwwJ/nmbii1wboyLZw05msLj8i7hAnUCC3zBdLI1MUTGmcyv6VCNKv7M+xuMSdhVSy7/HlWxiY1+qwR9V3ytU28/hIhVp6Ggm2P9OgYsRDIT8qHz17UURY3/tQyrad4FsI17tFp79T8+tRJkHPHPM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790143426; c=relaxed/simple; bh=eW154vuwCnKz1c7KGfng2fP6i6tTVHMoUQoTPohS0FM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KgzdR00spMGI7dET8HaKVNHBuR2YcNPfS7I+S9F5Lx94gOfBS7GdMrMfp1OOoy+IpYY7hNhSN1uqcyJJ0FXc0+VZ4oGWZfi5adntNl+8qyKcTcBBgb7Gvw2d5qhs/92LwuVt+7hE+WUuK1Qht6VCkHZLg4lfyK8F/dOnLH4LnEc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=ZLDJy1Mm; arc=none smtp.client-ip=117.135.210.2 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="ZLDJy1Mm" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=3U NTJfnZWBERJSwwoqadnuJwoSrBHaFkDYINBMhHsH8=; b=ZLDJy1MmBRNzXEEaIt z9xrGmGmynYVxuTwfBp+Y+TiWuRH+JKD/B3HSFMd+soXQE2ivF8QXarQ8wzpssBJ UGieTv8B8pylcELjSsozIGLL7SEq3sthqtleDFqr09sk1k/pZr2n8f3CfifYs4JT nQTtHYrmVAVh9IlCm7i+jZOzY= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgCH_E+Ia7NqXdW_Aw--.58316S12; Wed, 23 Sep 2026 14:03:09 +0800 (CST) From: Baolin Liu To: linkinjeon@kernel.org, hyc.lee@gmail.com Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org, chenxiaosong@chenxiaosong.com, Baolin Liu Subject: [PATCH v4 10/10] ntfs: persist nodump in the $LINUXFLAGS EA Date: Wed, 23 Sep 2026 14:02:46 +0800 Message-ID: <20260923060246.2499192-11-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260923060246.2499192-1-liubaolin12138@163.com> References: <20260923060246.2499192-1-liubaolin12138@163.com> 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 X-CM-TRANSID: PygvCgCH_E+Ia7NqXdW_Aw--.58316S12 X-Coremail-Antispam: 1Uf129KBjvJXoWxKry7CF1xtrWfGrWfZF4ruFg_yoW7CF18pF Z3GFyxKw4rXFy7W397CF4UWa1Fvrn3XFWUXr92qw18Aa4DJ3WSqFWfKF4rXa4kCrySk347 XayUtrWUXFWjv37anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07j4ApnUUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbCwR6JVGqza56+iAAA3Y Content-Type: text/plain; charset="utf-8" From: Baolin Liu Store nodump in bit 2 of LINUXFLAGS and allow it through fileattr_set. Report the cached persistent bit through fileattr_get and STATX_ATTR_NODUMP, including the statx supported-attribute mask. Reuse the existing EA update and inode-load paths; nodump needs no additional inode flag enforcement. Document the complete immutable, append-only and nodump bitmask. Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee --- Documentation/filesystems/ntfs.rst | 34 +++++++++++++++++------------- fs/ntfs/ea.h | 3 ++- fs/ntfs/file.c | 11 ++++++++-- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/Documentation/filesystems/ntfs.rst b/Documentation/filesystems= /ntfs.rst index fb9c89b9b493..d305fd4314d2 100644 --- a/Documentation/filesystems/ntfs.rst +++ b/Documentation/filesystems/ntfs.rst @@ -38,21 +38,25 @@ The project is available at: Linux file attributes =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 -The driver supports lsattr(1) and setting immutable and append-only with c= hattr(1). -Immutable is stored in bit 0 and append-only in bit 1 of a private ``$LINU= XFLAGS`` extended attribute -within the standard NTFS ``$EA``/``$EA_INFORMATION`` attributes. Its value -is a 32-bit little-endian bitmask. Unknown bits are preserved; the entry -is removed when the entire value becomes zero. These settings survive -inode eviction, unmount and reboot. Direct writes or removal through -xattr interfaces are rejected; use chattr(1) instead. - -System metadata files and files protected by ``sys_immutable`` cannot -have their immutable protection cleared. Protection derived solely from -that policy is not stored in the EA. Compression, encryption -and mount-wide case folding are reported but cannot be changed through -chattr(1). - -Windows does not enforce the Linux immutable and append-only flags stored = in this EA. +The driver supports lsattr(1) and chattr(1). Immutable, append-only and +nodump are stored in a private ``$LINUXFLAGS`` extended attribute within t= he +standard NTFS ``$EA``/``$EA_INFORMATION`` attributes. Its value is a +32-bit little-endian bitmask: bit 0 is immutable, bit 1 is append-only, +and bit 2 is nodump. Unknown bits are preserved when updating the flags; +the entry is removed when the entire value becomes zero. These settings +survive inode eviction, unmount and reboot. Direct writes to this EA +through setxattr(2) or removexattr(2) are rejected; use chattr(1) instead. + +System metadata files and files protected by the ``sys_immutable`` mount +option cannot have their immutable protection cleared with chattr(1). +Protection derived solely from the mount option is not stored in the EA. +Case folding is reported according to the mount options and cannot be +configured per file. Compression and encryption are reported but cannot +be changed through chattr(1). + +These Linux flags are enforced by Linux. Storing them in an NTFS EA does +not make Windows enforce immutable, append-only or nodump semantics. + =20 Supported mount options =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h index b988b39f5f00..8f144fa4f9bb 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -10,8 +10,9 @@ /* $LINUXFLAGS stores these bits in a single little-endian 32-bit value. */ #define NTFS_LINUXFLAGS_IMMUTABLE BIT(0) #define NTFS_LINUXFLAGS_APPEND BIT(1) +#define NTFS_LINUXFLAGS_NODUMP BIT(2) #define NTFS_LINUXFLAGS_MASK (NTFS_LINUXFLAGS_IMMUTABLE | \ - NTFS_LINUXFLAGS_APPEND) + NTFS_LINUXFLAGS_APPEND | NTFS_LINUXFLAGS_NODUMP) =20 extern const struct xattr_handler *const ntfs_xattr_handlers[]; =20 diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index cda8d02c130e..38309cbd98c2 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -159,6 +159,8 @@ int ntfs_fileattr_get(struct dentry *dentry, struct fil= e_kattr *fa) flags |=3D FS_IMMUTABLE_FL; if (vi->i_flags & S_APPEND) flags |=3D FS_APPEND_FL; + if (ni->linuxflags & NTFS_LINUXFLAGS_NODUMP) + flags |=3D FS_NODUMP_FL; if (!NVolCaseSensitive(ni->vol)) flags |=3D FS_CASEFOLD_FL; =20 @@ -190,7 +192,7 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct d= entry *dentry, { struct inode *vi =3D d_inode(dentry); struct ntfs_inode *ni =3D NTFS_I(vi); - u32 allowed =3D FS_IMMUTABLE_FL | FS_APPEND_FL; + u32 allowed =3D FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL; u32 readonly =3D 0; u32 linuxflags =3D ni->linuxflags & ~NTFS_LINUXFLAGS_MASK; unsigned int new_fl =3D 0; @@ -233,6 +235,8 @@ int ntfs_fileattr_set(struct mnt_idmap *idmap, struct d= entry *dentry, new_fl |=3D S_APPEND; linuxflags |=3D NTFS_LINUXFLAGS_APPEND; } + if (fa->flags & FS_NODUMP_FL) + linuxflags |=3D NTFS_LINUXFLAGS_NODUMP; =20 mutex_lock(&ni->mrec_lock); err =3D ntfs_ea_set_linuxflags(vi, linuxflags); @@ -507,9 +511,12 @@ int ntfs_getattr(struct mnt_idmap *idmap, const struct= path *path, =20 if (inode->i_flags & S_APPEND) stat->attributes |=3D STATX_ATTR_APPEND; + if (ni->linuxflags & NTFS_LINUXFLAGS_NODUMP) + stat->attributes |=3D STATX_ATTR_NODUMP; =20 stat->attributes_mask |=3D STATX_ATTR_COMPRESSED | STATX_ATTR_ENCRYPTED | - STATX_ATTR_IMMUTABLE | STATX_ATTR_APPEND; + STATX_ATTR_IMMUTABLE | STATX_ATTR_APPEND | + STATX_ATTR_NODUMP; =20 /* * If it's a compressed or encrypted file, NTFS currently --=20 2.51.0