// SPDX-License-Identifier: EUPL-1.2+ // SPDX-FileCopyrightText: 2025 Alyssa Ross #include "config.h" #include "metadata.h" #include #include #include #include #include #include #include #include #include #include static void bind_mount(int source_fd, const char *source, int target_fd, const char *target) { int source_tree = syscall(SYS_open_tree, source_fd, source, AT_EMPTY_PATH | OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE | AT_RECURSIVE); if (source_tree == -1) err(EXIT_FAILURE, "open_tree %s", source); if (syscall(SYS_move_mount, source_tree, "", target_fd, target, MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH) == -1) err(EXIT_FAILURE, "move_mount"); } static int mkdir_openat(int dirfd, const char *path, mode_t mode) { int fd; if (mkdirat(dirfd, path, mode) == -1) err(EXIT_FAILURE, "mkdirat %s", path); if ((fd = openat(dirfd, path, O_PATH | O_DIRECTORY | O_CLOEXEC)) == -1) err(EXIT_FAILURE, "openat %s", path); return fd; } // By failing on EEXIST when creating each directory, // we can be sure we don't end up following .. components. static int mkdirs_beneath(int root, const char *target) { int last, fd = root; size_t len = strlen(target); char *end, *path = malloc(len + 1), *dir = path; if (!dir) err(EXIT_FAILURE, "malloc %zu", len + 1); memcpy(dir, target, len + 1); do { // Find next non-empty directory component end = strchrnul(dir, '/'); while (*(end + 1) == '/') end++; // Replace path separator with string terminator. *end = 0; // Update fd to a new child, and close the previous one // unless it was the one provided by the caller. last = fd; fd = mkdir_openat(fd, dir, 0755); if (last != root) close(last); dir = end + 1; } while (end != &path[len]); free(path); return fd; } static int openat_beneath(int dirfd, const char *path, int flags) { struct open_how how = { .flags = flags, .resolve = RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS, }; int r = syscall(SYS_openat2, dirfd, path, &how, sizeof how); if (r == -1) err(EXIT_FAILURE, "openat2 %s", path); return r; } static int resolve_link(int dirfd, const char *path, char **target) { int r; struct stat sb; if (fstatat(dirfd, path, &sb, AT_SYMLINK_NOFOLLOW) == -1) err(EXIT_FAILURE, "fstatat %s", path); if (!(*target = malloc(sb.st_size + 1))) err(EXIT_FAILURE, "malloc %zu", sb.st_size + 1); if ((r = readlinkat(dirfd, path, *target, sb.st_size + 1)) <= -1) err(EXIT_FAILURE, "readlinkat %s", path); if (r == sb.st_size + 1) errx(EXIT_FAILURE, "symlink target lengthened"); (*target)[r] = 0; // NOLINT(clang-analyzer-security.ArrayBound) return openat_beneath(dirfd, *target, O_PATH | O_CLOEXEC | O_DIRECTORY); } static void write_to(int dirfd, const char *path, const char *text) { FILE *file; size_t len = strlen(text); int f = openat(dirfd, path, O_WRONLY | O_CLOEXEC | O_NOFOLLOW | O_CREAT, 0644); if (f == -1) err(EXIT_FAILURE, "openat %s", path); if (!(file = fdopen(f, "w"))) err(EXIT_FAILURE, "fdopen %s", path); if (fwrite(text, 1, len, file) != len) err(EXIT_FAILURE, "fwrite"); if (fclose(file) == EOF) err(EXIT_FAILURE, "fclose"); } static void set_up_app_dir(int source_commit_dir, int installation_dir, const char *id, const char *arch, const char *branch, const char *commit) { int app_dir, id_dir, arch_dir, branch_dir; app_dir = mkdir_openat(installation_dir, "app", 0755); id_dir = mkdir_openat(app_dir, id, 0755); close(app_dir); arch_dir = mkdir_openat(id_dir, arch, 0755); close(id_dir); branch_dir = mkdir_openat(arch_dir, branch, 0755); close(arch_dir); if (mkdirat(branch_dir, commit, 0755) == -1) err(EXIT_FAILURE, "mkdirat %s", commit); bind_mount(source_commit_dir, "", branch_dir, commit); close(branch_dir); } static int mount_app(int source_installation_dir, int target_installation_dir, int params_dir, const char *id) { char *arch_and_branch, *arch_end, *commit; int app_dir, id_dir, branch_dir, commit_dir; app_dir = openat_beneath(source_installation_dir, "app", O_PATH | O_CLOEXEC | O_DIRECTORY); id_dir = openat_beneath(app_dir, id, O_PATH | O_CLOEXEC | O_DIRECTORY); close(app_dir); branch_dir = resolve_link(id_dir, "current", &arch_and_branch); close(id_dir); commit_dir = resolve_link(branch_dir, "active", &commit); close(branch_dir); if (!(arch_end = strchr(arch_and_branch, '/'))) errx(EXIT_FAILURE, "unexpected current format"); *arch_end = 0; set_up_app_dir(commit_dir, target_installation_dir, id, arch_and_branch, arch_end + 1, commit); write_to(params_dir, "commit", commit); write_to(params_dir, "arch", arch_and_branch); write_to(params_dir, "branch", arch_end + 1); free(arch_and_branch); free(commit); return commit_dir; } static void set_up_runtime_dir(int source_commit_dir, int installation_dir, const char *ref, const char *commit) { int runtime_dir, branch_dir; runtime_dir = mkdir_openat(installation_dir, "runtime", 0755); branch_dir = mkdirs_beneath(runtime_dir, ref); close(runtime_dir); if (mkdirat(branch_dir, commit, 0755) == -1) err(EXIT_FAILURE, "mkdirat %s", commit); bind_mount(source_commit_dir, "", branch_dir, commit); close(branch_dir); } static void mount_runtime(int source_installation_dir, int target_installation_dir, int params_dir, int app_commit_dir) { char runtime[256], *commit; int runtime_dir, branch_dir, commit_dir; int metadata = openat_beneath(app_commit_dir, "metadata", O_RDONLY | O_CLOEXEC); extract_runtime(metadata, runtime); runtime_dir = openat_beneath(source_installation_dir, "runtime", O_PATH | O_CLOEXEC | O_DIRECTORY); branch_dir = openat_beneath(runtime_dir, runtime, O_PATH | O_CLOEXEC | O_DIRECTORY); close(runtime_dir); commit_dir = resolve_link(branch_dir, "active", &commit); close(branch_dir); set_up_runtime_dir(commit_dir, target_installation_dir, runtime, commit); write_to(params_dir, "runtime-commit", commit); free(commit); close(commit_dir); } static void set_up_repo(int target_installation_dir) { int config; if (mkdirat(target_installation_dir, "repo", 0755) == -1) err(EXIT_FAILURE, "mkdir repo"); if (mkdirat(target_installation_dir, "repo/objects", 0755) == -1) err(EXIT_FAILURE, "mkdir repo/objects"); if (mkdirat(target_installation_dir, "repo/tmp", 0775) == -1) err(EXIT_FAILURE, "mkdir repo/tmp"); if (mkdirat(target_installation_dir, "repo/tmp/cache", 0775) == -1) err(EXIT_FAILURE, "mkdir repo/tmp/cache"); if ((config = openat(target_installation_dir, "repo/config", O_WRONLY | O_CLOEXEC | O_NOFOLLOW | O_CREAT, 0644)) == -1) err(EXIT_FAILURE, "openat repo/config"); bind_mount(AT_FDCWD, CONFIG_PATH, config, ""); close(config); } int main(int, char **argv) { char *installation_path, *id; int params_dir, source_installation_dir, target_installation_dir, app_commit_dir; struct mount_attr attr = { .attr_clr = MOUNT_ATTR_NOSYMFOLLOW, .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NODEV, }; if (!(installation_path = *++argv)) errx(EXIT_FAILURE, "missing installation path"); if (!(id = *++argv)) errx(EXIT_FAILURE, "missing app ID"); if ((source_installation_dir = open(installation_path, O_PATH | O_CLOEXEC | O_DIRECTORY)) == -1) err(EXIT_FAILURE, "open %s", installation_path); params_dir = mkdir_openat(AT_FDCWD, "params", 0755); write_to(params_dir, "id", id); if (mkdir("flatpak", 0755) == -1) err(EXIT_FAILURE, "mkdir flatpak"); if ((target_installation_dir = syscall(SYS_open_tree, AT_FDCWD, "flatpak", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_RECURSIVE)) == -1) err(EXIT_FAILURE, "open_tree flatpak"); app_commit_dir = mount_app(source_installation_dir, target_installation_dir, params_dir, id); mount_runtime(source_installation_dir, target_installation_dir, params_dir, app_commit_dir); close(source_installation_dir); close(params_dir); set_up_repo(target_installation_dir); if (syscall(SYS_mount_setattr, target_installation_dir, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof attr) == -1) err(EXIT_FAILURE, "mount_setattr flatpak"); if (syscall(SYS_move_mount, target_installation_dir, "", AT_FDCWD, "flatpak", MOVE_MOUNT_F_EMPTY_PATH) == -1) err(EXIT_FAILURE, "move_mount flatpak flatpak"); close(target_installation_dir); }