diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | configure.ac | 4 | ||||
-rw-r--r-- | extras/Makemodule.am | 8 | ||||
-rw-r--r-- | extras/browse.c | 520 |
4 files changed, 533 insertions, 0 deletions
@@ -46,3 +46,4 @@ tests/cantrbry.sh mknastyfs mk42sqfs list_files +sqfsbrowse diff --git a/configure.ac b/configure.ac index e5d0a5c..e3c0998 100644 --- a/configure.ac +++ b/configure.ac @@ -223,6 +223,10 @@ AM_COND_IF([WITH_XZ], [libsqfs_dep_mod="$libsqfs_dep_mod liblzma >= 5.0.0"], []) AM_COND_IF([WITH_ZSTD], [libsqfs_dep_mod="$libsqfs_dep_mod libzstd"], []) AC_SUBST([LIBSQFS_DEP_MOD], ["$libsqfs_dep_mod"]) +PKG_CHECK_MODULES(READLINE, [readline], [have_readline="yes"], + [have_readline="no"]) +AM_CONDITIONAL([WITH_READLINE], [test "x$have_readline" = "xyes"]) + ##### additional checks ##### AX_COMPILE_CHECK_SIZEOF(time_t) AX_COMPILE_CHECK_SIZEOF(size_t) diff --git a/extras/Makemodule.am b/extras/Makemodule.am index b7882eb..d3b80f2 100644 --- a/extras/Makemodule.am +++ b/extras/Makemodule.am @@ -7,4 +7,12 @@ mk42sqfs_LDADD = libsquashfs.la list_files_SOURCES = extras/list_files.c list_files_LDADD = libsquashfs.la +if WITH_READLINE +sqfsbrowse_SOURCES = extras/browse.c +sqfsbrowse_CFLAGS = $(AM_CFLAGS) $(READLINE_CFLAGS) +sqfsbrowse_LDADD = libsquashfs.la $(READLINE_LIBS) + +noinst_PROGRAMS += sqfsbrowse +endif + noinst_PROGRAMS += mknastyfs mk42sqfs list_files diff --git a/extras/browse.c b/extras/browse.c new file mode 100644 index 0000000..1179b33 --- /dev/null +++ b/extras/browse.c @@ -0,0 +1,520 @@ +#include "sqfs/compressor.h" +#include "sqfs/dir_reader.h" +#include "sqfs/id_table.h" +#include "sqfs/inode.h" +#include "sqfs/super.h" +#include "sqfs/dir.h" +#include "sqfs/io.h" + +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> +#include <stdio.h> + +#include <readline/readline.h> +#include <readline/history.h> + +static sqfs_dir_reader_t *dr; +static sqfs_super_t super; +static sqfs_inode_generic_t *working_dir; +static sqfs_id_table_t *idtbl; + +static void change_directory(const char *dirname) +{ + sqfs_inode_generic_t *inode; + + if (dirname == NULL || *dirname == '/') { + free(working_dir); + working_dir = NULL; + + sqfs_dir_reader_get_root_inode(dr, &working_dir); + } + + if (dirname != NULL) { + sqfs_dir_reader_find_by_path(dr, working_dir, + dirname, &inode); + + free(working_dir); + working_dir = inode; + } +} + +static void list_directory(const char *dirname) +{ + sqfs_inode_generic_t *root, *inode; + size_t i, max_len, len, col_count; + sqfs_dir_entry_t *ent; + int ret; + + if (dirname == NULL) { + ret = sqfs_dir_reader_open_dir(dr, working_dir); + if (ret) + goto fail_open; + } else if (*dirname == '/') { + sqfs_dir_reader_get_root_inode(dr, &root); + + ret = sqfs_dir_reader_find_by_path(dr, root, dirname, &inode); + free(root); + if (ret) + goto fail_resolve; + + ret = sqfs_dir_reader_open_dir(dr, inode); + free(inode); + if (ret) + goto fail_open; + } else { + ret = sqfs_dir_reader_find_by_path(dr, working_dir, + dirname, &inode); + if (ret) + goto fail_resolve; + + ret = sqfs_dir_reader_open_dir(dr, inode); + free(inode); + if (ret) + goto fail_open; + } + + for (max_len = 0; ; max_len = len > max_len ? len : max_len) { + ret = sqfs_dir_reader_read(dr, &ent); + if (ret > 0) + break; + + if (ret < 0) { + fputs("Error while reading directory list\n", stderr); + break; + } + + len = ent->size + 1; + free(ent); + } + + sqfs_dir_reader_rewind(dr); + + col_count = 79 / (max_len + 1); + col_count = col_count < 1 ? 1 : col_count; + i = 0; + + for (;;) { + ret = sqfs_dir_reader_read(dr, &ent); + if (ret > 0) + break; + + if (ret < 0) { + fputs("Error while reading directory list\n", stderr); + break; + } + + switch (ent->type) { + case SQFS_INODE_DIR: + fputs("\033[01;34m", stdout); + break; + case SQFS_INODE_FILE: + break; + case SQFS_INODE_SLINK: + fputs("\033[01;36m", stdout); + break; + case SQFS_INODE_BDEV: + fputs("\033[22;33m", stdout); + break; + case SQFS_INODE_CDEV: + fputs("\033[01;33m", stdout); + break; + case SQFS_INODE_FIFO: + case SQFS_INODE_SOCKET: + fputs("\033[01;35m", stdout); + break; + } + + len = ent->size + 1; + + printf("%.*s", ent->size + 1, ent->name); + fputs("\033[0m", stdout); + free(ent); + + ++i; + if (i == col_count) { + i = 0; + fputc('\n', stdout); + } else { + while (len++ < max_len) + fputc(' ', stdout); + fputc(' ', stdout); + } + } + + if (i != 0) + fputc('\n', stdout); + + return; +fail_open: + printf("Error opening '%s', error code %d\n", dirname, ret); + return; +fail_resolve: + printf("Error resolving '%s', error code %d\n", dirname, ret); + return; +} + +static void mode_to_str(sqfs_u16 mode, char *p) +{ + *(p++) = (mode & SQFS_INODE_OWNER_R) ? 'r' : '-'; + *(p++) = (mode & SQFS_INODE_OWNER_W) ? 'w' : '-'; + + switch (mode & (SQFS_INODE_OWNER_X | SQFS_INODE_SET_UID)) { + case SQFS_INODE_OWNER_X | SQFS_INODE_SET_UID: *(p++) = 's'; break; + case SQFS_INODE_OWNER_X: *(p++) = 'x'; break; + case SQFS_INODE_SET_UID: *(p++) = 'S'; break; + default: *(p++) = '-'; break; + } + + *(p++) = (mode & SQFS_INODE_GROUP_R) ? 'r' : '-'; + *(p++) = (mode & SQFS_INODE_GROUP_W) ? 'w' : '-'; + + switch (mode & (SQFS_INODE_GROUP_X | SQFS_INODE_SET_GID)) { + case SQFS_INODE_GROUP_X | SQFS_INODE_SET_GID: *(p++) = 's'; break; + case SQFS_INODE_GROUP_X: *(p++) = 'x'; break; + case SQFS_INODE_SET_GID: *(p++) = 'S'; break; + default: *(p++) = '-'; break; + } + + *(p++) = (mode & SQFS_INODE_OTHERS_R) ? 'r' : '-'; + *(p++) = (mode & SQFS_INODE_OTHERS_W) ? 'w' : '-'; + + switch (mode & (SQFS_INODE_OTHERS_X | SQFS_INODE_STICKY)) { + case SQFS_INODE_OTHERS_X | SQFS_INODE_STICKY: *(p++) = 't'; break; + case SQFS_INODE_OTHERS_X: *(p++) = 'x'; break; + case SQFS_INODE_STICKY: *(p++) = 'T'; break; + default: *(p++) = '-'; break; + } + + *p = '\0'; +} + +static void stat_cmd(const char *filename) +{ + sqfs_inode_generic_t *inode, *root; + sqfs_dir_index_t *idx; + sqfs_u32 uid, gid; + const char *type; + char buffer[64]; + time_t timeval; + struct tm *tm; + size_t i; + int ret; + + if (filename == NULL) { + printf("Missing argument: file name\n"); + return; + } + + if (*filename == '/') { + sqfs_dir_reader_get_root_inode(dr, &root); + ret = sqfs_dir_reader_find_by_path(dr, root, filename, &inode); + free(root); + if (ret) + goto fail_resolve; + } else { + ret = sqfs_dir_reader_find_by_path(dr, working_dir, + filename, &inode); + if (ret) + goto fail_resolve; + } + + printf("Stat: %s\n", filename); + + switch (inode->base.type) { + case SQFS_INODE_DIR: type = "directory"; break; + case SQFS_INODE_FILE: type = "file"; break; + case SQFS_INODE_SLINK: type = "symbolic link"; break; + case SQFS_INODE_BDEV: type = "block device"; break; + case SQFS_INODE_CDEV: type = "character device"; break; + case SQFS_INODE_FIFO: type = "named pipe"; break; + case SQFS_INODE_SOCKET: type = "socket"; break; + case SQFS_INODE_EXT_DIR: type = "extended directory"; break; + case SQFS_INODE_EXT_FILE: type = "extended file"; break; + case SQFS_INODE_EXT_SLINK: type = "extended symbolic link"; break; + case SQFS_INODE_EXT_BDEV: type = "extended block device"; break; + case SQFS_INODE_EXT_CDEV: type = "extended character device"; break; + case SQFS_INODE_EXT_FIFO: type = "extended named pipe"; break; + case SQFS_INODE_EXT_SOCKET: type = "extended socket"; break; + default: type = "UNKNOWN"; break; + } + + printf("Type: %s\n", type); + printf("Inode number: %u\n", inode->base.inode_number); + + mode_to_str(inode->base.mode & ~SQFS_INODE_MODE_MASK, buffer); + printf("Access: 0%o/%s\n", inode->base.mode & ~SQFS_INODE_MODE_MASK, + buffer); + + if (sqfs_id_table_index_to_id(idtbl, inode->base.uid_idx, &uid)) { + strcpy(buffer, "-- error --"); + } else { + sprintf(buffer, "%u", uid); + } + + printf("UID: %s (index = %u)\n", buffer, inode->base.uid_idx); + + if (sqfs_id_table_index_to_id(idtbl, inode->base.gid_idx, &gid)) { + strcpy(buffer, "-- error --"); + } else { + sprintf(buffer, "%u", gid); + } + + printf("GID: %s (index = %u)\n", buffer, inode->base.gid_idx); + + timeval = inode->base.mod_time; + tm = gmtime(&timeval); + strftime(buffer, sizeof(buffer), "%a, %d %b %Y %T %z", tm); + printf("Last modified: %s (%u)\n", buffer, inode->base.mod_time); + + switch (inode->base.type) { + case SQFS_INODE_BDEV: + case SQFS_INODE_CDEV: + printf("Hard link count: %u\n", inode->data.dev.nlink); + printf("Device number: %u\n", inode->data.dev.devno); + break; + case SQFS_INODE_EXT_BDEV: + case SQFS_INODE_EXT_CDEV: + printf("Hard link count: %u\n", inode->data.dev_ext.nlink); + printf("Xattr index: 0x%X\n", inode->data.dev_ext.xattr_idx); + printf("Device number: %u\n", inode->data.dev_ext.devno); + break; + case SQFS_INODE_FIFO: + case SQFS_INODE_SOCKET: + printf("Hard link count: %u\n", inode->data.ipc.nlink); + break; + case SQFS_INODE_EXT_FIFO: + case SQFS_INODE_EXT_SOCKET: + printf("Hard link count: %u\n", inode->data.ipc_ext.nlink); + printf("Xattr index: 0x%X\n", inode->data.ipc_ext.xattr_idx); + break; + case SQFS_INODE_SLINK: + printf("Hard link count: %u\n", inode->data.slink.nlink); + printf("Link target: %.*s\n", inode->data.slink.target_size, + inode->slink_target); + break; + case SQFS_INODE_EXT_SLINK: + printf("Hard link count: %u\n", inode->data.slink_ext.nlink); + printf("Xattr index: 0x%X\n", inode->data.slink_ext.xattr_idx); + printf("Link target: %.*s\n", + inode->data.slink_ext.target_size, inode->slink_target); + break; + case SQFS_INODE_FILE: + printf("Blocks start: %u\n", inode->data.file.blocks_start); + printf("Block count: %lu\n", + (unsigned long)inode->num_file_blocks); + printf("Fragment index: 0x%X\n", + inode->data.file.fragment_index); + printf("Fragment offset: %u\n", + inode->data.file.fragment_offset); + printf("File size: %u\n", inode->data.file.file_size); + + for (i = 0; i < inode->num_file_blocks; ++i) { + printf("\tBlock #%lu size: %u (%s)\n", (unsigned long)i, + inode->block_sizes[i] & 0x00FFFFFF, + inode->block_sizes[i] & (1 << 24) ? + "uncompressed" : "compressed"); + } + break; + case SQFS_INODE_EXT_FILE: + printf("Blocks start: %lu\n", + inode->data.file_ext.blocks_start); + printf("Block count: %lu\n", + (unsigned long)inode->num_file_blocks); + printf("Fragment index: 0x%X\n", + inode->data.file_ext.fragment_idx); + printf("Fragment offset: %u\n", + inode->data.file_ext.fragment_offset); + printf("File size: %lu\n", inode->data.file_ext.file_size); + printf("Sparse: %lu\n", inode->data.file_ext.sparse); + printf("Hard link count: %u\n", inode->data.file_ext.nlink); + printf("Xattr index: 0x%X\n", inode->data.file_ext.xattr_idx); + + for (i = 0; i < inode->num_file_blocks; ++i) { + printf("\tBlock #%lu size: %u (%s)\n", (unsigned long)i, + inode->block_sizes[i] & 0x00FFFFFF, + inode->block_sizes[i] & (1 << 24) ? + "compressed" : "uncompressed"); + } + break; + case SQFS_INODE_DIR: + printf("Start block: %u\n", inode->data.dir.start_block); + printf("Offset: %u\n", inode->data.dir.offset); + printf("Hard link count: %u\n", inode->data.dir.nlink); + printf("Size: %u\n", inode->data.dir.size); + printf("Parent inode: %u\n", inode->data.dir.parent_inode); + break; + case SQFS_INODE_EXT_DIR: + printf("Start block: %u\n", inode->data.dir_ext.start_block); + printf("Offset: %u\n", inode->data.dir_ext.offset); + printf("Hard link count: %u\n", inode->data.dir_ext.nlink); + printf("Size: %u\n", inode->data.dir_ext.size); + printf("Parent inode: %u\n", inode->data.dir_ext.parent_inode); + printf("Xattr index: 0x%X\n", inode->data.dir_ext.xattr_idx); + printf("Directory index entries: %u\n", + inode->data.dir_ext.inodex_count); + + if (inode->data.dir_ext.size == 0) + break; + + idx = (sqfs_dir_index_t *)inode->extra; + + for (i = 0; i < inode->data.dir_ext.inodex_count; ++i) { + printf("\tIndex: %u\n", idx->index); + printf("\tStart block: %u\n", idx->start_block); + printf("\tSize: %u\n", idx->size + 1); + printf("\tEntry: %.*s\n\n", idx->size + 1, idx->name); + + idx = (sqfs_dir_index_t *) + ((char *)idx + sizeof(*idx) + idx->size + 1); + } + break; + } + + free(inode); + return; +fail_resolve: + printf("Error resolving '%s', error code %d\n", filename, ret); + return; +} + + +static const struct { + const char *cmd; + void (*handler)(const char *arg); +} commands[] = { + { "ls", list_directory }, + { "cd", change_directory }, + { "stat", stat_cmd }, +}; + +int main(int argc, char **argv) +{ + char *cmd, *arg, *buffer = NULL; + sqfs_compressor_config_t cfg; + sqfs_compressor_t *cmp; + sqfs_file_t *file; + int status = EXIT_FAILURE; + size_t i; + + /* open the SquashFS file we want to read */ + if (argc != 2) { + fputs("Usage: sqfsbrowse <squashfs-file>\n", stderr); + return EXIT_FAILURE; + } + + file = sqfs_open_file(argv[1], SQFS_FILE_OPEN_READ_ONLY); + if (file == NULL) { + perror(argv[1]); + return EXIT_FAILURE; + } + + /* read the super block, create a compressor and + process the compressor options */ + if (sqfs_super_read(&super, file)) { + fprintf(stderr, "%s: error reading super block.\n", argv[1]); + goto out_fd; + } + + if (!sqfs_compressor_exists(super.compression_id)) { + fprintf(stderr, "%s: unknown compressor used.\n", argv[1]); + goto out_fd; + } + + sqfs_compressor_config_init(&cfg, super.compression_id, + super.block_size, + SQFS_COMP_FLAG_UNCOMPRESS); + + cmp = sqfs_compressor_create(&cfg); + if (cmp == NULL) { + fprintf(stderr, "%s: error creating compressor.\n", argv[1]); + goto out_fd; + } + + if (super.flags & SQFS_FLAG_COMPRESSOR_OPTIONS) { + if (cmp->read_options(cmp, file)) { + fprintf(stderr, + "%s: error reading compressor options.\n", + argv[1]); + goto out_cmp; + } + } + + /* Create and read the UID/GID mapping table */ + idtbl = sqfs_id_table_create(); + if (idtbl == NULL) { + fputs("Error creating ID table.\n", stderr); + goto out_cmp; + } + + if (sqfs_id_table_read(idtbl, file, &super, cmp)) { + fprintf(stderr, "%s: error loading ID table.\n", argv[1]); + goto out_id; + } + + /* create a directory reader and scan the entire directory hiearchy */ + dr = sqfs_dir_reader_create(&super, cmp, file); + if (dr == NULL) { + fprintf(stderr, "%s: error creating directory reader.\n", + argv[1]); + goto out_id; + } + + if (sqfs_dir_reader_get_root_inode(dr, &working_dir)) { + fprintf(stderr, "%s: error reading root inode.\n", argv[1]); + goto out; + } + + /* main readline loop */ + for (;;) { + free(buffer); + buffer = readline("$ "); + + if (buffer == NULL) + goto out; + + for (cmd = buffer; isspace(*cmd); ++cmd) + ; + + if (*cmd == '\0') + continue; + + add_history(cmd); + + for (arg = cmd; *arg != '\0' && !isspace(*arg); ++arg) + ; + + if (isspace(*arg)) { + *(arg++) = '\0'; + while (isspace(*arg)) + ++arg; + if (*arg == '\0') + arg = NULL; + } else { + arg = NULL; + } + + for (i = 0; i < sizeof(commands) / sizeof(commands[0]); ++i) { + if (strcmp(commands[i].cmd, cmd) == 0) { + commands[i].handler(arg); + break; + } + } + } + + /* cleanup */ + status = EXIT_SUCCESS; + free(buffer); +out: + if (working_dir != NULL) + free(working_dir); + sqfs_dir_reader_destroy(dr); +out_id: + sqfs_id_table_destroy(idtbl); +out_cmp: + cmp->destroy(cmp); +out_fd: + file->destroy(file); + return status; +} |