From nobody Mon Dec 1 22:35:44 2025 Received: from mail-pl1-f181.google.com (mail-pl1-f181.google.com [209.85.214.181]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EE6D83112A1 for ; Thu, 27 Nov 2025 05:01:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1764219691; cv=none; b=G6Lyd6bjigApRmTHbVDWPJ9w5fE7h2X6K2xE8XP6UXZqALObt1dTK4MvEPX5M/Hc51mtQr1pF+b55yE9dDwROJzBK7CUg36Wn5w8oQB4/i97HtMHLUwK8GcVnJrtzWc65Y89gvVmx9sQN6TU42XzEjNHkiQ8bw83uhczgEXudCs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1764219691; c=relaxed/simple; bh=QBiGfnw6D0AisBjt8bLbb+tUDFuhu0cw0dXg6lX0aWQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=D7eCRerLQUHz6DBw+UH8H8cE/06+aGXQmhygwnBg+ybD6kNPT3giKWbBWYebb9wbFUH+pkP++Qwh/FHGI/OmhYru5RdspdYxnvpxQPGoH+rCIUvVLZk+Y6JU69LH/PqceA066w8Eh+vCcm3nxwG7EJWluA9tkc9GKKu2RkRdWmU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=kernel.org; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.214.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=kernel.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-pl1-f181.google.com with SMTP id d9443c01a7336-297ef378069so4782875ad.3 for ; Wed, 26 Nov 2025 21:01:23 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1764219683; x=1764824483; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=p37yDdAenay1YMuz8i0RhGCtbMbyJ/bdh+HdPgyeuXw=; b=IKQ7QU9xLW//+w+lni/NuXRc82gieRfBM5XlVpgFlIyClEJZhgtRW4+O8Bf1FHzzPn IXgQJnlEVsi/P2JscA0AYOMXXSXqrAAvr2edk2TNLupCffJy0YUvvC7pdn6brbPcBPYG vQWC0mfV/equzN3tFGZTvHL/ycP71FHp+4cD1HV2o5O3LtLrQG8CzWsQcmMgTVgrRrpb ligo4U1FZwM9WNqDC1I1CTnNDOvboBR7Fi+cO7UwJewWdx9PPZR8XAwX3anIjIlUJ2U/ qkRfFYonGSxJ89mrZLLe7Kgjvl0GARkGf5ePQAVatTxXDX0dBaS20p8T7UfwJE33wBBa 16ow== X-Forwarded-Encrypted: i=1; AJvYcCWu4mYZN2uVPQsDdRB7oqAE/S1xyn+iznjY0sBlpXRrh3yzrlqTgMgJNNtL1T3q+pBi82y1Hv3Y6Ggbb8g=@vger.kernel.org X-Gm-Message-State: AOJu0Yxt14ss6WBn0qoODqHCcRNIR7uTqX+Cx8UvPjbK/u2T8u9CrQ6m dH9/AN8wlekT7IMnSL1asiTUIAL3IhvsVx886JUUTAu/bBg6ZI+ZZ96FNx+PIdqv X-Gm-Gg: ASbGncu+wwi0ngaLbj+RlSWz8u2P0/TU7fZywhUiXEWRLo0VMZGkArUEXuG/ms2wwEd q2uZb+WOuY5Ypche0t4utIfoUpQS3RE7cjdxV6KbJYJqVDGHurBRb8GhAksmnO5TroNsjrBkX5s AIItYGC6jMREpCNOa30T7LvYlIyrn8h//hayy9nzxPNXW/sL6IORPeII5XfAOdgPpqyQIvheG8t io5n3aNUNfWSL80ZjZ/3FH9kbCnY/wyq+DdC/L6Uuq9vy7AVm6WbeK1yF+AGw5UP5agAZTqc6PT O9igxNRiDztm6lFbg+n3Jp+aPiukzt6pFVb43GI6SoI8l4ro9RJRKjWWsUnaX7pgw5UBQax79vi OeZpGYZbTcPuhw6h2wG9QM6a2LJRiEiWlFjBH9KKvM7pjYm53o8is2odlUro8dMAD+v13YlA4C3 izu43r6F+0p9Ngbwjg5qur9CR05Q== X-Google-Smtp-Source: AGHT+IFBSSL1UXzegiJD1H2NtWqupcVBiE98w6OyPepRmif+i3KdMurHbwH9C9I2tqiJesT29hYEFQ== X-Received: by 2002:a17:902:ebca:b0:295:3f35:a312 with SMTP id d9443c01a7336-29baaf9595cmr96705545ad.20.1764219682388; Wed, 26 Nov 2025 21:01:22 -0800 (PST) Received: from localhost.localdomain ([1.227.206.162]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-29bceb54454sm2719825ad.84.2025.11.26.21.01.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 Nov 2025 21:01:21 -0800 (PST) From: Namjae Jeon To: viro@zeniv.linux.org.uk, brauner@kernel.org, hch@infradead.org, hch@lst.de, tytso@mit.edu, willy@infradead.org, jack@suse.cz, djwong@kernel.org, josef@toxicpanda.com, sandeen@sandeen.net, rgoldwyn@suse.com, xiang@kernel.org, dsterba@suse.com, pali@kernel.org, ebiggers@kernel.org, neil@brown.name, amir73il@gmail.com Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, iamjoonsoo.kim@lge.com, cheol.lee@lge.com, jay.sim@lge.com, gunho.lee@lge.com, Namjae Jeon Subject: [PATCH v2 10/11] ntfsplus: add misc operations Date: Thu, 27 Nov 2025 13:59:43 +0900 Message-Id: <20251127045944.26009-11-linkinjeon@kernel.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20251127045944.26009-1-linkinjeon@kernel.org> References: <20251127045944.26009-1-linkinjeon@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable This adds the implementation of misc operations for ntfsplus. Signed-off-by: Namjae Jeon --- fs/ntfsplus/collate.c | 178 ++++++++++ fs/ntfsplus/logfile.c | 770 ++++++++++++++++++++++++++++++++++++++++++ fs/ntfsplus/misc.c | 213 ++++++++++++ fs/ntfsplus/unistr.c | 473 ++++++++++++++++++++++++++ fs/ntfsplus/upcase.c | 73 ++++ 5 files changed, 1707 insertions(+) create mode 100644 fs/ntfsplus/collate.c create mode 100644 fs/ntfsplus/logfile.c create mode 100644 fs/ntfsplus/misc.c create mode 100644 fs/ntfsplus/unistr.c create mode 100644 fs/ntfsplus/upcase.c diff --git a/fs/ntfsplus/collate.c b/fs/ntfsplus/collate.c new file mode 100644 index 000000000000..82aeab3a434c --- /dev/null +++ b/fs/ntfsplus/collate.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel collation handling. Part of the Linux-NTFS project. + * + * Copyright (c) 2004 Anton Altaparmakov + * + * Part of this file is based on code from the NTFS-3G project. + * and is copyrighted by the respective authors below: + * Copyright (c) 2004 Anton Altaparmakov + * Copyright (c) 2005 Yura Pakhuchiy + */ + +#include "collate.h" +#include "misc.h" +#include "ntfs.h" + +static int ntfs_collate_binary(struct ntfs_volume *vol, + const void *data1, const int data1_len, + const void *data2, const int data2_len) +{ + int rc; + + ntfs_debug("Entering."); + rc =3D memcmp(data1, data2, min(data1_len, data2_len)); + if (!rc && (data1_len !=3D data2_len)) { + if (data1_len < data2_len) + rc =3D -1; + else + rc =3D 1; + } + ntfs_debug("Done, returning %i", rc); + return rc; +} + +static int ntfs_collate_ntofs_ulong(struct ntfs_volume *vol, + const void *data1, const int data1_len, + const void *data2, const int data2_len) +{ + int rc; + u32 d1, d2; + + ntfs_debug("Entering."); + + if (data1_len !=3D data2_len || data1_len !=3D 4) + return -EINVAL; + + d1 =3D le32_to_cpup(data1); + d2 =3D le32_to_cpup(data2); + if (d1 < d2) + rc =3D -1; + else { + if (d1 =3D=3D d2) + rc =3D 0; + else + rc =3D 1; + } + ntfs_debug("Done, returning %i", rc); + return rc; +} + +/** + * ntfs_collate_ntofs_ulongs - Which of two le32 arrays should be listed f= irst + * + * Returns: -1, 0 or 1 depending of how the arrays compare + */ +static int ntfs_collate_ntofs_ulongs(struct ntfs_volume *vol, + const void *data1, const int data1_len, + const void *data2, const int data2_len) +{ + int rc; + int len; + const __le32 *p1, *p2; + u32 d1, d2; + + ntfs_debug("Entering."); + if ((data1_len !=3D data2_len) || (data1_len <=3D 0) || (data1_len & 3)) { + ntfs_error(vol->sb, "data1_len or data2_len not valid\n"); + return -1; + } + + p1 =3D (const __le32 *)data1; + p2 =3D (const __le32 *)data2; + len =3D data1_len; + do { + d1 =3D le32_to_cpup(p1); + p1++; + d2 =3D le32_to_cpup(p2); + p2++; + } while ((d1 =3D=3D d2) && ((len -=3D 4) > 0)); + if (d1 < d2) + rc =3D -1; + else { + if (d1 =3D=3D d2) + rc =3D 0; + else + rc =3D 1; + } + ntfs_debug("Done, returning %i.", rc); + return rc; +} + +/** + * ntfs_collate_file_name - Which of two filenames should be listed first + */ +static int ntfs_collate_file_name(struct ntfs_volume *vol, + const void *data1, const int __always_unused data1_len, + const void *data2, const int __always_unused data2_len) +{ + int rc; + + ntfs_debug("Entering.\n"); + rc =3D ntfs_file_compare_values(data1, data2, -2, + IGNORE_CASE, vol->upcase, vol->upcase_len); + if (!rc) + rc =3D ntfs_file_compare_values(data1, data2, + -2, CASE_SENSITIVE, vol->upcase, vol->upcase_len); + ntfs_debug("Done, returning %i.\n", rc); + return rc; +} + +typedef int (*ntfs_collate_func_t)(struct ntfs_volume *, const void *, con= st int, + const void *, const int); + +static ntfs_collate_func_t ntfs_do_collate0x0[3] =3D { + ntfs_collate_binary, + ntfs_collate_file_name, + NULL/*ntfs_collate_unicode_string*/, +}; + +static ntfs_collate_func_t ntfs_do_collate0x1[4] =3D { + ntfs_collate_ntofs_ulong, + NULL/*ntfs_collate_ntofs_sid*/, + NULL/*ntfs_collate_ntofs_security_hash*/, + ntfs_collate_ntofs_ulongs, +}; + +/** + * ntfs_collate - collate two data items using a specified collation rule + * @vol: ntfs volume to which the data items belong + * @cr: collation rule to use when comparing the items + * @data1: first data item to collate + * @data1_len: length in bytes of @data1 + * @data2: second data item to collate + * @data2_len: length in bytes of @data2 + * + * Collate the two data items @data1 and @data2 using the collation rule @= cr + * and return -1, 0, ir 1 if @data1 is found, respectively, to collate bef= ore, + * to match, or to collate after @data2. + * + * For speed we use the collation rule @cr as an index into two tables of + * function pointers to call the appropriate collation function. + */ +int ntfs_collate(struct ntfs_volume *vol, __le32 cr, + const void *data1, const int data1_len, + const void *data2, const int data2_len) +{ + int i; + + ntfs_debug("Entering."); + + if (cr !=3D COLLATION_BINARY && cr !=3D COLLATION_NTOFS_ULONG && + cr !=3D COLLATION_FILE_NAME && cr !=3D COLLATION_NTOFS_ULONGS) + return -EINVAL; + + i =3D le32_to_cpu(cr); + if (i < 0) + return -1; + if (i <=3D 0x02) + return ntfs_do_collate0x0[i](vol, data1, data1_len, + data2, data2_len); + if (i < 0x10) + return -1; + i -=3D 0x10; + if (likely(i <=3D 3)) + return ntfs_do_collate0x1[i](vol, data1, data1_len, + data2, data2_len); + return 0; +} diff --git a/fs/ntfsplus/logfile.c b/fs/ntfsplus/logfile.c new file mode 100644 index 000000000000..f13cf1456708 --- /dev/null +++ b/fs/ntfsplus/logfile.c @@ -0,0 +1,770 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel journal handling. Part of the Linux-NTFS project. + * + * Copyright (c) 2002-2007 Anton Altaparmakov + */ + +#include + +#include "attrib.h" +#include "aops.h" +#include "logfile.h" +#include "misc.h" +#include "ntfs.h" + +/** + * ntfs_check_restart_page_header - check the page header for consistency + * @vi: LogFile inode to which the restart page header belongs + * @rp: restart page header to check + * @pos: position in @vi at which the restart page header resides + * + * Check the restart page header @rp for consistency and return 'true' if = it is + * consistent and 'false' otherwise. + * + * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not + * require the full restart page. + */ +static bool ntfs_check_restart_page_header(struct inode *vi, + struct restart_page_header *rp, s64 pos) +{ + u32 logfile_system_page_size, logfile_log_page_size; + u16 ra_ofs, usa_count, usa_ofs, usa_end =3D 0; + bool have_usa =3D true; + + ntfs_debug("Entering."); + /* + * If the system or log page sizes are smaller than the ntfs block size + * or either is not a power of 2 we cannot handle this log file. + */ + logfile_system_page_size =3D le32_to_cpu(rp->system_page_size); + logfile_log_page_size =3D le32_to_cpu(rp->log_page_size); + if (logfile_system_page_size < NTFS_BLOCK_SIZE || + logfile_log_page_size < NTFS_BLOCK_SIZE || + logfile_system_page_size & + (logfile_system_page_size - 1) || + !is_power_of_2(logfile_log_page_size)) { + ntfs_error(vi->i_sb, "LogFile uses unsupported page size."); + return false; + } + /* + * We must be either at !pos (1st restart page) or at pos =3D system page + * size (2nd restart page). + */ + if (pos && pos !=3D logfile_system_page_size) { + ntfs_error(vi->i_sb, "Found restart area in incorrect position in LogFil= e."); + return false; + } + /* We only know how to handle version 1.1. */ + if (le16_to_cpu(rp->major_ver) !=3D 1 || + le16_to_cpu(rp->minor_ver) !=3D 1) { + ntfs_error(vi->i_sb, + "LogFile version %i.%i is not supported. (This driver supports version= 1.1 only.)", + (int)le16_to_cpu(rp->major_ver), + (int)le16_to_cpu(rp->minor_ver)); + return false; + } + /* + * If chkdsk has been run the restart page may not be protected by an + * update sequence array. + */ + if (ntfs_is_chkd_record(rp->magic) && !le16_to_cpu(rp->usa_count)) { + have_usa =3D false; + goto skip_usa_checks; + } + /* Verify the size of the update sequence array. */ + usa_count =3D 1 + (logfile_system_page_size >> NTFS_BLOCK_SIZE_BITS); + if (usa_count !=3D le16_to_cpu(rp->usa_count)) { + ntfs_error(vi->i_sb, + "LogFile restart page specifies inconsistent update sequence array coun= t."); + return false; + } + /* Verify the position of the update sequence array. */ + usa_ofs =3D le16_to_cpu(rp->usa_ofs); + usa_end =3D usa_ofs + usa_count * sizeof(u16); + if (usa_ofs < sizeof(struct restart_page_header) || + usa_end > NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_error(vi->i_sb, + "LogFile restart page specifies inconsistent update sequence array offs= et."); + return false; + } +skip_usa_checks: + /* + * Verify the position of the restart area. It must be: + * - aligned to 8-byte boundary, + * - after the update sequence array, and + * - within the system page size. + */ + ra_ofs =3D le16_to_cpu(rp->restart_area_offset); + if (ra_ofs & 7 || (have_usa ? ra_ofs < usa_end : + ra_ofs < sizeof(struct restart_page_header)) || + ra_ofs > logfile_system_page_size) { + ntfs_error(vi->i_sb, + "LogFile restart page specifies inconsistent restart area offset."); + return false; + } + /* + * Only restart pages modified by chkdsk are allowed to have chkdsk_lsn + * set. + */ + if (!ntfs_is_chkd_record(rp->magic) && le64_to_cpu(rp->chkdsk_lsn)) { + ntfs_error(vi->i_sb, + "LogFile restart page is not modified by chkdsk but a chkdsk LSN is spe= cified."); + return false; + } + ntfs_debug("Done."); + return true; +} + +/** + * ntfs_check_restart_area - check the restart area for consistency + * @vi: LogFile inode to which the restart page belongs + * @rp: restart page whose restart area to check + * + * Check the restart area of the restart page @rp for consistency and retu= rn + * 'true' if it is consistent and 'false' otherwise. + * + * This function assumes that the restart page header has already been + * consistency checked. + * + * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not + * require the full restart page. + */ +static bool ntfs_check_restart_area(struct inode *vi, struct restart_page_= header *rp) +{ + u64 file_size; + struct restart_area *ra; + u16 ra_ofs, ra_len, ca_ofs; + u8 fs_bits; + + ntfs_debug("Entering."); + ra_ofs =3D le16_to_cpu(rp->restart_area_offset); + ra =3D (struct restart_area *)((u8 *)rp + ra_ofs); + /* + * Everything before ra->file_size must be before the first word + * protected by an update sequence number. This ensures that it is + * safe to access ra->client_array_offset. + */ + if (ra_ofs + offsetof(struct restart_area, file_size) > + NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies inconsistent file offset."); + return false; + } + /* + * Now that we can access ra->client_array_offset, make sure everything + * up to the log client array is before the first word protected by an + * update sequence number. This ensures we can access all of the + * restart area elements safely. Also, the client array offset must be + * aligned to an 8-byte boundary. + */ + ca_ofs =3D le16_to_cpu(ra->client_array_offset); + if (((ca_ofs + 7) & ~7) !=3D ca_ofs || + ra_ofs + ca_ofs > NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies inconsistent client array offset."); + return false; + } + /* + * The restart area must end within the system page size both when + * calculated manually and as specified by ra->restart_area_length. + * Also, the calculated length must not exceed the specified length. + */ + ra_len =3D ca_ofs + le16_to_cpu(ra->log_clients) * + sizeof(struct log_client_record); + if (ra_ofs + ra_len > le32_to_cpu(rp->system_page_size) || + ra_ofs + le16_to_cpu(ra->restart_area_length) > + le32_to_cpu(rp->system_page_size) || + ra_len > le16_to_cpu(ra->restart_area_length)) { + ntfs_error(vi->i_sb, + "LogFile restart area is out of bounds of the system page size specifie= d by the restart page header and/or the specified restart area length is in= consistent."); + return false; + } + /* + * The ra->client_free_list and ra->client_in_use_list must be either + * LOGFILE_NO_CLIENT or less than ra->log_clients or they are + * overflowing the client array. + */ + if ((ra->client_free_list !=3D LOGFILE_NO_CLIENT && + le16_to_cpu(ra->client_free_list) >=3D + le16_to_cpu(ra->log_clients)) || + (ra->client_in_use_list !=3D LOGFILE_NO_CLIENT && + le16_to_cpu(ra->client_in_use_list) >=3D + le16_to_cpu(ra->log_clients))) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies overflowing client free and/or in use l= ists."); + return false; + } + /* + * Check ra->seq_number_bits against ra->file_size for consistency. + * We cannot just use ffs() because the file size is not a power of 2. + */ + file_size =3D le64_to_cpu(ra->file_size); + fs_bits =3D 0; + while (file_size) { + file_size >>=3D 1; + fs_bits++; + } + if (le32_to_cpu(ra->seq_number_bits) !=3D 67 - fs_bits) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies inconsistent sequence number bits."); + return false; + } + /* The log record header length must be a multiple of 8. */ + if (((le16_to_cpu(ra->log_record_header_length) + 7) & ~7) !=3D + le16_to_cpu(ra->log_record_header_length)) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies inconsistent log record header length."= ); + return false; + } + /* Dito for the log page data offset. */ + if (((le16_to_cpu(ra->log_page_data_offset) + 7) & ~7) !=3D + le16_to_cpu(ra->log_page_data_offset)) { + ntfs_error(vi->i_sb, + "LogFile restart area specifies inconsistent log page data offset."); + return false; + } + ntfs_debug("Done."); + return true; +} + +/** + * ntfs_check_log_client_array - check the log client array for consistency + * @vi: LogFile inode to which the restart page belongs + * @rp: restart page whose log client array to check + * + * Check the log client array of the restart page @rp for consistency and + * return 'true' if it is consistent and 'false' otherwise. + * + * This function assumes that the restart page header and the restart area= have + * already been consistency checked. + * + * Unlike ntfs_check_restart_page_header() and ntfs_check_restart_area(), = this + * function needs @rp->system_page_size bytes in @rp, i.e. it requires the= full + * restart page and the page must be multi sector transfer deprotected. + */ +static bool ntfs_check_log_client_array(struct inode *vi, + struct restart_page_header *rp) +{ + struct restart_area *ra; + struct log_client_record *ca, *cr; + u16 nr_clients, idx; + bool in_free_list, idx_is_first; + + ntfs_debug("Entering."); + ra =3D (struct restart_area *)((u8 *)rp + le16_to_cpu(rp->restart_area_of= fset)); + ca =3D (struct log_client_record *)((u8 *)ra + + le16_to_cpu(ra->client_array_offset)); + /* + * Check the ra->client_free_list first and then check the + * ra->client_in_use_list. Check each of the log client records in + * each of the lists and check that the array does not overflow the + * ra->log_clients value. Also keep track of the number of records + * visited as there cannot be more than ra->log_clients records and + * that way we detect eventual loops in within a list. + */ + nr_clients =3D le16_to_cpu(ra->log_clients); + idx =3D le16_to_cpu(ra->client_free_list); + in_free_list =3D true; +check_list: + for (idx_is_first =3D true; idx !=3D LOGFILE_NO_CLIENT_CPU; nr_clients--, + idx =3D le16_to_cpu(cr->next_client)) { + if (!nr_clients || idx >=3D le16_to_cpu(ra->log_clients)) + goto err_out; + /* Set @cr to the current log client record. */ + cr =3D ca + idx; + /* The first log client record must not have a prev_client. */ + if (idx_is_first) { + if (cr->prev_client !=3D LOGFILE_NO_CLIENT) + goto err_out; + idx_is_first =3D false; + } + } + /* Switch to and check the in use list if we just did the free list. */ + if (in_free_list) { + in_free_list =3D false; + idx =3D le16_to_cpu(ra->client_in_use_list); + goto check_list; + } + ntfs_debug("Done."); + return true; +err_out: + ntfs_error(vi->i_sb, "LogFile log client array is corrupt."); + return false; +} + +/** + * ntfs_check_and_load_restart_page - check the restart page for consisten= cy + * @vi: LogFile inode to which the restart page belongs + * @rp: restart page to check + * @pos: position in @vi at which the restart page resides + * @wrp: [OUT] copy of the multi sector transfer deprotected restart page + * @lsn: [OUT] set to the current logfile lsn on success + * + * Check the restart page @rp for consistency and return 0 if it is consis= tent + * and -errno otherwise. The restart page may have been modified by chkds= k in + * which case its magic is CHKD instead of RSTR. + * + * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not + * require the full restart page. + * + * If @wrp is not NULL, on success, *@wrp will point to a buffer containin= g a + * copy of the complete multi sector transfer deprotected page. On failur= e, + * *@wrp is undefined. + * + * Simillarly, if @lsn is not NULL, on success *@lsn will be set to the cu= rrent + * logfile lsn according to this restart page. On failure, *@lsn is undef= ined. + * + * The following error codes are defined: + * -EINVAL - The restart page is inconsistent. + * -ENOMEM - Not enough memory to load the restart page. + * -EIO - Failed to reading from LogFile. + */ +static int ntfs_check_and_load_restart_page(struct inode *vi, + struct restart_page_header *rp, s64 pos, struct restart_page_header **wr= p, + s64 *lsn) +{ + struct restart_area *ra; + struct restart_page_header *trp; + int size, err; + + ntfs_debug("Entering."); + /* Check the restart page header for consistency. */ + if (!ntfs_check_restart_page_header(vi, rp, pos)) { + /* Error output already done inside the function. */ + return -EINVAL; + } + /* Check the restart area for consistency. */ + if (!ntfs_check_restart_area(vi, rp)) { + /* Error output already done inside the function. */ + return -EINVAL; + } + ra =3D (struct restart_area *)((u8 *)rp + le16_to_cpu(rp->restart_area_of= fset)); + /* + * Allocate a buffer to store the whole restart page so we can multi + * sector transfer deprotect it. + */ + trp =3D ntfs_malloc_nofs(le32_to_cpu(rp->system_page_size)); + if (!trp) { + ntfs_error(vi->i_sb, "Failed to allocate memory for LogFile restart page= buffer."); + return -ENOMEM; + } + /* + * Read the whole of the restart page into the buffer. If it fits + * completely inside @rp, just copy it from there. Otherwise map all + * the required pages and copy the data from them. + */ + size =3D PAGE_SIZE - (pos & ~PAGE_MASK); + if (size >=3D le32_to_cpu(rp->system_page_size)) { + memcpy(trp, rp, le32_to_cpu(rp->system_page_size)); + } else { + pgoff_t idx; + struct folio *folio; + int have_read, to_read; + + /* First copy what we already have in @rp. */ + memcpy(trp, rp, size); + /* Copy the remaining data one page at a time. */ + have_read =3D size; + to_read =3D le32_to_cpu(rp->system_page_size) - size; + idx =3D (pos + size) >> PAGE_SHIFT; + do { + folio =3D ntfs_read_mapping_folio(vi->i_mapping, idx); + if (IS_ERR(folio)) { + ntfs_error(vi->i_sb, "Error mapping LogFile page (index %lu).", + idx); + err =3D PTR_ERR(folio); + if (err !=3D -EIO && err !=3D -ENOMEM) + err =3D -EIO; + goto err_out; + } + size =3D min_t(int, to_read, PAGE_SIZE); + memcpy((u8 *)trp + have_read, folio_address(folio), size); + folio_put(folio); + have_read +=3D size; + to_read -=3D size; + idx++; + } while (to_read > 0); + } + /* + * Perform the multi sector transfer deprotection on the buffer if the + * restart page is protected. + */ + if ((!ntfs_is_chkd_record(trp->magic) || le16_to_cpu(trp->usa_count)) && + post_read_mst_fixup((struct ntfs_record *)trp, le32_to_cpu(rp->system= _page_size))) { + /* + * A multi sector transfer error was detected. We only need to + * abort if the restart page contents exceed the multi sector + * transfer fixup of the first sector. + */ + if (le16_to_cpu(rp->restart_area_offset) + + le16_to_cpu(ra->restart_area_length) > + NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_error(vi->i_sb, + "Multi sector transfer error detected in LogFile restart page."); + err =3D -EINVAL; + goto err_out; + } + } + /* + * If the restart page is modified by chkdsk or there are no active + * logfile clients, the logfile is consistent. Otherwise, need to + * check the log client records for consistency, too. + */ + err =3D 0; + if (ntfs_is_rstr_record(rp->magic) && + ra->client_in_use_list !=3D LOGFILE_NO_CLIENT) { + if (!ntfs_check_log_client_array(vi, trp)) { + err =3D -EINVAL; + goto err_out; + } + } + if (lsn) { + if (ntfs_is_rstr_record(rp->magic)) + *lsn =3D le64_to_cpu(ra->current_lsn); + else /* if (ntfs_is_chkd_record(rp->magic)) */ + *lsn =3D le64_to_cpu(rp->chkdsk_lsn); + } + ntfs_debug("Done."); + if (wrp) + *wrp =3D trp; + else { +err_out: + ntfs_free(trp); + } + return err; +} + +/** + * ntfs_check_logfile - check the journal for consistency + * @log_vi: struct inode of loaded journal LogFile to check + * @rp: [OUT] on success this is a copy of the current restart page + * + * Check the LogFile journal for consistency and return 'true' if it is + * consistent and 'false' if not. On success, the current restart page is + * returned in *@rp. Caller must call ntfs_free(*@rp) when finished with = it. + * + * At present we only check the two restart pages and ignore the log record + * pages. + * + * Note that the MstProtected flag is not set on the LogFile inode and hen= ce + * when reading pages they are not deprotected. This is because we do not= know + * if the LogFile was created on a system with a different page size to ou= rs + * yet and mst deprotection would fail if our page size is smaller. + */ +bool ntfs_check_logfile(struct inode *log_vi, struct restart_page_header *= *rp) +{ + s64 size, pos; + s64 rstr1_lsn, rstr2_lsn; + struct ntfs_volume *vol =3D NTFS_SB(log_vi->i_sb); + struct address_space *mapping =3D log_vi->i_mapping; + struct folio *folio =3D NULL; + u8 *kaddr =3D NULL; + struct restart_page_header *rstr1_ph =3D NULL; + struct restart_page_header *rstr2_ph =3D NULL; + int log_page_size, err; + bool logfile_is_empty =3D true; + u8 log_page_bits; + + ntfs_debug("Entering."); + /* An empty LogFile must have been clean before it got emptied. */ + if (NVolLogFileEmpty(vol)) + goto is_empty; + size =3D i_size_read(log_vi); + /* Make sure the file doesn't exceed the maximum allowed size. */ + if (size > MaxLogFileSize) + size =3D MaxLogFileSize; + /* + * Truncate size to a multiple of the page cache size or the default + * log page size if the page cache size is between the default log page + * log page size if the page cache size is between the default log page + * size and twice that. + */ + if (DefaultLogPageSize <=3D PAGE_SIZE && + DefaultLogPageSize * 2 <=3D PAGE_SIZE) + log_page_size =3D DefaultLogPageSize; + else + log_page_size =3D PAGE_SIZE; + /* + * Use ntfs_ffs() instead of ffs() to enable the compiler to + * optimize log_page_size and log_page_bits into constants. + */ + log_page_bits =3D ntfs_ffs(log_page_size) - 1; + size &=3D ~(s64)(log_page_size - 1); + /* + * Ensure the log file is big enough to store at least the two restart + * pages and the minimum number of log record pages. + */ + if (size < log_page_size * 2 || (size - log_page_size * 2) >> + log_page_bits < MinLogRecordPages) { + ntfs_error(vol->sb, "LogFile is too small."); + return false; + } + /* + * Read through the file looking for a restart page. Since the restart + * page header is at the beginning of a page we only need to search at + * what could be the beginning of a page (for each page size) rather + * than scanning the whole file byte by byte. If all potential places + * contain empty and uninitialzed records, the log file can be assumed + * to be empty. + */ + for (pos =3D 0; pos < size; pos <<=3D 1) { + pgoff_t idx =3D pos >> PAGE_SHIFT; + + if (!folio || folio->index !=3D idx) { + if (folio) + ntfs_unmap_folio(folio, kaddr); + folio =3D ntfs_read_mapping_folio(mapping, idx); + if (IS_ERR(folio)) { + ntfs_error(vol->sb, "Error mapping LogFile page (index %lu).", + idx); + goto err_out; + } + } + kaddr =3D (u8 *)kmap_local_folio(folio, 0) + (pos & ~PAGE_MASK); + /* + * A non-empty block means the logfile is not empty while an + * empty block after a non-empty block has been encountered + * means we are done. + */ + if (!ntfs_is_empty_recordp((__le32 *)kaddr)) + logfile_is_empty =3D false; + else if (!logfile_is_empty) + break; + /* + * A log record page means there cannot be a restart page after + * this so no need to continue searching. + */ + if (ntfs_is_rcrd_recordp((__le32 *)kaddr)) + break; + /* If not a (modified by chkdsk) restart page, continue. */ + if (!ntfs_is_rstr_recordp((__le32 *)kaddr) && + !ntfs_is_chkd_recordp((__le32 *)kaddr)) { + if (!pos) + pos =3D NTFS_BLOCK_SIZE >> 1; + continue; + } + /* + * Check the (modified by chkdsk) restart page for consistency + * and get a copy of the complete multi sector transfer + * deprotected restart page. + */ + err =3D ntfs_check_and_load_restart_page(log_vi, + (struct restart_page_header *)kaddr, pos, + !rstr1_ph ? &rstr1_ph : &rstr2_ph, + !rstr1_ph ? &rstr1_lsn : &rstr2_lsn); + if (!err) { + /* + * If we have now found the first (modified by chkdsk) + * restart page, continue looking for the second one. + */ + if (!pos) { + pos =3D NTFS_BLOCK_SIZE >> 1; + continue; + } + /* + * We have now found the second (modified by chkdsk) + * restart page, so we can stop looking. + */ + break; + } + /* + * Error output already done inside the function. Note, we do + * not abort if the restart page was invalid as we might still + * find a valid one further in the file. + */ + if (err !=3D -EINVAL) { + ntfs_unmap_folio(folio, kaddr); + goto err_out; + } + /* Continue looking. */ + if (!pos) + pos =3D NTFS_BLOCK_SIZE >> 1; + } + if (folio) + ntfs_unmap_folio(folio, kaddr); + if (logfile_is_empty) { + NVolSetLogFileEmpty(vol); +is_empty: + ntfs_debug("Done. (LogFile is empty.)"); + return true; + } + if (!rstr1_ph) { + ntfs_error(vol->sb, + "Did not find any restart pages in LogFile and it was not empty."); + return false; + } + /* If both restart pages were found, use the more recent one. */ + if (rstr2_ph) { + /* + * If the second restart area is more recent, switch to it. + * Otherwise just throw it away. + */ + if (rstr2_lsn > rstr1_lsn) { + ntfs_debug("Using second restart page as it is more recent."); + ntfs_free(rstr1_ph); + rstr1_ph =3D rstr2_ph; + /* rstr1_lsn =3D rstr2_lsn; */ + } else { + ntfs_debug("Using first restart page as it is more recent."); + ntfs_free(rstr2_ph); + } + rstr2_ph =3D NULL; + } + /* All consistency checks passed. */ + if (rp) + *rp =3D rstr1_ph; + else + ntfs_free(rstr1_ph); + ntfs_debug("Done."); + return true; +err_out: + if (rstr1_ph) + ntfs_free(rstr1_ph); + return false; +} + +/** + * ntfs_empty_logfile - empty the contents of the LogFile journal + * @log_vi: struct inode of loaded journal LogFile to empty + * + * Empty the contents of the LogFile journal @log_vi and return 'true' on + * success and 'false' on error. + * + * This function assumes that the LogFile journal has already been consist= ency + * checked by a call to ntfs_check_logfile() and that ntfs_is_logfile_clea= n() + * has been used to ensure that the LogFile is clean. + */ +bool ntfs_empty_logfile(struct inode *log_vi) +{ + s64 vcn, end_vcn; + struct ntfs_inode *log_ni =3D NTFS_I(log_vi); + struct ntfs_volume *vol =3D log_ni->vol; + struct super_block *sb =3D vol->sb; + struct runlist_element *rl; + unsigned long flags; + int err; + bool should_wait =3D true; + char *empty_buf =3D NULL; + struct file_ra_state *ra =3D NULL; + + ntfs_debug("Entering."); + if (NVolLogFileEmpty(vol)) { + ntfs_debug("Done."); + return true; + } + + /* + * We cannot use ntfs_attr_set() because we may be still in the middle + * of a mount operation. Thus we do the emptying by hand by first + * zapping the page cache pages for the LogFile/DATA attribute and + * then emptying each of the buffers in each of the clusters specified + * by the runlist by hand. + */ + vcn =3D 0; + read_lock_irqsave(&log_ni->size_lock, flags); + end_vcn =3D (log_ni->initialized_size + vol->cluster_size_mask) >> + vol->cluster_size_bits; + read_unlock_irqrestore(&log_ni->size_lock, flags); + truncate_inode_pages(log_vi->i_mapping, 0); + down_write(&log_ni->runlist.lock); + rl =3D log_ni->runlist.rl; + if (unlikely(!rl || vcn < rl->vcn || !rl->length)) { +map_vcn: + err =3D ntfs_map_runlist_nolock(log_ni, vcn, NULL); + if (err) { + ntfs_error(sb, "Failed to map runlist fragment (error %d).", -err); + goto err; + } + rl =3D log_ni->runlist.rl; + } + /* Seek to the runlist element containing @vcn. */ + while (rl->length && vcn >=3D rl[1].vcn) + rl++; + + err =3D -ENOMEM; + empty_buf =3D ntfs_malloc_nofs(vol->cluster_size); + if (!empty_buf) + goto err; + + memset(empty_buf, 0xff, vol->cluster_size); + + ra =3D kzalloc(sizeof(*ra), GFP_NOFS); + if (!ra) + goto err; + + file_ra_state_init(ra, sb->s_bdev->bd_mapping); + do { + s64 lcn; + loff_t start, end; + s64 len; + + /* + * If this run is not mapped map it now and start again as the + * runlist will have been updated. + */ + lcn =3D rl->lcn; + if (unlikely(lcn =3D=3D LCN_RL_NOT_MAPPED)) { + vcn =3D rl->vcn; + ntfs_free(empty_buf); + goto map_vcn; + } + /* If this run is not valid abort with an error. */ + if (unlikely(!rl->length || lcn < LCN_HOLE)) + goto rl_err; + /* Skip holes. */ + if (lcn =3D=3D LCN_HOLE) + continue; + start =3D lcn << vol->cluster_size_bits; + len =3D rl->length; + if (rl[1].vcn > end_vcn) + len =3D end_vcn - rl->vcn; + end =3D (lcn + len) << vol->cluster_size_bits; + + page_cache_sync_readahead(sb->s_bdev->bd_mapping, ra, NULL, + start >> PAGE_SHIFT, (end - start) >> PAGE_SHIFT); + + do { + err =3D ntfs_dev_write(sb, empty_buf, start, + vol->cluster_size, should_wait); + if (err) { + ntfs_error(sb, "ntfs_dev_write failed, err : %d\n", err); + goto io_err; + } + + /* + * Submit the buffer and wait for i/o to complete but + * only for the first buffer so we do not miss really + * serious i/o errors. Once the first buffer has + * completed ignore errors afterwards as we can assume + * that if one buffer worked all of them will work. + */ + if (should_wait) + should_wait =3D false; + start +=3D vol->cluster_size; + } while (start < end); + } while ((++rl)->vcn < end_vcn); + up_write(&log_ni->runlist.lock); + kfree(empty_buf); + kfree(ra); + truncate_inode_pages(log_vi->i_mapping, 0); + /* Set the flag so we do not have to do it again on remount. */ + NVolSetLogFileEmpty(vol); + ntfs_debug("Done."); + return true; +io_err: + ntfs_error(sb, "Failed to write buffer. Unmount and run chkdsk."); + goto dirty_err; +rl_err: + ntfs_error(sb, "Runlist is corrupt. Unmount and run chkdsk."); +dirty_err: + NVolSetErrors(vol); + err =3D -EIO; +err: + ntfs_free(empty_buf); + kfree(ra); + up_write(&log_ni->runlist.lock); + ntfs_error(sb, "Failed to fill LogFile with 0xff bytes (error %d).", + -err); + return false; +} diff --git a/fs/ntfsplus/misc.c b/fs/ntfsplus/misc.c new file mode 100644 index 000000000000..d4d63c74db99 --- /dev/null +++ b/fs/ntfsplus/misc.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS kernel debug support. Part of the Linux-NTFS project. + * + * Copyright (C) 1997 Martin von L=C3=B6wis, R=C3=A9gis Duchesne + * Copyright (c) 2001-2005 Anton Altaparmakov + */ + +#include +#ifdef CONFIG_SYSCTL +#include +#include +#endif + +#include "misc.h" + +/** + * __ntfs_warning - output a warning to the syslog + * @function: name of function outputting the warning + * @sb: super block of mounted ntfs filesystem + * @fmt: warning string containing format specifications + * @...: a variable number of arguments specified in @fmt + * + * Outputs a warning to the syslog for the mounted ntfs filesystem describ= ed + * by @sb. + * + * @fmt and the corresponding @... is printf style format string containing + * the warning string and the corresponding format arguments, respectively. + * + * @function is the name of the function from which __ntfs_warning is being + * called. + * + * Note, you should be using debug.h::ntfs_warning(@sb, @fmt, @...) instead + * as this provides the @function parameter automatically. + */ +void __ntfs_warning(const char *function, const struct super_block *sb, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + int flen =3D 0; + + if (function) + flen =3D strlen(function); + va_start(args, fmt); + vaf.fmt =3D fmt; + vaf.va =3D &args; +#ifndef DEBUG + if (sb) + pr_warn_ratelimited("(device %s): %s(): %pV\n", + sb->s_id, flen ? function : "", &vaf); + else + pr_warn_ratelimited("%s(): %pV\n", flen ? function : "", &vaf); +#else + if (sb) + pr_warn("(device %s): %s(): %pV\n", + sb->s_id, flen ? function : "", &vaf); + else + pr_warn("%s(): %pV\n", flen ? function : "", &vaf); +#endif + va_end(args); +} + +/** + * __ntfs_error - output an error to the syslog + * @function: name of function outputting the error + * @sb: super block of mounted ntfs filesystem + * @fmt: error string containing format specifications + * @...: a variable number of arguments specified in @fmt + * + * Outputs an error to the syslog for the mounted ntfs filesystem described + * by @sb. + * + * @fmt and the corresponding @... is printf style format string containing + * the error string and the corresponding format arguments, respectively. + * + * @function is the name of the function from which __ntfs_error is being + * called. + * + * Note, you should be using debug.h::ntfs_error(@sb, @fmt, @...) instead + * as this provides the @function parameter automatically. + */ +void __ntfs_error(const char *function, struct super_block *sb, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + int flen =3D 0; + + if (function) + flen =3D strlen(function); + va_start(args, fmt); + vaf.fmt =3D fmt; + vaf.va =3D &args; +#ifndef DEBUG + if (sb) + pr_err_ratelimited("(device %s): %s(): %pV\n", + sb->s_id, flen ? function : "", &vaf); + else + pr_err_ratelimited("%s(): %pV\n", flen ? function : "", &vaf); +#else + if (sb) + pr_err("(device %s): %s(): %pV\n", + sb->s_id, flen ? function : "", &vaf); + else + pr_err("%s(): %pV\n", flen ? function : "", &vaf); +#endif + va_end(args); + + if (sb) + ntfs_handle_error(sb); +} + +#ifdef DEBUG + +/* If 1, output debug messages, and if 0, don't. */ +int debug_msgs; + +void __ntfs_debug(const char *file, int line, const char *function, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + int flen =3D 0; + + if (!debug_msgs) + return; + if (function) + flen =3D strlen(function); + va_start(args, fmt); + vaf.fmt =3D fmt; + vaf.va =3D &args; + pr_debug("(%s, %d): %s(): %pV", file, line, flen ? function : "", &vaf); + va_end(args); +} + +/* Dump a runlist. Caller has to provide synchronisation for @rl. */ +void ntfs_debug_dump_runlist(const struct runlist_element *rl) +{ + int i; + const char *lcn_str[5] =3D { "LCN_DELALLOC ", "LCN_HOLE ", + "LCN_RL_NOT_MAPPED", "LCN_ENOENT ", + "LCN_unknown " }; + + if (!debug_msgs) + return; + pr_debug("Dumping runlist (values in hex):\n"); + if (!rl) { + pr_debug("Run list not present.\n"); + return; + } + pr_debug("VCN LCN Run length\n"); + for (i =3D 0; ; i++) { + s64 lcn =3D (rl + i)->lcn; + + if (lcn < (s64)0) { + int index =3D -lcn - 1; + + if (index > -LCN_ENOENT - 1) + index =3D 3; + pr_debug("%-16Lx %s %-16Lx%s\n", + (long long)(rl + i)->vcn, lcn_str[index], + (long long)(rl + i)->length, + (rl + i)->length ? "" : + " (runlist end)"); + } else + pr_debug("%-16Lx %-16Lx %-16Lx%s\n", + (long long)(rl + i)->vcn, + (long long)(rl + i)->lcn, + (long long)(rl + i)->length, + (rl + i)->length ? "" : + " (runlist end)"); + if (!(rl + i)->length) + break; + } +} + +#ifdef CONFIG_SYSCTL +/* Definition of the ntfs sysctl. */ +static const struct ctl_table ntfs_sysctls[] =3D { + { + .procname =3D "ntfs-debug", + .data =3D &debug_msgs, /* Data pointer and size. */ + .maxlen =3D sizeof(debug_msgs), + .mode =3D 0644, /* Mode, proc handler. */ + .proc_handler =3D proc_dointvec + }, + {} +}; + +/* Storage for the sysctls header. */ +static struct ctl_table_header *sysctls_root_table; + +/** + * ntfs_sysctl - add or remove the debug sysctl + * @add: add (1) or remove (0) the sysctl + * + * Add or remove the debug sysctl. Return 0 on success or -errno on error. + */ +int ntfs_sysctl(int add) +{ + if (add) { + sysctls_root_table =3D register_sysctl("fs", ntfs_sysctls); + if (!sysctls_root_table) + return -ENOMEM; + } else { + unregister_sysctl_table(sysctls_root_table); + sysctls_root_table =3D NULL; + } + return 0; +} +#endif /* CONFIG_SYSCTL */ +#endif diff --git a/fs/ntfsplus/unistr.c b/fs/ntfsplus/unistr.c new file mode 100644 index 000000000000..810fdb2ab218 --- /dev/null +++ b/fs/ntfsplus/unistr.c @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NTFS Unicode string handling. Part of the Linux-NTFS project. + * + * Copyright (c) 2001-2006 Anton Altaparmakov + */ + +#include "ntfs.h" +#include "misc.h" + +/* + * IMPORTANT + * =3D=3D=3D=3D=3D=3D=3D=3D=3D + * + * All these routines assume that the Unicode characters are in little end= ian + * encoding inside the strings!!! + */ + +/* + * This is used by the name collation functions to quickly determine what + * characters are (in)valid. + */ +static const u8 legal_ansi_char_array[0x40] =3D { + 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + + 0x17, 0x07, 0x18, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x18, 0x16, 0x16, 0x17, 0x07, 0x00, + + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x04, 0x16, 0x18, 0x16, 0x18, 0x18, +}; + +/** + * ntfs_are_names_equal - compare two Unicode names for equality + * @s1: name to compare to @s2 + * @s1_len: length in Unicode characters of @s1 + * @s2: name to compare to @s1 + * @s2_len: length in Unicode characters of @s2 + * @ic: ignore case bool + * @upcase: upcase table (only if @ic =3D=3D IGNORE_CASE) + * @upcase_size: length in Unicode characters of @upcase (if present) + * + * Compare the names @s1 and @s2 and return 'true' (1) if the names are + * identical, or 'false' (0) if they are not identical. If @ic is IGNORE_C= ASE, + * the @upcase table is used to performa a case insensitive comparison. + */ +bool ntfs_are_names_equal(const __le16 *s1, size_t s1_len, + const __le16 *s2, size_t s2_len, const u32 ic, + const __le16 *upcase, const u32 upcase_size) +{ + if (s1_len !=3D s2_len) + return false; + if (ic =3D=3D CASE_SENSITIVE) + return !ntfs_ucsncmp(s1, s2, s1_len); + return !ntfs_ucsncasecmp(s1, s2, s1_len, upcase, upcase_size); +} + +/** + * ntfs_collate_names - collate two Unicode names + * @name1: first Unicode name to compare + * @name1_len: first Unicode name length + * @name2: second Unicode name to compare + * @name2_len: second Unicode name length + * @err_val: if @name1 contains an invalid character return this value + * @ic: either CASE_SENSITIVE or IGNORE_CASE + * @upcase: upcase table (ignored if @ic is CASE_SENSITIVE) + * @upcase_len: upcase table size (ignored if @ic is CASE_SENSITIVE) + * + * ntfs_collate_names collates two Unicode names and returns: + * + * -1 if the first name collates before the second one, + * 0 if the names match, + * 1 if the second name collates before the first one, or + * @err_val if an invalid character is found in @name1 during the comparis= on. + * + * The following characters are considered invalid: '"', '*', '<', '>' and= '?'. + */ +int ntfs_collate_names(const __le16 *name1, const u32 name1_len, + const __le16 *name2, const u32 name2_len, + const int err_val, const u32 ic, + const __le16 *upcase, const u32 upcase_len) +{ + u32 cnt, min_len; + u16 c1, c2; + + min_len =3D name1_len; + if (name1_len > name2_len) + min_len =3D name2_len; + for (cnt =3D 0; cnt < min_len; ++cnt) { + c1 =3D le16_to_cpu(*name1++); + c2 =3D le16_to_cpu(*name2++); + if (ic) { + if (c1 < upcase_len) + c1 =3D le16_to_cpu(upcase[c1]); + if (c2 < upcase_len) + c2 =3D le16_to_cpu(upcase[c2]); + } + if (c1 < 64 && legal_ansi_char_array[c1] & 8) + return err_val; + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + } + if (name1_len < name2_len) + return -1; + if (name1_len =3D=3D name2_len) + return 0; + /* name1_len > name2_len */ + c1 =3D le16_to_cpu(*name1); + if (c1 < 64 && legal_ansi_char_array[c1] & 8) + return err_val; + return 1; +} + +/** + * ntfs_ucsncmp - compare two little endian Unicode strings + * @s1: first string + * @s2: second string + * @n: maximum unicode characters to compare + * + * Compare the first @n characters of the Unicode strings @s1 and @s2, + * The strings in little endian format and appropriate le16_to_cpu() + * conversion is performed on non-little endian machines. + * + * The function returns an integer less than, equal to, or greater than ze= ro + * if @s1 (or the first @n Unicode characters thereof) is found, respectiv= ely, + * to be less than, to match, or be greater than @s2. + */ +int ntfs_ucsncmp(const __le16 *s1, const __le16 *s2, size_t n) +{ + u16 c1, c2; + size_t i; + + for (i =3D 0; i < n; ++i) { + c1 =3D le16_to_cpu(s1[i]); + c2 =3D le16_to_cpu(s2[i]); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + if (!c1) + break; + } + return 0; +} + +/** + * ntfs_ucsncasecmp - compare two little endian Unicode strings, ignoring = case + * @s1: first string + * @s2: second string + * @n: maximum unicode characters to compare + * @upcase: upcase table + * @upcase_size: upcase table size in Unicode characters + * + * Compare the first @n characters of the Unicode strings @s1 and @s2, + * ignoring case. The strings in little endian format and appropriate + * le16_to_cpu() conversion is performed on non-little endian machines. + * + * Each character is uppercased using the @upcase table before the compari= son. + * + * The function returns an integer less than, equal to, or greater than ze= ro + * if @s1 (or the first @n Unicode characters thereof) is found, respectiv= ely, + * to be less than, to match, or be greater than @s2. + */ +int ntfs_ucsncasecmp(const __le16 *s1, const __le16 *s2, size_t n, + const __le16 *upcase, const u32 upcase_size) +{ + size_t i; + u16 c1, c2; + + for (i =3D 0; i < n; ++i) { + c1 =3D le16_to_cpu(s1[i]); + if (c1 < upcase_size) + c1 =3D le16_to_cpu(upcase[c1]); + c2 =3D le16_to_cpu(s2[i]); + if (c2 < upcase_size) + c2 =3D le16_to_cpu(upcase[c2]); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + if (!c1) + break; + } + return 0; +} + +int ntfs_file_compare_values(const struct file_name_attr *file_name_attr1, + const struct file_name_attr *file_name_attr2, + const int err_val, const u32 ic, + const __le16 *upcase, const u32 upcase_len) +{ + return ntfs_collate_names((__le16 *)&file_name_attr1->file_name, + file_name_attr1->file_name_length, + (__le16 *)&file_name_attr2->file_name, + file_name_attr2->file_name_length, + err_val, ic, upcase, upcase_len); +} + +/** + * ntfs_nlstoucs - convert NLS string to little endian Unicode string + * + * Convert the input string @ins, which is in whatever format the loaded N= LS + * map dictates, into a little endian, 2-byte Unicode string. + * + * This function allocates the string and the caller is responsible for + * calling kmem_cache_free(ntfs_name_cache, *@outs); when finished with it. + * + * On success the function returns the number of Unicode characters writte= n to + * the output string *@outs (>=3D 0), not counting the terminating Unicode= NULL + * character. *@outs is set to the allocated output string buffer. + * + * On error, a negative number corresponding to the error code is returned= . In + * that case the output string is not allocated. Both *@outs and *@outs_len + * are then undefined. + * + * This might look a bit odd due to fast path optimization... + */ +int ntfs_nlstoucs(const struct ntfs_volume *vol, const char *ins, + const int ins_len, __le16 **outs, int max_name_len) +{ + struct nls_table *nls =3D vol->nls_map; + __le16 *ucs; + wchar_t wc; + int i, o, wc_len; + + /* We do not trust outside sources. */ + if (likely(ins)) { + if (max_name_len > NTFS_MAX_NAME_LEN) + ucs =3D kvmalloc((max_name_len + 2) * sizeof(__le16), + GFP_NOFS | __GFP_ZERO); + else + ucs =3D kmem_cache_alloc(ntfs_name_cache, GFP_NOFS); + if (likely(ucs)) { + if (vol->nls_utf8) { + o =3D utf8s_to_utf16s(ins, ins_len, + UTF16_LITTLE_ENDIAN, + ucs, + max_name_len + 2); + if (o < 0 || o > max_name_len) { + wc_len =3D o; + goto name_err; + } + } else { + for (i =3D o =3D 0; i < ins_len; i +=3D wc_len) { + wc_len =3D nls->char2uni(ins + i, ins_len - i, + &wc); + if (likely(wc_len >=3D 0 && + o < max_name_len)) { + if (likely(wc)) { + ucs[o++] =3D cpu_to_le16(wc); + continue; + } /* else if (!wc) */ + break; + } + + goto name_err; + } + } + ucs[o] =3D 0; + *outs =3D ucs; + return o; + } /* else if (!ucs) */ + ntfs_debug("Failed to allocate buffer for converted name from ntfs_name_= cache."); + return -ENOMEM; + } /* else if (!ins) */ + ntfs_error(vol->sb, "Received NULL pointer."); + return -EINVAL; +name_err: + if (max_name_len > NTFS_MAX_NAME_LEN) + kvfree(ucs); + else + kmem_cache_free(ntfs_name_cache, ucs); + if (wc_len < 0) { + ntfs_debug("Name using character set %s contains characters that cannot = be converted to Unicode.", + nls->charset); + i =3D -EILSEQ; + } else { + ntfs_debug("Name is too long (maximum length for a name on NTFS is %d Un= icode characters.", + max_name_len); + i =3D -ENAMETOOLONG; + } + return i; +} + +/** + * ntfs_ucstonls - convert little endian Unicode string to NLS string + * @vol: ntfs volume which we are working with + * @ins: input Unicode string buffer + * @ins_len: length of input string in Unicode characters + * @outs: on return contains the (allocated) output NLS string buffer + * @outs_len: length of output string buffer in bytes + * + * Convert the input little endian, 2-byte Unicode string @ins, of length + * @ins_len into the string format dictated by the loaded NLS. + * + * If *@outs is NULL, this function allocates the string and the caller is + * responsible for calling kfree(*@outs); when finished with it. In this c= ase + * @outs_len is ignored and can be 0. + * + * On success the function returns the number of bytes written to the outp= ut + * string *@outs (>=3D 0), not counting the terminating NULL byte. If the = output + * string buffer was allocated, *@outs is set to it. + * + * On error, a negative number corresponding to the error code is returned= . In + * that case the output string is not allocated. The contents of *@outs are + * then undefined. + * + * This might look a bit odd due to fast path optimization... + */ +int ntfs_ucstonls(const struct ntfs_volume *vol, const __le16 *ins, + const int ins_len, unsigned char **outs, int outs_len) +{ + struct nls_table *nls =3D vol->nls_map; + unsigned char *ns; + int i, o, ns_len, wc; + + /* We don't trust outside sources. */ + if (ins) { + ns =3D *outs; + ns_len =3D outs_len; + if (ns && !ns_len) { + wc =3D -ENAMETOOLONG; + goto conversion_err; + } + if (!ns) { + ns_len =3D ins_len * NLS_MAX_CHARSET_SIZE; + ns =3D kmalloc(ns_len + 1, GFP_NOFS); + if (!ns) + goto mem_err_out; + } + + if (vol->nls_utf8) { + o =3D utf16s_to_utf8s((const wchar_t *)ins, ins_len, + UTF16_LITTLE_ENDIAN, ns, ns_len); + if (o >=3D ns_len) { + wc =3D -ENAMETOOLONG; + goto conversion_err; + } + goto done; + } + + for (i =3D o =3D 0; i < ins_len; i++) { +retry: + wc =3D nls->uni2char(le16_to_cpu(ins[i]), ns + o, + ns_len - o); + if (wc > 0) { + o +=3D wc; + continue; + } else if (!wc) + break; + else if (wc =3D=3D -ENAMETOOLONG && ns !=3D *outs) { + unsigned char *tc; + /* Grow in multiples of 64 bytes. */ + tc =3D kmalloc((ns_len + 64) & + ~63, GFP_NOFS); + if (tc) { + memcpy(tc, ns, ns_len); + ns_len =3D ((ns_len + 64) & ~63) - 1; + kfree(ns); + ns =3D tc; + goto retry; + } /* No memory so goto conversion_error; */ + } /* wc < 0, real error. */ + goto conversion_err; + } +done: + ns[o] =3D 0; + *outs =3D ns; + return o; + } /* else (!ins) */ + ntfs_error(vol->sb, "Received NULL pointer."); + return -EINVAL; +conversion_err: + ntfs_error(vol->sb, + "Unicode name contains characters that cannot be converted to character = set %s. You might want to try to use the mount option nls=3Dutf8.", + nls->charset); + if (ns !=3D *outs) + kfree(ns); + if (wc !=3D -ENAMETOOLONG) + wc =3D -EILSEQ; + return wc; +mem_err_out: + ntfs_error(vol->sb, "Failed to allocate name!"); + return -ENOMEM; +} + +/** + * ntfs_ucsnlen - determine the length of a little endian Unicode string + * @s: pointer to Unicode string + * @maxlen: maximum length of string @s + * + * Return the number of Unicode characters in the little endian Unicode + * string @s up to a maximum of maxlen Unicode characters, not including + * the terminating (__le16)'\0'. If there is no (__le16)'\0' between @s + * and @s + @maxlen, @maxlen is returned. + * + * This function never looks beyond @s + @maxlen. + */ +static u32 ntfs_ucsnlen(const __le16 *s, u32 maxlen) +{ + u32 i; + + for (i =3D 0; i < maxlen; i++) { + if (!le16_to_cpu(s[i])) + break; + } + return i; +} + +/** + * ntfs_ucsndup - duplicate little endian Unicode string + * @s: pointer to Unicode string + * @maxlen: maximum length of string @s + * + * Return a pointer to a new little endian Unicode string which is a dupli= cate + * of the string s. Memory for the new string is obtained with ntfs_mallo= c(3), + * and can be freed with free(3). + * + * A maximum of @maxlen Unicode characters are copied and a terminating + * (__le16)'\0' little endian Unicode character is added. + * + * This function never looks beyond @s + @maxlen. + * + * Return a pointer to the new little endian Unicode string on success and= NULL + * on failure with errno set to the error code. + */ +__le16 *ntfs_ucsndup(const __le16 *s, u32 maxlen) +{ + __le16 *dst; + u32 len; + + len =3D ntfs_ucsnlen(s, maxlen); + dst =3D ntfs_malloc_nofs((len + 1) * sizeof(__le16)); + if (dst) { + memcpy(dst, s, len * sizeof(__le16)); + dst[len] =3D cpu_to_le16(L'\0'); + } + return dst; +} + +/** + * ntfs_names_are_equal - compare two Unicode names for equality + * @s1: name to compare to @s2 + * @s1_len: length in Unicode characters of @s1 + * @s2: name to compare to @s1 + * @s2_len: length in Unicode characters of @s2 + * @ic: ignore case bool + * @upcase: upcase table (only if @ic =3D=3D IGNORE_CASE) + * @upcase_size: length in Unicode characters of @upcase (if presen= t) + * + * Compare the names @s1 and @s2 and return TRUE (1) if the names are + * identical, or FALSE (0) if they are not identical. If @ic is IGNORE_CAS= E, + * the @upcase table is used to perform a case insensitive comparison. + */ +bool ntfs_names_are_equal(const __le16 *s1, size_t s1_len, + const __le16 *s2, size_t s2_len, + const u32 ic, + const __le16 *upcase, const u32 upcase_size) +{ + if (s1_len !=3D s2_len) + return false; + if (!s1_len) + return true; + if (ic =3D=3D CASE_SENSITIVE) + return ntfs_ucsncmp(s1, s2, s1_len) ? false : true; + return ntfs_ucsncasecmp(s1, s2, s1_len, upcase, upcase_size) ? false : tr= ue; +} diff --git a/fs/ntfsplus/upcase.c b/fs/ntfsplus/upcase.c new file mode 100644 index 000000000000..a2b8e56edeff --- /dev/null +++ b/fs/ntfsplus/upcase.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generate the full NTFS Unicode upcase table in little endian. + * Part of the Linux-NTFS project. + * + * Copyright (c) 2001 Richard Russon + * Copyright (c) 2001-2006 Anton Altaparmakov + */ + +#include "misc.h" +#include "ntfs.h" + +__le16 *generate_default_upcase(void) +{ + static const int uc_run_table[][3] =3D { /* Start, End, Add */ + {0x0061, 0x007B, -32}, {0x0451, 0x045D, -80}, {0x1F70, 0x1F72, 74}, + {0x00E0, 0x00F7, -32}, {0x045E, 0x0460, -80}, {0x1F72, 0x1F76, 86}, + {0x00F8, 0x00FF, -32}, {0x0561, 0x0587, -48}, {0x1F76, 0x1F78, 100}, + {0x0256, 0x0258, -205}, {0x1F00, 0x1F08, 8}, {0x1F78, 0x1F7A, 128}, + {0x028A, 0x028C, -217}, {0x1F10, 0x1F16, 8}, {0x1F7A, 0x1F7C, 112}, + {0x03AC, 0x03AD, -38}, {0x1F20, 0x1F28, 8}, {0x1F7C, 0x1F7E, 126}, + {0x03AD, 0x03B0, -37}, {0x1F30, 0x1F38, 8}, {0x1FB0, 0x1FB2, 8}, + {0x03B1, 0x03C2, -32}, {0x1F40, 0x1F46, 8}, {0x1FD0, 0x1FD2, 8}, + {0x03C2, 0x03C3, -31}, {0x1F51, 0x1F52, 8}, {0x1FE0, 0x1FE2, 8}, + {0x03C3, 0x03CC, -32}, {0x1F53, 0x1F54, 8}, {0x1FE5, 0x1FE6, 7}, + {0x03CC, 0x03CD, -64}, {0x1F55, 0x1F56, 8}, {0x2170, 0x2180, -16}, + {0x03CD, 0x03CF, -63}, {0x1F57, 0x1F58, 8}, {0x24D0, 0x24EA, -26}, + {0x0430, 0x0450, -32}, {0x1F60, 0x1F68, 8}, {0xFF41, 0xFF5B, -32}, + {0} + }; + + static const int uc_dup_table[][2] =3D { /* Start, End */ + {0x0100, 0x012F}, {0x01A0, 0x01A6}, {0x03E2, 0x03EF}, {0x04CB, 0x04CC}, + {0x0132, 0x0137}, {0x01B3, 0x01B7}, {0x0460, 0x0481}, {0x04D0, 0x04EB}, + {0x0139, 0x0149}, {0x01CD, 0x01DD}, {0x0490, 0x04BF}, {0x04EE, 0x04F5}, + {0x014A, 0x0178}, {0x01DE, 0x01EF}, {0x04BF, 0x04BF}, {0x04F8, 0x04F9}, + {0x0179, 0x017E}, {0x01F4, 0x01F5}, {0x04C1, 0x04C4}, {0x1E00, 0x1E95}, + {0x018B, 0x018B}, {0x01FA, 0x0218}, {0x04C7, 0x04C8}, {0x1EA0, 0x1EF9}, + {0} + }; + + static const int uc_word_table[][2] =3D { /* Offset, Value */ + {0x00FF, 0x0178}, {0x01AD, 0x01AC}, {0x01F3, 0x01F1}, {0x0269, 0x0196}, + {0x0183, 0x0182}, {0x01B0, 0x01AF}, {0x0253, 0x0181}, {0x026F, 0x019C}, + {0x0185, 0x0184}, {0x01B9, 0x01B8}, {0x0254, 0x0186}, {0x0272, 0x019D}, + {0x0188, 0x0187}, {0x01BD, 0x01BC}, {0x0259, 0x018F}, {0x0275, 0x019F}, + {0x018C, 0x018B}, {0x01C6, 0x01C4}, {0x025B, 0x0190}, {0x0283, 0x01A9}, + {0x0192, 0x0191}, {0x01C9, 0x01C7}, {0x0260, 0x0193}, {0x0288, 0x01AE}, + {0x0199, 0x0198}, {0x01CC, 0x01CA}, {0x0263, 0x0194}, {0x0292, 0x01B7}, + {0x01A8, 0x01A7}, {0x01DD, 0x018E}, {0x0268, 0x0197}, + {0} + }; + + int i, r; + __le16 *uc; + + uc =3D ntfs_malloc_nofs(default_upcase_len * sizeof(__le16)); + if (!uc) + return uc; + memset(uc, 0, default_upcase_len * sizeof(__le16)); + /* Generate the little endian Unicode upcase table used by ntfs. */ + for (i =3D 0; i < default_upcase_len; i++) + uc[i] =3D cpu_to_le16(i); + for (r =3D 0; uc_run_table[r][0]; r++) + for (i =3D uc_run_table[r][0]; i < uc_run_table[r][1]; i++) + le16_add_cpu(&uc[i], uc_run_table[r][2]); + for (r =3D 0; uc_dup_table[r][0]; r++) + for (i =3D uc_dup_table[r][0]; i < uc_dup_table[r][1]; i +=3D 2) + le16_add_cpu(&uc[i + 1], -1); + for (r =3D 0; uc_word_table[r][0]; r++) + uc[uc_word_table[r][0]] =3D cpu_to_le16(uc_word_table[r][1]); + return uc; +} --=20 2.25.1