From nobody Thu Oct 2 02:16:41 2025 Received: from sxb1plsmtpa01-03.prod.sxb1.secureserver.net (sxb1plsmtpa01-03.prod.sxb1.secureserver.net [92.204.81.39]) (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 5CAD930C357 for ; Tue, 23 Sep 2025 22:08:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.204.81.39 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758665340; cv=none; b=WBx9c7rbUSIo6PP8PUQnNw6C7urmrWztZNslRfrJrPF+2hXbKGzNKeJMt8/3YgaYFwZZ56AZUYSHPAzTVc+0NEJVbKV6gVayESQG6e++GFd3RW4Op1jamX2U+vg1QoO0pLfkBsbVUmodYQcOLexeZfo4BvSf66opkdekeabHras= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758665340; c=relaxed/simple; bh=u5fl5ICoaanZSbJ0nmGAh0TMt8jc4in7PCrNaO1/ufY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=AHEBk21fOwdOPSoBXjP5iWiLsa1hlXM1B01Fv8LW+lVvZ9JkjgZRgQqdKyTJPfe21NtwhysH5InXsSnDVF9VWGck9JyeMTTopxe59ZlHq/237X95N6b2orXayojtTANF2j0tiwi7XgSJ8OEjysPk9iwZun85dJ/0Dx+3LQSz9AA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=squashfs.org.uk; spf=pass smtp.mailfrom=squashfs.org.uk; arc=none smtp.client-ip=92.204.81.39 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=squashfs.org.uk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=squashfs.org.uk Received: from phoenix.fritz.box ([82.69.79.175]) by :SMTPAUTH: with ESMTPA id 1B9IvdJrlRKdg1B9Wvhvsx; Tue, 23 Sep 2025 15:06:27 -0700 X-CMAE-Analysis: v=2.4 cv=cL65cleN c=1 sm=1 tr=0 ts=68d319e4 a=84ok6UeoqCVsigPHarzEiQ==:117 a=84ok6UeoqCVsigPHarzEiQ==:17 a=FXvPX3liAAAA:8 a=_v5trmpiMvKuf-VRmTkA:9 a=UObqyxdv-6Yh2QiB9mM_:22 a=irvUdcZYBsSKPCvlqN3O:22 Feedback-ID: 7f792593433213f080771ca666aa9d6c:squashfs.org.uk:ssnet X-SECURESERVER-ACCT: phillip@squashfs.org.uk From: Phillip Lougher To: akpm@linux-foundation.org, linux-kernel@vger.kernel.org Cc: Phillip Lougher Subject: [PATCH 1/2] Squashfs: add additional inode sanity checking Date: Tue, 23 Sep 2025 23:06:51 +0100 Message-Id: <20250923220652.568416-2-phillip@squashfs.org.uk> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20250923220652.568416-1-phillip@squashfs.org.uk> References: <20250923220652.568416-1-phillip@squashfs.org.uk> 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-CMAE-Envelope: MS4xfOMQZJ0kxU0mMUfK66mmEu4M4QEIiXoUFSU419+CNP1y9AJv/tJV3V98tNNyY4OZCbqZUY71NBQkKkRrf/7dK5PMWEV7SotbMPxdbx7DKGv1A+YZU8Ka 7/3M3lfJUIcWca8lRa6YJaRVXb2kZTfygQarx2/Q7u/IypgYndO4U8EmOQ7GTD6J7VilMWGXBGWXwU9uxG/RIoRHQILjstb8GsWblFTgEoVd5frS6vYXB73+ 8PbdDb/k6zUtlMPD7o5y9YUoLlFnk9hg9vdIqoAIFWFk4Pp/8PdDyHimH9jWco1C Content-Type: text/plain; charset="utf-8" Add an additional sanity check when reading regular file inodes. A regular file if the file size is an exact multiple of the filesystem block size cannot have a fragment. This is because by definition a fragment block stores tailends which are not a whole block in size. Signed-off-by: Phillip Lougher --- fs/squashfs/inode.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c index d5918eba27e3..77eec1772998 100644 --- a/fs/squashfs/inode.c +++ b/fs/squashfs/inode.c @@ -140,8 +140,17 @@ int squashfs_read_inode(struct inode *inode, long long= ino) if (err < 0) goto failed_read; =20 + inode->i_size =3D le32_to_cpu(sqsh_ino->file_size); frag =3D le32_to_cpu(sqsh_ino->fragment); if (frag !=3D SQUASHFS_INVALID_FRAG) { + /* + * the file cannot have a fragment (tailend) and have a + * file size a multiple of the block size + */ + if ((inode->i_size & (msblk->block_size - 1)) =3D=3D 0) { + err =3D -EINVAL; + goto failed_read; + } frag_offset =3D le32_to_cpu(sqsh_ino->offset); frag_size =3D squashfs_frag_lookup(sb, frag, &frag_blk); if (frag_size < 0) { @@ -155,7 +164,6 @@ int squashfs_read_inode(struct inode *inode, long long = ino) } =20 set_nlink(inode, 1); - inode->i_size =3D le32_to_cpu(sqsh_ino->file_size); inode->i_fop =3D &generic_ro_fops; inode->i_mode |=3D S_IFREG; inode->i_blocks =3D ((inode->i_size - 1) >> 9) + 1; @@ -183,8 +191,17 @@ int squashfs_read_inode(struct inode *inode, long long= ino) if (err < 0) goto failed_read; =20 + inode->i_size =3D le64_to_cpu(sqsh_ino->file_size); frag =3D le32_to_cpu(sqsh_ino->fragment); if (frag !=3D SQUASHFS_INVALID_FRAG) { + /* + * the file cannot have a fragment (tailend) and have a + * file size a multiple of the block size + */ + if ((inode->i_size & (msblk->block_size - 1)) =3D=3D 0) { + err =3D -EINVAL; + goto failed_read; + } frag_offset =3D le32_to_cpu(sqsh_ino->offset); frag_size =3D squashfs_frag_lookup(sb, frag, &frag_blk); if (frag_size < 0) { @@ -199,7 +216,6 @@ int squashfs_read_inode(struct inode *inode, long long = ino) =20 xattr_id =3D le32_to_cpu(sqsh_ino->xattr); set_nlink(inode, le32_to_cpu(sqsh_ino->nlink)); - inode->i_size =3D le64_to_cpu(sqsh_ino->file_size); inode->i_op =3D &squashfs_inode_ops; inode->i_fop =3D &generic_ro_fops; inode->i_mode |=3D S_IFREG; --=20 2.39.5 From nobody Thu Oct 2 02:16:41 2025 Received: from sxb1plsmtpa01-03.prod.sxb1.secureserver.net (sxb1plsmtpa01-03.prod.sxb1.secureserver.net [188.121.53.28]) (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 5ADAC288C8B for ; Tue, 23 Sep 2025 22:14:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=188.121.53.28 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758665653; cv=none; b=SbPgM0hZRKG7YACUHDxKk2kUUwb315fn0DER7igD2R9O3L7oWFO56GaI+vBMc9kVI8AH0KjeM6A/DP0ZQtrlhykBOa+Y3PoXzFWG9Hw81oM3hX6NEIKdq0gWtfJEOSs7VWUlRha+sGeMv7mvP+6CF0JCjqXYEZGGG543oLjnVaQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758665653; c=relaxed/simple; bh=WUP0gLZZhYvaPyUD0UMhst8lpJlJkhpbcn69k5v/iSo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Lu8OmMWDcIrbqXpFDdfpJuUSsnLgf+hOCqUdzz9/bfgNDj7Q2qKwwad3HF3Cg3OY45OThWLAfoni7dLEasvA46GITH9wBy5ae9BcHEodCRQPUNrYieGNtmIYHHxw8WKHZQdaCPh9F/D7ESuOjCVpHDJQ7hiwED/Hc2fFy9KYEgI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=squashfs.org.uk; spf=pass smtp.mailfrom=squashfs.org.uk; arc=none smtp.client-ip=188.121.53.28 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=squashfs.org.uk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=squashfs.org.uk Received: from phoenix.fritz.box ([82.69.79.175]) by :SMTPAUTH: with ESMTPA id 1B9IvdJrlRKdg1B9Yvhvt4; Tue, 23 Sep 2025 15:06:29 -0700 X-CMAE-Analysis: v=2.4 cv=cL65cleN c=1 sm=1 tr=0 ts=68d319e6 a=84ok6UeoqCVsigPHarzEiQ==:117 a=84ok6UeoqCVsigPHarzEiQ==:17 a=FXvPX3liAAAA:8 a=PmvtE5NTWYoDmZ2mIecA:9 a=UObqyxdv-6Yh2QiB9mM_:22 a=irvUdcZYBsSKPCvlqN3O:22 Feedback-ID: e4ba3dcc65f267fca773178bb1f8c56e:squashfs.org.uk:ssnet X-SECURESERVER-ACCT: phillip@squashfs.org.uk From: Phillip Lougher To: akpm@linux-foundation.org, linux-kernel@vger.kernel.org Cc: Phillip Lougher Subject: [PATCH 2/2] Squashfs: add SEEK_DATA/SEEK_HOLE support Date: Tue, 23 Sep 2025 23:06:52 +0100 Message-Id: <20250923220652.568416-3-phillip@squashfs.org.uk> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20250923220652.568416-1-phillip@squashfs.org.uk> References: <20250923220652.568416-1-phillip@squashfs.org.uk> 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-CMAE-Envelope: MS4xfFzgAGs/sxXFVE9lZdAnY+CuxBiOxHex/bXKVsLtYWDr7c7WHCSJKyDlLmE5fkACUJFckEwMpEQf5UnePwsUiyRaaFN3sXNJFKVWNcNw8sj/W71So3Mq 5xy6L+8P6uXLJpLBqmx2hiyxe75EZDhngl6CbpL4STnrrO6vjjqEyUvThlRlmE7wIFDEbh6+jfI5I3Ew5GZzpCxOpFdUNIoeFK+DaLGejcXDsBjt19hCDL6r rjYBlJoi5N8UY80ywA9L5bQRvmD72FiLVwh9RIdNGSyY25xEIypExPIzzy5OOmqs Content-Type: text/plain; charset="utf-8" Add support for SEEK_DATA and SEEK_HOLE lseek() whence values. These allow much faster searches for holes and data in sparse files, which can significantly speed up file copying, e.g. before (GNU coreutils, Debian 13): cp --sparse=3Dalways big-file / took real 11m58s, user 0m5.764s, sys 11m48s after: real 0.047s, user 0.000s, sys 0.027s Where big-file has a 256 GB hole followed by 47 KB of data. Signed-off-by: Phillip Lougher --- fs/squashfs/file.c | 137 +++++++++++++++++++++++++++++++++++--- fs/squashfs/inode.c | 4 +- fs/squashfs/squashfs.h | 1 + fs/squashfs/squashfs_fs.h | 1 + 4 files changed, 130 insertions(+), 13 deletions(-) diff --git a/fs/squashfs/file.c b/fs/squashfs/file.c index ce7d661d5ad8..1582e0637a7e 100644 --- a/fs/squashfs/file.c +++ b/fs/squashfs/file.c @@ -307,7 +307,8 @@ static int fill_meta_index(struct inode *inode, int ind= ex, all_done: *index_block =3D cur_index_block; *index_offset =3D cur_offset; - *data_block =3D cur_data_block; + if (data_block) + *data_block =3D cur_data_block; =20 /* * Scale cache index (cache slot entry) to index @@ -324,17 +325,15 @@ static int fill_meta_index(struct inode *inode, int i= ndex, * Get the on-disk location and compressed size of the datablock * specified by index. Fill_meta_index() does most of the work. */ -static int read_blocklist(struct inode *inode, int index, u64 *block) +static int read_blocklist_ptrs(struct inode *inode, int index, u64 *start, + int *offset, u64 *block) { - u64 start; long long blks; - int offset; __le32 size; - int res =3D fill_meta_index(inode, index, &start, &offset, block); + int res =3D fill_meta_index(inode, index, start, offset, block); =20 - TRACE("read_blocklist: res %d, index %d, start 0x%llx, offset" - " 0x%x, block 0x%llx\n", res, index, start, offset, - *block); + TRACE("read_blocklist: res %d, index %d, start 0x%llx, offset 0x%x, block= 0x%llx\n", + res, index, *start, *offset, block ? *block : 0); =20 if (res < 0) return res; @@ -346,22 +345,31 @@ static int read_blocklist(struct inode *inode, int in= dex, u64 *block) * extra block indexes needed. */ if (res < index) { - blks =3D read_indexes(inode->i_sb, index - res, &start, &offset); + blks =3D read_indexes(inode->i_sb, index - res, start, offset); if (blks < 0) return (int) blks; - *block +=3D blks; + if (block) + *block +=3D blks; } =20 /* * Read length of block specified by index. */ - res =3D squashfs_read_metadata(inode->i_sb, &size, &start, &offset, + res =3D squashfs_read_metadata(inode->i_sb, &size, start, offset, sizeof(size)); if (res < 0) return res; return squashfs_block_size(size); } =20 +static inline int read_blocklist(struct inode *inode, int index, u64 *bloc= k) +{ + u64 start; + int offset; + + return read_blocklist_ptrs(inode, index, &start, &offset, block); +} + static bool squashfs_fill_page(struct folio *folio, struct squashfs_cache_entry *buffer, size_t offset, size_t avail) @@ -658,7 +666,114 @@ static void squashfs_readahead(struct readahead_contr= ol *ractl) kfree(pages); } =20 +static loff_t seek_hole_data(struct file *file, loff_t offset, int whence) +{ + struct inode *inode =3D file->f_mapping->host; + struct super_block *sb =3D inode->i_sb; + struct squashfs_sb_info *msblk =3D sb->s_fs_info; + u64 start, index =3D offset >> msblk->block_log; + u64 file_end =3D (i_size_read(inode) + msblk->block_size - 1) >> msblk->b= lock_log; + int s_offset, length; + __le32 *blist =3D NULL; + + /* reject offset if negative or beyond file end */ + if ((unsigned long long)offset >=3D i_size_read(inode)) + return -ENXIO; + + /* is offset within tailend and is tailend packed into a fragment? */ + if (index + 1 =3D=3D file_end && + squashfs_i(inode)->fragment_block !=3D SQUASHFS_INVALID_BLK) { + if (whence =3D=3D SEEK_DATA) + return offset; + + /* there is an implicit hole at the end of any file */ + return i_size_read(inode); + } + + length =3D read_blocklist_ptrs(inode, index, &start, &s_offset, NULL); + if (length < 0) + return length; + + /* nothing more to do if offset matches desired whence value */ + if ((length =3D=3D 0 && whence =3D=3D SEEK_HOLE) || + (length && whence =3D=3D SEEK_DATA)) + return offset; + + /* skip scanning forwards if we're at file end */ + if (++ index =3D=3D file_end) + goto not_found; + + blist =3D kmalloc(SQUASHFS_SCAN_INDEXES << 2, GFP_KERNEL); + if (blist =3D=3D NULL) { + ERROR("%s: Failed to allocate block_list\n", __func__); + return -ENOMEM; + } + + while (index < file_end) { + int i, indexes =3D min(file_end - index, SQUASHFS_SCAN_INDEXES); + + offset =3D squashfs_read_metadata(sb, blist, &start, &s_offset, indexes = << 2); + if (offset < 0) + goto finished; + + for (i =3D 0; i < indexes; i++) { + length =3D squashfs_block_size(blist[i]); + if (length < 0) { + offset =3D length; + goto finished; + } + + /* does this block match desired whence value? */ + if ((length =3D=3D 0 && whence =3D=3D SEEK_HOLE) || + (length && whence =3D=3D SEEK_DATA)) { + offset =3D (index + i) << msblk->block_log; + goto finished; + } + } + + index +=3D indexes; + } + +not_found: + /* whence value determines what happens */ + if (whence =3D=3D SEEK_DATA) + offset =3D -ENXIO; + else + /* there is an implicit hole at the end of any file */ + offset =3D i_size_read(inode); + +finished: + kfree(blist); + return offset; +} + +static loff_t squashfs_llseek(struct file *file, loff_t offset, int whence) +{ + struct inode *inode =3D file->f_mapping->host; + + switch (whence) { + default: + return generic_file_llseek(file, offset, whence); + case SEEK_DATA: + case SEEK_HOLE: + offset =3D seek_hole_data(file, offset, whence); + break; + } + + if (offset < 0) + return offset; + + return vfs_setpos(file, offset, inode->i_sb->s_maxbytes); +} + const struct address_space_operations squashfs_aops =3D { .read_folio =3D squashfs_read_folio, .readahead =3D squashfs_readahead }; + +const struct file_operations squashfs_file_operations =3D { + .llseek =3D squashfs_llseek, + .read_iter =3D generic_file_read_iter, + .mmap_prepare =3D generic_file_readonly_mmap_prepare, + .splice_read =3D filemap_splice_read +}; diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c index 77eec1772998..5920a33e44a1 100644 --- a/fs/squashfs/inode.c +++ b/fs/squashfs/inode.c @@ -164,7 +164,7 @@ int squashfs_read_inode(struct inode *inode, long long = ino) } =20 set_nlink(inode, 1); - inode->i_fop =3D &generic_ro_fops; + inode->i_fop =3D &squashfs_file_operations; inode->i_mode |=3D S_IFREG; inode->i_blocks =3D ((inode->i_size - 1) >> 9) + 1; squashfs_i(inode)->fragment_block =3D frag_blk; @@ -217,7 +217,7 @@ int squashfs_read_inode(struct inode *inode, long long = ino) xattr_id =3D le32_to_cpu(sqsh_ino->xattr); set_nlink(inode, le32_to_cpu(sqsh_ino->nlink)); inode->i_op =3D &squashfs_inode_ops; - inode->i_fop =3D &generic_ro_fops; + inode->i_fop =3D &squashfs_file_operations; inode->i_mode |=3D S_IFREG; inode->i_blocks =3D (inode->i_size - le64_to_cpu(sqsh_ino->sparse) + 511) >> 9; diff --git a/fs/squashfs/squashfs.h b/fs/squashfs/squashfs.h index 218868b20f16..4851bd964502 100644 --- a/fs/squashfs/squashfs.h +++ b/fs/squashfs/squashfs.h @@ -107,6 +107,7 @@ extern const struct address_space_operations squashfs_a= ops; =20 /* inode.c */ extern const struct inode_operations squashfs_inode_ops; +extern const struct file_operations squashfs_file_operations; =20 /* namei.c */ extern const struct inode_operations squashfs_dir_inode_ops; diff --git a/fs/squashfs/squashfs_fs.h b/fs/squashfs/squashfs_fs.h index 95f8e8901768..a955d9369749 100644 --- a/fs/squashfs/squashfs_fs.h +++ b/fs/squashfs/squashfs_fs.h @@ -208,6 +208,7 @@ static inline int squashfs_block_size(__le32 raw) #define SQUASHFS_META_INDEXES (SQUASHFS_METADATA_SIZE / sizeof(unsigned in= t)) #define SQUASHFS_META_ENTRIES 127 #define SQUASHFS_META_SLOTS 8 +#define SQUASHFS_SCAN_INDEXES 1024 =20 struct meta_entry { u64 data_block; --=20 2.39.5