From nobody Thu Sep 24 20:37:00 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 899613D9027 for ; Sun, 20 Sep 2026 08:32:09 +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=1789893131; cv=none; b=UazVOZocuvZM+Genks6y6SW0WL1fW/6ScRFJwgf9yDHDqoZz+wAbR+hTS/hwiuKz9vqT0G+lrhfhFd1GHp48BZMIpd3oY6u6ICHLSLLzlvLplgvFDTCtUJNpeUlpCq1fsYzUByelFv/wgNAc12Moe14lsz2uOE0v/jRDPhNeW5k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893131; c=relaxed/simple; bh=6VmmV/JuhqxQc7xBKPMLln2fs1lFZm9Xd7P0G/mSW1E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=I4ECZZa+daSnwlK6RC0Mx3azT/8MjSGyxsKGSKquxAK8iGUnG8vswdJkaKjI9ZwsGzt+oOPXG/BpIvnGuj9JiYmfE4P7i2c6DKAeS4XeydlZ5UL4NaEtN09CAfDmOcqoFrxYEpeb7kH3yLevtKQaFLKj+UDDHY71KXMO2CdWNM8= 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=l59vCU3p; 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="l59vCU3p" 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=5O ANPeAoJ3qwbO5lpNbD5qpk2dFx16dUqLcV09Tdn5A=; b=l59vCU3p04ceG1uPEh L3plUXmaqP/es9PyTJ6dl5nP5PE9ly58GZbLyHRiWOYydFqj4deBAh35hZ3L/SSs tFTHWZwcr+MgYX3P5DVqQkM2OAdg34adJztLfnoK8ZvM/ypZ2/fRqL31BueL0cFb jcN5jUHBRvVtQJEBPuBW/WxQY= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgB3m07lma9qs7GbAQ--.54162S3; Sun, 20 Sep 2026 16:31:38 +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 v2 01/10] ntfs: keep the EA presence flag after creating the first entry Date: Sun, 20 Sep 2026 16:31:22 +0800 Message-ID: <20260920083131.1880061-2-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260920083131.1880061-1-liubaolin12138@163.com> References: <20260920083131.1880061-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: PygvCgB3m07lma9qs7GbAQ--.54162S3 X-Coremail-Antispam: 1Uf129KBjvdXoW7GrWrXr15WF15CF4xAFyrZwb_yoWfAFb_Wa yxZr4kKw1Utrn3C3Z7GryY9r4SqF4qkr1qqr95tFs7XayDtws0krWkuws5uF4DC3yUtrZ8 uryvg34UJrs5WjkaLaAFLSUrUUUUjb8apTn2vfkv8UJUUUU8Yxn0WfASr-VFAUDa7-sFnT 9fnUUvcSsGvfC2KfnxnUUI43ZEXa7IU8iZ23UUUUU== X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbCwQon8WqvmeoLXwAA3- 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 --- 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 20:37:00 2026 Received: from m16.mail.163.com (m16.mail.163.com [220.197.31.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 73A1334A3BF for ; Sun, 20 Sep 2026 08:32:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=220.197.31.2 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893152; cv=none; b=rGAMpgzS+ss+iBLGxaGEucmrdnBhJvQTgKbME9Aek+Lcl+8vMYwYqvdFqSU3kuFjkFS0//dj6iYpBHOqSrApX16SeUvTB7hqs0sRkc+l+mxv9iHi0yerj8d3RtEG75WLO0o/ugzSc+ssbeNg/R0Slbiiqk7z6lZCAVSeqZNDF78= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893152; c=relaxed/simple; bh=aZth6nxuWM8ERphc/6Yf1Tf9N1VgkBIYC4VUoYbavjc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pfpF2Psnpv5baTZEAyUZYLzccF+NJk+jI1UY9V98y4bBj/BGwqli0WzEavRqB1z22YXDroyVHKT6Z67Ugp72+gSZqKiOKvvhHiGvVELt47c4rCa+8Cb3VZYoC8TL30AjuC2G/+jLJKsGP/piivV8T7mMsb9GXCocHw/vhtmbk7k= 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=ElDCOoo2; arc=none smtp.client-ip=220.197.31.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="ElDCOoo2" 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=B3 utF3Yej1TLrgwxqsdULLbpamiQnMXrQzVkb8JA0fY=; b=ElDCOoo2v3x8XVt/5L efPcJE7LNfvpaIWCPzyuxd1+8hvutejF+zR+brC8x5yU5LSjq7/GMAqEsng6dsdP uFg1E6iZRBylNxQblIzncyE47L2nj5sYpCejU94yZ/VhgkVOUXvTiQn0hATNUoTM SlAc9wiiIHXizUw3kqteGjC8I= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgB3m07lma9qs7GbAQ--.54162S4; Sun, 20 Sep 2026 16:31:40 +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 v2 02/10] ntfs: validate EA chains and link zero-terminated tails Date: Sun, 20 Sep 2026 16:31:23 +0800 Message-ID: <20260920083131.1880061-3-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260920083131.1880061-1-liubaolin12138@163.com> References: <20260920083131.1880061-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: PygvCgB3m07lma9qs7GbAQ--.54162S4 X-Coremail-Antispam: 1Uf129KBjvJXoWxAF4rJFy7JF1fAr1kJrWkWFg_yoWrCrW3pF Z8A3s8tr4fJrnrGFsayr4DXayYy397WFyUt347Kw1vkrnYvr15tas0kasa9ayFkrWkZr1F qFZ8KF48uF1UArJanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07jqeHDUUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6Qwo8mqvmeyyDQAA3w 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 and set paths. Keep ENOENT for a missing name and translate it to ENODATA only at the xattr read boundary. Preserve lower-level read errors. Signed-off-by: Baolin Liu --- fs/ntfs/ea.c | 49 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index bb97df3045a0..59160e49a53f 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; } =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); + 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); + if (err && err !=3D -ENOENT) + goto out; if (ea_info_qsize && !err) { if (flags & XATTR_CREATE) { err =3D -EEXIST; --=20 2.51.0 From nobody Thu Sep 24 20:37:00 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 73DB73BBFD8 for ; Sun, 20 Sep 2026 08:32:09 +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=1789893131; cv=none; b=mw4GswBca0jybwedVpQwdIx5XUVfWsZFbo3Uw74iqCEv/QnZy9P1VBydQRMiRxLyF/mi35rb+UCVY/6SzdihsKbqrI2U/jbwLzUC0F/EIU5JwPQFZ/GektlkVmkKmhPoPk3t0qaxi7DhJqK1L9B/cjnRW3hW9U1l1dCv7bFFLfw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893131; c=relaxed/simple; bh=Ie5COk0tmhQEcp5kIuQPo/ZUSVcEEGAhl4hJioiuhIA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=RxjpUcj+5W2uOX+PwPV1uuo3ccPQb01LE+O2ByJJt3wDzWJ9FDKks3TmD/cKoKaYM6K5eRd2cM0XPPfPWf+wraPUSOi3AyxsG6d788U5255sGfWWJj4tHpUUgqRPQUb/2XbvgE1CpF+0F0aM4IB14sfNcfSM/pxViu2U1Pug/gM= 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=hgMBDczh; 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="hgMBDczh" 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=sl m6wwvgS9oQ7CXUMaxnmw9GzX+LFkkuBvWkyoC2kCg=; b=hgMBDczhlZr7K79THU LX/Sur3f3BUO9giErav0VGo0ddMBknBX++H8XYBOfD/UHG36xKFENp7AmxB+eKPx 6KZAzmbXaqPgoEYL5tzJy90UO9DGXloLNTzfNu6rabPXI3aJaOSlRg5esY1iXe8p p6ubQDa30PA1OonPxkBBEGSbM= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgB3m07lma9qs7GbAQ--.54162S5; Sun, 20 Sep 2026 16:31:41 +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 v2 03/10] ntfs: propagate EA write and truncate errors Date: Sun, 20 Sep 2026 16:31:24 +0800 Message-ID: <20260920083131.1880061-4-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260920083131.1880061-1-liubaolin12138@163.com> References: <20260920083131.1880061-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: PygvCgB3m07lma9qs7GbAQ--.54162S5 X-Coremail-Antispam: 1Uf129KBjvdXoW7GFyfWFW7ZF4DXF17XF17Awb_yoWkXFX_u3 WxZF1kKr4jqF1kGanrArnI9ryqqa18ZF1qvwn5K3Wvy34Dt34qvrZ0grs5KFsrua10kr98 Gwn3WFy3AF13GjkaLaAFLSUrUUUUjb8apTn2vfkv8UJUUUU8Yxn0WfASr-VFAUDa7-sFnT 9fnUUvcSsGvfC2KfnxnUUI43ZEXa7IUnuVbPUUUUU== X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6Q0o8mqvme2yPAAA3B 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 --- 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 59160e49a53f..5c6facae4ed5 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 20:37:00 2026 Received: from m16.mail.163.com (m16.mail.163.com [220.197.31.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 AC4F43E9C3C for ; Sun, 20 Sep 2026 08:32:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=220.197.31.5 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893139; cv=none; b=Zy05niIsPzENzfkD7SamM4Kd5pm7hPEg662qx2Ek7F9Hurr/CGbZ5dSmj3G6LmbNuT8EhXZYSjbqMG74Kw9U1YtgyZo0PSTdI0aj+g6ueTkaI62FCb1Fu+Na/ZFJsIk7G7bCe4MaE7QDRN7sBPZEcOxuXhzcuHnEdy1xEJaE0sA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893139; c=relaxed/simple; bh=FNasp7qn5/aS/FD+KLe5fFnz3qQTxwRqsjG1qhfs2hI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NgSAa4RbvzH1rmgOgFVsrVbIcA6qed8u8Kp4mCXgtEmGsyGB5T8iCJTWqgqhx9uXaADIKZpJjo+mf2rFTtqeUAHvCQ49UsoEVEQUqeSUyFu7LCMWGROwyCj+A/5b4fdhT/wYTKak206mPJq8xDX6OD7sNPljg1yWFvnxtCa4iuc= 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=OvWCcME1; arc=none smtp.client-ip=220.197.31.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="OvWCcME1" 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=j5 HjGAHMTWSY12pij/fAtgg5ecT4GvjimQP4EK5oNPo=; b=OvWCcME1TQ/Maj0bwa B2QqmQUPk6G4FIZ2todkm6feDBCAgwPVr/vj5vAFlGbx6LHSmazau1tgogu2Vyzk 0Npg6ao/2e3zNYdkqxn4CQvmDuY2449YIK/CvjQHLmlvqKTmymCrmYcx8h13eMjb mCdWeF35G73nnfs7UyID6qdjU= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgB3m07lma9qs7GbAQ--.54162S6; Sun, 20 Sep 2026 16:31:43 +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 v2 04/10] ntfs: preserve EA attribute lookup errors Date: Sun, 20 Sep 2026 16:31:25 +0800 Message-ID: <20260920083131.1880061-5-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260920083131.1880061-1-liubaolin12138@163.com> References: <20260920083131.1880061-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: PygvCgB3m07lma9qs7GbAQ--.54162S6 X-Coremail-Antispam: 1Uf129KBjvJXoWxuryxurWxtr4UXr13Aw18Krg_yoW5tw45pF yfGry3twsxX3ZxKFs2yw1DX3WYvas7GryUKr9rK3Z2kr1rt34Dtw15KF9Y9FyFyrWrJ340 gF4UKay7Cr47Xr7anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07URE__UUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbCwQ8o8mqvme8L5QAA3J 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 --- 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 5c6facae4ed5..f5a112495fc9 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 20:37:00 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 BA39D3E7BBD for ; Sun, 20 Sep 2026 08:32:11 +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=1789893134; cv=none; b=WtXAUqdOfVUZXsSy9RgsYewFt+PJnV0Z3jdBz/5lurjW4KuQNeP2qE4N0mNOZ9T21LNWWYrFha2YSUCA7/cfb5Km2zS172zWK8jyAOL9+QVwOyqoBxPFiGprxn6A/G+ZVsTGnW0Zag2bf2sfjn4vjFsUXiuOMOV45UG/XyrIIcU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893134; c=relaxed/simple; bh=bQEf9vL5/VwlJlkWG8rTHjE1YUHTXkblTVwOrV3ixzM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MHR8n514jCPd2Re6q4FefeskUXQLEwv2oLWy88oMq6IDV8u1zojBBTLHjnwTes47sW3TsnxWvyy2tLRr95t05VFK1ndaB06u+94Yyl4oCPGetlbZRqRRoVMoHf8BDzG6w7SUFpCd+XCGRY6AY6bhW5nczmt0YO35sRku+5YUuCY= 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=EfMDAOnW; 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="EfMDAOnW" 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=TO 4+oQ7uYeq8iFmmQjDy915DK04djidl/nzAmYzA3oQ=; b=EfMDAOnWrS5cIZ48S5 HRgX8olVFeVUnyD9Vcjw3h31jLp9nkjaGb/f+hTxtnBiNYMeG2LvsEzgmYEAniX7 B1o+eaxx58tmTFWy5k8GSPSHlDU9KpumVKC7f6I8uvIuLjajd1cnu+jJhIpPsNDL c31xjdw8LqFVK2AnaZj+Vc99w= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgB3m07lma9qs7GbAQ--.54162S7; Sun, 20 Sep 2026 16:31:45 +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 v2 05/10] ntfs: distinguish empty EA values from removal Date: Sun, 20 Sep 2026 16:31:26 +0800 Message-ID: <20260920083131.1880061-6-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260920083131.1880061-1-liubaolin12138@163.com> References: <20260920083131.1880061-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: PygvCgB3m07lma9qs7GbAQ--.54162S7 X-Coremail-Antispam: 1Uf129KBjvJXoWxCr4UKrW8uw47XryDCw4fuFg_yoW5Xw13pF W3CrW5tF1rXFyDGr1vyw1DW3W5Zry8Kry8K3s7K3Wxurnaqw1Dt3Z5KFyagF4SyFykJ3W0 ganYgw4rXa17JFJanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07URCJQUUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6hIp82qvmfJU5QAA39 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 --- 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 f5a112495fc9..15bfcdddc7ee 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 20:37:00 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 73B0F386C39 for ; Sun, 20 Sep 2026 08:32:29 +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=1789893152; cv=none; b=i7As+QzBNRIwAzIO8MW6O0Rgcp98GOt1CJtwVgcXl9FQFiWggtQbLQ9H93b7uri6yxOLt6PFYT4oWTffjhSqKBDm7NdpOqGrJRzDG+hh7U5FA0Z3lm9PX/PQghkoz9CQztWcmGMPqKrBV2uI+h8XPYXYxIBhoVpk422zEdyurjA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893152; c=relaxed/simple; bh=KnWBpetLSIW2NcgHc/qdOhtbAgG7QeEht7w12H7gtoE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kiDLN+kxEwbDIjhzTTWHxtBDwbzABI7LgpM8CGQ6BoW+nNmkbo8NADNpX1NEbr6dDE/D9wWlNAeM3NREnlcwFIWhgG5jQ9fYEEMnrwbO3wJIPIrGRI5Yy8LyZmvEz6N3+4ypBL3/bLgXNEDEwiipOj75PHwU4eo+bF7Ai73fWJI= 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=f8DpYe5L; 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="f8DpYe5L" 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=zt QZCB9KQWbq+msDaiN3rOMomM+arOVVqFZ2JoMkek0=; b=f8DpYe5L6ueBAQiKSh 6eWhgbXpb+1r8OWl2Kl9tBhaW17HV0BVZ/HN8nmJSmFLhwAPSD5cVGDbGt2HwkGS QgjWmG5rPS0O49yV0PDpXxEpgfEXgez2X3+DASRmmCBU+lvsvUBTdhxVsKFdt6CV R7E/9kwN0HPLpv2bGCTv8bnRc= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgB3m07lma9qs7GbAQ--.54162S8; Sun, 20 Sep 2026 16:31:47 +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 v2 06/10] ntfs: restore EA attributes when an update fails Date: Sun, 20 Sep 2026 16:31:27 +0800 Message-ID: <20260920083131.1880061-7-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260920083131.1880061-1-liubaolin12138@163.com> References: <20260920083131.1880061-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: PygvCgB3m07lma9qs7GbAQ--.54162S8 X-Coremail-Antispam: 1Uf129KBjvAXoW3tFWkurW3uFyUAw13uryfZwb_yoW8JryrAo Wa9r45Gw1kuryjkw17Kw4qy3sxXF95K3yUJw4YkanFkF4j9w1Dtwnxt3WFgaySg3WrKF4D Ww1Sqwn7CF18Jryxn29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvjxUxDDJDUUUU X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbCwRMp82qvmfMMTQAA3m 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 --- 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 15bfcdddc7ee..888b531e8174 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 20:37:00 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 C6C9B3E63AA for ; Sun, 20 Sep 2026 08:32:13 +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=1789893136; cv=none; b=hJttY3K07wWE/7C6YSnR2nciydsESRTAQXGrwVGK0wWYKeUZxncAAfytC+q5V5H+ycPPnzui3hHT9CG4DOnxtOo0cvElHK9RTWMPoEIGMYxurD8t6l+4DwfcReHSOz/wZVEGtvG7I2976TYuFAxi0Eqhbq8s5DfavwhVb6Uvuyo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893136; c=relaxed/simple; bh=kV5roBWER2l6GRbizv/KlQDqfe6bAiyeoAyzeAxNCUY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ez48dBu+gGsivO7092F5bM42BDhZNt2WP24neBZyKJSdO1l2NV6RaXtqDW9T3AxkwU/XxH6utFJp/xuEskbNJQ6lk4op3UbAzthjKJnoGVyJZ63r0BldEIXLl/Z/dcox5P0AB5YQSli04t+AkayZogfqEdYg6oTQv2AxjSPU3PA= 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=DuIAbtME; 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="DuIAbtME" 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=QK XYQo1UlHyeLmzVKpt21zMJrcTLIeUIX3V2Q0gwJik=; b=DuIAbtMEddCdi5RAAf LNFPjrn+HCcyQBKpgSf73PM9D6vpR5vCnQnBg81IDgPLszN6smwrtNQjqTJzDOR/ zNox3rxx2e/rnRB8j93TljEjTCktvkZ/dsImCNgcj20rA18X6vHU+z0p1Zvd3Z5R AsEwOubNpoGnDKGH7RWWIFHn0= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgB3m07lma9qs7GbAQ--.54162S9; Sun, 20 Sep 2026 16:31:49 +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 v2 07/10] ntfs: add fileattr query support Date: Sun, 20 Sep 2026 16:31:28 +0800 Message-ID: <20260920083131.1880061-8-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260920083131.1880061-1-liubaolin12138@163.com> References: <20260920083131.1880061-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: PygvCgB3m07lma9qs7GbAQ--.54162S9 X-Coremail-Antispam: 1Uf129KBjvJXoWxAry8Zr4xuFy8tryfGr1UGFg_yoWrZF47pF Z3AFyfKa1fXFy2g392kFWjvw1Y9ryxWr4UXryxX34IkFnrtr93KF1fK3WruFn5KrZ5A3yj qr1YqrWUGr45ZwUanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07jcb1bUUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbCwhUq9GqvmfUkAgAA3G 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 --- 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 20:37:00 2026 Received: from m16.mail.163.com (m16.mail.163.com [220.197.31.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 BCD9A3E6DC9 for ; Sun, 20 Sep 2026 08:32:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=220.197.31.2 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893136; cv=none; b=BIoUdqqW7q3gjdxC8aTNSn/asc/wJYx7OW4f5C7ajE2ugfMFHKLv2+c+8fGV8GHLgXG7o+Ugiz2h6FIBK0qooqqscdZNnxLnV3iKgyiTDFpOiJz8X7z+v59CtHfgos+qS9hV60hC2X1Vttskc+/yqm6k0HslM5x0sc4wRU26LU0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893136; c=relaxed/simple; bh=OAk+ZZyO/WsyeGMYR/MQlroUkxN57pM03f4R2zSzQmw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=VQ/oHe2Jl9bDd9qPSVoXw0sWaRgSvdanQKn2zpEiCzrg0vk0VdvqMpB5i58todZOSLgysht4GYZGvy1AJzjnfBF6KcSClD0mS8CrZsJ27rob95GZ8SVeoszj7tTeNlGUILqE3L35pLaPV1SaoAueYvRcwfRuqLDP6v+IS9mNMvM= 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=lrD2a/gE; arc=none smtp.client-ip=220.197.31.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="lrD2a/gE" 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=RQ K/MZMQWWwOZGtI4bGVeoefpm14oEciDO4il997Sfo=; b=lrD2a/gEdXc0XEQrqE YWc200UNY/9lOgTR72U1S/mYdgoW+dzbhyT3Kz6ZyzGEgRp/aLF+Rd9QT3VtbTrB UWVDA7zWoahLuQ8PCbm4bJzoyPbxJg9GieTO4VVrWmVXYkzo2H1L7aXQ3yYg0WUM dhzorxQdSHFNf4MpROn+KMJPg= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgB3m07lma9qs7GbAQ--.54162S10; Sun, 20 Sep 2026 16:31:50 +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 v2 08/10] ntfs: persist immutable in the $LXFLAGS EA Date: Sun, 20 Sep 2026 16:31:29 +0800 Message-ID: <20260920083131.1880061-9-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260920083131.1880061-1-liubaolin12138@163.com> References: <20260920083131.1880061-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: PygvCgB3m07lma9qs7GbAQ--.54162S10 X-Coremail-Antispam: 1Uf129KBjvAXoW3urW8KFykAw1fWF4fCrW5GFg_yoW8JrWUJo WS9r13uws5ury5A3yFk3WxtayDWayUCrWxJ3y5C3yDCFnIqw1Duw17Ww43uF47Ww1F9Fn8 J34xXw4rJFW8XF98n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvjxUx6RRDUUUU X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbC6Rcq9GqvmfezHAAA3k 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 $LXFLAGS EA. Reuse the standard NTFS EA attributes and restore the flag when loading an inode, so eviction and unmount do not discard the setting. 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..ced164216a5b 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 ``$LXFLAGS`` 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 +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 888b531e8174..1e9b66e88655 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_lxflags(struct inode *inode) +{ + struct ntfs_inode *ni =3D NTFS_I(inode); + __le32 value; + int err; + + err =3D ntfs_get_ea(inode, "$LXFLAGS", sizeof("$LXFLAGS") - 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->lxflags =3D le32_to_cpu(value); + if (ni->lxflags & NTFS_LXFLAGS_IMMUTABLE) + inode->i_flags |=3D S_IMMUTABLE; + return 0; +} + +int ntfs_ea_set_lxflags(struct inode *inode, u32 lxflags) +{ + struct ntfs_inode *ni =3D NTFS_I(inode); + __le32 value; + int err; + + if (lxflags) { + value =3D cpu_to_le32(lxflags); + err =3D ntfs_set_ea(inode, "$LXFLAGS", sizeof("$LXFLAGS") - 1, + &value, sizeof(value), 0, NULL); + } else if (NInoHasEA(ni)) { + err =3D ntfs_set_ea(inode, "$LXFLAGS", sizeof("$LXFLAGS") - 1, + NULL, 0, XATTR_REPLACE, NULL); + if (err =3D=3D -ENODATA) + err =3D 0; + } else { + err =3D 0; + } + + if (!err) + ni->lxflags =3D lxflags; + return err; +} + ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size) { struct inode *inode =3D d_inode(dentry); @@ -858,6 +903,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, "$LXFLAGS")) + 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..19aea162b909 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 +/* $LXFLAGS stores these bits in a single little-endian 32-bit value. */ +#define NTFS_LXFLAGS_IMMUTABLE BIT(0) +#define NTFS_LXFLAGS_MASK NTFS_LXFLAGS_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_lxflags(struct inode *inode); +int ntfs_ea_set_lxflags(struct inode *inode, u32 lxflags); 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..882d33b67560 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 lxflags =3D ni->lxflags & ~NTFS_LXFLAGS_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->lxflags & NTFS_LXFLAGS_IMMUTABLE)) + lxflags |=3D NTFS_LXFLAGS_IMMUTABLE; + } + + mutex_lock(&ni->mrec_lock); + err =3D ntfs_ea_set_lxflags(vi, lxflags); + 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..6841dbc18a20 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->lxflags =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_lxflags(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..2968ffa92eb0 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. + * @lxflags: Linux-specific file attributes loaded from the $LXFLAGS EA. */ struct ntfs_inode { rwlock_t size_lock; @@ -145,6 +146,7 @@ struct ntfs_inode { __le32 reparse_tag; __le32 reparse_flags; char *target; + u32 lxflags; }; =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 20:37:00 2026 Received: from m16.mail.163.com (m16.mail.163.com [220.197.31.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 C6BA12F8E8F for ; Sun, 20 Sep 2026 08:32:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=220.197.31.2 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893139; cv=none; b=tPpMlCQ4JyNuRKhXtuFMb5XYbF61+RhI8hgS9REueOsNSddadTNXJBwlcc82M1CZXnF1ufNyjtSEgavBDVQU1+yZvwoKRk+Eo3NIaydBVeAEQQEnczPM+TSNEypDbLnfotrEWECE4ibet/hUTUzKNgqMIiLBHRul4rkFKHVkrko= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893139; c=relaxed/simple; bh=hEj7I8IgfGqJ/cTL1OJazCZCq/g+2hBc263juLIj6/Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JAw2fir6rQXM0raI1Oq57cycWDjRHlcl4OzmnKKZU3IldIvOt2P5sMxf+u5CaDfbj7DvVO8FE6VGiKIuABX/9Lw5+bGKp4xg0jnzggm8+D3sjsVtxjjk/FaUKPSihcPpU5l5YNe6oZxe9fhRdiyRKxBljJcq8hY5PLL72XBn0d0= 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=SMytY0MS; arc=none smtp.client-ip=220.197.31.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="SMytY0MS" 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=0i phEToKxwJp7tGqiPSTQxmduIvvdFiWOZBziqAo1yc=; b=SMytY0MSI1SEczfIRU C71P34GIrUkrJL13jYOsqpzq1lOrbEbJT5lUovGavi8CogY/rKoS2HVXTpnpcnB2 ZWxKpDUDb3+IpDOQdbFpYnsJBNoOLScLcC6CIwXbpIOjXMkdx9CPENEb7wMfAv2Z XQiJpT5S4ZXEimmS6K4CpDTOM= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgB3m07lma9qs7GbAQ--.54162S11; Sun, 20 Sep 2026 16:31:52 +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 v2 09/10] ntfs: persist append-only in the $LXFLAGS EA Date: Sun, 20 Sep 2026 16:31:30 +0800 Message-ID: <20260920083131.1880061-10-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260920083131.1880061-1-liubaolin12138@163.com> References: <20260920083131.1880061-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: PygvCgB3m07lma9qs7GbAQ--.54162S11 X-Coremail-Antispam: 1Uf129KBjvJXoWxCrW5Zw47KF18Jryxtw4kCrg_yoW7JF4xpF Z3JFyIg3yrWFyjgrZ2kF4q93WFvws3XF4UArWvgw18AasxJw1ftF1rtF4rGa48CryfC342 qF45trWUXrW3Z37anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07jU18PUUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbCwRgr9WqvmfgMswAA3c Content-Type: text/plain; charset="utf-8" From: Baolin Liu Extend the persistent fileattr interface to append-only using bit 1 of LXFLAGS. 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 --- 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 ced164216a5b..f00b618a3911 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 ``$LXFLAGS`` extended attribute +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 ``$LXFL= AGS`` 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 1e9b66e88655..ddac912de78c 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -500,6 +500,8 @@ int ntfs_ea_get_lxflags(struct inode *inode) ni->lxflags =3D le32_to_cpu(value); if (ni->lxflags & NTFS_LXFLAGS_IMMUTABLE) inode->i_flags |=3D S_IMMUTABLE; + if (ni->lxflags & NTFS_LXFLAGS_APPEND) + inode->i_flags |=3D S_APPEND; return 0; } =20 diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h index 19aea162b909..eac5ab4da44a 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -9,7 +9,9 @@ =20 /* $LXFLAGS stores these bits in a single little-endian 32-bit value. */ #define NTFS_LXFLAGS_IMMUTABLE BIT(0) -#define NTFS_LXFLAGS_MASK NTFS_LXFLAGS_IMMUTABLE +#define NTFS_LXFLAGS_APPEND BIT(1) +#define NTFS_LXFLAGS_MASK (NTFS_LXFLAGS_IMMUTABLE | \ + NTFS_LXFLAGS_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 882d33b67560..5b2a31790275 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 lxflags =3D ni->lxflags & ~NTFS_LXFLAGS_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->lxflags & NTFS_LXFLAGS_IMMUTABLE)) lxflags |=3D NTFS_LXFLAGS_IMMUTABLE; } + if (fa->flags & FS_APPEND_FL) { + new_fl |=3D S_APPEND; + lxflags |=3D NTFS_LXFLAGS_APPEND; + } =20 mutex_lock(&ni->mrec_lock); err =3D ntfs_ea_set_lxflags(vi, lxflags); @@ -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 20:37:00 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 B4BC33E7BA8 for ; Sun, 20 Sep 2026 08:32:29 +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=1789893152; cv=none; b=Yp1ebBl5Rc1TEI8+ZPOR7B7Kez7F13m1tzisB+ZZHWmCBE5sQw0kYJKM8LgTWh5vyReDuC5kHg9rgkREQM47OZbLWdl5hpVZ3dLhzwlqrEdtRz6X4H3f5QVaPMrYlgXiy6oUPfuT3tXv+NGio1OqhK71zx5NoFLCzfHds/xgXgc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789893152; c=relaxed/simple; bh=kBh7AbZYNmKoXkGK12cwDvQlee8zECCwGA2jplrh6Us=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=EXrKCQ0X9pQ83e8vFeI+w7xx04eFlonTsQl75s3KvayC7s7Ueh3r8hFGZHZC1zcI7wsQd0oGrsBDik5ThHBD0fPzOspVzxT/EXfTmTBbBNzTqOSz2QJRtcHi14l+/27bkwceDHXVed7Wb+LXaUaLG1oLQym0BI8cLuEQjVa9eIA= 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=IEKZ0UGj; 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="IEKZ0UGj" 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=w1 v8rrx+E6za1gVQbjxg4L9ygBuJSLjc6UDn0w5PHOs=; b=IEKZ0UGjL7AmWfCWgQ BetnwipjAg0VN7hTAU6kCDIQm4jDJtayIXTmwVu+TYQLMdt85KrH74U849q2h9jc 0vjg1eRG2SBcDeGN9H8+jf2rQvDCyHGcgMjGxWEykXscbVvROdgLJmZFw2KEESJ3 qPrXaacAiBmaR5im9LWcZL1Vk= Received: from liubaolin-ThinkPad-E15-Gen-2 (unknown []) by gzsmtp4 (Coremail) with SMTP id PygvCgB3m07lma9qs7GbAQ--.54162S12; Sun, 20 Sep 2026 16:31: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 v2 10/10] ntfs: persist nodump in the $LXFLAGS EA Date: Sun, 20 Sep 2026 16:31:31 +0800 Message-ID: <20260920083131.1880061-11-liubaolin12138@163.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260920083131.1880061-1-liubaolin12138@163.com> References: <20260920083131.1880061-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: PygvCgB3m07lma9qs7GbAQ--.54162S12 X-Coremail-Antispam: 1Uf129KBjvJXoWxKry7CF1xtrWfGrWfAFy3twb_yoW7Ary7pF Z3GFyxKw4FqFy7K397Cr4j9a1Fvrs3ZF4UXr92gw18Aas8J3WSqrySyF4rXa4kCr1Sk342 qF4UtrWUZFWYvwUanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07jcrc-UUUUU= X-CM-SenderInfo: xolxutxrol0iasrtmqqrwthudrp/xtbCwhor9WqvmfokgAAA3E Content-Type: text/plain; charset="utf-8" From: Baolin Liu Store nodump in bit 2 of LXFLAGS 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 --- 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 f00b618a3911..8b71bc6fca81 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 ``$LXFL= AGS`` 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 ``$LXFLAGS`` extended attribute within the +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 eac5ab4da44a..d66cdb4e2fe1 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -10,8 +10,9 @@ /* $LXFLAGS stores these bits in a single little-endian 32-bit value. */ #define NTFS_LXFLAGS_IMMUTABLE BIT(0) #define NTFS_LXFLAGS_APPEND BIT(1) +#define NTFS_LXFLAGS_NODUMP BIT(2) #define NTFS_LXFLAGS_MASK (NTFS_LXFLAGS_IMMUTABLE | \ - NTFS_LXFLAGS_APPEND) + NTFS_LXFLAGS_APPEND | NTFS_LXFLAGS_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 5b2a31790275..421dfa34ee7f 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->lxflags & NTFS_LXFLAGS_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 lxflags =3D ni->lxflags & ~NTFS_LXFLAGS_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; lxflags |=3D NTFS_LXFLAGS_APPEND; } + if (fa->flags & FS_NODUMP_FL) + lxflags |=3D NTFS_LXFLAGS_NODUMP; =20 mutex_lock(&ni->mrec_lock); err =3D ntfs_ea_set_lxflags(vi, lxflags); @@ -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->lxflags & NTFS_LXFLAGS_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