From 0101ed8a241767e5c1b1f290e32e557ccc604979 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sun, 16 Mar 2025 02:37:31 +0200 Subject: [PATCH] implement new loader --- Makefile | 41 +- mod/elf_loader.d.lua | 6 + mod/fs.d.lua | 6 +- src/entry/{build.lua => bundle.lua} | 0 src/entry/dump_loader.lua | 36 + src/entry/fmt.c | 15 +- src/entry/fs.c | 4 +- src/entry/http.c | 2 + src/entry/main.c | 14 +- src/entry/zlib.c | 1 + src/loader/entry.c | 204 ++++++ src/loader/loader.h | 16 + src/loader/offset.c | 228 ++++++ src/loader/slimfs.c | 1019 +++++++++++++++++++++++++++ src/loader/slimfs.h | 25 + src/main/cli/pack.lua | 58 +- src/main/ostree.lua | 2 +- 17 files changed, 1594 insertions(+), 83 deletions(-) create mode 100644 mod/elf_loader.d.lua rename src/entry/{build.lua => bundle.lua} (100%) create mode 100644 src/entry/dump_loader.lua create mode 100644 src/loader/entry.c create mode 100644 src/loader/loader.h create mode 100644 src/loader/offset.c create mode 100644 src/loader/slimfs.c create mode 100644 src/loader/slimfs.h diff --git a/Makefile b/Makefile index fe15ce6..b60a4d1 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,25 @@ -# requires lua, curl and zlib (in the future, lua will be statically linked) -libraries := -llua -lcurl -lz -flags := -Wall # lua is required in the build process, in order to compile the bytecode lua := lua cc := gcc +bundle = $(src_entry_dir)/bundle.lua +dump_loader = $(src_entry_dir)/dump_loader.lua + +all_flags += -W -Wall -fPIC -std=gnu11 + +# requires lua, curl and zlib (in the future, lua will be statically linked) +main_libraries += -llua -lcurl -lz +main_cflags += $(all_flags) + +loader_libraries += -lfuse -lpthread -lsquashfuse +loader_cflags += $(all_flags) +loader_cflags += -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=29 +loader_cflags += -I/usr/include/fuse dst_dir = dst src_main_dir = src/main src_entry_dir = src/entry +src_loader_dir = src/loader # Uncomment to get debugging symbols # flags := -g @@ -18,21 +29,27 @@ main_sources += $(wildcard $(src_main_dir)/cli/*.lua) main_sources += $(wildcard $(src_main_dir)/formats/*.lua) main_sources += $(wildcard $(src_main_dir)/util/*.lua) -c_sources = $(wildcard $(src_entry_dir)/*.c) +entry_sources = $(wildcard $(src_entry_dir)/*.c) -build_entry = $(src_entry_dir)/build.lua +loader_sources = $(wildcard $(src_loader_dir)/*.c) -target = $(dst_dir)/slimpack -bytecode_target = $(dst_dir)/bytecode.h +main_target = $(dst_dir)/slimpack +loader_target = $(dst_dir)/loader +bytecode_h_target = $(dst_dir)/bytecode.h +loader_h_target = $(dst_dir)/loader.h .PHONY: build -build: $(target) +build: $(main_target) $(dst_dir): mkdir -p $@ -$(bytecode_target): $(main_sources) $(dst_dir) $(build_entry) - $(lua) $(build_entry) $(src_main_dir) $@ $(main_sources) -$(target): $(c_sources) $(bytecode_target) $(dst_dir) - $(cc) $(flags) $(libraries) -include $(bytecode_target) $(c_sources) -o $@ +$(bytecode_h_target): $(dst_dir) $(bundle) $(main_sources) + $(lua) $(bundle) $(src_main_dir) $@ $(main_sources) +$(loader_h_target): $(dst_dir) $(dump_loader) $(loader_target) + $(lua) $(dump_loader) $(loader_target) $@ +$(loader_target): $(dst_dir) $(loader_sources) + $(cc) $(loader_cflags) $(loader_libraries) $(loader_sources) -o $@ +$(main_target): $(dst_dir) $(bytecode_h_target) $(loader_h_target) $(entry_sources) $(main_sources) + $(cc) $(main_cflags) $(main_libraries) -include $(bytecode_h_target) -include $(loader_h_target) $(entry_sources) -o $@ diff --git a/mod/elf_loader.d.lua b/mod/elf_loader.d.lua new file mode 100644 index 0000000..24e8d1c --- /dev/null +++ b/mod/elf_loader.d.lua @@ -0,0 +1,6 @@ +--- @meta elf_loader + +--- @type string +local res; + +return res; diff --git a/mod/fs.d.lua b/mod/fs.d.lua index 2d3505c..0abbb97 100644 --- a/mod/fs.d.lua +++ b/mod/fs.d.lua @@ -8,11 +8,11 @@ local fs = {}; --- @return string? err function fs.mkdir(path, recursive) end ---- @param src string ---- @param dst string +--- @param target string +--- @param name string --- @return boolean? ok --- @return string? err -function fs.symlink(src, dst) end +function fs.symlink(target, name) end --- @param path string --- @param mode string | integer diff --git a/src/entry/build.lua b/src/entry/bundle.lua similarity index 100% rename from src/entry/build.lua rename to src/entry/bundle.lua diff --git a/src/entry/dump_loader.lua b/src/entry/dump_loader.lua new file mode 100644 index 0000000..22a5c95 --- /dev/null +++ b/src/entry/dump_loader.lua @@ -0,0 +1,36 @@ +local function escape(str) + return "\"" .. str:gsub(".", function (c) + local b = string.byte(c); + + if c == "\n" then + return "\\n"; + elseif c == "\\" then + return "\\\\"; + elseif c == "\"" then + return "\\\""; + elseif b >= 32 and b <= 126 then + return c; + else + return ("\\%.3o"):format(b); + end + end) .. "\""; +end + +local function main(in_f, out_f) + local f = assert(io.open(out_f, "w")); + f:write "#include \n"; + f:write "#define LOADER loader_bytecode\n"; + f:write "#define LOADER_SIZE (sizeof loader_bytecode - 1)\n"; + f:write "static const uint8_t loader_bytecode[] = "; + + for part in io.lines(in_f, 64) do + f:write(escape(part)); + f:write"\n"; + end + + f:write ";"; + + assert(f:close()); +end + +main(...); diff --git a/src/entry/fmt.c b/src/entry/fmt.c index a111c74..ae9ba4d 100644 --- a/src/entry/fmt.c +++ b/src/entry/fmt.c @@ -77,7 +77,7 @@ static int fmt_parse_width(size_t *i, const char *raw, size_t raw_size) { static fmt_code_t fmt_parse_fmt(lua_State *ctx, const char *raw, size_t raw_size) { if (raw == NULL || *raw == 0) return FMT_BAD_ARGS; - if (raw_size == -1) raw_size = strlen(raw); + if (raw_size == SIZE_MAX) raw_size = strlen(raw); fmt_segment_t segments[raw_size]; size_t segments_n = 0; @@ -278,7 +278,7 @@ static void write_buff_fit(write_buff_t *buff) { static bool fmt_read_uint8(const uint8_t *raw, size_t *i, size_t size, uint8_t *res) { - if ((*i) + 1 > size || *i < 0) return false; + if ((*i) + 1 > size) return false; *res = raw[(*i)++]; @@ -289,7 +289,7 @@ static bool fmt_read_int8(const uint8_t *raw, size_t *i, size_t size, int8_t *re } static bool fmt_read_uint16(const uint8_t *raw, size_t *i, size_t size, uint16_t *res, bool little_endian) { - if ((*i) + 2 > size || *i < 0) return false; + if ((*i) + 2 > size) return false; uint16_t a = raw[(*i)++]; uint16_t b = raw[(*i)++]; @@ -304,7 +304,7 @@ static bool fmt_read_int16(const uint8_t *raw, size_t *i, size_t size, int16_t * } static bool fmt_read_uint32(const uint8_t *raw, size_t *i, size_t size, uint32_t *res, bool little_endian) { - if ((*i) + 4 > size || *i < 0) return false; + if ((*i) + 4 > size) return false; uint8_t a = raw[(*i)++]; uint8_t b = raw[(*i)++]; @@ -321,7 +321,7 @@ static bool fmt_read_int32(const uint8_t *raw, size_t *i, size_t size, int32_t * } static bool fmt_read_uint64(const uint8_t *raw, size_t *i, size_t size, uint64_t *res, bool little_endian) { - if ((*i) + 8 > size || *i < 0) return false; + if ((*i) + 8 > size) return false; uint64_t a = raw[(*i)++]; uint64_t b = raw[(*i)++]; @@ -342,7 +342,7 @@ static bool fmt_read_int64(const uint8_t *raw, size_t *i, size_t size, int64_t * } static bool fmt_read_float32(const uint8_t *raw, size_t *i, size_t size, float *res, bool little_endian) { - if ((*i) + 4 > size || *i < 0) return false; + if ((*i) + 4 > size) return false; uint8_t a = raw[(*i)++]; uint8_t b = raw[(*i)++]; @@ -356,7 +356,7 @@ static bool fmt_read_float32(const uint8_t *raw, size_t *i, size_t size, float * return true; } static bool fmt_read_float64(const uint8_t *raw, size_t *i, size_t size, double *res, bool little_endian) { - if ((*i) + 8 > size || *i < 0) return false; + if ((*i) + 8 > size) return false; uint8_t a = raw[(*i)++]; uint8_t b = raw[(*i)++]; @@ -411,7 +411,6 @@ static bool fmt_read_string(lua_State *ctx, fmt_segment_t segment, const uint8_t fprintf(stderr, "%lu %lu", len, *i); if ((*i) + len > size) return false; - if (*i < 0) return false; char data[len]; memcpy(data, raw + (*i), len); diff --git a/src/entry/fs.c b/src/entry/fs.c index e78777c..de5b745 100644 --- a/src/entry/fs.c +++ b/src/entry/fs.c @@ -18,7 +18,7 @@ static const char *fs_check_path(lua_State *ctx, int i, size_t *psize) { size_t n; - const char *path = luaL_checklstring(ctx, 1, &n); + const char *path = luaL_checklstring(ctx, i, &n); size_t real_len = strlen(path); if (n != real_len) { @@ -71,7 +71,7 @@ static int lib_fs_symlink(lua_State *ctx) { if (symlink(src, dst) < 0) { lua_pushnil(ctx); - lua_pushfstring(ctx, "failed to chmod: %s", strerror(errno)); + lua_pushfstring(ctx, "failed to symlink: %s", strerror(errno)); return 2; } diff --git a/src/entry/http.c b/src/entry/http.c index a12b059..9668eed 100644 --- a/src/entry/http.c +++ b/src/entry/http.c @@ -14,6 +14,8 @@ typedef struct { } http_body_buff_t; static size_t body_writer(char *ptr, size_t _, size_t size, void *pbuff) { + (void)_; + http_body_buff_t *buff = pbuff; size_t new_cap = buff->cap; diff --git a/src/entry/main.c b/src/entry/main.c index 71f2de9..a286f08 100644 --- a/src/entry/main.c +++ b/src/entry/main.c @@ -14,17 +14,29 @@ #define BYTECODE_SIZE (sizeof entry_bytecode - 1) #endif +#ifndef LOADER + static uint8_t loader_bytecode[] = ""; + #define LOADER loader_bytecode + #define LOADER_SIZE (sizeof loader_bytecode - 1) +#endif + static char err_bytecode[] = "local err = ...;" "local trace = debug.traceback(nil, 2):match \"^[^\\n]+\\n(.+)$\"" "print(table.concat { \"unhandled error: \", err, \"\\n\", trace })"; +static int elf_loader_open_lib(lua_State *ctx) { + lua_pushlstring(ctx, (const char*)LOADER, LOADER_SIZE); + return 1; +} + static void load_modules(lua_State *ctx) { luaL_openlibs(ctx); luaL_requiref(ctx, "http", http_open_lib, false); luaL_requiref(ctx, "fmt", fmt_open_lib, false); luaL_requiref(ctx, "zlib", zlib_open_lib, false); luaL_requiref(ctx, "fs", fs_open_lib, false); + luaL_requiref(ctx, "elf_loader", elf_loader_open_lib, false); } int main(int argc, const char **argv) { @@ -42,7 +54,7 @@ int main(int argc, const char **argv) { return 1; } - for (size_t i = 1; i < argc; i++) { + for (int i = 1; i < argc; i++) { lua_pushstring(ctx, argv[i]); } diff --git a/src/entry/zlib.c b/src/entry/zlib.c index 8009b7d..440f5ce 100644 --- a/src/entry/zlib.c +++ b/src/entry/zlib.c @@ -39,6 +39,7 @@ static int lib_decompress_iter(lua_State *ctx) { case Z_STREAM_END: inflateEnd(&stream->stream); stream->finalized = true; + // fall through case Z_OK: { lua_pushlstring(ctx, (const char*)stream->buff, sizeof stream->buff - stream->stream.avail_out); stream->processed = size - stream->stream.avail_in; diff --git a/src/loader/entry.c b/src/loader/entry.c new file mode 100644 index 0000000..48e7fec --- /dev/null +++ b/src/loader/entry.c @@ -0,0 +1,204 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "loader.h" +#include "slimfs.h" + +typedef struct { + char root_dir[PATH_MAX]; + struct fuse *fuse; + bool freed; +} *fuse_pthread_data_t; + +static slimfs_t fs; +static char *mount_dir; + +static void *fuse_pthread_entry(void *pdata) { + slimfs_t fs = pdata; + + if (fuse_loop(fs->fuse) < 0) err(1, "Couldn't start fuse loop"); + + return NULL; +} + +static slimfs_t slimfs_mount(const char *target_dir, const char *root_dir, const char *img_path, size_t img_offset) { + slimfs_t fs = slimfs_new(1, (char*[]){ "", NULL }, target_dir, root_dir, img_path, img_offset); + if (fs == NULL) return NULL; + + pthread_t thread; + pthread_create(&thread, NULL, fuse_pthread_entry, fs); + + return fs; +} + +static void handle_close() { + fprintf(stderr, "\nCTRL+C detected, closing SlimFS...\n"); + slimfs_free(fs); + rmdir(mount_dir); + exit(0); +} + +static int write_flag(const char *file, const char *format, ...) { + int fd = open(file, O_WRONLY); + if (fd < 0) { + warn("Failed to write to %s", file); + return -1; + } + + va_list list; + + va_start(list, format); + char buff[vsnprintf(NULL, 0, format, list) + 1]; + va_end(list); + + va_start(list, format); + vsnprintf(buff, sizeof buff, format, list); + va_end(list); + + int n = write(fd, buff, sizeof buff - 1); + if (n < 0 || (size_t)n != sizeof buff - 1) { + warn("Failed to write to %s", file); + return -1; + } + + close(fd); + return 0; +} + +static int err_mount(const char *name) { + warn("Failed to setup virtual mount for %s", name); + return 127; +} + +int slimpak_isolate(const char *root_dir, char **argv) { + pid_t pid = fork(); + if (pid < 0) err(EXIT_EXECERROR, "fork error"); + else if (pid == 0) { + int uid = geteuid(); + int gid = getegid(); + + errno = 0; + + // Child is responsible for running the actual command + if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0) { + warn("Failed to containerize"); + exit(127); + } + + if (write_flag("/proc/self/uid_map", "%d %d 1\n", uid, uid) < 0) exit(127); + if (write_flag("/proc/self/setgroups", "deny\n") < 0) exit(127); + if (write_flag("/proc/self/gid_map", "%d %d 1\n", gid, gid) < 0) exit(127); + + char *cwd = getcwd(NULL, 0); + chdir(root_dir); + + // if (mount(NULL, "/", NULL, MS_REC | MS_SILENT | MS_SLAVE, NULL) < 0) return err_mount("root"); + if (mount("/dev", "dev", NULL, MS_REC | MS_SILENT | MS_BIND, NULL) < 0) return err_mount("/dev"); + if (mount("/proc", "proc", NULL, MS_REC | MS_SILENT | MS_BIND, NULL) < 0) return err_mount("/proc"); + if (mount("/run", "run", NULL, MS_REC | MS_SILENT | MS_BIND, NULL) < 0) return err_mount("/run"); + + if (chroot(root_dir) < 0) { + warn("Failed to chroot into virtual root"); + exit(127); + } + + chdir(cwd); + free(cwd); + + execv(argv[0], argv); + + err(127, "Failed to execute command"); + } + else { + int status; + if (waitpid(pid, &status, 0) < 0) { + warn("Failed to get child status"); + return -1; + } + + if (WIFEXITED(status)) return (uint16_t)WEXITSTATUS(status); + else return -1; + } + + return 0; +} + +int main(int argc, char **argv) { + const char *self_path = "/proc/self/exe"; + const char *temp_base = "/tmp"; + const char *slimpack_arg = NULL; + + size_t offset = slimpack_get_offset(self_path); + + if (argc == 2 && argv[1][0] == '-' && argv[1][1] == '-') { + slimpack_arg = (const char*)argv[1] + 2; + } + + if (slimpack_arg && !strcmp(slimpack_arg, "slimpack-offset")) { + printf("%zu\n", offset); + return 0; + } + + mount_dir = malloc(strlen(temp_base) + 19 + 1); + strcpy(mount_dir, temp_base); + strcat(mount_dir, "/.slimpack.XXXXXXXX"); + + if (!mkdtemp(mount_dir)) err(EXIT_EXECERROR, "create mount dir error"); + + fs = slimfs_mount(mount_dir, "/", self_path, offset); + if (fs == NULL) errx(1, "Couldn't mount SlimFS"); + + signal(SIGINT, handle_close); + + int status; + + if (slimpack_arg && !strcmp(slimpack_arg, "slimpack-mount")) { + fprintf(stderr, "The mountpoint is at %s. Press Ctrl+D when you're ready\n", mount_dir); + + char buff[4096]; + + while (read(STDIN_FILENO, buff, sizeof buff) != 0); + + slimfs_free(fs); + rmdir(mount_dir); + + return 0; + } + + if (slimpack_arg && !strcmp(slimpack_arg, "slimpack-shell")) { + status = slimpak_isolate((const char*)mount_dir, (char*[]) { "/bin/bash", NULL }); + } + else { + argv[0] = "/entry"; + status = slimpak_isolate((const char*)mount_dir, argv); + } + + slimfs_free(fs); + rmdir(mount_dir); + + if (status < 0) { + err(127, "Failed to start isolated process (status %d)", status); + } + else { + return status; + } +} diff --git a/src/loader/loader.h b/src/loader/loader.h new file mode 100644 index 0000000..2387068 --- /dev/null +++ b/src/loader/loader.h @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +// Exit status to use when launching an AppImage fails. +// For applications that assign meanings to exit status codes (e.g. rsync), +// we avoid "cluttering" pre-defined exit status codes by using 127 which +// is known to alias an application exit status and also known as launcher +// error, see SYSTEM(3POSIX). +#define EXIT_EXECERROR 127 + +ssize_t slimpack_get_offset(const char *executable); +int slimpak_isolate(const char *root_dir, char **argv); +int slimpak_mount(const char *executable, const char *work_dir, const char *root_dir); +int slimpak_unmount(const char *work_dir, const char *root_dir); diff --git a/src/loader/offset.c b/src/loader/offset.c new file mode 100644 index 0000000..23d4198 --- /dev/null +++ b/src/loader/offset.c @@ -0,0 +1,228 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "loader.h" + +#define EI_NIDENT 16 + +#define ELFCLASS32 1 +#define ELFDATA2LSB 1 +#define ELFDATA2MSB 2 +#define ELFCLASS64 2 +#define EI_CLASS 4 +#define EI_DATA 5 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + #define ELFDATANATIVE ELFDATA2LSB +#elif __BYTE_ORDER == __BIG_ENDIAN + #define ELFDATANATIVE ELFDATA2MSB +#else + #error "Unknown machine endian" +#endif + +typedef struct { + unsigned char e_ident[EI_NIDENT]; + uint16_t e_type; + uint16_t e_machine; + uint32_t e_version; + uint32_t e_entry; /* Entry point */ + uint32_t e_phoff; + uint32_t e_shoff; + uint32_t e_flags; + uint16_t e_ehsize; + uint16_t e_phentsize; + uint16_t e_phnum; + uint16_t e_shentsize; + uint16_t e_shnum; + uint16_t e_shstrndx; +} elf32_hdr_t; + +typedef struct { + uint32_t sh_name; + uint32_t sh_type; + uint32_t sh_flags; + uint32_t sh_addr; + uint32_t sh_offset; + uint32_t sh_size; + uint32_t sh_link; + uint32_t sh_info; + uint32_t sh_addralign; + uint32_t sh_entsize; +} elf32_shdr_t; + +typedef struct { + unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */ + uint16_t e_type; + uint16_t e_machine; + uint32_t e_version; + uint64_t e_entry; /* Entry point virtual address */ + uint64_t e_phoff; /* Program header table file offset */ + uint64_t e_shoff; /* Section header table file offset */ + uint32_t e_flags; + uint16_t e_ehsize; + uint16_t e_phentsize; + uint16_t e_phnum; + uint16_t e_shentsize; + uint16_t e_shnum; + uint16_t e_shstrndx; +} elf64_hdr_t; + +typedef struct { + uint32_t sh_name; /* Section name, index in string tbl */ + uint32_t sh_type; /* Type of section */ + uint64_t sh_flags; /* Miscellaneous section attributes */ + uint64_t sh_addr; /* Section virtual addr at execution */ + uint64_t sh_offset; /* Section file offset */ + uint64_t sh_size; /* Size of section in bytes */ + uint32_t sh_link; /* Index of another section */ + uint32_t sh_info; /* Additional section information */ + uint64_t sh_addralign; /* Section alignment */ + uint64_t sh_entsize; /* Entry size if section holds table */ +} elf64_shdr_t; + +/* Note header in a PT_NOTE section */ +typedef struct elf32_note { + uint32_t n_namesz; /* Name size */ + uint32_t n_descsz; /* Content size */ + uint32_t n_type; /* Content type */ +} Elf32_Nhdr; + +typedef Elf32_Nhdr Elf_Nhdr; + +static uint16_t file16_to_cpu(uint8_t ident[EI_NIDENT], uint16_t val) { + if (ident[EI_DATA] != ELFDATANATIVE) { + uint16_t a = val & 0xFF; + uint16_t b = (val >> 8) & 0xFF; + val = a << 8 | b; + } + + return val; +} +static uint32_t file32_to_cpu(uint8_t ident[EI_NIDENT], uint32_t val) { + if (ident[EI_DATA] != ELFDATANATIVE) { + uint32_t a = val & 0xFF; + uint32_t b = (val >> 8) & 0xFF; + uint32_t c = (val >> 16) & 0xFF; + uint32_t d = (val >> 24) & 0xFF; + val = a << 24 | b << 16 | c << 8 | d; + } + return val; +} +static uint64_t file64_to_cpu(uint8_t ident[EI_NIDENT], uint64_t val) { + if (ident[EI_DATA] != ELFDATANATIVE) { + uint64_t a = val & 0xFF; + uint64_t b = (val >> 8) & 0xFF; + uint64_t c = (val >> 16) & 0xFF; + uint64_t d = (val >> 24) & 0xFF; + uint64_t e = (val >> 32) & 0xFF; + uint64_t f = (val >> 40) & 0xFF; + uint64_t g = (val >> 48) & 0xFF; + uint64_t h = (val >> 56) & 0xFF; + val = a << 56 | b << 48 | c << 40 | d << 32 | e << 24 | f << 16 | g << 8 | h; + } + return val; +} + +static ssize_t read_elf32_size(uint8_t ident[EI_NIDENT], FILE *fd) { + ssize_t ret; + + fseek(fd, 0, SEEK_SET); + + elf32_hdr_t header; + ret = fread(&header, 1, sizeof(header), fd); + if (ret < 0 || ret != sizeof header) { + warn("Read of ELF header failed"); + return -1; + } + + size_t sh_offset = file32_to_cpu(ident, header.e_shoff); + size_t sh_entry_size = file16_to_cpu(ident, header.e_shentsize); + size_t sh_entry_n = file16_to_cpu(ident, header.e_shnum); + + off_t last_shdr_offset = sh_offset + (sh_entry_size * (sh_entry_n - 1)); + fseek(fd, last_shdr_offset, SEEK_SET); + + elf32_shdr_t sect_header; + ret = fread(§_header, 1, sizeof(sect_header), fd); + if (ret < 0 || (size_t) ret != sizeof(sect_header)) { + warn("Read of ELF section header failed"); + return -1; + } + + /* ELF ends either with the table of section headers (SHT) or with a section. */ + off_t sh_end = sh_offset + (sh_entry_size * sh_entry_n); + off_t s_end = file64_to_cpu(ident, sect_header.sh_offset) + file64_to_cpu(ident, sect_header.sh_size); + + return sh_end > s_end ? sh_end : s_end; +} +static ssize_t read_elf64_size(uint8_t ident[EI_NIDENT], FILE *fd) { + off_t ret; + + fseek(fd, 0, SEEK_SET); + + elf64_hdr_t header; + ret = fread(&header, 1, sizeof(header), fd); + if (ret < 0 || (size_t) ret != sizeof(header)) { + warn("Read of ELF header failed"); + return -1; + } + + size_t sh_offset = file64_to_cpu(ident, header.e_shoff); + size_t sh_entry_size = file16_to_cpu(ident, header.e_shentsize); + size_t sh_entry_n = file16_to_cpu(ident, header.e_shnum); + + off_t last_shdr_offset = sh_offset + (sh_entry_size * (sh_entry_n - 1)); + fseek(fd, last_shdr_offset, SEEK_SET); + + elf64_shdr_t sect_header; + ret = fread(§_header, 1, sizeof(sect_header), fd); + if (ret < 0 || ret != sizeof(sect_header)) { + warn("Read of ELF section header failed"); + return -1; + } + + /* ELF ends either with the table of section headers (SHT) or with a section. */ + off_t sht_end = sh_offset + (sh_entry_size * sh_entry_n); + off_t s_end = file64_to_cpu(ident, sect_header.sh_offset) + file64_to_cpu(ident, sect_header.sh_size); + return sht_end > s_end ? sht_end : s_end; +} + +ssize_t slimpack_get_offset(const char *executable) { + FILE *fd = fopen(executable, "rb"); + if (fd == NULL) { + warn("Cannot open executable"); + return -1; + } + + uint8_t ident[EI_NIDENT]; + + off_t ret = fread(ident, 1, EI_NIDENT, fd); + if (ret != EI_NIDENT) { + warn("Read of e_ident failed"); + return -1; + } + + if (ident[EI_DATA] != ELFDATA2LSB && ident[EI_DATA] != ELFDATA2MSB) { + warnx("Unknown ELF data order %u", ident[EI_DATA]); + return -1; + } + + size_t res; + + if (ident[EI_CLASS] == ELFCLASS32) res = read_elf32_size(ident, fd); + else if (ident[EI_CLASS] == ELFCLASS64) res = read_elf64_size(ident, fd); + else { + fprintf(stderr, "Unknown ELF class %u\n", ident[EI_CLASS]); + return -1; + } + + fclose(fd); + return res; +} diff --git a/src/loader/slimfs.c b/src/loader/slimfs.c new file mode 100644 index 0000000..a57a693 --- /dev/null +++ b/src/loader/slimfs.c @@ -0,0 +1,1019 @@ +// Structure inspired by Radek Podgorny's unionfs +// License: BSD-style license +// Copyright: Radek Podgorny , +// Bernd Schubert + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "slimfs.h" + +#define DEBUG 0 + +typedef struct { + struct sqfs_inode sqfs_fd; + int real_fd; + bool is_sqfs; +} user_fd_t; + +static struct sqfs_inode *find_squashfs_node(struct sqfs *fs, const char *path, struct sqfs_inode *inode) { + bool found; + sqfs_err err; + + struct sqfs_inode node; + if ((err = sqfs_inode_get(fs, &node, fs->sb.root_inode)) != SQFS_OK) { + warnx("SQFS error during lookup of %s: %d", path, err); + return NULL; + } + + if ((err = sqfs_lookup_path(fs, &node, path, &found)) != SQFS_OK) { + warnx("SQFS error during lookup of %s: %d", path, err); + return NULL; + } + if (!found) return NULL; + + *inode = node; + return inode; +} + +static char *cat_path(char *res, size_t n, const char *root, const char *path) { + // TODO: check length + snprintf(res, n, "%s%s", root, path); + return res; +} + +static void *slimfs_init( + #if FUSE_USE_VERSION < 30 + struct fuse_conn_info *conn + #else + struct fuse_conn_info *conn, struct fuse_config *cfg + #endif +) { + if (DEBUG) fprintf(stderr, "SLIMFS INIT\n"); + + #ifdef FUSE_CAP_IOCTL_DIR + conn->want |= conn->capable & FUSE_CAP_IOCTL_DIR; + #endif + + slimfs_t data = fuse_get_context()->private_data; + return data; +} +static void slimfs_destroy(void *pdata) { + if (DEBUG) fprintf(stderr, "SLIMFS DESTROY\n"); + slimfs_t fs = pdata; + + fuse_unmount(fs->target_dir, fs->channel); + sqfs_destroy(&fs->image); + free(fs); +} + +static int slimfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) { + if (DEBUG) fprintf(stderr, "SLIMFS CREATE %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + if (find_squashfs_node(&data->image, path, &inode)) return -EEXIST; + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + + int res = open(buff, fi->flags, mode); + if (res == -1) return -errno; + + user_fd_t *res_fd = malloc(sizeof *res_fd); + res_fd->is_sqfs = false; + res_fd->real_fd = res; + + fi->fh = (uint64_t)res_fd; + + return 0; +} +static int slimfs_symlink(const char *from, const char *to) { + if (DEBUG) fprintf(stderr, "SLIMFS SYMLINK %s -> %s\n", from, to); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char real_from[PATH_MAX]; + char real_to[PATH_MAX]; + + // Same as link, we can't symlink from or to the overlay + if (find_squashfs_node(&data->image, from, &inode)) return -EROFS; + if (find_squashfs_node(&data->image, to, &inode)) return -EEXIST; + + if (!cat_path(real_from, sizeof real_from, data->root_dir, from)) return -ENAMETOOLONG; + if (!cat_path(real_to, sizeof real_to, data->root_dir, to)) return -ENAMETOOLONG; + + if (symlink(real_from, real_to) < 0) return -errno; + return 0; +} +static int slimfs_link(const char *from, const char *to) { + if (DEBUG) fprintf(stderr, "SLIMFS LINK %s -> %s\n", from, to); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char real_from[PATH_MAX]; + char real_to[PATH_MAX]; + + // We can't hard-link stuff from or to the overlay + if (find_squashfs_node(&data->image, from, &inode)) return -EROFS; + if (find_squashfs_node(&data->image, to, &inode)) return -EEXIST; + + if (!cat_path(real_from, sizeof real_from, data->root_dir, from)) return -ENAMETOOLONG; + if (!cat_path(real_to, sizeof real_to, data->root_dir, to)) return -ENAMETOOLONG; + + if (link(real_from, real_to) < 0) return -errno; + return 0; +} +static int slimfs_mkdir(const char *path, mode_t mode) { + if (DEBUG) fprintf(stderr, "SLIMFS MKDIR %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + // Already exists in overlay + if (find_squashfs_node(&data->image, path, &inode)) return -EEXIST; + + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + + // Let the OS figure out there's a collision + if (mkdir(buff, mode) < 0) return -errno; + return 0; +} +static int slimfs_mknod(const char *path, mode_t mode, dev_t rdev) { + if (DEBUG) fprintf(stderr, "SLIMFS MKNOD %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + // Something is already in the overlay + if (find_squashfs_node(&data->image, path, &inode)) return -EEXIST; + + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + if (mknod(buff, mode, rdev) < 0) return -errno; + return 0; + + // int i = find_rw_branch_cutlast(path); + // if (i == -1) return -errno; + + // char p[PATHLEN_MAX]; + // if (BUILD_PATH(p, uopt.branches[i].path, path)) return -ENAMETOOLONG; + + // int file_type = mode & S_IFMT; + // int file_perm = mode & (S_PROT_MASK); + + // int res = -1; + // if (file_type == S_IFREG) { + // // under FreeBSD, only the super-user can create ordinary files using mknod + // // Actually this workaround should not be required any more + // // since we now have the slimfs_create() method + // // So can we remove it? + + // usyslog(LOG_INFO, "deprecated mknod workaround, tell the slimfs-fuse authors if you see this!\n"); + + // res = creat(p, 0); + // if (res > 0 && close(res) == -1) usyslog(LOG_WARNING, "Warning, cannot close file\n"); + // } + // else { + // res = mknod(p, file_type, rdev); + // } + + // if (res == -1) return -errno; + + // set_owner(p); // no error check, since creating the file succeeded + // // NOW, that the file has the proper owner we may set the requested mode + // chmod(p, file_perm); + + // remove_hidden(path, i); + + // return 0; +} + +static int slimfs_rename( + #if FUSE_USE_VERSION < 30 + const char *from, const char *to + #else + const char *from, const char *to, unsigned int flags + #endif +) { + if (DEBUG) fprintf(stderr, "SLIMFS RENAME %s -> %s\n", from, to); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char real_from[PATH_MAX]; + char real_to[PATH_MAX]; + + // We can't move files from or to the overlay, since it's readonly + if (find_squashfs_node(&data->image, from, &inode)) return -EROFS; + if (find_squashfs_node(&data->image, to, &inode)) return -EEXIST; + + if (!cat_path(real_from, sizeof real_from, data->root_dir, from)) return -ENAMETOOLONG; + if (!cat_path(real_to, sizeof real_to, data->root_dir, to)) return -ENAMETOOLONG; + + if (rename(real_from, real_to) < 0) return -errno; + return 0; +} + +static int slimfs_rmdir(const char *path) { + if (DEBUG) fprintf(stderr, "SLIMFS RMDIR %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + // We have two ways we could handle this: + // 1. If it is in the root, delete it, otherwise, if it's in the overlay, throw a EROFS + // The pitfall of this is that if we have the same dir in the root and the overlay, + // we will be able to seemingly delete it, but the wrapper will still remain + // 2. Throw if it exists in the overlay + // This is what is done here, the pitfall is that we can't delete a RW directory in + // the root, if it's shadowed by the overlay, but is probably what we mean by "rmdir" + if (find_squashfs_node(&data->image, path, &inode)) return -EROFS; + + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + + int res = rmdir(buff); + if (res < 0) return -errno; + return 0; +} +static int slimfs_unlink(const char *path) { + if (DEBUG) fprintf(stderr, "SLIMFS RMDIR %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + if (find_squashfs_node(&data->image, path, &inode)) return -EROFS; + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + + int res = unlink(buff); + if (res < 0) return -errno; + return 0; +} + +static int slimfs_readlink(const char *path, char *buf, size_t size) { + if (DEBUG) fprintf(stderr, "SLIMFS READLINK %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + // If we have a file in the overlay, we can open it only in read-only mode + if (find_squashfs_node(&data->image, path, &inode)) { + size_t size = sizeof buff; + if (sqfs_readlink(&data->image, &inode, buf, &size) != F_OK) { + return -EINVAL; + } + + return 0; + } + + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + + int res = readlink(buff, buf, size); + if (res < 0) return -errno; + + buf[res] = '\0'; + return 0; +} +// TODO: BROKEN!!!! +static int slimfs_readdir( + #if FUSE_USE_VERSION < 30 + const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi + #else + const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags + #endif +) { + if (DEBUG) fprintf(stderr, "SLIMFS READDIR %s\n", path); + int err = 0; + + slimfs_t data = fuse_get_context()->private_data; + char buff[PATH_MAX]; + struct sqfs_inode inode; + + char **passed = NULL; + size_t passed_n = 0; + size_t passed_i = 0; + + // If we have a hit in the overlay, we will try to iterate it + if (find_squashfs_node(&data->image, path, &inode)) { + sqfs_dir dir; + sqfs_dir_entry entry; + sqfs_err err; + + if (sqfs_dir_open(&data->image, &inode, &dir, 0) != SQFS_OK) return -ENOTDIR; + + while (sqfs_dir_next(&data->image, &dir, &entry, &err)) { + if (err != SQFS_OK) return -EIO; + passed_n++; + } + + if (passed_n != 0) { + passed = malloc(sizeof *passed * passed_n); + + sqfs_dir_open(&data->image, &inode, &dir, 0); + + while (sqfs_dir_next(&data->image, &dir, &entry, &err)) { + if (err != SQFS_OK) { + err = -EIO; + goto error; + } + + char *name = malloc(entry.name_size + 1); + name[entry.name_size] = '\0'; + memcpy(name, entry.name, entry.name_size); + passed[passed_i++] = name; + + struct stat st = { + .st_ino = entry.inode_number, + .st_mode = entry.type << 12, + }; + + if ( + #if FUSE_USE_VERSION < 30 + filler(buf, name, &st, 0) + #else + filler(buf, name, &st, 0, 0) + #endif + ) goto error; + } + } + } + + if (!cat_path(buff, sizeof buff, data->root_dir, path)) { + err = -ENAMETOOLONG; + goto error; + } + + DIR *root_dir = opendir(buff); + if (!root_dir) return -errno; + + struct dirent *root_entry; + + while ((root_entry = readdir(root_dir))) { + struct stat st = { + .st_ino = root_entry->d_ino, + .st_mode = root_entry->d_type << 12, + }; + + // Will be slow for overlay dirs with a lot of files, but so what? + bool skip = false; + + for (size_t i = 0; i < passed_n; i++) { + if (!strcmp(passed[i], root_entry->d_name)) { + skip = true; + break; + } + } + + if (skip) continue; + + if ( + #if FUSE_USE_VERSION < 30 + filler(buf, root_entry->d_name, &st, 0) + #else + filler(buf, entry->d_name, &st, 0, 0) + #endif + ) { + closedir(root_dir); + goto error; + } + } + + closedir(root_dir); + +error: + if (passed) { + for (size_t i = 0; i < passed_i; i++) free(passed[i]); + free(passed); + } + + return err; +} +static int slimfs_getattr( + #if FUSE_USE_VERSION < 30 + const char *path, struct stat *stat + #else + const char *path, struct stat *stat, struct fuse_file_info *fi + #endif +) { + if (DEBUG) fprintf(stderr, "SLIMFS GETATTR %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + // The overlay FS can't overlay the root dir + if (strcmp(path, "/") && find_squashfs_node(&data->image, path, &inode)) { + memset(stat, 0, sizeof(*stat)); + stat->st_ino = inode.base.inode_number; + stat->st_mode = inode.base.mode; + stat->st_nlink = inode.nlink; + stat->st_mtime = stat->st_ctime = stat->st_atime = inode.base.mtime; + + if (S_ISREG(stat->st_mode)) { + // FIXME: do symlinks, dirs, etc have a size? + stat->st_size = inode.xtra.reg.file_size; + stat->st_blocks = stat->st_size / 512; + } + else if (S_ISBLK(stat->st_mode) || S_ISCHR(stat->st_mode)) { + stat->st_rdev = makedev(inode.xtra.dev.major, inode.xtra.dev.minor); + } + else if (S_ISLNK(stat->st_mode)) { + stat->st_size = inode.xtra.symlink_size; + } + + stat->st_blksize = data->image.sb.block_size; + + // if (data->image.uid > 0) { + stat->st_uid = data->image.uid; + // } + // else { + // uid_t id; + // fprintf(stderr, "HELL NO!"); + // if (sqfs_id_get(&data->image, inode.base.uid, &id) != SQFS_OK) return -EIO; + // stat->st_uid = id; + // } + + // if (data->image.gid > 0) { + stat->st_gid = data->image.gid; + // } + // else { + // uid_t id; + // fprintf(stderr, "HELL NO!"); + // if (sqfs_id_get(&data->image, inode.base.guid, &id) != SQFS_OK) return -EIO; + // stat->st_gid = id; + // } + + return 0; + } + else if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + else { + if (lstat(buff, stat) < 0) return -errno; + } + + // This is a workaround for broken gnu find implementations. Actually, + // n_links is not defined at all for directories by posix. However, it + // seems to be common for filesystems to set it to one if the actual value + // is unknown. Since nlink_t is unsigned and since these broken implementations + // always substract 2 (for . and ..) this will cause an underflow, setting + // it to max(nlink_t). + if (S_ISDIR(stat->st_mode)) stat->st_nlink = 1; + return 0; +} +static int slimfs_access(const char *path, int mask) { + if (DEBUG) fprintf(stderr, "SLIMFS ACCESS %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + if (find_squashfs_node(&data->image, path, &inode)) { + // The squashfs image is allowed to specify the executable perms + if (mask & X_OK) { + if ((inode.base.mode & S_IXUSR) | (inode.base.mode & S_IXGRP) | (inode.base.mode & S_IXOTH)) return 0; + return -EACCES; + } + // The image is always read-only + if (mask & W_OK) return -EACCES; + // Although the image can specify no read perms, we will ignore those + if (mask & R_OK) return 0; + } + else if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + else { + struct stat stat; + if (lstat(buff, &stat) < 0) return -errno; + + if ((mask & X_OK) && (stat.st_mode & S_IXUSR) == 0) return -EACCES; + if ((mask & W_OK) && (stat.st_mode & S_IWUSR) == 0) return -EACCES; + if ((mask & R_OK) && (stat.st_mode & S_IRUSR) == 0) return -EACCES; + } + + return 0; +} +// Wrapper function to convert the result of statfs() to statvfs() +// libfuse uses statvfs, since it conforms to POSIX. Unfortunately, +// glibc's statvfs parses /proc/mounts, which then results in reading +// the filesystem itself again - which would result in a deadlock. +// TODO: BSD/MacOSX +static int statvfs_local(const char *path, struct statvfs *stbuf) { +#ifdef linux + // glibc's statvfs walks /proc/mounts and stats entries found there + // in order to extract their mount flags, which may deadlock if they + // are mounted under the slimfs. As a result, we have to do this ourselves. + struct statfs stfs; + int res = statfs(path, &stfs); + if (res == -1) return res; + + *stbuf = (struct statvfs){ + .f_bsize = stfs.f_bsize, + .f_frsize = stfs.f_frsize ? stfs.f_frsize : stfs.f_bsize, + .f_blocks = stfs.f_blocks, + .f_bfree = stfs.f_bfree, + .f_bavail = stfs.f_bavail, + .f_files = stfs.f_files, + .f_ffree = stfs.f_ffree, + // nobody knows + .f_favail = stfs.f_ffree, + // We don't worry about flags, exactly because this would + // require reading /proc/mounts, and avoiding that and the + // resulting deadlocks is exactly what we're trying to avoid + // by doing this rather than using statvfs. + .f_flag = 0, + .f_namemax = stfs.f_namelen, + }; + + + return 0; +#else + return statvfs(path, stbuf); +#endif +} +// statfs implementation +// Note: We do not set the fsid, as fuse ignores it anyway. +static int slimfs_statfs(const char *path, struct statvfs *stbuf) { + if (DEBUG) fprintf(stderr, "SLIMFS STATFS %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + // We could do something more sophisticated OR we could just stat the root + + int res = statvfs_local(data->root_dir, stbuf); + if (res < 0) return -errno; + return 0; + + // (void)path; + + // int first = 1; + + // dev_t devno[uopt.nbranches]; + + // int retVal = 0; + + // int i = 0; + // for (i = 0; i < uopt.nbranches; i++) { + // struct statvfs stb; + // int res = statvfs_local(uopt.branches[i].path, &stb); + // if (res == -1) { + // retVal = -errno; + // break; + // } + + // struct stat st; + // res = stat(uopt.branches[i].path, &st); + // if (res == -1) { + // retVal = -errno; + // break; + // } + // devno[i] = st.st_dev; + + // if (first) { + // memcpy(stbuf, &stb, sizeof(*stbuf)); + // first = 0; + // stbuf->f_fsid = stb.f_fsid << 8; + // continue; + // } + + // // Eliminate same devices + // int j = 0; + // for (j = 0; j < i; j ++) { + // if (st.st_dev == devno[j]) break; + // } + + // if (j == i) { + // // Filesystem can have different block sizes -> normalize to first's block size + // double ratio = (double)stb.f_bsize / (double)stbuf->f_bsize; + + // if (uopt.branches[i].rw) { + // stbuf->f_blocks += stb.f_blocks * ratio; + // stbuf->f_bfree += stb.f_bfree * ratio; + // stbuf->f_bavail += stb.f_bavail * ratio; + + // stbuf->f_files += stb.f_files; + // stbuf->f_ffree += stb.f_ffree; + // stbuf->f_favail += stb.f_favail; + // } + // else if (!uopt.statfs_omit_ro) { + // // omitting the RO branches is not correct regarding + // // the block counts but it actually fixes the + // // percentage of free space. so, let the user decide. + // stbuf->f_blocks += stb.f_blocks * ratio; + // stbuf->f_files += stb.f_files; + // } + + // if (!(stb.f_flag & ST_RDONLY)) stbuf->f_flag &= ~ST_RDONLY; + // if (!(stb.f_flag & ST_NOSUID)) stbuf->f_flag &= ~ST_NOSUID; + + // if (stb.f_namemax < stbuf->f_namemax) stbuf->f_namemax = stb.f_namemax; + // } + // } + + // return retVal; +} +static int slimfs_utimens( + #if FUSE_USE_VERSION < 30 + const char *path, const struct timespec ts[2] + #else + const char *path, const struct timespec ts[2], struct fuse_file_info *fi + #endif +) { + if (DEBUG) fprintf(stderr, "SLIMFS UTIMENS %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + if (find_squashfs_node(&data->image, path, &inode)) return -EROFS; + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + + if (utimensat(0, buff, ts, AT_SYMLINK_NOFOLLOW) < 0) return -errno; + else return 0; +} + +static int slimfs_chmod( + #if FUSE_USE_VERSION < 30 + const char *path, mode_t mode + #else + const char *path, mode_t mode, struct fuse_file_info *fi + #endif +) { + if (DEBUG) fprintf(stderr, "SLIMFS CHMOD %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + // If the file is in the overlay, we can't touch it + if (find_squashfs_node(&data->image, path, &inode)) return -EROFS; + + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + + int res = chmod(buff, mode); + if (res == -1) return -errno; + + return 0; +} +static int slimfs_chown( + #if FUSE_USE_VERSION < 30 + const char *path, uid_t uid, gid_t gid + #else + const char *path, uid_t uid, gid_t gid, struct fuse_file_info *fi + #endif +) { + if (DEBUG) fprintf(stderr, "SLIMFS CHOWN %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + // If the file is in the overlay, we can't touch it + if (find_squashfs_node(&data->image, path, &inode)) return -EROFS; + + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + + int res = lchown(buff, uid, gid); + if (res == -1) return -errno; + + return 0; +} + +// Stub function, look into removing it +static int slimfs_ioctl( + #if FUSE_USE_VERSION < 35 + const char *path, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data + #else + const char *path, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data + #endif +) { + if (DEBUG) fprintf(stderr, "SLIMFS IOCTL %s\n", path); + + #ifdef FUSE_IOCTL_COMPAT + if (flags & FUSE_IOCTL_COMPAT) return -ENOSYS; + #endif + + // usyslog(LOG_ERR, "Unknown ioctl: %d", cmd); + return -EINVAL; +} + +static int slimfs_truncate( + #if FUSE_USE_VERSION < 30 + const char *path, off_t size + #else + const char *path, off_t size, struct fuse_file_info *fi + #endif +) { + if (DEBUG) fprintf(stderr, "SLIMFS TRUNCATE %s\n", path); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + // Can't truncate in overlay + if (find_squashfs_node(&data->image, path, &inode)) return -EROFS; + + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + + if (truncate(buff, size) < 0) return -errno; + return 0; +} + +static int slimfs_open(const char *path, struct fuse_file_info *fi) { + if (DEBUG) fprintf(stderr, "SLIMFS OPEN %s (flags %d)\n", path, fi->flags); + + slimfs_t data = fuse_get_context()->private_data; + struct sqfs_inode inode; + char buff[PATH_MAX]; + + if (find_squashfs_node(&data->image, path, &inode)) { + // Images in the overlay may be opened only in read-only mode + if (fi->flags & (O_WRONLY | O_RDWR)) return -EACCES; + if (!S_ISREG(inode.base.mode)) return -EINVAL; + + user_fd_t *res_fd = malloc(sizeof *res_fd); + res_fd->is_sqfs = true; + res_fd->sqfs_fd = inode; + + fi->fh = (uint64_t)res_fd; + return 0; + } + else { + if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + + int res = open(buff, fi->flags); + if (res < 0) return -errno; + + user_fd_t *res_fd = malloc(sizeof *res_fd); + res_fd->is_sqfs = false; + res_fd->real_fd = res; + + fi->fh = (uint64_t)res_fd; + return 0; + } +} +static int slimfs_release(const char *path, struct fuse_file_info *fi) { + if (DEBUG) fprintf(stderr, "SLIMFS CLOSE %s\n", path); + + user_fd_t *fd = (user_fd_t*)fi->fh; + + if (fd->is_sqfs) { + free(fd); + return 0; + } + else { + int err = close(fd->real_fd); + free(fd); + + if (err < 0) return -errno; + return 0; + } +} + +static int slimfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { + if (DEBUG) fprintf(stderr, "SLIMFS READ %s (%lu - %lu)", path, offset, offset + size); + + user_fd_t *fd = (user_fd_t*)fi->fh; + + if (fd->is_sqfs) { + slimfs_t data = fuse_get_context()->private_data; + if (sqfs_read_range(&data->image, &fd->sqfs_fd, offset, (sqfs_off_t*)&size, buf) != SQFS_OK) return -EIO; + return (int)size; + } + else { + int res = pread(fd->real_fd, buf, size, offset); + if (res == -1) return -errno; + return res; + } +} +static int slimfs_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { + if (DEBUG) fprintf(stderr, "SLIMFS WRITE %s (%lu - %lu)", path, offset, offset + size); + + user_fd_t *fd = (user_fd_t*)fi->fh; + + if (fd->is_sqfs) return -EACCES; + + int res = pwrite(fd->real_fd, buf, size, offset); + if (res == -1) return -errno; + return res; +} +static int slimfs_flush(const char *path, struct fuse_file_info *fi) { + if (DEBUG) fprintf(stderr, "SLIMFS FLUSH %s\n", path); + + user_fd_t *fd = (user_fd_t*)fi->fh; + + if (fd->is_sqfs) { + // We will pretend we have flushed the written data + // (we can't fail this operation since we aren't doing anything) + return 0; + } + else { + if (fsync(fd->real_fd) < 0) return -errno; + else return 0; + } + + // int fd = dup(fi->fh); + + // if (fd == -1) { + // // What to do now? + // if (fsync(fi->fh) == -1) return -EIO; + // return -errno; + // } + + // int res = close(fd); + // if (res == -1) return -errno; + + // return 0; +} + +// static int slimfs_getxattr( +// #if __APPLE__ +// const char *path, const char *name, char *value, size_t size, uint32_t position +// #else +// const char *path, const char *name, char *value, size_t size +// #endif +// ) { +// slimfs_t data = fuse_get_context()->private_data; +// char buff[PATH_MAX]; + +// if (!find_file(buff, sizeof buff, data->overlay_dir, path)) { +// if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; +// } + +// #if __APPLE__ +// int res = getxattr(buff, name, value, size, position, XATTR_NOFOLLOW); +// #else +// int res = lgetxattr(buff, name, value, size); +// #endif + +// if (res < 0) return -errno; +// return 0; +// } +// static int slimfs_listxattr(const char *path, char *list, size_t size) { +// slimfs_t data = fuse_get_context()->private_data; +// char buff[PATH_MAX]; + +// if (!find_file(buff, sizeof buff, data->overlay_dir, path)) { +// if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; +// } + +// #if __APPLE__ +// int res = listxattr(buff, list, size, XATTR_NOFOLLOW); +// #else +// int res = llistxattr(buff, list, size); +// #endif + +// if (res < 0) return -errno; +// return 0; +// } +// static int slimfs_removexattr(const char *path, const char *name) { +// slimfs_t data = fuse_get_context()->private_data; +// char buff[PATH_MAX]; + +// if (find_file(buff, sizeof buff, data->overlay_dir, path)) return -EROFS; +// if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + +// #if __APPLE__ +// int res = removexattr(buff, name, XATTR_NOFOLLOW); +// #else +// int res = lremovexattr(buff, name); +// #endif + +// if (res < 0) return -errno; +// return 0; +// } +// static int slimfs_setxattr( +// #if __APPLE__ +// const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position +// #else +// const char *path, const char *name, const char *value, size_t size, int flags +// #endif +// ) { +// slimfs_t data = fuse_get_context()->private_data; +// char buff[PATH_MAX]; + +// if (find_file(buff, sizeof buff, data->overlay_dir, path)) return -EROFS; +// if (!cat_path(buff, sizeof buff, data->root_dir, path)) return -ENAMETOOLONG; + +// #if __APPLE__ +// int res = setxattr(buff, name, value, size, position, (flags | XATTR_NOFOLLOW) & ~XATTR_NOSECURITY); +// #else +// int res = lsetxattr(buff, name, value, size, flags); +// #endif + +// if (res < 0) return -errno; +// return 0; +// } + +struct fuse_operations slimfs_oper = { + .chmod = slimfs_chmod, + .chown = slimfs_chown, + .create = slimfs_create, + .flush = slimfs_flush, + .getattr = slimfs_getattr, + .access = slimfs_access, + .init = slimfs_init, + .destroy = slimfs_destroy, + .ioctl = slimfs_ioctl, + .link = slimfs_link, + .mkdir = slimfs_mkdir, + .mknod = slimfs_mknod, + .open = slimfs_open, + .read = slimfs_read, + .readlink = slimfs_readlink, + .readdir = slimfs_readdir, + .release = slimfs_release, + .rename = slimfs_rename, + .rmdir = slimfs_rmdir, + .statfs = slimfs_statfs, + .symlink = slimfs_symlink, + .truncate = slimfs_truncate, + .unlink = slimfs_unlink, + .utimens = slimfs_utimens, + .write = slimfs_write, + // .getxattr = slimfs_getxattr, + // .listxattr = slimfs_listxattr, + // .removexattr = slimfs_removexattr, + // .setxattr = slimfs_setxattr, +}; + +slimfs_t slimfs_new(int argc, char **argv, const char *target_dir, const char *root_dir, const char *img_path, size_t img_offset) { + struct sqfs sqfs; + if (sqfs_open_image(&sqfs, img_path, img_offset) != SQFS_OK) { + warnx("Failed to open squashfs image"); + return NULL; + } + + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + for (int i = 0; i < argc; i++) { + if (!argv[i]) break; + fuse_opt_add_arg(&args, argv[i]); + } + + #ifdef FUSE_CAP_BIG_WRITES + fuse_opt_add_arg(&args, "-obig_writes"); + #endif + + struct fuse_chan *channel = fuse_mount(target_dir, &args); + if (!channel) { + warnx("Failed to create fuse channel"); + fuse_opt_free_args(&args); + sqfs_destroy(&sqfs); + return NULL; + } + + slimfs_t data = malloc(sizeof *data); + if (strlen(root_dir) <= 0) root_dir = "."; + strncpy(data->root_dir, root_dir, sizeof data->root_dir); + + size_t len = strlen(data->root_dir) - 1; + for (size_t i = len; i > 0; i--) { + if (data->root_dir[len - 1] != '/') break; + data->root_dir[len - 1] = '\0'; + } + + strncpy(data->target_dir, target_dir, sizeof data->target_dir); + data->image = sqfs; + + struct fuse *fuse = fuse_new(channel, &args, &slimfs_oper, sizeof slimfs_oper, data); + if (!fuse) { + warnx("Failed to create fuse"); + fuse_destroy(fuse); + fuse_unmount(target_dir, channel); + fuse_opt_free_args(&args); + sqfs_destroy(&sqfs); + return NULL; + } + + fuse_opt_free_args(&args); + + data->fuse = fuse; + data->channel = channel; + + return data; +} + +void slimfs_free(slimfs_t fs) { + fuse_destroy(fs->fuse); +} diff --git a/src/loader/slimfs.h b/src/loader/slimfs.h new file mode 100644 index 0000000..cc8bdc6 --- /dev/null +++ b/src/loader/slimfs.h @@ -0,0 +1,25 @@ +/* +* License: BSD-style license +* Copyright: Radek Podgorny , +* Bernd Schubert +*/ + +#pragma once + +#include +#include + +#include + +typedef struct { + struct fuse *fuse; + struct fuse_chan *channel; + struct sqfs image; + char root_dir[PATH_MAX]; + char target_dir[PATH_MAX]; +} *slimfs_t; + +extern struct fuse_operations unionfs_oper; + +slimfs_t slimfs_new(int argc, char **argv, const char *target_dir, const char *root_dir, const char *img_path, size_t img_offset); +void slimfs_free(slimfs_t fs); diff --git a/src/main/cli/pack.lua b/src/main/cli/pack.lua index 28ab3a9..2543a91 100644 --- a/src/main/cli/pack.lua +++ b/src/main/cli/pack.lua @@ -1,61 +1,7 @@ local ostree = require "ostree"; local flatpak = require "flatpak"; local fs = require "fs"; - -local loader = [[#!/bin/sh - -set -euo pipefail; - -cleanup() { - cd /; - - if [ "${mountpoint_dir+x}" ]; then - umount "$mountpoint_dir"; - fi - if [ "${squashfs_dir+x}" ]; then - umount "$squashfs_dir"; - fi - if [ "${tmp_dir+x}" ]; then - rm -rf "$tmp_dir"; - fi -} - -jail_run() { - unshare -cR $mountpoint_dir $@; -} - -trap cleanup EXIT; - -marker="#END_OF_"LOADER"_MARKER"; - -offset=$(( $(grep -abo "$marker" "$PWD/$0" | head -n 1 | cut -d: -f1) + ${#marker} + 1 )); - -if [ "${1-}" = "--slimpack-offset" ] || [ "${1-}" = "--appimage-offset" ]; then - echo $offset; - exit 0; -fi - -tmp_dir=$(mktemp -d /tmp/.slimpack.XXXXXXXXXXXX); - -mkdir "$tmp_dir/squashfs"; -squashfuse -o offset=$offset "$PWD/$0" "$tmp_dir/squashfs"; -squashfs_dir="$tmp_dir/squashfs"; - -mkdir "$tmp_dir/mount_point"; -unionfs -o suid,dev "$squashfs_dir"=RO:/=RW "$tmp_dir/mount_point"; -mountpoint_dir="$tmp_dir/mount_point"; - -if [ "${1-}" = "--slimpack-dbg" ]; then - jail_run /bin/bash; -elif [ "${1-}" = "--slimpack-dbg-2" ]; then - cd "$mountpoint_dir" && bash; -else - jail_run "/entry" $@; -fi -exit 0; - -#END_OF_LOADER_MARKER -]]; +local elf_loader = require "elf_loader"; local entry_preifx = [[#!/bin/sh @@ -98,7 +44,7 @@ end local function write_res(img_path, target) local f = assert(io.open(target, "wb")); - f:write(loader); + f:write(elf_loader); local img_f = assert(io.open(img_path, "rb")); diff --git a/src/main/ostree.lua b/src/main/ostree.lua index 7d119a5..0421c07 100644 --- a/src/main/ostree.lua +++ b/src/main/ostree.lua @@ -292,7 +292,7 @@ function ostree.dump_node(path, node) assert(fs.chmod(path, node.mode & 0xFFF)); elseif node.type == "link" then assert(fs.mkdir(path:match "^(.+)/[^/]+$", true)); - assert(fs.symlink(path, node.target)); + assert(fs.symlink(node.target, path)); else error("Unknown node type '" .. node.type .. "'"); end