summaryrefslogtreecommitdiffstats
path: root/squashfs-tools/unsquashfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'squashfs-tools/unsquashfs.c')
-rw-r--r--squashfs-tools/unsquashfs.c4655
1 files changed, 4655 insertions, 0 deletions
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;
+}