diff options
Diffstat (limited to 'squashfs-tools/reader.c')
-rw-r--r-- | squashfs-tools/reader.c | 715 |
1 files changed, 715 insertions, 0 deletions
diff --git a/squashfs-tools/reader.c b/squashfs-tools/reader.c new file mode 100644 index 0000000..5954a76 --- /dev/null +++ b/squashfs-tools/reader.c @@ -0,0 +1,715 @@ +/* + * Create a squashfs filesystem. This is a highly compressed read only + * filesystem. + * + * Copyright (c) 2021, 2022 + * Phillip Lougher <phillip@squashfs.org.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * reader.c + */ + +/* if throttling I/O, time to sleep between reads (in tenths of a second) */ +int sleep_time; + +#define TRUE 1 +#define FALSE 0 + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <dirent.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <pthread.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include "squashfs_fs.h" +#include "mksquashfs.h" +#include "caches-queues-lists.h" +#include "progressbar.h" +#include "mksquashfs_error.h" +#include "pseudo.h" +#include "sort.h" +#include "tar.h" +#include "reader.h" + +static struct readahead **readahead_table = NULL; + +static void sigalrm_handler(int arg) +{ + struct timespec requested_time, remaining; + + requested_time.tv_sec = sleep_time / 10; + requested_time.tv_nsec = (sleep_time % 10) * 100000000; + + nanosleep(&requested_time, &remaining); +} + + +static char *pathname(struct dir_ent *dir_ent) +{ + static char *pathname = NULL; + static int size = ALLOC_SIZE; + + if (dir_ent->nonstandard_pathname) + return dir_ent->nonstandard_pathname; + + if(pathname == NULL) { + pathname = malloc(ALLOC_SIZE); + if(pathname == NULL) + MEM_ERROR(); + } + + for(;;) { + int res = snprintf(pathname, size, "%s/%s", + dir_ent->our_dir->pathname, + dir_ent->source_name ? : dir_ent->name); + + if(res < 0) + BAD_ERROR("snprintf failed in pathname\n"); + else if(res >= size) { + /* + * pathname is too small to contain the result, so + * increase it and try again + */ + size = (res + ALLOC_SIZE) & ~(ALLOC_SIZE - 1); + pathname = realloc(pathname, size); + if(pathname == NULL) + MEM_ERROR(); + } else + break; + } + + return pathname; +} + + +static inline int is_fragment(struct inode_info *inode) +{ + off_t file_size = inode->buf.st_size; + + /* + * If this block is to be compressed differently to the + * fragment compression then it cannot be a fragment + */ + if(inode->noF != noF) + return FALSE; + + return !inode->no_fragments && file_size && (file_size < block_size || + (inode->always_use_fragments && file_size & (block_size - 1))); +} + + +static void put_file_buffer(struct file_buffer *file_buffer) +{ + /* + * Decide where to send the file buffer: + * - compressible non-fragment blocks go to the deflate threads, + * - fragments go to the process fragment threads, + * - all others go directly to the main thread + */ + if(file_buffer->error) { + file_buffer->fragment = 0; + seq_queue_put(to_main, file_buffer); + } else if (file_buffer->file_size == 0) + seq_queue_put(to_main, file_buffer); + else if(file_buffer->fragment) + queue_put(to_process_frag, file_buffer); + else + queue_put(to_deflate, file_buffer); +} + + +static void reader_read_process(struct dir_ent *dir_ent) +{ + long long bytes = 0; + struct inode_info *inode = dir_ent->inode; + struct file_buffer *prev_buffer = NULL, *file_buffer; + int status, byte, res, child; + int file; + + if(inode->read) + return; + + inode->read = TRUE; + + file = pseudo_exec_file(inode->pseudo, &child); + if(!file) { + file_buffer = cache_get_nohash(reader_buffer); + file_buffer->sequence = sequence_count ++; + goto read_err; + } + + while(1) { + file_buffer = cache_get_nohash(reader_buffer); + file_buffer->sequence = sequence_count ++; + file_buffer->noD = inode->noD; + + byte = read_bytes(file, file_buffer->data, block_size); + if(byte == -1) + goto read_err2; + + file_buffer->size = byte; + file_buffer->file_size = -1; + file_buffer->error = FALSE; + file_buffer->fragment = FALSE; + bytes += byte; + + if(byte == 0) + break; + + /* + * Update progress bar size. This is done + * on every block rather than waiting for all blocks to be + * read incase write_file_process() is running in parallel + * with this. Otherwise the current progress bar position + * may get ahead of the progress bar size. + */ + progress_bar_size(1); + + if(prev_buffer) + put_file_buffer(prev_buffer); + prev_buffer = file_buffer; + } + + /* + * Update inode file size now that the size of the dynamic pseudo file + * is known. This is needed for the -info option. + */ + inode->buf.st_size = bytes; + + while(1) { + res = waitpid(child, &status, 0); + if(res != -1) + break; + else if(errno != EINTR) + BAD_ERROR("read process: waitpid returned %d\n", errno); + } + + close(file); + + if(res == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) + goto read_err; + + if(prev_buffer == NULL) + prev_buffer = file_buffer; + else { + cache_block_put(file_buffer); + sequence_count --; + } + prev_buffer->file_size = bytes; + prev_buffer->fragment = is_fragment(inode); + put_file_buffer(prev_buffer); + + return; + +read_err2: + close(file); +read_err: + if(prev_buffer) { + cache_block_put(file_buffer); + sequence_count --; + file_buffer = prev_buffer; + } + file_buffer->error = TRUE; + put_file_buffer(file_buffer); +} + + +static void reader_read_file(struct dir_ent *dir_ent) +{ + struct stat *buf = &dir_ent->inode->buf, buf2; + struct file_buffer *file_buffer; + int blocks, file, res; + long long bytes, read_size; + struct inode_info *inode = dir_ent->inode; + + if(inode->read) + return; + + inode->read = TRUE; +again: + bytes = 0; + read_size = buf->st_size; + blocks = (read_size + block_size - 1) >> block_log; + + while(1) { + file = open(pathname(dir_ent), O_RDONLY); + if(file != -1 || errno != EINTR) + break; + } + + if(file == -1) { + file_buffer = cache_get_nohash(reader_buffer); + file_buffer->sequence = sequence_count ++; + goto read_err2; + } + + do { + file_buffer = cache_get_nohash(reader_buffer); + file_buffer->file_size = read_size; + file_buffer->sequence = sequence_count ++; + file_buffer->noD = inode->noD; + file_buffer->error = FALSE; + + /* + * Always try to read block_size bytes from the file rather + * than expected bytes (which will be less than the block_size + * at the file tail) to check that the file hasn't grown + * since being stated. If it is longer (or shorter) than + * expected, then restat, and try again. Note the special + * case where the file is an exact multiple of the block_size + * is dealt with later. + */ + file_buffer->size = read_bytes(file, file_buffer->data, + block_size); + if(file_buffer->size == -1) + goto read_err; + + bytes += file_buffer->size; + + if(blocks > 1) { + /* non-tail block should be exactly block_size */ + if(file_buffer->size < block_size) + goto restat; + + file_buffer->fragment = FALSE; + put_file_buffer(file_buffer); + } + } while(-- blocks > 0); + + /* Overall size including tail should match */ + if(read_size != bytes) + goto restat; + + if(read_size && read_size % block_size == 0) { + /* + * Special case where we've not tried to read past the end of + * the file. We expect to get EOF, i.e. the file isn't larger + * than we expect. + */ + char buffer; + int res; + + res = read_bytes(file, &buffer, 1); + if(res == -1) + goto read_err; + + if(res != 0) + goto restat; + } + + file_buffer->fragment = is_fragment(inode); + put_file_buffer(file_buffer); + + close(file); + + return; + +restat: + res = fstat(file, &buf2); + if(res == -1) { + ERROR("Cannot stat dir/file %s because %s\n", + pathname(dir_ent), strerror(errno)); + goto read_err; + } + + if(read_size != buf2.st_size) { + close(file); + memcpy(buf, &buf2, sizeof(struct stat)); + file_buffer->error = 2; + put_file_buffer(file_buffer); + goto again; + } +read_err: + close(file); +read_err2: + file_buffer->error = TRUE; + put_file_buffer(file_buffer); +} + + +static void remove_readahead(int index, struct readahead *prev, struct readahead *new) +{ + if(prev) + prev->next = new->next; + else + readahead_table[index] = new->next; +} + + +static void add_readahead(struct readahead *new) +{ + int index = READAHEAD_INDEX(new->start); + + new->next = readahead_table[index]; + readahead_table[index] = new; +} + + +static int get_bytes(char *data, int size) +{ + int res = fread(data, 1, size, stdin); + + if(res == size) + return res; + + return feof(stdin) ? 0 : -1; +} + + +static int get_readahead(struct pseudo_file *file, long long current, + struct file_buffer *file_buffer, int size) +{ + int count = size; + char *dest = file_buffer->data; + + if(readahead_table == NULL) + return -1; + + while(size) { + int index = READAHEAD_INDEX(current); + struct readahead *buffer = readahead_table[index], *prev = NULL; + + for(; buffer; prev = buffer, buffer = buffer->next) { + if(buffer->start <= current && buffer->start + buffer->size > current) { + int offset = READAHEAD_OFFSET(current); + int buffer_offset = READAHEAD_OFFSET(buffer->start); + + /* + * Four posibilities: + * 1. Wanted data is whole of buffer + * 2. Wanted data is at start of buffer + * 3. Wanted data is at end of buffer + * 4. Wanted data is in middle of buffer + */ + if(offset == buffer_offset && size >= buffer->size) { + memcpy(dest, buffer->src, buffer->size); + dest += buffer->size; + size -= buffer->size; + current += buffer->size; + + remove_readahead(index, prev, buffer); + free(buffer); + break; + } else if(offset == buffer_offset) { + memcpy(dest, buffer->src, size); + buffer->start += size; + buffer->src += size; + buffer->size -= size; + + remove_readahead(index, prev, buffer); + add_readahead(buffer); + + goto finished; + } else if(buffer_offset + buffer->size <= offset+ size) { + int bytes = buffer_offset + buffer->size - offset; + + memcpy(dest, buffer->src + offset - buffer_offset, bytes); + buffer->size -= bytes; + dest += bytes; + size -= bytes; + current += bytes; + break; + } else { + struct readahead *left, *right; + int left_size = offset - buffer_offset; + int right_size = buffer->size - (offset + size); + + memcpy(dest, buffer->src + offset - buffer_offset, size); + + /* Split buffer into two */ + left = malloc(sizeof(struct readahead) + left_size); + right = malloc(sizeof(struct readahead) + right_size); + + if(left == NULL || right == NULL) + MEM_ERROR(); + + left->start = buffer->start; + left->size = left_size; + left->src = left->data; + memcpy(left->data, buffer->src, left_size); + + right->start = current + size; + right->size = right_size; + right->src = right->data; + memcpy(right->data, buffer->src + offset + size, right_size); + + remove_readahead(index, prev, buffer); + free(buffer); + + add_readahead(left); + add_readahead(right); + goto finished; + } + } + } + + if(buffer == NULL) + return -1; + } + +finished: + return count; +} + + +static int do_readahead(struct pseudo_file *file, long long current, + struct file_buffer *file_buffer, int size) +{ + int res; + long long readahead = current - file->current; + + if(readahead_table == NULL) { + readahead_table = malloc(READAHEAD_ALLOC); + if(readahead_table == NULL) + MEM_ERROR(); + + memset(readahead_table, 0, READAHEAD_ALLOC); + } + + while(readahead) { + int offset = READAHEAD_OFFSET(file->current); + int bytes = READAHEAD_SIZE - offset < readahead ? READAHEAD_SIZE - offset : readahead; + struct readahead *buffer = malloc(sizeof(struct readahead) + bytes); + + if(buffer == NULL) + MEM_ERROR(); + + res = get_bytes(buffer->data, bytes); + + if(res == -1) { + free(buffer); + return res; + } + + buffer->start = file->current; + buffer->size = bytes; + buffer->src = buffer->data; + add_readahead(buffer); + + file->current += bytes; + readahead -= bytes; + } + + res = get_bytes(file_buffer->data, size); + + if(res != -1) + file->current += size; + + return res; +} + + +static int read_data(struct pseudo_file *file, long long current, + struct file_buffer *file_buffer, int size) +{ + int res; + + if(file->fd != STDIN_FILENO) { + if(current != file->current) { + /* + * File data reading is not in the same order as stored + * in the pseudo file. As this is not stdin, we can + * lseek() to the wanted data + */ + res = lseek(file->fd, current + file->start, SEEK_SET); + if(res == -1) + BAD_ERROR("Lseek on pseudo file %s failed because %s\n", + file->filename, strerror(errno)); + + file->current = current; + } + + res = read_bytes(file->fd, file_buffer->data, size); + + if(res != -1) + file->current += size; + + return res; + } + + /* + * Reading from stdin. Three possibilities + * 1. We are at the current place in stdin, so just read data + * 2. Data we want has already been read and buffered (readahead). + * 3. Data is later in the file, readahead and buffer data to that point + */ + + if(current == file->current) { + res = get_bytes(file_buffer->data, size); + + if(res != -1) + file->current += size; + + return res; + } else if(current < file->current) + return get_readahead(file, current, file_buffer, size); + else + return do_readahead(file, current, file_buffer, size); +} + + +static void reader_read_data(struct dir_ent *dir_ent) +{ + struct file_buffer *file_buffer; + int blocks; + long long bytes, read_size, current; + struct inode_info *inode = dir_ent->inode; + static struct pseudo_file *file = NULL; + + if(inode->read) + return; + + inode->read = TRUE; + bytes = 0; + read_size = inode->pseudo->data->length; + blocks = (read_size + block_size - 1) >> block_log; + + if(inode->pseudo->data->file != file) { + /* Reading the first or a different pseudo file, if + * a different one, first close the previous pseudo + * file, unless it is stdin */ + if(file && file->fd > 0) { + close(file->fd); + file->fd = -1; + } + + file = inode->pseudo->data->file; + + if(file->fd == -1) { + while(1) { + file->fd = open(file->filename, O_RDONLY); + if(file->fd != -1 || errno != EINTR) + break; + } + + if(file->fd == -1) + BAD_ERROR("Could not open pseudo file %s " + "because %s\n", file->filename, + strerror(errno)); + + file->current = -file->start; + } + } + + current = inode->pseudo->data->offset; + + do { + file_buffer = cache_get_nohash(reader_buffer); + file_buffer->file_size = read_size; + file_buffer->sequence = sequence_count ++; + file_buffer->noD = inode->noD; + file_buffer->error = FALSE; + + if(blocks > 1) { + /* non-tail block should be exactly block_size */ + file_buffer->size = read_data(file, current, file_buffer, block_size); + if(file_buffer->size != block_size) + BAD_ERROR("Failed to read pseudo file %s, it appears to be truncated or corrupted\n", file->filename); + + current += file_buffer->size; + bytes += file_buffer->size; + + file_buffer->fragment = FALSE; + put_file_buffer(file_buffer); + } else { + int expected = read_size - bytes; + + file_buffer->size = read_data(file, current, file_buffer, expected); + if(file_buffer->size != expected) + BAD_ERROR("Failed to read pseudo file %s, it appears to be truncated or corrupted\n", file->filename); + + current += file_buffer->size; + } + } while(-- blocks > 0); + + file_buffer->fragment = is_fragment(inode); + put_file_buffer(file_buffer); +} + + +void reader_scan(struct dir_info *dir) +{ + struct dir_ent *dir_ent = dir->list; + + for(; dir_ent; dir_ent = dir_ent->next) { + struct stat *buf = &dir_ent->inode->buf; + + if(dir_ent->inode->root_entry || IS_TARFILE(dir_ent->inode)) + continue; + + if(IS_PSEUDO_PROCESS(dir_ent->inode)) { + reader_read_process(dir_ent); + continue; + } + + if(IS_PSEUDO_DATA(dir_ent->inode)) { + reader_read_data(dir_ent); + continue; + } + + switch(buf->st_mode & S_IFMT) { + case S_IFREG: + reader_read_file(dir_ent); + break; + case S_IFDIR: + reader_scan(dir_ent->dir); + break; + } + } +} + + +void *reader(void *arg) +{ + struct itimerval itimerval; + struct dir_info *dir = queue_get(to_reader); + + if(sleep_time) { + signal(SIGALRM, sigalrm_handler); + + itimerval.it_value.tv_sec = 0; + itimerval.it_value.tv_usec = 100000; + itimerval.it_interval.tv_sec = 10; + itimerval.it_interval.tv_usec = 0; + setitimer(ITIMER_REAL, &itimerval, NULL); + } + + if(tarfile) { + read_tar_file(); + dir = queue_get(to_reader); + } + + if(!sorted) + reader_scan(dir); + else{ + int i; + struct priority_entry *entry; + + for(i = 65535; i >= 0; i--) + for(entry = priority_list[i]; entry; + entry = entry->next) + reader_read_file(entry->dir); + } + + pthread_exit(NULL); +} |