diff --git a/libr/fs/meson.build b/libr/fs/meson.build index 2652ca3007..da8f5a0a51 100644 --- a/libr/fs/meson.build +++ b/libr/fs/meson.build @@ -48,6 +48,16 @@ else ] endif +if use_libsqsh + r_fs_sources += [ + 'p/fs_squashfs.c' + ] + r_fs_deps += [ libsqsh_dep ] + if get_option('blob') + r_fs_static_deps += [ libsqsh_static_dep ] + endif +endif + r_fs = library('r_fs', r_fs_sources, include_directories: platform_inc, c_args: r_fs_cflags, diff --git a/libr/fs/p/fs_squashfs.c b/libr/fs/p/fs_squashfs.c new file mode 100644 index 0000000000..3108debcdf --- /dev/null +++ b/libr/fs/p/fs_squashfs.c @@ -0,0 +1,253 @@ +/* radare - BSD2 - Copyright 2024 - Enno Boland */ + +#include +#include + +static char sqsh_to_r_type(enum SqshFileType type) { + switch (type) { + case SQSH_FILE_TYPE_DIRECTORY: + return R_FS_FILE_TYPE_DIRECTORY; + case SQSH_FILE_TYPE_FILE: + return R_FS_FILE_TYPE_REGULAR; + case SQSH_FILE_TYPE_BLOCK: + return R_FS_FILE_TYPE_BLOCK; + case SQSH_FILE_TYPE_CHAR: + return R_FS_FILE_TYPE_CHAR; + case SQSH_FILE_TYPE_FIFO: + case SQSH_FILE_TYPE_SOCKET: + return R_FS_FILE_TYPE_SPECIAL; + default: + return 0; + } +} + +static void prepare_file(RFSFile *fsf, struct SqshFile *file, bool is_symlink) { + fsf->ptr = file; + fsf->size = sqsh_file_size (file); + fsf->time = sqsh_file_modified_time (file); + fsf->type = sqsh_to_r_type (sqsh_file_type (file)); + if (is_symlink) { + fsf->type = toupper (fsf->type); + } +} + +static int fs_sqsh_mapper_init( + struct SqshMapper *mapper, const void *input, size_t *size) { + (void)size; + // takes the input pointer, which is a RFSRoot object here and sets it as user data. + sqsh_mapper_set_user_data (mapper, (void *)input); + return 0; +} +static int fs_sqsh_mapper_map( + const struct SqshMapper *mapper, sqsh_index_t offset, size_t size, + uint8_t **data) { + RFSRoot *root = sqsh_mapper_user_data (mapper); + + ut8 *buf = calloc (size, 1); + if (!buf) { + R_LOG_ERROR ("cannot allocate %d bytes", size); + return -1; + } + int res = root->iob.read_at (root->iob.io, offset, buf, size); + if (res < 1) { + R_LOG_ERROR ("cannot allocate %d bytes", size); + free (buf); + return -1; + } + *data = buf; + return 0; +} +static int fs_sqsh_mapper_cleanup(struct SqshMapper *mapper) { + // Do nothing, cleanup happens in _umount. + return 0; +} + +static int fs_sqsh_mapping_unmap( + const struct SqshMapper *mapper, uint8_t *data, size_t size) { + (void)size; + free (data); + return 0; +} + +const static struct SqshMemoryMapperImpl r_sqsh_mapper = { + // 16 KB block size + .block_size_hint = 16 * 1024, + .init = fs_sqsh_mapper_init, + .map = fs_sqsh_mapper_map, + .unmap = fs_sqsh_mapping_unmap, + .cleanup = fs_sqsh_mapper_cleanup, +}; + +static RFSFile *fs_squashfs_open(RFSRoot *root, const char *path, bool create) { + int err = 0; + struct SqshArchive *archive = root->ptr; + + RFSFile *fsf = r_fs_file_new (root, path); + if (!fsf) { + return NULL; + } + struct SqshFile *file = sqsh_open (archive, path, &err); + if (err < 0) { + R_LOG_ERROR ("squashfs: %s", sqsh_error_str (err)); + r_fs_file_free (fsf); + return NULL; + } + prepare_file (fsf, file, false); + return fsf; +} + +static int fs_squashfs_read(RFSFile *file, ut64 addr, int len) { + int err = 0; + struct SqshFile *sqsh_file = file->ptr; + struct SqshFileReader *reader = sqsh_file_reader_new (sqsh_file, &err); + if (err < 0) { + R_LOG_ERROR ("squashfs: %s", sqsh_error_str (err)); + return -1; + } + err = sqsh_file_reader_advance (reader, addr, len); + if (err < 0) { + R_LOG_ERROR ("squashfs: %s", sqsh_error_str (err)); + sqsh_file_reader_free (reader); + return -1; + } + memcpy (file->data, sqsh_file_reader_data (reader), len); + sqsh_file_reader_free (reader); + return 0; +} + +static void fs_squashfs_close(RFSFile *file) { + sqsh_close (file->ptr); +} + +static int append_file(RList *list, struct SqshDirectoryIterator *entry) { + int err = 0; + enum SqshFileType sqsh_type; + struct SqshFile *file = sqsh_directory_iterator_open_file (entry, &err); + if (err < 0) { + R_LOG_ERROR ("squashfs: %s", sqsh_error_str (err)); + return -1; + } + char *name = sqsh_directory_iterator_name_dup (entry); + RFSFile *fsf = r_fs_file_new (NULL, name); + if (!fsf) { + free (name); + sqsh_close (file); + return -1; + } + + sqsh_type = sqsh_file_type (file); + if (sqsh_type == SQSH_FILE_TYPE_SYMLINK) { + err = sqsh_file_symlink_resolve_all (file); + if (err < 0) { + R_LOG_ERROR ("squashfs: %s", sqsh_error_str (err)); + sqsh_close (file); + return -1; + } + prepare_file (fsf, file, true); + } else { + prepare_file (fsf, file, false); + } + if (fsf->type == 0) { + R_LOG_ERROR ("squashfs: Unknown file type. This is a bug"); + sqsh_close (file); + r_fs_file_free (fsf); + return -1; + } + r_list_append (list, fsf); + + free (name); + sqsh_close (file); + return 0; +} + +static RList *fs_squashfs_dir(RFSRoot *root, const char *path, int view /*ignored*/) { + int err = 0; + struct SqshArchive *archive = root->ptr; + struct SqshFile *file = sqsh_open (archive, path, &err); + if (!file) { + R_LOG_ERROR ("squashfs: %s", sqsh_error_str (err)); + return NULL; + } + struct SqshDirectoryIterator *it = sqsh_directory_iterator_new (file, &err); + if (err < 0) { + sqsh_close (file); + R_LOG_ERROR ("squashfs: %s", sqsh_error_str (err)); + return NULL; + } + RList *list = r_list_new (); + if (!list) { + sqsh_close (file); + return NULL; + } + + while (sqsh_directory_iterator_next (it, &err)) { + int err2 = append_file (list, it); + if (err2 != 0) { + break; + } + } + if (err != 0) { + R_LOG_ERROR ("squashfs: %s", sqsh_error_str (err)); + r_list_free (list); + sqsh_directory_iterator_free (it); + sqsh_close (file); + return NULL; + } + + sqsh_directory_iterator_free (it); + sqsh_close (file); + return list; +} + +static bool fs_squashfs_mount(RFSRoot *root) { + int err = 0; + RIOMap *map = root->iob.map_get_at (root->iob.io, 0); + if (!map) { + R_LOG_ERROR ("no map"); + return NULL; + } + int size = r_itv_size (map->itv); + + const struct SqshConfig cfg = { + .source_mapper = &r_sqsh_mapper, + .archive_offset = root->delta, + .source_size = size, + }; + + struct SqshArchive *archive = sqsh_archive_open (root, &cfg, &err); + if (err < 0) { + R_LOG_ERROR ("squashfs: %s", sqsh_error_str (err)); + return false; + } + root->ptr = archive; + return true; +} + +static void fs_squashfs_umount(RFSRoot *root) { + struct SqshArchive *archive = root->ptr; + sqsh_archive_close (archive); + root->ptr = NULL; +} + +RFSPlugin r_fs_plugin_squashfs = { + .meta = { + .name = "squashfs", + .desc = "squashfs filesystem (libsqsh)", + .license = "MIT", + .author = "Enno Boland", + }, + .open = fs_squashfs_open, + .read = fs_squashfs_read, + .close = fs_squashfs_close, + .dir = fs_squashfs_dir, + .mount = fs_squashfs_mount, + .umount = fs_squashfs_umount, +}; + +#ifndef R2_PLUGIN_INCORE +R_API RLibStruct radare_plugin = { + .type = R_LIB_TYPE_FS, + .data = &r_fs_plugin_squashfs, + .version = R2_VERSION +}; +#endif diff --git a/libr/include/r_fs.h b/libr/include/r_fs.h index 9f4cafb8aa..b08c4904b3 100644 --- a/libr/include/r_fs.h +++ b/libr/include/r_fs.h @@ -206,6 +206,7 @@ extern RFSPlugin r_fs_plugin_posix; extern RFSPlugin r_fs_plugin_r2; extern RFSPlugin r_fs_plugin_reiserfs; extern RFSPlugin r_fs_plugin_sfs; +extern RFSPlugin r_fs_plugin_squashfs; extern RFSPlugin r_fs_plugin_tar; extern RFSPlugin r_fs_plugin_tar; extern RFSPlugin r_fs_plugin_udf; diff --git a/libr/meson.build b/libr/meson.build index 92134934c7..e90bf404e2 100644 --- a/libr/meson.build +++ b/libr/meson.build @@ -21,6 +21,9 @@ parse_plugins = [ 'att2intel' ] if get_option('nogpl') user_plugins += ['nogrub'] endif +if get_option('use_libsqsh') + fs_plugins += ['squashfs'] +endif # This must be splitted in meson.builds for each module # meson split is results in 1 empty element when splitting an empty string :facepalm: diff --git a/meson.build b/meson.build index a61af3f2f4..cce233f982 100644 --- a/meson.build +++ b/meson.build @@ -329,6 +329,16 @@ else message('Not using libuv, thus using fallback server implementations') endif +if get_option('use_libsqsh') + libsqsh_dep = dependency('libsqsh', version: '>=1.4.0', required: false) + use_libsqsh = libsqsh_dep.found() + if not libsqsh_dep.found() + warning('use_libsqsh option was set to true, but libsqsh was not found.') + endif +else + use_libsqsh = false +endif + has_debugger = get_option('debugger') have_ptrace = not ['haiku', 'windows', 'cygwin', 'sunos', 'aix'].contains(host_machine.system()) can_ptrace_wrap = ['linux'].contains(host_machine.system()) diff --git a/meson_options.txt b/meson_options.txt index 403ad22759..d8bca93b16 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -43,6 +43,7 @@ option('use_v35', type: 'boolean', value: false) option('use_sys_openssl', type: 'boolean', value: false) option('static_sys_openssl', type: 'boolean', value: false) option('use_libuv', type: 'boolean', value: false) +option('use_libsqsh', type: 'boolean', value: false) option('use_fork', type: 'boolean', value: true) # host_machine.system() != 'windows') option('sdb_cgen', type: 'boolean', value: false) diff --git a/subprojects/sqsh-tools.wrap b/subprojects/sqsh-tools.wrap new file mode 100644 index 0000000000..50cabbeaa0 --- /dev/null +++ b/subprojects/sqsh-tools.wrap @@ -0,0 +1,8 @@ +[wrap-file] +directory = sqsh-tools-1.4.0 +source_url = https://github.com/Gottox/sqsh-tools/releases/download/v1.4.0/sqsh-tools-1.4.0.tar.gz +source_filename = sqsh-tools-1.4.0.tar.gz +source_hash = 16cfeb1567dc56fcec291bfdac1f823c60841780c6d1e27b1e0f1826cea4640e + +[provide] +libsqsh=libsqsh_dep