summaryrefslogtreecommitdiffstats
path: root/squashfs-tools
diff options
context:
space:
mode:
Diffstat (limited to 'squashfs-tools')
-rw-r--r--squashfs-tools/.gitignore5
-rwxr-xr-xsquashfs-tools/Makefile470
-rw-r--r--squashfs-tools/action.c3574
-rw-r--r--squashfs-tools/action.h351
-rw-r--r--squashfs-tools/caches-queues-lists.c647
-rw-r--r--squashfs-tools/caches-queues-lists.h200
-rw-r--r--squashfs-tools/compressor.c145
-rw-r--r--squashfs-tools/compressor.h132
-rw-r--r--squashfs-tools/date.c129
-rw-r--r--squashfs-tools/date.h28
-rw-r--r--squashfs-tools/endian_compat.h34
-rw-r--r--squashfs-tools/error.h43
-rw-r--r--squashfs-tools/fnmatch_compat.h32
-rw-r--r--squashfs-tools/gzip_wrapper.c513
-rw-r--r--squashfs-tools/gzip_wrapper.h69
-rw-r--r--squashfs-tools/info.c174
-rw-r--r--squashfs-tools/info.h30
-rw-r--r--squashfs-tools/lz4_wrapper.c307
-rw-r--r--squashfs-tools/lz4_wrapper.h55
-rw-r--r--squashfs-tools/lzma_wrapper.c127
-rw-r--r--squashfs-tools/lzma_xz_wrapper.c169
-rw-r--r--squashfs-tools/lzo_wrapper.c450
-rw-r--r--squashfs-tools/lzo_wrapper.h72
-rw-r--r--squashfs-tools/merge_sort.h116
-rw-r--r--squashfs-tools/mksquashfs.c8902
-rw-r--r--squashfs-tools/mksquashfs.h285
-rw-r--r--squashfs-tools/mksquashfs_error.h74
-rw-r--r--squashfs-tools/process_fragments.c373
-rw-r--r--squashfs-tools/process_fragments.h28
-rw-r--r--squashfs-tools/progressbar.c300
-rw-r--r--squashfs-tools/progressbar.h35
-rw-r--r--squashfs-tools/pseudo.c1376
-rw-r--r--squashfs-tools/pseudo.h107
-rw-r--r--squashfs-tools/pseudo_xattr.c176
-rw-r--r--squashfs-tools/read_fs.c1090
-rw-r--r--squashfs-tools/read_fs.h36
-rw-r--r--squashfs-tools/read_xattrs.c454
-rw-r--r--squashfs-tools/reader.c715
-rw-r--r--squashfs-tools/reader.h39
-rw-r--r--squashfs-tools/restore.c168
-rw-r--r--squashfs-tools/restore.h28
-rw-r--r--squashfs-tools/signals.h54
-rw-r--r--squashfs-tools/sort.c373
-rw-r--r--squashfs-tools/sort.h37
-rw-r--r--squashfs-tools/squashfs_compat.h833
-rw-r--r--squashfs-tools/squashfs_fs.h502
-rw-r--r--squashfs-tools/squashfs_swap.h425
-rw-r--r--squashfs-tools/swap.c117
-rw-r--r--squashfs-tools/tar.c1682
-rw-r--r--squashfs-tools/tar.h153
-rw-r--r--squashfs-tools/tar_xattr.c122
-rw-r--r--squashfs-tools/unsquash-1.c582
-rw-r--r--squashfs-tools/unsquash-12.c30
-rw-r--r--squashfs-tools/unsquash-123.c79
-rw-r--r--squashfs-tools/unsquash-1234.c95
-rw-r--r--squashfs-tools/unsquash-2.c715
-rw-r--r--squashfs-tools/unsquash-3.c824
-rw-r--r--squashfs-tools/unsquash-34.c183
-rw-r--r--squashfs-tools/unsquash-4.c832
-rw-r--r--squashfs-tools/unsquashfs.c4655
-rw-r--r--squashfs-tools/unsquashfs.h345
-rw-r--r--squashfs-tools/unsquashfs_error.h64
-rw-r--r--squashfs-tools/unsquashfs_info.c125
-rw-r--r--squashfs-tools/unsquashfs_info.h30
-rw-r--r--squashfs-tools/unsquashfs_xattr.c302
-rw-r--r--squashfs-tools/version.mk2
-rw-r--r--squashfs-tools/xattr.c1322
-rw-r--r--squashfs-tools/xattr.h241
-rw-r--r--squashfs-tools/xz_wrapper.c551
-rw-r--r--squashfs-tools/xz_wrapper.h65
-rw-r--r--squashfs-tools/zstd_wrapper.c265
-rw-r--r--squashfs-tools/zstd_wrapper.h42
72 files changed, 37705 insertions, 0 deletions
diff --git a/squashfs-tools/.gitignore b/squashfs-tools/.gitignore
new file mode 100644
index 0000000..a2c80e8
--- /dev/null
+++ b/squashfs-tools/.gitignore
@@ -0,0 +1,5 @@
+*.o
+mksquashfs
+sqfscat
+sqfstar
+unsquashfs
diff --git a/squashfs-tools/Makefile b/squashfs-tools/Makefile
new file mode 100755
index 0000000..9aa4381
--- /dev/null
+++ b/squashfs-tools/Makefile
@@ -0,0 +1,470 @@
+# Squashfs-tools 4.6.1 release
+RELEASE_VERSION = 4.6.1
+RELEASE_DATE = 2023/03/25
+###############################################
+# Build options #
+###############################################
+#
+# Edit the following definitions to customise the build, or run
+# "CONFIG=1 make" with the build options below to customise the build on
+# the command line.
+#
+ifndef CONFIG
+############# Building gzip support ###########
+#
+# Gzip support is by default enabled, and the compression type default
+# (COMP_DEFAULT) is gzip.
+#
+# If you don't want/need gzip support then comment out the GZIP SUPPORT line
+# below, and change COMP_DEFAULT to one of the compression types you have
+# selected.
+#
+# Obviously, you must select at least one of the available gzip, xz, lzo,
+# lz4, zstd or lzma (deprecated) compression types.
+#
+GZIP_SUPPORT = 1
+
+########### Building XZ support #############
+#
+# LZMA2 compression.
+#
+# XZ Utils liblzma (http://tukaani.org/xz/) is supported
+#
+# Development packages (libraries and header files) should be
+# supported by most modern distributions. Please refer to
+# your distribution package manager.
+#
+# To build install the library and uncomment
+# the XZ_SUPPORT line below.
+#
+#XZ_SUPPORT = 1
+
+
+############ Building LZO support ##############
+#
+# The LZO library (http://www.oberhumer.com/opensource/lzo/) is supported.
+#
+# Development packages (libraries and header files) should be
+# supported by most modern distributions. Please refer to
+# your distribution package manager.
+#
+# To build install the library and uncomment
+# the LZO_SUPPORT line below.
+#
+#LZO_SUPPORT = 1
+
+
+########### Building LZ4 support #############
+#
+# Yann Collet's LZ4 tools are supported
+# LZ4 homepage: https://lz4.github.io/lz4/
+# LZ4 source repository: https://github.com/lz4/lz4/
+#
+# Development packages (libraries and header files) should be
+# supported by most modern distributions. Please refer to
+# your distribution package manager.
+#
+# To build install and uncomment
+# the LZ4_SUPPORT line below.
+#
+#LZ4_SUPPORT = 1
+
+
+########### Building ZSTD support ############
+#
+# The ZSTD library is supported
+# ZSTD homepage: https://facebook.github.io/zstd/
+# ZSTD source repository: https://github.com/facebook/zstd
+#
+# Development packages (libraries and header files) should be
+# supported by most modern distributions. Please refer to
+# your distribution package manager.
+#
+# To build install the library and uncomment
+# the ZSTD_SUPPORT line below.
+#
+#ZSTD_SUPPORT = 1
+
+
+######## Specifying default compression ########
+#
+# The next line specifies which compression algorithm is used by default
+# in Mksquashfs. Obviously the compression algorithm must have been
+# selected to be built
+#
+COMP_DEFAULT = gzip
+
+
+###############################################
+# Extended attribute (XATTRs) build options #
+###############################################
+#
+# Building XATTR support for Mksquashfs and Unsquashfs
+#
+# If your C library or build/target environment doesn't support XATTRs then
+# comment out the next line to build Mksquashfs and Unsquashfs without XATTR
+# support
+XATTR_SUPPORT = 1
+
+# Select whether you wish xattrs to be stored by Mksquashfs and extracted
+# by Unsquashfs by default. If selected users can disable xattr support by
+# using the -no-xattrs option
+#
+# If unselected, Mksquashfs/Unsquashfs won't store and extract xattrs by
+# default. Users can enable xattrs by using the -xattrs option.
+XATTR_DEFAULT = 1
+
+
+###############################################
+# Reproducible Image options #
+###############################################
+#
+# Select whether you wish reproducible builds by default. If selected users
+# can disable reproducible builds using the not-reproducible option.
+# If not selected, users can enable reproducible builds using the
+# -reproducible option
+REPRODUCIBLE_DEFAULT = 1
+
+###############################################
+# Manpage generation #
+###############################################
+#
+# If help2man is available on the system, on installation, the Makefile
+# will try to generate "custom" manpages from the built squashfs-tools.
+#
+# If the squashfs-tools have been cross-compiled, or for any other
+# reason they're not executable, this will generate errors at
+# installation and the install script will fall back to using
+# pre-built manpages.
+#
+# Change next variable to "y" to use the pre-built manpages by default,
+# and not attempt to generate "custom" manpages. This will eliminate
+# errors and warnings at install time.
+USE_PREBUILT_MANPAGES=n
+
+###############################################
+# INSTALL PATHS #
+###############################################
+#
+# Alter INSTALL_* to install binaries and manpages
+# elsewhere.
+#
+# To skip building and installing manpages,
+# unset INSTALL_MANPAGES_DIR or set to ""
+#
+INSTALL_PREFIX = /usr/local
+INSTALL_DIR = $(INSTALL_PREFIX)/bin
+INSTALL_MANPAGES_DIR = $(INSTALL_PREFIX)/man/man1
+
+###############################################
+# Obsolete options #
+###############################################
+
+########### Building LZMA support #############
+#
+# LZMA1 compression.
+#
+# LZMA1 compression is obsolete, and the newer and better XZ (LZMA2)
+# compression should be used in preference.
+#
+# Both XZ Utils liblzma (http://tukaani.org/xz/) and LZMA SDK
+# (http://www.7-zip.org/sdk.html) are supported
+#
+# To build using XZ Utils liblzma - install the library and uncomment
+# the LZMA_XZ_SUPPORT line below.
+#
+# To build using the LZMA SDK (4.65 used in development, other versions may
+# work) - download and unpack it, uncomment and set LZMA_DIR to unpacked source,
+# and uncomment the LZMA_SUPPORT line below.
+#
+#LZMA_XZ_SUPPORT = 1
+#LZMA_SUPPORT = 1
+#LZMA_DIR = ../../../../LZMA/lzma465
+else
+GZIP_SUPPORT ?= 1
+ZX_SUPPORT ?= 0
+LZO_SUPPORT ?= 0
+LZ4_SUPPORT ?= 0
+ZSTD_SUPPORT ?= 0
+COMP_DEFAULT ?= gzip
+XATTR_SUPPORT ?= 1
+XATTR_DEFAULT ?= 1
+REPRODUCIBLE_DEFAULT ?= 1
+USE_PREBUILT_MANPAGES ?= 1
+INSTALL_PREFIX ?= /usr/local
+INSTALL_DIR ?= $(INSTALL_PREFIX)/bin
+INSTALL_MANPAGES_DIR ?= $(INSTALL_PREFIX)/man/man1
+LZMA_XZ_SUPPORT ?= 0
+LZMA_SUPPORT ?= 0
+LZMA_DIR ?= ../../../../LZMA/lzma465
+endif
+
+
+###############################################
+# End of BUILD options section #
+###############################################
+#
+INCLUDEDIR = -I.
+
+MKSQUASHFS_OBJS = mksquashfs.o read_fs.o action.o swap.o pseudo.o compressor.o \
+ sort.o progressbar.o info.o restore.o process_fragments.o \
+ caches-queues-lists.o reader.o tar.o date.o
+
+UNSQUASHFS_OBJS = unsquashfs.o unsquash-1.o unsquash-2.o unsquash-3.o \
+ unsquash-4.o unsquash-123.o unsquash-34.o unsquash-1234.o unsquash-12.o \
+ swap.o compressor.o unsquashfs_info.o date.o
+
+CFLAGS ?= -O2
+CFLAGS += $(EXTRA_CFLAGS) $(INCLUDEDIR) -D_FILE_OFFSET_BITS=64 \
+ -D_LARGEFILE_SOURCE -D_GNU_SOURCE -DCOMP_DEFAULT=\"$(COMP_DEFAULT)\" \
+ -Wall
+
+LIBS = -lpthread -lm
+ifeq ($(GZIP_SUPPORT),1)
+CFLAGS += -DGZIP_SUPPORT
+MKSQUASHFS_OBJS += gzip_wrapper.o
+UNSQUASHFS_OBJS += gzip_wrapper.o
+LIBS += -lz
+COMPRESSORS += gzip
+endif
+
+ifeq ($(LZMA_SUPPORT),1)
+LZMA_OBJS = $(LZMA_DIR)/C/Alloc.o $(LZMA_DIR)/C/LzFind.o \
+ $(LZMA_DIR)/C/LzmaDec.o $(LZMA_DIR)/C/LzmaEnc.o $(LZMA_DIR)/C/LzmaLib.o
+INCLUDEDIR += -I$(LZMA_DIR)/C
+CFLAGS += -DLZMA_SUPPORT
+MKSQUASHFS_OBJS += lzma_wrapper.o $(LZMA_OBJS)
+UNSQUASHFS_OBJS += lzma_wrapper.o $(LZMA_OBJS)
+COMPRESSORS += lzma
+endif
+
+ifeq ($(LZMA_XZ_SUPPORT),1)
+CFLAGS += -DLZMA_SUPPORT
+MKSQUASHFS_OBJS += lzma_xz_wrapper.o
+UNSQUASHFS_OBJS += lzma_xz_wrapper.o
+LIBS += -llzma
+COMPRESSORS += lzma
+endif
+
+ifeq ($(XZ_SUPPORT),1)
+CFLAGS += -DXZ_SUPPORT
+MKSQUASHFS_OBJS += xz_wrapper.o
+UNSQUASHFS_OBJS += xz_wrapper.o
+LIBS += -llzma
+COMPRESSORS += xz
+endif
+
+ifeq ($(LZO_SUPPORT),1)
+CFLAGS += -DLZO_SUPPORT
+MKSQUASHFS_OBJS += lzo_wrapper.o
+UNSQUASHFS_OBJS += lzo_wrapper.o
+LIBS += -llzo2
+COMPRESSORS += lzo
+endif
+
+ifeq ($(LZ4_SUPPORT),1)
+CFLAGS += -DLZ4_SUPPORT
+MKSQUASHFS_OBJS += lz4_wrapper.o
+UNSQUASHFS_OBJS += lz4_wrapper.o
+LIBS += -llz4
+COMPRESSORS += lz4
+endif
+
+ifeq ($(ZSTD_SUPPORT),1)
+CFLAGS += -DZSTD_SUPPORT
+MKSQUASHFS_OBJS += zstd_wrapper.o
+UNSQUASHFS_OBJS += zstd_wrapper.o
+LIBS += -lzstd
+COMPRESSORS += zstd
+endif
+
+ifeq ($(XATTR_SUPPORT),1)
+ifeq ($(XATTR_DEFAULT),1)
+CFLAGS += -DXATTR_SUPPORT -DXATTR_DEFAULT
+else
+CFLAGS += -DXATTR_SUPPORT
+endif
+MKSQUASHFS_OBJS += xattr.o read_xattrs.o tar_xattr.o pseudo_xattr.o
+UNSQUASHFS_OBJS += read_xattrs.o unsquashfs_xattr.o
+endif
+
+ifeq ($(REPRODUCIBLE_DEFAULT),1)
+CFLAGS += -DREPRODUCIBLE_DEFAULT
+endif
+
+#
+# If LZMA_SUPPORT is specified then LZMA_DIR must be specified too
+#
+ifeq ($(LZMA_SUPPORT),1)
+ifndef LZMA_DIR
+$(error "LZMA_SUPPORT requires LZMA_DIR to be also defined")
+endif
+endif
+
+#
+# Both LZMA_XZ_SUPPORT and LZMA_SUPPORT cannot be specified
+#
+ifeq ($(LZMA_XZ_SUPPORT),1)
+ifeq ($(LZMA_SUPPORT),1)
+$(error "Both LZMA_XZ_SUPPORT and LZMA_SUPPORT cannot be specified")
+endif
+endif
+
+#
+# At least one compressor must have been selected
+#
+ifndef COMPRESSORS
+$(error "No compressor selected! Select one or more of GZIP, LZMA, XZ, LZO, \
+ LZ4 or ZSTD!")
+endif
+
+#
+# COMP_DEFAULT should be defined
+#
+ifndef COMP_DEFAULT
+$(error "COMP_DEFAULT must be set to a compressor!")
+endif
+
+#
+# COMP_DEFAULT must be a selected compressor
+#
+ifeq (, $(findstring $(COMP_DEFAULT), $(COMPRESSORS)))
+$(error "COMP_DEFAULT is set to ${COMP_DEFAULT}, which isn't selected to be \
+ built!")
+endif
+
+#
+# Get VERSION and DATE for Mksquashfs/Unsquashfs version strings.
+#
+# If RELEASE_VERSION/RELEASE_DATE set, use them.
+#
+# If not set, this is a development version, therefore
+#
+# If this source has been exported by "git archive" use automatically
+# expanded strings.
+#
+# Otherwise ask git for the details from current HEAD.
+#
+include version.mk
+
+ifdef RELEASE_VERSION
+VERSION := $(RELEASE_VERSION)
+DATE := $(RELEASE_DATE)
+else
+ifeq ($(HASH),$Format:%h$)
+VERSION := 4.6.1-$(shell git show -s --pretty=format:%h)
+DATE := $(firstword $(subst -,/,$(shell git show -s --pretty=format:%ci)))
+else
+VERSION := 4.6-$(HASH)
+DATE := $(firstword $(FULLDATE))
+endif
+endif
+
+YEAR := $(firstword $(subst /, , $(DATE)))
+
+CFLAGS += -DVERSION=\"$(VERSION)\" -DDATE=\"$(DATE)\" -DYEAR=\"$(YEAR)\"
+
+.PHONY: all
+all: mksquashfs unsquashfs
+
+mksquashfs: $(MKSQUASHFS_OBJS)
+ $(CC) $(LDFLAGS) $(EXTRA_LDFLAGS) $(MKSQUASHFS_OBJS) $(LIBS) -o $@
+ ln -sf mksquashfs sqfstar
+
+mksquashfs.o: Makefile mksquashfs.c squashfs_fs.h squashfs_swap.h mksquashfs.h \
+ sort.h pseudo.h compressor.h xattr.h action.h mksquashfs_error.h progressbar.h \
+ info.h caches-queues-lists.h read_fs.h restore.h process_fragments.h
+
+reader.o: squashfs_fs.h mksquashfs.h caches-queues-lists.h progressbar.h \
+ mksquashfs_error.h pseudo.h sort.h
+
+read_fs.o: read_fs.c squashfs_fs.h squashfs_swap.h compressor.h xattr.h \
+ mksquashfs_error.h mksquashfs.h
+
+sort.o: sort.c squashfs_fs.h mksquashfs.h sort.h mksquashfs_error.h progressbar.h
+
+swap.o: swap.c
+
+pseudo.o: pseudo.c pseudo.h mksquashfs_error.h progressbar.h
+
+pseudo_xattr.o: pseudo_xattr.c xattr.h mksquashfs_error.h progressbar.h
+
+compressor.o: Makefile compressor.c compressor.h squashfs_fs.h
+
+xattr.o: xattr.c squashfs_fs.h squashfs_swap.h mksquashfs.h xattr.h mksquashfs_error.h \
+ progressbar.h
+
+read_xattrs.o: read_xattrs.c squashfs_fs.h squashfs_swap.h xattr.h error.h
+
+action.o: action.c squashfs_fs.h mksquashfs.h action.h mksquashfs_error.h
+
+progressbar.o: progressbar.c mksquashfs_error.h
+
+info.o: info.c squashfs_fs.h mksquashfs.h mksquashfs_error.h progressbar.h \
+ caches-queues-lists.h
+
+restore.o: restore.c caches-queues-lists.h squashfs_fs.h mksquashfs.h mksquashfs_error.h \
+ progressbar.h info.h
+
+process_fragments.o: process_fragments.c process_fragments.h
+
+caches-queues-lists.o: caches-queues-lists.c mksquashfs_error.h caches-queues-lists.h
+
+tar.o: tar.h
+
+tar_xattr.o: tar.h xattr.h
+
+date.o: date.h error.h
+
+gzip_wrapper.o: gzip_wrapper.c squashfs_fs.h gzip_wrapper.h compressor.h
+
+lzma_wrapper.o: lzma_wrapper.c compressor.h squashfs_fs.h
+
+lzma_xz_wrapper.o: lzma_xz_wrapper.c compressor.h squashfs_fs.h
+
+lzo_wrapper.o: lzo_wrapper.c squashfs_fs.h lzo_wrapper.h compressor.h
+
+lz4_wrapper.o: lz4_wrapper.c squashfs_fs.h lz4_wrapper.h compressor.h
+
+xz_wrapper.o: xz_wrapper.c squashfs_fs.h xz_wrapper.h compressor.h
+
+unsquashfs: $(UNSQUASHFS_OBJS)
+ $(CC) $(LDFLAGS) $(EXTRA_LDFLAGS) $(UNSQUASHFS_OBJS) $(LIBS) -o $@
+ ln -sf unsquashfs sqfscat
+
+unsquashfs.o: unsquashfs.h unsquashfs.c squashfs_fs.h squashfs_swap.h \
+ squashfs_compat.h xattr.h read_fs.h compressor.h unsquashfs_error.h
+
+unsquash-1.o: unsquashfs.h unsquash-1.c squashfs_fs.h squashfs_compat.h unsquashfs_error.h
+
+unsquash-2.o: unsquashfs.h unsquash-2.c squashfs_fs.h squashfs_compat.h unsquashfs_error.h
+
+unsquash-3.o: unsquashfs.h unsquash-3.c squashfs_fs.h squashfs_compat.h unsquashfs_error.h
+
+unsquash-4.o: unsquashfs.h unsquash-4.c squashfs_fs.h squashfs_swap.h \
+ read_fs.h unsquashfs_error.h
+
+unsquash-123.o: unsquashfs.h unsquash-123.c squashfs_fs.h squashfs_compat.h unsquashfs_error.h
+
+unsquash-34.o: unsquashfs.h unsquash-34.c unsquashfs_error.h
+
+unsquash-1234.o: unsquash-1234.c unsquashfs_error.h
+
+unsquash-12.o: unsquash-12.c unsquashfs.h
+
+unsquashfs_xattr.o: unsquashfs_xattr.c unsquashfs.h squashfs_fs.h xattr.h unsquashfs_error.h
+
+unsquashfs_info.o: unsquashfs.h squashfs_fs.h unsquashfs_error.h
+
+.PHONY: clean
+clean:
+ -rm -f *.o mksquashfs unsquashfs sqfstar sqfscat *.1
+
+.PHONY: install
+install: mksquashfs unsquashfs
+ mkdir -p $(INSTALL_DIR)
+ cp mksquashfs $(INSTALL_DIR)
+ cp unsquashfs $(INSTALL_DIR)
+ ln -fs unsquashfs $(INSTALL_DIR)/sqfscat
+ ln -fs mksquashfs $(INSTALL_DIR)/sqfstar
+ ../generate-manpages/install-manpages.sh $(shell pwd)/.. "$(INSTALL_MANPAGES_DIR)" "$(USE_PREBUILT_MANPAGES)"
diff --git a/squashfs-tools/action.c b/squashfs-tools/action.c
new file mode 100644
index 0000000..182487b
--- /dev/null
+++ b/squashfs-tools/action.c
@@ -0,0 +1,3574 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2011, 2012, 2013, 2014, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * action.c
+ */
+
+#include <fcntl.h>
+#include <dirent.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/wait.h>
+#include <regex.h>
+#include <limits.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "action.h"
+#include "mksquashfs_error.h"
+#include "fnmatch_compat.h"
+#include "xattr.h"
+
+#define TRUE 1
+#define FALSE 0
+#define MAX_LINE 16384
+
+/*
+ * code to parse actions
+ */
+
+static char *cur_ptr, *source;
+static struct action *fragment_spec = NULL;
+static struct action *exclude_spec = NULL;
+static struct action *empty_spec = NULL;
+static struct action *move_spec = NULL;
+static struct action *prune_spec = NULL;
+static struct action *xattr_exc_spec = NULL;
+static struct action *xattr_inc_spec = NULL;
+static struct action *xattr_add_spec = NULL;
+static struct action *other_spec = NULL;
+static int fragment_count = 0;
+static int exclude_count = 0;
+static int empty_count = 0;
+static int move_count = 0;
+static int prune_count = 0;
+static int xattr_exc_count = 0;
+static int xattr_inc_count = 0;
+static int xattr_add_count = 0;
+static int other_count = 0;
+static struct action_entry *parsing_action;
+
+static struct file_buffer *def_fragment = NULL;
+static struct file_buffer *tail_fragment = NULL;
+
+static struct token_entry token_table[] = {
+ { "(", TOK_OPEN_BRACKET, 1, },
+ { ")", TOK_CLOSE_BRACKET, 1 },
+ { "&&", TOK_AND, 2 },
+ { "||", TOK_OR, 2 },
+ { "!", TOK_NOT, 1 },
+ { ",", TOK_COMMA, 1 },
+ { "@", TOK_AT, 1},
+ { " ", TOK_WHITE_SPACE, 1 },
+ { "\t ", TOK_WHITE_SPACE, 1 },
+ { "", -1, 0 }
+};
+
+
+static struct test_entry test_table[];
+
+static struct action_entry action_table[];
+
+static struct expr *parse_expr(int subexp);
+
+extern char *pathname(struct dir_ent *);
+
+/*
+ * Read a file, passing each line to parse_line() for
+ * parsing.
+ *
+ * Lines can be split across multiple lines using "\".
+ *
+ * Blank lines and comment lines indicated by # are supported.
+ */
+static int read_file(char *filename, char *type, int (parse_line)(char *))
+{
+ FILE *fd;
+ char *def, *err, *line = NULL;
+ int res, size = 0;
+
+ fd = fopen(filename, "r");
+ if(fd == NULL) {
+ ERROR("Could not open %s device file \"%s\" because %s\n",
+ type, filename, strerror(errno));
+ return FALSE;
+ }
+
+ while(1) {
+ int total = 0;
+
+ while(1) {
+ int len;
+
+ if(total + (MAX_LINE + 1) > size) {
+ line = realloc(line, size += (MAX_LINE + 1));
+ if(line == NULL)
+ MEM_ERROR();
+ }
+
+ err = fgets(line + total, MAX_LINE + 1, fd);
+ if(err == NULL)
+ break;
+
+ len = strlen(line + total);
+ total += len;
+
+ if(len == MAX_LINE && line[total - 1] != '\n') {
+ /* line too large */
+ ERROR("Line too long when reading "
+ "%s file \"%s\", larger than "
+ "%d bytes\n", type, filename, MAX_LINE);
+ goto failed;
+ }
+
+ /*
+ * Remove '\n' terminator if it exists (the last line
+ * in the file may not be '\n' terminated)
+ */
+ if(len && line[total - 1] == '\n') {
+ line[-- total] = '\0';
+ len --;
+ }
+
+ /*
+ * If no line continuation then jump out to
+ * process line. Note, we have to be careful to
+ * check for "\\" (backslashed backslash) and to
+ * ensure we don't look at the previous line
+ */
+ if(len == 0 || line[total - 1] != '\\' || (len >= 2 &&
+ strcmp(line + total - 2, "\\\\") == 0))
+ break;
+ else
+ total --;
+ }
+
+ if(err == NULL) {
+ if(ferror(fd)) {
+ ERROR("Reading %s file \"%s\" failed "
+ "because %s\n", type, filename,
+ strerror(errno));
+ goto failed;
+ }
+
+ /*
+ * At EOF, normally we'll be finished, but, have to
+ * check for special case where we had "\" line
+ * continuation and then hit EOF immediately afterwards
+ */
+ if(total == 0)
+ break;
+ else
+ line[total] = '\0';
+ }
+
+ /* Skip any leading whitespace */
+ for(def = line; isspace(*def); def ++);
+
+ /* if line is now empty after skipping characters, skip it */
+ if(*def == '\0')
+ continue;
+
+ /* if comment line, skip */
+ if(*def == '#')
+ continue;
+
+ res = parse_line(def);
+ if(res == FALSE)
+ goto failed;
+ }
+
+ fclose(fd);
+ free(line);
+ return TRUE;
+
+failed:
+ fclose(fd);
+ free(line);
+ return FALSE;
+}
+/*
+ * Lexical analyser
+ */
+#define STR_SIZE 256
+
+static int get_token(char **string)
+{
+ /* string buffer */
+ static char *str = NULL;
+ static int size = 0;
+
+ char *str_ptr;
+ int cur_size, i, quoted;
+
+ while (1) {
+ if (*cur_ptr == '\0')
+ return TOK_EOF;
+ for (i = 0; token_table[i].token != -1; i++)
+ if (strncmp(cur_ptr, token_table[i].string,
+ token_table[i].size) == 0)
+ break;
+ if (token_table[i].token != TOK_WHITE_SPACE)
+ break;
+ cur_ptr ++;
+ }
+
+ if (token_table[i].token != -1) {
+ cur_ptr += token_table[i].size;
+ return token_table[i].token;
+ }
+
+ /* string */
+ if(str == NULL) {
+ str = malloc(STR_SIZE);
+ if(str == NULL)
+ MEM_ERROR();
+ size = STR_SIZE;
+ }
+
+ /* Initialise string being read */
+ str_ptr = str;
+ cur_size = 0;
+ quoted = 0;
+
+ while(1) {
+ while(*cur_ptr == '"') {
+ cur_ptr ++;
+ quoted = !quoted;
+ }
+
+ if(*cur_ptr == '\0') {
+ /* inside quoted string EOF, otherwise end of string */
+ if(quoted)
+ return TOK_EOF;
+ else
+ break;
+ }
+
+ if(!quoted) {
+ for(i = 0; token_table[i].token != -1; i++)
+ if (strncmp(cur_ptr, token_table[i].string,
+ token_table[i].size) == 0)
+ break;
+ if (token_table[i].token != -1)
+ break;
+ }
+
+ if(*cur_ptr == '\\') {
+ cur_ptr ++;
+ if(*cur_ptr == '\0')
+ return TOK_EOF;
+ }
+
+ if(cur_size + 2 > size) {
+ char *tmp;
+ int offset = str_ptr - str;
+
+ size = (cur_size + 1 + STR_SIZE) & ~(STR_SIZE - 1);
+
+ tmp = realloc(str, size);
+ if(tmp == NULL)
+ MEM_ERROR();
+
+ str_ptr = tmp + offset;
+ str = tmp;
+ }
+
+ *str_ptr ++ = *cur_ptr ++;
+ cur_size ++;
+ }
+
+ *str_ptr = '\0';
+ *string = str;
+ return TOK_STRING;
+}
+
+
+static int peek_token(char **string)
+{
+ char *saved = cur_ptr;
+ int token = get_token(string);
+
+ cur_ptr = saved;
+
+ return token;
+}
+
+
+/*
+ * Expression parser
+ */
+static void free_parse_tree(struct expr *expr)
+{
+ if(expr->type == ATOM_TYPE) {
+ int i;
+
+ for(i = 0; i < expr->atom.test->args; i++)
+ free(expr->atom.argv[i]);
+
+ free(expr->atom.argv);
+ } else if (expr->type == UNARY_TYPE)
+ free_parse_tree(expr->unary_op.expr);
+ else {
+ free_parse_tree(expr->expr_op.lhs);
+ free_parse_tree(expr->expr_op.rhs);
+ }
+
+ free(expr);
+}
+
+
+static struct expr *create_expr(struct expr *lhs, int op, struct expr *rhs)
+{
+ struct expr *expr;
+
+ if (rhs == NULL) {
+ free_parse_tree(lhs);
+ return NULL;
+ }
+
+ expr = malloc(sizeof(*expr));
+ if (expr == NULL)
+ MEM_ERROR();
+
+ expr->type = OP_TYPE;
+ expr->expr_op.lhs = lhs;
+ expr->expr_op.rhs = rhs;
+ expr->expr_op.op = op;
+
+ return expr;
+}
+
+
+static struct expr *create_unary_op(struct expr *lhs, int op)
+{
+ struct expr *expr;
+
+ if (lhs == NULL)
+ return NULL;
+
+ expr = malloc(sizeof(*expr));
+ if (expr == NULL)
+ MEM_ERROR();
+
+ expr->type = UNARY_TYPE;
+ expr->unary_op.expr = lhs;
+ expr->unary_op.op = op;
+
+ return expr;
+}
+
+
+static struct expr *parse_test(char *name)
+{
+ char *string, **argv = NULL;
+ int token, args = 0;
+ int i;
+ struct test_entry *test;
+ struct expr *expr;
+
+ for (i = 0; test_table[i].args != -1; i++)
+ if (strcmp(name, test_table[i].name) == 0)
+ break;
+
+ test = &test_table[i];
+
+ if (test->args == -1) {
+ SYNTAX_ERROR("Non-existent test \"%s\"\n", name);
+ return NULL;
+ }
+
+ if(parsing_action->type == EXCLUDE_ACTION && !test->exclude_ok) {
+ fprintf(stderr, "Failed to parse action \"%s\"\n", source);
+ fprintf(stderr, "Test \"%s\" cannot be used in exclude "
+ "actions\n", name);
+ fprintf(stderr, "Use prune action instead ...\n");
+ return NULL;
+ }
+
+ expr = malloc(sizeof(*expr));
+ if (expr == NULL)
+ MEM_ERROR();
+
+ expr->type = ATOM_TYPE;
+
+ expr->atom.test = test;
+ expr->atom.data = NULL;
+
+ /*
+ * If the test has no arguments, then go straight to checking if there's
+ * enough arguments
+ */
+ token = peek_token(&string);
+
+ if (token != TOK_OPEN_BRACKET)
+ goto skip_args;
+
+ get_token(&string);
+
+ /*
+ * speculatively read all the arguments, and then see if the
+ * number of arguments read is the number expected, this handles
+ * tests with a variable number of arguments
+ */
+ token = get_token(&string);
+ if (token == TOK_CLOSE_BRACKET)
+ goto skip_args;
+
+ while(1) {
+ if (token != TOK_STRING) {
+ SYNTAX_ERROR("Unexpected token \"%s\", expected "
+ "argument\n", TOK_TO_STR(token, string));
+ goto failed;
+ }
+
+ argv = realloc(argv, (args + 1) * sizeof(char *));
+ if (argv == NULL)
+ MEM_ERROR();
+
+ argv[args ++ ] = strdup(string);
+
+ token = get_token(&string);
+
+ if (token == TOK_CLOSE_BRACKET)
+ break;
+
+ if (token != TOK_COMMA) {
+ SYNTAX_ERROR("Unexpected token \"%s\", expected "
+ "\",\" or \")\"\n", TOK_TO_STR(token, string));
+ goto failed;
+ }
+ token = get_token(&string);
+ }
+
+skip_args:
+ /*
+ * expected number of arguments?
+ */
+ if(test->args != -2 && args != test->args) {
+ SYNTAX_ERROR("Unexpected number of arguments, expected %d, "
+ "got %d\n", test->args, args);
+ goto failed;
+ }
+
+ expr->atom.args = args;
+ expr->atom.argv = argv;
+
+ if (test->parse_args) {
+ int res = test->parse_args(test, &expr->atom);
+
+ if (res == 0)
+ goto failed;
+ }
+
+ return expr;
+
+failed:
+ free(argv);
+ free(expr);
+ return NULL;
+}
+
+
+static struct expr *get_atom()
+{
+ char *string;
+ int token = get_token(&string);
+
+ switch(token) {
+ case TOK_NOT:
+ return create_unary_op(get_atom(), token);
+ case TOK_OPEN_BRACKET:
+ return parse_expr(1);
+ case TOK_STRING:
+ return parse_test(string);
+ default:
+ SYNTAX_ERROR("Unexpected token \"%s\", expected test "
+ "operation, \"!\", or \"(\"\n",
+ TOK_TO_STR(token, string));
+ return NULL;
+ }
+}
+
+
+static struct expr *parse_expr(int subexp)
+{
+ struct expr *expr = get_atom();
+
+ while (expr) {
+ char *string;
+ int op = get_token(&string);
+
+ if (op == TOK_EOF) {
+ if (subexp) {
+ free_parse_tree(expr);
+ SYNTAX_ERROR("Expected \"&&\", \"||\" or "
+ "\")\", got EOF\n");
+ return NULL;
+ }
+ break;
+ }
+
+ if (op == TOK_CLOSE_BRACKET) {
+ if (!subexp) {
+ free_parse_tree(expr);
+ SYNTAX_ERROR("Unexpected \")\", expected "
+ "\"&&\", \"!!\" or EOF\n");
+ return NULL;
+ }
+ break;
+ }
+
+ if (op != TOK_AND && op != TOK_OR) {
+ free_parse_tree(expr);
+ SYNTAX_ERROR("Unexpected token \"%s\", expected "
+ "\"&&\" or \"||\"\n", TOK_TO_STR(op, string));
+ return NULL;
+ }
+
+ expr = create_expr(expr, op, get_atom());
+ }
+
+ return expr;
+}
+
+
+/*
+ * Action parser
+ */
+int parse_action(char *s, int verbose)
+{
+ char *string, **argv = NULL;
+ int i, token, args = 0;
+ struct expr *expr;
+ struct action_entry *action;
+ void *data = NULL;
+ struct action **spec_list;
+ int spec_count;
+
+ cur_ptr = source = s;
+ token = get_token(&string);
+
+ if (token != TOK_STRING) {
+ SYNTAX_ERROR("Unexpected token \"%s\", expected name\n",
+ TOK_TO_STR(token, string));
+ return 0;
+ }
+
+ for (i = 0; action_table[i].args != -1; i++)
+ if (strcmp(string, action_table[i].name) == 0)
+ break;
+
+ if (action_table[i].args == -1) {
+ SYNTAX_ERROR("Non-existent action \"%s\"\n", string);
+ return 0;
+ }
+
+ action = &action_table[i];
+
+ token = get_token(&string);
+
+ if (token == TOK_AT)
+ goto skip_args;
+
+ if (token != TOK_OPEN_BRACKET) {
+ SYNTAX_ERROR("Unexpected token \"%s\", expected \"(\"\n",
+ TOK_TO_STR(token, string));
+ goto failed;
+ }
+
+ /*
+ * speculatively read all the arguments, and then see if the
+ * number of arguments read is the number expected, this handles
+ * actions with a variable number of arguments
+ */
+ token = get_token(&string);
+ if (token == TOK_CLOSE_BRACKET)
+ goto skip_args;
+
+ while (1) {
+ if (token != TOK_STRING) {
+ SYNTAX_ERROR("Unexpected token \"%s\", expected "
+ "argument\n", TOK_TO_STR(token, string));
+ goto failed;
+ }
+
+ argv = realloc(argv, (args + 1) * sizeof(char *));
+ if (argv == NULL)
+ MEM_ERROR();
+
+ argv[args ++] = strdup(string);
+
+ token = get_token(&string);
+
+ if (token == TOK_CLOSE_BRACKET)
+ break;
+
+ if (token != TOK_COMMA) {
+ SYNTAX_ERROR("Unexpected token \"%s\", expected "
+ "\",\" or \")\"\n", TOK_TO_STR(token, string));
+ goto failed;
+ }
+ token = get_token(&string);
+ }
+
+skip_args:
+ /*
+ * expected number of arguments?
+ */
+ if(action->args != -2 && args != action->args) {
+ SYNTAX_ERROR("Unexpected number of arguments, expected %d, "
+ "got %d\n", action->args, args);
+ goto failed;
+ }
+
+ if (action->parse_args) {
+ int res = action->parse_args(action, args, argv, &data);
+
+ if (res == 0)
+ goto failed;
+ }
+
+ if (token == TOK_CLOSE_BRACKET)
+ token = get_token(&string);
+
+ if (token != TOK_AT) {
+ SYNTAX_ERROR("Unexpected token \"%s\", expected \"@\"\n",
+ TOK_TO_STR(token, string));
+ goto failed;
+ }
+
+ parsing_action = action;
+ expr = parse_expr(0);
+
+ if (expr == NULL)
+ goto failed;
+
+ /*
+ * choose action list and increment action counter
+ */
+ switch(action->type) {
+ case FRAGMENT_ACTION:
+ spec_count = fragment_count ++;
+ spec_list = &fragment_spec;
+ break;
+ case EXCLUDE_ACTION:
+ spec_count = exclude_count ++;
+ spec_list = &exclude_spec;
+ break;
+ case EMPTY_ACTION:
+ spec_count = empty_count ++;
+ spec_list = &empty_spec;
+ break;
+ case MOVE_ACTION:
+ spec_count = move_count ++;
+ spec_list = &move_spec;
+ break;
+ case PRUNE_ACTION:
+ spec_count = prune_count ++;
+ spec_list = &prune_spec;
+ break;
+ case XATTR_EXC_ACTION:
+ spec_count = xattr_exc_count ++;
+ spec_list = &xattr_exc_spec;
+ break;
+ case XATTR_INC_ACTION:
+ spec_count = xattr_inc_count ++;
+ spec_list = &xattr_inc_spec;
+ break;
+ case XATTR_ADD_ACTION:
+ spec_count = xattr_add_count ++;
+ spec_list = &xattr_add_spec;
+ break;
+ default:
+ spec_count = other_count ++;
+ spec_list = &other_spec;
+ }
+
+ *spec_list = realloc(*spec_list, (spec_count + 1) *
+ sizeof(struct action));
+ if (*spec_list == NULL)
+ MEM_ERROR();
+
+ (*spec_list)[spec_count].type = action->type;
+ (*spec_list)[spec_count].action = action;
+ (*spec_list)[spec_count].args = args;
+ (*spec_list)[spec_count].argv = argv;
+ (*spec_list)[spec_count].expr = expr;
+ (*spec_list)[spec_count].data = data;
+ (*spec_list)[spec_count].verbose = verbose;
+
+ return 1;
+
+failed:
+ free(argv);
+ return 0;
+}
+
+
+/*
+ * Evaluate expressions
+ */
+
+#define ALLOC_SZ 128
+
+#define LOG_ENABLE 0
+#define LOG_DISABLE 1
+#define LOG_PRINT 2
+#define LOG_ENABLED 3
+
+static char *_expr_log(char *string, int cmnd)
+{
+ static char *expr_msg = NULL;
+ static int cur_size = 0, alloc_size = 0;
+ int size;
+
+ switch(cmnd) {
+ case LOG_ENABLE:
+ expr_msg = malloc(ALLOC_SZ);
+ alloc_size = ALLOC_SZ;
+ cur_size = 0;
+ return expr_msg;
+ case LOG_DISABLE:
+ free(expr_msg);
+ alloc_size = cur_size = 0;
+ return expr_msg = NULL;
+ case LOG_ENABLED:
+ return expr_msg;
+ default:
+ if(expr_msg == NULL)
+ return NULL;
+ break;
+ }
+
+ /* if string is empty append '\0' */
+ size = strlen(string) ? : 1;
+
+ if(alloc_size - cur_size < size) {
+ /* buffer too small, expand */
+ alloc_size = (cur_size + size + ALLOC_SZ - 1) & ~(ALLOC_SZ - 1);
+
+ expr_msg = realloc(expr_msg, alloc_size);
+ if(expr_msg == NULL)
+ MEM_ERROR();
+ }
+
+ memcpy(expr_msg + cur_size, string, size);
+ cur_size += size;
+
+ return expr_msg;
+}
+
+
+static char *expr_log_cmnd(int cmnd)
+{
+ return _expr_log(NULL, cmnd);
+}
+
+
+static char *expr_log(char *string)
+{
+ return _expr_log(string, LOG_PRINT);
+}
+
+
+static void expr_log_atom(struct atom *atom)
+{
+ int i;
+
+ if(atom->test->handle_logging)
+ return;
+
+ expr_log(atom->test->name);
+
+ if(atom->args) {
+ expr_log("(");
+ for(i = 0; i < atom->args; i++) {
+ expr_log(atom->argv[i]);
+ if (i + 1 < atom->args)
+ expr_log(",");
+ }
+ expr_log(")");
+ }
+}
+
+
+static void expr_log_match(int match)
+{
+ if(match)
+ expr_log("=True");
+ else
+ expr_log("=False");
+}
+
+
+static int eval_expr_log(struct expr *expr, struct action_data *action_data)
+{
+ int match;
+
+ switch (expr->type) {
+ case ATOM_TYPE:
+ expr_log_atom(&expr->atom);
+ match = expr->atom.test->fn(&expr->atom, action_data);
+ expr_log_match(match);
+ break;
+ case UNARY_TYPE:
+ expr_log("!");
+ match = !eval_expr_log(expr->unary_op.expr, action_data);
+ break;
+ default:
+ expr_log("(");
+ match = eval_expr_log(expr->expr_op.lhs, action_data);
+
+ if ((expr->expr_op.op == TOK_AND && match) ||
+ (expr->expr_op.op == TOK_OR && !match)) {
+ expr_log(token_table[expr->expr_op.op].string);
+ match = eval_expr_log(expr->expr_op.rhs, action_data);
+ }
+ expr_log(")");
+ break;
+ }
+
+ return match;
+}
+
+
+static int eval_expr(struct expr *expr, struct action_data *action_data)
+{
+ int match;
+
+ switch (expr->type) {
+ case ATOM_TYPE:
+ match = expr->atom.test->fn(&expr->atom, action_data);
+ break;
+ case UNARY_TYPE:
+ match = !eval_expr(expr->unary_op.expr, action_data);
+ break;
+ default:
+ match = eval_expr(expr->expr_op.lhs, action_data);
+
+ if ((expr->expr_op.op == TOK_AND && match) ||
+ (expr->expr_op.op == TOK_OR && !match))
+ match = eval_expr(expr->expr_op.rhs, action_data);
+ break;
+ }
+
+ return match;
+}
+
+
+static int eval_expr_top(struct action *action, struct action_data *action_data)
+{
+ if(action->verbose) {
+ int match, n;
+
+ expr_log_cmnd(LOG_ENABLE);
+
+ if(action_data->subpath)
+ expr_log(action_data->subpath);
+
+ expr_log("=");
+ expr_log(action->action->name);
+
+ if(action->args) {
+ expr_log("(");
+ for (n = 0; n < action->args; n++) {
+ expr_log(action->argv[n]);
+ if(n + 1 < action->args)
+ expr_log(",");
+ }
+ expr_log(")");
+ }
+
+ expr_log("@");
+
+ match = eval_expr_log(action->expr, action_data);
+
+ /*
+ * Print the evaluated expression log, if the
+ * result matches the logging specified
+ */
+ if((match && (action->verbose & ACTION_LOG_TRUE)) || (!match
+ && (action->verbose & ACTION_LOG_FALSE)))
+ progressbar_info("%s\n", expr_log(""));
+
+ expr_log_cmnd(LOG_DISABLE);
+
+ return match;
+ } else
+ return eval_expr(action->expr, action_data);
+}
+
+
+/*
+ * Read action file, passing each line to parse_action() for
+ * parsing.
+ *
+ * One action per line, of the form
+ * action(arg1,arg2)@expr(arg1,arg2)....
+ *
+ * Actions can be split across multiple lines using "\".
+ *
+ * Blank lines and comment lines indicated by # are supported.
+ */
+static int parse_action_true(char *s)
+{
+ return parse_action(s, ACTION_LOG_TRUE);
+}
+
+
+static int parse_action_false(char *s)
+{
+ return parse_action(s, ACTION_LOG_FALSE);
+}
+
+
+static int parse_action_verbose(char *s)
+{
+ return parse_action(s, ACTION_LOG_VERBOSE);
+}
+
+
+static int parse_action_nonverbose(char *s)
+{
+ return parse_action(s, ACTION_LOG_NONE);
+}
+
+
+int read_action_file(char *filename, int verbose)
+{
+ switch(verbose) {
+ case ACTION_LOG_TRUE:
+ return read_file(filename, "action", parse_action_true);
+ case ACTION_LOG_FALSE:
+ return read_file(filename, "action", parse_action_false);
+ case ACTION_LOG_VERBOSE:
+ return read_file(filename, "action", parse_action_verbose);
+ default:
+ return read_file(filename, "action", parse_action_nonverbose);
+ }
+}
+
+
+/*
+ * helper to evaluate whether action/test acts on this file type
+ */
+static int file_type_match(int st_mode, int type)
+{
+ switch(type) {
+ case ACTION_DIR:
+ return S_ISDIR(st_mode);
+ case ACTION_REG:
+ return S_ISREG(st_mode);
+ case ACTION_ALL:
+ return S_ISREG(st_mode) || S_ISDIR(st_mode) ||
+ S_ISCHR(st_mode) || S_ISBLK(st_mode) ||
+ S_ISFIFO(st_mode) || S_ISSOCK(st_mode);
+ case ACTION_LNK:
+ return S_ISLNK(st_mode);
+ case ACTION_ALL_LNK:
+ default:
+ return 1;
+ }
+}
+
+
+/*
+ * General action evaluation code
+ */
+int any_actions()
+{
+ return fragment_count + exclude_count + empty_count +
+ move_count + prune_count + other_count;
+}
+
+
+int actions()
+{
+ return other_count;
+}
+
+
+void eval_actions(struct dir_info *root, struct dir_ent *dir_ent)
+{
+ int i, match;
+ struct action_data action_data;
+ int st_mode = dir_ent->inode->buf.st_mode;
+
+ action_data.name = dir_ent->name;
+ action_data.pathname = strdup(pathname(dir_ent));
+ action_data.subpath = strdup(subpathname(dir_ent));
+ action_data.buf = &dir_ent->inode->buf;
+ action_data.depth = dir_ent->our_dir->depth;
+ action_data.dir_ent = dir_ent;
+ action_data.root = root;
+
+ for (i = 0; i < other_count; i++) {
+ struct action *action = &other_spec[i];
+
+ if (!file_type_match(st_mode, action->action->file_types))
+ /* action does not operate on this file type */
+ continue;
+
+ match = eval_expr_top(action, &action_data);
+
+ if (match)
+ action->action->run_action(action, dir_ent);
+ }
+
+ free(action_data.pathname);
+ free(action_data.subpath);
+}
+
+
+/*
+ * Fragment specific action code
+ */
+void *eval_frag_actions(struct dir_info *root, struct dir_ent *dir_ent, int tail)
+{
+ int i, match;
+ struct action_data action_data;
+
+ action_data.name = dir_ent->name;
+ action_data.pathname = strdup(pathname(dir_ent));
+ action_data.subpath = strdup(subpathname(dir_ent));
+ action_data.buf = &dir_ent->inode->buf;
+ action_data.depth = dir_ent->our_dir->depth;
+ action_data.dir_ent = dir_ent;
+ action_data.root = root;
+
+ for (i = 0; i < fragment_count; i++) {
+ match = eval_expr_top(&fragment_spec[i], &action_data);
+ if (match) {
+ free(action_data.pathname);
+ free(action_data.subpath);
+ return &fragment_spec[i].data;
+ }
+ }
+
+ free(action_data.pathname);
+ free(action_data.subpath);
+
+ if(tail)
+ return &tail_fragment;
+ else
+ return &def_fragment;
+}
+
+
+void *get_frag_action(void *fragment)
+{
+ struct action *spec_list_end = &fragment_spec[fragment_count];
+ struct action *action;
+
+ if (fragment == NULL)
+ return &def_fragment;
+
+ if(fragment == &def_fragment)
+ return &tail_fragment;
+
+ if (fragment_count == 0)
+ return NULL;
+
+ if (fragment == &tail_fragment)
+ action = &fragment_spec[0] - 1;
+ else
+ action = fragment - offsetof(struct action, data);
+
+ if (++action == spec_list_end)
+ return NULL;
+
+ return &action->data;
+}
+
+
+/*
+ * Exclude specific action code
+ */
+int exclude_actions()
+{
+ return exclude_count;
+}
+
+
+int eval_exclude_actions(char *name, char *pathname, char *subpath,
+ struct stat *buf, unsigned int depth, struct dir_ent *dir_ent)
+{
+ int i, match = 0;
+ struct action_data action_data;
+
+ action_data.name = name;
+ action_data.pathname = pathname;
+ action_data.subpath = subpath;
+ action_data.buf = buf;
+ action_data.depth = depth;
+ action_data.dir_ent = dir_ent;
+
+ for (i = 0; i < exclude_count && !match; i++)
+ match = eval_expr_top(&exclude_spec[i], &action_data);
+
+ return match;
+}
+
+
+/*
+ * Fragment specific action code
+ */
+static void frag_action(struct action *action, struct dir_ent *dir_ent)
+{
+ struct inode_info *inode = dir_ent->inode;
+
+ inode->no_fragments = 0;
+}
+
+static void no_frag_action(struct action *action, struct dir_ent *dir_ent)
+{
+ struct inode_info *inode = dir_ent->inode;
+
+ inode->no_fragments = 1;
+}
+
+static void always_frag_action(struct action *action, struct dir_ent *dir_ent)
+{
+ struct inode_info *inode = dir_ent->inode;
+
+ inode->always_use_fragments = 1;
+}
+
+static void no_always_frag_action(struct action *action, struct dir_ent *dir_ent)
+{
+ struct inode_info *inode = dir_ent->inode;
+
+ inode->always_use_fragments = 0;
+}
+
+
+/*
+ * Compression specific action code
+ */
+static void comp_action(struct action *action, struct dir_ent *dir_ent)
+{
+ struct inode_info *inode = dir_ent->inode;
+
+ inode->noD = inode->noF = 0;
+}
+
+static void uncomp_action(struct action *action, struct dir_ent *dir_ent)
+{
+ struct inode_info *inode = dir_ent->inode;
+
+ inode->noD = inode->noF = 1;
+}
+
+
+/*
+ * Uid/gid specific action code
+ */
+static long long parse_uid(char *arg) {
+ char *b;
+ long long uid = strtoll(arg, &b, 10);
+
+ if (*b == '\0') {
+ if (uid < 0 || uid >= (1LL << 32)) {
+ SYNTAX_ERROR("Uid out of range\n");
+ return -1;
+ }
+ } else {
+ struct passwd *passwd = getpwnam(arg);
+
+ if (passwd)
+ uid = passwd->pw_uid;
+ else {
+ SYNTAX_ERROR("Invalid uid or unknown user\n");
+ return -1;
+ }
+ }
+
+ return uid;
+}
+
+
+static long long parse_gid(char *arg) {
+ char *b;
+ long long gid = strtoll(arg, &b, 10);
+
+ if (*b == '\0') {
+ if (gid < 0 || gid >= (1LL << 32)) {
+ SYNTAX_ERROR("Gid out of range\n");
+ return -1;
+ }
+ } else {
+ struct group *group = getgrnam(arg);
+
+ if (group)
+ gid = group->gr_gid;
+ else {
+ SYNTAX_ERROR("Invalid gid or unknown group\n");
+ return -1;
+ }
+ }
+
+ return gid;
+}
+
+
+static int parse_uid_args(struct action_entry *action, int args, char **argv,
+ void **data)
+{
+ long long uid;
+ struct uid_info *uid_info;
+
+ uid = parse_uid(argv[0]);
+ if (uid == -1)
+ return 0;
+
+ uid_info = malloc(sizeof(struct uid_info));
+ if (uid_info == NULL)
+ MEM_ERROR();
+
+ uid_info->uid = uid;
+ *data = uid_info;
+
+ return 1;
+}
+
+
+static int parse_gid_args(struct action_entry *action, int args, char **argv,
+ void **data)
+{
+ long long gid;
+ struct gid_info *gid_info;
+
+ gid = parse_gid(argv[0]);
+ if (gid == -1)
+ return 0;
+
+ gid_info = malloc(sizeof(struct gid_info));
+ if (gid_info == NULL)
+ MEM_ERROR();
+
+ gid_info->gid = gid;
+ *data = gid_info;
+
+ return 1;
+}
+
+
+static int parse_guid_args(struct action_entry *action, int args, char **argv,
+ void **data)
+{
+ long long uid, gid;
+ struct guid_info *guid_info;
+
+ uid = parse_uid(argv[0]);
+ if (uid == -1)
+ return 0;
+
+ gid = parse_gid(argv[1]);
+ if (gid == -1)
+ return 0;
+
+ guid_info = malloc(sizeof(struct guid_info));
+ if (guid_info == NULL)
+ MEM_ERROR();
+
+ guid_info->uid = uid;
+ guid_info->gid = gid;
+ *data = guid_info;
+
+ return 1;
+}
+
+
+static void uid_action(struct action *action, struct dir_ent *dir_ent)
+{
+ struct inode_info *inode = dir_ent->inode;
+ struct uid_info *uid_info = action->data;
+
+ inode->buf.st_uid = uid_info->uid;
+}
+
+static void gid_action(struct action *action, struct dir_ent *dir_ent)
+{
+ struct inode_info *inode = dir_ent->inode;
+ struct gid_info *gid_info = action->data;
+
+ inode->buf.st_gid = gid_info->gid;
+}
+
+static void guid_action(struct action *action, struct dir_ent *dir_ent)
+{
+ struct inode_info *inode = dir_ent->inode;
+ struct guid_info *guid_info = action->data;
+
+ inode->buf.st_uid = guid_info->uid;
+ inode->buf.st_gid = guid_info->gid;
+
+}
+
+
+/*
+ * Mode specific action code
+ */
+static int parse_octal_mode_args(int args, char **argv,
+ void **data)
+{
+ int n, bytes;
+ unsigned int mode;
+ struct mode_data *mode_data;
+
+ /* octal mode number? */
+ n = sscanf(argv[0], "%o%n", &mode, &bytes);
+ if (n == 0)
+ return -1; /* not an octal number arg */
+
+
+ /* check there's no trailing junk */
+ if (argv[0][bytes] != '\0') {
+ SYNTAX_ERROR("Unexpected trailing bytes after octal "
+ "mode number\n");
+ return 0; /* bad octal number arg */
+ }
+
+ /* check there's only one argument */
+ if (args > 1) {
+ SYNTAX_ERROR("Octal mode number is first argument, "
+ "expected one argument, got %d\n", args);
+ return 0; /* bad octal number arg */
+ }
+
+ /* check mode is within range */
+ if (mode > 07777) {
+ SYNTAX_ERROR("Octal mode %o is out of range\n", mode);
+ return 0; /* bad octal number arg */
+ }
+
+ mode_data = malloc(sizeof(struct mode_data));
+ if (mode_data == NULL)
+ MEM_ERROR();
+
+ mode_data->operation = ACTION_MODE_OCT;
+ mode_data->mode = mode;
+ mode_data->next = NULL;
+ *data = mode_data;
+
+ return 1;
+}
+
+
+/*
+ * Parse symbolic mode of format [ugoa]*[[+-=]PERMS]+
+ * PERMS = [rwxXst]+ or [ugo]
+ */
+static int parse_sym_mode_arg(char *arg, struct mode_data **head,
+ struct mode_data **cur)
+{
+ struct mode_data *mode_data;
+ int mode;
+ int mask = 0;
+ int op;
+ char X;
+
+ if (arg[0] != 'u' && arg[0] != 'g' && arg[0] != 'o' && arg[0] != 'a') {
+ /* no ownership specifiers, default to a */
+ mask = 0777;
+ goto parse_operation;
+ }
+
+ /* parse ownership specifiers */
+ while(1) {
+ switch(*arg) {
+ case 'u':
+ mask |= 04700;
+ break;
+ case 'g':
+ mask |= 02070;
+ break;
+ case 'o':
+ mask |= 01007;
+ break;
+ case 'a':
+ mask = 07777;
+ break;
+ default:
+ goto parse_operation;
+ }
+ arg ++;
+ }
+
+parse_operation:
+ /* trap a symbolic mode with just an ownership specification */
+ if(*arg == '\0') {
+ SYNTAX_ERROR("Expected one of '+', '-' or '=', got EOF\n");
+ goto failed;
+ }
+
+ while(*arg != '\0') {
+ mode = 0;
+ X = 0;
+
+ switch(*arg) {
+ case '+':
+ op = ACTION_MODE_ADD;
+ break;
+ case '-':
+ op = ACTION_MODE_REM;
+ break;
+ case '=':
+ op = ACTION_MODE_SET;
+ break;
+ default:
+ SYNTAX_ERROR("Expected one of '+', '-' or '=', got "
+ "'%c'\n", *arg);
+ goto failed;
+ }
+
+ arg ++;
+
+ /* Parse PERMS */
+ if (*arg == 'u' || *arg == 'g' || *arg == 'o') {
+ /* PERMS = [ugo] */
+ mode = - *arg;
+ arg ++;
+ } else {
+ /* PERMS = [rwxXst]* */
+ while(1) {
+ switch(*arg) {
+ case 'r':
+ mode |= 0444;
+ break;
+ case 'w':
+ mode |= 0222;
+ break;
+ case 'x':
+ mode |= 0111;
+ break;
+ case 's':
+ mode |= 06000;
+ break;
+ case 't':
+ mode |= 01000;
+ break;
+ case 'X':
+ X = 1;
+ break;
+ case '+':
+ case '-':
+ case '=':
+ case '\0':
+ mode &= mask;
+ goto perms_parsed;
+ default:
+ SYNTAX_ERROR("Unrecognised permission "
+ "'%c'\n", *arg);
+ goto failed;
+ }
+
+ arg ++;
+ }
+ }
+
+perms_parsed:
+ mode_data = malloc(sizeof(*mode_data));
+ if (mode_data == NULL)
+ MEM_ERROR();
+
+ mode_data->operation = op;
+ mode_data->mode = mode;
+ mode_data->mask = mask;
+ mode_data->X = X;
+ mode_data->next = NULL;
+
+ if (*cur) {
+ (*cur)->next = mode_data;
+ *cur = mode_data;
+ } else
+ *head = *cur = mode_data;
+ }
+
+ return 1;
+
+failed:
+ return 0;
+}
+
+
+static int parse_sym_mode_args(struct action_entry *action, int args,
+ char **argv, void **data)
+{
+ int i, res = 1;
+ struct mode_data *head = NULL, *cur = NULL;
+
+ for (i = 0; i < args && res; i++)
+ res = parse_sym_mode_arg(argv[i], &head, &cur);
+
+ *data = head;
+
+ return res;
+}
+
+
+static int parse_mode_args(struct action_entry *action, int args,
+ char **argv, void **data)
+{
+ int res;
+
+ if (args == 0) {
+ SYNTAX_ERROR("Mode action expects one or more arguments\n");
+ return 0;
+ }
+
+ res = parse_octal_mode_args(args, argv, data);
+ if(res >= 0)
+ /* Got an octal mode argument */
+ return res;
+ else /* not an octal mode argument */
+ return parse_sym_mode_args(action, args, argv, data);
+}
+
+
+static int mode_execute(struct mode_data *mode_data, int st_mode)
+{
+ int mode = 0;
+
+ for (;mode_data; mode_data = mode_data->next) {
+ if (mode_data->mode < 0) {
+ /* 'u', 'g' or 'o' */
+ switch(-mode_data->mode) {
+ case 'u':
+ mode = (st_mode >> 6) & 07;
+ break;
+ case 'g':
+ mode = (st_mode >> 3) & 07;
+ break;
+ case 'o':
+ mode = st_mode & 07;
+ break;
+ }
+ mode = ((mode << 6) | (mode << 3) | mode) &
+ mode_data->mask;
+ } else if (mode_data->X &&
+ ((st_mode & S_IFMT) == S_IFDIR ||
+ (st_mode & 0111)))
+ /* X permission, only takes effect if inode is a
+ * directory or x is set for some owner */
+ mode = mode_data->mode | (0111 & mode_data->mask);
+ else
+ mode = mode_data->mode;
+
+ switch(mode_data->operation) {
+ case ACTION_MODE_OCT:
+ st_mode = (st_mode & S_IFMT) | mode;
+ break;
+ case ACTION_MODE_SET:
+ st_mode = (st_mode & ~mode_data->mask) | mode;
+ break;
+ case ACTION_MODE_ADD:
+ st_mode |= mode;
+ break;
+ case ACTION_MODE_REM:
+ st_mode &= ~mode;
+ }
+ }
+
+ return st_mode;
+}
+
+
+static void mode_action(struct action *action, struct dir_ent *dir_ent)
+{
+ dir_ent->inode->buf.st_mode = mode_execute(action->data,
+ dir_ent->inode->buf.st_mode);
+}
+
+
+/*
+ * Empty specific action code
+ */
+int empty_actions()
+{
+ return empty_count;
+}
+
+
+static int parse_empty_args(struct action_entry *action, int args,
+ char **argv, void **data)
+{
+ struct empty_data *empty_data;
+ int val;
+
+ if (args >= 2) {
+ SYNTAX_ERROR("Empty action expects zero or one argument\n");
+ return 0;
+ }
+
+ if (args == 0 || strcmp(argv[0], "all") == 0)
+ val = EMPTY_ALL;
+ else if (strcmp(argv[0], "source") == 0)
+ val = EMPTY_SOURCE;
+ else if (strcmp(argv[0], "excluded") == 0)
+ val = EMPTY_EXCLUDED;
+ else {
+ SYNTAX_ERROR("Empty action expects zero arguments, or one"
+ "argument containing \"all\", \"source\", or \"excluded\""
+ "\n");
+ return 0;
+ }
+
+ empty_data = malloc(sizeof(*empty_data));
+ if (empty_data == NULL)
+ MEM_ERROR();
+
+ empty_data->val = val;
+ *data = empty_data;
+
+ return 1;
+}
+
+
+int eval_empty_actions(struct dir_info *root, struct dir_ent *dir_ent)
+{
+ int i, match = 0;
+ struct action_data action_data;
+ struct empty_data *data;
+ struct dir_info *dir = dir_ent->dir;
+
+ /*
+ * Empty action only works on empty directories
+ */
+ if (dir->count != 0)
+ return 0;
+
+ action_data.name = dir_ent->name;
+ action_data.pathname = strdup(pathname(dir_ent));
+ action_data.subpath = strdup(subpathname(dir_ent));
+ action_data.buf = &dir_ent->inode->buf;
+ action_data.depth = dir_ent->our_dir->depth;
+ action_data.dir_ent = dir_ent;
+ action_data.root = root;
+
+ for (i = 0; i < empty_count && !match; i++) {
+ data = empty_spec[i].data;
+
+ /*
+ * determine the cause of the empty directory and evaluate
+ * the empty action specified. Three empty actions:
+ * - EMPTY_SOURCE: empty action triggers only if the directory
+ * was originally empty, i.e directories that are empty
+ * only due to excluding are ignored.
+ * - EMPTY_EXCLUDED: empty action triggers only if the directory
+ * is empty because of excluding, i.e. directories that
+ * were originally empty are ignored.
+ * - EMPTY_ALL (the default): empty action triggers if the
+ * directory is empty, irrespective of the reason, i.e.
+ * the directory could have been originally empty or could
+ * be empty due to excluding.
+ */
+ if ((data->val == EMPTY_EXCLUDED && !dir->excluded) ||
+ (data->val == EMPTY_SOURCE && dir->excluded))
+ continue;
+
+ match = eval_expr_top(&empty_spec[i], &action_data);
+ }
+
+ free(action_data.pathname);
+ free(action_data.subpath);
+
+ return match;
+}
+
+
+/*
+ * Move specific action code
+ */
+static struct move_ent *move_list = NULL;
+
+
+int move_actions()
+{
+ return move_count;
+}
+
+
+static char *move_pathname(struct move_ent *move)
+{
+ struct dir_info *dest;
+ char *name, *pathname;
+ int res;
+
+ dest = (move->ops & ACTION_MOVE_MOVE) ?
+ move->dest : move->dir_ent->our_dir;
+ name = (move->ops & ACTION_MOVE_RENAME) ?
+ move->name : move->dir_ent->name;
+
+ if(dest->subpath[0] != '\0')
+ res = asprintf(&pathname, "%s/%s", dest->subpath, name);
+ else
+ res = asprintf(&pathname, "/%s", name);
+
+ if(res == -1)
+ BAD_ERROR("asprintf failed in move_pathname\n");
+
+ return pathname;
+}
+
+
+static char *get_comp(char **pathname)
+{
+ char *path = *pathname, *start;
+
+ while(*path == '/')
+ path ++;
+
+ if(*path == '\0')
+ return NULL;
+
+ start = path;
+ while(*path != '/' && *path != '\0')
+ path ++;
+
+ *pathname = path;
+ return strndup(start, path - start);
+}
+
+
+static struct dir_ent *lookup_comp(char *comp, struct dir_info *dest)
+{
+ struct dir_ent *dir_ent;
+
+ for(dir_ent = dest->list; dir_ent; dir_ent = dir_ent->next)
+ if(strcmp(comp, dir_ent->name) == 0)
+ break;
+
+ return dir_ent;
+}
+
+
+void eval_move(struct action_data *action_data, struct move_ent *move,
+ struct dir_info *root, struct dir_ent *dir_ent, char *pathname)
+{
+ struct dir_info *dest, *source = dir_ent->our_dir;
+ struct dir_ent *comp_ent;
+ char *comp, *path = pathname;
+
+ /*
+ * Walk pathname to get the destination directory
+ *
+ * Like the mv command, if the last component exists and it
+ * is a directory, then move the file into that directory,
+ * otherwise, move the file into parent directory of the last
+ * component and rename to the last component.
+ */
+ if (pathname[0] == '/')
+ /* absolute pathname, walk from root directory */
+ dest = root;
+ else
+ /* relative pathname, walk from current directory */
+ dest = source;
+
+ for(comp = get_comp(&pathname); comp; free(comp),
+ comp = get_comp(&pathname)) {
+
+ if (strcmp(comp, ".") == 0)
+ continue;
+
+ if (strcmp(comp, "..") == 0) {
+ /* if we're in the root directory then ignore */
+ if(dest->depth > 1)
+ dest = dest->dir_ent->our_dir;
+ continue;
+ }
+
+ /*
+ * Look up comp in current directory, if it exists and it is a
+ * directory continue walking the pathname, otherwise exit,
+ * we've walked as far as we can go, normally this is because
+ * we've arrived at the leaf component which we are going to
+ * rename source to
+ */
+ comp_ent = lookup_comp(comp, dest);
+ if (comp_ent == NULL || (comp_ent->inode->buf.st_mode & S_IFMT)
+ != S_IFDIR)
+ break;
+
+ dest = comp_ent->dir;
+ }
+
+ if(comp) {
+ /* Leaf component? If so we're renaming to this */
+ char *remainder = get_comp(&pathname);
+ free(remainder);
+
+ if(remainder) {
+ /*
+ * trying to move source to a subdirectory of
+ * comp, but comp either doesn't exist, or it isn't
+ * a directory, which is impossible
+ */
+ if (comp_ent == NULL)
+ ERROR("Move action: cannot move %s to %s, no "
+ "such directory %s\n",
+ action_data->subpath, path, comp);
+ else
+ ERROR("Move action: cannot move %s to %s, %s "
+ "is not a directory\n",
+ action_data->subpath, path, comp);
+ free(comp);
+ return;
+ }
+
+ /*
+ * Multiple move actions triggering on one file can be merged
+ * if one is a RENAME and the other is a MOVE. Multiple RENAMEs
+ * can only merge if they're doing the same thing
+ */
+ if(move->ops & ACTION_MOVE_RENAME) {
+ if(strcmp(comp, move->name) != 0) {
+ char *conf_path = move_pathname(move);
+ ERROR("Move action: Cannot move %s to %s, "
+ "conflicting move, already moving "
+ "to %s via another move action!\n",
+ action_data->subpath, path, conf_path);
+ free(conf_path);
+ free(comp);
+ return;
+ }
+ free(comp);
+ } else {
+ move->name = comp;
+ move->ops |= ACTION_MOVE_RENAME;
+ }
+ }
+
+ if(dest != source) {
+ /*
+ * Multiple move actions triggering on one file can be merged
+ * if one is a RENAME and the other is a MOVE. Multiple MOVEs
+ * can only merge if they're doing the same thing
+ */
+ if(move->ops & ACTION_MOVE_MOVE) {
+ if(dest != move->dest) {
+ char *conf_path = move_pathname(move);
+ ERROR("Move action: Cannot move %s to %s, "
+ "conflicting move, already moving "
+ "to %s via another move action!\n",
+ action_data->subpath, path, conf_path);
+ free(conf_path);
+ return;
+ }
+ } else {
+ move->dest = dest;
+ move->ops |= ACTION_MOVE_MOVE;
+ }
+ }
+}
+
+
+static int subdirectory(struct dir_info *source, struct dir_info *dest)
+{
+ if(source == NULL)
+ return 0;
+
+ return strlen(source->subpath) <= strlen(dest->subpath) &&
+ (dest->subpath[strlen(source->subpath)] == '/' ||
+ dest->subpath[strlen(source->subpath)] == '\0') &&
+ strncmp(source->subpath, dest->subpath,
+ strlen(source->subpath)) == 0;
+}
+
+
+void eval_move_actions(struct dir_info *root, struct dir_ent *dir_ent)
+{
+ int i;
+ struct action_data action_data;
+ struct move_ent *move = NULL;
+
+ action_data.name = dir_ent->name;
+ action_data.pathname = strdup(pathname(dir_ent));
+ action_data.subpath = strdup(subpathname(dir_ent));
+ action_data.buf = &dir_ent->inode->buf;
+ action_data.depth = dir_ent->our_dir->depth;
+ action_data.dir_ent = dir_ent;
+ action_data.root = root;
+
+ /*
+ * Evaluate each move action against the current file. For any
+ * move actions that match don't actually perform the move now, but,
+ * store it, and execute all the stored move actions together once the
+ * directory scan is complete. This is done to ensure each separate
+ * move action does not nondeterministically interfere with other move
+ * actions. Each move action is considered to act independently, and
+ * each move action sees the directory tree in the same state.
+ */
+ for (i = 0; i < move_count; i++) {
+ struct action *action = &move_spec[i];
+ int match = eval_expr_top(action, &action_data);
+
+ if(match) {
+ if(move == NULL) {
+ move = malloc(sizeof(*move));
+ if(move == NULL)
+ MEM_ERROR();
+
+ move->ops = 0;
+ move->dir_ent = dir_ent;
+ }
+ eval_move(&action_data, move, root, dir_ent,
+ action->argv[0]);
+ }
+ }
+
+ if(move) {
+ struct dir_ent *comp_ent;
+ struct dir_info *dest;
+ char *name;
+
+ /*
+ * Move contains the result of all triggered move actions.
+ * Check the destination doesn't already exist
+ */
+ if(move->ops == 0) {
+ free(move);
+ goto finish;
+ }
+
+ dest = (move->ops & ACTION_MOVE_MOVE) ?
+ move->dest : dir_ent->our_dir;
+ name = (move->ops & ACTION_MOVE_RENAME) ?
+ move->name : dir_ent->name;
+ comp_ent = lookup_comp(name, dest);
+ if(comp_ent) {
+ char *conf_path = move_pathname(move);
+ ERROR("Move action: Cannot move %s to %s, "
+ "destination already exists\n",
+ action_data.subpath, conf_path);
+ free(conf_path);
+ free(move);
+ goto finish;
+ }
+
+ /*
+ * If we're moving a directory, check we're not moving it to a
+ * subdirectory of itself
+ */
+ if(subdirectory(dir_ent->dir, dest)) {
+ char *conf_path = move_pathname(move);
+ ERROR("Move action: Cannot move %s to %s, this is a "
+ "subdirectory of itself\n",
+ action_data.subpath, conf_path);
+ free(conf_path);
+ free(move);
+ goto finish;
+ }
+ move->next = move_list;
+ move_list = move;
+ }
+
+finish:
+ free(action_data.pathname);
+ free(action_data.subpath);
+}
+
+
+static void move_dir(struct dir_ent *dir_ent)
+{
+ struct dir_info *dir = dir_ent->dir;
+ struct dir_ent *comp_ent;
+
+ /* update our directory's subpath name */
+ free(dir->subpath);
+ dir->subpath = strdup(subpathname(dir_ent));
+
+ /* recursively update the subpaths of any sub-directories */
+ for(comp_ent = dir->list; comp_ent; comp_ent = comp_ent->next)
+ if(comp_ent->dir)
+ move_dir(comp_ent);
+}
+
+
+static void move_file(struct move_ent *move_ent)
+{
+ struct dir_ent *dir_ent = move_ent->dir_ent;
+
+ if(move_ent->ops & ACTION_MOVE_MOVE) {
+ struct dir_ent *comp_ent, *prev = NULL;
+ struct dir_info *source = dir_ent->our_dir,
+ *dest = move_ent->dest;
+ char *filename = pathname(dir_ent);
+
+ /*
+ * If we're moving a directory, check we're not moving it to a
+ * subdirectory of itself
+ */
+ if(subdirectory(dir_ent->dir, dest)) {
+ char *conf_path = move_pathname(move_ent);
+ ERROR("Move action: Cannot move %s to %s, this is a "
+ "subdirectory of itself\n",
+ subpathname(dir_ent), conf_path);
+ free(conf_path);
+ return;
+ }
+
+ /* Remove the file from source directory */
+ for(comp_ent = source->list; comp_ent != dir_ent;
+ prev = comp_ent, comp_ent = comp_ent->next);
+
+ if(prev)
+ prev->next = comp_ent->next;
+ else
+ source->list = comp_ent->next;
+
+ source->count --;
+ if((comp_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+ source->directory_count --;
+
+ /* Add the file to dest directory */
+ comp_ent->next = dest->list;
+ dest->list = comp_ent;
+ comp_ent->our_dir = dest;
+
+ dest->count ++;
+ if((comp_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+ dest->directory_count ++;
+
+ /*
+ * We've moved the file, and so we can't now use the
+ * parent directory's pathname to calculate the pathname
+ */
+ if(dir_ent->nonstandard_pathname == NULL) {
+ dir_ent->nonstandard_pathname = strdup(filename);
+ if(dir_ent->source_name) {
+ free(dir_ent->source_name);
+ dir_ent->source_name = NULL;
+ }
+ }
+ }
+
+ if(move_ent->ops & ACTION_MOVE_RENAME) {
+ /*
+ * If we're using name in conjunction with the parent
+ * directory's pathname to calculate the pathname, we need
+ * to use source_name to override. Otherwise it's already being
+ * over-ridden
+ */
+ if(dir_ent->nonstandard_pathname == NULL &&
+ dir_ent->source_name == NULL)
+ dir_ent->source_name = dir_ent->name;
+ else
+ free(dir_ent->name);
+
+ dir_ent->name = move_ent->name;
+ }
+
+ if(dir_ent->dir)
+ /*
+ * dir_ent is a directory, and we have to recursively fix-up
+ * its subpath, and the subpaths of all of its sub-directories
+ */
+ move_dir(dir_ent);
+}
+
+
+void do_move_actions()
+{
+ while(move_list) {
+ struct move_ent *temp = move_list;
+ struct dir_info *dest = (move_list->ops & ACTION_MOVE_MOVE) ?
+ move_list->dest : move_list->dir_ent->our_dir;
+ char *name = (move_list->ops & ACTION_MOVE_RENAME) ?
+ move_list->name : move_list->dir_ent->name;
+ struct dir_ent *comp_ent = lookup_comp(name, dest);
+ if(comp_ent) {
+ char *conf_path = move_pathname(move_list);
+ ERROR("Move action: Cannot move %s to %s, "
+ "destination already exists\n",
+ subpathname(move_list->dir_ent), conf_path);
+ free(conf_path);
+ } else
+ move_file(move_list);
+
+ move_list = move_list->next;
+ free(temp);
+ }
+}
+
+
+/*
+ * Prune specific action code
+ */
+int prune_actions()
+{
+ return prune_count;
+}
+
+
+int eval_prune_actions(struct dir_info *root, struct dir_ent *dir_ent)
+{
+ int i, match = 0;
+ struct action_data action_data;
+
+ action_data.name = dir_ent->name;
+ action_data.pathname = strdup(pathname(dir_ent));
+ action_data.subpath = strdup(subpathname(dir_ent));
+ action_data.buf = &dir_ent->inode->buf;
+ action_data.depth = dir_ent->our_dir->depth;
+ action_data.dir_ent = dir_ent;
+ action_data.root = root;
+
+ for (i = 0; i < prune_count && !match; i++)
+ match = eval_expr_top(&prune_spec[i], &action_data);
+
+ free(action_data.pathname);
+ free(action_data.subpath);
+
+ return match;
+}
+
+
+/*
+ * Xattr include/exclude specific action code
+ */
+static int parse_xattr_args(struct action_entry *action, int args,
+ char **argv, void **data)
+{
+ struct xattr_data *xattr_data;
+ int error;
+
+ xattr_data = malloc(sizeof(*xattr_data));
+ if (xattr_data == NULL)
+ MEM_ERROR();
+
+ error = regcomp(&xattr_data->preg, argv[0], REG_EXTENDED|REG_NOSUB);
+ if(error) {
+ char str[1024]; /* overflow safe */
+
+ regerror(error, &xattr_data->preg, str, 1024);
+ SYNTAX_ERROR("invalid regex %s because %s\n", argv[0], str);
+ free(xattr_data);
+ return 0;
+ }
+
+ *data = xattr_data;
+
+ return 1;
+}
+
+
+static struct xattr_data *eval_xattr_actions (struct action *spec,
+ int count, struct dir_info *root, struct dir_ent *dir_ent)
+{
+ int i;
+ struct action_data action_data;
+ struct xattr_data *head = NULL;
+
+ if(count == 0)
+ return NULL;
+
+ action_data.name = dir_ent->name;
+ action_data.pathname = strdup(pathname(dir_ent));
+ action_data.subpath = strdup(subpathname(dir_ent));
+ action_data.buf = &dir_ent->inode->buf;
+ action_data.depth = dir_ent->our_dir->depth;
+ action_data.dir_ent = dir_ent;
+ action_data.root = root;
+
+ for (i = 0; i < count; i++) {
+ struct xattr_data *data = spec[i].data;
+ int match = eval_expr_top(&spec[i], &action_data);
+
+ if(match) {
+ data->next = head;
+ head = data;
+ }
+ }
+
+ free(action_data.pathname);
+ free(action_data.subpath);
+
+ return head;
+}
+
+
+int xattr_exc_actions()
+{
+ return xattr_exc_count;
+}
+
+
+struct xattr_data *eval_xattr_exc_actions (struct dir_info *root,
+ struct dir_ent *dir_ent)
+{
+ return eval_xattr_actions(xattr_exc_spec, xattr_exc_count, root, dir_ent);
+}
+
+
+int match_xattr_exc_actions(struct xattr_data *head, char *name)
+{
+ struct xattr_data *cur;
+
+ for(cur = head; cur != NULL; cur = cur->next) {
+ int match = regexec(&cur->preg, name, (size_t) 0, NULL, 0);
+
+ if(match == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int xattr_inc_actions()
+{
+ return xattr_inc_count;
+}
+
+
+struct xattr_data *eval_xattr_inc_actions (struct dir_info *root,
+ struct dir_ent *dir_ent)
+{
+ return eval_xattr_actions(xattr_inc_spec, xattr_inc_count, root, dir_ent);
+}
+
+
+int match_xattr_inc_actions(struct xattr_data *head, char *name)
+{
+ if(head == NULL)
+ return 0;
+ else
+ return !match_xattr_exc_actions(head, name);
+}
+
+
+/*
+ * Xattr add specific action code
+ */
+static int parse_xattr_add_args(struct action_entry *action, int args,
+ char **argv, void **data)
+{
+ struct xattr_add *xattr = xattr_parse(argv[0], "", "action xattr add");
+
+ if(xattr == NULL)
+ return 0;
+
+ *data = xattr;
+
+ return 1;
+}
+
+
+struct xattr_add *eval_xattr_add_actions(struct dir_info *root,
+ struct dir_ent *dir_ent, int *items)
+{
+ int i, count = 0;
+ struct action_data action_data;
+ struct xattr_add *head = NULL;
+
+ if(xattr_add_count == 0) {
+ *items = 0;
+ return NULL;
+ }
+
+ action_data.name = dir_ent->name;
+ action_data.pathname = strdup(pathname(dir_ent));
+ action_data.subpath = strdup(subpathname(dir_ent));
+ action_data.buf = &dir_ent->inode->buf;
+ action_data.depth = dir_ent->our_dir->depth;
+ action_data.dir_ent = dir_ent;
+ action_data.root = root;
+
+ for (i = 0; i < xattr_add_count; i++) {
+ struct xattr_add *data = xattr_add_spec[i].data;
+ int match = eval_expr_top(&xattr_add_spec[i], &action_data);
+
+ if(match) {
+ data->next = head;
+ head = data;
+ count ++;
+ }
+ }
+
+ free(action_data.pathname);
+ free(action_data.subpath);
+
+ *items = count;
+ return head;
+}
+
+
+int xattr_add_actions()
+{
+ return xattr_add_count;
+}
+
+
+/*
+ * Noop specific action code
+ */
+static void noop_action(struct action *action, struct dir_ent *dir_ent)
+{
+}
+
+
+/*
+ * General test evaluation code
+ */
+
+/*
+ * A number can be of the form [range]number[size]
+ * [range] is either:
+ * '<' or '-', match on less than number
+ * '>' or '+', match on greater than number
+ * '' (nothing), match on exactly number
+ * [size] is either:
+ * '' (nothing), number
+ * 'k' or 'K', number * 2^10
+ * 'm' or 'M', number * 2^20
+ * 'g' or 'G', number * 2^30
+ */
+static int parse_number(char *start, long long *size, int *range, char **error)
+{
+ char *end;
+ long long number;
+
+ if (*start == '>' || *start == '+') {
+ *range = NUM_GREATER;
+ start ++;
+ } else if (*start == '<' || *start == '-') {
+ *range = NUM_LESS;
+ start ++;
+ } else
+ *range = NUM_EQ;
+
+ errno = 0; /* To enable failure after call to be determined */
+ number = strtoll(start, &end, 10);
+
+ if((errno == ERANGE && (number == LLONG_MAX || number == LLONG_MIN))
+ || (errno != 0 && number == 0)) {
+ /* long long underflow or overflow in conversion, or other
+ * conversion error.
+ * Note: we don't check for LLONG_MIN and LLONG_MAX only
+ * because strtoll can validly return that if the
+ * user used these values
+ */
+ *error = "Long long underflow, overflow or other conversion "
+ "error";
+ return 0;
+ }
+
+ if (end == start) {
+ /* Couldn't read any number */
+ *error = "Number expected";
+ return 0;
+ }
+
+ switch (end[0]) {
+ case 'g':
+ case 'G':
+ number *= 1024;
+ case 'm':
+ case 'M':
+ number *= 1024;
+ case 'k':
+ case 'K':
+ number *= 1024;
+
+ if (end[1] != '\0') {
+ *error = "Trailing junk after size specifier";
+ return 0;
+ }
+
+ break;
+ case '\0':
+ break;
+ default:
+ *error = "Trailing junk after number";
+ return 0;
+ }
+
+ *size = number;
+
+ return 1;
+}
+
+
+static int parse_number_arg(struct test_entry *test, struct atom *atom)
+{
+ struct test_number_arg *number;
+ long long size;
+ int range;
+ char *error;
+ int res = parse_number(atom->argv[0], &size, &range, &error);
+
+ if (res == 0) {
+ TEST_SYNTAX_ERROR(test, 0, "%s\n", error);
+ return 0;
+ }
+
+ number = malloc(sizeof(*number));
+ if (number == NULL)
+ MEM_ERROR();
+
+ number->range = range;
+ number->size = size;
+
+ atom->data = number;
+
+ return 1;
+}
+
+
+static int parse_range_args(struct test_entry *test, struct atom *atom)
+{
+ struct test_range_args *range;
+ long long start, end;
+ int type;
+ int res;
+ char *error;
+
+ res = parse_number(atom->argv[0], &start, &type, &error);
+ if (res == 0) {
+ TEST_SYNTAX_ERROR(test, 0, "%s\n", error);
+ return 0;
+ }
+
+ if (type != NUM_EQ) {
+ TEST_SYNTAX_ERROR(test, 0, "Range specifier (<, >, -, +) not "
+ "expected\n");
+ return 0;
+ }
+
+ res = parse_number(atom->argv[1], &end, &type, &error);
+ if (res == 0) {
+ TEST_SYNTAX_ERROR(test, 1, "%s\n", error);
+ return 0;
+ }
+
+ if (type != NUM_EQ) {
+ TEST_SYNTAX_ERROR(test, 1, "Range specifier (<, >, -, +) not "
+ "expected\n");
+ return 0;
+ }
+
+ range = malloc(sizeof(*range));
+ if (range == NULL)
+ MEM_ERROR();
+
+ range->start = start;
+ range->end = end;
+
+ atom->data = range;
+
+ return 1;
+}
+
+
+/*
+ * Generic test code macro
+ */
+#define TEST_FN(NAME, MATCH, CODE) \
+static int NAME##_fn(struct atom *atom, struct action_data *action_data) \
+{ \
+ /* test operates on MATCH file types only */ \
+ if (!file_type_match(action_data->buf->st_mode, MATCH)) \
+ return 0; \
+ \
+ CODE \
+}
+
+/*
+ * Generic test code macro testing VAR for size (eq, less than, greater than)
+ */
+#define TEST_VAR_FN(NAME, MATCH, VAR) TEST_FN(NAME, MATCH, \
+ { \
+ int match = 0; \
+ struct test_number_arg *number = atom->data; \
+ \
+ switch (number->range) { \
+ case NUM_EQ: \
+ match = VAR == number->size; \
+ break; \
+ case NUM_LESS: \
+ match = VAR < number->size; \
+ break; \
+ case NUM_GREATER: \
+ match = VAR > number->size; \
+ break; \
+ } \
+ \
+ return match; \
+ })
+
+
+/*
+ * Generic test code macro testing VAR for range [x, y] (value between x and y
+ * inclusive).
+ */
+#define TEST_VAR_RANGE_FN(NAME, MATCH, VAR) TEST_FN(NAME##_range, MATCH, \
+ { \
+ struct test_range_args *range = atom->data; \
+ \
+ return range->start <= VAR && VAR <= range->end; \
+ })
+
+
+/*
+ * Name, Pathname and Subpathname test specific code
+ */
+
+/*
+ * Add a leading "/" if subpathname and pathname lacks it
+ */
+static int check_pathname(struct test_entry *test, struct atom *atom)
+{
+ int res;
+ char *name;
+
+ if(atom->argv[0][0] != '/') {
+ res = asprintf(&name, "/%s", atom->argv[0]);
+ if(res == -1)
+ BAD_ERROR("asprintf failed in check_pathname\n");
+
+ free(atom->argv[0]);
+ atom->argv[0] = name;
+ }
+
+ return 1;
+}
+
+
+TEST_FN(name, ACTION_ALL_LNK, \
+ return fnmatch(atom->argv[0], action_data->name,
+ FNM_PATHNAME|FNM_EXTMATCH) == 0;)
+
+TEST_FN(pathname, ACTION_ALL_LNK, \
+ return fnmatch(atom->argv[0], action_data->subpath,
+ FNM_PATHNAME|FNM_EXTMATCH) == 0;)
+
+
+static int count_components(char *path)
+{
+ int count;
+
+ for (count = 0; *path != '\0'; count ++) {
+ while (*path == '/')
+ path ++;
+
+ while (*path != '\0' && *path != '/')
+ path ++;
+ }
+
+ return count;
+}
+
+
+static char *get_start(char *s, int n)
+{
+ int count;
+ char *path = s;
+
+ for (count = 0; *path != '\0' && count < n; count ++) {
+ while (*path == '/')
+ path ++;
+
+ while (*path != '\0' && *path != '/')
+ path ++;
+ }
+
+ if (count == n)
+ *path = '\0';
+
+ return s;
+}
+
+
+static int subpathname_fn(struct atom *atom, struct action_data *data)
+{
+ char *s = get_start(strdup(data->subpath), count_components(atom->argv[0]));
+ int res = fnmatch(atom->argv[0], s, FNM_PATHNAME|FNM_EXTMATCH);
+
+ free(s);
+
+ return res == 0;
+}
+
+/*
+ * Inode attribute test operations using generic
+ * TEST_VAR_FN(test name, file scope, attribute name) macro.
+ * This is for tests that do not need to be specially handled in any way.
+ * They just take a variable and compare it against a number.
+ */
+TEST_VAR_FN(filesize, ACTION_REG, action_data->buf->st_size)
+
+TEST_VAR_FN(dirsize, ACTION_DIR, action_data->buf->st_size)
+
+TEST_VAR_FN(size, ACTION_ALL_LNK, action_data->buf->st_size)
+
+TEST_VAR_FN(inode, ACTION_ALL_LNK, action_data->buf->st_ino)
+
+TEST_VAR_FN(nlink, ACTION_ALL_LNK, action_data->buf->st_nlink)
+
+TEST_VAR_FN(fileblocks, ACTION_REG, action_data->buf->st_blocks)
+
+TEST_VAR_FN(dirblocks, ACTION_DIR, action_data->buf->st_blocks)
+
+TEST_VAR_FN(blocks, ACTION_ALL_LNK, action_data->buf->st_blocks)
+
+TEST_VAR_FN(dircount, ACTION_DIR, action_data->dir_ent->dir->count)
+
+TEST_VAR_FN(depth, ACTION_ALL_LNK, action_data->depth)
+
+TEST_VAR_RANGE_FN(filesize, ACTION_REG, action_data->buf->st_size)
+
+TEST_VAR_RANGE_FN(dirsize, ACTION_DIR, action_data->buf->st_size)
+
+TEST_VAR_RANGE_FN(size, ACTION_ALL_LNK, action_data->buf->st_size)
+
+TEST_VAR_RANGE_FN(inode, ACTION_ALL_LNK, action_data->buf->st_ino)
+
+TEST_VAR_RANGE_FN(nlink, ACTION_ALL_LNK, action_data->buf->st_nlink)
+
+TEST_VAR_RANGE_FN(fileblocks, ACTION_REG, action_data->buf->st_blocks)
+
+TEST_VAR_RANGE_FN(dirblocks, ACTION_DIR, action_data->buf->st_blocks)
+
+TEST_VAR_RANGE_FN(blocks, ACTION_ALL_LNK, action_data->buf->st_blocks)
+
+TEST_VAR_RANGE_FN(gid, ACTION_ALL_LNK, action_data->buf->st_gid)
+
+TEST_VAR_RANGE_FN(uid, ACTION_ALL_LNK, action_data->buf->st_uid)
+
+TEST_VAR_RANGE_FN(depth, ACTION_ALL_LNK, action_data->depth)
+
+TEST_VAR_RANGE_FN(dircount, ACTION_DIR, action_data->dir_ent->dir->count)
+
+TEST_VAR_FN(uid, ACTION_ALL_LNK, action_data->buf->st_uid)
+
+TEST_VAR_FN(gid, ACTION_ALL_LNK, action_data->buf->st_gid)
+
+/*
+ * user specific test code
+ */
+TEST_VAR_FN(user, ACTION_ALL_LNK, action_data->buf->st_uid)
+
+static int parse_user_arg(struct test_entry *test, struct atom *atom)
+{
+ struct test_number_arg *number;
+ long long size;
+ struct passwd *uid = getpwnam(atom->argv[0]);
+
+ if(uid)
+ size = uid->pw_uid;
+ else {
+ TEST_SYNTAX_ERROR(test, 1, "Unknown user\n");
+ return 0;
+ }
+
+ number = malloc(sizeof(*number));
+ if(number == NULL)
+ MEM_ERROR();
+
+ number->range = NUM_EQ;
+ number->size = size;
+
+ atom->data = number;
+
+ return 1;
+}
+
+
+/*
+ * group specific test code
+ */
+TEST_VAR_FN(group, ACTION_ALL_LNK, action_data->buf->st_gid)
+
+static int parse_group_arg(struct test_entry *test, struct atom *atom)
+{
+ struct test_number_arg *number;
+ long long size;
+ struct group *gid = getgrnam(atom->argv[0]);
+
+ if(gid)
+ size = gid->gr_gid;
+ else {
+ TEST_SYNTAX_ERROR(test, 1, "Unknown group\n");
+ return 0;
+ }
+
+ number = malloc(sizeof(*number));
+ if(number == NULL)
+ MEM_ERROR();
+
+ number->range = NUM_EQ;
+ number->size= size;
+
+ atom->data = number;
+
+ return 1;
+}
+
+
+/*
+ * Type test specific code
+ */
+static struct type_entry type_table[] = {
+ { S_IFSOCK, 's' },
+ { S_IFLNK, 'l' },
+ { S_IFREG, 'f' },
+ { S_IFBLK, 'b' },
+ { S_IFDIR, 'd' },
+ { S_IFCHR, 'c' },
+ { S_IFIFO, 'p' },
+ { 0, 0 },
+};
+
+
+static int parse_type_arg(struct test_entry *test, struct atom *atom)
+{
+ int i;
+
+ if (strlen(atom->argv[0]) != 1)
+ goto failed;
+
+ for(i = 0; type_table[i].type != 0; i++)
+ if (type_table[i].type == atom->argv[0][0])
+ break;
+
+ atom->data = &type_table[i];
+
+ if(type_table[i].type != 0)
+ return 1;
+
+failed:
+ TEST_SYNTAX_ERROR(test, 0, "Unexpected file type, expected 'f', 'd', "
+ "'c', 'b', 'l', 's' or 'p'\n");
+ return 0;
+}
+
+
+static int type_fn(struct atom *atom, struct action_data *action_data)
+{
+ struct type_entry *type = atom->data;
+
+ return (action_data->buf->st_mode & S_IFMT) == type->value;
+}
+
+
+/*
+ * True test specific code
+ */
+static int true_fn(struct atom *atom, struct action_data *action_data)
+{
+ return 1;
+}
+
+
+/*
+ * False test specific code
+ */
+static int false_fn(struct atom *atom, struct action_data *action_data)
+{
+ return 0;
+}
+
+
+/*
+ * File test specific code
+ */
+static int parse_file_arg(struct test_entry *test, struct atom *atom)
+{
+ int res;
+ regex_t *preg = malloc(sizeof(regex_t));
+
+ if (preg == NULL)
+ MEM_ERROR();
+
+ res = regcomp(preg, atom->argv[0], REG_EXTENDED);
+ if (res) {
+ char str[1024]; /* overflow safe */
+
+ regerror(res, preg, str, 1024);
+ free(preg);
+ TEST_SYNTAX_ERROR(test, 0, "invalid regex \"%s\" because "
+ "\"%s\"\n", atom->argv[0], str);
+ return 0;
+ }
+
+ atom->data = preg;
+
+ return 1;
+}
+
+
+static int file_fn(struct atom *atom, struct action_data *action_data)
+{
+ int child, res, size = 0, status;
+ int pipefd[2];
+ char *buffer = NULL;
+ regex_t *preg = atom->data;
+
+ res = pipe(pipefd);
+ if (res == -1)
+ BAD_ERROR("file_fn pipe failed\n");
+
+ child = fork();
+ if (child == -1)
+ BAD_ERROR("file_fn fork_failed\n");
+
+ if (child == 0) {
+ /*
+ * Child process
+ * Connect stdout to pipefd[1] and execute file command
+ */
+ close(STDOUT_FILENO);
+ res = dup(pipefd[1]);
+ if (res == -1)
+ exit(EXIT_FAILURE);
+
+ execlp("file", "file", "-b", action_data->pathname,
+ (char *) NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * Parent process. Read stdout from file command
+ */
+ close(pipefd[1]);
+
+ do {
+ buffer = realloc(buffer, size + 512);
+ if (buffer == NULL)
+ MEM_ERROR();
+
+ res = read_bytes(pipefd[0], buffer + size, 512);
+
+ if (res == -1)
+ BAD_ERROR("file_fn pipe read error\n");
+
+ size += 512;
+
+ } while (res == 512);
+
+ size = size + res - 512;
+
+ buffer[size] = '\0';
+
+ res = waitpid(child, &status, 0);
+
+ if (res == -1)
+ BAD_ERROR("file_fn waitpid failed\n");
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ BAD_ERROR("file_fn file returned error\n");
+
+ close(pipefd[0]);
+
+ res = regexec(preg, buffer, (size_t) 0, NULL, 0);
+
+ free(buffer);
+
+ return res == 0;
+}
+
+
+/*
+ * Exec test specific code
+ */
+static int exec_fn(struct atom *atom, struct action_data *action_data)
+{
+ int child, i, res, status;
+
+ child = fork();
+ if (child == -1)
+ BAD_ERROR("exec_fn fork_failed\n");
+
+ if (child == 0) {
+ /*
+ * Child process
+ * redirect stdin, stdout & stderr to /dev/null and
+ * execute atom->argv[0]
+ */
+ int fd = open("/dev/null", O_RDWR);
+ if(fd == -1)
+ exit(EXIT_FAILURE);
+
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ for(i = 0; i < 3; i++) {
+ res = dup(fd);
+ if (res == -1)
+ exit(EXIT_FAILURE);
+ }
+ close(fd);
+
+ /*
+ * Create environment variables
+ * NAME: name of file
+ * PATHNAME: pathname of file relative to squashfs root
+ * SOURCE_PATHNAME: the pathname of the file in the source
+ * directory
+ */
+ res = setenv("NAME", action_data->name, 1);
+ if(res == -1)
+ exit(EXIT_FAILURE);
+
+ res = setenv("PATHNAME", action_data->subpath, 1);
+ if(res == -1)
+ exit(EXIT_FAILURE);
+
+ res = setenv("SOURCE_PATHNAME", action_data->pathname, 1);
+ if(res == -1)
+ exit(EXIT_FAILURE);
+
+ execl("/bin/sh", "sh", "-c", atom->argv[0], (char *) NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * Parent process.
+ */
+
+ res = waitpid(child, &status, 0);
+
+ if (res == -1)
+ BAD_ERROR("exec_fn waitpid failed\n");
+
+ return WIFEXITED(status) ? WEXITSTATUS(status) == 0 : 0;
+}
+
+
+/*
+ * Symbolic link specific test code
+ */
+
+/*
+ * Walk the supplied pathname and return the directory entry corresponding
+ * to the pathname. If any symlinks are encountered whilst walking the
+ * pathname, then recursively walk these, to obtain the fully
+ * dereferenced canonicalised directory entry.
+ *
+ * If follow_path fails to walk a pathname either because a component
+ * doesn't exist, it is a non directory component when a directory
+ * component is expected, a symlink with an absolute path is encountered,
+ * or a symlink is encountered which cannot be recursively walked due to
+ * the above failures, then return NULL.
+ */
+static struct dir_ent *follow_path(struct dir_info *dir, char *pathname)
+{
+ char *comp, *path = pathname;
+ struct dir_ent *dir_ent = NULL;
+
+ /* We cannot follow absolute paths */
+ if(pathname[0] == '/')
+ return NULL;
+
+ for(comp = get_comp(&path); comp; free(comp), comp = get_comp(&path)) {
+ if(strcmp(comp, ".") == 0)
+ continue;
+
+ if(strcmp(comp, "..") == 0) {
+ /* Move to parent if we're not in the root directory */
+ if(dir->depth > 1) {
+ dir = dir->dir_ent->our_dir;
+ dir_ent = NULL; /* lazily eval at loop exit */
+ continue;
+ } else
+ /* Failed to walk pathname */
+ return NULL;
+ }
+
+ /* Lookup comp in current directory */
+ dir_ent = lookup_comp(comp, dir);
+ if(dir_ent == NULL)
+ /* Doesn't exist, failed to walk pathname */
+ return NULL;
+
+ if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFLNK) {
+ /* Symbolic link, try to walk it */
+ dir_ent = follow_path(dir, dir_ent->inode->symlink);
+ if(dir_ent == NULL)
+ /* Failed to follow symlink */
+ return NULL;
+ }
+
+ if((dir_ent->inode->buf.st_mode & S_IFMT) != S_IFDIR)
+ /* Cannot walk further */
+ break;
+
+ dir = dir_ent->dir;
+ }
+
+ /* We will have exited the loop either because we've processed
+ * all the components, which means we've successfully walked the
+ * pathname, or because we've hit a non-directory, in which case
+ * it's success if this is the leaf component */
+ if(comp) {
+ free(comp);
+ comp = get_comp(&path);
+ free(comp);
+ if(comp != NULL)
+ /* Not a leaf component */
+ return NULL;
+ } else {
+ /* Fully walked pathname, dir_ent contains correct value unless
+ * we've walked to the parent ("..") in which case we need
+ * to resolve it here */
+ if(!dir_ent)
+ dir_ent = dir->dir_ent;
+ }
+
+ return dir_ent;
+}
+
+
+static int exists_fn(struct atom *atom, struct action_data *action_data)
+{
+ /*
+ * Test if a symlink exists within the output filesystem, that is,
+ * the symlink has a relative path, and the relative path refers
+ * to an entry within the output filesystem.
+ *
+ * This test function evaluates the path for symlinks - that is it
+ * follows any symlinks in the path (and any symlinks that it contains
+ * etc.), to discover the fully dereferenced canonicalised relative
+ * path.
+ *
+ * If any symlinks within the path do not exist or are absolute
+ * then the symlink is considered to not exist, as it cannot be
+ * fully dereferenced.
+ *
+ * exists operates on symlinks only, other files by definition
+ * exist
+ */
+ if (!file_type_match(action_data->buf->st_mode, ACTION_LNK))
+ return 1;
+
+ /* dereference the symlink, and return TRUE if it exists */
+ return follow_path(action_data->dir_ent->our_dir,
+ action_data->dir_ent->inode->symlink) ? 1 : 0;
+}
+
+
+static int absolute_fn(struct atom *atom, struct action_data *action_data)
+{
+ /*
+ * Test if a symlink has an absolute path, which by definition
+ * means the symbolic link may be broken (even if the absolute path
+ * does point into the filesystem being squashed, because the resultant
+ * filesystem can be mounted/unsquashed anywhere, it is unlikely the
+ * absolute path will still point to the right place). If you know that
+ * an absolute symlink will point to the right place then you don't need
+ * to use this function, and/or these symlinks can be excluded by
+ * use of other test operators.
+ *
+ * absolute operates on symlinks only, other files by definition
+ * don't have problems
+ */
+ if (!file_type_match(action_data->buf->st_mode, ACTION_LNK))
+ return 0;
+
+ return action_data->dir_ent->inode->symlink[0] == '/';
+}
+
+
+static int parse_expr_argX(struct test_entry *test, struct atom *atom,
+ int argno)
+{
+ /* Call parse_expr to parse argument, which should be an expression */
+
+ /* save the current parser state */
+ char *save_cur_ptr = cur_ptr;
+ char *save_source = source;
+
+ cur_ptr = source = atom->argv[argno];
+ atom->data = parse_expr(0);
+
+ cur_ptr = save_cur_ptr;
+ source = save_source;
+
+ if(atom->data == NULL) {
+ /* parse_expr(0) will have reported the exact syntax error,
+ * but, because we recursively evaluated the expression, it
+ * will have been reported without the context of the stat
+ * test(). So here additionally report our failure to parse
+ * the expression in the stat() test to give context */
+ TEST_SYNTAX_ERROR(test, 0, "Failed to parse expression\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static int parse_expr_arg0(struct test_entry *test, struct atom *atom)
+{
+ return parse_expr_argX(test, atom, 0);
+}
+
+
+static int parse_expr_arg1(struct test_entry *test, struct atom *atom)
+{
+ return parse_expr_argX(test, atom, 1);
+}
+
+
+static int stat_fn(struct atom *atom, struct action_data *action_data)
+{
+ struct stat buf;
+ struct action_data eval_action;
+ int match, res;
+
+ /* evaluate the expression using the context of the inode
+ * pointed to by the symlink. This allows the inode attributes
+ * of the file pointed to by the symlink to be evaluated, rather
+ * than the symlink itself.
+ *
+ * Note, stat() deliberately does not evaluate the pathname, name or
+ * depth of the symlink, these are left with the symlink values.
+ * This allows stat() to be used on any symlink, rather than
+ * just symlinks which are contained (if the symlink is *not*
+ * contained then pathname, name and depth are meaningless as they
+ * are relative to the filesystem being squashed). */
+
+ /* if this isn't a symlink then stat will just return the current
+ * information, i.e. stat(expr) == expr. This is harmless and
+ * is better than returning TRUE or FALSE in a non symlink case */
+ res = stat(action_data->pathname, &buf);
+ if(res == -1) {
+ if(expr_log_cmnd(LOG_ENABLED)) {
+ expr_log(atom->test->name);
+ expr_log("(");
+ expr_log_match(0);
+ expr_log(")");
+ }
+ return 0;
+ }
+
+ /* fill in the inode values of the file pointed to by the
+ * symlink, but, leave everything else the same */
+ memcpy(&eval_action, action_data, sizeof(struct action_data));
+ eval_action.buf = &buf;
+
+ if(expr_log_cmnd(LOG_ENABLED)) {
+ expr_log(atom->test->name);
+ expr_log("(");
+ match = eval_expr_log(atom->data, &eval_action);
+ expr_log(")");
+ } else
+ match = eval_expr(atom->data, &eval_action);
+
+ return match;
+}
+
+
+static int readlink_fn(struct atom *atom, struct action_data *action_data)
+{
+ int match = 0;
+ struct dir_ent *dir_ent;
+ struct action_data eval_action;
+
+ /* Dereference the symlink and evaluate the expression in the
+ * context of the file pointed to by the symlink.
+ * All attributes are updated to refer to the file that is pointed to.
+ * Thus the inode attributes, pathname, name and depth all refer to
+ * the dereferenced file, and not the symlink.
+ *
+ * If the symlink cannot be dereferenced because it doesn't exist in
+ * the output filesystem, or due to some other failure to
+ * walk the pathname (see follow_path above), then FALSE is returned.
+ *
+ * If you wish to evaluate the inode attributes of symlinks which
+ * exist in the source filestem (but not in the output filesystem then
+ * use stat instead (see above).
+ *
+ * readlink operates on symlinks only */
+ if (!file_type_match(action_data->buf->st_mode, ACTION_LNK))
+ goto finish;
+
+ /* dereference the symlink, and get the directory entry it points to */
+ dir_ent = follow_path(action_data->dir_ent->our_dir,
+ action_data->dir_ent->inode->symlink);
+ if(dir_ent == NULL)
+ goto finish;
+
+ eval_action.name = dir_ent->name;
+ eval_action.pathname = strdup(pathname(dir_ent));
+ eval_action.subpath = strdup(subpathname(dir_ent));
+ eval_action.buf = &dir_ent->inode->buf;
+ eval_action.depth = dir_ent->our_dir->depth;
+ eval_action.dir_ent = dir_ent;
+ eval_action.root = action_data->root;
+
+ if(expr_log_cmnd(LOG_ENABLED)) {
+ expr_log(atom->test->name);
+ expr_log("(");
+ match = eval_expr_log(atom->data, &eval_action);
+ expr_log(")");
+ } else
+ match = eval_expr(atom->data, &eval_action);
+
+ free(eval_action.pathname);
+ free(eval_action.subpath);
+
+ return match;
+
+finish:
+ if(expr_log_cmnd(LOG_ENABLED)) {
+ expr_log(atom->test->name);
+ expr_log("(");
+ expr_log_match(0);
+ expr_log(")");
+ }
+
+ return 0;
+}
+
+
+static int eval_fn(struct atom *atom, struct action_data *action_data)
+{
+ int match;
+ char *path = atom->argv[0];
+ struct dir_ent *dir_ent = action_data->dir_ent;
+ struct stat *buf = action_data->buf;
+ struct action_data eval_action;
+
+ /* Follow path (arg1) and evaluate the expression (arg2)
+ * in the context of the file discovered. All attributes are updated
+ * to refer to the file that is pointed to.
+ *
+ * This test operation allows you to add additional context to the
+ * evaluation of the file being scanned, such as "if current file is
+ * XXX and the parent is YYY, then ..." Often times you need or
+ * want to test a combination of file status
+ *
+ * If the file referenced by the path does not exist in
+ * the output filesystem, or some other failure is experienced in
+ * walking the path (see follow_path above), then FALSE is returned.
+ *
+ * If you wish to evaluate the inode attributes of files which
+ * exist in the source filestem (but not in the output filesystem then
+ * use stat instead (see above). */
+
+ /* try to follow path, and get the directory entry it points to */
+ if(path[0] == '/') {
+ /* absolute, walk from root - first skip the leading / */
+ while(path[0] == '/')
+ path ++;
+ if(path[0] == '\0')
+ dir_ent = action_data->root->dir_ent;
+ else
+ dir_ent = follow_path(action_data->root, path);
+ } else {
+ /* relative, if first component is ".." walk from parent,
+ * otherwise walk from dir_ent.
+ * Note: this has to be handled here because follow_path
+ * will quite correctly refuse to execute ".." on anything
+ * which isn't a directory */
+ if(strncmp(path, "..", 2) == 0 && (path[2] == '\0' ||
+ path[2] == '/')) {
+ /* walk from parent */
+ path += 2;
+ while(path[0] == '/')
+ path ++;
+ if(path[0] == '\0')
+ dir_ent = dir_ent->our_dir->dir_ent;
+ else
+ dir_ent = follow_path(dir_ent->our_dir, path);
+ } else if(!file_type_match(buf->st_mode, ACTION_DIR))
+ dir_ent = NULL;
+ else
+ dir_ent = follow_path(dir_ent->dir, path);
+ }
+
+ if(dir_ent == NULL) {
+ if(expr_log_cmnd(LOG_ENABLED)) {
+ expr_log(atom->test->name);
+ expr_log("(");
+ expr_log(atom->argv[0]);
+ expr_log(",");
+ expr_log_match(0);
+ expr_log(")");
+ }
+
+ return 0;
+ }
+
+ eval_action.name = dir_ent->name;
+ eval_action.pathname = strdup(pathname(dir_ent));
+ eval_action.subpath = strdup(subpathname(dir_ent));
+ eval_action.buf = &dir_ent->inode->buf;
+ eval_action.depth = dir_ent->our_dir->depth;
+ eval_action.dir_ent = dir_ent;
+ eval_action.root = action_data->root;
+
+ if(expr_log_cmnd(LOG_ENABLED)) {
+ expr_log(atom->test->name);
+ expr_log("(");
+ expr_log(eval_action.subpath);
+ expr_log(",");
+ match = eval_expr_log(atom->data, &eval_action);
+ expr_log(")");
+ } else
+ match = eval_expr(atom->data, &eval_action);
+
+ free(eval_action.pathname);
+ free(eval_action.subpath);
+
+ return match;
+}
+
+
+/*
+ * Perm specific test code
+ */
+static int parse_perm_args(struct test_entry *test, struct atom *atom)
+{
+ int res = 1, mode, op, i;
+ char *arg;
+ struct mode_data *head = NULL, *cur = NULL;
+ struct perm_data *perm_data;
+
+ if(atom->args == 0) {
+ TEST_SYNTAX_ERROR(test, 0, "One or more arguments expected\n");
+ return 0;
+ }
+
+ switch(atom->argv[0][0]) {
+ case '-':
+ op = PERM_ALL;
+ arg = atom->argv[0] + 1;
+ break;
+ case '/':
+ op = PERM_ANY;
+ arg = atom->argv[0] + 1;
+ break;
+ default:
+ op = PERM_EXACT;
+ arg = atom->argv[0];
+ break;
+ }
+
+ /* try to parse as an octal number */
+ res = parse_octal_mode_args(atom->args, atom->argv, (void **) &head);
+ if(res == -1) {
+ /* parse as sym mode argument */
+ for(i = 0; i < atom->args && res; i++, arg = atom->argv[i])
+ res = parse_sym_mode_arg(arg, &head, &cur);
+ }
+
+ if (res == 0)
+ goto finish;
+
+ /*
+ * Evaluate the symbolic mode against a permission of 0000 octal
+ */
+ mode = mode_execute(head, 0);
+
+ perm_data = malloc(sizeof(struct perm_data));
+ if (perm_data == NULL)
+ MEM_ERROR();
+
+ perm_data->op = op;
+ perm_data->mode = mode;
+
+ atom->data = perm_data;
+
+finish:
+ while(head) {
+ struct mode_data *tmp = head;
+ head = head->next;
+ free(tmp);
+ }
+
+ return res;
+}
+
+
+static int perm_fn(struct atom *atom, struct action_data *action_data)
+{
+ struct perm_data *perm_data = atom->data;
+ struct stat *buf = action_data->buf;
+
+ switch(perm_data->op) {
+ case PERM_EXACT:
+ return (buf->st_mode & ~S_IFMT) == perm_data->mode;
+ case PERM_ALL:
+ return (buf->st_mode & perm_data->mode) == perm_data->mode;
+ case PERM_ANY:
+ default:
+ /*
+ * if no permission bits are set in perm_data->mode match
+ * on any file, this is to be consistent with find, which
+ * does this to be consistent with the behaviour of
+ * -perm -000
+ */
+ return perm_data->mode == 0 || (buf->st_mode & perm_data->mode);
+ }
+}
+
+
+#ifdef SQUASHFS_TRACE
+static void dump_parse_tree(struct expr *expr)
+{
+ int i;
+
+ if(expr->type == ATOM_TYPE) {
+ printf("%s", expr->atom.test->name);
+ if(expr->atom.args) {
+ printf("(");
+ for(i = 0; i < expr->atom.args; i++) {
+ printf("%s", expr->atom.argv[i]);
+ if (i + 1 < expr->atom.args)
+ printf(",");
+ }
+ printf(")");
+ }
+ } else if (expr->type == UNARY_TYPE) {
+ printf("%s", token_table[expr->unary_op.op].string);
+ dump_parse_tree(expr->unary_op.expr);
+ } else {
+ printf("(");
+ dump_parse_tree(expr->expr_op.lhs);
+ printf("%s", token_table[expr->expr_op.op].string);
+ dump_parse_tree(expr->expr_op.rhs);
+ printf(")");
+ }
+}
+
+
+void dump_action_list(struct action *spec_list, int spec_count)
+{
+ int i;
+
+ for (i = 0; i < spec_count; i++) {
+ printf("%s", spec_list[i].action->name);
+ if (spec_list[i].args) {
+ int n;
+
+ printf("(");
+ for (n = 0; n < spec_list[i].args; n++) {
+ printf("%s", spec_list[i].argv[n]);
+ if (n + 1 < spec_list[i].args)
+ printf(",");
+ }
+ printf(")");
+ }
+ printf("=");
+ dump_parse_tree(spec_list[i].expr);
+ printf("\n");
+ }
+}
+
+
+void dump_actions()
+{
+ dump_action_list(exclude_spec, exclude_count);
+ dump_action_list(fragment_spec, fragment_count);
+ dump_action_list(other_spec, other_count);
+ dump_action_list(move_spec, move_count);
+ dump_action_list(empty_spec, empty_count);
+}
+#else
+void dump_actions()
+{
+}
+#endif
+
+
+static struct test_entry test_table[] = {
+ { "name", 1, name_fn, NULL, 1},
+ { "pathname", 1, pathname_fn, check_pathname, 1, 0},
+ { "subpathname", 1, subpathname_fn, check_pathname, 1, 0},
+ { "filesize", 1, filesize_fn, parse_number_arg, 1, 0},
+ { "dirsize", 1, dirsize_fn, parse_number_arg, 1, 0},
+ { "size", 1, size_fn, parse_number_arg, 1, 0},
+ { "inode", 1, inode_fn, parse_number_arg, 1, 0},
+ { "nlink", 1, nlink_fn, parse_number_arg, 1, 0},
+ { "fileblocks", 1, fileblocks_fn, parse_number_arg, 1, 0},
+ { "dirblocks", 1, dirblocks_fn, parse_number_arg, 1, 0},
+ { "blocks", 1, blocks_fn, parse_number_arg, 1, 0},
+ { "gid", 1, gid_fn, parse_number_arg, 1, 0},
+ { "group", 1, group_fn, parse_group_arg, 1, 0},
+ { "uid", 1, uid_fn, parse_number_arg, 1, 0},
+ { "user", 1, user_fn, parse_user_arg, 1, 0},
+ { "depth", 1, depth_fn, parse_number_arg, 1, 0},
+ { "dircount", 1, dircount_fn, parse_number_arg, 0, 0},
+ { "filesize_range", 2, filesize_range_fn, parse_range_args, 1, 0},
+ { "dirsize_range", 2, dirsize_range_fn, parse_range_args, 1, 0},
+ { "size_range", 2, size_range_fn, parse_range_args, 1, 0},
+ { "inode_range", 2, inode_range_fn, parse_range_args, 1, 0},
+ { "nlink_range", 2, nlink_range_fn, parse_range_args, 1, 0},
+ { "fileblocks_range", 2, fileblocks_range_fn, parse_range_args, 1, 0},
+ { "dirblocks_range", 2, dirblocks_range_fn, parse_range_args, 1, 0},
+ { "blocks_range", 2, blocks_range_fn, parse_range_args, 1, 0},
+ { "gid_range", 2, gid_range_fn, parse_range_args, 1, 0},
+ { "uid_range", 2, uid_range_fn, parse_range_args, 1, 0},
+ { "depth_range", 2, depth_range_fn, parse_range_args, 1, 0},
+ { "dircount_range", 2, dircount_range_fn, parse_range_args, 0, 0},
+ { "type", 1, type_fn, parse_type_arg, 1, 0},
+ { "true", 0, true_fn, NULL, 1, 0},
+ { "false", 0, false_fn, NULL, 1, 0},
+ { "file", 1, file_fn, parse_file_arg, 1, 0},
+ { "exec", 1, exec_fn, NULL, 1, 0},
+ { "exists", 0, exists_fn, NULL, 0, 0},
+ { "absolute", 0, absolute_fn, NULL, 0, 0},
+ { "stat", 1, stat_fn, parse_expr_arg0, 1, 1},
+ { "readlink", 1, readlink_fn, parse_expr_arg0, 0, 1},
+ { "eval", 2, eval_fn, parse_expr_arg1, 0, 1},
+ { "perm", -2, perm_fn, parse_perm_args, 1, 0},
+ { "", -1 }
+};
+
+
+static struct action_entry action_table[] = {
+ { "fragment", FRAGMENT_ACTION, 1, ACTION_REG, NULL, NULL},
+ { "exclude", EXCLUDE_ACTION, 0, ACTION_ALL_LNK, NULL, NULL},
+ { "fragments", FRAGMENTS_ACTION, 0, ACTION_REG, NULL, frag_action},
+ { "no-fragments", NO_FRAGMENTS_ACTION, 0, ACTION_REG, NULL, no_frag_action},
+ { "always-use-fragments", ALWAYS_FRAGS_ACTION, 0, ACTION_REG, NULL, always_frag_action},
+ { "dont-always-use-fragments", NO_ALWAYS_FRAGS_ACTION, 0, ACTION_REG, NULL, no_always_frag_action},
+ { "tailend", ALWAYS_FRAGS_ACTION, 0, ACTION_REG, NULL, always_frag_action},
+ { "no-tailend", NO_ALWAYS_FRAGS_ACTION, 0, ACTION_REG, NULL, no_always_frag_action},
+ { "compressed", COMPRESSED_ACTION, 0, ACTION_REG, NULL, comp_action},
+ { "uncompressed", UNCOMPRESSED_ACTION, 0, ACTION_REG, NULL, uncomp_action},
+ { "uid", UID_ACTION, 1, ACTION_ALL_LNK, parse_uid_args, uid_action},
+ { "gid", GID_ACTION, 1, ACTION_ALL_LNK, parse_gid_args, gid_action},
+ { "guid", GUID_ACTION, 2, ACTION_ALL_LNK, parse_guid_args, guid_action},
+ { "mode", MODE_ACTION, -2, ACTION_ALL, parse_mode_args, mode_action },
+ { "empty", EMPTY_ACTION, -2, ACTION_DIR, parse_empty_args, NULL},
+ { "move", MOVE_ACTION, 1, ACTION_ALL_LNK, NULL, NULL},
+ { "prune", PRUNE_ACTION, 0, ACTION_ALL_LNK, NULL, NULL},
+ { "chmod", MODE_ACTION, -2, ACTION_ALL, parse_mode_args, mode_action },
+ { "xattrs-exclude", XATTR_EXC_ACTION, 1, ACTION_ALL, parse_xattr_args, NULL},
+ { "xattrs-include", XATTR_INC_ACTION, 1, ACTION_ALL, parse_xattr_args, NULL},
+ { "xattrs-add", XATTR_ADD_ACTION, 1, ACTION_ALL, parse_xattr_add_args, NULL},
+ { "noop", NOOP_ACTION, 0, ACTION_ALL, NULL, noop_action },
+ { "", 0, -1, 0, NULL, NULL}
+};
diff --git a/squashfs-tools/action.h b/squashfs-tools/action.h
new file mode 100644
index 0000000..183daaa
--- /dev/null
+++ b/squashfs-tools/action.h
@@ -0,0 +1,351 @@
+#ifndef ACTION_H
+#define ACTION_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2011, 2012, 2013, 2014, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * action.h
+ */
+
+/*
+ * Lexical analyser definitions
+ */
+#define TOK_OPEN_BRACKET 0
+#define TOK_CLOSE_BRACKET 1
+#define TOK_AND 2
+#define TOK_OR 3
+#define TOK_NOT 4
+#define TOK_COMMA 5
+#define TOK_AT 6
+#define TOK_WHITE_SPACE 7
+#define TOK_STRING 8
+#define TOK_EOF 9
+
+#define TOK_TO_STR(OP, S) ({ \
+ char *s; \
+ switch(OP) { \
+ case TOK_EOF: \
+ s = "EOF"; \
+ break; \
+ case TOK_STRING: \
+ s = S; \
+ break; \
+ default: \
+ s = token_table[OP].string; \
+ break; \
+ } \
+ s; \
+})
+
+
+struct token_entry {
+ char *string;
+ int token;
+ int size;
+};
+
+/*
+ * Expression parser definitions
+ */
+#define OP_TYPE 0
+#define ATOM_TYPE 1
+#define UNARY_TYPE 2
+
+#define SYNTAX_ERROR(S, ARGS...) { \
+ char *src = strdup(source); \
+ src[cur_ptr - source] = '\0'; \
+ fprintf(stderr, "Failed to parse action \"%s\"\n", source); \
+ fprintf(stderr, "Syntax error: "S, ##ARGS); \
+ fprintf(stderr, "Got here \"%s\"\n", src); \
+ free(src); \
+}
+
+#define TEST_SYNTAX_ERROR(TEST, ARG, S, ARGS...) { \
+ char *src = strdup(source); \
+ src[cur_ptr - source] = '\0'; \
+ fprintf(stderr, "Failed to parse action \"%s\"\n", source); \
+ fprintf(stderr, "Syntax error in \"%s()\", arg %d: "S, TEST->name, \
+ ARG, ##ARGS); \
+ fprintf(stderr, "Got here \"%s\"\n", src); \
+ free(src); \
+}
+
+struct expr;
+
+struct expr_op {
+ struct expr *lhs;
+ struct expr *rhs;
+ int op;
+};
+
+
+struct atom {
+ struct test_entry *test;
+ int args;
+ char **argv;
+ void *data;
+};
+
+
+struct unary_op {
+ struct expr *expr;
+ int op;
+};
+
+
+struct expr {
+ int type;
+ union {
+ struct atom atom;
+ struct expr_op expr_op;
+ struct unary_op unary_op;
+ };
+};
+
+/*
+ * Test operation definitions
+ */
+#define NUM_EQ 1
+#define NUM_LESS 2
+#define NUM_GREATER 3
+
+struct test_number_arg {
+ long long size;
+ int range;
+};
+
+struct test_range_args {
+ long long start;
+ long long end;
+};
+
+struct action;
+struct action_data;
+
+struct test_entry {
+ char *name;
+ int args;
+ int (*fn)(struct atom *, struct action_data *);
+ int (*parse_args)(struct test_entry *, struct atom *);
+ int exclude_ok;
+ int handle_logging;
+};
+
+
+/*
+ * Type test specific definitions
+ */
+struct type_entry {
+ int value;
+ char type;
+};
+
+
+/*
+ * Action definitions
+ */
+#define FRAGMENT_ACTION 0
+#define EXCLUDE_ACTION 1
+#define FRAGMENTS_ACTION 2
+#define NO_FRAGMENTS_ACTION 3
+#define ALWAYS_FRAGS_ACTION 4
+#define NO_ALWAYS_FRAGS_ACTION 5
+#define COMPRESSED_ACTION 6
+#define UNCOMPRESSED_ACTION 7
+#define UID_ACTION 8
+#define GID_ACTION 9
+#define GUID_ACTION 10
+#define MODE_ACTION 11
+#define EMPTY_ACTION 12
+#define MOVE_ACTION 13
+#define PRUNE_ACTION 14
+#define NOOP_ACTION 15
+#define XATTR_EXC_ACTION 16
+#define XATTR_INC_ACTION 17
+#define XATTR_ADD_ACTION 18
+
+/*
+ * Define what file types each action operates over
+ */
+#define ACTION_DIR 1
+#define ACTION_REG 2
+#define ACTION_ALL_LNK 3
+#define ACTION_ALL 4
+#define ACTION_LNK 5
+
+
+/*
+ * Action logging requested, specified by the various
+ * -action, -true-action, -false-action and -verbose-action
+ * options
+ */
+#define ACTION_LOG_NONE 0
+#define ACTION_LOG_TRUE 1
+#define ACTION_LOG_FALSE 2
+#define ACTION_LOG_VERBOSE ACTION_LOG_TRUE | ACTION_LOG_FALSE
+
+struct action_entry {
+ char *name;
+ int type;
+ int args;
+ int file_types;
+ int (*parse_args)(struct action_entry *, int, char **, void **);
+ void (*run_action)(struct action *, struct dir_ent *);
+};
+
+
+struct action_data {
+ unsigned int depth;
+ char *name;
+ char *pathname;
+ char *subpath;
+ struct stat *buf;
+ struct dir_ent *dir_ent;
+ struct dir_info *root;
+};
+
+
+struct action {
+ int type;
+ struct action_entry *action;
+ int args;
+ char **argv;
+ struct expr *expr;
+ void *data;
+ int verbose;
+};
+
+
+/*
+ * Uid/gid action specific definitions
+ */
+struct uid_info {
+ uid_t uid;
+};
+
+struct gid_info {
+ gid_t gid;
+};
+
+struct guid_info {
+ uid_t uid;
+ gid_t gid;
+};
+
+
+/*
+ * Mode action specific definitions
+ */
+#define ACTION_MODE_SET 0
+#define ACTION_MODE_ADD 1
+#define ACTION_MODE_REM 2
+#define ACTION_MODE_OCT 3
+
+struct mode_data {
+ struct mode_data *next;
+ int operation;
+ int mode;
+ unsigned int mask;
+ char X;
+};
+
+
+/*
+ * Empty action specific definitions
+ */
+#define EMPTY_ALL 0
+#define EMPTY_SOURCE 1
+#define EMPTY_EXCLUDED 2
+
+struct empty_data {
+ int val;
+};
+
+
+/*
+ * Move action specific definitions
+ */
+#define ACTION_MOVE_RENAME 1
+#define ACTION_MOVE_MOVE 2
+
+struct move_ent {
+ int ops;
+ struct dir_ent *dir_ent;
+ char *name;
+ struct dir_info *dest;
+ struct move_ent *next;
+};
+
+
+/*
+ * Xattr action specific definitions
+ */
+struct xattr_data {
+ regex_t preg;
+ struct xattr_data *next;
+};
+
+
+/*
+ * Perm test function specific definitions
+ */
+#define PERM_ALL 1
+#define PERM_ANY 2
+#define PERM_EXACT 3
+
+struct perm_data {
+ int op;
+ int mode;
+};
+
+
+/*
+ * External function definitions
+ */
+extern int parse_action(char *, int verbose);
+extern void dump_actions();
+extern void *eval_frag_actions(struct dir_info *, struct dir_ent *, int);
+extern void *get_frag_action(void *);
+extern int eval_exclude_actions(char *, char *, char *, struct stat *,
+ unsigned int, struct dir_ent *);
+extern void eval_actions(struct dir_info *, struct dir_ent *);
+extern int eval_empty_actions(struct dir_info *, struct dir_ent *dir_ent);
+extern void eval_move_actions(struct dir_info *, struct dir_ent *);
+extern int eval_prune_actions(struct dir_info *, struct dir_ent *);
+extern struct xattr_data *eval_xattr_exc_actions(struct dir_info *,
+ struct dir_ent *);
+extern int match_xattr_exc_actions(struct xattr_data *, char *);
+extern struct xattr_data *eval_xattr_inc_actions(struct dir_info *,
+ struct dir_ent *);
+extern int match_xattr_inc_actions(struct xattr_data *, char *);
+extern struct xattr_add *eval_xattr_add_actions(struct dir_info *root,
+ struct dir_ent *dir_ent, int *items);
+extern void do_move_actions();
+extern long long read_bytes(int, void *, long long);
+extern int any_actions();
+extern int actions();
+extern int move_actions();
+extern int empty_actions();
+extern int read_action_file(char *, int);
+extern int exclude_actions();
+extern int prune_actions();
+extern int xattr_exc_actions();
+extern int xattr_add_actions();
+#endif
diff --git a/squashfs-tools/caches-queues-lists.c b/squashfs-tools/caches-queues-lists.c
new file mode 100644
index 0000000..f6bcba4
--- /dev/null
+++ b/squashfs-tools/caches-queues-lists.c
@@ -0,0 +1,647 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014, 2019, 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * caches-queues-lists.c
+ */
+
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "mksquashfs_error.h"
+#include "caches-queues-lists.h"
+
+extern int add_overflow(int, int);
+extern int multiply_overflow(int, int);
+
+#define TRUE 1
+#define FALSE 0
+
+struct queue *queue_init(int size)
+{
+ struct queue *queue = malloc(sizeof(struct queue));
+
+ if(queue == NULL)
+ MEM_ERROR();
+
+ if(add_overflow(size, 1) ||
+ multiply_overflow(size + 1, sizeof(void *)))
+ BAD_ERROR("Size too large in queue_init\n");
+
+ queue->data = malloc(sizeof(void *) * (size + 1));
+ if(queue->data == NULL)
+ MEM_ERROR();
+
+ queue->size = size + 1;
+ queue->readp = queue->writep = 0;
+ pthread_mutex_init(&queue->mutex, NULL);
+ pthread_cond_init(&queue->empty, NULL);
+ pthread_cond_init(&queue->full, NULL);
+
+ return queue;
+}
+
+
+void queue_put(struct queue *queue, void *data)
+{
+ int nextp;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+ pthread_mutex_lock(&queue->mutex);
+
+ while((nextp = (queue->writep + 1) % queue->size) == queue->readp)
+ pthread_cond_wait(&queue->full, &queue->mutex);
+
+ queue->data[queue->writep] = data;
+ queue->writep = nextp;
+ pthread_cond_signal(&queue->empty);
+ pthread_cleanup_pop(1);
+}
+
+
+void *queue_get(struct queue *queue)
+{
+ void *data;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+ pthread_mutex_lock(&queue->mutex);
+
+ while(queue->readp == queue->writep)
+ pthread_cond_wait(&queue->empty, &queue->mutex);
+
+ data = queue->data[queue->readp];
+ queue->readp = (queue->readp + 1) % queue->size;
+ pthread_cond_signal(&queue->full);
+ pthread_cleanup_pop(1);
+
+ return data;
+}
+
+
+int queue_empty(struct queue *queue)
+{
+ int empty;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+ pthread_mutex_lock(&queue->mutex);
+
+ empty = queue->readp == queue->writep;
+
+ pthread_cleanup_pop(1);
+
+ return empty;
+}
+
+
+void queue_flush(struct queue *queue)
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+ pthread_mutex_lock(&queue->mutex);
+
+ queue->readp = queue->writep;
+
+ pthread_cleanup_pop(1);
+}
+
+
+void dump_queue(struct queue *queue)
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+ pthread_mutex_lock(&queue->mutex);
+
+ printf("\tMax size %d, size %d%s\n", queue->size - 1,
+ queue->readp <= queue->writep ? queue->writep - queue->readp :
+ queue->size - queue->readp + queue->writep,
+ queue->readp == queue->writep ? " (EMPTY)" :
+ ((queue->writep + 1) % queue->size) == queue->readp ?
+ " (FULL)" : "");
+
+ pthread_cleanup_pop(1);
+}
+
+
+/* define seq queue hash tables */
+#define CALCULATE_SEQ_HASH(N) CALCULATE_HASH(N)
+
+/* Called with the seq queue mutex held */
+INSERT_HASH_TABLE(seq, struct seq_queue, CALCULATE_SEQ_HASH, sequence, seq)
+
+/* Called with the cache mutex held */
+REMOVE_HASH_TABLE(seq, struct seq_queue, CALCULATE_SEQ_HASH, sequence, seq);
+
+
+struct seq_queue *seq_queue_init()
+{
+ struct seq_queue *queue = malloc(sizeof(struct seq_queue));
+ if(queue == NULL)
+ MEM_ERROR();
+
+ memset(queue, 0, sizeof(struct seq_queue));
+
+ pthread_mutex_init(&queue->mutex, NULL);
+ pthread_cond_init(&queue->wait, NULL);
+
+ return queue;
+}
+
+
+void seq_queue_put(struct seq_queue *queue, struct file_buffer *entry)
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+ pthread_mutex_lock(&queue->mutex);
+
+ insert_seq_hash_table(queue, entry);
+
+ if(entry->fragment)
+ queue->fragment_count ++;
+ else
+ queue->block_count ++;
+
+ if(entry->sequence == queue->sequence)
+ pthread_cond_signal(&queue->wait);
+
+ pthread_cleanup_pop(1);
+}
+
+
+struct file_buffer *seq_queue_get(struct seq_queue *queue)
+{
+ /*
+ * Return next buffer from queue in sequence order (queue->sequence). If
+ * found return it, otherwise wait for it to arrive.
+ */
+ int hash = CALCULATE_SEQ_HASH(queue->sequence);
+ struct file_buffer *entry;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+ pthread_mutex_lock(&queue->mutex);
+
+ while(1) {
+ for(entry = queue->hash_table[hash]; entry;
+ entry = entry->seq_next)
+ if(entry->sequence == queue->sequence)
+ break;
+
+ if(entry) {
+ /*
+ * found the buffer in the queue, decrement the
+ * appropriate count, and remove from hash list
+ */
+ if(entry->fragment)
+ queue->fragment_count --;
+ else
+ queue->block_count --;
+
+ remove_seq_hash_table(queue, entry);
+
+ queue->sequence ++;
+
+ break;
+ }
+
+ /* entry not found, wait for it to arrive */
+ pthread_cond_wait(&queue->wait, &queue->mutex);
+ }
+
+ pthread_cleanup_pop(1);
+
+ return entry;
+}
+
+
+void seq_queue_flush(struct seq_queue *queue)
+{
+ int i;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+ pthread_mutex_lock(&queue->mutex);
+
+ for(i = 0; i < HASH_SIZE; i++)
+ queue->hash_table[i] = NULL;
+
+ queue->fragment_count = queue->block_count = 0;
+
+ pthread_cleanup_pop(1);
+}
+
+
+void dump_seq_queue(struct seq_queue *queue, int fragment_queue)
+{
+ int size;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
+ pthread_mutex_lock(&queue->mutex);
+
+ size = fragment_queue ? queue->fragment_count : queue->block_count;
+
+ printf("\tMax size unlimited, size %d%s\n", size,
+ size == 0 ? " (EMPTY)" : "");
+
+ pthread_cleanup_pop(1);
+}
+
+
+/* define cache hash tables */
+#define CALCULATE_CACHE_HASH(N) CALCULATE_HASH(llabs(N))
+
+/* Called with the cache mutex held */
+INSERT_HASH_TABLE(cache, struct cache, CALCULATE_CACHE_HASH, index, hash)
+
+/* Called with the cache mutex held */
+REMOVE_HASH_TABLE(cache, struct cache, CALCULATE_CACHE_HASH, index, hash);
+
+/* define cache free list */
+
+/* Called with the cache mutex held */
+INSERT_LIST(free, struct file_buffer)
+
+/* Called with the cache mutex held */
+REMOVE_LIST(free, struct file_buffer)
+
+
+struct cache *cache_init(int buffer_size, int max_buffers, int noshrink_lookup,
+ int first_freelist)
+{
+ struct cache *cache = malloc(sizeof(struct cache));
+
+ if(cache == NULL)
+ MEM_ERROR();
+
+ cache->max_buffers = max_buffers;
+ cache->buffer_size = buffer_size;
+ cache->count = 0;
+ cache->used = 0;
+ cache->free_list = NULL;
+
+ /*
+ * The cache will grow up to max_buffers in size in response to
+ * an increase in readhead/number of buffers in flight. But
+ * once the outstanding buffers gets returned, we can either elect
+ * to shrink the cache, or to put the freed blocks onto a free list.
+ *
+ * For the caches where we want to do lookup (fragment/writer),
+ * a don't shrink policy is best, for the reader cache it
+ * makes no sense to keep buffers around longer than necessary as
+ * we don't do any lookup on those blocks.
+ */
+ cache->noshrink_lookup = noshrink_lookup;
+
+ /*
+ * The default use freelist before growing cache policy behaves
+ * poorly with appending - with many duplicates the caches
+ * do not grow due to the fact that large queues of outstanding
+ * fragments/writer blocks do not occur, leading to small caches
+ * and un-uncessary performance loss to frequent cache
+ * replacement in the small caches. Therefore with appending
+ * change the policy to grow the caches before reusing blocks
+ * from the freelist
+ */
+ cache->first_freelist = first_freelist;
+
+ memset(cache->hash_table, 0, sizeof(struct file_buffer *) * 65536);
+ pthread_mutex_init(&cache->mutex, NULL);
+ pthread_cond_init(&cache->wait_for_free, NULL);
+ pthread_cond_init(&cache->wait_for_unlock, NULL);
+
+ return cache;
+}
+
+
+struct file_buffer *cache_lookup(struct cache *cache, long long index)
+{
+ /* Lookup block in the cache, if found return with usage count
+ * incremented, if not found return NULL */
+ int hash = CALCULATE_CACHE_HASH(index);
+ struct file_buffer *entry;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+ pthread_mutex_lock(&cache->mutex);
+
+ for(entry = cache->hash_table[hash]; entry; entry = entry->hash_next)
+ if(entry->index == index)
+ break;
+
+ if(entry) {
+ /* found the block in the cache, increment used count and
+ * if necessary remove from free list so it won't disappear
+ */
+ if(entry->used == 0) {
+ remove_free_list(&cache->free_list, entry);
+ cache->used ++;
+ }
+ entry->used ++;
+ }
+
+ pthread_cleanup_pop(1);
+
+ return entry;
+}
+
+
+static struct file_buffer *cache_freelist(struct cache *cache)
+{
+ struct file_buffer *entry = cache->free_list;
+
+ remove_free_list(&cache->free_list, entry);
+
+ /* a block on the free_list is hashed */
+ remove_cache_hash_table(cache, entry);
+
+ cache->used ++;
+ return entry;
+}
+
+
+static struct file_buffer *cache_alloc(struct cache *cache)
+{
+ struct file_buffer *entry = malloc(sizeof(struct file_buffer) +
+ cache->buffer_size);
+ if(entry == NULL)
+ MEM_ERROR();
+
+ entry->cache = cache;
+ entry->free_prev = entry->free_next = NULL;
+ cache->count ++;
+ return entry;
+}
+
+
+static struct file_buffer *_cache_get(struct cache *cache, long long index,
+ int hash)
+{
+ /* Get a free block out of the cache indexed on index. */
+ struct file_buffer *entry = NULL;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+ pthread_mutex_lock(&cache->mutex);
+
+ while(1) {
+ if(cache->noshrink_lookup) {
+ /* first try to get a block from the free list */
+ if(cache->first_freelist && cache->free_list)
+ entry = cache_freelist(cache);
+ else if(cache->count < cache->max_buffers) {
+ entry = cache_alloc(cache);
+ cache->used ++;
+ } else if(!cache->first_freelist && cache->free_list)
+ entry = cache_freelist(cache);
+ } else { /* shrinking non-lookup cache */
+ if(cache->count < cache->max_buffers) {
+ entry = cache_alloc(cache);
+ if(cache->count > cache->max_count)
+ cache->max_count = cache->count;
+ }
+ }
+
+ if(entry)
+ break;
+
+ /* wait for a block */
+ pthread_cond_wait(&cache->wait_for_free, &cache->mutex);
+ }
+
+ /* initialise block and if hash is set insert into the hash table */
+ entry->used = 1;
+ entry->locked = FALSE;
+ entry->wait_on_unlock = FALSE;
+ entry->error = FALSE;
+ if(hash) {
+ entry->index = index;
+ insert_cache_hash_table(cache, entry);
+ }
+
+ pthread_cleanup_pop(1);
+
+ return entry;
+}
+
+
+struct file_buffer *cache_get(struct cache *cache, long long index)
+{
+ return _cache_get(cache, index, 1);
+}
+
+
+struct file_buffer *cache_get_nohash(struct cache *cache)
+{
+ return _cache_get(cache, 0, 0);
+}
+
+
+void cache_hash(struct file_buffer *entry, long long index)
+{
+ struct cache *cache = entry->cache;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+ pthread_mutex_lock(&cache->mutex);
+
+ entry->index = index;
+ insert_cache_hash_table(cache, entry);
+
+ pthread_cleanup_pop(1);
+}
+
+
+void cache_block_put(struct file_buffer *entry)
+{
+ struct cache *cache;
+
+ /*
+ * Finished with this cache entry, once the usage count reaches zero it
+ * can be reused.
+ *
+ * If noshrink_lookup is set, put the block onto the free list.
+ * As blocks remain accessible via the hash table they can be found
+ * getting a new lease of life before they are reused.
+ *
+ * if noshrink_lookup is not set then shrink the cache.
+ */
+
+ if(entry == NULL)
+ return;
+
+ if(entry->cache == NULL) {
+ free(entry);
+ return;
+ }
+
+ cache = entry->cache;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+ pthread_mutex_lock(&cache->mutex);
+
+ entry->used --;
+ if(entry->used == 0) {
+ if(cache->noshrink_lookup) {
+ insert_free_list(&cache->free_list, entry);
+ cache->used --;
+ } else {
+ free(entry);
+ cache->count --;
+ }
+
+ /* One or more threads may be waiting on this block */
+ pthread_cond_signal(&cache->wait_for_free);
+ }
+
+ pthread_cleanup_pop(1);
+}
+
+
+void dump_cache(struct cache *cache)
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+ pthread_mutex_lock(&cache->mutex);
+
+ if(cache->noshrink_lookup)
+ printf("\tMax buffers %d, Current size %d, Used %d, %s\n",
+ cache->max_buffers, cache->count, cache->used,
+ cache->free_list ? "Free buffers" : "No free buffers");
+ else
+ printf("\tMax buffers %d, Current size %d, Maximum historical "
+ "size %d\n", cache->max_buffers, cache->count,
+ cache->max_count);
+
+ pthread_cleanup_pop(1);
+}
+
+
+struct file_buffer *cache_get_nowait(struct cache *cache, long long index)
+{
+ struct file_buffer *entry = NULL;
+ /*
+ * block doesn't exist, create it, but return it with the
+ * locked flag set, so nothing tries to use it while it doesn't
+ * contain data.
+ *
+ * If there's no space in the cache then return NULL.
+ */
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+ pthread_mutex_lock(&cache->mutex);
+
+ /* first try to get a block from the free list */
+ if(cache->first_freelist && cache->free_list)
+ entry = cache_freelist(cache);
+ else if(cache->count < cache->max_buffers) {
+ entry = cache_alloc(cache);
+ cache->used ++;
+ } else if(!cache->first_freelist && cache->free_list)
+ entry = cache_freelist(cache);
+
+ if(entry) {
+ /* initialise block and insert into the hash table */
+ entry->used = 1;
+ entry->locked = TRUE;
+ entry->wait_on_unlock = FALSE;
+ entry->error = FALSE;
+ entry->index = index;
+ insert_cache_hash_table(cache, entry);
+ }
+
+ pthread_cleanup_pop(1);
+
+ return entry;
+}
+
+
+struct file_buffer *cache_lookup_nowait(struct cache *cache, long long index,
+ char *locked)
+{
+ /*
+ * Lookup block in the cache, if found return it with the locked flag
+ * indicating whether it is currently locked. In both cases increment
+ * the used count.
+ *
+ * If it doesn't exist in the cache return NULL;
+ */
+ int hash = CALCULATE_CACHE_HASH(index);
+ struct file_buffer *entry;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+ pthread_mutex_lock(&cache->mutex);
+
+ /* first check if the entry already exists */
+ for(entry = cache->hash_table[hash]; entry; entry = entry->hash_next)
+ if(entry->index == index)
+ break;
+
+ if(entry) {
+ if(entry->used == 0) {
+ remove_free_list(&cache->free_list, entry);
+ cache->used ++;
+ }
+ entry->used ++;
+ *locked = entry->locked;
+ }
+
+ pthread_cleanup_pop(1);
+
+ return entry;
+}
+
+
+void cache_wait_unlock(struct file_buffer *buffer)
+{
+ struct cache *cache = buffer->cache;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+ pthread_mutex_lock(&cache->mutex);
+
+ while(buffer->locked) {
+ /*
+ * another thread is filling this in, wait until it
+ * becomes unlocked. Used has been incremented to ensure it
+ * doesn't get reused. By definition a block can't be
+ * locked and unused, and so we don't need to worry
+ * about it being on the freelist now, but, it may
+ * become unused when unlocked unless used is
+ * incremented
+ */
+ buffer->wait_on_unlock = TRUE;
+ pthread_cond_wait(&cache->wait_for_unlock, &cache->mutex);
+ }
+
+ pthread_cleanup_pop(1);
+}
+
+
+void cache_unlock(struct file_buffer *entry)
+{
+ struct cache *cache = entry->cache;
+
+ /*
+ * Unlock this locked cache entry. If anything is waiting for this
+ * to become unlocked, wake it up.
+ */
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
+ pthread_mutex_lock(&cache->mutex);
+
+ entry->locked = FALSE;
+
+ if(entry->wait_on_unlock) {
+ entry->wait_on_unlock = FALSE;
+ pthread_cond_broadcast(&cache->wait_for_unlock);
+ }
+
+ pthread_cleanup_pop(1);
+}
diff --git a/squashfs-tools/caches-queues-lists.h b/squashfs-tools/caches-queues-lists.h
new file mode 100644
index 0000000..353946b
--- /dev/null
+++ b/squashfs-tools/caches-queues-lists.h
@@ -0,0 +1,200 @@
+#ifndef CACHES_QUEUES_LISTS_H
+#define CACHES_QUEUES_LISTS_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014, 2019, 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * caches-queues-lists.h
+ */
+
+#define INSERT_LIST(NAME, TYPE) \
+void insert_##NAME##_list(TYPE **list, TYPE *entry) { \
+ if(*list) { \
+ entry->NAME##_next = *list; \
+ entry->NAME##_prev = (*list)->NAME##_prev; \
+ (*list)->NAME##_prev->NAME##_next = entry; \
+ (*list)->NAME##_prev = entry; \
+ } else { \
+ *list = entry; \
+ entry->NAME##_prev = entry->NAME##_next = entry; \
+ } \
+}
+
+
+#define REMOVE_LIST(NAME, TYPE) \
+void remove_##NAME##_list(TYPE **list, TYPE *entry) { \
+ if(entry->NAME##_prev == entry && entry->NAME##_next == entry) { \
+ /* only this entry in the list */ \
+ *list = NULL; \
+ } else if(entry->NAME##_prev != NULL && entry->NAME##_next != NULL) { \
+ /* more than one entry in the list */ \
+ entry->NAME##_next->NAME##_prev = entry->NAME##_prev; \
+ entry->NAME##_prev->NAME##_next = entry->NAME##_next; \
+ if(*list == entry) \
+ *list = entry->NAME##_next; \
+ } \
+ entry->NAME##_prev = entry->NAME##_next = NULL; \
+}
+
+
+#define INSERT_HASH_TABLE(NAME, TYPE, HASH_FUNCTION, FIELD, LINK) \
+void insert_##NAME##_hash_table(TYPE *container, struct file_buffer *entry) \
+{ \
+ int hash = HASH_FUNCTION(entry->FIELD); \
+\
+ entry->LINK##_next = container->hash_table[hash]; \
+ container->hash_table[hash] = entry; \
+ entry->LINK##_prev = NULL; \
+ if(entry->LINK##_next) \
+ entry->LINK##_next->LINK##_prev = entry; \
+}
+
+
+#define REMOVE_HASH_TABLE(NAME, TYPE, HASH_FUNCTION, FIELD, LINK) \
+void remove_##NAME##_hash_table(TYPE *container, struct file_buffer *entry) \
+{ \
+ if(entry->LINK##_prev) \
+ entry->LINK##_prev->LINK##_next = entry->LINK##_next; \
+ else \
+ container->hash_table[HASH_FUNCTION(entry->FIELD)] = \
+ entry->LINK##_next; \
+ if(entry->LINK##_next) \
+ entry->LINK##_next->LINK##_prev = entry->LINK##_prev; \
+\
+ entry->LINK##_prev = entry->LINK##_next = NULL; \
+}
+
+#define HASH_SIZE 65536
+#define CALCULATE_HASH(n) ((n) & 0xffff)
+
+
+/* struct describing a cache entry passed between threads */
+struct file_buffer {
+ long long index;
+ long long sequence;
+ long long file_size;
+ union {
+ long long block;
+ unsigned short checksum;
+ };
+ struct cache *cache;
+ union {
+ struct file_info *dupl_start;
+ struct file_buffer *hash_next;
+ };
+ union {
+ struct tar_file *tar_file;
+ struct file_buffer *hash_prev;
+ };
+ union {
+ struct {
+ struct file_buffer *free_next;
+ struct file_buffer *free_prev;
+ };
+ struct {
+ struct file_buffer *seq_next;
+ struct file_buffer *seq_prev;
+ };
+ };
+ int size;
+ int c_byte;
+ char used;
+ char fragment;
+ char error;
+ char locked;
+ char wait_on_unlock;
+ char noD;
+ char duplicate;
+ char data[0] __attribute__((aligned));
+};
+
+
+/* struct describing queues used to pass data between threads */
+struct queue {
+ int size;
+ int readp;
+ int writep;
+ pthread_mutex_t mutex;
+ pthread_cond_t empty;
+ pthread_cond_t full;
+ void **data;
+};
+
+
+/*
+ * struct describing seq_queues used to pass data between the read
+ * thread and the deflate and main threads
+ */
+struct seq_queue {
+ int fragment_count;
+ int block_count;
+ long long sequence;
+ struct file_buffer *hash_table[HASH_SIZE];
+ pthread_mutex_t mutex;
+ pthread_cond_t wait;
+};
+
+
+/* Cache status struct. Caches are used to keep
+ track of memory buffers passed between different threads */
+struct cache {
+ int max_buffers;
+ int count;
+ int buffer_size;
+ int noshrink_lookup;
+ int first_freelist;
+ union {
+ int used;
+ int max_count;
+ };
+ pthread_mutex_t mutex;
+ pthread_cond_t wait_for_free;
+ pthread_cond_t wait_for_unlock;
+ struct file_buffer *free_list;
+ struct file_buffer *hash_table[HASH_SIZE];
+};
+
+
+extern struct queue *queue_init(int);
+extern void queue_put(struct queue *, void *);
+extern void *queue_get(struct queue *);
+extern int queue_empty(struct queue *);
+extern void queue_flush(struct queue *);
+extern void dump_queue(struct queue *);
+extern struct seq_queue *seq_queue_init();
+extern void seq_queue_put(struct seq_queue *, struct file_buffer *);
+extern void dump_seq_queue(struct seq_queue *, int);
+extern struct file_buffer *seq_queue_get(struct seq_queue *);
+extern void seq_queue_flush(struct seq_queue *);
+extern struct cache *cache_init(int, int, int, int);
+extern struct file_buffer *cache_lookup(struct cache *, long long);
+extern struct file_buffer *cache_get(struct cache *, long long);
+extern struct file_buffer *cache_get_nohash(struct cache *);
+extern void cache_hash(struct file_buffer *, long long);
+extern void cache_block_put(struct file_buffer *);
+extern void dump_cache(struct cache *);
+extern struct file_buffer *cache_get_nowait(struct cache *, long long);
+extern struct file_buffer *cache_lookup_nowait(struct cache *, long long,
+ char *);
+extern void cache_wait_unlock(struct file_buffer *);
+extern void cache_unlock(struct file_buffer *);
+
+extern int first_freelist;
+#endif
diff --git a/squashfs-tools/compressor.c b/squashfs-tools/compressor.c
new file mode 100644
index 0000000..32300a9
--- /dev/null
+++ b/squashfs-tools/compressor.c
@@ -0,0 +1,145 @@
+/*
+ *
+ * Copyright (c) 2009, 2010, 2011, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * compressor.c
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "compressor.h"
+#include "squashfs_fs.h"
+
+#ifndef GZIP_SUPPORT
+static struct compressor gzip_comp_ops = {
+ ZLIB_COMPRESSION, "gzip"
+};
+#else
+extern struct compressor gzip_comp_ops;
+#endif
+
+#ifndef LZMA_SUPPORT
+static struct compressor lzma_comp_ops = {
+ LZMA_COMPRESSION, "lzma"
+};
+#else
+extern struct compressor lzma_comp_ops;
+#endif
+
+#ifndef LZO_SUPPORT
+static struct compressor lzo_comp_ops = {
+ LZO_COMPRESSION, "lzo"
+};
+#else
+extern struct compressor lzo_comp_ops;
+#endif
+
+#ifndef LZ4_SUPPORT
+static struct compressor lz4_comp_ops = {
+ LZ4_COMPRESSION, "lz4"
+};
+#else
+extern struct compressor lz4_comp_ops;
+#endif
+
+#ifndef XZ_SUPPORT
+static struct compressor xz_comp_ops = {
+ XZ_COMPRESSION, "xz"
+};
+#else
+extern struct compressor xz_comp_ops;
+#endif
+
+#ifndef ZSTD_SUPPORT
+static struct compressor zstd_comp_ops = {
+ ZSTD_COMPRESSION, "zstd"
+};
+#else
+extern struct compressor zstd_comp_ops;
+#endif
+
+static struct compressor unknown_comp_ops = {
+ 0, "unknown"
+};
+
+
+struct compressor *compressor[] = {
+ &gzip_comp_ops,
+ &lzo_comp_ops,
+ &lz4_comp_ops,
+ &xz_comp_ops,
+ &zstd_comp_ops,
+ &lzma_comp_ops,
+ &unknown_comp_ops
+};
+
+
+struct compressor *lookup_compressor(char *name)
+{
+ int i;
+
+ for(i = 0; compressor[i]->id; i++)
+ if(strcmp(compressor[i]->name, name) == 0)
+ break;
+
+ return compressor[i];
+}
+
+
+struct compressor *lookup_compressor_id(int id)
+{
+ int i;
+
+ for(i = 0; compressor[i]->id; i++)
+ if(id == compressor[i]->id)
+ break;
+
+ return compressor[i];
+}
+
+
+void display_compressors(FILE *stream, char *indent, char *def_comp)
+{
+ int i;
+
+ for(i = 0; compressor[i]->id; i++)
+ if(compressor[i]->supported)
+ fprintf(stream, "%s\t%s%s\n", indent,
+ compressor[i]->name,
+ strcmp(compressor[i]->name, def_comp) == 0 ?
+ " (default)" : "");
+}
+
+
+void display_compressor_usage(FILE *stream, char *def_comp)
+{
+ int i;
+
+ for(i = 0; compressor[i]->id; i++)
+ if(compressor[i]->supported) {
+ char *str = strcmp(compressor[i]->name, def_comp) == 0 ?
+ " (default)" : "";
+ if(compressor[i]->usage) {
+ fprintf(stream, "\t%s%s\n",
+ compressor[i]->name, str);
+ compressor[i]->usage(stream);
+ } else
+ fprintf(stream, "\t%s (no options)%s\n",
+ compressor[i]->name, str);
+ }
+}
diff --git a/squashfs-tools/compressor.h b/squashfs-tools/compressor.h
new file mode 100644
index 0000000..ba0de25
--- /dev/null
+++ b/squashfs-tools/compressor.h
@@ -0,0 +1,132 @@
+#ifndef COMPRESSOR_H
+#define COMPRESSOR_H
+/*
+ *
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * compressor.h
+ */
+
+struct compressor {
+ int id;
+ char *name;
+ int supported;
+ int (*init)(void **, int, int);
+ int (*compress)(void *, void *, void *, int, int, int *);
+ int (*uncompress)(void *, void *, int, int, int *);
+ int (*options)(char **, int);
+ int (*options_post)(int);
+ void *(*dump_options)(int, int *);
+ int (*extract_options)(int, void *, int);
+ int (*check_options)(int, void *, int);
+ void (*display_options)(void *, int);
+ void (*usage)(FILE *);
+ int (*option_args)(char *);
+};
+
+extern struct compressor *lookup_compressor(char *);
+extern struct compressor *lookup_compressor_id(int);
+extern void display_compressors(FILE *stream, char *, char *);
+extern void display_compressor_usage(FILE *stream, char *);
+
+static inline int compressor_init(struct compressor *comp, void **stream,
+ int block_size, int datablock)
+{
+ if(comp->init == NULL)
+ return 0;
+ return comp->init(stream, block_size, datablock);
+}
+
+
+static inline int compressor_compress(struct compressor *comp, void *strm,
+ void *dest, void *src, int size, int block_size, int *error)
+{
+ return comp->compress(strm, dest, src, size, block_size, error);
+}
+
+
+static inline int compressor_uncompress(struct compressor *comp, void *dest,
+ void *src, int size, int block_size, int *error)
+{
+ return comp->uncompress(dest, src, size, block_size, error);
+}
+
+
+/*
+ * For the following functions please see the lzo, lz4 or xz
+ * compressors for commented examples of how they are used.
+ */
+static inline int compressor_options(struct compressor *comp, char *argv[],
+ int argc)
+{
+ if(comp->options == NULL)
+ return -1;
+
+ return comp->options(argv, argc);
+}
+
+
+static inline int compressor_options_post(struct compressor *comp, int block_size)
+{
+ if(comp->options_post == NULL)
+ return 0;
+ return comp->options_post(block_size);
+}
+
+
+static inline void *compressor_dump_options(struct compressor *comp,
+ int block_size, int *size)
+{
+ if(comp->dump_options == NULL)
+ return NULL;
+ return comp->dump_options(block_size, size);
+}
+
+
+static inline int compressor_extract_options(struct compressor *comp,
+ int block_size, void *buffer, int size)
+{
+ if(comp->extract_options == NULL)
+ return size ? -1 : 0;
+ return comp->extract_options(block_size, buffer, size);
+}
+
+
+static inline int compressor_check_options(struct compressor *comp,
+ int block_size, void *buffer, int size)
+{
+ if(comp->check_options == NULL)
+ return 0;
+ return comp->check_options(block_size, buffer, size);
+}
+
+
+static inline void compressor_display_options(struct compressor *comp,
+ void *buffer, int size)
+{
+ if(comp->display_options != NULL)
+ comp->display_options(buffer, size);
+}
+
+static inline int compressor_option_args(struct compressor *comp, char *option)
+{
+ if(comp == NULL || comp->option_args == NULL)
+ return 0;
+ return comp->option_args(option);
+}
+#endif
diff --git a/squashfs-tools/date.c b/squashfs-tools/date.c
new file mode 100644
index 0000000..74320ef
--- /dev/null
+++ b/squashfs-tools/date.c
@@ -0,0 +1,129 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * date.c
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "date.h"
+#include "error.h"
+
+int exec_date(char *string, unsigned int *mtime)
+{
+ int res, pipefd[2], child, status;
+ int bytes = 0;
+ long long time;
+ char buffer[11];
+
+ res = pipe(pipefd);
+ if(res == -1) {
+ ERROR("Error executing date, pipe failed\n");
+ return FALSE;
+ }
+
+ child = fork();
+ if(child == -1) {
+ ERROR("Error executing date, fork failed\n");
+ goto failed;
+ }
+
+ if(child == 0) {
+ close(pipefd[0]);
+ close(STDOUT_FILENO);
+ res = dup(pipefd[1]);
+ if(res == -1)
+ exit(EXIT_FAILURE);
+
+ execl("/usr/bin/date", "date", "-d", string, "+%s", (char *) NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ close(pipefd[1]);
+
+ while(1) {
+ res = read_bytes(pipefd[0], buffer, 11);
+ if(res == -1) {
+ ERROR("Error executing date\n");
+ goto failed2;
+ } else if(res == 0)
+ break;
+
+ bytes += res;
+ }
+
+ while(1) {
+ res = waitpid(child, &status, 0);
+ if(res != -1)
+ break;
+ else if(errno != EINTR) {
+ ERROR("Error executing data, waitpid failed\n");
+ goto failed2;
+ }
+ }
+
+ close(pipefd[0]);
+
+ if(!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ ERROR("Error executing date, failed to parse date string\n");
+ return FALSE;
+ }
+
+ if(bytes == 0 || bytes > 11) {
+ ERROR("Error executing date, unexpected result\n");
+ return FALSE;
+ }
+
+ /* replace trailing newline with string terminator */
+ buffer[bytes - 1] = '\0';
+
+ res = sscanf(buffer, "%lld", &time);
+
+ if(res < 1) {
+ ERROR("Error, unexpected result from date\n");
+ return FALSE;
+ }
+
+ if(time < 0) {
+ ERROR("Error, negative number returned from date, dates should be on or after the epoch of 1970-01-01 00:00 UTC\n");
+ return FALSE;
+ }
+
+ if(time > UINT_MAX) {
+ ERROR("Error, number returned from date >= 2^32, dates should be before 2106-02-07 06:28:16 UTC\n");
+ return FALSE;
+ }
+
+ *mtime = (unsigned int) time;
+
+ return TRUE;
+
+failed:
+ close(pipefd[1]);
+failed2:
+ close(pipefd[0]);
+ return FALSE;
+}
diff --git a/squashfs-tools/date.h b/squashfs-tools/date.h
new file mode 100644
index 0000000..55cc462
--- /dev/null
+++ b/squashfs-tools/date.h
@@ -0,0 +1,28 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * date.h
+ */
+
+extern long long read_bytes(int, void *, long long);
+
+#define TRUE 1
+#define FALSE 0
diff --git a/squashfs-tools/endian_compat.h b/squashfs-tools/endian_compat.h
new file mode 100644
index 0000000..c416f7f
--- /dev/null
+++ b/squashfs-tools/endian_compat.h
@@ -0,0 +1,34 @@
+#ifndef ENDIAN_COMPAT_H
+#define ENDIAN_COMPAT_H
+
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * endian_compat.h
+ */
+#ifndef linux
+#define __BYTE_ORDER BYTE_ORDER
+#define __BIG_ENDIAN BIG_ENDIAN
+#define __LITTLE_ENDIAN LITTLE_ENDIAN
+#else
+#include <endian.h>
+#endif
+
+#endif
diff --git a/squashfs-tools/error.h b/squashfs-tools/error.h
new file mode 100644
index 0000000..78e39c0
--- /dev/null
+++ b/squashfs-tools/error.h
@@ -0,0 +1,43 @@
+#ifndef ERROR_H
+#define ERROR_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2012, 2013, 2014, 2019, 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * error.h
+ */
+
+extern void progressbar_error(char *fmt, ...);
+extern void progressbar_info(char *fmt, ...);
+
+#ifdef SQUASHFS_TRACE
+#define TRACE(s, args...) \
+ do { \
+ progressbar_info("squashfs: "s, ## args);\
+ } while(0)
+#else
+#define TRACE(s, args...)
+#endif
+
+#define ERROR(s, args...) \
+ do {\
+ progressbar_error(s, ## args); \
+ } while(0)
+#endif
diff --git a/squashfs-tools/fnmatch_compat.h b/squashfs-tools/fnmatch_compat.h
new file mode 100644
index 0000000..7b4afd8
--- /dev/null
+++ b/squashfs-tools/fnmatch_compat.h
@@ -0,0 +1,32 @@
+#ifndef FNMATCH_COMPAT
+#define FNMATCH_COMPAT
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2015
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * fnmatch_compat.h
+ */
+
+#include <fnmatch.h>
+
+#ifndef FNM_EXTMATCH
+#define FNM_EXTMATCH 0
+#endif
+
+#endif
diff --git a/squashfs-tools/gzip_wrapper.c b/squashfs-tools/gzip_wrapper.c
new file mode 100644
index 0000000..3cb1cc0
--- /dev/null
+++ b/squashfs-tools/gzip_wrapper.c
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c) 2009, 2010, 2013, 2014, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * gzip_wrapper.c
+ *
+ * Support for ZLIB compression http://www.zlib.net
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <zlib.h>
+
+#include "squashfs_fs.h"
+#include "gzip_wrapper.h"
+#include "compressor.h"
+
+static struct strategy strategy[] = {
+ { "default", Z_DEFAULT_STRATEGY, 0 },
+ { "filtered", Z_FILTERED, 0 },
+ { "huffman_only", Z_HUFFMAN_ONLY, 0 },
+ { "run_length_encoded", Z_RLE, 0 },
+ { "fixed", Z_FIXED, 0 },
+ { NULL, 0, 0 }
+};
+
+static int strategy_count = 0;
+
+/* default compression level */
+static int compression_level = GZIP_DEFAULT_COMPRESSION_LEVEL;
+
+/* default window size */
+static int window_size = GZIP_DEFAULT_WINDOW_SIZE;
+
+/*
+ * This function is called by the options parsing code in mksquashfs.c
+ * to parse any -X compressor option.
+ *
+ * This function returns:
+ * >=0 (number of additional args parsed) on success
+ * -1 if the option was unrecognised, or
+ * -2 if the option was recognised, but otherwise bad in
+ * some way (e.g. invalid parameter)
+ *
+ * Note: this function sets internal compressor state, but does not
+ * pass back the results of the parsing other than success/failure.
+ * The gzip_dump_options() function is called later to get the options in
+ * a format suitable for writing to the filesystem.
+ */
+static int gzip_options(char *argv[], int argc)
+{
+ if(strcmp(argv[0], "-Xcompression-level") == 0) {
+ if(argc < 2) {
+ fprintf(stderr, "gzip: -Xcompression-level missing "
+ "compression level\n");
+ fprintf(stderr, "gzip: -Xcompression-level it "
+ "should be 1 >= n <= 9\n");
+ goto failed;
+ }
+
+ compression_level = atoi(argv[1]);
+ if(compression_level < 1 || compression_level > 9) {
+ fprintf(stderr, "gzip: -Xcompression-level invalid, it "
+ "should be 1 >= n <= 9\n");
+ goto failed;
+ }
+
+ return 1;
+ } else if(strcmp(argv[0], "-Xwindow-size") == 0) {
+ if(argc < 2) {
+ fprintf(stderr, "gzip: -Xwindow-size missing window "
+ " size\n");
+ fprintf(stderr, "gzip: -Xwindow-size <window-size>\n");
+ goto failed;
+ }
+
+ window_size = atoi(argv[1]);
+ if(window_size < 8 || window_size > 15) {
+ fprintf(stderr, "gzip: -Xwindow-size invalid, it "
+ "should be 8 >= n <= 15\n");
+ goto failed;
+ }
+
+ return 1;
+ } else if(strcmp(argv[0], "-Xstrategy") == 0) {
+ char *name;
+ int i;
+
+ if(argc < 2) {
+ fprintf(stderr, "gzip: -Xstrategy missing "
+ "strategies\n");
+ goto failed;
+ }
+
+ name = argv[1];
+ while(name[0] != '\0') {
+ for(i = 0; strategy[i].name; i++) {
+ int n = strlen(strategy[i].name);
+ if((strncmp(name, strategy[i].name, n) == 0) &&
+ (name[n] == '\0' ||
+ name[n] == ',')) {
+ if(strategy[i].selected == 0) {
+ strategy[i].selected = 1;
+ strategy_count++;
+ }
+ name += name[n] == ',' ? n + 1 : n;
+ break;
+ }
+ }
+ if(strategy[i].name == NULL) {
+ fprintf(stderr, "gzip: -Xstrategy unrecognised "
+ "strategy\n");
+ goto failed;
+ }
+ }
+
+ return 1;
+ }
+
+ return -1;
+
+failed:
+ return -2;
+}
+
+
+/*
+ * This function is called after all options have been parsed.
+ * It is used to do post-processing on the compressor options using
+ * values that were not expected to be known at option parse time.
+ *
+ * This function returns 0 on successful post processing, or
+ * -1 on error
+ */
+static int gzip_options_post(int block_size)
+{
+ if(strategy_count == 1 && strategy[0].selected) {
+ strategy_count = 0;
+ strategy[0].selected = 0;
+ }
+
+ return 0;
+}
+
+
+/*
+ * This function is called by mksquashfs to dump the parsed
+ * compressor options in a format suitable for writing to the
+ * compressor options field in the filesystem (stored immediately
+ * after the superblock).
+ *
+ * This function returns a pointer to the compression options structure
+ * to be stored (and the size), or NULL if there are no compression
+ * options
+ *
+ */
+static void *gzip_dump_options(int block_size, int *size)
+{
+ static struct gzip_comp_opts comp_opts;
+ int i, strategies = 0;
+
+ /*
+ * If default compression options of:
+ * compression-level: 8 and
+ * window-size: 15 and
+ * strategy_count == 0 then
+ * don't store a compression options structure (this is compatible
+ * with the legacy implementation of GZIP for Squashfs)
+ */
+ if(compression_level == GZIP_DEFAULT_COMPRESSION_LEVEL &&
+ window_size == GZIP_DEFAULT_WINDOW_SIZE &&
+ strategy_count == 0)
+ return NULL;
+
+ for(i = 0; strategy[i].name; i++)
+ strategies |= strategy[i].selected << i;
+
+ comp_opts.compression_level = compression_level;
+ comp_opts.window_size = window_size;
+ comp_opts.strategy = strategies;
+
+ SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
+
+ *size = sizeof(comp_opts);
+ return &comp_opts;
+}
+
+
+/*
+ * This function is a helper specifically for the append mode of
+ * mksquashfs. Its purpose is to set the internal compressor state
+ * to the stored compressor options in the passed compressor options
+ * structure.
+ *
+ * In effect this function sets up the compressor options
+ * to the same state they were when the filesystem was originally
+ * generated, this is to ensure on appending, the compressor uses
+ * the same compression options that were used to generate the
+ * original filesystem.
+ *
+ * Note, even if there are no compressor options, this function is still
+ * called with an empty compressor structure (size == 0), to explicitly
+ * set the default options, this is to ensure any user supplied
+ * -X options on the appending mksquashfs command line are over-ridden
+ *
+ * This function returns 0 on sucessful extraction of options, and
+ * -1 on error
+ */
+static int gzip_extract_options(int block_size, void *buffer, int size)
+{
+ struct gzip_comp_opts *comp_opts = buffer;
+ int i;
+
+ if(size == 0) {
+ /* Set default values */
+ compression_level = GZIP_DEFAULT_COMPRESSION_LEVEL;
+ window_size = GZIP_DEFAULT_WINDOW_SIZE;
+ strategy_count = 0;
+ return 0;
+ }
+
+ /* we expect a comp_opts structure of sufficient size to be present */
+ if(size < sizeof(*comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ /* Check comp_opts structure for correctness */
+ if(comp_opts->compression_level < 1 ||
+ comp_opts->compression_level > 9) {
+ fprintf(stderr, "gzip: bad compression level in "
+ "compression options structure\n");
+ goto failed;
+ }
+ compression_level = comp_opts->compression_level;
+
+ if(comp_opts->window_size < 8 ||
+ comp_opts->window_size > 15) {
+ fprintf(stderr, "gzip: bad window size in "
+ "compression options structure\n");
+ goto failed;
+ }
+ window_size = comp_opts->window_size;
+
+ strategy_count = 0;
+ for(i = 0; strategy[i].name; i++) {
+ if((comp_opts->strategy >> i) & 1) {
+ strategy[i].selected = 1;
+ strategy_count ++;
+ } else
+ strategy[i].selected = 0;
+ }
+
+ return 0;
+
+failed:
+ fprintf(stderr, "gzip: error reading stored compressor options from "
+ "filesystem!\n");
+
+ return -1;
+}
+
+
+static void gzip_display_options(void *buffer, int size)
+{
+ struct gzip_comp_opts *comp_opts = buffer;
+ int i, printed;
+
+ /* we expect a comp_opts structure of sufficient size to be present */
+ if(size < sizeof(*comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ /* Check comp_opts structure for correctness */
+ if(comp_opts->compression_level < 1 ||
+ comp_opts->compression_level > 9) {
+ fprintf(stderr, "gzip: bad compression level in "
+ "compression options structure\n");
+ goto failed;
+ }
+ printf("\tcompression-level %d\n", comp_opts->compression_level);
+
+ if(comp_opts->window_size < 8 ||
+ comp_opts->window_size > 15) {
+ fprintf(stderr, "gzip: bad window size in "
+ "compression options structure\n");
+ goto failed;
+ }
+ printf("\twindow-size %d\n", comp_opts->window_size);
+
+ for(i = 0, printed = 0; strategy[i].name; i++) {
+ if((comp_opts->strategy >> i) & 1) {
+ if(printed)
+ printf(", ");
+ else
+ printf("\tStrategies selected: ");
+ printf("%s", strategy[i].name);
+ printed = 1;
+ }
+ }
+
+ if(!printed)
+ printf("\tStrategies selected: default\n");
+ else
+ printf("\n");
+
+ return;
+
+failed:
+ fprintf(stderr, "gzip: error reading stored compressor options from "
+ "filesystem!\n");
+}
+
+
+/*
+ * This function is called by mksquashfs to initialise the
+ * compressor, before compress() is called.
+ *
+ * This function returns 0 on success, and
+ * -1 on error
+ */
+static int gzip_init(void **strm, int block_size, int datablock)
+{
+ int i, j, res;
+ struct gzip_stream *stream;
+
+ if(!datablock || !strategy_count) {
+ stream = malloc(sizeof(*stream) + sizeof(struct gzip_strategy));
+ if(stream == NULL)
+ goto failed;
+
+ stream->strategies = 1;
+ stream->strategy[0].strategy = Z_DEFAULT_STRATEGY;
+ } else {
+ stream = malloc(sizeof(*stream) +
+ sizeof(struct gzip_strategy) * strategy_count);
+ if(stream == NULL)
+ goto failed;
+
+ memset(stream->strategy, 0, sizeof(struct gzip_strategy) *
+ strategy_count);
+
+ stream->strategies = strategy_count;
+
+ for(i = 0, j = 0; strategy[i].name; i++) {
+ if(!strategy[i].selected)
+ continue;
+
+ stream->strategy[j].strategy = strategy[i].strategy;
+ if(j) {
+ stream->strategy[j].buffer = malloc(block_size);
+ if(stream->strategy[j].buffer == NULL)
+ goto failed2;
+ }
+ j++;
+ }
+ }
+
+ stream->stream.zalloc = Z_NULL;
+ stream->stream.zfree = Z_NULL;
+ stream->stream.opaque = 0;
+
+ res = deflateInit2(&stream->stream, compression_level, Z_DEFLATED,
+ window_size, 8, stream->strategy[0].strategy);
+ if(res != Z_OK)
+ goto failed2;
+
+ *strm = stream;
+ return 0;
+
+failed2:
+ for(i = 1; i < stream->strategies; i++)
+ free(stream->strategy[i].buffer);
+ free(stream);
+failed:
+ return -1;
+}
+
+
+static int gzip_compress(void *strm, void *d, void *s, int size, int block_size,
+ int *error)
+{
+ int i, res;
+ struct gzip_stream *stream = strm;
+ struct gzip_strategy *selected = NULL;
+
+ stream->strategy[0].buffer = d;
+
+ for(i = 0; i < stream->strategies; i++) {
+ struct gzip_strategy *strategy = &stream->strategy[i];
+
+ res = deflateReset(&stream->stream);
+ if(res != Z_OK)
+ goto failed;
+
+ stream->stream.next_in = s;
+ stream->stream.avail_in = size;
+ stream->stream.next_out = strategy->buffer;
+ stream->stream.avail_out = block_size;
+
+ if(stream->strategies > 1) {
+ res = deflateParams(&stream->stream,
+ compression_level, strategy->strategy);
+ if(res != Z_OK)
+ goto failed;
+ }
+
+ stream->stream.total_out = 0;
+ res = deflate(&stream->stream, Z_FINISH);
+ strategy->length = stream->stream.total_out;
+ if(res == Z_STREAM_END) {
+ if(!selected || selected->length > strategy->length)
+ selected = strategy;
+ } else if(res != Z_OK)
+ goto failed;
+ }
+
+ if(!selected)
+ /*
+ * Output buffer overflow. Return out of buffer space
+ */
+ return 0;
+
+ if(selected->buffer != d)
+ memcpy(d, selected->buffer, selected->length);
+
+ return (int) selected->length;
+
+failed:
+ /*
+ * All other errors return failure, with the compressor
+ * specific error code in *error
+ */
+ *error = res;
+ return -1;
+}
+
+
+static int gzip_uncompress(void *d, void *s, int size, int outsize, int *error)
+{
+ int res;
+ unsigned long bytes = outsize;
+
+ res = uncompress(d, &bytes, s, size);
+
+ if(res == Z_OK)
+ return (int) bytes;
+ else {
+ *error = res;
+ return -1;
+ }
+}
+
+
+static void gzip_usage(FILE *stream)
+{
+ fprintf(stream, "\t -Xcompression-level <compression-level>\n");
+ fprintf(stream, "\t\t<compression-level> should be 1 .. 9 (default "
+ "%d)\n", GZIP_DEFAULT_COMPRESSION_LEVEL);
+ fprintf(stream, "\t -Xwindow-size <window-size>\n");
+ fprintf(stream, "\t\t<window-size> should be 8 .. 15 (default "
+ "%d)\n", GZIP_DEFAULT_WINDOW_SIZE);
+ fprintf(stream, "\t -Xstrategy strategy1,strategy2,...,strategyN\n");
+ fprintf(stream, "\t\tCompress using strategy1,strategy2,...,strategyN"
+ " in turn\n");
+ fprintf(stream, "\t\tand choose the best compression.\n");
+ fprintf(stream, "\t\tAvailable strategies: default, filtered, "
+ "huffman_only,\n\t\trun_length_encoded and fixed\n");
+}
+
+
+static int option_args(char *option)
+{
+ if(strcmp(option, "-Xcompression-level") == 0 ||
+ strcmp(option, "-Xwindow-size") == 0 ||
+ strcmp(option, "-Xstrategy") == 0)
+ return 1;
+
+ return 0;
+}
+
+
+struct compressor gzip_comp_ops = {
+ .init = gzip_init,
+ .compress = gzip_compress,
+ .uncompress = gzip_uncompress,
+ .options = gzip_options,
+ .options_post = gzip_options_post,
+ .dump_options = gzip_dump_options,
+ .extract_options = gzip_extract_options,
+ .display_options = gzip_display_options,
+ .usage = gzip_usage,
+ .option_args = option_args,
+ .id = ZLIB_COMPRESSION,
+ .name = "gzip",
+ .supported = 1
+};
diff --git a/squashfs-tools/gzip_wrapper.h b/squashfs-tools/gzip_wrapper.h
new file mode 100644
index 0000000..5f87373
--- /dev/null
+++ b/squashfs-tools/gzip_wrapper.h
@@ -0,0 +1,69 @@
+#ifndef GZIP_WRAPPER_H
+#define GZIP_WRAPPER_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2014
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * gzip_wrapper.h
+ *
+ */
+
+#include "endian_compat.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+extern unsigned int inswap_le16(unsigned short);
+extern unsigned int inswap_le32(unsigned int);
+
+#define SQUASHFS_INSWAP_COMP_OPTS(s) { \
+ (s)->compression_level = inswap_le32((s)->compression_level); \
+ (s)->window_size = inswap_le16((s)->window_size); \
+ (s)->strategy = inswap_le16((s)->strategy); \
+}
+#else
+#define SQUASHFS_INSWAP_COMP_OPTS(s)
+#endif
+
+/* Default compression */
+#define GZIP_DEFAULT_COMPRESSION_LEVEL 9
+#define GZIP_DEFAULT_WINDOW_SIZE 15
+
+struct gzip_comp_opts {
+ int compression_level;
+ short window_size;
+ short strategy;
+};
+
+struct strategy {
+ char *name;
+ int strategy;
+ int selected;
+};
+
+struct gzip_strategy {
+ int strategy;
+ int length;
+ void *buffer;
+};
+
+struct gzip_stream {
+ z_stream stream;
+ int strategies;
+ struct gzip_strategy strategy[0];
+};
+#endif
diff --git a/squashfs-tools/info.c b/squashfs-tools/info.c
new file mode 100644
index 0000000..49f0c72
--- /dev/null
+++ b/squashfs-tools/info.c
@@ -0,0 +1,174 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014, 2019, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * info.c
+ */
+
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "mksquashfs_error.h"
+#include "progressbar.h"
+#include "caches-queues-lists.h"
+#include "signals.h"
+
+static int silent = 0;
+static struct dir_ent *ent = NULL;
+
+pthread_t info_thread;
+
+
+void disable_info()
+{
+ ent = NULL;
+}
+
+
+void update_info(struct dir_ent *dir_ent)
+{
+ ent = dir_ent;
+}
+
+
+static void print_filename()
+{
+ struct dir_ent *dir_ent = ent;
+
+ if(dir_ent == NULL)
+ return;
+
+ if(dir_ent->our_dir->subpath[0] != '\0')
+ INFO("%s/%s\n", dir_ent->our_dir->subpath, dir_ent->name);
+ else
+ INFO("/%s\n", dir_ent->name);
+}
+
+
+static void dump_state()
+{
+ disable_progress_bar();
+
+ printf("Queue and Cache status dump\n");
+ printf("===========================\n");
+
+ printf("file buffer queue (reader thread -> deflate thread(s))\n");
+ dump_queue(to_deflate);
+
+ printf("uncompressed fragment queue (reader thread -> fragment"
+ " thread(s))\n");
+ dump_queue(to_process_frag);
+
+ printf("processed fragment queue (fragment thread(s) -> main"
+ " thread)\n");
+ dump_seq_queue(to_main, 1);
+
+ printf("compressed block queue (deflate thread(s) -> main thread)\n");
+ dump_seq_queue(to_main, 0);
+
+ printf("uncompressed packed fragment queue (main thread -> fragment"
+ " deflate thread(s))\n");
+ dump_queue(to_frag);
+
+ if(!reproducible) {
+ printf("locked frag queue (compressed frags waiting while multi-block"
+ " file is written)\n");
+ dump_queue(locked_fragment);
+
+ printf("compressed block queue (main & fragment deflate threads(s) ->"
+ " writer thread)\n");
+ dump_queue(to_writer);
+ } else {
+ printf("compressed fragment queue (fragment deflate threads(s) ->"
+ "fragment order thread)\n");
+
+ dump_seq_queue(to_order, 0);
+
+ printf("compressed block queue (main & fragment order threads ->"
+ " writer thread)\n");
+ dump_queue(to_writer);
+ }
+
+ printf("read cache (uncompressed blocks read by reader thread)\n");
+ dump_cache(reader_buffer);
+
+ printf("block write cache (compressed blocks waiting for the writer"
+ " thread)\n");
+ dump_cache(bwriter_buffer);
+ printf("fragment write cache (compressed fragments waiting for the"
+ " writer thread)\n");
+ dump_cache(fwriter_buffer);
+
+ printf("fragment cache (frags waiting to be compressed by fragment"
+ " deflate thread(s))\n");
+ dump_cache(fragment_buffer);
+
+ printf("fragment reserve cache (avoids pipeline stall if frag cache"
+ " full in dup check)\n");
+ dump_cache(reserve_cache);
+
+ enable_progress_bar();
+}
+
+
+static void *info_thrd(void *arg)
+{
+ sigset_t sigmask;
+ int sig, waiting = 0;
+
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGQUIT);
+ sigaddset(&sigmask, SIGHUP);
+
+ while(1) {
+ sig = wait_for_signal(&sigmask, &waiting);
+
+ if(sig == SIGQUIT && !waiting) {
+ print_filename();
+
+ /* set one second interval period, if ^\ received
+ within then, dump queue and cache status */
+ waiting = 1;
+ } else
+ dump_state();
+ }
+
+ return NULL;
+}
+
+
+void init_info()
+{
+ pthread_create(&info_thread, NULL, info_thrd, NULL);
+}
diff --git a/squashfs-tools/info.h b/squashfs-tools/info.h
new file mode 100644
index 0000000..bcf03a2
--- /dev/null
+++ b/squashfs-tools/info.h
@@ -0,0 +1,30 @@
+#ifndef INFO_H
+#define INFO_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * info.h
+ */
+
+extern void disable_info();
+extern void update_info(struct dir_ent *);
+extern void init_info();
+#endif
diff --git a/squashfs-tools/lz4_wrapper.c b/squashfs-tools/lz4_wrapper.c
new file mode 100644
index 0000000..44cd35e
--- /dev/null
+++ b/squashfs-tools/lz4_wrapper.c
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2013, 2019, 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * lz4_wrapper.c
+ *
+ * Support for LZ4 compression http://fastcompression.blogspot.com/p/lz4.html
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <lz4.h>
+#include <lz4hc.h>
+
+#include "squashfs_fs.h"
+#include "lz4_wrapper.h"
+#include "compressor.h"
+
+/* LZ4 1.7.0 introduced new functions, and since r131,
+ * the older functions produce deprecated warnings.
+ *
+ * There are still too many distros using older versions
+ * to switch to the newer functions, but, the deprecated
+ * functions may completely disappear. This is a mess.
+ *
+ * Support both by checking the library version and
+ * using shadow definitions
+ */
+
+/* Earlier (but > 1.7.0) versions don't define this */
+#ifndef LZ4HC_CLEVEL_MAX
+#define LZ4HC_CLEVEL_MAX 12
+#endif
+
+#if LZ4_VERSION_NUMBER >= 10700
+#define COMPRESS(src, dest, size, max) LZ4_compress_default(src, dest, size, max)
+#define COMPRESS_HC(src, dest, size, max) LZ4_compress_HC(src, dest, size, max, LZ4HC_CLEVEL_MAX)
+#else
+#define COMPRESS(src, dest, size, max) LZ4_compress_limitedOutput(src, dest, size, max)
+#define COMPRESS_HC(src, dest, size, max) LZ4_compressHC_limitedOutput(src, dest, size, max)
+#endif
+
+static int hc = 0;
+
+/*
+ * This function is called by the options parsing code in mksquashfs.c
+ * to parse any -X compressor option.
+ *
+ * This function returns:
+ * >=0 (number of additional args parsed) on success
+ * -1 if the option was unrecognised, or
+ * -2 if the option was recognised, but otherwise bad in
+ * some way (e.g. invalid parameter)
+ *
+ * Note: this function sets internal compressor state, but does not
+ * pass back the results of the parsing other than success/failure.
+ * The lz4_dump_options() function is called later to get the options in
+ * a format suitable for writing to the filesystem.
+ */
+static int lz4_options(char *argv[], int argc)
+{
+ if(strcmp(argv[0], "-Xhc") == 0) {
+ hc = 1;
+ return 0;
+ }
+
+ return -1;
+}
+
+
+/*
+ * This function is called by mksquashfs to dump the parsed
+ * compressor options in a format suitable for writing to the
+ * compressor options field in the filesystem (stored immediately
+ * after the superblock).
+ *
+ * This function returns a pointer to the compression options structure
+ * to be stored (and the size), or NULL if there are no compression
+ * options
+ *
+ * Currently LZ4 always returns a comp_opts structure, with
+ * the version indicating LZ4_LEGACY stream fomat. This is to
+ * easily accomodate changes in the kernel code to different
+ * stream formats
+ */
+static void *lz4_dump_options(int block_size, int *size)
+{
+ static struct lz4_comp_opts comp_opts;
+
+ comp_opts.version = LZ4_LEGACY;
+ comp_opts.flags = hc ? LZ4_HC : 0;
+ SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
+
+ *size = sizeof(comp_opts);
+ return &comp_opts;
+}
+
+
+/*
+ * This function is a helper specifically for the append mode of
+ * mksquashfs. Its purpose is to set the internal compressor state
+ * to the stored compressor options in the passed compressor options
+ * structure.
+ *
+ * In effect this function sets up the compressor options
+ * to the same state they were when the filesystem was originally
+ * generated, this is to ensure on appending, the compressor uses
+ * the same compression options that were used to generate the
+ * original filesystem.
+ *
+ * Note, even if there are no compressor options, this function is still
+ * called with an empty compressor structure (size == 0), to explicitly
+ * set the default options, this is to ensure any user supplied
+ * -X options on the appending mksquashfs command line are over-ridden
+ *
+ * This function returns 0 on sucessful extraction of options, and
+ * -1 on error
+ */
+static int lz4_extract_options(int block_size, void *buffer, int size)
+{
+ struct lz4_comp_opts *comp_opts = buffer;
+
+ /* we expect a comp_opts structure to be present */
+ if(size < sizeof(*comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ /* we expect the stream format to be LZ4_LEGACY */
+ if(comp_opts->version != LZ4_LEGACY) {
+ fprintf(stderr, "lz4: unknown LZ4 version\n");
+ goto failed;
+ }
+
+ /*
+ * Check compression flags, currently only LZ4_HC ("high compression")
+ * can be set.
+ */
+ if(comp_opts->flags == LZ4_HC)
+ hc = 1;
+ else if(comp_opts->flags != 0) {
+ fprintf(stderr, "lz4: unknown LZ4 flags\n");
+ goto failed;
+ }
+
+ return 0;
+
+failed:
+ fprintf(stderr, "lz4: error reading stored compressor options from "
+ "filesystem!\n");
+
+ return -1;
+}
+
+
+/*
+ * This function is a helper specifically for unsquashfs.
+ * Its purpose is to check that the compression options are
+ * understood by this version of LZ4.
+ *
+ * This is important for LZ4 because the format understood by the
+ * Linux kernel may change from the already obsolete legacy format
+ * currently supported.
+ *
+ * If this does happen, then this version of LZ4 will not be able to decode
+ * the newer format. So we need to check for this.
+ *
+ * This function returns 0 on sucessful checking of options, and
+ * -1 on error
+ */
+static int lz4_check_options(int block_size, void *buffer, int size)
+{
+ struct lz4_comp_opts *comp_opts = buffer;
+
+ /* we expect a comp_opts structure to be present */
+ if(size < sizeof(*comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ /* we expect the stream format to be LZ4_LEGACY */
+ if(comp_opts->version != LZ4_LEGACY) {
+ fprintf(stderr, "lz4: unknown LZ4 version\n");
+ goto failed;
+ }
+
+ return 0;
+
+failed:
+ fprintf(stderr, "lz4: error reading stored compressor options from "
+ "filesystem!\n");
+ return -1;
+}
+
+
+static void lz4_display_options(void *buffer, int size)
+{
+ struct lz4_comp_opts *comp_opts = buffer;
+
+ /* check passed comp opts struct is of the correct length */
+ if(size < sizeof(*comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ /* we expect the stream format to be LZ4_LEGACY */
+ if(comp_opts->version != LZ4_LEGACY) {
+ fprintf(stderr, "lz4: unknown LZ4 version\n");
+ goto failed;
+ }
+
+ /*
+ * Check compression flags, currently only LZ4_HC ("high compression")
+ * can be set.
+ */
+ if(comp_opts->flags & ~LZ4_FLAGS_MASK) {
+ fprintf(stderr, "lz4: unknown LZ4 flags\n");
+ goto failed;
+ }
+
+ if(comp_opts->flags & LZ4_HC)
+ printf("\tHigh Compression option specified (-Xhc)\n");
+
+ return;
+
+failed:
+ fprintf(stderr, "lz4: error reading stored compressor options from "
+ "filesystem!\n");
+}
+
+
+static int lz4_compress(void *strm, void *dest, void *src, int size,
+ int block_size, int *error)
+{
+ int res;
+
+ if(hc)
+ res = COMPRESS_HC(src, dest, size, block_size);
+ else
+ res = COMPRESS(src, dest, size, block_size);
+
+ if(res == 0) {
+ /*
+ * Output buffer overflow. Return out of buffer space
+ */
+ return 0;
+ } else if(res < 0) {
+ /*
+ * All other errors return failure, with the compressor
+ * specific error code in *error
+ */
+ *error = res;
+ return -1;
+ }
+
+ return res;
+}
+
+
+static int lz4_uncompress(void *dest, void *src, int size, int outsize,
+ int *error)
+{
+ int res = LZ4_decompress_safe(src, dest, size, outsize);
+ if(res < 0) {
+ *error = res;
+ return -1;
+ }
+
+ return res;
+}
+
+
+static void lz4_usage(FILE *stream)
+{
+ fprintf(stream, "\t -Xhc\n");
+ fprintf(stream, "\t\tCompress using LZ4 High Compression\n");
+}
+
+
+struct compressor lz4_comp_ops = {
+ .compress = lz4_compress,
+ .uncompress = lz4_uncompress,
+ .options = lz4_options,
+ .dump_options = lz4_dump_options,
+ .extract_options = lz4_extract_options,
+ .check_options = lz4_check_options,
+ .display_options = lz4_display_options,
+ .usage = lz4_usage,
+ .id = LZ4_COMPRESSION,
+ .name = "lz4",
+ .supported = 1
+};
diff --git a/squashfs-tools/lz4_wrapper.h b/squashfs-tools/lz4_wrapper.h
new file mode 100644
index 0000000..5eef1af
--- /dev/null
+++ b/squashfs-tools/lz4_wrapper.h
@@ -0,0 +1,55 @@
+#ifndef LZ4_WRAPPER_H
+#define LZ4_WRAPPER_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2013
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * lz4_wrapper.h
+ *
+ */
+
+#include "endian_compat.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+extern unsigned int inswap_le32(unsigned int);
+
+#define SQUASHFS_INSWAP_COMP_OPTS(s) { \
+ (s)->version = inswap_le32((s)->version); \
+ (s)->flags = inswap_le32((s)->flags); \
+}
+#else
+#define SQUASHFS_INSWAP_COMP_OPTS(s)
+#endif
+
+/*
+ * Define the various stream formats recognised.
+ * Currently omly legacy stream format is supported by the
+ * kernel
+ */
+#define LZ4_LEGACY 1
+#define LZ4_FLAGS_MASK 1
+
+/* Define the compression flags recognised. */
+#define LZ4_HC 1
+
+struct lz4_comp_opts {
+ int version;
+ int flags;
+};
+#endif
diff --git a/squashfs-tools/lzma_wrapper.c b/squashfs-tools/lzma_wrapper.c
new file mode 100644
index 0000000..d06cacc
--- /dev/null
+++ b/squashfs-tools/lzma_wrapper.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2009, 2010, 2013, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * lzma_wrapper.c
+ *
+ * Support for LZMA1 compression using LZMA SDK (4.65 used in
+ * development, other versions may work) http://www.7-zip.org/sdk.html
+ */
+
+#include <LzmaLib.h>
+
+#include "squashfs_fs.h"
+#include "compressor.h"
+
+#define LZMA_HEADER_SIZE (LZMA_PROPS_SIZE + 8)
+
+static int lzma_compress(void *strm, void *dest, void *src, int size, int block_size,
+ int *error)
+{
+ unsigned char *d = dest;
+ size_t props_size = LZMA_PROPS_SIZE,
+ outlen = block_size - LZMA_HEADER_SIZE;
+ int res;
+
+ res = LzmaCompress(dest + LZMA_HEADER_SIZE, &outlen, src, size, dest,
+ &props_size, 5, block_size, 3, 0, 2, 32, 1);
+
+ if(res == SZ_ERROR_OUTPUT_EOF) {
+ /*
+ * Output buffer overflow. Return out of buffer space error
+ */
+ return 0;
+ }
+
+ if(res != SZ_OK) {
+ /*
+ * All other errors return failure, with the compressor
+ * specific error code in *error
+ */
+ *error = res;
+ return -1;
+ }
+
+ /*
+ * Fill in the 8 byte little endian uncompressed size field in the
+ * LZMA header. 8 bytes is excessively large for squashfs but
+ * this is the standard LZMA header and which is expected by the kernel
+ * code
+ */
+ d[LZMA_PROPS_SIZE] = size & 255;
+ d[LZMA_PROPS_SIZE + 1] = (size >> 8) & 255;
+ d[LZMA_PROPS_SIZE + 2] = (size >> 16) & 255;
+ d[LZMA_PROPS_SIZE + 3] = (size >> 24) & 255;
+ d[LZMA_PROPS_SIZE + 4] = 0;
+ d[LZMA_PROPS_SIZE + 5] = 0;
+ d[LZMA_PROPS_SIZE + 6] = 0;
+ d[LZMA_PROPS_SIZE + 7] = 0;
+
+ /*
+ * Success, return the compressed size. Outlen returned by the LZMA
+ * compressor does not include the LZMA header space
+ */
+ return outlen + LZMA_HEADER_SIZE;
+}
+
+
+static int lzma_uncompress(void *dest, void *src, int size, int outsize,
+ int *error)
+{
+ unsigned char *s = src;
+ size_t outlen, inlen = size - LZMA_HEADER_SIZE;
+ int res;
+
+ outlen = s[LZMA_PROPS_SIZE] |
+ (s[LZMA_PROPS_SIZE + 1] << 8) |
+ (s[LZMA_PROPS_SIZE + 2] << 16) |
+ (s[LZMA_PROPS_SIZE + 3] << 24);
+
+ if(outlen > outsize) {
+ *error = 0;
+ return -1;
+ }
+
+ res = LzmaUncompress(dest, &outlen, src + LZMA_HEADER_SIZE, &inlen, src,
+ LZMA_PROPS_SIZE);
+
+ if(res == SZ_OK)
+ return outlen;
+ else {
+ *error = res;
+ return -1;
+ }
+}
+
+
+static void lzma_usage(FILE *stream)
+{
+ fprintf(stream, "\t (no options) (deprecated - no kernel support)\n");
+}
+
+
+struct compressor lzma_comp_ops = {
+ .init = NULL,
+ .compress = lzma_compress,
+ .uncompress = lzma_uncompress,
+ .options = NULL,
+ .usage = lzma_usage,
+ .id = LZMA_COMPRESSION,
+ .name = "lzma",
+ .supported = 1
+};
+
diff --git a/squashfs-tools/lzma_xz_wrapper.c b/squashfs-tools/lzma_xz_wrapper.c
new file mode 100644
index 0000000..ba90f60
--- /dev/null
+++ b/squashfs-tools/lzma_xz_wrapper.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2010, 2013, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * lzma_xz_wrapper.c
+ *
+ * Support for LZMA1 compression using XZ Utils liblzma http://tukaani.org/xz/
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <lzma.h>
+
+#include "squashfs_fs.h"
+#include "compressor.h"
+
+#define LZMA_PROPS_SIZE 5
+#define LZMA_UNCOMP_SIZE 8
+#define LZMA_HEADER_SIZE (LZMA_PROPS_SIZE + LZMA_UNCOMP_SIZE)
+
+#define LZMA_OPTIONS 5
+#define MEMLIMIT (32 * 1024 * 1024)
+
+static int lzma_compress(void *dummy, void *dest, void *src, int size,
+ int block_size, int *error)
+{
+ unsigned char *d = (unsigned char *) dest;
+ lzma_options_lzma opt;
+ lzma_stream strm = LZMA_STREAM_INIT;
+ int res;
+
+ lzma_lzma_preset(&opt, LZMA_OPTIONS);
+ opt.dict_size = block_size;
+
+ res = lzma_alone_encoder(&strm, &opt);
+ if(res != LZMA_OK) {
+ lzma_end(&strm);
+ goto failed;
+ }
+
+ strm.next_out = dest;
+ strm.avail_out = block_size;
+ strm.next_in = src;
+ strm.avail_in = size;
+
+ res = lzma_code(&strm, LZMA_FINISH);
+ lzma_end(&strm);
+
+ if(res == LZMA_STREAM_END) {
+ /*
+ * Fill in the 8 byte little endian uncompressed size field in
+ * the LZMA header. 8 bytes is excessively large for squashfs
+ * but this is the standard LZMA header and which is expected by
+ * the kernel code
+ */
+
+ d[LZMA_PROPS_SIZE] = size & 255;
+ d[LZMA_PROPS_SIZE + 1] = (size >> 8) & 255;
+ d[LZMA_PROPS_SIZE + 2] = (size >> 16) & 255;
+ d[LZMA_PROPS_SIZE + 3] = (size >> 24) & 255;
+ d[LZMA_PROPS_SIZE + 4] = 0;
+ d[LZMA_PROPS_SIZE + 5] = 0;
+ d[LZMA_PROPS_SIZE + 6] = 0;
+ d[LZMA_PROPS_SIZE + 7] = 0;
+
+ return (int) strm.total_out;
+ }
+
+ if(res == LZMA_OK)
+ /*
+ * Output buffer overflow. Return out of buffer space
+ */
+ return 0;
+
+failed:
+ /*
+ * All other errors return failure, with the compressor
+ * specific error code in *error
+ */
+ *error = res;
+ return -1;
+}
+
+
+static int lzma_uncompress(void *dest, void *src, int size, int outsize,
+ int *error)
+{
+ lzma_stream strm = LZMA_STREAM_INIT;
+ int uncompressed_size = 0, res;
+ unsigned char lzma_header[LZMA_HEADER_SIZE];
+
+ res = lzma_alone_decoder(&strm, MEMLIMIT);
+ if(res != LZMA_OK) {
+ lzma_end(&strm);
+ goto failed;
+ }
+
+ memcpy(lzma_header, src, LZMA_HEADER_SIZE);
+ uncompressed_size = lzma_header[LZMA_PROPS_SIZE] |
+ (lzma_header[LZMA_PROPS_SIZE + 1] << 8) |
+ (lzma_header[LZMA_PROPS_SIZE + 2] << 16) |
+ (lzma_header[LZMA_PROPS_SIZE + 3] << 24);
+
+ if(uncompressed_size > outsize) {
+ res = 0;
+ goto failed;
+ }
+
+ memset(lzma_header + LZMA_PROPS_SIZE, 255, LZMA_UNCOMP_SIZE);
+
+ strm.next_out = dest;
+ strm.avail_out = outsize;
+ strm.next_in = lzma_header;
+ strm.avail_in = LZMA_HEADER_SIZE;
+
+ res = lzma_code(&strm, LZMA_RUN);
+
+ if(res != LZMA_OK || strm.avail_in != 0) {
+ lzma_end(&strm);
+ goto failed;
+ }
+
+ strm.next_in = src + LZMA_HEADER_SIZE;
+ strm.avail_in = size - LZMA_HEADER_SIZE;
+
+ res = lzma_code(&strm, LZMA_FINISH);
+ lzma_end(&strm);
+
+ if(res == LZMA_STREAM_END || (res == LZMA_OK &&
+ strm.total_out >= uncompressed_size && strm.avail_in == 0))
+ return uncompressed_size;
+
+failed:
+ *error = res;
+ return -1;
+}
+
+
+static void lzma_usage(FILE *stream)
+{
+ fprintf(stream, "\t (no options) (deprecated - no kernel support)\n");
+}
+
+
+struct compressor lzma_comp_ops = {
+ .init = NULL,
+ .compress = lzma_compress,
+ .uncompress = lzma_uncompress,
+ .options = NULL,
+ .usage = lzma_usage,
+ .id = LZMA_COMPRESSION,
+ .name = "lzma",
+ .supported = 1
+};
+
diff --git a/squashfs-tools/lzo_wrapper.c b/squashfs-tools/lzo_wrapper.c
new file mode 100644
index 0000000..147c8ef
--- /dev/null
+++ b/squashfs-tools/lzo_wrapper.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2013, 2014, 2021. 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * lzo_wrapper.c
+ *
+ * Support for LZO compression http://www.oberhumer.com/opensource/lzo
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <lzo/lzoconf.h>
+#include <lzo/lzo1x.h>
+
+#include "squashfs_fs.h"
+#include "lzo_wrapper.h"
+#include "compressor.h"
+
+static struct lzo_algorithm lzo[] = {
+ { "lzo1x_1", LZO1X_1_MEM_COMPRESS, lzo1x_1_compress },
+ { "lzo1x_1_11", LZO1X_1_11_MEM_COMPRESS, lzo1x_1_11_compress },
+ { "lzo1x_1_12", LZO1X_1_12_MEM_COMPRESS, lzo1x_1_12_compress },
+ { "lzo1x_1_15", LZO1X_1_15_MEM_COMPRESS, lzo1x_1_15_compress },
+ { "lzo1x_999", LZO1X_999_MEM_COMPRESS, lzo1x_999_wrapper },
+ { NULL, 0, NULL }
+};
+
+/* default LZO compression algorithm and compression level */
+static int algorithm = SQUASHFS_LZO1X_999;
+static int compression_level = SQUASHFS_LZO1X_999_COMP_DEFAULT;
+
+/* user specified compression level */
+static int user_comp_level = -1;
+
+
+/*
+ * This function is called by the options parsing code in mksquashfs.c
+ * to parse any -X compressor option.
+ *
+ * This function returns:
+ * >=0 (number of additional args parsed) on success
+ * -1 if the option was unrecognised, or
+ * -2 if the option was recognised, but otherwise bad in
+ * some way (e.g. invalid parameter)
+ *
+ * Note: this function sets internal compressor state, but does not
+ * pass back the results of the parsing other than success/failure.
+ * The lzo_dump_options() function is called later to get the options in
+ * a format suitable for writing to the filesystem.
+ */
+static int lzo_options(char *argv[], int argc)
+{
+ int i;
+
+ if(strcmp(argv[0], "-Xalgorithm") == 0) {
+ if(argc < 2) {
+ fprintf(stderr, "lzo: -Xalgorithm missing algorithm\n");
+ fprintf(stderr, "lzo: -Xalgorithm <algorithm>\n");
+ goto failed2;
+ }
+
+ for(i = 0; lzo[i].name; i++) {
+ if(strcmp(argv[1], lzo[i].name) == 0) {
+ algorithm = i;
+ return 1;
+ }
+ }
+
+ fprintf(stderr, "lzo: -Xalgorithm unrecognised algorithm\n");
+ goto failed2;
+ } else if(strcmp(argv[0], "-Xcompression-level") == 0) {
+ if(argc < 2) {
+ fprintf(stderr, "lzo: -Xcompression-level missing "
+ "compression level\n");
+ fprintf(stderr, "lzo: -Xcompression-level it "
+ "should be 1 >= n <= 9\n");
+ goto failed;
+ }
+
+ user_comp_level = atoi(argv[1]);
+ if(user_comp_level < 1 || user_comp_level > 9) {
+ fprintf(stderr, "lzo: -Xcompression-level invalid, it "
+ "should be 1 >= n <= 9\n");
+ goto failed;
+ }
+
+ return 1;
+ }
+
+ return -1;
+
+failed:
+ return -2;
+
+failed2:
+ fprintf(stderr, "lzo: compression algorithm should be one of:\n");
+ for(i = 0; lzo[i].name; i++)
+ fprintf(stderr, "\t%s\n", lzo[i].name);
+ return -2;
+}
+
+
+/*
+ * This function is called after all options have been parsed.
+ * It is used to do post-processing on the compressor options using
+ * values that were not expected to be known at option parse time.
+ *
+ * In this case the LZO algorithm may not be known until after the
+ * compression level has been set (-Xalgorithm used after -Xcompression-level)
+ *
+ * This function returns 0 on successful post processing, or
+ * -1 on error
+ */
+static int lzo_options_post(int block_size)
+{
+ /*
+ * Use of compression level only makes sense for
+ * LZO1X_999 algorithm
+ */
+ if(user_comp_level != -1) {
+ if(algorithm != SQUASHFS_LZO1X_999) {
+ fprintf(stderr, "lzo: -Xcompression-level not "
+ "supported by selected %s algorithm\n",
+ lzo[algorithm].name);
+ fprintf(stderr, "lzo: -Xcompression-level is only "
+ "applicable for the lzo1x_999 algorithm\n");
+ goto failed;
+ }
+ compression_level = user_comp_level;
+ }
+
+ return 0;
+
+failed:
+ return -1;
+}
+
+
+/*
+ * This function is called by mksquashfs to dump the parsed
+ * compressor options in a format suitable for writing to the
+ * compressor options field in the filesystem (stored immediately
+ * after the superblock).
+ *
+ * This function returns a pointer to the compression options structure
+ * to be stored (and the size), or NULL if there are no compression
+ * options
+ *
+ */
+static void *lzo_dump_options(int block_size, int *size)
+{
+ static struct lzo_comp_opts comp_opts;
+
+ /*
+ * If default compression options of SQUASHFS_LZO1X_999 and
+ * compression level of SQUASHFS_LZO1X_999_COMP_DEFAULT then
+ * don't store a compression options structure (this is compatible
+ * with the legacy implementation of LZO for Squashfs)
+ */
+ if(algorithm == SQUASHFS_LZO1X_999 &&
+ compression_level == SQUASHFS_LZO1X_999_COMP_DEFAULT)
+ return NULL;
+
+ comp_opts.algorithm = algorithm;
+ comp_opts.compression_level = algorithm == SQUASHFS_LZO1X_999 ?
+ compression_level : 0;
+
+ SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
+
+ *size = sizeof(comp_opts);
+ return &comp_opts;
+}
+
+
+/*
+ * This function is a helper specifically for the append mode of
+ * mksquashfs. Its purpose is to set the internal compressor state
+ * to the stored compressor options in the passed compressor options
+ * structure.
+ *
+ * In effect this function sets up the compressor options
+ * to the same state they were when the filesystem was originally
+ * generated, this is to ensure on appending, the compressor uses
+ * the same compression options that were used to generate the
+ * original filesystem.
+ *
+ * Note, even if there are no compressor options, this function is still
+ * called with an empty compressor structure (size == 0), to explicitly
+ * set the default options, this is to ensure any user supplied
+ * -X options on the appending mksquashfs command line are over-ridden
+ *
+ * This function returns 0 on sucessful extraction of options, and
+ * -1 on error
+ */
+static int lzo_extract_options(int block_size, void *buffer, int size)
+{
+ struct lzo_comp_opts *comp_opts = buffer;
+
+ if(size == 0) {
+ /* Set default values */
+ algorithm = SQUASHFS_LZO1X_999;
+ compression_level = SQUASHFS_LZO1X_999_COMP_DEFAULT;
+ return 0;
+ }
+
+ /* we expect a comp_opts structure of sufficient size to be present */
+ if(size < sizeof(*comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ /* Check comp_opts structure for correctness */
+ switch(comp_opts->algorithm) {
+ case SQUASHFS_LZO1X_1:
+ case SQUASHFS_LZO1X_1_11:
+ case SQUASHFS_LZO1X_1_12:
+ case SQUASHFS_LZO1X_1_15:
+ if(comp_opts->compression_level != 0) {
+ fprintf(stderr, "lzo: bad compression level in "
+ "compression options structure\n");
+ goto failed;
+ }
+ break;
+ case SQUASHFS_LZO1X_999:
+ if(comp_opts->compression_level < 1 ||
+ comp_opts->compression_level > 9) {
+ fprintf(stderr, "lzo: bad compression level in "
+ "compression options structure\n");
+ goto failed;
+ }
+ compression_level = comp_opts->compression_level;
+ break;
+ default:
+ fprintf(stderr, "lzo: bad algorithm in compression options "
+ "structure\n");
+ goto failed;
+ }
+
+ algorithm = comp_opts->algorithm;
+
+ return 0;
+
+failed:
+ fprintf(stderr, "lzo: error reading stored compressor options from "
+ "filesystem!\n");
+
+ return -1;
+}
+
+
+static void lzo_display_options(void *buffer, int size)
+{
+ struct lzo_comp_opts *comp_opts = buffer;
+
+ /* we expect a comp_opts structure of sufficient size to be present */
+ if(size < sizeof(*comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ /* Check comp_opts structure for correctness */
+ switch(comp_opts->algorithm) {
+ case SQUASHFS_LZO1X_1:
+ case SQUASHFS_LZO1X_1_11:
+ case SQUASHFS_LZO1X_1_12:
+ case SQUASHFS_LZO1X_1_15:
+ printf("\talgorithm %s\n", lzo[comp_opts->algorithm].name);
+ break;
+ case SQUASHFS_LZO1X_999:
+ if(comp_opts->compression_level < 1 ||
+ comp_opts->compression_level > 9) {
+ fprintf(stderr, "lzo: bad compression level in "
+ "compression options structure\n");
+ goto failed;
+ }
+ printf("\talgorithm %s\n", lzo[comp_opts->algorithm].name);
+ printf("\tcompression level %d\n",
+ comp_opts->compression_level);
+ break;
+ default:
+ fprintf(stderr, "lzo: bad algorithm in compression options "
+ "structure\n");
+ goto failed;
+ }
+
+ return;
+
+failed:
+ fprintf(stderr, "lzo: error reading stored compressor options from "
+ "filesystem!\n");
+}
+
+
+/*
+ * This function is called by mksquashfs to initialise the
+ * compressor, before compress() is called.
+ *
+ * This function returns 0 on success, and
+ * -1 on error
+ */
+static int squashfs_lzo_init(void **strm, int block_size, int datablock)
+{
+ struct lzo_stream *stream;
+
+ stream = *strm = malloc(sizeof(struct lzo_stream));
+ if(stream == NULL)
+ goto failed;
+
+ stream->workspace = malloc(lzo[algorithm].size);
+ if(stream->workspace == NULL)
+ goto failed2;
+
+ stream->buffer = malloc(LZO_MAX_EXPANSION(block_size));
+ if(stream->buffer != NULL)
+ return 0;
+
+ free(stream->workspace);
+failed2:
+ free(stream);
+failed:
+ return -1;
+}
+
+
+static int lzo_compress(void *strm, void *dest, void *src, int size,
+ int block_size, int *error)
+{
+ int res;
+ lzo_uint compsize, orig_size = size;
+ struct lzo_stream *stream = strm;
+
+ res = lzo[algorithm].compress(src, size, stream->buffer, &compsize,
+ stream->workspace);
+ if(res != LZO_E_OK)
+ goto failed;
+
+ /* Successful compression, however, we need to check that
+ * the compressed size is not larger than the available
+ * buffer space. Normally in other compressor APIs they take
+ * a destination buffer size, and overflows return an error.
+ * With LZO it lacks a destination size and so we must output
+ * to a temporary buffer large enough to accomodate any
+ * result, and explictly check here for overflow
+ */
+ if(compsize > block_size)
+ return 0;
+
+ res = lzo1x_optimize(stream->buffer, compsize, src, &orig_size, NULL);
+
+ if (res != LZO_E_OK || orig_size != size)
+ goto failed;
+
+ memcpy(dest, stream->buffer, compsize);
+ return compsize;
+
+failed:
+ /* fail, compressor specific error code returned in error */
+ *error = res;
+ return -1;
+}
+
+
+static int lzo_uncompress(void *dest, void *src, int size, int outsize,
+ int *error)
+{
+ int res;
+ lzo_uint outlen = outsize;
+
+ res = lzo1x_decompress_safe(src, size, dest, &outlen, NULL);
+ if(res != LZO_E_OK) {
+ *error = res;
+ return -1;
+ }
+
+ return outlen;
+}
+
+
+static void lzo_usage(FILE *stream)
+{
+ int i;
+
+ fprintf(stream, "\t -Xalgorithm <algorithm>\n");
+ fprintf(stream, "\t\tWhere <algorithm> is one of:\n");
+
+ for(i = 0; lzo[i].name; i++)
+ fprintf(stream, "\t\t\t%s%s\n", lzo[i].name,
+ i == SQUASHFS_LZO1X_999 ? " (default)" : "");
+
+ fprintf(stream, "\t -Xcompression-level <compression-level>\n");
+ fprintf(stream, "\t\t<compression-level> should be 1 .. 9 (default "
+ "%d)\n", SQUASHFS_LZO1X_999_COMP_DEFAULT);
+ fprintf(stream, "\t\tOnly applies to lzo1x_999 algorithm\n");
+}
+
+
+/*
+ * Helper function for lzo1x_999 compression algorithm.
+ * All other lzo1x_xxx compressors do not take a compression level,
+ * so we need to wrap lzo1x_999 to pass the compression level which
+ * is applicable to it
+ */
+int lzo1x_999_wrapper(const lzo_bytep src, lzo_uint src_len, lzo_bytep dst,
+ lzo_uintp compsize, lzo_voidp workspace)
+{
+ return lzo1x_999_compress_level(src, src_len, dst, compsize,
+ workspace, NULL, 0, 0, compression_level);
+}
+
+
+static int option_args(char *option)
+{
+ if(strcmp(option, "-Xalgorithm") == 0 ||
+ strcmp(option, "-Xcompression-level") == 0)
+ return 1;
+
+ return 0;
+}
+
+
+struct compressor lzo_comp_ops = {
+ .init = squashfs_lzo_init,
+ .compress = lzo_compress,
+ .uncompress = lzo_uncompress,
+ .options = lzo_options,
+ .options_post = lzo_options_post,
+ .dump_options = lzo_dump_options,
+ .extract_options = lzo_extract_options,
+ .display_options = lzo_display_options,
+ .usage = lzo_usage,
+ .option_args = option_args,
+ .id = LZO_COMPRESSION,
+ .name = "lzo",
+ .supported = 1
+};
diff --git a/squashfs-tools/lzo_wrapper.h b/squashfs-tools/lzo_wrapper.h
new file mode 100644
index 0000000..f6223e1
--- /dev/null
+++ b/squashfs-tools/lzo_wrapper.h
@@ -0,0 +1,72 @@
+#ifndef LZO_WRAPPER_H
+#define LZO_WRAPPER_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2013
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * lzo_wrapper.h
+ *
+ */
+
+#include "endian_compat.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+extern unsigned int inswap_le32(unsigned int);
+
+#define SQUASHFS_INSWAP_COMP_OPTS(s) { \
+ (s)->algorithm = inswap_le32((s)->algorithm); \
+ (s)->compression_level = inswap_le32((s)->compression_level); \
+}
+#else
+#define SQUASHFS_INSWAP_COMP_OPTS(s)
+#endif
+
+/* Define the compression flags recognised. */
+#define SQUASHFS_LZO1X_1 0
+#define SQUASHFS_LZO1X_1_11 1
+#define SQUASHFS_LZO1X_1_12 2
+#define SQUASHFS_LZO1X_1_15 3
+#define SQUASHFS_LZO1X_999 4
+
+/* Default compression level used by SQUASHFS_LZO1X_999 */
+#define SQUASHFS_LZO1X_999_COMP_DEFAULT 8
+
+struct lzo_comp_opts {
+ int algorithm;
+ int compression_level;
+};
+
+struct lzo_algorithm {
+ char *name;
+ int size;
+ int (*compress) (const lzo_bytep, lzo_uint, lzo_bytep, lzo_uintp,
+ lzo_voidp);
+};
+
+struct lzo_stream {
+ void *workspace;
+ void *buffer;
+};
+
+#define LZO_MAX_EXPANSION(size) (size + (size / 16) + 64 + 3)
+
+int lzo1x_999_wrapper(const lzo_bytep, lzo_uint, lzo_bytep, lzo_uintp,
+ lzo_voidp);
+
+#endif
diff --git a/squashfs-tools/merge_sort.h b/squashfs-tools/merge_sort.h
new file mode 100644
index 0000000..345942c
--- /dev/null
+++ b/squashfs-tools/merge_sort.h
@@ -0,0 +1,116 @@
+#ifndef MERGE_SORT_H
+#define MERGE_SORT_H
+
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * merge_sort.h
+ */
+
+/*
+ * Bottom up linked list merge sort.
+ *
+ * Qsort and other O(n log n) algorithms work well with arrays but not
+ * linked lists. Merge sort another O(n log n) sort algorithm on the other hand
+ * is not ideal for arrays (as it needs an additonal n storage locations
+ * as sorting is not done in place), but it is ideal for linked lists because
+ * it doesn't require any extra storage,
+ */
+
+#define SORT(FUNCTION_NAME, LIST_TYPE, NAME, NEXT) \
+void FUNCTION_NAME(struct LIST_TYPE **head, int count) \
+{ \
+ struct LIST_TYPE *cur, *l1, *l2, *next; \
+ int len1, len2, stride = 1; \
+ \
+ if(*head == NULL || count < 2) \
+ return; \
+ \
+ /* \
+ * We can consider our linked-list to be made up of stride length \
+ * sublists. Eacn iteration around this loop merges adjacent \
+ * stride length sublists into larger 2*stride sublists. We stop \
+ * when stride becomes equal to the entire list. \
+ * \
+ * Initially stride = 1 (by definition a sublist of 1 is sorted), and \
+ * these 1 element sublists are merged into 2 element sublists, which \
+ * are then merged into 4 element sublists and so on. \
+ */ \
+ do { \
+ l2 = *head; /* head of current linked list */ \
+ cur = NULL; /* empty output list */ \
+ \
+ /* \
+ * Iterate through the linked list, merging adjacent sublists. \
+ * On each interation l2 points to the next sublist pair to be \
+ * merged (if there's only one sublist left this is simply added \
+ * to the output list) \
+ */ \
+ while(l2) { \
+ l1 = l2; \
+ for(len1 = 0; l2 && len1 < stride; len1 ++, l2 = l2->NEXT); \
+ len2 = stride; \
+ \
+ /* \
+ * l1 points to first sublist. \
+ * l2 points to second sublist. \
+ * Merge them onto the output list \
+ */ \
+ while(len1 && l2 && len2) { \
+ if(strcmp(l1->NAME, l2->NAME) <= 0) { \
+ next = l1; \
+ l1 = l1->NEXT; \
+ len1 --; \
+ } else { \
+ next = l2; \
+ l2 = l2->NEXT; \
+ len2 --; \
+ } \
+ \
+ if(cur) { \
+ cur->NEXT = next; \
+ cur = next; \
+ } else \
+ *head = cur = next; \
+ } \
+ /* \
+ * One sublist is now empty, copy the other one onto the \
+ * output list \
+ */ \
+ for(; len1; len1 --, l1 = l1->NEXT) { \
+ if(cur) { \
+ cur->NEXT = l1; \
+ cur = l1; \
+ } else \
+ *head = cur = l1; \
+ } \
+ for(; l2 && len2; len2 --, l2 = l2->NEXT) { \
+ if(cur) { \
+ cur->NEXT = l2; \
+ cur = l2; \
+ } else \
+ *head = cur = l2; \
+ } \
+ } \
+ cur->NEXT = NULL; \
+ stride = stride << 1; \
+ } while(stride < count); \
+}
+#endif
diff --git a/squashfs-tools/mksquashfs.c b/squashfs-tools/mksquashfs.c
new file mode 100644
index 0000000..ba28d65
--- /dev/null
+++ b/squashfs-tools/mksquashfs.c
@@ -0,0 +1,8902 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011,
+ * 2012, 2013, 2014, 2017, 2019, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * mksquashfs.c
+ */
+
+#define FALSE 0
+#define TRUE 1
+#define MAX_LINE 16384
+
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <pthread.h>
+#include <regex.h>
+#include <sys/wait.h>
+#include <limits.h>
+#include <ctype.h>
+
+#ifdef __linux__
+#include <sys/sysinfo.h>
+#include <sys/sysmacros.h>
+#include <sched.h>
+#else
+#include <sys/sysctl.h>
+#endif
+
+#include "squashfs_fs.h"
+#include "squashfs_swap.h"
+#include "mksquashfs.h"
+#include "sort.h"
+#include "pseudo.h"
+#include "compressor.h"
+#include "xattr.h"
+#include "action.h"
+#include "mksquashfs_error.h"
+#include "progressbar.h"
+#include "info.h"
+#include "caches-queues-lists.h"
+#include "read_fs.h"
+#include "restore.h"
+#include "process_fragments.h"
+#include "fnmatch_compat.h"
+#include "tar.h"
+#include "merge_sort.h"
+
+/* Compression options */
+int noF = FALSE;
+int noI = FALSE;
+int noId = FALSE;
+int noD = FALSE;
+int noX = FALSE;
+
+/* block size used to build filesystem */
+int block_size = SQUASHFS_FILE_SIZE;
+int block_log;
+
+/* Fragment options, are fragments in filesystem and are they used for tailends? */
+int no_fragments = FALSE;
+int always_use_fragments = FALSE;
+
+/* Are duplicates detected in fileystem ? */
+int duplicate_checking = TRUE;
+
+/* Are filesystems exportable via NFS? */
+int exportable = TRUE;
+
+/* Are sparse files detected and stored? */
+int sparse_files = TRUE;
+
+/* Options which override root inode settings */
+int root_mode_opt = FALSE;
+mode_t root_mode;
+int root_uid_opt = FALSE;
+unsigned int root_uid;
+int root_gid_opt = FALSE;
+unsigned int root_gid;
+unsigned int root_time;
+int root_time_opt = FALSE;
+
+/* Values that override uids and gids for all files and directories */
+int global_uid_opt = FALSE;
+unsigned int global_uid;
+int global_gid_opt = FALSE;
+unsigned int global_gid;
+
+/* Do pseudo uids and guids override -all-root, -force-uid and -force-gid? */
+int pseudo_override = FALSE;
+
+/* Time value over-ride options */
+unsigned int mkfs_time;
+int mkfs_time_opt = FALSE;
+unsigned int all_time;
+int all_time_opt = FALSE;
+int clamping = TRUE;
+
+/* Is max depth option in effect, and max depth to descend into directories */
+int max_depth_opt = FALSE;
+unsigned int max_depth;
+
+/* how should Mksquashfs treat the source files? */
+int tarstyle = FALSE;
+int keep_as_directory = FALSE;
+
+/* should Mksquashfs read files from stdin, like cpio? */
+int cpiostyle = FALSE;
+char filename_terminator = '\n';
+
+/* Should Mksquashfs detect hardlinked files? */
+int no_hardlinks = FALSE;
+
+/* Should Mksquashfs cross filesystem boundaries? */
+int one_file_system = FALSE;
+int one_file_system_x = FALSE;
+dev_t *source_dev;
+dev_t cur_dev;
+
+/* Is Mksquashfs processing a tarfile? */
+int tarfile = FALSE;
+
+/* Is Mksquashfs reading a pseudo file from stdin? */
+int pseudo_stdin = FALSE;
+
+/* Is Mksquashfs storing Xattrs, or excluding/including xattrs using regexs? */
+int no_xattrs = XATTR_DEF;
+unsigned int xattr_bytes = 0, total_xattr_bytes = 0;
+regex_t *xattr_exclude_preg = NULL;
+regex_t *xattr_include_preg = NULL;
+
+/* Does Mksquashfs print a summary and other information when running? */
+int quiet = FALSE;
+
+/* Does Mksquashfs display filenames as they are archived? */
+int silent = TRUE;
+
+/* Is Mksquashfs using the older non-wildcard exclude code? */
+int old_exclude = TRUE;
+
+/* Is Mksquashfs using regexs in exclude file matching (default wildcards)? */
+int use_regex = FALSE;
+
+/* Will Mksquashfs pad the filesystem to a multiple of 4 Kbytes? */
+int nopad = FALSE;
+
+/* Should Mksquashfs treat normally ignored errors as fatal? */
+int exit_on_error = FALSE;
+
+/* Is filesystem stored at an offset from the start of the block device/file? */
+long long start_offset = 0;
+
+/* File count statistics used to print summary and fill in superblock */
+unsigned int file_count = 0, sym_count = 0, dev_count = 0, dir_count = 0,
+fifo_count = 0, sock_count = 0, id_count = 0;
+long long hardlnk_count = 0;
+
+/* superblock attributes */
+struct squashfs_super_block sBlk;
+
+/* write position within data section */
+long long bytes = 0, total_bytes = 0;
+
+/* in memory directory table - possibly compressed */
+char *directory_table = NULL;
+long long directory_bytes = 0, directory_size = 0, total_directory_bytes = 0;
+
+/* cached directory table */
+char *directory_data_cache = NULL;
+unsigned int directory_cache_bytes = 0, directory_cache_size = 0;
+
+/* in memory inode table - possibly compressed */
+char *inode_table = NULL;
+long long inode_bytes = 0, inode_size = 0, total_inode_bytes = 0;
+
+/* cached inode table */
+char *data_cache = NULL;
+unsigned int cache_bytes = 0, cache_size = 0, inode_count = 0;
+
+/* inode lookup table */
+squashfs_inode *inode_lookup_table = NULL;
+struct inode_info *inode_info[INODE_HASH_SIZE];
+
+/* hash tables used to do fast duplicate searches in duplicate check */
+struct file_info **dupl_frag;
+struct file_info **dupl_block;
+unsigned int dup_files = 0;
+
+int exclude = 0;
+struct exclude_info *exclude_paths = NULL;
+
+struct path_entry {
+ char *name;
+ regex_t *preg;
+ struct pathname *paths;
+};
+
+struct pathnames *paths = NULL;
+struct pathname *path = NULL;
+struct pathname *stickypath = NULL;
+
+unsigned int fragments = 0;
+
+struct squashfs_fragment_entry *fragment_table = NULL;
+int fragments_outstanding = 0;
+
+int fragments_locked = FALSE;
+
+/* current inode number for directories and non directories */
+unsigned int inode_no = 1;
+unsigned int root_inode_number = 0;
+
+/* list of source dirs/files */
+int source = 0;
+char **source_path;
+int option_offset;
+
+/* flag whether destination file is a block device */
+int block_device = FALSE;
+
+/* flag indicating whether files are sorted using sort list(s) */
+int sorted = FALSE;
+
+/* save destination file name for deleting on error */
+char *destination_file = NULL;
+
+struct id *id_hash_table[ID_ENTRIES];
+struct id *id_table[SQUASHFS_IDS], *sid_table[SQUASHFS_IDS];
+unsigned int uid_count = 0, guid_count = 0;
+unsigned int sid_count = 0, suid_count = 0, sguid_count = 0;
+
+/* caches used to store buffers being worked on, and queues
+ * used to send buffers between threads */
+struct cache *reader_buffer, *fragment_buffer, *reserve_cache;
+struct cache *bwriter_buffer, *fwriter_buffer;
+struct queue *to_reader, *to_deflate, *to_writer, *from_writer,
+ *to_frag, *locked_fragment, *to_process_frag;
+struct seq_queue *to_main;
+
+/* pthread threads and mutexes */
+pthread_t reader_thread, writer_thread, main_thread;
+pthread_t *deflator_thread, *frag_deflator_thread, *frag_thread;
+pthread_t *restore_thread = NULL;
+pthread_mutex_t fragment_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t pos_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t dup_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* reproducible image queues and threads */
+struct seq_queue *to_order;
+pthread_t order_thread;
+pthread_cond_t fragment_waiting = PTHREAD_COND_INITIALIZER;
+int sequence_count = 0;
+int reproducible = REP_DEF;
+
+/* user options that control parallelisation */
+int processors = -1;
+int bwriter_size;
+
+/* Compressor options (-X) and initialised compressor (-comp XXX) */
+int comp_opts = FALSE;
+int X_opt_parsed = FALSE;
+struct compressor *comp = NULL;
+int compressor_opt_parsed = FALSE;
+void *stream = NULL;
+
+/* root of the in-core directory structure */
+struct dir_info *root_dir;
+
+/* log file */
+FILE *log_fd;
+int logging=FALSE;
+
+/* file descriptor of the output filesystem */
+int fd;
+
+/* Variables used for appending */
+int appending = TRUE;
+
+/* restore orignal filesystem state if appending to existing filesystem is
+ * cancelled */
+char *sdata_cache, *sdirectory_data_cache, *sdirectory_compressed;
+long long sbytes, stotal_bytes;
+long long sinode_bytes, stotal_inode_bytes;
+long long sdirectory_bytes, stotal_directory_bytes;
+unsigned int scache_bytes, sdirectory_cache_bytes,
+ sdirectory_compressed_bytes, sinode_count = 0,
+ sfile_count, ssym_count, sdev_count, sdir_count,
+ sfifo_count, ssock_count, sdup_files;
+unsigned int sfragments;
+
+/* list of root directory entries read from original filesystem */
+int old_root_entries = 0;
+struct old_root_entry_info *old_root_entry;
+
+/* fragment to file mapping used when appending */
+struct append_file **file_mapping;
+
+/* recovery file for abnormal exit on appending */
+char *recovery_file = NULL;
+char *recovery_pathname = NULL;
+int recover = TRUE;
+
+/* list of options that have an argument */
+char *option_table[] = { "comp", "b", "mkfs-time", "fstime", "all-time",
+ "root-mode", "force-uid", "force-gid", "action", "log-action",
+ "true-action", "false-action", "action-file", "log-action-file",
+ "true-action-file", "false-action-file", "p", "pf", "sort",
+ "root-becomes", "recover", "recovery-path", "throttle", "limit",
+ "processors", "mem", "offset", "o", "log", "a", "va", "ta", "fa", "af",
+ "vaf", "taf", "faf", "read-queue", "write-queue", "fragment-queue",
+ "root-time", "root-uid", "root-gid", "xattrs-exclude", "xattrs-include",
+ "xattrs-add", "default-mode", "default-uid", "default-gid",
+ "mem-percent", NULL
+};
+
+char *sqfstar_option_table[] = { "comp", "b", "mkfs-time", "fstime", "all-time",
+ "root-mode", "force-uid", "force-gid", "throttle", "limit",
+ "processors", "mem", "offset", "o", "root-time", "root-uid",
+ "root-gid", "xattrs-exclude", "xattrs-include", "xattrs-add", "p", "pf",
+ "default-mode", "default-uid", "default-gid", "mem-percent", NULL
+};
+
+static char *read_from_disk(long long start, unsigned int avail_bytes);
+static void add_old_root_entry(char *name, squashfs_inode inode,
+ unsigned int inode_number, int type);
+static struct file_info *duplicate(int *dup, int *block_dup,
+ long long file_size, long long bytes, unsigned int *block_list,
+ long long start, struct dir_ent *dir_ent,
+ struct file_buffer *file_buffer, int blocks, long long sparse,
+ int bl_hash);
+static struct dir_info *dir_scan1(char *, char *, struct pathnames *,
+ struct dir_ent *(_readdir)(struct dir_info *), unsigned int);
+static void dir_scan2(struct dir_info *dir, struct pseudo *pseudo);
+static void dir_scan3(struct dir_info *dir);
+static void dir_scan4(struct dir_info *dir, int symlink);
+static void dir_scan5(struct dir_info *dir);
+static void dir_scan6(struct dir_info *dir);
+static void dir_scan7(squashfs_inode *inode, struct dir_info *dir_info);
+static struct dir_ent *scan1_readdir(struct dir_info *dir);
+static struct dir_ent *scan1_single_readdir(struct dir_info *dir);
+static struct dir_ent *scan1_encomp_readdir(struct dir_info *dir);
+static struct file_info *add_non_dup(long long file_size, long long bytes,
+ unsigned int blocks, long long sparse, unsigned int *block_list,
+ long long start, struct fragment *fragment, unsigned short checksum,
+ unsigned short fragment_checksum, int checksum_flag,
+ int checksum_frag_flag, int blocks_dup, int frag_dup, int bl_hash);
+long long generic_write_table(long long, void *, int, void *, int);
+void restorefs();
+struct dir_info *scan1_opendir(char *pathname, char *subpath,
+ unsigned int depth);
+static void write_filesystem_tables(struct squashfs_super_block *sBlk);
+unsigned short get_checksum_mem(char *buff, int bytes);
+static void check_usable_phys_mem(int total_mem);
+static void print_summary();
+void write_destination(int fd, long long byte, long long bytes, void *buff);
+static int old_excluded(char *filename, struct stat *buf);
+
+
+void prep_exit()
+{
+ if(restore_thread) {
+ if(pthread_self() == *restore_thread) {
+ /*
+ * Recursive failure when trying to restore filesystem!
+ * Nothing to do except to exit, otherwise we'll just
+ * appear to hang. The user should be able to restore
+ * from the recovery file (which is why it was added, in
+ * case of catastrophic failure in Mksquashfs)
+ */
+ exit(1);
+ } else {
+ /* signal the restore thread to restore */
+ pthread_kill(*restore_thread, SIGUSR1);
+ pthread_exit(NULL);
+ }
+ } else if(!appending) {
+ if(destination_file && !block_device)
+ unlink(destination_file);
+ } else if(recovery_file)
+ unlink(recovery_file);
+}
+
+
+int add_overflow(int a, int b)
+{
+ return (INT_MAX - a) < b;
+}
+
+
+int shift_overflow(int a, int shift)
+{
+ return (INT_MAX >> shift) < a;
+}
+
+
+int multiply_overflow(int a, int multiplier)
+{
+ return (INT_MAX / multiplier) < a;
+}
+
+
+int multiply_overflowll(long long a, int multiplier)
+{
+ return (LLONG_MAX / multiplier) < a;
+}
+
+
+#define MKINODE(A) ((squashfs_inode)(((squashfs_inode) inode_bytes << 16) \
+ + (((char *)A) - data_cache)))
+
+
+void restorefs()
+{
+ int i, res;
+
+ ERROR("Exiting - restoring original filesystem!\n\n");
+
+ bytes = sbytes;
+ memcpy(data_cache, sdata_cache, cache_bytes = scache_bytes);
+ memcpy(directory_data_cache, sdirectory_data_cache,
+ sdirectory_cache_bytes);
+ directory_cache_bytes = sdirectory_cache_bytes;
+ inode_bytes = sinode_bytes;
+ directory_bytes = sdirectory_bytes;
+ memcpy(directory_table + directory_bytes, sdirectory_compressed,
+ sdirectory_compressed_bytes);
+ directory_bytes += sdirectory_compressed_bytes;
+ total_bytes = stotal_bytes;
+ total_inode_bytes = stotal_inode_bytes;
+ total_directory_bytes = stotal_directory_bytes;
+ inode_count = sinode_count;
+ file_count = sfile_count;
+ sym_count = ssym_count;
+ dev_count = sdev_count;
+ dir_count = sdir_count;
+ fifo_count = sfifo_count;
+ sock_count = ssock_count;
+ dup_files = sdup_files;
+ fragments = sfragments;
+ id_count = sid_count;
+ restore_xattrs();
+ write_filesystem_tables(&sBlk);
+
+ if(!block_device) {
+ int res = ftruncate(fd, bytes);
+ if(res != 0)
+ BAD_ERROR("Failed to truncate dest file because %s\n",
+ strerror(errno));
+ }
+
+ if(!nopad && (i = bytes & (4096 - 1))) {
+ char temp[4096] = {0};
+ write_destination(fd, bytes, 4096 - i, temp);
+ }
+
+ res = close(fd);
+
+ if(res == -1)
+ BAD_ERROR("Failed to close output filesystem, close returned %s\n",
+ strerror(errno));
+
+ if(recovery_file)
+ unlink(recovery_file);
+
+ if(!quiet)
+ print_summary();
+
+ exit(1);
+}
+
+
+void sighandler(int arg)
+{
+ EXIT_MKSQUASHFS();
+}
+
+
+static int mangle2(void *strm, char *d, char *s, int size,
+ int block_size, int uncompressed, int data_block)
+{
+ int error, c_byte = 0;
+
+ if(!uncompressed) {
+ c_byte = compressor_compress(comp, strm, d, s, size, block_size,
+ &error);
+ if(c_byte == -1)
+ BAD_ERROR("mangle2:: %s compress failed with error "
+ "code %d\n", comp->name, error);
+ }
+
+ if(c_byte == 0 || c_byte >= size) {
+ memcpy(d, s, size);
+ return size | (data_block ? SQUASHFS_COMPRESSED_BIT_BLOCK :
+ SQUASHFS_COMPRESSED_BIT);
+ }
+
+ return c_byte;
+}
+
+
+int mangle(char *d, char *s, int size, int block_size,
+ int uncompressed, int data_block)
+{
+ return mangle2(stream, d, s, size, block_size, uncompressed,
+ data_block);
+}
+
+
+static void *get_inode(int req_size)
+{
+ int data_space;
+ unsigned short c_byte;
+
+ while(cache_bytes >= SQUASHFS_METADATA_SIZE) {
+ if((inode_size - inode_bytes) <
+ ((SQUASHFS_METADATA_SIZE << 1)) + 2) {
+ void *it = realloc(inode_table, inode_size +
+ (SQUASHFS_METADATA_SIZE << 1) + 2);
+ if(it == NULL)
+ MEM_ERROR();
+ inode_table = it;
+ inode_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+ }
+
+ c_byte = mangle(inode_table + inode_bytes + BLOCK_OFFSET,
+ data_cache, SQUASHFS_METADATA_SIZE,
+ SQUASHFS_METADATA_SIZE, noI, 0);
+ TRACE("Inode block @ 0x%x, size %d\n", inode_bytes, c_byte);
+ SQUASHFS_SWAP_SHORTS(&c_byte, inode_table + inode_bytes, 1);
+ inode_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
+ total_inode_bytes += SQUASHFS_METADATA_SIZE + BLOCK_OFFSET;
+ memmove(data_cache, data_cache + SQUASHFS_METADATA_SIZE,
+ cache_bytes - SQUASHFS_METADATA_SIZE);
+ cache_bytes -= SQUASHFS_METADATA_SIZE;
+ }
+
+ data_space = (cache_size - cache_bytes);
+ if(data_space < req_size) {
+ int realloc_size = cache_size == 0 ?
+ ((req_size + SQUASHFS_METADATA_SIZE) &
+ ~(SQUASHFS_METADATA_SIZE - 1)) : req_size -
+ data_space;
+
+ void *dc = realloc(data_cache, cache_size +
+ realloc_size);
+ if(dc == NULL)
+ MEM_ERROR();
+ cache_size += realloc_size;
+ data_cache = dc;
+ }
+
+ cache_bytes += req_size;
+
+ return data_cache + cache_bytes - req_size;
+}
+
+
+long long read_bytes(int fd, void *buff, long long bytes)
+{
+ long long res, count;
+
+ for(count = 0; count < bytes; count += res) {
+ int len = (bytes - count) > MAXIMUM_READ_SIZE ?
+ MAXIMUM_READ_SIZE : bytes - count;
+
+ res = read(fd, buff + count, len);
+ if(res < 1) {
+ if(res == 0)
+ goto bytes_read;
+ else if(errno != EINTR) {
+ ERROR("Read failed because %s\n",
+ strerror(errno));
+ return -1;
+ } else
+ res = 0;
+ }
+ }
+
+bytes_read:
+ return count;
+}
+
+
+int read_fs_bytes(int fd, long long byte, long long bytes, void *buff)
+{
+ off_t off = byte;
+ int res = 1;
+
+ TRACE("read_fs_bytes: reading from position 0x%llx, bytes %lld\n",
+ byte, bytes);
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &pos_mutex);
+ pthread_mutex_lock(&pos_mutex);
+ if(lseek(fd, start_offset + off, SEEK_SET) == -1) {
+ ERROR("read_fs_bytes: Lseek on destination failed because %s, "
+ "offset=0x%llx\n", strerror(errno), start_offset + off);
+ res = 0;
+ } else if(read_bytes(fd, buff, bytes) < bytes) {
+ ERROR("Read on destination failed\n");
+ res = 0;
+ }
+
+ pthread_cleanup_pop(1);
+ return res;
+}
+
+
+int write_bytes(int fd, void *buff, long long bytes)
+{
+ long long res, count;
+
+ for(count = 0; count < bytes; count += res) {
+ int len = (bytes - count) > MAXIMUM_READ_SIZE ?
+ MAXIMUM_READ_SIZE : bytes - count;
+
+ res = write(fd, buff + count, len);
+ if(res == -1) {
+ if(errno != EINTR) {
+ ERROR("Write failed because %s\n",
+ strerror(errno));
+ return -1;
+ }
+ res = 0;
+ }
+ }
+
+ return 0;
+}
+
+
+void write_destination(int fd, long long byte, long long bytes, void *buff)
+{
+ off_t off = byte;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &pos_mutex);
+ pthread_mutex_lock(&pos_mutex);
+
+ if(lseek(fd, start_offset + off, SEEK_SET) == -1) {
+ ERROR("write_destination: Lseek on destination "
+ "failed because %s, offset=0x%llx\n", strerror(errno),
+ start_offset + off);
+ BAD_ERROR("Probably out of space on output %s\n",
+ block_device ? "block device" : "filesystem");
+ }
+
+ if(write_bytes(fd, buff, bytes) == -1)
+ BAD_ERROR("Failed to write to output %s\n",
+ block_device ? "block device" : "filesystem");
+
+ pthread_cleanup_pop(1);
+}
+
+
+static long long write_inodes()
+{
+ unsigned short c_byte;
+ int avail_bytes;
+ char *datap = data_cache;
+ long long start_bytes = bytes;
+
+ while(cache_bytes) {
+ if(inode_size - inode_bytes <
+ ((SQUASHFS_METADATA_SIZE << 1) + 2)) {
+ void *it = realloc(inode_table, inode_size +
+ ((SQUASHFS_METADATA_SIZE << 1) + 2));
+ if(it == NULL)
+ MEM_ERROR();
+ inode_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+ inode_table = it;
+ }
+ avail_bytes = cache_bytes > SQUASHFS_METADATA_SIZE ?
+ SQUASHFS_METADATA_SIZE : cache_bytes;
+ c_byte = mangle(inode_table + inode_bytes + BLOCK_OFFSET, datap,
+ avail_bytes, SQUASHFS_METADATA_SIZE, noI, 0);
+ TRACE("Inode block @ 0x%x, size %d\n", inode_bytes, c_byte);
+ SQUASHFS_SWAP_SHORTS(&c_byte, inode_table + inode_bytes, 1);
+ inode_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
+ total_inode_bytes += avail_bytes + BLOCK_OFFSET;
+ datap += avail_bytes;
+ cache_bytes -= avail_bytes;
+ }
+
+ write_destination(fd, bytes, inode_bytes, inode_table);
+ bytes += inode_bytes;
+
+ return start_bytes;
+}
+
+
+static long long write_directories()
+{
+ unsigned short c_byte;
+ int avail_bytes;
+ char *directoryp = directory_data_cache;
+ long long start_bytes = bytes;
+
+ while(directory_cache_bytes) {
+ if(directory_size - directory_bytes <
+ ((SQUASHFS_METADATA_SIZE << 1) + 2)) {
+ void *dt = realloc(directory_table,
+ directory_size + ((SQUASHFS_METADATA_SIZE << 1)
+ + 2));
+ if(dt == NULL)
+ MEM_ERROR();
+ directory_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+ directory_table = dt;
+ }
+ avail_bytes = directory_cache_bytes > SQUASHFS_METADATA_SIZE ?
+ SQUASHFS_METADATA_SIZE : directory_cache_bytes;
+ c_byte = mangle(directory_table + directory_bytes +
+ BLOCK_OFFSET, directoryp, avail_bytes,
+ SQUASHFS_METADATA_SIZE, noI, 0);
+ TRACE("Directory block @ 0x%x, size %d\n", directory_bytes,
+ c_byte);
+ SQUASHFS_SWAP_SHORTS(&c_byte,
+ directory_table + directory_bytes, 1);
+ directory_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) +
+ BLOCK_OFFSET;
+ total_directory_bytes += avail_bytes + BLOCK_OFFSET;
+ directoryp += avail_bytes;
+ directory_cache_bytes -= avail_bytes;
+ }
+ write_destination(fd, bytes, directory_bytes, directory_table);
+ bytes += directory_bytes;
+
+ return start_bytes;
+}
+
+
+static long long write_id_table()
+{
+ unsigned int id_bytes = SQUASHFS_ID_BYTES(id_count);
+ unsigned int p[id_count];
+ int i;
+
+ TRACE("write_id_table: ids %d, id_bytes %d\n", id_count, id_bytes);
+ for(i = 0; i < id_count; i++) {
+ TRACE("write_id_table: id index %d, id %d", i, id_table[i]->id);
+ SQUASHFS_SWAP_INTS(&id_table[i]->id, p + i, 1);
+ }
+
+ return generic_write_table(id_bytes, p, 0, NULL, noI || noId);
+}
+
+
+static struct id *get_id(unsigned int id)
+{
+ int hash = ID_HASH(id);
+ struct id *entry = id_hash_table[hash];
+
+ for(; entry; entry = entry->next)
+ if(entry->id == id)
+ break;
+
+ return entry;
+}
+
+
+struct id *create_id(unsigned int id)
+{
+ int hash = ID_HASH(id);
+ struct id *entry = malloc(sizeof(struct id));
+ if(entry == NULL)
+ MEM_ERROR();
+ entry->id = id;
+ entry->index = id_count ++;
+ entry->flags = 0;
+ entry->next = id_hash_table[hash];
+ id_hash_table[hash] = entry;
+ id_table[entry->index] = entry;
+ return entry;
+}
+
+
+unsigned int get_uid(unsigned int uid)
+{
+ struct id *entry = get_id(uid);
+
+ if(entry == NULL) {
+ if(id_count == SQUASHFS_IDS)
+ BAD_ERROR("Out of uids!\n");
+ entry = create_id(uid);
+ }
+
+ if((entry->flags & ISA_UID) == 0) {
+ entry->flags |= ISA_UID;
+ uid_count ++;
+ }
+
+ return entry->index;
+}
+
+
+unsigned int get_guid(unsigned int guid)
+{
+ struct id *entry = get_id(guid);
+
+ if(entry == NULL) {
+ if(id_count == SQUASHFS_IDS)
+ BAD_ERROR("Out of gids!\n");
+ entry = create_id(guid);
+ }
+
+ if((entry->flags & ISA_GID) == 0) {
+ entry->flags |= ISA_GID;
+ guid_count ++;
+ }
+
+ return entry->index;
+}
+
+
+char *pathname(struct dir_ent *dir_ent)
+{
+ static char *pathname = NULL;
+ static int size = ALLOC_SIZE;
+
+ if (dir_ent->nonstandard_pathname)
+ return dir_ent->nonstandard_pathname;
+
+ if(pathname == NULL) {
+ pathname = malloc(ALLOC_SIZE);
+ if(pathname == NULL)
+ MEM_ERROR();
+ }
+
+ for(;;) {
+ int res = snprintf(pathname, size, "%s/%s",
+ dir_ent->our_dir->pathname,
+ dir_ent->source_name ? : dir_ent->name);
+
+ if(res < 0)
+ BAD_ERROR("snprintf failed in pathname\n");
+ else if(res >= size) {
+ /*
+ * pathname is too small to contain the result, so
+ * increase it and try again
+ */
+ size = (res + ALLOC_SIZE) & ~(ALLOC_SIZE - 1);
+ pathname = realloc(pathname, size);
+ if(pathname == NULL)
+ MEM_ERROR();
+ } else
+ break;
+ }
+
+ return pathname;
+}
+
+
+
+char *subpathname(struct dir_ent *dir_ent)
+{
+ static char *subpath = NULL;
+ static int size = ALLOC_SIZE;
+ int res;
+
+ if(subpath == NULL) {
+ subpath = malloc(ALLOC_SIZE);
+ if(subpath == NULL)
+ MEM_ERROR();
+ }
+
+ for(;;) {
+ if(dir_ent->our_dir->subpath[0] != '\0')
+ res = snprintf(subpath, size, "%s/%s",
+ dir_ent->our_dir->subpath, dir_ent->name);
+ else
+ res = snprintf(subpath, size, "/%s", dir_ent->name);
+
+ if(res < 0)
+ BAD_ERROR("snprintf failed in subpathname\n");
+ else if(res >= size) {
+ /*
+ * subpath is too small to contain the result, so
+ * increase it and try again
+ */
+ size = (res + ALLOC_SIZE) & ~(ALLOC_SIZE - 1);
+ subpath = realloc(subpath, size);
+ if(subpath == NULL)
+ MEM_ERROR();
+ } else
+ break;
+ }
+
+ return subpath;
+}
+
+
+static inline unsigned int get_inode_no(struct inode_info *inode)
+{
+ return inode->inode_number;
+}
+
+
+static inline unsigned int get_parent_no(struct dir_info *dir)
+{
+ return dir->depth ? get_inode_no(dir->dir_ent->inode) : inode_no;
+}
+
+
+static inline time_t get_time(time_t time)
+{
+ if(all_time_opt) {
+ if(clamping)
+ return time > all_time ? all_time : time;
+ else
+ return all_time;
+ }
+
+ return time;
+}
+
+
+squashfs_inode create_inode(struct dir_info *dir_info,
+ struct dir_ent *dir_ent, int type, long long byte_size,
+ long long start_block, unsigned int offset, unsigned int *block_list,
+ struct fragment *fragment, struct directory *dir_in, long long sparse)
+{
+ struct stat *buf = &dir_ent->inode->buf;
+ union squashfs_inode_header inode_header;
+ struct squashfs_base_inode_header *base = &inode_header.base;
+ void *inode;
+ char *filename = pathname(dir_ent);
+ int nlink = dir_ent->inode->nlink;
+ int xattr = read_xattrs(dir_ent, type);
+ unsigned int uid, gid;
+
+ switch(type) {
+ case SQUASHFS_FILE_TYPE:
+ if(dir_ent->inode->nlink > 1 ||
+ byte_size >= (1LL << 32) ||
+ start_block >= (1LL << 32) ||
+ sparse || IS_XATTR(xattr))
+ type = SQUASHFS_LREG_TYPE;
+ break;
+ case SQUASHFS_DIR_TYPE:
+ if(dir_info->dir_is_ldir || IS_XATTR(xattr))
+ type = SQUASHFS_LDIR_TYPE;
+ break;
+ case SQUASHFS_SYMLINK_TYPE:
+ if(IS_XATTR(xattr))
+ type = SQUASHFS_LSYMLINK_TYPE;
+ break;
+ case SQUASHFS_BLKDEV_TYPE:
+ if(IS_XATTR(xattr))
+ type = SQUASHFS_LBLKDEV_TYPE;
+ break;
+ case SQUASHFS_CHRDEV_TYPE:
+ if(IS_XATTR(xattr))
+ type = SQUASHFS_LCHRDEV_TYPE;
+ break;
+ case SQUASHFS_FIFO_TYPE:
+ if(IS_XATTR(xattr))
+ type = SQUASHFS_LFIFO_TYPE;
+ break;
+ case SQUASHFS_SOCKET_TYPE:
+ if(IS_XATTR(xattr))
+ type = SQUASHFS_LSOCKET_TYPE;
+ break;
+ }
+
+ if(!pseudo_override && global_uid_opt)
+ uid = global_uid;
+ else
+ uid = buf->st_uid;
+
+ if(!pseudo_override && global_gid_opt)
+ gid = global_gid;
+ else
+ gid = buf->st_gid;
+
+ base->mode = SQUASHFS_MODE(buf->st_mode);
+ base->inode_type = type;
+ base->uid = get_uid(uid);
+ base->guid = get_guid(gid);
+ base->mtime = get_time(buf->st_mtime);
+ base->inode_number = get_inode_no(dir_ent->inode);
+
+ if(type == SQUASHFS_FILE_TYPE) {
+ int i;
+ struct squashfs_reg_inode_header *reg = &inode_header.reg;
+ size_t off = offsetof(struct squashfs_reg_inode_header, block_list);
+
+ inode = get_inode(sizeof(*reg) + offset * sizeof(unsigned int));
+ reg->file_size = byte_size;
+ reg->start_block = start_block;
+ reg->fragment = fragment->index;
+ reg->offset = fragment->offset;
+ SQUASHFS_SWAP_REG_INODE_HEADER(reg, inode);
+ SQUASHFS_SWAP_INTS(block_list, inode + off, offset);
+ TRACE("File inode, file_size %lld, start_block 0x%llx, blocks "
+ "%d, fragment %d, offset %d, size %d\n", byte_size,
+ start_block, offset, fragment->index, fragment->offset,
+ fragment->size);
+ for(i = 0; i < offset; i++)
+ TRACE("Block %d, size %d\n", i, block_list[i]);
+ }
+ else if(type == SQUASHFS_LREG_TYPE) {
+ int i;
+ struct squashfs_lreg_inode_header *reg = &inode_header.lreg;
+ size_t off = offsetof(struct squashfs_lreg_inode_header, block_list);
+
+ inode = get_inode(sizeof(*reg) + offset * sizeof(unsigned int));
+ reg->nlink = nlink;
+ reg->file_size = byte_size;
+ reg->start_block = start_block;
+ reg->fragment = fragment->index;
+ reg->offset = fragment->offset;
+ if(sparse && sparse >= byte_size)
+ sparse = byte_size - 1;
+ reg->sparse = sparse;
+ reg->xattr = xattr;
+ SQUASHFS_SWAP_LREG_INODE_HEADER(reg, inode);
+ SQUASHFS_SWAP_INTS(block_list, inode + off, offset);
+ TRACE("Long file inode, file_size %lld, start_block 0x%llx, "
+ "blocks %d, fragment %d, offset %d, size %d, nlink %d"
+ "\n", byte_size, start_block, offset, fragment->index,
+ fragment->offset, fragment->size, nlink);
+ for(i = 0; i < offset; i++)
+ TRACE("Block %d, size %d\n", i, block_list[i]);
+ }
+ else if(type == SQUASHFS_LDIR_TYPE) {
+ int i;
+ unsigned char *p;
+ struct squashfs_ldir_inode_header *dir = &inode_header.ldir;
+ struct cached_dir_index *index = dir_in->index;
+ unsigned int i_count = dir_in->i_count;
+ unsigned int i_size = dir_in->i_size;
+
+ if(byte_size >= 1LL << 32)
+ BAD_ERROR("directory greater than 2^32-1 bytes!\n");
+
+ inode = get_inode(sizeof(*dir) + i_size);
+ dir->inode_type = SQUASHFS_LDIR_TYPE;
+ dir->nlink = dir_ent->dir->directory_count + 2;
+ dir->file_size = byte_size;
+ dir->offset = offset;
+ dir->start_block = start_block;
+ dir->i_count = i_count;
+ dir->parent_inode = get_parent_no(dir_ent->our_dir);
+ dir->xattr = xattr;
+
+ SQUASHFS_SWAP_LDIR_INODE_HEADER(dir, inode);
+ p = inode + offsetof(struct squashfs_ldir_inode_header, index);
+ for(i = 0; i < i_count; i++) {
+ SQUASHFS_SWAP_DIR_INDEX(&index[i].index, p);
+ p += offsetof(struct squashfs_dir_index, name);
+ memcpy(p, index[i].name, index[i].index.size + 1);
+ p += index[i].index.size + 1;
+ }
+ TRACE("Long directory inode, file_size %lld, start_block "
+ "0x%llx, offset 0x%x, nlink %d\n", byte_size,
+ start_block, offset, dir_ent->dir->directory_count + 2);
+ }
+ else if(type == SQUASHFS_DIR_TYPE) {
+ struct squashfs_dir_inode_header *dir = &inode_header.dir;
+
+ inode = get_inode(sizeof(*dir));
+ dir->nlink = dir_ent->dir->directory_count + 2;
+ dir->file_size = byte_size;
+ dir->offset = offset;
+ dir->start_block = start_block;
+ dir->parent_inode = get_parent_no(dir_ent->our_dir);
+ SQUASHFS_SWAP_DIR_INODE_HEADER(dir, inode);
+ TRACE("Directory inode, file_size %lld, start_block 0x%llx, "
+ "offset 0x%x, nlink %d\n", byte_size, start_block,
+ offset, dir_ent->dir->directory_count + 2);
+ }
+ else if(type == SQUASHFS_CHRDEV_TYPE || type == SQUASHFS_BLKDEV_TYPE) {
+ struct squashfs_dev_inode_header *dev = &inode_header.dev;
+ unsigned int major = major(buf->st_rdev);
+ unsigned int minor = minor(buf->st_rdev);
+
+ if(major > 0xfff) {
+ ERROR("Major %d out of range in device node %s, "
+ "truncating to %d\n", major, filename,
+ major & 0xfff);
+ major &= 0xfff;
+ }
+ if(minor > 0xfffff) {
+ ERROR("Minor %d out of range in device node %s, "
+ "truncating to %d\n", minor, filename,
+ minor & 0xfffff);
+ minor &= 0xfffff;
+ }
+ inode = get_inode(sizeof(*dev));
+ dev->nlink = nlink;
+ dev->rdev = (major << 8) | (minor & 0xff) |
+ ((minor & ~0xff) << 12);
+ SQUASHFS_SWAP_DEV_INODE_HEADER(dev, inode);
+ TRACE("Device inode, rdev 0x%x, nlink %d\n", dev->rdev, nlink);
+ }
+ else if(type == SQUASHFS_LCHRDEV_TYPE || type == SQUASHFS_LBLKDEV_TYPE) {
+ struct squashfs_ldev_inode_header *dev = &inode_header.ldev;
+ unsigned int major = major(buf->st_rdev);
+ unsigned int minor = minor(buf->st_rdev);
+
+ if(major > 0xfff) {
+ ERROR("Major %d out of range in device node %s, "
+ "truncating to %d\n", major, filename,
+ major & 0xfff);
+ major &= 0xfff;
+ }
+ if(minor > 0xfffff) {
+ ERROR("Minor %d out of range in device node %s, "
+ "truncating to %d\n", minor, filename,
+ minor & 0xfffff);
+ minor &= 0xfffff;
+ }
+ inode = get_inode(sizeof(*dev));
+ dev->nlink = nlink;
+ dev->rdev = (major << 8) | (minor & 0xff) |
+ ((minor & ~0xff) << 12);
+ dev->xattr = xattr;
+ SQUASHFS_SWAP_LDEV_INODE_HEADER(dev, inode);
+ TRACE("Device inode, rdev 0x%x, nlink %d\n", dev->rdev, nlink);
+ }
+ else if(type == SQUASHFS_SYMLINK_TYPE) {
+ struct squashfs_symlink_inode_header *symlink = &inode_header.symlink;
+ int byte = strlen(dir_ent->inode->symlink);
+ size_t off = offsetof(struct squashfs_symlink_inode_header, symlink);
+
+ inode = get_inode(sizeof(*symlink) + byte);
+ symlink->nlink = nlink;
+ symlink->symlink_size = byte;
+ SQUASHFS_SWAP_SYMLINK_INODE_HEADER(symlink, inode);
+ strncpy(inode + off, dir_ent->inode->symlink, byte);
+ TRACE("Symbolic link inode, symlink_size %d, nlink %d\n", byte,
+ nlink);
+ }
+ else if(type == SQUASHFS_LSYMLINK_TYPE) {
+ struct squashfs_symlink_inode_header *symlink = &inode_header.symlink;
+ int byte = strlen(dir_ent->inode->symlink);
+ size_t off = offsetof(struct squashfs_symlink_inode_header, symlink);
+
+ inode = get_inode(sizeof(*symlink) + byte +
+ sizeof(unsigned int));
+ symlink->nlink = nlink;
+ symlink->symlink_size = byte;
+ SQUASHFS_SWAP_SYMLINK_INODE_HEADER(symlink, inode);
+ strncpy(inode + off, dir_ent->inode->symlink, byte);
+ SQUASHFS_SWAP_INTS(&xattr, inode + off + byte, 1);
+ TRACE("Symbolic link inode, symlink_size %d, nlink %d\n", byte,
+ nlink);
+ }
+ else if(type == SQUASHFS_FIFO_TYPE || type == SQUASHFS_SOCKET_TYPE) {
+ struct squashfs_ipc_inode_header *ipc = &inode_header.ipc;
+
+ inode = get_inode(sizeof(*ipc));
+ ipc->nlink = nlink;
+ SQUASHFS_SWAP_IPC_INODE_HEADER(ipc, inode);
+ TRACE("ipc inode, type %s, nlink %d\n", type ==
+ SQUASHFS_FIFO_TYPE ? "fifo" : "socket", nlink);
+ }
+ else if(type == SQUASHFS_LFIFO_TYPE || type == SQUASHFS_LSOCKET_TYPE) {
+ struct squashfs_lipc_inode_header *ipc = &inode_header.lipc;
+
+ inode = get_inode(sizeof(*ipc));
+ ipc->nlink = nlink;
+ ipc->xattr = xattr;
+ SQUASHFS_SWAP_LIPC_INODE_HEADER(ipc, inode);
+ TRACE("ipc inode, type %s, nlink %d\n", type ==
+ SQUASHFS_FIFO_TYPE ? "fifo" : "socket", nlink);
+ } else
+ BAD_ERROR("Unrecognised inode %d in create_inode\n", type);
+
+ inode_count ++;
+
+ TRACE("Created inode 0x%llx, type %d, uid %d, guid %d\n",
+ MKINODE(inode), type, base->uid, base->guid);
+
+ return MKINODE(inode);
+}
+
+
+static void add_dir(squashfs_inode inode, unsigned int inode_number, char *name,
+ int type, struct directory *dir)
+{
+ unsigned char *buff;
+ struct squashfs_dir_entry idir;
+ unsigned int start_block = inode >> 16;
+ unsigned int offset = inode & 0xffff;
+ unsigned int size = strlen(name);
+ size_t name_off = offsetof(struct squashfs_dir_entry, name);
+
+ if(size > SQUASHFS_NAME_LEN) {
+ size = SQUASHFS_NAME_LEN;
+ ERROR("Filename is greater than %d characters, truncating! ..."
+ "\n", SQUASHFS_NAME_LEN);
+ }
+
+ if(dir->p + sizeof(struct squashfs_dir_entry) + size +
+ sizeof(struct squashfs_dir_header)
+ >= dir->buff + dir->size) {
+ buff = realloc(dir->buff, dir->size += SQUASHFS_METADATA_SIZE);
+ if(buff == NULL)
+ MEM_ERROR();
+
+ dir->p = (dir->p - dir->buff) + buff;
+ if(dir->entry_count_p)
+ dir->entry_count_p = (dir->entry_count_p - dir->buff +
+ buff);
+ dir->index_count_p = dir->index_count_p - dir->buff + buff;
+ dir->buff = buff;
+ }
+
+ if(dir->entry_count == 256 || start_block != dir->start_block ||
+ ((dir->entry_count_p != NULL) &&
+ ((dir->p + sizeof(struct squashfs_dir_entry) + size -
+ dir->index_count_p) > SQUASHFS_METADATA_SIZE)) ||
+ ((long long) inode_number - dir->inode_number) > 32767
+ || ((long long) inode_number - dir->inode_number)
+ < -32768) {
+ if(dir->entry_count_p) {
+ struct squashfs_dir_header dir_header;
+
+ if((dir->p + sizeof(struct squashfs_dir_entry) + size -
+ dir->index_count_p) >
+ SQUASHFS_METADATA_SIZE) {
+ if(dir->i_count % I_COUNT_SIZE == 0) {
+ dir->index = realloc(dir->index,
+ (dir->i_count + I_COUNT_SIZE) *
+ sizeof(struct cached_dir_index));
+ if(dir->index == NULL)
+ MEM_ERROR();
+ }
+ dir->index[dir->i_count].index.index =
+ dir->p - dir->buff;
+ dir->index[dir->i_count].index.size = size - 1;
+ dir->index[dir->i_count++].name = name;
+ dir->i_size += sizeof(struct squashfs_dir_index)
+ + size;
+ dir->index_count_p = dir->p;
+ }
+
+ dir_header.count = dir->entry_count - 1;
+ dir_header.start_block = dir->start_block;
+ dir_header.inode_number = dir->inode_number;
+ SQUASHFS_SWAP_DIR_HEADER(&dir_header,
+ dir->entry_count_p);
+
+ }
+
+
+ dir->entry_count_p = dir->p;
+ dir->start_block = start_block;
+ dir->entry_count = 0;
+ dir->inode_number = inode_number;
+ dir->p += sizeof(struct squashfs_dir_header);
+ }
+
+ idir.offset = offset;
+ idir.type = type;
+ idir.size = size - 1;
+ idir.inode_number = ((long long) inode_number - dir->inode_number);
+ SQUASHFS_SWAP_DIR_ENTRY(&idir, dir->p);
+ strncpy((char *) dir->p + name_off, name, size);
+ dir->p += sizeof(struct squashfs_dir_entry) + size;
+ dir->entry_count ++;
+}
+
+
+static squashfs_inode write_dir(struct dir_info *dir_info,
+ struct directory *dir)
+{
+ long long dir_size = dir->p - dir->buff;
+ int data_space = directory_cache_size - directory_cache_bytes;
+ unsigned int directory_block, directory_offset, i_count, index;
+ unsigned short c_byte;
+
+ if(data_space < dir_size) {
+ int realloc_size = directory_cache_size == 0 ?
+ ((dir_size + SQUASHFS_METADATA_SIZE) &
+ ~(SQUASHFS_METADATA_SIZE - 1)) : dir_size - data_space;
+
+ void *dc = realloc(directory_data_cache,
+ directory_cache_size + realloc_size);
+ if(dc == NULL)
+ MEM_ERROR();
+ directory_cache_size += realloc_size;
+ directory_data_cache = dc;
+ }
+
+ if(dir_size) {
+ struct squashfs_dir_header dir_header;
+
+ dir_header.count = dir->entry_count - 1;
+ dir_header.start_block = dir->start_block;
+ dir_header.inode_number = dir->inode_number;
+ SQUASHFS_SWAP_DIR_HEADER(&dir_header, dir->entry_count_p);
+ memcpy(directory_data_cache + directory_cache_bytes, dir->buff,
+ dir_size);
+ }
+ directory_offset = directory_cache_bytes;
+ directory_block = directory_bytes;
+ directory_cache_bytes += dir_size;
+ i_count = 0;
+ index = SQUASHFS_METADATA_SIZE - directory_offset;
+
+ while(1) {
+ while(i_count < dir->i_count &&
+ dir->index[i_count].index.index < index)
+ dir->index[i_count++].index.start_block =
+ directory_bytes;
+ index += SQUASHFS_METADATA_SIZE;
+
+ if(directory_cache_bytes < SQUASHFS_METADATA_SIZE)
+ break;
+
+ if((directory_size - directory_bytes) <
+ ((SQUASHFS_METADATA_SIZE << 1) + 2)) {
+ void *dt = realloc(directory_table,
+ directory_size + (SQUASHFS_METADATA_SIZE << 1)
+ + 2);
+ if(dt == NULL)
+ MEM_ERROR();
+ directory_size += SQUASHFS_METADATA_SIZE << 1;
+ directory_table = dt;
+ }
+
+ c_byte = mangle(directory_table + directory_bytes +
+ BLOCK_OFFSET, directory_data_cache,
+ SQUASHFS_METADATA_SIZE, SQUASHFS_METADATA_SIZE,
+ noI, 0);
+ TRACE("Directory block @ 0x%x, size %d\n", directory_bytes,
+ c_byte);
+ SQUASHFS_SWAP_SHORTS(&c_byte,
+ directory_table + directory_bytes, 1);
+ directory_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) +
+ BLOCK_OFFSET;
+ total_directory_bytes += SQUASHFS_METADATA_SIZE + BLOCK_OFFSET;
+ memmove(directory_data_cache, directory_data_cache +
+ SQUASHFS_METADATA_SIZE, directory_cache_bytes -
+ SQUASHFS_METADATA_SIZE);
+ directory_cache_bytes -= SQUASHFS_METADATA_SIZE;
+ }
+
+ dir_count ++;
+
+#ifndef SQUASHFS_TRACE
+ return create_inode(dir_info, dir_info->dir_ent, SQUASHFS_DIR_TYPE,
+ dir_size + 3, directory_block, directory_offset, NULL, NULL,
+ dir, 0);
+#else
+ {
+ unsigned char *dirp;
+ int count;
+ squashfs_inode inode;
+
+ inode = create_inode(dir_info, dir_info->dir_ent, SQUASHFS_DIR_TYPE,
+ dir_size + 3, directory_block, directory_offset, NULL, NULL,
+ dir, 0);
+
+ TRACE("Directory contents of inode 0x%llx\n", inode);
+ dirp = dir->buff;
+ while(dirp < dir->p) {
+ char buffer[SQUASHFS_NAME_LEN + 1];
+ struct squashfs_dir_entry idir, *idirp;
+ struct squashfs_dir_header dirh;
+ SQUASHFS_SWAP_DIR_HEADER((struct squashfs_dir_header *) dirp,
+ &dirh);
+ count = dirh.count + 1;
+ dirp += sizeof(struct squashfs_dir_header);
+
+ TRACE("\tStart block 0x%x, count %d\n",
+ dirh.start_block, count);
+
+ while(count--) {
+ idirp = (struct squashfs_dir_entry *) dirp;
+ SQUASHFS_SWAP_DIR_ENTRY(idirp, &idir);
+ strncpy(buffer, idirp->name, idir.size + 1);
+ buffer[idir.size + 1] = '\0';
+ TRACE("\t\tname %s, inode offset 0x%x, type "
+ "%d\n", buffer, idir.offset, idir.type);
+ dirp += sizeof(struct squashfs_dir_entry) + idir.size +
+ 1;
+ }
+ }
+
+ return inode;
+ }
+#endif
+}
+
+
+static struct file_buffer *get_fragment(struct fragment *fragment)
+{
+ struct squashfs_fragment_entry *disk_fragment;
+ struct file_buffer *buffer, *compressed_buffer;
+ long long start_block;
+ int res, size, index = fragment->index, compressed;
+ char locked;
+
+ /*
+ * Lookup fragment block in cache.
+ * If the fragment block doesn't exist, then get the compressed version
+ * from the writer cache or off disk, and decompress it.
+ *
+ * This routine has two things which complicate the code:
+ *
+ * 1. Multiple threads can simultaneously lookup/create the
+ * same buffer. This means a buffer needs to be "locked"
+ * when it is being filled in, to prevent other threads from
+ * using it when it is not ready. This is because we now do
+ * fragment duplicate checking in parallel.
+ * 2. We have two caches which need to be checked for the
+ * presence of fragment blocks: the normal fragment cache
+ * and a "reserve" cache. The reserve cache is used to
+ * prevent an unnecessary pipeline stall when the fragment cache
+ * is full of fragments waiting to be compressed.
+ */
+
+ if(fragment->index == SQUASHFS_INVALID_FRAG)
+ return NULL;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+ pthread_mutex_lock(&dup_mutex);
+
+again:
+ buffer = cache_lookup_nowait(fragment_buffer, index, &locked);
+ if(buffer) {
+ pthread_mutex_unlock(&dup_mutex);
+ if(locked)
+ /* got a buffer being filled in. Wait for it */
+ cache_wait_unlock(buffer);
+ goto finished;
+ }
+
+ /* not in fragment cache, is it in the reserve cache? */
+ buffer = cache_lookup_nowait(reserve_cache, index, &locked);
+ if(buffer) {
+ pthread_mutex_unlock(&dup_mutex);
+ if(locked)
+ /* got a buffer being filled in. Wait for it */
+ cache_wait_unlock(buffer);
+ goto finished;
+ }
+
+ /* in neither cache, try to get it from the fragment cache */
+ buffer = cache_get_nowait(fragment_buffer, index);
+ if(!buffer) {
+ /*
+ * no room, get it from the reserve cache, this is
+ * dimensioned so it will always have space (no more than
+ * processors + 1 can have an outstanding reserve buffer)
+ */
+ buffer = cache_get_nowait(reserve_cache, index);
+ if(!buffer) {
+ /* failsafe */
+ ERROR("no space in reserve cache\n");
+ goto again;
+ }
+ }
+
+ pthread_mutex_unlock(&dup_mutex);
+
+ compressed_buffer = cache_lookup(fwriter_buffer, index);
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+ pthread_mutex_lock(&fragment_mutex);
+ disk_fragment = &fragment_table[index];
+ size = SQUASHFS_COMPRESSED_SIZE_BLOCK(disk_fragment->size);
+ compressed = SQUASHFS_COMPRESSED_BLOCK(disk_fragment->size);
+ start_block = disk_fragment->start_block;
+ pthread_cleanup_pop(1);
+
+ if(compressed) {
+ int error;
+ char *data;
+
+ if(compressed_buffer)
+ data = compressed_buffer->data;
+ else {
+ data = read_from_disk(start_block, size);
+ if(data == NULL) {
+ ERROR("Failed to read fragment from output"
+ " filesystem\n");
+ BAD_ERROR("Output filesystem corrupted?\n");
+ }
+ }
+
+ res = compressor_uncompress(comp, buffer->data, data, size,
+ block_size, &error);
+ if(res == -1)
+ BAD_ERROR("%s uncompress failed with error code %d\n",
+ comp->name, error);
+ } else if(compressed_buffer)
+ memcpy(buffer->data, compressed_buffer->data, size);
+ else {
+ res = read_fs_bytes(fd, start_block, size, buffer->data);
+ if(res == 0) {
+ ERROR("Failed to read fragment from output "
+ "filesystem\n");
+ BAD_ERROR("Output filesystem corrupted?\n");
+ }
+ }
+
+ cache_unlock(buffer);
+ cache_block_put(compressed_buffer);
+
+finished:
+ pthread_cleanup_pop(0);
+
+ return buffer;
+}
+
+
+static unsigned short get_fragment_checksum(struct file_info *file)
+{
+ struct file_buffer *frag_buffer;
+ struct append_file *append;
+ int res, index = file->fragment->index;
+ unsigned short checksum;
+
+ if(index == SQUASHFS_INVALID_FRAG)
+ return 0;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+ pthread_mutex_lock(&dup_mutex);
+ res = file->have_frag_checksum;
+ checksum = file->fragment_checksum;
+ pthread_cleanup_pop(1);
+
+ if(res)
+ return checksum;
+
+ frag_buffer = get_fragment(file->fragment);
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+
+ for(append = file_mapping[index]; append; append = append->next) {
+ int offset = append->file->fragment->offset;
+ int size = append->file->fragment->size;
+ unsigned short cksum =
+ get_checksum_mem(frag_buffer->data + offset, size);
+
+ if(file == append->file)
+ checksum = cksum;
+
+ pthread_mutex_lock(&dup_mutex);
+ append->file->fragment_checksum = cksum;
+ append->file->have_frag_checksum = TRUE;
+ pthread_mutex_unlock(&dup_mutex);
+ }
+
+ cache_block_put(frag_buffer);
+ pthread_cleanup_pop(0);
+
+ return checksum;
+}
+
+
+static void ensure_fragments_flushed()
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+ pthread_mutex_lock(&fragment_mutex);
+
+ while(fragments_outstanding)
+ pthread_cond_wait(&fragment_waiting, &fragment_mutex);
+
+ pthread_cleanup_pop(1);
+}
+
+
+static void lock_fragments()
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+ pthread_mutex_lock(&fragment_mutex);
+ fragments_locked = TRUE;
+ pthread_cleanup_pop(1);
+}
+
+
+static void log_fragment(unsigned int fragment, long long start)
+{
+ if(logging)
+ fprintf(log_fd, "Fragment %u, %lld\n", fragment, start);
+}
+
+
+static void unlock_fragments()
+{
+ int frg, size;
+ struct file_buffer *write_buffer;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+ pthread_mutex_lock(&fragment_mutex);
+
+ /*
+ * Note queue_empty() is inherently racy with respect to concurrent
+ * queue get and pushes. We avoid this because we're holding the
+ * fragment_mutex which ensures no other threads can be using the
+ * queue at this time.
+ */
+ while(!queue_empty(locked_fragment)) {
+ write_buffer = queue_get(locked_fragment);
+ frg = write_buffer->block;
+ size = SQUASHFS_COMPRESSED_SIZE_BLOCK(fragment_table[frg].size);
+ fragment_table[frg].start_block = bytes;
+ write_buffer->block = bytes;
+ bytes += size;
+ fragments_outstanding --;
+ queue_put(to_writer, write_buffer);
+ log_fragment(frg, fragment_table[frg].start_block);
+ TRACE("fragment_locked writing fragment %d, compressed size %d"
+ "\n", frg, size);
+ }
+ fragments_locked = FALSE;
+ pthread_cleanup_pop(1);
+}
+
+/* Called with the fragment_mutex locked */
+static void add_pending_fragment(struct file_buffer *write_buffer, int c_byte,
+ int fragment)
+{
+ fragment_table[fragment].size = c_byte;
+ write_buffer->block = fragment;
+
+ queue_put(locked_fragment, write_buffer);
+}
+
+
+static void write_fragment(struct file_buffer *fragment)
+{
+ static long long sequence = 0;
+
+ if(fragment == NULL)
+ return;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+ pthread_mutex_lock(&fragment_mutex);
+ fragment_table[fragment->block].unused = 0;
+ fragment->sequence = sequence ++;
+ fragments_outstanding ++;
+ queue_put(to_frag, fragment);
+ pthread_cleanup_pop(1);
+}
+
+
+static struct file_buffer *allocate_fragment()
+{
+ struct file_buffer *fragment = cache_get(fragment_buffer, fragments);
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+ pthread_mutex_lock(&fragment_mutex);
+
+ if(fragments % FRAG_SIZE == 0) {
+ void *ft = realloc(fragment_table, (fragments +
+ FRAG_SIZE) * sizeof(struct squashfs_fragment_entry));
+ if(ft == NULL)
+ MEM_ERROR();
+ fragment_table = ft;
+ }
+
+ fragment->size = 0;
+ fragment->block = fragments ++;
+
+ pthread_cleanup_pop(1);
+
+ return fragment;
+}
+
+
+static struct fragment empty_fragment = {SQUASHFS_INVALID_FRAG, 0, 0};
+
+
+void free_fragment(struct fragment *fragment)
+{
+ if(fragment != &empty_fragment)
+ free(fragment);
+}
+
+
+static struct fragment *get_and_fill_fragment(struct file_buffer *file_buffer,
+ struct dir_ent *dir_ent, int tail)
+{
+ struct fragment *ffrg;
+ struct file_buffer **fragment;
+
+ if(file_buffer == NULL || file_buffer->size == 0)
+ return &empty_fragment;
+
+ fragment = eval_frag_actions(root_dir, dir_ent, tail);
+
+ if((*fragment) && (*fragment)->size + file_buffer->size > block_size) {
+ write_fragment(*fragment);
+ *fragment = NULL;
+ }
+
+ ffrg = malloc(sizeof(struct fragment));
+ if(ffrg == NULL)
+ MEM_ERROR();
+
+ if(*fragment == NULL)
+ *fragment = allocate_fragment();
+
+ ffrg->index = (*fragment)->block;
+ ffrg->offset = (*fragment)->size;
+ ffrg->size = file_buffer->size;
+ memcpy((*fragment)->data + (*fragment)->size, file_buffer->data,
+ file_buffer->size);
+ (*fragment)->size += file_buffer->size;
+
+ return ffrg;
+}
+
+
+long long generic_write_table(long long length, void *buffer, int length2,
+ void *buffer2, int uncompressed)
+{
+ int meta_blocks = (length + SQUASHFS_METADATA_SIZE - 1) /
+ SQUASHFS_METADATA_SIZE;
+ long long *list, start_bytes;
+ int compressed_size, i, list_size = meta_blocks * sizeof(long long);
+ unsigned short c_byte;
+ char cbuffer[(SQUASHFS_METADATA_SIZE << 2) + 2];
+
+#ifdef SQUASHFS_TRACE
+ long long obytes = bytes;
+ long long olength = length;
+#endif
+
+ list = malloc(list_size);
+ if(list == NULL)
+ MEM_ERROR();
+
+ for(i = 0; i < meta_blocks; i++) {
+ int avail_bytes = length > SQUASHFS_METADATA_SIZE ?
+ SQUASHFS_METADATA_SIZE : length;
+ c_byte = mangle(cbuffer + BLOCK_OFFSET, buffer + i *
+ SQUASHFS_METADATA_SIZE , avail_bytes,
+ SQUASHFS_METADATA_SIZE, uncompressed, 0);
+ SQUASHFS_SWAP_SHORTS(&c_byte, cbuffer, 1);
+ list[i] = bytes;
+ compressed_size = SQUASHFS_COMPRESSED_SIZE(c_byte) +
+ BLOCK_OFFSET;
+ TRACE("block %d @ 0x%llx, compressed size %d\n", i, bytes,
+ compressed_size);
+ write_destination(fd, bytes, compressed_size, cbuffer);
+ bytes += compressed_size;
+ total_bytes += avail_bytes;
+ length -= avail_bytes;
+ }
+
+ start_bytes = bytes;
+ if(length2) {
+ write_destination(fd, bytes, length2, buffer2);
+ bytes += length2;
+ total_bytes += length2;
+ }
+
+ SQUASHFS_INSWAP_LONG_LONGS(list, meta_blocks);
+ write_destination(fd, bytes, list_size, list);
+ bytes += list_size;
+ total_bytes += list_size;
+
+ TRACE("generic_write_table: total uncompressed %lld compressed %lld\n",
+ olength, bytes - obytes);
+
+ free(list);
+
+ return start_bytes;
+}
+
+
+static long long write_fragment_table()
+{
+ long long frag_bytes = SQUASHFS_FRAGMENT_BYTES(fragments);
+ unsigned int i;
+
+ TRACE("write_fragment_table: fragments %u, frag_bytes %d\n", fragments,
+ frag_bytes);
+ for(i = 0; i < fragments; i++) {
+ TRACE("write_fragment_table: fragment %u, start_block 0x%llx, "
+ "size %d\n", i, fragment_table[i].start_block,
+ fragment_table[i].size);
+ SQUASHFS_INSWAP_FRAGMENT_ENTRY(&fragment_table[i]);
+ }
+
+ return generic_write_table(frag_bytes, fragment_table, 0, NULL, noF);
+}
+
+
+char read_from_file_buffer[SQUASHFS_FILE_MAX_SIZE];
+static char *read_from_disk(long long start, unsigned int avail_bytes)
+{
+ int res;
+
+ res = read_fs_bytes(fd, start, avail_bytes, read_from_file_buffer);
+ if(res == 0)
+ return NULL;
+
+ return read_from_file_buffer;
+}
+
+
+char read_from_file_buffer2[SQUASHFS_FILE_MAX_SIZE];
+static char *read_from_disk2(long long start, unsigned int avail_bytes)
+{
+ int res;
+
+ res = read_fs_bytes(fd, start, avail_bytes, read_from_file_buffer2);
+ if(res == 0)
+ return NULL;
+
+ return read_from_file_buffer2;
+}
+
+
+/*
+ * Compute 16 bit BSD checksum over the data
+ */
+unsigned short get_checksum(char *buff, int bytes, unsigned short chksum)
+{
+ unsigned char *b = (unsigned char *) buff;
+
+ while(bytes --) {
+ chksum = (chksum & 1) ? (chksum >> 1) | 0x8000 : chksum >> 1;
+ chksum += *b++;
+ }
+
+ return chksum;
+}
+
+
+static unsigned short get_checksum_disk(long long start, long long l,
+ unsigned int *blocks)
+{
+ unsigned short chksum = 0;
+ unsigned int bytes;
+ struct file_buffer *write_buffer;
+ int i;
+
+ for(i = 0; l; i++) {
+ bytes = SQUASHFS_COMPRESSED_SIZE_BLOCK(blocks[i]);
+ if(bytes == 0) /* sparse block */
+ continue;
+ write_buffer = cache_lookup(bwriter_buffer, start);
+ if(write_buffer) {
+ chksum = get_checksum(write_buffer->data, bytes,
+ chksum);
+ cache_block_put(write_buffer);
+ } else {
+ void *data = read_from_disk(start, bytes);
+ if(data == NULL) {
+ ERROR("Failed to checksum data from output"
+ " filesystem\n");
+ BAD_ERROR("Output filesystem corrupted?\n");
+ }
+
+ chksum = get_checksum(data, bytes, chksum);
+ }
+
+ l -= bytes;
+ start += bytes;
+ }
+
+ return chksum;
+}
+
+
+unsigned short get_checksum_mem(char *buff, int bytes)
+{
+ return get_checksum(buff, bytes, 0);
+}
+
+
+static int block_hash(int size, int blocks)
+{
+ return ((size << 10) & 0xffc00) | (blocks & 0x3ff);
+}
+
+
+void add_file(long long start, long long file_size, long long file_bytes,
+ unsigned int *block_listp, int blocks, unsigned int fragment,
+ int offset, int bytes)
+{
+ struct fragment *frg;
+ unsigned int *block_list = block_listp;
+ struct file_info *dupl_ptr;
+ struct append_file *append_file;
+ struct file_info *file;
+ int blocks_dup = FALSE, frag_dup = FALSE;
+ int bl_hash = 0;
+
+ if(!duplicate_checking || file_size == 0)
+ return;
+
+ if(blocks) {
+ bl_hash = block_hash(block_list[0], blocks);
+ dupl_ptr = dupl_block[bl_hash];
+
+ for(; dupl_ptr; dupl_ptr = dupl_ptr->block_next) {
+ if(start == dupl_ptr->start)
+ break;
+ }
+
+ if(dupl_ptr) {
+ /*
+ * Our blocks have already been added. If we don't
+ * have a fragment, then we've finished checking
+ */
+ if(fragment == SQUASHFS_INVALID_FRAG)
+ return;
+
+ /*
+ * This entry probably created both the blocks and
+ * the tail-end fragment, and so check for that
+ */
+ if((fragment == dupl_ptr->fragment->index) &&
+ (offset == dupl_ptr->fragment->offset)
+ && (bytes == dupl_ptr->fragment->size))
+ return;
+
+ /*
+ * Remember our blocks are duplicate, and continue
+ * looking for the tail-end fragment
+ */
+ blocks_dup = TRUE;
+ }
+ }
+
+ if(fragment != SQUASHFS_INVALID_FRAG) {
+ dupl_ptr = dupl_frag[bytes];
+
+ for(; dupl_ptr; dupl_ptr = dupl_ptr->frag_next)
+ if((fragment == dupl_ptr->fragment->index) &&
+ (offset == dupl_ptr->fragment->offset)
+ && (bytes == dupl_ptr->fragment->size))
+ break;
+
+ if(dupl_ptr) {
+ /*
+ * Our tail-end fragment entry has already been added.
+ * If there's no blocks or they're dup, then we're done
+ * here
+ */
+ if(blocks == 0 || blocks_dup)
+ return;
+
+ /* Remember our tail-end fragment entry is duplicate */
+ frag_dup = TRUE;
+ }
+ }
+
+ frg = malloc(sizeof(struct fragment));
+ if(frg == NULL)
+ MEM_ERROR();
+
+ frg->index = fragment;
+ frg->offset = offset;
+ frg->size = bytes;
+
+ file = add_non_dup(file_size, file_bytes, blocks, 0, block_list, start,
+ frg, 0, 0, FALSE, FALSE, blocks_dup, frag_dup, bl_hash);
+
+ if(fragment == SQUASHFS_INVALID_FRAG)
+ return;
+
+ append_file = malloc(sizeof(struct append_file));
+ if(append_file == NULL)
+ MEM_ERROR();
+
+ append_file->file = file;
+ append_file->next = file_mapping[fragment];
+ file_mapping[fragment] = append_file;
+}
+
+
+static int pre_duplicate(long long file_size, struct inode_info *inode,
+ struct file_buffer *buffer, int *bl_hash)
+{
+ struct file_info *dupl_ptr;
+ long long fragment_size;
+ int blocks;
+
+ if(inode->no_fragments || (!inode->always_use_fragments && file_size >=
+ block_size)) {
+ blocks = (file_size + block_size - 1) >> block_log;
+ fragment_size = 0;
+ } else {
+ blocks = file_size >> block_log;
+ fragment_size = file_size & (block_size - 1);
+ }
+
+ /* Look for a possible duplicate set of blocks */
+ if(blocks) {
+ *bl_hash = block_hash(buffer->size, blocks);
+ for(dupl_ptr = dupl_block[*bl_hash]; dupl_ptr;dupl_ptr = dupl_ptr->block_next)
+ if(dupl_ptr->blocks == blocks && dupl_ptr->block_list[0] == buffer->c_byte)
+ return TRUE;
+ }
+
+ /* Look for a possible duplicate fragment */
+ if(fragment_size) {
+ for(dupl_ptr = dupl_frag[fragment_size]; dupl_ptr; dupl_ptr = dupl_ptr->frag_next)
+ if(dupl_ptr->fragment->size == fragment_size)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static struct file_info *create_non_dup(long long file_size, long long bytes,
+ unsigned int blocks, long long sparse, unsigned int *block_list,
+ long long start,struct fragment *fragment,unsigned short checksum,
+ unsigned short fragment_checksum, int checksum_flag,
+ int checksum_frag_flag)
+{
+ struct file_info *dupl_ptr = malloc(sizeof(struct file_info));
+
+ if(dupl_ptr == NULL)
+ MEM_ERROR();
+
+ dupl_ptr->file_size = file_size;
+ dupl_ptr->bytes = bytes;
+ dupl_ptr->blocks = blocks;
+ dupl_ptr->sparse = sparse;
+ dupl_ptr->block_list = block_list;
+ dupl_ptr->start = start;
+ dupl_ptr->fragment = fragment;
+ dupl_ptr->checksum = checksum;
+ dupl_ptr->fragment_checksum = fragment_checksum;
+ dupl_ptr->have_frag_checksum = checksum_frag_flag;
+ dupl_ptr->have_checksum = checksum_flag;
+ dupl_ptr->block_next = NULL;
+ dupl_ptr->frag_next = NULL;
+ dupl_ptr->dup = NULL;
+
+ return dupl_ptr;
+}
+
+
+static struct file_info *add_non_dup(long long file_size, long long bytes,
+ unsigned int blocks, long long sparse, unsigned int *block_list,
+ long long start,struct fragment *fragment,unsigned short checksum,
+ unsigned short fragment_checksum, int checksum_flag,
+ int checksum_frag_flag, int blocks_dup, int frag_dup, int bl_hash)
+{
+ struct file_info *dupl_ptr = malloc(sizeof(struct file_info));
+ int fragment_size = fragment->size;
+
+ if(dupl_ptr == NULL)
+ MEM_ERROR();
+
+ dupl_ptr->file_size = file_size;
+ dupl_ptr->bytes = bytes;
+ dupl_ptr->blocks = blocks;
+ dupl_ptr->sparse = sparse;
+ dupl_ptr->block_list = block_list;
+ dupl_ptr->start = start;
+ dupl_ptr->fragment = fragment;
+ dupl_ptr->checksum = checksum;
+ dupl_ptr->fragment_checksum = fragment_checksum;
+ dupl_ptr->have_frag_checksum = checksum_frag_flag;
+ dupl_ptr->have_checksum = checksum_flag;
+ dupl_ptr->block_next = NULL;
+ dupl_ptr->frag_next = NULL;
+ dupl_ptr->dup = NULL;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+ pthread_mutex_lock(&dup_mutex);
+
+ if(blocks && !blocks_dup) {
+ dupl_ptr->block_next = dupl_block[bl_hash];
+ dupl_block[bl_hash] = dupl_ptr;
+ }
+
+ if(fragment_size && !frag_dup) {
+ dupl_ptr->frag_next = dupl_frag[fragment_size];
+ dupl_frag[fragment_size] = dupl_ptr;
+ }
+
+ dup_files ++;
+
+ pthread_cleanup_pop(1);
+
+ return dupl_ptr;
+}
+
+
+static struct file_info *frag_duplicate(struct file_buffer *file_buffer, int *duplicate)
+{
+ struct file_info *dupl_ptr;
+ struct file_buffer *buffer;
+ struct file_info *dupl_start = file_buffer->dupl_start;
+ long long file_size = file_buffer->file_size;
+ unsigned short checksum = file_buffer->checksum;
+ int res;
+
+ if(file_buffer->duplicate)
+ dupl_ptr = dupl_start;
+ else {
+ for(dupl_ptr = dupl_frag[file_size];
+ dupl_ptr && dupl_ptr != dupl_start;
+ dupl_ptr = dupl_ptr->frag_next) {
+ if(file_size == dupl_ptr->fragment->size) {
+ if(get_fragment_checksum(dupl_ptr) == checksum) {
+ buffer = get_fragment(dupl_ptr->fragment);
+ res = memcmp(file_buffer->data,
+ buffer->data +
+ dupl_ptr->fragment->offset,
+ file_size);
+ cache_block_put(buffer);
+ if(res == 0)
+ break;
+ }
+ }
+ }
+
+ if(!dupl_ptr || dupl_ptr == dupl_start) {
+ *duplicate = FALSE;
+ return NULL;
+ }
+ }
+
+ if(dupl_ptr->file_size == file_size) {
+ /* File only has a fragment, and so this is an exact match */
+ TRACE("Found duplicate file, fragment %u, size %d, offset %d, "
+ "checksum 0x%x\n", dupl_ptr->fragment->index, file_size,
+ dupl_ptr->fragment->offset, checksum);
+ *duplicate = TRUE;
+ return dupl_ptr;
+ } else {
+ struct dup_info *dup;
+
+ /*
+ * File also has a block list. Create a new file without
+ * a block_list, and link it to this file. First check whether
+ * it is already there.
+ */
+ if(dupl_ptr->dup) {
+ *duplicate = TRUE;
+ return dupl_ptr->dup->file;
+ }
+
+ dup = malloc(sizeof(struct dup_info));
+ if(dup == NULL)
+ MEM_ERROR();
+
+ dup->file = create_non_dup(file_size, 0, 0, 0, NULL, 0,
+ dupl_ptr->fragment, 0, checksum, TRUE, TRUE);
+ dup->next = NULL;
+ dupl_ptr->dup = dup;
+ *duplicate = FALSE;
+ return dup->file;
+ }
+}
+
+
+static struct file_info *duplicate(int *dupf, int *block_dup,
+ long long file_size, long long bytes, unsigned int *block_list,
+ long long start, struct dir_ent *dir_ent,
+ struct file_buffer *file_buffer, int blocks, long long sparse,
+ int bl_hash)
+{
+ struct file_info *dupl_ptr, *file;
+ struct file_info *block_dupl = NULL, *frag_dupl = NULL;
+ struct dup_info *dup;
+ int frag_bytes = file_buffer ? file_buffer->size : 0;
+ unsigned short fragment_checksum = file_buffer ?
+ file_buffer->checksum : 0;
+ unsigned short checksum = 0;
+ char checksum_flag = FALSE;
+ struct fragment *fragment;
+
+ /* Look for a possible duplicate set of blocks */
+ for(dupl_ptr = dupl_block[bl_hash]; dupl_ptr;
+ dupl_ptr = dupl_ptr->block_next) {
+ if(bytes == dupl_ptr->bytes && blocks == dupl_ptr->blocks) {
+ long long target_start, dup_start = dupl_ptr->start;
+ int block;
+
+ /*
+ * Block list has same uncompressed size and same
+ * compressed size. Now check if each block compressed
+ * to the same size
+ */
+ if(memcmp(block_list, dupl_ptr->block_list, blocks *
+ sizeof(unsigned int)) != 0)
+ continue;
+
+ /* Now get the checksums and compare */
+ if(checksum_flag == FALSE) {
+ checksum = get_checksum_disk(start, bytes, block_list);
+ checksum_flag = TRUE;
+ }
+
+ if(!dupl_ptr->have_checksum) {
+ dupl_ptr->checksum =
+ get_checksum_disk(dupl_ptr->start,
+ dupl_ptr->bytes, dupl_ptr->block_list);
+ dupl_ptr->have_checksum = TRUE;
+ }
+
+ if(checksum != dupl_ptr->checksum)
+ continue;
+
+ /*
+ * Checksums match, so now we need to do a byte by byte
+ * comparison
+ */
+ target_start = start;
+ for(block = 0; block < blocks; block ++) {
+ int size = SQUASHFS_COMPRESSED_SIZE_BLOCK(block_list[block]);
+ struct file_buffer *target_buffer = NULL;
+ struct file_buffer *dup_buffer = NULL;
+ char *target_data, *dup_data;
+ int res;
+
+ /* Sparse blocks obviously match */
+ if(size == 0)
+ continue;
+
+ /*
+ * Get the block for our file. This will be in
+ * the cache unless the cache wasn't large
+ * enough to hold the entire file, in which case
+ * the block will have been written to disk.
+ */
+ target_buffer = cache_lookup(bwriter_buffer,
+ target_start);
+ if(target_buffer)
+ target_data = target_buffer->data;
+ else {
+ target_data = read_from_disk(target_start, size);
+ if(target_data == NULL) {
+ ERROR("Failed to read data from"
+ " output filesystem\n");
+ BAD_ERROR("Output filesystem"
+ " corrupted?\n");
+ }
+ }
+
+ /*
+ * Get the block for the other file. This may
+ * still be in the cache (if it was written
+ * recently), otherwise it will have to be read
+ * back from disk
+ */
+ dup_buffer = cache_lookup(bwriter_buffer, dup_start);
+ if(dup_buffer)
+ dup_data = dup_buffer->data;
+ else {
+ dup_data = read_from_disk2(dup_start, size);
+ if(dup_data == NULL) {
+ ERROR("Failed to read data from"
+ " output filesystem\n");
+ BAD_ERROR("Output filesystem"
+ " corrupted?\n");
+ }
+ }
+
+ res = memcmp(target_data, dup_data, size);
+ cache_block_put(target_buffer);
+ cache_block_put(dup_buffer);
+ if(res != 0)
+ break;
+ target_start += size;
+ dup_start += size;
+ }
+
+ if(block != blocks)
+ continue;
+
+ /*
+ * Yes, the block list matches. We can use this, rather
+ * than writing an identical block list.
+ * If both it and us doesn't have a tail-end fragment
+ * then we're finished. Return the duplicate.
+ *
+ * We have to deal with the special case where the
+ * last block is a sparse block. This means the
+ * file will have matched, but, it may be a different
+ * file length (because a tail-end sparse block may be
+ * anything from 1 byte to block_size - 1 in size, but
+ * stored as zero). We can still use the block list in
+ * this case, but, we must return a new entry with the
+ * correct file size
+ */
+ if(!frag_bytes && !dupl_ptr->fragment->size) {
+ *dupf = *block_dup = TRUE;
+ if(file_size == dupl_ptr->file_size)
+ return dupl_ptr;
+ else
+ return create_non_dup(file_size, bytes,
+ blocks, sparse,
+ dupl_ptr->block_list,
+ dupl_ptr->start,
+ dupl_ptr->fragment, checksum, 0,
+ checksum_flag, FALSE);
+ }
+
+ /*
+ * We've got a tail-end fragment, and this file most
+ * likely has a matching tail-end fragment (i.e. it is
+ * a completely duplicate file). So save time and have
+ * a look now.
+ */
+ if(frag_bytes == dupl_ptr->fragment->size &&
+ fragment_checksum ==
+ get_fragment_checksum(dupl_ptr)) {
+ /*
+ * Checksums match, so now we need to do a byte
+ * by byte comparison
+ * */
+ struct file_buffer *frag_buffer = get_fragment(dupl_ptr->fragment);
+ int res = memcmp(file_buffer->data,
+ frag_buffer->data +
+ dupl_ptr->fragment->offset, frag_bytes);
+
+ cache_block_put(frag_buffer);
+
+ if(res == 0) {
+ /*
+ * Yes, the fragment matches. We're now
+ * finished. Return the duplicate
+ */
+ *dupf = *block_dup = TRUE;
+ return dupl_ptr;
+ }
+ }
+
+ /*
+ * No, the fragment didn't match. Remember the file
+ * with the matching blocks, and look for a matching
+ * fragment in the fragment list
+ */
+ block_dupl = dupl_ptr;
+ break;
+ }
+ }
+
+ /* Look for a possible duplicate fragment */
+ if(frag_bytes) {
+ for(dupl_ptr = dupl_frag[frag_bytes]; dupl_ptr;
+ dupl_ptr = dupl_ptr->frag_next) {
+ if(frag_bytes == dupl_ptr->fragment->size &&
+ fragment_checksum ==
+ get_fragment_checksum(dupl_ptr)) {
+ /*
+ * Checksums match, so now we need to do a byte
+ * by byte comparison
+ */
+ struct file_buffer *frag_buffer = get_fragment(dupl_ptr->fragment);
+ int res = memcmp(file_buffer->data,
+ frag_buffer->data +
+ dupl_ptr->fragment->offset, frag_bytes);
+
+ cache_block_put(frag_buffer);
+
+ if(res == 0) {
+ /*
+ * Yes, the fragment matches. This file
+ * may have a matching block list and
+ * fragment, in which case we're
+ * finished.
+ */
+ if(block_dupl && block_dupl->start == dupl_ptr->start) {
+ *dupf = *block_dup = TRUE;
+ return dupl_ptr;
+ }
+
+ /*
+ * Block list doesn't match. We can
+ * construct a hybrid from these two
+ * partially matching files
+ */
+ frag_dupl = dupl_ptr;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * If we've got here, then we've either matched on nothing, or got a
+ * partial match. Matched on nothing is straightforward
+ */
+ if(!block_dupl && !frag_dupl) {
+ *dupf = *block_dup = FALSE;
+ fragment = get_and_fill_fragment(file_buffer, dir_ent, TRUE);
+
+ return add_non_dup(file_size, bytes, blocks, sparse, block_list,
+ start, fragment, checksum, fragment_checksum,
+ checksum_flag, file_buffer != NULL, FALSE,
+ FALSE, bl_hash);
+ }
+
+ /*
+ * At this point, we may have
+ * 1. A partially matching single file. For example the file may
+ * contain the block list we want, but, it has the wrong tail-end,
+ * or vice-versa,
+ * 2. A partially matching single file for another reason. For example
+ * it has the block list we want, and a tail-end, whereas we don't
+ * have a tail-end. Note the vice-versa situation doesn't appear
+ * here (it is handled in frag_duplicate).
+ * 3. We have two partially matching files. One has the block list we
+ * want, and the other has the tail-end we want.
+ *
+ * Strictly speaking, a file which is constructed from one or two
+ * partial matches isn't a duplicate (of any single file), and it will
+ * be confusing to list it as such (using the -info option). But a
+ * second and thereafter appearance of this combination *is* a
+ * duplicate of another file. Some of this second and thereafter
+ * appearance is already handled above
+ */
+
+ if(block_dupl && (!frag_bytes || frag_dupl)) {
+ /*
+ * This file won't be added to any hash list, because it is a
+ * complete duplicate, and it doesn't need extra data to be
+ * stored, e.g. part 2 & 3 above. So keep track of it by adding
+ * it to a linked list. Obviously check if it's already there
+ * first.
+ */
+ for(dup = block_dupl->dup; dup; dup = dup->next)
+ if((!frag_bytes && dup->frag == NULL) ||
+ (frag_bytes && dup->frag == frag_dupl))
+ break;
+
+ if(dup) {
+ /* Found a matching file. Return the duplicate */
+ *dupf = *block_dup = TRUE;
+ return dup->file;
+ }
+ }
+
+ if(frag_dupl)
+ fragment = frag_dupl->fragment;
+ else
+ fragment = get_and_fill_fragment(file_buffer, dir_ent, TRUE);
+
+ if(block_dupl) {
+ start = block_dupl->start;
+ block_list = block_dupl->block_list;
+ }
+
+ *dupf = FALSE;
+ *block_dup = block_dupl != NULL;
+
+ file = create_non_dup(file_size, bytes, blocks, sparse, block_list,
+ start, fragment, checksum, fragment_checksum, checksum_flag,
+ file_buffer != NULL);
+
+ if(!block_dupl || (frag_bytes && !frag_dupl)) {
+ /*
+ * Partial duplicate, had to store some extra data for this
+ * file, either a block list, or a fragment
+ */
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+ pthread_mutex_lock(&dup_mutex);
+
+ if(!block_dupl) {
+ file->block_next = dupl_block[bl_hash];
+ dupl_block[bl_hash] = file;
+ }
+
+ if(frag_bytes && !frag_dupl) {
+ file->frag_next = dupl_frag[frag_bytes];
+ dupl_frag[frag_bytes] = file;
+ }
+
+ dup_files ++;
+
+ pthread_cleanup_pop(1);
+ } else {
+ dup = malloc(sizeof(struct dup_info));
+ if(dup == NULL)
+ MEM_ERROR();
+
+ dup->frag = frag_dupl;
+ dup->file = file;
+ dup->next = block_dupl->dup;
+ block_dupl->dup = dup;
+ }
+
+ return file;
+}
+
+
+static void *writer(void *arg)
+{
+ while(1) {
+ struct file_buffer *file_buffer = queue_get(to_writer);
+ off_t off;
+
+ if(file_buffer == NULL) {
+ queue_put(from_writer, NULL);
+ continue;
+ }
+
+ off = file_buffer->block;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &pos_mutex);
+ pthread_mutex_lock(&pos_mutex);
+
+ if(lseek(fd, start_offset + off, SEEK_SET) == -1) {
+ ERROR("writer: Lseek on destination failed because "
+ "%s, offset=0x%llx\n", strerror(errno), start_offset + off);
+ BAD_ERROR("Probably out of space on output "
+ "%s\n", block_device ? "block device" :
+ "filesystem");
+ }
+
+ if(write_bytes(fd, file_buffer->data,
+ file_buffer->size) == -1)
+ BAD_ERROR("Failed to write to output %s\n",
+ block_device ? "block device" : "filesystem");
+
+ pthread_cleanup_pop(1);
+
+ cache_block_put(file_buffer);
+ }
+}
+
+
+static int all_zero(struct file_buffer *file_buffer)
+{
+ int i;
+ long entries = file_buffer->size / sizeof(long);
+ long *p = (long *) file_buffer->data;
+
+ for(i = 0; i < entries && p[i] == 0; i++);
+
+ if(i == entries) {
+ for(i = file_buffer->size & ~(sizeof(long) - 1);
+ i < file_buffer->size && file_buffer->data[i] == 0;
+ i++);
+
+ return i == file_buffer->size;
+ }
+
+ return 0;
+}
+
+
+static void *deflator(void *arg)
+{
+ struct file_buffer *write_buffer = cache_get_nohash(bwriter_buffer);
+ void *stream = NULL;
+ int res;
+
+ res = compressor_init(comp, &stream, block_size, 1);
+ if(res)
+ BAD_ERROR("deflator:: compressor_init failed\n");
+
+ while(1) {
+ struct file_buffer *file_buffer = queue_get(to_deflate);
+
+ if(sparse_files && all_zero(file_buffer)) {
+ file_buffer->c_byte = 0;
+ seq_queue_put(to_main, file_buffer);
+ } else {
+ write_buffer->c_byte = mangle2(stream,
+ write_buffer->data, file_buffer->data,
+ file_buffer->size, block_size,
+ file_buffer->noD, 1);
+ write_buffer->sequence = file_buffer->sequence;
+ write_buffer->file_size = file_buffer->file_size;
+ write_buffer->block = file_buffer->block;
+ write_buffer->size = SQUASHFS_COMPRESSED_SIZE_BLOCK
+ (write_buffer->c_byte);
+ write_buffer->fragment = FALSE;
+ write_buffer->error = FALSE;
+ cache_block_put(file_buffer);
+ seq_queue_put(to_main, write_buffer);
+ write_buffer = cache_get_nohash(bwriter_buffer);
+ }
+ }
+}
+
+
+static void *frag_deflator(void *arg)
+{
+ void *stream = NULL;
+ int res;
+
+ res = compressor_init(comp, &stream, block_size, 1);
+ if(res)
+ BAD_ERROR("frag_deflator:: compressor_init failed\n");
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+
+ while(1) {
+ int c_byte, compressed_size;
+ struct file_buffer *file_buffer = queue_get(to_frag);
+ struct file_buffer *write_buffer =
+ cache_get(fwriter_buffer, file_buffer->block);
+
+ c_byte = mangle2(stream, write_buffer->data, file_buffer->data,
+ file_buffer->size, block_size, noF, 1);
+ compressed_size = SQUASHFS_COMPRESSED_SIZE_BLOCK(c_byte);
+ write_buffer->size = compressed_size;
+ pthread_mutex_lock(&fragment_mutex);
+ if(fragments_locked == FALSE) {
+ fragment_table[file_buffer->block].size = c_byte;
+ fragment_table[file_buffer->block].start_block = bytes;
+ write_buffer->block = bytes;
+ bytes += compressed_size;
+ fragments_outstanding --;
+ queue_put(to_writer, write_buffer);
+ log_fragment(file_buffer->block, fragment_table[file_buffer->block].start_block);
+ pthread_mutex_unlock(&fragment_mutex);
+ TRACE("Writing fragment %lld, uncompressed size %d, "
+ "compressed size %d\n", file_buffer->block,
+ file_buffer->size, compressed_size);
+ } else {
+ add_pending_fragment(write_buffer, c_byte,
+ file_buffer->block);
+ pthread_mutex_unlock(&fragment_mutex);
+ }
+ cache_block_put(file_buffer);
+ }
+
+ pthread_cleanup_pop(0);
+ return NULL;
+
+}
+
+
+static void *frag_order_deflator(void *arg)
+{
+ void *stream = NULL;
+ int res;
+
+ res = compressor_init(comp, &stream, block_size, 1);
+ if(res)
+ BAD_ERROR("frag_deflator:: compressor_init failed\n");
+
+ while(1) {
+ int c_byte;
+ struct file_buffer *file_buffer = queue_get(to_frag);
+ struct file_buffer *write_buffer =
+ cache_get(fwriter_buffer, file_buffer->block);
+
+ c_byte = mangle2(stream, write_buffer->data, file_buffer->data,
+ file_buffer->size, block_size, noF, 1);
+ write_buffer->block = file_buffer->block;
+ write_buffer->sequence = file_buffer->sequence;
+ write_buffer->size = SQUASHFS_COMPRESSED_SIZE_BLOCK(c_byte);
+ write_buffer->fragment = FALSE;
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+ pthread_mutex_lock(&fragment_mutex);
+ fragment_table[file_buffer->block].size = c_byte;
+ pthread_cleanup_pop(1);
+ seq_queue_put(to_order, write_buffer);
+ TRACE("Writing fragment %lld, uncompressed size %d, "
+ "compressed size %d\n", file_buffer->block,
+ file_buffer->size, SQUASHFS_COMPRESSED_SIZE_BLOCK(c_byte));
+ cache_block_put(file_buffer);
+ }
+}
+
+
+static void *frag_orderer(void *arg)
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+
+ while(1) {
+ struct file_buffer *write_buffer = seq_queue_get(to_order);
+ int block = write_buffer->block;
+
+ pthread_mutex_lock(&fragment_mutex);
+ fragment_table[block].start_block = bytes;
+ write_buffer->block = bytes;
+ bytes += SQUASHFS_COMPRESSED_SIZE_BLOCK(write_buffer->size);
+ fragments_outstanding --;
+ log_fragment(block, write_buffer->block);
+ queue_put(to_writer, write_buffer);
+ pthread_cond_signal(&fragment_waiting);
+ pthread_mutex_unlock(&fragment_mutex);
+ }
+
+ pthread_cleanup_pop(0);
+ return NULL;
+}
+
+
+static struct file_buffer *get_file_buffer()
+{
+ struct file_buffer *file_buffer = seq_queue_get(to_main);
+
+ return file_buffer;
+}
+
+
+static struct file_info *write_file_empty(struct dir_ent *dir_ent,
+ struct file_buffer *file_buffer, int *duplicate_file)
+{
+ file_count ++;
+ *duplicate_file = FALSE;
+ cache_block_put(file_buffer);
+ return create_non_dup(0, 0, 0, 0, NULL, 0, &empty_fragment, 0, 0,
+ FALSE, FALSE);
+}
+
+
+static struct file_info *write_file_frag(struct dir_ent *dir_ent,
+ struct file_buffer *file_buffer, int *duplicate_file)
+{
+ int size = file_buffer->file_size;
+ struct fragment *fragment;
+ unsigned short checksum = file_buffer->checksum;
+ struct file_info *file;
+
+ file = frag_duplicate(file_buffer, duplicate_file);
+ if(!file) {
+ fragment = get_and_fill_fragment(file_buffer, dir_ent, FALSE);
+
+ if(duplicate_checking)
+ file = add_non_dup(size, 0, 0, 0, NULL, 0, fragment, 0,
+ checksum, TRUE, TRUE, FALSE, FALSE, 0);
+ else
+ file = create_non_dup(size, 0, 0, 0, NULL, 0, fragment,
+ 0, checksum, TRUE, TRUE);
+ }
+
+ cache_block_put(file_buffer);
+
+ total_bytes += size;
+ file_count ++;
+
+ inc_progress_bar();
+
+ return file;
+}
+
+
+static void log_file(struct dir_ent *dir_ent, long long start)
+{
+ if(logging && start)
+ fprintf(log_fd, "%s, %lld\n", pathname(dir_ent), start);
+}
+
+
+static struct file_info *write_file_process(int *status, struct dir_ent *dir_ent,
+ struct file_buffer *read_buffer, int *duplicate_file)
+{
+ long long read_size, file_bytes, start;
+ struct fragment *fragment;
+ unsigned int *block_list = NULL;
+ int block = 0;
+ long long sparse = 0;
+ struct file_buffer *fragment_buffer = NULL;
+ struct file_info *file;
+
+ *duplicate_file = FALSE;
+
+ if(reproducible)
+ ensure_fragments_flushed();
+ else
+ lock_fragments();
+
+ file_bytes = 0;
+ start = bytes;
+ while (1) {
+ read_size = read_buffer->file_size;
+ if(read_buffer->fragment) {
+ fragment_buffer = read_buffer;
+ if(block == 0)
+ start=0;
+ } else {
+ block_list = realloc(block_list, (block + 1) *
+ sizeof(unsigned int));
+ if(block_list == NULL)
+ MEM_ERROR();
+ block_list[block ++] = read_buffer->c_byte;
+ if(read_buffer->c_byte) {
+ read_buffer->block = bytes;
+ bytes += read_buffer->size;
+ cache_hash(read_buffer, read_buffer->block);
+ file_bytes += read_buffer->size;
+ queue_put(to_writer, read_buffer);
+ } else {
+ sparse += read_buffer->size;
+ cache_block_put(read_buffer);
+ }
+ }
+ inc_progress_bar();
+
+ if(read_size != -1)
+ break;
+
+ read_buffer = get_file_buffer();
+ if(read_buffer->error)
+ goto read_err;
+ }
+
+ if(!reproducible)
+ unlock_fragments();
+
+ fragment = get_and_fill_fragment(fragment_buffer, dir_ent, block != 0);
+
+ if(duplicate_checking) {
+ int bl_hash = block ? block_hash(block_list[0], block) : 0;
+
+ file = add_non_dup(read_size, file_bytes, block, sparse,
+ block_list, start, fragment, 0, fragment_buffer ?
+ fragment_buffer->checksum : 0, FALSE, TRUE, FALSE,
+ FALSE, bl_hash);
+ } else
+ file = create_non_dup(read_size, file_bytes, block, sparse,
+ block_list, start, fragment, 0, fragment_buffer ?
+ fragment_buffer->checksum : 0, FALSE, TRUE);
+
+ cache_block_put(fragment_buffer);
+ file_count ++;
+ total_bytes += read_size;
+
+ log_file(dir_ent, start);
+
+ *status = 0;
+ return file;
+
+read_err:
+ dec_progress_bar(block);
+ *status = read_buffer->error;
+ bytes = start;
+ if(!block_device) {
+ int res;
+
+ queue_put(to_writer, NULL);
+ if(queue_get(from_writer) != 0)
+ EXIT_MKSQUASHFS();
+ res = ftruncate(fd, bytes);
+ if(res != 0)
+ BAD_ERROR("Failed to truncate dest file because %s\n",
+ strerror(errno));
+ }
+ if(!reproducible)
+ unlock_fragments();
+ free(block_list);
+ cache_block_put(read_buffer);
+ return NULL;
+}
+
+
+static struct file_info *write_file_blocks_dup(int *status, struct dir_ent *dir_ent,
+ struct file_buffer *read_buffer, int *duplicate_file, int bl_hash)
+{
+ int block, thresh;
+ long long read_size = read_buffer->file_size;
+ long long file_bytes, start;
+ int blocks = (read_size + block_size - 1) >> block_log;
+ unsigned int *block_list;
+ struct file_buffer **buffer_list;
+ long long sparse = 0;
+ struct file_buffer *fragment_buffer = NULL;
+ struct file_info *file;
+ int block_dup;
+
+ block_list = malloc(blocks * sizeof(unsigned int));
+ if(block_list == NULL)
+ MEM_ERROR();
+
+ buffer_list = malloc(blocks * sizeof(struct file_buffer *));
+ if(buffer_list == NULL)
+ MEM_ERROR();
+
+ if(reproducible)
+ ensure_fragments_flushed();
+ else
+ lock_fragments();
+
+ file_bytes = 0;
+ start = bytes;
+ thresh = blocks > bwriter_size ? blocks - bwriter_size : 0;
+
+ for(block = 0; block < blocks;) {
+ if(read_buffer->fragment) {
+ block_list[block] = 0;
+ buffer_list[block] = NULL;
+ fragment_buffer = read_buffer;
+ blocks = read_size >> block_log;
+ } else {
+ block_list[block] = read_buffer->c_byte;
+
+ if(read_buffer->c_byte) {
+ read_buffer->block = bytes;
+ bytes += read_buffer->size;
+ file_bytes += read_buffer->size;
+ cache_hash(read_buffer, read_buffer->block);
+ if(block < thresh) {
+ buffer_list[block] = NULL;
+ queue_put(to_writer, read_buffer);
+ } else
+ buffer_list[block] = read_buffer;
+ } else {
+ buffer_list[block] = NULL;
+ sparse += read_buffer->size;
+ cache_block_put(read_buffer);
+ }
+ }
+ inc_progress_bar();
+
+ if(++block < blocks) {
+ read_buffer = get_file_buffer();
+ if(read_buffer->error)
+ goto read_err;
+ }
+ }
+
+ /*
+ * sparse count is needed to ensure squashfs correctly reports a
+ * a smaller block count on stat calls to sparse files. This is
+ * to ensure intelligent applications like cp correctly handle the
+ * file as a sparse file. If the file in the original filesystem isn't
+ * stored as a sparse file then still store it sparsely in squashfs, but
+ * report it as non-sparse on stat calls to preserve semantics
+ */
+ if(sparse && (dir_ent->inode->buf.st_blocks << 9) >= read_size)
+ sparse = 0;
+
+ file = duplicate(duplicate_file, &block_dup, read_size, file_bytes, block_list,
+ start, dir_ent, fragment_buffer, blocks, sparse, bl_hash);
+
+ if(block_dup == FALSE) {
+ for(block = thresh; block < blocks; block ++)
+ if(buffer_list[block])
+ queue_put(to_writer, buffer_list[block]);
+ } else {
+ for(block = thresh; block < blocks; block ++)
+ cache_block_put(buffer_list[block]);
+ bytes = start;
+ if(thresh && !block_device) {
+ int res;
+
+ queue_put(to_writer, NULL);
+ if(queue_get(from_writer) != 0)
+ EXIT_MKSQUASHFS();
+ res = ftruncate(fd, bytes);
+ if(res != 0)
+ BAD_ERROR("Failed to truncate dest file because"
+ " %s\n", strerror(errno));
+ }
+ }
+
+ if(!reproducible)
+ unlock_fragments();
+ cache_block_put(fragment_buffer);
+ free(buffer_list);
+ file_count ++;
+ total_bytes += read_size;
+
+ if(block_dup == TRUE)
+ free(block_list);
+ else
+ log_file(dir_ent, file->start);
+
+ *status = 0;
+ return file;
+
+read_err:
+ dec_progress_bar(block);
+ *status = read_buffer->error;
+ bytes = start;
+ if(thresh && !block_device) {
+ int res;
+
+ queue_put(to_writer, NULL);
+ if(queue_get(from_writer) != 0)
+ EXIT_MKSQUASHFS();
+ res = ftruncate(fd, bytes);
+ if(res != 0)
+ BAD_ERROR("Failed to truncate dest file because %s\n",
+ strerror(errno));
+ }
+ if(!reproducible)
+ unlock_fragments();
+ for(blocks = thresh; blocks < block; blocks ++)
+ cache_block_put(buffer_list[blocks]);
+ free(buffer_list);
+ free(block_list);
+ cache_block_put(read_buffer);
+ return NULL;
+}
+
+
+static struct file_info *write_file_blocks(int *status, struct dir_ent *dir_ent,
+ struct file_buffer *read_buffer, int *dup)
+{
+ long long read_size = read_buffer->file_size;
+ long long file_bytes, start;
+ struct fragment *fragment;
+ unsigned int *block_list;
+ int block;
+ int blocks = (read_size + block_size - 1) >> block_log;
+ long long sparse = 0;
+ struct file_buffer *fragment_buffer = NULL;
+ struct file_info *file;
+ int bl_hash = 0;
+
+ if(pre_duplicate(read_size, dir_ent->inode, read_buffer, &bl_hash))
+ return write_file_blocks_dup(status, dir_ent, read_buffer, dup, bl_hash);
+
+ *dup = FALSE;
+
+ block_list = malloc(blocks * sizeof(unsigned int));
+ if(block_list == NULL)
+ MEM_ERROR();
+
+ if(reproducible)
+ ensure_fragments_flushed();
+ else
+ lock_fragments();
+
+ file_bytes = 0;
+ start = bytes;
+ for(block = 0; block < blocks;) {
+ if(read_buffer->fragment) {
+ block_list[block] = 0;
+ fragment_buffer = read_buffer;
+ blocks = read_size >> block_log;
+ } else {
+ block_list[block] = read_buffer->c_byte;
+ if(read_buffer->c_byte) {
+ read_buffer->block = bytes;
+ bytes += read_buffer->size;
+ cache_hash(read_buffer, read_buffer->block);
+ file_bytes += read_buffer->size;
+ queue_put(to_writer, read_buffer);
+ } else {
+ sparse += read_buffer->size;
+ cache_block_put(read_buffer);
+ }
+ }
+ inc_progress_bar();
+
+ if(++block < blocks) {
+ read_buffer = get_file_buffer();
+ if(read_buffer->error)
+ goto read_err;
+ }
+ }
+
+ /*
+ * sparse count is needed to ensure squashfs correctly reports a
+ * a smaller block count on stat calls to sparse files. This is
+ * to ensure intelligent applications like cp correctly handle the
+ * file as a sparse file. If the file in the original filesystem isn't
+ * stored as a sparse file then still store it sparsely in squashfs, but
+ * report it as non-sparse on stat calls to preserve semantics
+ */
+ if(sparse && (dir_ent->inode->buf.st_blocks << 9) >= read_size)
+ sparse = 0;
+
+ if(!reproducible)
+ unlock_fragments();
+
+ fragment = get_and_fill_fragment(fragment_buffer, dir_ent, TRUE);
+
+ if(duplicate_checking)
+ file = add_non_dup(read_size, file_bytes, blocks, sparse,
+ block_list, start, fragment, 0, fragment_buffer ?
+ fragment_buffer->checksum : 0, FALSE, TRUE, FALSE,
+ FALSE, bl_hash);
+ else
+ file = create_non_dup(read_size, file_bytes, blocks, sparse,
+ block_list, start, fragment, 0, fragment_buffer ?
+ fragment_buffer->checksum : 0, FALSE, TRUE);
+
+ cache_block_put(fragment_buffer);
+ file_count ++;
+ total_bytes += read_size;
+
+ log_file(dir_ent, start);
+
+ *status = 0;
+ return file;
+
+read_err:
+ dec_progress_bar(block);
+ *status = read_buffer->error;
+ bytes = start;
+ if(!block_device) {
+ int res;
+
+ queue_put(to_writer, NULL);
+ if(queue_get(from_writer) != 0)
+ EXIT_MKSQUASHFS();
+ res = ftruncate(fd, bytes);
+ if(res != 0)
+ BAD_ERROR("Failed to truncate dest file because %s\n",
+ strerror(errno));
+ }
+ if(!reproducible)
+ unlock_fragments();
+ free(block_list);
+ cache_block_put(read_buffer);
+ return NULL;
+}
+
+
+struct file_info *write_file(struct dir_ent *dir, int *dup)
+{
+ int status;
+ struct file_buffer *read_buffer;
+ struct file_info *file;
+
+again:
+ read_buffer = get_file_buffer();
+ status = read_buffer->error;
+
+ if(status)
+ cache_block_put(read_buffer);
+ else if(read_buffer->file_size == -1)
+ file = write_file_process(&status, dir, read_buffer, dup);
+ else if(read_buffer->file_size == 0)
+ file = write_file_empty(dir, read_buffer, dup);
+ else if(read_buffer->fragment && read_buffer->c_byte)
+ file = write_file_frag(dir, read_buffer, dup);
+ else
+ file = write_file_blocks(&status, dir, read_buffer, dup);
+
+ if(status == 2) {
+ ERROR("File %s changed size while reading filesystem, "
+ "attempting to re-read\n", pathname(dir));
+ goto again;
+ } else if(status == 1) {
+ ERROR_START("Failed to read file %s", pathname(dir));
+ ERROR_EXIT(", creating empty file\n");
+ file = write_file_empty(dir, NULL, dup);
+ } else if(status)
+ BAD_ERROR("Unexpected status value in write_file()");
+
+ return file;
+}
+
+
+#define BUFF_SIZE 512
+char *name;
+static char *basename_r();
+
+static char *getbase(char *pathname)
+{
+ static char *b_buffer = NULL;
+ static int b_size = BUFF_SIZE;
+ char *result;
+
+ if(b_buffer == NULL) {
+ b_buffer = malloc(b_size);
+ if(b_buffer == NULL)
+ MEM_ERROR();
+ }
+
+ while(1) {
+ if(*pathname != '/') {
+ result = getcwd(b_buffer, b_size);
+ if(result == NULL && errno != ERANGE)
+ BAD_ERROR("Getcwd failed in getbase\n");
+
+ /* enough room for pathname + "/" + '\0' terminator? */
+ if(result && strlen(pathname) + 2 <=
+ b_size - strlen(b_buffer)) {
+ strcat(strcat(b_buffer, "/"), pathname);
+ break;
+ }
+ } else if(strlen(pathname) < b_size) {
+ strcpy(b_buffer, pathname);
+ break;
+ }
+
+ /* Buffer not large enough, realloc and try again */
+ b_buffer = realloc(b_buffer, b_size += BUFF_SIZE);
+ if(b_buffer == NULL)
+ MEM_ERROR();
+ }
+
+ name = b_buffer;
+ if(((result = basename_r()) == NULL) || (strcmp(result, "..") == 0))
+ return NULL;
+ else
+ return result;
+}
+
+
+static char *basename_r()
+{
+ char *s;
+ char *p;
+ int n = 1;
+
+ for(;;) {
+ s = name;
+ if(*name == '\0')
+ return NULL;
+ if(*name != '/') {
+ while(*name != '\0' && *name != '/') name++;
+ n = name - s;
+ }
+ while(*name == '/') name++;
+ if(strncmp(s, ".", n) == 0)
+ continue;
+ if((*name == '\0') || (strncmp(s, "..", n) == 0) ||
+ ((p = basename_r()) == NULL)) {
+ s[n] = '\0';
+ return s;
+ }
+ if(strcmp(p, "..") == 0)
+ continue;
+ return p;
+ }
+}
+
+
+static inline void dec_nlink_inode(struct dir_ent *dir_ent)
+{
+ if(dir_ent->inode == NULL || dir_ent->inode->root_entry)
+ return;
+
+ if(dir_ent->inode->nlink == 1) {
+ /* Delete this inode, as the last or only reference
+ * to it is going away */
+ struct stat *buf = &dir_ent->inode->buf;
+ int ino_hash = INODE_HASH(buf->st_dev, buf->st_ino);
+ struct inode_info *inode = inode_info[ino_hash];
+ struct inode_info *prev = NULL;
+
+ while(inode && inode != dir_ent->inode) {
+ prev = inode;
+ inode = inode->next;
+ }
+
+ if(inode) {
+ if(prev)
+ prev->next = inode->next;
+ else
+ inode_info[ino_hash] = inode->next;
+ }
+
+ /* Decrement the progress bar */
+ if((buf->st_mode & S_IFMT) == S_IFREG)
+ progress_bar_size(-((buf->st_size + block_size - 1)
+ >> block_log));
+
+ free(dir_ent->inode);
+ dir_ent->inode = NULL;
+ } else
+ dir_ent->inode->nlink --;
+}
+
+
+static struct inode_info *lookup_inode3(struct stat *buf, struct pseudo_dev *pseudo,
+ char *symlink, int bytes)
+{
+ int ino_hash = INODE_HASH(buf->st_dev, buf->st_ino);
+ struct inode_info *inode;
+
+ /*
+ * Look-up inode in hash table, if it already exists we have a
+ * hardlink, so increment the nlink count and return it.
+ * Don't do the look-up for directories because Unix/Linux doesn't
+ * allow hard-links to directories.
+ */
+ if ((buf->st_mode & S_IFMT) != S_IFDIR && !no_hardlinks) {
+ for(inode = inode_info[ino_hash]; inode; inode = inode->next) {
+ if(memcmp(buf, &inode->buf, sizeof(struct stat)) == 0) {
+ inode->nlink ++;
+ return inode;
+ }
+ }
+ }
+
+ if((buf->st_mode & S_IFMT) == S_IFREG)
+ progress_bar_size((buf->st_size + block_size - 1)
+ >> block_log);
+
+ inode = malloc(sizeof(struct inode_info) + bytes);
+ if(inode == NULL)
+ MEM_ERROR();
+
+ if(bytes)
+ memcpy(&inode->symlink, symlink, bytes);
+ memcpy(&inode->buf, buf, sizeof(struct stat));
+ inode->read = FALSE;
+ inode->root_entry = FALSE;
+ inode->pseudo = pseudo;
+ inode->inode = SQUASHFS_INVALID_BLK;
+ inode->nlink = 1;
+ inode->inode_number = 0;
+ inode->dummy_root_dir = FALSE;
+ inode->xattr = NULL;
+ inode->tarfile = FALSE;
+
+ /*
+ * Copy filesystem wide defaults into inode, these filesystem
+ * wide defaults may be altered on an individual inode basis by
+ * user specified actions
+ *
+ */
+ inode->no_fragments = no_fragments;
+ inode->always_use_fragments = always_use_fragments;
+ inode->noD = noD;
+ inode->noF = noF;
+
+ inode->next = inode_info[ino_hash];
+ inode_info[ino_hash] = inode;
+
+ return inode;
+}
+
+
+static struct inode_info *lookup_inode2(struct stat *buf, struct pseudo_dev *pseudo)
+{
+ return lookup_inode3(buf, pseudo, NULL, 0);
+}
+
+
+struct inode_info *lookup_inode(struct stat *buf)
+{
+ return lookup_inode2(buf, NULL);
+}
+
+
+static inline void alloc_inode_no(struct inode_info *inode, unsigned int use_this)
+{
+ if (inode->inode_number == 0) {
+ inode->inode_number = use_this ? : inode_no ++;
+ }
+}
+
+
+struct dir_info *create_dir(char *pathname, char *subpath, unsigned int depth)
+{
+ struct dir_info *dir;
+
+ dir = malloc(sizeof(struct dir_info));
+ if(dir == NULL)
+ MEM_ERROR();
+
+ dir->pathname = strdup(pathname);
+ dir->subpath = strdup(subpath);
+ dir->count = 0;
+ dir->directory_count = 0;
+ dir->dir_is_ldir = TRUE;
+ dir->list = NULL;
+ dir->depth = depth;
+ dir->excluded = 0;
+
+ return dir;
+}
+
+
+struct dir_ent *lookup_name(struct dir_info *dir, char *name)
+{
+ struct dir_ent *dir_ent = dir->list;
+
+ for(; dir_ent && strcmp(dir_ent->name, name) != 0;
+ dir_ent = dir_ent->next);
+
+ return dir_ent;
+}
+
+
+struct dir_ent *create_dir_entry(char *name, char *source_name,
+ char *nonstandard_pathname, struct dir_info *dir)
+{
+ struct dir_ent *dir_ent = malloc(sizeof(struct dir_ent));
+ if(dir_ent == NULL)
+ MEM_ERROR();
+
+ dir_ent->name = name;
+ dir_ent->source_name = source_name;
+ dir_ent->nonstandard_pathname = nonstandard_pathname;
+ dir_ent->our_dir = dir;
+ dir_ent->inode = NULL;
+ dir_ent->next = NULL;
+
+ return dir_ent;
+}
+
+
+void add_dir_entry(struct dir_ent *dir_ent, struct dir_info *sub_dir,
+ struct inode_info *inode_info)
+{
+ struct dir_info *dir = dir_ent->our_dir;
+
+ if(sub_dir)
+ sub_dir->dir_ent = dir_ent;
+ dir_ent->inode = inode_info;
+ dir_ent->dir = sub_dir;
+
+ dir_ent->next = dir->list;
+ dir->list = dir_ent;
+ dir->count++;
+}
+
+
+static inline void add_dir_entry2(char *name, char *source_name,
+ char *nonstandard_pathname, struct dir_info *sub_dir,
+ struct inode_info *inode_info, struct dir_info *dir)
+{
+ struct dir_ent *dir_ent = create_dir_entry(name, source_name,
+ nonstandard_pathname, dir);
+
+
+ add_dir_entry(dir_ent, sub_dir, inode_info);
+}
+
+
+void free_dir_entry(struct dir_ent *dir_ent)
+{
+ if(dir_ent->name)
+ free(dir_ent->name);
+
+ if(dir_ent->source_name)
+ free(dir_ent->source_name);
+
+ if(dir_ent->nonstandard_pathname)
+ free(dir_ent->nonstandard_pathname);
+
+ /* if this entry has been associated with an inode, then we need
+ * to update the inode nlink count */
+ dec_nlink_inode(dir_ent);
+
+ free(dir_ent);
+}
+
+
+static inline void add_excluded(struct dir_info *dir)
+{
+ dir->excluded ++;
+}
+
+
+squashfs_inode do_directory_scans(struct dir_ent *dir_ent, int progress)
+{
+ squashfs_inode inode;
+ struct pseudo *pseudo = get_pseudo();
+
+ /*
+ * Process most actions and any pseudo files
+ */
+
+ /* if there's a root pseudo definition skip it, it will have already
+ * been handled if no sources specified on command line.
+ * If sources have been specified, then just ignore it, as sources
+ * on the command line take precedence.
+ */
+ if(pseudo != NULL && pseudo->names == 1 && strcmp(pseudo->name[0].name, "/") == 0) {
+ if(pseudo->name[0].xattr)
+ root_dir->dir_ent->inode->xattr = pseudo->name[0].xattr;
+
+ pseudo = pseudo->name[0].pseudo;
+ }
+
+ if(actions() || pseudo)
+ dir_scan2(root_dir, pseudo);
+
+ /*
+ * Process move actions
+ */
+ if(move_actions()) {
+ dir_scan3(root_dir);
+ do_move_actions();
+ }
+
+ /*
+ * Process prune actions
+ */
+ if(prune_actions()) {
+ dir_scan4(root_dir, TRUE);
+ dir_scan4(root_dir, FALSE);
+ }
+
+ /*
+ * Process empty actions
+ */
+ if(empty_actions())
+ dir_scan5(root_dir);
+
+ /*
+ * Sort directories and compute the inode numbers
+ */
+ dir_scan6(root_dir);
+
+ alloc_inode_no(dir_ent->inode, root_inode_number);
+
+ eval_actions(root_dir, dir_ent);
+
+ if(sorted)
+ generate_file_priorities(root_dir, 0,
+ &root_dir->dir_ent->inode->buf);
+
+ if(appending) {
+ sigset_t sigmask;
+
+ restore_thread = init_restore_thread();
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGINT);
+ sigaddset(&sigmask, SIGTERM);
+ sigaddset(&sigmask, SIGUSR1);
+ if(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) != 0)
+ BAD_ERROR("Failed to set signal mask\n");
+ write_destination(fd, SQUASHFS_START, 4, "\0\0\0\0");
+ }
+
+ queue_put(to_reader, root_dir);
+
+ if(sorted)
+ sort_files_and_write(root_dir);
+
+ dir_scan7(&inode, root_dir);
+ dir_ent->inode->inode = inode;
+ dir_ent->inode->type = SQUASHFS_DIR_TYPE;
+
+ return inode;
+}
+
+
+static squashfs_inode scan_single(char *pathname, int progress)
+{
+ struct stat buf;
+ struct dir_ent *dir_ent;
+
+ if(appending)
+ root_dir = dir_scan1(pathname, "", paths, scan1_single_readdir, 1);
+ else
+ root_dir = dir_scan1(pathname, "", paths, scan1_readdir, 1);
+
+ if(root_dir == NULL)
+ BAD_ERROR("Failed to scan source directory\n");
+
+ /* Create root directory dir_ent and associated inode, and connect
+ * it to the root directory dir_info structure */
+ dir_ent = create_dir_entry("", NULL, pathname, scan1_opendir("", "", 0));
+
+ if(lstat(pathname, &buf) == -1)
+ /* source directory has disappeared? */
+ BAD_ERROR("Cannot stat source directory %s because %s\n",
+ pathname, strerror(errno));
+ if(root_mode_opt)
+ buf.st_mode = root_mode | S_IFDIR;
+
+ if(root_uid_opt)
+ buf.st_uid = root_uid;
+
+ if(root_gid_opt)
+ buf.st_gid = root_gid;
+
+ if(root_time_opt)
+ buf.st_mtime = root_time;
+
+ if(pseudo_override && global_uid_opt)
+ buf.st_uid = global_uid;
+
+ if(pseudo_override && global_gid_opt)
+ buf.st_gid = global_gid;
+
+ dir_ent->inode = lookup_inode(&buf);
+ dir_ent->dir = root_dir;
+ root_dir->dir_ent = dir_ent;
+
+ return do_directory_scans(dir_ent, progress);
+}
+
+
+static squashfs_inode scan_encomp(int progress)
+{
+ struct stat buf;
+ struct dir_ent *dir_ent;
+
+ root_dir = dir_scan1("", "", paths, scan1_encomp_readdir, 1);
+ if(root_dir == NULL)
+ BAD_ERROR("Failed to scan source\n");
+
+ /* Create root directory dir_ent and associated inode, and connect
+ * it to the root directory dir_info structure */
+ dir_ent = create_dir_entry("", NULL, "", scan1_opendir("", "", 0));
+
+ /*
+ * dummy top level directory, multiple sources specified on
+ * command line
+ */
+ memset(&buf, 0, sizeof(buf));
+ if(root_mode_opt)
+ buf.st_mode = root_mode | S_IFDIR;
+ else
+ buf.st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR;
+ if(root_uid_opt)
+ buf.st_uid = root_uid;
+ else
+ buf.st_uid = getuid();
+ if(root_gid_opt)
+ buf.st_gid = root_gid;
+ else
+ buf.st_gid = getgid();
+ if(root_time_opt)
+ buf.st_mtime = root_time;
+ else
+ buf.st_mtime = time(NULL);
+ if(pseudo_override && global_uid_opt)
+ buf.st_uid = global_uid;
+
+ if(pseudo_override && global_gid_opt)
+ buf.st_gid = global_gid;
+
+ buf.st_dev = 0;
+ buf.st_ino = 0;
+ dir_ent->inode = lookup_inode(&buf);
+ dir_ent->inode->dummy_root_dir = TRUE;
+ dir_ent->dir = root_dir;
+ root_dir->dir_ent = dir_ent;
+
+ return do_directory_scans(dir_ent, progress);
+}
+
+
+squashfs_inode dir_scan(int directory, int progress)
+{
+ int single = !keep_as_directory && source == 1;
+
+ if(single && directory)
+ return scan_single(source_path[0], progress);
+ else
+ return scan_encomp(progress);
+}
+
+
+/*
+ * dir_scan1 routines...
+ * These scan the source directories into memory for processing.
+ * Exclude actions are processed here (in contrast to the other actions)
+ * because they affect what is scanned.
+ */
+struct dir_info *scan1_opendir(char *pathname, char *subpath, unsigned int depth)
+{
+ struct dir_info *dir;
+
+ dir = malloc(sizeof(struct dir_info));
+ if(dir == NULL)
+ MEM_ERROR();
+
+ if(pathname[0] != '\0') {
+ dir->linuxdir = opendir(pathname);
+ if(dir->linuxdir == NULL) {
+ free(dir);
+ return NULL;
+ }
+ }
+
+ dir->pathname = strdup(pathname);
+ dir->subpath = strdup(subpath);
+ dir->count = 0;
+ dir->directory_count = 0;
+ dir->dir_is_ldir = TRUE;
+ dir->list = NULL;
+ dir->depth = depth;
+ dir->excluded = 0;
+
+ return dir;
+}
+
+
+static struct dir_ent *scan1_encomp_readdir(struct dir_info *dir)
+{
+ static int index = 0;
+
+ if(dir->count < old_root_entries) {
+ int i;
+
+ for(i = 0; i < old_root_entries; i++) {
+ if(old_root_entry[i].inode.type == SQUASHFS_DIR_TYPE)
+ dir->directory_count ++;
+ add_dir_entry2(old_root_entry[i].name, NULL, NULL, NULL,
+ &old_root_entry[i].inode, dir);
+ }
+ }
+
+ while(index < source) {
+ char *basename = NULL;
+ char *dir_name = getbase(source_path[index]);
+ int pass = 1, res;
+
+ if(dir_name == NULL) {
+ ERROR_START("Bad source directory %s",
+ source_path[index]);
+ ERROR_EXIT(" - skipping ...\n");
+ index ++;
+ continue;
+ }
+ dir_name = strdup(dir_name);
+ for(;;) {
+ struct dir_ent *dir_ent = dir->list;
+
+ for(; dir_ent && strcmp(dir_ent->name, dir_name) != 0;
+ dir_ent = dir_ent->next);
+ if(dir_ent == NULL)
+ break;
+ ERROR("Source directory entry %s already used! - trying"
+ " ", dir_name);
+ if(pass == 1)
+ basename = dir_name;
+ else
+ free(dir_name);
+ res = asprintf(&dir_name, "%s_%d", basename, pass++);
+ if(res == -1)
+ BAD_ERROR("asprintf failed in "
+ "scan1_encomp_readdir\n");
+ ERROR("%s\n", dir_name);
+ }
+
+ if(one_file_system && source > 1)
+ cur_dev = source_dev[index];
+
+ return create_dir_entry(dir_name, basename,
+ strdup(source_path[index ++]), dir);
+ }
+ return NULL;
+}
+
+
+static struct dir_ent *scan1_single_readdir(struct dir_info *dir)
+{
+ struct dirent *d_name;
+ int i;
+
+ if(dir->count < old_root_entries) {
+ for(i = 0; i < old_root_entries; i++) {
+ if(old_root_entry[i].inode.type == SQUASHFS_DIR_TYPE)
+ dir->directory_count ++;
+ add_dir_entry2(old_root_entry[i].name, NULL, NULL, NULL,
+ &old_root_entry[i].inode, dir);
+ }
+ }
+
+ if((d_name = readdir(dir->linuxdir)) != NULL) {
+ char *basename = NULL;
+ char *dir_name = strdup(d_name->d_name);
+ int pass = 1, res;
+
+ for(;;) {
+ struct dir_ent *dir_ent = dir->list;
+
+ for(; dir_ent && strcmp(dir_ent->name, dir_name) != 0;
+ dir_ent = dir_ent->next);
+ if(dir_ent == NULL)
+ break;
+ ERROR("Source directory entry %s already used! - trying"
+ " ", dir_name);
+ if (pass == 1)
+ basename = dir_name;
+ else
+ free(dir_name);
+ res = asprintf(&dir_name, "%s_%d", d_name->d_name, pass++);
+ if(res == -1)
+ BAD_ERROR("asprintf failed in "
+ "scan1_single_readdir\n");
+ ERROR("%s\n", dir_name);
+ }
+ return create_dir_entry(dir_name, basename, NULL, dir);
+ }
+
+ return NULL;
+}
+
+
+static struct dir_ent *scan1_readdir(struct dir_info *dir)
+{
+ struct dirent *d_name = readdir(dir->linuxdir);
+
+ return d_name ?
+ create_dir_entry(strdup(d_name->d_name), NULL, NULL, dir) :
+ NULL;
+}
+
+
+static void scan1_freedir(struct dir_info *dir)
+{
+ if(dir->pathname[0] != '\0')
+ closedir(dir->linuxdir);
+}
+
+
+static struct dir_info *dir_scan1(char *filename, char *subpath,
+ struct pathnames *paths,
+ struct dir_ent *(_readdir)(struct dir_info *), unsigned int depth)
+{
+ struct dir_info *dir = scan1_opendir(filename, subpath, depth);
+ struct dir_ent *dir_ent;
+
+ if(dir == NULL) {
+ ERROR_START("Could not open %s", filename);
+ ERROR_EXIT(", skipping...\n");
+ return NULL;
+ }
+
+ if(max_depth_opt && depth > max_depth) {
+ add_excluded(dir);
+ scan1_freedir(dir);
+ return dir;
+ }
+
+ while((dir_ent = _readdir(dir))) {
+ struct dir_info *sub_dir;
+ struct stat buf;
+ struct pathnames *new = NULL;
+ char *filename = pathname(dir_ent);
+ char *subpath = NULL;
+ char *dir_name = dir_ent->name;
+ int create_empty_directory = FALSE;
+
+ if(strcmp(dir_name, ".") == 0 || strcmp(dir_name, "..") == 0) {
+ free_dir_entry(dir_ent);
+ continue;
+ }
+
+ if(lstat(filename, &buf) == -1) {
+ ERROR_START("Cannot stat dir/file %s because %s",
+ filename, strerror(errno));
+ ERROR_EXIT(", ignoring\n");
+ free_dir_entry(dir_ent);
+ continue;
+ }
+
+ if(one_file_system) {
+ if(buf.st_dev != cur_dev) {
+ if(!S_ISDIR(buf.st_mode) || one_file_system_x) {
+ ERROR("%s is on a different filesystem, ignored\n", filename);
+ free_dir_entry(dir_ent);
+ continue;
+ }
+
+ create_empty_directory = TRUE;
+ }
+ }
+
+ if((buf.st_mode & S_IFMT) != S_IFREG &&
+ (buf.st_mode & S_IFMT) != S_IFDIR &&
+ (buf.st_mode & S_IFMT) != S_IFLNK &&
+ (buf.st_mode & S_IFMT) != S_IFCHR &&
+ (buf.st_mode & S_IFMT) != S_IFBLK &&
+ (buf.st_mode & S_IFMT) != S_IFIFO &&
+ (buf.st_mode & S_IFMT) != S_IFSOCK) {
+ ERROR_START("File %s has unrecognised filetype %d",
+ filename, buf.st_mode & S_IFMT);
+ ERROR_EXIT(", ignoring\n");
+ free_dir_entry(dir_ent);
+ continue;
+ }
+
+ if(old_exclude && old_excluded(filename, &buf)) {
+ add_excluded(dir);
+ free_dir_entry(dir_ent);
+ continue;
+ }
+
+ if(!old_exclude && excluded(dir_name, paths, &new)) {
+ add_excluded(dir);
+ free_dir_entry(dir_ent);
+ continue;
+ }
+
+ if(exclude_actions()) {
+ subpath = subpathname(dir_ent);
+
+ if(eval_exclude_actions(dir_name, filename, subpath,
+ &buf, depth, dir_ent)) {
+ add_excluded(dir);
+ free_dir_entry(dir_ent);
+ continue;
+ }
+ }
+
+ switch(buf.st_mode & S_IFMT) {
+ case S_IFDIR:
+ if(subpath == NULL)
+ subpath = subpathname(dir_ent);
+
+ if(create_empty_directory) {
+ ERROR("%s is on a different filesystem, creating empty directory\n", filename);
+ sub_dir = create_dir(filename, subpath, depth + 1);
+ } else
+ sub_dir = dir_scan1(filename, subpath, new,
+ scan1_readdir, depth + 1);
+ if(sub_dir) {
+ dir->directory_count ++;
+ add_dir_entry(dir_ent, sub_dir,
+ lookup_inode(&buf));
+ } else
+ free_dir_entry(dir_ent);
+ break;
+ case S_IFLNK: {
+ int byte;
+ static char buff[65536]; /* overflow safe */
+
+ byte = readlink(filename, buff, 65536);
+ if(byte == -1) {
+ ERROR_START("Failed to read symlink %s",
+ filename);
+ ERROR_EXIT(", ignoring\n");
+ } else if(byte == 65536) {
+ ERROR_START("Symlink %s is greater than 65536 "
+ "bytes!", filename);
+ ERROR_EXIT(", ignoring\n");
+ } else {
+ /* readlink doesn't 0 terminate the returned
+ * path */
+ buff[byte] = '\0';
+ add_dir_entry(dir_ent, NULL, lookup_inode3(&buf,
+ NULL, buff, byte + 1));
+ }
+ break;
+ }
+ default:
+ add_dir_entry(dir_ent, NULL, lookup_inode(&buf));
+ }
+
+ free(new);
+ }
+
+ scan1_freedir(dir);
+
+ return dir;
+}
+
+
+/*
+ * dir_scan2 routines...
+ * This processes most actions and any pseudo files
+ */
+static struct dir_ent *scan2_readdir(struct dir_info *dir, struct dir_ent *dir_ent)
+{
+ if (dir_ent == NULL)
+ dir_ent = dir->list;
+ else
+ dir_ent = dir_ent->next;
+
+ for(; dir_ent && dir_ent->inode->root_entry; dir_ent = dir_ent->next);
+
+ return dir_ent;
+}
+
+
+static void dir_scan2(struct dir_info *dir, struct pseudo *pseudo)
+{
+ struct dir_ent *dirent = NULL;
+ struct pseudo_entry *pseudo_ent;
+ struct stat buf;
+
+ while((dirent = scan2_readdir(dir, dirent)) != NULL) {
+ struct inode_info *inode_info = dirent->inode;
+ struct stat *buf = &inode_info->buf;
+ char *name = dirent->name;
+
+ eval_actions(root_dir, dirent);
+
+ if(pseudo_override && global_uid_opt)
+ buf->st_uid = global_uid;
+
+ if(pseudo_override && global_gid_opt)
+ buf->st_gid = global_gid;
+
+ if((buf->st_mode & S_IFMT) == S_IFDIR)
+ dir_scan2(dirent->dir, pseudo_subdir(name, pseudo));
+ }
+
+ /*
+ * Process pseudo modify and add (file, directory etc) definitions
+ */
+ while((pseudo_ent = pseudo_readdir(pseudo)) != NULL) {
+ struct dir_ent *dir_ent = NULL;
+
+ if(appending && dir->depth == 1) {
+ dir_ent = lookup_name(dir, pseudo_ent->name);
+
+ if(dir_ent && dir_ent->inode->root_entry) {
+ BAD_ERROR("Pseudo files: File \"%s\" "
+ "already exists in root directory of "
+ "the\nfilesystem being appended to. "
+ "Pseudo definitions can\'t modify it "
+ "or (if directory) add files to it\n",
+ pseudo_ent->name);
+ }
+ }
+
+ if(pseudo_ent->dev == NULL)
+ continue;
+
+ if(dir_ent == NULL)
+ dir_ent = lookup_name(dir, pseudo_ent->name);
+
+ if(pseudo_ent->dev->type == 'm' || pseudo_ent->dev->type == 'M') {
+ struct stat *buf;
+ if(dir_ent == NULL) {
+ ERROR_START("Pseudo modify file \"%s\" does "
+ "not exist in source filesystem.",
+ pseudo_ent->pathname);
+ ERROR_EXIT(" Ignoring.\n");
+ continue;
+ }
+ buf = &dir_ent->inode->buf;
+ buf->st_mode = (buf->st_mode & S_IFMT) |
+ pseudo_ent->dev->buf->mode;
+ buf->st_uid = pseudo_ent->dev->buf->uid;
+ buf->st_gid = pseudo_ent->dev->buf->gid;
+ if(pseudo_ent->dev->type == 'M')
+ buf->st_mtime = pseudo_ent->dev->buf->mtime;
+ continue;
+ }
+
+ if(dir_ent) {
+ ERROR_START("Pseudo file \"%s\" exists in source "
+ "filesystem \"%s\".", pseudo_ent->pathname,
+ pathname(dir_ent));
+ ERROR_EXIT("\nIgnoring, exclude it (-e/-ef) to override.\n");
+ continue;
+ }
+
+ if(pseudo_ent->dev->type != 'l') {
+ memset(&buf, 0, sizeof(buf));
+ buf.st_mode = pseudo_ent->dev->buf->mode;
+ buf.st_uid = pseudo_ent->dev->buf->uid;
+ buf.st_gid = pseudo_ent->dev->buf->gid;
+ buf.st_rdev = makedev(pseudo_ent->dev->buf->major,
+ pseudo_ent->dev->buf->minor);
+ buf.st_mtime = pseudo_ent->dev->buf->mtime;
+ buf.st_ino = pseudo_ent->dev->buf->ino;
+
+ if(pseudo_ent->dev->type == 'r') {
+ buf.st_size = pseudo_ent->dev->data->length;
+ if(pseudo_ent->dev->data->sparse == FALSE)
+ buf.st_blocks = (buf.st_size + 511) >> 9;
+ }
+ }
+
+ if(pseudo_ent->dev->type == 'd') {
+ struct dir_ent *dir_ent =
+ create_dir_entry(pseudo_ent->name, NULL,
+ pseudo_ent->pathname, dir);
+ char *subpath = subpathname(dir_ent);
+ struct dir_info *sub_dir = scan1_opendir("", subpath,
+ dir->depth + 1);
+ dir_scan2(sub_dir, pseudo_ent->pseudo);
+ dir->directory_count ++;
+ add_dir_entry(dir_ent, sub_dir,
+ lookup_inode2(&buf, pseudo_ent->dev));
+ } else if(pseudo_ent->dev->type == 's') {
+ add_dir_entry2(pseudo_ent->name, NULL,
+ pseudo_ent->pathname, NULL,
+ lookup_inode3(&buf, pseudo_ent->dev,
+ pseudo_ent->dev->symlink,
+ strlen(pseudo_ent->dev->symlink) + 1), dir);
+ } else if(pseudo_ent->dev->type == 'l') {
+ add_dir_entry2(pseudo_ent->name, NULL,
+ pseudo_ent->dev->linkname, NULL,
+ lookup_inode(pseudo_ent->dev->linkbuf), dir);
+ } else {
+ add_dir_entry2(pseudo_ent->name, NULL,
+ pseudo_ent->pathname, NULL,
+ lookup_inode2(&buf, pseudo_ent->dev), dir);
+ }
+ }
+
+ /*
+ * Process pseudo xattr definitions
+ */
+ if(pseudo)
+ pseudo->count = 0;
+
+ while((pseudo_ent = pseudo_readdir(pseudo)) != NULL) {
+ struct dir_ent *dir_ent = NULL;
+
+ if(pseudo_ent->xattr == NULL)
+ continue;
+
+ dir_ent = lookup_name(dir, pseudo_ent->name);
+ if(dir_ent == NULL) {
+ ERROR_START("Pseudo xattr file \"%s\" does not "
+ "exist in source filesystem.",
+ pseudo_ent->pathname);
+ ERROR_EXIT(" Ignoring.\n");
+ continue;
+ }
+
+ dir_ent->inode->xattr = pseudo_ent->xattr;
+ }
+}
+
+
+/*
+ * dir_scan3 routines...
+ * This processes the move action
+ */
+static void dir_scan3(struct dir_info *dir)
+{
+ struct dir_ent *dir_ent = NULL;
+
+ while((dir_ent = scan2_readdir(dir, dir_ent)) != NULL) {
+
+ eval_move_actions(root_dir, dir_ent);
+
+ if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+ dir_scan3(dir_ent->dir);
+ }
+}
+
+
+/*
+ * dir_scan4 routines...
+ * This processes the prune action. This action is designed to do fine
+ * grained tuning of the in-core directory structure after the exclude,
+ * move and pseudo actions have been performed. This allows complex
+ * tests to be performed which are impossible at exclude time (i.e.
+ * tests which rely on the in-core directory structure)
+ */
+void free_dir(struct dir_info *dir)
+{
+ struct dir_ent *dir_ent = dir->list;
+
+ while(dir_ent) {
+ struct dir_ent *tmp = dir_ent;
+
+ if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+ if(dir_ent->dir)
+ free_dir(dir_ent->dir);
+
+ dir_ent = dir_ent->next;
+ free_dir_entry(tmp);
+ }
+
+ free(dir->pathname);
+ free(dir->subpath);
+ free(dir);
+}
+
+
+static void dir_scan4(struct dir_info *dir, int symlink)
+{
+ struct dir_ent *dir_ent = dir->list, *prev = NULL;
+
+ while(dir_ent) {
+ if(dir_ent->inode->root_entry) {
+ prev = dir_ent;
+ dir_ent = dir_ent->next;
+ continue;
+ }
+
+ if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+ dir_scan4(dir_ent->dir, symlink);
+
+ if(symlink != ((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFLNK)) {
+ prev = dir_ent;
+ dir_ent = dir_ent->next;
+ continue;
+ }
+
+ if(eval_prune_actions(root_dir, dir_ent)) {
+ struct dir_ent *tmp = dir_ent;
+
+ if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR) {
+ free_dir(dir_ent->dir);
+ dir->directory_count --;
+ }
+
+ dir->count --;
+
+ /* remove dir_ent from list */
+ dir_ent = dir_ent->next;
+ if(prev)
+ prev->next = dir_ent;
+ else
+ dir->list = dir_ent;
+
+ /* free it */
+ free_dir_entry(tmp);
+
+ add_excluded(dir);
+ continue;
+ }
+
+ prev = dir_ent;
+ dir_ent = dir_ent->next;
+ }
+}
+
+
+/*
+ * dir_scan5 routines...
+ * This processes the empty action. This action has to be processed after
+ * all other actions because the previous exclude and move actions and the
+ * pseudo actions affect whether a directory is empty
+ */
+static void dir_scan5(struct dir_info *dir)
+{
+ struct dir_ent *dir_ent = dir->list, *prev = NULL;
+
+ while(dir_ent) {
+ if(dir_ent->inode->root_entry) {
+ prev = dir_ent;
+ dir_ent = dir_ent->next;
+ continue;
+ }
+
+ if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR) {
+ dir_scan5(dir_ent->dir);
+
+ if(eval_empty_actions(root_dir, dir_ent)) {
+ struct dir_ent *tmp = dir_ent;
+
+ /*
+ * delete sub-directory, this is by definition
+ * empty
+ */
+ free(dir_ent->dir->pathname);
+ free(dir_ent->dir->subpath);
+ free(dir_ent->dir);
+
+ /* remove dir_ent from list */
+ dir_ent = dir_ent->next;
+ if(prev)
+ prev->next = dir_ent;
+ else
+ dir->list = dir_ent;
+
+ /* free it */
+ free_dir_entry(tmp);
+
+ /* update counts */
+ dir->directory_count --;
+ dir->count --;
+ add_excluded(dir);
+ continue;
+ }
+ }
+
+ prev = dir_ent;
+ dir_ent = dir_ent->next;
+ }
+}
+
+
+/*
+ * dir_scan6 routines...
+ * This sorts every directory and computes the inode numbers
+ */
+
+/*
+ * Instantiate bottom up linked list merge sort.
+ *
+ * Qsort and other O(n log n) algorithms work well with arrays but not
+ * linked lists. Merge sort another O(n log n) sort algorithm on the other hand
+ * is not ideal for arrays (as it needs an additonal n storage locations
+ * as sorting is not done in place), but it is ideal for linked lists because
+ * it doesn't require any extra storage,
+ */
+SORT(sort_directory, dir_ent, name, next);
+
+static void dir_scan6(struct dir_info *dir)
+{
+ struct dir_ent *dir_ent;
+ unsigned int byte_count = 0;
+
+ sort_directory(&(dir->list), dir->count);
+
+ for(dir_ent = dir->list; dir_ent; dir_ent = dir_ent->next) {
+ byte_count += strlen(dir_ent->name) +
+ sizeof(struct squashfs_dir_entry);
+
+ if(dir_ent->inode->root_entry)
+ continue;
+
+ alloc_inode_no(dir_ent->inode, 0);
+
+ if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
+ dir_scan6(dir_ent->dir);
+ }
+
+ if((dir->count < 257 && byte_count < SQUASHFS_METADATA_SIZE))
+ dir->dir_is_ldir = FALSE;
+}
+
+
+/*
+ * dir_scan6 routines...
+ * This generates the filesystem metadata and writes it out to the destination
+ */
+static void scan7_init_dir(struct directory *dir)
+{
+ dir->buff = malloc(SQUASHFS_METADATA_SIZE);
+ if(dir->buff == NULL)
+ MEM_ERROR();
+
+ dir->size = SQUASHFS_METADATA_SIZE;
+ dir->p = dir->index_count_p = dir->buff;
+ dir->entry_count = 256;
+ dir->entry_count_p = NULL;
+ dir->index = NULL;
+ dir->i_count = dir->i_size = 0;
+}
+
+
+static struct dir_ent *scan7_readdir(struct directory *dir, struct dir_info *dir_info,
+ struct dir_ent *dir_ent)
+{
+ if (dir_ent == NULL)
+ dir_ent = dir_info->list;
+ else
+ dir_ent = dir_ent->next;
+
+ for(; dir_ent && dir_ent->inode->root_entry; dir_ent = dir_ent->next)
+ add_dir(dir_ent->inode->inode, dir_ent->inode->inode_number,
+ dir_ent->name, dir_ent->inode->type, dir);
+
+ return dir_ent;
+}
+
+
+static void scan7_freedir(struct directory *dir)
+{
+ if(dir->index)
+ free(dir->index);
+ free(dir->buff);
+}
+
+
+static void dir_scan7(squashfs_inode *inode, struct dir_info *dir_info)
+{
+ int squashfs_type;
+ int duplicate_file;
+ struct directory dir;
+ struct dir_ent *dir_ent = NULL;
+ struct file_info *file;
+
+ scan7_init_dir(&dir);
+
+ while((dir_ent = scan7_readdir(&dir, dir_info, dir_ent)) != NULL) {
+ struct stat *buf = &dir_ent->inode->buf;
+
+ update_info(dir_ent);
+
+ if(dir_ent->inode->inode == SQUASHFS_INVALID_BLK) {
+ switch(buf->st_mode & S_IFMT) {
+ case S_IFREG:
+ if(dir_ent->inode->tarfile && dir_ent->inode->tar_file->file)
+ file = dir_ent->inode->tar_file->file;
+ else {
+ file = write_file(dir_ent, &duplicate_file);
+ INFO("file %s, uncompressed size %lld "
+ "bytes %s\n",
+ subpathname(dir_ent),
+ (long long) buf->st_size,
+ duplicate_file ? "DUPLICATE" :
+ "");
+ }
+ squashfs_type = SQUASHFS_FILE_TYPE;
+ *inode = create_inode(NULL, dir_ent,
+ squashfs_type, file->file_size,
+ file->start, file->blocks,
+ file->block_list,
+ file->fragment, NULL,
+ file->sparse);
+ if((duplicate_checking == FALSE &&
+ !(tarfile && no_hardlinks)) ||
+ file->file_size == 0) {
+ free_fragment(file->fragment);
+ free(file->block_list);
+ free(file);
+ }
+ break;
+
+ case S_IFDIR:
+ squashfs_type = SQUASHFS_DIR_TYPE;
+ dir_scan7(inode, dir_ent->dir);
+ break;
+
+ case S_IFLNK:
+ squashfs_type = SQUASHFS_SYMLINK_TYPE;
+ *inode = create_inode(NULL, dir_ent,
+ squashfs_type, 0, 0, 0, NULL,
+ NULL, NULL, 0);
+ INFO("symbolic link %s inode 0x%llx\n",
+ subpathname(dir_ent), *inode);
+ sym_count ++;
+ break;
+
+ case S_IFCHR:
+ squashfs_type = SQUASHFS_CHRDEV_TYPE;
+ *inode = create_inode(NULL, dir_ent,
+ squashfs_type, 0, 0, 0, NULL,
+ NULL, NULL, 0);
+ INFO("character device %s inode 0x%llx"
+ "\n", subpathname(dir_ent),
+ *inode);
+ dev_count ++;
+ break;
+
+ case S_IFBLK:
+ squashfs_type = SQUASHFS_BLKDEV_TYPE;
+ *inode = create_inode(NULL, dir_ent,
+ squashfs_type, 0, 0, 0, NULL,
+ NULL, NULL, 0);
+ INFO("block device %s inode 0x%llx\n",
+ subpathname(dir_ent), *inode);
+ dev_count ++;
+ break;
+
+ case S_IFIFO:
+ squashfs_type = SQUASHFS_FIFO_TYPE;
+ *inode = create_inode(NULL, dir_ent,
+ squashfs_type, 0, 0, 0, NULL,
+ NULL, NULL, 0);
+ INFO("fifo %s inode 0x%llx\n",
+ subpathname(dir_ent), *inode);
+ fifo_count ++;
+ break;
+
+ case S_IFSOCK:
+ squashfs_type = SQUASHFS_SOCKET_TYPE;
+ *inode = create_inode(NULL, dir_ent,
+ squashfs_type, 0, 0, 0, NULL,
+ NULL, NULL, 0);
+ INFO("unix domain socket %s inode "
+ "0x%llx\n",
+ subpathname(dir_ent), *inode);
+ sock_count ++;
+ break;
+
+ default:
+ BAD_ERROR("%s unrecognised file type, "
+ "mode is %x\n",
+ subpathname(dir_ent),
+ buf->st_mode);
+ }
+ dir_ent->inode->inode = *inode;
+ dir_ent->inode->type = squashfs_type;
+ } else {
+ *inode = dir_ent->inode->inode;
+ squashfs_type = dir_ent->inode->type;
+ switch(squashfs_type) {
+ case SQUASHFS_FILE_TYPE:
+ if(!sorted)
+ INFO("file %s, uncompressed "
+ "size %lld bytes LINK"
+ "\n",
+ subpathname(dir_ent),
+ (long long)
+ buf->st_size);
+ break;
+ case SQUASHFS_SYMLINK_TYPE:
+ INFO("symbolic link %s inode 0x%llx "
+ "LINK\n", subpathname(dir_ent),
+ *inode);
+ break;
+ case SQUASHFS_CHRDEV_TYPE:
+ INFO("character device %s inode 0x%llx "
+ "LINK\n", subpathname(dir_ent),
+ *inode);
+ break;
+ case SQUASHFS_BLKDEV_TYPE:
+ INFO("block device %s inode 0x%llx "
+ "LINK\n", subpathname(dir_ent),
+ *inode);
+ break;
+ case SQUASHFS_FIFO_TYPE:
+ INFO("fifo %s inode 0x%llx LINK\n",
+ subpathname(dir_ent), *inode);
+ break;
+ case SQUASHFS_SOCKET_TYPE:
+ INFO("unix domain socket %s inode "
+ "0x%llx LINK\n",
+ subpathname(dir_ent), *inode);
+ break;
+ }
+ hardlnk_count ++;
+ }
+
+ add_dir(*inode, get_inode_no(dir_ent->inode), dir_ent->name,
+ squashfs_type, &dir);
+ }
+
+ *inode = write_dir(dir_info, &dir);
+ INFO("directory %s inode 0x%llx\n", subpathname(dir_info->dir_ent),
+ *inode);
+
+ scan7_freedir(&dir);
+}
+
+
+static void handle_root_entries(struct dir_info *dir)
+{
+ int i;
+
+ if(dir->count == 0) {
+ for(i = 0; i < old_root_entries; i++) {
+ if(old_root_entry[i].inode.type == SQUASHFS_DIR_TYPE)
+ dir->directory_count ++;
+ add_dir_entry2(strdup(old_root_entry[i].name), NULL,
+ NULL, NULL, &old_root_entry[i].inode, dir);
+ }
+ }
+}
+
+
+static char *walk_source(char *source, char **pathname, char **name)
+{
+ char *path = source, *start;
+
+ while(*source == '/')
+ source ++;
+
+ start = source;
+ while(*source != '/' && *source != '\0')
+ source ++;
+
+ *name = strndup(start, source - start);
+
+ if(*pathname == NULL)
+ *pathname = strndup(path, source - path);
+ else {
+ char *orig = *pathname;
+ int size = strlen(orig) + (source - path) + 2;
+
+ *pathname = malloc(size);
+ strcpy(*pathname, orig);
+ strcat(*pathname, "/");
+ strncat(*pathname, path, source - path);
+ }
+
+ while(*source == '/')
+ source ++;
+
+ return source;
+}
+
+
+static struct dir_info *add_source(struct dir_info *sdir, char *source,
+ char *subpath, char *file, char **prefix,
+ struct pathnames *paths, unsigned int depth)
+{
+ struct dir_info *sub;
+ struct dir_ent *entry;
+ struct pathnames *new = NULL;
+ struct dir_info *dir = sdir;
+ struct stat buf;
+ char *name, *newsubpath = NULL;
+ int res;
+
+ if(max_depth_opt && depth > max_depth)
+ return NULL;
+
+ if(dir == NULL)
+ dir = create_dir("", subpath, depth);
+
+ if(depth == 1)
+ *prefix = source[0] == '/' ? strdup("/") : strdup(".");
+
+ if(appending && file == NULL)
+ handle_root_entries(dir);
+
+ source = walk_source(source, &file, &name);
+
+ while(depth == 1 && (name[0] == '\0' || strcmp(name, "..") == 0
+ || strcmp(name, ".") == 0)){
+ char *old = file;
+
+ if(name[0] == '\0' || source[0] == '\0') {
+ /* Ran out of pathname skipping leading ".." and "."
+ * If cpiostyle, just ignore it, find always produces
+ * these if run as "find ." or "find .." etc.
+ *
+ * If tarstyle after skipping what we *must* skip
+ * in the pathname (we can't store directories named
+ * ".." or "." or simply "/") there's nothing left after
+ * stripping (i.e. someone just typed "..", "." on
+ * the command line). This isn't what -tarstyle is
+ * intended for, and Mksquashfs without -tarstyle
+ * can handle this scenario */
+ if(cpiostyle)
+ goto failed_early;
+ else
+ BAD_ERROR("Empty source after stripping '/', "
+ "'..' and '.'. Run Mksquashfs without "
+ "-tarstyle to handle this!\n");
+ }
+
+ source = walk_source(source, &file, &name);
+ if(name[0] == '\0' || strcmp(name, "..") == 0 || strcmp(name, ".") == 0)
+ free(old);
+ else
+ *prefix = old;
+ }
+
+ if((strcmp(name, ".") == 0) || strcmp(name, "..") == 0)
+ BAD_ERROR("Source path can't have '.' or '..' embedded in it with -tarstyle/-cpiostyle[0]\n");
+
+ res = lstat(file, &buf);
+ if (res == -1)
+ BAD_ERROR("Can't stat %s because %s\n", file, strerror(errno));
+
+ entry = lookup_name(dir, name);
+
+ if(entry) {
+ /*
+ * name already there. This must be the same file, otherwise
+ * we have a clash, as we can't have two different files with
+ * the same pathname.
+ *
+ * An original root entry from the file being appended to
+ * is never the same file.
+ */
+ if(entry->inode->root_entry)
+ BAD_ERROR("Source %s conflicts with name in filesystem "
+ "being appended to\n", name);
+
+ res = memcmp(&buf, &(entry->inode->buf), sizeof(buf));
+ if(res)
+ BAD_ERROR("Can't have two different sources with same "
+ "pathname\n");
+
+ /*
+ * Matching file.
+ *
+ * For tarstyle source handling (leaf directores are
+ * recursively descended)
+ *
+ * - If we're at the leaf of the source, then we either match
+ * or encompass this pre-existing include. So delete any
+ * sub-directories of this pre-existing include.
+ *
+ * - If we're not at the leaf of the source, but we're at
+ * the leaf of the pre-existing include, then the
+ * pre-existing include encompasses this source. So nothing
+ * more to do.
+ *
+ * - Otherwise this is not the leaf of the source, or the leaf
+ * of the pre-existing include, so recurse continuing walking
+ * the source.
+ *
+ * For cpiostyle source handling (leaf directories are not
+ * recursively descended)
+ *
+ * - If we're at the leaf of the source, then we have a
+ * pre-existing include. So nothing to do.
+ *
+ * - If we're not at the leaf of the source, but we're at
+ * the leaf of the pre-existing include, then recurse
+ * walking the source.
+ *
+ * - Otherwise this is not the leaf of the source, or the leaf
+ * of the pre-existing include, so recurse continuing walking
+ * the source.
+ */
+ if(source[0] == '\0') {
+ if(tarstyle && entry->dir) {
+ free_dir(entry->dir);
+ entry->dir = NULL;
+ }
+ } else if(S_ISDIR(buf.st_mode)) {
+ if(cpiostyle || entry->dir) {
+ excluded(entry->name, paths, &new);
+ subpath = subpathname(entry);
+ sub = add_source(entry->dir, source, subpath,
+ file, prefix, new, depth + 1);
+ if(sub == NULL)
+ goto failed_match;
+ entry->dir = sub;
+ sub->dir_ent = entry;
+ }
+ } else
+ BAD_ERROR("Source component %s is not a directory\n", name);
+
+ free(name);
+ free(file);
+ } else {
+ /*
+ * No matching name found.
+ *
+ * - If we're at the leaf of the source, then add it.
+ *
+ * - If we're not at the leaf of the source, we will add it,
+ * and recurse walking the source
+ */
+ if(old_exclude && old_excluded(file, &buf))
+ goto failed_early;
+
+ if(old_exclude == FALSE && excluded(name, paths, &new))
+ goto failed_early;
+
+ entry = create_dir_entry(name, NULL, file, dir);
+
+ if(exclude_actions()) {
+ newsubpath = subpathname(entry);
+ if(eval_exclude_actions(name, file, newsubpath, &buf,
+ depth, entry)) {
+ goto failed_entry;
+ }
+ }
+
+ if(source[0] == '\0' && S_ISLNK(buf.st_mode)) {
+ int byte;
+ static char buff[65536]; /* overflow safe */
+ struct inode_info *i;
+
+ byte = readlink(file, buff, 65536);
+ if(byte == -1)
+ BAD_ERROR("Failed to read source symlink %s", file);
+ else if(byte == 65536)
+ BAD_ERROR("Symlink %s is greater than 65536 "
+ "bytes!", file);
+
+ /* readlink doesn't 0 terminate the returned path */
+ buff[byte] = '\0';
+ i = lookup_inode3(&buf, NULL, buff, byte + 1);
+ add_dir_entry(entry, NULL, i);
+ } else if(source[0] == '\0') {
+ add_dir_entry(entry, NULL, lookup_inode(&buf));
+ if(S_ISDIR(buf.st_mode))
+ dir->directory_count ++;
+ } else if(S_ISDIR(buf.st_mode)) {
+ if(newsubpath == NULL)
+ newsubpath = subpathname(entry);
+ sub = add_source(NULL, source, newsubpath, file, prefix,
+ new, depth + 1);
+ if(sub == NULL)
+ goto failed_entry;
+ add_dir_entry(entry, sub, lookup_inode(&buf));
+ dir->directory_count ++;
+ } else
+ BAD_ERROR("Source component %s is not a directory\n", name);
+ }
+
+ free(new);
+ return dir;
+
+failed_early:
+ free(new);
+ free(name);
+ free(file);
+ if(sdir == NULL)
+ free_dir(dir);
+ return NULL;
+
+failed_entry:
+ free(new);
+ free_dir_entry(entry);
+ if(sdir == NULL)
+ free_dir(dir);
+ return NULL;
+
+failed_match:
+ free(new);
+ free(name);
+ free(file);
+ return NULL;
+}
+
+
+static struct dir_info *populate_tree(struct dir_info *dir, struct pathnames *paths)
+{
+ struct dir_ent *entry;
+ struct dir_info *new;
+
+ for(entry = dir->list; entry; entry = entry->next)
+ if(S_ISDIR(entry->inode->buf.st_mode) && !entry->inode->root_entry) {
+ struct pathnames *newp = NULL;
+
+ excluded(entry->name, paths, &newp);
+
+ if(entry->dir == NULL && cpiostyle) {
+ entry->dir = create_dir(pathname(entry),
+ subpathname(entry), dir->depth + 1);
+ entry->dir->dir_ent = entry;
+ } else if(entry->dir == NULL) {
+ cur_dev = entry->inode->buf.st_dev;
+ new = dir_scan1(pathname(entry),
+ subpathname(entry), newp, scan1_readdir,
+ dir->depth + 1);
+ if(new == NULL)
+ return NULL;
+
+ entry->dir = new;
+ new->dir_ent = entry;
+ } else {
+ new = populate_tree(entry->dir, newp);
+ if(new == NULL)
+ return NULL;
+ }
+
+ free(newp);
+ }
+
+ return dir;
+}
+
+
+static char *get_filename_from_stdin(char terminator)
+{
+ static int path_max = -1;
+ static int bytes = 0;
+ static int size = 0;
+ static char *buffer = NULL;
+ static char *filename = NULL;
+ static char *src = NULL;
+ char *dest = filename;
+ int used = 0;
+
+ /* Get the maximum pathname size supported on this system */
+ if(path_max == -1) {
+#ifdef PATH_MAX
+ path_max = PATH_MAX;
+#else
+ path_max = pathconf(".", _PC_PATH_MAX);
+ if(path_max <= 0)
+ path_max = 4096;
+#endif
+ /* limit to no more than 64K */
+ if(path_max > 65536)
+ path_max = 65536;
+ }
+
+ if(buffer == NULL) {
+ buffer = malloc(4096);
+ if(buffer == NULL)
+ MEM_ERROR();
+ }
+
+ while(1) {
+ if(bytes == 0) {
+ bytes = read_bytes(STDIN_FILENO, buffer, 4096);
+
+ if(bytes == -1)
+ BAD_ERROR("Failed to read Tar file from STDIN\n");
+
+ if(bytes == 0) {
+ if(used)
+ ERROR("Got EOF when reading filename from STDIN, ignoring\n");
+ free(filename);
+ free(buffer);
+ return NULL;
+ }
+ src = buffer;
+ }
+
+ if(size - used <= 1) {
+ int offset = dest - filename;
+ char *buff = realloc(filename, size += 100);
+ if(buff == NULL)
+ MEM_ERROR();
+ dest = buff + offset;
+ filename = buff;
+ }
+
+ if(*src == terminator) {
+ src++;
+ bytes--;
+ break;
+ }
+
+ if(used >= (path_max - 1))
+ BAD_ERROR("Cpiostyle input filename exceeds maximum "
+ "path limit of %d bytes!\n", path_max);
+
+ *dest++ = *src++;
+ bytes --;
+ used ++;
+ }
+
+ *dest = '\0';
+ return filename;
+}
+
+
+static char *get_next_filename()
+{
+ static int cur = 0;
+ char *filename;
+
+ if(cpiostyle) {
+ while(1) {
+ filename = get_filename_from_stdin(filename_terminator);
+ if(filename == NULL || strlen(filename) != 0)
+ break;
+ }
+ return filename;
+ } else if(cur < source)
+ return source_path[cur ++];
+ else
+ return NULL;
+}
+
+
+static squashfs_inode process_source(int progress)
+{
+ int i, res, first = TRUE, same = FALSE;
+ char *filename, *prefix, *pathname;
+ struct stat buf, buf2;
+ struct dir_ent *entry;
+ struct dir_info *new;
+
+ for(i = 0; (filename = get_next_filename()); i++) {
+ new = add_source(root_dir, filename, "", NULL, &prefix, paths, 1);
+
+ if(new) {
+ /* does argv[i] start from the same directory? */
+ if(first) {
+ res = lstat(prefix, &buf);
+ if (res == -1)
+ BAD_ERROR("Can't stat %s because %s\n",
+ prefix, strerror(errno));
+ first = FALSE;
+ same = TRUE;
+ pathname = strdup(prefix);
+ } else if(same) {
+ res = lstat(prefix, &buf2);
+ if (res == -1)
+ BAD_ERROR("Can't stat %s because %s\n",
+ prefix, strerror(errno));
+
+ if(buf.st_dev != buf2.st_dev ||
+ buf.st_ino != buf2.st_ino)
+ same = FALSE;
+ }
+ free(prefix);
+ root_dir = new;
+ }
+ }
+
+ if(root_dir == NULL) {
+ /* Empty directory tree after processing the sources, and
+ * so everything was excluded.
+ * We need to create an empty directory to reflect this, and
+ * if appending, fill it with the original root directory
+ * contents */
+ root_dir = scan1_opendir("", "", 0);
+
+ if(appending)
+ handle_root_entries(root_dir);
+ }
+
+ new = scan1_opendir("", "", 0);
+
+ if(!same) {
+ /*
+ * Top level directory conflict. Create dummy
+ * top level directory
+ */
+ memset(&buf, 0, sizeof(buf));
+ buf.st_mode = (root_mode_opt) ? root_mode | S_IFDIR :
+ S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR;
+ if(root_uid_opt)
+ buf.st_uid = root_uid;
+ else
+ buf.st_uid = getuid();
+ if(root_gid_opt)
+ buf.st_gid = root_gid;
+ else
+ buf.st_gid = getgid();
+ if(root_time_opt)
+ buf.st_mtime = root_time;
+ else
+ buf.st_mtime = time(NULL);
+ if(pseudo_override && global_uid_opt)
+ buf.st_uid = global_uid;
+ if(pseudo_override && global_gid_opt)
+ buf.st_gid = global_gid;
+
+ entry = create_dir_entry("", NULL, "", new);
+ entry->inode = lookup_inode(&buf);
+ entry->inode->dummy_root_dir = TRUE;
+ } else {
+ if(root_mode_opt)
+ buf.st_mode = root_mode | S_IFDIR;
+ if(root_uid_opt)
+ buf.st_uid = root_uid;
+ if(root_gid_opt)
+ buf.st_gid = root_gid;
+ if(root_time_opt)
+ buf.st_mtime = root_time;
+ if(pseudo_override && global_uid_opt)
+ buf.st_uid = global_uid;
+ if(pseudo_override && global_gid_opt)
+ buf.st_gid = global_gid;
+
+ entry = create_dir_entry("", NULL, pathname, new);
+ entry->inode = lookup_inode(&buf);
+ }
+
+
+ entry->dir = root_dir;
+ root_dir->dir_ent = entry;
+
+ root_dir = populate_tree(root_dir, paths);
+ if(root_dir == NULL)
+ BAD_ERROR("Failed to read directory hierarchy\n");
+
+ return do_directory_scans(entry, progress);
+}
+
+
+/*
+ * Source directory specified as - which means no source directories
+ *
+ * Here the pseudo definitions will be providing the source directory
+ */
+static squashfs_inode no_sources(int progress)
+{
+ struct stat buf;
+ struct dir_ent *dir_ent;
+ struct pseudo_entry *pseudo_ent;
+ struct pseudo *pseudo = get_pseudo();
+
+ if(pseudo == NULL || pseudo->names != 1 || strcmp(pseudo->name[0].name, "/") != 0) {
+ ERROR_START("Source is \"-\", but no pseudo definition for \"/\"\n");
+ ERROR_EXIT("Did you forget to specify -cpiostyle or -tar?\n");
+ EXIT_MKSQUASHFS();
+ }
+
+ pseudo_ent = &pseudo->name[0];
+
+ /* create root directory */
+ root_dir = scan1_opendir("", "", 1);
+
+ if(appending)
+ handle_root_entries(root_dir);
+
+ /* Create root directory dir_ent and associated inode, and connect
+ * it to the root directory dir_info structure */
+ dir_ent = create_dir_entry("", NULL, "", scan1_opendir("", "", 0));
+
+ memset(&buf, 0, sizeof(buf));
+
+ if(root_mode_opt)
+ buf.st_mode = root_mode | S_IFDIR;
+ else
+ buf.st_mode = pseudo_ent->dev->buf->mode;
+
+ if(root_uid_opt)
+ buf.st_uid = root_uid;
+ else
+ buf.st_uid = pseudo_ent->dev->buf->uid;
+
+ if(root_gid_opt)
+ buf.st_gid = root_gid;
+ else
+ buf.st_gid = pseudo_ent->dev->buf->gid;
+
+ if(root_time_opt)
+ buf.st_mtime = root_time;
+ else
+ buf.st_mtime = pseudo_ent->dev->buf->mtime;
+
+ buf.st_ino = pseudo_ent->dev->buf->ino;
+
+ dir_ent->inode = lookup_inode2(&buf, pseudo_ent->dev);
+ dir_ent->dir = root_dir;
+ root_dir->dir_ent = dir_ent;
+
+ return do_directory_scans(dir_ent, progress);
+}
+
+
+static unsigned int slog(unsigned int block)
+{
+ int i;
+
+ for(i = 12; i <= 20; i++)
+ if(block == (1 << i))
+ return i;
+ return 0;
+}
+
+
+static int old_excluded(char *filename, struct stat *buf)
+{
+ int i;
+
+ for(i = 0; i < exclude; i++)
+ if((exclude_paths[i].st_dev == buf->st_dev) &&
+ (exclude_paths[i].st_ino == buf->st_ino))
+ return TRUE;
+ return FALSE;
+}
+
+
+#define ADD_ENTRY(buf) \
+ if(exclude % EXCLUDE_SIZE == 0) { \
+ exclude_paths = realloc(exclude_paths, (exclude + EXCLUDE_SIZE) \
+ * sizeof(struct exclude_info)); \
+ if(exclude_paths == NULL) \
+ MEM_ERROR(); \
+ } \
+ exclude_paths[exclude].st_dev = buf.st_dev; \
+ exclude_paths[exclude++].st_ino = buf.st_ino;
+static int old_add_exclude(char *path)
+{
+ int i;
+ char *filename;
+ struct stat buf;
+
+ if(path[0] == '/' || strncmp(path, "./", 2) == 0 ||
+ strncmp(path, "../", 3) == 0) {
+ if(lstat(path, &buf) == -1) {
+ ERROR_START("Cannot stat exclude dir/file %s because "
+ "%s", path, strerror(errno));
+ ERROR_EXIT(", ignoring\n");
+ return TRUE;
+ }
+ ADD_ENTRY(buf);
+ return TRUE;
+ }
+
+ for(i = 0; i < source; i++) {
+ int res = asprintf(&filename, "%s/%s", source_path[i], path);
+ if(res == -1)
+ BAD_ERROR("asprintf failed in old_add_exclude\n");
+ if(lstat(filename, &buf) == -1) {
+ if(!(errno == ENOENT || errno == ENOTDIR)) {
+ ERROR_START("Cannot stat exclude dir/file %s "
+ "because %s", filename, strerror(errno));
+ ERROR_EXIT(", ignoring\n");
+ }
+ free(filename);
+ continue;
+ }
+ free(filename);
+ ADD_ENTRY(buf);
+ }
+ return TRUE;
+}
+
+
+static void add_old_root_entry(char *name, squashfs_inode inode,
+ unsigned int inode_number, int type)
+{
+ old_root_entry = realloc(old_root_entry,
+ sizeof(struct old_root_entry_info) * (old_root_entries + 1));
+ if(old_root_entry == NULL)
+ MEM_ERROR();
+
+ old_root_entry[old_root_entries].name = strdup(name);
+ old_root_entry[old_root_entries].inode.inode = inode;
+ old_root_entry[old_root_entries].inode.inode_number = inode_number;
+ old_root_entry[old_root_entries].inode.type = type;
+ old_root_entry[old_root_entries++].inode.root_entry = TRUE;
+}
+
+
+static void initialise_threads(int readq, int fragq, int bwriteq, int fwriteq,
+ int freelst, char *destination_file)
+{
+ int i;
+ sigset_t sigmask, old_mask;
+ int total_mem = readq;
+ int reader_size;
+ int fragment_size;
+ int fwriter_size;
+ /*
+ * bwriter_size is global because it is needed in
+ * write_file_blocks_dup()
+ */
+
+ /*
+ * Never allow the total size of the queues to be larger than
+ * physical memory
+ *
+ * When adding together the possibly user supplied values, make
+ * sure they've not been deliberately contrived to overflow an int
+ */
+ if(add_overflow(total_mem, fragq))
+ BAD_ERROR("Queue sizes rediculously too large\n");
+ total_mem += fragq;
+ if(add_overflow(total_mem, bwriteq))
+ BAD_ERROR("Queue sizes rediculously too large\n");
+ total_mem += bwriteq;
+ if(add_overflow(total_mem, fwriteq))
+ BAD_ERROR("Queue sizes rediculously too large\n");
+ total_mem += fwriteq;
+
+ check_usable_phys_mem(total_mem);
+
+ /*
+ * convert from queue size in Mbytes to queue size in
+ * blocks.
+ *
+ * This isn't going to overflow an int unless there exists
+ * systems with more than 8 Petabytes of RAM!
+ */
+ reader_size = readq << (20 - block_log);
+ fragment_size = fragq << (20 - block_log);
+ bwriter_size = bwriteq << (20 - block_log);
+ fwriter_size = fwriteq << (20 - block_log);
+
+ /*
+ * setup signal handlers for the main thread, these cleanup
+ * deleting the destination file, if appending the
+ * handlers for SIGTERM and SIGINT will be replaced with handlers
+ * allowing the user to press ^C twice to restore the existing
+ * filesystem.
+ *
+ * SIGUSR1 is an internal signal, which is used by the sub-threads
+ * to tell the main thread to terminate, deleting the destination file,
+ * or if necessary restoring the filesystem on appending
+ */
+ signal(SIGTERM, sighandler);
+ signal(SIGINT, sighandler);
+ signal(SIGUSR1, sighandler);
+
+ /* block SIGQUIT and SIGHUP, these are handled by the info thread */
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGQUIT);
+ sigaddset(&sigmask, SIGHUP);
+ if(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) != 0)
+ BAD_ERROR("Failed to set signal mask in intialise_threads\n");
+
+ /*
+ * temporarily block these signals, so the created sub-threads
+ * will ignore them, ensuring the main thread handles them
+ */
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGINT);
+ sigaddset(&sigmask, SIGTERM);
+ sigaddset(&sigmask, SIGUSR1);
+ if(pthread_sigmask(SIG_BLOCK, &sigmask, &old_mask) != 0)
+ BAD_ERROR("Failed to set signal mask in intialise_threads\n");
+
+ if(processors == -1) {
+#ifdef __linux__
+ cpu_set_t cpu_set;
+ CPU_ZERO(&cpu_set);
+
+ if(sched_getaffinity(0, sizeof cpu_set, &cpu_set) == -1)
+ processors = sysconf(_SC_NPROCESSORS_ONLN);
+ else
+ processors = CPU_COUNT(&cpu_set);
+#else
+ int mib[2];
+ size_t len = sizeof(processors);
+
+ mib[0] = CTL_HW;
+#ifdef HW_AVAILCPU
+ mib[1] = HW_AVAILCPU;
+#else
+ mib[1] = HW_NCPU;
+#endif
+
+ if(sysctl(mib, 2, &processors, &len, NULL, 0) == -1) {
+ ERROR_START("Failed to get number of available "
+ "processors.");
+ ERROR_EXIT(" Defaulting to 1\n");
+ processors = 1;
+ }
+#endif
+ }
+
+ if(multiply_overflow(processors, 3) ||
+ multiply_overflow(processors * 3, sizeof(pthread_t)))
+ BAD_ERROR("Processors too large\n");
+
+ deflator_thread = malloc(processors * 3 * sizeof(pthread_t));
+ if(deflator_thread == NULL)
+ MEM_ERROR();
+
+ frag_deflator_thread = &deflator_thread[processors];
+ frag_thread = &frag_deflator_thread[processors];
+
+ to_reader = queue_init(1);
+ to_deflate = queue_init(reader_size);
+ to_process_frag = queue_init(reader_size);
+ to_writer = queue_init(bwriter_size + fwriter_size);
+ from_writer = queue_init(1);
+ to_frag = queue_init(fragment_size);
+ to_main = seq_queue_init();
+ if(reproducible)
+ to_order = seq_queue_init();
+ else
+ locked_fragment = queue_init(fragment_size);
+ reader_buffer = cache_init(block_size, reader_size, 0, 0);
+ bwriter_buffer = cache_init(block_size, bwriter_size, 1, freelst);
+ fwriter_buffer = cache_init(block_size, fwriter_size, 1, freelst);
+ fragment_buffer = cache_init(block_size, fragment_size, 1, 0);
+ reserve_cache = cache_init(block_size, processors + 1, 1, 0);
+ pthread_create(&reader_thread, NULL, reader, NULL);
+ pthread_create(&writer_thread, NULL, writer, NULL);
+ init_progress_bar();
+ init_info();
+
+ for(i = 0; i < processors; i++) {
+ if(pthread_create(&deflator_thread[i], NULL, deflator, NULL))
+ BAD_ERROR("Failed to create thread\n");
+ if(pthread_create(&frag_deflator_thread[i], NULL, reproducible ?
+ frag_order_deflator : frag_deflator, NULL) != 0)
+ BAD_ERROR("Failed to create thread\n");
+ if(pthread_create(&frag_thread[i], NULL, frag_thrd,
+ (void *) destination_file) != 0)
+ BAD_ERROR("Failed to create thread\n");
+ }
+
+ main_thread = pthread_self();
+
+ if(reproducible)
+ pthread_create(&order_thread, NULL, frag_orderer, NULL);
+
+ if(!quiet)
+ printf("Parallel mksquashfs: Using %d processor%s\n", processors,
+ processors == 1 ? "" : "s");
+
+ /* Restore the signal mask for the main thread */
+ if(pthread_sigmask(SIG_SETMASK, &old_mask, NULL) != 0)
+ BAD_ERROR("Failed to set signal mask in intialise_threads\n");
+}
+
+
+static long long write_inode_lookup_table()
+{
+ int i, lookup_bytes = SQUASHFS_LOOKUP_BYTES(inode_count);
+ unsigned int inode_number;
+ void *it;
+
+ if(inode_count == sinode_count)
+ goto skip_inode_hash_table;
+
+ it = realloc(inode_lookup_table, lookup_bytes);
+ if(it == NULL)
+ MEM_ERROR();
+ inode_lookup_table = it;
+
+ for(i = 0; i < INODE_HASH_SIZE; i ++) {
+ struct inode_info *inode;
+
+ for(inode = inode_info[i]; inode; inode = inode->next) {
+
+ inode_number = get_inode_no(inode);
+
+ /* The empty action will produce orphaned inode
+ * entries in the inode_info[] table. These
+ * entries because they are orphaned will not be
+ * allocated an inode number in dir_scan5(), so
+ * skip any entries with the default dummy inode
+ * number of 0 */
+ if(inode_number == 0)
+ continue;
+
+ SQUASHFS_SWAP_LONG_LONGS(&inode->inode,
+ &inode_lookup_table[inode_number - 1], 1);
+
+ }
+ }
+
+skip_inode_hash_table:
+ return generic_write_table(lookup_bytes, inode_lookup_table, 0, NULL,
+ noI);
+}
+
+
+static char *get_component(char *target, char **targname)
+{
+ char *start;
+
+ while(*target == '/')
+ target ++;
+
+ start = target;
+ while(*target != '/' && *target != '\0')
+ target ++;
+
+ *targname = strndup(start, target - start);
+
+ while(*target == '/')
+ target ++;
+
+ return target;
+}
+
+
+static void free_path(struct pathname *paths)
+{
+ int i;
+
+ for(i = 0; i < paths->names; i++) {
+ if(paths->name[i].paths)
+ free_path(paths->name[i].paths);
+ free(paths->name[i].name);
+ if(paths->name[i].preg) {
+ regfree(paths->name[i].preg);
+ free(paths->name[i].preg);
+ }
+ }
+
+ free(paths);
+}
+
+
+static struct pathname *add_path(struct pathname *paths, char *target, char *alltarget)
+{
+ char *targname;
+ int i, error;
+
+ target = get_component(target, &targname);
+
+ if(paths == NULL) {
+ paths = malloc(sizeof(struct pathname));
+ if(paths == NULL)
+ MEM_ERROR();
+
+ paths->names = 0;
+ paths->name = NULL;
+ }
+
+ for(i = 0; i < paths->names; i++)
+ if(strcmp(paths->name[i].name, targname) == 0)
+ break;
+
+ if(i == paths->names) {
+ /* allocate new name entry */
+ paths->names ++;
+ paths->name = realloc(paths->name, (i + 1) *
+ sizeof(struct path_entry));
+ if(paths->name == NULL)
+ MEM_ERROR();
+ paths->name[i].name = targname;
+ paths->name[i].paths = NULL;
+ if(use_regex) {
+ paths->name[i].preg = malloc(sizeof(regex_t));
+ if(paths->name[i].preg == NULL)
+ MEM_ERROR();
+ error = regcomp(paths->name[i].preg, targname,
+ REG_EXTENDED|REG_NOSUB);
+ if(error) {
+ char str[1024]; /* overflow safe */
+
+ regerror(error, paths->name[i].preg, str, 1024);
+ BAD_ERROR("invalid regex %s in export %s, "
+ "because %s\n", targname, alltarget,
+ str);
+ }
+ } else
+ paths->name[i].preg = NULL;
+
+ if(target[0] == '\0')
+ /* at leaf pathname component */
+ paths->name[i].paths = NULL;
+ else
+ /* recurse adding child components */
+ paths->name[i].paths = add_path(NULL, target,
+ alltarget);
+ } else {
+ /* existing matching entry */
+ free(targname);
+
+ if(paths->name[i].paths == NULL) {
+ /* No sub-directory which means this is the leaf
+ * component of a pre-existing exclude which subsumes
+ * the exclude currently being added, in which case stop
+ * adding components */
+ } else if(target[0] == '\0') {
+ /* at leaf pathname component and child components exist
+ * from more specific excludes, delete as they're
+ * subsumed by this exclude */
+ free_path(paths->name[i].paths);
+ paths->name[i].paths = NULL;
+ } else
+ /* recurse adding child components */
+ add_path(paths->name[i].paths, target, alltarget);
+ }
+
+ return paths;
+}
+
+
+static void add_exclude(char *target)
+{
+
+ if(target[0] == '/' || strncmp(target, "./", 2) == 0 ||
+ strncmp(target, "../", 3) == 0)
+ BAD_ERROR("/, ./ and ../ prefixed excludes not supported with "
+ "-wildcards or -regex options\n");
+ else if(strncmp(target, "... ", 4) == 0)
+ stickypath = add_path(stickypath, target + 4, target + 4);
+ else
+ path = add_path(path, target, target);
+}
+
+
+static struct pathnames *add_subdir(struct pathnames *paths, struct pathname *path)
+{
+ int count = paths == NULL ? 0 : paths->count;
+
+ if(count % PATHS_ALLOC_SIZE == 0) {
+ paths = realloc(paths, sizeof(struct pathnames) +
+ (count + PATHS_ALLOC_SIZE) * sizeof(struct pathname *));
+ if(paths == NULL)
+ MEM_ERROR();
+ }
+
+ paths->path[count] = path;
+ paths->count = count + 1;
+ return paths;
+}
+
+
+static int excluded_match(char *name, struct pathname *path, struct pathnames **new)
+{
+ int i;
+
+ for(i = 0; i < path->names; i++) {
+ int match = use_regex ?
+ regexec(path->name[i].preg, name, (size_t) 0,
+ NULL, 0) == 0 :
+ fnmatch(path->name[i].name, name,
+ FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0;
+
+ if(match) {
+ if(path->name[i].paths == NULL) {
+ /* match on a leaf component, any subdirectories
+ * in the filesystem should be excluded */
+ free(*new);
+ *new = NULL;
+ return TRUE;
+ } else
+ /* match on a non-leaf component, add any
+ * subdirectories to the new set of
+ * subdirectories to scan for this name */
+ *new = add_subdir(*new, path->name[i].paths);
+ }
+ }
+
+ return FALSE;
+}
+
+
+int excluded(char *name, struct pathnames *paths, struct pathnames **new)
+{
+ int n;
+
+ if(stickypath && excluded_match(name, stickypath, new))
+ return TRUE;
+
+ for(n = 0; paths && n < paths->count; n++) {
+ int res = excluded_match(name, paths->path[n], new);
+ if(res)
+ return TRUE;
+ }
+
+ /*
+ * Either:
+ * - no matching names found, return empty new search set, or
+ * - one or more matches with sub-directories found (no leaf matches),
+ * in which case return new search set.
+ *
+ * In either case return FALSE as we don't want to exclude this entry
+ */
+ return FALSE;
+}
+
+
+static void process_exclude_file(char *argv)
+{
+ FILE *fd;
+ char buffer[MAX_LINE + 1]; /* overflow safe */
+ char *filename;
+
+ fd = fopen(argv, "r");
+ if(fd == NULL)
+ BAD_ERROR("Failed to open exclude file \"%s\" because %s\n",
+ argv, strerror(errno));
+
+ while(fgets(filename = buffer, MAX_LINE + 1, fd) != NULL) {
+ int len = strlen(filename);
+
+ if(len == MAX_LINE && filename[len - 1] != '\n')
+ /* line too large */
+ BAD_ERROR("Line too long when reading "
+ "exclude file \"%s\", larger than %d "
+ "bytes\n", argv, MAX_LINE);
+
+ /*
+ * Remove '\n' terminator if it exists (the last line
+ * in the file may not be '\n' terminated)
+ */
+ if(len && filename[len - 1] == '\n')
+ filename[len - 1] = '\0';
+
+ /* Skip any leading whitespace */
+ while(isspace(*filename))
+ filename ++;
+
+ /* if comment line, skip */
+ if(*filename == '#')
+ continue;
+
+ /*
+ * check for initial backslash, to accommodate
+ * filenames with leading space or leading # character
+ */
+ if(*filename == '\\')
+ filename ++;
+
+ /* if line is now empty after skipping characters, skip it */
+ if(*filename == '\0')
+ continue;
+
+ if(old_exclude)
+ old_add_exclude(filename);
+ else
+ add_exclude(filename);
+ }
+
+ if(ferror(fd))
+ BAD_ERROR("Reading exclude file \"%s\" failed because %s\n",
+ argv, strerror(errno));
+
+ fclose(fd);
+}
+
+
+#define RECOVER_ID "Squashfs recovery file v1.0\n"
+#define RECOVER_ID_SIZE 28
+
+static void write_recovery_data(struct squashfs_super_block *sBlk)
+{
+ int recoverfd;
+ long long res, bytes = sBlk->bytes_used - sBlk->inode_table_start;
+ pid_t pid = getpid();
+ char *metadata;
+ char header[] = RECOVER_ID;
+
+ if(recover == FALSE) {
+ if(!quiet) {
+ printf("No recovery data option specified.\n");
+ printf("Skipping saving recovery file.\n\n");
+ }
+
+ return;
+ }
+
+ if(recovery_pathname == NULL) {
+ recovery_pathname = getenv("HOME");
+ if(recovery_pathname == NULL)
+ BAD_ERROR("Could not read $HOME, use -recovery-path or -no-recovery options\n");
+ }
+
+ res = asprintf(&recovery_file, "%s/squashfs_recovery_%s_%d", recovery_pathname,
+ getbase(destination_file), pid);
+ if(res == -1)
+ MEM_ERROR();
+
+ metadata = malloc(bytes);
+ if(metadata == NULL)
+ MEM_ERROR();
+
+ res = read_fs_bytes(fd, sBlk->inode_table_start, bytes, metadata);
+ if(res == 0) {
+ ERROR("Failed to read append filesystem metadata\n");
+ BAD_ERROR("Filesystem corrupted?\n");
+ }
+
+ recoverfd = open(recovery_file, O_CREAT | O_TRUNC | O_RDWR, S_IRWXU);
+ if(recoverfd == -1)
+ BAD_ERROR("Failed to create recovery file, because %s. "
+ "Aborting\n", strerror(errno));
+
+ if(write_bytes(recoverfd, header, RECOVER_ID_SIZE) == -1)
+ BAD_ERROR("Failed to write recovery file, because %s\n",
+ strerror(errno));
+
+ if(write_bytes(recoverfd, sBlk, sizeof(struct squashfs_super_block)) == -1)
+ BAD_ERROR("Failed to write recovery file, because %s\n",
+ strerror(errno));
+
+ if(write_bytes(recoverfd, metadata, bytes) == -1)
+ BAD_ERROR("Failed to write recovery file, because %s\n",
+ strerror(errno));
+
+ res = close(recoverfd);
+
+ if(res == -1)
+ BAD_ERROR("Failed to close recovery file, close returned %s\n",
+ strerror(errno));
+
+ free(metadata);
+
+ printf("Recovery file \"%s\" written\n", recovery_file);
+ printf("If Mksquashfs aborts abnormally (i.e. power failure), run\n");
+ printf("mksquashfs - %s -recover %s\n", destination_file,
+ recovery_file);
+ printf("to restore filesystem\n\n");
+}
+
+
+static void read_recovery_data(char *recovery_file, char *destination_file)
+{
+ int fd, recoverfd;
+ struct squashfs_super_block orig_sBlk, sBlk;
+ char *metadata;
+ long long res, bytes;
+ struct stat buf;
+ char header[] = RECOVER_ID;
+ char header2[RECOVER_ID_SIZE];
+
+ recoverfd = open(recovery_file, O_RDONLY);
+ if(recoverfd == -1)
+ BAD_ERROR("Failed to open recovery file because %s\n",
+ strerror(errno));
+
+ if(stat(destination_file, &buf) == -1)
+ BAD_ERROR("Failed to stat destination file, because %s\n",
+ strerror(errno));
+
+ fd = open(destination_file, O_RDWR);
+ if(fd == -1)
+ BAD_ERROR("Failed to open destination file because %s\n",
+ strerror(errno));
+
+ res = read_bytes(recoverfd, header2, RECOVER_ID_SIZE);
+ if(res == -1)
+ BAD_ERROR("Failed to read recovery file, because %s\n",
+ strerror(errno));
+ if(res < RECOVER_ID_SIZE)
+ BAD_ERROR("Recovery file appears to be truncated\n");
+ if(strncmp(header, header2, RECOVER_ID_SIZE) !=0 )
+ BAD_ERROR("Not a recovery file\n");
+
+ res = read_bytes(recoverfd, &sBlk, sizeof(struct squashfs_super_block));
+ if(res == -1)
+ BAD_ERROR("Failed to read recovery file, because %s\n",
+ strerror(errno));
+ if(res < sizeof(struct squashfs_super_block))
+ BAD_ERROR("Recovery file appears to be truncated\n");
+
+ res = read_fs_bytes(fd, 0, sizeof(struct squashfs_super_block), &orig_sBlk);
+ if(res == 0) {
+ ERROR("Failed to read superblock from output filesystem\n");
+ BAD_ERROR("Output filesystem is empty!\n");
+ }
+
+ if(memcmp(((char *) &sBlk) + 4, ((char *) &orig_sBlk) + 4,
+ sizeof(struct squashfs_super_block) - 4) != 0)
+ BAD_ERROR("Recovery file and destination file do not seem to "
+ "match\n");
+
+ bytes = sBlk.bytes_used - sBlk.inode_table_start;
+
+ metadata = malloc(bytes);
+ if(metadata == NULL)
+ MEM_ERROR();
+
+ res = read_bytes(recoverfd, metadata, bytes);
+ if(res == -1)
+ BAD_ERROR("Failed to read recovery file, because %s\n",
+ strerror(errno));
+ if(res < bytes)
+ BAD_ERROR("Recovery file appears to be truncated\n");
+
+ write_destination(fd, 0, sizeof(struct squashfs_super_block), &sBlk);
+
+ write_destination(fd, sBlk.inode_table_start, bytes, metadata);
+
+ res = close(recoverfd);
+
+ if(res == -1)
+ BAD_ERROR("Failed to close recovery file, close returned %s\n",
+ strerror(errno));
+
+ res = close(fd);
+
+ if(res == -1)
+ BAD_ERROR("Failed to close output filesystem, close returned %s\n",
+ strerror(errno));
+
+ printf("Successfully wrote recovery file \"%s\". Exiting\n",
+ recovery_file);
+
+ exit(0);
+}
+
+
+static void write_filesystem_tables(struct squashfs_super_block *sBlk)
+{
+ sBlk->fragments = fragments;
+ sBlk->no_ids = id_count;
+ sBlk->inode_table_start = write_inodes();
+ sBlk->directory_table_start = write_directories();
+ sBlk->fragment_table_start = write_fragment_table();
+ sBlk->lookup_table_start = exportable ? write_inode_lookup_table() :
+ SQUASHFS_INVALID_BLK;
+ sBlk->id_table_start = write_id_table();
+ sBlk->xattr_id_table_start = write_xattrs();
+
+ TRACE("sBlk->inode_table_start 0x%llx\n", sBlk->inode_table_start);
+ TRACE("sBlk->directory_table_start 0x%llx\n",
+ sBlk->directory_table_start);
+ TRACE("sBlk->fragment_table_start 0x%llx\n", sBlk->fragment_table_start);
+ if(exportable)
+ TRACE("sBlk->lookup_table_start 0x%llx\n",
+ sBlk->lookup_table_start);
+
+ sBlk->bytes_used = bytes;
+
+ sBlk->compression = comp->id;
+
+ SQUASHFS_INSWAP_SUPER_BLOCK(sBlk);
+ write_destination(fd, SQUASHFS_START, sizeof(*sBlk), sBlk);
+
+ total_bytes += total_inode_bytes + total_directory_bytes +
+ sizeof(struct squashfs_super_block) + total_xattr_bytes;
+}
+
+
+static int _parse_numberll(char *start, long long *res, int size, int base)
+{
+ char *end;
+ long long number;
+
+ errno = 0; /* To distinguish success/failure after call */
+
+ number = strtoll(start, &end, base);
+
+ /*
+ * check for strtoll underflow or overflow in conversion, and other
+ * errors.
+ */
+ if((errno == ERANGE && (number == LLONG_MIN || number == LLONG_MAX)) ||
+ (errno != 0 && number == 0))
+ return 0;
+
+ /* reject negative numbers as invalid */
+ if(number < 0)
+ return 0;
+
+ if(size) {
+ /*
+ * Check for multiplier and trailing junk.
+ * But first check that a number exists before the
+ * multiplier
+ */
+ if(end == start)
+ return 0;
+
+ switch(end[0]) {
+ case 'g':
+ case 'G':
+ if(multiply_overflowll(number, 1073741824))
+ return 0;
+ number *= 1073741824;
+
+ if(end[1] != '\0')
+ /* trailing junk after multiplier, but
+ * allow it to be "bytes" */
+ if(strcmp(end + 1, "bytes"))
+ return 0;
+
+ break;
+ case 'm':
+ case 'M':
+ if(multiply_overflowll(number, 1048576))
+ return 0;
+ number *= 1048576;
+
+ if(end[1] != '\0')
+ /* trailing junk after multiplier, but
+ * allow it to be "bytes" */
+ if(strcmp(end + 1, "bytes"))
+ return 0;
+
+ break;
+ case 'k':
+ case 'K':
+ if(multiply_overflowll(number, 1024))
+ return 0;
+ number *= 1024;
+
+ if(end[1] != '\0')
+ /* trailing junk after multiplier, but
+ * allow it to be "bytes" */
+ if(strcmp(end + 1, "bytes"))
+ return 0;
+
+ break;
+ case '\0':
+ break;
+ default:
+ /* trailing junk after number */
+ return 0;
+ }
+ } else if(end[0] != '\0')
+ /* trailing junk after number */
+ return 0;
+
+ *res = number;
+ return 1;
+}
+
+
+static int parse_numberll(char *start, long long *res, int size)
+{
+ return _parse_numberll(start, res, size, 10);
+}
+
+
+static int parse_number(char *start, int *res, int size)
+{
+ long long number;
+
+ if(!_parse_numberll(start, &number, size, 10))
+ return 0;
+
+ /* check if long result will overflow signed int */
+ if(number > INT_MAX)
+ return 0;
+
+ *res = (int) number;
+ return 1;
+}
+
+
+static int parse_number_unsigned(char *start, unsigned int *res, int size)
+{
+ long long number;
+
+ if(!_parse_numberll(start, &number, size, 10))
+ return 0;
+
+ /* check if long result will overflow unsigned int */
+ if(number > UINT_MAX)
+ return 0;
+
+ *res = (unsigned int) number;
+ return 1;
+}
+
+
+static int parse_num(char *arg, int *res)
+{
+ return parse_number(arg, res, 0);
+}
+
+
+static int parse_num_unsigned(char *arg, unsigned int *res)
+{
+ return parse_number_unsigned(arg, res, 0);
+}
+
+
+static int parse_mode(char *arg, mode_t *res)
+{
+ long long number;
+
+ if(!_parse_numberll(arg, &number, 0, 8))
+ return 0;
+
+ if(number > 07777)
+ return 0;
+
+ *res = (mode_t) number;
+ return 1;
+}
+
+
+static int get_physical_memory()
+{
+ /*
+ * Long longs are used here because with PAE, a 32-bit
+ * machine can have more than 4GB of physical memory
+ *
+ * sysconf(_SC_PHYS_PAGES) relies on /proc being mounted.
+ * If it fails use sysinfo, if that fails return 0
+ */
+ long long num_pages = sysconf(_SC_PHYS_PAGES);
+ long long page_size = sysconf(_SC_PAGESIZE);
+ int phys_mem;
+
+#ifdef __linux__
+ if(num_pages == -1 || page_size == -1) {
+ struct sysinfo sys;
+ int res = sysinfo(&sys);
+
+ if(res == -1)
+ return 0;
+
+ num_pages = sys.totalram;
+ page_size = sys.mem_unit;
+ }
+#endif
+
+ phys_mem = num_pages * page_size >> 20;
+
+ if(phys_mem < SQUASHFS_LOWMEM)
+ BAD_ERROR("Mksquashfs requires more physical memory than is "
+ "available!\n");
+
+ return phys_mem;
+}
+
+
+static void check_usable_phys_mem(int total_mem)
+{
+ /*
+ * We want to allow users to use as much of their physical
+ * memory as they wish. However, for practical reasons there are
+ * limits which need to be imposed, to protect users from themselves
+ * and to prevent people from using Mksquashfs as a DOS attack by using
+ * all physical memory. Mksquashfs uses memory to cache data from disk
+ * to optimise performance. It is pointless to ask it to use more
+ * than 75% of physical memory, as this causes thrashing and it is thus
+ * self-defeating.
+ */
+ int mem = get_physical_memory();
+
+ mem = (mem >> 1) + (mem >> 2); /* 75% */
+
+ if(total_mem > mem && mem) {
+ ERROR("Total memory requested is more than 75%% of physical "
+ "memory.\n");
+ ERROR("Mksquashfs uses memory to cache data from disk to "
+ "optimise performance.\n");
+ ERROR("It is pointless to ask it to use more than this amount "
+ "of memory, as this\n");
+ ERROR("causes thrashing and it is thus self-defeating.\n");
+ BAD_ERROR("Requested memory size too large\n");
+ }
+
+ if(sizeof(void *) == 4 && total_mem > 2048) {
+ /*
+ * If we're running on a kernel with PAE or on a 64-bit kernel,
+ * then the 75% physical memory limit can still easily exceed
+ * the addressable memory by this process.
+ *
+ * Due to the typical kernel/user-space split (1GB/3GB, or
+ * 2GB/2GB), we have to conservatively assume the 32-bit
+ * processes can only address 2-3GB. So refuse if the user
+ * tries to allocate more than 2GB.
+ */
+ ERROR("Total memory requested may exceed maximum "
+ "addressable memory by this process\n");
+ BAD_ERROR("Requested memory size too large\n");
+ }
+}
+
+
+static int get_default_phys_mem()
+{
+ /*
+ * get_physical_memory() relies on /proc being mounted.
+ * If it fails, issue a warning, and use
+ * SQUASHFS_LOWMEM / SQUASHFS_TAKE as default,
+ * and allow a larger value to be set with -mem.
+ */
+ int mem = get_physical_memory();
+
+ if(mem == 0) {
+ mem = SQUASHFS_LOWMEM / SQUASHFS_TAKE;
+
+ ERROR("Warning: Cannot get size of physical memory, probably "
+ "because /proc is missing.\n");
+ ERROR("Warning: Defaulting to minimal use of %d Mbytes, use "
+ "-mem to set a better value,\n", mem);
+ ERROR("Warning: or fix /proc.\n");
+ } else
+ mem /= SQUASHFS_TAKE;
+
+ if(sizeof(void *) == 4 && mem > 640) {
+ /*
+ * If we're running on a kernel with PAE or on a 64-bit kernel,
+ * the default memory usage can exceed the addressable
+ * memory by this process.
+ * Due to the typical kernel/user-space split (1GB/3GB, or
+ * 2GB/2GB), we have to conservatively assume the 32-bit
+ * processes can only address 2-3GB. So limit the default
+ * usage to 640M, which gives room for other data.
+ */
+ mem = 640;
+ }
+
+ return mem;
+}
+
+
+static void calculate_queue_sizes(int mem, int *readq, int *fragq, int *bwriteq,
+ int *fwriteq)
+{
+ *readq = mem / SQUASHFS_READQ_MEM;
+ *bwriteq = mem / SQUASHFS_BWRITEQ_MEM;
+ *fwriteq = mem / SQUASHFS_FWRITEQ_MEM;
+ *fragq = mem - *readq - *bwriteq - *fwriteq;
+}
+
+
+static void open_log_file(char *filename)
+{
+ log_fd=fopen(filename, "w");
+ if(log_fd == NULL)
+ BAD_ERROR("Failed to open log file \"%s\" because %s\n", filename, strerror(errno));
+
+ logging=TRUE;
+}
+
+
+static void check_env_var()
+{
+ char *time_string = getenv("SOURCE_DATE_EPOCH");
+ unsigned int time;
+
+ if(time_string != NULL) {
+ /*
+ * We cannot have both command-line options and environment
+ * variable trying to set the timestamp(s) at the same
+ * time. Semantically both are FORCE options which cannot be
+ * over-ridden elsewhere (otherwise they can't be relied on).
+ *
+ * So refuse to continue if both are set.
+ */
+ if(mkfs_time_opt || all_time_opt)
+ BAD_ERROR("SOURCE_DATE_EPOCH and command line options "
+ "can't be used at the same time to set "
+ "timestamp(s)\n");
+
+ if(!parse_num_unsigned(time_string, &time)) {
+ ERROR("Env Var SOURCE_DATE_EPOCH has invalid time value\n");
+ EXIT_MKSQUASHFS();
+ }
+
+ all_time = mkfs_time = time;
+ all_time_opt = mkfs_time_opt = TRUE;
+ }
+}
+
+
+static void print_options(FILE *stream, char *name, int total_mem)
+{
+ fprintf(stream, "SYNTAX:%s source1 source2 ... FILESYSTEM [OPTIONS] ", name);
+ fprintf(stream, "[-e list of\nexclude dirs/files]\n");
+ fprintf(stream, "\nFilesystem compression options:\n");
+ fprintf(stream, "-b <block_size>\t\tset data block to <block_size>. Default ");
+ fprintf(stream, "128 Kbytes.\n");
+ fprintf(stream, "\t\t\tOptionally a suffix of K or M can be given to ");
+ fprintf(stream, "specify\n\t\t\tKbytes or Mbytes respectively\n");
+ fprintf(stream, "-comp <comp>\t\tselect <comp> compression\n");
+ fprintf(stream, "\t\t\tCompressors available:\n");
+ display_compressors(stream, "\t\t\t", COMP_DEFAULT);
+ fprintf(stream, "-noI\t\t\tdo not compress inode table\n");
+ fprintf(stream, "-noId\t\t\tdo not compress the uid/gid table (implied by ");
+ fprintf(stream, "-noI)\n");
+ fprintf(stream, "-noD\t\t\tdo not compress data blocks\n");
+ fprintf(stream, "-noF\t\t\tdo not compress fragment blocks\n");
+ fprintf(stream, "-noX\t\t\tdo not compress extended attributes\n");
+ fprintf(stream, "-no-compression\t\tdo not compress any of the data ");
+ fprintf(stream, "or metadata. This is\n\t\t\tequivalent to ");
+ fprintf(stream, "specifying -noI -noD -noF and -noX\n");
+ fprintf(stream, "\nFilesystem build options:\n");
+ fprintf(stream, "-tar\t\t\tread uncompressed tar file from standard in (stdin)\n");
+ fprintf(stream, "-no-strip\t\tact like tar, and do not strip leading ");
+ fprintf(stream, "directories\n\t\t\tfrom source files\n");
+ fprintf(stream, "-tarstyle\t\talternative name for -no-strip\n");
+ fprintf(stream, "-cpiostyle\t\tact like cpio, and read file ");
+ fprintf(stream, "pathnames from standard in\n\t\t\t(stdin)\n");
+ fprintf(stream, "-cpiostyle0\t\tlike -cpiostyle, but filenames are ");
+ fprintf(stream, "null terminated. Can\n\t\t\tbe used with find ");
+ fprintf(stream, "-print0 action\n");
+ fprintf(stream, "-reproducible\t\tbuild filesystems that are reproducible");
+ fprintf(stream, REP_STR "\n");
+ fprintf(stream, "-not-reproducible\tbuild filesystems that are not reproducible");
+ fprintf(stream, NOREP_STR "\n");
+ fprintf(stream, "-mkfs-time <time>\tset filesystem creation ");
+ fprintf(stream, "timestamp to <time>. <time> can\n\t\t\tbe an ");
+ fprintf(stream, "unsigned 32-bit int indicating seconds since the\n");
+ fprintf(stream, "\t\t\tepoch (1970-01-01) or a string value which ");
+ fprintf(stream, "is passed to\n\t\t\tthe \"date\" command to ");
+ fprintf(stream, "parse. Any string value which the\n\t\t\tdate ");
+ fprintf(stream, "command recognises can be used such as \"now\",\n");
+ fprintf(stream, "\t\t\t\"last week\", or \"Wed Feb 15 21:02:39 ");
+ fprintf(stream, "GMT 2023\"\n");
+ fprintf(stream, "-all-time <time>\tset all file timestamps to ");
+ fprintf(stream, "<time>. <time> can be an\n\t\t\tunsigned 32-bit ");
+ fprintf(stream, "int indicating seconds since the epoch\n\t\t\t");
+ fprintf(stream, "(1970-01-01) or a string value which is passed to ");
+ fprintf(stream, "the\n\t\t\t\"date\" command to parse. Any string ");
+ fprintf(stream, "value which the date\n\t\t\tcommand recognises can ");
+ fprintf(stream, "be used such as \"now\", \"last\n\t\t\tweek\", or ");
+ fprintf(stream, "\"Wed Feb 15 21:02:39 GMT 2023\"\n");
+ fprintf(stream, "-root-time <time>\tset root directory time to ");
+ fprintf(stream, "<time>. <time> can be an\n\t\t\tunsigned 32-bit ");
+ fprintf(stream, "int indicating seconds since the epoch\n\t\t\t");
+ fprintf(stream, "(1970-01-01) or a string value which is passed to ");
+ fprintf(stream, "the\n\t\t\t\"date\" command to parse. Any string ");
+ fprintf(stream, "value which the date\n\t\t\tcommand recognises can ");
+ fprintf(stream, "be used such as \"now\", \"last\n\t\t\tweek\", or ");
+ fprintf(stream, "\"Wed Feb 15 21:02:39 GMT 2023\"\n");
+ fprintf(stream, "-root-mode <mode>\tset root directory permissions ");
+ fprintf(stream, "to octal <mode>\n");
+ fprintf(stream, "-root-uid <value>\tset root directory owner to ");
+ fprintf(stream, "specified <value>,\n\t\t\t<value> can be either an ");
+ fprintf(stream, "integer uid or user name\n");
+ fprintf(stream, "-root-gid <value>\tset root directory group to ");
+ fprintf(stream, "specified <value>,\n\t\t\t<value> can be either an ");
+ fprintf(stream, "integer gid or group name\n");
+ fprintf(stream, "-all-root\t\tmake all files owned by root\n");
+ fprintf(stream, "-force-uid <value>\tset all file uids to specified ");
+ fprintf(stream, "<value>, <value> can be\n\t\t\teither an integer ");
+ fprintf(stream, "uid or user name\n");
+ fprintf(stream, "-force-gid <value>\tset all file gids to specified ");
+ fprintf(stream, "<value>, <value> can be\n\t\t\teither an integer ");
+ fprintf(stream, "gid or group name\n");
+ fprintf(stream, "-pseudo-override\tmake pseudo file uids and gids ");
+ fprintf(stream, "override -all-root,\n\t\t\t-force-uid and ");
+ fprintf(stream, "-force-gid options\n");
+ fprintf(stream, "-no-exports\t\tdo not make filesystem exportable via NFS (-tar default)\n");
+ fprintf(stream, "-exports\t\tmake filesystem exportable via NFS (default)\n");
+ fprintf(stream, "-no-sparse\t\tdo not detect sparse files\n");
+ fprintf(stream, "-no-tailends\t\tdo not pack tail ends into fragments (default)\n");
+ fprintf(stream, "-tailends\t\tpack tail ends into fragments\n");
+ fprintf(stream, "-no-fragments\t\tdo not use fragments\n");
+ fprintf(stream, "-no-duplicates\t\tdo not perform duplicate checking\n");
+ fprintf(stream, "-no-hardlinks\t\tdo not hardlink files, instead store duplicates\n");
+ fprintf(stream, "-keep-as-directory\tif one source directory is specified, ");
+ fprintf(stream, "create a root\n");
+ fprintf(stream, "\t\t\tdirectory containing that directory, rather than the\n");
+ fprintf(stream, "\t\t\tcontents of the directory\n");
+ fprintf(stream, "\nFilesystem filter options:\n");
+ fprintf(stream, "-p <pseudo-definition>\tadd pseudo file ");
+ fprintf(stream, "definition. The definition should\n");
+ fprintf(stream, "\t\t\tbe quoted\n");
+ fprintf(stream, "-pf <pseudo-file>\tadd list of pseudo file ");
+ fprintf(stream, "definitions from <pseudo-file>,\n\t\t\tuse - for ");
+ fprintf(stream, "stdin. Pseudo file definitions should not be\n");
+ fprintf(stream, "\t\t\tquoted\n");
+ fprintf(stream, "-sort <sort_file>\tsort files according to priorities in ");
+ fprintf(stream, "<sort_file>. One\n\t\t\tfile or dir with priority per ");
+ fprintf(stream, "line. Priority -32768 to\n\t\t\t32767, default priority 0\n");
+ fprintf(stream, "-ef <exclude_file>\tlist of exclude dirs/files. ");
+ fprintf(stream, "One per line\n");
+ fprintf(stream, "-wildcards\t\tallow extended shell wildcards (globbing) to be ");
+ fprintf(stream, "used in\n\t\t\texclude dirs/files\n");
+ fprintf(stream, "-regex\t\t\tallow POSIX regular expressions to be used in ");
+ fprintf(stream, "exclude\n\t\t\tdirs/files\n");
+ fprintf(stream, "-max-depth <levels>\tdescend at most <levels> of ");
+ fprintf(stream, "directories when scanning\n\t\t\tfilesystem\n");
+ fprintf(stream, "-one-file-system\tdo not cross filesystem ");
+ fprintf(stream, "boundaries. If a directory\n\t\t\tcrosses the ");
+ fprintf(stream, "boundary, create an empty directory for\n\t\t\teach ");
+ fprintf(stream, "mount point. If a file crosses the boundary\n\t\t\t");
+ fprintf(stream, "ignore it\n");
+ fprintf(stream, "-one-file-system-x\tdo not cross filesystem ");
+ fprintf(stream, "boundaries. Like\n\t\t\t-one-file-system option ");
+ fprintf(stream, "except directories are also\n\t\t\tignored if they ");
+ fprintf(stream, "cross the boundary\n");
+ fprintf(stream, "\nFilesystem extended attribute (xattrs) options:\n");
+ fprintf(stream, "-no-xattrs\t\tdo not store extended attributes" NOXOPT_STR "\n");
+ fprintf(stream, "-xattrs\t\t\tstore extended attributes" XOPT_STR "\n");
+ fprintf(stream, "-xattrs-exclude <regex>\texclude any xattr names ");
+ fprintf(stream, "matching <regex>. <regex> is a\n\t\t\tPOSIX ");
+ fprintf(stream, "regular expression, e.g. -xattrs-exclude ");
+ fprintf(stream, "'^user.'\n\t\t\texcludes xattrs from the user ");
+ fprintf(stream, "namespace\n");
+ fprintf(stream, "-xattrs-include <regex>\tinclude any xattr names ");
+ fprintf(stream, "matching <regex>. <regex> is a\n\t\t\tPOSIX ");
+ fprintf(stream, "regular expression, e.g. -xattrs-include ");
+ fprintf(stream, "'^user.'\n\t\t\tincludes xattrs from the user ");
+ fprintf(stream, "namespace\n");
+ fprintf(stream, "-xattrs-add <name=val>\tadd the xattr <name> with ");
+ fprintf(stream, "<val> to files. If an\n\t\t\tuser xattr it ");
+ fprintf(stream, "will be added to regular files and\n");
+ fprintf(stream, "\t\t\tdirectories (see man 7 xattr). Otherwise it ");
+ fprintf(stream, "will be\n\t\t\tadded to all files. <val> by ");
+ fprintf(stream, "default will be treated as\n\t\t\tbinary (i.e. an ");
+ fprintf(stream, "uninterpreted byte sequence), but it can\n\t\t\tbe ");
+ fprintf(stream, "prefixed with 0s, where it will be treated as ");
+ fprintf(stream, "base64\n\t\t\tencoded, or prefixed with 0x, where ");
+ fprintf(stream, "val will be treated\n\t\t\tas hexidecimal. ");
+ fprintf(stream, "Additionally it can be prefixed with\n\t\t\t0t ");
+ fprintf(stream, "where this encoding is similar to binary encoding,\n");
+ fprintf(stream, "\t\t\texcept backslashes are specially treated, and ");
+ fprintf(stream, "a\n\t\t\tbackslash followed by 3 octal digits can ");
+ fprintf(stream, "be used to\n\t\t\tencode any ASCII character, ");
+ fprintf(stream, "which obviously can be used\n\t\t\tto encode ");
+ fprintf(stream, "control codes. The option can be repeated\n");
+ fprintf(stream, "\t\t\tmultiple times to add multiple xattrs\n");
+ fprintf(stream, "\nMksquashfs runtime options:\n");
+ fprintf(stream, "-version\t\tprint version, licence and copyright message\n");
+ fprintf(stream, "-exit-on-error\t\ttreat normally ignored errors as fatal\n");
+ fprintf(stream, "-quiet\t\t\tno verbose output\n");
+ fprintf(stream, "-info\t\t\tprint files written to filesystem\n");
+ fprintf(stream, "-no-progress\t\tdo not display the progress bar\n");
+ fprintf(stream, "-progress\t\tdisplay progress bar when using the -info ");
+ fprintf(stream, "option\n");
+ fprintf(stream, "-percentage\t\tdisplay a percentage rather than the ");
+ fprintf(stream, "full progress bar.\n\t\t\tCan be used with dialog ");
+ fprintf(stream, "--gauge etc.\n");
+ fprintf(stream, "-throttle <percentage>\tthrottle the I/O input rate by the ");
+ fprintf(stream, "given percentage.\n\t\t\tThis can be used to reduce the I/O ");
+ fprintf(stream, "and CPU consumption\n\t\t\tof Mksquashfs\n");
+ fprintf(stream, "-limit <percentage>\tlimit the I/O input rate to the given ");
+ fprintf(stream, "percentage.\n\t\t\tThis can be used to reduce the I/O and CPU ");
+ fprintf(stream, "consumption\n\t\t\tof Mksquashfs (alternative to -throttle)\n");
+ fprintf(stream, "-processors <number>\tuse <number> processors. By default ");
+ fprintf(stream, "will use number of\n\t\t\tprocessors available\n");
+ fprintf(stream, "-mem <size>\t\tuse <size> physical memory for ");
+ fprintf(stream, "caches. Use K, M or G to\n\t\t\tspecify Kbytes,");
+ fprintf(stream, " Mbytes or Gbytes respectively\n");
+ fprintf(stream, "-mem-percent <percent>\tuse <percent> physical ");
+ fprintf(stream, "memory for caches. Default 25%%\n");
+ fprintf(stream, "-mem-default\t\tprint default memory usage in Mbytes\n");
+ fprintf(stream, "\nFilesystem append options:\n");
+ fprintf(stream, "-noappend\t\tdo not append to existing filesystem\n");
+ fprintf(stream, "-root-becomes <name>\twhen appending source ");
+ fprintf(stream, "files/directories, make the\n");
+ fprintf(stream, "\t\t\toriginal root become a subdirectory in the new root\n");
+ fprintf(stream, "\t\t\tcalled <name>, rather than adding the new source items\n");
+ fprintf(stream, "\t\t\tto the original root\n");
+ fprintf(stream, "-no-recovery\t\tdo not generate a recovery file\n");
+ fprintf(stream, "-recovery-path <name>\tuse <name> as the directory ");
+ fprintf(stream, "to store the recovery file\n");
+ fprintf(stream, "-recover <name>\t\trecover filesystem data using recovery ");
+ fprintf(stream, "file <name>\n");
+ fprintf(stream, "\nFilesystem actions options:\n");
+ fprintf(stream, "-action <action@expr>\tevaluate <expr> on every file, ");
+ fprintf(stream, "and execute <action>\n\t\t\tif it returns TRUE\n");
+ fprintf(stream, "-log-action <act@expr>\tas above, but log expression ");
+ fprintf(stream, "evaluation results and\n\t\t\tactions performed\n");
+ fprintf(stream, "-true-action <act@expr>\tas above, but only log expressions ");
+ fprintf(stream, "which return TRUE\n");
+ fprintf(stream, "-false-action <act@exp>\tas above, but only log expressions ");
+ fprintf(stream, "which return FALSE\n");
+ fprintf(stream, "-action-file <file>\tas action, but read actions ");
+ fprintf(stream, "from <file>\n");
+ fprintf(stream, "-log-action-file <file>\tas -log-action, but read ");
+ fprintf(stream, "actions from <file>\n");
+ fprintf(stream, "-true-action-file <f>\tas -true-action, but read ");
+ fprintf(stream, "actions from <f>\n");
+ fprintf(stream, "-false-action-file <f>\tas -false-action, but read ");
+ fprintf(stream, "actions from <f>\n");
+ fprintf(stream, "\nTar file only options:\n");
+ fprintf(stream, "-default-mode <mode>\ttar files often do not store ");
+ fprintf(stream, "permissions for\n\t\t\tintermediate directories. ");
+ fprintf(stream, "This option sets the default\n\t\t\tdirectory ");
+ fprintf(stream, "permissions to octal <mode>, rather than 0755.\n");
+ fprintf(stream, "\t\t\tThis also sets the root inode mode\n");
+ fprintf(stream, "-default-uid <uid>\ttar files often do not store ");
+ fprintf(stream, "uids for intermediate\n\t\t\tdirectories. This ");
+ fprintf(stream, "option sets the default directory\n\t\t\towner to ");
+ fprintf(stream, "<uid>, rather than the user running Mksquashfs.\n");
+ fprintf(stream, "\t\t\tThis also sets the root inode uid\n");
+ fprintf(stream, "-default-gid <gid>\ttar files often do not store ");
+ fprintf(stream, "gids for intermediate\n\t\t\tdirectories. This ");
+ fprintf(stream, "option sets the default directory\n\t\t\tgroup to ");
+ fprintf(stream, "<gid>, rather than the group of the user\n");
+ fprintf(stream, "\t\t\trunning Mksquashfs. This also sets the root ");
+ fprintf(stream, "inode gid\n");
+ fprintf(stream, "-ignore-zeros\t\tallow tar files to be concatenated ");
+ fprintf(stream, "together and fed to\n\t\t\tMksquashfs. Normally a ");
+ fprintf(stream, "tarfile has two consecutive 512\n\t\t\tbyte blocks ");
+ fprintf(stream, "filled with zeros which means EOF and\n");
+ fprintf(stream, "\t\t\tMksquashfs will stop reading after the first tar ");
+ fprintf(stream, "file on\n\t\t\tencountering them. This option makes ");
+ fprintf(stream, "Mksquashfs ignore\n\t\t\tthe zero filled blocks\n");
+ fprintf(stream, "\nExpert options (these may make the filesystem unmountable):\n");
+ fprintf(stream, "-nopad\t\t\tdo not pad filesystem to a multiple of 4K\n");
+ fprintf(stream, "-offset <offset>\tskip <offset> bytes at the beginning of ");
+ fprintf(stream, "FILESYSTEM.\n\t\t\tOptionally a suffix of K, M or G can be given ");
+ fprintf(stream, "to specify\n\t\t\tKbytes, Mbytes or Gbytes respectively.\n");
+ fprintf(stream, "\t\t\tDefault 0 bytes\n");
+ fprintf(stream, "-o <offset>\t\tsynonym for -offset\n");
+ fprintf(stream, "\nMiscellaneous options:\n");
+ fprintf(stream, "-fstime <time>\t\talternative name for -mkfs-time\n");
+ fprintf(stream, "-always-use-fragments\talternative name for -tailends\n");
+ fprintf(stream, "-root-owned\t\talternative name for -all-root\n");
+ fprintf(stream, "-noInodeCompression\talternative name for -noI\n");
+ fprintf(stream, "-noIdTableCompression\talternative name for -noId\n");
+ fprintf(stream, "-noDataCompression\talternative name for -noD\n");
+ fprintf(stream, "-noFragmentCompression\talternative name for -noF\n");
+ fprintf(stream, "-noXattrCompression\talternative name for -noX\n");
+ fprintf(stream, "\n-help\t\t\toutput this options text to stdout\n");
+ fprintf(stream, "-h\t\t\toutput this options text to stdout\n");
+ fprintf(stream, "\n-Xhelp\t\t\tprint compressor options for selected ");
+ fprintf(stream, "compressor\n");
+ fprintf(stream, "\nPseudo file definition format:\n");;
+ fprintf(stream, "\"filename d mode uid gid\"\t\tcreate a directory\n");
+ fprintf(stream, "\"filename m mode uid gid\"\t\tmodify filename\n");
+ fprintf(stream, "\"filename b mode uid gid major minor\"\tcreate a block device\n");
+ fprintf(stream, "\"filename c mode uid gid major minor\"\tcreate a character device\n");
+ fprintf(stream, "\"filename f mode uid gid command\"\tcreate file from stdout of command\n");
+ fprintf(stream, "\"filename s mode uid gid symlink\"\tcreate a symbolic link\n");
+ fprintf(stream, "\"filename i mode uid gid [s|f]\"\t\tcreate a socket (s) or FIFO (f)\n");
+ fprintf(stream, "\"filename x name=val\"\t\t\tcreate an extended attribute\n");
+ fprintf(stream, "\"filename l linkname\"\t\t\tcreate a hard-link to linkname\n");
+ fprintf(stream, "\"filename L pseudo_filename\"\t\tsame, but link to pseudo file\n");
+ fprintf(stream, "\"filename D time mode uid gid\"\t\tcreate a directory with timestamp time\n");
+ fprintf(stream, "\"filename M time mode uid gid\"\t\tmodify a file with timestamp time\n");
+ fprintf(stream, "\"filename B time mode uid gid major minor\"\n\t\t\t\t\tcreate block device with timestamp time\n");
+ fprintf(stream, "\"filename C time mode uid gid major minor\"\n\t\t\t\t\tcreate char device with timestamp time\n");
+ fprintf(stream, "\"filename F time mode uid gid command\"\tcreate file with timestamp time\n");
+ fprintf(stream, "\"filename S time mode uid gid symlink\"\tcreate symlink with timestamp time\n");
+ fprintf(stream, "\"filename I time mode uid gid [s|f]\"\tcreate socket/fifo with timestamp time\n");
+ fprintf(stream, "\nCompressors available and compressor specific options:\n");
+ display_compressor_usage(stream, COMP_DEFAULT);
+
+ fprintf(stream, "\nEnvironment:\n");
+ fprintf(stream, "SOURCE_DATE_EPOCH\tIf set, this is used as the ");
+ fprintf(stream, "filesystem creation\n");
+ fprintf(stream, "\t\t\ttimestamp. Also any file timestamps which are\n");
+ fprintf(stream, "\t\t\tafter SOURCE_DATE_EPOCH will be clamped to\n");
+ fprintf(stream, "\t\t\tSOURCE_DATE_EPOCH. See\n");
+ fprintf(stream, "\t\t\thttps://reproducible-builds.org/docs/source-date-epoch/\n");
+ fprintf(stream, "\t\t\tfor more information\n");
+ fprintf(stream, "\nSee also:");
+ fprintf(stream, "\nThe README for the Squashfs-tools 4.6.1 release, ");
+ fprintf(stream, "describing the new features can be\n");
+ fprintf(stream, "read here https://github.com/plougher/squashfs-tools/blob/master/README-4.6.1\n");
+
+ fprintf(stream, "\nThe Squashfs-tools USAGE guide can be read here\n");
+ fprintf(stream, "https://github.com/plougher/squashfs-tools/blob/master/USAGE-4.6\n");
+ fprintf(stream, "\nThe ACTIONS-README file describing how to use the new actions feature can be\n");
+ fprintf(stream, "read here https://github.com/plougher/squashfs-tools/blob/master/ACTIONS-README\n");
+}
+
+
+static void print_sqfstar_options(FILE *stream, char *name, int total_mem)
+{
+ fprintf(stream, "SYNTAX:%s [OPTIONS] FILESYSTEM ", name);
+ fprintf(stream, "[list of exclude dirs/files]\n");
+ fprintf(stream, "\nFilesystem compression options:\n");
+ fprintf(stream, "-b <block_size>\t\tset data block to <block_size>. Default ");
+ fprintf(stream, "128 Kbytes.\n");
+ fprintf(stream, "\t\t\tOptionally a suffix of K or M can be given to ");
+ fprintf(stream, "specify\n\t\t\tKbytes or Mbytes respectively\n");
+ fprintf(stream, "-comp <comp>\t\tselect <comp> compression\n");
+ fprintf(stream, "\t\t\tCompressors available:\n");
+ display_compressors(stream, "\t\t\t", COMP_DEFAULT);
+ fprintf(stream, "-noI\t\t\tdo not compress inode table\n");
+ fprintf(stream, "-noId\t\t\tdo not compress the uid/gid table (implied by ");
+ fprintf(stream, "-noI)\n");
+ fprintf(stream, "-noD\t\t\tdo not compress data blocks\n");
+ fprintf(stream, "-noF\t\t\tdo not compress fragment blocks\n");
+ fprintf(stream, "-noX\t\t\tdo not compress extended attributes\n");
+ fprintf(stream, "-no-compression\t\tdo not compress any of the data ");
+ fprintf(stream, "or metadata. This is\n\t\t\tequivalent to ");
+ fprintf(stream, "specifying -noI -noD -noF and -noX\n");
+ fprintf(stream, "\nFilesystem build options:\n");
+ fprintf(stream, "-reproducible\t\tbuild filesystems that are reproducible");
+ fprintf(stream, REP_STR "\n");
+ fprintf(stream, "-not-reproducible\tbuild filesystems that are not reproducible");
+ fprintf(stream, NOREP_STR "\n");
+ fprintf(stream, "-mkfs-time <time>\tset filesystem creation ");
+ fprintf(stream, "timestamp to <time>. <time> can\n\t\t\tbe an ");
+ fprintf(stream, "unsigned 32-bit int indicating seconds since the\n");
+ fprintf(stream, "\t\t\tepoch (1970-01-01) or a string value which ");
+ fprintf(stream, "is passed to\n\t\t\tthe \"date\" command to ");
+ fprintf(stream, "parse. Any string value which the\n\t\t\tdate ");
+ fprintf(stream, "command recognises can be used such as \"now\",\n");
+ fprintf(stream, "\t\t\t\"last week\", or \"Wed Feb 15 21:02:39 ");
+ fprintf(stream, "GMT 2023\"\n");
+ fprintf(stream, "-all-time <time>\tset all file timestamps to ");
+ fprintf(stream, "<time>. <time> can be an\n\t\t\tunsigned 32-bit ");
+ fprintf(stream, "int indicating seconds since the epoch\n\t\t\t");
+ fprintf(stream, "(1970-01-01) or a string value which is passed to ");
+ fprintf(stream, "the\n\t\t\t\"date\" command to parse. Any string ");
+ fprintf(stream, "value which the date\n\t\t\tcommand recognises can ");
+ fprintf(stream, "be used such as \"now\", \"last\n\t\t\tweek\", or ");
+ fprintf(stream, "\"Wed Feb 15 21:02:39 GMT 2023\"\n");
+ fprintf(stream, "-root-time <time>\tset root directory time to ");
+ fprintf(stream, "<time>. <time> can be an\n\t\t\tunsigned 32-bit ");
+ fprintf(stream, "int indicating seconds since the epoch\n\t\t\t");
+ fprintf(stream, "(1970-01-01) or a string value which is passed to ");
+ fprintf(stream, "the\n\t\t\t\"date\" command to parse. Any string ");
+ fprintf(stream, "value which the date\n\t\t\tcommand recognises can ");
+ fprintf(stream, "be used such as \"now\", \"last\n\t\t\tweek\", or ");
+ fprintf(stream, "\"Wed Feb 15 21:02:39 GMT 2023\"\n");
+ fprintf(stream, "-root-mode <mode>\tset root directory permissions to octal ");
+ fprintf(stream, "<mode>\n");
+ fprintf(stream, "-root-uid <value>\tset root directory owner to ");
+ fprintf(stream, "specified <value>,\n\t\t\t<value> can be either an ");
+ fprintf(stream, "integer uid or user name\n");
+ fprintf(stream, "-root-gid <value>\tset root directory group to ");
+ fprintf(stream, "specified <value>,\n\t\t\t<value> can be either an ");
+ fprintf(stream, "integer gid or group name\n");
+ fprintf(stream, "-all-root\t\tmake all files owned by root\n");
+ fprintf(stream, "-force-uid <value>\tset all file uids to specified ");
+ fprintf(stream, "<value>, <value> can be\n\t\t\teither an integer ");
+ fprintf(stream, "uid or user name\n");
+ fprintf(stream, "-force-gid <value>\tset all file gids to specified ");
+ fprintf(stream, "<value>, <value> can be\n\t\t\teither an integer ");
+ fprintf(stream, "gid or group name\n");
+ fprintf(stream, "-default-mode <mode>\ttar files often do not store ");
+ fprintf(stream, "permissions for\n\t\t\tintermediate directories. ");
+ fprintf(stream, "This option sets the default\n\t\t\tdirectory ");
+ fprintf(stream, "permissions to octal <mode>, rather than 0755.\n");
+ fprintf(stream, "\t\t\tThis also sets the root inode mode\n");
+ fprintf(stream, "-default-uid <uid>\ttar files often do not store ");
+ fprintf(stream, "uids for intermediate\n\t\t\tdirectories. This ");
+ fprintf(stream, "option sets the default directory\n\t\t\towner to ");
+ fprintf(stream, "<uid>, rather than the user running Sqfstar.\n");
+ fprintf(stream, "\t\t\tThis also sets the root inode uid\n");
+ fprintf(stream, "-default-gid <gid>\ttar files often do not store ");
+ fprintf(stream, "gids for intermediate\n\t\t\tdirectories. This ");
+ fprintf(stream, "option sets the default directory\n\t\t\tgroup to ");
+ fprintf(stream, "<gid>, rather than the group of the user\n");
+ fprintf(stream, "\t\t\trunning Sqfstar. This also sets the root ");
+ fprintf(stream, "inode gid\n");
+ fprintf(stream, "-pseudo-override\tmake pseudo file uids and gids ");
+ fprintf(stream, "override -all-root,\n\t\t\t-force-uid and ");
+ fprintf(stream, "-force-gid options\n");
+ fprintf(stream, "-exports\t\tmake the filesystem exportable via NFS\n");
+ fprintf(stream, "-no-sparse\t\tdo not detect sparse files\n");
+ fprintf(stream, "-no-fragments\t\tdo not use fragments\n");
+ fprintf(stream, "-no-tailends\t\tdo not pack tail ends into fragments\n");
+ fprintf(stream, "-no-duplicates\t\tdo not perform duplicate checking\n");
+ fprintf(stream, "-no-hardlinks\t\tdo not hardlink files, instead store duplicates\n");
+ fprintf(stream, "\nFilesystem filter options:\n");
+ fprintf(stream, "-p <pseudo-definition>\tadd pseudo file ");
+ fprintf(stream, "definition. The definition should\n");
+ fprintf(stream, "\t\t\tbe quoted\n");
+ fprintf(stream, "-pf <pseudo-file>\tadd list of pseudo file ");
+ fprintf(stream, "definitions. Pseudo file\n\t\t\tdefinitions in ");
+ fprintf(stream, "pseudo-files should not be quoted\n");
+ fprintf(stream, "-ef <exclude_file>\tlist of exclude dirs/files. ");
+ fprintf(stream, "One per line\n");
+ fprintf(stream, "-regex\t\t\tallow POSIX regular expressions to be used in ");
+ fprintf(stream, "exclude\n\t\t\tdirs/files\n");
+ fprintf(stream, "-ignore-zeros\t\tallow tar files to be concatenated ");
+ fprintf(stream, "together and fed to\n\t\t\tSqfstar. Normally a ");
+ fprintf(stream, "tarfile has two consecutive 512\n\t\t\tbyte blocks ");
+ fprintf(stream, "filled with zeros which means EOF and\n");
+ fprintf(stream, "\t\t\tSqfstar will stop reading after the first tar ");
+ fprintf(stream, "file on\n\t\t\tencountering them. This option makes ");
+ fprintf(stream, "Sqfstar ignore the\n\t\t\tzero filled blocks\n");
+ fprintf(stream, "\nFilesystem extended attribute (xattrs) options:\n");
+ fprintf(stream, "-no-xattrs\t\tdo not store extended attributes" NOXOPT_STR "\n");
+ fprintf(stream, "-xattrs\t\t\tstore extended attributes" XOPT_STR "\n");
+ fprintf(stream, "-xattrs-exclude <regex>\texclude any xattr names ");
+ fprintf(stream, "matching <regex>. <regex> is a\n\t\t\tPOSIX ");
+ fprintf(stream, "regular expression, e.g. -xattrs-exclude ");
+ fprintf(stream, "'^user.'\n\t\t\texcludes xattrs from the user ");
+ fprintf(stream, "namespace\n");
+ fprintf(stream, "-xattrs-include <regex>\tinclude any xattr names ");
+ fprintf(stream, "matching <regex>. <regex> is a\n\t\t\tPOSIX ");
+ fprintf(stream, "regular expression, e.g. -xattrs-include ");
+ fprintf(stream, "'^user.'\n\t\t\tincludes xattrs from the user ");
+ fprintf(stream, "namespace\n");
+ fprintf(stream, "-xattrs-add <name=val>\tadd the xattr <name> with ");
+ fprintf(stream, "<val> to files. If an\n\t\t\tuser xattr it ");
+ fprintf(stream, "will be added to regular files and\n");
+ fprintf(stream, "\t\t\tdirectories (see man 7 xattr). Otherwise it ");
+ fprintf(stream, "will be\n\t\t\tadded to all files. <val> by ");
+ fprintf(stream, "default will be treated as\n\t\t\tbinary (i.e. an ");
+ fprintf(stream, "uninterpreted byte sequence), but it can\n\t\t\tbe ");
+ fprintf(stream, "prefixed with 0s, where it will be treated as ");
+ fprintf(stream, "base64\n\t\t\tencoded, or prefixed with 0x, where ");
+ fprintf(stream, "val will be treated\n\t\t\tas hexidecimal. ");
+ fprintf(stream, "Additionally it can be prefixed with\n\t\t\t0t ");
+ fprintf(stream, "where this encoding is similar to binary encoding,\n");
+ fprintf(stream, "\t\t\texcept backslashes are specially treated, and ");
+ fprintf(stream, "a\n\t\t\tbackslash followed by 3 octal digits can ");
+ fprintf(stream, "be used to\n\t\t\tencode any ASCII character, ");
+ fprintf(stream, "which obviously can be used\n\t\t\tto encode ");
+ fprintf(stream, "control codes. The option can be repeated\n");
+ fprintf(stream, "\t\t\tmultiple times to add multiple xattrs\n");
+ fprintf(stream, "\nSqfstar runtime options:\n");
+ fprintf(stream, "-version\t\tprint version, licence and copyright message\n");
+ fprintf(stream, "-force\t\t\tforce Sqfstar to write to block device ");
+ fprintf(stream, "or file\n");
+ fprintf(stream, "-exit-on-error\t\ttreat normally ignored errors as fatal\n");
+ fprintf(stream, "-quiet\t\t\tno verbose output\n");
+ fprintf(stream, "-info\t\t\tprint files written to filesystem\n");
+ fprintf(stream, "-no-progress\t\tdo not display the progress bar\n");
+ fprintf(stream, "-progress\t\tdisplay progress bar when using the -info ");
+ fprintf(stream, "option\n");
+ fprintf(stream, "-percentage\t\tdisplay a percentage rather than the ");
+ fprintf(stream, "full progress bar.\n\t\t\tCan be used with dialog ");
+ fprintf(stream, "--gauge etc.\n");
+ fprintf(stream, "-throttle <percentage>\tthrottle the I/O input rate by the ");
+ fprintf(stream, "given percentage.\n\t\t\tThis can be used to reduce the I/O ");
+ fprintf(stream, "and CPU consumption\n\t\t\tof Sqfstar\n");
+ fprintf(stream, "-limit <percentage>\tlimit the I/O input rate to the given ");
+ fprintf(stream, "percentage.\n\t\t\tThis can be used to reduce the I/O and CPU ");
+ fprintf(stream, "consumption\n\t\t\tof Sqfstar (alternative to -throttle)\n");
+ fprintf(stream, "-processors <number>\tuse <number> processors. By default ");
+ fprintf(stream, "will use number of\n\t\t\tprocessors available\n");
+ fprintf(stream, "-mem <size>\t\tuse <size> physical memory for ");
+ fprintf(stream, "caches. Use K, M or G to\n\t\t\tspecify Kbytes,");
+ fprintf(stream, " Mbytes or Gbytes respectively\n");
+ fprintf(stream, "-mem-percent <percent>\tuse <percent> physical ");
+ fprintf(stream, "memory for caches. Default 25%%\n");
+ fprintf(stream, "-mem-default\t\tprint default memory usage in Mbytes\n");
+ fprintf(stream, "\nExpert options (these may make the filesystem unmountable):\n");
+ fprintf(stream, "-nopad\t\t\tdo not pad filesystem to a multiple of 4K\n");
+ fprintf(stream, "-offset <offset>\tskip <offset> bytes at the beginning of ");
+ fprintf(stream, "FILESYSTEM.\n\t\t\tOptionally a suffix of K, M or G can be given ");
+ fprintf(stream, "to specify\n\t\t\tKbytes, Mbytes or Gbytes respectively.\n");
+ fprintf(stream, "\t\t\tDefault 0 bytes\n");
+ fprintf(stream, "-o <offset>\t\tsynonym for -offset\n");
+ fprintf(stream, "\nMiscellaneous options:\n");
+ fprintf(stream, "-fstime <time>\t\talternative name for mkfs-time\n");
+ fprintf(stream, "-root-owned\t\talternative name for -all-root\n");
+ fprintf(stream, "-noInodeCompression\talternative name for -noI\n");
+ fprintf(stream, "-noIdTableCompression\talternative name for -noId\n");
+ fprintf(stream, "-noDataCompression\talternative name for -noD\n");
+ fprintf(stream, "-noFragmentCompression\talternative name for -noF\n");
+ fprintf(stream, "-noXattrCompression\talternative name for -noX\n");
+ fprintf(stream, "\n-help\t\t\toutput this options text to stdout\n");
+ fprintf(stream, "-h\t\t\toutput this options text to stdout\n");
+ fprintf(stream, "\n-Xhelp\t\t\tprint compressor options for selected ");
+ fprintf(stream, "compressor\n");
+ fprintf(stream, "\nPseudo file definition format:\n");;
+ fprintf(stream, "\"filename d mode uid gid\"\t\tcreate a directory\n");
+ fprintf(stream, "\"filename m mode uid gid\"\t\tmodify filename\n");
+ fprintf(stream, "\"filename b mode uid gid major minor\"\tcreate a block device\n");
+ fprintf(stream, "\"filename c mode uid gid major minor\"\tcreate a character device\n");
+ fprintf(stream, "\"filename f mode uid gid command\"\tcreate file from stdout of command\n");
+ fprintf(stream, "\"filename s mode uid gid symlink\"\tcreate a symbolic link\n");
+ fprintf(stream, "\"filename i mode uid gid [s|f]\"\t\tcreate a socket (s) or FIFO (f)\n");
+ fprintf(stream, "\"filename x name=val\"\t\t\tcreate an extended attribute\n");
+ fprintf(stream, "\"filename l linkname\"\t\t\tcreate a hard-link to linkname\n");
+ fprintf(stream, "\"filename L pseudo_filename\"\t\tsame, but link to pseudo file\n");
+ fprintf(stream, "\"filename D time mode uid gid\"\t\tcreate a directory with timestamp time\n");
+ fprintf(stream, "\"filename M time mode uid gid\"\t\tmodify a file with timestamp time\n");
+ fprintf(stream, "\"filename B time mode uid gid major minor\"\n\t\t\t\t\tcreate block device with timestamp time\n");
+ fprintf(stream, "\"filename C time mode uid gid major minor\"\n\t\t\t\t\tcreate char device with timestamp time\n");
+ fprintf(stream, "\"filename F time mode uid gid command\"\tcreate file with timestamp time\n");
+ fprintf(stream, "\"filename S time mode uid gid symlink\"\tcreate symlink with timestamp time\n");
+ fprintf(stream, "\"filename I time mode uid gid [s|f]\"\tcreate socket/fifo with timestamp time\n");
+ fprintf(stream, "\nCompressors available and compressor specific options:\n");
+ display_compressor_usage(stream, COMP_DEFAULT);
+
+ fprintf(stream, "\nEnvironment:\n");
+ fprintf(stream, "SOURCE_DATE_EPOCH\tIf set, this is used as the ");
+ fprintf(stream, "filesystem creation\n");
+ fprintf(stream, "\t\t\ttimestamp. Also any file timestamps which are\n");
+ fprintf(stream, "\t\t\tafter SOURCE_DATE_EPOCH will be clamped to\n");
+ fprintf(stream, "\t\t\tSOURCE_DATE_EPOCH. See\n");
+ fprintf(stream, "\t\t\thttps://reproducible-builds.org/docs/source-date-epoch/\n");
+ fprintf(stream, "\t\t\tfor more information\n");
+ fprintf(stream, "\nSee also:\n");
+ fprintf(stream, "The README for the Squashfs-tools 4.6.1 release, ");
+ fprintf(stream, "describing the new features can be\n");
+ fprintf(stream, "read here https://github.com/plougher/squashfs-tools/blob/master/README-4.6.1\n");
+
+ fprintf(stream, "\nThe Squashfs-tools USAGE guide can be read here\n");
+ fprintf(stream, "https://github.com/plougher/squashfs-tools/blob/master/USAGE-4.6\n");
+}
+
+
+static void print_version(char *string)
+{
+ printf("%s version " VERSION " (" DATE ")\n", string);
+ printf("copyright (C) " YEAR " Phillip Lougher ");
+ printf("<phillip@squashfs.org.uk>\n\n");
+ printf("This program is free software; you can redistribute it and/or\n");
+ printf("modify it under the terms of the GNU General Public License\n");
+ printf("as published by the Free Software Foundation; either version ");
+ printf("2,\n");
+ printf("or (at your option) any later version.\n\n");
+ printf("This program is distributed in the hope that it will be ");
+ printf("useful,\n");
+ printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
+ printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n");
+ printf("GNU General Public License for more details.\n");
+}
+
+
+static void print_summary()
+{
+ int i;
+
+ printf("\n%sSquashfs %d.%d filesystem, %s compressed, data block size"
+ " %d\n", exportable ? "Exportable " : "", SQUASHFS_MAJOR,
+ SQUASHFS_MINOR, comp->name, block_size);
+ printf("\t%s data, %s metadata, %s fragments,\n\t%s xattrs, %s ids\n",
+ noD ? "uncompressed" : "compressed", noI ? "uncompressed" :
+ "compressed", no_fragments ? "no" : noF ? "uncompressed" :
+ "compressed", no_xattrs ? "no" : noX ? "uncompressed" :
+ "compressed", noI || noId ? "uncompressed" : "compressed");
+ printf("\tduplicates are %sremoved\n", duplicate_checking ? "" :
+ "not ");
+ printf("Filesystem size %.2f Kbytes (%.2f Mbytes)\n", bytes / 1024.0,
+ bytes / (1024.0 * 1024.0));
+ printf("\t%.2f%% of uncompressed filesystem size (%.2f Kbytes)\n",
+ ((float) bytes / total_bytes) * 100.0, total_bytes / 1024.0);
+ printf("Inode table size %lld bytes (%.2f Kbytes)\n",
+ inode_bytes, inode_bytes / 1024.0);
+ printf("\t%.2f%% of uncompressed inode table size (%lld bytes)\n",
+ ((float) inode_bytes / total_inode_bytes) * 100.0,
+ total_inode_bytes);
+ printf("Directory table size %lld bytes (%.2f Kbytes)\n",
+ directory_bytes, directory_bytes / 1024.0);
+ printf("\t%.2f%% of uncompressed directory table size (%lld bytes)\n",
+ ((float) directory_bytes / total_directory_bytes) * 100.0,
+ total_directory_bytes);
+ if(total_xattr_bytes) {
+ printf("Xattr table size %d bytes (%.2f Kbytes)\n",
+ xattr_bytes, xattr_bytes / 1024.0);
+ printf("\t%.2f%% of uncompressed xattr table size (%d bytes)\n",
+ ((float) xattr_bytes / total_xattr_bytes) * 100.0,
+ total_xattr_bytes);
+ }
+ if(duplicate_checking)
+ printf("Number of duplicate files found %u\n", file_count -
+ dup_files);
+ else
+ printf("No duplicate files removed\n");
+ printf("Number of inodes %u\n", inode_count);
+ printf("Number of files %u\n", file_count);
+ if(!no_fragments)
+ printf("Number of fragments %u\n", fragments);
+ printf("Number of symbolic links %u\n", sym_count);
+ printf("Number of device nodes %u\n", dev_count);
+ printf("Number of fifo nodes %u\n", fifo_count);
+ printf("Number of socket nodes %u\n", sock_count);
+ printf("Number of directories %u\n", dir_count);
+ printf("Number of hard-links %lld\n", hardlnk_count);
+ printf("Number of ids (unique uids + gids) %d\n", id_count);
+ printf("Number of uids %d\n", uid_count);
+
+ for(i = 0; i < id_count; i++) {
+ if(id_table[i]->flags & ISA_UID) {
+ struct passwd *user = getpwuid(id_table[i]->id);
+ printf("\t%s (%u)\n", user == NULL ? "unknown" :
+ user->pw_name, id_table[i]->id);
+ }
+ }
+
+ printf("Number of gids %d\n", guid_count);
+
+ for(i = 0; i < id_count; i++) {
+ if(id_table[i]->flags & ISA_GID) {
+ struct group *group = getgrgid(id_table[i]->id);
+ printf("\t%s (%d)\n", group == NULL ? "unknown" :
+ group->gr_name, id_table[i]->id);
+ }
+ }
+}
+
+
+int option_with_arg(char *string, char *table[])
+{
+ int i;
+
+ if(*string != '-')
+ return FALSE;
+
+ for(i = 0; table[i] != NULL; i++)
+ if(strcmp(string + 1, table[i]) == 0)
+ break;
+
+ if(table[i] != NULL)
+ return TRUE;
+
+ return compressor_option_args(comp, string);
+}
+
+
+static int get_uid_from_arg(char *arg, unsigned int *uid)
+{
+ char *last;
+ long long res;
+
+ res = strtoll(arg, &last, 10);
+ if(*last == '\0') {
+ if(res < 0 || res > (((long long) 1 << 32) - 1))
+ return -2;
+
+ *uid = res;
+ return 0;
+ } else {
+ struct passwd *id = getpwnam(arg);
+
+ if(id) {
+ *uid = id->pw_uid;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+
+static int get_gid_from_arg(char *arg, unsigned int *gid)
+{
+ char *last;
+ long long res;
+
+ res = strtoll(arg, &last, 10);
+ if(*last == '\0') {
+ if(res < 0 || res > (((long long) 1 << 32) - 1))
+ return -2;
+
+ *gid = res;
+ return 0;
+ } else {
+ struct group *id = getgrnam(arg);
+
+ if(id) {
+ *gid = id->gr_gid;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+
+int sqfstar(int argc, char *argv[])
+{
+ struct stat buf;
+ int res, i;
+ squashfs_inode inode;
+ int readq;
+ int fragq;
+ int bwriteq;
+ int fwriteq;
+ int total_mem = get_default_phys_mem();
+ int progress = TRUE;
+ int force_progress = FALSE;
+ int dest_index;
+ struct file_buffer **fragment = NULL;
+ int size;
+ void *comp_data;
+
+ if(argc == 2 && strcmp(argv[1], "-version") == 0) {
+ print_version("sqfstar");
+ exit(0);
+ }
+
+ block_log = slog(block_size);
+ calculate_queue_sizes(total_mem, &readq, &fragq, &bwriteq, &fwriteq);
+
+ if(argc == 2 && (strcmp(argv[1], "-help") == 0 || strcmp(argv[1], "-h") == 0)) {
+ print_sqfstar_options(stdout, argv[0], total_mem);
+ exit(0);
+ }
+
+ if(argc == 2 && strcmp(argv[1], "-mem-default") == 0) {
+ printf("%d\n", total_mem);
+ exit(0);
+ }
+
+ comp = lookup_compressor(COMP_DEFAULT);
+
+ /*
+ * Scan the command line for -comp xxx option, this should occur before
+ * any -X compression specific options to ensure these options are passed
+ * to the correct compressor
+ */
+ for(i = 1; i < argc; i++) {
+ if(strncmp(argv[i], "-X", 2) == 0)
+ X_opt_parsed = 1;
+
+ if(strcmp(argv[i], "-comp") == 0) {
+ struct compressor *prev_comp = comp;
+
+ if(++i == argc) {
+ ERROR("%s: -comp missing compression type\n",
+ argv[0]);
+ exit(1);
+ }
+ comp = lookup_compressor(argv[i]);
+ if(!comp->supported) {
+ ERROR("%s: Compressor \"%s\" is not supported!"
+ "\n", argv[0], argv[i]);
+ ERROR("%s: Compressors available:\n", argv[0]);
+ display_compressors(stderr, "", COMP_DEFAULT);
+ exit(1);
+ }
+ if(compressor_opt_parsed) {
+ ERROR("%s: -comp multiple conflicting -comp"
+ " options specified on command line"
+ ", previously %s, now %s\n", argv[0],
+ prev_comp->name, comp->name);
+ exit(1);
+ }
+ compressor_opt_parsed = 1;
+ if(X_opt_parsed) {
+ ERROR("%s: -comp option should be before any "
+ "-X option\n", argv[0]);
+ exit(1);
+ }
+ } else if(argv[i][0] != '-')
+ break;
+ else if(option_with_arg(argv[i], sqfstar_option_table))
+ i++;
+ }
+
+ if(i >= argc) {
+ print_sqfstar_options(stderr, argv[0], total_mem);
+ exit(1);
+ }
+
+ dest_index = i;
+ source_path = NULL;
+ source = 0;
+ old_exclude = FALSE;
+ tarfile = TRUE;
+
+ /* By default images generated from tar files are not exportable.
+ * Exportable by default is a "legacy" setting in Mksquashfs, which
+ * will cause too many problems to change now. But tarfile reading
+ * has no such issues */
+ exportable = FALSE;
+
+ /* By default images generated from tar files use tail-end packing.
+ * No tailend packing is a "legacy" setting in Mksquashfs, which
+ * will cause too many problems to change now. But tarfile reading
+ * has no such issues */
+ always_use_fragments = TRUE;
+
+ for(i = 1; i < dest_index; i++) {
+ if(strcmp(argv[i], "-ignore-zeros") == 0)
+ ignore_zeros = TRUE;
+ else if(strcmp(argv[i], "-no-hardlinks") == 0)
+ no_hardlinks = TRUE;
+ else if(strcmp(argv[i], "-throttle") == 0) {
+ if((++i == dest_index) || !parse_num(argv[i], &sleep_time)) {
+ ERROR("%s: %s missing or invalid value\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ if(sleep_time > 99) {
+ ERROR("%s: %s value should be between 0 and "
+ "99\n", argv[0], argv[i - 1]);
+ exit(1);
+ }
+ readq = 4;
+ } else if(strcmp(argv[i], "-limit") == 0) {
+ if((++i == dest_index) || !parse_num(argv[i], &sleep_time)) {
+ ERROR("%s: %s missing or invalid value\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ if(sleep_time < 1 || sleep_time > 100) {
+ ERROR("%s: %s value should be between 1 and "
+ "100\n", argv[0], argv[i - 1]);
+ exit(1);
+ }
+ sleep_time = 100 - sleep_time;
+ readq = 4;
+ } else if(strcmp(argv[i], "-mkfs-time") == 0 ||
+ strcmp(argv[i], "-fstime") == 0) {
+ if((++i == dest_index) ||
+ (!parse_num_unsigned(argv[i], &mkfs_time) &&
+ !exec_date(argv[i], &mkfs_time))) {
+ ERROR("%s: %s missing or invalid time "
+ "value\n", argv[0],
+ argv[i - 1]);
+ exit(1);
+ }
+ mkfs_time_opt = TRUE;
+ } else if(strcmp(argv[i], "-all-time") == 0) {
+ if((++i == dest_index) ||
+ (!parse_num_unsigned(argv[i], &all_time) &&
+ !exec_date(argv[i], &all_time))) {
+ ERROR("%s: %s missing or invalid time "
+ "value\n", argv[0],
+ argv[i - 1]);
+ exit(1);
+ }
+ all_time_opt = TRUE;
+ clamping = FALSE;
+ } else if(strcmp(argv[i], "-reproducible") == 0)
+ reproducible = TRUE;
+ else if(strcmp(argv[i], "-not-reproducible") == 0)
+ reproducible = FALSE;
+ else if(strcmp(argv[i], "-root-mode") == 0) {
+ if((++i == dest_index) || !parse_mode(argv[i], &root_mode)) {
+ ERROR("%s: -root-mode missing or invalid mode,"
+ " octal number <= 07777 expected\n", argv[0]);
+ exit(1);
+ }
+ root_mode_opt = TRUE;
+ } else if(strcmp(argv[i], "-root-uid") == 0) {
+ if(++i == dest_index) {
+ ERROR("%s: -root-uid missing uid or user name\n",
+ argv[0]);
+ exit(1);
+ }
+
+ res = get_uid_from_arg(argv[i], &root_uid);
+ if(res) {
+ if(res == -2)
+ ERROR("%s: -root-uid uid out of range\n",
+ argv[0]);
+ else
+ ERROR("%s: -root-uid invalid uid or "
+ "unknown user name\n", argv[0]);
+ exit(1);
+ }
+ root_uid_opt = TRUE;
+ } else if(strcmp(argv[i], "-root-gid") == 0) {
+ if(++i == dest_index) {
+ ERROR("%s: -root-gid missing gid or group name\n",
+ argv[0]);
+ exit(1);
+ }
+
+ res = get_gid_from_arg(argv[i], &root_gid);
+ if(res) {
+ if(res == -2)
+ ERROR("%s: -root-gid gid out of range\n",
+ argv[0]);
+ else
+ ERROR("%s: -root-gid invalid gid or "
+ "unknown group name\n", argv[0]);
+ exit(1);
+ }
+ root_gid_opt = TRUE;
+ } else if(strcmp(argv[i], "-root-time") == 0) {
+ if((++i == argc) ||
+ (!parse_num_unsigned(argv[i], &root_time) &&
+ !exec_date(argv[i], &root_time))) {
+ ERROR("%s: -root-time missing or invalid time\n",
+ argv[0]);
+ exit(1);
+ }
+ root_time_opt = TRUE;
+ } else if(strcmp(argv[i], "-default-mode") == 0) {
+ if((++i == dest_index) || !parse_mode(argv[i], &default_mode)) {
+ ERROR("%s: -default-mode missing or invalid mode,"
+ " octal number <= 07777 expected\n", argv[0]);
+ exit(1);
+ }
+ root_mode = default_mode;
+ default_mode_opt = root_mode_opt = TRUE;
+ } else if(strcmp(argv[i], "-default-uid") == 0) {
+ if((++i == dest_index) || !parse_num_unsigned(argv[i], &default_uid)) {
+ ERROR("%s: -default-uid missing or invalid uid\n",
+ argv[0]);
+ exit(1);
+ }
+ root_uid = default_uid;
+ default_uid_opt = root_uid_opt = TRUE;
+ } else if(strcmp(argv[i], "-default-gid") == 0) {
+ if((++i == dest_index) || !parse_num_unsigned(argv[i], &default_gid)) {
+ ERROR("%s: -default-gid missing or invalid gid\n",
+ argv[0]);
+ exit(1);
+ }
+ root_gid = default_gid;
+ default_gid_opt = root_gid_opt = TRUE;
+ } else if(strcmp(argv[i], "-comp") == 0)
+ /* parsed previously */
+ i++;
+ else if(strncmp(argv[i], "-X", 2) == 0) {
+ int args;
+
+ if(strcmp(argv[i] + 2, "help") == 0)
+ goto print_sqfstar_compressor_options;
+
+ args = compressor_options(comp, argv + i, dest_index - i);
+ if(args < 0) {
+ if(args == -1) {
+ ERROR("%s: Unrecognised compressor"
+ " option %s\n", argv[0],
+ argv[i]);
+ if(!compressor_opt_parsed)
+ ERROR("%s: Did you forget to"
+ " specify -comp, or "
+ "specify it after the"
+ " -X options?\n",
+ argv[0]);
+print_sqfstar_compressor_options:
+ ERROR("%s: selected compressor \"%s\""
+ ". Options supported: %s\n",
+ argv[0], comp->name,
+ comp->usage ? "" : "none");
+ if(comp->usage)
+ comp->usage(stderr);
+ }
+ exit(1);
+ }
+ i += args;
+
+ } else if(strcmp(argv[i], "-pf") == 0) {
+ if(++i == dest_index) {
+ ERROR("%s: -pf missing filename\n", argv[0]);
+ exit(1);
+ }
+ if(read_pseudo_file(argv[i], argv[dest_index]) == FALSE)
+ exit(1);
+ } else if(strcmp(argv[i], "-p") == 0) {
+ if(++i == dest_index) {
+ ERROR("%s: -p missing pseudo file definition\n",
+ argv[0]);
+ exit(1);
+ }
+ if(read_pseudo_definition(argv[i], argv[dest_index]) == FALSE)
+ exit(1);
+ } else if(strcmp(argv[i], "-regex") == 0)
+ use_regex = TRUE;
+ else if(strcmp(argv[i], "-no-sparse") == 0)
+ sparse_files = FALSE;
+ else if(strcmp(argv[i], "-no-progress") == 0)
+ progress = FALSE;
+ else if(strcmp(argv[i], "-progress") == 0)
+ force_progress = TRUE;
+ else if(strcmp(argv[i], "-exports") == 0)
+ exportable = TRUE;
+ else if(strcmp(argv[i], "-offset") == 0 ||
+ strcmp(argv[i], "-o") == 0) {
+ if((++i == dest_index) ||
+ !parse_numberll(argv[i], &start_offset, 1)) {
+ ERROR("%s: %s missing or invalid offset "
+ "size\n", argv[0], argv[i - 1]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-processors") == 0) {
+ if((++i == dest_index) || !parse_num(argv[i], &processors)) {
+ ERROR("%s: -processors missing or invalid "
+ "processor number\n", argv[0]);
+ exit(1);
+ }
+ if(processors < 1) {
+ ERROR("%s: -processors should be 1 or larger\n",
+ argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-mem") == 0) {
+ long long number;
+
+ if((++i == dest_index) ||
+ !parse_numberll(argv[i], &number, 1)) {
+ ERROR("%s: -mem missing or invalid mem size\n",
+ argv[0]);
+ exit(1);
+ }
+
+ /*
+ * convert from bytes to Mbytes, ensuring the value
+ * does not overflow a signed int
+ */
+ if(number >= (1LL << 51)) {
+ ERROR("%s: -mem invalid mem size\n", argv[0]);
+ exit(1);
+ }
+
+ total_mem = number / 1048576;
+ if(total_mem < (SQUASHFS_LOWMEM / SQUASHFS_TAKE)) {
+ ERROR("%s: -mem should be %d Mbytes or "
+ "larger\n", argv[0],
+ SQUASHFS_LOWMEM / SQUASHFS_TAKE);
+ exit(1);
+ }
+ calculate_queue_sizes(total_mem, &readq, &fragq,
+ &bwriteq, &fwriteq);
+ } else if(strcmp(argv[i], "-mem-percent") == 0) {
+ int percent, phys_mem;
+
+ /*
+ * Percentage of 75% and larger is dealt with later.
+ * In the same way a fixed mem size if more than 75%
+ * of memory is dealt with later.
+ */
+ if((++i == dest_index) ||
+ !parse_number(argv[i], &percent, 1) ||
+ (percent < 1)) {
+ ERROR("%s: -mem-percent missing or invalid "
+ "percentage: it should be 1 - 75%\n",
+ argv[0]);
+ exit(1);
+ }
+
+ phys_mem = get_physical_memory();
+
+ if(phys_mem == 0) {
+ ERROR("%s: -mem-percent unable to get physical "
+ "memory, use -mem instead\n", argv[0]);
+ exit(1);
+ }
+
+ if(multiply_overflow(phys_mem, percent)) {
+ ERROR("%s: -mem-percent requested phys mem too "
+ "large\n", argv[0]);
+ exit(1);
+ }
+
+ total_mem = phys_mem * percent / 100;
+
+ if(total_mem < (SQUASHFS_LOWMEM / SQUASHFS_TAKE)) {
+ ERROR("%s: -mem-percent mem too small, should "
+ "be %d Mbytes or larger\n", argv[0],
+ SQUASHFS_LOWMEM / SQUASHFS_TAKE);
+ exit(1);
+ }
+ calculate_queue_sizes(total_mem, &readq, &fragq,
+ &bwriteq, &fwriteq);
+ } else if(strcmp(argv[i], "-mem-default") == 0) {
+ printf("%d\n", total_mem);
+ exit(0);
+ } else if(strcmp(argv[i], "-b") == 0) {
+ if(++i == dest_index) {
+ ERROR("%s: -b missing block size\n", argv[0]);
+ exit(1);
+ }
+ if(!parse_number(argv[i], &block_size, 1)) {
+ ERROR("%s: -b invalid block size\n", argv[0]);
+ exit(1);
+ }
+ if((block_log = slog(block_size)) == 0) {
+ ERROR("%s: -b block size not power of two or "
+ "not between 4096 and 1Mbyte\n",
+ argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-ef") == 0) {
+ if(++i == dest_index) {
+ ERROR("%s: -ef missing filename\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-no-duplicates") == 0)
+ duplicate_checking = FALSE;
+
+ else if(strcmp(argv[i], "-no-fragments") == 0)
+ no_fragments = TRUE;
+
+ else if(strcmp(argv[i], "-no-tailends") == 0)
+ always_use_fragments = FALSE;
+
+ else if(strcmp(argv[i], "-all-root") == 0 ||
+ strcmp(argv[i], "-root-owned") == 0) {
+ global_uid = global_gid = 0;
+ global_uid_opt = global_gid_opt = TRUE;
+ } else if(strcmp(argv[i], "-force-uid") == 0) {
+ if(++i == dest_index) {
+ ERROR("%s: -force-uid missing uid or user name\n",
+ argv[0]);
+ exit(1);
+ }
+
+ res = get_uid_from_arg(argv[i], &global_uid);
+ if(res) {
+ if(res == -2)
+ ERROR("%s: -force-uid uid out of range\n",
+ argv[0]);
+ else
+ ERROR("%s: -force-uid invalid uid or "
+ "unknown user name\n", argv[0]);
+ exit(1);
+ }
+ global_uid_opt = TRUE;
+ } else if(strcmp(argv[i], "-force-gid") == 0) {
+ if(++i == dest_index) {
+ ERROR("%s: -force-gid missing gid or group name\n",
+ argv[0]);
+ exit(1);
+ }
+
+ res = get_gid_from_arg(argv[i], &global_gid);
+ if(res) {
+ if(res == -2)
+ ERROR("%s: -force-gid gid out of range"
+ "\n", argv[0]);
+ else
+ ERROR("%s: -force-gid invalid gid or "
+ "unknown group name\n", argv[0]);
+ exit(1);
+ }
+ global_gid_opt = TRUE;
+ } else if(strcmp(argv[i], "-pseudo-override") == 0)
+ pseudo_override = TRUE;
+ else if(strcmp(argv[i], "-noI") == 0 ||
+ strcmp(argv[i], "-noInodeCompression") == 0)
+ noI = TRUE;
+
+ else if(strcmp(argv[i], "-noId") == 0 ||
+ strcmp(argv[i], "-noIdTableCompression") == 0)
+ noId = TRUE;
+
+ else if(strcmp(argv[i], "-noD") == 0 ||
+ strcmp(argv[i], "-noDataCompression") == 0)
+ noD = TRUE;
+
+ else if(strcmp(argv[i], "-noF") == 0 ||
+ strcmp(argv[i], "-noFragmentCompression") == 0)
+ noF = TRUE;
+
+ else if(strcmp(argv[i], "-noX") == 0 ||
+ strcmp(argv[i], "-noXattrCompression") == 0)
+ noX = TRUE;
+
+ else if(strcmp(argv[i], "-no-compression") == 0)
+ noI = noD = noF = noX = TRUE;
+
+ else if(strcmp(argv[i], "-no-xattrs") == 0) {
+ if(xattr_exclude_preg || xattr_include_preg ||
+ add_xattrs()) {
+ ERROR("%s: -no-xattrs should not be used in "
+ "combination with -xattrs-* options\n",
+ argv[0]);
+ exit(1);
+ }
+
+ no_xattrs = TRUE;
+
+ } else if(strcmp(argv[i], "-xattrs") == 0) {
+ if(xattrs_supported())
+ no_xattrs = FALSE;
+ else {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ }
+
+ } else if(strcmp(argv[i], "-xattrs-exclude") == 0) {
+ if(!xattrs_supported()) {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ } else if(++i == dest_index) {
+ ERROR("%s: -xattrs-exclude missing regex pattern\n",
+ argv[0]);
+ exit(1);
+ } else {
+ xattr_exclude_preg = xattr_regex(argv[i], "exclude");
+ no_xattrs = FALSE;
+ }
+ } else if(strcmp(argv[i], "-xattrs-include") == 0) {
+ if(!xattrs_supported()) {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ } else if(++i == dest_index) {
+ ERROR("%s: -xattrs-include missing regex pattern\n",
+ argv[0]);
+ exit(1);
+ } else {
+ xattr_include_preg = xattr_regex(argv[i], "include");
+ no_xattrs = FALSE;
+ }
+ } else if(strcmp(argv[i], "-xattrs-add") == 0) {
+ if(!xattrs_supported()) {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ } else if(++i == dest_index) {
+ ERROR("%s: -xattrs-add missing xattr argument\n",
+ argv[0]);
+ exit(1);
+ } else {
+ xattrs_add(argv[i]);
+ no_xattrs = FALSE;
+ }
+
+ } else if(strcmp(argv[i], "-nopad") == 0)
+ nopad = TRUE;
+
+ else if(strcmp(argv[i], "-info") == 0)
+ silent = FALSE;
+
+ else if(strcmp(argv[i], "-force") == 0)
+ appending = FALSE;
+
+ else if(strcmp(argv[i], "-quiet") == 0)
+ quiet = TRUE;
+
+ else if(strcmp(argv[i], "-exit-on-error") == 0)
+ exit_on_error = TRUE;
+
+ else if(strcmp(argv[i], "-percentage") == 0) {
+ progressbar_percentage();
+ progress = silent = TRUE;
+
+ } else if(strcmp(argv[i], "-version") == 0) {
+ print_version("sqfstar");
+ } else {
+ ERROR("%s: invalid option\n\n", argv[0]);
+ print_sqfstar_options(stderr, argv[0], total_mem);
+ exit(1);
+ }
+ }
+
+ check_env_var();
+
+ /*
+ * The -noI option implies -noId for backwards compatibility, so reset noId
+ * if both have been specified
+ */
+ if(noI && noId)
+ noId = FALSE;
+
+ /*
+ * Some compressors may need the options to be checked for validity
+ * once all the options have been processed
+ */
+ res = compressor_options_post(comp, block_size);
+ if(res)
+ EXIT_MKSQUASHFS();
+
+ /*
+ * If the -info option has been selected then disable the
+ * progress bar unless it has been explicitly enabled with
+ * the -progress option
+ */
+ if(!silent)
+ progress = force_progress;
+
+ /*
+ * Sort all the xattr-add options now they're all processed
+ */
+ sort_xattr_add_list();
+
+ /*
+ * If -pseudo-override option has been specified and there are
+ * no pseudo files then reset option. -pseudo-override relies
+ * on dir_scan2() being run, which won't be if there's no
+ * actions or pseudo files
+ */
+ if(pseudo_override && !get_pseudo())
+ pseudo_override = FALSE;
+
+#ifdef SQUASHFS_TRACE
+ /*
+ * Disable progress bar if full debug tracing is enabled.
+ * The progress bar in this case just gets in the way of the
+ * debug trace output
+ */
+ progress = FALSE;
+#endif
+
+ destination_file = argv[dest_index];
+ if(stat(destination_file, &buf) == -1) {
+ if(errno == ENOENT) { /* Does not exist */
+ appending = FALSE;
+ fd = open(destination_file, O_CREAT | O_TRUNC | O_RDWR,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if(fd == -1) {
+ perror("Could not create destination file");
+ exit(1);
+ }
+
+ /* ensure Sqfstar doesn't try to read
+ * the destination file as input, which
+ * will result in an I/O loop */
+ if(stat(destination_file, &buf) == -1) {
+ /* disappered after creating? */
+ perror("Could not stat destination file");
+ exit(1);
+ }
+ ADD_ENTRY(buf);
+ } else {
+ perror("Could not stat destination file");
+ exit(1);
+ }
+
+ } else {
+ if(!S_ISBLK(buf.st_mode) && !S_ISREG(buf.st_mode)) {
+ ERROR("Destination not block device or regular file\n");
+ exit(1);
+ }
+
+ if(appending) {
+ ERROR("Appending is not supported reading tar files\n");
+ ERROR("To force Sqfstar to write to this %s "
+ "use -force\n", S_ISBLK(buf.st_mode) ?
+ "block device" : "file");
+ EXIT_MKSQUASHFS();
+ }
+
+ if(S_ISBLK(buf.st_mode)) {
+ if((fd = open(destination_file, O_RDWR)) == -1) {
+ perror("Could not open block device as "
+ "destination");
+ exit(1);
+ }
+ block_device = 1;
+
+ } else {
+ fd = open(destination_file, O_TRUNC | O_RDWR);
+ if(fd == -1) {
+ perror("Could not open regular file for "
+ "writing as destination");
+ exit(1);
+ }
+ /* ensure Sqfstar doesn't try to read
+ * the destination file as input, which
+ * will result in an I/O loop */
+ ADD_ENTRY(buf);
+ }
+ }
+
+ /*
+ * process the exclude files - must be done afer destination file has
+ * been possibly created
+ */
+ for(i = 1; i < dest_index; i++) {
+ if(strcmp(argv[i], "-ef") == 0)
+ /*
+ * Note presence of filename arg has already
+ * been checked
+ */
+ process_exclude_file(argv[++i]);
+ else if(option_with_arg(argv[i], sqfstar_option_table))
+ i++;
+ }
+
+ for(i = dest_index + 1; i < argc; i++)
+ add_exclude(argv[i]);
+
+ initialise_threads(readq, fragq, bwriteq, fwriteq, !appending,
+ destination_file);
+
+ res = compressor_init(comp, &stream, SQUASHFS_METADATA_SIZE, 0);
+ if(res)
+ BAD_ERROR("compressor_init failed\n");
+
+ dupl_block = malloc(1048576 * sizeof(struct file_info *));
+ if(dupl_block == NULL)
+ MEM_ERROR();
+
+ dupl_frag = malloc(block_size * sizeof(struct file_info *));
+ if(dupl_frag == NULL)
+ MEM_ERROR();
+
+ memset(dupl_block, 0, 1048576 * sizeof(struct file_info *));
+ memset(dupl_frag, 0, block_size * sizeof(struct file_info *));
+
+ comp_data = compressor_dump_options(comp, block_size, &size);
+
+ if(!quiet)
+ printf("Creating %d.%d filesystem on %s, block size %d.\n",
+ SQUASHFS_MAJOR, SQUASHFS_MINOR,
+ destination_file, block_size);
+
+ /*
+ * store any compressor specific options after the superblock,
+ * and set the COMP_OPT flag to show that the filesystem has
+ * compressor specfic options
+ */
+ if(comp_data) {
+ unsigned short c_byte = size | SQUASHFS_COMPRESSED_BIT;
+
+ SQUASHFS_INSWAP_SHORTS(&c_byte, 1);
+ write_destination(fd, sizeof(struct squashfs_super_block),
+ sizeof(c_byte), &c_byte);
+ write_destination(fd, sizeof(struct squashfs_super_block) +
+ sizeof(c_byte), size, comp_data);
+ bytes = sizeof(struct squashfs_super_block) + sizeof(c_byte)
+ + size;
+ comp_opts = TRUE;
+ } else
+ bytes = sizeof(struct squashfs_super_block);
+
+ if(path)
+ paths = add_subdir(paths, path);
+
+ dump_actions();
+ dump_pseudos();
+
+ set_progressbar_state(progress);
+
+ inode = process_tar_file(progress);
+
+ sBlk.root_inode = inode;
+ sBlk.inodes = inode_count;
+ sBlk.s_magic = SQUASHFS_MAGIC;
+ sBlk.s_major = SQUASHFS_MAJOR;
+ sBlk.s_minor = SQUASHFS_MINOR;
+ sBlk.block_size = block_size;
+ sBlk.block_log = block_log;
+ sBlk.flags = SQUASHFS_MKFLAGS(noI, noD, noF, noX, noId, no_fragments,
+ always_use_fragments, duplicate_checking, exportable,
+ no_xattrs, comp_opts);
+ sBlk.mkfs_time = mkfs_time_opt ? mkfs_time : time(NULL);
+
+ disable_info();
+
+ while((fragment = get_frag_action(fragment)))
+ write_fragment(*fragment);
+ if(!reproducible)
+ unlock_fragments();
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+ pthread_mutex_lock(&fragment_mutex);
+ while(fragments_outstanding) {
+ pthread_mutex_unlock(&fragment_mutex);
+ pthread_testcancel();
+ sched_yield();
+ pthread_mutex_lock(&fragment_mutex);
+ }
+ pthread_cleanup_pop(1);
+
+ queue_put(to_writer, NULL);
+ if(queue_get(from_writer) != 0)
+ EXIT_MKSQUASHFS();
+
+ set_progressbar_state(FALSE);
+ write_filesystem_tables(&sBlk);
+
+ if(!nopad && (i = bytes & (4096 - 1))) {
+ char temp[4096] = {0};
+ write_destination(fd, bytes, 4096 - i, temp);
+ }
+
+ res = close(fd);
+
+ if(res == -1)
+ BAD_ERROR("Failed to close output filesystem, close returned %s\n",
+ strerror(errno));
+
+ if(recovery_file)
+ unlink(recovery_file);
+
+ if(!quiet)
+ print_summary();
+
+ if(logging)
+ fclose(log_fd);
+
+ return 0;
+}
+
+
+int main(int argc, char *argv[])
+{
+ struct stat buf, source_buf;
+ int res, i;
+ char *root_name = NULL;
+ squashfs_inode inode;
+ int readq;
+ int fragq;
+ int bwriteq;
+ int fwriteq;
+ int total_mem = get_default_phys_mem();
+ int progress = TRUE;
+ int force_progress = FALSE;
+ struct file_buffer **fragment = NULL;
+ char *command;
+
+ /* skip leading path components in invocation command */
+ for(command = argv[0] + strlen(argv[0]) - 1;
+ command >= argv[0] && command[0] != '/'; command--);
+
+ if(command < argv[0])
+ command = argv[0];
+ else
+ command++;
+
+ if(strcmp(command, "sqfstar") == 0)
+ return sqfstar(argc, argv);
+
+ if(argc > 1 && strcmp(argv[1], "-version") == 0) {
+ print_version("mksquashfs");
+ exit(0);
+ }
+
+ block_log = slog(block_size);
+ calculate_queue_sizes(total_mem, &readq, &fragq, &bwriteq, &fwriteq);
+
+ for(i = 1; i < argc && (argv[i][0] != '-' || strcmp(argv[i], "-") == 0);
+ i++);
+
+ if(i < argc && (strcmp(argv[i], "-help") == 0 ||
+ strcmp(argv[i], "-h") == 0)) {
+ print_options(stdout, argv[0], total_mem);
+ exit(0);
+ }
+
+ if(i < argc && strcmp(argv[i], "-mem-default") == 0) {
+ printf("%d\n", total_mem);
+ exit(0);
+ }
+
+ if(i < 3) {
+ print_options(stderr, argv[0], total_mem);
+ exit(1);
+ }
+
+ option_offset = i;
+ destination_file = argv[i - 1];
+
+ if(argv[1][0] != '-') {
+ source_path = argv + 1;
+ source = i - 2;
+ } else {
+ source_path = NULL;
+ source = 0;
+ }
+
+ /*
+ * Scan the command line for -comp xxx option, this is to ensure
+ * any -X compressor specific options are passed to the
+ * correct compressor
+ */
+ for(; i < argc; i++) {
+ struct compressor *prev_comp = comp;
+
+ if(strcmp(argv[i], "-comp") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -comp missing compression type\n",
+ argv[0]);
+ exit(1);
+ }
+ comp = lookup_compressor(argv[i]);
+ if(!comp->supported) {
+ ERROR("%s: Compressor \"%s\" is not supported!"
+ "\n", argv[0], argv[i]);
+ ERROR("%s: Compressors available:\n", argv[0]);
+ display_compressors(stderr, "", COMP_DEFAULT);
+ exit(1);
+ }
+ if(prev_comp != NULL && prev_comp != comp) {
+ ERROR("%s: -comp multiple conflicting -comp"
+ " options specified on command line"
+ ", previously %s, now %s\n", argv[0],
+ prev_comp->name, comp->name);
+ exit(1);
+ }
+ compressor_opt_parsed = 1;
+
+ } else if(strcmp(argv[i], "-e") == 0)
+ break;
+ else if(option_with_arg(argv[i], option_table))
+ i++;
+ }
+
+ /*
+ * if no -comp option specified lookup default compressor. Note the
+ * Makefile ensures the default compressor has been built, and so we
+ * don't need to to check for failure here
+ */
+ if(comp == NULL)
+ comp = lookup_compressor(COMP_DEFAULT);
+
+ /*
+ * Scan the command line for -cpiostyle, -tar and -pf xxx options, this
+ * is to ensure only one thing is trying to read from stdin
+ */
+ for(i = option_offset; i < argc; i++) {
+ if(strcmp(argv[i], "-cpiostyle") == 0)
+ cpiostyle = TRUE;
+ else if(strcmp(argv[i], "-cpiostyle0") == 0) {
+ cpiostyle = TRUE;
+ filename_terminator = '\0';
+ } else if(strcmp(argv[i], "-tar") == 0) {
+ tarfile = TRUE;
+ always_use_fragments = TRUE;
+ } else if(strcmp(argv[i], "-pf") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -pf missing filename\n", argv[0]);
+ exit(1);
+ }
+ if(strcmp(argv[i], "-") == 0)
+ pseudo_stdin = TRUE;
+ } else if(strcmp(argv[i], "-e") == 0)
+ break;
+ else if(option_with_arg(argv[i], option_table))
+ i++;
+ }
+
+ /*
+ * Only one of cpiostyle, tar and pseudo file reading from stdin can
+ * be specified
+ */
+ if((!cpiostyle || tarfile || pseudo_stdin) &&
+ (!tarfile || cpiostyle || pseudo_stdin) &&
+ (!pseudo_stdin || cpiostyle || tarfile) &&
+ (cpiostyle || tarfile || pseudo_stdin))
+ BAD_ERROR("Only one of cpiostyle, tar file or pseudo file "
+ "reading from stdin can be specified\n");
+
+ for(i = option_offset; i < argc; i++) {
+ if(strcmp(argv[i], "-ignore-zeros") == 0)
+ ignore_zeros = TRUE;
+ if(strcmp(argv[i], "-one-file-system") == 0)
+ one_file_system = TRUE;
+ else if(strcmp(argv[i], "-one-file-system-x") == 0)
+ one_file_system = one_file_system_x = TRUE;
+ else if(strcmp(argv[i], "-recovery-path") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -recovery-path missing pathname\n",
+ argv[0]);
+ exit(1);
+ }
+ recovery_pathname = argv[i];
+ } else if(strcmp(argv[i], "-help") == 0 ||
+ strcmp(argv[i], "-h") == 0) {
+ print_options(stdout, argv[0], total_mem);
+ exit(0);
+ } else if(strcmp(argv[i], "-no-hardlinks") == 0)
+ no_hardlinks = TRUE;
+ else if(strcmp(argv[i], "-no-strip") == 0 ||
+ strcmp(argv[i], "-tarstyle") == 0)
+ tarstyle = TRUE;
+ else if(strcmp(argv[i], "-max-depth") == 0) {
+ if((++i == argc) || !parse_num_unsigned(argv[i], &max_depth)) {
+ ERROR("%s: %s missing or invalid value\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-throttle") == 0) {
+ if((++i == argc) || !parse_num(argv[i], &sleep_time)) {
+ ERROR("%s: %s missing or invalid value\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ if(sleep_time > 99) {
+ ERROR("%s: %s value should be between 0 and "
+ "99\n", argv[0], argv[i - 1]);
+ exit(1);
+ }
+ readq = 4;
+ } else if(strcmp(argv[i], "-limit") == 0) {
+ if((++i == argc) || !parse_num(argv[i], &sleep_time)) {
+ ERROR("%s: %s missing or invalid value\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ if(sleep_time < 1 || sleep_time > 100) {
+ ERROR("%s: %s value should be between 1 and "
+ "100\n", argv[0], argv[i - 1]);
+ exit(1);
+ }
+ sleep_time = 100 - sleep_time;
+ readq = 4;
+ } else if(strcmp(argv[i], "-mkfs-time") == 0 ||
+ strcmp(argv[i], "-fstime") == 0) {
+ if((++i == argc) ||
+ (!parse_num_unsigned(argv[i], &mkfs_time) &&
+ !exec_date(argv[i], &mkfs_time))) {
+ ERROR("%s: %s missing or invalid time "
+ "value\n", argv[0],
+ argv[i - 1]);
+ exit(1);
+ }
+ mkfs_time_opt = TRUE;
+ } else if(strcmp(argv[i], "-all-time") == 0) {
+ if((++i == argc) ||
+ (!parse_num_unsigned(argv[i], &all_time) &&
+ !exec_date(argv[i], &all_time))) {
+ ERROR("%s: %s missing or invalid time "
+ "value\n", argv[0],
+ argv[i - 1]);
+ exit(1);
+ }
+ all_time_opt = TRUE;
+ clamping = FALSE;
+ } else if(strcmp(argv[i], "-reproducible") == 0)
+ reproducible = TRUE;
+ else if(strcmp(argv[i], "-not-reproducible") == 0)
+ reproducible = FALSE;
+ else if(strcmp(argv[i], "-root-mode") == 0) {
+ if((++i == argc) || !parse_mode(argv[i], &root_mode)) {
+ ERROR("%s: -root-mode missing or invalid mode,"
+ " octal number <= 07777 expected\n",
+ argv[0]);
+ exit(1);
+ }
+ root_mode_opt = TRUE;
+ } else if(strcmp(argv[i], "-root-uid") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -root-uid missing uid or user name\n",
+ argv[0]);
+ exit(1);
+ }
+
+ res = get_uid_from_arg(argv[i], &root_uid);
+ if(res) {
+ if(res == -2)
+ ERROR("%s: -root-uid uid out of range\n",
+ argv[0]);
+ else
+ ERROR("%s: -root-uid invalid uid or "
+ "unknown user name\n", argv[0]);
+ exit(1);
+ }
+ root_uid_opt = TRUE;
+ } else if(strcmp(argv[i], "-root-gid") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -root-gid missing gid or group name\n",
+ argv[0]);
+ exit(1);
+ }
+
+ res = get_gid_from_arg(argv[i], &root_gid);
+ if(res) {
+ if(res == -2)
+ ERROR("%s: -root-gid gid out of range\n",
+ argv[0]);
+ else
+ ERROR("%s: -root-gid invalid gid or "
+ "unknown group name\n", argv[0]);
+ exit(1);
+ }
+ root_gid_opt = TRUE;
+ } else if(strcmp(argv[i], "-root-time") == 0) {
+ if((++i == argc) ||
+ (!parse_num_unsigned(argv[i], &root_time) &&
+ !exec_date(argv[i], &root_time))) {
+ ERROR("%s: -root-time missing or invalid time\n",
+ argv[0]);
+ exit(1);
+ }
+ root_time_opt = TRUE;
+ } else if(strcmp(argv[i], "-default-mode") == 0) {
+ if((++i == argc) || !parse_mode(argv[i], &default_mode)) {
+ ERROR("%s: -default-mode missing or invalid mode,"
+ " octal number <= 07777 expected\n", argv[0]);
+ exit(1);
+ }
+ root_mode = default_mode;
+ default_mode_opt = root_mode_opt = TRUE;
+ } else if(strcmp(argv[i], "-default-uid") == 0) {
+ if((++i == argc) || !parse_num_unsigned(argv[i], &default_uid)) {
+ ERROR("%s: -default-uid missing or invalid uid\n",
+ argv[0]);
+ exit(1);
+ }
+ root_uid = default_uid;
+ default_uid_opt = root_uid_opt = TRUE;
+ } else if(strcmp(argv[i], "-default-gid") == 0) {
+ if((++i == argc) || !parse_num_unsigned(argv[i], &default_gid)) {
+ ERROR("%s: -default-gid missing or invalid gid\n",
+ argv[0]);
+ exit(1);
+ }
+ root_gid = default_gid;
+ default_gid_opt = root_gid_opt = TRUE;
+ } else if(strcmp(argv[i], "-log") == 0) {
+ if(++i == argc) {
+ ERROR("%s: %s missing log file\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ open_log_file(argv[i]);
+
+ } else if(strcmp(argv[i], "-action") == 0 ||
+ strcmp(argv[i], "-a") ==0) {
+ if(++i == argc) {
+ ERROR("%s: %s missing action\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ res = parse_action(argv[i], ACTION_LOG_NONE);
+ if(res == 0)
+ exit(1);
+
+ } else if(strcmp(argv[i], "-log-action") == 0 ||
+ strcmp(argv[i], "-va") ==0) {
+ if(++i == argc) {
+ ERROR("%s: %s missing action\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ res = parse_action(argv[i], ACTION_LOG_VERBOSE);
+ if(res == 0)
+ exit(1);
+
+ } else if(strcmp(argv[i], "-true-action") == 0 ||
+ strcmp(argv[i], "-ta") ==0) {
+ if(++i == argc) {
+ ERROR("%s: %s missing action\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ res = parse_action(argv[i], ACTION_LOG_TRUE);
+ if(res == 0)
+ exit(1);
+
+ } else if(strcmp(argv[i], "-false-action") == 0 ||
+ strcmp(argv[i], "-fa") ==0) {
+ if(++i == argc) {
+ ERROR("%s: %s missing action\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ res = parse_action(argv[i], ACTION_LOG_FALSE);
+ if(res == 0)
+ exit(1);
+
+ } else if(strcmp(argv[i], "-action-file") == 0 ||
+ strcmp(argv[i], "-af") ==0) {
+ if(++i == argc) {
+ ERROR("%s: %s missing filename\n", argv[0],
+ argv[i - 1]);
+ exit(1);
+ }
+ if(read_action_file(argv[i], ACTION_LOG_NONE) == FALSE)
+ exit(1);
+
+ } else if(strcmp(argv[i], "-log-action-file") == 0 ||
+ strcmp(argv[i], "-vaf") ==0) {
+ if(++i == argc) {
+ ERROR("%s: %s missing filename\n", argv[0],
+ argv[i - 1]);
+ exit(1);
+ }
+ if(read_action_file(argv[i], ACTION_LOG_VERBOSE) == FALSE)
+ exit(1);
+
+ } else if(strcmp(argv[i], "-true-action-file") == 0 ||
+ strcmp(argv[i], "-taf") ==0) {
+ if(++i == argc) {
+ ERROR("%s: %s missing filename\n", argv[0],
+ argv[i - 1]);
+ exit(1);
+ }
+ if(read_action_file(argv[i], ACTION_LOG_TRUE) == FALSE)
+ exit(1);
+
+ } else if(strcmp(argv[i], "-false-action-file") == 0 ||
+ strcmp(argv[i], "-faf") ==0) {
+ if(++i == argc) {
+ ERROR("%s: %s missing filename\n", argv[0],
+ argv[i - 1]);
+ exit(1);
+ }
+ if(read_action_file(argv[i], ACTION_LOG_FALSE) == FALSE)
+ exit(1);
+
+ } else if(strncmp(argv[i], "-X", 2) == 0) {
+ int args;
+
+ if(strcmp(argv[i] + 2, "help") == 0)
+ goto print_compressor_options;
+
+ args = compressor_options(comp, argv + i, argc - i);
+ if(args < 0) {
+ if(args == -1) {
+ ERROR("%s: Unrecognised compressor"
+ " option %s\n", argv[0],
+ argv[i]);
+ if(!compressor_opt_parsed)
+ ERROR("%s: Did you forget to"
+ " specify -comp?\n",
+ argv[0]);
+print_compressor_options:
+ ERROR("%s: selected compressor \"%s\""
+ ". Options supported: %s\n",
+ argv[0], comp->name,
+ comp->usage ? "" : "none");
+ if(comp->usage)
+ comp->usage(stderr);
+ }
+ exit(1);
+ }
+ i += args;
+
+ } else if(strcmp(argv[i], "-pf") == 0) {
+ if(read_pseudo_file(argv[++i], destination_file) == FALSE)
+ exit(1);
+ } else if(strcmp(argv[i], "-p") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -p missing pseudo file definition\n",
+ argv[0]);
+ exit(1);
+ }
+ if(read_pseudo_definition(argv[i], destination_file) == FALSE)
+ exit(1);
+ } else if(strcmp(argv[i], "-recover") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -recover missing recovery file\n",
+ argv[0]);
+ exit(1);
+ }
+ read_recovery_data(argv[i], destination_file);
+ } else if(strcmp(argv[i], "-no-recovery") == 0)
+ recover = FALSE;
+ else if(strcmp(argv[i], "-wildcards") == 0) {
+ old_exclude = FALSE;
+ use_regex = FALSE;
+ } else if(strcmp(argv[i], "-regex") == 0) {
+ old_exclude = FALSE;
+ use_regex = TRUE;
+ } else if(strcmp(argv[i], "-no-sparse") == 0)
+ sparse_files = FALSE;
+ else if(strcmp(argv[i], "-no-progress") == 0)
+ progress = FALSE;
+ else if(strcmp(argv[i], "-progress") == 0)
+ force_progress = TRUE;
+ else if(strcmp(argv[i], "-exports") == 0)
+ exportable = TRUE;
+ else if(strcmp(argv[i], "-no-exports") == 0)
+ exportable = FALSE;
+ else if(strcmp(argv[i], "-offset") == 0 ||
+ strcmp(argv[i], "-o") == 0) {
+ if((++i == argc) ||
+ !parse_numberll(argv[i], &start_offset, 1)) {
+ ERROR("%s: %s missing or invalid offset "
+ "size\n", argv[0], argv[i - 1]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-processors") == 0) {
+ if((++i == argc) || !parse_num(argv[i], &processors)) {
+ ERROR("%s: -processors missing or invalid "
+ "processor number\n", argv[0]);
+ exit(1);
+ }
+ if(processors < 1) {
+ ERROR("%s: -processors should be 1 or larger\n",
+ argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-read-queue") == 0) {
+ if((++i == argc) || !parse_num(argv[i], &readq)) {
+ ERROR("%s: -read-queue missing or invalid "
+ "queue size\n", argv[0]);
+ exit(1);
+ }
+ if(readq < 1) {
+ ERROR("%s: -read-queue should be 1 megabyte or "
+ "larger\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-write-queue") == 0) {
+ if((++i == argc) || !parse_num(argv[i], &bwriteq)) {
+ ERROR("%s: -write-queue missing or invalid "
+ "queue size\n", argv[0]);
+ exit(1);
+ }
+ if(bwriteq < 2) {
+ ERROR("%s: -write-queue should be 2 megabytes "
+ "or larger\n", argv[0]);
+ exit(1);
+ }
+ fwriteq = bwriteq >> 1;
+ bwriteq -= fwriteq;
+ } else if(strcmp(argv[i], "-fragment-queue") == 0) {
+ if((++i == argc) || !parse_num(argv[i], &fragq)) {
+ ERROR("%s: -fragment-queue missing or invalid "
+ "queue size\n", argv[0]);
+ exit(1);
+ }
+ if(fragq < 1) {
+ ERROR("%s: -fragment-queue should be 1 "
+ "megabyte or larger\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-mem") == 0) {
+ long long number;
+
+ if((++i == argc) ||
+ !parse_numberll(argv[i], &number, 1)) {
+ ERROR("%s: -mem missing or invalid mem size\n",
+ argv[0]);
+ exit(1);
+ }
+
+ /*
+ * convert from bytes to Mbytes, ensuring the value
+ * does not overflow a signed int
+ */
+ if(number >= (1LL << 51)) {
+ ERROR("%s: -mem invalid mem size\n", argv[0]);
+ exit(1);
+ }
+
+ total_mem = number / 1048576;
+ if(total_mem < (SQUASHFS_LOWMEM / SQUASHFS_TAKE)) {
+ ERROR("%s: -mem should be %d Mbytes or "
+ "larger\n", argv[0],
+ SQUASHFS_LOWMEM / SQUASHFS_TAKE);
+ exit(1);
+ }
+ calculate_queue_sizes(total_mem, &readq, &fragq,
+ &bwriteq, &fwriteq);
+ } else if(strcmp(argv[i], "-mem-percent") == 0) {
+ int percent, phys_mem;
+
+ /*
+ * Percentage of 75% and larger is dealt with later.
+ * In the same way a fixed mem size if more than 75%
+ * of memory is dealt with later.
+ */
+ if((++i == argc) ||
+ !parse_number(argv[i], &percent, 1) ||
+ (percent < 1)) {
+ ERROR("%s: -mem-percent missing or invalid "
+ "percentage: it should be 1 - 75%\n",
+ argv[0]);
+ exit(1);
+ }
+
+ phys_mem = get_physical_memory();
+
+ if(phys_mem == 0) {
+ ERROR("%s: -mem-percent unable to get physical "
+ "memory, use -mem instead\n", argv[0]);
+ exit(1);
+ }
+
+ if(multiply_overflow(phys_mem, percent)) {
+ ERROR("%s: -mem-percent requested phys mem too "
+ "large\n", argv[0]);
+ exit(1);
+ }
+
+ total_mem = phys_mem * percent / 100;
+
+ if(total_mem < (SQUASHFS_LOWMEM / SQUASHFS_TAKE)) {
+ ERROR("%s: -mem-percent mem too small, should "
+ "be %d Mbytes or larger\n", argv[0],
+ SQUASHFS_LOWMEM / SQUASHFS_TAKE);
+ exit(1);
+ }
+
+ calculate_queue_sizes(total_mem, &readq, &fragq,
+ &bwriteq, &fwriteq);
+ } else if(strcmp(argv[i], "-mem-default") == 0) {
+ printf("%d\n", total_mem);
+ exit(0);
+ } else if(strcmp(argv[i], "-b") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -b missing block size\n", argv[0]);
+ exit(1);
+ }
+ if(!parse_number(argv[i], &block_size, 1)) {
+ ERROR("%s: -b invalid block size\n", argv[0]);
+ exit(1);
+ }
+ if((block_log = slog(block_size)) == 0) {
+ ERROR("%s: -b block size not power of two or "
+ "not between 4096 and 1Mbyte\n",
+ argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-ef") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -ef missing filename\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-no-duplicates") == 0)
+ duplicate_checking = FALSE;
+
+ else if(strcmp(argv[i], "-no-fragments") == 0)
+ no_fragments = TRUE;
+
+ else if(strcmp(argv[i], "-tailends") == 0 ||
+ strcmp(argv[i], "-always-use-fragments") == 0)
+ always_use_fragments = TRUE;
+
+ else if(strcmp(argv[i], "-no-tailends") == 0)
+ always_use_fragments = FALSE;
+
+ else if(strcmp(argv[i], "-sort") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -sort missing filename\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-all-root") == 0 ||
+ strcmp(argv[i], "-root-owned") == 0) {
+ global_uid = global_gid = 0;
+ global_uid_opt = global_gid_opt = TRUE;
+ } else if(strcmp(argv[i], "-force-uid") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -force-uid missing uid or user name\n",
+ argv[0]);
+ exit(1);
+ }
+
+ res = get_uid_from_arg(argv[i], &global_uid);
+ if(res) {
+ if(res == -2)
+ ERROR("%s: -force-uid uid out of range\n",
+ argv[0]);
+ else
+ ERROR("%s: -force-uid invalid uid or "
+ "unknown user name\n", argv[0]);
+ exit(1);
+ }
+ global_uid_opt = TRUE;
+ } else if(strcmp(argv[i], "-force-gid") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -force-gid missing gid or group name\n",
+ argv[0]);
+ exit(1);
+ }
+
+ res = get_gid_from_arg(argv[i], &global_gid);
+ if(res) {
+ if(res == -2)
+ ERROR("%s: -force-gid gid out of range"
+ "\n", argv[0]);
+ else
+ ERROR("%s: -force-gid invalid gid or "
+ "unknown group name\n", argv[0]);
+ exit(1);
+ }
+ global_gid_opt = TRUE;
+ } else if(strcmp(argv[i], "-pseudo-override") == 0)
+ pseudo_override = TRUE;
+ else if(strcmp(argv[i], "-noI") == 0 ||
+ strcmp(argv[i], "-noInodeCompression") == 0)
+ noI = TRUE;
+
+ else if(strcmp(argv[i], "-noId") == 0 ||
+ strcmp(argv[i], "-noIdTableCompression") == 0)
+ noId = TRUE;
+
+ else if(strcmp(argv[i], "-noD") == 0 ||
+ strcmp(argv[i], "-noDataCompression") == 0)
+ noD = TRUE;
+
+ else if(strcmp(argv[i], "-noF") == 0 ||
+ strcmp(argv[i], "-noFragmentCompression") == 0)
+ noF = TRUE;
+
+ else if(strcmp(argv[i], "-noX") == 0 ||
+ strcmp(argv[i], "-noXattrCompression") == 0)
+ noX = TRUE;
+
+ else if(strcmp(argv[i], "-no-compression") == 0)
+ noI = noD = noF = noX = TRUE;
+
+ else if(strcmp(argv[i], "-no-xattrs") == 0) {
+ if(xattr_exclude_preg || xattr_include_preg ||
+ add_xattrs()) {
+ ERROR("%s: -no-xattrs should not be used in "
+ "combination with -xattrs-* options\n",
+ argv[0]);
+ exit(1);
+ }
+
+ no_xattrs = TRUE;
+
+ } else if(strcmp(argv[i], "-xattrs") == 0) {
+ if(xattrs_supported())
+ no_xattrs = FALSE;
+ else {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ }
+
+ } else if(strcmp(argv[i], "-xattrs-exclude") == 0) {
+ if(!xattrs_supported()) {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ } else if(++i == argc) {
+ ERROR("%s: -xattrs-exclude missing regex pattern\n",
+ argv[0]);
+ exit(1);
+ } else {
+ xattr_exclude_preg = xattr_regex(argv[i], "exclude");
+ no_xattrs = FALSE;
+ }
+
+ } else if(strcmp(argv[i], "-xattrs-include") == 0) {
+ if(!xattrs_supported()) {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ } else if(++i == argc) {
+ ERROR("%s: -xattrs-include missing regex pattern\n",
+ argv[0]);
+ exit(1);
+ } else {
+ xattr_include_preg = xattr_regex(argv[i], "include");
+ no_xattrs = FALSE;
+ }
+ } else if(strcmp(argv[i], "-xattrs-add") == 0) {
+ if(!xattrs_supported()) {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ } else if(++i == argc) {
+ ERROR("%s: -xattrs-add missing xattr argument\n",
+ argv[0]);
+ exit(1);
+ } else {
+ xattrs_add(argv[i]);
+ no_xattrs = FALSE;
+ }
+ } else if(strcmp(argv[i], "-nopad") == 0)
+ nopad = TRUE;
+
+ else if(strcmp(argv[i], "-info") == 0)
+ silent = FALSE;
+
+ else if(strcmp(argv[i], "-e") == 0)
+ break;
+
+ else if(strcmp(argv[i], "-noappend") == 0)
+ appending = FALSE;
+
+ else if(strcmp(argv[i], "-quiet") == 0)
+ quiet = TRUE;
+
+ else if(strcmp(argv[i], "-keep-as-directory") == 0)
+ keep_as_directory = TRUE;
+
+ else if(strcmp(argv[i], "-exit-on-error") == 0)
+ exit_on_error = TRUE;
+
+ else if(strcmp(argv[i], "-root-becomes") == 0) {
+ if(++i == argc) {
+ ERROR("%s: -root-becomes: missing name\n",
+ argv[0]);
+ exit(1);
+ }
+ root_name = argv[i];
+ } else if(strcmp(argv[i], "-percentage") == 0) {
+ progressbar_percentage();
+ progress = silent = TRUE;
+ } else if(strcmp(argv[i], "-version") == 0) {
+ print_version("mksquashfs");
+ } else if(strcmp(argv[i], "-cpiostyle") == 0 ||
+ strcmp(argv[i], "-cpiostyle0") == 0 ||
+ strcmp(argv[i], "-tar") == 0) {
+ /* parsed previously */
+ } else if(strcmp(argv[i], "-comp") == 0) {
+ /* parsed previously */
+ i++;
+ } else {
+ ERROR("%s: invalid option\n\n", argv[0]);
+ print_options(stderr, argv[0], total_mem);
+ exit(1);
+ }
+ }
+
+ check_env_var();
+
+ /* If cpiostyle is set, then file names will be read-in
+ * from standard in. We do not expect to have any sources
+ * specified on the command line */
+ if(cpiostyle && source)
+ BAD_ERROR("Sources on the command line should be -, "
+ "when using -cpiostyle[0] options\n");
+
+ /* If -tar option is set, then files will be read-in
+ * from standard in. We do not expect to have any sources
+ * specified on the command line */
+ if(tarfile && source)
+ BAD_ERROR("Sources on the command line should be -, "
+ "when using -tar option\n");
+
+ /* If -tar option is set, then check that actions have not been
+ * specified, which are unsupported with tar file reading
+ */
+ if(tarfile && any_actions())
+ BAD_ERROR("Actions are unsupported when reading tar files\n");
+
+ /*
+ * The -noI option implies -noId for backwards compatibility, so reset
+ * noId if both have been specified
+ */
+ if(noI && noId)
+ noId = FALSE;
+
+ /*
+ * Some compressors may need the options to be checked for validity
+ * once all the options have been processed
+ */
+ res = compressor_options_post(comp, block_size);
+ if(res)
+ EXIT_MKSQUASHFS();
+
+ /*
+ * If the -info option has been selected then disable the
+ * progress bar unless it has been explicitly enabled with
+ * the -progress option
+ */
+ if(!silent)
+ progress = force_progress;
+
+ /*
+ * Sort all the xattr-add options now they're all processed
+ */
+ sort_xattr_add_list();
+
+ /*
+ * If -pseudo-override option has been specified and there are
+ * no pseudo files then reset option. -pseudo-override relies
+ * on dir_scan2() being run, which won't be if there's no
+ * actions or pseudo files
+ */
+ if(pseudo_override && !get_pseudo())
+ pseudo_override = FALSE;
+
+#ifdef SQUASHFS_TRACE
+ /*
+ * Disable progress bar if full debug tracing is enabled.
+ * The progress bar in this case just gets in the way of the
+ * debug trace output
+ */
+ progress = FALSE;
+#endif
+
+ if(one_file_system && source > 1) {
+ source_dev = malloc(source * sizeof(dev_t));
+ if(source_dev == NULL)
+ MEM_ERROR();
+ }
+
+ for(i = 0; i < source; i++) {
+ if(lstat(source_path[i], &source_buf) == -1) {
+ fprintf(stderr, "Cannot stat source directory \"%s\" "
+ "because %s\n", source_path[i],
+ strerror(errno));
+ EXIT_MKSQUASHFS();
+ }
+
+ if(one_file_system) {
+ if(source > 1)
+ source_dev[i] = source_buf.st_dev;
+ else
+ cur_dev = source_buf.st_dev;
+ }
+ }
+
+ if(stat(destination_file, &buf) == -1) {
+ if(errno == ENOENT) { /* Does not exist */
+ appending = FALSE;
+ fd = open(destination_file, O_CREAT | O_TRUNC | O_RDWR,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if(fd == -1) {
+ perror("Could not create destination file");
+ exit(1);
+ }
+
+ /* ensure Mksquashfs doesn't try to read
+ * the destination file as input, which
+ * will result in an I/O loop */
+ if(stat(destination_file, &buf) == -1) {
+ /* disappered after creating? */
+ perror("Could not stat destination file");
+ exit(1);
+ }
+ ADD_ENTRY(buf);
+ } else {
+ perror("Could not stat destination file");
+ exit(1);
+ }
+
+ } else {
+ if(!S_ISBLK(buf.st_mode) && !S_ISREG(buf.st_mode)) {
+ ERROR("Destination not block device or regular file\n");
+ exit(1);
+ }
+
+ if(tarfile && appending) {
+ ERROR("Appending is not supported reading tar files\n");
+ ERROR("To force Mksquashfs to write to this %s "
+ "use -noappend\n", S_ISBLK(buf.st_mode) ?
+ "block device" : "file");
+ EXIT_MKSQUASHFS();
+ }
+
+ if(S_ISBLK(buf.st_mode)) {
+ if((fd = open(destination_file, O_RDWR)) == -1) {
+ perror("Could not open block device as "
+ "destination");
+ exit(1);
+ }
+ block_device = 1;
+
+ } else {
+ fd = open(destination_file, (!appending ? O_TRUNC : 0) |
+ O_RDWR);
+ if(fd == -1) {
+ perror("Could not open regular file for "
+ "writing as destination");
+ exit(1);
+ }
+ /* ensure Mksquashfs doesn't try to read
+ * the destination file as input, which
+ * will result in an I/O loop */
+ ADD_ENTRY(buf);
+ }
+ }
+
+ /*
+ * process the exclude files - must be done afer destination file has
+ * been possibly created
+ */
+ for(i = option_offset; i < argc; i++)
+ if(strcmp(argv[i], "-ef") == 0)
+ /*
+ * Note presence of filename arg has already
+ * been checked
+ */
+ process_exclude_file(argv[++i]);
+ else if(strcmp(argv[i], "-e") == 0)
+ break;
+ else if(option_with_arg(argv[i], option_table))
+ i++;
+
+ if(i != argc) {
+ if(++i == argc) {
+ ERROR("%s: -e missing arguments\n", argv[0]);
+ EXIT_MKSQUASHFS();
+ }
+ while(i < argc)
+ if(old_exclude)
+ old_add_exclude(argv[i++]);
+ else
+ add_exclude(argv[i++]);
+ }
+
+ /* process the sort files - must be done afer the exclude files */
+ for(i = option_offset; i < argc; i++)
+ if(strcmp(argv[i], "-sort") == 0) {
+ if(tarfile)
+ BAD_ERROR("Sorting files is unsupported when "
+ "reading tar files\n");
+
+ res = read_sort_file(argv[++i], source, source_path);
+ if(res == FALSE)
+ BAD_ERROR("Failed to read sort file\n");
+ sorted ++;
+ } else if(strcmp(argv[i], "-e") == 0)
+ break;
+ else if(option_with_arg(argv[i], option_table))
+ i++;
+
+ if(appending) {
+ comp = read_super(fd, &sBlk, destination_file);
+ if(comp == NULL) {
+ ERROR("Failed to read existing filesystem - will not "
+ "overwrite - ABORTING!\n");
+ ERROR("To force Mksquashfs to write to this %s "
+ "use -noappend\n", block_device ?
+ "block device" : "file");
+ EXIT_MKSQUASHFS();
+ }
+
+ block_log = slog(block_size = sBlk.block_size);
+ noI = SQUASHFS_UNCOMPRESSED_INODES(sBlk.flags);
+ noD = SQUASHFS_UNCOMPRESSED_DATA(sBlk.flags);
+ noF = SQUASHFS_UNCOMPRESSED_FRAGMENTS(sBlk.flags);
+ noX = SQUASHFS_UNCOMPRESSED_XATTRS(sBlk.flags);
+ noId = SQUASHFS_UNCOMPRESSED_IDS(sBlk.flags);
+ no_fragments = SQUASHFS_NO_FRAGMENTS(sBlk.flags);
+ always_use_fragments = SQUASHFS_ALWAYS_FRAGMENTS(sBlk.flags);
+ duplicate_checking = SQUASHFS_DUPLICATES(sBlk.flags);
+ exportable = SQUASHFS_EXPORTABLE(sBlk.flags);
+ no_xattrs = SQUASHFS_NO_XATTRS(sBlk.flags);
+ comp_opts = SQUASHFS_COMP_OPTS(sBlk.flags);
+ }
+
+ initialise_threads(readq, fragq, bwriteq, fwriteq, !appending,
+ destination_file);
+
+ res = compressor_init(comp, &stream, SQUASHFS_METADATA_SIZE, 0);
+ if(res)
+ BAD_ERROR("compressor_init failed\n");
+
+ dupl_block = malloc(1048576 * sizeof(struct file_info *));
+ if(dupl_block == NULL)
+ MEM_ERROR();
+
+ dupl_frag = malloc(block_size * sizeof(struct file_info *));
+ if(dupl_frag == NULL)
+ MEM_ERROR();
+
+ memset(dupl_block, 0, 1048576 * sizeof(struct file_info *));
+ memset(dupl_frag, 0, block_size * sizeof(struct file_info *));
+
+ if(!appending) {
+ int size;
+ void *comp_data = compressor_dump_options(comp, block_size,
+ &size);
+
+ if(!quiet)
+ printf("Creating %d.%d filesystem on %s, block size %d.\n",
+ SQUASHFS_MAJOR, SQUASHFS_MINOR,
+ destination_file, block_size);
+
+ /*
+ * store any compressor specific options after the superblock,
+ * and set the COMP_OPT flag to show that the filesystem has
+ * compressor specfic options
+ */
+ if(comp_data) {
+ unsigned short c_byte = size | SQUASHFS_COMPRESSED_BIT;
+
+ SQUASHFS_INSWAP_SHORTS(&c_byte, 1);
+ write_destination(fd, sizeof(struct squashfs_super_block),
+ sizeof(c_byte), &c_byte);
+ write_destination(fd, sizeof(struct squashfs_super_block) +
+ sizeof(c_byte), size, comp_data);
+ bytes = sizeof(struct squashfs_super_block) + sizeof(c_byte)
+ + size;
+ comp_opts = TRUE;
+ } else
+ bytes = sizeof(struct squashfs_super_block);
+ } else {
+ unsigned int last_directory_block, inode_dir_file_size,
+ root_inode_size, inode_dir_start_block,
+ compressed_data, inode_dir_inode_number,
+ inode_dir_parent_inode;
+ unsigned int root_inode_start =
+ SQUASHFS_INODE_BLK(sBlk.root_inode),
+ root_inode_offset =
+ SQUASHFS_INODE_OFFSET(sBlk.root_inode);
+ int inode_dir_offset, uncompressed_data;
+
+ if((bytes = read_filesystem(root_name, fd, &sBlk, &inode_table,
+ &data_cache, &directory_table,
+ &directory_data_cache, &last_directory_block,
+ &inode_dir_offset, &inode_dir_file_size,
+ &root_inode_size, &inode_dir_start_block,
+ &file_count, &sym_count, &dev_count, &dir_count,
+ &fifo_count, &sock_count, &total_bytes,
+ &total_inode_bytes, &total_directory_bytes,
+ &inode_dir_inode_number,
+ &inode_dir_parent_inode, add_old_root_entry,
+ &fragment_table, &inode_lookup_table)) == 0) {
+ ERROR("Failed to read existing filesystem - will not "
+ "overwrite - ABORTING!\n");
+ ERROR("To force Mksquashfs to write to this block "
+ "device or file use -noappend\n");
+ EXIT_MKSQUASHFS();
+ }
+ if((fragments = sBlk.fragments)) {
+ fragment_table = realloc((char *) fragment_table,
+ ((fragments + FRAG_SIZE - 1) & ~(FRAG_SIZE - 1))
+ * sizeof(struct squashfs_fragment_entry));
+ if(fragment_table == NULL)
+ BAD_ERROR("Out of memory in save filesystem state\n");
+ }
+
+ if(!quiet) {
+ printf("Appending to existing %d.%d filesystem on "
+ "%s, block size %d\n", SQUASHFS_MAJOR,
+ SQUASHFS_MINOR, destination_file, block_size);
+ printf("All -b, -noI, -noD, -noF, -noX, -noId, "
+ "-no-duplicates, -no-fragments,\n"
+ "-always-use-fragments, -exportable and "
+ "-comp options ignored\n");
+ printf("\nIf appending is not wanted, please re-run "
+ "with -noappend specified!\n\n");
+ }
+
+ compressed_data = ((long long) inode_dir_offset +
+ inode_dir_file_size) & ~(SQUASHFS_METADATA_SIZE - 1);
+ uncompressed_data = ((long long) inode_dir_offset +
+ inode_dir_file_size) & (SQUASHFS_METADATA_SIZE - 1);
+
+ /* save original filesystem state for restoring ... */
+ sfragments = fragments;
+ sbytes = bytes;
+ sinode_count = sBlk.inodes;
+ scache_bytes = root_inode_offset + root_inode_size;
+ sdirectory_cache_bytes = uncompressed_data;
+ sdata_cache = malloc(scache_bytes);
+ if(sdata_cache == NULL)
+ BAD_ERROR("Out of memory in save filesystem state\n");
+ sdirectory_data_cache = malloc(sdirectory_cache_bytes);
+ if(sdirectory_data_cache == NULL)
+ BAD_ERROR("Out of memory in save filesystem state\n");
+ memcpy(sdata_cache, data_cache, scache_bytes);
+ memcpy(sdirectory_data_cache, directory_data_cache +
+ compressed_data, sdirectory_cache_bytes);
+ sinode_bytes = root_inode_start;
+ stotal_bytes = total_bytes;
+ stotal_inode_bytes = total_inode_bytes;
+ stotal_directory_bytes = total_directory_bytes +
+ compressed_data;
+ sfile_count = file_count;
+ ssym_count = sym_count;
+ sdev_count = dev_count;
+ sdir_count = dir_count + 1;
+ sfifo_count = fifo_count;
+ ssock_count = sock_count;
+ sdup_files = dup_files;
+ sid_count = id_count;
+ write_recovery_data(&sBlk);
+ save_xattrs();
+
+ /*
+ * set the filesystem state up to be able to append to the
+ * original filesystem. The filesystem state differs depending
+ * on whether we're appending to the original root directory, or
+ * if the original root directory becomes a sub-directory
+ * (root-becomes specified on command line, here root_name !=
+ * NULL)
+ */
+ inode_bytes = inode_size = root_inode_start;
+ directory_size = last_directory_block;
+ cache_size = root_inode_offset + root_inode_size;
+ directory_cache_size = inode_dir_offset + inode_dir_file_size;
+ if(root_name) {
+ sdirectory_bytes = last_directory_block;
+ sdirectory_compressed_bytes = 0;
+ root_inode_number = inode_dir_parent_inode;
+ inode_no = sBlk.inodes + 2;
+ directory_bytes = last_directory_block;
+ directory_cache_bytes = uncompressed_data;
+ memmove(directory_data_cache, directory_data_cache +
+ compressed_data, uncompressed_data);
+ cache_bytes = root_inode_offset + root_inode_size;
+ add_old_root_entry(root_name, sBlk.root_inode,
+ inode_dir_inode_number, SQUASHFS_DIR_TYPE);
+ total_directory_bytes += compressed_data;
+ dir_count ++;
+ } else {
+ sdirectory_compressed_bytes = last_directory_block -
+ inode_dir_start_block;
+ sdirectory_compressed =
+ malloc(sdirectory_compressed_bytes);
+ if(sdirectory_compressed == NULL)
+ BAD_ERROR("Out of memory in save filesystem "
+ "state\n");
+ memcpy(sdirectory_compressed, directory_table +
+ inode_dir_start_block,
+ sdirectory_compressed_bytes);
+ sdirectory_bytes = inode_dir_start_block;
+ root_inode_number = inode_dir_inode_number;
+ inode_no = sBlk.inodes + 1;
+ directory_bytes = inode_dir_start_block;
+ directory_cache_bytes = inode_dir_offset;
+ cache_bytes = root_inode_offset;
+ }
+
+ inode_count = file_count + dir_count + sym_count + dev_count +
+ fifo_count + sock_count;
+ }
+
+ if(path)
+ paths = add_subdir(paths, path);
+
+ dump_actions();
+ dump_pseudos();
+
+ set_progressbar_state(progress);
+
+ if(tarfile)
+ inode = process_tar_file(progress);
+ else if(tarstyle || cpiostyle)
+ inode = process_source(progress);
+ else if(!source)
+ inode = no_sources(progress);
+ else
+ inode = dir_scan(S_ISDIR(source_buf.st_mode), progress);
+
+ sBlk.root_inode = inode;
+ sBlk.inodes = inode_count;
+ sBlk.s_magic = SQUASHFS_MAGIC;
+ sBlk.s_major = SQUASHFS_MAJOR;
+ sBlk.s_minor = SQUASHFS_MINOR;
+ sBlk.block_size = block_size;
+ sBlk.block_log = block_log;
+ sBlk.flags = SQUASHFS_MKFLAGS(noI, noD, noF, noX, noId, no_fragments,
+ always_use_fragments, duplicate_checking, exportable,
+ no_xattrs, comp_opts);
+ sBlk.mkfs_time = mkfs_time_opt ? mkfs_time : time(NULL);
+
+ disable_info();
+
+ while((fragment = get_frag_action(fragment)))
+ write_fragment(*fragment);
+ if(!reproducible)
+ unlock_fragments();
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+ pthread_mutex_lock(&fragment_mutex);
+ while(fragments_outstanding) {
+ pthread_mutex_unlock(&fragment_mutex);
+ pthread_testcancel();
+ sched_yield();
+ pthread_mutex_lock(&fragment_mutex);
+ }
+ pthread_cleanup_pop(1);
+
+ queue_put(to_writer, NULL);
+ if(queue_get(from_writer) != 0)
+ EXIT_MKSQUASHFS();
+
+ set_progressbar_state(FALSE);
+ write_filesystem_tables(&sBlk);
+
+ if(!nopad && (i = bytes & (4096 - 1))) {
+ char temp[4096] = {0};
+ write_destination(fd, bytes, 4096 - i, temp);
+ }
+
+ res = close(fd);
+
+ if(res == -1)
+ BAD_ERROR("Failed to close output filesystem, close returned %s\n",
+ strerror(errno));
+
+ if(recovery_file)
+ unlink(recovery_file);
+
+ if(!quiet)
+ print_summary();
+
+ if(logging)
+ fclose(log_fd);
+
+ return 0;
+}
diff --git a/squashfs-tools/mksquashfs.h b/squashfs-tools/mksquashfs.h
new file mode 100644
index 0000000..10d31ff
--- /dev/null
+++ b/squashfs-tools/mksquashfs.h
@@ -0,0 +1,285 @@
+#ifndef MKSQUASHFS_H
+#define MKSQUASHFS_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
+ * 2012, 2013, 2014, 2019, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * mksquashfs.h
+ *
+ */
+
+struct dir_info {
+ char *pathname;
+ char *subpath;
+ unsigned int count;
+ unsigned int directory_count;
+ unsigned int depth;
+ unsigned int excluded;
+ char dir_is_ldir;
+ struct dir_ent *dir_ent;
+ struct dir_ent *list;
+ DIR *linuxdir;
+};
+
+struct dir_ent {
+ char *name;
+ char *source_name;
+ char *nonstandard_pathname;
+ struct inode_info *inode;
+ struct dir_info *dir;
+ struct dir_info *our_dir;
+ struct dir_ent *next;
+};
+
+struct inode_info {
+ struct stat buf;
+ struct inode_info *next;
+ struct pseudo_dev *pseudo;
+ struct tar_file *tar_file;
+ struct pseudo_xattr *xattr;
+ squashfs_inode inode;
+ unsigned int inode_number;
+ unsigned int nlink;
+ char dummy_root_dir;
+ char type;
+ char read;
+ char root_entry;
+ char no_fragments;
+ char always_use_fragments;
+ char noD;
+ char noF;
+ char tarfile;
+ char symlink[0];
+};
+
+
+/* in memory file info */
+struct file_info {
+ long long file_size;
+ long long bytes;
+ long long start;
+ long long sparse;
+ unsigned int *block_list;
+ struct file_info *frag_next;
+ struct file_info *block_next;
+ struct fragment *fragment;
+ struct dup_info *dup;
+ unsigned int blocks;
+ unsigned short checksum;
+ unsigned short fragment_checksum;
+ char have_frag_checksum;
+ char have_checksum;
+};
+
+
+struct dup_info {
+ struct file_info *file;
+ struct file_info *frag;
+ struct dup_info *next;
+};
+
+
+/* fragment block data structures */
+struct fragment {
+ unsigned int index;
+ int offset;
+ int size;
+};
+
+/* in memory uid tables */
+#define ID_ENTRIES 256
+#define ID_HASH(id) (id & (ID_ENTRIES - 1))
+#define ISA_UID 1
+#define ISA_GID 2
+
+struct id {
+ unsigned int id;
+ int index;
+ char flags;
+ struct id *next;
+};
+
+/* fragment to file mapping used when appending */
+struct append_file {
+ struct file_info *file;
+ struct append_file *next;
+};
+
+/*
+ * Amount of physical memory to use by default, and the default queue
+ * ratios
+ */
+#define SQUASHFS_TAKE 4
+#define SQUASHFS_READQ_MEM 4
+#define SQUASHFS_BWRITEQ_MEM 4
+#define SQUASHFS_FWRITEQ_MEM 4
+
+/*
+ * Lowest amount of physical memory considered viable for Mksquashfs
+ * to run in Mbytes
+ */
+#define SQUASHFS_LOWMEM 64
+
+/* offset of data in compressed metadata blocks (allowing room for
+ * compressed size */
+#define BLOCK_OFFSET 2
+
+#ifdef REPRODUCIBLE_DEFAULT
+#define NOREP_STR
+#define REP_STR " (default)"
+#define REP_DEF 1
+#else
+#define NOREP_STR " (default)"
+#define REP_STR
+#define REP_DEF 0
+#endif
+
+/* in memory directory data */
+#define I_COUNT_SIZE 128
+#define DIR_ENTRIES 32
+#define INODE_HASH_SIZE 65536
+#define INODE_HASH_MASK (INODE_HASH_SIZE - 1)
+#define INODE_HASH(dev, ino) (ino & INODE_HASH_MASK)
+
+struct cached_dir_index {
+ struct squashfs_dir_index index;
+ char *name;
+};
+
+struct directory {
+ unsigned int start_block;
+ unsigned int size;
+ unsigned char *buff;
+ unsigned char *p;
+ unsigned int entry_count;
+ unsigned char *entry_count_p;
+ unsigned int i_count;
+ unsigned int i_size;
+ struct cached_dir_index *index;
+ unsigned char *index_count_p;
+ unsigned int inode_number;
+};
+
+/* exclude file handling */
+/* list of exclude dirs/files */
+struct exclude_info {
+ dev_t st_dev;
+ ino_t st_ino;
+};
+
+#define EXCLUDE_SIZE 8192
+
+struct pathname {
+ int names;
+ struct path_entry *name;
+};
+
+struct pathnames {
+ int count;
+ struct pathname *path[0];
+};
+#define PATHS_ALLOC_SIZE 10
+
+#define FRAG_SIZE 32768
+
+struct old_root_entry_info {
+ char *name;
+ struct inode_info inode;
+};
+
+#define ALLOC_SIZE 128
+
+/* Maximum transfer size for Linux read() call on both 32-bit and 64-bit systems.
+ * See READ(2) */
+#define MAXIMUM_READ_SIZE 0x7ffff000
+
+extern int sleep_time;
+extern struct cache *reader_buffer, *fragment_buffer, *reserve_cache;
+extern struct cache *bwriter_buffer, *fwriter_buffer;
+extern struct queue *to_reader, *to_deflate, *to_writer, *from_writer,
+ *to_frag, *locked_fragment, *to_process_frag;
+extern struct append_file **file_mapping;
+extern struct seq_queue *to_main, *to_order;
+extern pthread_mutex_t fragment_mutex, dup_mutex;
+extern struct squashfs_fragment_entry *fragment_table;
+extern struct compressor *comp;
+extern int block_size;
+extern int block_log;
+extern int sorted;
+extern int noF;
+extern int noD;
+extern int old_exclude;
+extern int no_fragments;
+extern int always_use_fragments;
+extern struct file_info **dupl_frag;
+extern int duplicate_checking;
+extern int no_hardlinks;
+extern struct dir_info *root_dir;
+extern struct pathnames *paths;
+extern int tarfile;
+extern int root_mode_opt;
+extern mode_t root_mode;
+extern int root_time_opt;
+extern unsigned int root_time;
+extern int root_uid_opt;
+extern unsigned int root_uid;
+extern int root_gid_opt;
+extern unsigned int root_gid;
+extern struct inode_info *inode_info[INODE_HASH_SIZE];
+extern int quiet;
+extern int sequence_count;
+extern int pseudo_override;
+extern int global_uid_opt;
+extern unsigned int global_uid;
+extern int global_gid_opt;
+extern unsigned int global_gid;
+extern int sleep_time;
+
+extern int read_fs_bytes(int, long long, long long, void *);
+extern void add_file(long long, long long, long long, unsigned int *, int,
+ unsigned int, int, int);
+extern struct id *create_id(unsigned int);
+extern unsigned int get_uid(unsigned int);
+extern unsigned int get_guid(unsigned int);
+extern long long read_bytes(int, void *, long long);
+extern unsigned short get_checksum_mem(char *, int);
+extern int reproducible;
+extern void *reader(void *arg);
+extern squashfs_inode create_inode(struct dir_info *dir_info,
+ struct dir_ent *dir_ent, int type, long long byte_size,
+ long long start_block, unsigned int offset, unsigned int *block_list,
+ struct fragment *fragment, struct directory *dir_in, long long sparse);
+extern void free_fragment(struct fragment *fragment);
+extern struct file_info *write_file(struct dir_ent *dir_ent, int *dup);
+extern int excluded(char *name, struct pathnames *paths, struct pathnames **new);
+extern struct dir_ent *lookup_name(struct dir_info *dir, char *name);
+extern struct dir_ent *create_dir_entry(char *name, char *source_name,
+ char *nonstandard_pathname, struct dir_info *dir);
+extern void add_dir_entry(struct dir_ent *dir_ent, struct dir_info *sub_dir,
+ struct inode_info *inode_info);
+extern void free_dir_entry(struct dir_ent *dir_ent);
+extern void free_dir(struct dir_info *dir);
+extern struct dir_info *create_dir(char *pathname, char *subpath, unsigned int depth);
+extern char *subpathname(struct dir_ent *dir_ent);
+extern struct dir_info *scan1_opendir(char *pathname, char *subpath, unsigned int depth);
+extern squashfs_inode do_directory_scans(struct dir_ent *dir_ent, int progress);
+extern struct inode_info *lookup_inode(struct stat *buf);
+extern int exec_date(char *, unsigned int *);
+#endif
diff --git a/squashfs-tools/mksquashfs_error.h b/squashfs-tools/mksquashfs_error.h
new file mode 100644
index 0000000..5f861b5
--- /dev/null
+++ b/squashfs-tools/mksquashfs_error.h
@@ -0,0 +1,74 @@
+#ifndef MKSQUASHFS_ERROR_H
+#define MKSQUASHFS_ERROR_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * mksquashfs_error.h
+ */
+
+#include "error.h"
+
+extern int exit_on_error;
+extern void prep_exit();
+
+#define INFO(s, args...) \
+ do {\
+ if(!silent)\
+ progressbar_info(s, ## args);\
+ } while(0)
+
+
+#define ERROR_START(s, args...) \
+ do { \
+ disable_progress_bar(); \
+ fprintf(stderr, s, ## args); \
+ } while(0)
+
+#define ERROR_EXIT(s, args...) \
+ do {\
+ if (exit_on_error) { \
+ fprintf(stderr, "\n"); \
+ EXIT_MKSQUASHFS(); \
+ } else { \
+ fprintf(stderr, s, ## args); \
+ enable_progress_bar(); \
+ } \
+ } while(0)
+
+#define EXIT_MKSQUASHFS() \
+ do {\
+ prep_exit();\
+ exit(1);\
+ } while(0)
+
+#define BAD_ERROR(s, args...) \
+ do {\
+ progressbar_error("FATAL ERROR: " s, ##args); \
+ EXIT_MKSQUASHFS();\
+ } while(0)
+
+#define MEM_ERROR() \
+ do {\
+ progressbar_error("FATAL ERROR: Out of memory (%s)\n", \
+ __func__); \
+ EXIT_MKSQUASHFS();\
+ } while(0)
+#endif
diff --git a/squashfs-tools/process_fragments.c b/squashfs-tools/process_fragments.c
new file mode 100644
index 0000000..5e78e9d
--- /dev/null
+++ b/squashfs-tools/process_fragments.c
@@ -0,0 +1,373 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2014, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * process_fragments.c
+ */
+
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "caches-queues-lists.h"
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "mksquashfs_error.h"
+#include "progressbar.h"
+#include "info.h"
+#include "compressor.h"
+#include "process_fragments.h"
+
+#define FALSE 0
+#define TRUE 1
+
+extern struct queue *to_process_frag;
+extern struct seq_queue *to_main;
+extern int sparse_files;
+extern long long start_offset;
+
+/*
+ * Compute 16 bit BSD checksum over the data, and check for sparseness
+ */
+static int checksum_sparse(struct file_buffer *file_buffer)
+{
+ unsigned char *b = (unsigned char *) file_buffer->data;
+ unsigned short chksum = 0;
+ int bytes = file_buffer->size, sparse = TRUE, value;
+
+ while(bytes --) {
+ chksum = (chksum & 1) ? (chksum >> 1) | 0x8000 : chksum >> 1;
+ value = *b++;
+ if(value) {
+ sparse = FALSE;
+ chksum += value;
+ }
+ }
+
+ file_buffer->checksum = chksum;
+ return sparse;
+}
+
+
+static int read_filesystem(int fd, long long byte, int bytes, void *buff)
+{
+ off_t off = byte;
+
+ TRACE("read_filesystem: reading from position 0x%llx, bytes %d\n",
+ byte, bytes);
+
+ if(lseek(fd, start_offset + off, SEEK_SET) == -1) {
+ ERROR("read_filesystem: Lseek on destination failed because %s, "
+ "offset=0x%llx\n", strerror(errno), start_offset + off);
+ return 0;
+ } else if(read_bytes(fd, buff, bytes) < bytes) {
+ ERROR("Read on destination failed\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static struct file_buffer *get_fragment(struct fragment *fragment,
+ char *data_buffer, int fd)
+{
+ struct squashfs_fragment_entry *disk_fragment;
+ struct file_buffer *buffer, *compressed_buffer;
+ long long start_block;
+ int res, size, index = fragment->index, compressed;
+ char locked;
+
+ /*
+ * Lookup fragment block in cache.
+ * If the fragment block doesn't exist, then get the compressed version
+ * from the writer cache or off disk, and decompress it.
+ *
+ * This routine has two things which complicate the code:
+ *
+ * 1. Multiple threads can simultaneously lookup/create the
+ * same buffer. This means a buffer needs to be "locked"
+ * when it is being filled in, to prevent other threads from
+ * using it when it is not ready. This is because we now do
+ * fragment duplicate checking in parallel.
+ * 2. We have two caches which need to be checked for the
+ * presence of fragment blocks: the normal fragment cache
+ * and a "reserve" cache. The reserve cache is used to
+ * prevent an unnecessary pipeline stall when the fragment cache
+ * is full of fragments waiting to be compressed.
+ */
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+ pthread_mutex_lock(&dup_mutex);
+
+again:
+ buffer = cache_lookup_nowait(fragment_buffer, index, &locked);
+ if(buffer) {
+ pthread_mutex_unlock(&dup_mutex);
+ if(locked)
+ /* got a buffer being filled in. Wait for it */
+ cache_wait_unlock(buffer);
+ goto finished;
+ }
+
+ /* not in fragment cache, is it in the reserve cache? */
+ buffer = cache_lookup_nowait(reserve_cache, index, &locked);
+ if(buffer) {
+ pthread_mutex_unlock(&dup_mutex);
+ if(locked)
+ /* got a buffer being filled in. Wait for it */
+ cache_wait_unlock(buffer);
+ goto finished;
+ }
+
+ /* in neither cache, try to get it from the fragment cache */
+ buffer = cache_get_nowait(fragment_buffer, index);
+ if(!buffer) {
+ /*
+ * no room, get it from the reserve cache, this is
+ * dimensioned so it will always have space (no more than
+ * processors + 1 can have an outstanding reserve buffer)
+ */
+ buffer = cache_get_nowait(reserve_cache, index);
+ if(!buffer) {
+ /* failsafe */
+ ERROR("no space in reserve cache\n");
+ goto again;
+ }
+ }
+
+ pthread_mutex_unlock(&dup_mutex);
+
+ compressed_buffer = cache_lookup(fwriter_buffer, index);
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &fragment_mutex);
+ pthread_mutex_lock(&fragment_mutex);
+ disk_fragment = &fragment_table[index];
+ size = SQUASHFS_COMPRESSED_SIZE_BLOCK(disk_fragment->size);
+ compressed = SQUASHFS_COMPRESSED_BLOCK(disk_fragment->size);
+ start_block = disk_fragment->start_block;
+ pthread_cleanup_pop(1);
+
+ if(compressed) {
+ int error;
+ char *data;
+
+ if(compressed_buffer)
+ data = compressed_buffer->data;
+ else {
+ res = read_filesystem(fd, start_block, size, data_buffer);
+ if(res == 0) {
+ ERROR("Failed to read fragment from output"
+ " filesystem\n");
+ BAD_ERROR("Output filesystem corrupted?\n");
+ }
+ data = data_buffer;
+ }
+
+ res = compressor_uncompress(comp, buffer->data, data, size,
+ block_size, &error);
+ if(res == -1)
+ BAD_ERROR("%s uncompress failed with error code %d\n",
+ comp->name, error);
+ } else if(compressed_buffer)
+ memcpy(buffer->data, compressed_buffer->data, size);
+ else {
+ res = read_filesystem(fd, start_block, size, buffer->data);
+ if(res == 0) {
+ ERROR("Failed to read fragment from output "
+ "filesystem\n");
+ BAD_ERROR("Output filesystem corrupted?\n");
+ }
+ }
+
+ cache_unlock(buffer);
+ cache_block_put(compressed_buffer);
+
+finished:
+ pthread_cleanup_pop(0);
+
+ return buffer;
+}
+
+
+struct file_buffer *get_fragment_cksum(struct file_info *file,
+ char *data_buffer, int fd, unsigned short *checksum)
+{
+ struct file_buffer *frag_buffer;
+ struct append_file *append;
+ int index = file->fragment->index;
+
+ frag_buffer = get_fragment(file->fragment, data_buffer, fd);
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+
+ for(append = file_mapping[index]; append; append = append->next) {
+ int offset = append->file->fragment->offset;
+ int size = append->file->fragment->size;
+ char *data = frag_buffer->data + offset;
+ unsigned short cksum = get_checksum_mem(data, size);
+
+ if(file == append->file)
+ *checksum = cksum;
+
+ pthread_mutex_lock(&dup_mutex);
+ append->file->fragment_checksum = cksum;
+ append->file->have_frag_checksum = TRUE;
+ pthread_mutex_unlock(&dup_mutex);
+ }
+
+ pthread_cleanup_pop(0);
+
+ return frag_buffer;
+}
+
+
+void *frag_thrd(void *destination_file)
+{
+ sigset_t sigmask, old_mask;
+ char *data_buffer;
+ int fd;
+
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGINT);
+ sigaddset(&sigmask, SIGTERM);
+ sigaddset(&sigmask, SIGUSR1);
+ pthread_sigmask(SIG_BLOCK, &sigmask, &old_mask);
+
+ fd = open(destination_file, O_RDONLY);
+ if(fd == -1)
+ BAD_ERROR("frag_thrd: can't open destination for reading\n");
+
+ data_buffer = malloc(SQUASHFS_FILE_MAX_SIZE);
+ if(data_buffer == NULL)
+ MEM_ERROR();
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &dup_mutex);
+
+ while(1) {
+ struct file_buffer *file_buffer = queue_get(to_process_frag);
+ struct file_buffer *buffer;
+ int sparse = checksum_sparse(file_buffer);
+ struct file_info *dupl_ptr;
+ long long file_size;
+ unsigned short checksum;
+ char flag;
+ int res;
+
+ if(sparse_files && sparse) {
+ file_buffer->c_byte = 0;
+ file_buffer->fragment = FALSE;
+ } else
+ file_buffer->c_byte = file_buffer->size;
+
+ /*
+ * Specutively pull into the fragment cache any fragment blocks
+ * which contain fragments which *this* fragment may be
+ * be a duplicate.
+ *
+ * By ensuring the fragment block is in cache ahead of time
+ * should eliminate the parallelisation stall when the
+ * main thread needs to read the fragment block to do a
+ * duplicate check on it.
+ *
+ * If this is a fragment belonging to a larger file
+ * (with additional blocks) then ignore it. Here we're
+ * interested in the "low hanging fruit" of files which
+ * consist of only a fragment
+ */
+ if(file_buffer->file_size != file_buffer->size) {
+ seq_queue_put(to_main, file_buffer);
+ continue;
+ }
+
+ file_size = file_buffer->file_size;
+
+ pthread_mutex_lock(&dup_mutex);
+ dupl_ptr = dupl_frag[file_size];
+ pthread_mutex_unlock(&dup_mutex);
+
+ file_buffer->dupl_start = dupl_ptr;
+ file_buffer->duplicate = FALSE;
+
+ for(; dupl_ptr; dupl_ptr = dupl_ptr->frag_next) {
+ if(file_size != dupl_ptr->fragment->size)
+ continue;
+
+ pthread_mutex_lock(&dup_mutex);
+ flag = dupl_ptr->have_frag_checksum;
+ checksum = dupl_ptr->fragment_checksum;
+ pthread_mutex_unlock(&dup_mutex);
+
+ /*
+ * If we have the checksum and it matches then
+ * read in the fragment block.
+ *
+ * If we *don't* have the checksum, then we are
+ * appending, and the fragment block is on the
+ * "old" filesystem. Read it in and checksum
+ * the entire fragment buffer
+ */
+ if(!flag) {
+ buffer = get_fragment_cksum(dupl_ptr,
+ data_buffer, fd, &checksum);
+ if(checksum != file_buffer->checksum) {
+ cache_block_put(buffer);
+ continue;
+ }
+ } else if(checksum == file_buffer->checksum)
+ buffer = get_fragment(dupl_ptr->fragment,
+ data_buffer, fd);
+ else
+ continue;
+
+ res = memcmp(file_buffer->data, buffer->data +
+ dupl_ptr->fragment->offset, file_size);
+ cache_block_put(buffer);
+ if(res == 0) {
+ struct file_buffer *dup = malloc(sizeof(*dup));
+ if(dup == NULL)
+ MEM_ERROR();
+ memcpy(dup, file_buffer, sizeof(*dup));
+ cache_block_put(file_buffer);
+ dup->dupl_start = dupl_ptr;
+ dup->duplicate = TRUE;
+ dup->cache = NULL;
+ file_buffer = dup;
+ break;
+ }
+ }
+
+ seq_queue_put(to_main, file_buffer);
+ }
+
+ pthread_cleanup_pop(0);
+ return NULL;
+}
diff --git a/squashfs-tools/process_fragments.h b/squashfs-tools/process_fragments.h
new file mode 100644
index 0000000..2a01d66
--- /dev/null
+++ b/squashfs-tools/process_fragments.h
@@ -0,0 +1,28 @@
+#ifndef PROCESS_FRAGMENTS_H
+#define PROCESS_FRAGMENTS_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2014, 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * process_fragments.h
+ */
+
+extern void *frag_thrd(void *);
+#endif
diff --git a/squashfs-tools/progressbar.c b/squashfs-tools/progressbar.c
new file mode 100644
index 0000000..19ad807
--- /dev/null
+++ b/squashfs-tools/progressbar.c
@@ -0,0 +1,300 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2012, 2013, 2014, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * progressbar.c
+ */
+
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "mksquashfs_error.h"
+
+#define FALSE 0
+#define TRUE 1
+
+/* flag whether progressbar display is enabled or not */
+static int display_progress_bar = FALSE;
+
+/* flag whether the progress bar is temporarily disbled */
+static int temp_disabled = FALSE;
+
+/* flag whether to display full progress bar or just a percentage */
+static int percent = FALSE;
+
+/* flag whether we need to output a newline before printing
+ * a line - this is because progressbar printing does *not*
+ * output a newline */
+static int need_nl = FALSE;
+
+static int rotate = 0;
+static long long cur_uncompressed = 0, estimated_uncompressed = 0;
+static int columns;
+
+static pthread_t progress_thread;
+static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t size_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+
+static void sigwinch_handler(int arg)
+{
+ struct winsize winsize;
+
+ if(ioctl(1, TIOCGWINSZ, &winsize) == -1) {
+ if(isatty(STDOUT_FILENO))
+ ERROR("TIOCGWINSZ ioctl failed, defaulting to 80 "
+ "columns\n");
+ columns = 80;
+ } else
+ columns = winsize.ws_col;
+}
+
+
+void progressbar_percentage()
+{
+ percent = TRUE;
+}
+
+
+void inc_progress_bar()
+{
+ cur_uncompressed ++;
+}
+
+
+void dec_progress_bar(int count)
+{
+ cur_uncompressed -= count;
+}
+
+
+void progress_bar_size(int count)
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &size_mutex);
+ pthread_mutex_lock(&size_mutex);
+ estimated_uncompressed += count;
+ pthread_cleanup_pop(1);
+}
+
+
+static void progressbar(long long current, long long max, int columns)
+{
+ char rotate_list[] = { '|', '/', '-', '\\' };
+ int max_digits, used, hashes, spaces, percentage;
+ static int tty = -1;
+
+ if(max == 0) {
+ max_digits = 1;
+ used = 13;
+ hashes = 0;
+ spaces = columns - 13;
+ percentage = 100;
+ } else {
+ max_digits = floor(log10(max)) + 1;
+ used = max_digits * 2 + 11;
+ hashes = (current * (columns - used)) / max;
+ spaces = columns - used - hashes;
+ percentage = current * 100 / max;
+ }
+
+ if((current > max) || (columns - used < 0))
+ return;
+
+ if(tty == -1)
+ tty = isatty(STDOUT_FILENO);
+ if(!tty) {
+ static long long previous = -1;
+
+ /* Updating much more frequently than this results in huge
+ * log files. */
+ if((current % 100) != 0 && current != max)
+ return;
+ /* Don't update just to rotate the spinner. */
+ if(current == previous)
+ return;
+ previous = current;
+ }
+
+ printf("\r[");
+
+ while (hashes --)
+ putchar('=');
+
+ putchar(rotate_list[rotate]);
+
+ while(spaces --)
+ putchar(' ');
+
+ printf("] %*lld/%*lld", max_digits, current, max_digits, max);
+ printf(" %3d%%", percentage);
+ fflush(stdout);
+}
+
+
+static void display_percentage(long long current, long long max)
+{
+ int percentage = max == 0 ? 100 : current * 100 / max;
+ static int previous = -1;
+
+ if(percentage != previous) {
+ printf("%d\n", percentage);
+ fflush(stdout);
+ previous = percentage;
+ }
+}
+
+
+static void progress_bar(long long current, long long max, int columns)
+{
+ if(percent)
+ display_percentage(current, max);
+ else
+ progressbar(current, max, columns);
+}
+
+
+void enable_progress_bar()
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
+ pthread_mutex_lock(&progress_mutex);
+ if(display_progress_bar)
+ progress_bar(cur_uncompressed, estimated_uncompressed, columns);
+ temp_disabled = FALSE;
+ pthread_cleanup_pop(1);
+}
+
+
+void disable_progress_bar()
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
+ pthread_mutex_lock(&progress_mutex);
+ if(need_nl) {
+ printf("\n");
+ need_nl = FALSE;
+ }
+ temp_disabled = TRUE;
+ pthread_cleanup_pop(1);
+}
+
+
+void set_progressbar_state(int state)
+{
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
+ pthread_mutex_lock(&progress_mutex);
+ if(display_progress_bar != state) {
+ if(display_progress_bar && !temp_disabled) {
+ progress_bar(cur_uncompressed, estimated_uncompressed,
+ columns);
+ printf("\n");
+ need_nl = FALSE;
+ }
+ display_progress_bar = state;
+ }
+ pthread_cleanup_pop(1);
+}
+
+
+static void *progress_thrd(void *arg)
+{
+ struct timespec requested_time, remaining;
+ struct winsize winsize;
+
+ if(ioctl(1, TIOCGWINSZ, &winsize) == -1) {
+ if(isatty(STDOUT_FILENO))
+ ERROR("TIOCGWINSZ ioctl failed, defaulting to 80 "
+ "columns\n");
+ columns = 80;
+ } else
+ columns = winsize.ws_col;
+ signal(SIGWINCH, sigwinch_handler);
+
+ requested_time.tv_sec = 0;
+ requested_time.tv_nsec = 250000000;
+
+ while(1) {
+ int res = nanosleep(&requested_time, &remaining);
+
+ if(res == -1 && errno != EINTR)
+ BAD_ERROR("nanosleep failed in progress thread\n");
+
+ pthread_mutex_lock(&progress_mutex);
+ rotate = (rotate + 1) % 4;
+ if(display_progress_bar && !temp_disabled) {
+ progress_bar(cur_uncompressed, estimated_uncompressed, columns);
+ need_nl = TRUE;
+ }
+ pthread_mutex_unlock(&progress_mutex);
+ }
+}
+
+
+void init_progress_bar()
+{
+ pthread_create(&progress_thread, NULL, progress_thrd, NULL);
+}
+
+
+void progressbar_error(char *fmt, ...)
+{
+ va_list ap;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
+ pthread_mutex_lock(&progress_mutex);
+
+ if(need_nl) {
+ printf("\n");
+ need_nl = FALSE;
+ }
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ pthread_cleanup_pop(1);
+}
+
+
+void progressbar_info(char *fmt, ...)
+{
+ va_list ap;
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &progress_mutex);
+ pthread_mutex_lock(&progress_mutex);
+
+ if(need_nl) {
+ printf("\n");
+ need_nl = FALSE;
+ }
+
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+
+ pthread_cleanup_pop(1);
+}
+
diff --git a/squashfs-tools/progressbar.h b/squashfs-tools/progressbar.h
new file mode 100644
index 0000000..a97ff37
--- /dev/null
+++ b/squashfs-tools/progressbar.h
@@ -0,0 +1,35 @@
+#ifndef PROGRESSBAR_H
+#define PROGRESSBAR_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2012, 2013, 2014, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * progressbar.h
+ */
+
+extern void inc_progress_bar();
+extern void dec_progress_bar(int count);
+extern void progress_bar_size(int count);
+extern void enable_progress_bar();
+extern void disable_progress_bar();
+extern void init_progress_bar();
+extern void set_progressbar_state(int);
+extern void progressbar_percentage();
+#endif
diff --git a/squashfs-tools/pseudo.c b/squashfs-tools/pseudo.c
new file mode 100644
index 0000000..2542173
--- /dev/null
+++ b/squashfs-tools/pseudo.c
@@ -0,0 +1,1376 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2012, 2014, 2017, 2019, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * pseudo.c
+ */
+
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <time.h>
+#include <ctype.h>
+#include <regex.h>
+#include <dirent.h>
+#include <sys/types.h>
+
+#include "pseudo.h"
+#include "mksquashfs_error.h"
+#include "progressbar.h"
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "xattr.h"
+
+#define TRUE 1
+#define FALSE 0
+#define MAX_LINE 16384
+
+struct pseudo *pseudo = NULL;
+
+char *get_element(char *target, char **targname)
+{
+ char *start;
+
+ start = target;
+ while(*target != '/' && *target != '\0')
+ target ++;
+
+ *targname = strndup(start, target - start);
+
+ while(*target == '/')
+ target ++;
+
+ return target;
+}
+
+
+/*
+ * Add pseudo device target to the set of pseudo devices. Pseudo_dev
+ * describes the pseudo device attributes.
+ */
+static struct pseudo *add_pseudo(struct pseudo *pseudo, struct pseudo_dev *pseudo_dev,
+ char *target, char *alltarget)
+{
+ char *targname;
+ int i;
+
+ target = get_element(target, &targname);
+
+ if(pseudo == NULL) {
+ pseudo = malloc(sizeof(struct pseudo));
+ if(pseudo == NULL)
+ MEM_ERROR();
+
+ pseudo->names = 0;
+ pseudo->count = 0;
+ pseudo->name = NULL;
+ }
+
+ for(i = 0; i < pseudo->names; i++)
+ if(strcmp(pseudo->name[i].name, targname) == 0)
+ break;
+
+ if(i == pseudo->names) {
+ /* allocate new name entry */
+ pseudo->names ++;
+ pseudo->name = realloc(pseudo->name, (i + 1) *
+ sizeof(struct pseudo_entry));
+ if(pseudo->name == NULL)
+ MEM_ERROR();
+ pseudo->name[i].name = targname;
+ pseudo->name[i].xattr = NULL;
+
+ if(target[0] == '\0') {
+ /* at leaf pathname component */
+ pseudo->name[i].pseudo = NULL;
+ pseudo->name[i].pathname = strdup(alltarget);
+ pseudo->name[i].dev = pseudo_dev;
+ } else {
+ /* recurse adding child components */
+ pseudo->name[i].dev = NULL;
+ pseudo->name[i].pseudo = add_pseudo(NULL, pseudo_dev,
+ target, alltarget);
+ }
+ } else {
+ /* existing matching entry */
+ free(targname);
+
+ if(pseudo->name[i].pseudo == NULL) {
+ /* No sub-directory which means this is the leaf
+ * component, this may or may not be a pre-existing
+ * pseudo file.
+ */
+ if(target[0] != '\0') {
+ /*
+ * entry must exist as either a 'd' type or
+ * 'm' type pseudo file, or not exist at all
+ */
+ if(pseudo->name[i].dev == NULL ||
+ pseudo->name[i].dev->type == 'd' ||
+ pseudo->name[i].dev->type == 'm')
+ /* recurse adding child components */
+ pseudo->name[i].pseudo =
+ add_pseudo(NULL, pseudo_dev,
+ target, alltarget);
+ else {
+ ERROR_START("%s already exists as a "
+ "non directory.",
+ pseudo->name[i].name);
+ ERROR_EXIT(". Ignoring %s!\n",
+ alltarget);
+ }
+ } else if(pseudo->name[i].dev == NULL) {
+ /* add this pseudo definition */
+ pseudo->name[i].pathname = strdup(alltarget);
+ pseudo->name[i].dev = pseudo_dev;
+ } else if(memcmp(pseudo_dev, pseudo->name[i].dev,
+ sizeof(struct pseudo_dev)) != 0) {
+ ERROR_START("%s already exists as a different "
+ "pseudo definition.", alltarget);
+ ERROR_EXIT(" Ignoring!\n");
+ } else {
+ ERROR_START("%s already exists as an identical "
+ "pseudo definition!", alltarget);
+ ERROR_EXIT(" Ignoring!\n");
+ }
+ } else {
+ if(target[0] == '\0') {
+ /*
+ * sub-directory exists, which means we can only
+ * add a pseudo file of type 'd' or type 'm'
+ */
+ if(pseudo->name[i].dev == NULL &&
+ (pseudo_dev->type == 'd' ||
+ pseudo_dev->type == 'm')) {
+ pseudo->name[i].pathname =
+ strdup(alltarget);
+ pseudo->name[i].dev = pseudo_dev;
+ } else {
+ ERROR_START("%s already exists as a "
+ "different pseudo definition.",
+ pseudo->name[i].name);
+ ERROR_EXIT(" Ignoring %s!\n",
+ alltarget);
+ }
+ } else
+ /* recurse adding child components */
+ add_pseudo(pseudo->name[i].pseudo, pseudo_dev,
+ target, alltarget);
+ }
+ }
+
+ return pseudo;
+}
+
+
+static struct pseudo *add_pseudo_definition(struct pseudo *pseudo, struct pseudo_dev *pseudo_dev,
+ char *target, char *alltarget)
+{
+ /* special case if a root pseudo definition is being added */
+ if(strcmp(target, "/") == 0) {
+ /* type must be 'd' */
+ if(pseudo_dev->type != 'd') {
+ ERROR("Pseudo definition / is not a directory. Ignoring!\n");
+ return pseudo;
+ }
+
+ /* if already have a root pseudo just replace */
+ if(pseudo && pseudo->names == 1 && strcmp(pseudo->name[0].name, "/") == 0) {
+ pseudo->name[0].dev = pseudo_dev;
+ return pseudo;
+ } else {
+ struct pseudo *new = malloc(sizeof(struct pseudo));
+ if(new == NULL)
+ MEM_ERROR();
+
+ new->names = 1;
+ new->count = 0;
+ new->name = malloc(sizeof(struct pseudo_entry));
+ if(new->name == NULL)
+ MEM_ERROR();
+
+ new->name[0].name = "/";
+ new->name[0].pseudo = pseudo;
+ new->name[0].pathname = "/";
+ new->name[0].dev = pseudo_dev;
+ new->name[0].xattr = NULL;
+ return new;
+ }
+ }
+
+ /* if there's a root pseudo definition, skip it before walking target */
+ if(pseudo && pseudo->names == 1 && strcmp(pseudo->name[0].name, "/") == 0) {
+ pseudo->name[0].pseudo = add_pseudo(pseudo->name[0].pseudo, pseudo_dev, target, alltarget);
+ return pseudo;
+ } else
+ return add_pseudo(pseudo, pseudo_dev, target, alltarget);
+}
+
+
+/*
+ * Find subdirectory in pseudo directory referenced by pseudo, matching
+ * filename. If filename doesn't exist or if filename is a leaf file
+ * return NULL
+ */
+struct pseudo *pseudo_subdir(char *filename, struct pseudo *pseudo)
+{
+ int i;
+
+ if(pseudo == NULL)
+ return NULL;
+
+ for(i = 0; i < pseudo->names; i++)
+ if(strcmp(filename, pseudo->name[i].name) == 0)
+ return pseudo->name[i].pseudo;
+
+ return NULL;
+}
+
+
+struct pseudo_entry *pseudo_readdir(struct pseudo *pseudo)
+{
+ if(pseudo == NULL)
+ return NULL;
+
+ while(pseudo->count < pseudo->names)
+ return &pseudo->name[pseudo->count++];
+
+ return NULL;
+}
+
+
+int pseudo_exec_file(struct pseudo_dev *dev, int *child)
+{
+ int res, pipefd[2];
+
+ res = pipe(pipefd);
+ if(res == -1) {
+ ERROR("Executing dynamic pseudo file, pipe failed\n");
+ return 0;
+ }
+
+ *child = fork();
+ if(*child == -1) {
+ ERROR("Executing dynamic pseudo file, fork failed\n");
+ goto failed;
+ }
+
+ if(*child == 0) {
+ close(pipefd[0]);
+ close(STDOUT_FILENO);
+ res = dup(pipefd[1]);
+ if(res == -1)
+ exit(EXIT_FAILURE);
+
+ execl("/bin/sh", "sh", "-c", dev->command, (char *) NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ close(pipefd[1]);
+ return pipefd[0];
+
+failed:
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return 0;
+}
+
+
+static struct pseudo_entry *pseudo_lookup(struct pseudo *pseudo, char *target)
+{
+ char *targname;
+ int i;
+
+ if(pseudo == NULL)
+ return NULL;
+
+ target = get_element(target, &targname);
+
+ for(i = 0; i < pseudo->names; i++)
+ if(strcmp(pseudo->name[i].name, targname) == 0)
+ break;
+
+ free(targname);
+
+ if(i == pseudo->names)
+ return NULL;
+
+ if(target[0] == '\0')
+ return &pseudo->name[i];
+
+ if(pseudo->name[i].pseudo == NULL)
+ return NULL;
+
+ return pseudo_lookup(pseudo->name[i].pseudo, target);
+}
+
+
+void print_definitions()
+{
+ ERROR("Pseudo definitions should be of the format\n");
+ ERROR("\tfilename d mode uid gid\n");
+ ERROR("\tfilename m mode uid gid\n");
+ ERROR("\tfilename b mode uid gid major minor\n");
+ ERROR("\tfilename c mode uid gid major minor\n");
+ ERROR("\tfilename f mode uid gid command\n");
+ ERROR("\tfilename s mode uid gid symlink\n");
+ ERROR("\tfilename i mode uid gid [s|f]\n");
+ ERROR("\tfilename x name=value\n");
+ ERROR("\tfilename l filename\n");
+ ERROR("\tfilename L pseudo_filename\n");
+ ERROR("\tfilename D time mode uid gid\n");
+ ERROR("\tfilename M time mode uid gid\n");
+ ERROR("\tfilename B time mode uid gid major minor\n");
+ ERROR("\tfilename C time mode uid gid major minor\n");
+ ERROR("\tfilename F time mode uid gid command\n");
+ ERROR("\tfilename S time mode uid gid symlink\n");
+ ERROR("\tfilename I time mode uid gid [s|f]\n");
+ ERROR("\tfilename R time mode uid gid length offset sparse\n");
+}
+
+
+static int read_pseudo_def_pseudo_link(char *orig_def, char *filename, char *name, char *def)
+{
+ char *linkname, *link;
+ int quoted = FALSE;
+ struct pseudo_entry *pseudo_ent;
+
+ /*
+ * Scan for filename, don't use sscanf() and "%s" because
+ * that can't handle filenames with spaces.
+ *
+ * Filenames with spaces should either escape (backslash) the
+ * space or use double quotes.
+ */
+ linkname = malloc(strlen(def) + 1);
+ if(linkname == NULL)
+ MEM_ERROR();
+
+ for(link = linkname; (quoted || !isspace(*def)) && *def != '\0';) {
+ if(*def == '"') {
+ quoted = !quoted;
+ def ++;
+ continue;
+ }
+
+ if(*def == '\\') {
+ def ++;
+ if (*def == '\0')
+ break;
+ }
+ *link ++ = *def ++;
+ }
+ *link = '\0';
+
+ /* Skip any leading slashes (/) */
+ for(link = linkname; *link == '/'; link ++);
+
+ if(*link == '\0') {
+ ERROR("Not enough or invalid arguments in pseudo LINK file "
+ "definition \"%s\"\n", orig_def);
+ goto error;
+ }
+
+ /* Lookup linkname in pseudo definition tree */
+ /* if there's a root pseudo definition, skip it before walking target */
+ if(pseudo && pseudo->names == 1 && strcmp(pseudo->name[0].name, "/") == 0)
+ pseudo_ent = pseudo_lookup(pseudo->name[0].pseudo, link);
+ else
+ pseudo_ent = pseudo_lookup(pseudo, link);
+
+ if(pseudo_ent == NULL || pseudo_ent->dev == NULL) {
+ ERROR("Pseudo LINK file %s doesn't exist\n", linkname);
+ goto error;
+ }
+
+ if(pseudo_ent->dev->type == 'd' || pseudo_ent->dev->type == 'm') {
+ ERROR("Cannot hardlink to a Pseudo directory or modify definition\n");
+ goto error;
+ }
+
+ pseudo = add_pseudo_definition(pseudo, pseudo_ent->dev, name, name);
+
+ free(filename);
+ free(linkname);
+ return TRUE;
+
+error:
+ print_definitions();
+ free(filename);
+ free(linkname);
+ return FALSE;
+}
+
+
+static int read_pseudo_def_link(char *orig_def, char *filename, char *name, char *def, char *destination)
+{
+ char *linkname, *link;
+ int quoted = FALSE;
+ struct pseudo_dev *dev = NULL;
+ static struct stat *dest_buf = NULL;
+
+ /*
+ * Stat destination file. We need to do this to prevent people
+ * from creating a circular loop, connecting the output to the
+ * input (only needed for appending, otherwise the destination
+ * file will not exist).
+ */
+ if(dest_buf == NULL) {
+ dest_buf = malloc(sizeof(struct stat));
+ if(dest_buf == NULL)
+ MEM_ERROR();
+
+ memset(dest_buf, 0, sizeof(struct stat));
+ lstat(destination, dest_buf);
+ }
+
+
+ /*
+ * Scan for filename, don't use sscanf() and "%s" because
+ * that can't handle filenames with spaces.
+ *
+ * Filenames with spaces should either escape (backslash) the
+ * space or use double quotes.
+ */
+ linkname = malloc(strlen(def) + 1);
+ if(linkname == NULL)
+ MEM_ERROR();
+
+ for(link = linkname; (quoted || !isspace(*def)) && *def != '\0';) {
+ if(*def == '"') {
+ quoted = !quoted;
+ def ++;
+ continue;
+ }
+
+ if(*def == '\\') {
+ def ++;
+ if (*def == '\0')
+ break;
+ }
+ *link ++ = *def ++;
+ }
+ *link = '\0';
+
+ if(*linkname == '\0') {
+ ERROR("Not enough or invalid arguments in pseudo link file "
+ "definition \"%s\"\n", orig_def);
+ goto error;
+ }
+
+ dev = malloc(sizeof(struct pseudo_dev));
+ if(dev == NULL)
+ MEM_ERROR();
+
+ memset(dev, 0, sizeof(struct pseudo_dev));
+
+ dev->linkbuf = malloc(sizeof(struct stat));
+ if(dev->linkbuf == NULL)
+ MEM_ERROR();
+
+ if(lstat(linkname, dev->linkbuf) == -1) {
+ ERROR("Cannot stat pseudo link file %s because %s\n",
+ linkname, strerror(errno));
+ goto error;
+ }
+
+ if(S_ISDIR(dev->linkbuf->st_mode)) {
+ ERROR("Pseudo link file %s is a directory, ", linkname);
+ ERROR("which cannot be hardlinked to\n");
+ goto error;
+ }
+
+ if(S_ISREG(dev->linkbuf->st_mode)) {
+ /*
+ * Check we're not trying to create a circular loop,
+ * connecting the output destination file to the
+ * input
+ */
+ if(memcmp(dev->linkbuf, dest_buf, sizeof(struct stat)) == 0) {
+ ERROR("Pseudo link file %s is the ", linkname);
+ ERROR("destination output file, which cannot be linked to\n");
+ goto error;
+ }
+ }
+
+ dev->type = 'l';
+ dev->pseudo_type = PSEUDO_FILE_OTHER;
+ dev->linkname = strdup(linkname);
+
+ pseudo = add_pseudo_definition(pseudo, dev, name, name);
+
+ free(filename);
+ free(linkname);
+ return TRUE;
+
+error:
+ print_definitions();
+ if(dev)
+ free(dev->linkbuf);
+ free(dev);
+ free(filename);
+ free(linkname);
+ return FALSE;
+}
+
+
+static int read_pseudo_def_extended(char type, char *orig_def, char *filename,
+ char *name, char *def, char *pseudo_file, struct pseudo_file **file)
+{
+ int n, bytes;
+ int quoted = FALSE;
+ unsigned int major = 0, minor = 0, mode, mtime;
+ char *ptr, *str, *string, *command = NULL, *symlink = NULL;
+ char suid[100], sgid[100]; /* overflow safe */
+ char ipc_type;
+ long long uid, gid;
+ struct pseudo_dev *dev;
+ static int pseudo_ino = 1;
+ long long file_length, pseudo_offset;
+ int sparse;
+
+ n = sscanf(def, "%u %o %n", &mtime, &mode, &bytes);
+
+ if(n < 2) {
+ /*
+ * Couldn't match date and mode. Date may not be quoted
+ * and is instead using backslashed spaces (i.e. 1\ jan\ 1980)
+ * where the "1" matched for the integer, but, jan didn't for
+ * the octal number.
+ *
+ * Scan for date string, don't use sscanf() and "%s" because
+ * that can't handle strings with spaces.
+ *
+ * Strings with spaces should either escape (backslash) the
+ * space or use double quotes.
+ */
+ string = malloc(strlen(def) + 1);
+ if(string == NULL)
+ MEM_ERROR();
+
+ for(str = string; (quoted || !isspace(*def)) && *def != '\0';) {
+ if(*def == '"') {
+ quoted = !quoted;
+ def ++;
+ continue;
+ }
+
+ if(*def == '\\') {
+ def ++;
+ if (*def == '\0')
+ break;
+ }
+ *str++ = *def ++;
+ }
+ *str = '\0';
+
+ if(string[0] == '\0') {
+ ERROR("Not enough or invalid arguments in pseudo file "
+ "definition \"%s\"\n", orig_def);
+ free(string);
+ goto error;
+ }
+
+ n = exec_date(string, &mtime);
+ if(n == FALSE) {
+ ERROR("Couldn't parse time, date string or "
+ "unsigned decimal integer "
+ "expected\n");
+ free(string);
+ goto error;
+ }
+
+ free(string);
+
+ n = sscanf(def, "%o %99s %99s %n", &mode, suid, sgid, &bytes);
+ def += bytes;
+ if(n < 3) {
+ ERROR("Not enough or invalid arguments in pseudo file "
+ "definition \"%s\"\n", orig_def);
+ switch(n) {
+ case -1:
+ /* FALLTHROUGH */
+ case 0:
+ ERROR("Couldn't parse mode, octal integer expected\n");
+ break;
+ case 1:
+ ERROR("Read filename, type, time and mode, but failed to "
+ "read or match uid\n");
+ break;
+ default:
+ ERROR("Read filename, type, time, mode and uid, but failed "
+ "to read or match gid\n");
+ break;
+ }
+ goto error;
+ }
+ } else {
+ def += bytes;
+ n = sscanf(def, "%99s %99s %n", suid, sgid, &bytes);
+ def += bytes;
+
+ if(n < 2) {
+ ERROR("Not enough or invalid arguments in pseudo file "
+ "definition \"%s\"\n", orig_def);
+ switch(n) {
+ case -1:
+ /* FALLTHROUGH */
+ case 0:
+ ERROR("Read filename, type, time and mode, but failed to "
+ "read or match uid\n");
+ break;
+ default:
+ ERROR("Read filename, type, time, mode and uid, but failed "
+ "to read or match gid\n");
+ break;
+ }
+ goto error;
+ }
+ }
+
+ switch(type) {
+ case 'B':
+ /* FALLTHROUGH */
+ case 'C':
+ n = sscanf(def, "%u %u %n", &major, &minor, &bytes);
+ def += bytes;
+
+ if(n < 2) {
+ ERROR("Not enough or invalid arguments in %s device "
+ "pseudo file definition \"%s\"\n", type == 'B' ?
+ "block" : "character", orig_def);
+ if(n < 1)
+ ERROR("Read filename, type, time, mode, uid and "
+ "gid, but failed to read or match major\n");
+ else
+ ERROR("Read filename, type, time, mode, uid, gid "
+ "and major, but failed to read or "
+ "match minor\n");
+ goto error;
+ }
+
+ if(major > 0xfff) {
+ ERROR("Major %d out of range\n", major);
+ goto error;
+ }
+
+ if(minor > 0xfffff) {
+ ERROR("Minor %d out of range\n", minor);
+ goto error;
+ }
+ break;
+ case 'I':
+ n = sscanf(def, "%c %n", &ipc_type, &bytes);
+ def += bytes;
+
+ if(n < 1) {
+ ERROR("Not enough or invalid arguments in ipc "
+ "pseudo file definition \"%s\"\n", orig_def);
+ ERROR("Read filename, type, mode, uid and gid, "
+ "but failed to read or match ipc_type\n");
+ goto error;
+ }
+
+ if(ipc_type != 's' && ipc_type != 'f') {
+ ERROR("Ipc_type should be s or f\n");
+ goto error;
+ }
+ break;
+ case 'R':
+ if(pseudo_file == NULL) {
+ ERROR("'R' definition can only be used in a Pseudo file\n");
+ goto error;
+ }
+
+ n = sscanf(def, "%lld %lld %d %n", &file_length, &pseudo_offset,
+ &sparse, &bytes);
+ def += bytes;
+
+ if(n < 3) {
+ ERROR("Not enough or invalid arguments in inline read "
+ "pseudo file definition \"%s\"\n", orig_def);
+ ERROR("Read filename, type, time, mode, uid and gid, "
+ "but failed to read or match file length, "
+ "offset or sparse\n");
+ goto error;
+ }
+ break;
+ case 'D':
+ case 'M':
+ break;
+ case 'F':
+ if(def[0] == '\0') {
+ ERROR("Not enough arguments in dynamic file pseudo "
+ "definition \"%s\"\n", orig_def);
+ ERROR("Expected command, which can be an executable "
+ "or a piece of shell script\n");
+ goto error;
+ }
+ command = def;
+ def += strlen(def);
+ break;
+ case 'S':
+ if(def[0] == '\0') {
+ ERROR("Not enough arguments in symlink pseudo "
+ "definition \"%s\"\n", orig_def);
+ ERROR("Expected symlink\n");
+ goto error;
+ }
+
+ if(strlen(def) > 65535) {
+ ERROR("Symlink pseudo definition %s is greater than 65535"
+ " bytes!\n", def);
+ goto error;
+ }
+ symlink = def;
+ def += strlen(def);
+ break;
+ default:
+ ERROR("Unsupported type %c\n", type);
+ goto error;
+ }
+
+ /*
+ * Check for trailing junk after expected arguments
+ */
+ if(def[0] != '\0') {
+ ERROR("Unexpected tailing characters in pseudo file "
+ "definition \"%s\"\n", orig_def);
+ goto error;
+ }
+
+ if(mode > 07777) {
+ ERROR("Mode %o out of range\n", mode);
+ goto error;
+ }
+
+ uid = strtoll(suid, &ptr, 10);
+ if(*ptr == '\0') {
+ if(uid < 0 || uid > ((1LL << 32) - 1)) {
+ ERROR("Uid %s out of range\n", suid);
+ goto error;
+ }
+ } else {
+ struct passwd *pwuid = getpwnam(suid);
+ if(pwuid)
+ uid = pwuid->pw_uid;
+ else {
+ ERROR("Uid %s invalid uid or unknown user\n", suid);
+ goto error;
+ }
+ }
+
+ gid = strtoll(sgid, &ptr, 10);
+ if(*ptr == '\0') {
+ if(gid < 0 || gid > ((1LL << 32) - 1)) {
+ ERROR("Gid %s out of range\n", sgid);
+ goto error;
+ }
+ } else {
+ struct group *grgid = getgrnam(sgid);
+ if(grgid)
+ gid = grgid->gr_gid;
+ else {
+ ERROR("Gid %s invalid uid or unknown user\n", sgid);
+ goto error;
+ }
+ }
+
+ switch(type) {
+ case 'B':
+ mode |= S_IFBLK;
+ break;
+ case 'C':
+ mode |= S_IFCHR;
+ break;
+ case 'I':
+ if(ipc_type == 's')
+ mode |= S_IFSOCK;
+ else
+ mode |= S_IFIFO;
+ break;
+ case 'D':
+ mode |= S_IFDIR;
+ break;
+ case 'F':
+ case 'R':
+ mode |= S_IFREG;
+ break;
+ case 'S':
+ /* permissions on symlinks are always rwxrwxrwx */
+ mode = 0777 | S_IFLNK;
+ break;
+ }
+
+ dev = malloc(sizeof(struct pseudo_dev));
+ if(dev == NULL)
+ MEM_ERROR();
+
+ dev->buf = malloc(sizeof(struct pseudo_stat));
+ if(dev->buf == NULL)
+ MEM_ERROR();
+
+ dev->type = type == 'M' ? 'M' : tolower(type);
+ dev->buf->mode = mode;
+ dev->buf->uid = uid;
+ dev->buf->gid = gid;
+ dev->buf->major = major;
+ dev->buf->minor = minor;
+ dev->buf->mtime = mtime;
+ dev->buf->ino = pseudo_ino ++;
+
+ if(type == 'R') {
+ if(*file == NULL) {
+ *file = malloc(sizeof(struct pseudo_file));
+ if(*file == NULL)
+ MEM_ERROR();
+
+ (*file)->filename = strdup(pseudo_file);
+ (*file)->fd = -1;
+ }
+
+ dev->data = malloc(sizeof(struct pseudo_data));
+ if(dev->data == NULL)
+ MEM_ERROR();
+
+ dev->pseudo_type = PSEUDO_FILE_DATA;
+ dev->data->file = *file;
+ dev->data->length = file_length;
+ dev->data->offset = pseudo_offset;
+ dev->data->sparse = sparse;
+ } else if(type == 'F') {
+ dev->pseudo_type = PSEUDO_FILE_PROCESS;
+ dev->command = strdup(command);
+ } else
+ dev->pseudo_type = PSEUDO_FILE_OTHER;
+
+ if(type == 'S')
+ dev->symlink = strdup(symlink);
+
+ pseudo = add_pseudo_definition(pseudo, dev, name, name);
+
+ free(filename);
+ return TRUE;
+
+error:
+ print_definitions();
+ free(filename);
+ return FALSE;
+}
+
+
+static int read_pseudo_def_original(char type, char *orig_def, char *filename, char *name, char *def)
+{
+ int n, bytes;
+ unsigned int major = 0, minor = 0, mode;
+ char *ptr, *command = NULL, *symlink = NULL;
+ char suid[100], sgid[100]; /* overflow safe */
+ char ipc_type;
+ long long uid, gid;
+ struct pseudo_dev *dev;
+ static int pseudo_ino = 1;
+
+ n = sscanf(def, "%o %99s %99s %n", &mode, suid, sgid, &bytes);
+ def += bytes;
+
+ if(n < 3) {
+ ERROR("Not enough or invalid arguments in pseudo file "
+ "definition \"%s\"\n", orig_def);
+ switch(n) {
+ case -1:
+ /* FALLTHROUGH */
+ case 0:
+ /* FALLTHROUGH */
+ case 1:
+ ERROR("Couldn't parse filename, type or octal mode\n");
+ ERROR("If the filename has spaces, either quote it, or "
+ "backslash the spaces\n");
+ break;
+ case 2:
+ ERROR("Read filename, type and mode, but failed to "
+ "read or match uid\n");
+ break;
+ default:
+ ERROR("Read filename, type, mode and uid, but failed "
+ "to read or match gid\n");
+ break;
+ }
+ goto error;
+ }
+
+ switch(type) {
+ case 'b':
+ /* FALLTHROUGH */
+ case 'c':
+ n = sscanf(def, "%u %u %n", &major, &minor, &bytes);
+ def += bytes;
+
+ if(n < 2) {
+ ERROR("Not enough or invalid arguments in %s device "
+ "pseudo file definition \"%s\"\n", type == 'b' ?
+ "block" : "character", orig_def);
+ if(n < 1)
+ ERROR("Read filename, type, mode, uid and gid, "
+ "but failed to read or match major\n");
+ else
+ ERROR("Read filename, type, mode, uid, gid "
+ "and major, but failed to read or "
+ "match minor\n");
+ goto error;
+ }
+
+ if(major > 0xfff) {
+ ERROR("Major %d out of range\n", major);
+ goto error;
+ }
+
+ if(minor > 0xfffff) {
+ ERROR("Minor %d out of range\n", minor);
+ goto error;
+ }
+ break;
+ case 'i':
+ n = sscanf(def, "%c %n", &ipc_type, &bytes);
+ def += bytes;
+
+ if(n < 1) {
+ ERROR("Not enough or invalid arguments in ipc "
+ "pseudo file definition \"%s\"\n", orig_def);
+ ERROR("Read filename, type, mode, uid and gid, "
+ "but failed to read or match ipc_type\n");
+ goto error;
+ }
+
+ if(ipc_type != 's' && ipc_type != 'f') {
+ ERROR("Ipc_type should be s or f\n");
+ goto error;
+ }
+ break;
+ case 'd':
+ case 'm':
+ break;
+ case 'f':
+ if(def[0] == '\0') {
+ ERROR("Not enough arguments in dynamic file pseudo "
+ "definition \"%s\"\n", orig_def);
+ ERROR("Expected command, which can be an executable "
+ "or a piece of shell script\n");
+ goto error;
+ }
+ command = def;
+ def += strlen(def);
+ break;
+ case 's':
+ if(def[0] == '\0') {
+ ERROR("Not enough arguments in symlink pseudo "
+ "definition \"%s\"\n", orig_def);
+ ERROR("Expected symlink\n");
+ goto error;
+ }
+
+ if(strlen(def) > 65535) {
+ ERROR("Symlink pseudo definition %s is greater than 65535"
+ " bytes!\n", def);
+ goto error;
+ }
+ symlink = def;
+ def += strlen(def);
+ break;
+ default:
+ ERROR("Unsupported type %c\n", type);
+ goto error;
+ }
+
+ /*
+ * Check for trailing junk after expected arguments
+ */
+ if(def[0] != '\0') {
+ ERROR("Unexpected tailing characters in pseudo file "
+ "definition \"%s\"\n", orig_def);
+ goto error;
+ }
+
+ if(mode > 07777) {
+ ERROR("Mode %o out of range\n", mode);
+ goto error;
+ }
+
+ uid = strtoll(suid, &ptr, 10);
+ if(*ptr == '\0') {
+ if(uid < 0 || uid > ((1LL << 32) - 1)) {
+ ERROR("Uid %s out of range\n", suid);
+ goto error;
+ }
+ } else {
+ struct passwd *pwuid = getpwnam(suid);
+ if(pwuid)
+ uid = pwuid->pw_uid;
+ else {
+ ERROR("Uid %s invalid uid or unknown user\n", suid);
+ goto error;
+ }
+ }
+
+ gid = strtoll(sgid, &ptr, 10);
+ if(*ptr == '\0') {
+ if(gid < 0 || gid > ((1LL << 32) - 1)) {
+ ERROR("Gid %s out of range\n", sgid);
+ goto error;
+ }
+ } else {
+ struct group *grgid = getgrnam(sgid);
+ if(grgid)
+ gid = grgid->gr_gid;
+ else {
+ ERROR("Gid %s invalid uid or unknown user\n", sgid);
+ goto error;
+ }
+ }
+
+ switch(type) {
+ case 'b':
+ mode |= S_IFBLK;
+ break;
+ case 'c':
+ mode |= S_IFCHR;
+ break;
+ case 'i':
+ if(ipc_type == 's')
+ mode |= S_IFSOCK;
+ else
+ mode |= S_IFIFO;
+ break;
+ case 'd':
+ mode |= S_IFDIR;
+ break;
+ case 'f':
+ mode |= S_IFREG;
+ break;
+ case 's':
+ /* permissions on symlinks are always rwxrwxrwx */
+ mode = 0777 | S_IFLNK;
+ break;
+ }
+
+ dev = malloc(sizeof(struct pseudo_dev));
+ if(dev == NULL)
+ MEM_ERROR();
+
+ dev->buf = malloc(sizeof(struct pseudo_stat));
+ if(dev->buf == NULL)
+ MEM_ERROR();
+
+ dev->type = type;
+ dev->buf->mode = mode;
+ dev->buf->uid = uid;
+ dev->buf->gid = gid;
+ dev->buf->major = major;
+ dev->buf->minor = minor;
+ dev->buf->mtime = time(NULL);
+ dev->buf->ino = pseudo_ino ++;
+
+ if(type == 'f') {
+ dev->pseudo_type = PSEUDO_FILE_PROCESS;
+ dev->command = strdup(command);
+ } else
+ dev->pseudo_type = PSEUDO_FILE_OTHER;
+
+ if(type == 's')
+ dev->symlink = strdup(symlink);
+
+ pseudo = add_pseudo_definition(pseudo, dev, name, name);
+
+ free(filename);
+ return TRUE;
+
+error:
+ print_definitions();
+ free(filename);
+ return FALSE;
+}
+
+
+static int read_pseudo_def(char *def, char *destination, char *pseudo_file, struct pseudo_file **file)
+{
+ int n, bytes;
+ int quoted = 0;
+ char type;
+ char *filename, *name;
+ char *orig_def = def;
+
+ /*
+ * Scan for filename, don't use sscanf() and "%s" because
+ * that can't handle filenames with spaces.
+ *
+ * Filenames with spaces should either escape (backslash) the
+ * space or use double quotes.
+ */
+ filename = malloc(strlen(def) + 1);
+ if(filename == NULL)
+ MEM_ERROR();
+
+ for(name = filename; (quoted || !isspace(*def)) && *def != '\0';) {
+ if(*def == '"') {
+ quoted = !quoted;
+ def ++;
+ continue;
+ }
+
+ if(*def == '\\') {
+ def ++;
+ if (*def == '\0')
+ break;
+ }
+ *name ++ = *def ++;
+ }
+ *name = '\0';
+
+ /* Skip any leading slashes (/) */
+ for(name = filename; *name == '/'; name ++);
+
+ if(*name == '\0') {
+ strcpy(filename, "/");
+ name = filename;
+ }
+
+ n = sscanf(def, " %c %n", &type, &bytes);
+ def += bytes;
+
+ if(n < 1) {
+ ERROR("Not enough or invalid arguments in pseudo file "
+ "definition \"%s\"\n", orig_def);
+ goto error;
+ }
+
+ if(type == 'x')
+ return read_pseudo_xattr(orig_def, filename, name, def);
+ else if(type == 'l')
+ return read_pseudo_def_link(orig_def, filename, name, def, destination);
+ else if(type == 'L')
+ return read_pseudo_def_pseudo_link(orig_def, filename, name, def);
+ else if(isupper(type))
+ return read_pseudo_def_extended(type, orig_def, filename, name, def, pseudo_file, file);
+ else
+ return read_pseudo_def_original(type, orig_def, filename, name, def);
+
+error:
+ print_definitions();
+ free(filename);
+ return FALSE;
+}
+
+
+int read_pseudo_definition(char *filename, char *destination)
+{
+ return read_pseudo_def(filename, destination, NULL, NULL);
+}
+
+
+int read_pseudo_file(char *filename, char *destination)
+{
+ FILE *fd;
+ char *def, *err, *line = NULL;
+ int res, size = 0;
+ struct pseudo_file *file = NULL;
+ long long bytes = 0;
+ int pseudo_stdin = strcmp(filename, "-") == 0;
+
+ if(pseudo_stdin)
+ fd = stdin;
+ else {
+ fd = fopen(filename, "r");
+ if(fd == NULL) {
+ ERROR("Could not open pseudo device file \"%s\" "
+ "because %s\n", filename, strerror(errno));
+ return FALSE;
+ }
+ }
+
+ while(1) {
+ int total = 0;
+
+ while(1) {
+ int len;
+
+ if(total + (MAX_LINE + 1) > size) {
+ line = realloc(line, size += (MAX_LINE + 1));
+ if(line == NULL)
+ MEM_ERROR();
+ }
+
+ err = fgets(line + total, MAX_LINE + 1, fd);
+ if(err == NULL)
+ break;
+
+ len = strlen(line + total);
+ total += len;
+ bytes += len;
+
+ if(len == MAX_LINE && line[total - 1] != '\n') {
+ /* line too large */
+ ERROR("Line too long when reading "
+ "pseudo file \"%s\", larger than "
+ "%d bytes\n", filename, MAX_LINE);
+ goto failed;
+ }
+
+ /*
+ * Remove '\n' terminator if it exists (the last line
+ * in the file may not be '\n' terminated)
+ */
+ if(len && line[total - 1] == '\n') {
+ line[-- total] = '\0';
+ len --;
+ }
+
+ /*
+ * If no line continuation then jump out to
+ * process line. Note, we have to be careful to
+ * check for "\\" (backslashed backslash) and to
+ * ensure we don't look at the previous line
+ */
+ if(len == 0 || line[total - 1] != '\\' || (len >= 2 &&
+ strcmp(line + total - 2, "\\\\") == 0))
+ break;
+ else
+ total --;
+ }
+
+ if(err == NULL) {
+ if(ferror(fd)) {
+ ERROR("Reading pseudo file \"%s\" failed "
+ "because %s\n", filename,
+ strerror(errno));
+ goto failed;
+ }
+
+ /*
+ * At EOF, normally we'll be finished, but, have to
+ * check for special case where we had "\" line
+ * continuation and then hit EOF immediately afterwards
+ */
+ if(total == 0)
+ break;
+ else
+ line[total] = '\0';
+ }
+
+ /* Skip any leading whitespace */
+ for(def = line; isspace(*def); def ++);
+
+ /* if line is now empty after skipping characters, skip it */
+ if(*def == '\0')
+ continue;
+
+ /* if comment line, skip it. But, we also have to check if
+ * it is the data demarker */
+ if(*def == '#') {
+ if(strcmp(def, "# START OF DATA - DO NOT MODIFY") == 0) {
+ if(file) {
+ file->start = bytes + 2;
+ file->current = 0;
+ file->fd = pseudo_stdin ? 0 : -1;
+ fgetc(fd);
+ fgetc(fd);
+ }
+ if(!pseudo_stdin)
+ fclose(fd);
+ free(line);
+ return TRUE;
+ } else
+ continue;
+ }
+
+ res = read_pseudo_def(def, destination, filename, &file);
+ if(res == FALSE)
+ goto failed;
+ }
+
+ if(file) {
+ /* No Data demarker found */
+ ERROR("No START OF DATA demarker found in pseudo file %s\n", filename);
+ goto failed;
+ }
+
+ if(!pseudo_stdin)
+ fclose(fd);
+ free(line);
+ return TRUE;
+
+failed:
+ if(!pseudo_stdin)
+ fclose(fd);
+ free(line);
+ return FALSE;
+}
+
+
+struct pseudo *get_pseudo()
+{
+ return pseudo;
+}
+
+
+#ifdef SQUASHFS_TRACE
+static void dump_pseudo(struct pseudo *pseudo, char *string)
+{
+ int i, res;
+ char *path;
+
+ for(i = 0; i < pseudo->names; i++) {
+ struct pseudo_entry *entry = &pseudo->name[i];
+ if(string) {
+ res = asprintf(&path, "%s/%s", string, entry->name);
+ if(res == -1)
+ BAD_ERROR("asprintf failed in dump_pseudo\n");
+ } else
+ path = entry->name;
+ if(entry->dev)
+ ERROR("%s %c 0%o %d %d %d %d\n", path, entry->dev->type,
+ entry->dev->buf->mode & ~S_IFMT, entry->dev->buf->uid,
+ entry->dev->buf->gid, entry->dev->buf->major,
+ entry->dev->buf->minor);
+ if(entry->pseudo)
+ dump_pseudo(entry->pseudo, path);
+ if(string)
+ free(path);
+ }
+}
+
+
+void dump_pseudos()
+{
+ if (pseudo)
+ dump_pseudo(pseudo, NULL);
+}
+#else
+void dump_pseudos()
+{
+}
+#endif
diff --git a/squashfs-tools/pseudo.h b/squashfs-tools/pseudo.h
new file mode 100644
index 0000000..8372861
--- /dev/null
+++ b/squashfs-tools/pseudo.h
@@ -0,0 +1,107 @@
+#ifndef PSEUDO_H
+#define PSEUDO_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2014, 2017, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * pseudo.h
+ */
+
+#define PSEUDO_FILE_OTHER 1
+#define PSEUDO_FILE_PROCESS 2
+#define PSEUDO_FILE_DATA 4
+
+#define IS_PSEUDO(a) ((a)->pseudo)
+#define IS_PSEUDO_PROCESS(a) ((a)->pseudo && ((a)->pseudo->pseudo_type & PSEUDO_FILE_PROCESS))
+#define IS_PSEUDO_OTHER(a) ((a)->pseudo && ((a)->pseudo->pseudo_type & PSEUDO_FILE_OTHER))
+#define IS_PSEUDO_DATA(a) ((a)->pseudo && ((a)->pseudo->pseudo_type & PSEUDO_FILE_DATA))
+
+struct pseudo_stat {
+ unsigned int mode;
+ unsigned int uid;
+ unsigned int gid;
+ unsigned int major;
+ unsigned int minor;
+ time_t mtime;
+ int ino;
+};
+
+struct pseudo_file {
+ char *filename;
+ long long start;
+ long long current;
+ int fd;
+};
+
+struct pseudo_data {
+ struct pseudo_file *file;
+ long long offset;
+ long long length;
+ int sparse;
+};
+
+struct pseudo_dev {
+ char type;
+ int pseudo_type;
+ union {
+ struct pseudo_stat *buf;
+ struct stat *linkbuf;
+ };
+ union {
+ struct pseudo_data *data;
+ char *command;
+ char *symlink;
+ char *linkname;
+ };
+};
+
+struct pseudo_entry {
+ char *name;
+ char *pathname;
+ struct pseudo *pseudo;
+ struct pseudo_dev *dev;
+ struct pseudo_xattr *xattr;
+};
+
+struct pseudo {
+ int names;
+ int count;
+ struct pseudo_entry *name;
+};
+
+struct pseudo_xattr {
+ int count;
+ struct xattr_add *xattr;
+};
+
+extern struct pseudo *pseudo;
+
+extern long long read_bytes(int, void *, long long);
+extern int read_pseudo_definition(char *, char *);
+extern int read_pseudo_file(char *, char *);
+extern struct pseudo *pseudo_subdir(char *, struct pseudo *);
+extern struct pseudo_entry *pseudo_readdir(struct pseudo *);
+extern struct pseudo_dev *get_pseudo_file(int);
+extern int pseudo_exec_file(struct pseudo_dev *, int *);
+extern struct pseudo *get_pseudo();
+extern void dump_pseudos();
+extern char *get_element(char *target, char **targname);
+extern void print_definitions();
+#endif
diff --git a/squashfs-tools/pseudo_xattr.c b/squashfs-tools/pseudo_xattr.c
new file mode 100644
index 0000000..454e8e8
--- /dev/null
+++ b/squashfs-tools/pseudo_xattr.c
@@ -0,0 +1,176 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * pseudo_xattr.c
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <regex.h>
+#include <dirent.h>
+
+#include "pseudo.h"
+#include "mksquashfs_error.h"
+#include "squashfs_fs.h"
+#include "xattr.h"
+
+#define TRUE 1
+#define FALSE 0
+
+static void add_xattr(struct pseudo_xattr **xattr, struct xattr_add *entry)
+{
+ if(*xattr == NULL) {
+ *xattr = malloc(sizeof(struct pseudo_xattr));
+ if(*xattr == NULL)
+ MEM_ERROR();
+
+ (*xattr)->xattr = entry;
+ entry->next = NULL;
+ (*xattr)->count = 1;
+ } else {
+ entry->next = (*xattr)->xattr;
+ (*xattr)->xattr = entry;
+ (*xattr)->count ++;
+ }
+}
+
+
+/*
+ * Add pseudo xattr to the set of pseudo definitions.
+ */
+static struct pseudo *add_pseudo_xattr(struct pseudo *pseudo, struct xattr_add *xattr,
+ char *target, char *alltarget)
+{
+ char *targname;
+ int i;
+
+ target = get_element(target, &targname);
+
+ if(pseudo == NULL) {
+ pseudo = malloc(sizeof(struct pseudo));
+ if(pseudo == NULL)
+ MEM_ERROR();
+
+ pseudo->names = 0;
+ pseudo->count = 0;
+ pseudo->name = NULL;
+ }
+
+ for(i = 0; i < pseudo->names; i++)
+ if(strcmp(pseudo->name[i].name, targname) == 0)
+ break;
+
+ if(i == pseudo->names) {
+ /* allocate new name entry */
+ pseudo->names ++;
+ pseudo->name = realloc(pseudo->name, (i + 1) *
+ sizeof(struct pseudo_entry));
+ if(pseudo->name == NULL)
+ MEM_ERROR();
+ pseudo->name[i].name = targname;
+ pseudo->name[i].pathname = NULL;
+ pseudo->name[i].dev = NULL;
+ pseudo->name[i].xattr = NULL;
+
+ if(target[0] == '\0') {
+ /* at leaf pathname component */
+ pseudo->name[i].pathname = strdup(alltarget);
+ pseudo->name[i].pseudo = NULL;
+ add_xattr(&pseudo->name[i].xattr, xattr);
+ } else {
+ /* recurse adding child components */
+ pseudo->name[i].pseudo = add_pseudo_xattr(NULL, xattr,
+ target, alltarget);
+ }
+ } else {
+ /* existing matching entry */
+
+ free(targname);
+
+ if(target[0] == '\0') {
+ /* Add xattr to this entry */
+ pseudo->name[i].pathname = strdup(alltarget);
+ add_xattr(&pseudo->name[i].xattr, xattr);
+ } else {
+ /* recurse adding child components */
+ pseudo->name[i].pseudo = add_pseudo_xattr(pseudo->name[i].pseudo, xattr, target, alltarget);
+ }
+ }
+
+ return pseudo;
+}
+
+
+static struct pseudo *add_pseudo_xattr_definition(struct pseudo *pseudo,
+ struct xattr_add *xattr, char *target, char *alltarget)
+{
+ /* special case if a root pseudo definition is being added */
+ if(strcmp(target, "/") == 0) {
+ /* if already have a root pseudo just add xattr */
+ if(pseudo && pseudo->names == 1 && strcmp(pseudo->name[0].name, "/") == 0) {
+ add_xattr(&pseudo->name[0].xattr, xattr);
+ return pseudo;
+ } else {
+ struct pseudo *new = malloc(sizeof(struct pseudo));
+ if(new == NULL)
+ MEM_ERROR();
+
+ new->names = 1;
+ new->count = 0;
+ new->name = malloc(sizeof(struct pseudo_entry));
+ if(new->name == NULL)
+ MEM_ERROR();
+
+ new->name[0].name = "/";
+ new->name[0].pseudo = pseudo;
+ new->name[0].pathname = "/";
+ new->name[0].dev = NULL;
+ new->name[0].xattr = NULL;
+ add_xattr(&new->name[0].xattr, xattr);
+ return new;
+ }
+ }
+
+ /* if there's a root pseudo definition, skip it before walking target */
+ if(pseudo && pseudo->names == 1 && strcmp(pseudo->name[0].name, "/") == 0) {
+ pseudo->name[0].pseudo = add_pseudo_xattr(pseudo->name[0].pseudo, xattr, target, alltarget);
+ return pseudo;
+ } else
+ return add_pseudo_xattr(pseudo, xattr, target, alltarget);
+}
+
+
+int read_pseudo_xattr(char *orig_def, char *filename, char *name, char *def)
+{
+ struct xattr_add *xattr = xattr_parse(def, "", "pseudo xattr");
+
+ if(xattr == NULL) {
+ print_definitions();
+ free(filename);
+ return FALSE;
+ }
+
+ pseudo = add_pseudo_xattr_definition(pseudo, xattr, name, name);
+
+ free(filename);
+ return TRUE;
+}
diff --git a/squashfs-tools/read_fs.c b/squashfs-tools/read_fs.c
new file mode 100644
index 0000000..f9976c0
--- /dev/null
+++ b/squashfs-tools/read_fs.c
@@ -0,0 +1,1090 @@
+/*
+ * Read a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2012, 2013, 2014, 2019, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * read_fs.c
+ */
+
+#define TRUE 1
+#define FALSE 0
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <limits.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <regex.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_swap.h"
+#include "compressor.h"
+#include "mksquashfs.h"
+#include "xattr.h"
+#include "mksquashfs_error.h"
+
+int read_block(int fd, long long start, long long *next, int expected,
+ void *block)
+{
+ unsigned short c_byte;
+ int res, compressed;
+ int outlen = expected ? expected : SQUASHFS_METADATA_SIZE;
+
+ /* Read block size */
+ res = read_fs_bytes(fd, start, 2, &c_byte);
+ if(res == 0)
+ return 0;
+
+ SQUASHFS_INSWAP_SHORTS(&c_byte, 1);
+ compressed = SQUASHFS_COMPRESSED(c_byte);
+ c_byte = SQUASHFS_COMPRESSED_SIZE(c_byte);
+
+ /*
+ * The block size should not be larger than
+ * the uncompressed size (or max uncompressed size if
+ * expected is 0)
+ */
+ if (c_byte > outlen)
+ return 0;
+
+ if(compressed) {
+ char buffer[c_byte];
+ int error;
+
+ res = read_fs_bytes(fd, start + 2, c_byte, buffer);
+ if(res == 0)
+ return 0;
+
+ res = compressor_uncompress(comp, block, buffer, c_byte,
+ outlen, &error);
+ if(res == -1) {
+ ERROR("%s uncompress failed with error code %d\n",
+ comp->name, error);
+ return 0;
+ }
+ } else {
+ res = read_fs_bytes(fd, start + 2, c_byte, block);
+ if(res == 0)
+ return 0;
+ res = c_byte;
+ }
+
+ if(next)
+ *next = start + 2 + c_byte;
+
+ /*
+ * if expected, then check the (uncompressed) return data
+ * is of the expected size
+ */
+ if(expected && expected != res)
+ return 0;
+ else
+ return res;
+}
+
+
+#define NO_BYTES(SIZE) \
+ (bytes - (cur_ptr - inode_table) < (SIZE))
+
+#define NO_INODE_BYTES(INODE) NO_BYTES(sizeof(struct INODE))
+
+unsigned char *scan_inode_table(int fd, long long start, long long end,
+ long long root_inode_start, int root_inode_offset,
+ struct squashfs_super_block *sBlk, union squashfs_inode_header
+ *dir_inode, long long *root_inode_block, unsigned int
+ *root_inode_size, long long *uncompressed_file, long long
+ *uncompressed_directory, unsigned int *file_count, unsigned int *sym_count,
+ unsigned int *dev_count, unsigned int *dir_count, unsigned int *fifo_count,
+ unsigned int *sock_count, unsigned int *id_table)
+{
+ unsigned char *cur_ptr;
+ unsigned char *inode_table = NULL;
+ int byte, files = 0;
+ unsigned int directory_start_block;
+ struct squashfs_base_inode_header base;
+ long long alloc_size, bytes = 0, size = 0;
+
+ TRACE("scan_inode_table: start 0x%llx, end 0x%llx, root_inode_start "
+ "0x%llx\n", start, end, root_inode_start);
+
+ /*
+ * Use the size of the compressed inode table as an initial
+ * memory allocation value, and the reallocation value, if
+ * this is too small.
+ *
+ * With a 50% compression ratio, this should require 2 alloc calls
+ * With a 25% compression ratio, this should require 4 alloc calls
+ * With a 12.5% compression ratio, this should require 8 alloc calls
+ *
+ * Always round to a multiple of SQUASHFS_METADATA_SIZE
+ */
+ alloc_size = ((end - start) + SQUASHFS_METADATA_SIZE) & ~(SQUASHFS_METADATA_SIZE - 1);
+
+ /* Rogue value used to check if it was found */
+ *root_inode_block = -1LL;
+ while(start < end) {
+ if(start == root_inode_start) {
+ TRACE("scan_inode_table: read compressed block 0x%llx "
+ "containing root inode\n", start);
+ *root_inode_block = bytes;
+ }
+ if(size - bytes < SQUASHFS_METADATA_SIZE) {
+ inode_table = realloc(inode_table, size += alloc_size);
+ if(inode_table == NULL)
+ MEM_ERROR();
+ }
+ TRACE("scan_inode_table: reading block 0x%llx\n", start);
+ byte = read_block(fd, start, &start, 0, inode_table + bytes);
+ if(byte == 0)
+ goto corrupted;
+
+ bytes += byte;
+
+ /* If this is not the last metadata block in the inode table
+ * then it should be SQUASHFS_METADATA_SIZE in size.
+ * Note, we can't use expected in read_block() above for this
+ * because we don't know if this is the last block until
+ * after reading.
+ */
+ if(start != end && byte != SQUASHFS_METADATA_SIZE)
+ goto corrupted;
+ }
+
+ /*
+ * We expect to have found the metadata block containing the
+ * root inode in the above inode_table metadata block scan. If it
+ * hasn't been found then the filesystem is corrupted
+ */
+ if(*root_inode_block == -1LL)
+ goto corrupted;
+
+ /*
+ * The number of bytes available after the root inode metadata block
+ * should be at least the root inode offset + the size of a
+ * regular directory inode, if not the filesystem is corrupted
+ *
+ * +-----------------------+-----------------------+
+ * | | directory |
+ * | | inode |
+ * +-----------------------+-----------------------+
+ * ^ ^ ^
+ * *root_inode_block root_inode_offset bytes
+ */
+ if((bytes - *root_inode_block) < (root_inode_offset +
+ sizeof(struct squashfs_dir_inode_header)))
+ goto corrupted;
+
+ /*
+ * Read the last inode in the inode table, which is the root directory
+ * inode, and get the directory start block. This is used when
+ * calculating the uncompressed directory size. The directory
+ * bytes in the last block will be counted as normal.
+ *
+ * Note, the previous check ensures the following calculation won't
+ * underflow, and we won't access beyond the buffer
+ */
+ *root_inode_size = bytes - (*root_inode_block + root_inode_offset);
+ bytes = *root_inode_block + root_inode_offset;
+ SQUASHFS_SWAP_DIR_INODE_HEADER(inode_table + bytes, &dir_inode->dir);
+
+ if(dir_inode->base.inode_type == SQUASHFS_DIR_TYPE) {
+ directory_start_block = dir_inode->dir.start_block;
+ if(*root_inode_size < sizeof(struct squashfs_dir_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+ } else if(dir_inode->base.inode_type == SQUASHFS_LDIR_TYPE) {
+ if(*root_inode_size < sizeof(struct squashfs_ldir_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+ SQUASHFS_SWAP_LDIR_INODE_HEADER(inode_table + bytes,
+ &dir_inode->ldir);
+ directory_start_block = dir_inode->ldir.start_block;
+ } else
+ /* bad type, corrupted filesystem */
+ goto corrupted;
+
+ if(dir_inode->base.uid >= sBlk->no_ids) {
+ ERROR("File system corrupted - uid index in inode too large (uid: %d)\n", dir_inode->base.uid);
+ goto corrupted2;
+ }
+
+ if(dir_inode->base.guid >= sBlk->no_ids) {
+ ERROR("File system corrupted - gid index in inode too large (gid: %d)\n", dir_inode->base.guid);
+ goto corrupted2;
+ }
+
+ get_uid(id_table[dir_inode->base.uid]);
+ get_guid(id_table[dir_inode->base.guid]);
+
+ /* allocate fragment to file mapping table */
+ file_mapping = calloc(sBlk->fragments, sizeof(struct append_file *));
+ if(file_mapping == NULL)
+ MEM_ERROR();
+
+ for(cur_ptr = inode_table; cur_ptr < inode_table + bytes; files ++) {
+ /*
+ * There should always be enough bytes to read the base
+ * inode header
+ */
+ if(NO_INODE_BYTES(squashfs_base_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ SQUASHFS_SWAP_BASE_INODE_HEADER(cur_ptr, &base);
+
+ TRACE("scan_inode_table: processing inode @ byte position "
+ "0x%x, type 0x%x\n",
+ (unsigned int) (cur_ptr - inode_table),
+ base.inode_type);
+
+ if(base.uid >= sBlk->no_ids) {
+ ERROR("File system corrupted - uid index in inode too large (uid: %d)\n", base.uid);
+ goto corrupted2;
+ }
+
+ if(base.guid >= sBlk->no_ids) {
+ ERROR("File system corrupted - gid index in inode too large (gid: %d)\n", base.guid);
+ goto corrupted2;
+ }
+
+ get_uid(id_table[base.uid]);
+ get_guid(id_table[base.guid]);
+
+ switch(base.inode_type) {
+ case SQUASHFS_FILE_TYPE: {
+ struct squashfs_reg_inode_header inode;
+ int frag_bytes, blocks, i;
+ long long start, file_bytes = 0;
+ unsigned int *block_list;
+
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_reg_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ SQUASHFS_SWAP_REG_INODE_HEADER(cur_ptr, &inode);
+
+ frag_bytes = inode.fragment == SQUASHFS_INVALID_FRAG ?
+ 0 : inode.file_size % sBlk->block_size;
+ blocks = inode.fragment == SQUASHFS_INVALID_FRAG ?
+ (inode.file_size + sBlk->block_size - 1) >>
+ sBlk->block_log : inode.file_size >>
+ sBlk->block_log;
+ start = inode.start_block;
+
+ TRACE("scan_inode_table: regular file, file_size %d, "
+ "blocks %d\n", inode.file_size, blocks);
+
+ cur_ptr += sizeof(inode);
+
+ if(NO_BYTES(blocks * sizeof(unsigned int)))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ block_list = malloc(blocks * sizeof(unsigned int));
+ if(block_list == NULL)
+ MEM_ERROR();
+
+ SQUASHFS_SWAP_INTS(cur_ptr, block_list, blocks);
+
+ *uncompressed_file += inode.file_size;
+ (*file_count) ++;
+
+ for(i = 0; i < blocks; i++)
+ file_bytes +=
+ SQUASHFS_COMPRESSED_SIZE_BLOCK
+ (block_list[i]);
+
+ if(inode.fragment != SQUASHFS_INVALID_FRAG &&
+ inode.fragment >= sBlk->fragments) {
+ free(block_list);
+ goto corrupted;
+ }
+
+ add_file(start, inode.file_size, file_bytes,
+ block_list, blocks, inode.fragment,
+ inode.offset, frag_bytes);
+
+ cur_ptr += blocks * sizeof(unsigned int);
+ break;
+ }
+ case SQUASHFS_LREG_TYPE: {
+ struct squashfs_lreg_inode_header inode;
+ int frag_bytes, blocks, i;
+ long long start, file_bytes = 0;
+ unsigned int *block_list;
+
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_lreg_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ SQUASHFS_SWAP_LREG_INODE_HEADER(cur_ptr, &inode);
+
+ frag_bytes = inode.fragment == SQUASHFS_INVALID_FRAG ?
+ 0 : inode.file_size % sBlk->block_size;
+ blocks = inode.fragment == SQUASHFS_INVALID_FRAG ?
+ (inode.file_size + sBlk->block_size - 1) >>
+ sBlk->block_log : inode.file_size >>
+ sBlk->block_log;
+ start = inode.start_block;
+
+ TRACE("scan_inode_table: extended regular "
+ "file, file_size %lld, blocks %d\n",
+ inode.file_size, blocks);
+
+ cur_ptr += sizeof(inode);
+
+ if(NO_BYTES(blocks * sizeof(unsigned int)))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ block_list = malloc(blocks * sizeof(unsigned int));
+ if(block_list == NULL)
+ MEM_ERROR();
+
+ SQUASHFS_SWAP_INTS(cur_ptr, block_list, blocks);
+
+ *uncompressed_file += inode.file_size;
+ (*file_count) ++;
+
+ for(i = 0; i < blocks; i++)
+ file_bytes +=
+ SQUASHFS_COMPRESSED_SIZE_BLOCK
+ (block_list[i]);
+
+ if(inode.fragment != SQUASHFS_INVALID_FRAG &&
+ inode.fragment >= sBlk->fragments) {
+ free(block_list);
+ goto corrupted;
+ }
+
+ add_file(start, inode.file_size, file_bytes,
+ block_list, blocks, inode.fragment,
+ inode.offset, frag_bytes);
+
+ cur_ptr += blocks * sizeof(unsigned int);
+ break;
+ }
+ case SQUASHFS_SYMLINK_TYPE:
+ case SQUASHFS_LSYMLINK_TYPE: {
+ struct squashfs_symlink_inode_header inode;
+
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_symlink_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ SQUASHFS_SWAP_SYMLINK_INODE_HEADER(cur_ptr, &inode);
+
+ (*sym_count) ++;
+
+ cur_ptr += sizeof(inode);
+
+ if (inode.inode_type == SQUASHFS_LSYMLINK_TYPE) {
+ if(NO_BYTES(inode.symlink_size +
+ sizeof(unsigned int)))
+ /* corrupted filesystem */
+ goto corrupted;
+ cur_ptr += inode.symlink_size +
+ sizeof(unsigned int);
+ } else {
+ if(NO_BYTES(inode.symlink_size))
+ /* corrupted filesystem */
+ goto corrupted;
+ cur_ptr += inode.symlink_size;
+ }
+ break;
+ }
+ case SQUASHFS_DIR_TYPE: {
+ struct squashfs_dir_inode_header dir_inode;
+
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_dir_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ SQUASHFS_SWAP_DIR_INODE_HEADER(cur_ptr, &dir_inode);
+
+ if(dir_inode.start_block < directory_start_block)
+ *uncompressed_directory += dir_inode.file_size;
+
+ (*dir_count) ++;
+ cur_ptr += sizeof(struct squashfs_dir_inode_header);
+ break;
+ }
+ case SQUASHFS_LDIR_TYPE: {
+ struct squashfs_ldir_inode_header dir_inode;
+ int i;
+
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_ldir_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ SQUASHFS_SWAP_LDIR_INODE_HEADER(cur_ptr, &dir_inode);
+
+ if(dir_inode.start_block < directory_start_block)
+ *uncompressed_directory += dir_inode.file_size;
+
+ (*dir_count) ++;
+ cur_ptr += sizeof(struct squashfs_ldir_inode_header);
+
+ /*
+ * Read and check the directory index for correctness
+ */
+ for(i = 0; i < dir_inode.i_count; i++) {
+ struct squashfs_dir_index index;
+
+ if(NO_BYTES(sizeof(index)))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ SQUASHFS_SWAP_DIR_INDEX(cur_ptr, &index);
+
+ cur_ptr += sizeof(index);
+
+ if(NO_BYTES(index.size + 1))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ cur_ptr += index.size + 1;
+ }
+ break;
+ }
+ case SQUASHFS_BLKDEV_TYPE:
+ case SQUASHFS_CHRDEV_TYPE:
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_dev_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ (*dev_count) ++;
+ cur_ptr += sizeof(struct squashfs_dev_inode_header);
+ break;
+ case SQUASHFS_LBLKDEV_TYPE:
+ case SQUASHFS_LCHRDEV_TYPE:
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_ldev_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ (*dev_count) ++;
+ cur_ptr += sizeof(struct squashfs_ldev_inode_header);
+ break;
+ case SQUASHFS_FIFO_TYPE:
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_ipc_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ (*fifo_count) ++;
+ cur_ptr += sizeof(struct squashfs_ipc_inode_header);
+ break;
+ case SQUASHFS_LFIFO_TYPE:
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_lipc_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ (*fifo_count) ++;
+ cur_ptr += sizeof(struct squashfs_lipc_inode_header);
+ break;
+ case SQUASHFS_SOCKET_TYPE:
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_ipc_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ (*sock_count) ++;
+ cur_ptr += sizeof(struct squashfs_ipc_inode_header);
+ break;
+ case SQUASHFS_LSOCKET_TYPE:
+ /*
+ * There should always be enough bytes to read an
+ * inode of the expected type
+ */
+ if(NO_INODE_BYTES(squashfs_lipc_inode_header))
+ /* corrupted filesystem */
+ goto corrupted;
+
+ (*sock_count) ++;
+ cur_ptr += sizeof(struct squashfs_lipc_inode_header);
+ break;
+ default:
+ ERROR("Unknown inode type %d in scan_inode_table!\n",
+ base.inode_type);
+ goto corrupted;
+ }
+ }
+
+ if(!quiet)
+ printf("Read existing filesystem, %d inodes scanned\n", files);
+
+ return inode_table;
+
+corrupted:
+ ERROR("scan_inode_table: filesystem corruption detected in "
+ "scanning metadata\n");
+corrupted2:
+ free(inode_table);
+ return NULL;
+}
+
+
+struct compressor *read_super(int fd, struct squashfs_super_block *sBlk, char *source)
+{
+ int res, bytes = 0;
+ char buffer[SQUASHFS_METADATA_SIZE] __attribute__ ((aligned));
+
+ res = read_fs_bytes(fd, SQUASHFS_START, sizeof(struct squashfs_super_block),
+ sBlk);
+ if(res == 0) {
+ ERROR("Can't find a SQUASHFS superblock on %s\n",
+ source);
+ ERROR("Wrong filesystem or filesystem is corrupted!\n");
+ goto failed_mount;
+ }
+
+ SQUASHFS_INSWAP_SUPER_BLOCK(sBlk);
+
+ if(sBlk->s_magic != SQUASHFS_MAGIC) {
+ if(sBlk->s_magic == SQUASHFS_MAGIC_SWAP)
+ ERROR("Pre 4.0 big-endian filesystem on %s, appending"
+ " to this is unsupported\n", source);
+ else {
+ ERROR("Can't find a SQUASHFS superblock on %s\n",
+ source);
+ ERROR("Wrong filesystem or filesystem is corrupted!\n");
+ }
+ goto failed_mount;
+ }
+
+ /* Check the MAJOR & MINOR versions */
+ if(sBlk->s_major != SQUASHFS_MAJOR || sBlk->s_minor > SQUASHFS_MINOR) {
+ if(sBlk->s_major < 4)
+ ERROR("Filesystem on %s is a SQUASHFS %d.%d filesystem."
+ " Appending\nto SQUASHFS %d.%d filesystems is "
+ "not supported. Please convert it to a "
+ "SQUASHFS 4 filesystem\n", source,
+ sBlk->s_major,
+ sBlk->s_minor, sBlk->s_major, sBlk->s_minor);
+ else
+ ERROR("Filesystem on %s is %d.%d, which is a later "
+ "filesystem version than I support\n",
+ source, sBlk->s_major, sBlk->s_minor);
+ goto failed_mount;
+ }
+
+ /* Check the compression type */
+ comp = lookup_compressor_id(sBlk->compression);
+ if(!comp->supported) {
+ ERROR("Filesystem on %s uses %s compression, this is "
+ "unsupported by this version\n", source, comp->name);
+ ERROR("Compressors available:\n");
+ display_compressors(stderr, "", "");
+ goto failed_mount;
+ }
+
+ /*
+ * Read extended superblock information from disk.
+ *
+ * Read compressor specific options from disk if present, and pass
+ * to compressor to set compressor options.
+ *
+ * Note, if there's no compressor options present, the compressor
+ * is still called to set the default options (the defaults may have
+ * been changed by the user specifying options on the command
+ * line which need to be over-ridden).
+ *
+ * Compressor_extract_options is also used to ensure that
+ * we know how to decompress a filesystem compressed with these
+ * compression options.
+ */
+ if(SQUASHFS_COMP_OPTS(sBlk->flags)) {
+ bytes = read_block(fd, sizeof(*sBlk), NULL, 0, buffer);
+
+ if(bytes == 0) {
+ ERROR("Failed to read compressor options from append "
+ "filesystem\n");
+ ERROR("Filesystem corrupted?\n");
+ goto failed_mount;
+ }
+ }
+
+ res = compressor_extract_options(comp, sBlk->block_size, buffer, bytes);
+ if(res == -1) {
+ ERROR("Compressor failed to set compressor options\n");
+ goto failed_mount;
+ }
+
+ if(quiet)
+ return comp;
+
+ printf("Found a valid %sSQUASHFS superblock on %s.\n",
+ SQUASHFS_EXPORTABLE(sBlk->flags) ? "exportable " : "", source);
+ printf("\tCompression used %s\n", comp->name);
+ printf("\tInodes are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_INODES(sBlk->flags) ? "un" : "");
+ printf("\tData is %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_DATA(sBlk->flags) ? "un" : "");
+ printf("\tFragments are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_FRAGMENTS(sBlk->flags) ? "un" : "");
+ printf("\tXattrs are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_XATTRS(sBlk->flags) ? "un" : "");
+ printf("\tFragments are %spresent in the filesystem\n",
+ SQUASHFS_NO_FRAGMENTS(sBlk->flags) ? "not " : "");
+ printf("\tAlways-use-fragments option is %sspecified\n",
+ SQUASHFS_ALWAYS_FRAGMENTS(sBlk->flags) ? "" : "not ");
+ printf("\tDuplicates are %sremoved\n",
+ SQUASHFS_DUPLICATES(sBlk->flags) ? "" : "not ");
+ printf("\tXattrs are %sstored\n",
+ SQUASHFS_NO_XATTRS(sBlk->flags) ? "not " : "");
+ printf("\tFilesystem size %.2f Kbytes (%.2f Mbytes)\n",
+ sBlk->bytes_used / 1024.0, sBlk->bytes_used
+ / (1024.0 * 1024.0));
+ printf("\tBlock size %d\n", sBlk->block_size);
+ printf("\tNumber of fragments %u\n", sBlk->fragments);
+ printf("\tNumber of inodes %d\n", sBlk->inodes);
+ printf("\tNumber of ids %d\n", sBlk->no_ids);
+ TRACE("sBlk->inode_table_start %llx\n", sBlk->inode_table_start);
+ TRACE("sBlk->directory_table_start %llx\n",
+ sBlk->directory_table_start);
+ TRACE("sBlk->id_table_start %llx\n", sBlk->id_table_start);
+ TRACE("sBlk->fragment_table_start %llx\n", sBlk->fragment_table_start);
+ TRACE("sBlk->lookup_table_start %llx\n", sBlk->lookup_table_start);
+ TRACE("sBlk->xattr_id_table_start %llx\n", sBlk->xattr_id_table_start);
+ printf("\n");
+
+ return comp;
+
+failed_mount:
+ return NULL;
+}
+
+
+static unsigned char *squashfs_readdir(int fd, int root_entries,
+ unsigned int directory_start_block, int offset, unsigned int dir_size,
+ unsigned int *last_directory_block, struct squashfs_super_block *sBlk,
+ void (push_directory_entry)(char *, squashfs_inode, unsigned int, int))
+{
+ struct squashfs_dir_header dirh;
+ char buffer[sizeof(struct squashfs_dir_entry) + SQUASHFS_NAME_LEN + 1]
+ __attribute__ ((aligned));
+ struct squashfs_dir_entry *dire = (struct squashfs_dir_entry *) buffer;
+ unsigned char *directory_table = NULL;
+ int byte, dir_count;
+ long long start = sBlk->directory_table_start + directory_start_block;
+ long long last_start_block = start, size = dir_size, bytes = 0;
+
+ size += offset;
+ directory_table = malloc((size + SQUASHFS_METADATA_SIZE * 2 - 1) &
+ ~(SQUASHFS_METADATA_SIZE - 1));
+ if(directory_table == NULL)
+ MEM_ERROR();
+
+ while(bytes < size) {
+ int expected = (size - bytes) >= SQUASHFS_METADATA_SIZE ?
+ SQUASHFS_METADATA_SIZE : 0;
+
+ TRACE("squashfs_readdir: reading block 0x%llx, bytes read so "
+ "far %lld\n", start, bytes);
+
+ last_start_block = start;
+ byte = read_block(fd, start, &start, expected, directory_table + bytes);
+ if(byte == 0) {
+ ERROR("Failed to read directory\n");
+ ERROR("Filesystem corrupted?\n");
+ free(directory_table);
+ return NULL;
+ }
+ bytes += byte;
+ }
+
+ if(!root_entries)
+ goto all_done;
+
+ bytes = offset;
+ while(bytes < size) {
+ SQUASHFS_SWAP_DIR_HEADER(directory_table + bytes, &dirh);
+
+ dir_count = dirh.count + 1;
+
+ /* dir_count should never be larger than SQUASHFS_DIR_COUNT */
+ if(dir_count > SQUASHFS_DIR_COUNT) {
+ ERROR("File system corrupted: too many entries in directory\n");
+ free(directory_table);
+ return NULL;
+ }
+
+ TRACE("squashfs_readdir: Read directory header @ byte position "
+ "0x%llx, 0x%x directory entries\n", bytes, dir_count);
+ bytes += sizeof(dirh);
+
+ while(dir_count--) {
+ SQUASHFS_SWAP_DIR_ENTRY(directory_table + bytes, dire);
+ bytes += sizeof(*dire);
+
+ /* size should never be SQUASHFS_NAME_LEN or larger */
+ if(dire->size >= SQUASHFS_NAME_LEN) {
+ ERROR("File system corrupted: filename too long\n");
+ free(directory_table);
+ return NULL;
+ }
+
+ memcpy(dire->name, directory_table + bytes,
+ dire->size + 1);
+ dire->name[dire->size + 1] = '\0';
+ TRACE("squashfs_readdir: pushing directory entry %s, "
+ "inode %x:%x, type 0x%x\n", dire->name,
+ dirh.start_block, dire->offset, dire->type);
+ push_directory_entry(dire->name,
+ SQUASHFS_MKINODE(dirh.start_block,
+ dire->offset), dirh.inode_number +
+ dire->inode_number, dire->type);
+ bytes += dire->size + 1;
+ }
+ }
+
+all_done:
+ *last_directory_block = (unsigned int) last_start_block -
+ sBlk->directory_table_start;
+ return directory_table;
+}
+
+
+static unsigned int *read_id_table(int fd, struct squashfs_super_block *sBlk)
+{
+ int indexes = SQUASHFS_ID_BLOCKS(sBlk->no_ids);
+ long long index[indexes];
+ int bytes = SQUASHFS_ID_BYTES(sBlk->no_ids);
+ unsigned int *id_table;
+ int res, i;
+
+ id_table = malloc(bytes);
+ if(id_table == NULL)
+ MEM_ERROR();
+
+ res = read_fs_bytes(fd, sBlk->id_table_start,
+ SQUASHFS_ID_BLOCK_BYTES(sBlk->no_ids), index);
+ if(res == 0) {
+ ERROR("Failed to read id table index\n");
+ ERROR("Filesystem corrupted?\n");
+ free(id_table);
+ return NULL;
+ }
+
+ SQUASHFS_INSWAP_ID_BLOCKS(index, indexes);
+
+ for(i = 0; i < indexes; i++) {
+ int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
+ bytes & (SQUASHFS_METADATA_SIZE - 1);
+ int length = read_block(fd, index[i], NULL, expected,
+ ((unsigned char *) id_table) +
+ (i * SQUASHFS_METADATA_SIZE));
+ TRACE("Read id table block %d, from 0x%llx, length %d\n", i,
+ index[i], length);
+ if(length == 0) {
+ ERROR("Failed to read id table block %d, from 0x%llx, "
+ "length %d\n", i, index[i], length);
+ ERROR("Filesystem corrupted?\n");
+ free(id_table);
+ return NULL;
+ }
+ }
+
+ SQUASHFS_INSWAP_INTS(id_table, sBlk->no_ids);
+
+ for(i = 0; i < sBlk->no_ids; i++) {
+ TRACE("Adding id %d to id tables\n", id_table[i]);
+ create_id(id_table[i]);
+ }
+
+ return id_table;
+}
+
+
+static struct squashfs_fragment_entry *read_fragment_table(int fd, struct squashfs_super_block *sBlk)
+{
+ int res;
+ unsigned int i;
+ long long bytes = SQUASHFS_FRAGMENT_BYTES(sBlk->fragments);
+ int indexes = SQUASHFS_FRAGMENT_INDEXES(sBlk->fragments);
+ long long fragment_table_index[indexes];
+ struct squashfs_fragment_entry *fragment_table;
+
+ TRACE("read_fragment_table: %u fragments, reading %d fragment indexes "
+ "from 0x%llx\n", sBlk->fragments, indexes,
+ sBlk->fragment_table_start);
+
+ fragment_table = malloc(bytes);
+ if(fragment_table == NULL)
+ MEM_ERROR();
+
+ res = read_fs_bytes(fd, sBlk->fragment_table_start,
+ SQUASHFS_FRAGMENT_INDEX_BYTES(sBlk->fragments),
+ fragment_table_index);
+ if(res == 0) {
+ ERROR("Failed to read fragment table index\n");
+ ERROR("Filesystem corrupted?\n");
+ free(fragment_table);
+ return NULL;
+ }
+
+ SQUASHFS_INSWAP_FRAGMENT_INDEXES(fragment_table_index, indexes);
+
+ for(i = 0; i < indexes; i++) {
+ int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
+ bytes & (SQUASHFS_METADATA_SIZE - 1);
+ int length = read_block(fd, fragment_table_index[i], NULL,
+ expected, ((unsigned char *) fragment_table) +
+ (i * SQUASHFS_METADATA_SIZE));
+ TRACE("Read fragment table block %d, from 0x%llx, length %d\n",
+ i, fragment_table_index[i], length);
+ if(length == 0) {
+ ERROR("Failed to read fragment table block %d, from "
+ "0x%llx, length %d\n", i,
+ fragment_table_index[i], length);
+ ERROR("Filesystem corrupted?\n");
+ free(fragment_table);
+ return NULL;
+ }
+ }
+
+ for(i = 0; i < sBlk->fragments; i++)
+ SQUASHFS_INSWAP_FRAGMENT_ENTRY(&fragment_table[i]);
+
+ return fragment_table;
+}
+
+
+static squashfs_inode *read_inode_lookup_table(int fd, struct squashfs_super_block *sBlk)
+{
+ int lookup_bytes = SQUASHFS_LOOKUP_BYTES(sBlk->inodes);
+ int indexes = SQUASHFS_LOOKUP_BLOCKS(sBlk->inodes);
+ long long index[indexes];
+ int res, i;
+ squashfs_inode *inode_lookup_table;
+
+ inode_lookup_table = malloc(lookup_bytes);
+ if(inode_lookup_table == NULL)
+ MEM_ERROR();
+
+ res = read_fs_bytes(fd, sBlk->lookup_table_start,
+ SQUASHFS_LOOKUP_BLOCK_BYTES(sBlk->inodes), index);
+ if(res == 0) {
+ ERROR("Failed to read inode lookup table index\n");
+ ERROR("Filesystem corrupted?\n");
+ free(inode_lookup_table);
+ return NULL;
+ }
+
+ SQUASHFS_INSWAP_LONG_LONGS(index, indexes);
+
+ for(i = 0; i < indexes; i++) {
+ int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
+ lookup_bytes & (SQUASHFS_METADATA_SIZE - 1);
+ int length = read_block(fd, index[i], NULL, expected,
+ ((unsigned char *) inode_lookup_table) +
+ (i * SQUASHFS_METADATA_SIZE));
+ TRACE("Read inode lookup table block %d, from 0x%llx, length "
+ "%d\n", i, index[i], length);
+ if(length == 0) {
+ ERROR("Failed to read inode lookup table block %d, "
+ "from 0x%llx, length %d\n", i, index[i],
+ length);
+ ERROR("Filesystem corrupted?\n");
+ free(inode_lookup_table);
+ return NULL;
+ }
+ }
+
+ SQUASHFS_INSWAP_LONG_LONGS(inode_lookup_table, sBlk->inodes);
+
+ return inode_lookup_table;
+}
+
+
+long long read_filesystem(char *root_name, int fd, struct squashfs_super_block *sBlk,
+ char **cinode_table, char **data_cache, char **cdirectory_table,
+ char **directory_data_cache, unsigned int *last_directory_block,
+ int *inode_dir_offset, unsigned int *inode_dir_file_size,
+ unsigned int *root_inode_size, unsigned int *inode_dir_start_block,
+ unsigned int *file_count, unsigned int *sym_count, unsigned int *dev_count,
+ unsigned int *dir_count, unsigned int *fifo_count, unsigned int *sock_count,
+ long long *uncompressed_file, long long *uncompressed_inode,
+ long long *uncompressed_directory, unsigned int *inode_dir_inode_number,
+ unsigned int *inode_dir_parent_inode,
+ void (push_directory_entry)(char *, squashfs_inode, unsigned int, int),
+ struct squashfs_fragment_entry **fragment_table,
+ squashfs_inode **inode_lookup_table)
+{
+ unsigned char *inode_table = NULL, *directory_table = NULL;
+ long long start = sBlk->inode_table_start;
+ long long end = sBlk->directory_table_start;
+ long long root_inode_start = start +
+ SQUASHFS_INODE_BLK(sBlk->root_inode);
+ unsigned int root_inode_offset =
+ SQUASHFS_INODE_OFFSET(sBlk->root_inode);
+ long long root_inode_block;
+ union squashfs_inode_header inode;
+ unsigned int *id_table = NULL;
+ int res;
+
+ if(!quiet)
+ printf("Scanning existing filesystem...\n");
+
+ if(get_xattrs(fd, sBlk) == 0)
+ goto error;
+
+ if(sBlk->fragments > 0) {
+ *fragment_table = read_fragment_table(fd, sBlk);
+ if(*fragment_table == NULL)
+ goto error;
+ }
+
+ if(sBlk->lookup_table_start != SQUASHFS_INVALID_BLK) {
+ *inode_lookup_table = read_inode_lookup_table(fd, sBlk);
+ if(*inode_lookup_table == NULL)
+ goto error;
+ }
+
+ id_table = read_id_table(fd, sBlk);
+ if(id_table == NULL)
+ goto error;
+
+ inode_table = scan_inode_table(fd, start, end, root_inode_start,
+ root_inode_offset, sBlk, &inode, &root_inode_block,
+ root_inode_size, uncompressed_file, uncompressed_directory,
+ file_count, sym_count, dev_count, dir_count, fifo_count,
+ sock_count, id_table);
+ if(inode_table == NULL)
+ goto error;
+
+ *uncompressed_inode = root_inode_block;
+
+ if(inode.base.inode_type == SQUASHFS_DIR_TYPE ||
+ inode.base.inode_type == SQUASHFS_LDIR_TYPE) {
+ if(inode.base.inode_type == SQUASHFS_DIR_TYPE) {
+ *inode_dir_start_block = inode.dir.start_block;
+ *inode_dir_offset = inode.dir.offset;
+ *inode_dir_file_size = inode.dir.file_size - 3;
+ *inode_dir_inode_number = inode.dir.inode_number;
+ *inode_dir_parent_inode = inode.dir.parent_inode;
+ } else {
+ *inode_dir_start_block = inode.ldir.start_block;
+ *inode_dir_offset = inode.ldir.offset;
+ *inode_dir_file_size = inode.ldir.file_size - 3;
+ *inode_dir_inode_number = inode.ldir.inode_number;
+ *inode_dir_parent_inode = inode.ldir.parent_inode;
+ }
+
+ directory_table = squashfs_readdir(fd, !root_name,
+ *inode_dir_start_block, *inode_dir_offset,
+ *inode_dir_file_size, last_directory_block, sBlk,
+ push_directory_entry);
+ if(directory_table == NULL)
+ goto error;
+
+ root_inode_start -= start;
+ *cinode_table = malloc(root_inode_start);
+ if(*cinode_table == NULL)
+ MEM_ERROR();
+
+ res = read_fs_bytes(fd, start, root_inode_start, *cinode_table);
+ if(res == 0) {
+ ERROR("Failed to read inode table\n");
+ ERROR("Filesystem corrupted?\n");
+ goto error;
+ }
+
+ *cdirectory_table = malloc(*last_directory_block);
+ if(*cdirectory_table == NULL)
+ MEM_ERROR();
+
+ res = read_fs_bytes(fd, sBlk->directory_table_start,
+ *last_directory_block, *cdirectory_table);
+ if(res == 0) {
+ ERROR("Failed to read directory table\n");
+ ERROR("Filesystem corrupted?\n");
+ goto error;
+ }
+
+ *data_cache = malloc(root_inode_offset + *root_inode_size);
+ if(*data_cache == NULL)
+ MEM_ERROR();
+
+ memcpy(*data_cache, inode_table + root_inode_block,
+ root_inode_offset + *root_inode_size);
+
+ *directory_data_cache = malloc(*inode_dir_offset +
+ *inode_dir_file_size);
+ if(*directory_data_cache == NULL)
+ MEM_ERROR();
+
+ memcpy(*directory_data_cache, directory_table,
+ *inode_dir_offset + *inode_dir_file_size);
+
+ free(id_table);
+ free(inode_table);
+ free(directory_table);
+ return sBlk->inode_table_start;
+ }
+
+error:
+ free(id_table);
+ free(inode_table);
+ free(directory_table);
+ return 0;
+}
diff --git a/squashfs-tools/read_fs.h b/squashfs-tools/read_fs.h
new file mode 100644
index 0000000..1c94742
--- /dev/null
+++ b/squashfs-tools/read_fs.h
@@ -0,0 +1,36 @@
+#ifndef READ_FS_H
+#define READ_FS_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2013,
+ * 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * read_fs.h
+ *
+ */
+extern struct compressor *read_super(int, struct squashfs_super_block *,
+ char *);
+extern long long read_filesystem(char *, int, struct squashfs_super_block *,
+char **, char **, char **, char **, unsigned int *, int *, unsigned int *,
+unsigned int *, unsigned int *, unsigned int *, unsigned int *, unsigned int *,
+unsigned int *, unsigned int *, unsigned int *, long long *, long long *,
+long long *, unsigned int *, unsigned int *, void (push_directory_entry)
+(char *, squashfs_inode, unsigned int, int), struct squashfs_fragment_entry **,
+squashfs_inode **);
+#endif
diff --git a/squashfs-tools/read_xattrs.c b/squashfs-tools/read_xattrs.c
new file mode 100644
index 0000000..a9d044f
--- /dev/null
+++ b/squashfs-tools/read_xattrs.c
@@ -0,0 +1,454 @@
+/*
+ * Read a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2010, 2012, 2013, 2019, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * read_xattrs.c
+ */
+
+/*
+ * Common xattr read code shared between mksquashfs and unsquashfs
+ */
+
+#define TRUE 1
+#define FALSE 0
+#include <stdio.h>
+#include <string.h>
+#include <regex.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_swap.h"
+#include "xattr.h"
+#include "error.h"
+
+#include <stdlib.h>
+
+extern int read_fs_bytes(int, long long, long long, void *);
+extern int read_block(int, long long, long long *, int, void *);
+
+static struct hash_entry {
+ long long start;
+ long long offset;
+ struct hash_entry *next;
+} *hash_table[65536];
+
+static struct squashfs_xattr_id *xattr_ids;
+static void *xattrs = NULL;
+static long long xattr_table_start;
+
+/*
+ * Prefix lookup table, storing mapping to/from prefix string and prefix id
+ */
+struct prefix prefix_table[] = {
+ { "user.", SQUASHFS_XATTR_USER },
+ { "trusted.", SQUASHFS_XATTR_TRUSTED },
+ { "security.", SQUASHFS_XATTR_SECURITY },
+ { "", -1 }
+};
+
+/*
+ * store mapping from location of compressed block in fs ->
+ * location of uncompressed block in memory
+ */
+static int save_xattr_block(long long start, long long offset)
+{
+ struct hash_entry *hash_entry = malloc(sizeof(*hash_entry));
+ int hash = start & 0xffff;
+
+ TRACE("save_xattr_block: start %lld, offset %d\n", start, offset);
+
+ if(hash_entry == NULL)
+ return FALSE;
+
+ hash_entry->start = start;
+ hash_entry->offset = offset;
+ hash_entry->next = hash_table[hash];
+ hash_table[hash] = hash_entry;
+
+ return TRUE;
+}
+
+
+/*
+ * map from location of compressed block in fs ->
+ * location of uncompressed block in memory
+ */
+static long long get_xattr_block(long long start)
+{
+ int hash = start & 0xffff;
+ struct hash_entry *hash_entry = hash_table[hash];
+
+ for(; hash_entry; hash_entry = hash_entry->next)
+ if(hash_entry->start == start)
+ break;
+
+ TRACE("get_xattr_block: start %lld, offset %d\n", start,
+ hash_entry ? hash_entry->offset : -1);
+
+ return hash_entry ? hash_entry->offset : -1;
+}
+
+
+/*
+ * construct the xattr_list entry from the fs xattr, including
+ * mapping name and prefix into a full name
+ */
+static int read_xattr_entry(struct xattr_list *xattr,
+ struct squashfs_xattr_entry *entry, void *name)
+{
+ int i, len, type = entry->type & XATTR_PREFIX_MASK;
+
+ for(i = 0; prefix_table[i].type != -1; i++)
+ if(prefix_table[i].type == type)
+ break;
+
+ if(prefix_table[i].type == -1) {
+ ERROR("read_xattr_entry: Unrecognised xattr type %d\n", type);
+ return 0;
+ }
+
+ len = strlen(prefix_table[i].prefix);
+ xattr->full_name = malloc(len + entry->size + 1);
+ if(xattr->full_name == NULL) {
+ ERROR("FATAL ERROR: Out of memory (%s)\n", __func__);
+ return -1;
+ }
+
+ memcpy(xattr->full_name, prefix_table[i].prefix, len);
+ memcpy(xattr->full_name + len, name, entry->size);
+ xattr->full_name[len + entry->size] = '\0';
+ xattr->name = xattr->full_name + len;
+ xattr->size = entry->size;
+ xattr->type = type;
+
+ return 1;
+}
+
+
+/*
+ * Read and decompress the xattr id table and the xattr metadata.
+ * This is cached in memory for later use by get_xattr()
+ */
+unsigned int read_xattrs_from_disk(int fd, struct squashfs_super_block *sBlk, int sanity_only, long long *table_start)
+{
+ /*
+ * Note on overflow limits:
+ * Size of ids (id_table.xattr_ids) is 2^32 (unsigned int)
+ * Max size of bytes is 2^32*16 or 2^36
+ * Max indexes is (2^32*16)/8K or 2^23
+ * Max index_bytes is ((2^32*16)/8K)*8 or 2^26 or 64M
+ */
+ int res, i, indexes, index_bytes;
+ unsigned int ids;
+ long long bytes;
+ long long *index, start, end;
+ struct squashfs_xattr_table id_table;
+
+ TRACE("read_xattrs_from_disk\n");
+
+ /*
+ * Read xattr id table, containing start of xattr metadata and the
+ * number of xattrs in the file system
+ */
+ res = read_fs_bytes(fd, sBlk->xattr_id_table_start, sizeof(id_table),
+ &id_table);
+ if(res == 0)
+ goto failed;
+
+ SQUASHFS_INSWAP_XATTR_TABLE(&id_table);
+
+ /*
+ * Compute index table values
+ */
+ ids = id_table.xattr_ids;
+ if(ids == 0) {
+ ERROR("FATAL ERROR: File system corrupted - xattr_ids is 0 in xattr table\n");
+ goto failed;
+ }
+
+ xattr_table_start = id_table.xattr_table_start;
+ index_bytes = SQUASHFS_XATTR_BLOCK_BYTES(ids);
+ indexes = SQUASHFS_XATTR_BLOCKS(ids);
+
+ /*
+ * The size of the index table (index_bytes) should match the
+ * table start and end points
+ */
+ if(index_bytes != (sBlk->bytes_used - (sBlk->xattr_id_table_start + sizeof(id_table)))) {
+ ERROR("FATAL ERROR: File system corrupted - Bad xattr_ids count in super block\n");
+ goto failed;
+ }
+
+ /*
+ * id_table.xattr_table_start stores the start of the compressed xattr
+ * metadata blocks. This by definition is also the end of the previous
+ * filesystem table - the id lookup table.
+ */
+ if(table_start != NULL)
+ *table_start = id_table.xattr_table_start;
+
+ /*
+ * If sanity_only is set then return once we've read the above
+ * table_start. That value is necessary for sanity checking,
+ * but we don't actually want to extract the xattrs, and so
+ * stop here.
+ */
+ if(sanity_only)
+ return id_table.xattr_ids;
+
+ /*
+ * Allocate and read the index to the xattr id table metadata
+ * blocks
+ */
+ index = malloc(index_bytes);
+ if(index == NULL) {
+ ERROR("FATAL ERROR: Out of memory (%s)\n", __func__);
+ goto failed;
+ }
+
+ res = read_fs_bytes(fd, sBlk->xattr_id_table_start + sizeof(id_table),
+ index_bytes, index);
+ if(res ==0)
+ goto failed1;
+
+ SQUASHFS_INSWAP_LONG_LONGS(index, indexes);
+
+ /*
+ * Allocate enough space for the uncompressed xattr id table, and
+ * read and decompress it
+ */
+ bytes = SQUASHFS_XATTR_BYTES(ids);
+ xattr_ids = malloc(bytes);
+ if(xattr_ids == NULL) {
+ ERROR("FATAL ERROR: Out of memory (%s)\n", __func__);
+ goto failed1;
+ }
+
+ for(i = 0; i < indexes; i++) {
+ int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
+ bytes & (SQUASHFS_METADATA_SIZE - 1);
+ int length = read_block(fd, index[i], NULL, expected,
+ ((unsigned char *) xattr_ids) +
+ ((long long) i * SQUASHFS_METADATA_SIZE));
+ TRACE("Read xattr id table block %d, from 0x%llx, length "
+ "%d\n", i, index[i], length);
+ if(length == 0) {
+ ERROR("FATAL ERROR - Failed to read xattr id table block %d, "
+ "from 0x%llx, length %d. File system corrupted?\n", i, index[i],
+ length);
+ goto failed2;
+ }
+ }
+
+ /*
+ * Read and decompress the xattr metadata
+ *
+ * Note the first xattr id table metadata block is immediately after
+ * the last xattr metadata block, so we can use index[0] to work out
+ * the end of the xattr metadata
+ */
+ start = xattr_table_start;
+ end = index[0];
+ for(i = 0; start < end; i++) {
+ int length, res;
+ xattrs = realloc(xattrs, (i + 1) * SQUASHFS_METADATA_SIZE);
+ if(xattrs == NULL) {
+ ERROR("FATAL ERROR: Out of memory (%s)\n", __func__);
+ goto failed3;
+ }
+
+ /* store mapping from location of compressed block in fs ->
+ * location of uncompressed block in memory */
+ res = save_xattr_block(start, i * SQUASHFS_METADATA_SIZE);
+ if (res == FALSE) {
+ ERROR("FATAL ERROR: Out of memory (%s)\n", __func__);
+ goto failed3;
+ }
+
+ length = read_block(fd, start, &start, 0,
+ ((unsigned char *) xattrs) +
+ (i * SQUASHFS_METADATA_SIZE));
+ TRACE("Read xattr block %d, length %d\n", i, length);
+ if(length == 0) {
+ ERROR("FATAL ERROR - Failed to read xattr block %d. File system corrupted?\n", i);
+ goto failed3;
+ }
+
+ /*
+ * If this is not the last metadata block in the xattr metadata
+ * then it should be SQUASHFS_METADATA_SIZE in size.
+ * Note, we can't use expected in read_block() above for this
+ * because we don't know if this is the last block until
+ * after reading.
+ */
+ if(start != end && length != SQUASHFS_METADATA_SIZE) {
+ ERROR("FATAL ERROR: Xattr block %d should be %d bytes in length, "
+ "it is %d bytes. File system corrupted?\n", i, SQUASHFS_METADATA_SIZE,
+ length);
+ goto failed3;
+ }
+ }
+
+ /* swap if necessary the xattr id entries */
+ for(i = 0; i < ids; i++)
+ SQUASHFS_INSWAP_XATTR_ID(&xattr_ids[i]);
+
+ free(index);
+
+ return ids;
+
+failed3:
+ free(xattrs);
+failed2:
+ free(xattr_ids);
+failed1:
+ free(index);
+
+failed:
+ return FALSE;
+}
+
+
+void free_xattr(struct xattr_list *xattr_list, int count)
+{
+ int i;
+
+ for(i = 0; i < count; i++)
+ free(xattr_list[i].full_name);
+
+ free(xattr_list);
+}
+
+
+/*
+ * Construct and return the list of xattr name:value pairs for the passed xattr
+ * id
+ *
+ * There are two users for get_xattr(), Mksquashfs uses it to read the
+ * xattrs from the filesystem on appending, and Unsquashfs uses it
+ * to retrieve the xattrs for writing to disk.
+ *
+ * Unfortunately, the two users disagree on what to do with unknown
+ * xattr prefixes, Mksquashfs wants to treat this as fatal otherwise
+ * this will cause xattrs to be be lost on appending. Unsquashfs
+ * on the otherhand wants to retrieve the xattrs which are known and
+ * to ignore the rest, this allows Unsquashfs to cope more gracefully
+ * with future versions which may have unknown xattrs, as long as the
+ * general xattr structure is adhered to, Unsquashfs should be able
+ * to safely ignore unknown xattrs, and to write the ones it knows about,
+ * this is better than completely refusing to retrieve all the xattrs.
+ *
+ * So return an error flag if any unrecognised types were found.
+ */
+struct xattr_list *get_xattr(int i, unsigned int *count, int *failed)
+{
+ long long start, xptr_offset;
+ struct xattr_list *xattr_list = NULL;
+ unsigned int offset;
+ void *xptr;
+ int j, n, res = 1;
+
+ TRACE("get_xattr\n");
+
+ if(xattr_ids[i].count == 0) {
+ ERROR("get_xattr: xattr count unexpectedly 0 - corrupt fs?\n");
+ *failed = TRUE;
+ *count = 0;
+ return NULL;
+ } else
+ *failed = FALSE;
+
+ start = SQUASHFS_XATTR_BLK(xattr_ids[i].xattr) + xattr_table_start;
+ offset = SQUASHFS_XATTR_OFFSET(xattr_ids[i].xattr);
+ xptr_offset = get_xattr_block(start);
+
+ if(xptr_offset == -1) {
+ ERROR("FATAL ERROR: file system is corrupt - incorrect xattr value in metadata\n");
+ *failed = FALSE;
+ return NULL;
+ }
+
+
+ xptr = xattrs + xptr_offset + offset;
+
+ TRACE("get_xattr: xattr_id %d, count %d, start %lld, offset %d\n", i,
+ xattr_ids[i].count, start, offset);
+
+ for(j = 0, n = 0; n < xattr_ids[i].count; n++) {
+ struct squashfs_xattr_entry entry;
+ struct squashfs_xattr_val val;
+
+ if(res != 0) {
+ xattr_list = realloc(xattr_list, (j + 1) *
+ sizeof(struct xattr_list));
+ if(xattr_list == NULL) {
+ ERROR("FATAL ERROR: Out of memory (%s)\n", __func__);
+ *failed = FALSE;
+ return NULL;
+ }
+ }
+
+ SQUASHFS_SWAP_XATTR_ENTRY(xptr, &entry);
+ xptr += sizeof(entry);
+
+ res = read_xattr_entry(&xattr_list[j], &entry, xptr);
+ if(res == 0) {
+ /* unknown type, skip, and set error flag */
+ xptr += entry.size;
+ SQUASHFS_SWAP_XATTR_VAL(xptr, &val);
+ xptr += sizeof(val) + val.vsize;
+ *failed = TRUE;
+ continue;
+ } else if(res == -1) {
+ ERROR("FATAL ERROR: Out of memory (%s)\n", __func__);
+ *failed = FALSE;
+ return NULL;
+ }
+
+ xptr += entry.size;
+
+ TRACE("get_xattr: xattr %d, type %d, size %d, name %s\n", j,
+ entry.type, entry.size, xattr_list[j].full_name);
+
+ if(entry.type & SQUASHFS_XATTR_VALUE_OOL) {
+ long long xattr;
+ void *ool_xptr;
+
+ xptr += sizeof(val);
+ SQUASHFS_SWAP_LONG_LONGS(xptr, &xattr, 1);
+ xptr += sizeof(xattr);
+ start = SQUASHFS_XATTR_BLK(xattr) + xattr_table_start;
+ offset = SQUASHFS_XATTR_OFFSET(xattr);
+ ool_xptr = xattrs + get_xattr_block(start) + offset;
+ SQUASHFS_SWAP_XATTR_VAL(ool_xptr, &val);
+ xattr_list[j].value = ool_xptr + sizeof(val);
+ } else {
+ SQUASHFS_SWAP_XATTR_VAL(xptr, &val);
+ xattr_list[j].value = xptr + sizeof(val);
+ xptr += sizeof(val) + val.vsize;
+ }
+
+ TRACE("get_xattr: xattr %d, vsize %d\n", j, val.vsize);
+
+ xattr_list[j++].vsize = val.vsize;
+ }
+
+ *count = j;
+ return xattr_list;
+}
diff --git a/squashfs-tools/reader.c b/squashfs-tools/reader.c
new file mode 100644
index 0000000..5954a76
--- /dev/null
+++ b/squashfs-tools/reader.c
@@ -0,0 +1,715 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * reader.c
+ */
+
+/* if throttling I/O, time to sleep between reads (in tenths of a second) */
+int sleep_time;
+
+#define TRUE 1
+#define FALSE 0
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <pthread.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "caches-queues-lists.h"
+#include "progressbar.h"
+#include "mksquashfs_error.h"
+#include "pseudo.h"
+#include "sort.h"
+#include "tar.h"
+#include "reader.h"
+
+static struct readahead **readahead_table = NULL;
+
+static void sigalrm_handler(int arg)
+{
+ struct timespec requested_time, remaining;
+
+ requested_time.tv_sec = sleep_time / 10;
+ requested_time.tv_nsec = (sleep_time % 10) * 100000000;
+
+ nanosleep(&requested_time, &remaining);
+}
+
+
+static char *pathname(struct dir_ent *dir_ent)
+{
+ static char *pathname = NULL;
+ static int size = ALLOC_SIZE;
+
+ if (dir_ent->nonstandard_pathname)
+ return dir_ent->nonstandard_pathname;
+
+ if(pathname == NULL) {
+ pathname = malloc(ALLOC_SIZE);
+ if(pathname == NULL)
+ MEM_ERROR();
+ }
+
+ for(;;) {
+ int res = snprintf(pathname, size, "%s/%s",
+ dir_ent->our_dir->pathname,
+ dir_ent->source_name ? : dir_ent->name);
+
+ if(res < 0)
+ BAD_ERROR("snprintf failed in pathname\n");
+ else if(res >= size) {
+ /*
+ * pathname is too small to contain the result, so
+ * increase it and try again
+ */
+ size = (res + ALLOC_SIZE) & ~(ALLOC_SIZE - 1);
+ pathname = realloc(pathname, size);
+ if(pathname == NULL)
+ MEM_ERROR();
+ } else
+ break;
+ }
+
+ return pathname;
+}
+
+
+static inline int is_fragment(struct inode_info *inode)
+{
+ off_t file_size = inode->buf.st_size;
+
+ /*
+ * If this block is to be compressed differently to the
+ * fragment compression then it cannot be a fragment
+ */
+ if(inode->noF != noF)
+ return FALSE;
+
+ return !inode->no_fragments && file_size && (file_size < block_size ||
+ (inode->always_use_fragments && file_size & (block_size - 1)));
+}
+
+
+static void put_file_buffer(struct file_buffer *file_buffer)
+{
+ /*
+ * Decide where to send the file buffer:
+ * - compressible non-fragment blocks go to the deflate threads,
+ * - fragments go to the process fragment threads,
+ * - all others go directly to the main thread
+ */
+ if(file_buffer->error) {
+ file_buffer->fragment = 0;
+ seq_queue_put(to_main, file_buffer);
+ } else if (file_buffer->file_size == 0)
+ seq_queue_put(to_main, file_buffer);
+ else if(file_buffer->fragment)
+ queue_put(to_process_frag, file_buffer);
+ else
+ queue_put(to_deflate, file_buffer);
+}
+
+
+static void reader_read_process(struct dir_ent *dir_ent)
+{
+ long long bytes = 0;
+ struct inode_info *inode = dir_ent->inode;
+ struct file_buffer *prev_buffer = NULL, *file_buffer;
+ int status, byte, res, child;
+ int file;
+
+ if(inode->read)
+ return;
+
+ inode->read = TRUE;
+
+ file = pseudo_exec_file(inode->pseudo, &child);
+ if(!file) {
+ file_buffer = cache_get_nohash(reader_buffer);
+ file_buffer->sequence = sequence_count ++;
+ goto read_err;
+ }
+
+ while(1) {
+ file_buffer = cache_get_nohash(reader_buffer);
+ file_buffer->sequence = sequence_count ++;
+ file_buffer->noD = inode->noD;
+
+ byte = read_bytes(file, file_buffer->data, block_size);
+ if(byte == -1)
+ goto read_err2;
+
+ file_buffer->size = byte;
+ file_buffer->file_size = -1;
+ file_buffer->error = FALSE;
+ file_buffer->fragment = FALSE;
+ bytes += byte;
+
+ if(byte == 0)
+ break;
+
+ /*
+ * Update progress bar size. This is done
+ * on every block rather than waiting for all blocks to be
+ * read incase write_file_process() is running in parallel
+ * with this. Otherwise the current progress bar position
+ * may get ahead of the progress bar size.
+ */
+ progress_bar_size(1);
+
+ if(prev_buffer)
+ put_file_buffer(prev_buffer);
+ prev_buffer = file_buffer;
+ }
+
+ /*
+ * Update inode file size now that the size of the dynamic pseudo file
+ * is known. This is needed for the -info option.
+ */
+ inode->buf.st_size = bytes;
+
+ while(1) {
+ res = waitpid(child, &status, 0);
+ if(res != -1)
+ break;
+ else if(errno != EINTR)
+ BAD_ERROR("read process: waitpid returned %d\n", errno);
+ }
+
+ close(file);
+
+ if(res == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ goto read_err;
+
+ if(prev_buffer == NULL)
+ prev_buffer = file_buffer;
+ else {
+ cache_block_put(file_buffer);
+ sequence_count --;
+ }
+ prev_buffer->file_size = bytes;
+ prev_buffer->fragment = is_fragment(inode);
+ put_file_buffer(prev_buffer);
+
+ return;
+
+read_err2:
+ close(file);
+read_err:
+ if(prev_buffer) {
+ cache_block_put(file_buffer);
+ sequence_count --;
+ file_buffer = prev_buffer;
+ }
+ file_buffer->error = TRUE;
+ put_file_buffer(file_buffer);
+}
+
+
+static void reader_read_file(struct dir_ent *dir_ent)
+{
+ struct stat *buf = &dir_ent->inode->buf, buf2;
+ struct file_buffer *file_buffer;
+ int blocks, file, res;
+ long long bytes, read_size;
+ struct inode_info *inode = dir_ent->inode;
+
+ if(inode->read)
+ return;
+
+ inode->read = TRUE;
+again:
+ bytes = 0;
+ read_size = buf->st_size;
+ blocks = (read_size + block_size - 1) >> block_log;
+
+ while(1) {
+ file = open(pathname(dir_ent), O_RDONLY);
+ if(file != -1 || errno != EINTR)
+ break;
+ }
+
+ if(file == -1) {
+ file_buffer = cache_get_nohash(reader_buffer);
+ file_buffer->sequence = sequence_count ++;
+ goto read_err2;
+ }
+
+ do {
+ file_buffer = cache_get_nohash(reader_buffer);
+ file_buffer->file_size = read_size;
+ file_buffer->sequence = sequence_count ++;
+ file_buffer->noD = inode->noD;
+ file_buffer->error = FALSE;
+
+ /*
+ * Always try to read block_size bytes from the file rather
+ * than expected bytes (which will be less than the block_size
+ * at the file tail) to check that the file hasn't grown
+ * since being stated. If it is longer (or shorter) than
+ * expected, then restat, and try again. Note the special
+ * case where the file is an exact multiple of the block_size
+ * is dealt with later.
+ */
+ file_buffer->size = read_bytes(file, file_buffer->data,
+ block_size);
+ if(file_buffer->size == -1)
+ goto read_err;
+
+ bytes += file_buffer->size;
+
+ if(blocks > 1) {
+ /* non-tail block should be exactly block_size */
+ if(file_buffer->size < block_size)
+ goto restat;
+
+ file_buffer->fragment = FALSE;
+ put_file_buffer(file_buffer);
+ }
+ } while(-- blocks > 0);
+
+ /* Overall size including tail should match */
+ if(read_size != bytes)
+ goto restat;
+
+ if(read_size && read_size % block_size == 0) {
+ /*
+ * Special case where we've not tried to read past the end of
+ * the file. We expect to get EOF, i.e. the file isn't larger
+ * than we expect.
+ */
+ char buffer;
+ int res;
+
+ res = read_bytes(file, &buffer, 1);
+ if(res == -1)
+ goto read_err;
+
+ if(res != 0)
+ goto restat;
+ }
+
+ file_buffer->fragment = is_fragment(inode);
+ put_file_buffer(file_buffer);
+
+ close(file);
+
+ return;
+
+restat:
+ res = fstat(file, &buf2);
+ if(res == -1) {
+ ERROR("Cannot stat dir/file %s because %s\n",
+ pathname(dir_ent), strerror(errno));
+ goto read_err;
+ }
+
+ if(read_size != buf2.st_size) {
+ close(file);
+ memcpy(buf, &buf2, sizeof(struct stat));
+ file_buffer->error = 2;
+ put_file_buffer(file_buffer);
+ goto again;
+ }
+read_err:
+ close(file);
+read_err2:
+ file_buffer->error = TRUE;
+ put_file_buffer(file_buffer);
+}
+
+
+static void remove_readahead(int index, struct readahead *prev, struct readahead *new)
+{
+ if(prev)
+ prev->next = new->next;
+ else
+ readahead_table[index] = new->next;
+}
+
+
+static void add_readahead(struct readahead *new)
+{
+ int index = READAHEAD_INDEX(new->start);
+
+ new->next = readahead_table[index];
+ readahead_table[index] = new;
+}
+
+
+static int get_bytes(char *data, int size)
+{
+ int res = fread(data, 1, size, stdin);
+
+ if(res == size)
+ return res;
+
+ return feof(stdin) ? 0 : -1;
+}
+
+
+static int get_readahead(struct pseudo_file *file, long long current,
+ struct file_buffer *file_buffer, int size)
+{
+ int count = size;
+ char *dest = file_buffer->data;
+
+ if(readahead_table == NULL)
+ return -1;
+
+ while(size) {
+ int index = READAHEAD_INDEX(current);
+ struct readahead *buffer = readahead_table[index], *prev = NULL;
+
+ for(; buffer; prev = buffer, buffer = buffer->next) {
+ if(buffer->start <= current && buffer->start + buffer->size > current) {
+ int offset = READAHEAD_OFFSET(current);
+ int buffer_offset = READAHEAD_OFFSET(buffer->start);
+
+ /*
+ * Four posibilities:
+ * 1. Wanted data is whole of buffer
+ * 2. Wanted data is at start of buffer
+ * 3. Wanted data is at end of buffer
+ * 4. Wanted data is in middle of buffer
+ */
+ if(offset == buffer_offset && size >= buffer->size) {
+ memcpy(dest, buffer->src, buffer->size);
+ dest += buffer->size;
+ size -= buffer->size;
+ current += buffer->size;
+
+ remove_readahead(index, prev, buffer);
+ free(buffer);
+ break;
+ } else if(offset == buffer_offset) {
+ memcpy(dest, buffer->src, size);
+ buffer->start += size;
+ buffer->src += size;
+ buffer->size -= size;
+
+ remove_readahead(index, prev, buffer);
+ add_readahead(buffer);
+
+ goto finished;
+ } else if(buffer_offset + buffer->size <= offset+ size) {
+ int bytes = buffer_offset + buffer->size - offset;
+
+ memcpy(dest, buffer->src + offset - buffer_offset, bytes);
+ buffer->size -= bytes;
+ dest += bytes;
+ size -= bytes;
+ current += bytes;
+ break;
+ } else {
+ struct readahead *left, *right;
+ int left_size = offset - buffer_offset;
+ int right_size = buffer->size - (offset + size);
+
+ memcpy(dest, buffer->src + offset - buffer_offset, size);
+
+ /* Split buffer into two */
+ left = malloc(sizeof(struct readahead) + left_size);
+ right = malloc(sizeof(struct readahead) + right_size);
+
+ if(left == NULL || right == NULL)
+ MEM_ERROR();
+
+ left->start = buffer->start;
+ left->size = left_size;
+ left->src = left->data;
+ memcpy(left->data, buffer->src, left_size);
+
+ right->start = current + size;
+ right->size = right_size;
+ right->src = right->data;
+ memcpy(right->data, buffer->src + offset + size, right_size);
+
+ remove_readahead(index, prev, buffer);
+ free(buffer);
+
+ add_readahead(left);
+ add_readahead(right);
+ goto finished;
+ }
+ }
+ }
+
+ if(buffer == NULL)
+ return -1;
+ }
+
+finished:
+ return count;
+}
+
+
+static int do_readahead(struct pseudo_file *file, long long current,
+ struct file_buffer *file_buffer, int size)
+{
+ int res;
+ long long readahead = current - file->current;
+
+ if(readahead_table == NULL) {
+ readahead_table = malloc(READAHEAD_ALLOC);
+ if(readahead_table == NULL)
+ MEM_ERROR();
+
+ memset(readahead_table, 0, READAHEAD_ALLOC);
+ }
+
+ while(readahead) {
+ int offset = READAHEAD_OFFSET(file->current);
+ int bytes = READAHEAD_SIZE - offset < readahead ? READAHEAD_SIZE - offset : readahead;
+ struct readahead *buffer = malloc(sizeof(struct readahead) + bytes);
+
+ if(buffer == NULL)
+ MEM_ERROR();
+
+ res = get_bytes(buffer->data, bytes);
+
+ if(res == -1) {
+ free(buffer);
+ return res;
+ }
+
+ buffer->start = file->current;
+ buffer->size = bytes;
+ buffer->src = buffer->data;
+ add_readahead(buffer);
+
+ file->current += bytes;
+ readahead -= bytes;
+ }
+
+ res = get_bytes(file_buffer->data, size);
+
+ if(res != -1)
+ file->current += size;
+
+ return res;
+}
+
+
+static int read_data(struct pseudo_file *file, long long current,
+ struct file_buffer *file_buffer, int size)
+{
+ int res;
+
+ if(file->fd != STDIN_FILENO) {
+ if(current != file->current) {
+ /*
+ * File data reading is not in the same order as stored
+ * in the pseudo file. As this is not stdin, we can
+ * lseek() to the wanted data
+ */
+ res = lseek(file->fd, current + file->start, SEEK_SET);
+ if(res == -1)
+ BAD_ERROR("Lseek on pseudo file %s failed because %s\n",
+ file->filename, strerror(errno));
+
+ file->current = current;
+ }
+
+ res = read_bytes(file->fd, file_buffer->data, size);
+
+ if(res != -1)
+ file->current += size;
+
+ return res;
+ }
+
+ /*
+ * Reading from stdin. Three possibilities
+ * 1. We are at the current place in stdin, so just read data
+ * 2. Data we want has already been read and buffered (readahead).
+ * 3. Data is later in the file, readahead and buffer data to that point
+ */
+
+ if(current == file->current) {
+ res = get_bytes(file_buffer->data, size);
+
+ if(res != -1)
+ file->current += size;
+
+ return res;
+ } else if(current < file->current)
+ return get_readahead(file, current, file_buffer, size);
+ else
+ return do_readahead(file, current, file_buffer, size);
+}
+
+
+static void reader_read_data(struct dir_ent *dir_ent)
+{
+ struct file_buffer *file_buffer;
+ int blocks;
+ long long bytes, read_size, current;
+ struct inode_info *inode = dir_ent->inode;
+ static struct pseudo_file *file = NULL;
+
+ if(inode->read)
+ return;
+
+ inode->read = TRUE;
+ bytes = 0;
+ read_size = inode->pseudo->data->length;
+ blocks = (read_size + block_size - 1) >> block_log;
+
+ if(inode->pseudo->data->file != file) {
+ /* Reading the first or a different pseudo file, if
+ * a different one, first close the previous pseudo
+ * file, unless it is stdin */
+ if(file && file->fd > 0) {
+ close(file->fd);
+ file->fd = -1;
+ }
+
+ file = inode->pseudo->data->file;
+
+ if(file->fd == -1) {
+ while(1) {
+ file->fd = open(file->filename, O_RDONLY);
+ if(file->fd != -1 || errno != EINTR)
+ break;
+ }
+
+ if(file->fd == -1)
+ BAD_ERROR("Could not open pseudo file %s "
+ "because %s\n", file->filename,
+ strerror(errno));
+
+ file->current = -file->start;
+ }
+ }
+
+ current = inode->pseudo->data->offset;
+
+ do {
+ file_buffer = cache_get_nohash(reader_buffer);
+ file_buffer->file_size = read_size;
+ file_buffer->sequence = sequence_count ++;
+ file_buffer->noD = inode->noD;
+ file_buffer->error = FALSE;
+
+ if(blocks > 1) {
+ /* non-tail block should be exactly block_size */
+ file_buffer->size = read_data(file, current, file_buffer, block_size);
+ if(file_buffer->size != block_size)
+ BAD_ERROR("Failed to read pseudo file %s, it appears to be truncated or corrupted\n", file->filename);
+
+ current += file_buffer->size;
+ bytes += file_buffer->size;
+
+ file_buffer->fragment = FALSE;
+ put_file_buffer(file_buffer);
+ } else {
+ int expected = read_size - bytes;
+
+ file_buffer->size = read_data(file, current, file_buffer, expected);
+ if(file_buffer->size != expected)
+ BAD_ERROR("Failed to read pseudo file %s, it appears to be truncated or corrupted\n", file->filename);
+
+ current += file_buffer->size;
+ }
+ } while(-- blocks > 0);
+
+ file_buffer->fragment = is_fragment(inode);
+ put_file_buffer(file_buffer);
+}
+
+
+void reader_scan(struct dir_info *dir)
+{
+ struct dir_ent *dir_ent = dir->list;
+
+ for(; dir_ent; dir_ent = dir_ent->next) {
+ struct stat *buf = &dir_ent->inode->buf;
+
+ if(dir_ent->inode->root_entry || IS_TARFILE(dir_ent->inode))
+ continue;
+
+ if(IS_PSEUDO_PROCESS(dir_ent->inode)) {
+ reader_read_process(dir_ent);
+ continue;
+ }
+
+ if(IS_PSEUDO_DATA(dir_ent->inode)) {
+ reader_read_data(dir_ent);
+ continue;
+ }
+
+ switch(buf->st_mode & S_IFMT) {
+ case S_IFREG:
+ reader_read_file(dir_ent);
+ break;
+ case S_IFDIR:
+ reader_scan(dir_ent->dir);
+ break;
+ }
+ }
+}
+
+
+void *reader(void *arg)
+{
+ struct itimerval itimerval;
+ struct dir_info *dir = queue_get(to_reader);
+
+ if(sleep_time) {
+ signal(SIGALRM, sigalrm_handler);
+
+ itimerval.it_value.tv_sec = 0;
+ itimerval.it_value.tv_usec = 100000;
+ itimerval.it_interval.tv_sec = 10;
+ itimerval.it_interval.tv_usec = 0;
+ setitimer(ITIMER_REAL, &itimerval, NULL);
+ }
+
+ if(tarfile) {
+ read_tar_file();
+ dir = queue_get(to_reader);
+ }
+
+ if(!sorted)
+ reader_scan(dir);
+ else{
+ int i;
+ struct priority_entry *entry;
+
+ for(i = 65535; i >= 0; i--)
+ for(entry = priority_list[i]; entry;
+ entry = entry->next)
+ reader_read_file(entry->dir);
+ }
+
+ pthread_exit(NULL);
+}
diff --git a/squashfs-tools/reader.h b/squashfs-tools/reader.h
new file mode 100644
index 0000000..dd79865
--- /dev/null
+++ b/squashfs-tools/reader.h
@@ -0,0 +1,39 @@
+#ifndef READER_H
+#define READER_H
+
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * reader.h
+ */
+
+#define READAHEAD_SIZE 8192
+#define READAHEAD_ALLOC (0x100000 * sizeof(struct readahead *))
+#define READAHEAD_INDEX(A) ((A >> 13) & 0xfffff)
+#define READAHEAD_OFFSET(A) (A % READAHEAD_SIZE)
+
+struct readahead {
+ long long start;
+ int size;
+ struct readahead *next;
+ char *src;
+ char data[0] __attribute__((aligned));
+};
+#endif
diff --git a/squashfs-tools/restore.c b/squashfs-tools/restore.c
new file mode 100644
index 0000000..cec5ce9
--- /dev/null
+++ b/squashfs-tools/restore.c
@@ -0,0 +1,168 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014, 2019, 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * restore.c
+ */
+
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "caches-queues-lists.h"
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "mksquashfs_error.h"
+#include "progressbar.h"
+#include "info.h"
+
+#define FALSE 0
+#define TRUE 1
+
+extern pthread_t reader_thread, writer_thread, main_thread, order_thread;
+extern pthread_t *deflator_thread, *frag_deflator_thread, *frag_thread;
+extern struct queue *to_deflate, *to_writer, *to_frag, *to_process_frag;
+extern struct seq_queue *to_main, *to_order;
+extern void restorefs();
+extern int processors;
+extern int reproducible;
+
+static int interrupted = 0;
+static pthread_t restore_thread;
+
+void *restore_thrd(void *arg)
+{
+ sigset_t sigmask, old_mask;
+ int i, sig;
+
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGINT);
+ sigaddset(&sigmask, SIGTERM);
+ sigaddset(&sigmask, SIGUSR1);
+ pthread_sigmask(SIG_BLOCK, &sigmask, &old_mask);
+
+ while(1) {
+ sigwait(&sigmask, &sig);
+
+ if((sig == SIGINT || sig == SIGTERM) && !interrupted) {
+ ERROR("Interrupting will restore original "
+ "filesystem!\n");
+ ERROR("Interrupt again to quit\n");
+ interrupted = TRUE;
+ continue;
+ }
+
+ /* kill main thread/worker threads and restore */
+ set_progressbar_state(FALSE);
+ disable_info();
+
+ /* first kill the reader thread */
+ pthread_cancel(reader_thread);
+ pthread_join(reader_thread, NULL);
+
+ /*
+ * then flush the reader to deflator thread(s) output queue.
+ * The deflator thread(s) will idle
+ */
+ queue_flush(to_deflate);
+
+ /* now kill the deflator thread(s) */
+ for(i = 0; i < processors; i++)
+ pthread_cancel(deflator_thread[i]);
+ for(i = 0; i < processors; i++)
+ pthread_join(deflator_thread[i], NULL);
+
+ /*
+ * then flush the reader to process fragment thread(s) output
+ * queue. The process fragment thread(s) will idle
+ */
+ queue_flush(to_process_frag);
+
+ /* now kill the process fragment thread(s) */
+ for(i = 0; i < processors; i++)
+ pthread_cancel(frag_thread[i]);
+ for(i = 0; i < processors; i++)
+ pthread_join(frag_thread[i], NULL);
+
+ /*
+ * then flush the reader/deflator/process fragment to main
+ * thread output queue. The main thread will idle
+ */
+ seq_queue_flush(to_main);
+
+ /* now kill the main thread */
+ pthread_cancel(main_thread);
+ pthread_join(main_thread, NULL);
+
+ /* then flush the main thread to fragment deflator thread(s)
+ * queue. The fragment deflator thread(s) will idle
+ */
+ queue_flush(to_frag);
+
+ /* now kill the fragment deflator thread(s) */
+ for(i = 0; i < processors; i++)
+ pthread_cancel(frag_deflator_thread[i]);
+ for(i = 0; i < processors; i++)
+ pthread_join(frag_deflator_thread[i], NULL);
+
+ if(reproducible) {
+ /* then flush the fragment deflator_threads(s)
+ * to frag orderer thread. The frag orderer
+ * thread will idle
+ */
+ seq_queue_flush(to_order);
+
+ /* now kill the frag orderer thread */
+ pthread_cancel(order_thread);
+ pthread_join(order_thread, NULL);
+ }
+
+ /*
+ * then flush the main thread/fragment deflator thread(s)
+ * to writer thread queue. The writer thread will idle
+ */
+ queue_flush(to_writer);
+
+ /* now kill the writer thread */
+ pthread_cancel(writer_thread);
+ pthread_join(writer_thread, NULL);
+
+ TRACE("All threads cancelled\n");
+
+ restorefs();
+ }
+}
+
+
+pthread_t *init_restore_thread()
+{
+ pthread_create(&restore_thread, NULL, restore_thrd, NULL);
+ return &restore_thread;
+}
diff --git a/squashfs-tools/restore.h b/squashfs-tools/restore.h
new file mode 100644
index 0000000..35129f0
--- /dev/null
+++ b/squashfs-tools/restore.h
@@ -0,0 +1,28 @@
+#ifndef RESTORE_H
+#define RESTORE_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * restore.h
+ */
+
+extern pthread_t *init_restore_thread();
+#endif
diff --git a/squashfs-tools/signals.h b/squashfs-tools/signals.h
new file mode 100644
index 0000000..5418448
--- /dev/null
+++ b/squashfs-tools/signals.h
@@ -0,0 +1,54 @@
+#ifndef SIGNALS_H
+#define SIGNALS_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * signals.h
+ */
+
+static inline int wait_for_signal(sigset_t *sigmask, int *waiting)
+{
+ int sig;
+
+#if defined(__APPLE__) && defined(__MACH__)
+ sigwait(sigmask, &sig);
+ *waiting = 0;
+#else
+ struct timespec timespec = { .tv_sec = 1, .tv_nsec = 0 };
+
+ while(1) {
+ if(*waiting)
+ sig = sigtimedwait(sigmask, NULL, &timespec);
+ else
+ sig = sigwaitinfo(sigmask, NULL);
+
+ if(sig != -1)
+ break;
+
+ if(errno == EAGAIN)
+ *waiting = 0;
+ else if(errno != EINTR)
+ BAD_ERROR("sigtimedwait/sigwaitinfo failed because %s\n", strerror(errno));
+ }
+#endif
+ return sig;
+}
+#endif
diff --git a/squashfs-tools/sort.c b/squashfs-tools/sort.c
new file mode 100644
index 0000000..8814d95
--- /dev/null
+++ b/squashfs-tools/sort.c
@@ -0,0 +1,373 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012,
+ * 2013, 2014, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * sort.c
+ */
+
+#define TRUE 1
+#define FALSE 0
+#define MAX_LINE 16384
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "sort.h"
+#include "mksquashfs_error.h"
+#include "progressbar.h"
+
+static int mkisofs_style = -1;
+
+struct sort_info {
+ dev_t st_dev;
+ ino_t st_ino;
+ int priority;
+ struct sort_info *next;
+};
+
+static struct sort_info *sort_info_list[65536];
+
+struct priority_entry *priority_list[65536];
+
+extern int silent;
+extern char *pathname(struct dir_ent *dir_ent);
+extern long long hardlnk_count;
+
+static void add_priority_list(struct dir_ent *dir, int priority)
+{
+ struct priority_entry *new_priority_entry;
+
+ priority += 32768;
+ new_priority_entry = malloc(sizeof(struct priority_entry));
+ if(new_priority_entry == NULL)
+ MEM_ERROR();
+
+ new_priority_entry->dir = dir;;
+ new_priority_entry->next = priority_list[priority];
+ priority_list[priority] = new_priority_entry;
+}
+
+
+static int get_priority(char *filename, struct stat *buf, int priority)
+{
+ int hash = buf->st_ino & 0xffff;
+ struct sort_info *s;
+
+ for(s = sort_info_list[hash]; s; s = s->next)
+ if((s->st_dev == buf->st_dev) && (s->st_ino == buf->st_ino)) {
+ TRACE("returning priority %d (%s)\n", s->priority,
+ filename);
+ return s->priority;
+ }
+ TRACE("returning priority %d (%s)\n", priority, filename);
+ return priority;
+}
+
+
+#define ADD_ENTRY(buf, priority) {\
+ int hash = buf.st_ino & 0xffff;\
+ struct sort_info *s;\
+ if((s = malloc(sizeof(struct sort_info))) == NULL) \
+ MEM_ERROR(); \
+ s->st_dev = buf.st_dev;\
+ s->st_ino = buf.st_ino;\
+ s->priority = priority;\
+ s->next = sort_info_list[hash];\
+ sort_info_list[hash] = s;\
+ }
+static int add_sort_list(char *path, int priority, int source, char *source_path[])
+{
+ int i, n;
+ struct stat buf;
+
+ TRACE("add_sort_list: filename %s, priority %d\n", path, priority);
+ if(strlen(path) > 1 && strcmp(path + strlen(path) - 2, "/*") == 0)
+ path[strlen(path) - 2] = '\0';
+
+ TRACE("add_sort_list: filename %s, priority %d\n", path, priority);
+re_read:
+ if(path[0] == '/' || strncmp(path, "./", 2) == 0 ||
+ strncmp(path, "../", 3) == 0 || mkisofs_style == 1) {
+ if(lstat(path, &buf) == -1)
+ goto error;
+ TRACE("adding filename %s, priority %d, st_dev %d, st_ino "
+ "%lld\n", path, priority, (int) buf.st_dev,
+ (long long) buf.st_ino);
+ ADD_ENTRY(buf, priority);
+ return TRUE;
+ }
+
+ for(i = 0, n = 0; i < source; i++) {
+ char *filename;
+ int res = asprintf(&filename, "%s/%s", source_path[i], path);
+ if(res == -1)
+ BAD_ERROR("asprintf failed in add_sort_list\n");
+ res = lstat(filename, &buf);
+ free(filename);
+ if(res == -1) {
+ if(!(errno == ENOENT || errno == ENOTDIR))
+ goto error;
+ continue;
+ }
+ ADD_ENTRY(buf, priority);
+ n ++;
+ }
+
+ if(n == 0 && mkisofs_style == -1 && lstat(path, &buf) != -1) {
+ ERROR("WARNING: Mkisofs style sortlist detected! This is "
+ "supported but please\n");
+ ERROR("convert to mksquashfs style sortlist! A sortlist entry");
+ ERROR(" should be\neither absolute (starting with ");
+ ERROR("'/') start with './' or '../' (taken to be\nrelative to "
+ "$PWD), otherwise it ");
+ ERROR("is assumed the entry is relative to one\nof the source "
+ "directories, i.e. with ");
+ ERROR("\"mksquashfs test test.sqsh\",\nthe sortlist ");
+ ERROR("entry \"file\" is assumed to be inside the directory "
+ "test.\n\n");
+ mkisofs_style = 1;
+ goto re_read;
+ }
+
+ mkisofs_style = 0;
+
+ if(n == 1)
+ return TRUE;
+ if(n > 1) {
+ ERROR(" Ambiguous sortlist entry \"%s\"\n\nIt maps to more "
+ "than one source entry! Please use an absolute path."
+ "\n", path);
+ return FALSE;
+ }
+
+error:
+ ERROR_START("Cannot stat sortlist entry \"%s\"\n", path);
+ ERROR("This is probably because you're using the wrong file\n");
+ ERROR("path relative to the source directories.");
+ ERROR_EXIT(" Ignoring\n");
+ /*
+ * Historical note
+ * Failure to stat a sortlist entry is deliberately ignored, even
+ * though it is an error. Squashfs release 2.2 changed the behaviour
+ * to treat it as a fatal error, but it was changed back to
+ * the original behaviour to ignore it in release 2.2-r2 following
+ * feedback from users at the time.
+ */
+ return TRUE;
+}
+
+
+void generate_file_priorities(struct dir_info *dir, int priority,
+ struct stat *buf)
+{
+ struct dir_ent *dir_ent = dir->list;
+
+ priority = get_priority(dir->pathname, buf, priority);
+
+ for(; dir_ent; dir_ent = dir_ent->next) {
+ struct stat *buf = &dir_ent->inode->buf;
+ if(dir_ent->inode->root_entry)
+ continue;
+
+ switch(buf->st_mode & S_IFMT) {
+ case S_IFREG:
+ add_priority_list(dir_ent,
+ get_priority(pathname(dir_ent), buf,
+ priority));
+ break;
+ case S_IFDIR:
+ generate_file_priorities(dir_ent->dir,
+ priority, buf);
+ break;
+ }
+ }
+}
+
+
+int read_sort_file(char *filename, int source, char *source_path[])
+{
+ FILE *fd;
+ char line_buffer[MAX_LINE + 1]; /* overflow safe */
+ char sort_filename[MAX_LINE + 1]; /* overflow safe */
+ char *line, *name;
+ int n, priority, res;
+
+ if((fd = fopen(filename, "r")) == NULL) {
+ ERROR("Failed to open sort file \"%s\" because %s\n",
+ filename, strerror(errno));
+ return FALSE;
+ }
+
+ while(fgets(line = line_buffer, MAX_LINE + 1, fd) != NULL) {
+ int len = strlen(line);
+
+ if(len == MAX_LINE && line[len - 1] != '\n') {
+ /* line too large */
+ ERROR("Line too long when reading "
+ "sort file \"%s\", larger than %d "
+ "bytes\n", filename, MAX_LINE);
+ goto failed;
+ }
+
+ /*
+ * Remove '\n' terminator if it exists (the last line
+ * in the file may not be '\n' terminated)
+ */
+ if(len && line[len - 1] == '\n')
+ line[len - 1] = '\0';
+
+ /* Skip any leading whitespace */
+ while(isspace(*line))
+ line ++;
+
+ /* if comment line, skip */
+ if(*line == '#')
+ continue;
+
+ /*
+ * Scan for filename, don't use sscanf() and "%s" because
+ * that can't handle filenames with spaces
+ */
+ for(name = sort_filename; !isspace(*line) && *line != '\0';) {
+ if(*line == '\\') {
+ line ++;
+ if (*line == '\0')
+ break;
+ }
+ *name ++ = *line ++;
+ }
+ *name = '\0';
+
+ /*
+ * if filename empty, then line was empty of anything but
+ * whitespace or a backslash character. Skip empy lines
+ */
+ if(sort_filename[0] == '\0')
+ continue;
+
+ /*
+ * Scan the rest of the line, we expect a decimal number
+ * which is the filename priority
+ */
+ errno = 0;
+ res = sscanf(line, "%d%n", &priority, &n);
+
+ if((res < 1 || errno) && errno != ERANGE) {
+ if(errno == 0)
+ /* No error, assume EOL or match failure */
+ ERROR("Sort file \"%s\", can't find priority "
+ "in entry \"%s\", EOL or match "
+ "failure\n", filename, line_buffer);
+ else
+ /* Some other failure not ERANGE */
+ ERROR("Sscanf failed reading sort file \"%s\" "
+ "because %s\n", filename,
+ strerror(errno));
+ goto failed;
+ } else if((errno == ERANGE) ||
+ (priority < -32768 || priority > 32767)) {
+ ERROR("Sort file \"%s\", entry \"%s\" has priority "
+ "outside range of -32767:32768.\n", filename,
+ line_buffer);
+ goto failed;
+ }
+
+ /* Skip any trailing whitespace */
+ line += n;
+ while(isspace(*line))
+ line ++;
+
+ if(*line != '\0') {
+ ERROR("Sort file \"%s\", trailing characters after "
+ "priority in entry \"%s\"\n", filename,
+ line_buffer);
+ goto failed;
+ }
+
+ res = add_sort_list(sort_filename, priority, source,
+ source_path);
+ if(res == FALSE)
+ goto failed;
+ }
+
+ if(ferror(fd)) {
+ ERROR("Reading sort file \"%s\" failed because %s\n", filename,
+ strerror(errno));
+ goto failed;
+ }
+
+ fclose(fd);
+ return TRUE;
+
+failed:
+ fclose(fd);
+ return FALSE;
+}
+
+
+void sort_files_and_write(struct dir_info *dir)
+{
+ int i;
+ struct priority_entry *entry;
+ squashfs_inode inode;
+ int duplicate_file;
+ struct file_info *file;
+
+ for(i = 65535; i >= 0; i--)
+ for(entry = priority_list[i]; entry; entry = entry->next) {
+ TRACE("%d: %s\n", i - 32768, pathname(entry->dir));
+ if(entry->dir->inode->inode == SQUASHFS_INVALID_BLK) {
+ file = write_file(entry->dir, &duplicate_file);
+ inode = create_inode(NULL, entry->dir,
+ SQUASHFS_FILE_TYPE, file->file_size,
+ file->start, file->blocks,
+ file->block_list,
+ file->fragment, NULL,
+ file->sparse);
+ if(duplicate_checking == FALSE) {
+ free_fragment(file->fragment);
+ free(file->block_list);
+ }
+ INFO("file %s, uncompressed size %lld bytes %s"
+ "\n", pathname(entry->dir),
+ (long long)
+ entry->dir->inode->buf.st_size,
+ duplicate_file ? "DUPLICATE" : "");
+ entry->dir->inode->inode = inode;
+ entry->dir->inode->type = SQUASHFS_FILE_TYPE;
+ hardlnk_count --;
+ } else
+ INFO("file %s, uncompressed size %lld bytes "
+ "LINK\n", pathname(entry->dir),
+ (long long)
+ entry->dir->inode->buf.st_size);
+ }
+}
diff --git a/squashfs-tools/sort.h b/squashfs-tools/sort.h
new file mode 100644
index 0000000..98db62c
--- /dev/null
+++ b/squashfs-tools/sort.h
@@ -0,0 +1,37 @@
+#ifndef SORT_H
+#define SORT_H
+
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2013
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * sort.h
+ */
+
+struct priority_entry {
+ struct dir_ent *dir;
+ struct priority_entry *next;
+};
+
+extern int read_sort_file(char *, int, char *[]);
+extern void sort_files_and_write(struct dir_info *);
+extern void generate_file_priorities(struct dir_info *, int priority,
+ struct stat *);
+extern struct priority_entry *priority_list[65536];
+#endif
diff --git a/squashfs-tools/squashfs_compat.h b/squashfs-tools/squashfs_compat.h
new file mode 100644
index 0000000..1f58266
--- /dev/null
+++ b/squashfs-tools/squashfs_compat.h
@@ -0,0 +1,833 @@
+#ifndef SQUASHFS_COMPAT
+#define SQUASHFS_COMPAT
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2014, 2019
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * squashfs_compat.h
+ */
+
+/*
+ * definitions for structures on disk - layout 3.x
+ */
+
+#define SQUASHFS_CHECK 2
+#define SQUASHFS_CHECK_DATA(flags) SQUASHFS_BIT(flags, SQUASHFS_CHECK)
+
+/* Max number of uids and gids */
+#define SQUASHFS_UIDS 256
+#define SQUASHFS_GUIDS 255
+
+struct squashfs_super_block_3 {
+ unsigned int s_magic;
+ unsigned int inodes;
+ unsigned int bytes_used_2;
+ unsigned int uid_start_2;
+ unsigned int guid_start_2;
+ unsigned int inode_table_start_2;
+ unsigned int directory_table_start_2;
+ unsigned int s_major:16;
+ unsigned int s_minor:16;
+ unsigned int block_size_1:16;
+ unsigned int block_log:16;
+ unsigned int flags:8;
+ unsigned int no_uids:8;
+ unsigned int no_guids:8;
+ int mkfs_time /* time of filesystem creation */;
+ squashfs_inode root_inode;
+ unsigned int block_size;
+ unsigned int fragments;
+ unsigned int fragment_table_start_2;
+ long long bytes_used;
+ long long uid_start;
+ long long guid_start;
+ long long inode_table_start;
+ long long directory_table_start;
+ long long fragment_table_start;
+ long long lookup_table_start;
+} __attribute__ ((packed));
+
+struct squashfs_dir_index_3 {
+ unsigned int index;
+ unsigned int start_block;
+ unsigned char size;
+ unsigned char name[0];
+} __attribute__ ((packed));
+
+struct squashfs_base_inode_header_3 {
+ unsigned int inode_type:4;
+ unsigned int mode:12;
+ unsigned int uid:8;
+ unsigned int guid:8;
+ int mtime;
+ unsigned int inode_number;
+} __attribute__ ((packed));
+
+struct squashfs_ipc_inode_header_3 {
+ unsigned int inode_type:4;
+ unsigned int mode:12;
+ unsigned int uid:8;
+ unsigned int guid:8;
+ int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+} __attribute__ ((packed));
+
+struct squashfs_dev_inode_header_3 {
+ unsigned int inode_type:4;
+ unsigned int mode:12;
+ unsigned int uid:8;
+ unsigned int guid:8;
+ int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+ unsigned short rdev;
+} __attribute__ ((packed));
+
+struct squashfs_symlink_inode_header_3 {
+ unsigned int inode_type:4;
+ unsigned int mode:12;
+ unsigned int uid:8;
+ unsigned int guid:8;
+ int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+ unsigned short symlink_size;
+ char symlink[0];
+} __attribute__ ((packed));
+
+struct squashfs_reg_inode_header_3 {
+ unsigned int inode_type:4;
+ unsigned int mode:12;
+ unsigned int uid:8;
+ unsigned int guid:8;
+ int mtime;
+ unsigned int inode_number;
+ squashfs_block start_block;
+ unsigned int fragment;
+ unsigned int offset;
+ unsigned int file_size;
+ unsigned short block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_lreg_inode_header_3 {
+ unsigned int inode_type:4;
+ unsigned int mode:12;
+ unsigned int uid:8;
+ unsigned int guid:8;
+ int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+ squashfs_block start_block;
+ unsigned int fragment;
+ unsigned int offset;
+ long long file_size;
+ unsigned short block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_inode_header_3 {
+ unsigned int inode_type:4;
+ unsigned int mode:12;
+ unsigned int uid:8;
+ unsigned int guid:8;
+ int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+ unsigned int file_size:19;
+ unsigned int offset:13;
+ unsigned int start_block;
+ unsigned int parent_inode;
+} __attribute__ ((packed));
+
+struct squashfs_ldir_inode_header_3 {
+ unsigned int inode_type:4;
+ unsigned int mode:12;
+ unsigned int uid:8;
+ unsigned int guid:8;
+ int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+ unsigned int file_size:27;
+ unsigned int offset:13;
+ unsigned int start_block;
+ unsigned int i_count:16;
+ unsigned int parent_inode;
+ struct squashfs_dir_index_3 index[0];
+} __attribute__ ((packed));
+
+union squashfs_inode_header_3 {
+ struct squashfs_base_inode_header_3 base;
+ struct squashfs_dev_inode_header_3 dev;
+ struct squashfs_symlink_inode_header_3 symlink;
+ struct squashfs_reg_inode_header_3 reg;
+ struct squashfs_lreg_inode_header_3 lreg;
+ struct squashfs_dir_inode_header_3 dir;
+ struct squashfs_ldir_inode_header_3 ldir;
+ struct squashfs_ipc_inode_header_3 ipc;
+};
+
+struct squashfs_dir_entry_3 {
+ unsigned int offset:13;
+ unsigned int type:3;
+ unsigned int size:8;
+ int inode_number:16;
+ char name[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_header_3 {
+ unsigned int count:8;
+ unsigned int start_block;
+ unsigned int inode_number;
+} __attribute__ ((packed));
+
+struct squashfs_fragment_entry_3 {
+ long long start_block;
+ unsigned int size;
+ unsigned int pending;
+} __attribute__ ((packed));
+
+
+typedef struct squashfs_super_block_3 squashfs_super_block_3;
+typedef struct squashfs_dir_index_3 squashfs_dir_index_3;
+typedef struct squashfs_base_inode_header_3 squashfs_base_inode_header_3;
+typedef struct squashfs_ipc_inode_header_3 squashfs_ipc_inode_header_3;
+typedef struct squashfs_dev_inode_header_3 squashfs_dev_inode_header_3;
+typedef struct squashfs_symlink_inode_header_3 squashfs_symlink_inode_header_3;
+typedef struct squashfs_reg_inode_header_3 squashfs_reg_inode_header_3;
+typedef struct squashfs_lreg_inode_header_3 squashfs_lreg_inode_header_3;
+typedef struct squashfs_dir_inode_header_3 squashfs_dir_inode_header_3;
+typedef struct squashfs_ldir_inode_header_3 squashfs_ldir_inode_header_3;
+typedef struct squashfs_dir_entry_3 squashfs_dir_entry_3;
+typedef struct squashfs_dir_header_3 squashfs_dir_header_3;
+typedef struct squashfs_fragment_entry_3 squashfs_fragment_entry_3;
+
+/*
+ * macros to convert each packed bitfield structure from little endian to big
+ * endian and vice versa. These are needed when creating or using a filesystem
+ * on a machine with different byte ordering to the target architecture.
+ *
+ */
+
+#define SQUASHFS_SWAP_START \
+ int bits;\
+ int b_pos;\
+ unsigned long long val;\
+ unsigned char *s;\
+ unsigned char *d;
+
+#define SQUASHFS_SWAP_SUPER_BLOCK_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_super_block_3));\
+ SQUASHFS_SWAP((s)->s_magic, d, 0, 32);\
+ SQUASHFS_SWAP((s)->inodes, d, 32, 32);\
+ SQUASHFS_SWAP((s)->bytes_used_2, d, 64, 32);\
+ SQUASHFS_SWAP((s)->uid_start_2, d, 96, 32);\
+ SQUASHFS_SWAP((s)->guid_start_2, d, 128, 32);\
+ SQUASHFS_SWAP((s)->inode_table_start_2, d, 160, 32);\
+ SQUASHFS_SWAP((s)->directory_table_start_2, d, 192, 32);\
+ SQUASHFS_SWAP((s)->s_major, d, 224, 16);\
+ SQUASHFS_SWAP((s)->s_minor, d, 240, 16);\
+ SQUASHFS_SWAP((s)->block_size_1, d, 256, 16);\
+ SQUASHFS_SWAP((s)->block_log, d, 272, 16);\
+ SQUASHFS_SWAP((s)->flags, d, 288, 8);\
+ SQUASHFS_SWAP((s)->no_uids, d, 296, 8);\
+ SQUASHFS_SWAP((s)->no_guids, d, 304, 8);\
+ SQUASHFS_SWAP((s)->mkfs_time, d, 312, 32);\
+ SQUASHFS_SWAP((s)->root_inode, d, 344, 64);\
+ SQUASHFS_SWAP((s)->block_size, d, 408, 32);\
+ SQUASHFS_SWAP((s)->fragments, d, 440, 32);\
+ SQUASHFS_SWAP((s)->fragment_table_start_2, d, 472, 32);\
+ SQUASHFS_SWAP((s)->bytes_used, d, 504, 64);\
+ SQUASHFS_SWAP((s)->uid_start, d, 568, 64);\
+ SQUASHFS_SWAP((s)->guid_start, d, 632, 64);\
+ SQUASHFS_SWAP((s)->inode_table_start, d, 696, 64);\
+ SQUASHFS_SWAP((s)->directory_table_start, d, 760, 64);\
+ SQUASHFS_SWAP((s)->fragment_table_start, d, 824, 64);\
+ SQUASHFS_SWAP((s)->lookup_table_start, d, 888, 64);\
+}
+
+#define SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, n)\
+ SQUASHFS_MEMSET(s, d, n);\
+ SQUASHFS_SWAP((s)->inode_type, d, 0, 4);\
+ SQUASHFS_SWAP((s)->mode, d, 4, 12);\
+ SQUASHFS_SWAP((s)->uid, d, 16, 8);\
+ SQUASHFS_SWAP((s)->guid, d, 24, 8);\
+ SQUASHFS_SWAP((s)->mtime, d, 32, 32);\
+ SQUASHFS_SWAP((s)->inode_number, d, 64, 32);
+
+#define SQUASHFS_SWAP_BASE_INODE_HEADER_3(s, d, n) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, n)\
+}
+
+#define SQUASHFS_SWAP_IPC_INODE_HEADER_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+ sizeof(struct squashfs_ipc_inode_header_3))\
+ SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+}
+
+#define SQUASHFS_SWAP_DEV_INODE_HEADER_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+ sizeof(struct squashfs_dev_inode_header_3)); \
+ SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+ SQUASHFS_SWAP((s)->rdev, d, 128, 16);\
+}
+
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+ sizeof(struct squashfs_symlink_inode_header_3));\
+ SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+ SQUASHFS_SWAP((s)->symlink_size, d, 128, 16);\
+}
+
+#define SQUASHFS_SWAP_REG_INODE_HEADER_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+ sizeof(struct squashfs_reg_inode_header_3));\
+ SQUASHFS_SWAP((s)->start_block, d, 96, 64);\
+ SQUASHFS_SWAP((s)->fragment, d, 160, 32);\
+ SQUASHFS_SWAP((s)->offset, d, 192, 32);\
+ SQUASHFS_SWAP((s)->file_size, d, 224, 32);\
+}
+
+#define SQUASHFS_SWAP_LREG_INODE_HEADER_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+ sizeof(struct squashfs_lreg_inode_header_3));\
+ SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+ SQUASHFS_SWAP((s)->start_block, d, 128, 64);\
+ SQUASHFS_SWAP((s)->fragment, d, 192, 32);\
+ SQUASHFS_SWAP((s)->offset, d, 224, 32);\
+ SQUASHFS_SWAP((s)->file_size, d, 256, 64);\
+}
+
+#define SQUASHFS_SWAP_DIR_INODE_HEADER_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+ sizeof(struct squashfs_dir_inode_header_3));\
+ SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+ SQUASHFS_SWAP((s)->file_size, d, 128, 19);\
+ SQUASHFS_SWAP((s)->offset, d, 147, 13);\
+ SQUASHFS_SWAP((s)->start_block, d, 160, 32);\
+ SQUASHFS_SWAP((s)->parent_inode, d, 192, 32);\
+}
+
+#define SQUASHFS_SWAP_LDIR_INODE_HEADER_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_3(s, d, \
+ sizeof(struct squashfs_ldir_inode_header_3));\
+ SQUASHFS_SWAP((s)->nlink, d, 96, 32);\
+ SQUASHFS_SWAP((s)->file_size, d, 128, 27);\
+ SQUASHFS_SWAP((s)->offset, d, 155, 13);\
+ SQUASHFS_SWAP((s)->start_block, d, 168, 32);\
+ SQUASHFS_SWAP((s)->i_count, d, 200, 16);\
+ SQUASHFS_SWAP((s)->parent_inode, d, 216, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_INDEX_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_index_3));\
+ SQUASHFS_SWAP((s)->index, d, 0, 32);\
+ SQUASHFS_SWAP((s)->start_block, d, 32, 32);\
+ SQUASHFS_SWAP((s)->size, d, 64, 8);\
+}
+
+#define SQUASHFS_SWAP_DIR_HEADER_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_header_3));\
+ SQUASHFS_SWAP((s)->count, d, 0, 8);\
+ SQUASHFS_SWAP((s)->start_block, d, 8, 32);\
+ SQUASHFS_SWAP((s)->inode_number, d, 40, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_ENTRY_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_entry_3));\
+ SQUASHFS_SWAP((s)->offset, d, 0, 13);\
+ SQUASHFS_SWAP((s)->type, d, 13, 3);\
+ SQUASHFS_SWAP((s)->size, d, 16, 8);\
+ SQUASHFS_SWAP((s)->inode_number, d, 24, 16);\
+}
+
+#define SQUASHFS_SWAP_INODE_T_3(s, d) SQUASHFS_SWAP_LONG_LONGS_3(s, d, 1)
+
+#define SQUASHFS_SWAP_SHORTS_3(s, d, n) {\
+ int entry;\
+ int bit_position;\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, n * 2);\
+ for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+ 16)\
+ SQUASHFS_SWAP(s[entry], d, bit_position, 16);\
+}
+
+#define SQUASHFS_SWAP_INTS_3(s, d, n) {\
+ int entry;\
+ int bit_position;\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, n * 4);\
+ for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+ 32)\
+ SQUASHFS_SWAP(s[entry], d, bit_position, 32);\
+}
+
+#define SQUASHFS_SWAP_LONG_LONGS_3(s, d, n) {\
+ int entry;\
+ int bit_position;\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, n * 8);\
+ for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+ 64)\
+ SQUASHFS_SWAP(s[entry], d, bit_position, 64);\
+}
+
+#define SQUASHFS_SWAP_DATA(s, d, n, bits) {\
+ int entry;\
+ int bit_position;\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, n * bits / 8);\
+ for(entry = 0, bit_position = 0; entry < n; entry++, bit_position += \
+ bits)\
+ SQUASHFS_SWAP(s[entry], d, bit_position, bits);\
+}
+
+#define SQUASHFS_SWAP_FRAGMENT_INDEXES_3(s, d, n) SQUASHFS_SWAP_LONG_LONGS_3(s, d, n)
+#define SQUASHFS_SWAP_LOOKUP_BLOCKS_3(s, d, n) SQUASHFS_SWAP_LONG_LONGS_3(s, d, n)
+
+#define SQUASHFS_SWAP_FRAGMENT_ENTRY_3(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_fragment_entry_3));\
+ SQUASHFS_SWAP((s)->start_block, d, 0, 64);\
+ SQUASHFS_SWAP((s)->size, d, 64, 32);\
+}
+
+/* fragment and fragment table defines */
+#define SQUASHFS_FRAGMENT_BYTES_3(A) ((A) * sizeof(struct squashfs_fragment_entry_3))
+
+#define SQUASHFS_FRAGMENT_INDEX_3(A) (SQUASHFS_FRAGMENT_BYTES_3(A) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_OFFSET_3(A) (SQUASHFS_FRAGMENT_BYTES_3(A) % \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEXES_3(A) ((SQUASHFS_FRAGMENT_BYTES_3(A) + \
+ SQUASHFS_METADATA_SIZE - 1) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_BYTES_3(A) (SQUASHFS_FRAGMENT_INDEXES_3(A) *\
+ sizeof(long long))
+
+/* inode lookup table defines */
+#define SQUASHFS_LOOKUP_BYTES_3(A) ((A) * sizeof(squashfs_inode))
+
+#define SQUASHFS_LOOKUP_BLOCK_3(A) (SQUASHFS_LOOKUP_BYTES_3(A) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCK_OFFSET_3(A) (SQUASHFS_LOOKUP_BYTES_3(A) % \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCKS_3(A) ((SQUASHFS_LOOKUP_BYTES_3(A) + \
+ SQUASHFS_METADATA_SIZE - 1) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCK_BYTES_3(A) (SQUASHFS_LOOKUP_BLOCKS(A) *\
+ sizeof(long long))
+
+/*
+ * definitions for structures on disk - layout 1.x
+ */
+#define SQUASHFS_TYPES 5
+#define SQUASHFS_IPC_TYPE 0
+
+struct squashfs_base_inode_header_1 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:4; /* index into uid table */
+ unsigned int guid:4; /* index into guid table */
+} __attribute__ ((packed));
+
+struct squashfs_ipc_inode_header_1 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:4; /* index into uid table */
+ unsigned int guid:4; /* index into guid table */
+ unsigned int type:4;
+ unsigned int offset:4;
+} __attribute__ ((packed));
+
+struct squashfs_dev_inode_header_1 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:4; /* index into uid table */
+ unsigned int guid:4; /* index into guid table */
+ unsigned short rdev;
+} __attribute__ ((packed));
+
+struct squashfs_symlink_inode_header_1 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:4; /* index into uid table */
+ unsigned int guid:4; /* index into guid table */
+ unsigned short symlink_size;
+ char symlink[0];
+} __attribute__ ((packed));
+
+struct squashfs_reg_inode_header_1 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:4; /* index into uid table */
+ unsigned int guid:4; /* index into guid table */
+ int mtime;
+ unsigned int start_block;
+ unsigned int file_size:32;
+ unsigned short block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_inode_header_1 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:4; /* index into uid table */
+ unsigned int guid:4; /* index into guid table */
+ unsigned int file_size:19;
+ unsigned int offset:13;
+ int mtime;
+ unsigned int start_block:24;
+} __attribute__ ((packed));
+
+union squashfs_inode_header_1 {
+ struct squashfs_base_inode_header_1 base;
+ struct squashfs_dev_inode_header_1 dev;
+ struct squashfs_symlink_inode_header_1 symlink;
+ struct squashfs_reg_inode_header_1 reg;
+ struct squashfs_dir_inode_header_1 dir;
+ struct squashfs_ipc_inode_header_1 ipc;
+};
+
+typedef struct squashfs_dir_index_1 squashfs_dir_index_1;
+typedef struct squashfs_base_inode_header_1 squashfs_base_inode_header_1;
+typedef struct squashfs_ipc_inode_header_1 squashfs_ipc_inode_header_1;
+typedef struct squashfs_dev_inode_header_1 squashfs_dev_inode_header_1;
+typedef struct squashfs_symlink_inode_header_1 squashfs_symlink_inode_header_1;
+typedef struct squashfs_reg_inode_header_1 squashfs_reg_inode_header_1;
+typedef struct squashfs_dir_inode_header_1 squashfs_dir_inode_header_1;
+
+#define SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, n) \
+ SQUASHFS_MEMSET(s, d, n);\
+ SQUASHFS_SWAP((s)->inode_type, d, 0, 4);\
+ SQUASHFS_SWAP((s)->mode, d, 4, 12);\
+ SQUASHFS_SWAP((s)->uid, d, 16, 4);\
+ SQUASHFS_SWAP((s)->guid, d, 20, 4);
+
+#define SQUASHFS_SWAP_BASE_INODE_HEADER_1(s, d, n) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, n)\
+}
+
+#define SQUASHFS_SWAP_IPC_INODE_HEADER_1(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+ sizeof(struct squashfs_ipc_inode_header_1));\
+ SQUASHFS_SWAP((s)->type, d, 24, 4);\
+ SQUASHFS_SWAP((s)->offset, d, 28, 4);\
+}
+
+#define SQUASHFS_SWAP_DEV_INODE_HEADER_1(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+ sizeof(struct squashfs_dev_inode_header_1));\
+ SQUASHFS_SWAP((s)->rdev, d, 24, 16);\
+}
+
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER_1(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+ sizeof(struct squashfs_symlink_inode_header_1));\
+ SQUASHFS_SWAP((s)->symlink_size, d, 24, 16);\
+}
+
+#define SQUASHFS_SWAP_REG_INODE_HEADER_1(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+ sizeof(struct squashfs_reg_inode_header_1));\
+ SQUASHFS_SWAP((s)->mtime, d, 24, 32);\
+ SQUASHFS_SWAP((s)->start_block, d, 56, 32);\
+ SQUASHFS_SWAP((s)->file_size, d, 88, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_INODE_HEADER_1(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_1(s, d, \
+ sizeof(struct squashfs_dir_inode_header_1));\
+ SQUASHFS_SWAP((s)->file_size, d, 24, 19);\
+ SQUASHFS_SWAP((s)->offset, d, 43, 13);\
+ SQUASHFS_SWAP((s)->mtime, d, 56, 32);\
+ SQUASHFS_SWAP((s)->start_block, d, 88, 24);\
+}
+
+/*
+ * definitions for structures on disk - layout 2.x
+ */
+struct squashfs_dir_index_2 {
+ unsigned int index:27;
+ unsigned int start_block:29;
+ unsigned char size;
+ unsigned char name[0];
+} __attribute__ ((packed));
+
+struct squashfs_base_inode_header_2 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:8; /* index into uid table */
+ unsigned int guid:8; /* index into guid table */
+} __attribute__ ((packed));
+
+struct squashfs_ipc_inode_header_2 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:8; /* index into uid table */
+ unsigned int guid:8; /* index into guid table */
+} __attribute__ ((packed));
+
+struct squashfs_dev_inode_header_2 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:8; /* index into uid table */
+ unsigned int guid:8; /* index into guid table */
+ unsigned short rdev;
+} __attribute__ ((packed));
+
+struct squashfs_symlink_inode_header_2 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:8; /* index into uid table */
+ unsigned int guid:8; /* index into guid table */
+ unsigned short symlink_size;
+ char symlink[0];
+} __attribute__ ((packed));
+
+struct squashfs_reg_inode_header_2 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:8; /* index into uid table */
+ unsigned int guid:8; /* index into guid table */
+ int mtime;
+ unsigned int start_block;
+ unsigned int fragment;
+ unsigned int offset;
+ unsigned int file_size:32;
+ unsigned short block_list[0];
+} __attribute__ ((packed));
+
+struct squashfs_dir_inode_header_2 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:8; /* index into uid table */
+ unsigned int guid:8; /* index into guid table */
+ unsigned int file_size:19;
+ unsigned int offset:13;
+ int mtime;
+ unsigned int start_block:24;
+} __attribute__ ((packed));
+
+struct squashfs_ldir_inode_header_2 {
+ unsigned int inode_type:4;
+ unsigned int mode:12; /* protection */
+ unsigned int uid:8; /* index into uid table */
+ unsigned int guid:8; /* index into guid table */
+ unsigned int file_size:27;
+ unsigned int offset:13;
+ int mtime;
+ unsigned int start_block:24;
+ unsigned int i_count:16;
+ struct squashfs_dir_index_2 index[0];
+} __attribute__ ((packed));
+
+union squashfs_inode_header_2 {
+ struct squashfs_base_inode_header_2 base;
+ struct squashfs_dev_inode_header_2 dev;
+ struct squashfs_symlink_inode_header_2 symlink;
+ struct squashfs_reg_inode_header_2 reg;
+ struct squashfs_dir_inode_header_2 dir;
+ struct squashfs_ldir_inode_header_2 ldir;
+ struct squashfs_ipc_inode_header_2 ipc;
+};
+
+struct squashfs_dir_header_2 {
+ unsigned int count:8;
+ unsigned int start_block:24;
+} __attribute__ ((packed));
+
+struct squashfs_dir_entry_2 {
+ unsigned int offset:13;
+ unsigned int type:3;
+ unsigned int size:8;
+ char name[0];
+} __attribute__ ((packed));
+
+struct squashfs_fragment_entry_2 {
+ unsigned int start_block;
+ unsigned int size;
+} __attribute__ ((packed));
+
+typedef struct squashfs_dir_index_2 squashfs_dir_index_2;
+typedef struct squashfs_base_inode_header_2 squashfs_base_inode_header_2;
+typedef struct squashfs_ipc_inode_header_2 squashfs_ipc_inode_header_2;
+typedef struct squashfs_dev_inode_header_2 squashfs_dev_inode_header_2;
+typedef struct squashfs_symlink_inode_header_2 squashfs_symlink_inode_header_2;
+typedef struct squashfs_reg_inode_header_2 squashfs_reg_inode_header_2;
+typedef struct squashfs_lreg_inode_header_2 squashfs_lreg_inode_header_2;
+typedef struct squashfs_dir_inode_header_2 squashfs_dir_inode_header_2;
+typedef struct squashfs_ldir_inode_header_2 squashfs_ldir_inode_header_2;
+typedef struct squashfs_dir_entry_2 squashfs_dir_entry_2;
+typedef struct squashfs_dir_header_2 squashfs_dir_header_2;
+typedef struct squashfs_fragment_entry_2 squashfs_fragment_entry_2;
+
+#define SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, n)\
+ SQUASHFS_MEMSET(s, d, n);\
+ SQUASHFS_SWAP((s)->inode_type, d, 0, 4);\
+ SQUASHFS_SWAP((s)->mode, d, 4, 12);\
+ SQUASHFS_SWAP((s)->uid, d, 16, 8);\
+ SQUASHFS_SWAP((s)->guid, d, 24, 8);\
+
+#define SQUASHFS_SWAP_BASE_INODE_HEADER_2(s, d, n) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, n)\
+}
+
+#define SQUASHFS_SWAP_IPC_INODE_HEADER_2(s, d) \
+ SQUASHFS_SWAP_BASE_INODE_HEADER_2(s, d, sizeof(struct squashfs_ipc_inode_header_2))
+
+#define SQUASHFS_SWAP_DEV_INODE_HEADER_2(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+ sizeof(struct squashfs_dev_inode_header_2)); \
+ SQUASHFS_SWAP((s)->rdev, d, 32, 16);\
+}
+
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER_2(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+ sizeof(struct squashfs_symlink_inode_header_2));\
+ SQUASHFS_SWAP((s)->symlink_size, d, 32, 16);\
+}
+
+#define SQUASHFS_SWAP_REG_INODE_HEADER_2(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+ sizeof(struct squashfs_reg_inode_header_2));\
+ SQUASHFS_SWAP((s)->mtime, d, 32, 32);\
+ SQUASHFS_SWAP((s)->start_block, d, 64, 32);\
+ SQUASHFS_SWAP((s)->fragment, d, 96, 32);\
+ SQUASHFS_SWAP((s)->offset, d, 128, 32);\
+ SQUASHFS_SWAP((s)->file_size, d, 160, 32);\
+}
+
+#define SQUASHFS_SWAP_DIR_INODE_HEADER_2(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+ sizeof(struct squashfs_dir_inode_header_2));\
+ SQUASHFS_SWAP((s)->file_size, d, 32, 19);\
+ SQUASHFS_SWAP((s)->offset, d, 51, 13);\
+ SQUASHFS_SWAP((s)->mtime, d, 64, 32);\
+ SQUASHFS_SWAP((s)->start_block, d, 96, 24);\
+}
+
+#define SQUASHFS_SWAP_LDIR_INODE_HEADER_2(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_SWAP_BASE_INODE_CORE_2(s, d, \
+ sizeof(struct squashfs_ldir_inode_header_2));\
+ SQUASHFS_SWAP((s)->file_size, d, 32, 27);\
+ SQUASHFS_SWAP((s)->offset, d, 59, 13);\
+ SQUASHFS_SWAP((s)->mtime, d, 72, 32);\
+ SQUASHFS_SWAP((s)->start_block, d, 104, 24);\
+ SQUASHFS_SWAP((s)->i_count, d, 128, 16);\
+}
+
+#define SQUASHFS_SWAP_DIR_INDEX_2(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_index_2));\
+ SQUASHFS_SWAP((s)->index, d, 0, 27);\
+ SQUASHFS_SWAP((s)->start_block, d, 27, 29);\
+ SQUASHFS_SWAP((s)->size, d, 56, 8);\
+}
+#define SQUASHFS_SWAP_DIR_HEADER_2(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_header_2));\
+ SQUASHFS_SWAP((s)->count, d, 0, 8);\
+ SQUASHFS_SWAP((s)->start_block, d, 8, 24);\
+}
+
+#define SQUASHFS_SWAP_DIR_ENTRY_2(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_dir_entry_2));\
+ SQUASHFS_SWAP((s)->offset, d, 0, 13);\
+ SQUASHFS_SWAP((s)->type, d, 13, 3);\
+ SQUASHFS_SWAP((s)->size, d, 16, 8);\
+}
+
+#define SQUASHFS_SWAP_FRAGMENT_ENTRY_2(s, d) {\
+ SQUASHFS_SWAP_START\
+ SQUASHFS_MEMSET(s, d, sizeof(struct squashfs_fragment_entry_2));\
+ SQUASHFS_SWAP((s)->start_block, d, 0, 32);\
+ SQUASHFS_SWAP((s)->size, d, 32, 32);\
+}
+
+#define SQUASHFS_SWAP_FRAGMENT_INDEXES_2(s, d, n) SQUASHFS_SWAP_INTS_3(s, d, n)
+
+/* fragment and fragment table defines */
+#define SQUASHFS_FRAGMENT_BYTES_2(A) ((A) * sizeof(struct squashfs_fragment_entry_2))
+
+#define SQUASHFS_FRAGMENT_INDEX_2(A) (SQUASHFS_FRAGMENT_BYTES_2(A) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_OFFSET_2(A) (SQUASHFS_FRAGMENT_BYTES_2(A) % \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEXES_2(A) ((SQUASHFS_FRAGMENT_BYTES_2(A) + \
+ SQUASHFS_METADATA_SIZE - 1) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_BYTES_2(A) (SQUASHFS_FRAGMENT_INDEXES_2(A) *\
+ sizeof(int))
+/*
+ * macros used to swap each structure entry, taking into account
+ * bitfields and different bitfield placing conventions on differing architectures
+ */
+#if __BYTE_ORDER == __BIG_ENDIAN
+ /* convert from little endian to big endian */
+#define SQUASHFS_SWAP(value, p, pos, tbits) _SQUASHFS_SWAP(value, p, pos, tbits, b_pos)
+#else
+ /* convert from big endian to little endian */
+#define SQUASHFS_SWAP(value, p, pos, tbits) _SQUASHFS_SWAP(value, p, pos, tbits, 64 - tbits - b_pos)
+#endif
+
+#define _SQUASHFS_SWAP(value, p, pos, tbits, SHIFT) {\
+ b_pos = pos % 8;\
+ val = 0;\
+ s = (unsigned char *)p + (pos / 8);\
+ d = ((unsigned char *) &val) + 7;\
+ for(bits = 0; bits < (tbits + b_pos); bits += 8) \
+ *d-- = *s++;\
+ value = (val >> (SHIFT));\
+}
+#define SQUASHFS_MEMSET(s, d, n) memset(s, 0, n);
+#endif
diff --git a/squashfs-tools/squashfs_fs.h b/squashfs-tools/squashfs_fs.h
new file mode 100644
index 0000000..659f17c
--- /dev/null
+++ b/squashfs-tools/squashfs_fs.h
@@ -0,0 +1,502 @@
+#ifndef SQUASHFS_FS
+#define SQUASHFS_FS
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012,
+ * 2013, 2014, 2017, 2019, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * squashfs_fs.h
+ */
+
+#define SQUASHFS_CACHED_FRAGMENTS CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE
+#define SQUASHFS_MAJOR 4
+#define SQUASHFS_MINOR 0
+#define SQUASHFS_MAGIC 0x73717368
+#define SQUASHFS_MAGIC_SWAP 0x68737173
+#define SQUASHFS_START 0
+
+/* size of metadata (inode and directory) blocks */
+#define SQUASHFS_METADATA_SIZE 8192
+#define SQUASHFS_METADATA_LOG 13
+
+/* default size of data blocks */
+#define SQUASHFS_FILE_SIZE 131072
+
+#define SQUASHFS_FILE_MAX_SIZE 1048576
+#define SQUASHFS_FILE_MAX_LOG 20
+
+/* Max number of uids and gids */
+#define SQUASHFS_IDS 65536
+
+/* Max length of filename (not 255) */
+#define SQUASHFS_NAME_LEN 256
+
+/* Max value for directory header count */
+#define SQUASHFS_DIR_COUNT 256
+
+/* Max length of a symbolic ink */
+#define SQUASHFS_SYMLINK_MAX 65535
+
+#define SQUASHFS_INVALID ((long long) 0xffffffffffff)
+#define SQUASHFS_INVALID_FRAG ((unsigned int) 0xffffffff)
+#define SQUASHFS_INVALID_XATTR ((unsigned int) 0xffffffff)
+#define SQUASHFS_INVALID_BLK ((long long) -1)
+#define SQUASHFS_USED_BLK ((long long) -2)
+
+/* Filesystem flags */
+#define SQUASHFS_NOI 0
+#define SQUASHFS_NOD 1
+#define SQUASHFS_CHECK 2
+#define SQUASHFS_NOF 3
+#define SQUASHFS_NO_FRAG 4
+#define SQUASHFS_ALWAYS_FRAG 5
+#define SQUASHFS_DUPLICATE 6
+#define SQUASHFS_EXPORT 7
+#define SQUASHFS_NOX 8
+#define SQUASHFS_NO_XATTR 9
+#define SQUASHFS_COMP_OPT 10
+#define SQUASHFS_NOID 11
+
+#define SQUASHFS_BIT(flag, bit) ((flag >> bit) & 1)
+
+#define SQUASHFS_UNCOMPRESSED_INODES(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_NOI)
+
+#define SQUASHFS_UNCOMPRESSED_DATA(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_NOD)
+
+#define SQUASHFS_UNCOMPRESSED_FRAGMENTS(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_NOF)
+
+#define SQUASHFS_NO_FRAGMENTS(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_NO_FRAG)
+
+#define SQUASHFS_ALWAYS_FRAGMENTS(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_ALWAYS_FRAG)
+
+#define SQUASHFS_DUPLICATES(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_DUPLICATE)
+
+#define SQUASHFS_EXPORTABLE(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_EXPORT)
+
+#define SQUASHFS_UNCOMPRESSED_XATTRS(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_NOX)
+
+#define SQUASHFS_NO_XATTRS(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_NO_XATTR)
+
+#define SQUASHFS_COMP_OPTS(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_COMP_OPT)
+
+#define SQUASHFS_UNCOMPRESSED_IDS(flags) SQUASHFS_BIT(flags, \
+ SQUASHFS_NOID)
+
+#define SQUASHFS_MKFLAGS(noi, nod, nof, nox, noid, no_frag, always_frag, \
+ duplicate_checking, exportable, no_xattr, comp_opt) (noi | \
+ (nod << 1) | (nof << 3) | (no_frag << 4) | \
+ (always_frag << 5) | (duplicate_checking << 6) | \
+ (exportable << 7) | (nox << 8) | (no_xattr << 9) | \
+ (comp_opt << 10) | (noid << 11))
+
+/* Max number of types and file types */
+#define SQUASHFS_DIR_TYPE 1
+#define SQUASHFS_FILE_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
+
+/* 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
+
+/* Flag whether block is compressed or uncompressed, bit is set if block is
+ * uncompressed */
+#define SQUASHFS_COMPRESSED_BIT (1 << 15)
+
+#define SQUASHFS_COMPRESSED_SIZE(B) (((B) & ~SQUASHFS_COMPRESSED_BIT) ? \
+ (B) & ~SQUASHFS_COMPRESSED_BIT : SQUASHFS_COMPRESSED_BIT)
+
+#define SQUASHFS_COMPRESSED(B) (!((B) & SQUASHFS_COMPRESSED_BIT))
+
+#define SQUASHFS_COMPRESSED_BIT_BLOCK (1 << 24)
+
+#define SQUASHFS_COMPRESSED_SIZE_BLOCK(B) ((B) & \
+ ~SQUASHFS_COMPRESSED_BIT_BLOCK)
+
+#define SQUASHFS_COMPRESSED_BLOCK(B) (!((B) & SQUASHFS_COMPRESSED_BIT_BLOCK))
+
+/*
+ * Inode number ops. Inodes consist of a compressed block number, and an
+ * uncompressed offset within that block
+ */
+#define SQUASHFS_INODE_BLK(a) ((unsigned int) ((a) >> 16))
+
+#define SQUASHFS_INODE_OFFSET(a) ((unsigned int) ((a) & 0xffff))
+
+#define SQUASHFS_MKINODE(A, B) ((squashfs_inode)(((squashfs_inode) (A)\
+ << 16) + (B)))
+
+/* Compute 32 bit VFS inode number from squashfs inode number */
+#define SQUASHFS_MK_VFS_INODE(a, b) ((unsigned int) (((a) << 8) + \
+ ((b) >> 2) + 1))
+
+/* Translate between VFS mode and squashfs mode */
+#define SQUASHFS_MODE(a) ((a) & 0xfff)
+
+/* fragment and fragment table defines */
+#define SQUASHFS_FRAGMENT_BYTES(A) ((A) * \
+ sizeof(struct squashfs_fragment_entry))
+
+#define SQUASHFS_FRAGMENT_INDEX(A) (SQUASHFS_FRAGMENT_BYTES(A) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_OFFSET(A) (SQUASHFS_FRAGMENT_BYTES(A) % \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEXES(A) ((SQUASHFS_FRAGMENT_BYTES(A) + \
+ SQUASHFS_METADATA_SIZE - 1) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_FRAGMENT_INDEX_BYTES(A) (SQUASHFS_FRAGMENT_INDEXES(A) *\
+ sizeof(long long))
+
+/* inode lookup table defines */
+#define SQUASHFS_LOOKUP_BYTES(A) ((A) * sizeof(squashfs_inode))
+
+#define SQUASHFS_LOOKUP_BLOCK(A) (SQUASHFS_LOOKUP_BYTES(A) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCK_OFFSET(A) (SQUASHFS_LOOKUP_BYTES(A) % \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCKS(A) ((SQUASHFS_LOOKUP_BYTES(A) + \
+ SQUASHFS_METADATA_SIZE - 1) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_LOOKUP_BLOCK_BYTES(A) (SQUASHFS_LOOKUP_BLOCKS(A) *\
+ sizeof(long long))
+
+/* uid lookup table defines */
+#define SQUASHFS_ID_BYTES(A) ((A) * sizeof(unsigned int))
+
+#define SQUASHFS_ID_BLOCK(A) (SQUASHFS_ID_BYTES(A) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCK_OFFSET(A) (SQUASHFS_ID_BYTES(A) % \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCKS(A) ((SQUASHFS_ID_BYTES(A) + \
+ SQUASHFS_METADATA_SIZE - 1) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_ID_BLOCK_BYTES(A) (SQUASHFS_ID_BLOCKS(A) *\
+ sizeof(long long))
+
+/* xattr id lookup table defines */
+#define SQUASHFS_XATTR_BYTES(A) (((long long) (A)) * sizeof(struct squashfs_xattr_id))
+
+#define SQUASHFS_XATTR_BLOCK(A) (SQUASHFS_XATTR_BYTES(A) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_XATTR_BLOCK_OFFSET(A) (SQUASHFS_XATTR_BYTES(A) % \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_XATTR_BLOCKS(A) ((SQUASHFS_XATTR_BYTES(A) + \
+ SQUASHFS_METADATA_SIZE - 1) / \
+ SQUASHFS_METADATA_SIZE)
+
+#define SQUASHFS_XATTR_BLOCK_BYTES(A) (SQUASHFS_XATTR_BLOCKS(A) *\
+ sizeof(long long))
+
+#define SQUASHFS_XATTR_BLK(A) ((unsigned int) ((A) >> 16))
+
+#define SQUASHFS_XATTR_OFFSET(A) ((unsigned int) ((A) & 0xffff))
+
+/* cached data constants for filesystem */
+#define SQUASHFS_CACHED_BLKS 8
+
+#define SQUASHFS_MAX_FILE_SIZE_LOG 64
+
+#define SQUASHFS_MAX_FILE_SIZE ((long long) 1 << \
+ (SQUASHFS_MAX_FILE_SIZE_LOG - 2))
+
+#define SQUASHFS_MARKER_BYTE 0xff
+
+/* meta index cache */
+#define SQUASHFS_META_INDEXES (SQUASHFS_METADATA_SIZE / sizeof(unsigned int))
+#define SQUASHFS_META_ENTRIES 31
+#define SQUASHFS_META_NUMBER 8
+#define SQUASHFS_SLOTS 4
+
+struct meta_entry {
+ long long data_block;
+ unsigned int index_block;
+ unsigned short offset;
+ unsigned short pad;
+};
+
+struct meta_index {
+ unsigned int inode_number;
+ unsigned int offset;
+ unsigned short entries;
+ unsigned short skip;
+ unsigned short locked;
+ unsigned short pad;
+ struct meta_entry meta_entry[SQUASHFS_META_ENTRIES];
+};
+
+
+/*
+ * definitions for structures on disk
+ */
+
+typedef long long squashfs_block;
+typedef long long squashfs_inode;
+
+#define ZLIB_COMPRESSION 1
+#define LZMA_COMPRESSION 2
+#define LZO_COMPRESSION 3
+#define XZ_COMPRESSION 4
+#define LZ4_COMPRESSION 5
+#define ZSTD_COMPRESSION 6
+
+struct squashfs_super_block {
+ unsigned int s_magic;
+ unsigned int inodes;
+ unsigned int mkfs_time /* time of filesystem creation */;
+ unsigned int block_size;
+ unsigned int fragments;
+ unsigned short compression;
+ unsigned short block_log;
+ unsigned short flags;
+ unsigned short no_ids;
+ unsigned short s_major;
+ unsigned short s_minor;
+ squashfs_inode root_inode;
+ long long bytes_used;
+ long long id_table_start;
+ long long xattr_id_table_start;
+ long long inode_table_start;
+ long long directory_table_start;
+ long long fragment_table_start;
+ long long lookup_table_start;
+};
+
+struct squashfs_dir_index {
+ unsigned int index;
+ unsigned int start_block;
+ unsigned int size;
+ unsigned char name[0];
+};
+
+struct squashfs_base_inode_header {
+ unsigned short inode_type;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short guid;
+ unsigned int mtime;
+ unsigned int inode_number;
+};
+
+struct squashfs_ipc_inode_header {
+ unsigned short inode_type;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short guid;
+ unsigned int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+};
+
+struct squashfs_lipc_inode_header {
+ unsigned short inode_type;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short guid;
+ unsigned int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+ unsigned int xattr;
+};
+
+struct squashfs_dev_inode_header {
+ unsigned short inode_type;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short guid;
+ unsigned int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+ unsigned int rdev;
+};
+
+struct squashfs_ldev_inode_header {
+ unsigned short inode_type;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short guid;
+ unsigned int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+ unsigned int rdev;
+ unsigned int xattr;
+};
+
+struct squashfs_symlink_inode_header {
+ unsigned short inode_type;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short guid;
+ unsigned int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+ unsigned int symlink_size;
+ char symlink[0];
+};
+
+struct squashfs_reg_inode_header {
+ unsigned short inode_type;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short guid;
+ unsigned int mtime;
+ unsigned int inode_number;
+ unsigned int start_block;
+ unsigned int fragment;
+ unsigned int offset;
+ unsigned int file_size;
+ unsigned int block_list[0];
+};
+
+struct squashfs_lreg_inode_header {
+ unsigned short inode_type;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short guid;
+ unsigned int mtime;
+ unsigned int inode_number;
+ squashfs_block start_block;
+ long long file_size;
+ long long sparse;
+ unsigned int nlink;
+ unsigned int fragment;
+ unsigned int offset;
+ unsigned int xattr;
+ unsigned int block_list[0];
+};
+
+struct squashfs_dir_inode_header {
+ unsigned short inode_type;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short guid;
+ unsigned int mtime;
+ unsigned int inode_number;
+ unsigned int start_block;
+ unsigned int nlink;
+ unsigned short file_size;
+ unsigned short offset;
+ unsigned int parent_inode;
+};
+
+struct squashfs_ldir_inode_header {
+ unsigned short inode_type;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short guid;
+ unsigned int mtime;
+ unsigned int inode_number;
+ unsigned int nlink;
+ unsigned int file_size;
+ unsigned int start_block;
+ unsigned int parent_inode;
+ unsigned short i_count;
+ unsigned short offset;
+ unsigned int xattr;
+ struct squashfs_dir_index index[0];
+};
+
+union squashfs_inode_header {
+ struct squashfs_base_inode_header base;
+ struct squashfs_dev_inode_header dev;
+ struct squashfs_ldev_inode_header ldev;
+ struct squashfs_symlink_inode_header symlink;
+ struct squashfs_reg_inode_header reg;
+ struct squashfs_lreg_inode_header lreg;
+ struct squashfs_dir_inode_header dir;
+ struct squashfs_ldir_inode_header ldir;
+ struct squashfs_ipc_inode_header ipc;
+ struct squashfs_lipc_inode_header lipc;
+};
+
+struct squashfs_dir_entry {
+ unsigned short offset;
+ short inode_number;
+ unsigned short type;
+ unsigned short size;
+ char name[0];
+};
+
+struct squashfs_dir_header {
+ unsigned int count;
+ unsigned int start_block;
+ unsigned int inode_number;
+};
+
+struct squashfs_fragment_entry {
+ long long start_block;
+ unsigned int size;
+ unsigned int unused;
+};
+
+struct squashfs_xattr_entry {
+ unsigned short type;
+ unsigned short size;
+};
+
+struct squashfs_xattr_val {
+ unsigned int vsize;
+};
+
+struct squashfs_xattr_id {
+ long long xattr;
+ unsigned int count;
+ unsigned int size;
+};
+
+struct squashfs_xattr_table {
+ long long xattr_table_start;
+ unsigned int xattr_ids;
+ unsigned int unused;
+};
+
+#endif
diff --git a/squashfs-tools/squashfs_swap.h b/squashfs-tools/squashfs_swap.h
new file mode 100644
index 0000000..edaf029
--- /dev/null
+++ b/squashfs-tools/squashfs_swap.h
@@ -0,0 +1,425 @@
+#ifndef SQUASHFS_SWAP_H
+#define SQUASHFS_SWAP_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2008, 2009, 2010, 2013, 2019, 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * squashfs_swap.h
+ */
+
+/*
+ * macros to convert each stucture from big endian to little endian
+ */
+
+#include "endian_compat.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#include <stddef.h>
+extern void swap_le16(void *, void *);
+extern void swap_le32(void *, void *);
+extern void swap_le64(void *, void *);
+extern void swap_le16_num(void *, void *, int);
+extern void swap_le32_num(void *, void *, int);
+extern void swap_le64_num(void *, void *, int);
+extern unsigned short inswap_le16(unsigned short);
+extern unsigned int inswap_le32(unsigned int);
+extern long long inswap_le64(long long);
+extern void inswap_le16_num(unsigned short *, int);
+extern void inswap_le32_num(unsigned int *, int);
+extern void inswap_le64_num(long long *, int);
+
+#define _SQUASHFS_SWAP_SUPER_BLOCK(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(32, s, d, s_magic, struct squashfs_super_block);\
+ SWAP_FUNC(32, s, d, inodes, struct squashfs_super_block);\
+ SWAP_FUNC(32, s, d, mkfs_time, struct squashfs_super_block);\
+ SWAP_FUNC(32, s, d, block_size, struct squashfs_super_block);\
+ SWAP_FUNC(32, s, d, fragments, struct squashfs_super_block);\
+ SWAP_FUNC(16, s, d, compression, struct squashfs_super_block);\
+ SWAP_FUNC(16, s, d, block_log, struct squashfs_super_block);\
+ SWAP_FUNC(16, s, d, flags, struct squashfs_super_block);\
+ SWAP_FUNC(16, s, d, no_ids, struct squashfs_super_block);\
+ SWAP_FUNC(16, s, d, s_major, struct squashfs_super_block);\
+ SWAP_FUNC(16, s, d, s_minor, struct squashfs_super_block);\
+ SWAP_FUNC(64, s, d, root_inode, struct squashfs_super_block);\
+ SWAP_FUNC(64, s, d, bytes_used, struct squashfs_super_block);\
+ SWAP_FUNC(64, s, d, id_table_start, struct squashfs_super_block);\
+ SWAP_FUNC(64, s, d, xattr_id_table_start, struct squashfs_super_block);\
+ SWAP_FUNC(64, s, d, inode_table_start, struct squashfs_super_block);\
+ SWAP_FUNC(64, s, d, directory_table_start, struct squashfs_super_block);\
+ SWAP_FUNC(64, s, d, fragment_table_start, struct squashfs_super_block);\
+ SWAP_FUNC(64, s, d, lookup_table_start, struct squashfs_super_block);\
+}
+
+#define _SQUASHFS_SWAP_DIR_INDEX(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(32, s, d, index, struct squashfs_dir_index);\
+ SWAP_FUNC(32, s, d, start_block, struct squashfs_dir_index);\
+ SWAP_FUNC(32, s, d, size, struct squashfs_dir_index);\
+}
+
+#define _SQUASHFS_SWAP_BASE_INODE_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, inode_type, struct squashfs_base_inode_header);\
+ SWAP_FUNC(16, s, d, mode, struct squashfs_base_inode_header);\
+ SWAP_FUNC(16, s, d, uid, struct squashfs_base_inode_header);\
+ SWAP_FUNC(16, s, d, guid, struct squashfs_base_inode_header);\
+ SWAP_FUNC(32, s, d, mtime, struct squashfs_base_inode_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_base_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_IPC_INODE_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, inode_type, struct squashfs_ipc_inode_header);\
+ SWAP_FUNC(16, s, d, mode, struct squashfs_ipc_inode_header);\
+ SWAP_FUNC(16, s, d, uid, struct squashfs_ipc_inode_header);\
+ SWAP_FUNC(16, s, d, guid, struct squashfs_ipc_inode_header);\
+ SWAP_FUNC(32, s, d, mtime, struct squashfs_ipc_inode_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_ipc_inode_header);\
+ SWAP_FUNC(32, s, d, nlink, struct squashfs_ipc_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_LIPC_INODE_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, inode_type, struct squashfs_lipc_inode_header);\
+ SWAP_FUNC(16, s, d, mode, struct squashfs_lipc_inode_header);\
+ SWAP_FUNC(16, s, d, uid, struct squashfs_lipc_inode_header);\
+ SWAP_FUNC(16, s, d, guid, struct squashfs_lipc_inode_header);\
+ SWAP_FUNC(32, s, d, mtime, struct squashfs_lipc_inode_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_lipc_inode_header);\
+ SWAP_FUNC(32, s, d, nlink, struct squashfs_lipc_inode_header);\
+ SWAP_FUNC(32, s, d, xattr, struct squashfs_lipc_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_DEV_INODE_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, inode_type, struct squashfs_dev_inode_header);\
+ SWAP_FUNC(16, s, d, mode, struct squashfs_dev_inode_header);\
+ SWAP_FUNC(16, s, d, uid, struct squashfs_dev_inode_header);\
+ SWAP_FUNC(16, s, d, guid, struct squashfs_dev_inode_header);\
+ SWAP_FUNC(32, s, d, mtime, struct squashfs_dev_inode_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_dev_inode_header);\
+ SWAP_FUNC(32, s, d, nlink, struct squashfs_dev_inode_header);\
+ SWAP_FUNC(32, s, d, rdev, struct squashfs_dev_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_LDEV_INODE_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, inode_type, struct squashfs_ldev_inode_header);\
+ SWAP_FUNC(16, s, d, mode, struct squashfs_ldev_inode_header);\
+ SWAP_FUNC(16, s, d, uid, struct squashfs_ldev_inode_header);\
+ SWAP_FUNC(16, s, d, guid, struct squashfs_ldev_inode_header);\
+ SWAP_FUNC(32, s, d, mtime, struct squashfs_ldev_inode_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_ldev_inode_header);\
+ SWAP_FUNC(32, s, d, nlink, struct squashfs_ldev_inode_header);\
+ SWAP_FUNC(32, s, d, rdev, struct squashfs_ldev_inode_header);\
+ SWAP_FUNC(32, s, d, xattr, struct squashfs_ldev_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, inode_type, struct squashfs_symlink_inode_header);\
+ SWAP_FUNC(16, s, d, mode, struct squashfs_symlink_inode_header);\
+ SWAP_FUNC(16, s, d, uid, struct squashfs_symlink_inode_header);\
+ SWAP_FUNC(16, s, d, guid, struct squashfs_symlink_inode_header);\
+ SWAP_FUNC(32, s, d, mtime, struct squashfs_symlink_inode_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_symlink_inode_header);\
+ SWAP_FUNC(32, s, d, nlink, struct squashfs_symlink_inode_header);\
+ SWAP_FUNC(32, s, d, symlink_size, struct squashfs_symlink_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_REG_INODE_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, inode_type, struct squashfs_reg_inode_header);\
+ SWAP_FUNC(16, s, d, mode, struct squashfs_reg_inode_header);\
+ SWAP_FUNC(16, s, d, uid, struct squashfs_reg_inode_header);\
+ SWAP_FUNC(16, s, d, guid, struct squashfs_reg_inode_header);\
+ SWAP_FUNC(32, s, d, mtime, struct squashfs_reg_inode_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_reg_inode_header);\
+ SWAP_FUNC(32, s, d, start_block, struct squashfs_reg_inode_header);\
+ SWAP_FUNC(32, s, d, fragment, struct squashfs_reg_inode_header);\
+ SWAP_FUNC(32, s, d, offset, struct squashfs_reg_inode_header);\
+ SWAP_FUNC(32, s, d, file_size, struct squashfs_reg_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_LREG_INODE_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, inode_type, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(16, s, d, mode, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(16, s, d, uid, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(16, s, d, guid, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(32, s, d, mtime, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(64, s, d, start_block, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(64, s, d, file_size, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(64, s, d, sparse, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(32, s, d, nlink, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(32, s, d, fragment, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(32, s, d, offset, struct squashfs_lreg_inode_header);\
+ SWAP_FUNC(32, s, d, xattr, struct squashfs_lreg_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_DIR_INODE_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, inode_type, struct squashfs_dir_inode_header);\
+ SWAP_FUNC(16, s, d, mode, struct squashfs_dir_inode_header);\
+ SWAP_FUNC(16, s, d, uid, struct squashfs_dir_inode_header);\
+ SWAP_FUNC(16, s, d, guid, struct squashfs_dir_inode_header);\
+ SWAP_FUNC(32, s, d, mtime, struct squashfs_dir_inode_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_dir_inode_header);\
+ SWAP_FUNC(32, s, d, start_block, struct squashfs_dir_inode_header);\
+ SWAP_FUNC(32, s, d, nlink, struct squashfs_dir_inode_header);\
+ SWAP_FUNC(16, s, d, file_size, struct squashfs_dir_inode_header);\
+ SWAP_FUNC(16, s, d, offset, struct squashfs_dir_inode_header);\
+ SWAP_FUNC(32, s, d, parent_inode, struct squashfs_dir_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_LDIR_INODE_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, inode_type, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(16, s, d, mode, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(16, s, d, uid, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(16, s, d, guid, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(32, s, d, mtime, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(32, s, d, nlink, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(32, s, d, file_size, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(32, s, d, start_block, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(32, s, d, parent_inode, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(16, s, d, i_count, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(16, s, d, offset, struct squashfs_ldir_inode_header);\
+ SWAP_FUNC(32, s, d, xattr, struct squashfs_ldir_inode_header);\
+}
+
+#define _SQUASHFS_SWAP_DIR_ENTRY(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, offset, struct squashfs_dir_entry);\
+ SWAP_FUNC##S(16, s, d, inode_number, struct squashfs_dir_entry);\
+ SWAP_FUNC(16, s, d, type, struct squashfs_dir_entry);\
+ SWAP_FUNC(16, s, d, size, struct squashfs_dir_entry);\
+}
+
+#define _SQUASHFS_SWAP_DIR_HEADER(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(32, s, d, count, struct squashfs_dir_header);\
+ SWAP_FUNC(32, s, d, start_block, struct squashfs_dir_header);\
+ SWAP_FUNC(32, s, d, inode_number, struct squashfs_dir_header);\
+}
+
+#define _SQUASHFS_SWAP_FRAGMENT_ENTRY(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(64, s, d, start_block, struct squashfs_fragment_entry);\
+ SWAP_FUNC(32, s, d, size, struct squashfs_fragment_entry);\
+}
+
+#define _SQUASHFS_SWAP_XATTR_ENTRY(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(16, s, d, type, struct squashfs_xattr_entry);\
+ SWAP_FUNC(16, s, d, size, struct squashfs_xattr_entry);\
+}
+
+#define _SQUASHFS_SWAP_XATTR_VAL(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(32, s, d, vsize, struct squashfs_xattr_val);\
+}
+
+#define _SQUASHFS_SWAP_XATTR_ID(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(64, s, d, xattr, struct squashfs_xattr_id);\
+ SWAP_FUNC(32, s, d, count, struct squashfs_xattr_id);\
+ SWAP_FUNC(32, s, d, size, struct squashfs_xattr_id);\
+}
+
+#define _SQUASHFS_SWAP_XATTR_TABLE(s, d, SWAP_FUNC) {\
+ SWAP_FUNC(64, s, d, xattr_table_start, struct squashfs_xattr_table);\
+ SWAP_FUNC(32, s, d, xattr_ids, struct squashfs_xattr_table);\
+}
+
+/* big endian architecture copy and swap macros */
+#define SQUASHFS_SWAP_SUPER_BLOCK(s, d) \
+ _SQUASHFS_SWAP_SUPER_BLOCK(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_DIR_INDEX(s, d) \
+ _SQUASHFS_SWAP_DIR_INDEX(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_BASE_INODE_HEADER(s, d) \
+ _SQUASHFS_SWAP_BASE_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_IPC_INODE_HEADER(s, d) \
+ _SQUASHFS_SWAP_IPC_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_LIPC_INODE_HEADER(s, d) \
+ _SQUASHFS_SWAP_LIPC_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_DEV_INODE_HEADER(s, d) \
+ _SQUASHFS_SWAP_DEV_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_LDEV_INODE_HEADER(s, d) \
+ _SQUASHFS_SWAP_LDEV_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, d) \
+ _SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_REG_INODE_HEADER(s, d) \
+ _SQUASHFS_SWAP_REG_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_LREG_INODE_HEADER(s, d) \
+ _SQUASHFS_SWAP_LREG_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_DIR_INODE_HEADER(s, d) \
+ _SQUASHFS_SWAP_DIR_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_LDIR_INODE_HEADER(s, d) \
+ _SQUASHFS_SWAP_LDIR_INODE_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_DIR_ENTRY(s, d) \
+ _SQUASHFS_SWAP_DIR_ENTRY(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_DIR_HEADER(s, d) \
+ _SQUASHFS_SWAP_DIR_HEADER(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_FRAGMENT_ENTRY(s, d) \
+ _SQUASHFS_SWAP_FRAGMENT_ENTRY(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_XATTR_ENTRY(s, d) \
+ _SQUASHFS_SWAP_XATTR_ENTRY(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_XATTR_VAL(s, d) \
+ _SQUASHFS_SWAP_XATTR_VAL(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_XATTR_ID(s, d) \
+ _SQUASHFS_SWAP_XATTR_ID(s, d, SWAP_LE)
+#define SQUASHFS_SWAP_XATTR_TABLE(s, d) \
+ _SQUASHFS_SWAP_XATTR_TABLE(s, d, SWAP_LE)
+#define SWAP_LE(bits, s, d, field, type) \
+ SWAP_LE##bits(((void *)(s)) + offsetof(type, field), \
+ ((void *)(d)) + offsetof(type, field))
+#define SWAP_LES(bits, s, d, field, type) \
+ SWAP_LE(bits, s, d, field, type)
+#define SQUASHFS_SWAP_INODE_T(s, d) SQUASHFS_SWAP_LONG_LONGS(s, d, 1)
+#define SQUASHFS_SWAP_FRAGMENT_INDEXES(s, d, n) \
+ SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+#define SQUASHFS_SWAP_LOOKUP_BLOCKS(s, d, n) SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+#define SQUASHFS_SWAP_ID_BLOCKS(s, d, n) SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+
+#define SQUASHFS_SWAP_SHORTS(s, d, n) swap_le16_num(s, d, n)
+#define SQUASHFS_SWAP_INTS(s, d, n) swap_le32_num(s, d, n)
+#define SQUASHFS_SWAP_LONG_LONGS(s, d, n) swap_le64_num(s, d, n)
+
+#define SWAP_LE16(s, d) swap_le16(s, d)
+#define SWAP_LE32(s, d) swap_le32(s, d)
+#define SWAP_LE64(s, d) swap_le64(s, d)
+
+/* big endian architecture swap in-place macros */
+#define SQUASHFS_INSWAP_SUPER_BLOCK(s) \
+ _SQUASHFS_SWAP_SUPER_BLOCK(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_DIR_INDEX(s) \
+ _SQUASHFS_SWAP_DIR_INDEX(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_BASE_INODE_HEADER(s) \
+ _SQUASHFS_SWAP_BASE_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_IPC_INODE_HEADER(s) \
+ _SQUASHFS_SWAP_IPC_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_LIPC_INODE_HEADER(s) \
+ _SQUASHFS_SWAP_LIPC_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_DEV_INODE_HEADER(s) \
+ _SQUASHFS_SWAP_DEV_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_LDEV_INODE_HEADER(s) \
+ _SQUASHFS_SWAP_LDEV_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_SYMLINK_INODE_HEADER(s) \
+ _SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_REG_INODE_HEADER(s) \
+ _SQUASHFS_SWAP_REG_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_LREG_INODE_HEADER(s) \
+ _SQUASHFS_SWAP_LREG_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_DIR_INODE_HEADER(s) \
+ _SQUASHFS_SWAP_DIR_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_LDIR_INODE_HEADER(s) \
+ _SQUASHFS_SWAP_LDIR_INODE_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_DIR_ENTRY(s) \
+ _SQUASHFS_SWAP_DIR_ENTRY(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_DIR_HEADER(s) \
+ _SQUASHFS_SWAP_DIR_HEADER(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_FRAGMENT_ENTRY(s) \
+ _SQUASHFS_SWAP_FRAGMENT_ENTRY(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_XATTR_ENTRY(s) \
+ _SQUASHFS_SWAP_XATTR_ENTRY(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_XATTR_VAL(s) \
+ _SQUASHFS_SWAP_XATTR_VAL(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_XATTR_ID(s) \
+ _SQUASHFS_SWAP_XATTR_ID(s, s, INSWAP_LE)
+#define SQUASHFS_INSWAP_XATTR_TABLE(s) \
+ _SQUASHFS_SWAP_XATTR_TABLE(s, s, INSWAP_LE)
+#define INSWAP_LE(bits, s, d, field, type) \
+ (s)->field = inswap_le##bits((s)->field)
+#define INSWAP_LES(bits, s, d, field, type) \
+ (s)->field = (short) inswap_le##bits((unsigned short) \
+ (s)->field)
+#define SQUASHFS_INSWAP_INODE_T(s) s = inswap_le64(s)
+#define SQUASHFS_INSWAP_FRAGMENT_INDEXES(s, n) inswap_le64_num(s, n)
+#define SQUASHFS_INSWAP_LOOKUP_BLOCKS(s, n) inswap_le64_num(s, n)
+#define SQUASHFS_INSWAP_ID_BLOCKS(s, n) inswap_le64_num(s, n)
+#define SQUASHFS_INSWAP_SHORTS(s, n) inswap_le16_num(s, n)
+#define SQUASHFS_INSWAP_INTS(s, n) inswap_le32_num(s, n)
+#define SQUASHFS_INSWAP_LONG_LONGS(s, n) inswap_le64_num(s, n)
+#else
+/* little endian architecture, just copy */
+#define SQUASHFS_SWAP_SUPER_BLOCK(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_super_block))
+#define SQUASHFS_SWAP_DIR_INDEX(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_dir_index))
+#define SQUASHFS_SWAP_BASE_INODE_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_base_inode_header))
+#define SQUASHFS_SWAP_IPC_INODE_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_ipc_inode_header))
+#define SQUASHFS_SWAP_LIPC_INODE_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_lipc_inode_header))
+#define SQUASHFS_SWAP_DEV_INODE_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_dev_inode_header))
+#define SQUASHFS_SWAP_LDEV_INODE_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_ldev_inode_header))
+#define SQUASHFS_SWAP_SYMLINK_INODE_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_symlink_inode_header))
+#define SQUASHFS_SWAP_REG_INODE_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_reg_inode_header))
+#define SQUASHFS_SWAP_LREG_INODE_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_lreg_inode_header))
+#define SQUASHFS_SWAP_DIR_INODE_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_dir_inode_header))
+#define SQUASHFS_SWAP_LDIR_INODE_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_ldir_inode_header))
+#define SQUASHFS_SWAP_DIR_ENTRY(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_dir_entry))
+#define SQUASHFS_SWAP_DIR_HEADER(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_dir_header))
+#define SQUASHFS_SWAP_FRAGMENT_ENTRY(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_fragment_entry))
+#define SQUASHFS_SWAP_XATTR_ENTRY(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_xattr_entry))
+#define SQUASHFS_SWAP_XATTR_VAL(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_xattr_val))
+#define SQUASHFS_SWAP_XATTR_ID(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_xattr_id))
+#define SQUASHFS_SWAP_XATTR_TABLE(s, d) \
+ SQUASHFS_MEMCPY(s, d, sizeof(struct squashfs_xattr_table))
+#define SQUASHFS_SWAP_INODE_T(s, d) SQUASHFS_SWAP_LONG_LONGS(s, d, 1)
+#define SQUASHFS_SWAP_FRAGMENT_INDEXES(s, d, n) \
+ SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+#define SQUASHFS_SWAP_LOOKUP_BLOCKS(s, d, n) SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+#define SQUASHFS_SWAP_ID_BLOCKS(s, d, n) SQUASHFS_SWAP_LONG_LONGS(s, d, n)
+
+#define SQUASHFS_MEMCPY(s, d, n) memcpy(d, s, n)
+#define SQUASHFS_SWAP_SHORTS(s, d, n) memcpy(d, s, n * sizeof(short))
+#define SQUASHFS_SWAP_INTS(s, d, n) memcpy(d, s, n * sizeof(int))
+#define SQUASHFS_SWAP_LONG_LONGS(s, d, n) \
+ memcpy(d, s, n * sizeof(long long))
+
+/* little endian architecture, data already in place so do nothing */
+#define SQUASHFS_INSWAP_SUPER_BLOCK(s)
+#define SQUASHFS_INSWAP_DIR_INDEX(s)
+#define SQUASHFS_INSWAP_BASE_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_IPC_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_LIPC_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_DEV_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_LDEV_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_SYMLINK_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_REG_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_LREG_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_DIR_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_LDIR_INODE_HEADER(s)
+#define SQUASHFS_INSWAP_DIR_ENTRY(s)
+#define SQUASHFS_INSWAP_DIR_HEADER(s)
+#define SQUASHFS_INSWAP_FRAGMENT_ENTRY(s)
+#define SQUASHFS_INSWAP_XATTR_ENTRY(s)
+#define SQUASHFS_INSWAP_XATTR_VAL(s)
+#define SQUASHFS_INSWAP_XATTR_ID(s)
+#define SQUASHFS_INSWAP_XATTR_TABLE(s)
+#define SQUASHFS_INSWAP_INODE_T(s)
+#define SQUASHFS_INSWAP_FRAGMENT_INDEXES(s, n)
+#define SQUASHFS_INSWAP_LOOKUP_BLOCKS(s, n)
+#define SQUASHFS_INSWAP_ID_BLOCKS(s, n)
+#define SQUASHFS_INSWAP_SHORTS(s, n)
+#define SQUASHFS_INSWAP_INTS(s, n)
+#define SQUASHFS_INSWAP_LONG_LONGS(s, n)
+#endif
+#endif
diff --git a/squashfs-tools/swap.c b/squashfs-tools/swap.c
new file mode 100644
index 0000000..eb0f326
--- /dev/null
+++ b/squashfs-tools/swap.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009, 2010, 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * swap.c
+ */
+
+#include "endian_compat.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+void swap_le16(void *src, void *dest)
+{
+ unsigned char *s = src;
+ unsigned char *d = dest;
+
+ d[0] = s[1];
+ d[1] = s[0];
+}
+
+
+void swap_le32(void *src, void *dest)
+{
+ unsigned char *s = src;
+ unsigned char *d = dest;
+
+ d[0] = s[3];
+ d[1] = s[2];
+ d[2] = s[1];
+ d[3] = s[0];
+}
+
+
+void swap_le64(void *src, void *dest)
+{
+ unsigned char *s = src;
+ unsigned char *d = dest;
+
+ d[0] = s[7];
+ d[1] = s[6];
+ d[2] = s[5];
+ d[3] = s[4];
+ d[4] = s[3];
+ d[5] = s[2];
+ d[6] = s[1];
+ d[7] = s[0];
+}
+
+
+unsigned short inswap_le16(unsigned short num)
+{
+ return (num >> 8) |
+ ((num & 0xff) << 8);
+}
+
+
+unsigned int inswap_le32(unsigned int num)
+{
+ return (num >> 24) |
+ ((num & 0xff0000) >> 8) |
+ ((num & 0xff00) << 8) |
+ ((num & 0xff) << 24);
+}
+
+
+long long inswap_le64(long long n)
+{
+ unsigned long long num = n;
+
+ return (num >> 56) |
+ ((num & 0xff000000000000LL) >> 40) |
+ ((num & 0xff0000000000LL) >> 24) |
+ ((num & 0xff00000000LL) >> 8) |
+ ((num & 0xff000000) << 8) |
+ ((num & 0xff0000) << 24) |
+ ((num & 0xff00) << 40) |
+ ((num & 0xff) << 56);
+}
+
+
+#define SWAP_LE_NUM(BITS) \
+void swap_le##BITS##_num(void *s, void *d, int n) \
+{\
+ int i;\
+ for(i = 0; i < n; i++, s += BITS / 8, d += BITS / 8)\
+ swap_le##BITS(s, d);\
+}
+
+SWAP_LE_NUM(16)
+SWAP_LE_NUM(32)
+SWAP_LE_NUM(64)
+
+#define INSWAP_LE_NUM(BITS, TYPE) \
+void inswap_le##BITS##_num(TYPE *s, int n) \
+{\
+ int i;\
+ for(i = 0; i < n; i++)\
+ s[i] = inswap_le##BITS(s[i]);\
+}
+
+INSWAP_LE_NUM(16, unsigned short)
+INSWAP_LE_NUM(32, unsigned int)
+INSWAP_LE_NUM(64, long long)
+#endif
diff --git a/squashfs-tools/tar.c b/squashfs-tools/tar.c
new file mode 100644
index 0000000..b02b797
--- /dev/null
+++ b/squashfs-tools/tar.c
@@ -0,0 +1,1682 @@
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * tar.c
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#include <regex.h>
+
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "caches-queues-lists.h"
+#include "mksquashfs_error.h"
+#include "xattr.h"
+#include "tar.h"
+#include "progressbar.h"
+#include "info.h"
+
+#define TRUE 1
+#define FALSE 0
+
+extern int silent;
+int ignore_zeros = FALSE;
+extern int ignore_zeros;
+int default_uid_opt = FALSE;
+unsigned int default_uid;
+int default_gid_opt = FALSE;
+unsigned int default_gid;
+int default_mode_opt = FALSE;
+mode_t default_mode;
+
+static long long read_octal(char *s, int size)
+{
+ long long res = 0;
+
+ for(; size && *s == ' '; s++, size--);
+
+ if(size == 0)
+ return -1;
+
+ for(; size && *s >= '0' && *s < '8'; s++, size--)
+ res = (res << 3) + *s - '0';
+
+ if(size && (*s != ' ' && *s != '\0'))
+ return -1;
+
+ return res;
+}
+
+
+static long long read_binary(char *src, int size)
+{
+ unsigned char *s = (unsigned char *) src;
+ long long res = 0;
+
+ for(; size; s++, size --)
+ res = (res << 8) + *s;
+
+ return res;
+}
+
+
+static long long read_number(char *s, int size)
+{
+ if(*((signed char *) s) == -128)
+ return read_binary(s + 1, size - 1);
+ else
+ return read_octal(s, size);
+}
+
+
+static long long read_decimal(char *s, int maxsize, int *bytes)
+{
+ long long res = 0;
+ int size = maxsize;
+
+ for(; size && *s >= '0' && *s <= '9'; s++, size--)
+ res = (res * 10) + *s - '0';
+
+ /* size should be > 0, and we should be at the terminator */
+ if(size > 0 && *s == '\n') {
+ *bytes = maxsize - size + 1;
+ return res;
+ }
+
+ /* Bad value or out of bytes? */
+ if(size)
+ return -1;
+ else
+ return -2;
+}
+
+
+static char *read_long_string(int size, int skip)
+{
+ char buffer[512];
+ char *name = malloc(size + 1);
+ int i, res, length = size;
+
+ if(name == NULL)
+ MEM_ERROR();
+
+ for(i = 0; size > 0; i++) {
+ int expected = size > 512 ? 512 : size;
+
+ res = read_bytes(STDIN_FILENO, buffer, 512);
+ if(res < 512) {
+ if(res != -1)
+ ERROR("Unexpected EOF (end of file), the tarfile appears to be truncated or corrupted\n");
+ free(name);
+ return NULL;
+ }
+ memcpy(name + i * 512, buffer, expected);
+ size -= 512;
+ }
+
+ name[length] = '\0';
+
+ if(skip) {
+ char *filename = name;
+
+ while(1) {
+ if(length >= 3 && strncmp(filename, "../", 3) == 0) {
+ filename += 3;
+ length -= 3;
+ } else if(length >= 2 && strncmp(filename, "./", 2) == 0) {
+ filename += 2;
+ length -= 2;
+ } else if(length >= 1 && *filename == '/') {
+ filename++;
+ length--;
+ } else
+ break;
+ }
+
+ if(filename != name) {
+ if(length == 0) {
+ ERROR("Empty tar filename after skipping leading /, ./, or ../\n");
+ free(name);
+ return NULL;
+ }
+
+ memmove(name, filename, length + 1);
+ name = realloc(name, length + 1);
+ if(name == NULL)
+ MEM_ERROR();
+ }
+ }
+
+ return name;
+}
+
+
+static int all_zero(struct tar_header *header)
+{
+ int i;
+
+ for(i = 0; i < 512; i++)
+ if(header->udata[i])
+ return FALSE;
+
+ return TRUE;
+}
+
+
+static int checksum_matches(struct tar_header *header, int silent)
+{
+ int checksum = read_number(header->checksum, 8);
+ int computed = 0;
+ int i;
+
+ if(checksum == -1) {
+ if(!silent)
+ ERROR("Failed to read checksum in tar header\n");
+ return FALSE;
+ }
+
+ /* The checksum is computed with the checksum field
+ * filled with spaces */
+ memcpy(header->checksum, " ", 8);
+
+ /* Header bytes should be treated as unsigned */
+ for(i = 0; i < 512; i++)
+ computed += header->udata[i];
+
+ if(computed == checksum)
+ return TRUE;
+
+ /* Some historical implementations treated header bytes as signed */
+ for(computed = 0, i = 0; i < 512; i++)
+ computed += header->sdata[i];
+
+ return computed == checksum;
+}
+
+
+static char *get_component(char *target, char **targname)
+{
+ char *start;
+
+ start = target;
+ while(*target != '/' && *target != '\0')
+ target ++;
+
+ *targname = strndup(start, target - start);
+
+ while(*target == '/')
+ target ++;
+
+ return target;
+}
+
+
+static struct inode_info *new_inode(struct tar_file *tar_file)
+{
+ struct inode_info *inode;
+ int bytes = tar_file->link ? strlen(tar_file->link) + 1 : 0;
+
+ inode = malloc(sizeof(struct inode_info) + bytes);
+ if(inode == NULL)
+ MEM_ERROR();
+
+ if(bytes)
+ memcpy(&inode->symlink, tar_file->link, bytes);
+ memcpy(&inode->buf, &tar_file->buf, sizeof(struct stat));
+ inode->read = FALSE;
+ inode->root_entry = FALSE;
+ inode->tar_file = tar_file;
+ inode->inode = SQUASHFS_INVALID_BLK;
+ inode->nlink = 1;
+ inode->inode_number = 0;
+ inode->pseudo = NULL;
+ inode->dummy_root_dir = FALSE;
+ inode->xattr = NULL;
+ inode->tarfile = TRUE;
+
+ /*
+ * Copy filesystem wide defaults into inode, these filesystem
+ * wide defaults may be altered on an individual inode basis by
+ * user specified actions
+ *
+ */
+ inode->no_fragments = no_fragments;
+ inode->always_use_fragments = always_use_fragments;
+ inode->noD = noD;
+ inode->noF = noF;
+
+ inode->next = inode_info[0];
+ inode_info[0] = inode;
+
+ return inode;
+}
+
+
+static struct inode_info *copy_inode(struct inode_info *source)
+{
+ struct inode_info *inode;
+ int bytes = S_ISLNK(source->buf.st_mode) ? strlen(source->symlink) + 1 : 0;
+
+ inode = malloc(sizeof(struct inode_info) + bytes);
+ if(inode == NULL)
+ MEM_ERROR();
+
+ memcpy(inode, source, sizeof(struct inode_info) + bytes);
+
+ return inode;
+}
+
+
+static void fixup_tree(struct dir_info *dir)
+{
+ struct dir_ent *entry;
+
+ for(entry = dir->list; entry; entry = entry->next) {
+ if(entry->dir && entry->inode == NULL) {
+ /* Tar file didn't create this directory, and so it lacks
+ * an inode with metadata. Create a default definition ... */
+ struct stat buf;
+
+ memset(&buf, 0, sizeof(buf));
+ if(default_mode_opt)
+ buf.st_mode = default_mode | S_IFDIR;
+ else
+ buf.st_mode = S_IRWXU | S_IRGRP | S_IXGRP |
+ S_IROTH | S_IXOTH | S_IFDIR;
+ if(default_uid_opt)
+ buf.st_uid = default_uid;
+ else
+ buf.st_uid = getuid();
+ if(default_gid_opt)
+ buf.st_gid = default_gid;
+ else
+ buf.st_gid = getgid();
+ buf.st_mtime = time(NULL);
+ buf.st_dev = 0;
+ buf.st_ino = 0;
+ entry->inode = lookup_inode(&buf);
+ entry->inode->tar_file = NULL;
+ entry->inode->tarfile = TRUE;
+ }
+
+ if(entry->dir == NULL && S_ISDIR(entry->inode->buf.st_mode)) {
+ /* Tar file created this directory, but, never created
+ * anything in it. This will leave a NULL sub-directory,
+ * where the scanning code expects to find an empty
+ * directory. Create an empty directory in this case ... */
+ char *subpath = subpathname(entry);
+
+ entry->dir = create_dir("", subpath, dir->depth + 1);
+ entry->dir->dir_ent = entry;
+ }
+
+ if(entry->dir)
+ fixup_tree(entry->dir);
+ }
+}
+
+
+/*
+ * Add source to the tardir directory hierachy.
+ * Tarfile describes the tar file to be added.
+ */
+static struct dir_info *add_tarfile(struct dir_info *sdir, char *source,
+ char *subpath, struct tar_file *tarfile, struct pathnames *paths,
+ int depth, struct dir_ent **dir_ent, struct inode_info *link)
+{
+ struct dir_info *sub;
+ struct dir_ent *entry;
+ struct pathnames *new = NULL;
+ struct dir_info *dir = sdir;
+ char *name;
+
+ if(dir == NULL)
+ dir = create_dir("", subpath, depth);
+
+ source = get_component(source, &name);
+
+ if((strcmp(name, ".") == 0) || strcmp(name, "..") == 0)
+ BAD_ERROR("Error: Tar pathname can't have '.' or '..' in it\n");
+
+ entry = lookup_name(dir, name);
+
+ if(entry) {
+ /* existing matching entry */
+ if(entry->dir == NULL) {
+ /* No sub-directory which means this is the leaf
+ * component of a pre-existing tarfile */
+ if(source[0] != '\0') {
+ /* existing entry must be a directory */
+ subpath = subpathname(entry);
+ if(S_ISDIR(entry->inode->buf.st_mode)) {
+ /* recurse adding child components */
+ excluded(name, paths, &new);
+ entry->dir = add_tarfile(NULL, source, subpath, tarfile, new, depth + 1, dir_ent, link);
+ if(entry->dir == NULL)
+ goto failed_early;
+ entry->dir->dir_ent = entry;
+ } else
+ BAD_ERROR("%s exists in the tar file as"
+ " a non-directory, cannot add"
+ " tar pathname %s!\n",
+ subpath, tarfile->pathname);
+ } else {
+ ERROR("%s already exists in the tar file, ignoring\n", tarfile->pathname);
+ goto failed_early;
+ }
+ } else {
+ if(source[0] == '\0') {
+ /* sub-directory exists, we must be adding a
+ * directory, and we must not already have a
+ * definition for this directory */
+ if(S_ISDIR(tarfile->buf.st_mode)) {
+ if(entry->inode == NULL)
+ entry->inode = new_inode(tarfile);
+ else {
+ ERROR("%s already exists in the tar file, ignoring!\n", tarfile->pathname);
+ goto failed_early;
+ }
+ } else
+ BAD_ERROR("%s exists in the tar file as"
+ " both a directory and"
+ " non-directory!\n",
+ tarfile->pathname);
+ } else {
+ /* recurse adding child components */
+ excluded(name, paths, &new);
+ subpath = subpathname(entry);
+ sub = add_tarfile(entry->dir, source, subpath, tarfile, new, depth + 1, dir_ent, link);
+ if(sub == NULL)
+ goto failed_early;
+ }
+ }
+
+ free(name);
+ } else {
+ /*
+ * No matching name found.
+ *
+ * - If we're at the leaf of the source, then add it.
+ *
+ * - If we're not at the leaf of the source, we will add it,
+ * and recurse walking the source
+ */
+ if(old_exclude == FALSE && excluded(name, paths, &new))
+ goto failed_early;
+
+ entry = create_dir_entry(name, NULL, NULL, dir);
+
+ if(source[0] == '\0') {
+ if(S_ISDIR(tarfile->buf.st_mode)) {
+ add_dir_entry(entry, NULL, new_inode(tarfile));
+ dir->directory_count ++;
+ } else if (link == FALSE) {
+ add_dir_entry(entry, NULL, new_inode(tarfile));
+ if(S_ISREG(tarfile->buf.st_mode))
+ *dir_ent = entry;
+ } else if(no_hardlinks)
+ add_dir_entry(entry, NULL, copy_inode(link));
+ else
+ add_dir_entry(entry, NULL, link);
+ } else {
+ subpath = subpathname(entry);
+ sub = add_tarfile(NULL, source, subpath, tarfile, new, depth + 1, dir_ent, link);
+ if(sub == NULL)
+ goto failed_entry;
+ add_dir_entry(entry, sub, NULL);
+ dir->directory_count ++;
+ }
+ }
+
+ free(new);
+ return dir;
+
+failed_early:
+ free(new);
+ free(name);
+ if(sdir == NULL)
+ free_dir(dir);
+ return NULL;
+
+failed_entry:
+ free(new);
+ free_dir_entry(entry);
+ if(sdir == NULL)
+ free_dir(dir);
+ return NULL;
+}
+
+
+struct dir_ent *lookup_pathname(struct dir_info *dir, char *pathname)
+{
+ char *name;
+ struct dir_ent *entry;
+
+ pathname = get_component(pathname, &name);
+
+ if((strcmp(name, ".") == 0) || strcmp(name, "..") == 0) {
+ ERROR("Error: Tar hardlink pathname can't have '.' or '..' in it\n");
+ return NULL;
+ }
+
+ entry = lookup_name(dir, name);
+ free(name);
+
+ if(entry == NULL)
+ return NULL;
+
+ if(pathname[0] == '\0')
+ return entry;
+
+ if(entry->dir == NULL)
+ return NULL;
+
+ return lookup_pathname(entry->dir, pathname);
+}
+
+
+static inline int is_fragment(long long file_size)
+{
+ return !no_fragments && file_size && (file_size < block_size ||
+ (always_use_fragments && file_size & (block_size - 1)));
+}
+
+
+static void put_file_buffer(struct file_buffer *file_buffer)
+{
+ /*
+ * Decide where to send the file buffer:
+ * - compressible non-fragment blocks go to the deflate threads,
+ * - fragments go to the process fragment threads,
+ */
+ if(file_buffer->fragment)
+ queue_put(to_process_frag, file_buffer);
+ else
+ queue_put(to_deflate, file_buffer);
+}
+
+
+int sparse_reader(struct tar_file *file, long long cur_offset, char *dest, int bytes, long long *off)
+{
+ static int cur;
+ static long long offset;
+ static long long number;
+ int avail, res;
+
+ if(bytes == 0) {
+ cur = 0;
+ offset = file->map[0].offset;
+ number = file->map[0].number;
+ *off = offset;
+ return 0;
+ }
+
+ if(cur_offset != offset)
+ return -1;
+
+ avail = bytes > number ? number : bytes;
+ res = read_bytes(STDIN_FILENO, dest, avail);
+ if(res != avail)
+ BAD_ERROR("Failed to read tar file %s, the tarfile appears to be truncated or corrupted\n", file->pathname);
+
+ offset += avail;
+ number -= avail;
+
+ if(number == 0) {
+ cur ++;
+ offset = file->map[cur].offset;
+ number = (file->map[cur].number + 511) & ~511;
+ }
+
+ *off = offset;
+ return avail;
+}
+
+
+static int read_sparse_block(struct tar_file *file, int fd, char *dest, int bytes, int block)
+{
+ static long long offset;
+ long long cur_offset = (long long) block * block_size;
+ int avail, copied = bytes;
+
+ if(block == 0)
+ sparse_reader(file, cur_offset, dest, 0, &offset);
+
+ if(offset - cur_offset >= block_size && bytes == block_size) {
+ memset(dest, 0, block_size);
+ return block_size;
+ }
+
+ while(bytes) {
+ if(offset - cur_offset > 0) {
+ avail = offset - cur_offset < bytes ? offset - cur_offset : bytes;
+
+ memset(dest, 0, avail);
+ dest += avail;
+ cur_offset += avail;
+ bytes -= avail;
+ } else if(cur_offset == offset) {
+ avail = sparse_reader(file, cur_offset, dest, bytes, &offset);
+
+ dest += avail;
+ cur_offset += avail;
+ bytes -= avail;
+ } else
+ return -1;
+ }
+
+ return copied;
+}
+
+
+static int read_block(struct tar_file *file, int fd, char *data, int bytes, int block)
+{
+ if(file->map)
+ return read_sparse_block(file, fd, data, bytes, block);
+ else
+ return read_bytes(fd, data, bytes);
+}
+
+
+static void skip_file(struct tar_file *tar_file)
+{
+ int blocks = (tar_file->buf.st_size + block_size - 1) >> block_log, i;
+
+ for(i = 0; i < blocks; i++)
+ cache_block_put(seq_queue_get(to_main));
+
+ progress_bar_size(-blocks);
+}
+
+static void read_tar_data(struct tar_file *tar_file)
+{
+ struct stat *buf = &tar_file->buf;
+ struct file_buffer *file_buffer;
+ int blocks, block = 0;
+ long long bytes, read_size;
+
+ bytes = 0;
+ read_size = buf->st_size;
+ blocks = (read_size + block_size - 1) >> block_log;
+
+ do {
+ file_buffer = cache_get_nohash(reader_buffer);
+ file_buffer->file_size = read_size;
+ file_buffer->tar_file = tar_file;
+ file_buffer->sequence = sequence_count ++;
+ file_buffer->noD = noD;
+ file_buffer->error = FALSE;
+
+ if((block + 1) < blocks) {
+ /* non-tail block should be exactly block_size */
+ file_buffer->size = read_block(tar_file, STDIN_FILENO, file_buffer->data, block_size, block);
+ if(file_buffer->size != block_size)
+ BAD_ERROR("Failed to read tar file %s, the tarfile appears to be truncated or corrupted\n", tar_file->pathname);
+
+ bytes += file_buffer->size;
+
+ file_buffer->fragment = FALSE;
+ put_file_buffer(file_buffer);
+ } else {
+ /* The remaining bytes will be rounded up to 512 bytes */
+ int expected = (read_size + 511 - bytes) & ~511;
+ int size = read_block(tar_file, STDIN_FILENO, file_buffer->data, expected, block);
+
+ if(size != expected)
+ BAD_ERROR("Failed to read tar file %s, the tarfile appears to be truncated or corrupted\n", tar_file->pathname);
+
+ file_buffer->size = read_size - bytes;
+ }
+ } while(++ block < blocks);
+
+ file_buffer->fragment = is_fragment(read_size);
+ put_file_buffer(file_buffer);
+
+ return;
+}
+
+
+static char *skip_components(char *filename, int size, int *sizep)
+{
+ while(1) {
+ if(size >= 3 && strncmp(filename, "../", 3) == 0) {
+ filename += 3;
+ size -= 3;
+ } else if(size >= 2 && strncmp(filename, "./", 2) == 0) {
+ filename += 2;
+ size -= 2;
+ } else if(size >= 1 && *filename == '/') {
+ filename++;
+ size--;
+ } else
+ break;
+ }
+
+ if(sizep)
+ *sizep = size;
+
+ return filename;
+}
+
+
+static int read_sparse_value(struct tar_file *file, char *value, int map_entries)
+{
+ int bytes, res, i = 0;
+ long long number;
+
+ while(1) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != ',')
+ goto failed;
+
+ file->map[i].offset = number;
+
+ value += bytes + 1;
+
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || (value[bytes] != ',' && value[bytes] != '\0'))
+ goto failed;
+
+ file->map[i++].number = number;
+
+ if(value[bytes] == '\0' || i >= map_entries)
+ break;
+
+ value += bytes + 1;
+ }
+
+ return TRUE;
+
+failed:
+ return FALSE;
+}
+
+
+static int read_pax_header(struct tar_file *file, long long st_size)
+{
+ long long size = (st_size + 511) & ~511;
+ char *data, *ptr, *end, *keyword, *value;
+ int res, length, bytes, vsize;
+ long long number;
+ long long major = -1, minor = -1, realsize = -1;
+ int old_gnu_pax = FALSE, old_gnu_ver = -1;
+ int map_entries = 0, cur_entry = 0;
+ char *name = NULL;
+
+ data = malloc(size);
+ if(data == NULL)
+ MEM_ERROR();
+
+ res = read_bytes(STDIN_FILENO, data, size);
+ if(res < size) {
+ if(res != -1)
+ ERROR("Unexpected EOF (end of file), the tarfile appears to be truncated or corrupted\n");
+ free(data);
+ return FALSE;
+ }
+
+ for(ptr = data, end = data + st_size; ptr < end;) {
+ /*
+ * What follows should be <length> <keyword>=<value>,
+ * where <length> is the full length, including the
+ * <length> field and newline
+ */
+ res = sscanf(ptr, "%d%n", &length, &bytes);
+ if(res < 1 || length <= bytes || length > st_size)
+ goto failed;
+
+ length -= bytes;
+ ptr += bytes;
+
+ /* Skip whitespace */
+ for(; length && *ptr == ' '; length--, ptr++);
+
+ /* Store and parse keyword */
+ for(keyword = ptr; length && *ptr != '='; length--, ptr++);
+
+ /* length should be 2 or more, given it includes the = and newline */
+ if(length < 2)
+ goto failed;
+
+ /* Terminate the keyword string */
+ *ptr++ = '\0';
+ length --;
+
+ /* Store value */
+ value = ptr;
+
+ /* Check the string is terminated by '\n' */
+ if(value[length - 1] != '\n')
+ goto failed;
+
+ /* Replace the '\n' with a nul terminator.
+ * In some tars the value may be binary, and include nul
+ * characters, and so we have to not treat it as a
+ * null terminated string then, and so also store
+ * the length of the string */
+ value[length - 1] = '\0';
+ vsize = length - 1;
+
+ /* Evaluate keyword */
+ if(strcmp(keyword, "size") == 0) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '\0')
+ goto failed;
+ file->buf.st_size = number;
+ file->have_size = TRUE;
+ } else if(strcmp(keyword, "uid") == 0) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '\0')
+ goto failed;
+ file->buf.st_uid = number;
+ file->have_uid = TRUE;
+ } else if(strcmp(keyword, "gid") == 0) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '\0')
+ goto failed;
+ file->buf.st_gid = number;
+ file->have_gid = TRUE;
+ } else if(strcmp(keyword, "mtime") == 0) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '.')
+ goto failed;
+ file->buf.st_mtime = number;
+ file->have_mtime = TRUE;
+ } else if(strcmp(keyword, "uname") == 0)
+ file->uname = strdup(value);
+ else if(strcmp(keyword, "gname") == 0)
+ file->gname = strdup(value);
+ else if(strcmp(keyword, "path") == 0)
+ file->pathname = strdup(skip_components(value, vsize, NULL));
+ else if(strcmp(keyword, "linkpath") == 0)
+ file->link = strdup(value);
+ else if(strcmp(keyword, "GNU.sparse.major") == 0) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '\0')
+ goto failed;
+ major = number;
+ } else if(strcmp(keyword, "GNU.sparse.minor") == 0) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '\0')
+ goto failed;
+ minor = number;
+ } else if(strcmp(keyword, "GNU.sparse.realsize") == 0) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '\0')
+ goto failed;
+ realsize = number;
+ } else if(strcmp(keyword, "GNU.sparse.name") == 0)
+ name = strdup(value);
+ else if(strcmp(keyword, "GNU.sparse.size") == 0) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '\0')
+ goto failed;
+ realsize = number;
+ old_gnu_pax = 1;
+ } else if(strcmp(keyword, "GNU.sparse.numblocks") == 0 && old_gnu_pax == 1) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '\0')
+ goto failed;
+ file->map = malloc(number * sizeof(struct file_map));
+ if(file->map == NULL)
+ MEM_ERROR();
+ map_entries = number;
+ cur_entry = 0;
+ old_gnu_pax = 2;
+ } else if(strcmp(keyword, "GNU.sparse.offset") == 0 && old_gnu_pax == 2 && old_gnu_ver != 1) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '\0')
+ goto failed;
+ if(cur_entry < map_entries)
+ file->map[cur_entry].offset = number;
+ old_gnu_ver = 0;
+ } else if(strcmp(keyword, "GNU.sparse.numbytes") == 0 && old_gnu_pax == 2 && old_gnu_ver != 1) {
+ res = sscanf(value, "%lld %n", &number, &bytes);
+ if(res < 1 || value[bytes] != '\0')
+ goto failed;
+ if(cur_entry < map_entries)
+ file->map[cur_entry++].number = number;
+ old_gnu_ver = 0;
+ } else if(strcmp(keyword, "GNU.sparse.map") == 0 && old_gnu_pax == 2 && old_gnu_ver != 0) {
+ res = read_sparse_value(file, value, map_entries);
+ if(res == FALSE)
+ goto failed;
+ old_gnu_ver = 1;
+ } else if(strncmp(keyword, "LIBARCHIVE.xattr.", strlen("LIBARCHIVE.xattr.")) == 0)
+ read_tar_xattr(keyword + strlen("LIBARCHIVE.xattr."), value, strlen(value), ENCODING_BASE64, file);
+ else if(strncmp(keyword, "SCHILY.xattr.", strlen("SCHILY.xattr.")) == 0)
+ read_tar_xattr(keyword + strlen("SCHILY.xattr."), value, vsize, ENCODING_BINARY, file);
+ else if(strcmp(keyword, "GNU.sparse.numblocks") != 0 &&
+ strcmp(keyword, "GNU.sparse.offset") != 0 &&
+ strcmp(keyword, "GNU.sparse.numbytes") != 0 &&
+ strcmp(keyword, "GNU.sparse.map") != 0 &&
+ strcmp(keyword, "atime") != 0 &&
+ strcmp(keyword, "ctime") != 0 &&
+ strcmp(keyword, "comment") != 0)
+ ERROR("Unrecognised keyword \"%s\" in pax header, ignoring\n", keyword);
+
+ ptr += length;
+ }
+
+ /* Is this a sparse file, and version (1.0)?
+ * If it is flag it, and the sparse map will be read
+ * later */
+ if(!old_gnu_pax && major != -1 && minor != -1 && realsize != -1 && name) {
+ if(major == 1 && minor == 0) {
+ file->realsize = realsize;
+ file->sparse_pax = 2;
+ file->pathname = name;
+ } else {
+ ERROR("Pax sparse file not Major 1, Minor 0!\n");
+ free(name);
+ }
+ }
+
+ /* Is this an older sparse format? */
+ if(old_gnu_pax == 2 && (old_gnu_ver == 0 || (old_gnu_ver == 1 && name))) {
+ file->realsize = realsize;
+ file->map_entries = map_entries;
+ file->sparse_pax = 1;
+ if(old_gnu_ver == 1)
+ file->pathname = name;
+ }
+
+ free(data);
+ return TRUE;
+
+failed:
+ ERROR("Failed to parse pax header\n");
+ free(data);
+ return FALSE;
+}
+
+
+static int check_sparse_map(struct file_map *map, int map_entries, long long size, long long realsize)
+{
+ long long total_data = 0;
+ long long total_sparse = map[0].offset;
+ int i;
+
+ for(i = 0; i < map_entries; i++) {
+ if(i > 0)
+ total_sparse += (map[i].offset - (map[i - 1].offset + map[i - 1].number));
+ total_data += map[i].number;
+ }
+
+ return total_data == size && total_data + total_sparse == realsize;
+}
+
+
+static struct file_map *read_sparse_headers(struct tar_file *file, struct short_sparse_header *short_header, int *entries)
+{
+ struct long_sparse_header long_header;
+ int res, i, map_entries, isextended;
+ struct file_map *map = NULL;
+ long long realsize;
+
+ realsize = read_number(short_header->realsize, 12);
+ if(realsize == -1) {
+ ERROR("Failed to read offset from sparse header\n");
+ goto failed;
+ }
+
+ map = malloc(4 * sizeof(struct file_map));
+ if(map == NULL)
+ MEM_ERROR();
+
+ /* There should always be at least one sparse entry */
+ map[0].offset = read_number(short_header->sparse[0].offset, 12);
+ if(map[0].offset == -1) {
+ ERROR("Failed to read offset from sparse header\n");
+ goto failed;
+ }
+
+ map[0].number = read_number(short_header->sparse[0].number, 12);
+ if(map[0].number == -1) {
+ ERROR("Failed to read number from sparse header\n");
+ goto failed;
+ }
+
+ /* There may be three more sparse entries in this short header.
+ * An offset of 0 means unused */
+ for(i = 1; i < 4; i++) {
+ map[i].offset = read_number(short_header->sparse[i].offset, 12);
+ if(map[i].offset == -1) {
+ ERROR("Failed to read offset from sparse header\n");
+ goto failed;
+ }
+
+ if(map[i].offset == 0)
+ break;
+
+ map[i].number = read_number(short_header->sparse[i].number, 12);
+ if(map[i].number == -1) {
+ ERROR("Failed to read number from sparse header\n");
+ goto failed;
+ }
+ }
+
+ /* If we've read two or less entries, then we expect the isextended
+ * entry to be FALSE */
+ isextended = read_number(&short_header->isextended, 1);
+ if(i < 3 && isextended) {
+ ERROR("Invalid sparse header\n");
+ goto failed;
+ }
+
+ map_entries = i;
+
+ while(isextended) {
+ res = read_bytes(STDIN_FILENO, &long_header, 512);
+ if(res < 512) {
+ if(res != -1)
+ ERROR("Unexpected EOF (end of file), the tarfile appears to be truncated or corrupted\n");
+ goto failed;
+ }
+
+ map = realloc(map, (map_entries + 21) * sizeof(struct file_map));
+ if(map == NULL)
+ MEM_ERROR();
+
+ /* There may be up to 21 sparse entries in this long header.
+ * An offset of 0 means unused */
+ for(i = map_entries; i < (map_entries + 21); i++) {
+ map[i].offset = read_number(long_header.sparse[i - map_entries].offset, 12);
+ if(map[i].offset == -1) {
+ ERROR("Failed to read offset from sparse header\n");
+ goto failed;
+ }
+
+ if(map[i].offset == 0)
+ break;
+
+ map[i].number = read_number(long_header.sparse[i - map_entries].number, 12);
+ if(map[i].number == -1) {
+ ERROR("Failed to read number from sparse header\n");
+ goto failed;
+ }
+ }
+
+ /* If we've read less than 21 entries, then we expect the isextended
+ * entry to be FALSE */
+ isextended = read_number(&long_header.isextended, 1);
+ if(i < (map_entries + 21) && isextended) {
+ ERROR("Invalid sparse header\n");
+ goto failed;
+ }
+
+ map_entries = i;
+ }
+
+ res = check_sparse_map(map, map_entries, file->buf.st_size, realsize);
+ if(res == FALSE) {
+ ERROR("Sparse file map inconsistent\n");
+ goto failed;
+ }
+
+ *entries = map_entries;
+ file->buf.st_size = realsize;
+
+ return map;
+
+failed:
+ free(map);
+ return NULL;
+}
+
+
+static struct file_map *read_sparse_map(struct tar_file *file, int *entries)
+{
+ int map_entries, bytes, size;
+ struct file_map *map = NULL;
+ char buffer[529], *src = buffer;
+ long long offset = 0, number, res;
+ int atoffset = TRUE, i = 0;
+
+ res = read_bytes(STDIN_FILENO, buffer, 512);
+ if(res < 512) {
+ if(res != -1)
+ ERROR("Unexpected EOF (end of file), the tarfile appears to be truncated or corrupted\n");
+ goto failed;
+ }
+
+ /* First number is the number of map entries */
+ map_entries = read_decimal(src, 512, &bytes);
+ if(map_entries < 0) {
+ ERROR("Could not parse Pax sparse map data\n");
+ goto failed;
+ }
+
+ src += bytes;
+ size = 512 - bytes;
+ file->buf.st_size -= 512;
+
+ while(i < map_entries) {
+ res = read_decimal(src, size, &bytes);
+ if(res == -1) {
+ ERROR("Could not parse Pax sparse map data\n");
+ goto failed;
+ }
+
+ if(res == -2) {
+ /* Out of data */
+ if(size >= 18) {
+ /* Too large block of '0' .. '9' without a '\n' */
+ ERROR("Could not parse Pax sparse map data\n");
+ goto failed;
+ }
+
+ memmove(buffer, src, size);
+ res = read_bytes(STDIN_FILENO, buffer + size, 512);
+ if(res < 512) {
+ if(res != -1)
+ ERROR("Unexpected EOF (end of file), the tarfile appears to be truncated or corrupted\n");
+ goto failed;
+ }
+
+ src = buffer;
+ size += 512;
+ file->buf.st_size -= 512;
+ continue;
+ }
+
+ src += bytes;
+ size -= bytes;
+
+ if(atoffset)
+ offset = res;
+ else {
+ number = res;
+
+ if(i % 50 == 0) {
+ map = realloc(map, (i + 50) * sizeof(struct file_map));
+ if(map == NULL)
+ MEM_ERROR();
+ }
+
+ map[i].offset = offset;
+ map[i++].number = number;
+ }
+
+ atoffset = !atoffset;
+ }
+
+ *entries = map_entries;
+ return map;
+
+failed:
+ free(map);
+ return NULL;
+}
+
+
+static void copy_tar_header(struct tar_file *dest, struct tar_file *source)
+{
+ memcpy(dest, source, sizeof(struct tar_file));
+ if(source->pathname)
+ dest->pathname = strdup(source->pathname);
+ if(source->link)
+ dest->link = strdup(source->link);
+ if(source->uname)
+ dest->uname = strdup(source->uname);
+ if(source->gname)
+ dest->gname = strdup(source->gname);
+}
+
+
+static int skip_to_valid_header(struct tar_header *header)
+{
+ int res, first = TRUE;
+
+ while(1) {
+ res = read_bytes(STDIN_FILENO, header, 512);
+
+ if(res < 512) {
+ if(res == 0)
+ return 0;
+ if(res != -1)
+ ERROR("Unexpected EOF (end of file), the tarfile appears to be truncated or corrupted\n");
+ return -1;
+ }
+
+ if(all_zero(header))
+ continue;
+
+ if(checksum_matches(header, TRUE))
+ return 1;
+
+ if(first) {
+ ERROR("sqfstar: Skipping to next header\n");
+ first = FALSE;
+ }
+ }
+}
+
+
+static struct tar_file *read_tar_header(int *status)
+{
+ struct tar_header header;
+ struct tar_file *file;
+ long long res;
+ int size, type;
+ char *filename, *user, *group;
+ static struct tar_file *global = NULL;
+
+ file = malloc(sizeof(struct tar_file));
+ if(file == NULL)
+ MEM_ERROR();
+
+ if(global)
+ copy_tar_header(file, global);
+ else
+ memset(file, 0, sizeof(struct tar_file));
+
+again:
+ res = read_bytes(STDIN_FILENO, &header, 512);
+ if(res < 512) {
+ if(res == 0)
+ goto eof;
+ if(res != -1)
+ ERROR("Unexpected EOF (end of file), the tarfile appears to be truncated or corrupted\n");
+ goto failed;
+ }
+
+ if(all_zero(&header)) {
+ if(ignore_zeros) {
+ res = skip_to_valid_header(&header);
+ if(res == 0)
+ goto eof;
+ if(res == -1)
+ goto failed;
+ } else
+ goto eof;
+ } else if(checksum_matches(&header, FALSE) == FALSE) {
+ ERROR("Tar header checksum does not match!\n");
+ goto failed;
+ }
+
+ /* Read filesize */
+ if(file->have_size == FALSE) {
+ res = read_number(header.size, 12);
+ if(res == -1) {
+ ERROR("Failed to read file size from tar header\n");
+ goto failed;
+ }
+ file->buf.st_size = res;
+ }
+
+ switch(header.type) {
+ case GNUTAR_SPARSE:
+ file->map = read_sparse_headers(file, (struct short_sparse_header *) &header, &file->map_entries);
+ if(file->map == NULL)
+ goto failed;
+ /* fall through */
+ case TAR_NORMAL1:
+ case TAR_NORMAL2:
+ case TAR_NORMAL3:
+ type = S_IFREG;
+ break;
+ case TAR_DIR:
+ type = S_IFDIR;
+ break;
+ case TAR_SYM:
+ type = S_IFLNK;
+ break;
+ case TAR_HARD:
+ type = S_IFHRD;
+ break;
+ case TAR_CHAR:
+ type = S_IFCHR;
+ break;
+ case TAR_BLOCK:
+ type = S_IFBLK;
+ break;
+ case TAR_FIFO:
+ type = S_IFIFO;
+ break;
+ case TAR_XHDR:
+ case SOLARIS_XHDR:
+ res = read_pax_header(file, file->buf.st_size);
+ if(res == FALSE) {
+ ERROR("Failed to read pax header\n");
+ goto failed;
+ }
+ goto again;
+ case TAR_GXHDR:
+ if(global == NULL) {
+ global = malloc(sizeof(struct tar_file));
+ if(global == NULL)
+ MEM_ERROR();
+ memset(global, 0, sizeof(struct tar_file));
+ }
+ res = read_pax_header(global, file->buf.st_size);
+ if(res == FALSE) {
+ ERROR("Failed to read pax header\n");
+ goto failed;
+ }
+ /* file is now out of date, and needs to be
+ * (re-)synced with the global header */
+ free(file->pathname);
+ free(file->link);
+ free(file->uname);
+ free(file->gname);
+ copy_tar_header(file, global);
+ goto again;
+ case GNUTAR_LONG_NAME:
+ file->pathname = read_long_string(file->buf.st_size, TRUE);
+ if(file->pathname == NULL) {
+ ERROR("Failed to read GNU Long Name\n");
+ goto failed;
+ }
+ goto again;
+ case GNUTAR_LONG_LINK:
+ file->link = read_long_string(file->buf.st_size, FALSE);
+ if(file->link == NULL) {
+ ERROR("Failed to read GNU Long Name\n");
+ goto failed;
+ }
+ goto again;
+ default:
+ ERROR("Unhandled tar type in header 0x%x - ignoring\n", header.type);
+ goto ignored;
+ }
+
+ /* Process filename - skip any leading slashes or ./ or ../ */
+ if(file->pathname == NULL && header.prefix[0] != '\0') {
+ int length1, length2;
+
+ filename = skip_components(header.prefix, 155, &size);
+ length1 = strnlen(filename, size);
+ length2 = strnlen(header.name, 100);
+ file->pathname = malloc(length1 + length2 + 2);
+ if(file->pathname == NULL)
+ MEM_ERROR();
+
+ memcpy(file->pathname, filename, length1);
+ file->pathname[length1] = '/';
+ memcpy(file->pathname + length1 + 1, header.name, length2);
+ file->pathname[length1 + length2 + 1] = '\0';
+ } else if (file->pathname == NULL) {
+ filename = skip_components(header.name, 100, &size);
+ file->pathname = strndup(filename, size);
+ }
+
+ /* Ignore empty filenames */
+ if(strlen(file->pathname) == 0) {
+ ERROR("Empty tar filename after skipping leading /, ./, or ../, ignoring\n");
+ goto ignored;
+ }
+
+ /* Read mtime */
+ if(file->have_mtime == FALSE) {
+ res = read_number(header.mtime, 12);
+ if(res == -1) {
+ ERROR("Failed to read file mtime from tar header\n");
+ goto failed;
+ }
+ file->buf.st_mtime = res;
+ }
+
+ /* Read mode and file type */
+ res = read_number(header.mode, 8);
+ if(res == -1) {
+ ERROR("Failed to read file mode from tar header\n");
+ goto failed;
+ }
+ file->buf.st_mode = res;
+
+ /* V7 and others used to append a trailing '/' to indicate a
+ * directory */
+ if(file->pathname[strlen(file->pathname) - 1] == '/') {
+ file->pathname[strlen(file->pathname) - 1] = '\0';
+ type = S_IFDIR;
+ }
+
+ file->buf.st_mode |= type;
+
+ /* Get user information - if file->uname non NULL (from PAX header),
+ * use that if recognised by the system, otherwise if header.user
+ * filled, and it is recognised by the system use that, otherwise
+ * fallback to using uid, either from PAX header (if have_uid TRUE),
+ * or header.uid */
+ res = -1;
+ if(file->uname)
+ user = file->uname;
+ else
+ user = strndup(header.user, 32);
+
+ if(strlen(user)) {
+ struct passwd *pwuid = getpwnam(user);
+ if(pwuid)
+ res = pwuid->pw_uid;
+ }
+
+ if(res == -1) {
+ if(file->have_uid == FALSE) {
+ res = read_number(header.uid, 8);
+ if(res == -1) {
+ ERROR("Failed to read file uid from tar header\n");
+ goto failed;
+ }
+ file->buf.st_uid = res;
+ }
+ } else
+ file->buf.st_uid = res;
+
+ free(user);
+
+ /* Get group information - if file->gname non NULL (from PAX header),
+ * use that if recognised by the system, otherwise if header.group
+ * filled, and it is recognised by the system use that, otherwise
+ * fallback to using gid, either from PAX header (if have_gid TRUE),
+ * or header.gid */
+ res = -1;
+ if(file->gname)
+ group = file->gname;
+ else
+ group = strndup(header.group, 32);
+
+ if(strlen(group)) {
+ struct group *grgid = getgrnam(group);
+ if(grgid)
+ res = grgid->gr_gid;
+ }
+
+ if(res == -1) {
+ if(file->have_gid == FALSE) {
+ res = read_number(header.gid, 8);
+ if(res == -1) {
+ ERROR("Failed to read file gid from tar header\n");
+ goto failed;
+ }
+ file->buf.st_gid = res;
+ }
+ } else
+ file->buf.st_gid = res;
+
+ free(group);
+
+ /* Read major and minor for device files */
+ if(type == S_IFCHR || type == S_IFBLK) {
+ int major, minor;
+
+ major = read_number(header.major, 8);
+ if(major == -1) {
+ ERROR("Failed to read device major tar header\n");
+ goto failed;
+ }
+
+ minor = read_number(header.minor, 8);
+ if(minor == -1) {
+ ERROR("Failed to read device minor from tar header\n");
+ goto failed;
+ }
+ file->buf.st_rdev = (major << 8) | (minor & 0xff) | ((minor & ~0xff) << 12);
+ }
+
+ /* Handle symbolic links */
+ if(type == S_IFLNK) {
+ /* Permissions on symbolic links are always rwxrwxrwx */
+ file->buf.st_mode = 0777 | S_IFLNK;
+
+ if(file->link == FALSE)
+ file->link = strndup(header.link, 100);
+ }
+
+ /* Handle hard links */
+ if(type == S_IFHRD) {
+ if(file->link) {
+ char *link = skip_components(file->link, strlen(file->link), NULL);
+
+ if(link != file->link) {
+ char *old = file->link;
+
+ file->link = strdup(link);
+ free(old);
+ }
+ } else {
+ filename = skip_components(header.link, 100, &size);
+ file->link = strndup(filename, size);
+ }
+ }
+
+ *status = TAR_OK;
+ return file;
+
+failed:
+ free_tar_xattrs(file);
+ free(file->pathname);
+ free(file->link);
+ free(file);
+ *status = TAR_ERROR;
+ return NULL;
+
+ignored:
+ if(file->buf.st_size) {
+ /* Skip any data blocks */
+ long long size = file->buf.st_size;
+
+ while(size > 0) {
+ res = read_bytes(STDIN_FILENO, &header, 512);
+ if(res < 512) {
+ if(res != -1)
+ ERROR("Unexpected EOF (end of file), the tarfile appears to be truncated or corrupted\n");
+ goto failed;
+ }
+ size -= 512;
+ }
+ }
+
+ free(file->pathname);
+ free(file->link);
+ free(file);
+ *status = TAR_IGNORED;
+ return NULL;
+
+eof:
+ *status = TAR_EOF;
+ free(file);
+ return NULL;
+}
+
+
+void read_tar_file()
+{
+ struct tar_file *tar_file;
+ int status, res;
+
+ while(1) {
+ struct file_buffer *file_buffer;
+
+ file_buffer = malloc(sizeof(struct file_buffer));
+ if(file_buffer == NULL)
+ MEM_ERROR();
+
+ while(1) {
+ tar_file = read_tar_header(&status);
+ if(status != TAR_IGNORED)
+ break;
+ }
+
+ if(status == TAR_ERROR)
+ BAD_ERROR("Error occurred reading tar file. Aborting\n");
+
+ /* If Pax 1.0 sparse file, read the map data now */
+ if(tar_file && tar_file->sparse_pax == 2) {
+ tar_file->map = read_sparse_map(tar_file, &tar_file->map_entries);
+ if(tar_file->map == NULL)
+ BAD_ERROR("Error occurred reading tar file. Aborting\n");
+ }
+
+ /* Check Pax sparse map for consistency */
+ if(tar_file && tar_file->sparse_pax) {
+ res = check_sparse_map(tar_file->map, tar_file->map_entries, tar_file->buf.st_size, tar_file->realsize);
+ if(res == FALSE)
+ BAD_ERROR("Sparse file map inconsistent. Aborting\n");
+ tar_file->buf.st_size = tar_file->realsize;
+ }
+
+ if(tar_file && (tar_file->buf.st_mode & S_IFMT) == S_IFREG)
+ progress_bar_size((tar_file->buf.st_size + block_size - 1)
+ >> block_log);
+
+ file_buffer->cache = NULL;
+ file_buffer->fragment = FALSE;
+ file_buffer->tar_file = tar_file;
+ file_buffer->sequence = sequence_count ++;
+ seq_queue_put(to_main, file_buffer);
+
+ if(status == TAR_EOF)
+ break;
+
+ if(S_ISREG(tar_file->buf.st_mode))
+ read_tar_data(tar_file);
+ }
+}
+
+
+squashfs_inode process_tar_file(int progress)
+{
+ struct stat buf;
+ struct dir_info *new;
+ struct dir_ent *dir_ent;
+ struct tar_file *tar_file;
+ struct file_buffer *file_buffer;
+
+ queue_put(to_reader, NULL);
+ set_progressbar_state(progress);
+
+ while(1) {
+ struct inode_info *link = NULL;
+
+ file_buffer = seq_queue_get(to_main);
+ if(file_buffer->tar_file == NULL)
+ break;
+
+ tar_file = file_buffer->tar_file;
+
+ if(S_ISHRD(tar_file->buf.st_mode)) {
+ /* Hard link, need to resolve where it points to, and
+ * replace with a reference to that inode */
+ struct dir_ent *entry = lookup_pathname(root_dir, tar_file->link);
+ if(entry== NULL) {
+ ERROR("Could not resolve hardlink %s, file %s doesn't exist\n", tar_file->pathname, tar_file->link);
+ free(file_buffer);
+ free(tar_file->pathname);
+ free(tar_file->link);
+ free(tar_file);
+ continue;
+ }
+
+ if(entry->inode == NULL || S_ISDIR(entry->inode->buf.st_mode)) {
+ ERROR("Could not resolve hardlink %s, because %s is a directory\n", tar_file->pathname, tar_file->link);
+ free(file_buffer);
+ free(tar_file->pathname);
+ free(tar_file->link);
+ free(tar_file);
+ continue;
+ }
+
+ link = entry->inode;
+ free(tar_file->link);
+ tar_file->link = NULL;
+ }
+
+ new = add_tarfile(root_dir, tar_file->pathname, "",
+ tar_file, paths, 1, &dir_ent, link);
+
+ if(new) {
+ int duplicate_file;
+ root_dir = new;
+
+ if(S_ISREG(tar_file->buf.st_mode) && dir_ent->inode->read == FALSE) {
+ update_info(dir_ent);
+ tar_file->file = write_file(dir_ent, &duplicate_file);
+ dir_ent->inode->read = TRUE;
+ INFO("file %s, uncompressed size %lld bytes %s\n", tar_file->pathname,
+ (long long) tar_file->buf.st_size, duplicate_file ? "DUPLICATE" : "");
+ }
+
+ if(link) {
+ if(no_hardlinks)
+ INFO("file %s, uncompressed size %lld bytes DUPLICATE\n", tar_file->pathname,
+ (long long) link->buf.st_size);
+ else
+ link->nlink ++;
+ free(tar_file->pathname);
+ free(tar_file);
+ }
+ } else if(S_ISREG(tar_file->buf.st_mode))
+ skip_file(file_buffer->tar_file);
+
+ free(file_buffer);
+ }
+
+ free(file_buffer);
+
+ if(root_dir)
+ fixup_tree(root_dir);
+ else
+ root_dir = scan1_opendir("", "", 0);
+
+ /* Create root directory dir_ent and associated inode, and connect
+ * it to the root directory dir_info structure */
+ dir_ent = create_dir_entry("", NULL, "", scan1_opendir("", "", 0));
+
+ memset(&buf, 0, sizeof(buf));
+ if(root_mode_opt)
+ buf.st_mode = root_mode | S_IFDIR;
+ else
+ buf.st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR;
+ if(root_uid_opt)
+ buf.st_uid = root_uid;
+ else
+ buf.st_uid = getuid();
+ if(root_gid_opt)
+ buf.st_gid = root_gid;
+ else
+ buf.st_gid = getgid();
+ if(root_time_opt)
+ buf.st_mtime = root_time;
+ else
+ buf.st_mtime = time(NULL);
+ if(pseudo_override && global_uid_opt)
+ buf.st_uid = global_uid;
+
+ if(pseudo_override && global_gid_opt)
+ buf.st_gid = global_gid;
+ buf.st_dev = 0;
+ buf.st_ino = 0;
+ dir_ent->inode = lookup_inode(&buf);
+ dir_ent->inode->dummy_root_dir = TRUE;
+ dir_ent->dir = root_dir;
+ root_dir->dir_ent = dir_ent;
+
+ return do_directory_scans(dir_ent, progress);
+}
diff --git a/squashfs-tools/tar.h b/squashfs-tools/tar.h
new file mode 100644
index 0000000..1de762d
--- /dev/null
+++ b/squashfs-tools/tar.h
@@ -0,0 +1,153 @@
+#ifndef TAR_H
+#define TAR_H
+
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * tar.h
+ */
+
+struct tar_header {
+ union {
+ unsigned char udata[512];
+ signed char sdata[512];
+ struct {
+ char name[100];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char size[12];
+ char mtime[12];
+ char checksum[8];
+ char type;
+ char link[100];
+ char magic[8];
+ char user[32];
+ char group[32];
+ char major[8];
+ char minor[8];
+ char prefix[155];
+ };
+ };
+};
+
+
+struct sparse_entry {
+ char offset[12];
+ char number[12];
+};
+
+
+struct short_sparse_header {
+ char pad[386];
+ struct sparse_entry sparse[4];
+ char isextended;
+ char realsize[12];
+};
+
+
+struct long_sparse_header {
+ struct sparse_entry sparse[21];
+ char isextended;
+ char pad[7];
+};
+
+
+struct file_map {
+ long long offset;
+ long long number;
+};
+
+
+struct tar_file {
+ long long realsize;
+ struct stat buf;
+ struct file_info *file;
+ struct xattr_list *xattr_list;
+ struct file_map *map;
+ char *pathname;
+ char *link;
+ char *uname;
+ char *gname;
+ int xattrs;
+ int map_entries;
+ char have_size;
+ char have_uid;
+ char have_gid;
+ char have_mtime;
+ char sparse_pax;
+};
+
+#define IS_TARFILE(a) (a->tarfile)
+#define TAR_NORMAL1 '0'
+#define TAR_NORMAL2 '\0'
+#define TAR_HARD '1'
+#define TAR_SYM '2'
+#define TAR_CHAR '3'
+#define TAR_BLOCK '4'
+#define TAR_DIR '5'
+#define TAR_FIFO '6'
+#define TAR_NORMAL3 '7'
+#define TAR_GXHDR 'g'
+#define TAR_XHDR 'x'
+
+#define GNUTAR_LONG_NAME 'L'
+#define GNUTAR_LONG_LINK 'K'
+#define GNUTAR_SPARSE 'S'
+
+#define SOLARIS_XHDR 'X'
+
+#define V7_MAGIC "\0\0\0\0\0\0\0"
+#define GNU_MAGIC "ustar "
+#define USTAR_MAGIC "ustar\00000"
+
+#define S_IFHRD S_IFMT
+
+#define S_ISHRD(a) ((a & S_IFMT) == S_IFHRD)
+
+#define TAR_OK 0
+#define TAR_EOF 1
+#define TAR_ERROR 2
+#define TAR_IGNORED 3
+
+#define ENCODING_BASE64 0
+#define ENCODING_BINARY 1
+
+extern void read_tar_file();
+extern squashfs_inode process_tar_file(int progress);
+extern int ignore_zeros;
+extern int default_uid_opt;
+extern unsigned int default_uid;
+extern int default_gid_opt;
+extern unsigned int default_gid;
+extern int default_mode_opt;
+extern mode_t default_mode;
+
+#ifdef XATTR_SUPPORT
+extern int xattr_get_prefix(struct xattr_list *, char *);
+extern void read_tar_xattr(char *, char *, int, int, struct tar_file *);
+extern void free_tar_xattrs(struct tar_file *);
+extern int read_xattrs_from_tarfile(struct inode_info *, struct xattr_list **);
+#else
+#define read_tar_xattr(a, b, c, d, e)
+#define free_tar_xattrs(a)
+#define read_xattrs_from_tarfile(a, b) 0
+#endif
+#endif
diff --git a/squashfs-tools/tar_xattr.c b/squashfs-tools/tar_xattr.c
new file mode 100644
index 0000000..fc9eef5
--- /dev/null
+++ b/squashfs-tools/tar_xattr.c
@@ -0,0 +1,122 @@
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * tar_xattr.c
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <regex.h>
+
+#include "squashfs_fs.h"
+#include "mksquashfs.h"
+#include "mksquashfs_error.h"
+#include "tar.h"
+#include "xattr.h"
+
+#define TRUE 1
+#define FALSE 0
+
+extern regex_t *xattr_exclude_preg;
+extern regex_t *xattr_include_preg;
+
+
+void read_tar_xattr(char *name, char *value, int size, int encoding, struct tar_file *file)
+{
+ char *data;
+ struct xattr_list *xattr;
+ int i;
+
+ /* Some tars output both LIBARCHIVE and SCHILY xattrs, which
+ * will lead to multiple definitions of the same xattr.
+ * So check that this xattr hasn't already been defined */
+ for(i = 0; i < file->xattrs; i++)
+ if(strcmp(name, file->xattr_list[i].full_name) == 0)
+ return;
+
+ if(xattr_exclude_preg) {
+ int res = regexec(xattr_exclude_preg, name, (size_t) 0, NULL, 0);
+
+ if(res == 0)
+ return;
+ }
+
+ if(xattr_include_preg) {
+ int res = regexec(xattr_include_preg, name, (size_t) 0, NULL, 0);
+
+ if(res)
+ return;
+ }
+
+ if(encoding == ENCODING_BASE64) {
+ data = base64_decode(value, size, &size);
+ if(data == NULL) {
+ ERROR("Invalid LIBARCHIVE xattr base64 value, ignoring\n");
+ return;
+ }
+ } else {
+ data = malloc(size);
+ if(data == NULL)
+ MEM_ERROR();
+ memcpy(data, value, size);
+ }
+
+ file->xattr_list = realloc(file->xattr_list, (file->xattrs + 1) *
+ sizeof(struct xattr_list));
+ if(file->xattr_list == NULL)
+ MEM_ERROR();
+
+ xattr = &file->xattr_list[file->xattrs];
+
+ xattr->type = xattr_get_prefix(xattr, name);
+ if(xattr->type == -1) {
+ ERROR("Unrecognised tar xattr prefix %s, ignoring\n", name);
+ free(data);
+ return;
+ }
+
+ xattr->value = data;
+ xattr->vsize = size;
+ file->xattrs ++;
+}
+
+
+int read_xattrs_from_tarfile(struct inode_info *inode, struct xattr_list **xattr_list)
+{
+ if(inode->tar_file) {
+ *xattr_list = inode->tar_file->xattr_list;
+ return inode->tar_file->xattrs;
+ } else
+ return 0;
+}
+
+
+void free_tar_xattrs(struct tar_file *file)
+{
+ int i;
+
+ for(i = 0; i < file->xattrs; i++)
+ free(file->xattr_list[i].full_name);
+
+ free(file->xattr_list);
+}
diff --git a/squashfs-tools/unsquash-1.c b/squashfs-tools/unsquash-1.c
new file mode 100644
index 0000000..27b2766
--- /dev/null
+++ b/squashfs-tools/unsquash-1.c
@@ -0,0 +1,582 @@
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2011, 2012, 2019, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquash-1.c
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_compat.h"
+#include "compressor.h"
+
+static unsigned int *uid_table, *guid_table;
+static squashfs_operations ops;
+
+static void read_block_list(unsigned int *block_list, long long start,
+ unsigned int offset, int blocks)
+{
+ unsigned short *source;
+ int i, res;
+
+ TRACE("read_block_list: blocks %d\n", blocks);
+
+ source = malloc(blocks * sizeof(unsigned short));
+ if(source == NULL)
+ MEM_ERROR();
+
+ if(swap) {
+ char *swap_buff;
+
+ swap_buff = malloc(blocks * sizeof(unsigned short));
+ if(swap_buff == NULL)
+ MEM_ERROR();
+
+ res = read_inode_data(swap_buff, &start, &offset, blocks * sizeof(unsigned short));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_block_list: failed to read "
+ "inode index %lld:%d\n", start, offset);
+ SQUASHFS_SWAP_SHORTS_3(source, swap_buff, blocks);
+ free(swap_buff);
+ } else {
+ res = read_inode_data(source, &start, &offset, blocks * sizeof(unsigned short));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_block_list: failed to read "
+ "inode index %lld:%d\n", start, offset);
+ }
+
+ for(i = 0; i < blocks; i++)
+ block_list[i] = SQUASHFS_COMPRESSED_SIZE(source[i]) |
+ (SQUASHFS_COMPRESSED(source[i]) ? 0 :
+ SQUASHFS_COMPRESSED_BIT_BLOCK);
+ free(source);
+}
+
+
+static struct inode *read_inode(unsigned int start_block, unsigned int offset)
+{
+ static union squashfs_inode_header_1 header;
+ long long start = sBlk.s.inode_table_start + start_block;
+ long long st = start;
+ unsigned int off = offset, uid;
+ static struct inode i;
+ int res;
+
+ TRACE("read_inode: reading inode [%d:%d]\n", start_block, offset);
+
+ if(swap) {
+ squashfs_base_inode_header_1 sinode;
+ res = read_inode_data(&sinode, &st, &off, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_BASE_INODE_HEADER_1(&header.base, &sinode,
+ sizeof(squashfs_base_inode_header_1));
+ } else
+ res = read_inode_data(&header.base, &st, &off, sizeof(header.base));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read inode %lld:%d\n", st, off);
+
+ uid = (header.base.inode_type - 1) / SQUASHFS_TYPES * 16 + header.base.uid;
+
+ if(uid >= sBlk.no_uids)
+ EXIT_UNSQUASH("File system corrupted - uid index in inode too large (uid: %u)\n", uid);
+
+ i.uid = (uid_t) uid_table[uid];
+
+ if(header.base.inode_type == SQUASHFS_IPC_TYPE) {
+ squashfs_ipc_inode_header_1 *inodep = &header.ipc;
+
+ if(swap) {
+ squashfs_ipc_inode_header_1 sinodep;
+ res = read_inode_data(&sinodep, &start, &offset, sizeof(sinodep));
+ if(res)
+ SQUASHFS_SWAP_IPC_INODE_HEADER_1(inodep, &sinodep);
+ } else
+ res = read_inode_data(inodep, &start, &offset, sizeof(*inodep));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ if(inodep->type == SQUASHFS_SOCKET_TYPE) {
+ i.mode = S_IFSOCK | header.base.mode;
+ i.type = SQUASHFS_SOCKET_TYPE;
+ } else {
+ i.mode = S_IFIFO | header.base.mode;
+ i.type = SQUASHFS_FIFO_TYPE;
+ }
+
+ uid = inodep->offset * 16 + inodep->uid;
+ if(uid >= sBlk.no_uids)
+ EXIT_UNSQUASH("File system corrupted - uid index in inode too large (uid: %u)\n", uid);
+
+ i.uid = (uid_t) uid_table[uid];
+ } else {
+ i.mode = lookup_type[(header.base.inode_type - 1) %
+ SQUASHFS_TYPES + 1] | header.base.mode;
+ i.type = (header.base.inode_type - 1) % SQUASHFS_TYPES + 1;
+ }
+
+ i.xattr = SQUASHFS_INVALID_XATTR;
+
+ if(header.base.guid == 15)
+ i.gid = i.uid;
+ else if(header.base.guid >= sBlk.no_guids)
+ EXIT_UNSQUASH("File system corrupted - gid index in inode too large (gid: %u)\n", header.base.guid);
+ else
+ i.gid = (uid_t) guid_table[header.base.guid];
+
+ i.inode_number = inode_number ++;
+
+ switch(i.type) {
+ case SQUASHFS_DIR_TYPE: {
+ squashfs_dir_inode_header_1 *inode = &header.dir;
+
+ if(swap) {
+ squashfs_dir_inode_header_1 sinode;
+ res = read_inode_data(&sinode, &start, &offset, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_DIR_INODE_HEADER_1(inode,
+ &sinode);
+ } else
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inode->file_size;
+ i.offset = inode->offset;
+ i.start = inode->start_block;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = inode->mtime;
+ break;
+ }
+ case SQUASHFS_FILE_TYPE: {
+ squashfs_reg_inode_header_1 *inode = &header.reg;
+
+ if(swap) {
+ squashfs_reg_inode_header_1 sinode;
+ res = read_inode_data(&sinode, &start, &offset, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_REG_INODE_HEADER_1(inode,
+ &sinode);
+ } else
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inode->file_size;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = inode->mtime;
+ i.blocks = (i.data + sBlk.s.block_size - 1) >>
+ sBlk.s.block_log;
+ i.start = inode->start_block;
+ i.block_start = start;
+ i.block_offset = offset;
+ i.fragment = 0;
+ i.frag_bytes = 0;
+ i.offset = 0;
+ i.sparse = 0;
+ break;
+ }
+ case SQUASHFS_SYMLINK_TYPE: {
+ squashfs_symlink_inode_header_1 *inodep =
+ &header.symlink;
+
+ if(swap) {
+ squashfs_symlink_inode_header_1 sinodep;
+ res = read_inode_data(&sinodep, &start, &offset, sizeof(sinodep));
+ if(res)
+ SQUASHFS_SWAP_SYMLINK_INODE_HEADER_1(inodep,
+ &sinodep);
+ } else
+ res = read_inode_data(inodep, &start, &offset, sizeof(*inodep));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.symlink = malloc(inodep->symlink_size + 1);
+ if(i.symlink == NULL)
+ MEM_ERROR();
+
+ res = read_inode_data(i.symlink, &start, &offset, inodep->symlink_size);
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode symbolic link %lld:%d\n", start, offset);
+ i.symlink[inodep->symlink_size] = '\0';
+ i.data = inodep->symlink_size;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = sBlk.s.mkfs_time;
+ break;
+ }
+ case SQUASHFS_BLKDEV_TYPE:
+ case SQUASHFS_CHRDEV_TYPE: {
+ squashfs_dev_inode_header_1 *inodep = &header.dev;
+
+ if(swap) {
+ squashfs_dev_inode_header_1 sinodep;
+ res = read_inode_data(&sinodep, &start, &offset, sizeof(sinodep));
+ if(res)
+ SQUASHFS_SWAP_DEV_INODE_HEADER_1(inodep,
+ &sinodep);
+ } else
+ res = read_inode_data(inodep, &start, &offset, sizeof(*inodep));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inodep->rdev;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = sBlk.s.mkfs_time;
+ break;
+ }
+ case SQUASHFS_FIFO_TYPE:
+ case SQUASHFS_SOCKET_TYPE: {
+ i.data = 0;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = sBlk.s.mkfs_time;
+ break;
+ }
+ default:
+ EXIT_UNSQUASH("Unknown inode type %d in "
+ " read_inode_header_1!\n",
+ header.base.inode_type);
+ }
+ return &i;
+}
+
+
+static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offset,
+ struct inode **i)
+{
+ squashfs_dir_header_2 dirh;
+ char buffer[sizeof(squashfs_dir_entry_2) + SQUASHFS_NAME_LEN + 1]
+ __attribute__((aligned));
+ squashfs_dir_entry_2 *dire = (squashfs_dir_entry_2 *) buffer;
+ long long start;
+ int bytes = 0;
+ int dir_count, size, res;
+ struct dir_ent *ent, *cur_ent = NULL;
+ struct dir *dir;
+
+ TRACE("squashfs_opendir: inode start block %d, offset %d\n",
+ block_start, offset);
+
+ *i = read_inode(block_start, offset);
+
+ dir = malloc(sizeof(struct dir));
+ if(dir == NULL)
+ MEM_ERROR();
+
+ dir->dir_count = 0;
+ dir->cur_entry = NULL;
+ dir->mode = (*i)->mode;
+ dir->uid = (*i)->uid;
+ dir->guid = (*i)->gid;
+ dir->mtime = (*i)->time;
+ dir->xattr = (*i)->xattr;
+ dir->dirs = NULL;
+
+ if ((*i)->data == 0)
+ /*
+ * if the directory is empty, skip the unnecessary
+ * lookup_entry, this fixes the corner case with
+ * completely empty filesystems where lookup_entry correctly
+ * returning -1 is incorrectly treated as an error
+ */
+ return dir;
+
+ start = sBlk.s.directory_table_start + (*i)->start;
+ offset = (*i)->offset;
+ size = (*i)->data + bytes;
+
+ while(bytes < size) {
+ if(swap) {
+ squashfs_dir_header_2 sdirh;
+ res = read_directory_data(&sdirh, &start, &offset, sizeof(sdirh));
+ if(res)
+ SQUASHFS_SWAP_DIR_HEADER_2(&dirh, &sdirh);
+ } else
+ res = read_directory_data(&dirh, &start, &offset, sizeof(dirh));
+
+ if(res == FALSE)
+ goto corrupted;
+
+ dir_count = dirh.count + 1;
+ TRACE("squashfs_opendir: Read directory header @ byte position "
+ "%d, %d directory entries\n", bytes, dir_count);
+ bytes += sizeof(dirh);
+
+ /* dir_count should never be larger than SQUASHFS_DIR_COUNT */
+ if(dir_count > SQUASHFS_DIR_COUNT) {
+ ERROR("File system corrupted: too many entries in directory\n");
+ goto corrupted;
+ }
+
+ while(dir_count--) {
+ if(swap) {
+ squashfs_dir_entry_2 sdire;
+ res = read_directory_data(&sdire, &start,
+ &offset, sizeof(sdire));
+ if(res)
+ SQUASHFS_SWAP_DIR_ENTRY_2(dire, &sdire);
+ } else
+ res = read_directory_data(dire, &start,
+ &offset, sizeof(*dire));
+
+ if(res == FALSE)
+ goto corrupted;
+
+ bytes += sizeof(*dire);
+
+ /* size should never be SQUASHFS_NAME_LEN or larger */
+ if(dire->size >= SQUASHFS_NAME_LEN) {
+ ERROR("File system corrupted: filename too long\n");
+ goto corrupted;
+ }
+
+ res = read_directory_data(dire->name, &start, &offset,
+ dire->size + 1);
+
+ if(res == FALSE)
+ goto corrupted;
+
+ dire->name[dire->size + 1] = '\0';
+
+ /* check name for invalid characters (i.e /, ., ..) */
+ if(check_name(dire->name, dire->size + 1) == FALSE) {
+ ERROR("File system corrupted: invalid characters in name\n");
+ goto corrupted;
+ }
+
+ TRACE("squashfs_opendir: directory entry %s, inode "
+ "%d:%d, type %d\n", dire->name,
+ dirh.start_block, dire->offset, dire->type);
+
+ ent = malloc(sizeof(struct dir_ent));
+ if(ent == NULL)
+ MEM_ERROR();
+
+ ent->name = strdup(dire->name);
+ ent->start_block = dirh.start_block;
+ ent->offset = dire->offset;
+ ent->type = dire->type;
+ ent->next = NULL;
+ if(cur_ent == NULL)
+ dir->dirs = ent;
+ else
+ cur_ent->next = ent;
+ cur_ent = ent;
+ dir->dir_count ++;
+ bytes += dire->size + 1;
+ }
+ }
+
+ /* check directory for duplicate names. Need to sort directory first */
+ sort_directory(&(dir->dirs), dir->dir_count);
+ if(check_directory(dir) == FALSE) {
+ ERROR("File system corrupted: directory has duplicate names\n");
+ goto corrupted;
+ }
+ return dir;
+
+corrupted:
+ squashfs_closedir(dir);
+ return NULL;
+}
+
+
+static int read_filesystem_tables()
+{
+ long long table_start;
+
+ /* Read uid and gid lookup tables */
+
+ /* Sanity check super block contents */
+ if(sBlk.no_guids) {
+ if(sBlk.guid_start >= sBlk.s.bytes_used) {
+ ERROR("read_filesystem_tables: gid start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* In 1.x filesystems, there should never be more than 15 gids */
+ if(sBlk.no_guids > 15) {
+ ERROR("read_filesystem_tables: gids too large in super block\n");
+ goto corrupted;
+ }
+
+ if(read_ids(sBlk.no_guids, sBlk.guid_start, sBlk.s.bytes_used, &guid_table) == FALSE)
+ goto corrupted;
+
+ table_start = sBlk.guid_start;
+ } else {
+ /* no guids, guid_start should be 0 */
+ if(sBlk.guid_start != 0) {
+ ERROR("read_filesystem_tables: gid start too large in super block\n");
+ goto corrupted;
+ }
+
+ table_start = sBlk.s.bytes_used;
+ }
+
+ if(sBlk.uid_start >= table_start) {
+ ERROR("read_filesystem_tables: uid start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* There should be at least one uid */
+ if(sBlk.no_uids == 0) {
+ ERROR("read_filesystem_tables: uid count bad in super block\n");
+ goto corrupted;
+ }
+
+ /* In 1.x filesystems, there should never be more than 48 uids */
+ if(sBlk.no_uids > 48) {
+ ERROR("read_filesystem_tables: uids too large in super block\n");
+ goto corrupted;
+ }
+
+ if(read_ids(sBlk.no_uids, sBlk.uid_start, table_start, &uid_table) == FALSE)
+ goto corrupted;
+
+ table_start = sBlk.uid_start;
+
+ /* Sanity check super block directory table values */
+ if(sBlk.s.directory_table_start > table_start) {
+ ERROR("read_filesystem_tables: directory table start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* Sanity check super block inode table values */
+ if(sBlk.s.inode_table_start >= sBlk.s.directory_table_start) {
+ ERROR("read_filesystem_tables: inode table start too large in super block\n");
+ goto corrupted;
+ }
+
+ return TRUE;
+
+corrupted:
+ return FALSE;
+}
+
+
+int read_super_1(squashfs_operations **s_ops, void *s)
+{
+ squashfs_super_block_3 *sBlk_3 = s;
+
+ if(sBlk_3->s_magic != SQUASHFS_MAGIC || sBlk_3->s_major != 1 ||
+ sBlk_3->s_minor != 0)
+ return -1;
+
+ sBlk.s.s_magic = sBlk_3->s_magic;
+ sBlk.s.inodes = sBlk_3->inodes;
+ sBlk.s.mkfs_time = sBlk_3->mkfs_time;
+ sBlk.s.block_size = sBlk_3->block_size_1;
+ sBlk.s.fragments = 0;
+ sBlk.s.block_log = sBlk_3->block_log;
+ sBlk.s.flags = sBlk_3->flags;
+ sBlk.s.s_major = sBlk_3->s_major;
+ sBlk.s.s_minor = sBlk_3->s_minor;
+ sBlk.s.root_inode = sBlk_3->root_inode;
+ sBlk.s.bytes_used = sBlk_3->bytes_used_2;
+ sBlk.s.inode_table_start = sBlk_3->inode_table_start_2;
+ sBlk.s.directory_table_start = sBlk_3->directory_table_start_2;
+ sBlk.s.fragment_table_start = SQUASHFS_INVALID_BLK;
+ sBlk.s.lookup_table_start = sBlk_3->lookup_table_start;
+ sBlk.no_uids = sBlk_3->no_uids;
+ sBlk.no_guids = sBlk_3->no_guids;
+ sBlk.uid_start = sBlk_3->uid_start_2;
+ sBlk.guid_start = sBlk_3->guid_start_2;
+ sBlk.s.xattr_id_table_start = SQUASHFS_INVALID_BLK;
+
+ *s_ops = &ops;
+
+ /*
+ * 1.x filesystems use gzip compression.
+ */
+ comp = lookup_compressor("gzip");
+ return TRUE;
+}
+
+
+static void squashfs_stat(char *source)
+{
+ time_t mkfs_time = (time_t) sBlk.s.mkfs_time;
+ struct tm *t = use_localtime ? localtime(&mkfs_time) :
+ gmtime(&mkfs_time);
+ char *mkfs_str = asctime(t);
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+ printf("Found a valid %sSQUASHFS %d:%d superblock on %s.\n",
+ swap ? "little endian " : "big endian ", sBlk.s.s_major,
+ sBlk.s.s_minor, source);
+#else
+ printf("Found a valid %sSQUASHFS %d:%d superblock on %s.\n",
+ swap ? "big endian " : "little endian ", sBlk.s.s_major,
+ sBlk.s.s_minor, source);
+#endif
+
+ printf("Creation or last append time %s", mkfs_str ? mkfs_str :
+ "failed to get time\n");
+ printf("Filesystem size %llu bytes (%.2f Kbytes / %.2f Mbytes)\n",
+ sBlk.s.bytes_used, sBlk.s.bytes_used / 1024.0,
+ sBlk.s.bytes_used / (1024.0 * 1024.0));
+ printf("Block size %d\n", sBlk.s.block_size);
+ printf("Filesystem is %sexportable via NFS\n",
+ SQUASHFS_EXPORTABLE(sBlk.s.flags) ? "" : "not ");
+ printf("Inodes are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_INODES(sBlk.s.flags) ? "un" : "");
+ printf("Data is %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_DATA(sBlk.s.flags) ? "un" : "");
+ printf("Check data is %spresent in the filesystem\n",
+ SQUASHFS_CHECK_DATA(sBlk.s.flags) ? "" : "not ");
+ printf("Duplicates are removed\n");
+ printf("Number of inodes %d\n", sBlk.s.inodes);
+ printf("Number of uids %d\n", sBlk.no_uids);
+ printf("Number of gids %d\n", sBlk.no_guids);
+
+ TRACE("sBlk.s.inode_table_start 0x%llx\n", sBlk.s.inode_table_start);
+ TRACE("sBlk.s.directory_table_start 0x%llx\n", sBlk.s.directory_table_start);
+ TRACE("sBlk.uid_start 0x%llx\n", sBlk.uid_start);
+ TRACE("sBlk.guid_start 0x%llx\n", sBlk.guid_start);
+}
+
+
+static squashfs_operations ops = {
+ .opendir = squashfs_opendir,
+ .read_block_list = read_block_list,
+ .read_inode = read_inode,
+ .read_filesystem_tables = read_filesystem_tables,
+ .stat = squashfs_stat
+};
diff --git a/squashfs-tools/unsquash-12.c b/squashfs-tools/unsquash-12.c
new file mode 100644
index 0000000..3a6f0ae
--- /dev/null
+++ b/squashfs-tools/unsquash-12.c
@@ -0,0 +1,30 @@
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquash-12.c
+ *
+ * Helper functions used by unsquash-1 and unsquash-2.
+ */
+
+#include "unsquashfs.h"
+#include "merge_sort.h"
+
+SORT(sort_directory, dir_ent, name, next);
diff --git a/squashfs-tools/unsquash-123.c b/squashfs-tools/unsquash-123.c
new file mode 100644
index 0000000..8c0a51c
--- /dev/null
+++ b/squashfs-tools/unsquash-123.c
@@ -0,0 +1,79 @@
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2019
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquash-123.c
+ *
+ * Helper functions used by unsquash-1, unsquash-2 and unsquash-3.
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_compat.h"
+
+int read_ids(int ids, long long start, long long end, unsigned int **id_table)
+{
+ /* Note on overflow limits:
+ * Size of ids is 2^8
+ * Max length is 2^8*4 or 1024
+ */
+ int res;
+ int length = ids * sizeof(unsigned int);
+
+ /*
+ * The size of the index table (length bytes) should match the
+ * table start and end points
+ */
+ if(length != (end - start)) {
+ ERROR("read_ids: Bad inode count in super block\n");
+ return FALSE;
+ }
+
+ TRACE("read_ids: no_ids %d\n", ids);
+
+ *id_table = malloc(length);
+ if(*id_table == NULL)
+ MEM_ERROR();
+
+ if(swap) {
+ unsigned int *sid_table = malloc(length);
+
+ if(sid_table == NULL)
+ MEM_ERROR();
+
+ res = read_fs_bytes(fd, start, length, sid_table);
+ if(res == FALSE) {
+ ERROR("read_ids: failed to read uid/gid table"
+ "\n");
+ free(sid_table);
+ return FALSE;
+ }
+ SQUASHFS_SWAP_INTS_3((*id_table), sid_table, ids);
+ free(sid_table);
+ } else {
+ res = read_fs_bytes(fd, start, length, *id_table);
+ if(res == FALSE) {
+ ERROR("read_ids: failed to read uid/gid table"
+ "\n");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
diff --git a/squashfs-tools/unsquash-1234.c b/squashfs-tools/unsquash-1234.c
new file mode 100644
index 0000000..98a81ed
--- /dev/null
+++ b/squashfs-tools/unsquash-1234.c
@@ -0,0 +1,95 @@
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquash-1234.c
+ *
+ * Helper functions used by unsquash-1, unsquash-2, unsquash-3 and
+ * unsquash-4.
+ */
+
+#include "unsquashfs.h"
+
+/*
+ * Check name for validity, name should not
+ * - be ".", "./", or
+ * - be "..", "../" or
+ * - have a "/" anywhere in the name, or
+ * - be shorter than the expected size
+ */
+int check_name(char *name, int size)
+{
+ char *start = name;
+
+ if(name[0] == '.') {
+ if(name[1] == '.')
+ name++;
+ if(name[1] == '/' || name[1] == '\0')
+ return FALSE;
+ }
+
+ while(name[0] != '/' && name[0] != '\0')
+ name ++;
+
+ if(name[0] == '/')
+ return FALSE;
+
+ if((name - start) != size)
+ return FALSE;
+
+ return TRUE;
+}
+
+
+void squashfs_closedir(struct dir *dir)
+{
+ struct dir_ent *ent = dir->dirs;
+
+ while(ent) {
+ struct dir_ent *tmp = ent;
+
+ ent = ent->next;
+ free(tmp->name);
+ free(tmp);
+ }
+
+ free(dir);
+}
+
+
+/*
+ * Check directory for duplicate names. As the directory should be sorted,
+ * duplicates will be consecutive. Obviously we also need to check if the
+ * directory has been deliberately unsorted, to evade this check.
+ */
+int check_directory(struct dir *dir)
+{
+ int i;
+ struct dir_ent *ent;
+
+ if(dir->dir_count < 2)
+ return TRUE;
+
+ for(ent = dir->dirs, i = 0; i < dir->dir_count - 1; ent = ent->next, i++)
+ if(strcmp(ent->name, ent->next->name) >= 0)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/squashfs-tools/unsquash-2.c b/squashfs-tools/unsquash-2.c
new file mode 100644
index 0000000..b2546d5
--- /dev/null
+++ b/squashfs-tools/unsquash-2.c
@@ -0,0 +1,715 @@
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2013, 2019, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquash-2.c
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_compat.h"
+#include "compressor.h"
+
+static squashfs_fragment_entry_2 *fragment_table;
+static unsigned int *uid_table, *guid_table;
+static squashfs_operations ops;
+static int needs_sorting = FALSE;
+
+
+static void read_block_list(unsigned int *block_list, long long start,
+ unsigned int offset, int blocks)
+{
+ int res;
+
+ TRACE("read_block_list: blocks %d\n", blocks);
+
+ if(swap) {
+ char *block_ptr = malloc(blocks * sizeof(unsigned int));
+ if(block_ptr == NULL)
+ MEM_ERROR();
+ res = read_inode_data(block_ptr, &start, &offset, blocks * sizeof(unsigned int));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_block_list: failed to read "
+ "inode index %lld:%d\n", start, offset);
+ SQUASHFS_SWAP_INTS_3(block_list, block_ptr, blocks);
+ free(block_ptr);
+ } else {
+ res = read_inode_data(block_list, &start, &offset, blocks * sizeof(unsigned int));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_block_list: failed to read "
+ "inode index %lld:%d\n", start, offset);
+ }
+}
+
+
+static int read_fragment_table(long long *table_start)
+{
+ /*
+ * Note on overflow limits:
+ * Size of SBlk.s.fragments is 2^32 (unsigned int)
+ * Max size of bytes is 2^32*8 or 2^35
+ * Max indexes is (2^32*8)/8K or 2^22
+ * Max length is ((2^32*8)/8K)*4 or 2^24 or 16M
+ */
+ int res, i;
+ long long bytes = SQUASHFS_FRAGMENT_BYTES_2((long long) sBlk.s.fragments);
+ int indexes = SQUASHFS_FRAGMENT_INDEXES_2((long long) sBlk.s.fragments);
+ int length = SQUASHFS_FRAGMENT_INDEX_BYTES_2((long long) sBlk.s.fragments);
+ unsigned int *fragment_table_index;
+
+ /*
+ * The size of the index table (length bytes) should match the
+ * table start and end points
+ */
+ if(length != (*table_start- sBlk.s.fragment_table_start)) {
+ ERROR("read_ids: Bad inode count in super block\n");
+ return FALSE;
+ }
+
+ TRACE("read_fragment_table: %d fragments, reading %d fragment indexes "
+ "from 0x%llx\n", sBlk.s.fragments, indexes,
+ sBlk.s.fragment_table_start);
+
+ fragment_table_index = malloc(length);
+ if(fragment_table_index == NULL)
+ MEM_ERROR();
+
+ fragment_table = malloc(bytes);
+ if(fragment_table == NULL)
+ MEM_ERROR();
+
+ if(swap) {
+ unsigned int *sfragment_table_index = malloc(length);
+
+ if(sfragment_table_index == NULL)
+ MEM_ERROR();
+
+ res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
+ length, sfragment_table_index);
+ if(res == FALSE) {
+ ERROR("read_fragment_table: failed to read fragment "
+ "table index\n");
+ free(sfragment_table_index);
+ goto failed;
+ }
+ SQUASHFS_SWAP_FRAGMENT_INDEXES_2(fragment_table_index,
+ sfragment_table_index, indexes);
+ free(sfragment_table_index);
+ } else {
+ res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
+ length, fragment_table_index);
+ if(res == FALSE) {
+ ERROR("read_fragment_table: failed to read fragment "
+ "table index\n");
+ goto failed;
+ }
+ }
+
+ for(i = 0; i < indexes; i++) {
+ int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
+ bytes & (SQUASHFS_METADATA_SIZE - 1);
+ int length = read_block(fd, fragment_table_index[i], NULL,
+ expected, ((char *) fragment_table) + ((long long) i *
+ SQUASHFS_METADATA_SIZE));
+ TRACE("Read fragment table block %d, from 0x%x, length %d\n", i,
+ fragment_table_index[i], length);
+ if(length == FALSE) {
+ ERROR("read_fragment_table: failed to read fragment "
+ "table block\n");
+ goto failed;
+ }
+ }
+
+ if(swap) {
+ squashfs_fragment_entry_2 sfragment;
+ for(i = 0; i < sBlk.s.fragments; i++) {
+ SQUASHFS_SWAP_FRAGMENT_ENTRY_2((&sfragment),
+ (&fragment_table[i]));
+ memcpy((char *) &fragment_table[i], (char *) &sfragment,
+ sizeof(squashfs_fragment_entry_2));
+ }
+ }
+
+ *table_start = fragment_table_index[0];
+ free(fragment_table_index);
+
+ return TRUE;
+
+failed:
+ free(fragment_table_index);
+ return FALSE;
+}
+
+
+static void read_fragment(unsigned int fragment, long long *start_block, int *size)
+{
+ TRACE("read_fragment: reading fragment %d\n", fragment);
+
+ squashfs_fragment_entry_2 *fragment_entry = &fragment_table[fragment];
+ *start_block = fragment_entry->start_block;
+ *size = fragment_entry->size;
+}
+
+
+static struct inode *read_inode(unsigned int start_block, unsigned int offset)
+{
+ static union squashfs_inode_header_2 header;
+ long long start = sBlk.s.inode_table_start + start_block;
+ long long st = start;
+ unsigned int off = offset;
+ static struct inode i;
+ int res;
+
+ TRACE("read_inode: reading inode [%d:%d]\n", start_block, offset);
+
+ if(swap) {
+ squashfs_base_inode_header_2 sinode;
+ res = read_inode_data(&sinode, &st, &off, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_BASE_INODE_HEADER_2(&header.base, &sinode,
+ sizeof(squashfs_base_inode_header_2));
+ } else
+ res = read_inode_data(&header.base, &st, &off, sizeof(header.base));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read inode %lld:%d\n", st, off);
+
+ i.xattr = SQUASHFS_INVALID_XATTR;
+
+ if(header.base.uid >= sBlk.no_uids)
+ EXIT_UNSQUASH("File system corrupted - uid index in inode too large (uid: %u)\n", header.base.uid);
+
+ i.uid = (uid_t) uid_table[header.base.uid];
+
+ if(header.base.guid == SQUASHFS_GUIDS)
+ i.gid = i.uid;
+ else if(header.base.guid >= sBlk.no_guids)
+ EXIT_UNSQUASH("File system corrupted - gid index in inode too large (gid: %d)\n", header.base.guid);
+ else
+ i.gid = (uid_t) guid_table[header.base.guid];
+
+ if(header.base.inode_type < 1 || header.base.inode_type > 8)
+ EXIT_UNSQUASH("File system corrupted - invalid type in inode (type: %u)\n", header.base.inode_type);
+
+ i.mode = lookup_type[header.base.inode_type] | header.base.mode;
+ i.type = header.base.inode_type;
+ i.inode_number = inode_number++;
+
+ switch(header.base.inode_type) {
+ case SQUASHFS_DIR_TYPE: {
+ squashfs_dir_inode_header_2 *inode = &header.dir;
+
+ if(swap) {
+ squashfs_dir_inode_header_2 sinode;
+ res = read_inode_data(&sinode, &start, &offset, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_DIR_INODE_HEADER_2(&header.dir,
+ &sinode);
+ } else
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inode->file_size;
+ i.offset = inode->offset;
+ i.start = inode->start_block;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = inode->mtime;
+ break;
+ }
+ case SQUASHFS_LDIR_TYPE: {
+ squashfs_ldir_inode_header_2 *inode = &header.ldir;
+
+ if(swap) {
+ squashfs_ldir_inode_header_2 sinode;
+ res = read_inode_data(&sinode, &start, &offset, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_LDIR_INODE_HEADER_2(&header.ldir,
+ &sinode);
+ } else
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inode->file_size;
+ i.offset = inode->offset;
+ i.start = inode->start_block;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = inode->mtime;
+ break;
+ }
+ case SQUASHFS_FILE_TYPE: {
+ squashfs_reg_inode_header_2 *inode = &header.reg;
+
+ if(swap) {
+ squashfs_reg_inode_header_2 sinode;
+ res = read_inode_data(&sinode, &start, &offset, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_REG_INODE_HEADER_2(inode,
+ &sinode);
+ } else
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inode->file_size;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = inode->mtime;
+ i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
+ ? 0 : inode->file_size % sBlk.s.block_size;
+ i.fragment = inode->fragment;
+ i.offset = inode->offset;
+ i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
+ (i.data + sBlk.s.block_size - 1) >>
+ sBlk.s.block_log : i.data >>
+ sBlk.s.block_log;
+ i.start = inode->start_block;
+ i.block_start = start;
+ i.block_offset = offset;
+ i.sparse = 0;
+ break;
+ }
+ case SQUASHFS_SYMLINK_TYPE: {
+ squashfs_symlink_inode_header_2 *inodep =
+ &header.symlink;
+
+ if(swap) {
+ squashfs_symlink_inode_header_2 sinodep;
+ res = read_inode_data(&sinodep, &start, &offset, sizeof(sinodep));
+ if(res)
+ SQUASHFS_SWAP_SYMLINK_INODE_HEADER_2(inodep,
+ &sinodep);
+ } else
+ res = read_inode_data(inodep, &start, &offset, sizeof(*inodep));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.symlink = malloc(inodep->symlink_size + 1);
+ if(i.symlink == NULL)
+ MEM_ERROR();
+
+ res = read_inode_data(i.symlink, &start, &offset, inodep->symlink_size);
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode symbolic link %lld:%d\n", start, offset);
+ i.symlink[inodep->symlink_size] = '\0';
+ i.data = inodep->symlink_size;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = sBlk.s.mkfs_time;
+ break;
+ }
+ case SQUASHFS_BLKDEV_TYPE:
+ case SQUASHFS_CHRDEV_TYPE: {
+ squashfs_dev_inode_header_2 *inodep = &header.dev;
+
+ if(swap) {
+ squashfs_dev_inode_header_2 sinodep;
+ res = read_inode_data(&sinodep, &start, &offset, sizeof(sinodep));
+ if(res)
+ SQUASHFS_SWAP_DEV_INODE_HEADER_2(inodep,
+ &sinodep);
+ } else
+ res = read_inode_data(inodep, &start, &offset, sizeof(*inodep));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inodep->rdev;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = sBlk.s.mkfs_time;
+ break;
+ }
+ case SQUASHFS_FIFO_TYPE:
+ case SQUASHFS_SOCKET_TYPE:
+ i.data = 0;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = sBlk.s.mkfs_time;
+ break;
+ default:
+ EXIT_UNSQUASH("Unknown inode type %d in "
+ "read_inode_header_2!\n",
+ header.base.inode_type);
+ }
+ return &i;
+}
+
+
+static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offset,
+ struct inode **i)
+{
+ squashfs_dir_header_2 dirh;
+ char buffer[sizeof(squashfs_dir_entry_2) + SQUASHFS_NAME_LEN + 1]
+ __attribute__((aligned));
+ squashfs_dir_entry_2 *dire = (squashfs_dir_entry_2 *) buffer;
+ long long start;
+ int bytes = 0;
+ int dir_count, size, res;
+ struct dir_ent *ent, *cur_ent = NULL;
+ struct dir *dir;
+
+ TRACE("squashfs_opendir: inode start block %d, offset %d\n",
+ block_start, offset);
+
+ *i = read_inode(block_start, offset);
+
+ dir = malloc(sizeof(struct dir));
+ if(dir == NULL)
+ MEM_ERROR();
+
+ dir->dir_count = 0;
+ dir->cur_entry = NULL;
+ dir->mode = (*i)->mode;
+ dir->uid = (*i)->uid;
+ dir->guid = (*i)->gid;
+ dir->mtime = (*i)->time;
+ dir->xattr = (*i)->xattr;
+ dir->dirs = NULL;
+
+ if ((*i)->data == 0)
+ /*
+ * if the directory is empty, skip the unnecessary
+ * lookup_entry, this fixes the corner case with
+ * completely empty filesystems where lookup_entry correctly
+ * returning -1 is incorrectly treated as an error
+ */
+ return dir;
+
+ start = sBlk.s.directory_table_start + (*i)->start;
+ offset = (*i)->offset;
+ size = (*i)->data + bytes;
+
+ while(bytes < size) {
+ if(swap) {
+ squashfs_dir_header_2 sdirh;
+ res = read_directory_data(&sdirh, &start, &offset, sizeof(sdirh));
+ if(res)
+ SQUASHFS_SWAP_DIR_HEADER_2(&dirh, &sdirh);
+ } else
+ res = read_directory_data(&dirh, &start, &offset, sizeof(dirh));
+
+ if(res == FALSE)
+ goto corrupted;
+
+ dir_count = dirh.count + 1;
+ TRACE("squashfs_opendir: Read directory header @ byte position "
+ "%d, %d directory entries\n", bytes, dir_count);
+ bytes += sizeof(dirh);
+
+ /* dir_count should never be larger than SQUASHFS_DIR_COUNT */
+ if(dir_count > SQUASHFS_DIR_COUNT) {
+ ERROR("File system corrupted: too many entries in directory\n");
+ goto corrupted;
+ }
+
+ while(dir_count--) {
+ if(swap) {
+ squashfs_dir_entry_2 sdire;
+ res = read_directory_data(&sdire, &start,
+ &offset, sizeof(sdire));
+ if(res)
+ SQUASHFS_SWAP_DIR_ENTRY_2(dire, &sdire);
+ } else
+ res = read_directory_data(dire, &start,
+ &offset, sizeof(*dire));
+
+ if(res == FALSE)
+ goto corrupted;
+
+ bytes += sizeof(*dire);
+
+ /* size should never be SQUASHFS_NAME_LEN or larger */
+ if(dire->size >= SQUASHFS_NAME_LEN) {
+ ERROR("File system corrupted: filename too long\n");
+ goto corrupted;
+ }
+
+ res = read_directory_data(dire->name, &start, &offset,
+ dire->size + 1);
+
+ if(res == FALSE)
+ goto corrupted;
+
+ dire->name[dire->size + 1] = '\0';
+
+ /* check name for invalid characters (i.e /, ., ..) */
+ if(check_name(dire->name, dire->size + 1) == FALSE) {
+ ERROR("File system corrupted: invalid characters in name\n");
+ goto corrupted;
+ }
+
+ TRACE("squashfs_opendir: directory entry %s, inode "
+ "%d:%d, type %d\n", dire->name,
+ dirh.start_block, dire->offset, dire->type);
+
+ ent = malloc(sizeof(struct dir_ent));
+ if(ent == NULL)
+ MEM_ERROR();
+
+ ent->name = strdup(dire->name);
+ ent->start_block = dirh.start_block;
+ ent->offset = dire->offset;
+ ent->type = dire->type;
+ ent->next = NULL;
+ if(cur_ent == NULL)
+ dir->dirs = ent;
+ else
+ cur_ent->next = ent;
+ cur_ent = ent;
+ dir->dir_count ++;
+ bytes += dire->size + 1;
+ }
+ }
+
+ if(needs_sorting)
+ sort_directory(&(dir->dirs), dir->dir_count);
+
+ /* check directory for duplicate names and sorting */
+ if(check_directory(dir) == FALSE) {
+ if(needs_sorting)
+ ERROR("File system corrupted: directory has duplicate names\n");
+ else
+ ERROR("File system corrupted: directory has duplicate names or is unsorted\n");
+ goto corrupted;
+ }
+ return dir;
+
+corrupted:
+ squashfs_closedir(dir);
+ return NULL;
+}
+
+
+static int read_filesystem_tables()
+{
+ long long table_start;
+
+ /* Read uid and gid lookup tables */
+
+ /* Sanity check super block contents */
+ if(sBlk.no_guids) {
+ if(sBlk.guid_start >= sBlk.s.bytes_used) {
+ ERROR("read_filesystem_tables: gid start too large in super block\n");
+ goto corrupted;
+ }
+
+ if(read_ids(sBlk.no_guids, sBlk.guid_start, sBlk.s.bytes_used, &guid_table) == FALSE)
+ goto corrupted;
+
+ table_start = sBlk.guid_start;
+ } else {
+ /* no guids, guid_start should be 0 */
+ if(sBlk.guid_start != 0) {
+ ERROR("read_filesystem_tables: gid start too large in super block\n");
+ goto corrupted;
+ }
+
+ table_start = sBlk.s.bytes_used;
+ }
+
+ if(sBlk.uid_start >= table_start) {
+ ERROR("read_filesystem_tables: uid start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* There should be at least one uid */
+ if(sBlk.no_uids == 0) {
+ ERROR("read_filesystem_tables: uid count bad in super block\n");
+ goto corrupted;
+ }
+
+ if(read_ids(sBlk.no_uids, sBlk.uid_start, table_start, &uid_table) == FALSE)
+ goto corrupted;
+
+ table_start = sBlk.uid_start;
+
+ /* Read fragment table */
+ if(sBlk.s.fragments != 0) {
+
+ /* Sanity check super block contents */
+ if(sBlk.s.fragment_table_start >= table_start) {
+ ERROR("read_filesystem_tables: fragment table start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* The number of fragments should not exceed the number of inodes */
+ if(sBlk.s.fragments > sBlk.s.inodes) {
+ ERROR("read_filesystem_tables: Bad fragment count in super block\n");
+ goto corrupted;
+ }
+
+ if(read_fragment_table(&table_start) == FALSE)
+ goto corrupted;
+ } else {
+ /*
+ * Sanity check super block contents - with 0 fragments,
+ * the fragment table should be empty
+ */
+ if(sBlk.s.fragment_table_start != table_start) {
+ ERROR("read_filesystem_tables: fragment table start invalid in super block\n");
+ goto corrupted;
+ }
+ }
+
+ /* Sanity check super block directory table values */
+ if(sBlk.s.directory_table_start > table_start) {
+ ERROR("read_filesystem_tables: directory table start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* Sanity check super block inode table values */
+ if(sBlk.s.inode_table_start >= sBlk.s.directory_table_start) {
+ ERROR("read_filesystem_tables: inode table start too large in super block\n");
+ goto corrupted;
+ }
+
+ return TRUE;
+
+corrupted:
+ return FALSE;
+}
+
+
+int read_super_2(squashfs_operations **s_ops, void *s)
+{
+ squashfs_super_block_3 *sBlk_3 = s;
+
+ if(sBlk_3->s_magic != SQUASHFS_MAGIC || sBlk_3->s_major != 2 ||
+ sBlk_3->s_minor > 1)
+ return -1;
+
+ sBlk.s.s_magic = sBlk_3->s_magic;
+ sBlk.s.inodes = sBlk_3->inodes;
+ sBlk.s.mkfs_time = sBlk_3->mkfs_time;
+ sBlk.s.block_size = sBlk_3->block_size;
+ sBlk.s.fragments = sBlk_3->fragments;
+ sBlk.s.block_log = sBlk_3->block_log;
+ sBlk.s.flags = sBlk_3->flags;
+ sBlk.s.s_major = sBlk_3->s_major;
+ sBlk.s.s_minor = sBlk_3->s_minor;
+ sBlk.s.root_inode = sBlk_3->root_inode;
+ sBlk.s.bytes_used = sBlk_3->bytes_used_2;
+ sBlk.s.inode_table_start = sBlk_3->inode_table_start;
+ sBlk.s.directory_table_start = sBlk_3->directory_table_start_2;
+ sBlk.s.fragment_table_start = sBlk_3->fragment_table_start_2;
+ sBlk.s.inode_table_start = sBlk_3->inode_table_start_2;
+ sBlk.no_uids = sBlk_3->no_uids;
+ sBlk.no_guids = sBlk_3->no_guids;
+ sBlk.uid_start = sBlk_3->uid_start_2;
+ sBlk.guid_start = sBlk_3->guid_start_2;
+ sBlk.s.xattr_id_table_start = SQUASHFS_INVALID_BLK;
+
+ *s_ops = &ops;
+
+ /*
+ * 2.x filesystems use gzip compression.
+ */
+ comp = lookup_compressor("gzip");
+
+ if(sBlk_3->s_minor == 0)
+ needs_sorting = TRUE;
+
+ return TRUE;
+}
+
+
+static void squashfs_stat(char *source)
+{
+ time_t mkfs_time = (time_t) sBlk.s.mkfs_time;
+ struct tm *t = use_localtime ? localtime(&mkfs_time) :
+ gmtime(&mkfs_time);
+ char *mkfs_str = asctime(t);
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+ printf("Found a valid %sSQUASHFS %d:%d superblock on %s.\n",
+ swap ? "little endian " : "big endian ", sBlk.s.s_major,
+ sBlk.s.s_minor, source);
+#else
+ printf("Found a valid %sSQUASHFS %d:%d superblock on %s.\n",
+ swap ? "big endian " : "little endian ", sBlk.s.s_major,
+ sBlk.s.s_minor, source);
+#endif
+
+ printf("Creation or last append time %s", mkfs_str ? mkfs_str :
+ "failed to get time\n");
+ printf("Filesystem size %llu bytes (%.2f Kbytes / %.2f Mbytes)\n",
+ sBlk.s.bytes_used, sBlk.s.bytes_used / 1024.0,
+ sBlk.s.bytes_used / (1024.0 * 1024.0));
+
+ printf("Block size %d\n", sBlk.s.block_size);
+ printf("Filesystem is %sexportable via NFS\n",
+ SQUASHFS_EXPORTABLE(sBlk.s.flags) ? "" : "not ");
+ printf("Inodes are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_INODES(sBlk.s.flags) ? "un" : "");
+ printf("Data is %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_DATA(sBlk.s.flags) ? "un" : "");
+
+ if(SQUASHFS_NO_FRAGMENTS(sBlk.s.flags))
+ printf("Fragments are not stored\n");
+ else {
+ printf("Fragments are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_FRAGMENTS(sBlk.s.flags) ? "un" : "");
+ printf("Always-use-fragments option is %sspecified\n",
+ SQUASHFS_ALWAYS_FRAGMENTS(sBlk.s.flags) ? "" : "not ");
+ }
+
+ printf("Check data is %spresent in the filesystem\n",
+ SQUASHFS_CHECK_DATA(sBlk.s.flags) ? "" : "not ");
+ printf("Duplicates are %sremoved\n", SQUASHFS_DUPLICATES(sBlk.s.flags) ? "" : "not ");
+ printf("Number of fragments %d\n", sBlk.s.fragments);
+ printf("Number of inodes %d\n", sBlk.s.inodes);
+ printf("Number of uids %d\n", sBlk.no_uids);
+ printf("Number of gids %d\n", sBlk.no_guids);
+
+ TRACE("sBlk.s.inode_table_start 0x%llx\n", sBlk.s.inode_table_start);
+ TRACE("sBlk.s.directory_table_start 0x%llx\n", sBlk.s.directory_table_start);
+ TRACE("sBlk.s.fragment_table_start 0x%llx\n\n", sBlk.s.fragment_table_start);
+ TRACE("sBlk.uid_start 0x%llx\n", sBlk.uid_start);
+ TRACE("sBlk.guid_start 0x%llx\n", sBlk.guid_start);
+}
+
+
+static squashfs_operations ops = {
+ .opendir = squashfs_opendir,
+ .read_fragment = read_fragment,
+ .read_block_list = read_block_list,
+ .read_inode = read_inode,
+ .read_filesystem_tables = read_filesystem_tables,
+ .stat = squashfs_stat
+};
diff --git a/squashfs-tools/unsquash-3.c b/squashfs-tools/unsquash-3.c
new file mode 100644
index 0000000..8eab216
--- /dev/null
+++ b/squashfs-tools/unsquash-3.c
@@ -0,0 +1,824 @@
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2019, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquash-3.c
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_compat.h"
+#include "compressor.h"
+
+static squashfs_fragment_entry_3 *fragment_table;
+static unsigned int *uid_table, *guid_table;
+static squashfs_operations ops;
+
+static long long *salloc_index_table(int indexes)
+{
+ static long long *alloc_table = NULL;
+ static int alloc_size = 0;
+ int length = indexes * sizeof(long long);
+
+ if(alloc_size < length || length == 0) {
+ long long *table = realloc(alloc_table, length);
+
+ if(table == NULL && length !=0 )
+ MEM_ERROR();
+
+ alloc_table = table;
+ alloc_size = length;
+ }
+
+ return alloc_table;
+}
+
+
+static void read_block_list(unsigned int *block_list, long long start,
+ unsigned int offset, int blocks)
+{
+ int res;
+
+ TRACE("read_block_list: blocks %d\n", blocks);
+
+ if(swap) {
+ unsigned int *block_ptr = malloc(blocks * sizeof(unsigned int));
+ if(block_ptr == NULL)
+ MEM_ERROR();
+ res = read_inode_data(block_ptr, &start, &offset, blocks * sizeof(unsigned int));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_block_list: failed to read "
+ "inode index %lld:%d\n", start, offset);
+ SQUASHFS_SWAP_INTS_3(block_list, block_ptr, blocks);
+ free(block_ptr);
+ } else {
+ res = read_inode_data(block_list, &start, &offset, blocks * sizeof(unsigned int));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_block_list: failed to read "
+ "inode index %lld:%d\n", start, offset);
+ }
+}
+
+
+static int read_fragment_table(long long *table_start)
+{
+ /*
+ * Note on overflow limits:
+ * Size of SBlk.s.fragments is 2^32 (unsigned int)
+ * Max size of bytes is 2^32*16 or 2^36
+ * Max indexes is (2^32*16)/8K or 2^23
+ * Max length is ((2^32*16)/8K)*8 or 2^26 or 64M
+ */
+ int res, i;
+ long long bytes = SQUASHFS_FRAGMENT_BYTES_3((long long) sBlk.s.fragments);
+ int indexes = SQUASHFS_FRAGMENT_INDEXES_3((long long) sBlk.s.fragments);
+ int length = SQUASHFS_FRAGMENT_INDEX_BYTES_3((long long) sBlk.s.fragments);
+ long long *fragment_table_index;
+
+ /*
+ * The size of the index table (length bytes) should match the
+ * table start and end points
+ */
+ if(length != (*table_start - sBlk.s.fragment_table_start)) {
+ ERROR("read_fragment_table: Bad fragment count in super block\n");
+ return FALSE;
+ }
+
+ TRACE("read_fragment_table: %d fragments, reading %d fragment indexes "
+ "from 0x%llx\n", sBlk.s.fragments, indexes,
+ sBlk.s.fragment_table_start);
+
+ fragment_table_index = alloc_index_table(indexes);
+ fragment_table = malloc(bytes);
+ if(fragment_table == NULL)
+ EXIT_UNSQUASH("read_fragment_table: failed to allocate "
+ "fragment table\n");
+
+ if(swap) {
+ long long *sfragment_table_index = salloc_index_table(indexes);
+
+ res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
+ length, sfragment_table_index);
+ if(res == FALSE) {
+ ERROR("read_fragment_table: failed to read fragment "
+ "table index\n");
+ return FALSE;
+ }
+ SQUASHFS_SWAP_FRAGMENT_INDEXES_3(fragment_table_index,
+ sfragment_table_index, indexes);
+ } else {
+ res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
+ length, fragment_table_index);
+ if(res == FALSE) {
+ ERROR("read_fragment_table: failed to read fragment "
+ "table index\n");
+ return FALSE;
+ }
+ }
+
+ for(i = 0; i < indexes; i++) {
+ int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
+ bytes & (SQUASHFS_METADATA_SIZE - 1);
+ int length = read_block(fd, fragment_table_index[i], NULL,
+ expected, ((char *) fragment_table) + ((long long) i *
+ SQUASHFS_METADATA_SIZE));
+ TRACE("Read fragment table block %d, from 0x%llx, length %d\n",
+ i, fragment_table_index[i], length);
+ if(length == FALSE) {
+ ERROR("read_fragment_table: failed to read fragment "
+ "table block\n");
+ return FALSE;
+ }
+ }
+
+ if(swap) {
+ squashfs_fragment_entry_3 sfragment;
+ for(i = 0; i < sBlk.s.fragments; i++) {
+ SQUASHFS_SWAP_FRAGMENT_ENTRY_3((&sfragment),
+ (&fragment_table[i]));
+ memcpy((char *) &fragment_table[i], (char *) &sfragment,
+ sizeof(squashfs_fragment_entry_3));
+ }
+ }
+
+ *table_start = fragment_table_index[0];
+ return TRUE;
+}
+
+
+static void read_fragment(unsigned int fragment, long long *start_block, int *size)
+{
+ TRACE("read_fragment: reading fragment %d\n", fragment);
+
+ squashfs_fragment_entry_3 *fragment_entry = &fragment_table[fragment];
+ *start_block = fragment_entry->start_block;
+ *size = fragment_entry->size;
+}
+
+
+static struct inode *read_inode(unsigned int start_block, unsigned int offset)
+{
+ static union squashfs_inode_header_3 header;
+ long long start = sBlk.s.inode_table_start + start_block;
+ long long st = start;
+ unsigned int off = offset;
+ static struct inode i;
+ int res;
+
+ TRACE("read_inode: reading inode [%d:%d]\n", start_block, offset);
+
+ if(swap) {
+ squashfs_base_inode_header_3 sinode;
+ res = read_inode_data(&sinode, &st, &off, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_BASE_INODE_HEADER_3(&header.base, &sinode,
+ sizeof(squashfs_base_inode_header_3));
+ } else
+ res = read_inode_data(&header.base, &st, &off, sizeof(header.base));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read inode %lld:%d\n", st, off);
+
+ i.xattr = SQUASHFS_INVALID_XATTR;
+
+ if(header.base.uid >= sBlk.no_uids)
+ EXIT_UNSQUASH("File system corrupted - uid index in inode too large (uid: %u)\n", header.base.uid);
+
+ i.uid = (uid_t) uid_table[header.base.uid];
+
+ if(header.base.guid == SQUASHFS_GUIDS)
+ i.gid = i.uid;
+ else if(header.base.guid >= sBlk.no_guids)
+ EXIT_UNSQUASH("File system corrupted - gid index in inode too large (gid: %u)\n", header.base.guid);
+ else
+ i.gid = (uid_t) guid_table[header.base.guid];
+
+ if(header.base.inode_type < 1 || header.base.inode_type > 9)
+ EXIT_UNSQUASH("File system corrupted - invalid type in inode (type: %u)\n", header.base.inode_type);
+
+ if(header.base.inode_number > sBlk.s.inodes)
+ EXIT_UNSQUASH("File system corrupted - inode number in inode too large (inode_number: %u)\n", header.base.inode_number);
+
+ if(header.base.inode_number == 0)
+ EXIT_UNSQUASH("File system corrupted - inode number zero is invalid\n", header.base.inode_number);
+
+ i.mode = lookup_type[header.base.inode_type] | header.base.mode;
+ i.type = header.base.inode_type;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = header.base.mtime;
+ i.inode_number = header.base.inode_number;
+
+ switch(header.base.inode_type) {
+ case SQUASHFS_DIR_TYPE: {
+ squashfs_dir_inode_header_3 *inode = &header.dir;
+
+ if(swap) {
+ squashfs_dir_inode_header_3 sinode;
+ res = read_inode_data(&sinode, &start, &offset, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_DIR_INODE_HEADER_3(&header.dir,
+ &sinode);
+ } else
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inode->file_size;
+ i.offset = inode->offset;
+ i.start = inode->start_block;
+ break;
+ }
+ case SQUASHFS_LDIR_TYPE: {
+ squashfs_ldir_inode_header_3 *inode = &header.ldir;
+
+ if(swap) {
+ squashfs_ldir_inode_header_3 sinode;
+ res = read_inode_data(&sinode, &start, &offset, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_LDIR_INODE_HEADER_3(&header.ldir,
+ &sinode);
+ } else
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inode->file_size;
+ i.offset = inode->offset;
+ i.start = inode->start_block;
+ break;
+ }
+ case SQUASHFS_FILE_TYPE: {
+ squashfs_reg_inode_header_3 *inode = &header.reg;
+
+ if(swap) {
+ squashfs_reg_inode_header_3 sinode;
+ res = read_inode_data(&sinode, &start, &offset, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_REG_INODE_HEADER_3(inode,
+ &sinode);
+ } else
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inode->file_size;
+ i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
+ ? 0 : inode->file_size % sBlk.s.block_size;
+ i.fragment = inode->fragment;
+ i.offset = inode->offset;
+ i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
+ (i.data + sBlk.s.block_size - 1) >>
+ sBlk.s.block_log :
+ i.data >> sBlk.s.block_log;
+ i.start = inode->start_block;
+ i.block_start = start;
+ i.block_offset = offset;
+ i.sparse = 1;
+ break;
+ }
+ case SQUASHFS_LREG_TYPE: {
+ squashfs_lreg_inode_header_3 *inode = &header.lreg;
+
+ if(swap) {
+ squashfs_lreg_inode_header_3 sinode;
+ res = read_inode_data(&sinode, &start, &offset, sizeof(sinode));
+ if(res)
+ SQUASHFS_SWAP_LREG_INODE_HEADER_3(inode,
+ &sinode);
+ } else
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inode->file_size;
+ i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
+ ? 0 : inode->file_size % sBlk.s.block_size;
+ i.fragment = inode->fragment;
+ i.offset = inode->offset;
+ i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
+ (inode->file_size + sBlk.s.block_size - 1) >>
+ sBlk.s.block_log :
+ inode->file_size >> sBlk.s.block_log;
+ i.start = inode->start_block;
+ i.block_start = start;
+ i.block_offset = offset;
+ i.sparse = 1;
+ break;
+ }
+ case SQUASHFS_SYMLINK_TYPE: {
+ squashfs_symlink_inode_header_3 *inodep =
+ &header.symlink;
+
+ if(swap) {
+ squashfs_symlink_inode_header_3 sinodep;
+ res = read_inode_data(&sinodep, &start, &offset, sizeof(sinodep));
+ if(res)
+ SQUASHFS_SWAP_SYMLINK_INODE_HEADER_3(inodep,
+ &sinodep);
+ } else
+ res = read_inode_data(inodep, &start, &offset, sizeof(*inodep));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.symlink = malloc(inodep->symlink_size + 1);
+ if(i.symlink == NULL)
+ MEM_ERROR();
+
+ res = read_inode_data(i.symlink, &start, &offset, inodep->symlink_size);
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode symbolic link %lld:%d\n", start, offset);
+
+ i.symlink[inodep->symlink_size] = '\0';
+ i.data = inodep->symlink_size;
+ break;
+ }
+ case SQUASHFS_BLKDEV_TYPE:
+ case SQUASHFS_CHRDEV_TYPE: {
+ squashfs_dev_inode_header_3 *inodep = &header.dev;
+
+ if(swap) {
+ squashfs_dev_inode_header_3 sinodep;
+ res = read_inode_data(&sinodep, &start, &offset, sizeof(sinodep));
+ if(res)
+ SQUASHFS_SWAP_DEV_INODE_HEADER_3(inodep,
+ &sinodep);
+ } else
+ res = read_inode_data(inodep, &start, &offset, sizeof(*inodep));
+
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ i.data = inodep->rdev;
+ break;
+ }
+ case SQUASHFS_FIFO_TYPE:
+ case SQUASHFS_SOCKET_TYPE:
+ i.data = 0;
+ break;
+ default:
+ EXIT_UNSQUASH("Unknown inode type %d in read_inode!\n",
+ header.base.inode_type);
+ }
+ return &i;
+}
+
+
+static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offset,
+ struct inode **i)
+{
+ squashfs_dir_header_3 dirh;
+ char buffer[sizeof(squashfs_dir_entry_3) + SQUASHFS_NAME_LEN + 1]
+ __attribute__((aligned));
+ squashfs_dir_entry_3 *dire = (squashfs_dir_entry_3 *) buffer;
+ long long start;
+ int bytes = 0;
+ int dir_count, size, res;
+ struct dir_ent *ent, *cur_ent = NULL;
+ struct dir *dir;
+
+ TRACE("squashfs_opendir: inode start block %d, offset %d\n",
+ block_start, offset);
+
+ *i = read_inode(block_start, offset);
+
+ dir = malloc(sizeof(struct dir));
+ if(dir == NULL)
+ MEM_ERROR();
+
+ dir->dir_count = 0;
+ dir->cur_entry = NULL;
+ dir->mode = (*i)->mode;
+ dir->uid = (*i)->uid;
+ dir->guid = (*i)->gid;
+ dir->mtime = (*i)->time;
+ dir->xattr = (*i)->xattr;
+ dir->dirs = NULL;
+
+ if ((*i)->data == 3)
+ /*
+ * if the directory is empty, skip the unnecessary
+ * lookup_entry, this fixes the corner case with
+ * completely empty filesystems where lookup_entry correctly
+ * returning -1 is incorrectly treated as an error
+ */
+ return dir;
+
+ start = sBlk.s.directory_table_start + (*i)->start;
+ offset = (*i)->offset;
+ size = (*i)->data + bytes - 3;
+
+ while(bytes < size) {
+ if(swap) {
+ squashfs_dir_header_3 sdirh;
+ res = read_directory_data(&sdirh, &start, &offset, sizeof(sdirh));
+ if(res)
+ SQUASHFS_SWAP_DIR_HEADER_3(&dirh, &sdirh);
+ } else
+ res = read_directory_data(&dirh, &start, &offset, sizeof(dirh));
+
+ if(res == FALSE)
+ goto corrupted;
+
+ dir_count = dirh.count + 1;
+ TRACE("squashfs_opendir: Read directory header @ byte position "
+ "%d, %d directory entries\n", bytes, dir_count);
+ bytes += sizeof(dirh);
+
+ /* dir_count should never be larger than SQUASHFS_DIR_COUNT */
+ if(dir_count > SQUASHFS_DIR_COUNT) {
+ ERROR("File system corrupted: too many entries in directory\n");
+ goto corrupted;
+ }
+
+ while(dir_count--) {
+ if(swap) {
+ squashfs_dir_entry_3 sdire;
+ res = read_directory_data(&sdire, &start,
+ &offset, sizeof(sdire));
+ if(res)
+ SQUASHFS_SWAP_DIR_ENTRY_3(dire, &sdire);
+ } else
+ res = read_directory_data(dire, &start,
+ &offset, sizeof(*dire));
+
+ if(res == FALSE)
+ goto corrupted;
+
+ bytes += sizeof(*dire);
+
+ /* size should never be SQUASHFS_NAME_LEN or larger */
+ if(dire->size >= SQUASHFS_NAME_LEN) {
+ ERROR("File system corrupted: filename too long\n");
+ goto corrupted;
+ }
+
+ res = read_directory_data(dire->name, &start, &offset,
+ dire->size + 1);
+
+ if(res == FALSE)
+ goto corrupted;
+
+ dire->name[dire->size + 1] = '\0';
+
+ /* check name for invalid characters (i.e /, ., ..) */
+ if(check_name(dire->name, dire->size + 1) == FALSE) {
+ ERROR("File system corrupted: invalid characters in name\n");
+ goto corrupted;
+ }
+
+ TRACE("squashfs_opendir: directory entry %s, inode "
+ "%d:%d, type %d\n", dire->name,
+ dirh.start_block, dire->offset, dire->type);
+
+ ent = malloc(sizeof(struct dir_ent));
+ if(ent == NULL)
+ MEM_ERROR();
+
+ ent->name = strdup(dire->name);
+ ent->start_block = dirh.start_block;
+ ent->offset = dire->offset;
+ ent->type = dire->type;
+ ent->next = NULL;
+ if(cur_ent == NULL)
+ dir->dirs = ent;
+ else
+ cur_ent->next = ent;
+ cur_ent = ent;
+ dir->dir_count ++;
+ bytes += dire->size + 1;
+ }
+ }
+
+ /* check directory for duplicate names and sorting */
+ if(check_directory(dir) == FALSE) {
+ ERROR("File system corrupted: directory has duplicate names or is unsorted\n");
+ goto corrupted;
+ }
+
+ return dir;
+
+corrupted:
+ squashfs_closedir(dir);
+ return NULL;
+}
+
+
+static int parse_exports_table(long long *table_start)
+{
+ /*
+ * Note on overflow limits:
+ * Size of SBlk.s.inodes is 2^32 (unsigned int)
+ * Max indexes is (2^32*8)/8K or 2^22
+ * Max length is ((2^32*8)/8K)*8 or 2^25
+ */
+ int res;
+ int indexes = SQUASHFS_LOOKUP_BLOCKS_3((long long) sBlk.s.inodes);
+ int length = SQUASHFS_LOOKUP_BLOCK_BYTES_3((long long) sBlk.s.inodes);
+ long long *export_index_table;
+
+ /*
+ * The size of the index table (length bytes) should match the
+ * table start and end points
+ */
+ if(length != (*table_start - sBlk.s.lookup_table_start)) {
+ ERROR("parse_exports_table: Bad inode count in super block\n");
+ return FALSE;
+ }
+
+ export_index_table = alloc_index_table(indexes);
+
+ if(swap) {
+ long long *sexport_index_table = salloc_index_table(indexes);
+
+ res = read_fs_bytes(fd, sBlk.s.lookup_table_start,
+ length, sexport_index_table);
+ if(res == FALSE) {
+ ERROR("parse_exorts_table: failed to read export "
+ "index table\n");
+ return FALSE;
+ }
+ SQUASHFS_SWAP_LOOKUP_BLOCKS_3(export_index_table,
+ sexport_index_table, indexes);
+ } else {
+ res = read_fs_bytes(fd, sBlk.s.lookup_table_start, length,
+ export_index_table);
+ if(res == FALSE) {
+ ERROR("parse_exorts_table: failed to read export "
+ "index table\n");
+ return FALSE;
+ }
+ }
+
+ /*
+ * export_index_table[0] stores the start of the compressed export blocks.
+ * This by definition is also the end of the previous filesystem
+ * table - the fragment table.
+ */
+ *table_start = export_index_table[0];
+
+ return TRUE;
+}
+
+
+static int read_filesystem_tables()
+{
+ long long table_start;
+
+ /* Read uid and gid lookup tables */
+
+ /* Sanity check super block contents */
+ if(sBlk.no_guids) {
+ if(sBlk.guid_start >= sBlk.s.bytes_used) {
+ ERROR("read_filesystem_tables: gid start too large in super block\n");
+ goto corrupted;
+ }
+
+ if(read_ids(sBlk.no_guids, sBlk.guid_start, sBlk.s.bytes_used, &guid_table) == FALSE)
+ goto corrupted;
+
+ table_start = sBlk.guid_start;
+ } else {
+ /* no guids, guid_start should be 0 */
+ if(sBlk.guid_start != 0) {
+ ERROR("read_filesystem_tables: gid start too large in super block\n");
+ goto corrupted;
+ }
+
+ table_start = sBlk.s.bytes_used;
+ }
+
+ if(sBlk.uid_start >= table_start) {
+ ERROR("read_filesystem_tables: uid start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* There should be at least one uid */
+ if(sBlk.no_uids == 0) {
+ ERROR("read_filesystem_tables: uid count bad in super block\n");
+ goto corrupted;
+ }
+
+ if(read_ids(sBlk.no_uids, sBlk.uid_start, table_start, &uid_table) == FALSE)
+ goto corrupted;
+
+ table_start = sBlk.uid_start;
+
+ /* Read exports table */
+ if(sBlk.s.lookup_table_start != SQUASHFS_INVALID_BLK) {
+
+ /* sanity check super block contents */
+ if(sBlk.s.lookup_table_start >= table_start) {
+ ERROR("read_filesystem_tables: lookup table start too large in super block\n");
+ goto corrupted;
+ }
+
+ if(parse_exports_table(&table_start) == FALSE)
+ goto corrupted;
+ }
+
+ /* Read fragment table */
+ if(sBlk.s.fragments != 0) {
+
+ /* Sanity check super block contents */
+ if(sBlk.s.fragment_table_start >= table_start) {
+ ERROR("read_filesystem_tables: fragment table start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* The number of fragments should not exceed the number of inodes */
+ if(sBlk.s.fragments > sBlk.s.inodes) {
+ ERROR("read_filesystem_tables: Bad fragment count in super block\n");
+ goto corrupted;
+ }
+
+ if(read_fragment_table(&table_start) == FALSE)
+ goto corrupted;
+ } else {
+ /*
+ * Sanity check super block contents - with 0 fragments,
+ * the fragment table should be empty
+ */
+ if(sBlk.s.fragment_table_start != table_start) {
+ ERROR("read_filesystem_tables: fragment table start invalid in super block\n");
+ goto corrupted;
+ }
+ }
+
+ /* Sanity check super block directory table values */
+ if(sBlk.s.directory_table_start > table_start) {
+ ERROR("read_filesystem_tables: directory table start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* Sanity check super block inode table values */
+ if(sBlk.s.inode_table_start >= sBlk.s.directory_table_start) {
+ ERROR("read_filesystem_tables: inode table start too large in super block\n");
+ goto corrupted;
+ }
+
+ alloc_index_table(0);
+ salloc_index_table(0);
+
+ return TRUE;
+
+corrupted:
+ alloc_index_table(0);
+ salloc_index_table(0);
+
+ return FALSE;
+}
+
+
+int read_super_3(char *source, squashfs_operations **s_ops, void *s)
+{
+ squashfs_super_block_3 *sBlk_3 = s;
+
+ /*
+ * Try to read a squashfs 3 superblock (compatible with 1 and 2 filesystems)
+ */
+ int res = read_fs_bytes(fd, SQUASHFS_START, sizeof(*sBlk_3), sBlk_3);
+
+ if(res == FALSE)
+ return res;
+ /*
+ * Check it is a SQUASHFS superblock
+ */
+ swap = 0;
+ if(sBlk_3->s_magic == SQUASHFS_MAGIC_SWAP) {
+ squashfs_super_block_3 sblk;
+ ERROR("Reading a different endian SQUASHFS filesystem on %s\n", source);
+ SQUASHFS_SWAP_SUPER_BLOCK_3(&sblk, sBlk_3);
+ memcpy(sBlk_3, &sblk, sizeof(squashfs_super_block_3));
+ swap = 1;
+ }
+
+ if(sBlk_3->s_magic != SQUASHFS_MAGIC || sBlk_3->s_major != 3 ||
+ sBlk_3->s_minor > 1)
+ return -1;
+
+ sBlk.s.s_magic = sBlk_3->s_magic;
+ sBlk.s.inodes = sBlk_3->inodes;
+ sBlk.s.mkfs_time = sBlk_3->mkfs_time;
+ sBlk.s.block_size = sBlk_3->block_size;
+ sBlk.s.fragments = sBlk_3->fragments;
+ sBlk.s.block_log = sBlk_3->block_log;
+ sBlk.s.flags = sBlk_3->flags;
+ sBlk.s.s_major = sBlk_3->s_major;
+ sBlk.s.s_minor = sBlk_3->s_minor;
+ sBlk.s.root_inode = sBlk_3->root_inode;
+ sBlk.s.bytes_used = sBlk_3->bytes_used;
+ sBlk.s.inode_table_start = sBlk_3->inode_table_start;
+ sBlk.s.directory_table_start = sBlk_3->directory_table_start;
+ sBlk.s.fragment_table_start = sBlk_3->fragment_table_start;
+ sBlk.s.lookup_table_start = sBlk_3->lookup_table_start;
+ sBlk.no_uids = sBlk_3->no_uids;
+ sBlk.no_guids = sBlk_3->no_guids;
+ sBlk.uid_start = sBlk_3->uid_start;
+ sBlk.guid_start = sBlk_3->guid_start;
+ sBlk.s.xattr_id_table_start = SQUASHFS_INVALID_BLK;
+
+ *s_ops = &ops;
+
+ /*
+ * 3.x filesystems use gzip compression.
+ */
+ comp = lookup_compressor("gzip");
+ return TRUE;
+}
+
+
+static void squashfs_stat(char *source)
+{
+ time_t mkfs_time = (time_t) sBlk.s.mkfs_time;
+ struct tm *t = use_localtime ? localtime(&mkfs_time) :
+ gmtime(&mkfs_time);
+ char *mkfs_str = asctime(t);
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+ printf("Found a valid %sSQUASHFS %d:%d superblock on %s.\n",
+ swap ? "little endian " : "big endian ", sBlk.s.s_major,
+ sBlk.s.s_minor, source);
+#else
+ printf("Found a valid %sSQUASHFS %d:%d superblock on %s.\n",
+ swap ? "big endian " : "little endian ", sBlk.s.s_major,
+ sBlk.s.s_minor, source);
+#endif
+
+ printf("Creation or last append time %s", mkfs_str ? mkfs_str :
+ "failed to get time\n");
+ printf("Filesystem size %llu bytes (%.2f Kbytes / %.2f Mbytes)\n",
+ sBlk.s.bytes_used, sBlk.s.bytes_used / 1024.0,
+ sBlk.s.bytes_used / (1024.0 * 1024.0));
+ printf("Block size %d\n", sBlk.s.block_size);
+ printf("Filesystem is %sexportable via NFS\n",
+ SQUASHFS_EXPORTABLE(sBlk.s.flags) ? "" : "not ");
+ printf("Inodes are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_INODES(sBlk.s.flags) ? "un" : "");
+ printf("Data is %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_DATA(sBlk.s.flags) ? "un" : "");
+
+ if(SQUASHFS_NO_FRAGMENTS(sBlk.s.flags))
+ printf("Fragments are not stored\n");
+ else {
+ printf("Fragments are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_FRAGMENTS(sBlk.s.flags) ? "un" : "");
+ printf("Always-use-fragments option is %sspecified\n",
+ SQUASHFS_ALWAYS_FRAGMENTS(sBlk.s.flags) ? "" : "not ");
+ }
+
+ printf("Check data is %spresent in the filesystem\n",
+ SQUASHFS_CHECK_DATA(sBlk.s.flags) ? "" : "not ");
+ printf("Duplicates are %sremoved\n", SQUASHFS_DUPLICATES(sBlk.s.flags)
+ ? "" : "not ");
+ printf("Number of fragments %d\n", sBlk.s.fragments);
+ printf("Number of inodes %d\n", sBlk.s.inodes);
+ printf("Number of uids %d\n", sBlk.no_uids);
+ printf("Number of gids %d\n", sBlk.no_guids);
+
+ TRACE("sBlk.s.inode_table_start 0x%llx\n", sBlk.s.inode_table_start);
+ TRACE("sBlk.s.directory_table_start 0x%llx\n", sBlk.s.directory_table_start);
+ TRACE("sBlk.s.fragment_table_start 0x%llx\n\n", sBlk.s.fragment_table_start);
+ TRACE("sBlk.s.lookup_table_start 0x%llx\n\n", sBlk.s.lookup_table_start);
+ TRACE("sBlk.uid_start 0x%llx\n", sBlk.uid_start);
+ TRACE("sBlk.guid_start 0x%llx\n", sBlk.guid_start);
+}
+
+
+static squashfs_operations ops = {
+ .opendir = squashfs_opendir,
+ .read_fragment = read_fragment,
+ .read_block_list = read_block_list,
+ .read_inode = read_inode,
+ .read_filesystem_tables = read_filesystem_tables,
+ .stat = squashfs_stat
+};
diff --git a/squashfs-tools/unsquash-34.c b/squashfs-tools/unsquash-34.c
new file mode 100644
index 0000000..59d28f1
--- /dev/null
+++ b/squashfs-tools/unsquash-34.c
@@ -0,0 +1,183 @@
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2019, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquash-34.c
+ *
+ * Helper functions used by unsquash-3 and unsquash-4.
+ */
+
+#include "unsquashfs.h"
+
+static unsigned int **inumber_table = NULL;
+static char ***lookup_table = NULL;
+
+long long *alloc_index_table(int indexes)
+{
+ static long long *alloc_table = NULL;
+ static int alloc_size = 0;
+ int length = indexes * sizeof(long long);
+
+ if(alloc_size < length || length == 0) {
+ long long *table = realloc(alloc_table, length);
+
+ if(table == NULL && length !=0)
+ MEM_ERROR();
+
+ alloc_table = table;
+ alloc_size = length;
+ }
+
+ return alloc_table;
+}
+
+
+/* These functions implement a bit-table to track whether directories have been
+ * already visited. This is to trap corrupted filesystems which have multiple
+ * links to the same directory, which is invalid, and which may also create
+ * a directory loop, where Unsquashfs will endlessly recurse until either
+ * the pathname is too large (extracting), or the stack overflows.
+ *
+ * Each index entry is 8 Kbytes, and tracks 65536 inode numbers. The index is
+ * allocated on demand because Unsquashfs may not walk the complete filesystem.
+ */
+static void create_inumber_table()
+{
+ int indexes = INUMBER_INDEXES(sBlk.s.inodes);
+
+ inumber_table = malloc(indexes * sizeof(unsigned int *));
+ if(inumber_table == NULL)
+ MEM_ERROR();
+ memset(inumber_table, 0, indexes * sizeof(unsigned int *));
+}
+
+
+int inumber_lookup(unsigned int number)
+{
+ int index = INUMBER_INDEX(number - 1);
+ int offset = INUMBER_OFFSET(number - 1);
+ int bit = INUMBER_BIT(number - 1);
+
+ if(inumber_table == NULL)
+ create_inumber_table();
+
+ /* Lookup number in the bit table */
+ if(inumber_table[index] && (inumber_table[index][offset] & bit))
+ return TRUE;
+
+ if(inumber_table[index] == NULL) {
+ inumber_table[index] = malloc(INUMBER_BYTES);
+ if(inumber_table[index] == NULL)
+ MEM_ERROR();
+ memset(inumber_table[index], 0, INUMBER_BYTES);
+ }
+
+ inumber_table[index][offset] |= bit;
+ return FALSE;
+}
+
+
+void free_inumber_table()
+{
+ int i, indexes = INUMBER_INDEXES(sBlk.s.inodes);
+
+ if(inumber_table) {
+ for(i = 0; i < indexes; i++)
+ if(inumber_table[i])
+ free(inumber_table[i]);
+ free(inumber_table);
+ inumber_table = NULL;
+ }
+}
+
+
+/* These functions implement a lookup table to track creation of (non-directory)
+ * inodes, and to discover if a hard-link to a previously created file should
+ * be made.
+ *
+ * Each index entry is 32 Kbytes, and tracks 4096 inode numbers. The index is
+ * allocated on demand because Unsquashfs may not walk the complete filesystem.
+ */
+static void create_lookup_table()
+{
+ int indexes = LOOKUP_INDEXES(sBlk.s.inodes);
+
+ lookup_table = malloc(indexes * sizeof(char *));
+ if(lookup_table == NULL)
+ MEM_ERROR();
+ memset(lookup_table, 0, indexes * sizeof(char *));
+}
+
+
+char *lookup(unsigned int number)
+{
+ int index = LOOKUP_INDEX(number - 1);
+ int offset = LOOKUP_OFFSET(number - 1);
+
+ if(lookup_table == NULL)
+ create_lookup_table();
+
+ /* Lookup number in table */
+ if(lookup_table[index] == NULL)
+ return NULL;
+
+ return lookup_table[index][offset];
+}
+
+
+void insert_lookup(unsigned int number, char *pathname)
+{
+ int index = LOOKUP_INDEX(number - 1);
+ int offset = LOOKUP_OFFSET(number - 1);
+
+ if(lookup_table == NULL)
+ create_lookup_table();
+
+ if(lookup_table[index] == NULL) {
+ lookup_table[index] = malloc(LOOKUP_BYTES);
+ if(lookup_table[index] == NULL)
+ MEM_ERROR();
+ memset(lookup_table[index], 0, LOOKUP_BYTES);
+ }
+
+ lookup_table[index][offset] = pathname;
+}
+
+
+void free_lookup_table(int free_pathname)
+{
+ int i, indexes = LOOKUP_INDEXES(sBlk.s.inodes);
+
+ if(lookup_table) {
+ for(i = 0; i < indexes; i++)
+ if(lookup_table[i]) {
+ if(free_pathname) {
+ int j;
+
+ for(j = 0; j < LOOKUP_OFFSETS; j++)
+ if(lookup_table[i][j])
+ free(lookup_table[i][j]);
+ }
+ free(lookup_table[i]);
+ }
+ free(lookup_table);
+ lookup_table = NULL;
+ }
+}
diff --git a/squashfs-tools/unsquash-4.c b/squashfs-tools/unsquash-4.c
new file mode 100644
index 0000000..d0f6920
--- /dev/null
+++ b/squashfs-tools/unsquash-4.c
@@ -0,0 +1,832 @@
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2019, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquash-4.c
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_swap.h"
+#include "xattr.h"
+#include "compressor.h"
+
+static struct squashfs_fragment_entry *fragment_table;
+static unsigned int *id_table;
+static squashfs_operations ops;
+
+static void read_block_list(unsigned int *block_list, long long start,
+ unsigned int offset, int blocks)
+{
+ int res;
+
+ TRACE("read_block_list: blocks %d\n", blocks);
+
+ res = read_inode_data(block_list, &start, &offset, blocks * sizeof(unsigned int));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_block_list: failed to read "
+ "inode index %lld:%d\n", start, offset);
+
+ SQUASHFS_INSWAP_INTS(block_list, blocks);
+}
+
+
+static int read_fragment_table(long long *table_start)
+{
+ /*
+ * Note on overflow limits:
+ * Size of SBlk.s.fragments is 2^32 (unsigned int)
+ * Max size of bytes is 2^32*16 or 2^36
+ * Max indexes is (2^32*16)/8K or 2^23
+ * Max length is ((2^32*16)/8K)*8 or 2^26 or 64M
+ */
+ int res;
+ unsigned int i;
+ long long bytes = SQUASHFS_FRAGMENT_BYTES((long long) sBlk.s.fragments);
+ int indexes = SQUASHFS_FRAGMENT_INDEXES((long long) sBlk.s.fragments);
+ int length = SQUASHFS_FRAGMENT_INDEX_BYTES((long long) sBlk.s.fragments);
+ long long *fragment_table_index;
+
+ /*
+ * The size of the index table (length bytes) should match the
+ * table start and end points
+ */
+ if(length != (*table_start - sBlk.s.fragment_table_start)) {
+ ERROR("read_fragment_table: Bad fragment count in super block\n");
+ return FALSE;
+ }
+
+ TRACE("read_fragment_table: %u fragments, reading %d fragment indexes "
+ "from 0x%llx\n", sBlk.s.fragments, indexes,
+ sBlk.s.fragment_table_start);
+
+ fragment_table_index = alloc_index_table(indexes);
+ fragment_table = malloc(bytes);
+ if(fragment_table == NULL)
+ MEM_ERROR();
+
+ res = read_fs_bytes(fd, sBlk.s.fragment_table_start, length,
+ fragment_table_index);
+ if(res == FALSE) {
+ ERROR("read_fragment_table: failed to read fragment table "
+ "index\n");
+ return FALSE;
+ }
+ SQUASHFS_INSWAP_FRAGMENT_INDEXES(fragment_table_index, indexes);
+
+ for(i = 0; i < indexes; i++) {
+ int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
+ bytes & (SQUASHFS_METADATA_SIZE - 1);
+ int length = read_block(fd, fragment_table_index[i], NULL,
+ expected, ((char *) fragment_table) + (i *
+ SQUASHFS_METADATA_SIZE));
+ TRACE("Read fragment table block %d, from 0x%llx, length %d\n",
+ i, fragment_table_index[i], length);
+ if(length == FALSE) {
+ ERROR("read_fragment_table: failed to read fragment "
+ "table index\n");
+ return FALSE;
+ }
+ }
+
+ for(i = 0; i < sBlk.s.fragments; i++)
+ SQUASHFS_INSWAP_FRAGMENT_ENTRY(&fragment_table[i]);
+
+ *table_start = fragment_table_index[0];
+ return TRUE;
+}
+
+
+static void read_fragment(unsigned int fragment, long long *start_block, int *size)
+{
+ TRACE("read_fragment: reading fragment %d\n", fragment);
+
+ struct squashfs_fragment_entry *fragment_entry;
+
+ if(fragment >= sBlk.s.fragments)
+ EXIT_UNSQUASH("File system corrupted - fragment index in inode too large (fragment: %u)\n", fragment);
+
+ fragment_entry = &fragment_table[fragment];
+ *start_block = fragment_entry->start_block;
+ *size = fragment_entry->size;
+}
+
+
+static struct inode *read_inode(unsigned int start_block, unsigned int offset)
+{
+ static union squashfs_inode_header header;
+ long long start = sBlk.s.inode_table_start + start_block;
+ long long st = start;
+ unsigned int off = offset;
+ static struct inode i;
+ int res;
+
+ TRACE("read_inode: reading inode [%d:%d]\n", start_block, offset);
+
+ res = read_inode_data(&header.base, &st, &off, sizeof(header.base));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read inode %lld:%d\n", st, off);
+
+ SQUASHFS_INSWAP_BASE_INODE_HEADER(&header.base);
+
+ if(header.base.uid >= sBlk.s.no_ids)
+ EXIT_UNSQUASH("File system corrupted - uid index in inode too large (uid: %u)\n", header.base.uid);
+
+ if(header.base.guid >= sBlk.s.no_ids)
+ EXIT_UNSQUASH("File system corrupted - gid index in inode too large (gid: %u)\n", header.base.guid);
+
+ if(header.base.inode_type < 1 || header.base.inode_type > 14)
+ EXIT_UNSQUASH("File system corrupted - invalid type in inode (type: %u)\n", header.base.inode_type);
+
+ if(header.base.inode_number > sBlk.s.inodes)
+ EXIT_UNSQUASH("File system corrupted - inode number in inode too large (inode_number: %u)\n", header.base.inode_number);
+
+ if(header.base.inode_number == 0)
+ EXIT_UNSQUASH("File system corrupted - inode number zero is invalid\n", header.base.inode_number);
+
+ i.uid = (uid_t) id_table[header.base.uid];
+ i.gid = (uid_t) id_table[header.base.guid];
+ i.mode = lookup_type[header.base.inode_type] | header.base.mode;
+ i.type = header.base.inode_type;
+ if(time_opt)
+ i.time = timeval;
+ else
+ i.time = header.base.mtime;
+ i.inode_number = header.base.inode_number;
+
+ switch(header.base.inode_type) {
+ case SQUASHFS_DIR_TYPE: {
+ struct squashfs_dir_inode_header *inode = &header.dir;
+
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ SQUASHFS_INSWAP_DIR_INODE_HEADER(inode);
+
+ i.data = inode->file_size;
+ i.offset = inode->offset;
+ i.start = inode->start_block;
+ i.xattr = SQUASHFS_INVALID_XATTR;
+ break;
+ }
+ case SQUASHFS_LDIR_TYPE: {
+ struct squashfs_ldir_inode_header *inode = &header.ldir;
+
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ SQUASHFS_INSWAP_LDIR_INODE_HEADER(inode);
+
+ i.data = inode->file_size;
+ i.offset = inode->offset;
+ i.start = inode->start_block;
+ i.xattr = inode->xattr;
+ break;
+ }
+ case SQUASHFS_FILE_TYPE: {
+ struct squashfs_reg_inode_header *inode = &header.reg;
+
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ SQUASHFS_INSWAP_REG_INODE_HEADER(inode);
+
+ i.data = inode->file_size;
+ i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
+ ? 0 : inode->file_size % sBlk.s.block_size;
+ i.fragment = inode->fragment;
+ i.offset = inode->offset;
+ i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
+ (i.data + sBlk.s.block_size - 1) >>
+ sBlk.s.block_log :
+ i.data >> sBlk.s.block_log;
+ i.start = inode->start_block;
+ i.block_start = start;
+ i.block_offset = offset;
+ i.sparse = 0;
+ i.xattr = SQUASHFS_INVALID_XATTR;
+ break;
+ }
+ case SQUASHFS_LREG_TYPE: {
+ struct squashfs_lreg_inode_header *inode = &header.lreg;
+
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ SQUASHFS_INSWAP_LREG_INODE_HEADER(inode);
+
+ i.data = inode->file_size;
+ i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
+ ? 0 : inode->file_size % sBlk.s.block_size;
+ i.fragment = inode->fragment;
+ i.offset = inode->offset;
+ i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
+ (inode->file_size + sBlk.s.block_size - 1) >>
+ sBlk.s.block_log :
+ inode->file_size >> sBlk.s.block_log;
+ i.start = inode->start_block;
+ i.block_start = start;
+ i.block_offset = offset;
+ i.sparse = inode->sparse != 0;
+ i.xattr = inode->xattr;
+ break;
+ }
+ case SQUASHFS_SYMLINK_TYPE:
+ case SQUASHFS_LSYMLINK_TYPE: {
+ struct squashfs_symlink_inode_header *inode = &header.symlink;
+
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ SQUASHFS_INSWAP_SYMLINK_INODE_HEADER(inode);
+
+ if(inode->symlink_size > SQUASHFS_SYMLINK_MAX)
+ EXIT_UNSQUASH("File system corrupted - symlink_size in inode too large (symlink_size: %u)\n", inode->symlink_size);
+
+ i.symlink = malloc(inode->symlink_size + 1);
+ if(i.symlink == NULL)
+ MEM_ERROR();
+
+ res = read_inode_data(i.symlink, &start, &offset, inode->symlink_size);
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode symbolic link %lld:%d\n", start, offset);
+
+ i.symlink[inode->symlink_size] = '\0';
+ i.data = inode->symlink_size;
+
+ if(header.base.inode_type == SQUASHFS_LSYMLINK_TYPE) {
+ res = read_inode_data(&i.xattr, &start, &offset, sizeof(unsigned int));
+ SQUASHFS_INSWAP_INTS(&i.xattr, 1);
+ } else
+ i.xattr = SQUASHFS_INVALID_XATTR;
+ break;
+ }
+ case SQUASHFS_BLKDEV_TYPE:
+ case SQUASHFS_CHRDEV_TYPE: {
+ struct squashfs_dev_inode_header *inode = &header.dev;
+
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ SQUASHFS_INSWAP_DEV_INODE_HEADER(inode);
+
+ i.data = inode->rdev;
+ i.xattr = SQUASHFS_INVALID_XATTR;
+ break;
+ }
+ case SQUASHFS_LBLKDEV_TYPE:
+ case SQUASHFS_LCHRDEV_TYPE: {
+ struct squashfs_ldev_inode_header *inode = &header.ldev;
+
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ SQUASHFS_INSWAP_LDEV_INODE_HEADER(inode);
+
+ i.data = inode->rdev;
+ i.xattr = inode->xattr;
+ break;
+ }
+ case SQUASHFS_FIFO_TYPE:
+ case SQUASHFS_SOCKET_TYPE:
+ i.data = 0;
+ i.xattr = SQUASHFS_INVALID_XATTR;
+ break;
+ case SQUASHFS_LFIFO_TYPE:
+ case SQUASHFS_LSOCKET_TYPE: {
+ struct squashfs_lipc_inode_header *inode = &header.lipc;
+
+ res = read_inode_data(inode, &start, &offset, sizeof(*inode));
+ if(res == FALSE)
+ EXIT_UNSQUASH("read_inode: failed to read "
+ "inode %lld:%d\n", start, offset);
+
+ SQUASHFS_INSWAP_LIPC_INODE_HEADER(inode);
+
+ i.data = 0;
+ i.xattr = inode->xattr;
+ break;
+ }
+ default:
+ EXIT_UNSQUASH("Unknown inode type %d in read_inode!\n",
+ header.base.inode_type);
+ }
+ return &i;
+}
+
+
+static struct dir *squashfs_opendir(unsigned int block_start, unsigned int offset,
+ struct inode **i)
+{
+ struct squashfs_dir_header dirh;
+ char buffer[sizeof(struct squashfs_dir_entry) + SQUASHFS_NAME_LEN + 1]
+ __attribute__((aligned));
+ struct squashfs_dir_entry *dire = (struct squashfs_dir_entry *) buffer;
+ long long start;
+ int bytes = 0, dir_count, size, res;
+ struct dir_ent *ent, *cur_ent = NULL;
+ struct dir *dir;
+
+ TRACE("squashfs_opendir: inode start block %d, offset %d\n",
+ block_start, offset);
+
+ *i = read_inode(block_start, offset);
+
+ dir = malloc(sizeof(struct dir));
+ if(dir == NULL)
+ MEM_ERROR();
+
+ dir->dir_count = 0;
+ dir->cur_entry = NULL;
+ dir->mode = (*i)->mode;
+ dir->uid = (*i)->uid;
+ dir->guid = (*i)->gid;
+ dir->mtime = (*i)->time;
+ dir->xattr = (*i)->xattr;
+ dir->dirs = NULL;
+
+ if ((*i)->data == 3)
+ /*
+ * if the directory is empty, skip the unnecessary
+ * lookup_entry, this fixes the corner case with
+ * completely empty filesystems where lookup_entry correctly
+ * returning -1 is incorrectly treated as an error
+ */
+ return dir;
+
+ start = sBlk.s.directory_table_start + (*i)->start;
+ offset = (*i)->offset;
+ size = (*i)->data + bytes - 3;
+
+ while(bytes < size) {
+ res = read_directory_data(&dirh, &start, &offset, sizeof(dirh));
+ if(res == FALSE)
+ goto corrupted;
+
+ SQUASHFS_INSWAP_DIR_HEADER(&dirh);
+
+ dir_count = dirh.count + 1;
+ TRACE("squashfs_opendir: Read directory header @ byte position "
+ "%d, %d directory entries\n", bytes, dir_count);
+ bytes += sizeof(dirh);
+
+ /* dir_count should never be larger than SQUASHFS_DIR_COUNT */
+ if(dir_count > SQUASHFS_DIR_COUNT) {
+ ERROR("File system corrupted: too many entries in directory\n");
+ goto corrupted;
+ }
+
+ while(dir_count--) {
+ res = read_directory_data(dire, &start, &offset, sizeof(*dire));
+ if(res == FALSE)
+ goto corrupted;
+
+ SQUASHFS_INSWAP_DIR_ENTRY(dire);
+
+ bytes += sizeof(*dire);
+
+ /* size should never be SQUASHFS_NAME_LEN or larger */
+ if(dire->size >= SQUASHFS_NAME_LEN) {
+ ERROR("File system corrupted: filename too long\n");
+ goto corrupted;
+ }
+
+ res = read_directory_data(dire->name, &start, &offset,
+ dire->size + 1);
+ if(res == FALSE)
+ goto corrupted;
+
+ dire->name[dire->size + 1] = '\0';
+
+ /* check name for invalid characters (i.e /, ., ..) */
+ if(check_name(dire->name, dire->size + 1) == FALSE) {
+ ERROR("File system corrupted: invalid characters in name\n");
+ goto corrupted;
+ }
+
+ TRACE("squashfs_opendir: directory entry %s, inode "
+ "%d:%d, type %d\n", dire->name,
+ dirh.start_block, dire->offset, dire->type);
+
+ ent = malloc(sizeof(struct dir_ent));
+ if(ent == NULL)
+ MEM_ERROR();
+
+ ent->name = strdup(dire->name);
+ ent->start_block = dirh.start_block;
+ ent->offset = dire->offset;
+ ent->type = dire->type;
+ ent->next = NULL;
+ if(cur_ent == NULL)
+ dir->dirs = ent;
+ else
+ cur_ent->next = ent;
+ cur_ent = ent;
+ dir->dir_count ++;
+ bytes += dire->size + 1;
+ }
+ }
+
+ /* check directory for duplicate names and sorting */
+ if(check_directory(dir) == FALSE) {
+ ERROR("File system corrupted: directory has duplicate names or is unsorted\n");
+ goto corrupted;
+ }
+
+ return dir;
+
+corrupted:
+ squashfs_closedir(dir);
+ return NULL;
+}
+
+
+static int read_id_table(long long *table_start)
+{
+ /*
+ * Note on overflow limits:
+ * Size of SBlk.s.no_ids is 2^16 (unsigned short)
+ * Max size of bytes is 2^16*4 or 256K
+ * Max indexes is (2^16*4)/8K or 32
+ * Max length is ((2^16*4)/8K)*8 or 256
+ */
+ int res, i;
+ int bytes = SQUASHFS_ID_BYTES(sBlk.s.no_ids);
+ int indexes = SQUASHFS_ID_BLOCKS(sBlk.s.no_ids);
+ int length = SQUASHFS_ID_BLOCK_BYTES(sBlk.s.no_ids);
+ long long *id_index_table;
+
+ /*
+ * The size of the index table (length bytes) should match the
+ * table start and end points
+ */
+ if(length != (*table_start - sBlk.s.id_table_start)) {
+ ERROR("read_id_table: Bad id count in super block\n");
+ return FALSE;
+ }
+
+ TRACE("read_id_table: no_ids %d\n", sBlk.s.no_ids);
+
+ id_index_table = alloc_index_table(indexes);
+
+ id_table = malloc(bytes);
+ if(id_table == NULL) {
+ ERROR("read_id_table: failed to allocate id table\n");
+ return FALSE;
+ }
+
+ res = read_fs_bytes(fd, sBlk.s.id_table_start, length, id_index_table);
+ if(res == FALSE) {
+ ERROR("read_id_table: failed to read id index table\n");
+ return FALSE;
+ }
+ SQUASHFS_INSWAP_ID_BLOCKS(id_index_table, indexes);
+
+ /*
+ * id_index_table[0] stores the start of the compressed id blocks.
+ * This by definition is also the end of the previous filesystem
+ * table - this may be the exports table if it is present, or the
+ * fragments table if it isn't.
+ */
+ *table_start = id_index_table[0];
+
+ for(i = 0; i < indexes; i++) {
+ int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
+ bytes & (SQUASHFS_METADATA_SIZE - 1);
+ res = read_block(fd, id_index_table[i], NULL, expected,
+ ((char *) id_table) + i * SQUASHFS_METADATA_SIZE);
+ if(res == FALSE) {
+ ERROR("read_id_table: failed to read id table block"
+ "\n");
+ return FALSE;
+ }
+ }
+
+ SQUASHFS_INSWAP_INTS(id_table, sBlk.s.no_ids);
+
+ return TRUE;
+}
+
+
+static int parse_exports_table(long long *table_start)
+{
+ /*
+ * Note on overflow limits:
+ * Size of SBlk.s.inodes is 2^32 (unsigned int)
+ * Max indexes is (2^32*8)/8K or 2^22
+ * Max length is ((2^32*8)/8K)*8 or 2^25
+ */
+ int res;
+ int indexes = SQUASHFS_LOOKUP_BLOCKS((long long) sBlk.s.inodes);
+ int length = SQUASHFS_LOOKUP_BLOCK_BYTES((long long) sBlk.s.inodes);
+ long long *export_index_table;
+
+ /*
+ * The size of the index table (length bytes) should match the
+ * table start and end points
+ */
+ if(length != (*table_start - sBlk.s.lookup_table_start)) {
+ ERROR("parse_exports_table: Bad inode count in super block\n");
+ return FALSE;
+ }
+
+ export_index_table = alloc_index_table(indexes);
+
+ res = read_fs_bytes(fd, sBlk.s.lookup_table_start, length,
+ export_index_table);
+ if(res == FALSE) {
+ ERROR("parse_exports_table: failed to read export index table\n");
+ return FALSE;
+ }
+ SQUASHFS_INSWAP_LOOKUP_BLOCKS(export_index_table, indexes);
+
+ /*
+ * export_index_table[0] stores the start of the compressed export blocks.
+ * This by definition is also the end of the previous filesystem
+ * table - the fragment table.
+ */
+ *table_start = export_index_table[0];
+
+ return TRUE;
+}
+
+
+static int read_filesystem_tables()
+{
+ long long table_start;
+
+ /* Read xattrs */
+ if(sBlk.s.xattr_id_table_start != SQUASHFS_INVALID_BLK) {
+ /* sanity check super block contents */
+ if(sBlk.s.xattr_id_table_start >= sBlk.s.bytes_used) {
+ ERROR("read_filesystem_tables: xattr id table start too large in super block\n");
+ goto corrupted;
+ }
+
+ sBlk.xattr_ids = read_xattrs_from_disk(fd, &sBlk.s, no_xattrs, &table_start);
+ if(sBlk.xattr_ids == 0)
+ exit(1);
+ } else
+ table_start = sBlk.s.bytes_used;
+
+ /* Read id lookup table */
+
+ /* Sanity check super block contents */
+ if(sBlk.s.id_table_start >= table_start) {
+ ERROR("read_filesystem_tables: id table start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* there should always be at least one id */
+ if(sBlk.s.no_ids == 0) {
+ ERROR("read_filesystem_tables: Bad id count in super block\n");
+ goto corrupted;
+ }
+
+ /*
+ * the number of ids can never be more than double the number of inodes
+ * (the maximum is a unique uid and gid for each inode).
+ */
+ if(sBlk.s.no_ids > (sBlk.s.inodes * 2LL)) {
+ ERROR("read_filesystem_tables: Bad id count in super block\n");
+ goto corrupted;
+ }
+
+ if(read_id_table(&table_start) == FALSE)
+ goto corrupted;
+
+ /* Read exports table */
+ if(sBlk.s.lookup_table_start != SQUASHFS_INVALID_BLK) {
+
+ /* sanity check super block contents */
+ if(sBlk.s.lookup_table_start >= table_start) {
+ ERROR("read_filesystem_tables: lookup table start too large in super block\n");
+ goto corrupted;
+ }
+
+ if(parse_exports_table(&table_start) == FALSE)
+ goto corrupted;
+ }
+
+ /* Read fragment table */
+ if(sBlk.s.fragments != 0) {
+
+ /* Sanity check super block contents */
+ if(sBlk.s.fragment_table_start >= table_start) {
+ ERROR("read_filesystem_tables: fragment table start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* The number of fragments should not exceed the number of inodes */
+ if(sBlk.s.fragments > sBlk.s.inodes) {
+ ERROR("read_filesystem_tables: Bad fragment count in super block\n");
+ goto corrupted;
+ }
+
+ if(read_fragment_table(&table_start) == FALSE)
+ goto corrupted;
+ }
+
+ /* Sanity check super block directory table values */
+ if(sBlk.s.directory_table_start > table_start) {
+ ERROR("read_filesystem_tables: directory table start too large in super block\n");
+ goto corrupted;
+ }
+
+ /* Sanity check super block inode table values */
+ if(sBlk.s.inode_table_start >= sBlk.s.directory_table_start) {
+ ERROR("read_filesystem_tables: inode table start too large in super block\n");
+ goto corrupted;
+ }
+
+ if(no_xattrs)
+ sBlk.s.xattr_id_table_start = SQUASHFS_INVALID_BLK;
+
+ alloc_index_table(0);
+
+ return TRUE;
+
+corrupted:
+ alloc_index_table(0);
+
+ return FALSE;
+}
+
+
+int read_super_4(squashfs_operations **s_ops)
+{
+ struct squashfs_super_block sBlk_4;
+
+ /*
+ * Try to read a Squashfs 4 superblock
+ */
+ int res = read_fs_bytes(fd, SQUASHFS_START,
+ sizeof(struct squashfs_super_block), &sBlk_4);
+
+ if(res == FALSE)
+ return res;
+
+ swap = sBlk_4.s_magic != SQUASHFS_MAGIC;
+ SQUASHFS_INSWAP_SUPER_BLOCK(&sBlk_4);
+
+ if(sBlk_4.s_magic == SQUASHFS_MAGIC && sBlk_4.s_major == 4 &&
+ sBlk_4.s_minor == 0) {
+ *s_ops = &ops;
+ memcpy(&sBlk, &sBlk_4, sizeof(sBlk_4));
+
+ /*
+ * Check the compression type
+ */
+ comp = lookup_compressor_id(sBlk.s.compression);
+ return TRUE;
+ }
+
+ return -1;
+}
+
+
+static long long read_xattr_ids()
+{
+ int res;
+ struct squashfs_xattr_table id_table;
+
+ if(sBlk.s.xattr_id_table_start == SQUASHFS_INVALID_BLK)
+ return 0;
+
+ /*
+ * Read xattr id table, containing start of xattr metadata and the
+ * number of xattrs in the file system
+ */
+ res = read_fs_bytes(fd, sBlk.s.xattr_id_table_start, sizeof(id_table),
+ &id_table);
+ if(res == FALSE)
+ return -1;
+
+ SQUASHFS_INSWAP_XATTR_TABLE(&id_table);
+
+ return id_table.xattr_ids;
+}
+
+
+static void squashfs_stat(char *source)
+{
+ time_t mkfs_time = (time_t) sBlk.s.mkfs_time;
+ struct tm *t = use_localtime ? localtime(&mkfs_time) :
+ gmtime(&mkfs_time);
+ char *mkfs_str = asctime(t);
+ long long xattr_ids = read_xattr_ids();
+
+ if(xattr_ids == -1)
+ EXIT_UNSQUASH("File system corruption detected\n");
+
+ printf("Found a valid SQUASHFS 4:0 superblock on %s.\n", source);
+ printf("Creation or last append time %s", mkfs_str ? mkfs_str :
+ "failed to get time\n");
+ printf("Filesystem size %llu bytes (%.2f Kbytes / %.2f Mbytes)\n",
+ sBlk.s.bytes_used, sBlk.s.bytes_used / 1024.0,
+ sBlk.s.bytes_used / (1024.0 * 1024.0));
+ printf("Compression %s\n", comp->name);
+
+ if(SQUASHFS_COMP_OPTS(sBlk.s.flags)) {
+ char buffer[SQUASHFS_METADATA_SIZE] __attribute__ ((aligned));
+ int bytes;
+
+ if(!comp->supported)
+ printf("\tCould not display compressor options, because"
+ " %s compression is not supported\n",
+ comp->name);
+ else {
+ bytes = read_block(fd, sizeof(sBlk.s), NULL, 0, buffer);
+ if(bytes == 0) {
+ ERROR("Failed to read compressor options\n");
+ return;
+ }
+
+ compressor_display_options(comp, buffer, bytes);
+ }
+ }
+
+ printf("Block size %d\n", sBlk.s.block_size);
+ printf("Filesystem is %sexportable via NFS\n",
+ SQUASHFS_EXPORTABLE(sBlk.s.flags) ? "" : "not ");
+ printf("Inodes are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_INODES(sBlk.s.flags) ? "un" : "");
+ printf("Data is %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_DATA(sBlk.s.flags) ? "un" : "");
+ printf("Uids/Gids (Id table) are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_INODES(sBlk.s.flags) ||
+ SQUASHFS_UNCOMPRESSED_IDS(sBlk.s.flags) ? "un" : "");
+
+ if(SQUASHFS_NO_FRAGMENTS(sBlk.s.flags))
+ printf("Fragments are not stored\n");
+ else {
+ printf("Fragments are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_FRAGMENTS(sBlk.s.flags) ?
+ "un" : "");
+ printf("Always-use-fragments option is %sspecified\n",
+ SQUASHFS_ALWAYS_FRAGMENTS(sBlk.s.flags) ? "" : "not ");
+ }
+
+ if(SQUASHFS_NO_XATTRS(sBlk.s.flags))
+ printf("Xattrs are not stored\n");
+ else
+ printf("Xattrs are %scompressed\n",
+ SQUASHFS_UNCOMPRESSED_XATTRS(sBlk.s.flags) ? "un" : "");
+
+ printf("Duplicates are %sremoved\n", SQUASHFS_DUPLICATES(sBlk.s.flags)
+ ? "" : "not ");
+ printf("Number of fragments %u\n", sBlk.s.fragments);
+ printf("Number of inodes %u\n", sBlk.s.inodes);
+ printf("Number of ids %d\n", sBlk.s.no_ids);
+
+ if(!SQUASHFS_NO_XATTRS(sBlk.s.flags))
+ printf("Number of xattr ids %lld\n", xattr_ids);
+
+ TRACE("sBlk.s.inode_table_start 0x%llx\n", sBlk.s.inode_table_start);
+ TRACE("sBlk.s.directory_table_start 0x%llx\n", sBlk.s.directory_table_start);
+ TRACE("sBlk.s.fragment_table_start 0x%llx\n", sBlk.s.fragment_table_start);
+ TRACE("sBlk.s.lookup_table_start 0x%llx\n", sBlk.s.lookup_table_start);
+ TRACE("sBlk.s.id_table_start 0x%llx\n", sBlk.s.id_table_start);
+ TRACE("sBlk.s.xattr_id_table_start 0x%llx\n", sBlk.s.xattr_id_table_start);
+}
+
+
+static squashfs_operations ops = {
+ .opendir = squashfs_opendir,
+ .read_fragment = read_fragment,
+ .read_block_list = read_block_list,
+ .read_inode = read_inode,
+ .read_filesystem_tables = read_filesystem_tables,
+ .stat = squashfs_stat
+};
diff --git a/squashfs-tools/unsquashfs.c b/squashfs-tools/unsquashfs.c
new file mode 100644
index 0000000..0ac6356
--- /dev/null
+++ b/squashfs-tools/unsquashfs.c
@@ -0,0 +1,4655 @@
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011,
+ * 2012, 2013, 2014, 2017, 2019, 2020, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquashfs.c
+ */
+
+#include "unsquashfs.h"
+#include "squashfs_compat.h"
+#include "squashfs_swap.h"
+#include "compressor.h"
+#include "xattr.h"
+#include "unsquashfs_info.h"
+#include "stdarg.h"
+#include "fnmatch_compat.h"
+
+#ifdef __linux__
+#include <sched.h>
+#include <sys/sysinfo.h>
+#include <sys/sysmacros.h>
+#else
+#include <sys/sysctl.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <limits.h>
+#include <ctype.h>
+
+struct cache *fragment_cache, *data_cache;
+struct queue *to_reader, *to_inflate, *to_writer, *from_writer;
+pthread_t *thread, *inflator_thread;
+pthread_mutex_t fragment_mutex;
+static long long start_offset = 0;
+
+/* user options that control parallelisation */
+int processors = -1;
+
+struct super_block sBlk;
+squashfs_operations *s_ops;
+struct compressor *comp;
+
+int bytes = 0, swap, file_count = 0, dir_count = 0, sym_count = 0,
+ dev_count = 0, fifo_count = 0, socket_count = 0, hardlnk_count = 0;
+struct hash_table_entry *inode_table_hash[65536], *directory_table_hash[65536];
+int fd;
+unsigned int cached_frag = SQUASHFS_INVALID_FRAG;
+unsigned int block_size;
+unsigned int block_log;
+int lsonly = FALSE, info = FALSE, force = FALSE, short_ls = TRUE;
+int concise = FALSE, quiet = FALSE, numeric = FALSE;
+int use_regex = FALSE;
+int root_process;
+int columns;
+int rotate = 0;
+pthread_mutex_t screen_mutex;
+pthread_mutex_t pos_mutex = PTHREAD_MUTEX_INITIALIZER;
+int progress = TRUE, progress_enabled = FALSE, percent = FALSE;
+unsigned int total_files = 0, total_inodes = 0;
+long long total_blocks = 0;
+long long cur_blocks = 0;
+int inode_number = 1;
+int ignore_errors = FALSE;
+int strict_errors = FALSE;
+int use_localtime = TRUE;
+int max_depth = -1; /* unlimited */
+int follow_symlinks = FALSE;
+int missing_symlinks = FALSE;
+int no_wildcards = FALSE;
+int set_exit_code = TRUE;
+int treat_as_excludes = FALSE;
+int stat_sys = FALSE;
+int version = FALSE;
+int mkfs_time_opt = FALSE;
+int cat_files = FALSE;
+int fragment_buffer_size = FRAGMENT_BUFFER_DEFAULT;
+int data_buffer_size = DATA_BUFFER_DEFAULT;
+char *dest = "squashfs-root";
+struct pathnames *extracts = NULL, *excludes = NULL;
+struct pathname *extract = NULL, *exclude = NULL, *stickypath = NULL;
+int writer_fd = 1;
+int pseudo_file = FALSE;
+int pseudo_stdout = FALSE;
+char *pseudo_name;
+unsigned int timeval;
+int time_opt = FALSE;
+int full_precision = FALSE;
+
+/* extended attribute flags */
+int no_xattrs = XATTR_DEF;
+regex_t *xattr_exclude_preg = NULL;
+regex_t *xattr_include_preg = NULL;
+
+int lookup_type[] = {
+ 0,
+ S_IFDIR,
+ S_IFREG,
+ S_IFLNK,
+ S_IFBLK,
+ S_IFCHR,
+ S_IFIFO,
+ S_IFSOCK,
+ S_IFDIR,
+ S_IFREG,
+ S_IFLNK,
+ S_IFBLK,
+ S_IFCHR,
+ S_IFIFO,
+ S_IFSOCK
+};
+
+struct test table[] = {
+ { S_IFMT, S_IFSOCK, 0, 's' },
+ { S_IFMT, S_IFLNK, 0, 'l' },
+ { S_IFMT, S_IFBLK, 0, 'b' },
+ { S_IFMT, S_IFDIR, 0, 'd' },
+ { S_IFMT, S_IFCHR, 0, 'c' },
+ { S_IFMT, S_IFIFO, 0, 'p' },
+ { S_IRUSR, S_IRUSR, 1, 'r' },
+ { S_IWUSR, S_IWUSR, 2, 'w' },
+ { S_IRGRP, S_IRGRP, 4, 'r' },
+ { S_IWGRP, S_IWGRP, 5, 'w' },
+ { S_IROTH, S_IROTH, 7, 'r' },
+ { S_IWOTH, S_IWOTH, 8, 'w' },
+ { S_IXUSR | S_ISUID, S_IXUSR | S_ISUID, 3, 's' },
+ { S_IXUSR | S_ISUID, S_ISUID, 3, 'S' },
+ { S_IXUSR | S_ISUID, S_IXUSR, 3, 'x' },
+ { S_IXGRP | S_ISGID, S_IXGRP | S_ISGID, 6, 's' },
+ { S_IXGRP | S_ISGID, S_ISGID, 6, 'S' },
+ { S_IXGRP | S_ISGID, S_IXGRP, 6, 'x' },
+ { S_IXOTH | S_ISVTX, S_IXOTH | S_ISVTX, 9, 't' },
+ { S_IXOTH | S_ISVTX, S_ISVTX, 9, 'T' },
+ { S_IXOTH | S_ISVTX, S_IXOTH, 9, 'x' },
+ { 0, 0, 0, 0}
+};
+
+void progress_bar(long long current, long long max, int columns);
+
+#define MAX_LINE 16384
+
+void sigwinch_handler(int arg)
+{
+ struct winsize winsize;
+
+ if(ioctl(1, TIOCGWINSZ, &winsize) == -1) {
+ if(isatty(STDOUT_FILENO))
+ ERROR("TIOCGWINSZ ioctl failed, defaulting to 80 "
+ "columns\n");
+ columns = 80;
+ } else
+ columns = winsize.ws_col;
+}
+
+
+void sigalrm_handler(int arg)
+{
+ rotate = (rotate + 1) % 4;
+}
+
+
+int add_overflow(int a, int b)
+{
+ return (INT_MAX - a) < b;
+}
+
+
+int shift_overflow(int a, int shift)
+{
+ return (INT_MAX >> shift) < a;
+}
+
+
+int multiply_overflow(int a, int multiplier)
+{
+ return (INT_MAX / multiplier) < a;
+}
+
+
+struct queue *queue_init(int size)
+{
+ struct queue *queue = malloc(sizeof(struct queue));
+ if(queue == NULL)
+ MEM_ERROR();
+
+ if(add_overflow(size, 1) ||
+ multiply_overflow(size + 1, sizeof(void *)))
+ EXIT_UNSQUASH("Size too large in queue_init\n");
+
+ queue->data = malloc(sizeof(void *) * (size + 1));
+ if(queue->data == NULL)
+ MEM_ERROR();
+
+ queue->size = size + 1;
+ queue->readp = queue->writep = 0;
+ pthread_mutex_init(&queue->mutex, NULL);
+ pthread_cond_init(&queue->empty, NULL);
+ pthread_cond_init(&queue->full, NULL);
+
+ return queue;
+}
+
+
+void queue_put(struct queue *queue, void *data)
+{
+ int nextp;
+
+ pthread_mutex_lock(&queue->mutex);
+
+ while((nextp = (queue->writep + 1) % queue->size) == queue->readp)
+ pthread_cond_wait(&queue->full, &queue->mutex);
+
+ queue->data[queue->writep] = data;
+ queue->writep = nextp;
+ pthread_cond_signal(&queue->empty);
+ pthread_mutex_unlock(&queue->mutex);
+}
+
+
+void *queue_get(struct queue *queue)
+{
+ void *data;
+ pthread_mutex_lock(&queue->mutex);
+
+ while(queue->readp == queue->writep)
+ pthread_cond_wait(&queue->empty, &queue->mutex);
+
+ data = queue->data[queue->readp];
+ queue->readp = (queue->readp + 1) % queue->size;
+ pthread_cond_signal(&queue->full);
+ pthread_mutex_unlock(&queue->mutex);
+
+ return data;
+}
+
+
+void dump_queue(struct queue *queue)
+{
+ pthread_mutex_lock(&queue->mutex);
+
+ printf("Max size %d, size %d%s\n", queue->size - 1,
+ queue->readp <= queue->writep ? queue->writep - queue->readp :
+ queue->size - queue->readp + queue->writep,
+ queue->readp == queue->writep ? " (EMPTY)" :
+ ((queue->writep + 1) % queue->size) == queue->readp ?
+ " (FULL)" : "");
+
+ pthread_mutex_unlock(&queue->mutex);
+}
+
+
+/* Called with the cache mutex held */
+void insert_hash_table(struct cache *cache, struct cache_entry *entry)
+{
+ int hash = TABLE_HASH(entry->block);
+
+ entry->hash_next = cache->hash_table[hash];
+ cache->hash_table[hash] = entry;
+ entry->hash_prev = NULL;
+ if(entry->hash_next)
+ entry->hash_next->hash_prev = entry;
+}
+
+
+/* Called with the cache mutex held */
+void remove_hash_table(struct cache *cache, struct cache_entry *entry)
+{
+ if(entry->hash_prev)
+ entry->hash_prev->hash_next = entry->hash_next;
+ else
+ cache->hash_table[TABLE_HASH(entry->block)] =
+ entry->hash_next;
+ if(entry->hash_next)
+ entry->hash_next->hash_prev = entry->hash_prev;
+
+ entry->hash_prev = entry->hash_next = NULL;
+}
+
+
+/* Called with the cache mutex held */
+void insert_free_list(struct cache *cache, struct cache_entry *entry)
+{
+ if(cache->free_list) {
+ entry->free_next = cache->free_list;
+ entry->free_prev = cache->free_list->free_prev;
+ cache->free_list->free_prev->free_next = entry;
+ cache->free_list->free_prev = entry;
+ } else {
+ cache->free_list = entry;
+ entry->free_prev = entry->free_next = entry;
+ }
+}
+
+
+/* Called with the cache mutex held */
+void remove_free_list(struct cache *cache, struct cache_entry *entry)
+{
+ if(entry->free_prev == NULL || entry->free_next == NULL)
+ /* not in free list */
+ return;
+ else if(entry->free_prev == entry && entry->free_next == entry) {
+ /* only this entry in the free list */
+ cache->free_list = NULL;
+ } else {
+ /* more than one entry in the free list */
+ entry->free_next->free_prev = entry->free_prev;
+ entry->free_prev->free_next = entry->free_next;
+ if(cache->free_list == entry)
+ cache->free_list = entry->free_next;
+ }
+
+ entry->free_prev = entry->free_next = NULL;
+}
+
+
+struct cache *cache_init(int buffer_size, int max_buffers)
+{
+ struct cache *cache = malloc(sizeof(struct cache));
+ if(cache == NULL)
+ MEM_ERROR();
+
+ cache->max_buffers = max_buffers;
+ cache->buffer_size = buffer_size;
+ cache->count = 0;
+ cache->used = 0;
+ cache->free_list = NULL;
+ memset(cache->hash_table, 0, sizeof(struct cache_entry *) * 65536);
+ cache->wait_free = FALSE;
+ cache->wait_pending = FALSE;
+ pthread_mutex_init(&cache->mutex, NULL);
+ pthread_cond_init(&cache->wait_for_free, NULL);
+ pthread_cond_init(&cache->wait_for_pending, NULL);
+
+ return cache;
+}
+
+
+struct cache_entry *cache_get(struct cache *cache, long long block, int size)
+{
+ /*
+ * Get a block out of the cache. If the block isn't in the cache
+ * it is added and queued to the reader() and inflate() threads for
+ * reading off disk and decompression. The cache grows until max_blocks
+ * is reached, once this occurs existing discarded blocks on the free
+ * list are reused
+ */
+ int hash = TABLE_HASH(block);
+ struct cache_entry *entry;
+
+ pthread_mutex_lock(&cache->mutex);
+
+ for(entry = cache->hash_table[hash]; entry; entry = entry->hash_next)
+ if(entry->block == block)
+ break;
+
+ if(entry) {
+ /*
+ * found the block in the cache. If the block is currently
+ * unused remove it from the free list and increment cache
+ * used count.
+ */
+ if(entry->used == 0) {
+ cache->used ++;
+ remove_free_list(cache, entry);
+ }
+ entry->used ++;
+ pthread_mutex_unlock(&cache->mutex);
+ } else {
+ /*
+ * not in the cache
+ *
+ * first try to allocate new block
+ */
+ if(cache->count < cache->max_buffers) {
+ entry = malloc(sizeof(struct cache_entry));
+ if(entry == NULL)
+ MEM_ERROR();
+
+ entry->data = malloc(cache->buffer_size);
+ if(entry->data == NULL)
+ MEM_ERROR();
+
+ entry->cache = cache;
+ entry->free_prev = entry->free_next = NULL;
+ cache->count ++;
+ } else {
+ /*
+ * try to get from free list
+ */
+ while(cache->free_list == NULL) {
+ cache->wait_free = TRUE;
+ pthread_cond_wait(&cache->wait_for_free,
+ &cache->mutex);
+ }
+ entry = cache->free_list;
+ remove_free_list(cache, entry);
+ remove_hash_table(cache, entry);
+ }
+
+ /*
+ * Initialise block and insert into the hash table.
+ * Increment used which tracks how many buffers in the
+ * cache are actively in use (the other blocks, count - used,
+ * are in the cache and available for lookup, but can also be
+ * re-used).
+ */
+ entry->block = block;
+ entry->size = size;
+ entry->used = 1;
+ entry->error = FALSE;
+ entry->pending = TRUE;
+ insert_hash_table(cache, entry);
+ cache->used ++;
+
+ /*
+ * queue to read thread to read and ultimately (via the
+ * decompress threads) decompress the buffer
+ */
+ pthread_mutex_unlock(&cache->mutex);
+ queue_put(to_reader, entry);
+ }
+
+ return entry;
+}
+
+
+void cache_block_ready(struct cache_entry *entry, int error)
+{
+ /*
+ * mark cache entry as being complete, reading and (if necessary)
+ * decompression has taken place, and the buffer is valid for use.
+ * If an error occurs reading or decompressing, the buffer also
+ * becomes ready but with an error...
+ */
+ pthread_mutex_lock(&entry->cache->mutex);
+ entry->pending = FALSE;
+ entry->error = error;
+
+ /*
+ * if the wait_pending flag is set, one or more threads may be waiting
+ * on this buffer
+ */
+ if(entry->cache->wait_pending) {
+ entry->cache->wait_pending = FALSE;
+ pthread_cond_broadcast(&entry->cache->wait_for_pending);
+ }
+
+ pthread_mutex_unlock(&entry->cache->mutex);
+}
+
+
+void cache_block_wait(struct cache_entry *entry)
+{
+ /*
+ * wait for this cache entry to become ready, when reading and (if
+ * necessary) decompression has taken place
+ */
+ pthread_mutex_lock(&entry->cache->mutex);
+
+ while(entry->pending) {
+ entry->cache->wait_pending = TRUE;
+ pthread_cond_wait(&entry->cache->wait_for_pending,
+ &entry->cache->mutex);
+ }
+
+ pthread_mutex_unlock(&entry->cache->mutex);
+}
+
+
+void cache_block_put(struct cache_entry *entry)
+{
+ /*
+ * finished with this cache entry, once the usage count reaches zero it
+ * can be reused and is put onto the free list. As it remains
+ * accessible via the hash table it can be found getting a new lease of
+ * life before it is reused.
+ */
+ pthread_mutex_lock(&entry->cache->mutex);
+
+ entry->used --;
+ if(entry->used == 0) {
+ insert_free_list(entry->cache, entry);
+ entry->cache->used --;
+
+ /*
+ * if the wait_free flag is set, one or more threads may be
+ * waiting on this buffer
+ */
+ if(entry->cache->wait_free) {
+ entry->cache->wait_free = FALSE;
+ pthread_cond_broadcast(&entry->cache->wait_for_free);
+ }
+ }
+
+ pthread_mutex_unlock(&entry->cache->mutex);
+}
+
+
+void dump_cache(struct cache *cache)
+{
+ pthread_mutex_lock(&cache->mutex);
+
+ printf("Max buffers %d, Current size %d, Used %d, %s\n",
+ cache->max_buffers, cache->count, cache->used,
+ cache->free_list ? "Free buffers" : "No free buffers");
+
+ pthread_mutex_unlock(&cache->mutex);
+}
+
+
+char *modestr(char *str, int mode)
+{
+ int i;
+
+ strcpy(str, "----------");
+
+ for(i = 0; table[i].mask != 0; i++) {
+ if((mode & table[i].mask) == table[i].value)
+ str[table[i].position] = table[i].mode;
+ }
+
+ return str;
+}
+
+
+#define TOTALCHARS 25
+void print_filename(char *pathname, struct inode *inode)
+{
+ char str[11], dummy[12], dummy2[12]; /* overflow safe */
+ char *userstr, *groupstr;
+ int padchars;
+ struct passwd *user;
+ struct group *group;
+ struct tm *t;
+
+ if(short_ls) {
+ printf("%s\n", pathname);
+ return;
+ }
+
+ user = numeric ? NULL : getpwuid(inode->uid);
+ if(user == NULL) {
+ int res = snprintf(dummy, 12, "%u", inode->uid);
+ if(res < 0)
+ EXIT_UNSQUASH("snprintf failed in print_filename()\n");
+ else if(res >= 12)
+ /* unsigned int shouldn't ever need more than 11 bytes
+ * (including terminating '\0') to print in base 10 */
+ userstr = "*";
+ else
+ userstr = dummy;
+ } else
+ userstr = user->pw_name;
+
+ group = numeric ? NULL : getgrgid(inode->gid);
+ if(group == NULL) {
+ int res = snprintf(dummy2, 12, "%u", inode->gid);
+ if(res < 0)
+ EXIT_UNSQUASH("snprintf failed in print_filename()\n");
+ else if(res >= 12)
+ /* unsigned int shouldn't ever need more than 11 bytes
+ * (including terminating '\0') to print in base 10 */
+ groupstr = "*";
+ else
+ groupstr = dummy2;
+ } else
+ groupstr = group->gr_name;
+
+ printf("%s %s/%s ", modestr(str, inode->mode), userstr, groupstr);
+
+ switch(inode->mode & S_IFMT) {
+ case S_IFREG:
+ case S_IFDIR:
+ case S_IFSOCK:
+ case S_IFIFO:
+ case S_IFLNK:
+ padchars = TOTALCHARS - strlen(userstr) -
+ strlen(groupstr);
+
+ printf("%*lld ", padchars > 0 ? padchars : 0,
+ inode->data);
+ break;
+ case S_IFCHR:
+ case S_IFBLK:
+ padchars = TOTALCHARS - strlen(userstr) -
+ strlen(groupstr) - 7;
+
+ printf("%*s%3d,%3d ", padchars > 0 ? padchars : 0, " ",
+ (int) inode->data >> 8, (int) inode->data &
+ 0xff);
+ break;
+ }
+
+ t = use_localtime ? localtime(&inode->time) : gmtime(&inode->time);
+
+ if(full_precision)
+ printf("%d-%02d-%02d %02d:%02d:%02d %s", t->tm_year + 1900,
+ t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min,
+ t->tm_sec, pathname);
+ else
+ printf("%d-%02d-%02d %02d:%02d %s", t->tm_year + 1900,
+ t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, pathname);
+ if((inode->mode & S_IFMT) == S_IFLNK)
+ printf(" -> %s", inode->symlink);
+ printf("\n");
+}
+
+
+long long read_bytes(int fd, void *buff, long long bytes)
+{
+ long long res, count;
+
+ for(count = 0; count < bytes; count += res) {
+ int len = (bytes - count) > MAXIMUM_READ_SIZE ?
+ MAXIMUM_READ_SIZE : bytes - count;
+
+ res = read(fd, buff + count, len);
+ if(res < 1) {
+ if(res == 0)
+ break;
+ else if(errno != EINTR) {
+ ERROR("Read failed because %s\n",
+ strerror(errno));
+ return -1;
+ } else
+ res = 0;
+ }
+ }
+
+ return count;
+}
+
+
+int read_fs_bytes(int fd, long long byte, long long bytes, void *buff)
+{
+ off_t off = byte;
+ long long res;
+
+ TRACE("read_bytes: reading from position 0x%llx, bytes %lld\n", byte,
+ bytes);
+
+ pthread_cleanup_push((void *) pthread_mutex_unlock, &pos_mutex);
+ pthread_mutex_lock(&pos_mutex);
+ if(lseek(fd, start_offset + off, SEEK_SET) == -1) {
+ ERROR("Lseek failed because %s\n", strerror(errno));
+ res = FALSE;
+ goto done;
+ }
+
+ res = read_bytes(fd, buff, bytes);
+
+ if(res != -1 && res < bytes)
+ ERROR("Read on filesystem failed because EOF\n");
+
+ res = res == bytes;
+
+done:
+ pthread_cleanup_pop(1);
+ return res;
+}
+
+
+int read_block(int fd, long long start, long long *next, int expected,
+ void *block)
+{
+ unsigned short c_byte;
+ int offset = 2, res, compressed;
+ int outlen = expected ? expected : SQUASHFS_METADATA_SIZE;
+ static char *buffer = NULL;
+
+ if(outlen > SQUASHFS_METADATA_SIZE)
+ return FALSE;
+
+ if(swap) {
+ if(read_fs_bytes(fd, start, 2, &c_byte) == FALSE)
+ goto failed;
+ c_byte = (c_byte >> 8) | ((c_byte & 0xff) << 8);
+ } else
+ if(read_fs_bytes(fd, start, 2, &c_byte) == FALSE)
+ goto failed;
+
+ TRACE("read_block: block @0x%llx, %d %s bytes\n", start,
+ SQUASHFS_COMPRESSED_SIZE(c_byte), SQUASHFS_COMPRESSED(c_byte) ?
+ "compressed" : "uncompressed");
+
+ if(SQUASHFS_CHECK_DATA(sBlk.s.flags))
+ offset = 3;
+
+ compressed = SQUASHFS_COMPRESSED(c_byte);
+ c_byte = SQUASHFS_COMPRESSED_SIZE(c_byte);
+
+ /*
+ * The block size should not be larger than
+ * the uncompressed size (or max uncompressed size if
+ * expected is 0)
+ */
+ if(c_byte > outlen)
+ return FALSE;
+
+ if(compressed) {
+ int error;
+
+ if(buffer == NULL) {
+ buffer = malloc(SQUASHFS_METADATA_SIZE);
+ if(buffer == NULL)
+ MEM_ERROR();
+ }
+
+ res = read_fs_bytes(fd, start + offset, c_byte, buffer);
+ if(res == FALSE)
+ goto failed;
+
+ res = compressor_uncompress(comp, block, buffer, c_byte,
+ outlen, &error);
+
+ if(res == -1) {
+ ERROR("%s uncompress failed with error code %d\n",
+ comp->name, error);
+ goto failed;
+ }
+ } else {
+ res = read_fs_bytes(fd, start + offset, c_byte, block);
+ if(res == FALSE)
+ goto failed;
+ res = c_byte;
+ }
+
+ if(next)
+ *next = start + offset + c_byte;
+
+ /*
+ * if expected, then check the (uncompressed) return data
+ * is of the expected size
+ */
+ if(expected && expected != res)
+ return FALSE;
+ else
+ return res;
+
+failed:
+ ERROR("read_block: failed to read block @0x%llx\n", start);
+ return FALSE;
+}
+
+
+static struct hash_table_entry *get_metadata(struct hash_table_entry *hash_table[],
+ long long start)
+{
+ int res, hash = TABLE_HASH(start);
+ struct hash_table_entry *entry;
+ void *buffer;
+ long long next;
+
+ for(entry = hash_table[hash]; entry; entry = entry->next)
+ if(entry->start == start)
+ return entry;
+
+ buffer = malloc(SQUASHFS_METADATA_SIZE);
+ if(buffer == NULL)
+ MEM_ERROR();
+
+ res = read_block(fd, start, &next, 0, buffer);
+ if(res == 0) {
+ ERROR("get_metadata: failed to read block\n");
+ free(buffer);
+ return NULL;
+ }
+
+ entry = malloc(sizeof(struct hash_table_entry));
+ if(entry == NULL)
+ MEM_ERROR();
+
+ entry->start = start;
+ entry->length = res;
+ entry->buffer = buffer;
+ entry->next_index = next;
+ entry->next = hash_table[hash];
+ hash_table[hash] = entry;
+
+ return entry;
+}
+
+/*
+ * Read length bytes from metadata position <block, offset> (block is the
+ * start of the compressed block on disk, and offset is the offset into
+ * the block once decompressed). Data is packed into consecutive blocks,
+ * and length bytes may require reading more than one block.
+ */
+static int read_metadata(struct hash_table_entry *hash_table[], void *buffer,
+ long long *blk, unsigned int *off, int length)
+{
+ int res = length;
+ struct hash_table_entry *entry;
+ long long block = *blk;
+ unsigned int offset = *off;
+
+ while (1) {
+ entry = get_metadata(hash_table, block);
+ if (entry == NULL || offset >= entry->length)
+ return FALSE;
+
+ if((entry->length - offset) < length) {
+ int copy = entry->length - offset;
+ memcpy(buffer, entry->buffer + offset, copy);
+ buffer += copy;
+ length -= copy;
+ block = entry->next_index;
+ offset = 0;
+ } else if((entry->length - offset) == length) {
+ memcpy(buffer, entry->buffer + offset, length);
+ *blk = entry->next_index;
+ *off = 0;
+ break;
+ } else {
+ memcpy(buffer, entry->buffer + offset, length);
+ *blk = block;
+ *off = offset + length;
+ break;
+ }
+ }
+
+ return res;
+}
+
+
+int read_inode_data(void *buffer, long long *blk, unsigned int *off, int length)
+{
+ return read_metadata(inode_table_hash, buffer, blk, off, length);
+}
+
+
+int read_directory_data(void *buffer, long long *blk, unsigned int *off, int length)
+{
+ return read_metadata(directory_table_hash, buffer, blk, off, length);
+}
+
+
+int set_attributes(char *pathname, int mode, uid_t uid, gid_t guid, time_t time,
+ unsigned int xattr, unsigned int set_mode)
+{
+ struct utimbuf times = { time, time };
+ int failed = FALSE;
+
+ if(utime(pathname, &times) == -1) {
+ EXIT_UNSQUASH_STRICT("set_attributes: failed to set time on "
+ "%s, because %s\n", pathname, strerror(errno));
+ failed = TRUE;
+ }
+
+ if(root_process) {
+ if(chown(pathname, uid, guid) == -1) {
+ EXIT_UNSQUASH_STRICT("set_attributes: failed to change"
+ " uid and gids on %s, because %s\n", pathname,
+ strerror(errno));
+ failed = TRUE;
+ }
+ } else
+ mode &= ~06000;
+
+ if(write_xattr(pathname, xattr) == FALSE)
+ failed = TRUE;
+
+ if((set_mode || (mode & 07000)) &&
+ chmod(pathname, (mode_t) mode) == -1) {
+ /*
+ * Some filesystems require root privileges to use the sticky
+ * bit. If we're not root and chmod() failed with EPERM when the
+ * sticky bit was included in the mode, try again without the
+ * sticky bit. Otherwise, fail with an error message.
+ */
+ if (root_process || errno != EPERM || !(mode & 01000) ||
+ chmod(pathname, (mode_t) (mode & ~01000)) == -1) {
+ EXIT_UNSQUASH_STRICT("set_attributes: failed to change"
+ " mode %s, because %s\n", pathname,
+ strerror(errno));
+ failed = TRUE;
+ }
+ }
+
+ return !failed;
+}
+
+
+int write_bytes(int fd, char *buff, int bytes)
+{
+ int res, count;
+
+ for(count = 0; count < bytes; count += res) {
+ res = write(fd, buff + count, bytes - count);
+ if(res == -1) {
+ if(errno != EINTR) {
+ ERROR("Write on output file failed because "
+ "%s\n", strerror(errno));
+ return -1;
+ }
+ res = 0;
+ }
+ }
+
+ return 0;
+}
+
+
+int lseek_broken = FALSE;
+char *zero_data = NULL;
+
+int write_block(int file_fd, char *buffer, int size, long long hole, int sparse)
+{
+ off_t off = hole;
+
+ if(hole) {
+ if(sparse && lseek_broken == FALSE) {
+ int error = lseek(file_fd, off, SEEK_CUR);
+ if(error == -1)
+ /* failed to seek beyond end of file */
+ lseek_broken = TRUE;
+ }
+
+ if((sparse == FALSE || lseek_broken) && zero_data == NULL) {
+ zero_data = malloc(block_size);
+ if(zero_data == NULL)
+ MEM_ERROR();
+ memset(zero_data, 0, block_size);
+ }
+
+ if(sparse == FALSE || lseek_broken) {
+ int blocks = (hole + block_size -1) / block_size;
+ int avail_bytes, i;
+ for(i = 0; i < blocks; i++, hole -= avail_bytes) {
+ avail_bytes = hole > block_size ? block_size :
+ hole;
+ if(write_bytes(file_fd, zero_data, avail_bytes)
+ == -1)
+ goto failure;
+ }
+ }
+ }
+
+ if(write_bytes(file_fd, buffer, size) == -1)
+ goto failure;
+
+ return TRUE;
+
+failure:
+ return FALSE;
+}
+
+
+pthread_mutex_t open_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t open_empty = PTHREAD_COND_INITIALIZER;
+int open_unlimited, open_count;
+#define OPEN_FILE_MARGIN 10
+
+
+void open_init(int count)
+{
+ open_count = count;
+ open_unlimited = count == -1;
+}
+
+
+int open_wait(char *pathname, int flags, mode_t mode)
+{
+ if (!open_unlimited) {
+ pthread_mutex_lock(&open_mutex);
+ while (open_count == 0)
+ pthread_cond_wait(&open_empty, &open_mutex);
+ open_count --;
+ pthread_mutex_unlock(&open_mutex);
+ }
+
+ return open(pathname, flags, mode);
+}
+
+
+void close_wake(int fd)
+{
+ close(fd);
+
+ if (!open_unlimited) {
+ pthread_mutex_lock(&open_mutex);
+ open_count ++;
+ pthread_cond_signal(&open_empty);
+ pthread_mutex_unlock(&open_mutex);
+ }
+}
+
+
+void queue_file(char *pathname, int file_fd, struct inode *inode)
+{
+ struct squashfs_file *file = malloc(sizeof(struct squashfs_file));
+ if(file == NULL)
+ MEM_ERROR();
+
+ file->fd = file_fd;
+ file->file_size = inode->data;
+ file->mode = inode->mode;
+ file->gid = inode->gid;
+ file->uid = inode->uid;
+ file->time = inode->time;
+ file->pathname = strdup(pathname);
+ file->blocks = inode->blocks + (inode->frag_bytes > 0);
+ file->sparse = inode->sparse;
+ file->xattr = inode->xattr;
+ queue_put(to_writer, file);
+}
+
+
+void queue_dir(char *pathname, struct dir *dir)
+{
+ struct squashfs_file *file = malloc(sizeof(struct squashfs_file));
+ if(file == NULL)
+ MEM_ERROR();
+
+ file->fd = -1;
+ file->mode = dir->mode;
+ file->gid = dir->guid;
+ file->uid = dir->uid;
+ file->time = dir->mtime;
+ file->pathname = strdup(pathname);
+ file->xattr = dir->xattr;
+ queue_put(to_writer, file);
+}
+
+
+int write_file(struct inode *inode, char *pathname)
+{
+ unsigned int file_fd, i;
+ unsigned int *block_list = NULL;
+ int file_end = inode->data / block_size, res;
+ long long start = inode->start;
+ mode_t mode = inode->mode;
+ struct stat buf;
+
+ TRACE("write_file: regular file, blocks %d\n", inode->blocks);
+
+ if(!root_process && !(mode & S_IWUSR) && has_xattrs(inode->xattr))
+ mode |= S_IWUSR;
+
+ res = lstat(pathname, &buf);
+ if(res != -1 && force) {
+ res = unlink(pathname);
+ if(res == -1)
+ EXIT_UNSQUASH("write_file: failed to unlink file %s,"
+ " because %s\n", pathname, strerror(errno));
+ } else if(res != -1)
+ EXIT_UNSQUASH("write_file: file %s already exists\n", pathname);
+ else if(errno != ENOENT)
+ EXIT_UNSQUASH("write_file: failed to lstat file %s,"
+ " because %s\n", pathname, strerror(errno));
+
+ file_fd = open_wait(pathname, O_CREAT | O_WRONLY, mode & 0777);
+ if(file_fd == -1) {
+ EXIT_UNSQUASH_IGNORE("write_file: failed to create file %s,"
+ " because %s\n", pathname, strerror(errno));
+ return FALSE;
+ }
+
+ if(inode->blocks) {
+ block_list = malloc(inode->blocks * sizeof(unsigned int));
+ if(block_list == NULL)
+ MEM_ERROR();
+
+ s_ops->read_block_list(block_list, inode->block_start,
+ inode->block_offset, inode->blocks);
+ }
+
+ /*
+ * the writer thread is queued a squashfs_file structure describing the
+ * file. If the file has one or more blocks or a fragment they are
+ * queued separately (references to blocks in the cache).
+ */
+ queue_file(pathname, file_fd, inode);
+
+ for(i = 0; i < inode->blocks; i++) {
+ int c_byte = SQUASHFS_COMPRESSED_SIZE_BLOCK(block_list[i]);
+ struct file_entry *block = malloc(sizeof(struct file_entry));
+
+ if(block == NULL)
+ MEM_ERROR();
+
+ block->offset = 0;
+ block->size = i == file_end ? inode->data & (block_size - 1) :
+ block_size;
+ if(block_list[i] == 0) /* sparse block */
+ block->buffer = NULL;
+ else {
+ block->buffer = cache_get(data_cache, start,
+ block_list[i]);
+ start += c_byte;
+ }
+ queue_put(to_writer, block);
+ }
+
+ if(inode->frag_bytes) {
+ int size;
+ long long start;
+ struct file_entry *block = malloc(sizeof(struct file_entry));
+
+ if(block == NULL)
+ MEM_ERROR();
+
+ s_ops->read_fragment(inode->fragment, &start, &size);
+ block->buffer = cache_get(fragment_cache, start, size);
+ block->offset = inode->offset;
+ block->size = inode->frag_bytes;
+ queue_put(to_writer, block);
+ }
+
+ free(block_list);
+ return TRUE;
+}
+
+
+int cat_file(struct inode *inode, char *pathname)
+{
+ unsigned int i;
+ unsigned int *block_list = NULL;
+ int file_end = inode->data / block_size;
+ long long start = inode->start;
+
+ TRACE("cat_file: regular file, blocks %d\n", inode->blocks);
+
+ if(inode->blocks) {
+ block_list = malloc(inode->blocks * sizeof(unsigned int));
+ if(block_list == NULL)
+ MEM_ERROR();
+
+ s_ops->read_block_list(block_list, inode->block_start,
+ inode->block_offset, inode->blocks);
+ }
+
+ /*
+ * the writer thread is queued a squashfs_file structure describing the
+ * file. If the file has one or more blocks or a fragment they are
+ * queued separately (references to blocks in the cache).
+ */
+ queue_file(pathname, 0, inode);
+
+ for(i = 0; i < inode->blocks; i++) {
+ int c_byte = SQUASHFS_COMPRESSED_SIZE_BLOCK(block_list[i]);
+ struct file_entry *block = malloc(sizeof(struct file_entry));
+
+ if(block == NULL)
+ MEM_ERROR();
+
+ block->offset = 0;
+ block->size = i == file_end ? inode->data & (block_size - 1) :
+ block_size;
+ if(block_list[i] == 0) /* sparse block */
+ block->buffer = NULL;
+ else {
+ block->buffer = cache_get(data_cache, start,
+ block_list[i]);
+ start += c_byte;
+ }
+ queue_put(to_writer, block);
+ }
+
+ if(inode->frag_bytes) {
+ int size;
+ long long start;
+ struct file_entry *block = malloc(sizeof(struct file_entry));
+
+ if(block == NULL)
+ MEM_ERROR();
+
+ s_ops->read_fragment(inode->fragment, &start, &size);
+ block->buffer = cache_get(fragment_cache, start, size);
+ block->offset = inode->offset;
+ block->size = inode->frag_bytes;
+ queue_put(to_writer, block);
+ }
+
+ free(block_list);
+ return TRUE;
+}
+
+
+int create_inode(char *pathname, struct inode *i)
+{
+ int res;
+ int failed = FALSE;
+ char *link_path = lookup(i->inode_number);
+
+ TRACE("create_inode: pathname %s\n", pathname);
+
+ if(link_path) {
+ TRACE("create_inode: hard link\n");
+ if(force)
+ unlink(pathname);
+
+ if(link(link_path, pathname) == -1) {
+ EXIT_UNSQUASH_IGNORE("create_inode: failed to create"
+ " hardlink, because %s\n", strerror(errno));
+ return FALSE;
+ }
+
+ hardlnk_count++;
+ return TRUE;
+ }
+
+ switch(i->type) {
+ case SQUASHFS_FILE_TYPE:
+ case SQUASHFS_LREG_TYPE:
+ TRACE("create_inode: regular file, file_size %lld, "
+ "blocks %d\n", i->data, i->blocks);
+
+ res = write_file(i, pathname);
+ if(res == FALSE)
+ goto failed;
+
+ file_count ++;
+ break;
+ case SQUASHFS_SYMLINK_TYPE:
+ case SQUASHFS_LSYMLINK_TYPE: {
+ struct timeval times[2] = {
+ { i->time, 0 },
+ { i->time, 0 }
+ };
+
+ TRACE("create_inode: symlink, symlink_size %lld\n",
+ i->data);
+
+ if(force)
+ unlink(pathname);
+
+ res = symlink(i->symlink, pathname);
+ if(res == -1) {
+ EXIT_UNSQUASH_STRICT("create_inode: failed to"
+ " create symlink %s, because %s\n",
+ pathname, strerror(errno));
+ goto failed;
+ }
+
+ res = lutimes(pathname, times);
+ if(res == -1) {
+ EXIT_UNSQUASH_STRICT("create_inode: failed to"
+ " set time on %s, because %s\n",
+ pathname, strerror(errno));
+ }
+
+ if(root_process) {
+ res = lchown(pathname, i->uid, i->gid);
+ if(res == -1) {
+ EXIT_UNSQUASH_STRICT("create_inode: "
+ "failed to change uid and "
+ "gids on %s, because %s\n",
+ pathname, strerror(errno));
+ failed = TRUE;
+ }
+ }
+
+ res = write_xattr(pathname, i->xattr);
+ if(res == FALSE)
+ failed = TRUE;
+
+ if(failed)
+ goto failed;
+
+ sym_count ++;
+ break;
+ }
+ case SQUASHFS_BLKDEV_TYPE:
+ case SQUASHFS_CHRDEV_TYPE:
+ case SQUASHFS_LBLKDEV_TYPE:
+ case SQUASHFS_LCHRDEV_TYPE: {
+ int chrdev = 0;
+ unsigned major, minor;
+ if ( i->type == SQUASHFS_CHRDEV_TYPE ||
+ i->type == SQUASHFS_LCHRDEV_TYPE)
+ chrdev = 1;
+
+ TRACE("create_inode: dev, rdev 0x%llx\n", i->data);
+ if(root_process) {
+ if(force)
+ unlink(pathname);
+
+ /* Based on new_decode_dev() in kernel source */
+ major = (i->data & 0xfff00) >> 8;
+ minor = (i->data & 0xff) | ((i->data >> 12)
+ & 0xfff00);
+
+ res = mknod(pathname, chrdev ? S_IFCHR :
+ S_IFBLK, makedev(major, minor));
+ if(res == -1) {
+ EXIT_UNSQUASH_STRICT("create_inode: "
+ "failed to create %s device "
+ "%s, because %s\n", chrdev ?
+ "character" : "block", pathname,
+ strerror(errno));
+ goto failed;
+ }
+ res = set_attributes(pathname, i->mode, i->uid,
+ i->gid, i->time, i->xattr, TRUE);
+ if(res == FALSE)
+ goto failed;
+
+ dev_count ++;
+ } else {
+ EXIT_UNSQUASH_STRICT("create_inode: could not"
+ " create %s device %s, because you're"
+ " not superuser!\n", chrdev ?
+ "character" : "block", pathname);
+ goto failed;
+ }
+ break;
+ }
+ case SQUASHFS_FIFO_TYPE:
+ case SQUASHFS_LFIFO_TYPE:
+ TRACE("create_inode: fifo\n");
+
+ if(force)
+ unlink(pathname);
+
+ res = mknod(pathname, S_IFIFO, 0);
+ if(res == -1) {
+ ERROR("create_inode: failed to create fifo %s, "
+ "because %s\n", pathname,
+ strerror(errno));
+ goto failed;
+ }
+ res = set_attributes(pathname, i->mode, i->uid, i->gid,
+ i->time, i->xattr, TRUE);
+ if(res == FALSE)
+ goto failed;
+
+ fifo_count ++;
+ break;
+ case SQUASHFS_SOCKET_TYPE:
+ case SQUASHFS_LSOCKET_TYPE:
+ TRACE("create_inode: socket\n");
+
+ res = mknod(pathname, S_IFSOCK, 0);
+ if (res == -1) {
+ ERROR("create_inode: failed to create socket "
+ "%s, because %s\n", pathname,
+ strerror(errno));
+ goto failed;
+ }
+ res = set_attributes(pathname, i->mode, i->uid, i->gid,
+ i->time, i->xattr, TRUE);
+ if(res == FALSE)
+ goto failed;
+
+ socket_count++;
+ break;
+ default:
+ EXIT_UNSQUASH_STRICT("Unknown inode type %d in "
+ "create_inode_table!\n", i->type);
+ return FALSE;
+ }
+
+ insert_lookup(i->inode_number, strdup(pathname));
+
+ return TRUE;
+
+failed:
+ /*
+ * Mark the file as created (even though it may not have been), so
+ * any future hard links to it fail with a file not found, which
+ * is correct as the file *is* missing.
+ *
+ * If we don't mark it here as created, then any future hard links
+ * will try to create the file as a separate unlinked file.
+ * If we've had some transitory errors, this may produce files
+ * in various states, which should be hard-linked, but are not.
+ */
+ insert_lookup(i->inode_number, strdup(pathname));
+
+ return FALSE;
+}
+
+
+int squashfs_readdir(struct dir *dir, char **name, unsigned int *start_block,
+unsigned int *offset, unsigned int *type)
+{
+ if(dir->cur_entry == NULL)
+ dir->cur_entry = dir->dirs;
+ else
+ dir->cur_entry = dir->cur_entry->next;
+
+ if(dir->cur_entry == NULL)
+ return FALSE;
+
+ *name = dir->cur_entry->name;
+ *start_block = dir->cur_entry->start_block;
+ *offset = dir->cur_entry->offset;
+ *type = dir->cur_entry->type;
+
+ return TRUE;
+}
+
+
+char *get_component(char *target, char **targname)
+{
+ char *start;
+
+ while(*target == '/')
+ target ++;
+
+ if(*target == '\0')
+ return NULL;
+
+ start = target;
+ while(*target != '/' && *target != '\0')
+ target ++;
+
+ *targname = strndup(start, target - start);
+
+ while(*target == '/')
+ target ++;
+
+ return target;
+}
+
+
+void free_path(struct pathname *paths)
+{
+ int i;
+
+ for(i = 0; i < paths->names; i++) {
+ if(paths->name[i].paths)
+ free_path(paths->name[i].paths);
+ free(paths->name[i].name);
+ if(paths->name[i].preg) {
+ regfree(paths->name[i].preg);
+ free(paths->name[i].preg);
+ }
+ }
+
+ free(paths);
+}
+
+
+struct pathname *add_path(struct pathname *paths, int type, char *target,
+ char *alltarget)
+{
+ char *targname;
+ int i, error;
+
+ if(type == PATH_TYPE_EXTRACT)
+ TRACE("add_path: adding \"%s\" extract file\n", target);
+ else
+ TRACE("add_path: adding \"%s\" exclude file\n", target);
+
+ target = get_component(target, &targname);
+
+ if(target == NULL) {
+ if(type == PATH_TYPE_EXTRACT)
+ EXIT_UNSQUASH("Invalid extract file %s\n", alltarget);
+ else
+ EXIT_UNSQUASH("Invalid exclude file %s\n", alltarget);
+ }
+
+ if(paths == NULL) {
+ paths = malloc(sizeof(struct pathname));
+ if(paths == NULL)
+ MEM_ERROR();
+
+ paths->names = 0;
+ paths->name = NULL;
+ }
+
+ for(i = 0; i < paths->names; i++)
+ if(strcmp(paths->name[i].name, targname) == 0)
+ break;
+
+ if(i == paths->names) {
+ /*
+ * allocate new name entry
+ */
+ paths->names ++;
+ paths->name = realloc(paths->name, (i + 1) *
+ sizeof(struct path_entry));
+ if(paths->name == NULL)
+ MEM_ERROR();
+
+ paths->name[i].name = targname;
+ paths->name[i].paths = NULL;
+ if(use_regex) {
+ paths->name[i].preg = malloc(sizeof(regex_t));
+ if(paths->name[i].preg == NULL)
+ MEM_ERROR();
+ error = regcomp(paths->name[i].preg, targname,
+ REG_EXTENDED|REG_NOSUB);
+ if(error) {
+ char str[1024]; /* overflow safe */
+
+ regerror(error, paths->name[i].preg, str, 1024);
+ if(type == PATH_TYPE_EXTRACT)
+ EXIT_UNSQUASH("invalid regex %s in extract %s, "
+ "because %s\n", targname, alltarget,
+ str);
+ else
+ EXIT_UNSQUASH("invalid regex %s in exclude %s, "
+ "because %s\n", targname, alltarget,
+ str);
+ }
+ } else
+ paths->name[i].preg = NULL;
+
+ if(target[0] == '\0') {
+ /*
+ * at leaf pathname component
+ */
+ paths->name[i].paths = NULL;
+ paths->name[i].type = type;
+ } else {
+ /*
+ * recurse adding child components
+ */
+ paths->name[i].type = PATH_TYPE_LINK;
+ paths->name[i].paths = add_path(NULL, type, target,
+ alltarget);
+ }
+ } else {
+ /*
+ * existing matching entry
+ */
+ free(targname);
+
+ if(paths->name[i].type != PATH_TYPE_LINK) {
+ /*
+ * This is the leaf component of a pre-existing
+ * extract/exclude which is either the same as the one
+ * we're adding, or encompasses it (if the one we're
+ * adding still has some path to walk). In either case
+ * we don't need to add this extract/exclude file
+ */
+ } else if(target[0] == '\0') {
+ /*
+ * at leaf pathname component of the extract/exclude
+ * being added, but, child components exist from more
+ * specific extracts/excludes. Delete as they're
+ * encompassed by this
+ */
+ free_path(paths->name[i].paths);
+ paths->name[i].paths = NULL;
+ paths->name[i].type = type;
+ } else
+ /*
+ * recurse adding child components
+ */
+ add_path(paths->name[i].paths, type, target, alltarget);
+ }
+
+ return paths;
+}
+
+
+void add_extract(char *target)
+{
+ extract = add_path(extract, PATH_TYPE_EXTRACT, target, target);
+}
+
+
+void add_exclude(char *str)
+{
+ if(strncmp(str, "... ", 4) == 0)
+ stickypath = add_path(stickypath, PATH_TYPE_EXCLUDE, str + 4, str + 4);
+ else
+ exclude = add_path(exclude, PATH_TYPE_EXCLUDE, str, str);
+}
+
+
+struct pathnames *init_subdir()
+{
+ struct pathnames *new = malloc(sizeof(struct pathnames));
+ if(new == NULL)
+ MEM_ERROR();
+
+ new->count = 0;
+ return new;
+}
+
+
+struct pathnames *add_subdir(struct pathnames *paths, struct pathname *path)
+{
+ if(paths->count % PATHS_ALLOC_SIZE == 0) {
+ paths = realloc(paths, sizeof(struct pathnames *) +
+ (paths->count + PATHS_ALLOC_SIZE) *
+ sizeof(struct pathname *));
+ if(paths == NULL)
+ MEM_ERROR();
+ }
+
+ paths->path[paths->count++] = path;
+ return paths;
+}
+
+
+void free_subdir(struct pathnames *paths)
+{
+ free(paths);
+}
+
+
+int extract_matches(struct pathnames *paths, char *name, struct pathnames **new)
+{
+ int i, n;
+
+ /* nothing to match, extract */
+ if(paths == NULL) {
+ *new = NULL;
+ return TRUE;
+ }
+
+ *new = init_subdir();
+
+ for(n = 0; n < paths->count; n++) {
+ struct pathname *path = paths->path[n];
+ for(i = 0; i < path->names; i++) {
+ int match;
+
+ if(no_wildcards)
+ match = strcmp(path->name[i].name, name) == 0;
+ else if(use_regex)
+ match = regexec(path->name[i].preg, name,
+ (size_t) 0, NULL, 0) == 0;
+ else
+ match = fnmatch(path->name[i].name,
+ name, FNM_PATHNAME|FNM_PERIOD|
+ FNM_EXTMATCH) == 0;
+
+ if(match && path->name[i].type == PATH_TYPE_EXTRACT)
+ /*
+ * match on a leaf component, any subdirectories
+ * will implicitly match, therefore return an
+ * empty new search set
+ */
+ goto empty_set;
+
+ if(match)
+ /*
+ * match on a non-leaf component, add any
+ * subdirectories to the new set of
+ * subdirectories to scan for this name
+ */
+ *new = add_subdir(*new, path->name[i].paths);
+ }
+ }
+
+ if((*new)->count == 0) {
+ /*
+ * no matching names found, delete empty search set, and return
+ * FALSE
+ */
+ free_subdir(*new);
+ *new = NULL;
+ return FALSE;
+ }
+
+ /*
+ * one or more matches with sub-directories found (no leaf matches),
+ * return new search set and return TRUE
+ */
+ return TRUE;
+
+empty_set:
+ /*
+ * found matching leaf extract, return empty search set and return TRUE
+ */
+ free_subdir(*new);
+ *new = NULL;
+ return TRUE;
+}
+
+
+int exclude_match(struct pathname *path, char *name, struct pathnames **new)
+{
+ int i, match;
+
+ for(i = 0; i < path->names; i++) {
+ if(no_wildcards)
+ match = strcmp(path->name[i].name, name) == 0;
+ else if(use_regex)
+ match = regexec(path->name[i].preg, name,
+ (size_t) 0, NULL, 0) == 0;
+ else
+ match = fnmatch(path->name[i].name, name,
+ FNM_PATHNAME|FNM_PERIOD| FNM_EXTMATCH) == 0;
+
+ if(match && path->name[i].type == PATH_TYPE_EXCLUDE) {
+ /*
+ * match on a leaf component, any subdirectories
+ * will implicitly match, therefore return an
+ * empty new search set
+ */
+ free(*new);
+ *new = NULL;
+ return TRUE;
+ }
+
+ if(match)
+ /*
+ * match on a non-leaf component, add any
+ * subdirectories to the new set of
+ * subdirectories to scan for this name
+ */
+ *new = add_subdir(*new, path->name[i].paths);
+ }
+
+ return FALSE;
+}
+
+
+int exclude_matches(struct pathnames *paths, char *name, struct pathnames **new)
+{
+ int n;
+
+ /* nothing to match, don't exclude */
+ if(paths == NULL && stickypath == NULL) {
+ *new = NULL;
+ return FALSE;
+ }
+
+ *new = init_subdir();
+
+ if(stickypath && exclude_match(stickypath, name, new))
+ return TRUE;
+
+ for(n = 0; paths && n < paths->count; n++) {
+ int res = exclude_match(paths->path[n], name, new);
+
+ if(res)
+ return TRUE;
+ }
+
+ if((*new)->count == 0) {
+ /*
+ * no matching names found, don't exclude. Delete empty search
+ * set, and return FALSE
+ */
+ free_subdir(*new);
+ *new = NULL;
+ return FALSE;
+ }
+
+ /*
+ * one or more matches with sub-directories found (no leaf matches),
+ * return new search set and return FALSE
+ */
+ return FALSE;
+}
+
+
+struct directory_stack *create_stack()
+{
+ struct directory_stack *stack = malloc(sizeof(struct directory_stack));
+ if(stack == NULL)
+ MEM_ERROR();
+
+ stack->size = 0;
+ stack->stack = NULL;
+ stack->symlink = NULL;
+ stack->name = NULL;
+
+ return stack;
+}
+
+
+void add_stack(struct directory_stack *stack, unsigned int start_block,
+ unsigned int offset, char *name, int depth)
+{
+ if((depth - 1) == stack->size) {
+ /* Stack growing an extra level */
+ stack->stack = realloc(stack->stack, depth *
+ sizeof(struct directory_level));
+
+ if(stack->stack == NULL)
+ MEM_ERROR();
+
+ stack->stack[depth - 1].start_block = start_block;
+ stack->stack[depth - 1].offset = offset;
+ stack->stack[depth - 1].name = strdup(name);
+ } else if((depth + 1) == stack->size)
+ /* Stack shrinking a level */
+ free(stack->stack[depth].name);
+ else if(depth == stack->size)
+ /* Stack staying same size - nothing to do */
+ return;
+ else
+ /* Any other change in size is invalid */
+ EXIT_UNSQUASH("Invalid state in add_stack\n");
+
+ stack->size = depth;
+}
+
+
+struct directory_stack *clone_stack(struct directory_stack *stack)
+{
+ int i;
+ struct directory_stack *new = malloc(sizeof(struct directory_stack));
+ if(stack == NULL)
+ MEM_ERROR();
+
+ new->stack = malloc(stack->size * sizeof(struct directory_level));
+ if(new->stack == NULL)
+ MEM_ERROR();
+
+ for(i = 0; i < stack->size; i++) {
+ new->stack[i].start_block = stack->stack[i].start_block;
+ new->stack[i].offset = stack->stack[i].offset;
+ new->stack[i].name = strdup(stack->stack[i].name);
+ }
+
+ new->size = stack->size;
+ new->symlink = NULL;
+ new->name = NULL;
+
+ return new;
+}
+
+
+void pop_stack(struct directory_stack *stack)
+{
+ free(stack->stack[--stack->size].name);
+}
+
+
+void free_stack(struct directory_stack *stack)
+{
+ int i;
+ struct symlink *symlink = stack->symlink;
+
+ for(i = 0; i < stack->size; i++)
+ free(stack->stack[i].name);
+
+ while(symlink) {
+ struct symlink *s = symlink;
+
+ symlink = symlink->next;
+ free(s->pathname);
+ free(s);
+ }
+
+ free(stack->stack);
+ free(stack->name);
+ free(stack);
+}
+
+
+char *stack_pathname(struct directory_stack *stack, char *name)
+{
+ int i, size = 0;
+ char *pathname;
+
+ /* work out how much space is needed for the pathname */
+ for(i = 1; i < stack->size; i++)
+ size += strlen(stack->stack[i].name);
+
+ /* add room for leaf name, slashes and '\0' terminator */
+ size += strlen(name) + stack->size;
+
+ pathname = malloc(size);
+ if (pathname == NULL)
+ MEM_ERROR();
+
+ pathname[0] = '\0';
+
+ /* concatenate */
+ for(i = 1; i < stack->size; i++) {
+ strcat(pathname, stack->stack[i].name);
+ strcat(pathname, "/");
+ }
+
+ strcat(pathname, name);
+
+ return pathname;
+}
+
+
+void add_symlink(struct directory_stack *stack, char *name)
+{
+ struct symlink *symlink = malloc(sizeof(struct symlink));
+ if(symlink == NULL)
+ MEM_ERROR();
+
+ symlink->pathname = stack_pathname(stack, name);
+ symlink->next = stack->symlink;
+ stack->symlink = symlink;
+}
+
+
+/*
+ * Walk the supplied pathname. If any symlinks are encountered whilst walking
+ * the pathname, then recursively walk those, to obtain the fully
+ * dereferenced canonicalised pathname. Return that and the pathnames
+ * of all symlinks found during the walk.
+ *
+ * follow_path (-follow-symlinks option) implies no wildcard matching,
+ * due to the fact that with wildcards there is no single canonical pathname
+ * to be found. Many pathnames may match or none at all.
+ *
+ * If follow_path fails to walk a pathname either because a component
+ * doesn't exist, it is a non directory component when a directory
+ * component is expected, a symlink with an absolute path is encountered,
+ * or a symlink is encountered which cannot be recursively walked due to
+ * the above failures, then return FALSE.
+ */
+int follow_path(char *path, char *name, unsigned int start_block,
+ unsigned int offset, int depth, int symlinks,
+ struct directory_stack *stack)
+{
+ struct inode *i;
+ struct dir *dir;
+ char *target, *symlink;
+ unsigned int type;
+ int traversed = FALSE;
+ unsigned int entry_start, entry_offset;
+
+ while((path = get_component(path, &target))) {
+ if(strcmp(target, ".") != 0)
+ break;
+
+ free(target);
+ }
+
+ if(path == NULL)
+ return FALSE;
+
+ add_stack(stack, start_block, offset, name, depth);
+
+ if(strcmp(target, "..") == 0) {
+ if(depth > 1) {
+ start_block = stack->stack[depth - 2].start_block;
+ offset = stack->stack[depth - 2].offset;
+
+ traversed = follow_path(path, "", start_block, offset,
+ depth - 1, symlinks, stack);
+ }
+
+ free(target);
+ return traversed;
+ }
+
+ dir = s_ops->opendir(start_block, offset, &i);
+ if(dir == NULL) {
+ free(target);
+ return FALSE;
+ }
+
+ while(squashfs_readdir(dir, &name, &entry_start, &entry_offset, &type)) {
+ if(strcmp(name, target) == 0) {
+ switch(type) {
+ case SQUASHFS_SYMLINK_TYPE:
+ i = s_ops->read_inode(entry_start, entry_offset);
+ symlink = i->symlink;
+
+ /* Symlink must be relative to current
+ * directory and not be absolute, otherwise
+ * we can't follow it, as it is probably
+ * outside the Squashfs filesystem */
+ if(symlink[0] == '/') {
+ traversed = FALSE;
+ free(symlink);
+ break;
+ }
+
+ /* Detect circular symlinks */
+ if(symlinks >= MAX_FOLLOW_SYMLINKS) {
+ ERROR("Too many levels of symbolic "
+ "links\n");
+ traversed = FALSE;
+ free(symlink);
+ break;
+ }
+
+ /* Add symlink to list of symlinks found
+ * traversing the pathname */
+ add_symlink(stack, name);
+
+ traversed = follow_path(symlink, "",
+ start_block, offset, depth,
+ symlinks + 1, stack);
+
+ free(symlink);
+
+ if(traversed == TRUE) {
+ /* If we still have some path to
+ * walk, then walk it from where
+ * the symlink traversal left us
+ *
+ * Obviously symlink traversal must
+ * have left us at a directory to do
+ * this */
+ if(path[0] != '\0') {
+ if(stack->type !=
+ SQUASHFS_DIR_TYPE) {
+ traversed = FALSE;
+ break;
+ }
+
+ /* "Jump" to the traversed
+ * point */
+ depth = stack->size;
+ start_block = stack->start_block;
+ offset = stack->offset;
+ name = stack->name;
+
+ /* continue following path */
+ traversed = follow_path(path,
+ name, start_block,
+ offset, depth + 1,
+ symlinks, stack);
+ }
+ }
+
+ break;
+ case SQUASHFS_DIR_TYPE:
+ /* if at end of path, traversed OK */
+ if(path[0] == '\0') {
+ traversed = TRUE;
+ stack->name = strdup(name);
+ stack->type = type;
+ stack->start_block = entry_start;
+ stack->offset = entry_offset;
+ } else /* follow the path */
+ traversed = follow_path(path, name,
+ entry_start, entry_offset,
+ depth + 1, symlinks, stack);
+ break;
+ default:
+ /* leaf directory entry, can't go any further,
+ * and so path must not continue */
+ if(path[0] == '\0') {
+ traversed = TRUE;
+ stack->name = strdup(name);
+ stack->type = type;
+ stack->start_block = entry_start;
+ stack->offset = entry_offset;
+ } else
+ traversed = FALSE;
+ }
+ }
+ }
+
+ free(target);
+ squashfs_closedir(dir);
+
+ return traversed;
+}
+
+
+int pre_scan(char *parent_name, unsigned int start_block, unsigned int offset,
+ struct pathnames *extracts, struct pathnames *excludes, int depth)
+{
+ unsigned int type;
+ int scan_res = TRUE;
+ char *name;
+ struct pathnames *newt, *newc = NULL;
+ struct inode *i;
+ struct dir *dir;
+
+ if(max_depth != -1 && depth > max_depth)
+ return TRUE;
+
+ dir = s_ops->opendir(start_block, offset, &i);
+ if(dir == NULL)
+ return FALSE;
+
+ if(inumber_lookup(i->inode_number))
+ EXIT_UNSQUASH("File System corrupted: directory loop detected\n");
+
+ while(squashfs_readdir(dir, &name, &start_block, &offset, &type)) {
+ struct inode *i;
+ char *pathname;
+ int res;
+
+ TRACE("pre_scan: name %s, start_block %d, offset %d, type %d\n",
+ name, start_block, offset, type);
+
+ if(!extract_matches(extracts, name, &newt))
+ continue;
+
+ if(exclude_matches(excludes, name, &newc)) {
+ free_subdir(newt);
+ continue;
+ }
+
+ res = asprintf(&pathname, "%s/%s", parent_name, name);
+ if(res == -1)
+ MEM_ERROR();
+
+ if(type == SQUASHFS_DIR_TYPE) {
+ res = pre_scan(parent_name, start_block, offset, newt,
+ newc, depth + 1);
+ if(res == FALSE)
+ scan_res = FALSE;
+ } else if(newt == NULL) {
+ if(type == SQUASHFS_FILE_TYPE) {
+ i = s_ops->read_inode(start_block, offset);
+ if(lookup(i->inode_number) == NULL) {
+ insert_lookup(i->inode_number, (char *) i);
+ total_blocks += (i->data +
+ (block_size - 1)) >> block_log;
+ }
+ total_files ++;
+ }
+ total_inodes ++;
+ }
+
+ free_subdir(newt);
+ free_subdir(newc);
+ free(pathname);
+ }
+
+ squashfs_closedir(dir);
+
+ return scan_res;
+}
+
+
+int dir_scan(char *parent_name, unsigned int start_block, unsigned int offset,
+ struct pathnames *extracts, struct pathnames *excludes, int depth)
+{
+ unsigned int type;
+ int scan_res = TRUE;
+ char *name;
+ struct pathnames *newt, *newc = NULL;
+ struct inode *i;
+ struct dir *dir = s_ops->opendir(start_block, offset, &i);
+
+ if(dir == NULL) {
+ EXIT_UNSQUASH_IGNORE("dir_scan: failed to read directory %s\n",
+ parent_name);
+ return FALSE;
+ }
+
+ if(inumber_lookup(i->inode_number))
+ EXIT_UNSQUASH("File System corrupted: directory loop detected\n");
+
+ if((lsonly || info) && (!concise || dir->dir_count ==0))
+ print_filename(parent_name, i);
+
+ if(!lsonly) {
+ /*
+ * Make directory with default User rwx permissions rather than
+ * the permissions from the filesystem, as these may not have
+ * write/execute permission. These are fixed up later in
+ * set_attributes().
+ */
+ int res = mkdir(parent_name, S_IRUSR|S_IWUSR|S_IXUSR);
+ if(res == -1) {
+ /*
+ * Skip directory if mkdir fails, unless we're
+ * forcing and the error is -EEXIST
+ */
+ if((depth != 1 && !force) || errno != EEXIST) {
+ EXIT_UNSQUASH_IGNORE("dir_scan: failed to make"
+ " directory %s, because %s\n",
+ parent_name, strerror(errno));
+ squashfs_closedir(dir);
+ return FALSE;
+ }
+
+ /*
+ * Try to change permissions of existing directory so
+ * that we can write to it
+ */
+ res = chmod(parent_name, S_IRUSR|S_IWUSR|S_IXUSR);
+ if (res == -1) {
+ EXIT_UNSQUASH_IGNORE("dir_scan: failed to "
+ "change permissions for directory %s,"
+ " because %s\n", parent_name,
+ strerror(errno));
+ squashfs_closedir(dir);
+ return FALSE;
+ }
+ }
+ }
+
+ if(max_depth == -1 || depth <= max_depth) {
+ while(squashfs_readdir(dir, &name, &start_block, &offset,
+ &type)) {
+ char *pathname;
+ int res;
+
+ TRACE("dir_scan: name %s, start_block %d, offset %d,"
+ " type %d\n", name, start_block, offset, type);
+
+
+ if(!extract_matches(extracts, name, &newt))
+ continue;
+
+ if(exclude_matches(excludes, name, &newc)) {
+ free_subdir(newt);
+ continue;
+ }
+
+ res = asprintf(&pathname, "%s/%s", parent_name, name);
+ if(res == -1)
+ MEM_ERROR();
+
+ if(type == SQUASHFS_DIR_TYPE) {
+ res = dir_scan(pathname, start_block, offset,
+ newt, newc, depth + 1);
+ if(res == FALSE)
+ scan_res = FALSE;
+ free(pathname);
+ } else if(newt == NULL) {
+ update_info(pathname);
+
+ i = s_ops->read_inode(start_block, offset);
+
+ if(lsonly || info)
+ print_filename(pathname, i);
+
+ if(!lsonly) {
+ res = create_inode(pathname, i);
+ if(res == FALSE)
+ scan_res = FALSE;
+ }
+
+ if(i->type == SQUASHFS_SYMLINK_TYPE ||
+ i->type == SQUASHFS_LSYMLINK_TYPE)
+ free(i->symlink);
+ } else {
+ free(pathname);
+
+ if(i->type == SQUASHFS_SYMLINK_TYPE ||
+ i->type == SQUASHFS_LSYMLINK_TYPE)
+ free(i->symlink);
+ }
+
+ free_subdir(newt);
+ free_subdir(newc);
+ }
+ }
+
+ if(!lsonly)
+ queue_dir(parent_name, dir);
+
+ squashfs_closedir(dir);
+ dir_count ++;
+
+ return scan_res;
+}
+
+
+int check_compression(struct compressor *comp)
+{
+ int res, bytes = 0;
+ char buffer[SQUASHFS_METADATA_SIZE] __attribute__ ((aligned));
+
+ if(!comp->supported) {
+ ERROR("Filesystem uses %s compression, this is "
+ "unsupported by this version\n", comp->name);
+ ERROR("Decompressors available:\n");
+ display_compressors(stderr, "", "");
+ return FALSE;
+ }
+
+ /*
+ * Read compression options from disk if present, and pass to
+ * the compressor to ensure we know how to decompress a filesystem
+ * compressed with these compression options.
+ *
+ * Note, even if there is no compression options we still call the
+ * compressor because some compression options may be mandatory
+ * for some compressors.
+ */
+ if(SQUASHFS_COMP_OPTS(sBlk.s.flags)) {
+ bytes = read_block(fd, sizeof(sBlk.s), NULL, 0, buffer);
+ if(bytes == 0) {
+ ERROR("Failed to read compressor options\n");
+ return FALSE;
+ }
+ }
+
+ res = compressor_check_options(comp, sBlk.s.block_size, buffer, bytes);
+
+ return res != -1;
+}
+
+
+int read_super(char *source)
+{
+ squashfs_super_block_3 sBlk_3;
+
+ /*
+ * Try to read a Squashfs 4 superblock
+ */
+ int res = read_super_4(&s_ops);
+
+ if(res != -1)
+ return res;
+ res = read_super_3(source, &s_ops, &sBlk_3);
+ if(res != -1)
+ return res;
+ res = read_super_2(&s_ops, &sBlk_3);
+ if(res != -1)
+ return res;
+ res = read_super_1(&s_ops, &sBlk_3);
+ if(res != -1)
+ return res;
+
+ return FALSE;
+}
+
+
+void process_extract_files(char *filename)
+{
+ FILE *fd;
+ char buffer[MAX_LINE + 1]; /* overflow safe */
+ char *name;
+
+ fd = fopen(filename, "r");
+ if(fd == NULL)
+ EXIT_UNSQUASH("Failed to open extract file \"%s\" because %s\n",
+ filename, strerror(errno));
+
+ while(fgets(name = buffer, MAX_LINE + 1, fd) != NULL) {
+ int len = strlen(name);
+
+ if(len == MAX_LINE && name[len - 1] != '\n')
+ /* line too large */
+ EXIT_UNSQUASH("Line too long when reading "
+ "extract file \"%s\", larger than %d "
+ "bytes\n", filename, MAX_LINE);
+
+ /*
+ * Remove '\n' terminator if it exists (the last line
+ * in the file may not be '\n' terminated)
+ */
+ if(len && name[len - 1] == '\n')
+ name[len - 1] = '\0';
+
+ /* Skip any leading whitespace */
+ while(isspace(*name))
+ name ++;
+
+ /* if comment line, skip */
+ if(*name == '#')
+ continue;
+
+ /* check for initial backslash, to accommodate
+ * filenames with leading space or leading # character
+ */
+ if(*name == '\\')
+ name ++;
+
+ /* if line is now empty after skipping characters, skip it */
+ if(*name == '\0')
+ continue;
+
+ add_extract(name);
+ }
+
+ if(ferror(fd))
+ EXIT_UNSQUASH("Reading extract file \"%s\" failed because %s\n",
+ filename, strerror(errno));
+
+ fclose(fd);
+}
+
+
+void process_exclude_files(char *filename)
+{
+ FILE *fd;
+ char buffer[MAX_LINE + 1]; /* overflow safe */
+ char *name;
+
+ fd = fopen(filename, "r");
+ if(fd == NULL)
+ EXIT_UNSQUASH("Failed to open exclude file \"%s\" because %s\n",
+ filename, strerror(errno));
+
+ while(fgets(name = buffer, MAX_LINE + 1, fd) != NULL) {
+ int len = strlen(name);
+
+ if(len == MAX_LINE && name[len - 1] != '\n')
+ /* line too large */
+ EXIT_UNSQUASH("Line too long when reading "
+ "exclude file \"%s\", larger than %d "
+ "bytes\n", filename, MAX_LINE);
+
+ /*
+ * Remove '\n' terminator if it exists (the last line
+ * in the file may not be '\n' terminated)
+ */
+ if(len && name[len - 1] == '\n')
+ name[len - 1] = '\0';
+
+ /* Skip any leading whitespace */
+ while(isspace(*name))
+ name ++;
+
+ /* if comment line, skip */
+ if(*name == '#')
+ continue;
+
+ /* check for initial backslash, to accommodate
+ * filenames with leading space or leading # character
+ */
+ if(*name == '\\')
+ name ++;
+
+ /* if line is now empty after skipping characters, skip it */
+ if(*name == '\0')
+ continue;
+
+ add_exclude(name);
+ }
+
+ if(ferror(fd))
+ EXIT_UNSQUASH("Reading exclude file \"%s\" failed because %s\n",
+ filename, strerror(errno));
+
+ fclose(fd);
+}
+
+
+/*
+ * reader thread. This thread processes read requests queued by the
+ * cache_get() routine.
+ */
+void *reader(void *arg)
+{
+ while(1) {
+ struct cache_entry *entry = queue_get(to_reader);
+ int res = read_fs_bytes(fd, entry->block,
+ SQUASHFS_COMPRESSED_SIZE_BLOCK(entry->size),
+ entry->data);
+
+ if(res && SQUASHFS_COMPRESSED_BLOCK(entry->size))
+ /*
+ * queue successfully read block to the inflate
+ * thread(s) for further processing
+ */
+ queue_put(to_inflate, entry);
+ else
+ /*
+ * block has either been successfully read and is
+ * uncompressed, or an error has occurred, clear pending
+ * flag, set error appropriately, and wake up any
+ * threads waiting on this buffer
+ */
+ cache_block_ready(entry, !res);
+ }
+}
+
+
+/*
+ * writer thread. This processes file write requests queued by the
+ * write_file() routine.
+ */
+void *writer(void *arg)
+{
+ int i;
+ long exit_code = FALSE;
+
+ while(1) {
+ struct squashfs_file *file = queue_get(to_writer);
+ int file_fd;
+ long long hole = 0;
+ int local_fail = FALSE;
+ int res;
+
+ if(file == NULL) {
+ queue_put(from_writer, (void *) exit_code);
+ continue;
+ } else if(file->fd == -1) {
+ /* write attributes for directory file->pathname */
+ res = set_attributes(file->pathname, file->mode,
+ file->uid, file->gid, file->time, file->xattr,
+ TRUE);
+ if(res == FALSE)
+ exit_code = TRUE;
+ free(file->pathname);
+ free(file);
+ continue;
+ }
+
+ TRACE("writer: regular file, blocks %d\n", file->blocks);
+
+ file_fd = file->fd;
+
+ for(i = 0; i < file->blocks; i++, cur_blocks ++) {
+ struct file_entry *block = queue_get(to_writer);
+
+ if(block->buffer == 0) { /* sparse file */
+ hole += block->size;
+ free(block);
+ continue;
+ }
+
+ cache_block_wait(block->buffer);
+
+ if(block->buffer->error) {
+ EXIT_UNSQUASH_IGNORE("writer: failed to "
+ "read/uncompress file %s\n",
+ file->pathname);
+ exit_code = local_fail = TRUE;
+ }
+
+ if(local_fail == FALSE) {
+ res = write_block(file_fd,
+ block->buffer->data + block->offset,
+ block->size, hole, file->sparse);
+
+ if(res == FALSE) {
+ EXIT_UNSQUASH_IGNORE("writer: failed "
+ "to write file %s\n",
+ file->pathname);
+ exit_code = local_fail = TRUE;
+ }
+ }
+
+ hole = 0;
+ cache_block_put(block->buffer);
+ free(block);
+ }
+
+ if(hole && local_fail == FALSE) {
+ /*
+ * corner case for hole extending to end of file
+ */
+ if(file->sparse == FALSE ||
+ lseek(file_fd, hole, SEEK_CUR) == -1) {
+ /*
+ * for files which we don't want to write
+ * sparsely, or for broken lseeks which cannot
+ * seek beyond end of file, write_block will do
+ * the right thing
+ */
+ hole --;
+ if(write_block(file_fd, "\0", 1, hole,
+ file->sparse) == FALSE) {
+ EXIT_UNSQUASH_IGNORE("writer: failed "
+ "to write sparse data block "
+ "for file %s\n",
+ file->pathname);
+ exit_code = local_fail = TRUE;
+ }
+ } else if(ftruncate(file_fd, file->file_size) == -1) {
+ EXIT_UNSQUASH_IGNORE("writer: failed to write "
+ "sparse data block for file %s\n",
+ file->pathname);
+ exit_code = local_fail = TRUE;
+ }
+ }
+
+ close_wake(file_fd);
+ if(local_fail == FALSE) {
+ int set = !root_process && !(file->mode & S_IWUSR) && has_xattrs(file->xattr);
+
+ res = set_attributes(file->pathname, file->mode,
+ file->uid, file->gid, file->time, file->xattr,
+ force || set);
+ if(res == FALSE)
+ exit_code = TRUE;
+ } else
+ unlink(file->pathname);
+ free(file->pathname);
+ free(file);
+
+ }
+}
+
+
+void *cat_writer(void *arg)
+{
+ int i;
+ long exit_code = FALSE;
+
+ while(1) {
+ struct squashfs_file *file = queue_get(to_writer);
+ long long hole = 0;
+ int local_fail = FALSE;
+ int res;
+
+ if(file == NULL) {
+ queue_put(from_writer, (void *) exit_code);
+ continue;
+ }
+
+ TRACE("cat_writer: regular file, blocks %d\n", file->blocks);
+
+ for(i = 0; i < file->blocks; i++, cur_blocks ++) {
+ struct file_entry *block = queue_get(to_writer);
+
+ if(block->buffer == 0) { /* sparse file */
+ hole += block->size;
+ free(block);
+ continue;
+ }
+
+ cache_block_wait(block->buffer);
+
+ if(block->buffer->error) {
+ EXIT_UNSQUASH_IGNORE("cat: failed to "
+ "read/uncompress file %s\n",
+ file->pathname);
+ exit_code = local_fail = TRUE;
+ }
+
+ if(local_fail == FALSE) {
+ res = write_block(writer_fd,
+ block->buffer->data + block->offset,
+ block->size, hole, FALSE);
+
+ if(res == FALSE) {
+ EXIT_UNSQUASH_IGNORE("cat: failed "
+ "to write file %s\n",
+ file->pathname);
+ exit_code = local_fail = TRUE;
+ }
+ }
+
+ hole = 0;
+ cache_block_put(block->buffer);
+ free(block);
+ }
+
+ if(hole && local_fail == FALSE) {
+ /*
+ * corner case for hole extending to end of file
+ */
+ hole --;
+ if(write_block(writer_fd, "\0", 1, hole,
+ file->sparse) == FALSE) {
+ EXIT_UNSQUASH_IGNORE("cat: failed "
+ "to write sparse data block "
+ "for file %s\n",
+ file->pathname);
+ exit_code = local_fail = TRUE;
+ }
+ }
+
+ free(file->pathname);
+ free(file);
+ }
+}
+
+
+/*
+ * decompress thread. This decompresses buffers queued by the read thread
+ */
+void *inflator(void *arg)
+{
+ char *tmp = malloc(block_size);
+ if(tmp == NULL)
+ MEM_ERROR();
+
+ while(1) {
+ struct cache_entry *entry = queue_get(to_inflate);
+ int error, res;
+
+ res = compressor_uncompress(comp, tmp, entry->data,
+ SQUASHFS_COMPRESSED_SIZE_BLOCK(entry->size), block_size,
+ &error);
+
+ if(res == -1)
+ ERROR("%s uncompress failed with error code %d\n",
+ comp->name, error);
+ else
+ memcpy(entry->data, tmp, res);
+
+ /*
+ * block has been either successfully decompressed, or an error
+ * occurred, clear pending flag, set error appropriately and
+ * wake up any threads waiting on this block
+ */
+ cache_block_ready(entry, res == -1);
+ }
+}
+
+
+void *progress_thread(void *arg)
+{
+ struct timespec requested_time, remaining;
+ struct itimerval itimerval;
+ struct winsize winsize;
+
+ if(ioctl(1, TIOCGWINSZ, &winsize) == -1) {
+ if(isatty(STDOUT_FILENO))
+ ERROR("TIOCGWINSZ ioctl failed, defaulting to 80 "
+ "columns\n");
+ columns = 80;
+ } else
+ columns = winsize.ws_col;
+ signal(SIGWINCH, sigwinch_handler);
+ signal(SIGALRM, sigalrm_handler);
+
+ itimerval.it_value.tv_sec = 0;
+ itimerval.it_value.tv_usec = 250000;
+ itimerval.it_interval.tv_sec = 0;
+ itimerval.it_interval.tv_usec = 250000;
+ setitimer(ITIMER_REAL, &itimerval, NULL);
+
+ requested_time.tv_sec = 0;
+ requested_time.tv_nsec = 250000000;
+
+ while(1) {
+ int res = nanosleep(&requested_time, &remaining);
+
+ if(res == -1 && errno != EINTR)
+ EXIT_UNSQUASH("nanosleep failed in progress thread\n");
+
+ if(progress_enabled) {
+ pthread_mutex_lock(&screen_mutex);
+ progress_bar(sym_count + dev_count + fifo_count +
+ socket_count + file_count + hardlnk_count +
+ cur_blocks, total_inodes + total_blocks,
+ columns);
+ pthread_mutex_unlock(&screen_mutex);
+ }
+ }
+}
+
+
+void initialise_threads(int fragment_buffer_size, int data_buffer_size, int cat_file)
+{
+ struct rlimit rlim;
+ int i, max_files, res;
+ sigset_t sigmask, old_mask;
+
+ if(cat_file == FALSE) {
+ /* block SIGQUIT and SIGHUP, these are handled by the info thread */
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGQUIT);
+ sigaddset(&sigmask, SIGHUP);
+ if(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) != 0)
+ EXIT_UNSQUASH("Failed to set signal mask in initialise_threads\n");
+
+ /*
+ * temporarily block these signals so the created sub-threads will
+ * ignore them, ensuring the main thread handles them
+ */
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGINT);
+ sigaddset(&sigmask, SIGTERM);
+ if(pthread_sigmask(SIG_BLOCK, &sigmask, &old_mask) != 0)
+ EXIT_UNSQUASH("Failed to set signal mask in initialise_threads\n");
+ } else {
+ /*
+ * temporarily block these signals so the created sub-threads will
+ * ignore them, ensuring the main thread handles them
+ */
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGQUIT);
+ sigaddset(&sigmask, SIGHUP);
+ sigaddset(&sigmask, SIGINT);
+ sigaddset(&sigmask, SIGTERM);
+ if(pthread_sigmask(SIG_BLOCK, &sigmask, &old_mask) != 0)
+ EXIT_UNSQUASH("Failed to set signal mask in initialise_threads\n");
+ }
+
+ if(processors == -1) {
+#ifdef __linux__
+ cpu_set_t cpu_set;
+ CPU_ZERO(&cpu_set);
+
+ if(sched_getaffinity(0, sizeof cpu_set, &cpu_set) == -1)
+ processors = sysconf(_SC_NPROCESSORS_ONLN);
+ else
+ processors = CPU_COUNT(&cpu_set);
+#else
+ int mib[2];
+ size_t len = sizeof(processors);
+
+ mib[0] = CTL_HW;
+#ifdef HW_AVAILCPU
+ mib[1] = HW_AVAILCPU;
+#else
+ mib[1] = HW_NCPU;
+#endif
+
+ if(sysctl(mib, 2, &processors, &len, NULL, 0) == -1) {
+ ERROR("Failed to get number of available processors. "
+ "Defaulting to 1\n");
+ processors = 1;
+ }
+#endif
+ }
+
+ if(add_overflow(processors, 3) ||
+ multiply_overflow(processors + 3, sizeof(pthread_t)))
+ EXIT_UNSQUASH("Processors too large\n");
+
+ thread = malloc((3 + processors) * sizeof(pthread_t));
+ if(thread == NULL)
+ MEM_ERROR();
+
+ inflator_thread = &thread[3];
+
+ /*
+ * dimensioning the to_reader and to_inflate queues. The size of
+ * these queues is directly related to the amount of block
+ * read-ahead possible. To_reader queues block read requests to
+ * the reader thread and to_inflate queues block decompression
+ * requests to the inflate thread(s) (once the block has been read by
+ * the reader thread). The amount of read-ahead is determined by
+ * the combined size of the data_block and fragment caches which
+ * determine the total number of blocks which can be "in flight"
+ * at any one time (either being read or being decompressed)
+ *
+ * The maximum file open limit, however, affects the read-ahead
+ * possible, in that for normal sizes of the fragment and data block
+ * caches, where the incoming files have few data blocks or one fragment
+ * only, the file open limit is likely to be reached before the
+ * caches are full. This means the worst case sizing of the combined
+ * sizes of the caches is unlikely to ever be necessary. However, is is
+ * obvious read-ahead up to the data block cache size is always possible
+ * irrespective of the file open limit, because a single file could
+ * contain that number of blocks.
+ *
+ * Choosing the size as "file open limit + data block cache size" seems
+ * to be a reasonable estimate. We can reasonably assume the maximum
+ * likely read-ahead possible is data block cache size + one fragment
+ * per open file.
+ *
+ * dimensioning the to_writer queue. The size of this queue is
+ * directly related to the amount of block read-ahead possible.
+ * However, unlike the to_reader and to_inflate queues, this is
+ * complicated by the fact the to_writer queue not only contains
+ * entries for fragments and data_blocks but it also contains
+ * file entries, one per open file in the read-ahead.
+ *
+ * Choosing the size as "2 * (file open limit) +
+ * data block cache size" seems to be a reasonable estimate.
+ * We can reasonably assume the maximum likely read-ahead possible
+ * is data block cache size + one fragment per open file, and then
+ * we will have a file_entry for each open file.
+ */
+ res = getrlimit(RLIMIT_NOFILE, &rlim);
+ if (res == -1) {
+ ERROR("failed to get open file limit! Defaulting to 1\n");
+ rlim.rlim_cur = 1;
+ }
+
+ if (rlim.rlim_cur != RLIM_INFINITY) {
+ /*
+ * leave OPEN_FILE_MARGIN free (rlim_cur includes fds used by
+ * stdin, stdout, stderr and filesystem fd
+ */
+ if (rlim.rlim_cur <= OPEN_FILE_MARGIN)
+ /* no margin, use minimum possible */
+ max_files = 1;
+ else
+ max_files = rlim.rlim_cur - OPEN_FILE_MARGIN;
+ } else
+ max_files = -1;
+
+ /* set amount of available files for use by open_wait and close_wake */
+ open_init(max_files);
+
+ /*
+ * allocate to_reader, to_inflate and to_writer queues. Set based on
+ * cache limits, unless there is an open file limit which would produce
+ * smaller queues
+ *
+ * In doing so, check that the user supplied values do not overflow
+ * a signed int
+ */
+ if (max_files != -1 && max_files < fragment_buffer_size) {
+ if(add_overflow(data_buffer_size, max_files) ||
+ add_overflow(data_buffer_size, max_files * 2))
+ EXIT_UNSQUASH("Data queue size is too large\n");
+
+ to_reader = queue_init(max_files + data_buffer_size);
+ to_inflate = queue_init(max_files + data_buffer_size);
+ to_writer = queue_init(max_files * 2 + data_buffer_size);
+ } else {
+ int all_buffers_size;
+
+ if(add_overflow(fragment_buffer_size, data_buffer_size))
+ EXIT_UNSQUASH("Data and fragment queues combined are"
+ " too large\n");
+
+ all_buffers_size = fragment_buffer_size + data_buffer_size;
+
+ if(add_overflow(all_buffers_size, all_buffers_size))
+ EXIT_UNSQUASH("Data and fragment queues combined are"
+ " too large\n");
+
+ to_reader = queue_init(all_buffers_size);
+ to_inflate = queue_init(all_buffers_size);
+ to_writer = queue_init(all_buffers_size * 2);
+ }
+
+ from_writer = queue_init(1);
+
+ fragment_cache = cache_init(block_size, fragment_buffer_size);
+ data_cache = cache_init(block_size, data_buffer_size);
+
+ pthread_create(&thread[0], NULL, reader, NULL);
+ pthread_create(&thread[2], NULL, progress_thread, NULL);
+
+ if(pseudo_file) {
+ pthread_create(&thread[1], NULL, cat_writer, NULL);
+ init_info();
+ } else if(cat_files)
+ pthread_create(&thread[1], NULL, cat_writer, NULL);
+ else {
+ pthread_create(&thread[1], NULL, writer, NULL);
+ init_info();
+ }
+
+ pthread_mutex_init(&fragment_mutex, NULL);
+
+ for(i = 0; i < processors; i++) {
+ if(pthread_create(&inflator_thread[i], NULL, inflator, NULL) !=
+ 0)
+ EXIT_UNSQUASH("Failed to create thread\n");
+ }
+
+ if(pthread_sigmask(SIG_SETMASK, &old_mask, NULL) != 0)
+ EXIT_UNSQUASH("Failed to set signal mask in initialise_threads"
+ "\n");
+}
+
+
+void enable_progress_bar()
+{
+ pthread_mutex_lock(&screen_mutex);
+ progress_enabled = progress;
+ pthread_mutex_unlock(&screen_mutex);
+}
+
+
+void disable_progress_bar()
+{
+ pthread_mutex_lock(&screen_mutex);
+ if(progress_enabled) {
+ progress_bar(sym_count + dev_count + fifo_count + socket_count
+ + file_count + hardlnk_count + cur_blocks, total_inodes
+ + total_blocks, columns);
+ printf("\n");
+ }
+ progress_enabled = FALSE;
+ pthread_mutex_unlock(&screen_mutex);
+}
+
+
+void progressbar_error(char *fmt, ...)
+{
+ va_list ap;
+
+ pthread_mutex_lock(&screen_mutex);
+
+ if(progress_enabled)
+ fprintf(stderr, "\n");
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ pthread_mutex_unlock(&screen_mutex);
+}
+
+
+void progressbar_info(char *fmt, ...)
+{
+ va_list ap;
+
+ pthread_mutex_lock(&screen_mutex);
+
+ if(progress_enabled)
+ printf("\n");
+
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+
+ pthread_mutex_unlock(&screen_mutex);
+}
+
+
+void progressbar(long long current, long long max, int columns)
+{
+ char rotate_list[] = { '|', '/', '-', '\\' };
+ int max_digits, used, hashes, spaces;
+ static int tty = -1;
+
+ if(max == 0)
+ return;
+
+ max_digits = floor(log10(max)) + 1;
+ used = max_digits * 2 + 11;
+ hashes = (current * (columns - used)) / max;
+ spaces = columns - used - hashes;
+
+ if((current > max) || (columns - used < 0))
+ return;
+
+ if(tty == -1)
+ tty = isatty(STDOUT_FILENO);
+ if(!tty) {
+ static long long previous = -1;
+
+ /* Updating too frequently results in huge log files */
+ if(current * 100 / max == previous && current != max)
+ return;
+ previous = current * 100 / max;
+ }
+
+ printf("\r[");
+
+ while (hashes --)
+ putchar('=');
+
+ putchar(rotate_list[rotate]);
+
+ while(spaces --)
+ putchar(' ');
+
+ printf("] %*lld/%*lld", max_digits, current, max_digits, max);
+ printf(" %3lld%%", current * 100 / max);
+ fflush(stdout);
+}
+
+
+void display_percentage(long long current, long long max)
+{
+ int percentage = max == 0 ? 100 : current * 100 / max;
+ static int previous = -1;
+
+ if(percentage != previous) {
+ printf("%d\n", percentage);
+ fflush(stdout);
+ previous = percentage;
+ }
+}
+
+
+void progress_bar(long long current, long long max, int columns)
+{
+ if(percent)
+ display_percentage(current, max);
+ else
+ progressbar(current, max, columns);
+}
+
+
+int multiply_overflowll(long long a, int multiplier)
+{
+ return (LLONG_MAX / multiplier) < a;
+}
+
+
+int parse_numberll(char *start, long long *res, int size)
+{
+ char *end;
+ long long number;
+
+ errno = 0; /* To distinguish success/failure after call */
+
+ number = strtoll(start, &end, 10);
+
+ /*
+ * check for strtoll underflow or overflow in conversion, and other
+ * errors.
+ */
+ if((errno == ERANGE && (number == LLONG_MIN || number == LLONG_MAX)) ||
+ (errno != 0 && number == 0))
+ return 0;
+
+ /* reject negative numbers as invalid */
+ if(number < 0)
+ return 0;
+
+ if(size) {
+ /*
+ * Check for multiplier and trailing junk.
+ * But first check that a number exists before the
+ * multiplier
+ */
+ if(end == start)
+ return 0;
+
+ switch(end[0]) {
+ case 'g':
+ case 'G':
+ if(multiply_overflowll(number, 1073741824))
+ return 0;
+ number *= 1073741824;
+
+ if(end[1] != '\0')
+ /* trailing junk after multiplier, but
+ * allow it to be "bytes" */
+ if(strcmp(end + 1, "bytes"))
+ return 0;
+
+ break;
+ case 'm':
+ case 'M':
+ if(multiply_overflowll(number, 1048576))
+ return 0;
+ number *= 1048576;
+
+ if(end[1] != '\0')
+ /* trailing junk after multiplier, but
+ * allow it to be "bytes" */
+ if(strcmp(end + 1, "bytes"))
+ return 0;
+
+ break;
+ case 'k':
+ case 'K':
+ if(multiply_overflowll(number, 1024))
+ return 0;
+ number *= 1024;
+
+ if(end[1] != '\0')
+ /* trailing junk after multiplier, but
+ * allow it to be "bytes" */
+ if(strcmp(end + 1, "bytes"))
+ return 0;
+
+ break;
+ case '\0':
+ break;
+ default:
+ /* trailing junk after number */
+ return 0;
+ }
+ } else if(end[0] != '\0')
+ /* trailing junk after number */
+ return 0;
+
+ *res = number;
+ return 1;
+}
+
+
+int parse_number(char *start, int *res)
+{
+ long long number;
+
+ if(!parse_numberll(start, &number, 0))
+ return 0;
+
+ /* check if long result will overflow signed int */
+ if(number > INT_MAX)
+ return 0;
+
+ *res = (int) number;
+ return 1;
+}
+
+
+int parse_number_unsigned(char *start, unsigned int *res)
+{
+ long long number;
+
+ if(!parse_numberll(start, &number, 0))
+ return 0;
+
+ /* check if long result will overflow unsigned int */
+ if(number > UINT_MAX)
+ return 0;
+
+ *res = (unsigned int) number;
+ return 1;
+}
+
+
+void resolve_symlinks(int argc, char *argv[])
+{
+ int n, found;
+ struct directory_stack *stack;
+ struct symlink *symlink;
+ char *pathname;
+
+ for(n = 0; n < argc; n++) {
+ /*
+ * Try to follow the extract file pathname, and
+ * return the canonicalised pathname, and all
+ * symlinks necessary to resolve it.
+ */
+ stack = create_stack();
+
+ found = follow_path(argv[n], "",
+ SQUASHFS_INODE_BLK(sBlk.s.root_inode),
+ SQUASHFS_INODE_OFFSET(sBlk.s.root_inode),
+ 1, 0, stack);
+
+ if(!found) {
+ if(missing_symlinks)
+ EXIT_UNSQUASH("Extract filename %s can't be "
+ "resolved\n", argv[n]);
+ else
+ ERROR("Extract filename %s can't be resolved\n",
+ argv[n]);
+
+ add_extract(argv[n]);
+ free_stack(stack);
+ continue;
+ }
+
+ pathname = stack_pathname(stack, stack->name);
+ add_extract(pathname);
+ free(pathname);
+
+ for(symlink = stack->symlink; symlink; symlink = symlink->next)
+ add_extract(symlink->pathname);
+
+ free_stack(stack);
+ }
+}
+
+
+char *new_pathname(char *path, char *name)
+{
+ char *newpath;
+
+ if(strcmp(path, "/") == 0) {
+ newpath = malloc(strlen(name) + 2);
+ if(newpath == NULL)
+ MEM_ERROR();
+
+ strcpy(newpath, "/");
+ strcat(newpath, name);
+ } else {
+ newpath = malloc(strlen(path) + strlen(name) + 2);
+ if(newpath == NULL)
+ MEM_ERROR();
+
+ strcpy(newpath, path);
+ strcat(newpath, "/");
+ strcat(newpath, name);
+ }
+
+ return newpath;
+}
+
+
+char *add_pathname(char *path, char *name)
+{
+ if(strcmp(path, "/") == 0) {
+ path = realloc(path, strlen(name) + 2);
+ if(path == NULL)
+ MEM_ERROR();
+
+ strcat(path, name);
+ } else {
+ path = realloc(path, strlen(path) + strlen(name) + 2);
+ if(path == NULL)
+ MEM_ERROR();
+
+ strcat(path, "/");
+ strcat(path, name);
+ }
+
+ return path;
+}
+
+
+int cat_scan(char *path, char *curpath, char *name, unsigned int start_block,
+ unsigned int offset, int depth, struct directory_stack *stack)
+{
+ struct inode *i;
+ struct dir *dir;
+ char *target, *newpath, *addpath, *symlink;
+ unsigned int type;
+ int matched = FALSE, traversed = TRUE;
+ int match, res;
+ unsigned int entry_start, entry_offset;
+ regex_t preg;
+ struct directory_stack *new;
+
+ newpath = new_pathname(curpath, name);
+
+ while((path = get_component(path, &target))) {
+ if(strcmp(target, ".") != 0)
+ break;
+
+ newpath = add_pathname(newpath, ".");
+ free(target);
+ }
+
+ if(path == NULL) {
+ ERROR("cat: %s is a directory\n", newpath);
+ free(newpath);
+ return FALSE;
+ }
+
+ add_stack(stack, start_block, offset, name, depth);
+
+ if(strcmp(target, "..") == 0) {
+ if(depth > 1) {
+ free(target);
+ start_block = stack->stack[depth - 2].start_block;
+ offset = stack->stack[depth - 2].offset;
+
+ new = clone_stack(stack);
+ res = cat_scan(path, newpath, "..", start_block, offset,
+ depth - 1, new);
+
+ free_stack(new);
+ return res;
+ } else {
+ newpath = add_pathname(newpath, "..");
+ ERROR("cat: %s, cannot ascend beyond root directory\n", newpath);
+ free(newpath);
+ free(target);
+ return FALSE;
+ }
+ }
+
+ dir = s_ops->opendir(start_block, offset, &i);
+ if(dir == NULL) {
+ free(newpath);
+ free(target);
+ return FALSE;
+ }
+
+ if(use_regex) {
+ res = regcomp(&preg, target, REG_EXTENDED|REG_NOSUB);
+ if(res) {
+ char str[1024]; /* overflow safe */
+
+ regerror(res, &preg, str, 1024);
+ ERROR("cat: invalid regex %s because %s\n", target, str);
+ free(newpath);
+ free(target);
+ squashfs_closedir(dir);
+ return FALSE;
+ }
+ }
+
+ while(squashfs_readdir(dir, &name, &entry_start, &entry_offset, &type)) {
+ if(no_wildcards)
+ match = strcmp(name, target) == 0;
+ else if(use_regex)
+ match = regexec(&preg, name, (size_t) 0, NULL, 0) == 0;
+ else
+ match = fnmatch(target, name, FNM_PATHNAME|FNM_PERIOD| FNM_EXTMATCH) == 0;
+
+ if(match) {
+ matched = TRUE;
+
+ switch(type) {
+ case SQUASHFS_DIR_TYPE:
+ /* if we're at leaf component then fail */
+ if(path[0] == '\0') {
+ addpath = new_pathname(newpath, name);
+ ERROR("cat: %s is a directory\n", addpath);
+ free(addpath);
+ traversed = FALSE;
+ continue;
+ }
+
+ /* follow the path */
+ res = cat_scan(path, newpath, name, entry_start, entry_offset,
+ depth + 1, stack);
+ if(res == FALSE)
+ traversed = FALSE;
+ pop_stack(stack);
+ break;
+ case SQUASHFS_FILE_TYPE:
+ /* if there's path still to walk, fail */
+ addpath = new_pathname(newpath, name);
+ if(path[0] != '\0') {
+ ERROR("cat: %s is not a directory\n", addpath);
+ free(addpath);
+ traversed = FALSE;
+ continue;
+ }
+
+ i = s_ops->read_inode(entry_start, entry_offset);
+ res = cat_file(i, addpath);
+ if(res == FALSE)
+ traversed = FALSE;
+ free(addpath);
+ break;
+ case SQUASHFS_SYMLINK_TYPE:
+ i = s_ops->read_inode(entry_start, entry_offset);
+ symlink = i->symlink;
+
+ /* Symlink must be relative to current
+ * directory and not be absolute, otherwise
+ * we can't follow it, as it is probably
+ * outside the Squashfs filesystem */
+ if(symlink[0] == '/') {
+ addpath = new_pathname(newpath, name);
+ ERROR("cat: %s failed to resolve symbolic link\n", addpath);
+ free(addpath);
+ traversed = FALSE;
+ free(symlink);
+ continue;
+ }
+
+ new = clone_stack(stack);
+
+ /* follow the symlink */
+ res= follow_path(symlink, name,
+ start_block, offset, depth, 1, new);
+
+ free(symlink);
+
+ if(res == FALSE) {
+ addpath = new_pathname(newpath, name);
+ ERROR("cat: %s failed to resolve symbolic link\n", addpath);
+ free(addpath);
+ free_stack(new);
+ traversed = FALSE;
+ continue;
+ }
+
+ /* If we still have some path to
+ * walk, then walk it from where
+ * the symlink traversal left us
+ *
+ * Obviously symlink traversal must
+ * have left us at a directory to do
+ * this */
+ if(path[0] != '\0') {
+ if(new->type != SQUASHFS_DIR_TYPE) {
+ addpath = new_pathname(newpath, name);
+ ERROR("cat: %s symbolic link does not resolve to a directory\n", addpath);
+ free(addpath);
+ traversed = FALSE;
+ free_stack(new);
+ continue;
+ }
+
+ /* continue following path */
+ res = cat_scan(path, newpath, name,
+ new->start_block, new->offset,
+ new->size + 1, new);
+ if(res == FALSE)
+ traversed = FALSE;
+ free_stack(new);
+ continue;
+ }
+
+ /* At leaf component, symlink must have
+ * resolved to a regular file */
+ if(new->type != SQUASHFS_FILE_TYPE) {
+ addpath = new_pathname(newpath, name);
+ ERROR("cat: %s symbolic link does not resolve to a regular file\n", addpath);
+ free(addpath);
+ free_stack(new);
+ traversed = FALSE;
+ continue;
+ }
+
+ i = s_ops->read_inode(new->start_block, new->offset);
+ addpath = new_pathname(newpath, name);
+ res = cat_file(i, addpath);
+ if(res == FALSE)
+ traversed = FALSE;
+ free_stack(new);
+ free(addpath);
+ break;
+ default:
+ /* not a directory, or a regular file, fail */
+ addpath = new_pathname(newpath, name);
+ if(path[0] == '\0')
+ ERROR("cat: %s is not a regular file\n", addpath);
+ else
+ ERROR("cat: %s is not a directory\n", addpath);
+ free(addpath);
+ traversed = FALSE;
+ continue;
+ }
+ }
+ }
+
+ if(matched == FALSE) {
+ newpath = add_pathname(newpath, target);
+ ERROR("cat: no matches for %s\n", newpath);
+ traversed = FALSE;
+ }
+
+ free(newpath);
+ free(target);
+ squashfs_closedir(dir);
+
+ return traversed;
+}
+
+
+int cat_path(int argc, char *argv[])
+{
+ int n, res, failed = FALSE;
+ struct directory_stack *stack;
+
+ for(n = 0; n < argc; n++) {
+ stack = create_stack();
+
+ res = cat_scan(argv[n], "/", "",
+ SQUASHFS_INODE_BLK(sBlk.s.root_inode),
+ SQUASHFS_INODE_OFFSET(sBlk.s.root_inode),
+ 1, stack);
+
+ if(res == FALSE)
+ failed = TRUE;
+
+ free_stack(stack);
+ }
+
+ queue_put(to_writer, NULL);
+ res = (long) queue_get(from_writer);
+
+ return (failed == TRUE || res == TRUE) && set_exit_code ? 2 : 0;
+}
+
+
+char *process_filename(char *filename)
+{
+ static char *saved = NULL;
+ char *ptr;
+ int count = 0;
+
+ for(ptr = filename; *ptr == '/'; ptr ++);
+
+ if(*ptr == '\0')
+ return "/";
+
+ filename = ptr;
+
+ while(*ptr != '\0') {
+ if(*ptr == '\"' || *ptr == '\\' || isspace(*ptr))
+ count ++;
+ ptr ++;
+ }
+
+ if(count == 0)
+ return filename;
+
+ saved = realloc(saved, strlen(filename) + count + 1);
+ if(saved == NULL)
+ MEM_ERROR();
+
+ for(ptr = saved; *filename != '\0'; ptr ++, filename ++) {
+ if(*filename == '\"' || *filename == '\\' || isspace(*filename))
+ *ptr ++ = '\\';
+
+ *ptr = *filename;
+ }
+
+ *ptr = '\0';
+
+ return saved;
+}
+
+
+void pseudo_print(char *pathname, struct inode *inode, char *link, long long offset)
+{
+ char userstr[12], groupstr[12]; /* overflow safe */
+ char *type_string = "DRSBCIIDRSBCII";
+ char *filename = process_filename(pathname);
+ char type = type_string[inode->type - 1];
+ int res;
+
+ if(link) {
+ char *name = strdup(filename);
+ char *linkname = process_filename(link);
+ res = dprintf(writer_fd, "%s L %s\n", name, linkname);
+ if(res == -1)
+ EXIT_UNSQUASH("Failed to write to pseudo output file\n");
+ free(name);
+ return;
+ }
+
+ res = snprintf(userstr, 12, "%d", inode->uid);
+ if(res < 0)
+ EXIT_UNSQUASH("snprintf failed in pseudo_print()\n");
+ else if(res >= 12)
+ EXIT_UNSQUASH("snprintf returned more than 11 digits in pseudo_print()\n");
+
+ res = snprintf(groupstr, 12, "%d", inode->gid);
+ if(res < 0)
+ EXIT_UNSQUASH("snprintf failed in pseudo_print()\n");
+ else if(res >= 12)
+ EXIT_UNSQUASH("snprintf returned more than 11 digits in pseudo_print()\n");
+
+ res = dprintf(writer_fd, "%s %c %ld %o %s %s", filename, type, inode->time, inode->mode & ~S_IFMT, userstr, groupstr);
+ if(res == -1)
+ EXIT_UNSQUASH("Failed to write to pseudo output file\n");
+
+ switch(inode->mode & S_IFMT) {
+ case S_IFDIR:
+ res = dprintf(writer_fd, "\n");
+ break;
+ case S_IFLNK:
+ res = dprintf(writer_fd, " %s\n", inode->symlink);
+ break;
+ case S_IFSOCK:
+ case S_IFIFO:
+ if(inode->type == SQUASHFS_SOCKET_TYPE || inode->type == SQUASHFS_LSOCKET_TYPE)
+ res = dprintf(writer_fd, " s\n");
+ else
+ res = dprintf(writer_fd, " f\n");
+ break;
+ case S_IFCHR:
+ case S_IFBLK:
+ res = dprintf(writer_fd, " %d %d\n", (int) inode->data >> 8, (int) inode->data & 0xff);
+ break;
+ case S_IFREG:
+ res = dprintf(writer_fd, " %lld %lld %d\n", inode->data,
+ offset, inode->sparse);
+ }
+
+ if(res == -1)
+ EXIT_UNSQUASH("Failed to write to pseudo output file\n");
+
+ print_xattr(filename, inode->xattr, writer_fd);
+}
+
+
+int pseudo_scan1(char *parent_name, unsigned int start_block, unsigned int offset,
+ struct pathnames *extracts, struct pathnames *excludes, int depth)
+{
+ unsigned int type;
+ char *name;
+ struct pathnames *newt, *newc = NULL;
+ struct inode *i;
+ struct dir *dir;
+ static long long byte_offset = 0;
+
+ if(max_depth != -1 && depth > max_depth)
+ return TRUE;
+
+ dir = s_ops->opendir(start_block, offset, &i);
+ if(dir == NULL) {
+ ERROR("pseudo_scan1: failed to read directory %s\n", parent_name);
+ return FALSE;
+ }
+
+ if(inumber_lookup(i->inode_number))
+ EXIT_UNSQUASH("File System corrupted: directory loop detected\n");
+
+ pseudo_print(parent_name, i, NULL, 0);
+
+ while(squashfs_readdir(dir, &name, &start_block, &offset, &type)) {
+ struct inode *i;
+ char *pathname;
+ int res;
+
+ TRACE("pseudo_scan1: name %s, start_block %d, offset %d, type %d\n",
+ name, start_block, offset, type);
+
+ if(!extract_matches(extracts, name, &newt))
+ continue;
+
+ if(exclude_matches(excludes, name, &newc)) {
+ free_subdir(newt);
+ continue;
+ }
+
+ res = asprintf(&pathname, "%s/%s", parent_name, name);
+ if(res == -1)
+ MEM_ERROR();
+
+ if(type == SQUASHFS_DIR_TYPE) {
+ res = pseudo_scan1(pathname, start_block, offset, newt,
+ newc, depth + 1);
+ if(res == FALSE) {
+ free_subdir(newt);
+ free_subdir(newc);
+ free(pathname);
+ return FALSE;
+ }
+ } else if(newt == NULL) {
+ char *link;
+
+ i = s_ops->read_inode(start_block, offset);
+ link = lookup(i->inode_number);
+
+ if(link == NULL) {
+ pseudo_print(pathname, i, NULL, byte_offset);
+ if(type == SQUASHFS_FILE_TYPE) {
+ byte_offset += i->data;
+ total_blocks += (i->data + (block_size - 1)) >> block_log;
+ }
+ insert_lookup(i->inode_number, strdup(pathname));
+ } else
+ pseudo_print(pathname, i, link, 0);
+
+ if(i->type == SQUASHFS_SYMLINK_TYPE || i->type == SQUASHFS_LSYMLINK_TYPE)
+ free(i->symlink);
+
+ }
+
+ free_subdir(newt);
+ free_subdir(newc);
+ free(pathname);
+ }
+
+ squashfs_closedir(dir);
+
+ return TRUE;
+}
+
+
+int pseudo_scan2(char *parent_name, unsigned int start_block, unsigned int offset,
+ struct pathnames *extracts, struct pathnames *excludes, int depth)
+{
+ unsigned int type;
+ char *name;
+ struct pathnames *newt, *newc = NULL;
+ struct inode *i;
+ struct dir *dir = s_ops->opendir(start_block, offset, &i);
+
+ if(dir == NULL) {
+ ERROR("pseudo_scan2: failed to read directory %s\n", parent_name);
+ return FALSE;
+ }
+
+ if(inumber_lookup(i->inode_number))
+ EXIT_UNSQUASH("File System corrupted: directory loop detected\n");
+
+ if(max_depth == -1 || depth <= max_depth) {
+ while(squashfs_readdir(dir, &name, &start_block, &offset, &type)) {
+ char *pathname;
+ int res;
+
+ TRACE("pseudo_scan2: name %s, start_block %d, offset %d,"
+ " type %d\n", name, start_block, offset, type);
+
+
+ if(!extract_matches(extracts, name, &newt))
+ continue;
+
+ if(exclude_matches(excludes, name, &newc)) {
+ free_subdir(newt);
+ continue;
+ }
+
+ res = asprintf(&pathname, "%s/%s", parent_name, name);
+ if(res == -1)
+ MEM_ERROR();
+
+ if(type == SQUASHFS_DIR_TYPE) {
+ res = pseudo_scan2(pathname, start_block, offset,
+ newt, newc, depth + 1);
+ free(pathname);
+ if(res == FALSE) {
+ free_subdir(newt);
+ free_subdir(newc);
+ return FALSE;
+ }
+ } else if(newt == NULL && type == SQUASHFS_FILE_TYPE) {
+ i = s_ops->read_inode(start_block, offset);
+
+ if(lookup(i->inode_number) == NULL) {
+ update_info(pathname);
+
+ res = cat_file(i, pathname);
+ if(res == FALSE) {
+ free_subdir(newt);
+ free_subdir(newc);
+ return FALSE;
+ }
+
+ insert_lookup(i->inode_number, strdup(pathname));
+ } else
+ free(pathname);
+ } else
+ free(pathname);
+
+ free_subdir(newt);
+ free_subdir(newc);
+ }
+ }
+
+ squashfs_closedir(dir);
+
+ return TRUE;
+}
+
+
+int generate_pseudo(char *pseudo_file)
+{
+ int res;
+
+ if(pseudo_stdout)
+ writer_fd = STDOUT_FILENO;
+ else {
+ writer_fd = open_wait(pseudo_file, O_CREAT | O_TRUNC | O_WRONLY,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if(writer_fd == -1)
+ EXIT_UNSQUASH("generate_pseudo: failed to create "
+ "pseudo file %s, because %s\n", pseudo_file,
+ strerror(errno));
+ }
+
+ res = pseudo_scan1("/", SQUASHFS_INODE_BLK(sBlk.s.root_inode),
+ SQUASHFS_INODE_OFFSET(sBlk.s.root_inode), extracts, excludes, 1);
+ if(res == FALSE)
+ goto failed;
+
+ free_inumber_table();
+ inode_number = 1;
+ free_lookup_table(TRUE);
+
+ res = dprintf(writer_fd, "#\n# START OF DATA - DO NOT MODIFY\n#\n");
+ if(res == -1)
+ EXIT_UNSQUASH("Failed to write to pseudo output file\n");
+
+ enable_progress_bar();
+
+ res = pseudo_scan2("/", SQUASHFS_INODE_BLK(sBlk.s.root_inode),
+ SQUASHFS_INODE_OFFSET(sBlk.s.root_inode), extracts, excludes, 1);
+ if(res == FALSE)
+ goto failed;
+
+ queue_put(to_writer, NULL);
+ res = (long) queue_get(from_writer);
+ if(res == TRUE)
+ goto failed;
+
+ disable_progress_bar();
+
+ if(pseudo_stdout == FALSE)
+ close(writer_fd);
+
+ return 0;
+
+failed:
+ disable_progress_bar();
+ queue_put(to_writer, NULL);
+ queue_get(from_writer);
+ unlink(pseudo_file);
+ return 1;
+}
+
+
+int parse_excludes(int argc, char *argv[])
+{
+ int i;
+
+ for(i = 0; i < argc; i ++) {
+ if(strcmp(argv[i], ";") == 0)
+ break;
+ add_exclude(argv[i]);
+ }
+
+ return (i == argc) ? 0 : i;
+}
+
+
+static void print_cat_options(FILE *stream, char *name)
+{
+ fprintf(stream, "SYNTAX: %s [OPTIONS] FILESYSTEM [list of files to cat to stdout]\n", name);
+ fprintf(stream, "\t-v[ersion]\t\tprint version, licence and copyright ");
+ fprintf(stream, "information\n");
+ fprintf(stream, "\t-p[rocessors] <number>\tuse <number> processors. ");
+ fprintf(stream, "By default will use\n");
+ fprintf(stream, "\t\t\t\tthe number of processors available\n");
+ fprintf(stream, "\t-o[ffset] <bytes>\tskip <bytes> at start of FILESYSTEM.\n");
+ fprintf(stream, "\t\t\t\tOptionally a suffix of K, M or G can be given to\n");
+ fprintf(stream, "\t\t\t\tspecify Kbytes, Mbytes or Gbytes respectively\n");
+ fprintf(stream, "\t\t\t\t(default 0 bytes).\n");
+ fprintf(stream, "\t-ig[nore-errors]\ttreat errors writing files to stdout ");
+ fprintf(stream, "as\n\t\t\t\tnon-fatal\n");
+ fprintf(stream, "\t-st[rict-errors]\ttreat all errors as fatal\n");
+ fprintf(stream, "\t-no-exit[-code]\t\tdon't set exit code (to nonzero) on ");
+ fprintf(stream, "non-fatal\n\t\t\t\terrors\n");
+ fprintf(stream, "\t-da[ta-queue] <size>\tset data queue to <size> Mbytes. ");
+ fprintf(stream, "Default %d\n\t\t\t\tMbytes\n", DATA_BUFFER_DEFAULT);
+ fprintf(stream, "\t-fr[ag-queue] <size>\tset fragment queue to <size> Mbytes. ");
+ fprintf(stream, "Default\n\t\t\t\t%d Mbytes\n", FRAGMENT_BUFFER_DEFAULT);
+ fprintf(stream, "\t-no-wild[cards]\t\tdo not use wildcard matching in filenames\n");
+ fprintf(stream, "\t-r[egex]\t\ttreat filenames as POSIX regular ");
+ fprintf(stream, "expressions\n");
+ fprintf(stream, "\t\t\t\trather than use the default shell ");
+ fprintf(stream, "wildcard\n\t\t\t\texpansion (globbing)\n");
+ fprintf(stream, "\t-h[elp]\t\t\toutput options text to stdout\n");
+ fprintf(stream, "\nDecompressors available:\n");
+ display_compressors(stream, "", "");
+
+ fprintf(stream, "\nExit status:\n");
+ fprintf(stream, " 0\tThe file or files were output to stdout OK.\n");
+ fprintf(stream, " 1\tFATAL errors occurred, e.g. filesystem ");
+ fprintf(stream, "corruption, I/O errors.\n");
+ fprintf(stream, "\tSqfscat did not continue and aborted.\n");
+ fprintf(stream, " 2\tNon-fatal errors occurred, e.g. not a regular ");
+ fprintf(stream, "file, or failed to resolve\n\tpathname. Sqfscat ");
+ fprintf(stream, "continued and did not abort.\n");
+ fprintf(stream, "\nSee -ignore-errors, -strict-errors and ");
+ fprintf(stream, "-no-exit-code options for how they affect\nthe exit ");
+ fprintf(stream, "status.\n");
+ fprintf(stream, "\nSee also:\n");
+ fprintf(stream, "The README for the Squashfs-tools 4.6.1 release, ");
+ fprintf(stream, "describing the new features can be\n");
+ fprintf(stream, "read here https://github.com/plougher/squashfs-tools/blob/master/README-4.6.1\n");
+
+ fprintf(stream, "\nThe Squashfs-tools USAGE guide can be read here\n");
+ fprintf(stream, "https://github.com/plougher/squashfs-tools/blob/master/USAGE-4.6\n");
+}
+
+
+static void print_options(FILE *stream, char *name)
+{
+ fprintf(stream, "SYNTAX: %s [OPTIONS] FILESYSTEM [files ", name);
+ fprintf(stream, "to extract or exclude (with -excludes) or cat (with -cat )]\n");
+ fprintf(stream, "\nFilesystem extraction (filtering) options:\n");
+ fprintf(stream, "\t-d[est] <pathname>\textract to <pathname>, ");
+ fprintf(stream, "default \"squashfs-root\".\n\t\t\t\tThis option ");
+ fprintf(stream, "also sets the prefix used when\n\t\t\t\tlisting the ");
+ fprintf(stream, "filesystem\n");
+ fprintf(stream, "\t-max[-depth] <levels>\tdescend at most <levels> of ");
+ fprintf(stream, "directories when\n\t\t\t\textracting\n");
+ fprintf(stream, "\t-excludes\t\ttreat files on command line as exclude files\n");
+ fprintf(stream, "\t-ex[clude-list]\t\tlist of files to be excluded, terminated\n");
+ fprintf(stream, "\t\t\t\twith ; e.g. file1 file2 ;\n");
+ fprintf(stream, "\t-extract-file <file>\tlist of directories or files to ");
+ fprintf(stream, "extract.\n\t\t\t\tOne per line\n");
+ fprintf(stream, "\t-exclude-file <file>\tlist of directories or files to ");
+ fprintf(stream, "exclude.\n\t\t\t\tOne per line\n");
+ fprintf(stream, "\t-match\t\t\tabort if any extract file does not ");
+ fprintf(stream, "match on\n\t\t\t\tanything, and can not be ");
+ fprintf(stream, "resolved. Implies\n\t\t\t\t-missing-symlinks and ");
+ fprintf(stream, "-no-wildcards\n");
+ fprintf(stream, "\t-follow[-symlinks]\tfollow symlinks in extract files, and ");
+ fprintf(stream, "add all\n\t\t\t\tfiles/symlinks needed to resolve extract ");
+ fprintf(stream, "file.\n\t\t\t\tImplies -no-wildcards\n");
+ fprintf(stream, "\t-missing[-symlinks]\tUnsquashfs will abort if any symlink ");
+ fprintf(stream, "can't be\n\t\t\t\tresolved in -follow-symlinks\n");
+ fprintf(stream, "\t-no-wild[cards]\t\tdo not use wildcard matching in extract ");
+ fprintf(stream, "and\n\t\t\t\texclude names\n");
+ fprintf(stream, "\t-r[egex]\t\ttreat extract names as POSIX regular ");
+ fprintf(stream, "expressions\n");
+ fprintf(stream, "\t\t\t\trather than use the default shell ");
+ fprintf(stream, "wildcard\n\t\t\t\texpansion (globbing)\n");
+ fprintf(stream, "\t-all[-time] <time>\tset all file timestamps to ");
+ fprintf(stream, "<time>, rather than\n\t\t\t\tthe time stored in the ");
+ fprintf(stream, "filesystem inode. <time>\n\t\t\t\tcan be an ");
+ fprintf(stream, "unsigned 32-bit int indicating\n\t\t\t\tseconds ");
+ fprintf(stream, "since the epoch (1970-01-01) or a string\n\t\t\t\t");
+ fprintf(stream, "value which is passed to the \"date\" command to\n");
+ fprintf(stream, "\t\t\t\tparse. Any string value which the date ");
+ fprintf(stream, "command\n\t\t\t\trecognises can be used such as ");
+ fprintf(stream, "\"now\", \"last\n\t\t\t\tweek\", or \"Wed Feb 15 ");
+ fprintf(stream, "21:02:39 GMT 2023\"\n");
+ fprintf(stream, "\t-cat\t\t\tcat the files on the command line to stdout\n");
+ fprintf(stream, "\t-f[orce]\t\tif file already exists then overwrite\n");
+ fprintf(stream, "\t-pf <file>\t\toutput a pseudo file equivalent ");
+ fprintf(stream, "of the input\n\t\t\t\tSquashfs filesystem, use - for stdout\n");
+ fprintf(stream, "\nFilesystem information and listing options:\n");
+ fprintf(stream, "\t-s[tat]\t\t\tdisplay filesystem superblock information\n");
+ fprintf(stream, "\t-max[-depth] <levels>\tdescend at most <levels> of ");
+ fprintf(stream, "directories when\n\t\t\t\tlisting\n");
+ fprintf(stream, "\t-i[nfo]\t\t\tprint files as they are extracted\n");
+ fprintf(stream, "\t-li[nfo]\t\tprint files as they are extracted with file\n");
+ fprintf(stream, "\t\t\t\tattributes (like ls -l output)\n");
+ fprintf(stream, "\t-l[s]\t\t\tlist filesystem, but do not extract files\n");
+ fprintf(stream, "\t-ll[s]\t\t\tlist filesystem with file attributes (like\n");
+ fprintf(stream, "\t\t\t\tls -l output), but do not extract files\n");
+ fprintf(stream, "\t-lln[umeric]\t\tsame as -lls but with numeric uids and gids\n");
+ fprintf(stream, "\t-lc\t\t\tlist filesystem concisely, displaying only ");
+ fprintf(stream, "files\n\t\t\t\tand empty directories. Do not extract files\n");
+ fprintf(stream, "\t-llc\t\t\tlist filesystem concisely with file ");
+ fprintf(stream, "attributes,\n\t\t\t\tdisplaying only files and empty ");
+ fprintf(stream, "directories.\n\t\t\t\tDo not extract files\n");
+ fprintf(stream, "\t-full[-precision]\tuse full precision when ");
+ fprintf(stream, "displaying times\n\t\t\t\tincluding seconds. Use ");
+ fprintf(stream, "with -linfo, -lls, -lln\n\t\t\t\tand -llc\n");
+ fprintf(stream, "\t-UTC\t\t\tuse UTC rather than local time zone ");
+ fprintf(stream, "when\n\t\t\t\tdisplaying time\n");
+ fprintf(stream, "\t-mkfs-time\t\tdisplay filesystem superblock time, which is an\n");
+ fprintf(stream, "\t\t\t\tunsigned 32-bit int representing the time in\n");
+ fprintf(stream, "\t\t\t\tseconds since the epoch (1970-01-01)\n");
+ fprintf(stream, "\nFilesystem extended attribute (xattrs) options:\n");
+ fprintf(stream, "\t-no[-xattrs]\t\tdo not extract xattrs in file system");
+ fprintf(stream, NOXOPT_STR"\n");
+ fprintf(stream, "\t-x[attrs]\t\textract xattrs in file system" XOPT_STR "\n");
+ fprintf(stream, "\t-xattrs-exclude <regex>\texclude any xattr names ");
+ fprintf(stream, "matching <regex>.\n\t\t\t\t<regex> is a POSIX ");
+ fprintf(stream, "regular expression, e.g.\n\t\t\t\t-xattrs-exclude ");
+ fprintf(stream, "'^user.' excludes xattrs from\n\t\t\t\tthe user ");
+ fprintf(stream, "namespace\n");
+ fprintf(stream, "\t-xattrs-include <regex>\tinclude any xattr names ");
+ fprintf(stream, "matching <regex>.\n\t\t\t\t<regex> is a POSIX ");
+ fprintf(stream, "regular expression, e.g.\n\t\t\t\t-xattrs-include ");
+ fprintf(stream, "'^user.' includes xattrs from\n\t\t\t\tthe user ");
+ fprintf(stream, "namespace\n");
+ fprintf(stream, "\nUnsquashfs runtime options:\n");
+ fprintf(stream, "\t-v[ersion]\t\tprint version, licence and ");
+ fprintf(stream, "copyright information\n");
+ fprintf(stream, "\t-p[rocessors] <number>\tuse <number> processors. ");
+ fprintf(stream, "By default will use\n");
+ fprintf(stream, "\t\t\t\tthe number of processors available\n");
+ fprintf(stream, "\t-q[uiet]\t\tno verbose output\n");
+ fprintf(stream, "\t-n[o-progress]\t\tdo not display the progress ");
+ fprintf(stream, "bar\n");
+ fprintf(stream, "\t-percentage\t\tdisplay a percentage rather than ");
+ fprintf(stream, "the full\n\t\t\t\tprogress bar. Can be used with ");
+ fprintf(stream, "dialog --gauge\n\t\t\t\tetc.\n");
+ fprintf(stream, "\t-ig[nore-errors]\ttreat errors writing files to ");
+ fprintf(stream, "output as\n\t\t\t\tnon-fatal\n");
+ fprintf(stream, "\t-st[rict-errors]\ttreat all errors as fatal\n");
+ fprintf(stream, "\t-no-exit[-code]\t\tdo not set exit code (to ");
+ fprintf(stream, "nonzero) on non-fatal\n\t\t\t\terrors\n");
+ fprintf(stream, "\t-da[ta-queue] <size>\tset data queue to <size> ");
+ fprintf(stream, "Mbytes. Default ");
+ fprintf(stream, "%d\n\t\t\t\tMbytes\n", DATA_BUFFER_DEFAULT);
+ fprintf(stream, "\t-fr[ag-queue] <size>\tset fragment queue to ");
+ fprintf(stream, "<size> Mbytes. Default\n\t\t\t\t");
+ fprintf(stream, "%d Mbytes\n", FRAGMENT_BUFFER_DEFAULT);
+ fprintf(stream, "\nMiscellaneous options:\n");
+ fprintf(stream, "\t-h[elp]\t\t\toutput this options text to stdout\n");
+ fprintf(stream, "\t-o[ffset] <bytes>\tskip <bytes> at start of FILESYSTEM. ");
+ fprintf(stream, "Optionally\n\t\t\t\ta suffix of K, M or G can be given to ");
+ fprintf(stream, "specify\n\t\t\t\tKbytes, Mbytes or Gbytes respectively ");
+ fprintf(stream, "(default\n\t\t\t\t0 bytes).\n");
+ fprintf(stream, "\t-fstime\t\t\tsynonym for -mkfs-time\n");
+ fprintf(stream, "\t-e[f] <extract file>\tsynonym for -extract-file\n");
+ fprintf(stream, "\t-exc[f] <exclude file>\tsynonym for -exclude-file\n");
+ fprintf(stream, "\t-L\t\t\tsynonym for -follow-symlinks\n");
+ fprintf(stream, "\t-pseudo-file <file>\talternative name for -pf\n");
+ fprintf(stream, "\nDecompressors available:\n");
+ display_compressors(stream, "", "");
+
+ fprintf(stream, "\nExit status:\n");
+ fprintf(stream, " 0\tThe filesystem listed or extracted OK.\n");
+ fprintf(stream, " 1\tFATAL errors occurred, e.g. filesystem corruption, ");
+ fprintf(stream, "I/O errors.\n");
+ fprintf(stream, "\tUnsquashfs did not continue and aborted.\n");
+ fprintf(stream, " 2\tNon-fatal errors occurred, e.g. no support for ");
+ fprintf(stream, "XATTRs, Symbolic links\n\tin output filesystem or ");
+ fprintf(stream, "couldn't write permissions to output filesystem.\n");
+ fprintf(stream, "\tUnsquashfs continued and did not abort.\n");
+ fprintf(stream, "\nSee -ignore-errors, -strict-errors and ");
+ fprintf(stream, "-no-exit-code options for how they affect\nthe exit ");
+ fprintf(stream, "status.\n");
+ fprintf(stream, "\nSee also:\n");
+ fprintf(stream, "The README for the Squashfs-tools 4.6.1 release, ");
+ fprintf(stream, "describing the new features can be\n");
+ fprintf(stream, "read here https://github.com/plougher/squashfs-tools/blob/master/README-4.6.1\n");
+
+ fprintf(stream, "\nThe Squashfs-tools USAGE guide can be read here\n");
+ fprintf(stream, "https://github.com/plougher/squashfs-tools/blob/master/USAGE-4.6\n");
+}
+
+
+void print_version(char *string)
+{
+ printf("%s version " VERSION " (" DATE ")\n", string);
+ printf("copyright (C) " YEAR " Phillip Lougher ");
+ printf("<phillip@squashfs.org.uk>\n\n");
+ printf("This program is free software; you can redistribute it and/or\n");
+ printf("modify it under the terms of the GNU General Public License\n");
+ printf("as published by the Free Software Foundation; either version ");
+ printf("2,\n");
+ printf("or (at your option) any later version.\n\n");
+ printf("This program is distributed in the hope that it will be ");
+ printf("useful,\n");
+ printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
+ printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n");
+ printf("GNU General Public License for more details.\n");
+}
+
+
+int parse_cat_options(int argc, char *argv[])
+{
+ int i;
+
+ cat_files = TRUE;
+
+ for(i = 1; i < argc; i++) {
+ if(*argv[i] != '-')
+ break;
+ if(strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "-h") == 0) {
+ print_cat_options(stdout, argv[0]);
+ exit(0);
+ } else if(strcmp(argv[i], "-no-exit-code") == 0 ||
+ strcmp(argv[i], "-no-exit") == 0)
+ set_exit_code = FALSE;
+ else if(strcmp(argv[i], "-no-wildcards") == 0 ||
+ strcmp(argv[i], "-no-wild") == 0)
+ no_wildcards = TRUE;
+ else if(strcmp(argv[i], "-strict-errors") == 0 ||
+ strcmp(argv[i], "-st") == 0)
+ strict_errors = TRUE;
+ else if(strcmp(argv[i], "-ignore-errors") == 0 ||
+ strcmp(argv[i], "-ig") == 0)
+ ignore_errors = TRUE;
+ else if(strcmp(argv[i], "-version") == 0 ||
+ strcmp(argv[i], "-v") == 0) {
+ print_version("sqfscat");
+ version = TRUE;
+ } else if(strcmp(argv[i], "-processors") == 0 ||
+ strcmp(argv[i], "-p") == 0) {
+ if((++i == argc) ||
+ !parse_number(argv[i],
+ &processors)) {
+ ERROR("%s: -processors missing or invalid "
+ "processor number\n", argv[0]);
+ exit(1);
+ }
+ if(processors < 1) {
+ ERROR("%s: -processors should be 1 or larger\n",
+ argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-data-queue") == 0 ||
+ strcmp(argv[i], "-da") == 0) {
+ if((++i == argc) ||
+ !parse_number(argv[i],
+ &data_buffer_size)) {
+ ERROR("%s: -data-queue missing or invalid "
+ "queue size\n", argv[0]);
+ exit(1);
+ }
+ if(data_buffer_size < 1) {
+ ERROR("%s: -data-queue should be 1 Mbyte or "
+ "larger\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-frag-queue") == 0 ||
+ strcmp(argv[i], "-fr") == 0) {
+ if((++i == argc) ||
+ !parse_number(argv[i],
+ &fragment_buffer_size)) {
+ ERROR("%s: -frag-queue missing or invalid "
+ "queue size\n", argv[0]);
+ exit(1);
+ }
+ if(fragment_buffer_size < 1) {
+ ERROR("%s: -frag-queue should be 1 Mbyte or "
+ "larger\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-regex") == 0 ||
+ strcmp(argv[i], "-r") == 0)
+ use_regex = TRUE;
+ else if(strcmp(argv[i], "-offset") == 0 ||
+ strcmp(argv[i], "-o") == 0) {
+ if((++i == argc) ||
+ !parse_numberll(argv[i], &start_offset,
+ 1)) {
+ ERROR("%s: %s missing or invalid offset size\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ } else {
+ print_cat_options(stderr, argv[0]);
+ exit(1);
+ }
+ }
+
+ if(strict_errors && ignore_errors)
+ EXIT_UNSQUASH("Both -strict-errors and -ignore-errors should "
+ "not be set\n");
+ if(strict_errors && set_exit_code == FALSE)
+ EXIT_UNSQUASH("Both -strict-errors and -no-exit-code should "
+ "not be set. All errors are fatal\n");
+
+ if(no_wildcards && use_regex)
+ EXIT_UNSQUASH("Both -no-wildcards and -regex should not be "
+ "set\n");
+ if(i == argc) {
+ if(!version)
+ print_cat_options(stderr, argv[0]);
+ exit(1);
+ }
+
+ return i;
+}
+
+
+int parse_options(int argc, char *argv[])
+{
+ int i, res;
+
+ for(i = 1; i < argc; i++) {
+ if(*argv[i] != '-')
+ break;
+ if(strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "-h") == 0) {
+ print_options(stdout, argv[0]);
+ exit(0);
+ } else if(strcmp(argv[i], "-pseudo-file") == 0 ||
+ strcmp(argv[i], "-pf") == 0) {
+ if(++i == argc) {
+ fprintf(stderr, "%s: -pf missing filename\n",
+ argv[0]);
+ exit(1);
+ }
+ pseudo_name = argv[i];
+ pseudo_file = TRUE;
+ } else if(strcmp(argv[i], "-cat") == 0)
+ cat_files = TRUE;
+ else if(strcmp(argv[i], "-excludes") == 0)
+ treat_as_excludes = TRUE;
+ else if(strcmp(argv[i], "-exclude-list") == 0 ||
+ strcmp(argv[i], "-ex") == 0) {
+ res = parse_excludes(argc - i - 1, argv + i + 1);
+ if(res == 0) {
+ fprintf(stderr, "%s: -exclude-list missing "
+ "filenames or no ';' terminator\n", argv[0]);
+ exit(1);
+ }
+ i += res + 1;
+ } else if(strcmp(argv[i], "-no-exit-code") == 0 ||
+ strcmp(argv[i], "-no-exit") == 0)
+ set_exit_code = FALSE;
+ else if(strcmp(argv[i], "-follow-symlinks") == 0 ||
+ strcmp(argv[i], "-follow") == 0 ||
+ strcmp(argv[i], "-L") == 0) {
+ follow_symlinks = TRUE;
+ no_wildcards = TRUE;
+ } else if(strcmp(argv[i], "missing-symlinks") == 0 ||
+ strcmp(argv[i], "-missing") == 0 ||
+ strcmp(argv[i], "-match") == 0)
+ missing_symlinks = TRUE;
+ else if(strcmp(argv[i], "-no-wildcards") == 0 ||
+ strcmp(argv[i], "-no-wild") == 0)
+ no_wildcards = TRUE;
+ else if(strcmp(argv[i], "-UTC") == 0)
+ use_localtime = FALSE;
+ else if(strcmp(argv[i], "-strict-errors") == 0 ||
+ strcmp(argv[i], "-st") == 0)
+ strict_errors = TRUE;
+ else if(strcmp(argv[i], "-ignore-errors") == 0 ||
+ strcmp(argv[i], "-ig") == 0)
+ ignore_errors = TRUE;
+ else if(strcmp(argv[i], "-quiet") == 0 ||
+ strcmp(argv[i], "-q") == 0)
+ quiet = TRUE;
+ else if(strcmp(argv[i], "-version") == 0 ||
+ strcmp(argv[i], "-v") == 0) {
+ print_version("unsquashfs");
+ version = TRUE;
+ } else if(strcmp(argv[i], "-info") == 0 ||
+ strcmp(argv[i], "-i") == 0)
+ info = TRUE;
+ else if(strcmp(argv[i], "-ls") == 0 ||
+ strcmp(argv[i], "-l") == 0)
+ lsonly = TRUE;
+ else if(strcmp(argv[i], "-lc") == 0) {
+ lsonly = TRUE;
+ concise = TRUE;
+ } else if(strcmp(argv[i], "-no-progress") == 0 ||
+ strcmp(argv[i], "-n") == 0)
+ progress = FALSE;
+ else if(strcmp(argv[i], "-percentage") == 0)
+ percent = progress = TRUE;
+ else if(strcmp(argv[i], "-no-xattrs") == 0 ||
+ strcmp(argv[i], "-no") == 0)
+ no_xattrs = TRUE;
+ else if(strcmp(argv[i], "-xattrs") == 0 ||
+ strcmp(argv[i], "-x") == 0) {
+ if(xattrs_supported())
+ no_xattrs = FALSE;
+ else {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-user-xattrs") == 0 ||
+ strcmp(argv[i], "-u") == 0) {
+ if(!xattrs_supported()) {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ } else {
+ xattr_include_preg = xattr_regex("^user.", "include");
+ no_xattrs = FALSE;
+ }
+ } else if(strcmp(argv[i], "-xattrs-exclude") == 0) {
+ if(!xattrs_supported()) {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ } else if(++i == argc) {
+ ERROR("%s: -xattrs-exclude missing regex pattern\n", argv[0]);
+ exit(1);
+ } else {
+ xattr_exclude_preg = xattr_regex(argv[i], "exclude");
+ no_xattrs = FALSE;
+ }
+ } else if(strcmp(argv[i], "-xattrs-include") == 0) {
+ if(!xattrs_supported()) {
+ ERROR("%s: xattrs are unsupported in "
+ "this build\n", argv[0]);
+ exit(1);
+ } else if(++i == argc) {
+ ERROR("%s: -xattrs-include missing regex pattern\n", argv[0]);
+ exit(1);
+ } else {
+ xattr_include_preg = xattr_regex(argv[i], "include");
+ no_xattrs = FALSE;
+ }
+ } else if(strcmp(argv[i], "-dest") == 0 ||
+ strcmp(argv[i], "-d") == 0) {
+ if(++i == argc) {
+ fprintf(stderr, "%s: -dest missing filename\n",
+ argv[0]);
+ exit(1);
+ }
+ dest = argv[i];
+ } else if(strcmp(argv[i], "-processors") == 0 ||
+ strcmp(argv[i], "-p") == 0) {
+ if((++i == argc) ||
+ !parse_number(argv[i],
+ &processors)) {
+ ERROR("%s: -processors missing or invalid "
+ "processor number\n", argv[0]);
+ exit(1);
+ }
+ if(processors < 1) {
+ ERROR("%s: -processors should be 1 or larger\n",
+ argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-max-depth") == 0 ||
+ strcmp(argv[i], "-max") == 0) {
+ if((++i == argc) ||
+ !parse_number(argv[i],
+ &max_depth)) {
+ ERROR("%s: -max-depth missing or invalid "
+ "levels\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-data-queue") == 0 ||
+ strcmp(argv[i], "-da") == 0) {
+ if((++i == argc) ||
+ !parse_number(argv[i],
+ &data_buffer_size)) {
+ ERROR("%s: -data-queue missing or invalid "
+ "queue size\n", argv[0]);
+ exit(1);
+ }
+ if(data_buffer_size < 1) {
+ ERROR("%s: -data-queue should be 1 Mbyte or "
+ "larger\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-frag-queue") == 0 ||
+ strcmp(argv[i], "-fr") == 0) {
+ if((++i == argc) ||
+ !parse_number(argv[i],
+ &fragment_buffer_size)) {
+ ERROR("%s: -frag-queue missing or invalid "
+ "queue size\n", argv[0]);
+ exit(1);
+ }
+ if(fragment_buffer_size < 1) {
+ ERROR("%s: -frag-queue should be 1 Mbyte or "
+ "larger\n", argv[0]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-force") == 0 ||
+ strcmp(argv[i], "-f") == 0)
+ force = TRUE;
+ else if(strcmp(argv[i], "-stat") == 0 ||
+ strcmp(argv[i], "-s") == 0)
+ stat_sys = TRUE;
+ else if(strcmp(argv[i], "-mkfs-time") == 0 ||
+ strcmp(argv[i], "-fstime") == 0)
+ mkfs_time_opt = TRUE;
+ else if(strcmp(argv[i], "-lls") == 0 ||
+ strcmp(argv[i], "-ll") == 0) {
+ lsonly = TRUE;
+ short_ls = FALSE;
+ } else if(strcmp(argv[i], "-llnumeric") == 0 ||
+ strcmp(argv[i], "-lln") == 0) {
+ lsonly = TRUE;
+ short_ls = FALSE;
+ numeric = TRUE;
+ } else if(strcmp(argv[i], "-llc") == 0) {
+ lsonly = TRUE;
+ short_ls = FALSE;
+ concise = TRUE;
+ } else if(strcmp(argv[i], "-linfo") == 0 ||
+ strcmp(argv[i], "-li") == 0) {
+ info = TRUE;
+ short_ls = FALSE;
+ } else if(strcmp(argv[i], "-extract-file") == 0 ||
+ strcmp(argv[i], "-ef") == 0 ||
+ strcmp(argv[i], "-e") == 0) {
+ if(++i == argc) {
+ fprintf(stderr, "%s: -extract-file missing filename\n",
+ argv[0]);
+ exit(1);
+ }
+ process_extract_files(argv[i]);
+ } else if(strcmp(argv[i], "-exclude-file") == 0 ||
+ strcmp(argv[i], "-excf") == 0 ||
+ strcmp(argv[i], "-exc") == 0) {
+ if(++i == argc) {
+ fprintf(stderr, "%s: -exclude-file missing filename\n",
+ argv[0]);
+ exit(1);
+ }
+ process_exclude_files(argv[i]);
+ } else if(strcmp(argv[i], "-regex") == 0 ||
+ strcmp(argv[i], "-r") == 0)
+ use_regex = TRUE;
+ else if(strcmp(argv[i], "-offset") == 0 ||
+ strcmp(argv[i], "-o") == 0) {
+ if((++i == argc) ||
+ !parse_numberll(argv[i], &start_offset,
+ 1)) {
+ ERROR("%s: %s missing or invalid offset size\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ } else if(strcmp(argv[i], "-all-time") == 0 ||
+ strcmp(argv[i], "-all") == 0) {
+ if((++i == argc) ||
+ (!parse_number_unsigned(argv[i], &timeval)
+ && !exec_date(argv[i], &timeval))) {
+ ERROR("%s: %s missing or invalid time value\n",
+ argv[0], argv[i - 1]);
+ exit(1);
+ }
+ time_opt = TRUE;
+ } else if(strcmp(argv[i], "-full-precision") == 0 ||
+ strcmp(argv[i], "-full") == 0)
+ full_precision = TRUE;
+ else {
+ print_options(stderr, argv[0]);
+ exit(1);
+ }
+ }
+
+ if(dest[0] == '\0' && !lsonly)
+ EXIT_UNSQUASH("-dest: <pathname> is empty! Use '.' to "
+ "extract to current directory\n");
+
+ if(lsonly || info)
+ progress = FALSE;
+
+ if(lsonly)
+ quiet = TRUE;
+
+ if(lsonly && pseudo_file)
+ EXIT_UNSQUASH("File listing only (-ls, -lls etc.) and -pf "
+ "should not be set\n");
+
+ if(strict_errors && ignore_errors)
+ EXIT_UNSQUASH("Both -strict-errors and -ignore-errors should "
+ "not be set\n");
+ if(strict_errors && set_exit_code == FALSE)
+ EXIT_UNSQUASH("Both -strict-errors and -no-exit-code should "
+ "not be set. All errors are fatal\n");
+
+ if(missing_symlinks && !follow_symlinks) {
+ follow_symlinks = TRUE;
+ no_wildcards = TRUE;
+ }
+
+ if(no_wildcards && use_regex)
+ EXIT_UNSQUASH("Both -no-wildcards and -regex should not be "
+ "set\n");
+
+ if(pseudo_file && strcmp(pseudo_name, "-") == 0) {
+ info = progress = FALSE;
+ pseudo_stdout = quiet = TRUE;
+ }
+
+#ifdef SQUASHFS_TRACE
+ /*
+ * Disable progress bar if full debug tracing is enabled.
+ * The progress bar in this case just gets in the way of the
+ * debug trace output
+ */
+ progress = FALSE;
+#endif
+
+ if(i == argc) {
+ if(!version)
+ print_options(stderr, argv[0]);
+ exit(1);
+ }
+
+ return i;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int i, n;
+ long res;
+ int exit_code = 0;
+ char *command;
+
+ pthread_mutex_init(&screen_mutex, NULL);
+ root_process = geteuid() == 0;
+ if(root_process)
+ umask(0);
+
+ /* skip leading path components in invocation command */
+ for(command = argv[0] + strlen(argv[0]) - 1; command >= argv[0] && command[0] != '/'; command--);
+
+ if(command < argv[0])
+ command = argv[0];
+ else
+ command++;
+
+ if(strcmp(command, "sqfscat") == 0)
+ i = parse_cat_options(argc, argv);
+ else
+ i = parse_options(argc, argv);
+
+ if((fd = open(argv[i], O_RDONLY)) == -1) {
+ ERROR("Could not open %s, because %s\n", argv[i],
+ strerror(errno));
+ exit(1);
+ }
+
+ if(read_super(argv[i]) == FALSE)
+ EXIT_UNSQUASH("Can't find a valid SQUASHFS superblock on %s\n", argv[i]);
+
+ if(mkfs_time_opt) {
+ printf("%u\n", sBlk.s.mkfs_time);
+ exit(0);
+ }
+
+ if(stat_sys) {
+ s_ops->stat(argv[i]);
+ exit(0);
+ }
+
+ if(!check_compression(comp))
+ exit(1);
+
+ block_size = sBlk.s.block_size;
+ block_log = sBlk.s.block_log;
+
+ /*
+ * Sanity check block size and block log.
+ *
+ * Check they're within correct limits
+ */
+ if(block_size > SQUASHFS_FILE_MAX_SIZE ||
+ block_log > SQUASHFS_FILE_MAX_LOG)
+ EXIT_UNSQUASH("Block size or block_log too large."
+ " File system is corrupt.\n");
+
+ if(block_size < 4096)
+ EXIT_UNSQUASH("Block size too small."
+ " File system is corrupt.\n");
+
+ /*
+ * Check block_size and block_log match
+ */
+ if(block_size != (1 << block_log))
+ EXIT_UNSQUASH("Block size and block_log do not match."
+ " File system is corrupt.\n");
+
+ /*
+ * convert from queue size in Mbytes to queue size in
+ * blocks.
+ *
+ * In doing so, check that the user supplied values do not
+ * overflow a signed int
+ */
+ if(shift_overflow(fragment_buffer_size, 20 - block_log))
+ EXIT_UNSQUASH("Fragment queue size is too large\n");
+ else
+ fragment_buffer_size <<= 20 - block_log;
+
+ if(shift_overflow(data_buffer_size, 20 - block_log))
+ EXIT_UNSQUASH("Data queue size is too large\n");
+ else
+ data_buffer_size <<= 20 - block_log;
+
+ if(!lsonly)
+ initialise_threads(fragment_buffer_size, data_buffer_size, cat_files);
+
+ res = s_ops->read_filesystem_tables();
+ if(res == FALSE)
+ EXIT_UNSQUASH("File system corruption detected\n");
+
+ if(cat_files)
+ return cat_path(argc - i - 1, argv + i + 1);
+ else if(treat_as_excludes)
+ for(n = i + 1; n < argc; n++)
+ add_exclude(argv[n]);
+ else if(follow_symlinks)
+ resolve_symlinks(argc - i - 1, argv + i + 1);
+ else
+ for(n = i + 1; n < argc; n++)
+ add_extract(argv[n]);
+
+ if(extract) {
+ extracts = init_subdir();
+ extracts = add_subdir(extracts, extract);
+ }
+
+ if(exclude) {
+ excludes = init_subdir();
+ excludes = add_subdir(excludes, exclude);
+ }
+
+ if(pseudo_file)
+ return generate_pseudo(pseudo_name);
+
+ if(!quiet || progress) {
+ res = pre_scan(dest, SQUASHFS_INODE_BLK(sBlk.s.root_inode),
+ SQUASHFS_INODE_OFFSET(sBlk.s.root_inode), extracts,
+ excludes, 1);
+ if(res == FALSE && set_exit_code)
+ exit_code = 2;
+
+ free_inumber_table();
+ inode_number = 1;
+ free_lookup_table(FALSE);
+
+ if(!quiet) {
+ printf("Parallel unsquashfs: Using %d processor%s\n",
+ processors, processors == 1 ? "" : "s");
+
+ printf("%u inodes (%lld blocks) to write\n\n",
+ total_inodes, total_blocks);
+ }
+
+ enable_progress_bar();
+ }
+
+ res = dir_scan(dest, SQUASHFS_INODE_BLK(sBlk.s.root_inode),
+ SQUASHFS_INODE_OFFSET(sBlk.s.root_inode), extracts, excludes, 1);
+ if(res == FALSE && set_exit_code)
+ exit_code = 2;
+
+ if(!lsonly) {
+ queue_put(to_writer, NULL);
+ res = (long) queue_get(from_writer);
+ if(res == TRUE && set_exit_code)
+ exit_code = 2;
+ }
+
+ disable_progress_bar();
+
+ if(!quiet) {
+ printf("\n");
+ printf("created %d %s\n", file_count, file_count == 1 ? "file" : "files");
+ printf("created %d %s\n", dir_count, dir_count == 1 ? "directory" : "directories");
+ printf("created %d %s\n", sym_count, sym_count == 1 ? "symlink" : "symlinks");
+ printf("created %d %s\n", dev_count, dev_count == 1 ? "device" : "devices");
+ printf("created %d %s\n", fifo_count, fifo_count == 1 ? "fifo" : "fifos");
+ printf("created %d %s\n", socket_count, socket_count == 1 ? "socket" : "sockets");
+ printf("created %d %s\n", hardlnk_count, hardlnk_count == 1 ? "hardlink" : "hardlinks");
+ }
+
+ return exit_code;
+}
diff --git a/squashfs-tools/unsquashfs.h b/squashfs-tools/unsquashfs.h
new file mode 100644
index 0000000..8871d6f
--- /dev/null
+++ b/squashfs-tools/unsquashfs.h
@@ -0,0 +1,345 @@
+#ifndef UNSQUASHFS_H
+#define UNSQUASHFS_H
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2009, 2010, 2013, 2014, 2019, 2021, 2022, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquashfs.h
+ */
+
+#define TRUE 1
+#define FALSE 0
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <utime.h>
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#include <regex.h>
+#include <signal.h>
+#include <pthread.h>
+#include <math.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#include "endian_compat.h"
+#include "squashfs_fs.h"
+#include "unsquashfs_error.h"
+
+#define TABLE_HASH(start) (start & 0xffff)
+
+/*
+ * Unified superblock containing fields for all superblocks
+ */
+struct super_block {
+ struct squashfs_super_block s;
+ /* fields only used by squashfs 3 and earlier layouts */
+ unsigned int no_uids;
+ unsigned int no_guids;
+ long long uid_start;
+ long long guid_start;
+ /* fields only used by squashfs 4 */
+ unsigned int xattr_ids;
+};
+
+
+struct hash_table_entry {
+ long long start;
+ int length;
+ void *buffer;
+ long long next_index;
+ struct hash_table_entry *next;
+};
+
+struct inode {
+ int blocks;
+ long long block_start;
+ unsigned int block_offset;
+ long long data;
+ unsigned int fragment;
+ int frag_bytes;
+ gid_t gid;
+ unsigned int inode_number;
+ int mode;
+ int offset;
+ long long start;
+ char *symlink;
+ time_t time;
+ int type;
+ uid_t uid;
+ char sparse;
+ unsigned int xattr;
+};
+
+typedef struct squashfs_operations {
+ struct dir *(*opendir)(unsigned int block_start,
+ unsigned int offset, struct inode **i);
+ void (*read_fragment)(unsigned int fragment, long long *start_block,
+ int *size);
+ void (*read_block_list)(unsigned int *block_list, long long start,
+ unsigned int offset, int blocks);
+ struct inode *(*read_inode)(unsigned int start_block,
+ unsigned int offset);
+ int (*read_filesystem_tables)();
+ void (*stat)(char *);
+} squashfs_operations;
+
+struct test {
+ int mask;
+ int value;
+ int position;
+ char mode;
+};
+
+
+/* Cache status struct. Caches are used to keep
+ track of memory buffers passed between different threads */
+struct cache {
+ int max_buffers;
+ int count;
+ int used;
+ int buffer_size;
+ int wait_free;
+ int wait_pending;
+ pthread_mutex_t mutex;
+ pthread_cond_t wait_for_free;
+ pthread_cond_t wait_for_pending;
+ struct cache_entry *free_list;
+ struct cache_entry *hash_table[65536];
+};
+
+/* struct describing a cache entry passed between threads */
+struct cache_entry {
+ struct cache *cache;
+ long long block;
+ int size;
+ int used;
+ int error;
+ int pending;
+ struct cache_entry *hash_next;
+ struct cache_entry *hash_prev;
+ struct cache_entry *free_next;
+ struct cache_entry *free_prev;
+ char *data;
+};
+
+/* struct describing queues used to pass data between threads */
+struct queue {
+ int size;
+ int readp;
+ int writep;
+ pthread_mutex_t mutex;
+ pthread_cond_t empty;
+ pthread_cond_t full;
+ void **data;
+};
+
+/* default size of fragment buffer in Mbytes */
+#define FRAGMENT_BUFFER_DEFAULT 256
+/* default size of data buffer in Mbytes */
+#define DATA_BUFFER_DEFAULT 256
+
+#define DIR_ENT_SIZE 16
+
+struct dir_ent {
+ char *name;
+ unsigned int start_block;
+ unsigned int offset;
+ unsigned int type;
+ struct dir_ent *next;
+};
+
+struct dir {
+ int dir_count;
+ unsigned int mode;
+ uid_t uid;
+ gid_t guid;
+ unsigned int mtime;
+ unsigned int xattr;
+ struct dir_ent *dirs;
+ struct dir_ent *cur_entry;
+};
+
+struct file_entry {
+ int offset;
+ int size;
+ struct cache_entry *buffer;
+};
+
+
+struct squashfs_file {
+ int fd;
+ int blocks;
+ long long file_size;
+ int mode;
+ uid_t uid;
+ gid_t gid;
+ time_t time;
+ char *pathname;
+ char sparse;
+ unsigned int xattr;
+};
+
+struct path_entry {
+ char *name;
+ int type;
+ regex_t *preg;
+ struct pathname *paths;
+};
+
+struct pathname {
+ int names;
+ struct path_entry *name;
+};
+
+struct pathnames {
+ int count;
+ struct pathname *path[0];
+};
+
+#define PATHS_ALLOC_SIZE 10
+#define PATH_TYPE_LINK 1
+#define PATH_TYPE_EXTRACT 2
+#define PATH_TYPE_EXCLUDE 4
+
+struct directory_level {
+ unsigned int start_block;
+ unsigned int offset;
+ char *name;
+};
+
+struct symlink {
+ char *pathname;
+ struct symlink *next;
+};
+
+struct directory_stack {
+ int size;
+ unsigned int type;
+ unsigned int start_block;
+ unsigned int offset;
+ char *name;
+ struct directory_level *stack;
+ struct symlink *symlink;
+};
+
+#define MAX_FOLLOW_SYMLINKS 256
+
+/* These macros implement a bit-table to track whether directories have been
+ * already visited. This is to trap corrupted filesystems which have multiple
+ * links to the same directory, which is invalid, and which may also create
+ * a directory loop, where Unsquashfs will endlessly recurse until either
+ * the pathname is too large (extracting), or the stack overflows.
+ *
+ * Each index entry is 8 Kbytes, and tracks 65536 inode numbers. The index is
+ * allocated on demand because Unsquashfs may not walk the complete filesystem.
+ */
+#define INUMBER_INDEXES(INODES) ((((INODES) - 1) >> 16) + 1)
+#define INUMBER_INDEX(NUMBER) ((NUMBER) >> 16)
+#define INUMBER_OFFSET(NUMBER) (((NUMBER) & 0xffff) >> 5)
+#define INUMBER_BIT(NUMBER) (1 << ((NUMBER) & 0x1f))
+#define INUMBER_BYTES 8192
+
+/* These macros implement a lookup table to track creation of (non-directory)
+ * inodes, and to discover if a hard-link to a previously created file should
+ * be made.
+ *
+ * Each index entry is 32 Kbytes, and tracks 4096 inode numbers. The index is
+ * allocated on demand because Unsquashfs may not walk the complete filesystem.
+ */
+#define LOOKUP_INDEXES(INODES) ((((INODES) - 1) >> 12) + 1)
+#define LOOKUP_INDEX(NUMBER) ((NUMBER) >> 12)
+#define LOOKUP_OFFSET(NUMBER) ((NUMBER) & 0xfff)
+#define LOOKUP_BYTES 32768
+#define LOOKUP_OFFSETS 4096
+
+/* Maximum transfer size for Linux read() call on both 32-bit and 64-bit systems.
+ * See READ(2) */
+#define MAXIMUM_READ_SIZE 0x7ffff000
+
+/* globals */
+extern struct super_block sBlk;
+extern int swap;
+extern struct hash_table_entry *directory_table_hash[65536];
+extern pthread_mutex_t screen_mutex;
+extern int progress_enabled;
+extern int inode_number;
+extern int lookup_type[];
+extern int fd;
+extern int no_xattrs;
+extern struct queue *to_reader, *to_inflate, *to_writer;
+extern struct cache *fragment_cache, *data_cache;
+extern struct compressor *comp;
+extern int use_localtime;
+extern unsigned int timeval;
+extern int time_opt;
+
+/* unsquashfs.c */
+extern int read_inode_data(void *, long long *, unsigned int *, int);
+extern int read_directory_data(void *, long long *, unsigned int *, int);
+extern int read_fs_bytes(int fd, long long, long long, void *);
+extern int read_block(int, long long, long long *, int, void *);
+extern void enable_progress_bar();
+extern void disable_progress_bar();
+extern void dump_queue(struct queue *);
+extern void dump_cache(struct cache *);
+extern int write_bytes(int, char *, int);
+
+/* unsquash-1.c */
+int read_super_1(squashfs_operations **, void *);
+
+/* unsquash-2.c */
+int read_super_2(squashfs_operations **, void *);
+
+/* unsquash-3.c */
+int read_super_3(char *, squashfs_operations **, void *);
+
+/* unsquash-4.c */
+int read_super_4(squashfs_operations **);
+
+/* unsquash-123.c */
+extern int read_ids(int, long long, long long, unsigned int **);
+
+/* unsquash-34.c */
+extern long long *alloc_index_table(int);
+extern int inumber_lookup(unsigned int);
+extern void free_inumber_table();
+extern char *lookup(unsigned int);
+extern void insert_lookup(unsigned int, char *);
+extern void free_lookup_table(int);
+
+/* unsquash-1234.c */
+extern int check_name(char *, int);
+extern void squashfs_closedir(struct dir *);
+extern int check_directory(struct dir *);
+
+/* unsquash-12.c */
+extern void sort_directory(struct dir_ent **, int);
+
+/* date.c */
+extern int exec_date(char *, unsigned int *);
+#endif
diff --git a/squashfs-tools/unsquashfs_error.h b/squashfs-tools/unsquashfs_error.h
new file mode 100644
index 0000000..6b90537
--- /dev/null
+++ b/squashfs-tools/unsquashfs_error.h
@@ -0,0 +1,64 @@
+#ifndef UNSQUASHFS_ERROR_H
+#define UNSQUASHFS_ERROR_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2021
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquashfs_error.h
+ */
+
+#include "error.h"
+
+#define INFO(s, args...) \
+ do {\
+ progressbar_info(s, ## args);\
+ } while(0)
+
+#define BAD_ERROR(s, args...) \
+ do {\
+ progressbar_error("FATAL ERROR: " s, ##args); \
+ exit(1); \
+ } while(0)
+
+#define EXIT_UNSQUASH(s, args...) BAD_ERROR(s, ##args)
+
+#define EXIT_UNSQUASH_IGNORE(s, args...) \
+ do {\
+ if(ignore_errors) \
+ ERROR(s, ##args); \
+ else \
+ BAD_ERROR(s, ##args); \
+ } while(0)
+
+#define EXIT_UNSQUASH_STRICT(s, args...) \
+ do {\
+ if(!strict_errors) \
+ ERROR(s, ##args); \
+ else \
+ BAD_ERROR(s, ##args); \
+ } while(0)
+
+#define MEM_ERROR() \
+ do {\
+ progressbar_error("FATAL ERROR: Out of memory (%s)\n", \
+ __func__); \
+ exit(1); \
+ } while(0)
+#endif
diff --git a/squashfs-tools/unsquashfs_info.c b/squashfs-tools/unsquashfs_info.c
new file mode 100644
index 0000000..2be9f66
--- /dev/null
+++ b/squashfs-tools/unsquashfs_info.c
@@ -0,0 +1,125 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2021, 2023
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquashfs_info.c
+ */
+
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#include "squashfs_fs.h"
+#include "unsquashfs.h"
+#include "unsquashfs_error.h"
+#include "signals.h"
+
+char *pathname = NULL;
+
+pthread_t info_thread;
+
+
+void disable_info()
+{
+ if(pathname)
+ free(pathname);
+
+ pathname = NULL;
+}
+
+
+void update_info(char *name)
+{
+ if(pathname)
+ free(pathname);
+
+ pathname = name;
+}
+
+
+void dump_state()
+{
+ disable_progress_bar();
+
+ printf("Queue and cache status dump\n");
+ printf("===========================\n");
+
+ printf("file buffer read queue (main thread -> reader thread)\n");
+ dump_queue(to_reader);
+
+ printf("file buffer decompress queue (reader thread -> inflate"
+ " thread(s))\n");
+ dump_queue(to_inflate);
+
+ printf("file buffer write queue (main thread -> writer thread)\n");
+ dump_queue(to_writer);
+
+ printf("\nbuffer cache (uncompressed blocks and compressed blocks "
+ "'in flight')\n");
+ dump_cache(data_cache);
+
+ printf("fragment buffer cache (uncompressed frags and compressed"
+ " frags 'in flight')\n");
+ dump_cache(fragment_cache);
+
+ enable_progress_bar();
+}
+
+
+void *info_thrd(void *arg)
+{
+ sigset_t sigmask;
+ int sig, waiting = 0;
+
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGQUIT);
+ sigaddset(&sigmask, SIGHUP);
+
+ while(1) {
+ sig = wait_for_signal(&sigmask, &waiting);
+
+ if(sig == SIGQUIT && !waiting) {
+ if(pathname)
+ INFO("%s\n", pathname);
+
+ /* set one second interval period, if ^\ received
+ within then, dump queue and cache status */
+ waiting = 1;
+ } else
+ dump_state();
+ }
+}
+
+
+void init_info()
+{
+ pthread_create(&info_thread, NULL, info_thrd, NULL);
+}
diff --git a/squashfs-tools/unsquashfs_info.h b/squashfs-tools/unsquashfs_info.h
new file mode 100644
index 0000000..f85efd1
--- /dev/null
+++ b/squashfs-tools/unsquashfs_info.h
@@ -0,0 +1,30 @@
+#ifndef UNSQUASHFS_INFO_H
+#define UNSQUASHFS_INFO_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2013, 2014
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquashfs_info.h
+ */
+
+extern void disable_info();
+extern void update_info(char *);
+extern void init_info();
+#endif
diff --git a/squashfs-tools/unsquashfs_xattr.c b/squashfs-tools/unsquashfs_xattr.c
new file mode 100644
index 0000000..377f9e2
--- /dev/null
+++ b/squashfs-tools/unsquashfs_xattr.c
@@ -0,0 +1,302 @@
+/*
+ * Unsquash a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2010, 2012, 2019, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * unsquashfs_xattr.c
+ */
+
+#include "unsquashfs.h"
+#include "xattr.h"
+
+#include <sys/xattr.h>
+
+#ifdef XATTR_NOFOLLOW /* Apple's xattrs */
+ #define lsetxattr(path_, name_, val_, sz_, flags_) \
+ setxattr(path_, name_, val_, sz_, 0, flags_ | XATTR_NOFOLLOW)
+#endif
+
+#define NOSPACE_MAX 10
+
+extern int root_process;
+extern int ignore_errors;
+extern int strict_errors;
+extern regex_t *xattr_exclude_preg;
+extern regex_t *xattr_include_preg;
+
+int has_xattrs(unsigned int xattr)
+{
+ if(xattr == SQUASHFS_INVALID_XATTR ||
+ sBlk.s.xattr_id_table_start == SQUASHFS_INVALID_BLK)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+
+static void print_xattr_name_value(struct xattr_list *xattr, int writer_fd)
+{
+ unsigned char *value = xattr->value;
+ int i, count = 0, printable = TRUE, res;
+
+ for(i = 0; i < xattr->vsize; i++) {
+ if(value[i] < 32 || value[i] > 126) {
+ printable = FALSE;
+ count += 4;
+ } else if(value[i] == '\\')
+ count += 4;
+ else
+ count ++;
+ }
+
+ if(!printable) {
+ unsigned char *new = malloc(count + 2), *dest;
+ if(new == NULL)
+ MEM_ERROR();
+
+ memcpy(new, "0t", 2);
+ count += 2;
+
+ for(dest = new + 2, i = 0; i < xattr->vsize; i++) {
+ if(value[i] < 32 || value[i] > 126 || value[i] == '\\') {
+ sprintf((char *) dest, "\\%03o", value[i]);
+ dest += 4;
+ } else
+ *dest ++ = value[i];
+ }
+
+ value = new;
+ } else
+ count = xattr->vsize;
+
+ res = dprintf(writer_fd, "%s=", xattr->full_name);
+ if(res == -1)
+ EXIT_UNSQUASH("Failed to write to pseudo output file\n");
+
+ res = write_bytes(writer_fd, (char *) value, count);
+ if(res == -1)
+ EXIT_UNSQUASH("Failed to write to pseudo output file\n");
+
+ res = dprintf(writer_fd, "\n");
+ if(res == -1)
+ EXIT_UNSQUASH("Failed to write to pseudo output file\n");
+
+ if(!printable)
+ free(value);
+}
+
+
+void print_xattr(char *pathname, unsigned int xattr, int writer_fd)
+{
+ unsigned int count;
+ struct xattr_list *xattr_list;
+ int i, failed, res;
+
+ if(!has_xattrs(xattr))
+ return;
+
+ if(xattr >= sBlk.xattr_ids)
+ EXIT_UNSQUASH("File system corrupted - xattr index in inode too large (xattr: %u)\n", xattr);
+
+ xattr_list = get_xattr(xattr, &count, &failed);
+ if(xattr_list == NULL && failed == FALSE)
+ exit(1);
+
+ if(failed)
+ EXIT_UNSQUASH_STRICT("write_xattr: Failed to read one or more xattrs for %s\n", pathname);
+
+ for(i = 0; i < count; i++) {
+ if(xattr_exclude_preg) {
+ int res = regexec(xattr_exclude_preg,
+ xattr_list[i].full_name, (size_t) 0, NULL, 0);
+
+ if(res == 0)
+ continue;
+ }
+
+ if(xattr_include_preg) {
+ int res = regexec(xattr_include_preg,
+ xattr_list[i].full_name, (size_t) 0, NULL, 0);
+
+ if(res)
+ continue;
+ }
+
+ res = dprintf(writer_fd, "%s x ", pathname);
+ if(res == -1)
+ EXIT_UNSQUASH("Failed to write to pseudo output file\n");
+
+ print_xattr_name_value(&xattr_list[i], writer_fd);
+ }
+
+ free_xattr(xattr_list, count);
+}
+
+
+int write_xattr(char *pathname, unsigned int xattr)
+{
+ unsigned int count;
+ struct xattr_list *xattr_list;
+ int i;
+ static int nonsuper_error = FALSE;
+ static int ignore_xattrs = FALSE;
+ static int nospace_error = 0;
+ int failed;
+
+ if(ignore_xattrs || !has_xattrs(xattr))
+ return TRUE;
+
+ if(xattr >= sBlk.xattr_ids)
+ EXIT_UNSQUASH("File system corrupted - xattr index in inode too large (xattr: %u)\n", xattr);
+
+ xattr_list = get_xattr(xattr, &count, &failed);
+ if(xattr_list == NULL && failed == FALSE)
+ exit(1);
+
+ if(failed)
+ EXIT_UNSQUASH_STRICT("write_xattr: Failed to read one or more xattrs for %s\n", pathname);
+
+ for(i = 0; i < count; i++) {
+ int prefix = xattr_list[i].type & SQUASHFS_XATTR_PREFIX_MASK;
+
+ if(ignore_xattrs)
+ continue;
+
+ if(xattr_exclude_preg) {
+ int res = regexec(xattr_exclude_preg,
+ xattr_list[i].full_name, (size_t) 0, NULL, 0);
+
+ if(res == 0)
+ continue;
+ }
+
+ if(xattr_include_preg) {
+ int res = regexec(xattr_include_preg,
+ xattr_list[i].full_name, (size_t) 0, NULL, 0);
+
+ if(res)
+ continue;
+ }
+
+ if(root_process || prefix == SQUASHFS_XATTR_USER) {
+ int res = lsetxattr(pathname, xattr_list[i].full_name,
+ xattr_list[i].value, xattr_list[i].vsize, 0);
+
+ if(res == -1) {
+ if(errno == ENOTSUP) {
+ /*
+ * If the destination filesystem cannot
+ * suppport xattrs, print error, and
+ * disable xattr output as this error is
+ * unlikely to go away, and printing
+ * screenfulls of the same error message
+ * is rather annoying
+ */
+ ERROR("write_xattr: failed to write "
+ "xattr %s for file %s because "
+ "extended attributes are not "
+ "supported by the destination "
+ "filesystem\n",
+ xattr_list[i].full_name,
+ pathname);
+ ERROR("Ignoring xattrs in "
+ "filesystem\n");
+ EXIT_UNSQUASH_STRICT("To avoid this error message, "
+ "specify -no-xattrs\n");
+ ignore_xattrs = TRUE;
+ } else if((errno == ENOSPC || errno == EDQUOT)
+ && nospace_error < NOSPACE_MAX) {
+ /*
+ * Many filesystems like ext2/3/4 have
+ * limits on the amount of xattr
+ * data that can be stored per file
+ * (typically one block or 4K), so
+ * we shouldn't disable xattr ouput,
+ * as the error may be restriced to one
+ * file only. If we get a lot of these
+ * then suppress the error messsage
+ */
+ EXIT_UNSQUASH_IGNORE("write_xattr: failed to write "
+ "xattr %s for file %s because "
+ "no extended attribute space "
+ "remaining (per file or "
+ "filesystem limit)\n",
+ xattr_list[i].full_name,
+ pathname);
+ if(++ nospace_error == NOSPACE_MAX)
+ ERROR("%d of these errors "
+ "printed, further error "
+ "messages of this type "
+ "are suppressed!\n",
+ NOSPACE_MAX);
+ } else
+ EXIT_UNSQUASH_IGNORE("write_xattr: failed to write "
+ "xattr %s for file %s because "
+ "%s\n", xattr_list[i].full_name,
+ pathname, strerror(errno));
+ failed = TRUE;
+ }
+ } else if(nonsuper_error == FALSE) {
+ /*
+ * if extract user xattrs only then
+ * error message is suppressed, if not
+ * print error, and then suppress further error
+ * messages to avoid possible screenfulls of the
+ * same error message!
+ */
+ ERROR("write_xattr: could not write xattr %s "
+ "for file %s because you're not "
+ "superuser!\n",
+ xattr_list[i].full_name, pathname);
+ EXIT_UNSQUASH_STRICT("write_xattr: to avoid this error message, either"
+ " specify -xattrs-include '^user.', -no-xattrs, or run as "
+ "superuser!\n");
+ ERROR("Further error messages of this type are "
+ "suppressed!\n");
+ nonsuper_error = TRUE;
+ failed = TRUE;
+ }
+ }
+
+ free_xattr(xattr_list, count);
+
+ return !failed;
+}
+
+
+regex_t *xattr_regex(char *pattern, char *option)
+{
+ int error;
+ regex_t *preg = malloc(sizeof(regex_t));
+
+ if(preg == NULL)
+ MEM_ERROR();
+
+ error = regcomp(preg, pattern, REG_EXTENDED|REG_NOSUB);
+
+ if(error) {
+ char str[1024]; /* overflow safe */
+
+ regerror(error, preg, str, 1024);
+ BAD_ERROR("invalid regex %s in xattrs-%s option, because %s\n",
+ pattern, option, str);
+ }
+
+ return preg;
+}
diff --git a/squashfs-tools/version.mk b/squashfs-tools/version.mk
new file mode 100644
index 0000000..2f17c6b
--- /dev/null
+++ b/squashfs-tools/version.mk
@@ -0,0 +1,2 @@
+HASH := d8cb82d9
+FULLDATE := 2023-03-25 20:53:37 +0000
diff --git a/squashfs-tools/xattr.c b/squashfs-tools/xattr.c
new file mode 100644
index 0000000..f9f4cc3
--- /dev/null
+++ b/squashfs-tools/xattr.c
@@ -0,0 +1,1322 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2008, 2009, 2010, 2012, 2014, 2019, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * xattr.c
+ */
+
+#include "endian_compat.h"
+
+#define TRUE 1
+#define FALSE 0
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/xattr.h>
+#include <regex.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_swap.h"
+#include "mksquashfs.h"
+#include "xattr.h"
+#include "mksquashfs_error.h"
+#include "progressbar.h"
+#include "pseudo.h"
+#include "tar.h"
+#include "action.h"
+#include "merge_sort.h"
+
+#ifdef XATTR_NOFOLLOW /* Apple's xattrs */
+ #define llistxattr(path_, buf_, sz_) \
+ listxattr(path_, buf_, sz_, XATTR_NOFOLLOW)
+ #define lgetxattr(path_, name_, val_, sz_) \
+ getxattr(path_, name_, val_, sz_, 0, XATTR_NOFOLLOW)
+#endif
+
+/* compressed xattr table */
+static char *xattr_table = NULL;
+static unsigned int xattr_size = 0;
+
+/* cached uncompressed xattr data */
+static char *data_cache = NULL;
+static int cache_bytes = 0, cache_size = 0;
+
+/* cached uncompressed xattr id table */
+static struct squashfs_xattr_id *xattr_id_table = NULL;
+static int xattr_ids = 0;
+
+/* saved compressed xattr table */
+unsigned int sxattr_bytes = 0, stotal_xattr_bytes = 0;
+
+/* saved cached uncompressed xattr data */
+static char *sdata_cache = NULL;
+static int scache_bytes = 0;
+
+/* saved cached uncompressed xattr id table */
+static int sxattr_ids = 0;
+
+/* xattr hash table for value duplicate detection */
+static struct xattr_list *dupl_value[65536];
+
+/* xattr hash table for id duplicate detection */
+static struct dupl_id *dupl_id[65536];
+
+/* xattr-add option names and values */
+static struct xattr_add *xattr_add_list = NULL;
+static int xattr_add_count = 0;
+
+/* file system globals from mksquashfs.c */
+extern int no_xattrs, noX;
+extern long long bytes;
+extern int fd;
+extern unsigned int xattr_bytes, total_xattr_bytes;
+extern regex_t *xattr_exclude_preg;
+extern regex_t *xattr_include_preg;
+
+/* helper functions from mksquashfs.c */
+extern unsigned short get_checksum(char *, int, unsigned short);
+extern void write_destination(int, long long, long long, void *);
+extern long long generic_write_table(long long, void *, int, void *, int);
+extern int mangle(char *, char *, int, int, int, int);
+extern char *pathname(struct dir_ent *);
+
+/* helper functions and definitions from read_xattrs.c */
+extern unsigned int read_xattrs_from_disk(int, struct squashfs_super_block *, int, long long *);
+extern struct xattr_list *get_xattr(int, unsigned int *, int *);
+extern struct prefix prefix_table[];
+
+
+static int xattr_get_type(char *name)
+{
+ int i;
+
+ for(i = 0; prefix_table[i].type != -1; i++) {
+ struct prefix *p = &prefix_table[i];
+ if(strncmp(name, p->prefix, strlen(p->prefix)) == 0)
+ break;
+ }
+
+ return prefix_table[i].type;
+}
+
+
+static void xattr_copy_prefix(struct xattr_list *xattr, int t, char *name)
+{
+ xattr->full_name = strdup(name);
+ xattr->name = xattr->full_name + strlen(prefix_table[t].prefix);
+ xattr->size = strlen(xattr->name);
+}
+
+
+int xattr_get_prefix(struct xattr_list *xattr, char *name)
+{
+ int type = xattr_get_type(name);
+
+ if(type != -1)
+ xattr_copy_prefix(xattr, type, name);
+
+ return type;
+}
+
+
+static int read_xattrs_from_system(struct dir_ent *dir_ent, char *filename,
+ struct xattr_list **xattrs)
+{
+ ssize_t size, vsize;
+ char *xattr_names, *p;
+ int i = 0;
+ struct xattr_list *xattr_list = NULL;
+ struct xattr_data *xattr_exc_list;
+ struct xattr_data *xattr_inc_list;
+
+ while(1) {
+ size = llistxattr(filename, NULL, 0);
+ if(size <= 0) {
+ if(size < 0 && errno != ENOTSUP) {
+ ERROR_START("llistxattr for %s failed in "
+ "read_attrs, because %s", filename,
+ strerror(errno));
+ ERROR_EXIT(". Ignoring\n");
+ }
+ return 0;
+ }
+
+ xattr_names = malloc(size);
+ if(xattr_names == NULL)
+ MEM_ERROR();
+
+ size = llistxattr(filename, xattr_names, size);
+ if(size < 0) {
+ free(xattr_names);
+ if(errno == ERANGE)
+ /* xattr list grew? Try again */
+ continue;
+ else {
+ ERROR_START("llistxattr for %s failed in "
+ "read_attrs, because %s", filename,
+ strerror(errno));
+ ERROR_EXIT(". Ignoring\n");
+ return 0;
+ }
+ }
+
+ break;
+ }
+
+ xattr_exc_list = eval_xattr_exc_actions(root_dir, dir_ent);
+ xattr_inc_list = eval_xattr_inc_actions(root_dir, dir_ent);
+
+ for(p = xattr_names; p < xattr_names + size;) {
+ struct xattr_list *x;
+ int res;
+
+ res = match_xattr_exc_actions(xattr_exc_list, p);
+ if(res) {
+ p += strlen(p) + 1;
+ continue;
+ }
+
+ if(xattr_exclude_preg) {
+ res = regexec(xattr_exclude_preg, p, (size_t) 0, NULL, 0);
+ if(res == 0) {
+ p += strlen(p) + 1;
+ continue;
+ }
+ }
+
+ res = match_xattr_inc_actions(xattr_inc_list, p);
+ if(res) {
+ p += strlen(p) + 1;
+ continue;
+ }
+
+ if(xattr_include_preg) {
+ res = regexec(xattr_include_preg, p, (size_t) 0, NULL, 0);
+ if(res) {
+ p += strlen(p) + 1;
+ continue;
+ }
+ }
+
+ x = realloc(xattr_list, (i + 1) * sizeof(struct xattr_list));
+ if(x == NULL)
+ MEM_ERROR();
+ xattr_list = x;
+
+ xattr_list[i].type = xattr_get_prefix(&xattr_list[i], p);
+
+ if(xattr_list[i].type == -1) {
+ ERROR("Unrecognised xattr prefix %s\n", p);
+ p += strlen(p) + 1;
+ continue;
+ }
+
+ p += strlen(p) + 1;
+
+ while(1) {
+ vsize = lgetxattr(filename, xattr_list[i].full_name,
+ NULL, 0);
+ if(vsize < 0) {
+ ERROR_START("lgetxattr failed for %s in "
+ "read_attrs, because %s", filename,
+ strerror(errno));
+ ERROR_EXIT(". Ignoring\n");
+ free(xattr_list[i].full_name);
+ goto failed;
+ }
+
+ xattr_list[i].value = malloc(vsize);
+ if(xattr_list[i].value == NULL)
+ MEM_ERROR();
+
+ vsize = lgetxattr(filename, xattr_list[i].full_name,
+ xattr_list[i].value, vsize);
+ if(vsize < 0) {
+ free(xattr_list[i].value);
+ if(errno == ERANGE)
+ /* xattr grew? Try again */
+ continue;
+ else {
+ ERROR_START("lgetxattr failed for %s "
+ "in read_attrs, because %s",
+ filename, strerror(errno));
+ ERROR_EXIT(". Ignoring\n");
+ free(xattr_list[i].full_name);
+ goto failed;
+ }
+ }
+
+ break;
+ }
+
+ xattr_list[i].vsize = vsize;
+
+ TRACE("read_xattrs_from_system: filename %s, xattr name %s,"
+ " vsize %d\n", filename, xattr_list[i].full_name,
+ xattr_list[i].vsize);
+ i++;
+ }
+
+ free(xattr_names);
+
+ if(i > 0)
+ *xattrs = xattr_list;
+ else
+ free(xattr_list);
+ return i;
+
+failed:
+ while(--i >= 0) {
+ free(xattr_list[i].full_name);
+ free(xattr_list[i].value);
+ }
+ free(xattr_list);
+ free(xattr_names);
+ return 0;
+}
+
+
+static int get_xattr_size(struct xattr_list *xattr)
+{
+ int size = sizeof(struct squashfs_xattr_entry) +
+ sizeof(struct squashfs_xattr_val) + xattr->size;
+
+ if(xattr->type & XATTR_VALUE_OOL)
+ size += XATTR_VALUE_OOL_SIZE;
+ else
+ size += xattr->vsize;
+
+ return size;
+}
+
+
+static void *get_xattr_space(unsigned int req_size, long long *disk)
+{
+ int data_space;
+ unsigned short c_byte;
+
+ /*
+ * Move and compress cached uncompressed data into xattr table.
+ */
+ while(cache_bytes >= SQUASHFS_METADATA_SIZE) {
+ if((xattr_size - xattr_bytes) <
+ ((SQUASHFS_METADATA_SIZE << 1)) + 2) {
+ xattr_table = realloc(xattr_table, xattr_size +
+ (SQUASHFS_METADATA_SIZE << 1) + 2);
+ if(xattr_table == NULL)
+ MEM_ERROR();
+ xattr_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+ }
+
+ c_byte = mangle(xattr_table + xattr_bytes + BLOCK_OFFSET,
+ data_cache, SQUASHFS_METADATA_SIZE,
+ SQUASHFS_METADATA_SIZE, noX, 0);
+ TRACE("Xattr block @ 0x%x, size %d\n", xattr_bytes, c_byte);
+ SQUASHFS_SWAP_SHORTS(&c_byte, xattr_table + xattr_bytes, 1);
+ xattr_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
+ memmove(data_cache, data_cache + SQUASHFS_METADATA_SIZE,
+ cache_bytes - SQUASHFS_METADATA_SIZE);
+ cache_bytes -= SQUASHFS_METADATA_SIZE;
+ }
+
+ /*
+ * Ensure there's enough space in the uncompressed data cache
+ */
+ data_space = cache_size - cache_bytes;
+ if(data_space < req_size) {
+ int realloc_size = req_size - data_space;
+ data_cache = realloc(data_cache, cache_size +
+ realloc_size);
+ if(data_cache == NULL)
+ MEM_ERROR();
+ cache_size += realloc_size;
+ }
+
+ if(disk)
+ *disk = ((long long) xattr_bytes << 16) | cache_bytes;
+ cache_bytes += req_size;
+ return data_cache + cache_bytes - req_size;
+}
+
+
+static struct dupl_id *check_id_dupl(struct xattr_list *xattr_list, int xattrs)
+{
+ struct dupl_id *entry;
+ int i;
+ unsigned short checksum = 0;
+
+ /* compute checksum over all xattrs */
+ for(i = 0; i < xattrs; i++) {
+ struct xattr_list *xattr = &xattr_list[i];
+
+ checksum = get_checksum(xattr->full_name,
+ strlen(xattr->full_name), checksum);
+ checksum = get_checksum(xattr->value,
+ xattr->vsize, checksum);
+ }
+
+ for(entry = dupl_id[checksum]; entry; entry = entry->next) {
+ if (entry->xattrs != xattrs)
+ continue;
+
+ for(i = 0; i < xattrs; i++) {
+ struct xattr_list *xattr = &xattr_list[i];
+ struct xattr_list *dup_xattr = &entry->xattr_list[i];
+
+ if(strcmp(xattr->full_name, dup_xattr->full_name))
+ break;
+
+ if(xattr->vsize != dup_xattr->vsize)
+ break;
+
+ if(memcmp(xattr->value, dup_xattr->value, xattr->vsize))
+ break;
+ }
+
+ if(i == xattrs)
+ break;
+ }
+
+ if(entry == NULL) {
+ /* no duplicate exists */
+ entry = malloc(sizeof(*entry));
+ if(entry == NULL)
+ MEM_ERROR();
+ entry->xattrs = xattrs;
+ entry->xattr_list = xattr_list;
+ entry->xattr_id = SQUASHFS_INVALID_XATTR;
+ entry->next = dupl_id[checksum];
+ dupl_id[checksum] = entry;
+ }
+
+ return entry;
+}
+
+
+static void check_value_dupl(struct xattr_list *xattr)
+{
+ struct xattr_list *entry;
+
+ if(xattr->vsize < XATTR_VALUE_OOL_SIZE)
+ return;
+
+ /* Check if this is a duplicate of an existing value */
+ xattr->vchecksum = get_checksum(xattr->value, xattr->vsize, 0);
+ for(entry = dupl_value[xattr->vchecksum]; entry; entry = entry->vnext) {
+ if(entry->vsize != xattr->vsize)
+ continue;
+
+ if(memcmp(entry->value, xattr->value, xattr->vsize) == 0)
+ break;
+ }
+
+ if(entry == NULL) {
+ /*
+ * No duplicate exists, add to hash table, and mark as
+ * requiring writing
+ */
+ xattr->vnext = dupl_value[xattr->vchecksum];
+ dupl_value[xattr->vchecksum] = xattr;
+ xattr->ool_value = SQUASHFS_INVALID_BLK;
+ } else {
+ /*
+ * Duplicate exists, make type XATTR_VALUE_OOL, and
+ * remember where the duplicate is
+ */
+ xattr->type |= XATTR_VALUE_OOL;
+ xattr->ool_value = entry->ool_value;
+ /* on appending don't free duplicate values because the
+ * duplicate value already points to the non-duplicate value */
+ if(xattr->value != entry->value) {
+ free(xattr->value);
+ xattr->value = entry->value;
+ }
+ }
+}
+
+
+static int get_xattr_id(int xattrs, struct xattr_list *xattr_list,
+ long long xattr_disk, struct dupl_id *xattr_dupl)
+{
+ int i, size = 0;
+ struct squashfs_xattr_id *xattr_id;
+
+ xattr_id_table = realloc(xattr_id_table, (xattr_ids + 1) *
+ sizeof(struct squashfs_xattr_id));
+ if(xattr_id_table == NULL)
+ MEM_ERROR();
+
+ /* get total uncompressed size of xattr data, needed for stat */
+ for(i = 0; i < xattrs; i++)
+ size += strlen(xattr_list[i].full_name) + 1 +
+ xattr_list[i].vsize;
+
+ xattr_id = &xattr_id_table[xattr_ids];
+ xattr_id->xattr = xattr_disk;
+ xattr_id->count = xattrs;
+ xattr_id->size = size;
+
+ /*
+ * keep track of total uncompressed xattr data, needed for mksquashfs
+ * file system summary
+ */
+ total_xattr_bytes += size;
+
+ xattr_dupl->xattr_id = xattr_ids ++;
+ return xattr_dupl->xattr_id;
+}
+
+
+long long write_xattrs()
+{
+ unsigned short c_byte;
+ int i, avail_bytes;
+ char *datap = data_cache;
+ long long start_bytes = bytes;
+ struct squashfs_xattr_table header = {};
+
+ if(xattr_ids == 0)
+ return SQUASHFS_INVALID_BLK;
+
+ /*
+ * Move and compress cached uncompressed data into xattr table.
+ */
+ while(cache_bytes) {
+ if((xattr_size - xattr_bytes) <
+ ((SQUASHFS_METADATA_SIZE << 1)) + 2) {
+ xattr_table = realloc(xattr_table, xattr_size +
+ (SQUASHFS_METADATA_SIZE << 1) + 2);
+ if(xattr_table == NULL)
+ MEM_ERROR();
+ xattr_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+ }
+
+ avail_bytes = cache_bytes > SQUASHFS_METADATA_SIZE ?
+ SQUASHFS_METADATA_SIZE : cache_bytes;
+ c_byte = mangle(xattr_table + xattr_bytes + BLOCK_OFFSET, datap,
+ avail_bytes, SQUASHFS_METADATA_SIZE, noX, 0);
+ TRACE("Xattr block @ 0x%x, size %d\n", xattr_bytes, c_byte);
+ SQUASHFS_SWAP_SHORTS(&c_byte, xattr_table + xattr_bytes, 1);
+ xattr_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
+ datap += avail_bytes;
+ cache_bytes -= avail_bytes;
+ }
+
+ /*
+ * Write compressed xattr table to file system
+ */
+ write_destination(fd, bytes, xattr_bytes, xattr_table);
+ bytes += xattr_bytes;
+
+ /*
+ * Swap if necessary the xattr id table
+ */
+ for(i = 0; i < xattr_ids; i++)
+ SQUASHFS_INSWAP_XATTR_ID(&xattr_id_table[i]);
+
+ header.xattr_ids = xattr_ids;
+ header.xattr_table_start = start_bytes;
+ SQUASHFS_INSWAP_XATTR_TABLE(&header);
+
+ return generic_write_table(xattr_ids * sizeof(struct squashfs_xattr_id),
+ xattr_id_table, sizeof(header), &header, noX);
+}
+
+
+void free_xattr_list(int xattrs, struct xattr_list *xattr_list)
+{
+ int i;
+
+ for(i = 0; i < xattrs; i++) {
+ free(xattr_list[i].full_name);
+ free(xattr_list[i].value);
+ }
+
+ free(xattr_list);
+}
+
+
+int generate_xattrs(int xattrs, struct xattr_list *xattr_list)
+{
+ int total_size, i;
+ int xattr_value_max;
+ void *xp;
+ long long xattr_disk;
+ struct dupl_id *xattr_dupl;
+
+ /*
+ * check if the file xattrs are a complete duplicate of a pre-existing
+ * id
+ */
+ xattr_dupl = check_id_dupl(xattr_list, xattrs);
+ if(xattr_dupl->xattr_id != SQUASHFS_INVALID_XATTR) {
+ free_xattr_list(xattrs, xattr_list);
+ return xattr_dupl->xattr_id;
+ }
+
+ /*
+ * Scan the xattr_list deciding which type to assign to each
+ * xattr. The choice is fairly straightforward, and depends on the
+ * size of each xattr name/value and the overall size of the
+ * resultant xattr list stored in the xattr metadata table.
+ *
+ * Choices are whether to store data inline or out of line.
+ *
+ * The overall goal is to optimise xattr scanning and lookup, and
+ * to enable the file system layout to scale from a couple of
+ * small xattr name/values to a large number of large xattr
+ * names/values without affecting performance. While hopefully
+ * enabling the common case of a couple of small xattr name/values
+ * to be stored efficiently
+ *
+ * Code repeatedly scans, doing the following
+ * move xattr data out of line if it exceeds
+ * xattr_value_max. Where xattr_value_max is
+ * initially XATTR_INLINE_MAX. If the final uncompressed
+ * xattr list is larger than XATTR_TARGET_MAX then more
+ * aggressively move xattr data out of line by repeatedly
+ * setting inline threshold to 1/2, then 1/4, 1/8 of
+ * XATTR_INLINE_MAX until target achieved or there's
+ * nothing left to move out of line
+ */
+ xattr_value_max = XATTR_INLINE_MAX;
+ while(1) {
+ for(total_size = 0, i = 0; i < xattrs; i++) {
+ struct xattr_list *xattr = &xattr_list[i];
+ xattr->type &= XATTR_PREFIX_MASK; /* all inline */
+ if (xattr->vsize > xattr_value_max)
+ xattr->type |= XATTR_VALUE_OOL;
+
+ total_size += get_xattr_size(xattr);
+ }
+
+ /*
+ * If the total size of the uncompressed xattr list is <=
+ * XATTR_TARGET_MAX we're done
+ */
+ if(total_size <= XATTR_TARGET_MAX)
+ break;
+
+ if(xattr_value_max == XATTR_VALUE_OOL_SIZE)
+ break;
+
+ /*
+ * Inline target not yet at minimum and so reduce it, and
+ * try again
+ */
+ xattr_value_max /= 2;
+ if(xattr_value_max < XATTR_VALUE_OOL_SIZE)
+ xattr_value_max = XATTR_VALUE_OOL_SIZE;
+ }
+
+ /*
+ * Check xattr values for duplicates
+ */
+ for(i = 0; i < xattrs; i++) {
+ check_value_dupl(&xattr_list[i]);
+ }
+
+ /*
+ * Add each out of line value to the file system xattr table
+ * if it doesn't already exist as a duplicate
+ */
+ for(i = 0; i < xattrs; i++) {
+ struct xattr_list *xattr = &xattr_list[i];
+
+ if((xattr->type & XATTR_VALUE_OOL) &&
+ (xattr->ool_value == SQUASHFS_INVALID_BLK)) {
+ struct squashfs_xattr_val val;
+ int size = sizeof(val) + xattr->vsize;
+ xp = get_xattr_space(size, &xattr->ool_value);
+ val.vsize = xattr->vsize;
+ SQUASHFS_SWAP_XATTR_VAL(&val, xp);
+ memcpy(xp + sizeof(val), xattr->value, xattr->vsize);
+ }
+ }
+
+ /*
+ * Create xattr list and add to file system xattr table
+ */
+ get_xattr_space(0, &xattr_disk);
+ for(i = 0; i < xattrs; i++) {
+ struct xattr_list *xattr = &xattr_list[i];
+ struct squashfs_xattr_entry entry;
+ struct squashfs_xattr_val val;
+
+ xp = get_xattr_space(sizeof(entry) + xattr->size, NULL);
+ entry.type = xattr->type;
+ entry.size = xattr->size;
+ SQUASHFS_SWAP_XATTR_ENTRY(&entry, xp);
+ memcpy(xp + sizeof(entry), xattr->name, xattr->size);
+
+ if(xattr->type & XATTR_VALUE_OOL) {
+ int size = sizeof(val) + XATTR_VALUE_OOL_SIZE;
+ xp = get_xattr_space(size, NULL);
+ val.vsize = XATTR_VALUE_OOL_SIZE;
+ SQUASHFS_SWAP_XATTR_VAL(&val, xp);
+ SQUASHFS_SWAP_LONG_LONGS(&xattr->ool_value, xp +
+ sizeof(val), 1);
+ } else {
+ int size = sizeof(val) + xattr->vsize;
+ xp = get_xattr_space(size, &xattr->ool_value);
+ val.vsize = xattr->vsize;
+ SQUASHFS_SWAP_XATTR_VAL(&val, xp);
+ memcpy(xp + sizeof(val), xattr->value, xattr->vsize);
+ }
+ }
+
+ /*
+ * Add to xattr id lookup table
+ */
+ return get_xattr_id(xattrs, xattr_list, xattr_disk, xattr_dupl);
+}
+
+
+/*
+ * Instantiate two implementations of merge sort with different types and names
+ */
+SORT(sort_list, xattr_add, name, next);
+SORT(sort_xattr_list, xattr_list, full_name, vnext);
+
+
+int read_xattrs(void *d, int type)
+{
+ struct dir_ent *dir_ent = d;
+ struct inode_info *inode = dir_ent->inode;
+ char *filename = pathname(dir_ent);
+ struct xattr_list *xattr_list = NULL, *head;
+ int count, i = 0, j;
+ struct xattr_add *l1 = xattr_add_list, *l2 = NULL, *l3 = NULL;
+ struct xattr_add *action_add_list;
+
+ if(no_xattrs || inode->root_entry)
+ return SQUASHFS_INVALID_XATTR;
+
+ if(IS_TARFILE(inode))
+ i = read_xattrs_from_tarfile(inode, &xattr_list);
+ else if(!inode->dummy_root_dir && !IS_PSEUDO(inode))
+ i = read_xattrs_from_system(dir_ent, filename, &xattr_list);
+
+ action_add_list = eval_xattr_add_actions(root_dir, dir_ent, &count);
+
+ /*
+ * At this point we may have up to 3 lists of xattrs:
+ *
+ * 1. a list of xattrs created by the global xattrs-add command line
+ * 2. a list of xattrs created by one or more pseudo xattr definitions
+ * on this file.
+ * 3. a list of xattrs created by one or more xattr add actions on this
+ * file.
+ *
+ * The global xattrs are sorted, but, the pseudo xattr list and action
+ * xattr list are not.
+ *
+ * So sort the pseudo and action lists, and merge the three sorted lists
+ * together whilst adding them to the xattr_list
+ */
+
+ if(inode->xattr) {
+ sort_list(&(inode->xattr->xattr), inode->xattr->count);
+ l2 = inode->xattr->xattr;
+ }
+
+ if(action_add_list) {
+ sort_list(&action_add_list, count);
+ l3 = action_add_list;
+ }
+
+ while(l1 || l2 || l3) {
+ struct xattr_list *x;
+ struct xattr_add *entry;
+
+ if(l1 && l2 && l3) {
+ if(strcmp(l1->name, l2->name) <= 0) {
+ if(strcmp(l1->name, l3->name) <= 0) {
+ entry= l1;
+ l1 = l1->next;
+ } else {
+ entry = l3;
+ l3 = l3->next;
+ }
+ } else {
+ if(strcmp(l2->name, l3->name) <= 0) {
+ entry = l2;
+ l2 = l2->next;
+ } else {
+ entry = l3;
+ l3 = l3->next;
+ }
+ }
+ } else if(l1 && l2) {
+ if(strcmp(l1->name, l2->name) <= 0) {
+ entry = l1;
+ l1 = l1->next;
+ } else {
+ entry = l2;
+ l2 = l2->next;
+ }
+ } else if(l1 && l3) {
+ if(strcmp(l1->name, l3->name) <= 0) {
+ entry = l1;
+ l1 = l1->next;
+ } else {
+ entry = l3;
+ l3 = l3->next;
+ }
+ } else if(l2 && l3) {
+ if(strcmp(l2->name, l3->name) <= 0) {
+ entry = l2;
+ l2 = l2->next;
+ } else {
+ entry = l3;
+ l3 = l3->next;
+ }
+ } else if(l1) {
+ entry = l1;
+ l1 = l1->next;
+ } else if(l2) {
+ entry = l2;
+ l2 = l2->next;
+ } else {
+ entry = l3;
+ l3 = l3->next;
+ }
+
+ /*
+ * User extended attributes are only allowed for files and
+ * directories. See man 7 xattr for explanation.
+ */
+ if((entry->type == SQUASHFS_XATTR_USER) &&
+ (type != SQUASHFS_FILE_TYPE &&
+ type != SQUASHFS_DIR_TYPE))
+ continue;
+
+ x = realloc(xattr_list, (i + 1) * sizeof(struct xattr_list));
+ if(x == NULL)
+ MEM_ERROR();
+ xattr_list = x;
+
+ xattr_list[i].type = entry->type;
+ xattr_copy_prefix(&xattr_list[i], entry->type, entry->name);
+
+ xattr_list[i].value = malloc(entry->vsize);
+ if(xattr_list[i].value == NULL)
+ MEM_ERROR();
+
+ memcpy(xattr_list[i].value, entry->value, entry->vsize);
+ xattr_list[i].vsize = entry->vsize;
+
+ TRACE("read_xattrs: filename %s, xattr name %s,"
+ " vsize %d\n", filename, xattr_list[i].full_name,
+ xattr_list[i].vsize);
+ i++;
+ }
+
+ if(i == 0)
+ return SQUASHFS_INVALID_XATTR;
+ else if(i == 1)
+ goto skip_dup_check;
+
+ /*
+ * Sort and check xattr list for duplicates
+ */
+ for(j = 1; j < i; j++)
+ xattr_list[j - 1].vnext = &xattr_list[j];
+
+ xattr_list[i - 1].vnext = NULL;
+ head = xattr_list;
+
+ sort_xattr_list(&head, i);
+
+ for(j = 0; j < i - 1; head=head->vnext, j++)
+ if(strcmp(head->full_name, head->vnext->full_name) == 0)
+ BAD_ERROR("Duplicate xattr name %s in file %s\n",
+ head->full_name, filename);
+
+skip_dup_check:
+ return generate_xattrs(i, xattr_list);
+}
+
+
+/*
+ * Add the existing xattr ids and xattr metadata in the file system being
+ * appended to, to the in-memory xattr cache. This allows duplicate checking to
+ * take place against the xattrs already in the file system being appended to,
+ * and ensures the pre-existing xattrs are written out along with any new xattrs
+ */
+int get_xattrs(int fd, struct squashfs_super_block *sBlk)
+{
+ int res, i, id;
+ unsigned int count, ids;
+
+ TRACE("get_xattrs\n");
+
+ if(sBlk->xattr_id_table_start == SQUASHFS_INVALID_BLK)
+ return SQUASHFS_INVALID_BLK;
+
+ ids = read_xattrs_from_disk(fd, sBlk, FALSE, NULL);
+ if(ids == 0)
+ EXIT_MKSQUASHFS();
+
+ /*
+ * for each xattr id read and construct its list of xattr
+ * name:value pairs, and add them to the in-memory xattr cache
+ */
+ for(i = 0; i < ids; i++) {
+ struct xattr_list *xattr_list = get_xattr(i, &count, &res);
+ if(xattr_list == NULL && res == FALSE)
+ EXIT_MKSQUASHFS();
+
+ if(res) {
+ free_xattr(xattr_list, count);
+ return FALSE;
+ }
+ id = generate_xattrs(count, xattr_list);
+
+ /*
+ * Sanity check, the new xattr id should be the same as the
+ * xattr id in the original file system
+ */
+ if(id != i) {
+ ERROR("BUG, different xattr_id in get_xattrs\n");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * Save current state of xattrs, needed for restoring state in the event of an
+ * abort in appending
+ */
+void save_xattrs()
+{
+ /* save the current state of the compressed xattr data */
+ sxattr_bytes = xattr_bytes;
+ stotal_xattr_bytes = total_xattr_bytes;
+
+ /*
+ * save the current state of the cached uncompressed xattr data.
+ * Note we have to save the contents of the data cache because future
+ * operations will delete the current contents
+ */
+ sdata_cache = malloc(cache_bytes);
+ if(sdata_cache == NULL)
+ MEM_ERROR();
+
+ memcpy(sdata_cache, data_cache, cache_bytes);
+ scache_bytes = cache_bytes;
+
+ /* save the current state of the xattr id table */
+ sxattr_ids = xattr_ids;
+}
+
+
+/*
+ * Restore xattrs in the event of an abort in appending
+ */
+void restore_xattrs()
+{
+ /* restore the state of the compressed xattr data */
+ xattr_bytes = sxattr_bytes;
+ total_xattr_bytes = stotal_xattr_bytes;
+
+ /* restore the state of the uncomoressed xattr data */
+ memcpy(data_cache, sdata_cache, scache_bytes);
+ cache_bytes = scache_bytes;
+
+ /* restore the state of the xattr id table */
+ xattr_ids = sxattr_ids;
+}
+
+
+regex_t *xattr_regex(char *pattern, char *option)
+{
+ int error;
+ regex_t *preg = malloc(sizeof(regex_t));
+
+ if(preg == NULL)
+ MEM_ERROR();
+
+ error = regcomp(preg, pattern, REG_EXTENDED|REG_NOSUB);
+
+ if(error) {
+ char str[1024]; /* overflow safe */
+
+ regerror(error, preg, str, 1024);
+ BAD_ERROR("invalid regex %s in xattrs-%s option, because %s\n",
+ pattern, option, str);
+ }
+
+ return preg;
+}
+
+
+char *base64_decode(char *source, int size, int *bytes)
+{
+ char *dest;
+ unsigned char *dest_ptr, *source_ptr = (unsigned char *) source;
+ int bit_pos = 0;
+ int output = 0;
+ int count;
+
+ if(size % 4 == 0) {
+ /* Check for and ignore any end padding */
+ if(source_ptr[size - 2] == '=' && source_ptr[size - 1] == '=')
+ size -= 2;
+ else if(source_ptr[size - 1] == '=')
+ size --;
+ }
+
+ /* Calculate number of bytes the base64 encoding represents */
+ count = size * 3 / 4;
+
+ dest = malloc(count);
+
+ for(dest_ptr = (unsigned char *) dest; size; size --, source_ptr ++) {
+ int value = *source_ptr;
+
+ if(value >= 'A' && value <= 'Z')
+ value -= 'A';
+ else if(value >= 'a' && value <= 'z')
+ value -= 'a' - 26;
+ else if(value >= '0' && value <= '9')
+ value -= '0' - 52;
+ else if(value == '+')
+ value = 62;
+ else if(value == '/')
+ value = 63;
+ else
+ goto failed;
+
+ if(bit_pos == 24) {
+ dest_ptr[0] = output >> 16;
+ dest_ptr[1] = (output >> 8) & 0xff;
+ dest_ptr[2] = output & 0xff;
+ bit_pos = 0;
+ output = 0;
+ dest_ptr += 3;
+ }
+
+ output = (output << 6) | value;
+ bit_pos += 6;
+ }
+
+ output = output << (24 - bit_pos);
+
+ if(bit_pos == 6)
+ goto failed;
+
+ if(bit_pos >= 12)
+ dest_ptr[0] = output >> 16;
+
+ if(bit_pos >= 18)
+ dest_ptr[1] = (output >> 8) & 0xff;
+
+ if(bit_pos == 24)
+ dest_ptr[2] = output & 0xff;
+
+ *bytes = (dest_ptr - (unsigned char *) dest) + (bit_pos / 8);
+ return dest;
+
+failed:
+ free(dest);
+ return NULL;
+}
+
+
+char *hex_decode(char *source, int size, int *bytes)
+{
+ char *dest;
+ unsigned char *dest_ptr, *source_ptr = (unsigned char *) source;
+ int first = 0;
+
+ if(size % 2 != 0)
+ return NULL;
+
+ dest = malloc(size >> 2);
+ if(dest == NULL)
+ MEM_ERROR();
+
+ for(dest_ptr = (unsigned char *) dest ; size; size --) {
+ int digit = *source_ptr ++;
+
+ if(digit >= 'A' && digit <= 'F')
+ digit -= 'A' - 10;
+ else if(digit >= 'a' && digit <= 'f')
+ digit -= 'a' - 10;
+ else if(digit >= '0' && digit <= '9')
+ digit -= '0';
+ else
+ goto failed;
+
+ if(size % 2 == 0)
+ first = digit;
+ else
+ *dest_ptr ++ = (first << 4) | digit;
+ }
+
+ *bytes = dest_ptr - (unsigned char *) dest;
+
+ return dest;
+
+failed:
+ free(dest);
+ return NULL;
+}
+
+
+int decode_octal(unsigned char *ptr)
+{
+ int i, output = 0;
+
+ for(i = 0; i < 3; i++) {
+ int val = *ptr ++;
+
+ if(val < '0' || val > '7')
+ return -1;
+
+ output = (output << 3) | (val - '0');
+ }
+
+ return output < 256 ? output : -1;
+}
+
+
+char *text_decode(char *source, int *bytes)
+{
+ unsigned char *dest, *dest_ptr, *ptr = (unsigned char *) source;
+ int size = 0;
+
+ for(; *ptr; size ++, ptr ++) {
+ if(*ptr == '\\') {
+ if(ptr[1] != '\0' && ptr[2] != '\0' && ptr[3] != '\0')
+ ptr += 3;
+ else
+ return NULL;
+ }
+ }
+
+ dest = malloc(size);
+ if(dest == NULL)
+ MEM_ERROR();
+
+ *bytes = size;
+
+ for(ptr = (unsigned char *) source, dest_ptr = dest; size; size --) {
+ if(*ptr == '\\') {
+ int res = decode_octal(++ ptr);
+
+ if(res == -1)
+ goto failed;
+
+ *dest_ptr ++ = res;
+ ptr += 3;
+ } else
+ *dest_ptr ++ = *ptr ++;
+ }
+
+ return (char *) dest;
+
+failed:
+ free(dest);
+ return NULL;
+}
+
+
+struct xattr_add *xattr_parse(char *str, char *pre, char *option)
+{
+ struct xattr_add *entry;
+ char *value;
+ int prefix, size;
+
+ /*
+ * Look for the "=" separating the xattr name from the value
+ */
+ for(value = str; *value != '=' && *value != '\0'; value ++);
+ if(*value == '\0') {
+ ERROR("%sinvalid argument \"%s\" in %s option, because no "
+ "`=` found\n", pre, str, option);
+ goto failed;
+ }
+
+ if(value == str) {
+ ERROR("%sinvalid argument \"%s\" in %s option, because xattr "
+ "name is empty\n", pre, str, option);
+ goto failed;
+ }
+
+ if(*(value + 1) == '\0') {
+ ERROR("%sinvalid argument \"%s\" in %s option, because xattr "
+ "value is empty\n", pre, str, option);
+ goto failed;
+ }
+
+ entry = malloc(sizeof(struct xattr_add));
+ if(entry == NULL)
+ MEM_ERROR();
+
+ entry->name = strndup(str, value++ - str);
+ entry->type = xattr_get_type(entry->name);
+
+ if(entry->type == -1) {
+ ERROR("%s%s: unrecognised xattr prefix in %s\n", pre, option,
+ entry->name);
+ goto failed2;
+ }
+
+ /*
+ * Evaluate the format prefix (if any)
+ */
+ if(*(value + 1) == '\0')
+ /*
+ * By definition an xattr value of 1 byte hasn't a prefix,
+ * and should be treated as binary
+ */
+ prefix = 0;
+ else
+ prefix = (*value << 8) + *(value + 1);
+
+ switch(prefix) {
+ case PREFIX_BASE64_0S:
+ case PREFIX_BASE64_0s:
+ value += 2;
+ if(*value == 0) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "xattr value is empty after format prefix 0S "
+ "or 0s\n", pre, str, option);
+ goto failed2;
+ }
+
+ entry->value = base64_decode(value, strlen(value), &size);
+ entry->vsize = size;
+
+ if(entry->value == NULL) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "invalid base64 value\n", pre, str, option);
+ goto failed2;
+ }
+ break;
+
+ case PREFIX_HEX_0X:
+ case PREFIX_HEX_0x:
+ value += 2;
+ if(*value == 0) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "xattr value is empty after format prefix 0X "
+ "or 0x\n", pre, str, option);
+ goto failed2;
+ }
+
+ entry->value = hex_decode(value, strlen(value), &size);
+ entry->vsize = size;
+
+ if(entry->value == NULL) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "invalid hexidecimal value\n", pre, str, option);
+ goto failed2;
+ }
+ break;
+
+ case PREFIX_TEXT_0T:
+ case PREFIX_TEXT_0t:
+ value += 2;
+ if(*value == 0) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "xattr value is empty after format prefix 0T "
+ "or 0t\n", pre, str, option);
+ goto failed2;
+ }
+
+ entry->value = text_decode(value, &size);
+ entry->vsize = size;
+
+ if(entry->value == NULL) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "invalid text value\n", pre, str, option);
+ goto failed2;
+ }
+ break;
+
+ case PREFIX_BINARY_0B:
+ case PREFIX_BINARY_0b:
+ value += 2;
+ if(*value == 0) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "xattr value is empty after format prefix 0B "
+ "or 0b\n", pre, str, option);
+ goto failed2;
+ }
+
+ /* fall through */
+ default:
+ entry->vsize = strlen(value);
+ entry->value = malloc(entry->vsize);
+
+ if(entry->value == NULL)
+ MEM_ERROR();
+
+ memcpy(entry->value, value, entry->vsize);
+ }
+
+ return entry;
+
+failed2:
+ free(entry->name);
+ free(entry);
+failed:
+ return NULL;
+}
+
+
+void xattrs_add(char *str)
+{
+ struct xattr_add *entry;
+
+ entry = xattr_parse(str, "FATAL ERROR: ", "xattrs-add");
+
+ if(entry) {
+ entry->next = xattr_add_list;
+ xattr_add_list = entry;
+
+ xattr_add_count ++;
+ } else
+ exit(1);
+}
+
+
+int add_xattrs(void) {
+ return xattr_add_count;
+}
+
+
+void sort_xattr_add_list(void)
+{
+ sort_list(&xattr_add_list, xattr_add_count);
+}
diff --git a/squashfs-tools/xattr.h b/squashfs-tools/xattr.h
new file mode 100644
index 0000000..0697f83
--- /dev/null
+++ b/squashfs-tools/xattr.h
@@ -0,0 +1,241 @@
+#ifndef XATTR_H
+#define XATTR_H
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2010, 2012, 2013, 2014, 2019, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * xattr.h
+ */
+
+#define XATTR_VALUE_OOL SQUASHFS_XATTR_VALUE_OOL
+#define XATTR_PREFIX_MASK SQUASHFS_XATTR_PREFIX_MASK
+
+#define XATTR_VALUE_OOL_SIZE sizeof(long long)
+
+/* maximum size of xattr value data that will be inlined */
+#define XATTR_INLINE_MAX 128
+
+/* the target size of an inode's xattr name:value list. If it
+ * exceeds this, then xattr value data will be successively out of lined
+ * until it meets the target */
+#define XATTR_TARGET_MAX 65536
+
+#define IS_XATTR(a) (a != SQUASHFS_INVALID_XATTR)
+
+#define PREFIX_BASE64_0S (0x3000 + 0x53)
+#define PREFIX_BASE64_0s (0x3000 + 0x73)
+#define PREFIX_BINARY_0B (0x3000 + 0x42)
+#define PREFIX_BINARY_0b (0x3000 + 0x62)
+#define PREFIX_HEX_0X (0x3000 + 0x58)
+#define PREFIX_HEX_0x (0x3000 + 0x78)
+#define PREFIX_TEXT_0T (0x3000 + 0x54)
+#define PREFIX_TEXT_0t (0x3000 + 0x74)
+
+struct xattr_list {
+ char *name;
+ char *full_name;
+ int size;
+ int vsize;
+ void *value;
+ int type;
+ long long ool_value;
+ unsigned short vchecksum;
+ struct xattr_list *vnext;
+};
+
+struct dupl_id {
+ struct xattr_list *xattr_list;
+ int xattrs;
+ int xattr_id;
+ struct dupl_id *next;
+};
+
+struct prefix {
+ char *prefix;
+ int type;
+};
+
+struct xattr_add {
+ char *name;
+ char *value;
+ unsigned int vsize;
+ int type;
+ struct xattr_add *next;
+};
+
+extern int generate_xattrs(int, struct xattr_list *);
+
+#ifdef XATTR_SUPPORT
+extern int get_xattrs(int, struct squashfs_super_block *);
+extern int read_xattrs(void *, int type);
+extern long long write_xattrs();
+extern void save_xattrs();
+extern void restore_xattrs();
+extern unsigned int xattr_bytes, total_xattr_bytes;
+extern int write_xattr(char *, unsigned int);
+extern unsigned int read_xattrs_from_disk(int, struct squashfs_super_block *, int, long long *);
+extern struct xattr_list *get_xattr(int, unsigned int *, int *);
+extern void free_xattr(struct xattr_list *, int);
+extern regex_t *xattr_regex(char *pattern, char *option);
+extern void xattrs_add(char *str);
+extern void sort_xattr_add_list(void);
+extern char *base64_decode(char *source, int size, int *bytes);
+extern int add_xattrs(void);
+extern struct xattr_add *xattr_parse(char *, char *, char *);
+extern int read_pseudo_xattr(char *orig_def, char *filename, char *name, char *def);
+extern void print_xattr(char *, unsigned int, int);
+extern int has_xattrs(unsigned int);
+#else
+#include "squashfs_swap.h"
+
+static inline int get_xattrs(int fd, struct squashfs_super_block *sBlk)
+{
+ if(sBlk->xattr_id_table_start != SQUASHFS_INVALID_BLK) {
+ fprintf(stderr, "Xattrs in filesystem! These are not "
+ "supported on this build of Mksquashfs\n");
+ return 0;
+ } else
+ return SQUASHFS_INVALID_BLK;
+}
+
+
+static inline int read_xattrs(void *dir_ent, int type)
+{
+ return SQUASHFS_INVALID_XATTR;
+}
+
+
+static inline long long write_xattrs()
+{
+ return SQUASHFS_INVALID_BLK;
+}
+
+
+static inline void save_xattrs()
+{
+}
+
+
+static inline void restore_xattrs()
+{
+}
+
+
+static inline int write_xattr(char *pathname, unsigned int xattr)
+{
+ return 1;
+}
+
+
+static inline unsigned int read_xattrs_from_disk(int fd, struct squashfs_super_block *sBlk, int sanity_only, long long *table_start)
+{
+ int res;
+ struct squashfs_xattr_table id_table;
+
+ /*
+ * Read sufficient xattr metadata to obtain the start of the xattr
+ * metadata on disk (table_start). This value is needed to do
+ * sanity checking of the filesystem.
+ */
+ res = read_fs_bytes(fd, sBlk->xattr_id_table_start, sizeof(id_table), &id_table);
+ if(res == 0)
+ return 0;
+
+ SQUASHFS_INSWAP_XATTR_TABLE(&id_table);
+
+ /*
+ * id_table.xattr_table_start stores the start of the compressed xattr
+ * metadata blocks. This by definition is also the end of the previous
+ * filesystem table - the id lookup table.
+ */
+ if(table_start != NULL)
+ *table_start = id_table.xattr_table_start;
+
+ return id_table.xattr_ids;
+}
+
+
+static inline struct xattr_list *get_xattr(int i, unsigned int *count, int j)
+{
+ return NULL;
+}
+
+static inline regex_t *xattr_regex(char *pattern, char *option)
+{
+ return NULL;
+}
+
+static inline void xattrs_add(char *str)
+{
+}
+
+static inline void sort_xattr_add_list(void)
+{
+}
+
+static inline int add_xattrs(void)
+{
+ return 0;
+}
+
+static inline struct xattr_add *xattr_parse(char *a, char *b, char *c)
+{
+ return NULL;
+}
+
+
+static inline int read_pseudo_xattr(char *orig_def, char *filename, char *name, char *def)
+{
+ free(filename);
+ fprintf(stderr, "Xattrs are unsupported in this build\n");
+
+ return 0;
+}
+
+
+static inline void print_xattr(char *pathname, unsigned int xattr, int writer_fd)
+{
+}
+
+
+static inline int has_xattrs(unsigned int xattr)
+{
+ return 0;
+}
+#endif
+
+#ifdef XATTR_SUPPORT
+#define xattrs_supported() TRUE
+#ifdef XATTR_DEFAULT
+#define NOXOPT_STR
+#define XOPT_STR " (default)"
+#define XATTR_DEF 0
+#else
+#define NOXOPT_STR " (default)"
+#define XOPT_STR
+#define XATTR_DEF 1
+#endif
+#else
+#define xattrs_supported() FALSE
+#define NOXOPT_STR " (default)"
+#define XOPT_STR " (unsupported)"
+#define XATTR_DEF 1
+#endif
+#endif
diff --git a/squashfs-tools/xz_wrapper.c b/squashfs-tools/xz_wrapper.c
new file mode 100644
index 0000000..0d650e0
--- /dev/null
+++ b/squashfs-tools/xz_wrapper.c
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 2010, 2011, 2012, 2013, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * xz_wrapper.c
+ *
+ * Support for XZ (LZMA2) compression using XZ Utils liblzma
+ * http://tukaani.org/xz/
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <lzma.h>
+
+#include "squashfs_fs.h"
+#include "xz_wrapper.h"
+#include "compressor.h"
+
+static struct bcj bcj[] = {
+ { "x86", LZMA_FILTER_X86, 0 },
+ { "powerpc", LZMA_FILTER_POWERPC, 0 },
+ { "ia64", LZMA_FILTER_IA64, 0 },
+ { "arm", LZMA_FILTER_ARM, 0 },
+ { "armthumb", LZMA_FILTER_ARMTHUMB, 0 },
+ { "sparc", LZMA_FILTER_SPARC, 0 },
+ { NULL, LZMA_VLI_UNKNOWN, 0 }
+};
+
+static int filter_count = 1;
+static int dictionary_size = 0;
+static float dictionary_percent = 0;
+
+
+/*
+ * This function is called by the options parsing code in mksquashfs.c
+ * to parse any -X compressor option.
+ *
+ * Two specific options are supported:
+ * -Xbcj
+ * -Xdict-size
+ *
+ * This function returns:
+ * >=0 (number of additional args parsed) on success
+ * -1 if the option was unrecognised, or
+ * -2 if the option was recognised, but otherwise bad in
+ * some way (e.g. invalid parameter)
+ *
+ * Note: this function sets internal compressor state, but does not
+ * pass back the results of the parsing other than success/failure.
+ * The xz_dump_options() function is called later to get the options in
+ * a format suitable for writing to the filesystem.
+ */
+static int xz_options(char *argv[], int argc)
+{
+ int i;
+ char *name;
+
+ if(strcmp(argv[0], "-Xbcj") == 0) {
+ if(argc < 2) {
+ fprintf(stderr, "xz: -Xbcj missing filter\n");
+ goto failed;
+ }
+
+ name = argv[1];
+ while(name[0] != '\0') {
+ for(i = 0; bcj[i].name; i++) {
+ int n = strlen(bcj[i].name);
+ if((strncmp(name, bcj[i].name, n) == 0) &&
+ (name[n] == '\0' ||
+ name[n] == ',')) {
+ if(bcj[i].selected == 0) {
+ bcj[i].selected = 1;
+ filter_count++;
+ }
+ name += name[n] == ',' ? n + 1 : n;
+ break;
+ }
+ }
+ if(bcj[i].name == NULL) {
+ fprintf(stderr, "xz: -Xbcj unrecognised "
+ "filter\n");
+ goto failed;
+ }
+ }
+
+ return 1;
+ } else if(strcmp(argv[0], "-Xdict-size") == 0) {
+ char *b;
+ float size;
+
+ if(argc < 2) {
+ fprintf(stderr, "xz: -Xdict-size missing dict-size\n");
+ goto failed;
+ }
+
+ size = strtof(argv[1], &b);
+ if(*b == '%') {
+ if(size <= 0 || size > 100) {
+ fprintf(stderr, "xz: -Xdict-size percentage "
+ "should be 0 < dict-size <= 100\n");
+ goto failed;
+ }
+
+ dictionary_percent = size;
+ dictionary_size = 0;
+ } else {
+ if((float) ((int) size) != size) {
+ fprintf(stderr, "xz: -Xdict-size can't be "
+ "fractional unless a percentage of the"
+ " block size\n");
+ goto failed;
+ }
+
+ dictionary_percent = 0;
+ dictionary_size = (int) size;
+
+ if(*b == 'k' || *b == 'K')
+ dictionary_size *= 1024;
+ else if(*b == 'm' || *b == 'M')
+ dictionary_size *= 1024 * 1024;
+ else if(*b != '\0') {
+ fprintf(stderr, "xz: -Xdict-size invalid "
+ "dict-size\n");
+ goto failed;
+ }
+ }
+
+ return 1;
+ }
+
+ return -1;
+
+failed:
+ return -2;
+}
+
+
+/*
+ * This function is called after all options have been parsed.
+ * It is used to do post-processing on the compressor options using
+ * values that were not expected to be known at option parse time.
+ *
+ * In this case block_size may not be known until after -Xdict-size has
+ * been processed (in the case where -b is specified after -Xdict-size)
+ *
+ * This function returns 0 on successful post processing, or
+ * -1 on error
+ */
+static int xz_options_post(int block_size)
+{
+ /*
+ * if -Xdict-size has been specified use this to compute the datablock
+ * dictionary size
+ */
+ if(dictionary_size || dictionary_percent) {
+ int n;
+
+ if(dictionary_size) {
+ if(dictionary_size > block_size) {
+ fprintf(stderr, "xz: -Xdict-size is larger than"
+ " block_size\n");
+ goto failed;
+ }
+ } else
+ dictionary_size = block_size * dictionary_percent / 100;
+
+ if(dictionary_size < 8192) {
+ fprintf(stderr, "xz: -Xdict-size should be 8192 bytes "
+ "or larger\n");
+ goto failed;
+ }
+
+ /*
+ * dictionary_size must be storable in xz header as either
+ * 2^n or as 2^n+2^(n+1)
+ */
+ n = ffs(dictionary_size) - 1;
+ if(dictionary_size != (1 << n) &&
+ dictionary_size != ((1 << n) + (1 << (n + 1)))) {
+ fprintf(stderr, "xz: -Xdict-size is an unsupported "
+ "value, dict-size must be storable in xz "
+ "header\n");
+ fprintf(stderr, "as either 2^n or as 2^n+2^(n+1). "
+ "Example dict-sizes are 75%%, 50%%, 37.5%%, "
+ "25%%,\n");
+ fprintf(stderr, "or 32K, 16K, 8K etc.\n");
+ goto failed;
+ }
+
+ } else
+ /* No -Xdict-size specified, use defaults */
+ dictionary_size = block_size;
+
+ return 0;
+
+failed:
+ return -1;
+}
+
+
+/*
+ * This function is called by mksquashfs to dump the parsed
+ * compressor options in a format suitable for writing to the
+ * compressor options field in the filesystem (stored immediately
+ * after the superblock).
+ *
+ * This function returns a pointer to the compression options structure
+ * to be stored (and the size), or NULL if there are no compression
+ * options
+ */
+static void *xz_dump_options(int block_size, int *size)
+{
+ static struct comp_opts comp_opts;
+ int flags = 0, i;
+
+ /*
+ * don't store compressor specific options in file system if the
+ * default options are being used - no compressor options in the
+ * file system means the default options are always assumed
+ *
+ * Defaults are:
+ * metadata dictionary size: SQUASHFS_METADATA_SIZE
+ * datablock dictionary size: block_size
+ * 1 filter
+ */
+ if(dictionary_size == block_size && filter_count == 1)
+ return NULL;
+
+ for(i = 0; bcj[i].name; i++)
+ flags |= bcj[i].selected << i;
+
+ comp_opts.dictionary_size = dictionary_size;
+ comp_opts.flags = flags;
+
+ SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
+
+ *size = sizeof(comp_opts);
+ return &comp_opts;
+}
+
+
+/*
+ * This function is a helper specifically for the append mode of
+ * mksquashfs. Its purpose is to set the internal compressor state
+ * to the stored compressor options in the passed compressor options
+ * structure.
+ *
+ * In effect this function sets up the compressor options
+ * to the same state they were when the filesystem was originally
+ * generated, this is to ensure on appending, the compressor uses
+ * the same compression options that were used to generate the
+ * original filesystem.
+ *
+ * Note, even if there are no compressor options, this function is still
+ * called with an empty compressor structure (size == 0), to explicitly
+ * set the default options, this is to ensure any user supplied
+ * -X options on the appending mksquashfs command line are over-ridden
+ *
+ * This function returns 0 on sucessful extraction of options, and
+ * -1 on error
+ */
+static int xz_extract_options(int block_size, void *buffer, int size)
+{
+ struct comp_opts *comp_opts = buffer;
+ int flags, i, n;
+
+ if(size == 0) {
+ /* set defaults */
+ dictionary_size = block_size;
+ flags = 0;
+ } else {
+ /* check passed comp opts struct is of the correct length */
+ if(size != sizeof(struct comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ dictionary_size = comp_opts->dictionary_size;
+ flags = comp_opts->flags;
+
+ /*
+ * check that the dictionary size seems correct - the dictionary
+ * size should 2^n or 2^n+2^(n+1)
+ */
+ n = ffs(dictionary_size) - 1;
+ if(dictionary_size != (1 << n) &&
+ dictionary_size != ((1 << n) + (1 << (n + 1))))
+ goto failed;
+ }
+
+ filter_count = 1;
+ for(i = 0; bcj[i].name; i++) {
+ if((flags >> i) & 1) {
+ bcj[i].selected = 1;
+ filter_count ++;
+ } else
+ bcj[i].selected = 0;
+ }
+
+ return 0;
+
+failed:
+ fprintf(stderr, "xz: error reading stored compressor options from "
+ "filesystem!\n");
+
+ return -1;
+}
+
+
+static void xz_display_options(void *buffer, int size)
+{
+ struct comp_opts *comp_opts = buffer;
+ int dictionary_size, flags, printed;
+ int i, n;
+
+ /* check passed comp opts struct is of the correct length */
+ if(size != sizeof(struct comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ dictionary_size = comp_opts->dictionary_size;
+ flags = comp_opts->flags;
+
+ /*
+ * check that the dictionary size seems correct - the dictionary
+ * size should 2^n or 2^n+2^(n+1)
+ */
+ n = ffs(dictionary_size) - 1;
+ if(dictionary_size != (1 << n) &&
+ dictionary_size != ((1 << n) + (1 << (n + 1))))
+ goto failed;
+
+ printf("\tDictionary size %d\n", dictionary_size);
+
+ printed = 0;
+ for(i = 0; bcj[i].name; i++) {
+ if((flags >> i) & 1) {
+ if(printed)
+ printf(", ");
+ else
+ printf("\tFilters selected: ");
+ printf("%s", bcj[i].name);
+ printed = 1;
+ }
+ }
+
+ if(!printed)
+ printf("\tNo filters specified\n");
+ else
+ printf("\n");
+
+ return;
+
+failed:
+ fprintf(stderr, "xz: error reading stored compressor options from "
+ "filesystem!\n");
+}
+
+
+/*
+ * This function is called by mksquashfs to initialise the
+ * compressor, before compress() is called.
+ *
+ * This function returns 0 on success, and
+ * -1 on error
+ */
+static int xz_init(void **strm, int block_size, int datablock)
+{
+ int i, j, filters = datablock ? filter_count : 1;
+ struct filter *filter = malloc(filters * sizeof(struct filter));
+ struct xz_stream *stream;
+
+ if(filter == NULL)
+ goto failed;
+
+ stream = *strm = malloc(sizeof(struct xz_stream));
+ if(stream == NULL)
+ goto failed2;
+
+ stream->filter = filter;
+ stream->filters = filters;
+
+ memset(filter, 0, filters * sizeof(struct filter));
+
+ stream->dictionary_size = datablock ? dictionary_size :
+ SQUASHFS_METADATA_SIZE;
+
+ filter[0].filter[0].id = LZMA_FILTER_LZMA2;
+ filter[0].filter[0].options = &stream->opt;
+ filter[0].filter[1].id = LZMA_VLI_UNKNOWN;
+
+ for(i = 0, j = 1; datablock && bcj[i].name; i++) {
+ if(bcj[i].selected) {
+ filter[j].buffer = malloc(block_size);
+ if(filter[j].buffer == NULL)
+ goto failed3;
+ filter[j].filter[0].id = bcj[i].id;
+ filter[j].filter[1].id = LZMA_FILTER_LZMA2;
+ filter[j].filter[1].options = &stream->opt;
+ filter[j].filter[2].id = LZMA_VLI_UNKNOWN;
+ j++;
+ }
+ }
+
+ return 0;
+
+failed3:
+ for(i = 1; i < filters; i++)
+ free(filter[i].buffer);
+ free(stream);
+
+failed2:
+ free(filter);
+
+failed:
+ return -1;
+}
+
+
+static int xz_compress(void *strm, void *dest, void *src, int size,
+ int block_size, int *error)
+{
+ int i;
+ lzma_ret res = 0;
+ struct xz_stream *stream = strm;
+ struct filter *selected = NULL;
+
+ stream->filter[0].buffer = dest;
+
+ for(i = 0; i < stream->filters; i++) {
+ struct filter *filter = &stream->filter[i];
+
+ if(lzma_lzma_preset(&stream->opt, LZMA_PRESET_DEFAULT))
+ goto failed;
+
+ stream->opt.dict_size = stream->dictionary_size;
+
+ filter->length = 0;
+ res = lzma_stream_buffer_encode(filter->filter,
+ LZMA_CHECK_CRC32, NULL, src, size, filter->buffer,
+ &filter->length, block_size);
+
+ if(res == LZMA_OK) {
+ if(!selected || selected->length > filter->length)
+ selected = filter;
+ } else if(res != LZMA_BUF_ERROR)
+ goto failed;
+ }
+
+ if(!selected)
+ /*
+ * Output buffer overflow. Return out of buffer space
+ */
+ return 0;
+
+ if(selected->buffer != dest)
+ memcpy(dest, selected->buffer, selected->length);
+
+ return (int) selected->length;
+
+failed:
+ /*
+ * All other errors return failure, with the compressor
+ * specific error code in *error
+ */
+ *error = res;
+ return -1;
+}
+
+
+static int xz_uncompress(void *dest, void *src, int size, int outsize,
+ int *error)
+{
+ size_t src_pos = 0;
+ size_t dest_pos = 0;
+ uint64_t memlimit = MEMLIMIT;
+
+ lzma_ret res = lzma_stream_buffer_decode(&memlimit, 0, NULL,
+ src, &src_pos, size, dest, &dest_pos, outsize);
+
+ if(res == LZMA_OK && size == (int) src_pos)
+ return (int) dest_pos;
+ else {
+ *error = res;
+ return -1;
+ }
+}
+
+
+static void xz_usage(FILE *stream)
+{
+ fprintf(stream, "\t -Xbcj filter1,filter2,...,filterN\n");
+ fprintf(stream, "\t\tCompress using filter1,filter2,...,filterN in");
+ fprintf(stream, " turn\n\t\t(in addition to no filter), and choose");
+ fprintf(stream, " the best compression.\n");
+ fprintf(stream, "\t\tAvailable filters: x86, arm, armthumb,");
+ fprintf(stream, " powerpc, sparc, ia64\n");
+ fprintf(stream, "\t -Xdict-size <dict-size>\n");
+ fprintf(stream, "\t\tUse <dict-size> as the XZ dictionary size. The");
+ fprintf(stream, " dictionary size\n\t\tcan be specified as a");
+ fprintf(stream, " percentage of the block size, or as an\n\t\t");
+ fprintf(stream, "absolute value. The dictionary size must be less");
+ fprintf(stream, " than or equal\n\t\tto the block size and 8192 bytes");
+ fprintf(stream, " or larger. It must also be\n\t\tstorable in the xz");
+ fprintf(stream, " header as either 2^n or as 2^n+2^(n+1).\n\t\t");
+ fprintf(stream, "Example dict-sizes are 75%%, 50%%, 37.5%%, 25%%, or");
+ fprintf(stream, " 32K, 16K, 8K\n\t\tetc.\n");
+}
+
+
+static int option_args(char *option)
+{
+ if(strcmp(option, "-Xbcj") == 0 ||
+ strcmp(option, "-Xdict-size") == 0)
+ return 1;
+
+ return 0;
+}
+
+
+struct compressor xz_comp_ops = {
+ .init = xz_init,
+ .compress = xz_compress,
+ .uncompress = xz_uncompress,
+ .options = xz_options,
+ .options_post = xz_options_post,
+ .dump_options = xz_dump_options,
+ .extract_options = xz_extract_options,
+ .display_options = xz_display_options,
+ .usage = xz_usage,
+ .option_args = option_args,
+ .id = XZ_COMPRESSION,
+ .name = "xz",
+ .supported = 1
+};
diff --git a/squashfs-tools/xz_wrapper.h b/squashfs-tools/xz_wrapper.h
new file mode 100644
index 0000000..eec4fe2
--- /dev/null
+++ b/squashfs-tools/xz_wrapper.h
@@ -0,0 +1,65 @@
+#ifndef XZ_WRAPPER_H
+#define XZ_WRAPPER_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2010
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * xz_wrapper.h
+ *
+ */
+
+#include "endian_compat.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+extern unsigned int inswap_le32(unsigned int);
+
+#define SQUASHFS_INSWAP_COMP_OPTS(s) { \
+ (s)->dictionary_size = inswap_le32((s)->dictionary_size); \
+ (s)->flags = inswap_le32((s)->flags); \
+}
+#else
+#define SQUASHFS_INSWAP_COMP_OPTS(s)
+#endif
+
+#define MEMLIMIT (32 * 1024 * 1024)
+
+struct bcj {
+ char *name;
+ lzma_vli id;
+ int selected;
+};
+
+struct filter {
+ void *buffer;
+ lzma_filter filter[3];
+ size_t length;
+};
+
+struct xz_stream {
+ struct filter *filter;
+ int filters;
+ int dictionary_size;
+ lzma_options_lzma opt;
+};
+
+struct comp_opts {
+ int dictionary_size;
+ int flags;
+};
+#endif
diff --git a/squashfs-tools/zstd_wrapper.c b/squashfs-tools/zstd_wrapper.c
new file mode 100644
index 0000000..3fa8676
--- /dev/null
+++ b/squashfs-tools/zstd_wrapper.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2017, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * zstd_wrapper.c
+ *
+ * Support for ZSTD compression http://zstd.net
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <zstd.h>
+#include <zstd_errors.h>
+
+#include "squashfs_fs.h"
+#include "zstd_wrapper.h"
+#include "compressor.h"
+
+static int compression_level = ZSTD_DEFAULT_COMPRESSION_LEVEL;
+
+/*
+ * This function is called by the options parsing code in mksquashfs.c
+ * to parse any -X compressor option.
+ *
+ * This function returns:
+ * >=0 (number of additional args parsed) on success
+ * -1 if the option was unrecognised, or
+ * -2 if the option was recognised, but otherwise bad in
+ * some way (e.g. invalid parameter)
+ *
+ * Note: this function sets internal compressor state, but does not
+ * pass back the results of the parsing other than success/failure.
+ * The zstd_dump_options() function is called later to get the options in
+ * a format suitable for writing to the filesystem.
+ */
+static int zstd_options(char *argv[], int argc)
+{
+ if (strcmp(argv[0], "-Xcompression-level") == 0) {
+ if (argc < 2) {
+ fprintf(stderr, "zstd: -Xcompression-level missing "
+ "compression level\n");
+ fprintf(stderr, "zstd: -Xcompression-level it should "
+ "be 1 <= n <= %d\n", ZSTD_maxCLevel());
+ goto failed;
+ }
+
+ compression_level = atoi(argv[1]);
+ if (compression_level < 1 ||
+ compression_level > ZSTD_maxCLevel()) {
+ fprintf(stderr, "zstd: -Xcompression-level invalid, it "
+ "should be 1 <= n <= %d\n", ZSTD_maxCLevel());
+ goto failed;
+ }
+
+ return 1;
+ }
+
+ return -1;
+failed:
+ return -2;
+}
+
+/*
+ * This function is called by mksquashfs to dump the parsed
+ * compressor options in a format suitable for writing to the
+ * compressor options field in the filesystem (stored immediately
+ * after the superblock).
+ *
+ * This function returns a pointer to the compression options structure
+ * to be stored (and the size), or NULL if there are no compression
+ * options.
+ */
+static void *zstd_dump_options(int block_size, int *size)
+{
+ static struct zstd_comp_opts comp_opts;
+
+ /* don't return anything if the options are all default */
+ if (compression_level == ZSTD_DEFAULT_COMPRESSION_LEVEL)
+ return NULL;
+
+ comp_opts.compression_level = compression_level;
+
+ SQUASHFS_INSWAP_COMP_OPTS(&comp_opts);
+
+ *size = sizeof(comp_opts);
+ return &comp_opts;
+}
+
+/*
+ * This function is a helper specifically for the append mode of
+ * mksquashfs. Its purpose is to set the internal compressor state
+ * to the stored compressor options in the passed compressor options
+ * structure.
+ *
+ * In effect this function sets up the compressor options
+ * to the same state they were when the filesystem was originally
+ * generated, this is to ensure on appending, the compressor uses
+ * the same compression options that were used to generate the
+ * original filesystem.
+ *
+ * Note, even if there are no compressor options, this function is still
+ * called with an empty compressor structure (size == 0), to explicitly
+ * set the default options, this is to ensure any user supplied
+ * -X options on the appending mksquashfs command line are over-ridden.
+ *
+ * This function returns 0 on sucessful extraction of options, and -1 on error.
+ */
+static int zstd_extract_options(int block_size, void *buffer, int size)
+{
+ struct zstd_comp_opts *comp_opts = buffer;
+
+ if (size == 0) {
+ /* Set default values */
+ compression_level = ZSTD_DEFAULT_COMPRESSION_LEVEL;
+ return 0;
+ }
+
+ /* we expect a comp_opts structure of sufficient size to be present */
+ if (size < sizeof(*comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ if (comp_opts->compression_level < 1 ||
+ comp_opts->compression_level > ZSTD_maxCLevel()) {
+ fprintf(stderr, "zstd: bad compression level in compression "
+ "options structure\n");
+ goto failed;
+ }
+
+ compression_level = comp_opts->compression_level;
+
+ return 0;
+
+failed:
+ fprintf(stderr, "zstd: error reading stored compressor options from "
+ "filesystem!\n");
+
+ return -1;
+}
+
+static void zstd_display_options(void *buffer, int size)
+{
+ struct zstd_comp_opts *comp_opts = buffer;
+
+ /* we expect a comp_opts structure of sufficient size to be present */
+ if (size < sizeof(*comp_opts))
+ goto failed;
+
+ SQUASHFS_INSWAP_COMP_OPTS(comp_opts);
+
+ if (comp_opts->compression_level < 1 ||
+ comp_opts->compression_level > ZSTD_maxCLevel()) {
+ fprintf(stderr, "zstd: bad compression level in compression "
+ "options structure\n");
+ goto failed;
+ }
+
+ printf("\tcompression-level %d\n", comp_opts->compression_level);
+
+ return;
+
+failed:
+ fprintf(stderr, "zstd: error reading stored compressor options from "
+ "filesystem!\n");
+}
+
+/*
+ * This function is called by mksquashfs to initialise the
+ * compressor, before compress() is called.
+ *
+ * This function returns 0 on success, and -1 on error.
+ */
+static int zstd_init(void **strm, int block_size, int datablock)
+{
+ ZSTD_CCtx *cctx = ZSTD_createCCtx();
+
+ if (!cctx) {
+ fprintf(stderr, "zstd: failed to allocate compression "
+ "context!\n");
+ return -1;
+ }
+
+ *strm = cctx;
+ return 0;
+}
+
+static int zstd_compress(void *strm, void *dest, void *src, int size,
+ int block_size, int *error)
+{
+ const size_t res = ZSTD_compressCCtx((ZSTD_CCtx*)strm, dest, block_size,
+ src, size, compression_level);
+
+ if (ZSTD_isError(res)) {
+ /* FIXME:
+ * zstd does not expose stable error codes. The error enum may
+ * change between versions. Until upstream zstd stablizes the
+ * error codes, we have no way of knowing why the error occurs.
+ * zstd shouldn't fail to compress any input unless there isn't
+ * enough output space. We assume that is the cause and return
+ * the special error code for not enough output space.
+ */
+ return 0;
+ }
+
+ return (int)res;
+}
+
+static int zstd_uncompress(void *dest, void *src, int size, int outsize,
+ int *error)
+{
+ const size_t res = ZSTD_decompress(dest, outsize, src, size);
+
+ if (ZSTD_isError(res)) {
+ fprintf(stderr, "\t%d %d\n", outsize, size);
+
+ *error = (int)ZSTD_getErrorCode(res);
+ return -1;
+ }
+
+ return (int)res;
+}
+
+static void zstd_usage(FILE *stream)
+{
+ fprintf(stream, "\t -Xcompression-level <compression-level>\n");
+ fprintf(stream, "\t\t<compression-level> should be 1 .. %d (default "
+ "%d)\n", ZSTD_maxCLevel(), ZSTD_DEFAULT_COMPRESSION_LEVEL);
+}
+
+
+static int option_args(char *option)
+{
+ if(strcmp(option, "-Xcompression-level") == 0)
+ return 1;
+
+ return 0;
+}
+
+
+struct compressor zstd_comp_ops = {
+ .init = zstd_init,
+ .compress = zstd_compress,
+ .uncompress = zstd_uncompress,
+ .options = zstd_options,
+ .dump_options = zstd_dump_options,
+ .extract_options = zstd_extract_options,
+ .display_options = zstd_display_options,
+ .usage = zstd_usage,
+ .option_args = option_args,
+ .id = ZSTD_COMPRESSION,
+ .name = "zstd",
+ .supported = 1
+};
diff --git a/squashfs-tools/zstd_wrapper.h b/squashfs-tools/zstd_wrapper.h
new file mode 100644
index 0000000..29fb33d
--- /dev/null
+++ b/squashfs-tools/zstd_wrapper.h
@@ -0,0 +1,42 @@
+#ifndef ZSTD_WRAPPER_H
+#define ZSTD_WRAPPER_H
+/*
+ * Squashfs
+ *
+ * Copyright (c) 2017
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * zstd_wrapper.h
+ *
+ */
+
+#include "endian_compat.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+extern unsigned int inswap_le16(unsigned short);
+extern unsigned int inswap_le32(unsigned int);
+
+#define SQUASHFS_INSWAP_COMP_OPTS(s) { \
+ (s)->compression_level = inswap_le32((s)->compression_level); \
+}
+#else
+#define SQUASHFS_INSWAP_COMP_OPTS(s)
+#endif
+
+/* Default compression */
+#define ZSTD_DEFAULT_COMPRESSION_LEVEL 15
+
+struct zstd_comp_opts {
+ int compression_level;
+};
+#endif