diff --git a/include/Makefile b/include/Makefile --- a/include/Makefile +++ b/include/Makefile @@ -55,7 +55,7 @@ dev/ofw dev/pbio dev/pci ${_dev_powermac_nvram} dev/ppbus dev/pwm \ dev/smbus dev/speaker dev/tcp_log dev/veriexec dev/vkbd dev/wg \ fs/devfs fs/fdescfs fs/msdosfs fs/nfs fs/nullfs \ - fs/procfs fs/smbfs fs/udf fs/unionfs \ + fs/procfs fs/smbfs fs/squashfs fs/udf fs/unionfs \ geom/cache geom/concat geom/eli geom/gate geom/journal geom/label \ geom/mirror geom/mountver geom/multipath geom/nop \ geom/raid geom/raid3 geom/shsec geom/stripe geom/union geom/virstor \ diff --git a/share/man/man4/squashfs.4 b/share/man/man4/squashfs.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/squashfs.4 @@ -0,0 +1,63 @@ +.\"- +.\" Copyright (c) 2025 Chuck Tuffli +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd June 12, 2025 +.Dt SQUASHFS 4 +.Os +.Sh NAME +.Nm squashfs +.Nd squash filesystem +.Sh SYNOPSIS +.Cd "options SQUASHFS" +.Pp +In +.Xr loader.conf 5 : +.Cd squashfs_load="YES" +.Sh DESCRIPTION +The +.Nm +driver provides support for mounting and accessing SquashFS compressed +file system images. +SquashFS is a compressed read-only file system designed for general +read-only file system use and in constrained block device/memory systems. +Use a program like +.Sy mksquashfs +or +.Sy gensquashfs +to create a suitable image. +.Sh EXAMPLES +Mount the SquashFS image +.Pa image.sqfs : +.PP +.Dl mount -t squashfs image.sqfs /mnt +.Ed +.Sh SEE ALSO +.Xr gzip 1 , +.Xr xz 1 , +.Xr zstd 1 , +.Xr mount 8 , +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was developed by +.An Raghav Sharma Aq Mt raghav@FreeBSD.org +and enhanced by +.An Kyle Evans Aq Mt kevans@FreeBSD.org +as a Google Summer of Code project. +.Pp +This manual page was written by +.An Chuck Tuffli Aq Mt chuck@FreeBSD.org +.Sh BUGS +The squashfs format allows several compression formats the driver does +not support including +.Bl -bullet -compact -width indent +.It +LZMA +.It +LZO +.It +LZ4 +.El diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -3639,6 +3639,13 @@ fs/tmpfs/tmpfs_fifoops.c optional tmpfs fs/tmpfs/tmpfs_vfsops.c optional tmpfs fs/tmpfs/tmpfs_subr.c optional tmpfs +fs/squashfs/squashfs_vfsops.c optional squashfs +fs/squashfs/squashfs_vnops.c optional squashfs +fs/squashfs/squashfs_decompressor.c optional squashfs +fs/squashfs/squashfs_block.c optional squashfs +fs/squashfs/squashfs_init.c optional squashfs +fs/squashfs/squashfs_inode.c optional squashfs +fs/squashfs/squashfs_io.c optional squashfs gdb/gdb_cons.c optional gdb gdb/gdb_main.c optional gdb gdb/gdb_packet.c optional gdb diff --git a/sys/conf/options b/sys/conf/options --- a/sys/conf/options +++ b/sys/conf/options @@ -277,6 +277,7 @@ PROCFS opt_dontuse.h PSEUDOFS opt_dontuse.h SMBFS opt_dontuse.h +SQUASHFS opt_dontuse.h TARFS opt_dontuse.h TMPFS opt_dontuse.h UDF opt_dontuse.h @@ -287,6 +288,9 @@ # Pseudofs debugging PSEUDOFS_TRACE opt_pseudofs.h +# squashfs debugging +SQUASHFS_DEBUG opt_squashfs.h + # Tarfs debugging TARFS_DEBUG opt_tarfs.h diff --git a/sys/fs/squashfs/squashfs.h b/sys/fs/squashfs/squashfs.h new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs.h @@ -0,0 +1,358 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef SQUASHFS_H +#define SQUASHFS_H + +#include + +#ifdef _KERNEL +#define SQUASHFS_MALLOC(sz, type, flags) malloc(sz, type, flags) +#define SQUASHFS_FREE(obj, type) free(obj, type) +#else +#define SQUASHFS_MALLOC(sz, type, flags) malloc(sz) +#define SQUASHFS_FREE(obj, type) free(obj) +#endif /* _KERNEL */ + +#define SQUASHFS_MAGIC 0x73717368 +#define SQUASHFS_MAGIC_SWAP 0x68737173 + +#define SQUASHFS_CACHED_FRAGMENTS 3 +#define SQUASHFS_MAJOR 4 +#define SQUASHFS_MINOR 0 +#define SQUASHFS_START 0 + +#define SQUASHFS_METADATA_SIZE 8192 +#define SQUASHFS_METADATA_LOG 13 + +#define SQUASHFS_FILE_SIZE 131072 +#define SQUASHFS_FILE_LOG 17 + +#define SQUASHFS_FILE_MAX_SIZE 1048576 +#define SQUASHFS_FILE_MAX_LOG 20 + +#define SQUASHFS_IDS 65536 + +#define SQUASHFS_MAX_NAME_LEN 256 + +#define SQUASHFS_INVALID_FRAG (0xffffffffU) +#define SQUASHFS_INVALID_XATTR (0xffffffffU) +#define SQUASHFS_INVALID_BLK ((int64_t)-1) + +#define SQUASHFS_COOKIE_DOT 0 +#define SQUASHFS_COOKIE_DOTDOT 1 +#define SQUASHFS_COOKIE_EOF ~((off_t)1 << (sizeof(off_t) * 8 - 1)) + +#define SQUASHFS_TYPE_MIN_VALID 1 +#define SQUASHFS_TYPE_MAX_VALID 14 +#define SQUASHFS_INODE_MIN_COUNT 1 + +/* Filesystem flags */ +#define SQUASHFS_NOI 0 +#define SQUASHFS_NOD 1 +#define SQUASHFS_NOF 3 +#define SQUASHFS_NO_FRAG 4 +#define SQUASHFS_ALWAYS_FRAG 5 +#define SQUASHFS_DUPLICATE 6 +#define SQUASHFS_EXPORT 7 +#define SQUASHFS_COMP_OPT 10 + +/* Max number of types and file types */ +#define SQUASHFS_DIR_TYPE 1 +#define SQUASHFS_REG_TYPE 2 +#define SQUASHFS_SYMLINK_TYPE 3 +#define SQUASHFS_BLKDEV_TYPE 4 +#define SQUASHFS_CHRDEV_TYPE 5 +#define SQUASHFS_FIFO_TYPE 6 +#define SQUASHFS_SOCKET_TYPE 7 +#define SQUASHFS_LDIR_TYPE 8 +#define SQUASHFS_LREG_TYPE 9 +#define SQUASHFS_LSYMLINK_TYPE 10 +#define SQUASHFS_LBLKDEV_TYPE 11 +#define SQUASHFS_LCHRDEV_TYPE 12 +#define SQUASHFS_LFIFO_TYPE 13 +#define SQUASHFS_LSOCKET_TYPE 14 + +#define SQUASHFS_COMPRESSED_BIT (1 << 15) +#define SQUASHFS_COMPRESSED_BIT_BLOCK (1 << 24) + +/* cached data constants for filesystem */ +#define SQUASHFS_CACHED_BLKS 8 + +#define SQUASHFS_MAX_FILE_SIZE_LOG 64 + +#define SQUASHFS_MAX_FILE_SIZE (1LL << (SQUASHFS_MAX_FILE_SIZE_LOG - 2)) + +/* meta index cache */ +#define SQUASHFS_META_INDEXES (SQUASHFS_METADATA_SIZE / sizeof(unsigned int)) +#define SQUASHFS_META_ENTRIES 127 +#define SQUASHFS_META_SLOTS 8 + +/* definitions for structures on disk */ +#define ZLIB_COMPRESSION 1 +#define LZMA_COMPRESSION 2 +#define LZO_COMPRESSION 3 +#define XZ_COMPRESSION 4 +#define LZ4_COMPRESSION 5 +#define ZSTD_COMPRESSION 6 + +/* Xattr types */ +#define SQUASHFS_XATTR_USER 0 +#define SQUASHFS_XATTR_TRUSTED 1 +#define SQUASHFS_XATTR_SECURITY 2 +#define SQUASHFS_XATTR_VALUE_OOL 256 +#define SQUASHFS_XATTR_PREFIX_MASK 0xff +#define SQFS_XATTR_PREFIX_MAX SQUASHFS_XATTR_SECURITY + +struct sqsh_sb { + uint32_t s_magic; + uint32_t inodes; + uint32_t mkfs_time; + uint32_t block_size; + uint32_t fragments; + uint16_t compression; + uint16_t block_log; + uint16_t flags; + uint16_t no_ids; + uint16_t s_major; + uint16_t s_minor; + uint64_t root_inode; + uint64_t bytes_used; + uint64_t id_table_start; + uint64_t xattr_id_table_start; + uint64_t inode_table_start; + uint64_t directory_table_start; + uint64_t fragment_table_start; + uint64_t lookup_table_start; +}; + +struct sqsh_dir_index { + uint32_t index; + uint32_t start_block; + uint32_t size; +}; + +struct sqsh_base_inode { + uint16_t inode_type; + uint16_t mode; + uint16_t uid; + uint16_t guid; + uint32_t mtime; + uint32_t inode_number; +}; + +struct sqsh_ipc_inode { + uint16_t inode_type; + uint16_t mode; + uint16_t uid; + uint16_t guid; + uint32_t mtime; + uint32_t inode_number; + uint32_t nlink; +}; + +struct sqsh_lipc_inode { + uint16_t inode_type; + uint16_t mode; + uint16_t uid; + uint16_t guid; + uint32_t mtime; + uint32_t inode_number; + uint32_t nlink; + uint32_t xattr; +}; + +struct sqsh_dev_inode { + uint16_t inode_type; + uint16_t mode; + uint16_t uid; + uint16_t guid; + uint32_t mtime; + uint32_t inode_number; + uint32_t nlink; + uint32_t rdev; +}; + +struct sqsh_ldev_inode { + uint16_t inode_type; + uint16_t mode; + uint16_t uid; + uint16_t guid; + uint32_t mtime; + uint32_t inode_number; + uint32_t nlink; + uint32_t rdev; + uint32_t xattr; +}; + +struct sqsh_symlink_inode { + uint16_t inode_type; + uint16_t mode; + uint16_t uid; + uint16_t guid; + uint32_t mtime; + uint32_t inode_number; + uint32_t nlink; + uint32_t symlink_size; +}; + +struct sqsh_reg_inode { + uint16_t inode_type; + uint16_t mode; + uint16_t uid; + uint16_t guid; + uint32_t mtime; + uint32_t inode_number; + uint32_t start_block; + uint32_t fragment; + uint32_t offset; + uint32_t file_size; +}; + +struct sqsh_lreg_inode { + uint16_t inode_type; + uint16_t mode; + uint16_t uid; + uint16_t guid; + uint32_t mtime; + uint32_t inode_number; + uint64_t start_block; + uint64_t file_size; + uint64_t sparse; + uint32_t nlink; + uint32_t fragment; + uint32_t offset; + uint32_t xattr; +}; + +struct sqsh_dir_inode { + uint16_t inode_type; + uint16_t mode; + uint16_t uid; + uint16_t guid; + uint32_t mtime; + uint32_t inode_number; + uint32_t start_block; + uint32_t nlink; + uint16_t file_size; + uint16_t offset; + uint32_t parent_inode; +}; + +struct sqsh_ldir_inode { + uint16_t inode_type; + uint16_t mode; + uint16_t uid; + uint16_t guid; + uint32_t mtime; + uint32_t inode_number; + uint32_t nlink; + uint32_t file_size; + uint32_t start_block; + uint32_t parent_inode; + uint16_t i_count; + uint16_t offset; + uint32_t xattr; +}; + +struct squashfs_dir_entry { + uint16_t offset; + uint16_t inode_number; + uint16_t type; + uint16_t size; +}; + +struct sqsh_dir_header { + uint32_t count; + uint32_t start_block; + uint32_t inode_number; +}; + +struct sqsh_fragment_entry { + uint64_t start_block; + uint32_t size; + uint32_t unused; +}; + +struct sqsh_xattr_entry { + uint16_t type; + uint16_t size; +}; + +struct sqsh_xattr_val { + uint32_t vsize; +}; + +struct sqsh_xattr_id { + uint64_t xattr; + uint32_t count; + uint32_t size; +}; + +struct sqsh_xattr_id_table { + uint64_t xattr_table_start; + uint32_t xattr_ids; + uint32_t unused; +}; + +/* This struct handles tables which contains inodes, inode ID and fragments */ +struct sqsh_table { + size_t each; + uint64_t *blocks; +}; + +/* This overlays the fid structure (see mount.h) */ +struct sqsh_fid { + uint16_t len; + uint16_t pad; + ino_t ino; + uint32_t gen; +}; + +extern struct vop_vector squashfs_vnodeops; + +#ifdef SQUASHFS_DEBUG +#define TRACE(x...) printf("\n\033[0;34msquashfs:\33[0m " x) +#else /* !SQUASHFS_DEBUG */ +#define TRACE(x...) +#endif /* SQUASHFS_DEBUG */ +#define ERROR(x...) printf("\n\033[0;31msquashfs:\33[0m " x) + +typedef enum { + SQFS_OK, /* everything fine */ + SQFS_BADFORMAT, /* unsupported file format */ + SQFS_BADVERSION, /* unsupported squashfs version */ + SQFS_BADCOMP, /* unsupported compression method */ + SQFS_UNSUP, /* unsupported feature */ + SQFS_END_OF_DIRECTORY, /* reached end of directory */ + SQFS_ERR /* error in operation */ +} sqsh_err; + +#endif /* SQUASHFS_H */ diff --git a/sys/fs/squashfs/squashfs_block.h b/sys/fs/squashfs/squashfs_block.h new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_block.h @@ -0,0 +1,80 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef SQUASHFS_BLOCK_H +#define SQUASHFS_BLOCK_H + +struct sqsh_block { + size_t size; + void *data; +}; + +struct sqsh_block_run { + off_t block; + size_t offset; +}; + +struct sqsh_dir { + struct sqsh_block_run cur; + off_t offset; + off_t total; + struct sqsh_dir_header header; +}; + +struct sqsh_dir_entry { + uint64_t inode_id; + uint32_t inode_number; + char name[100]; + size_t name_size; + off_t offset; + off_t next_offset; +}; + +/* Helper functions to check if metadata/data block is compressed and its size + */ +void sqsh_metadata_header(uint16_t hdr, bool *compressed, uint16_t *size); +void sqsh_data_header(uint32_t hdr, bool *compressed, uint32_t *size); + +sqsh_err sqsh_block_read(struct sqsh_mount *ump, off_t pos, bool compressed, + uint32_t size, size_t outsize, struct sqsh_block **block); +void sqsh_free_block(struct sqsh_block *block); + +sqsh_err sqsh_metadata_read(struct sqsh_mount *ump, off_t pos, + size_t *data_size, struct sqsh_block **block); +sqsh_err sqsh_data_read(struct sqsh_mount *ump, off_t pos, uint32_t hdr, + struct sqsh_block **block); + +sqsh_err sqsh_metadata_get(struct sqsh_mount *ump, struct sqsh_block_run *cur, + void *buf, size_t size); + +/* Number of groups of size "group" required to hold size "total" */ +size_t sqsh_ceil(uint64_t total, size_t group); + +#endif \ No newline at end of file diff --git a/sys/fs/squashfs/squashfs_block.c b/sys/fs/squashfs/squashfs_block.c new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_block.c @@ -0,0 +1,239 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include "opt_squashfs.h" + +#include + +#include +#ifdef _KERNEL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else /* !_KERNEL */ +#include +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _KERNEL +static MALLOC_DEFINE(M_SQUASHFSBLOCK, "SQUASHFS block", + "SQUASHFS block structure"); +static MALLOC_DEFINE(M_SQUASHFSBLOCKD, "SQUASHFS block data", + "SQUASHFS block data buffer"); +#endif + +void +sqsh_metadata_header(uint16_t hdr, bool *compressed, uint16_t *size) +{ + /* Bit is set if block is uncompressed */ + *compressed = !(hdr & SQUASHFS_COMPRESSED_BIT); + *size = hdr & ~SQUASHFS_COMPRESSED_BIT; + if (!*size) + *size = SQUASHFS_COMPRESSED_BIT; +} + +void +sqsh_data_header(uint32_t hdr, bool *compressed, uint32_t *size) +{ + *compressed = !(hdr & SQUASHFS_COMPRESSED_BIT_BLOCK); + *size = hdr & ~SQUASHFS_COMPRESSED_BIT_BLOCK; +} + +sqsh_err +sqsh_block_read(struct sqsh_mount *ump, off_t pos, bool compressed, + uint32_t size, size_t outsize, struct sqsh_block **block) +{ + sqsh_err err; + /* allocate block on heap */ + *block = SQUASHFS_MALLOC(sizeof(**block), M_SQUASHFSBLOCK, M_WAITOK); + if (*block == NULL) + return (SQFS_ERR); + (*block)->data = SQUASHFS_MALLOC(size, M_SQUASHFSBLOCKD, M_WAITOK); + if ((*block)->data == NULL) + goto error; + + if (sqsh_io_read_buf(ump, (*block)->data, pos, size) != size) { + ERROR("Failed to read data, I/O error"); + goto error; + } + + /* if block is compressed, first decompressed it and then initialize + * block */ + if (compressed) { + char *decomp = SQUASHFS_MALLOC(outsize, M_SQUASHFSBLOCKD, + M_WAITOK); + if (decomp == NULL) + goto error; + + err = ump->decompressor->decompressor((*block)->data, size, + decomp, &outsize); + if (err != SQFS_OK) { + SQUASHFS_FREE(decomp, M_SQUASHFSBLOCKD); + goto error; + } + SQUASHFS_FREE((*block)->data, M_SQUASHFSBLOCKD); + (*block)->data = decomp; + (*block)->size = outsize; + } else { + (*block)->size = size; + } + + return (SQFS_OK); + +error: + sqsh_free_block(*block); + *block = NULL; + return (SQFS_ERR); +} + +void +sqsh_free_block(struct sqsh_block *block) +{ + SQUASHFS_FREE(block->data, M_SQUASHFSBLOCKD); + SQUASHFS_FREE(block, M_SQUASHFSBLOCK); +} + +sqsh_err +sqsh_metadata_read(struct sqsh_mount *ump, off_t pos, size_t *data_size, + struct sqsh_block **block) +{ + uint16_t hdr; + bool compressed; + uint16_t size; + sqsh_err err; + + *data_size = 0; + + if (sqsh_io_read_buf(ump, &hdr, pos, sizeof(hdr)) != sizeof(hdr)) { + ERROR("Failed to read data, I/O error"); + return (SQFS_ERR); + } + + pos += sizeof(hdr); + *data_size += sizeof(hdr); + hdr = le16toh(hdr); + + sqsh_metadata_header(hdr, &compressed, &size); + + err = sqsh_block_read(ump, pos, compressed, size, + SQUASHFS_METADATA_SIZE, block); + *data_size += size; + + return (err); +} + +sqsh_err +sqsh_data_read(struct sqsh_mount *ump, off_t pos, uint32_t hdr, + struct sqsh_block **block) +{ + bool compressed; + uint32_t size; + sqsh_err err; + + sqsh_data_header(hdr, &compressed, &size); + err = sqsh_block_read(ump, pos, compressed, size, ump->sb.block_size, + block); + + return (err); +} + +sqsh_err +sqsh_metadata_get(struct sqsh_mount *ump, struct sqsh_block_run *cur, void *buf, + size_t size) +{ + off_t pos; + + pos = cur->block; + while (size > 0) { + struct sqsh_block *block; + size_t take; + size_t data_size; + sqsh_err err; + + data_size = 0; + err = sqsh_metadata_read(ump, pos, &data_size, &block); + if (err != SQFS_OK) + return (err); + + pos += data_size; + + take = block->size - cur->offset; + if (take > size) + take = size; + if (buf) + memcpy(buf, (char *)block->data + cur->offset, take); + + if (buf) + buf = (char *)buf + take; + size -= take; + cur->offset += take; + if (cur->offset == block->size) { + cur->block = pos; + cur->offset = 0; + } + + /* Free block since currently we have no cache */ + sqsh_free_block(block); + } + return (SQFS_OK); +} + +/* This is a normal ceil function */ +size_t +sqsh_ceil(uint64_t total, size_t group) +{ + size_t ans; + + ans = (size_t)(total / group); + if (total % group) + ans += 1; + + return (ans); +} diff --git a/sys/fs/squashfs/squashfs_decompressor.h b/sys/fs/squashfs/squashfs_decompressor.h new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_decompressor.h @@ -0,0 +1,44 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef SQUASHFS_DECOMPRESSOR_H +#define SQUASHFS_DECOMPRESSOR_H + +struct sqsh_decompressor { + sqsh_err (*decompressor)(void *input, size_t input_size, void *output, + size_t *output_size); + + int id; + char *name; +}; + +const struct sqsh_decompressor *sqsh_lookup_decompressor(int id); + +#endif /* SQUASHFS_DECOMPRESSOR_H */ diff --git a/sys/fs/squashfs/squashfs_decompressor.c b/sys/fs/squashfs/squashfs_decompressor.c new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_decompressor.c @@ -0,0 +1,189 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include "opt_squashfs.h" + +#ifdef _KERNEL +#include "opt_gzio.h" +#include "opt_zstdio.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef GZIO +#include +#endif +#include +#ifdef ZSTDIO +#define ZSTD_STATIC_LINKING_ONLY +#include +#endif + +/* Support for zlib compressor */ +#ifdef GZIO +static sqsh_err +zlib_decompressor(void *input, size_t input_size, void *output, + size_t *output_size) +{ + uLongf zout; + int zerr; + + zout = *output_size; + zerr = uncompress((Bytef *)output, &zout, input, input_size); + if (zerr != Z_OK) + return (SQFS_ERR); + *output_size = zout; + return (SQFS_OK); +} +#endif /* GZIO */ + +#ifdef _KERNEL +static sqsh_err +xz_decompressor(void *input, size_t input_size, void *output, + size_t *output_size) +{ + static struct xz_dec *sqsh_xz_dec; + struct xz_buf xzb; + enum xz_ret ret; + + /* XXX FIXME: Need per-mount */ + if (sqsh_xz_dec == NULL) { + sqsh_xz_dec = xz_dec_init(XZ_SINGLE, 0); + if (sqsh_xz_dec == NULL) + return (SQFS_ERR); + } + + xzb.in = input; + xzb.in_pos = 0; + xzb.in_size = input_size; + + xzb.out = output; + xzb.out_pos = 0; + xzb.out_size = *output_size; + + ret = xz_dec_run(sqsh_xz_dec, &xzb); + if (ret != XZ_STREAM_END) + return (SQFS_ERR); + + *output_size = xzb.out_size; + return (SQFS_OK); +} +#endif + +#ifdef ZSTDIO +static sqsh_err +zstd_decompressor(void *input, size_t input_size, void *output, + size_t *output_size) +{ + size_t zstdout; + + zstdout = ZSTD_decompress(output, *output_size, input, input_size); + if (ZSTD_isError(zstdout)) + return (SQFS_ERR); + *output_size = zstdout; + return (SQFS_OK); +} +#endif /* ZSTDIO */ + +static const struct sqsh_decompressor decompressors[] = { + { +#ifdef GZIO + .decompressor = zlib_decompressor, +#endif + .id = ZLIB_COMPRESSION, + .name = "zlib", + }, + { + .decompressor = NULL, + .id = LZMA_COMPRESSION, + .name = "lzma", + }, + { + .decompressor = NULL, + .id = LZO_COMPRESSION, + .name = "lzo", + }, + { +#ifdef _KERNEL + .decompressor = xz_decompressor, +#endif + .id = XZ_COMPRESSION, + .name = "xz", + }, + { + .decompressor = NULL, + .id = LZ4_COMPRESSION, + .name = "lz4", + }, + { +#ifdef ZSTDIO + .decompressor = zstd_decompressor, +#endif + .id = ZSTD_COMPRESSION, + .name = "zstd", + }, +}; + +const struct sqsh_decompressor * +sqsh_lookup_decompressor(int id) +{ + const struct sqsh_decompressor *decom; + + for (size_t i = 0; i < nitems(decompressors); i++) { + decom = &decompressors[i]; + + if (id == decom->id) + return (decom); + } + + return (NULL); +} diff --git a/sys/fs/squashfs/squashfs_dir.h b/sys/fs/squashfs/squashfs_dir.h new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_dir.h @@ -0,0 +1,61 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef SQUASHFS_DIR_H +#define SQUASHFS_DIR_H + +/* Helper for sqsh_dir_lookup */ +struct sqsh_dir_ff_name_t { + const char *cmp; + size_t cmplen; + char *name; +}; + +/* Initialise directory from inode */ +sqsh_err sqsh_dir_init(struct sqsh_mount *ump, struct sqsh_inode *inode, + struct sqsh_dir *dir); + +/* Directory indexing helper functions */ +sqsh_err sqsh_dir_f_header(struct sqsh_mount *ump, struct sqsh_block_run *cur, + struct sqsh_dir_index *idx, bool *stop, void *arg); +sqsh_err sqsh_dir_ff_header(struct sqsh_mount *ump, struct sqsh_inode *inode, + struct sqsh_dir *dir, void *arg); + +sqsh_err sqsh_dir_metadata_read(struct sqsh_mount *mnt, struct sqsh_dir *dir, + void *buf, size_t size); + +/* Directory traverse helper functions for vnops readdir and lookup */ +sqsh_err sqsh_dir_getnext(struct sqsh_mount *ump, struct sqsh_dir *dir, + struct sqsh_dir_entry *entry); +sqsh_err sqsh_dir_lookup(struct sqsh_mount *ump, struct sqsh_inode *inode, + const char *name, size_t namelen, struct sqsh_dir_entry *entry, + bool *found); + +#endif /* SQUASHFS_DIR_H */ \ No newline at end of file diff --git a/sys/fs/squashfs/squashfs_dir.c b/sys/fs/squashfs/squashfs_dir.c new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_dir.c @@ -0,0 +1,266 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include "opt_squashfs.h" + +#include +#ifdef _KERNEL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +void swapendian_dir_header(struct sqsh_dir_header *hdr); +void swapendian_dir_index(struct sqsh_dir_index *idx); +void swapendian_dir_entry(struct squashfs_dir_entry *entry); + +sqsh_err +sqsh_dir_init(struct sqsh_mount *ump, struct sqsh_inode *inode, + struct sqsh_dir *dir) +{ + memset(dir, 0, sizeof(*dir)); + dir->cur.block = inode->xtra.dir.start_block + + ump->sb.directory_table_start; + dir->cur.offset = inode->xtra.dir.offset; + dir->offset = 0; + + /* + * For better compression '.' and '..' entries + * are not there in squashfs but Inode entires + * does keep directory count including them. + * Here we are just chekcing for that and updating + * dir count accordingly. + */ + dir->total = inode->size <= 3 ? 0 : inode->size - 3; + + return SQFS_OK; +} + +sqsh_err +sqsh_dir_f_header(struct sqsh_mount *ump, struct sqsh_block_run *cur, + struct sqsh_dir_index *idx, bool *stop, void *arg) +{ + off_t offset = *(off_t *)arg; + + if (idx->index >= offset) { + *stop = true; + return SQFS_OK; + } + + return sqsh_metadata_get(ump, cur, NULL, idx->size + 1); +} + +sqsh_err +sqsh_dir_ff_header(struct sqsh_mount *ump, struct sqsh_inode *inode, + struct sqsh_dir *dir, void *arg) +{ + struct sqsh_dir_index idx; + struct sqsh_block_run cur; + size_t count; + + cur = inode->next; + count = inode->xtra.dir.idx_count; + + if (count == 0) + return SQFS_OK; + + while (count--) { + sqsh_err err; + bool stop; + + stop = false; + + err = sqsh_metadata_get(ump, &cur, &idx, sizeof(idx)); + + if (err != SQFS_OK) + return err; + + swapendian_dir_index(&idx); + + err = sqsh_dir_f_header(ump, &cur, &idx, &stop, arg); + + if (err != SQFS_OK) + return err; + if (stop) + break; + + dir->cur.block = idx.start_block + + ump->sb.directory_table_start; + dir->offset = idx.index; + } + + dir->cur.offset = (dir->cur.offset + dir->offset) % + SQUASHFS_METADATA_SIZE; + return SQFS_OK; +} + +sqsh_err +sqsh_dir_metadata_read(struct sqsh_mount *ump, struct sqsh_dir *dir, void *buf, + size_t size) +{ + dir->offset += size; + return sqsh_metadata_get(ump, &dir->cur, buf, size); +} + +sqsh_err +sqsh_dir_getnext(struct sqsh_mount *ump, struct sqsh_dir *dir, + struct sqsh_dir_entry *entry) +{ + struct squashfs_dir_entry e; + sqsh_err err; + + entry->offset = dir->offset; + + while (dir->header.count == 0) { + if (dir->offset >= dir->total) { + err = SQFS_END_OF_DIRECTORY; + return err; + } + + err = sqsh_dir_metadata_read(ump, dir, &dir->header, + sizeof(dir->header)); + + if (err != SQFS_OK) + return err; + swapendian_dir_header(&dir->header); + ++(dir->header.count); + } + + err = sqsh_dir_metadata_read(ump, dir, &e, sizeof(e)); + + if (err != SQFS_OK) + return err; + swapendian_dir_entry(&e); + --(dir->header.count); + + /* Initialise new entry fields */ + entry->name_size = e.size + 1; + entry->inode_id = ((uint64_t)dir->header.start_block << 16) + e.offset; + entry->inode_number = dir->header.inode_number + + (int16_t)e.inode_number; + + err = sqsh_dir_metadata_read(ump, dir, entry->name, entry->name_size); + if (err != SQFS_OK) + return err; + + entry->next_offset = dir->offset; + + return SQFS_OK; +} + +sqsh_err +sqsh_dir_lookup(struct sqsh_mount *ump, struct sqsh_inode *inode, + const char *name, size_t namelen, struct sqsh_dir_entry *entry, bool *found) +{ + sqsh_err err; + struct sqsh_dir dir; + struct sqsh_dir_ff_name_t arg; + + *found = false; + + err = sqsh_dir_init(ump, inode, &dir); + + if (err != SQFS_OK) + return err; + + /* Fast forward to header */ + arg.cmp = name; + arg.cmplen = namelen; + arg.name = entry->name; + + err = sqsh_dir_ff_header(ump, inode, &dir, &arg); + if (err != SQFS_OK) + return err; + + /* Iterate to find the right entry */ + while (sqsh_dir_getnext(ump, &dir, entry) == SQFS_OK) { + int order; + + if (entry->name_size != namelen) + continue; + + order = strncmp(entry->name, name, namelen); + if (order == 0 && entry->name_size == namelen) { + *found = true; + break; + } + } + + return SQFS_OK; +} + +void +swapendian_dir_header(struct sqsh_dir_header *hdr) +{ + hdr->count = le32toh(hdr->count); + hdr->start_block = le32toh(hdr->start_block); + hdr->inode_number = le32toh(hdr->inode_number); +} + +void +swapendian_dir_index(struct sqsh_dir_index *idx) +{ + idx->index = le32toh(idx->index); + idx->start_block = le32toh(idx->start_block); + idx->size = le32toh(idx->size); +} + +void +swapendian_dir_entry(struct squashfs_dir_entry *entry) +{ + entry->offset = le16toh(entry->offset); + entry->inode_number = le16toh(entry->inode_number); + entry->type = le16toh(entry->type); + entry->size = le16toh(entry->size); +} diff --git a/sys/fs/squashfs/squashfs_file.h b/sys/fs/squashfs/squashfs_file.h new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_file.h @@ -0,0 +1,56 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef SQUASHFS_FILE_H +#define SQUASHFS_FILE_H + +struct sqsh_blocklist { + struct sqsh_mount *ump; + size_t remain; + struct sqsh_block_run cur; + bool started; + uint64_t pos; + uint64_t block; + uint32_t header; + uint32_t input_size; +}; + +struct sqsh_blockidx_entry { + uint64_t data_block; + uint32_t md_block; +}; + +/* sqsh_blocklist helper functions */ +size_t sqsh_blocklist_count(struct sqsh_mount *ump, struct sqsh_inode *inode); + +sqsh_err sqsh_read_file(struct sqsh_mount *ump, struct sqsh_inode *inode, + off_t start, off_t *size, struct uio *uiop); + +#endif diff --git a/sys/fs/squashfs/squashfs_file.c b/sys/fs/squashfs/squashfs_file.c new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_file.c @@ -0,0 +1,361 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include "opt_squashfs.h" + +#include +#ifdef _KERNEL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include + +#include +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _KERNEL +static MALLOC_DEFINE(M_SQSHBLKIDX, "Sqsh Blk idx", "Squashfs block index"); +#endif + +static void +swapendian_fragment_entry(struct sqsh_fragment_entry *temp) +{ + temp->start_block = le64toh(temp->start_block); + temp->size = le32toh(temp->size); + temp->unused = le32toh(temp->unused); +} + +size_t +sqsh_blocklist_count(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + uint64_t size = inode->size; + size_t block = ump->sb.block_size; + if (inode->xtra.reg.frag_idx == SQUASHFS_INVALID_FRAG) { + return sqsh_ceil(size, block); + } else { + return (size_t)(size / block); + } +} + +static void +sqsh_blocklist_init(struct sqsh_mount *ump, struct sqsh_inode *inode, + struct sqsh_blocklist *bl) +{ + bl->ump = ump; + bl->remain = sqsh_blocklist_count(ump, inode); + bl->cur = inode->next; + bl->started = false; + bl->pos = 0; + bl->block = inode->xtra.reg.start_block; + bl->input_size = 0; +} + +static sqsh_err +sqsh_blocklist_next(struct sqsh_blocklist *bl) +{ + sqsh_err err; + bool compressed; + + if (bl->remain == 0) + return SQFS_ERR; + --(bl->remain); + + err = sqsh_metadata_get(bl->ump, &bl->cur, &bl->header, + sizeof(bl->header)); + if (err != SQFS_OK) + return err; + bl->header = le32toh(bl->header); + bl->block += bl->input_size; + sqsh_data_header(bl->header, &compressed, &bl->input_size); + + if (bl->started) + bl->pos += bl->ump->sb.block_size; + bl->started = true; + + return SQFS_OK; +} + +static bool +sqsh_blockidx_indexable(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + size_t blocks = sqsh_blocklist_count(ump, inode); + size_t md_size = blocks * sizeof(uint32_t); + return md_size >= SQUASHFS_METADATA_SIZE; +} + +static sqsh_err +sqsh_blockidx_add(struct sqsh_mount *ump, struct sqsh_inode *inode, + struct sqsh_blockidx_entry **out) +{ + size_t blocks; + size_t md_size; + size_t count; + + struct sqsh_blockidx_entry *blockidx; + struct sqsh_blocklist bl; + + size_t i; + bool first; + + i = 0; + first = true; + *out = NULL; + + blocks = sqsh_blocklist_count(ump, inode); + md_size = blocks * sizeof(uint32_t); + count = (inode->next.offset + md_size - 1) / SQUASHFS_METADATA_SIZE; + blockidx = SQUASHFS_MALLOC(count * sizeof(struct sqsh_blockidx_entry), + M_SQSHBLKIDX, M_WAITOK | M_ZERO); + if (blockidx == NULL) + return SQFS_ERR; + + sqsh_blocklist_init(ump, inode, &bl); + while (bl.remain && i < count) { + sqsh_err err; + /* skip the first metadata block since its stored in inode */ + if (bl.cur.offset < sizeof(uint32_t) && !first) { + blockidx[i].data_block = bl.block + bl.input_size; + blockidx[i++].md_block = (uint32_t)(bl.cur.block - + ump->sb.inode_table_start); + } + first = false; + + err = sqsh_blocklist_next(&bl); + if (err != SQFS_OK) { + SQUASHFS_FREE(blockidx, M_SQSHBLKIDX); + return SQFS_ERR; + } + } + + *out = blockidx; + return SQFS_OK; +} + +static sqsh_err +sqsh_blockidx_blocklist(struct sqsh_mount *ump, struct sqsh_inode *inode, + struct sqsh_blocklist *bl, off_t start) +{ + size_t block, metablock, skipped; + struct sqsh_blockidx_entry *blockidx, *blockpos; + sqsh_err err; + + sqsh_blocklist_init(ump, inode, bl); + block = (size_t)(start / ump->sb.block_size); + /* fragment */ + if (block > bl->remain) { + bl->remain = 0; + return SQFS_OK; + } + + /* Total blocks we need to skip */ + metablock = (bl->cur.offset + block * sizeof(uint32_t)) / + SQUASHFS_METADATA_SIZE; + if (metablock == 0) + return SQFS_OK; + if (!sqsh_blockidx_indexable(ump, inode)) + return SQFS_OK; + + err = sqsh_blockidx_add(ump, inode, &blockidx); + if (err != SQFS_OK) { + return err; + } + + skipped = (metablock * SQUASHFS_METADATA_SIZE / sizeof(uint32_t)) - + (bl->cur.offset / sizeof(uint32_t)); + + blockpos = blockidx + (metablock - 1); + bl->cur.block = blockpos->md_block + ump->sb.inode_table_start; + bl->cur.offset %= sizeof(uint32_t); + bl->remain -= skipped; + bl->pos = (uint64_t)skipped * ump->sb.block_size; + bl->block = blockpos->data_block; + + /* free blockidx */ + SQUASHFS_FREE(blockidx, M_SQSHBLKIDX); + + return SQFS_OK; +} + +static sqsh_err +sqsh_frag_entry(struct sqsh_mount *ump, struct sqsh_fragment_entry *frag, + uint32_t idx) +{ + sqsh_err err; + + if (idx == SQUASHFS_INVALID_FRAG) + return SQFS_ERR; + + err = sqsh_get_table(&ump->frag_table, ump, idx, frag); + swapendian_fragment_entry(frag); + return err; +} + +static sqsh_err +sqsh_frag_block(struct sqsh_mount *ump, struct sqsh_inode *inode, + size_t *offset, size_t *size, struct sqsh_block **block) +{ + struct sqsh_fragment_entry frag; + sqsh_err err; + +#ifdef _KERNEL + if (inode->type != VREG) + return SQFS_ERR; +#endif + + err = sqsh_frag_entry(ump, &frag, inode->xtra.reg.frag_idx); + if (err != SQFS_OK) + return err; + + err = sqsh_data_read(ump, frag.start_block, frag.size, block); + if (err != SQFS_OK) + return SQFS_ERR; + + *offset = inode->xtra.reg.frag_off; + *size = inode->size % ump->sb.block_size; + + return (err); +} + +sqsh_err +sqsh_read_file(struct sqsh_mount *ump, struct sqsh_inode *inode, off_t start, + off_t *size, struct uio *uiop) +{ + sqsh_err err; + off_t file_size; + size_t block_size; + struct sqsh_blocklist bl; + size_t read_off; + off_t data_read; + int error; + + data_read = 0; + file_size = inode->size; + block_size = ump->sb.block_size; + + if (*size < 0 || start > file_size) + return SQFS_ERR; + if (start == file_size) { + *size = 0; + return SQFS_OK; + } + + err = sqsh_blockidx_blocklist(ump, inode, &bl, start); + if (err != SQFS_OK) + return err; + + read_off = start % block_size; + while (*size > 0) { + struct sqsh_block *block; + size_t data_off, data_size; + size_t take; + bool fragment; + + block = NULL; + fragment = (bl.remain == 0); + + if (fragment) { + if (inode->xtra.reg.frag_idx == SQUASHFS_INVALID_FRAG) + break; + err = sqsh_frag_block(ump, inode, &data_off, &data_size, + &block); + if (err != SQFS_OK) + return err; + } else { + err = sqsh_blocklist_next(&bl); + if (err != SQFS_OK) + return err; + if (bl.pos + block_size <= start) + continue; + + data_off = 0; + if (bl.input_size == 0) { + data_size = (size_t)(file_size - bl.pos); + if (data_size > block_size) + data_size = block_size; + } else { + err = sqsh_data_read(ump, bl.block, bl.header, + &block); + if (err != SQFS_OK) + return err; + data_size = block->size; + } + } + + take = data_size - read_off; + if (take > *size) + take = (size_t)(*size); + if (block != NULL) { + error = uiomove((char *)block->data + data_off + + read_off, + take, uiop); + if (error != 0) + return SQFS_ERR; + /* free the allocated block since we have no cache now + */ + sqsh_free_block(block); + } else { + error = uiomove(__DECONST(void *, zero_region), take, + uiop); + if (error != 0) + return SQFS_ERR; + } + read_off = 0; + *size -= take; + data_read += take; + + if (fragment) + break; + } + + return data_read ? SQFS_OK : SQFS_ERR; +} diff --git a/sys/fs/squashfs/squashfs_init.c b/sys/fs/squashfs/squashfs_init.c new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_init.c @@ -0,0 +1,222 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opt_squashfs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +static void +squashfs_swapendian_sb(struct sqsh_sb *sb) +{ + sb->s_magic = le32toh(sb->s_magic); + sb->inodes = le32toh(sb->inodes); + sb->mkfs_time = le32toh(sb->mkfs_time); + sb->block_size = le32toh(sb->block_size); + sb->fragments = le32toh(sb->fragments); + sb->compression = le16toh(sb->compression); + sb->block_log = le16toh(sb->block_log); + sb->flags = le16toh(sb->flags); + sb->no_ids = le16toh(sb->no_ids); + sb->s_major = le16toh(sb->s_major); + sb->s_minor = le16toh(sb->s_minor); + sb->root_inode = le64toh(sb->root_inode); + sb->bytes_used = le64toh(sb->bytes_used); + sb->id_table_start = le64toh(sb->id_table_start); + sb->xattr_id_table_start = le64toh(sb->xattr_id_table_start); + sb->inode_table_start = le64toh(sb->inode_table_start); + sb->directory_table_start = le64toh(sb->directory_table_start); + sb->fragment_table_start = le64toh(sb->fragment_table_start); + sb->lookup_table_start = le64toh(sb->lookup_table_start); +} + +static sqsh_err +is_valid_superblock(struct sqsh_sb *sb) +{ + /* Check magic number */ + if (sb->s_magic != SQUASHFS_MAGIC && + sb->s_magic != SQUASHFS_MAGIC_SWAP) { +#ifdef _KERNEL + ERROR("Bad superblock magic number"); +#endif + return (SQFS_BADFORMAT); + } + + /* Check for version of mounted fs */ + if (sb->s_major != SQUASHFS_MAJOR || sb->s_minor > SQUASHFS_MINOR) { +#ifdef _KERNEL + ERROR("Unsupported version of squashfs is mounted"); +#endif + return (SQFS_BADVERSION); + } + + /* Check if filesystem size is not negative for sanity */ + if (sb->bytes_used < 0) { + ERROR("Filesystem size is negative!"); + return (SQFS_ERR); + } + + /* Check block size for sanity */ + if (sb->block_size > SQUASHFS_FILE_MAX_SIZE) { + ERROR("Invalid block size"); + return (SQFS_ERR); + } + + /* Check block log for sanity */ + if (sb->block_log > SQUASHFS_FILE_MAX_LOG) { + ERROR("Invalid block log"); + return (SQFS_ERR); + } + + /* Check that block_size and block_log match */ + if (sb->block_size != (1 << sb->block_log)) { + ERROR("Block size and log mismatch"); + return (SQFS_ERR); + } + + /* Check the root inode for sanity */ + if (SQUASHFS_INODE_OFFSET(sb->root_inode) > SQUASHFS_METADATA_SIZE) { + ERROR("Invalid root inode size"); + return (SQFS_ERR); + } + + /* A valid superblock is detected */ + TRACE("A valid superblock is detected"); + return (SQFS_OK); +} + +sqsh_err +squashfs_init(struct sqsh_mount *ump) +{ + sqsh_err error; + + /* squashfs superblock is at offset zero */ + if (sqsh_io_read_buf(ump, &ump->sb, 0, sizeof(struct sqsh_sb)) != + sizeof(struct sqsh_sb)) { +#ifdef _KERNEL + ERROR("Failed to read superblock, I/O error"); +#endif + return (SQFS_ERR); + } + squashfs_swapendian_sb(&ump->sb); + + /* check superblock to see if everything is fine */ + error = is_valid_superblock(&ump->sb); + if (error != SQFS_OK) + return (error); + + /* Init decompressor for squashfs and check if it is unknown or + * supported? */ + ump->decompressor = sqsh_lookup_decompressor(ump->sb.compression); + if (ump->decompressor == NULL) { + ERROR("Filesystem compression type not found"); + return (SQFS_BADCOMP); + } else if (ump->decompressor->decompressor == NULL) { + ERROR( + "Filesystem uses \"%s\" compression, which is not included in this kernel.", + ump->decompressor->name); + return (SQFS_BADCOMP); + } + + error = sqsh_init_table(&ump->id_table, ump, ump->sb.id_table_start, + sizeof(uint32_t), ump->sb.no_ids); + if (error != SQFS_OK) + goto id_table_fail; + + error = sqsh_init_table(&ump->frag_table, ump, + ump->sb.fragment_table_start, sizeof(struct sqsh_fragment_entry), + ump->sb.fragments); + if (error != SQFS_OK) + goto frag_table_fail; + + if (sqsh_export_ok(ump)) { + error = sqsh_init_table(&ump->export_table, ump, + ump->sb.lookup_table_start, sizeof(uint64_t), + ump->sb.inodes); + if (error != SQFS_OK) + goto export_table_fail; + } + + error = sqsh_init_xattr(ump); + if (error != SQFS_OK) + goto xattrs_fail; + + TRACE("Table init() passed!"); + + /* Everything fine */ + return (SQFS_OK); + +id_table_fail: + sqsh_free_table(&ump->id_table); + return (error); + +frag_table_fail: + sqsh_free_table(&ump->id_table); + sqsh_free_table(&ump->frag_table); + return (error); + +export_table_fail: + sqsh_free_table(&ump->id_table); + sqsh_free_table(&ump->frag_table); + sqsh_free_table(&ump->export_table); + return (error); +xattrs_fail: + sqsh_free_table(&ump->id_table); + sqsh_free_table(&ump->frag_table); + sqsh_free_table(&ump->export_table); + sqsh_free_table(&ump->xattr_table); + return (error); +} diff --git a/sys/fs/squashfs/squashfs_inode.h b/sys/fs/squashfs/squashfs_inode.h new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_inode.h @@ -0,0 +1,107 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef SQUASHFS_INODE_H +#define SQUASHFS_INODE_H + +#include + +#define SQUASHFS_INODE_OFFSET(A) ((unsigned int)((A) & 0xffff)) + +struct sqsh_inode { + struct sqsh_base_inode base; + int nlink; + uint32_t xattr; + size_t size; + +#ifdef _KERNEL + __enum_uint8(vtype) type; +#else + int type; +#endif + + struct sqsh_block_run next; + + union { + struct { + int major; + int minor; + } dev; + struct { + uint64_t start_block; + uint64_t sparse; + uint32_t frag_idx; + uint32_t frag_off; + } reg; + struct { + uint32_t start_block; + uint16_t offset; + uint16_t idx_count; + uint32_t parent_inode; + struct sqsh_dir d; + struct sqsh_dir_entry entry; + } dir; + } xtra; + + uint64_t ino_id; + uint64_t parent_id; + struct vnode *vnode; + struct sqsh_mount *ump; +}; + +/* helper functions to query on table */ +sqsh_err sqsh_init_table(struct sqsh_table *table, struct sqsh_mount *ump, + off_t start, size_t each, size_t count); +void sqsh_free_table(struct sqsh_table *table); +sqsh_err sqsh_get_table(struct sqsh_table *table, struct sqsh_mount *ump, + size_t idx, void *buf); + +/* helper functions to query on inode */ +void sqsh_metadata_run_inode(struct sqsh_block_run *cur, uint64_t id, + off_t base); +sqsh_err sqsh_get_inode(struct sqsh_mount *ump, struct sqsh_inode *inode, + uint64_t id); + +#ifdef _KERNEL +__enum_uint8(vtype) + sqsh_inode_type_from_id(struct sqsh_mount *ump, uint64_t inode_id); +#else +int sqsh_inode_type_from_id(struct sqsh_mount *ump, uint64_t inode_id); +#endif + +sqsh_err sqsh_get_inode_id(struct sqsh_mount *ump, uint16_t idx, uint32_t *id); + +bool sqsh_export_ok(struct sqsh_mount *ump); +sqsh_err sqsh_export_inode(struct sqsh_mount *ump, uint32_t n, uint64_t *i); + +uint64_t sqsh_root_inode(struct sqsh_mount *ump); +sqsh_err sqsh_verify_inode(struct sqsh_mount *ump, struct sqsh_inode *inode); + +#endif diff --git a/sys/fs/squashfs/squashfs_inode.c b/sys/fs/squashfs/squashfs_inode.c new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_inode.c @@ -0,0 +1,677 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include "opt_squashfs.h" + +#include +#ifdef _KERNEL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +/* Swapendian functions for all types of inodes */ +void swapendian_base_inode(struct sqsh_base_inode *temp); +void swapendian_reg_inode(struct sqsh_reg_inode *temp); +void swapendian_lreg_inode(struct sqsh_lreg_inode *temp); +void swapendian_dir_inode(struct sqsh_dir_inode *temp); +void swapendian_ldir_inode(struct sqsh_ldir_inode *temp); + +/* init functions for all types of inodes */ +sqsh_err sqsh_init_reg_inode(struct sqsh_mount *ump, struct sqsh_inode *inode); +sqsh_err sqsh_init_lreg_inode(struct sqsh_mount *ump, struct sqsh_inode *inode); +sqsh_err sqsh_init_dir_inode(struct sqsh_mount *ump, struct sqsh_inode *inode); +sqsh_err sqsh_init_ldir_inode(struct sqsh_mount *ump, struct sqsh_inode *inode); +sqsh_err sqsh_init_symlink_inode(struct sqsh_mount *ump, + struct sqsh_inode *inode); +sqsh_err sqsh_init_dev_inode(struct sqsh_mount *ump, struct sqsh_inode *inode); +sqsh_err sqsh_init_ldev_inode(struct sqsh_mount *ump, struct sqsh_inode *inode); +sqsh_err sqsh_init_ipc_inode(struct sqsh_mount *ump, struct sqsh_inode *inode); +sqsh_err sqsh_init_lipc_inode(struct sqsh_mount *ump, struct sqsh_inode *inode); + +#ifdef _KERNEL +static MALLOC_DEFINE(M_SQUASHFSTABLEBLK, "SQUASHFS tab blk", + "SQUASHFS table block"); +#endif + +sqsh_err +sqsh_init_table(struct sqsh_table *table, struct sqsh_mount *ump, off_t start, + size_t each, size_t count) +{ + size_t i; + size_t nblocks, bread; + + if (count == 0) + return (SQFS_OK); + + nblocks = sqsh_ceil(each * count, SQUASHFS_METADATA_SIZE); + bread = nblocks * sizeof(uint64_t); + + table->each = each; + table->blocks = SQUASHFS_MALLOC(bread, M_SQUASHFSTABLEBLK, M_WAITOK); + if (table->blocks == NULL) + return (SQFS_ERR); + + if (sqsh_io_read_buf(ump, table->blocks, start, bread) != bread) { + ERROR("Failed to read data, I/O error"); + sqsh_free_table(table); + return (SQFS_ERR); + } + + /* SwapEndian data to host */ + for (i = 0; i < nblocks; ++i) + table->blocks[i] = le64toh(table->blocks[i]); + + return (SQFS_OK); +} + +void +sqsh_free_table(struct sqsh_table *table) +{ + SQUASHFS_FREE(table->blocks, M_SQUASHFSTABLEBLK); + table->blocks = NULL; +} + +sqsh_err +sqsh_get_table(struct sqsh_table *table, struct sqsh_mount *ump, size_t idx, + void *buf) +{ + struct sqsh_block *block; + size_t pos, bnum, off, data_size; + off_t bpos; + + pos = idx * table->each; + bnum = pos / SQUASHFS_METADATA_SIZE; + off = pos % SQUASHFS_METADATA_SIZE; + + bpos = table->blocks[bnum]; + data_size = 0; + if (sqsh_metadata_read(ump, bpos, &data_size, &block)) + return (SQFS_ERR); + + memcpy(buf, (char *)(block->data) + off, table->each); + /* Free block since currently we have no cache */ + sqsh_free_block(block); + return (SQFS_OK); +} + +bool +sqsh_export_ok(struct sqsh_mount *ump) +{ + return (ump->sb.lookup_table_start != SQUASHFS_INVALID_BLK); +} + +void +sqsh_metadata_run_inode(struct sqsh_block_run *cur, uint64_t id, off_t base) +{ + cur->block = (id >> 16) + base; + cur->offset = id & 0xffff; +} + +#ifdef _KERNEL +static __enum_uint8(vtype) +#else +static int +#endif + sqsh_inode_type(int inode_type) +{ + switch (inode_type) { + case SQUASHFS_DIR_TYPE: + case SQUASHFS_LDIR_TYPE: +#ifdef _KERNEL + return (VDIR); +#else + return (DT_DIR); +#endif + case SQUASHFS_REG_TYPE: + case SQUASHFS_LREG_TYPE: +#ifdef _KERNEL + return (VREG); +#else + return (DT_REG); +#endif + case SQUASHFS_SYMLINK_TYPE: + case SQUASHFS_LSYMLINK_TYPE: +#ifdef _KERNEL + return (VLNK); +#else + return (DT_LNK); +#endif + case SQUASHFS_BLKDEV_TYPE: + case SQUASHFS_LBLKDEV_TYPE: +#ifdef _KERNEL + return (VBLK); +#else + return (DT_BLK); +#endif + case SQUASHFS_CHRDEV_TYPE: + case SQUASHFS_LCHRDEV_TYPE: +#ifdef _KERNEL + return (VCHR); +#else + return (DT_CHR); +#endif + case SQUASHFS_FIFO_TYPE: + case SQUASHFS_LFIFO_TYPE: +#ifdef _KERNEL + return (VFIFO); +#else + return (DT_FIFO); +#endif + case SQUASHFS_SOCKET_TYPE: + case SQUASHFS_LSOCKET_TYPE: +#ifdef _KERNEL + return (VSOCK); +#else + return (DT_SOCK); +#endif + } +#ifdef _KERNEL + return (VBAD); +#else + return (DT_UNKNOWN); +#endif +} + +#ifdef _KERNEL +__enum_uint8(vtype) +#else +int +#endif + sqsh_inode_type_from_id(struct sqsh_mount *ump, uint64_t inode_id) +{ + struct sqsh_block_run cur; + struct sqsh_base_inode base; + sqsh_err err; + + sqsh_metadata_run_inode(&cur, inode_id, ump->sb.inode_table_start); + + err = sqsh_metadata_get(ump, &cur, &base, sizeof(base)); + if (err != SQFS_OK) +#ifdef _KERNEL + return (VBAD); +#else + return (DT_UNKNOWN); +#endif + swapendian_base_inode(&base); + return sqsh_inode_type(base.inode_type); +} + +sqsh_err +sqsh_verify_inode(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + /* check for inode type */ + if (inode->base.inode_type < SQUASHFS_TYPE_MIN_VALID || + inode->base.inode_type > SQUASHFS_TYPE_MAX_VALID) + return (SQFS_ERR); + + /* + * check for inode_number. + * The inode numbers are from 1 to the total number of inodes. + * Note that 0 is always invalid because we will + * always have at least a root inode. + */ + if (inode->base.inode_number < SQUASHFS_INODE_MIN_COUNT || + inode->base.inode_number > ump->sb.inodes) + return (SQFS_ERR); + + /* + * If inode type is directory then check for parent inode. + * Note that we add +1 because for root inode parent_inode + * is total inodes + 1. + * For some squashfs archives it is 0 too. + */ + if (inode->base.inode_type == SQUASHFS_DIR_TYPE) { + if ((inode->xtra.dir.parent_inode < SQUASHFS_INODE_MIN_COUNT || + inode->xtra.dir.parent_inode > ump->sb.inodes + 1) && + inode->xtra.dir.parent_inode != 0) + return (SQFS_ERR); + } + + return (SQFS_OK); +} + +sqsh_err +sqsh_get_inode_id(struct sqsh_mount *ump, uint16_t idx, uint32_t *id) +{ + uint32_t rid; + sqsh_err err; + + err = sqsh_get_table(&ump->id_table, ump, idx, &rid); + if (err != SQFS_OK) + return (err); + rid = le32toh(rid); + *id = rid; + return (SQFS_OK); +} + +sqsh_err +sqsh_export_inode(struct sqsh_mount *ump, uint32_t n, uint64_t *i) +{ + uint64_t r; + sqsh_err err; + + if (!sqsh_export_ok(ump)) + return (SQFS_ERR); + + err = sqsh_get_table(&ump->export_table, ump, n - 1, &r); + if (err) + return (err); + r = le64toh(r); + *i = r; + return (SQFS_OK); +} + +uint64_t +sqsh_root_inode(struct sqsh_mount *ump) +{ + return (ump->sb.root_inode); +} + +sqsh_err +sqsh_get_inode(struct sqsh_mount *ump, struct sqsh_inode *inode, uint64_t id) +{ + struct sqsh_block_run cur; + sqsh_err err; + + KASSERT(inode != NULL, ("Inode: NULL")); + + memset(inode, 0, sizeof(*inode)); + inode->xattr = SQUASHFS_INVALID_XATTR; + + sqsh_metadata_run_inode(&cur, id, ump->sb.inode_table_start); + inode->next = cur; + + err = sqsh_metadata_get(ump, &cur, &inode->base, sizeof(inode->base)); + if (err != SQFS_OK) + return (err); + + swapendian_base_inode(&inode->base); + inode->type = sqsh_inode_type(inode->base.inode_type); + + inode->vnode = NULL; + inode->ump = ump; + inode->ino_id = id; + inode->parent_id = 0; + + switch (inode->base.inode_type) { + case SQUASHFS_REG_TYPE: { + err = sqsh_init_reg_inode(ump, inode); + if (err != SQFS_OK) + return (err); + break; + } + case SQUASHFS_LREG_TYPE: { + err = sqsh_init_lreg_inode(ump, inode); + if (err != SQFS_OK) + return (err); + break; + } + case SQUASHFS_DIR_TYPE: { + err = sqsh_init_dir_inode(ump, inode); + if (err != SQFS_OK) + return (err); + break; + } + case SQUASHFS_LDIR_TYPE: { + err = sqsh_init_ldir_inode(ump, inode); + if (err != SQFS_OK) + return (err); + break; + } + case SQUASHFS_SYMLINK_TYPE: + case SQUASHFS_LSYMLINK_TYPE: { + err = sqsh_init_symlink_inode(ump, inode); + if (err != SQFS_OK) + return (err); + break; + } + case SQUASHFS_BLKDEV_TYPE: + case SQUASHFS_CHRDEV_TYPE: { + err = sqsh_init_dev_inode(ump, inode); + if (err != SQFS_OK) + return (err); + break; + } + case SQUASHFS_LBLKDEV_TYPE: + case SQUASHFS_LCHRDEV_TYPE: { + err = sqsh_init_ldev_inode(ump, inode); + if (err != SQFS_OK) + return (err); + break; + } + case SQUASHFS_SOCKET_TYPE: + case SQUASHFS_FIFO_TYPE: { + err = sqsh_init_ipc_inode(ump, inode); + if (err != SQFS_OK) + return (err); + break; + } + case SQUASHFS_LSOCKET_TYPE: + case SQUASHFS_LFIFO_TYPE: { + err = sqsh_init_lipc_inode(ump, inode); + if (err != SQFS_OK) + return (err); + break; + } + default: + return (SQFS_ERR); + } + + err = sqsh_verify_inode(ump, inode); + + return (err); +} + +sqsh_err +sqsh_init_reg_inode(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + struct sqsh_reg_inode temp; + sqsh_err err; + + err = sqsh_metadata_get(ump, &inode->next, &temp, sizeof(temp)); + if (err != SQFS_OK) + return (err); + swapendian_reg_inode(&temp); + + inode->nlink = 1; + inode->xtra.reg.start_block = temp.start_block; + inode->size = temp.file_size; + inode->xtra.reg.frag_idx = temp.fragment; + inode->xtra.reg.frag_off = temp.offset; + inode->xtra.reg.sparse = 0; + + return (SQFS_OK); +} + +sqsh_err +sqsh_init_lreg_inode(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + struct sqsh_lreg_inode temp; + sqsh_err err; + + err = sqsh_metadata_get(ump, &inode->next, &temp, sizeof(temp)); + if (err != SQFS_OK) + return (err); + swapendian_lreg_inode(&temp); + + inode->nlink = temp.nlink; + inode->xtra.reg.start_block = temp.start_block; + inode->size = temp.file_size; + inode->xtra.reg.frag_idx = temp.fragment; + inode->xtra.reg.frag_off = temp.offset; + inode->xattr = temp.xattr; + inode->xtra.reg.sparse = temp.sparse; + + return (SQFS_OK); +} + +sqsh_err +sqsh_init_dir_inode(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + struct sqsh_dir_inode temp; + sqsh_err err; + + err = sqsh_metadata_get(ump, &inode->next, &temp, sizeof(temp)); + if (err != SQFS_OK) + return (err); + swapendian_dir_inode(&temp); + + inode->nlink = temp.nlink; + inode->xtra.dir.start_block = temp.start_block; + inode->xtra.dir.offset = temp.offset; + inode->size = temp.file_size; + inode->xtra.dir.idx_count = 0; + inode->xtra.dir.parent_inode = temp.parent_inode; + + sqsh_dir_init(ump, inode, &inode->xtra.dir.d); + memset(&inode->xtra.dir.entry, 0, sizeof(struct sqsh_dir_entry)); + + return (SQFS_OK); +} + +sqsh_err +sqsh_init_ldir_inode(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + struct sqsh_ldir_inode temp; + sqsh_err err; + + err = sqsh_metadata_get(ump, &inode->next, &temp, sizeof(temp)); + if (err != SQFS_OK) + return (err); + swapendian_ldir_inode(&temp); + + inode->nlink = temp.nlink; + inode->xtra.dir.start_block = temp.start_block; + inode->xtra.dir.offset = temp.offset; + inode->size = temp.file_size; + inode->xtra.dir.idx_count = temp.i_count; + inode->xtra.dir.parent_inode = temp.parent_inode; + inode->xattr = temp.xattr; + + sqsh_dir_init(ump, inode, &inode->xtra.dir.d); + memset(&inode->xtra.dir.entry, 0, sizeof(struct sqsh_dir_entry)); + + return (SQFS_OK); +} + +sqsh_err +sqsh_init_symlink_inode(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + struct sqsh_symlink_inode temp; + sqsh_err err; + + err = sqsh_metadata_get(ump, &inode->next, &temp, sizeof(temp)); + if (err != SQFS_OK) + return (err); + + inode->nlink = le32toh(temp.nlink); + inode->size = le32toh(temp.symlink_size); + + return (SQFS_OK); +} + +sqsh_err +sqsh_init_dev_inode(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + struct sqsh_dev_inode temp; + sqsh_err err; + + err = sqsh_metadata_get(ump, &inode->next, &temp, sizeof(temp)); + if (err != SQFS_OK) + return (err); + + inode->size = 0; + inode->nlink = le32toh(temp.nlink); + temp.rdev = le32toh(temp.rdev); + inode->xtra.dev.major = (temp.rdev >> 8) & 0xfff; + inode->xtra.dev.minor = (temp.rdev & 0xff) | + ((temp.rdev >> 12) & 0xfff00); + + return (SQFS_OK); +} + +sqsh_err +sqsh_init_ldev_inode(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + struct sqsh_ldev_inode temp; + sqsh_err err; + + err = sqsh_metadata_get(ump, &inode->next, &temp, sizeof(temp)); + if (err != SQFS_OK) + return (err); + + inode->size = 0; + inode->nlink = le32toh(temp.nlink); + inode->xattr = le32toh(temp.xattr); + temp.rdev = le32toh(temp.rdev); + inode->xtra.dev.major = (temp.rdev >> 8) & 0xfff; + inode->xtra.dev.minor = (temp.rdev & 0xff) | + ((temp.rdev >> 12) & 0xfff00); + + return (SQFS_OK); +} + +sqsh_err +sqsh_init_ipc_inode(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + struct sqsh_ipc_inode temp; + sqsh_err err; + + err = sqsh_metadata_get(ump, &inode->next, &temp, sizeof(temp)); + if (err != SQFS_OK) + return (err); + + inode->size = 0; + inode->nlink = le32toh(temp.nlink); + + return (SQFS_OK); +} + +sqsh_err +sqsh_init_lipc_inode(struct sqsh_mount *ump, struct sqsh_inode *inode) +{ + struct sqsh_lipc_inode temp; + sqsh_err err; + + err = sqsh_metadata_get(ump, &inode->next, &temp, sizeof(temp)); + if (err != SQFS_OK) + return (err); + + inode->size = 0; + inode->nlink = le32toh(temp.nlink); + inode->xattr = le32toh(temp.xattr); + + return (SQFS_OK); +} + +void +swapendian_base_inode(struct sqsh_base_inode *temp) +{ + temp->inode_type = le16toh(temp->inode_type); + temp->mode = le16toh(temp->mode); + temp->uid = le16toh(temp->uid); + temp->guid = le16toh(temp->guid); + temp->mtime = le32toh(temp->mtime); + temp->inode_number = le32toh(temp->inode_number); +} + +void +swapendian_reg_inode(struct sqsh_reg_inode *temp) +{ + temp->inode_type = le16toh(temp->inode_type); + temp->mode = le16toh(temp->mode); + temp->uid = le16toh(temp->uid); + temp->guid = le16toh(temp->guid); + temp->mtime = le32toh(temp->mtime); + temp->inode_number = le32toh(temp->inode_number); + temp->start_block = le32toh(temp->start_block); + temp->fragment = le32toh(temp->fragment); + temp->offset = le32toh(temp->offset); + temp->file_size = le32toh(temp->file_size); +} + +void +swapendian_lreg_inode(struct sqsh_lreg_inode *temp) +{ + temp->inode_type = le16toh(temp->inode_type); + temp->mode = le16toh(temp->mode); + temp->uid = le16toh(temp->uid); + temp->guid = le16toh(temp->guid); + temp->mtime = le32toh(temp->mtime); + temp->inode_number = le32toh(temp->inode_number); + temp->start_block = le64toh(temp->start_block); + temp->file_size = le64toh(temp->file_size); + temp->sparse = le64toh(temp->sparse); + temp->nlink = le32toh(temp->nlink); + temp->fragment = le32toh(temp->fragment); + temp->offset = le32toh(temp->offset); + temp->xattr = le32toh(temp->xattr); +} + +void +swapendian_dir_inode(struct sqsh_dir_inode *temp) +{ + temp->inode_type = le16toh(temp->inode_type); + temp->mode = le16toh(temp->mode); + temp->uid = le16toh(temp->uid); + temp->guid = le16toh(temp->guid); + temp->mtime = le32toh(temp->mtime); + temp->inode_number = le32toh(temp->inode_number); + temp->start_block = le32toh(temp->start_block); + temp->nlink = le32toh(temp->nlink); + temp->file_size = le16toh(temp->file_size); + temp->offset = le16toh(temp->offset); + temp->parent_inode = le32toh(temp->parent_inode); +} + +void +swapendian_ldir_inode(struct sqsh_ldir_inode *temp) +{ + temp->inode_type = le16toh(temp->inode_type); + temp->mode = le16toh(temp->mode); + temp->uid = le16toh(temp->uid); + temp->guid = le16toh(temp->guid); + temp->mtime = le32toh(temp->mtime); + temp->inode_number = le32toh(temp->inode_number); + temp->nlink = le32toh(temp->nlink); + temp->file_size = le32toh(temp->file_size); + temp->start_block = le32toh(temp->start_block); + temp->parent_inode = le32toh(temp->parent_inode); + temp->i_count = le16toh(temp->i_count); + temp->offset = le16toh(temp->offset); + temp->xattr = le32toh(temp->xattr); +} diff --git a/sys/fs/squashfs/squashfs_io.h b/sys/fs/squashfs/squashfs_io.h new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_io.h @@ -0,0 +1,38 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef SQUASHFS_IO +#define SQUASHFS_IO + +#include + +ssize_t sqsh_io_read_buf(struct sqsh_mount *ump, void *buf, off_t off, + size_t len); + +#endif diff --git a/sys/fs/squashfs/squashfs_io.c b/sys/fs/squashfs/squashfs_io.c new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_io.c @@ -0,0 +1,163 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_squashfs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +sqsh_err sqsh_io_read(struct sqsh_mount *ump, struct uio *uiop); + +/* + * Reads data according to the provided uio. + * This function reads directly from disk file + * and all decompression reads are handled by seperate + * functions in squashfs_block.h file. + */ +sqsh_err +sqsh_io_read(struct sqsh_mount *ump, struct uio *uiop) +{ + struct vnode *vp; + off_t off; + size_t len; + int error; + + vp = ump->um_vp; + + if (vp->v_type == VREG) { + void *rl; + + off = uiop->uio_offset; + len = uiop->uio_resid; + + rl = vn_rangelock_rlock(vp, off, off + len); + error = vn_lock(vp, LK_SHARED); + if (error == 0) { + error = VOP_READ(vp, uiop, IO_NODELOCKED, + uiop->uio_td->td_ucred); + } + VOP_UNLOCK(vp); + vn_rangelock_unlock(vp, rl); + } else { + struct buf *bp; + size_t sectorsz; + daddr_t lbn; + off_t trim; + + sectorsz = ump->cp->provider->sectorsize; + + off = uiop->uio_offset & ~PAGE_MASK; + trim = uiop->uio_offset - off; + + do { + lbn = btodb(off); + + len = MIN(MAXBSIZE, + roundup2(uiop->uio_resid + trim, sectorsz)); + error = bread(vp, lbn, len, NOCRED, &bp); + if (error != 0) + break; + + error = uiomove(bp->b_data + trim, len - trim, uiop); + brelse(bp); + + off = uiop->uio_offset; + trim = 0; + } while (error == 0 && uiop->uio_resid > 0); + } + + return (error != 0 ? SQFS_ERR : SQFS_OK); +} + +/* + * Reads data into the provided buffer. + * This function reads directly from disk file + * and all decompression reads are handled by seperate + * functions in squashfs_block.h file. + * On succes it return number of bytes read else negative + * value on failure. + */ +ssize_t +sqsh_io_read_buf(struct sqsh_mount *ump, void *buf, off_t off, size_t len) +{ + struct uio auio; + struct iovec aiov; + sqsh_err error; + ssize_t res; + + /* return success and reading zero bytes of data */ + if (len == 0) + return (0); + + /* initialize iovec */ + aiov.iov_base = buf; + aiov.iov_len = len; + + /* initialize uio */ + auio.uio_iov = &aiov; + auio.uio_iovcnt = 1; + auio.uio_offset = off; + auio.uio_segflg = UIO_SYSSPACE; + auio.uio_rw = UIO_READ; + auio.uio_resid = len; + auio.uio_td = curthread; + + error = sqsh_io_read(ump, &auio); + + /* return negative value on reading failure */ + if (error != SQFS_OK) + return (-1); + + res = len - auio.uio_resid; + + return (res); +} diff --git a/sys/fs/squashfs/squashfs_mount.h b/sys/fs/squashfs/squashfs_mount.h new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_mount.h @@ -0,0 +1,73 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef SQUASHFS_MOUNT_H +#define SQUASHFS_MOUNT_H + +#if defined(_KERNEL) || defined(_STANDALONE) + +/* This structure describes squashfs mount structure data */ +struct sqsh_mount { +#ifdef _KERNEL + struct mount *um_mountp; + struct vnode *um_vp; +#endif + struct sqsh_sb sb; + struct sqsh_table id_table; + struct sqsh_table frag_table; + struct sqsh_table export_table; + struct sqsh_table xattr_table; + struct sqsh_xattr_id_table xattr_info; +#ifdef _KERNEL + struct g_consumer *cp; +#endif + const struct sqsh_decompressor *decompressor; +}; + +#ifdef _KERNEL + +#ifdef MALLOC_DECLARE +MALLOC_DECLARE(M_SQUASHFS_NODE); +#endif + +static inline struct sqsh_mount * +MP_TO_SQSH_MOUNT(struct mount *mp) +{ + MPASS(mp != NULL && mp->mnt_data != NULL); + return (mp->mnt_data); +} + +#endif /* _KERNEL */ + +sqsh_err squashfs_init(struct sqsh_mount *); + +#endif /* _KERNEL || _STANDALONE */ + +#endif /* SQUASHFS_MOUNT_H */ diff --git a/sys/fs/squashfs/squashfs_vfsops.c b/sys/fs/squashfs/squashfs_vfsops.c new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_vfsops.c @@ -0,0 +1,407 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opt_squashfs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +MALLOC_DEFINE(M_SQUASHFS_NODE, "SQUASHFS inode", "SQUASHFS vnode private data"); +static MALLOC_DEFINE(M_SQUASHFSMNT, "SQUASHFS mount", + "SQUASHFS mount structure"); + +static vfs_mount_t squashfs_mount; +static vfs_unmount_t squashfs_unmount; +static vfs_root_t squashfs_root; +static vfs_statfs_t squashfs_statfs; +static vfs_vget_t squashfs_vget; +static vfs_fhtovp_t squashfs_fhtovp; + +static const char *squashfs_opts[] = { + "as", + "from", + NULL, +}; + +static const char *squashfs_updateopts[] = { + "from", + NULL, +}; + +/* VFS operations */ +static int +squashfs_mount(struct mount *mp) +{ + struct nameidata nd; + struct sqsh_mount *ump = NULL; + struct vnode *vp; + struct thread *td = curthread; + char *from, *as; + struct g_consumer *cp; + int error, isverified, len, aslen; + accmode_t accmode; + sqsh_err err; + + cp = NULL; + TRACE("squashfs_mount(mp = %p)\n", mp); + + if (mp->mnt_flag & MNT_UPDATE) { + if (vfs_filteropt(mp->mnt_optnew, squashfs_updateopts)) + return (EOPNOTSUPP); + + /* + * We don't really allow any meaningful options at the moment, + * so just succeed. Perhaps later we'll allow, e.g., noexec. + */ + return (EOPNOTSUPP); + } + + if (vfs_filteropt(mp->mnt_optnew, squashfs_opts)) + return (EINVAL); + + /* Get argument */ + error = vfs_getopt(mp->mnt_optnew, "from", (void **)&from, &len); + if (error != 0 || from[len - 1] != '\0') + return (EINVAL); + error = vfs_getopt(mp->mnt_optnew, "as", (void **)&as, &aslen); + if (error || as[aslen - 1] != '\0') + as = from; + + /* find and initialise squashfs disk file vnode vp */ + NDINIT(&nd, LOOKUP, ISOPEN | FOLLOW | LOCKLEAF, UIO_SYSSPACE, from); + error = namei(&nd); + if (error != 0) + return (error); + NDFREE_PNBUF(&nd); + vp = nd.ni_vp; + /* vp is now held and locked */ + + /* check if vnode is of file type (squashfs disk is always of regular + * file type) */ + if (vp->v_type != VREG && !vn_isdisk(vp)) { + ERROR("Squashfs disk is not a regular file or disk"); + error = EOPNOTSUPP; + vput(vp); + return (error); + } + + /* check if file is not private */ + accmode = VREAD; + error = VOP_ACCESS(vp, accmode, td->td_ucred, td); + if (error != 0) + error = priv_check(td, PRIV_VFS_MOUNT_PERM); + if (error != 0) { + ERROR("insufficient permissions to mount disk"); + error = EOPNOTSUPP; + vput(vp); + return (error); + } + + isverified = 0; + if (vp->v_type != VREG) { + struct cdev *dev; + + dev = vp->v_rdev; + + dev_ref(dev); + g_topology_lock(); + error = g_vfs_open(vp, &cp, "squashfs", 0); + if (error == 0) + g_getattr("MNT::verified", cp, &isverified); + g_topology_unlock(); + + if (error != 0) { + ERROR("failed to open disk"); + vput(vp); + return (error); + } + } + VOP_UNLOCK(vp); + + /* Create squashfs mount */ + ump = SQUASHFS_MALLOC(sizeof(struct sqsh_mount), M_SQUASHFSMNT, + M_WAITOK | M_ZERO); + ump->um_mountp = mp; + ump->um_vp = vp; + ump->cp = cp; + + err = squashfs_init(ump); + + switch (err) { + case SQFS_OK: + break; + case SQFS_BADFORMAT: + ERROR("Wrong squashfs image"); + break; + case SQFS_BADVERSION: + ERROR("Squashfs 4.0 to 4.%d is supported", SQUASHFS_MINOR); + break; + case SQFS_BADCOMP: + break; + default: + ERROR( + "Some unknown error happend while mounting squashfs image"); + } + + if (err != SQFS_OK) + goto failed_mount; + + mp->mnt_data = ump; + mp->mnt_iosize_max = vp->v_mount->mnt_iosize_max; + + /* Unconditionally mount squashfs as read only */ + MNT_ILOCK(mp); + mp->mnt_flag |= (MNT_LOCAL | MNT_RDONLY); + if (isverified) + mp->mnt_flag |= MNT_VERIFIED; + MNT_IUNLOCK(mp); + + vfs_getnewfsid(mp); + vfs_mountedfrom(mp, as); + TRACE("Squashfs mount successful"); + return (0); + +failed_mount: + TRACE("Squashfs mount failed"); + if (cp != NULL) { + g_topology_lock(); + g_vfs_close(cp); + g_topology_unlock(); + } + if (vp->v_type != VREG) + dev_rel(vp->v_rdev); + vrele(vp); + SQUASHFS_FREE(ump, M_SQUASHFSMNT); + return (EINVAL); +} + +static int +squashfs_unmount(struct mount *mp, int mntflags) +{ + TRACE("%s:", __func__); + struct sqsh_mount *ump; + struct vnode *vp; + int flags; + int error; + + flags = 0; + + /* Handle forced unmounts */ + if (mntflags & MNT_FORCE) + flags |= FORCECLOSE; + + error = vflush(mp, 0, flags, curthread); + if (error != 0) + return (error); + + ump = MP_TO_SQSH_MOUNT(mp); + vp = ump->um_vp; + + if (ump->cp != NULL) { + g_topology_lock(); + g_vfs_close(ump->cp); + g_topology_unlock(); + ump->cp = NULL; + + dev_rel(vp->v_rdev); + } + + vrele(vp); + + /* destroy fs internals */ + sqsh_free_table(&ump->id_table); + sqsh_free_table(&ump->frag_table); + if (sqsh_export_ok(ump)) + sqsh_free_table(&ump->export_table); + + SQUASHFS_FREE(ump, M_SQUASHFSMNT); + mp->mnt_data = NULL; + TRACE("%s: completed", __func__); + + return (0); +} + +static int +squashfs_root(struct mount *mp, int flags, struct vnode **vpp) +{ + TRACE("%s:", __func__); + struct vnode *nvp; + struct sqsh_mount *ump; + int error; + + ump = MP_TO_SQSH_MOUNT(mp); + + error = VFS_VGET(mp, sqsh_root_inode(ump), LK_EXCLUSIVE, &nvp); + if (error != 0) + return (error); + + nvp->v_vflag |= VV_ROOT; + *vpp = nvp; + TRACE("%s: completed", __func__); + return (0); +} + +static int +squashfs_statfs(struct mount *mp, struct statfs *sbp) +{ + struct sqsh_mount *ump; + + TRACE("%s:", __func__); + + ump = MP_TO_SQSH_MOUNT(mp); + + sbp->f_bsize = ump->sb.block_size; + sbp->f_iosize = PAGE_SIZE; + sbp->f_blocks = ump->sb.bytes_used / ump->sb.block_size; + sbp->f_bfree = 0; + sbp->f_bavail = 0; + sbp->f_files = ump->sb.inodes; + sbp->f_ffree = 0; + + return (0); +} + +static int +squashfs_vget(struct mount *mp, ino_t ino, int lkflags, struct vnode **vpp) +{ + TRACE("%s:", __func__); + struct sqsh_mount *ump; + struct sqsh_inode *inode; + struct thread *td; + struct vnode *vp; + int error; + sqsh_err err; + + td = curthread; + error = vfs_hash_get(mp, ino, lkflags, td, vpp, NULL, NULL); + if (error || *vpp != NULL) + return (error); + + ump = MP_TO_SQSH_MOUNT(mp); + inode = SQUASHFS_MALLOC(sizeof(struct sqsh_inode), M_SQUASHFS_NODE, + M_WAITOK | M_ZERO); + + /* populate inode data as per inode number */ + err = sqsh_get_inode(ump, inode, ino); + if (err != SQFS_OK) { + *vpp = NULL; + SQUASHFS_FREE(inode, M_SQUASHFS_NODE); + return (EINVAL); + } + + error = getnewvnode("squashfs", mp, &squashfs_vnodeops, &vp); + if (error != 0) { + *vpp = NULL; + SQUASHFS_FREE(inode, M_SQUASHFS_NODE); + return (error); + } + + vp->v_data = inode; + vp->v_type = inode->type; + inode->vnode = vp; + + lockmgr(vp->v_vnlock, lkflags, NULL); + error = insmntque(vp, mp); + if (error != 0) { + *vpp = NULL; + SQUASHFS_FREE(inode, M_SQUASHFS_NODE); + return (error); + } + error = vfs_hash_insert(vp, ino, lkflags, td, vpp, NULL, NULL); + if (error != 0 || *vpp != NULL) + return (error); + + /* vn_set_state(vp, VSTATE_CONSTRUCTED); */ + TRACE("%s: completed", __func__); + *vpp = vp; + return (0); +} + +static int +squashfs_fhtovp(struct mount *mp, struct fid *fhp, int flags, + struct vnode **vpp) +{ + TRACE("%s:", __func__); + struct sqsh_fid *tfp; + struct vnode *vp; + int error; + + tfp = (struct sqsh_fid *)fhp; + + error = VFS_VGET(mp, tfp->ino, LK_EXCLUSIVE, &vp); + if (error != 0) { + *vpp = NULL; + return (error); + } + /* TODO : add checks for inode */ + + *vpp = vp; + TRACE("%s: completed", __func__); + return (0); +} + +static struct vfsops squashfs_vfsops = { + .vfs_fhtovp = squashfs_fhtovp, + .vfs_mount = squashfs_mount, + .vfs_root = squashfs_root, + .vfs_statfs = squashfs_statfs, + .vfs_unmount = squashfs_unmount, + .vfs_vget = squashfs_vget, +}; + +VFS_SET(squashfs_vfsops, squashfs, VFCF_READONLY); +MODULE_VERSION(squashfs, 1); +MODULE_DEPEND(squashfs, xz, 1, 1, 1); diff --git a/sys/fs/squashfs/squashfs_vnops.c b/sys/fs/squashfs/squashfs_vnops.c new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_vnops.c @@ -0,0 +1,813 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opt_squashfs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static int +squashfs_open(struct vop_open_args *ap) +{ + TRACE("%s:", __func__); + + struct sqsh_inode *inode; + struct vnode *vp; + + vp = ap->a_vp; + MPASS(VOP_ISLOCKED(vp)); + inode = vp->v_data; + + if (vp->v_type != VREG && vp->v_type != VDIR) + return (EOPNOTSUPP); + + vnode_create_vobject(vp, inode->size, ap->a_td); + return (0); +} + +static int +squashfs_close(struct vop_close_args *ap) +{ + TRACE("%s:", __func__); + return (0); +} + +static int +squashfs_access(struct vop_access_args *ap) +{ + TRACE("%s:", __func__); + + struct sqsh_inode *inode; + struct vnode *vp; + accmode_t accmode; + struct ucred *cred; + int error; + + vp = ap->a_vp; + accmode = ap->a_accmode; + cred = ap->a_cred; + + MPASS(VOP_ISLOCKED(vp)); + inode = vp->v_data; + + switch (vp->v_type) { + case VDIR: + case VLNK: + case VREG: + if ((accmode & VWRITE) != 0) + return (EROFS); + break; + case VBLK: + case VCHR: + case VFIFO: + case VSOCK: + break; + default: + return (EINVAL); + } + + if ((accmode & VWRITE) != 0) + return (EPERM); + + error = vaccess(vp->v_type, inode->base.mode, inode->base.uid, + inode->base.guid, accmode, cred); + + return (error); +} + +static int +squashfs_bmap(struct vop_bmap_args *ap) +{ + struct vnode *vp; + struct sqsh_inode *inode; + uint64_t ahead, behind, iosize; + int rmax; + + vp = ap->a_vp; + inode = vp->v_data; + if (ap->a_bop != NULL) + *ap->a_bop = &ap->a_vp->v_bufobj; + if (ap->a_bnp == NULL) + return (0); + + iosize = vp->v_mount->mnt_stat.f_iosize; + + *ap->a_bnp = ap->a_bn * btodb(iosize); + if (ap->a_runb == NULL) + return (0); + + ahead = behind = 0; + + /* + * Punt on sparse files for now; we need to poll the block index to + * determine how large the hole that we're in is, but that's still on + * disk because we don't cache it at all. + */ + if (inode->xtra.reg.sparse == 0) { + off_t nextblk, off; + uint64_t blksz; + + blksz = inode->ump->sb.block_size; + + off = ap->a_bn * iosize; + + /* + * All of the file data is contiguous on the disk, with + * exception to the very end of the file if it's not a multiple + * of the block size. + */ + nextblk = roundup2(off, blksz); + if (inode->xtra.reg.frag_idx == SQUASHFS_INVALID_FRAG) { + /* No fragment - read on ahead. */ + nextblk = MIN(inode->size, nextblk); + } else { + off_t startoff; + + /* + * Fragment - read up to the last block, unless we are + * in the middle of the fragment. + */ + startoff = rounddown2(inode->size, blksz); + if (off > startoff) { + /* + * In the fragment, we can only read behind to + * the start of the fragment or read ahead to + * the end of the file. + */ + startoff = 0; + nextblk = MIN(inode->size, nextblk); + } else { + nextblk = MIN(startoff, nextblk); + } + } + + behind = howmany(off - rounddown2(off, blksz), iosize); + if (nextblk - iosize < off) + ahead = 0; + else + ahead = howmany(nextblk - (off + iosize), iosize); + } + + rmax = vp->v_mount->mnt_iosize_max / iosize - 1; + + *ap->a_runb = imin(rmax, behind); + *ap->a_runp = imin(rmax, ahead); + + return (0); +} + +static int +squashfs_getattr(struct vop_getattr_args *ap) +{ + TRACE("%s:", __func__); + + struct sqsh_inode *inode; + struct vnode *vp; + struct vattr *vap; + + vp = ap->a_vp; + vap = ap->a_vap; + inode = vp->v_data; + + /* fill up vattr for squashfs inode */ + vap->va_type = vp->v_type; + vap->va_mode = inode->base.mode; + vap->va_nlink = inode->nlink; + vap->va_gid = inode->base.guid; + vap->va_uid = inode->base.uid; + if (sqsh_get_inode_id(inode->ump, inode->base.guid, &vap->va_gid) != + SQFS_OK) + return (EINVAL); + + if (sqsh_get_inode_id(inode->ump, inode->base.uid, &vap->va_uid) != + SQFS_OK) + return (EINVAL); + + vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0]; + vap->va_fileid = inode->base.inode_number; + vap->va_size = inode->size; + vap->va_blocksize = vp->v_mount->mnt_stat.f_iosize; + vap->va_atime.tv_sec = inode->base.mtime; + vap->va_atime.tv_nsec = 0; + vap->va_ctime.tv_sec = inode->base.mtime; + vap->va_ctime.tv_nsec = 0; + vap->va_mtime.tv_sec = inode->base.mtime; + vap->va_mtime.tv_nsec = 0; + vap->va_birthtime.tv_sec = inode->base.mtime; + vap->va_birthtime.tv_nsec = 0; + vap->va_rdev = (vp->v_type == VBLK || vp->v_type == VCHR) ? + inode->xtra.dev.major : + NODEV; + vap->va_bytes = inode->size; + vap->va_filerev = 0; + vap->va_flags = 0; + + return (0); +} + +static int +squashfs_read(struct vop_read_args *ap) +{ + TRACE("%s:", __func__); + + struct sqsh_mount *ump; + struct sqsh_inode *inode; + struct uio *uiop; + struct vnode *vp; + off_t resid; + size_t len; + sqsh_err err; + + uiop = ap->a_uio; + vp = ap->a_vp; + + if (vp->v_type == VCHR || vp->v_type == VBLK) + return (EOPNOTSUPP); + + if (vp->v_type != VREG) + return (EISDIR); + + if (uiop->uio_offset < 0) + return (EINVAL); + + inode = vp->v_data; + ump = inode->ump; + err = SQFS_OK; + + while ((resid = uiop->uio_resid) > 0) { + if (inode->size <= uiop->uio_offset) + break; + len = MIN(inode->size - uiop->uio_offset, resid); + if (len == 0) + break; + + err = sqsh_read_file(ump, inode, uiop->uio_offset, &len, uiop); + if (err != SQFS_OK) + break; + } + + if (err != SQFS_OK) + return (EINVAL); + + return (0); +} + +static int +squashfs_lookup(struct vop_cachedlookup_args *ap) +{ + TRACE("%s:", __func__); + + struct sqsh_mount *ump; + struct sqsh_inode *inode; + struct sqsh_inode *child_inode; + struct componentname *cnp; + struct vnode *dvp, **vpp; + int error; + sqsh_err err; + + dvp = ap->a_dvp; + vpp = ap->a_vpp; + cnp = ap->a_cnp; + + *vpp = NULLVP; + inode = dvp->v_data; + ump = inode->ump; + + error = VOP_ACCESS(dvp, VEXEC, cnp->cn_cred, curthread); + if (error != 0) + return (error); + + if (cnp->cn_flags & ISDOTDOT) { + /* Do not allow .. on the root node */ + if (inode->xtra.dir.parent_inode == ump->sb.inodes + 1 || + inode->xtra.dir.parent_inode == 0) + return (ENOENT); + + /* Allocate a new vnode on the matching entry */ + error = vn_vget_ino(dvp, inode->parent_id, cnp->cn_lkflags, + vpp); + if (error != 0) + return (error); + } else if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') { + VREF(dvp); + *vpp = dvp; + } else { + struct sqsh_dir_entry entry; + bool found; + + found = false; + + /* Lookup for entry in directory, if found populate entry */ + err = sqsh_dir_lookup(ump, inode, cnp->cn_nameptr, + cnp->cn_namelen, &entry, &found); + if (err != SQFS_OK) + return (EINVAL); + + if (found == false) + return (ENOENT); + + error = VFS_VGET(ump->um_mountp, entry.inode_id, + cnp->cn_lkflags, vpp); + if (error != 0) + return (error); + child_inode = (*vpp)->v_data; + child_inode->parent_id = inode->ino_id; + } + + /* Store the result the the cache if MAKEENTRY is specified in flags */ + if ((cnp->cn_flags & MAKEENTRY) != 0 && cnp->cn_nameiop != CREATE) + cache_enter(dvp, *vpp, cnp); + + return (error); +} + +static int +squashfs_readdir(struct vop_readdir_args *ap) +{ + TRACE("%s:", __func__); + + struct sqsh_mount *ump; + struct dirent cde = { 0 }; + struct sqsh_inode *inode; + struct vnode *vp; + struct uio *uio; + int *eofflag; +#if 0 + uint64_t **cookies; + int *ncookies; + off_t off; +#endif + u_int ndirents; + int error; + sqsh_err err = SQFS_OK; + + vp = ap->a_vp; + uio = ap->a_uio; + eofflag = ap->a_eofflag; +#if 0 + cookies = ap->a_cookies; + ncookies = ap->a_ncookies; +#endif + + if (vp->v_type != VDIR) + return (ENOTDIR); + + inode = vp->v_data; + ump = inode->ump; +#if 0 + off = uio->uio_offset; +#endif + ndirents = 0; + + error = 0; + if (uio->uio_offset == SQUASHFS_COOKIE_EOF) + return (0); + + if (uio->uio_offset == SQUASHFS_COOKIE_DOT) { + /* fake . entry */ + cde.d_fileno = inode->base.inode_number; + cde.d_type = DT_DIR; + cde.d_namlen = 1; + cde.d_name[0] = '.'; + cde.d_name[1] = '\0'; + cde.d_reclen = GENERIC_DIRSIZ(&cde); + if (cde.d_reclen > uio->uio_resid) + goto full; + dirent_terminate(&cde); + error = uiomove(&cde, cde.d_reclen, uio); + if (error) + return (error); + /* next is .. */ + uio->uio_offset = SQUASHFS_COOKIE_DOTDOT; + ndirents++; + } + + if (uio->uio_offset == SQUASHFS_COOKIE_DOTDOT) { + /* fake .. entry */ + /* Get inode number of parent inode */ + + cde.d_fileno = inode->xtra.dir.parent_inode; + cde.d_type = DT_DIR; + cde.d_namlen = 2; + cde.d_name[0] = '.'; + cde.d_name[1] = '.'; + cde.d_name[2] = '\0'; + cde.d_reclen = GENERIC_DIRSIZ(&cde); + if (cde.d_reclen > uio->uio_resid) + goto full; + dirent_terminate(&cde); + error = uiomove(&cde, cde.d_reclen, uio); + if (error) + return (error); + /* next is first child */ + err = sqsh_dir_getnext(ump, &inode->xtra.dir.d, + &inode->xtra.dir.entry); + if (err == SQFS_END_OF_DIRECTORY) + goto done; + if (err != SQFS_OK) { + error = EINVAL; + goto done; + } + uio->uio_offset = inode->xtra.dir.entry.inode_id; + ndirents++; + } + + for (;;) { + __enum_uint8(vtype) type; + + cde.d_fileno = inode->xtra.dir.entry.inode_number; + type = sqsh_inode_type_from_id(ump, + inode->xtra.dir.entry.inode_id); + switch (type) { + case VBLK: + cde.d_type = DT_BLK; + break; + case VCHR: + cde.d_type = DT_CHR; + break; + case VDIR: + cde.d_type = DT_DIR; + break; + case VFIFO: + cde.d_type = DT_FIFO; + break; + case VLNK: + cde.d_type = DT_LNK; + break; + case VREG: + cde.d_type = DT_REG; + break; + default: + panic("%s: inode_type %d\n", __func__, type); + } + cde.d_namlen = inode->xtra.dir.entry.name_size; + /* XXX Really crash? */ + MPASS(inode->xtra.dir.entry.name_size < sizeof(cde.d_name)); + (void)memcpy(cde.d_name, inode->xtra.dir.entry.name, + inode->xtra.dir.entry.name_size); + cde.d_name[inode->xtra.dir.entry.name_size] = '\0'; + cde.d_reclen = GENERIC_DIRSIZ(&cde); + if (cde.d_reclen > uio->uio_resid) + goto full; + dirent_terminate(&cde); + error = uiomove(&cde, cde.d_reclen, uio); + if (error != 0) + goto done; + ndirents++; + /* next sibling */ + err = sqsh_dir_getnext(ump, &inode->xtra.dir.d, + &inode->xtra.dir.entry); + if (err == SQFS_END_OF_DIRECTORY) + goto done; + if (err != SQFS_OK) { + error = EINVAL; + goto done; + } + uio->uio_offset = inode->xtra.dir.entry.inode_id; + } + +full: + if (cde.d_reclen > uio->uio_resid) + error = (ndirents == 0) ? EINVAL : 0; +done: + TRACE("%s: %u entries written\n", __func__, ndirents); + + if (err == SQFS_END_OF_DIRECTORY) { + uio->uio_offset = SQUASHFS_COOKIE_EOF; + /* Restart the directory */ + sqsh_dir_init(ump, inode, &inode->xtra.dir.d); + } + + if (eofflag != NULL) { + TRACE("%s: Setting EOF flag\n", __func__); + *eofflag = (error == 0 && + uio->uio_offset == SQUASHFS_COOKIE_EOF); + } + + return (error); +} + +static int +squashfs_readlink(struct vop_readlink_args *ap) +{ + TRACE("%s:", __func__); + + struct sqsh_inode *inode; + struct uio *uiop; + struct vnode *vp; + int error; + size_t want; + struct sqsh_block_run cur; + sqsh_err err; + + uiop = ap->a_uio; + vp = ap->a_vp; + + MPASS(uiop->uio_offset == 0); + MPASS(vp->v_type == VLNK); + + inode = vp->v_data; + + char buf[inode->size]; + + want = inode->size; + cur = inode->next; + err = sqsh_metadata_get(inode->ump, &cur, buf, want); + if (err != SQFS_OK) + return (EINVAL); + + error = uiomove(buf, MIN(inode->size, uiop->uio_resid), uiop); + + return (error); +} + +static int +squashfs_reclaim(struct vop_reclaim_args *ap) +{ + TRACE("%s:", __func__); + struct vnode *vp; + + vp = ap->a_vp; + + vfs_hash_remove(vp); + + SQUASHFS_FREE(vp->v_data, M_SQUASHFS_NODE); + vp->v_data = NULL; + + TRACE("%s: completed", __func__); + return (0); +} + +static int +squashfs_print(struct vop_print_args *ap) +{ + TRACE("%s:", __func__); + + struct sqsh_inode *inode; + struct vnode *vp; + + vp = ap->a_vp; + inode = vp->v_data; + + printf("tag squashfs, squashfs_inode %p, links %lu\n", inode, + (unsigned long)inode->nlink); + printf("\tmode 0%o, owner %d, group %d, size %zd\n", inode->base.mode, + inode->base.uid, inode->base.guid, inode->size); + + if (vp->v_type == VFIFO) + fifo_printinfo(vp); + + printf("\n"); + + return (0); +} + +static int +squashfs_strategy(struct vop_strategy_args *ap) +{ + TRACE("%s:", __func__); + + struct sqsh_mount *ump; + struct vnode *vp; + struct sqsh_inode *inode; + struct buf *bp; + off_t off; + size_t len; + int error; + struct uio auio; + struct iovec iov; + sqsh_err err; + + error = 0; + vp = ap->a_vp; + bp = ap->a_bp; + MPASS(bp->b_iocmd == BIO_READ); + MPASS(bp->b_iooffset >= 0); + MPASS(bp->b_bcount > 0); + MPASS(bp->b_bufsize >= bp->b_bcount); + + iov.iov_base = bp->b_data; + iov.iov_len = bp->b_bcount; + inode = vp->v_data; + ump = inode->ump; + off = bp->b_iooffset; + len = bp->b_bcount; + bp->b_resid = len; + + if (off > inode->size) { + error = EIO; + goto out; + } + if (off + len > inode->size) + len = inode->size - off; + + auio.uio_iov = &iov; + auio.uio_iovcnt = 1; + auio.uio_offset = off; + auio.uio_resid = len; + auio.uio_segflg = UIO_SYSSPACE; + auio.uio_rw = UIO_READ; + auio.uio_td = curthread; + + err = sqsh_read_file(ump, inode, off, &bp->b_resid, &auio); + if (err != SQFS_OK) { + error = EINVAL; + goto out; + } +out: + if (error != 0) { + bp->b_ioflags |= BIO_ERROR; + bp->b_error = error; + } + bp->b_flags |= B_DONE; + return (0); +} + +static int +squashfs_vptofh(struct vop_vptofh_args *ap) +{ + TRACE("%s:", __func__); + + struct vnode *vp; + struct sqsh_fid *tfp; + struct sqsh_inode *inode; + sqsh_err err; + + tfp = (struct sqsh_fid *)ap->a_fhp; + vp = ap->a_vp; + inode = vp->v_data; + + uint64_t i_ino; + err = sqsh_export_inode(inode->ump, inode->base.inode_number, &i_ino); + if (err != SQFS_OK) + return (EINVAL); + + tfp->len = sizeof(struct sqsh_fid); + tfp->ino = i_ino; + + return (0); +} + +static int +squashfs_getextattr(struct vop_getextattr_args *ap) +{ + TRACE("%s:", __func__); + + struct vnode *vp; + struct sqsh_inode *inode; + struct sqsh_mount *ump; + sqsh_err err; + int error; + + vp = ap->a_vp; + inode = vp->v_data; + ump = inode->ump; + + error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace, ap->a_cred, + ap->a_td, VREAD); + + if (error != 0) + return (error); + + if (ap->a_size != NULL) + *ap->a_size = 0; + + error = ENOATTR; + + err = sqsh_xattr_lookup(ump, inode, ap->a_name, ap->a_uio, ap->a_size); + + if (err != SQFS_OK || (ap->a_size != NULL && ap->a_size == 0)) + return (error); + + return (0); +} + +static int +squashfs_listextattr(struct vop_listextattr_args *ap) +{ + TRACE("%s:", __func__); + + struct vnode *vp; + struct sqsh_inode *inode; + struct sqsh_mount *ump; + struct uio *uio; + struct sqsh_xattr attr; + sqsh_err err; + int error; + + vp = ap->a_vp; + inode = vp->v_data; + ump = inode->ump; + uio = ap->a_uio; + + error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace, ap->a_cred, + ap->a_td, VREAD); + + if (error != 0) + return (error); + + if (ap->a_size != NULL) + *ap->a_size = 0; + + err = sqsh_xattr_open(ump, inode, &attr); + if (err != SQFS_OK) + return (ENOATTR); + + while ((err = sqsh_xattr_read(&attr)) == SQFS_OK) { + size_t name_len = sqsh_xattr_name_size(&attr); + char name[name_len + 1]; + name[0] = name_len; + + if (ap->a_size != NULL) + *ap->a_size += name_len + 1; + + err = sqsh_xattr_name(&attr, name + 1, false); + if (err != SQFS_OK) + return (ENOATTR); + + error = uiomove(name, name_len + 1, uio); + if (error != 0) + return (error); + } + + if (err == SQFS_ERR) + return (ENOATTR); + + return (0); +} + +struct vop_vector squashfs_vnodeops = { + .vop_default = &default_vnodeops, + + .vop_access = squashfs_access, + .vop_bmap = squashfs_bmap, + .vop_cachedlookup = squashfs_lookup, + .vop_close = squashfs_close, + .vop_getattr = squashfs_getattr, + .vop_lookup = vfs_cache_lookup, + .vop_open = squashfs_open, + .vop_print = squashfs_print, + .vop_read = squashfs_read, + .vop_readdir = squashfs_readdir, + .vop_readlink = squashfs_readlink, + .vop_reclaim = squashfs_reclaim, + .vop_strategy = squashfs_strategy, + .vop_vptofh = squashfs_vptofh, + .vop_getextattr = squashfs_getextattr, + .vop_listextattr = squashfs_listextattr, +}; + +VFS_VOP_VECTOR_REGISTER(squashfs_vnodeops); diff --git a/sys/fs/squashfs/squashfs_xattr.h b/sys/fs/squashfs/squashfs_xattr.h new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_xattr.h @@ -0,0 +1,81 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef SQUASHFS_XATTR_H +#define SQUASHFS_XATTR_H + +struct sqsh_prefix { + const char *pref; + size_t len; +}; + +static struct sqsh_prefix sqsh_xattr_prefixes[] = { + { "user.", 5 }, + { "trusted.", 8 }, + { "security.", 9 }, +}; + +typedef enum { CURS_VSIZE = 1, CURS_VAL = 2, CURS_NEXT = 4 } sqsh_xattr_curs; + +struct sqsh_xattr { + struct sqsh_mount *ump; + int cursors; + struct sqsh_block_run c_name; + struct sqsh_block_run c_vsize; + struct sqsh_block_run c_val; + struct sqsh_block_run c_next; + size_t remain; + struct sqsh_xattr_id info; + uint16_t type; + bool ool; + struct sqsh_xattr_entry entry; + struct sqsh_xattr_val val; +}; + +sqsh_err sqsh_init_xattr(struct sqsh_mount *ump); + +sqsh_err sqsh_xattr_open(struct sqsh_mount *ump, struct sqsh_inode *inode, + struct sqsh_xattr *x); + +sqsh_err sqsh_xattr_read(struct sqsh_xattr *x); + +/* Helper functions on sqsh_xattr */ +size_t sqsh_xattr_name_size(struct sqsh_xattr *x); +sqsh_err sqsh_xattr_name(struct sqsh_xattr *x, char *name, bool prefix); +sqsh_err sqsh_xattr_value_size(struct sqsh_xattr *x, size_t *size); +sqsh_err sqsh_xattr_value(struct sqsh_xattr *x, void *buf); + +static sqsh_err sqsh_xattr_find_prefix(const char *name, uint16_t *type); + +sqsh_err sqsh_xattr_find(struct sqsh_xattr *x, const char *name, bool *found); +sqsh_err sqsh_xattr_lookup(struct sqsh_mount *ump, struct sqsh_inode *inode, + const char *name, struct uio *uio, size_t *size); + +#endif \ No newline at end of file diff --git a/sys/fs/squashfs/squashfs_xattr.c b/sys/fs/squashfs/squashfs_xattr.c new file mode 100644 --- /dev/null +++ b/sys/fs/squashfs/squashfs_xattr.c @@ -0,0 +1,369 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Raghav Sharma + * Parts Copyright (c) 2014 Dave Vasilevsky + * Obtained from the squashfuse project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include "opt_squashfs.h" + +#include +#ifdef _KERNEL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _KERNEL +static MALLOC_DEFINE(M_SQUASHFSEXT, "SQUASHFS xattrs", + "SQUASHFS Extended attributes"); +#endif + +void swapendian_xattr_id_table(struct sqsh_xattr_id_table *temp); +void swapendian_xattr_id(struct sqsh_xattr_id *temp); +void swapendian_xattr_entry(struct sqsh_xattr_entry *temp); +void swapendian_xattr_value(struct sqsh_xattr_val *temp); + +sqsh_err +sqsh_init_xattr(struct sqsh_mount *ump) +{ + off_t start; + size_t data_read; + + start = ump->sb.xattr_id_table_start; + if (start == SQUASHFS_INVALID_BLK) + return SQFS_OK; + data_read = sqsh_io_read_buf(ump, &ump->xattr_info, + sizeof(ump->xattr_info), start); + if (data_read != sizeof(ump->xattr_info)) + return SQFS_ERR; + swapendian_xattr_id_table(&ump->xattr_info); + return sqsh_init_table(&ump->xattr_table, ump, + start + sizeof(ump->xattr_info), sizeof(struct sqsh_xattr_id), + ump->xattr_info.xattr_ids); +} + +sqsh_err +sqsh_xattr_open(struct sqsh_mount *ump, struct sqsh_inode *inode, + struct sqsh_xattr *x) +{ + sqsh_err err; + + x->remain = 0; + if (ump->xattr_info.xattr_ids == 0 || + inode->xattr == SQUASHFS_INVALID_XATTR) + return SQFS_OK; + + err = sqsh_get_table(&ump->xattr_table, ump, inode->xattr, &x->info); + if (err != SQFS_OK) + return SQFS_ERR; + swapendian_xattr_id(&x->info); + + sqsh_metadata_run_inode(&x->c_next, x->info.xattr, + ump->xattr_info.xattr_table_start); + + x->ump = ump; + x->remain = x->info.count; + x->cursors = CURS_NEXT; + return SQFS_OK; +} + +sqsh_err +sqsh_xattr_read(struct sqsh_xattr *x) +{ + sqsh_err err; + + if (x->remain == 0) + return SQFS_END_OF_DIRECTORY; + + if (!(x->cursors & CURS_NEXT)) { + x->ool = false; + err = sqsh_xattr_value(x, NULL); + if (err != SQFS_OK) + return err; + } + + x->c_name = x->c_next; + err = sqsh_metadata_get(x->ump, &x->c_name, &x->entry, + sizeof(x->entry)); + if (err != SQFS_OK) + return err; + swapendian_xattr_entry(&x->entry); + + x->type = x->entry.type & SQUASHFS_XATTR_PREFIX_MASK; + x->ool = x->entry.type & SQUASHFS_XATTR_VALUE_OOL; + if (x->type > SQFS_XATTR_PREFIX_MAX) + return SQFS_ERR; + + --(x->remain); + x->cursors = 0; + return err; +} + +size_t +sqsh_xattr_name_size(struct sqsh_xattr *x) +{ + return x->entry.size; +} + +sqsh_err +sqsh_xattr_name(struct sqsh_xattr *x, char *name, bool prefix) +{ + sqsh_err err; + + if (name && prefix) { + struct sqsh_prefix *p = &sqsh_xattr_prefixes[x->type]; + memcpy(name, p->pref, p->len); + name += p->len; + } + + x->c_vsize = x->c_name; + err = sqsh_metadata_get(x->ump, &x->c_vsize, name, x->entry.size); + if (err != SQFS_OK) + return err; + + x->cursors |= CURS_VSIZE; + return err; +} + +sqsh_err +sqsh_xattr_value_size(struct sqsh_xattr *x, size_t *size) +{ + sqsh_err err; + + if (!(x->cursors & CURS_VSIZE)) { + err = sqsh_xattr_name(x, NULL, false); + if (err != SQFS_OK) + return err; + } + + x->c_val = x->c_vsize; + err = sqsh_metadata_get(x->ump, &x->c_val, &x->val, sizeof(x->val)); + if (err != SQFS_OK) + return err; + swapendian_xattr_value(&x->val); + + if (x->ool) { + uint64_t pos; + x->c_next = x->c_val; + err = sqsh_metadata_get(x->ump, &x->c_next, &pos, sizeof(pos)); + if (err != SQFS_OK) + return err; + pos = le64toh(pos); + x->cursors |= CURS_NEXT; + + sqsh_metadata_run_inode(&x->c_val, pos, + x->ump->xattr_info.xattr_table_start); + err = sqsh_metadata_get(x->ump, &x->c_val, &x->val, + sizeof(x->val)); + if (err != SQFS_OK) + return err; + swapendian_xattr_value(&x->val); + } + + if (size) + *size = x->val.vsize; + x->cursors |= CURS_VAL; + return err; +} + +sqsh_err +sqsh_xattr_value(struct sqsh_xattr *x, void *buf) +{ + sqsh_err err; + struct sqsh_block_run c; + + if (!(x->cursors & CURS_VAL)) { + err = sqsh_xattr_value_size(x, NULL); + if (err != SQFS_OK) + return err; + } + + c = x->c_val; + err = sqsh_metadata_get(x->ump, &c, buf, x->val.vsize); + if (err != SQFS_OK) + return err; + + if (!x->ool) { + x->c_next = c; + x->cursors |= CURS_NEXT; + } + return err; +} + +static sqsh_err +sqsh_xattr_find_prefix(const char *name, uint16_t *type) +{ + int i; + for (i = 0; i <= SQFS_XATTR_PREFIX_MAX; ++i) { + struct sqsh_prefix *p = &sqsh_xattr_prefixes[i]; + if (strncmp(name, p->pref, p->len) == 0) { + *type = i; + return SQFS_OK; + } + } + return SQFS_ERR; +} + +sqsh_err +sqsh_xattr_find(struct sqsh_xattr *x, const char *name, bool *found) +{ + sqsh_err err; + char *cmp = NULL; + uint16_t type; + size_t len; + + err = sqsh_xattr_find_prefix(name, &type); + + if (err != SQFS_OK) { + *found = false; + return SQFS_OK; + } + + name += sqsh_xattr_prefixes[type].len; + len = strlen(name); + cmp = SQUASHFS_MALLOC(len, M_SQUASHFSEXT, M_WAITOK | M_ZERO); + + while (x->remain) { + err = sqsh_xattr_read(x); + if (err != SQFS_OK) + goto done; + if (x->type != type && x->entry.size != len) + continue; + err = sqsh_xattr_name(x, cmp, false); + if (err != SQFS_OK) + goto done; + if (strncmp(name, cmp, len) == 0) { + *found = true; + goto done; + } + } + *found = false; + +done: + SQUASHFS_FREE(cmp, M_SQUASHFSEXT); + return err; +} + +sqsh_err +sqsh_xattr_lookup(struct sqsh_mount *ump, struct sqsh_inode *inode, + const char *name, struct uio *uio, size_t *size) +{ + sqsh_err err; + bool found; + char *buf = NULL; + + struct sqsh_xattr xattr; + err = sqsh_xattr_open(ump, inode, &xattr); + if (err != SQFS_OK) + return err; + + found = false; + err = sqsh_xattr_find(&xattr, name, &found); + if (err != SQFS_OK) + return err; + if (!found) { + if (size != NULL) + *size = 0; + return err; + } + + size_t real; + err = sqsh_xattr_value_size(&xattr, &real); + if (err != SQFS_OK) + return err; + + buf = SQUASHFS_MALLOC(real, M_SQUASHFSEXT, M_WAITOK | M_ZERO); + + if (buf) { + err = sqsh_xattr_value(&xattr, buf); + if (err != SQFS_OK) { + SQUASHFS_FREE(buf, M_SQUASHFSEXT); + return err; + } + } + + if (size != NULL) + *size = real; + if (uiomove(buf, real, uio) != 0) + err = SQFS_ERR; + + SQUASHFS_FREE(buf, M_SQUASHFSEXT); + return err; +} + +void +swapendian_xattr_id_table(struct sqsh_xattr_id_table *temp) +{ + temp->xattr_table_start = le64toh(temp->xattr_table_start); + temp->xattr_ids = le32toh(temp->xattr_ids); + temp->unused = le32toh(temp->unused); +} + +void +swapendian_xattr_id(struct sqsh_xattr_id *temp) +{ + temp->xattr = le64toh(temp->xattr); + temp->count = le32toh(temp->count); + temp->size = le32toh(temp->size); +} + +void +swapendian_xattr_entry(struct sqsh_xattr_entry *temp) +{ + temp->type = le16toh(temp->type); + temp->size = le16toh(temp->size); +} + +void +swapendian_xattr_value(struct sqsh_xattr_val *temp) +{ + temp->vsize = le32toh(temp->vsize); +} diff --git a/sys/modules/Makefile b/sys/modules/Makefile --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -382,6 +382,7 @@ ${_speaker} \ spi \ ${_splash} \ + squashfs \ ste \ stge \ ${_sume} \ diff --git a/sys/modules/squashfs/Makefile b/sys/modules/squashfs/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/squashfs/Makefile @@ -0,0 +1,33 @@ +.PATH: ${.CURDIR:H:H}/fs/squashfs + +KMOD= squashfs +SRCS= \ + opt_squashfs.h \ + vnode_if.h \ + squashfs_vfsops.c \ + squashfs_vnops.c \ + squashfs_decompressor.c \ + squashfs_block.c \ + squashfs_init.c \ + squashfs_inode.c \ + squashfs_io.c \ + squashfs_dir.c \ + squashfs_file.c \ + squashfs_xattr.c + +.if !defined(KERNBUILDDIR) +CFLAGS+= -DZSTDIO +CFLAGS+= -DGZIO +.ifdef SQUASHFS_DEBUG +CFLAGS+= -DSQUASHFS_DEBUG +.endif +.endif + +SRCS+= opt_gzio.h +SRCS+= opt_zstdio.h + +CFLAGS+= -I${SRCTOP}/sys/fs/squashfs +CFLAGS+= -I${SRCTOP}/sys/contrib/zstd/lib/freebsd +CFLAGS+= -I${SRCTOP}/sys/contrib/zlib + +.include