/* SPDX-License-Identifier: 0BSD */ /* * browse.c * * Copyright (C) 2020 David Oberhollenzer */ #include "sqfs/data_reader.h" #include "sqfs/compressor.h" #include "sqfs/dir_reader.h" #include "sqfs/id_table.h" #include "sqfs/inode.h" #include "sqfs/block.h" #include "sqfs/super.h" #include "sqfs/error.h" #include "sqfs/dir.h" #include "sqfs/io.h" #include "compat.h" #include #include #include #include #include #include #include static sqfs_dir_reader_t *dr; static sqfs_super_t super; static sqfs_id_table_t *idtbl; static sqfs_data_reader_t *data; static sqfs_u64 working_dir; static char *prompt; static int resolve_ref(const char *path, sqfs_u64 *out) { sqfs_inode_generic_t *inode; sqfs_u64 ref; int ret; ref = (*path == '/') ? super.root_inode_ref : working_dir; ret = sqfs_dir_reader_get_inode(dr, ref, &inode); if (ret == 0) { ret = sqfs_dir_reader_resolve_path(dr, path, inode, &ref); sqfs_free(inode); if (ret == 0) *out = ref; } return ret; } static char *add_prefix(const char *pfx, size_t pfxlen, char *path) { size_t len; char *out; if (path == NULL) { out = calloc(1, pfxlen + 1); if (out == NULL) goto fail; memcpy(out, pfx, pfxlen); } else { len = strlen(path) + 1; out = realloc(path, pfxlen + 1 + len); if (out == NULL) goto fail; memmove(out + pfxlen + 1, out, len); memcpy(out, pfx, pfxlen); out[pfxlen] = '/'; } return out; fail: fputs("out-of-memory\n", stderr); free(path); return NULL; } static char *full_path(void) { sqfs_inode_generic_t *inode = NULL, *pnode = NULL; sqfs_dir_node_t *ent = NULL; char *path = NULL; sqfs_u64 iref = working_dir; int ret; if (iref == super.root_inode_ref) return strdup("/"); ret = sqfs_dir_reader_get_inode(dr, iref, &inode); if (ret) { fprintf(stderr, "Loading inode: %d\n", ret); return NULL; } while (iref != super.root_inode_ref) { sqfs_dir_reader_state_t state; sqfs_u32 parent; sqfs_u64 pref; if (inode->base.type == SQFS_INODE_DIR) { parent = inode->data.dir.parent_inode; } else if (inode->base.type == SQFS_INODE_EXT_DIR) { parent = inode->data.dir_ext.parent_inode; } else { fputs("Inode not a directory??\n", stderr); goto fail; } ret = sqfs_dir_reader_resolve_inum(dr, parent, &pref); if (ret) { fputs("Parent inode not cached!\n", stderr); goto fail; } ret = sqfs_dir_reader_get_inode(dr, pref, &pnode); if (ret) { fprintf(stderr, "Error loading parent inode: %d\n", ret); goto fail; } ret = sqfs_dir_reader_open_dir(dr, pnode, &state, 0); if (ret) { fprintf(stderr, "Error opening parent dir: %d\n", ret); goto fail; } do { sqfs_free(ent); ret = sqfs_dir_reader_read(dr, &state, &ent); } while (ret == 0 && state.ent_ref != iref); if (ret) { fprintf(stderr, "Error finding entry in dir: %d\n", ret); goto fail; } path = add_prefix((const char *)ent->name, ent->size + 1, path); if (path == NULL) goto fail; sqfs_free(ent); sqfs_free(inode); inode = pnode; pnode = NULL; ent = NULL; iref = pref; } path = add_prefix("", 0, path); if (path == NULL) goto fail; sqfs_free(inode); return path; fail: sqfs_free(inode); sqfs_free(pnode); sqfs_free(ent); free(path); return NULL; } /*****************************************************************************/ static void change_directory(const char *dirname) { char *path, *str; const char *end; size_t len; int ret; if (dirname == NULL) { working_dir = super.root_inode_ref; } else { ret = resolve_ref(dirname, &working_dir); if (ret < 0) { fprintf(stderr, "%s: error resolving path: %d\n", dirname, ret); } else if (ret > 0) { fprintf(stderr, "%s: no such file or directory\n", dirname); } } path = full_path(); if (path == NULL) return; end = strrchr(path, '/'); end = end == NULL ? path : (end + 1); len = strlen(end); str = calloc(1, len + 3); if (str == NULL) { free(path); return; } memcpy(str, end, len); str[len] = '$'; str[len + 1] = ' '; free(prompt); free(path); prompt = str; } /*****************************************************************************/ static void list_directory(const char *dirname) { sqfs_dir_reader_state_t state, init_state; size_t i, max_len, len, col_count; sqfs_inode_generic_t *inode; sqfs_dir_node_t *ent; sqfs_u64 ref; int ret; /* Get the directory inode we want to dump and open the directory */ if (dirname == NULL) { ref = working_dir; } else { ret = resolve_ref(dirname, &ref); if (ret < 0) goto fail_resolve; if (ret > 0) goto fail_no_ent; } ret = sqfs_dir_reader_get_inode(dr, ref, &inode); if (ret) goto fail_open; ret = sqfs_dir_reader_open_dir(dr, inode, &state, 0); sqfs_free(inode); if (ret) goto fail_open; /* first pass over the directory to figure out column count/length */ init_state = state; for (max_len = 0; ; max_len = len > max_len ? len : max_len) { ret = sqfs_dir_reader_read(dr, &state, &ent); if (ret > 0) break; if (ret < 0) goto fail_read; len = ent->size + 1; sqfs_free(ent); } col_count = 79 / (max_len + 1); col_count = col_count < 1 ? 1 : col_count; i = 0; state = init_state; /* second pass for printing directory contents */ for (;;) { ret = sqfs_dir_reader_read(dr, &state, &ent); if (ret > 0) break; if (ret < 0) goto fail_read; /* entries always use basic types only */ 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; default: break; } len = ent->size + 1; printf("%.*s", ent->size + 1, ent->name); fputs("\033[0m", stdout); sqfs_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_read: fputs("Error while reading directory list\n", stderr); 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; fail_no_ent: fprintf(stderr, "%s: no such file or directory\n", dirname); 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; sqfs_dir_index_t *idx; sqfs_u32 uid, gid; const char *type; char buffer[64]; time_t timeval; struct tm *tm; sqfs_u64 ref; size_t i; int ret; /* get the inode we are interested in */ if (filename == NULL) { printf("Missing argument: file name\n"); return; } ret = resolve_ref(filename, &ref); if (ret) goto fail_resolve; ret = sqfs_dir_reader_get_inode(dr, ref, &inode); if (ret) goto fail_resolve; /* some basic information */ 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("Stat: %s\n", filename); 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", (unsigned int)inode->base.mode & ~SQFS_INODE_MODE_MASK, buffer); /* resolve and print UID/GID */ 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); /* last modification time stamp */ 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); /* inode type specific data */ 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", (int)inode->data.slink.target_size, (const char *)inode->extra); 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", (int)inode->data.slink_ext.target_size, (const char *)inode->extra); break; case SQFS_INODE_FILE: printf("Blocks start: %u\n", inode->data.file.blocks_start); printf("Block count: %lu\n", (unsigned long)sqfs_inode_get_file_block_count(inode)); 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 < sqfs_inode_get_file_block_count(inode); ++i) { printf("\tBlock #%lu size: %u (%s)\n", (unsigned long)i, SQFS_ON_DISK_BLOCK_SIZE(inode->extra[i]), SQFS_IS_BLOCK_COMPRESSED(inode->extra[i]) ? "compressed" : "uncompressed"); } break; case SQFS_INODE_EXT_FILE: printf("Blocks start: " PRI_U64 "\n", inode->data.file_ext.blocks_start); printf("Block count: %lu\n", (unsigned long)sqfs_inode_get_file_block_count(inode)); 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: " PRI_U64 "\n", inode->data.file_ext.file_size); printf("Sparse: " PRI_U64 "\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 < sqfs_inode_get_file_block_count(inode); ++i) { printf("\tBlock #%lu size: %u (%s)\n", (unsigned long)i, SQFS_ON_DISK_BLOCK_SIZE(inode->extra[i]), SQFS_IS_BLOCK_COMPRESSED(inode->extra[i]) ? "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; /* dump the extended directories fast-lookup index */ for (i = 0; ; ++i) { ret = sqfs_inode_unpack_dir_index_entry(inode, &idx, i); if (ret == SQFS_ERROR_OUT_OF_BOUNDS) break; if (ret < 0) { printf("Error reading index, error code %d\n", ret); break; } 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", (int)(idx->size + 1), idx->name); sqfs_free(idx); } break; default: break; } sqfs_free(inode); return; fail_resolve: printf("Error resolving '%s', error code %d\n", filename, ret); return; } /*****************************************************************************/ static void cat_cmd(const char *filename) { sqfs_inode_generic_t *inode; sqfs_istream_t *stream; char buffer[512]; sqfs_u64 ref; int ret; /* get the inode of the file */ if (filename == NULL) { printf("Missing argument: file name\n"); return; } ret = resolve_ref(filename, &ref); if (ret) { printf("Error resolving '%s', error code %d\n", filename, ret); return; } ret = sqfs_dir_reader_get_inode(dr, ref, &inode); if (ret) { printf("Error reading inode for '%s', error code %d\n", filename, ret); return; } ret = sqfs_data_reader_create_stream(data, inode, filename, &stream); sqfs_free(inode); if (ret) { printf("Opening file '%s', error code %d\n", filename, ret); return; } /* Use the high level read-like function that uses the data-reader * internal cache. There are also other functions for direct block * or fragment access. */ for (;;) { ret = sqfs_istream_read(stream, buffer, sizeof(buffer)); if (ret == 0) break; if (ret < 0) { printf("Error reading from file '%s', error code %d\n", filename, ret); break; } fwrite(buffer, 1, ret, stdout); } sqfs_drop(stream); } /*****************************************************************************/ static void pwd_cmd(const char *filename) { (void)filename; char *path; path = full_path(); if (path != NULL) { printf("%s\n", path); free(path); } } /*****************************************************************************/ static const struct { const char *cmd; void (*handler)(const char *arg); } commands[] = { { "ls", list_directory }, { "cd", change_directory }, { "stat", stat_cmd }, { "cat", cat_cmd }, { "pwd", pwd_cmd }, }; int main(int argc, char **argv) { char *cmd, *arg, *buffer = NULL; int ret, status = EXIT_FAILURE; sqfs_compressor_config_t cfg; sqfs_compressor_t *cmp; sqfs_file_t *file; size_t i; /* open the SquashFS file we want to read */ if (argc != 2) { fputs("Usage: sqfsbrowse \n", stderr); return EXIT_FAILURE; } if (sqfs_file_open(&file, argv[1], SQFS_FILE_OPEN_READ_ONLY)) { fprintf(stderr, "%s: error opening file.\n", 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; } working_dir = super.root_inode_ref; sqfs_compressor_config_init(&cfg, super.compression_id, super.block_size, SQFS_COMP_FLAG_UNCOMPRESS); ret = sqfs_compressor_create(&cfg, &cmp); if (ret != 0) { fprintf(stderr, "%s: error creating compressor: %d.\n", argv[1], ret); goto out_fd; } /* Create and read the UID/GID mapping table */ idtbl = sqfs_id_table_create(0); 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 get the root inode */ dr = sqfs_dir_reader_create(&super, cmp, file, SQFS_DIR_READER_DOT_ENTRIES); if (dr == NULL) { fprintf(stderr, "%s: error creating directory reader.\n", argv[1]); goto out_id; } /* create a data reader */ data = sqfs_data_reader_create(file, super.block_size, cmp, 0); if (data == NULL) { fprintf(stderr, "%s: error creating data reader.\n", argv[1]); goto out_dir; } if (sqfs_data_reader_load_fragment_table(data, &super)) { fprintf(stderr, "%s: error loading fragment table.\n", argv[1]); goto out; } /* main readline loop */ prompt = strdup("$ "); if (prompt == NULL) { fputs("out-of-memory\n", stderr); goto out; } for (;;) { free(buffer); buffer = readline(prompt); 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: free(prompt); sqfs_drop(data); out_dir: sqfs_drop(dr); out_id: sqfs_drop(idtbl); out_cmp: sqfs_drop(cmp); out_fd: sqfs_drop(file); return status; }