From a6736532001fd625f1ab3dd82abc2a4c5366c79c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Ter=C3=A4s?= Date: Fri, 5 Nov 2021 13:20:19 +0200 Subject: [PATCH] database: implement uvol support by adding an abstraction layer to the file system --- src/Makefile | 2 +- src/apk_context.h | 11 +- src/apk_crypto.h | 8 +- src/apk_defines.h | 4 +- src/apk_extract.h | 10 -- src/apk_fs.h | 70 ++++++++++ src/apk_io.h | 4 +- src/app_add.c | 8 +- src/app_extract.c | 16 +-- src/context.c | 22 +++- src/database.c | 210 +++++++++++------------------- src/extract.c | 235 ---------------------------------- src/extract_v3.c | 31 ++--- src/fs_fsys.c | 316 ++++++++++++++++++++++++++++++++++++++++++++++ src/fs_uvol.c | 162 ++++++++++++++++++++++++ src/io.c | 14 +- src/meson.build | 4 +- src/package.c | 15 +-- src/print.c | 4 +- 19 files changed, 711 insertions(+), 435 deletions(-) create mode 100644 src/apk_fs.h delete mode 100644 src/extract.c create mode 100644 src/fs_fsys.c create mode 100644 src/fs_uvol.c diff --git a/src/Makefile b/src/Makefile index f7ebb66..0540cd8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -21,7 +21,7 @@ libapk_so := $(obj)/libapk.so.$(libapk_soname) libapk.so.$(libapk_soname)-objs := \ adb.o adb_comp.o adb_walk_adb.o adb_walk_genadb.o adb_walk_gentext.o adb_walk_text.o apk_adb.o \ atom.o blob.o commit.o common.o context.o crypto_openssl.o database.o hash.o \ - extract.o extract_v2.o extract_v3.o io.o io_gunzip.o io_url.o tar.o \ + extract_v2.o extract_v3.o fs_fsys.o fs_uvol.o io.o io_gunzip.o io_url.o tar.o \ package.o pathbuilder.o print.o solver.o trust.o version.o libapk.so.$(libapk_soname)-libs := libfetch/libfetch.a diff --git a/src/apk_context.h b/src/apk_context.h index 1bec2b9..7aae61b 100644 --- a/src/apk_context.h +++ b/src/apk_context.h @@ -9,9 +9,11 @@ #ifndef APK_CONTEXT_H #define APK_CONTEXT_H +#include "apk_blob.h" #include "apk_print.h" #include "apk_trust.h" #include "apk_io.h" +#include "apk_crypto.h" #include "adb.h" #define APK_SIMULATE BIT(0) @@ -34,8 +36,6 @@ #define APK_FORCE_NON_REPOSITORY BIT(4) #define APK_FORCE_BINARY_STDOUT BIT(5) -struct apk_database; - #define APK_OPENF_READ 0x0001 #define APK_OPENF_WRITE 0x0002 #define APK_OPENF_CREATE 0x0004 @@ -53,6 +53,8 @@ struct apk_database; APK_OPENF_NO_SCRIPTS | \ APK_OPENF_NO_WORLD) +struct apk_database; + struct apk_ctx { unsigned int flags, force, lock_wait; struct apk_out out; @@ -70,7 +72,9 @@ struct apk_ctx { struct apk_trust trust; struct apk_id_cache id_cache; struct apk_database *db; - int root_fd; + int root_fd, dest_fd; + + struct apk_digest_ctx dctx; }; void apk_ctx_init(struct apk_ctx *ac); @@ -81,6 +85,7 @@ struct apk_trust *apk_ctx_get_trust(struct apk_ctx *ac); struct apk_id_cache *apk_ctx_get_id_cache(struct apk_ctx *ac); static inline int apk_ctx_fd_root(struct apk_ctx *ac) { return ac->root_fd; } +static inline int apk_ctx_fd_dest(struct apk_ctx *ac) { return ac->dest_fd; } static inline time_t apk_ctx_since(struct apk_ctx *ac, time_t since) { return (ac->force & APK_FORCE_REFRESH) ? APK_ISTREAM_FORCE_REFRESH : since; } diff --git a/src/apk_crypto.h b/src/apk_crypto.h index 0330694..18bf3b5 100644 --- a/src/apk_crypto.h +++ b/src/apk_crypto.h @@ -90,10 +90,16 @@ static inline int apk_digest_ctx_init(struct apk_digest_ctx *dctx, uint8_t alg) #ifdef EVP_MD_CTX_FLAG_FINALISE EVP_MD_CTX_set_flags(dctx->mdctx, EVP_MD_CTX_FLAG_FINALISE); #endif - EVP_DigestInit_ex(dctx->mdctx, apk_digest_alg_to_evp(alg), 0); + if (alg != APK_DIGEST_NONE) EVP_DigestInit_ex(dctx->mdctx, apk_digest_alg_to_evp(alg), 0); return 0; } +static inline void apk_digest_ctx_reset(struct apk_digest_ctx *dctx, uint8_t alg) +{ + dctx->alg = alg; + EVP_DigestInit_ex(dctx->mdctx, apk_digest_alg_to_evp(alg), 0); +} + static inline void apk_digest_ctx_free(struct apk_digest_ctx *dctx) { EVP_MD_CTX_free(dctx->mdctx); dctx->mdctx = 0; diff --git a/src/apk_defines.h b/src/apk_defines.h index 395958b..0a50284 100644 --- a/src/apk_defines.h +++ b/src/apk_defines.h @@ -62,7 +62,9 @@ enum { APKE_INDEX_STALE, APKE_FILE_INTEGRITY, APKE_CACHE_NOT_AVAILABLE, - APKE_UVOL, + APKE_UVOL_NOT_AVAILABLE, + APKE_UVOL_ERROR, + APKE_UVOL_ROOT, }; static inline void *ERR_PTR(long error) { return (void*) error; } diff --git a/src/apk_extract.h b/src/apk_extract.h index ecb39ed..e3fabad 100644 --- a/src/apk_extract.h +++ b/src/apk_extract.h @@ -18,16 +18,6 @@ struct adb_obj; struct apk_ctx; struct apk_extract_ctx; -#define APK_EXTRACT_SKIP_FILE 0x111 - -#define APK_EXTRACTF_NO_CHOWN 0x0001 -#define APK_EXTRACTF_NO_OVERWRITE 0x0002 - -int apk_extract_file(int atfd, const struct apk_file_info *ae, - const char *extract_name, const char *hardlink_name, - struct apk_istream *is, apk_progress_cb cb, void *cb_ctx, - unsigned int extract_flags, struct apk_ctx *ac); - struct apk_extract_ops { int (*v2index)(struct apk_extract_ctx *, apk_blob_t *desc, struct apk_istream *is); int (*v2meta)(struct apk_extract_ctx *, struct apk_istream *is); diff --git a/src/apk_fs.h b/src/apk_fs.h new file mode 100644 index 0000000..6a2a285 --- /dev/null +++ b/src/apk_fs.h @@ -0,0 +1,70 @@ +/* apk_fs.h - Alpine Package Keeper (APK) + * + * Copyright (C) 2021 Timo Teräs + * All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#ifndef APK_FS_H +#define APK_FS_H + +#include "apk_context.h" +#include "apk_io.h" +#include "apk_pathbuilder.h" + +#define APK_FS_CTRL_COMMIT 1 +#define APK_FS_CTRL_APKNEW 2 +#define APK_FS_CTRL_CANCEL 3 +#define APK_FS_CTRL_DELETE 4 + +#define APK_FS_DIR_MODIFIED 1 + +struct apk_fsdir_ops; + +struct apk_fsdir { + struct apk_ctx *ac; + const struct apk_fsdir_ops *ops; + struct apk_pathbuilder pb; + apk_blob_t pkgctx; +}; + +struct apk_fsdir_ops { + int (*dir_create)(struct apk_fsdir *, mode_t); + int (*dir_delete)(struct apk_fsdir *); + int (*dir_check)(struct apk_fsdir *, mode_t, uid_t, gid_t); + int (*dir_update_perms)(struct apk_fsdir *, mode_t, uid_t, gid_t); + + int (*file_extract)(struct apk_ctx *, const struct apk_file_info *, struct apk_istream *, apk_progress_cb, void *, unsigned int, apk_blob_t); + int (*file_control)(struct apk_fsdir *, apk_blob_t, int); + int (*file_digest)(struct apk_fsdir *, apk_blob_t, uint8_t alg, struct apk_digest *); +}; + +#define APK_FSEXTRACTF_NO_CHOWN 0x0001 +#define APK_FSEXTRACTF_NO_OVERWRITE 0x0002 + +int apk_fs_extract(struct apk_ctx *, const struct apk_file_info *, struct apk_istream *, apk_progress_cb, void *, unsigned int, apk_blob_t); + +void apk_fsdir_get(struct apk_fsdir *, apk_blob_t dir, struct apk_ctx *, apk_blob_t); + +static inline int apk_fsdir_create(struct apk_fsdir *fs, mode_t mode) { + return fs->ops->dir_create(fs, mode); +} +static inline int apk_fsdir_delete(struct apk_fsdir *fs) { + return fs->ops->dir_delete(fs); +} +static inline int apk_fsdir_check(struct apk_fsdir *fs, mode_t mode, uid_t uid, gid_t gid) { + return fs->ops->dir_check(fs, mode, uid, gid); +} +static inline int apk_fsdir_update_perms(struct apk_fsdir *fs, mode_t mode, uid_t uid, gid_t gid) { + return fs->ops->dir_update_perms(fs, mode, uid, gid); +} + +static inline int apk_fsdir_file_control(struct apk_fsdir *fs, apk_blob_t filename, int ctrl) { + return fs->ops->file_control(fs, filename, ctrl); +} +static inline int apk_fsdir_file_digest(struct apk_fsdir *fs, apk_blob_t filename, uint8_t alg, struct apk_digest *dgst) { + return fs->ops->file_digest(fs, filename, alg, dgst); +} + +#endif diff --git a/src/apk_io.h b/src/apk_io.h index 762f9e0..d04638a 100644 --- a/src/apk_io.h +++ b/src/apk_io.h @@ -44,7 +44,6 @@ struct apk_file_meta { struct apk_file_info { const char *name; const char *link_target; - const char *uvol_name; const char *uname; const char *gname; off_t size; @@ -140,8 +139,9 @@ struct apk_digest_istream { struct apk_istream *pis; struct apk_digest *digest; struct apk_digest_ctx dctx; + off_t size_left; }; -struct apk_istream *apk_istream_verify(struct apk_digest_istream *dis, struct apk_istream *is, struct apk_digest *d); +struct apk_istream *apk_istream_verify(struct apk_digest_istream *dis, struct apk_istream *is, off_t size, struct apk_digest *d); #define APK_ISTREAM_TEE_COPY_META 1 #define APK_ISTREAM_TEE_OPTIONAL 2 diff --git a/src/app_add.c b/src/app_add.c index 1d20e81..614dfb4 100644 --- a/src/app_add.c +++ b/src/app_add.c @@ -10,11 +10,13 @@ #include #include #include + #include "apk_applet.h" #include "apk_database.h" #include "apk_print.h" #include "apk_solver.h" #include "apk_extract.h" +#include "apk_fs.h" struct add_ctx { const char *virtpkg; @@ -43,7 +45,7 @@ static int option_parse_applet(void *ctx, struct apk_ctx *ac, int opt, const cha actx->solver_flags |= APK_SOLVERF_LATEST; break; case OPT_ADD_no_chown: - actx->extract_flags |= APK_EXTRACTF_NO_CHOWN; + actx->extract_flags |= APK_FSEXTRACTF_NO_CHOWN; break; case OPT_ADD_upgrade: actx->solver_flags |= APK_SOLVERF_UPGRADE; @@ -124,8 +126,8 @@ static int add_main(void *ctx, struct apk_ctx *ac, struct apk_string_array *args apk_dependency_array_copy(&world, db->world); - if (getuid() != 0 || (actx->extract_flags & APK_EXTRACTF_NO_CHOWN)) - db->extract_flags |= APK_EXTRACTF_NO_CHOWN; + if (getuid() != 0 || (actx->extract_flags & APK_FSEXTRACTF_NO_CHOWN)) + db->extract_flags |= APK_FSEXTRACTF_NO_CHOWN; if (actx->virtpkg) { apk_blob_t b = APK_BLOB_STR(actx->virtpkg); diff --git a/src/app_extract.c b/src/app_extract.c index 2dd310b..fea4924 100644 --- a/src/app_extract.c +++ b/src/app_extract.c @@ -15,6 +15,7 @@ #include "apk_applet.h" #include "apk_print.h" #include "apk_extract.h" +#include "apk_fs.h" struct extract_ctx { const char *destination; @@ -22,7 +23,6 @@ struct extract_ctx { struct apk_extract_ctx ectx; struct apk_ctx *ac; - int root_fd; }; @@ -41,7 +41,7 @@ static int option_parse_applet(void *pctx, struct apk_ctx *ac, int opt, const ch ctx->destination = optarg; break; case OPT_EXTRACT_no_chown: - ctx->extract_flags |= APK_EXTRACTF_NO_CHOWN; + ctx->extract_flags |= APK_FSEXTRACTF_NO_CHOWN; break; default: return -ENOTSUP; @@ -66,8 +66,7 @@ static int extract_file(struct apk_extract_ctx *ectx, const struct apk_file_info apk_dbg2(out, "%s", fi->name); - return apk_extract_file(ctx->root_fd, fi, 0, 0, is, 0, 0, - ctx->extract_flags, ectx->ac); + return apk_fs_extract(ctx->ac, fi, is, 0, 0, ctx->extract_flags, APK_BLOB_NULL); } static const struct apk_extract_ops extract_ops = { @@ -84,10 +83,11 @@ static int extract_main(void *pctx, struct apk_ctx *ac, struct apk_string_array int r = 0; ctx->ac = ac; - if (!(ac->force & APK_FORCE_OVERWRITE)) ctx->extract_flags |= APK_EXTRACTF_NO_OVERWRITE; + if (!(ac->force & APK_FORCE_OVERWRITE)) ctx->extract_flags |= APK_FSEXTRACTF_NO_OVERWRITE; if (!ctx->destination) ctx->destination = "."; - ctx->root_fd = openat(AT_FDCWD, ctx->destination, O_RDONLY); - if (ctx->root_fd < 0) { + + ac->dest_fd = openat(AT_FDCWD, ctx->destination, O_RDONLY); + if (ac->dest_fd < 0) { r = -errno; apk_err(out, "Error opening destination '%s': %s", ctx->destination, apk_error_str(r)); @@ -103,7 +103,7 @@ static int extract_main(void *pctx, struct apk_ctx *ac, struct apk_string_array break; } } - close(ctx->root_fd); + close(ac->dest_fd); return r; } diff --git a/src/context.c b/src/context.c index 4ad2bbf..7afb579 100644 --- a/src/context.c +++ b/src/context.c @@ -21,6 +21,7 @@ void apk_ctx_init(struct apk_ctx *ac) ac->out.out = stdout; ac->out.err = stderr; ac->out.verbosity = 1; + apk_digest_ctx_init(&ac->dctx, APK_DIGEST_SHA256); } void apk_ctx_free(struct apk_ctx *ac) @@ -43,8 +44,19 @@ int apk_ctx_prepare(struct apk_ctx *ac) if (!ac->keys_dir) ac->keys_dir = "etc/apk/keys"; if (!ac->root) ac->root = "/"; if (!ac->cache_max_age) ac->cache_max_age = 4*60*60; /* 4 hours default */ - if (!strcmp(ac->root, "/")) ac->flags |= APK_NO_CHROOT; /* skip chroot if root is default */ - ac->uvol = getenv("APK_UVOL") ?: "/usr/bin/uvol"; + + if (!strcmp(ac->root, "/")) { + // No chroot needed if using system root + ac->flags |= APK_NO_CHROOT; + + // Check uvol availability + ac->uvol = getenv("APK_UVOL") ?: "/usr/bin/uvol"; + if (access(ac->uvol, X_OK) != 0) + ac->uvol = ERR_PTR(-APKE_UVOL_NOT_AVAILABLE); + } else { + ac->uvol = ERR_PTR(-APKE_UVOL_ROOT); + } + ac->root_fd = openat(AT_FDCWD, ac->root, O_RDONLY | O_CLOEXEC); if (ac->root_fd < 0 && (ac->open_flags & APK_OPENF_CREATE)) { @@ -55,13 +67,15 @@ int apk_ctx_prepare(struct apk_ctx *ac) apk_err(&ac->out, "Unable to open root: %s", apk_error_str(errno)); return -errno; } + ac->dest_fd = ac->root_fd; if (ac->open_flags & APK_OPENF_WRITE) { - const char *log_path = "var/log/apk.log", *log_dir = "var/log"; + const char *log_path = "var/log/apk.log"; const int lflags = O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC; int fd = openat(ac->root_fd, log_path, lflags, 0644); if (fd < 0 && (ac->open_flags & APK_OPENF_CREATE)) { - mkdirat(ac->root_fd, log_dir, 0755); + mkdirat(ac->root_fd, "var", 0755); + mkdirat(ac->root_fd, "var/log", 0755); fd = openat(ac->root_fd, log_path, lflags, 0644); } if (fd < 0) { diff --git a/src/database.c b/src/database.c index f5c8d8c..a3a5800 100644 --- a/src/database.c +++ b/src/database.c @@ -37,6 +37,7 @@ #include "apk_openssl.h" #include "apk_tar.h" #include "apk_adb.h" +#include "apk_fs.h" static const apk_spn_match_def apk_spn_repo_separators = { [1] = (1<<1) /* tab */, @@ -83,6 +84,11 @@ struct install_ctx { struct hlist_node **file_diri_node; }; +static apk_blob_t apk_pkg_ctx(struct apk_package *pkg) +{ + return APK_BLOB_PTR_LEN(pkg->name->name, strlen(pkg->name->name)+1); +} + static apk_blob_t pkg_name_get_key(apk_hash_item item) { return APK_BLOB_STR(((struct apk_name *) item)->name); @@ -244,23 +250,21 @@ static struct apk_db_acl *apk_db_acl_atomize_digest(struct apk_database *db, mod static void apk_db_dir_prepare(struct apk_database *db, struct apk_db_dir *dir, mode_t newmode) { - struct stat st; + struct apk_fsdir d; if (dir->namelen == 0) return; if (dir->created) return; - if (fstatat(db->root_fd, dir->name, &st, AT_SYMLINK_NOFOLLOW) == 0) { - /* If directory exists and stats match what we expect, - * then we can allow auto updating the permissions */ - dir->created = 1; - dir->update_permissions |= - (st.st_mode & 07777) == (dir->mode & 07777) && - st.st_uid == dir->uid && st.st_gid == dir->gid; - } else if (newmode) { + apk_fsdir_get(&d, APK_BLOB_PTR_LEN(dir->name, dir->namelen), db->ctx, APK_BLOB_NULL); + switch (apk_fsdir_check(&d, dir->mode, dir->uid, dir->gid)) { + default: if (!(db->ctx->flags & APK_SIMULATE)) - mkdirat(db->root_fd, dir->name, newmode); - dir->created = 1; + apk_fsdir_create(&d, dir->mode); + case 0: dir->update_permissions = 1; + case APK_FS_DIR_MODIFIED: + dir->created = 1; + break; } } @@ -272,9 +276,11 @@ void apk_db_dir_unref(struct apk_database *db, struct apk_db_dir *dir, int rmdir if (dir->namelen != 0) { if (rmdir_mode == APK_DIR_REMOVE) { dir->modified = 1; - if (!(db->ctx->flags & APK_SIMULATE) && - unlinkat(db->root_fd, dir->name, AT_REMOVEDIR) != 0) - ; + if (!(db->ctx->flags & APK_SIMULATE)) { + struct apk_fsdir d; + apk_fsdir_get(&d, APK_BLOB_PTR_LEN(dir->name, dir->namelen), db->ctx, APK_BLOB_NULL); + apk_fsdir_delete(&d); + } } apk_db_dir_unref(db, dir->parent, rmdir_mode); dir->parent = NULL; @@ -1953,20 +1959,15 @@ static int update_permissions(apk_hash_item item, void *pctx) struct update_permissions_ctx *ctx = pctx; struct apk_database *db = ctx->db; struct apk_db_dir *dir = (struct apk_db_dir *) item; - struct stat st; - int r; + struct apk_fsdir d; if (dir->refs == 0) return 0; if (!dir->update_permissions) return 0; dir->seen = 0; - r = fstatat(db->root_fd, dir->name, &st, AT_SYMLINK_NOFOLLOW); - if (r < 0 || (st.st_mode & 07777) != (dir->mode & 07777)) - if (fchmodat(db->root_fd, dir->name, dir->mode, 0) < 0) - ctx->errors++; - if (r < 0 || st.st_uid != dir->uid || st.st_gid != dir->gid) - if (fchownat(db->root_fd, dir->name, dir->uid, dir->gid, 0) < 0) - ctx->errors++; + apk_fsdir_get(&d, APK_BLOB_PTR_LEN(dir->name, dir->namelen), db->ctx, APK_BLOB_NULL); + if (apk_fsdir_update_perms(&d, dir->mode, dir->uid, dir->gid) != 0) + ctx->errors++; return 0; } @@ -2330,37 +2331,6 @@ static struct apk_db_dir_instance *apk_db_install_directory_entry(struct install return diri; } -#define TMPNAME_MAX (PATH_MAX + 64) - -static const char *format_tmpname(struct apk_package *pkg, struct apk_db_file *f, char tmpname[static TMPNAME_MAX]) -{ - struct apk_digest_ctx dctx; - struct apk_digest d; - apk_blob_t b = APK_BLOB_PTR_LEN(tmpname, TMPNAME_MAX); - - if (!f) return NULL; - - if (apk_digest_ctx_init(&dctx, APK_DIGEST_SHA256) != 0) return NULL; - - apk_digest_ctx_update(&dctx, pkg->name->name, strlen(pkg->name->name) + 1); - apk_digest_ctx_update(&dctx, f->diri->dir->name, f->diri->dir->namelen); - apk_digest_ctx_update(&dctx, "/", 1); - apk_digest_ctx_update(&dctx, f->name, f->namelen); - apk_digest_ctx_final(&dctx, &d); - apk_digest_ctx_free(&dctx); - - apk_blob_push_blob(&b, APK_BLOB_PTR_LEN(f->diri->dir->name, f->diri->dir->namelen)); - if (f->diri->dir->namelen > 0) { - apk_blob_push_blob(&b, APK_BLOB_STR("/.apk.")); - } else { - apk_blob_push_blob(&b, APK_BLOB_STR(".apk.")); - } - apk_blob_push_hexdump(&b, APK_BLOB_PTR_LEN((char *)d.data, 24)); - apk_blob_push_blob(&b, APK_BLOB_PTR_LEN("", 1)); - - return tmpname; -} - static int contains_control_character(const char *str) { for (const uint8_t *p = (const uint8_t *) str; *p; p++) { @@ -2386,7 +2356,7 @@ static int apk_db_install_v3meta(struct apk_extract_ctx *ectx, struct adb_obj *p struct adb_obj triggers, pkginfo, obj; int i; - apk_pkg_from_adb(db, ctx->pkg, pkg); + // Extract the information not available in index adb_ro_obj(pkg, ADBI_PKG_PKGINFO, &pkginfo); apk_deps_from_adb(&ipkg->replaces, db, adb_ro_obj(&pkginfo, ADBI_PI_REPLACES, &obj)); ipkg->replaces_priority = adb_ro_int(&pkginfo, ADBI_PI_PRIORITY); @@ -2424,7 +2394,6 @@ static int apk_db_install_file(struct apk_extract_ctx *ectx, const struct apk_fi struct apk_db_dir_instance *diri = ctx->diri; struct apk_db_file *file, *link_target_file = NULL; int ret = 0, r; - char tmpname_file[TMPNAME_MAX], tmpname_link_target[TMPNAME_MAX]; apk_db_run_pending_script(ctx); if (ae->name[0] == '.') return 0; @@ -2440,13 +2409,6 @@ static int apk_db_install_file(struct apk_extract_ctx *ectx, const struct apk_fi return 0; } - if (ae->uvol_name) { - apk_warn(out, PKG_VER_FMT": %s: uvol not supported yet", - PKG_VER_PRINTF(pkg), ae->name); - ipkg->broken_files = 1; - return APK_EXTRACT_SKIP_FILE; - } - /* Installable entry */ ctx->current_file_size = apk_calc_installed_size(ae->size); if (!S_ISDIR(ae->mode)) { @@ -2563,12 +2525,7 @@ static int apk_db_install_file(struct apk_extract_ctx *ectx, const struct apk_fi /* Extract the file with temporary name */ file->acl = apk_db_acl_atomize_digest(db, ae->mode, ae->uid, ae->gid, &ae->xattr_digest); - r = apk_extract_file( - db->root_fd, ae, - format_tmpname(pkg, file, tmpname_file), - format_tmpname(pkg, link_target_file, tmpname_link_target), - is, extract_cb, ctx, db->extract_flags, ac); - + r = apk_fs_extract(ac, ae, is, extract_cb, ctx, db->extract_flags, apk_pkg_ctx(pkg)); switch (r) { case 0: /* Hardlinks need special care for checksum */ @@ -2596,6 +2553,10 @@ static int apk_db_install_file(struct apk_extract_ctx *ectx, const struct apk_fi ctx->missing_checksum = 1; } break; + case -APKE_UVOL_ROOT: + case -APKE_UVOL_NOT_AVAILABLE: + ipkg->broken_files = 1; + break; case -ENOTSUP: ipkg->broken_xattr = 1; break; @@ -2638,22 +2599,20 @@ static void apk_db_purge_pkg(struct apk_database *db, struct apk_db_dir_instance *diri; struct apk_db_file *file; struct apk_db_file_hash_key key; - struct apk_file_info fi; + struct apk_fsdir d; + struct apk_digest dgst; struct hlist_node *dc, *dn, *fc, *fn; unsigned long hash; - char name[TMPNAME_MAX]; + int ctrl = is_installed ? APK_FS_CTRL_DELETE : APK_FS_CTRL_CANCEL; hlist_for_each_entry_safe(diri, dc, dn, &ipkg->owned_dirs, pkg_dirs_list) { + apk_blob_t dirname = APK_BLOB_PTR_LEN(diri->dir->name, diri->dir->namelen); if (is_installed) diri->dir->modified = 1; + apk_fsdir_get(&d, dirname, db->ctx, apk_pkg_ctx(ipkg->pkg)); hlist_for_each_entry_safe(file, fc, fn, &diri->owned_files, diri_files_list) { - if (is_installed) - snprintf(name, sizeof name, DIR_FILE_FMT, DIR_FILE_PRINTF(diri->dir, file)); - else - format_tmpname(ipkg->pkg, file, name); - key = (struct apk_db_file_hash_key) { - .dirname = APK_BLOB_PTR_LEN(diri->dir->name, diri->dir->namelen), + .dirname = dirname, .filename = APK_BLOB_PTR_LEN(file->name, file->namelen), }; hash = apk_blob_hash_seed(key.filename, diri->dir->hash); @@ -2661,10 +2620,11 @@ static void apk_db_purge_pkg(struct apk_database *db, (diri->dir->protect_mode == APK_PROTECT_NONE) || (db->ctx->flags & APK_PURGE) || (file->csum.type != APK_CHECKSUM_NONE && - apk_fileinfo_get(db->root_fd, name, APK_FI_NOFOLLOW | APK_FI_DIGEST(apk_dbf_digest(file)), &fi, &db->atoms) == 0 && - apk_digest_cmp_csum(&fi.digest, &file->csum) == 0)) - unlinkat(db->root_fd, name, 0); - apk_dbg2(out, "%s", name); + apk_fsdir_file_digest(&d, key.filename, apk_dbf_digest(file), &dgst) == 0 && + apk_digest_cmp_csum(&dgst, &file->csum) == 0)) + apk_fsdir_file_control(&d, key.filename, ctrl); + + apk_dbg2(out, DIR_FILE_FMT, DIR_FILE_PRINTF(diri->dir, file)); __hlist_del(fc, &diri->owned_files.first); if (is_installed) { apk_hash_delete_hashed(&db->installed.files, APK_BLOB_BUF(&key), hash); @@ -2685,22 +2645,22 @@ static void apk_db_migrate_files(struct apk_database *db, struct apk_db_dir *dir; struct apk_db_file *file, *ofile; struct apk_db_file_hash_key key; - struct apk_file_info fi; struct hlist_node *dc, *dn, *fc, *fn; + struct apk_fsdir d; + struct apk_digest dgst; unsigned long hash; - char name[PATH_MAX], tmpname[TMPNAME_MAX]; - int cstype, r; + apk_blob_t dirname; + int r, ctrl; hlist_for_each_entry_safe(diri, dc, dn, &ipkg->owned_dirs, pkg_dirs_list) { dir = diri->dir; dir->modified = 1; + dirname = APK_BLOB_PTR_LEN(dir->name, dir->namelen); + apk_fsdir_get(&d, dirname, db->ctx, apk_pkg_ctx(ipkg->pkg)); hlist_for_each_entry_safe(file, fc, fn, &diri->owned_files, diri_files_list) { - snprintf(name, sizeof(name), DIR_FILE_FMT, DIR_FILE_PRINTF(diri->dir, file)); - format_tmpname(ipkg->pkg, file, tmpname); - key = (struct apk_db_file_hash_key) { - .dirname = APK_BLOB_PTR_LEN(dir->name, dir->namelen), + .dirname = dirname, .filename = APK_BLOB_PTR_LEN(file->name, file->namelen), }; @@ -2710,61 +2670,41 @@ static void apk_db_migrate_files(struct apk_database *db, ofile = (struct apk_db_file *) apk_hash_get_hashed( &db->installed.files, APK_BLOB_BUF(&key), hash); - /* We want to compare checksums only if one exists - * in db, and the file is in a protected path */ - cstype = APK_CHECKSUM_NONE; - if (ofile != NULL && diri->dir->protect_mode != APK_PROTECT_NONE) - cstype = APK_FI_DIGEST(apk_dbf_digest(ofile)); - cstype |= APK_FI_NOFOLLOW; - - r = apk_fileinfo_get(db->root_fd, name, cstype, &fi, &db->atoms); + ctrl = APK_FS_CTRL_COMMIT; if (ofile && ofile->diri->pkg->name == NULL) { - /* File was from overlay, delete the - * packages version */ - unlinkat(db->root_fd, tmpname, 0); + // File was from overlay, delete the package's version + ctrl = APK_FS_CTRL_CANCEL; } else if ((diri->dir->protect_mode != APK_PROTECT_NONE) && - (r == 0) && - (ofile == NULL || - ofile->csum.type == APK_CHECKSUM_NONE || - apk_digest_cmp_csum(&fi.digest, &ofile->csum) != 0)) { - /* Protected directory, with file without - * db entry, or local modifications. - * - * Delete the apk-new if it's identical with the - * existing file */ - if (ofile == NULL || - ofile->csum.type != file->csum.type) - apk_fileinfo_get(db->root_fd, name, - APK_FI_NOFOLLOW |APK_FI_DIGEST(apk_dbf_digest(file)), - &fi, &db->atoms); + (!ofile || ofile->csum.type == APK_CHECKSUM_NONE || + (apk_fsdir_file_digest(&d, key.filename, apk_dbf_digest(ofile), &dgst) == 0 && + apk_digest_cmp_csum(&dgst, &ofile->csum) != 0))) { + // Protected directory, and a file without db entry + // or with local modifications. Keep the filesystem file. + // Determine if the package's file should be kept as .apk-new if ((db->ctx->flags & APK_CLEAN_PROTECTED) || (file->csum.type != APK_CHECKSUM_NONE && - apk_digest_cmp_csum(&fi.digest, &file->csum) == 0)) { - unlinkat(db->root_fd, tmpname, 0); + (apk_fsdir_file_digest(&d, key.filename, apk_dbf_digest(file), &dgst) == 0 && + apk_digest_cmp_csum(&dgst, &file->csum) == 0))) { + // No .apk-new files allowed, or the file on disk has the same + // hash as the file from new package. Keep the on disk one. + ctrl = APK_FS_CTRL_CANCEL; } else { - snprintf(name, sizeof name, - DIR_FILE_FMT ".apk-new", - DIR_FILE_PRINTF(diri->dir, file)); - if (renameat(db->root_fd, tmpname, - db->root_fd, name) != 0) { - apk_err(out, PKG_VER_FMT": failed to rename %s to %s.", - PKG_VER_PRINTF(ipkg->pkg), - tmpname, name); - ipkg->broken_files = 1; - } - } - - } else { - /* Overwrite the old file */ - if (renameat(db->root_fd, tmpname, - db->root_fd, name) != 0) { - apk_err(out, PKG_VER_FMT": failed to rename %s to %s.", - PKG_VER_PRINTF(ipkg->pkg), tmpname, name); - ipkg->broken_files = 1; + // All files difference. Use the package's file as .apk-new. + ctrl = APK_FS_CTRL_APKNEW; } } - /* Claim ownership of the file in db */ + // Commit changes + r = apk_fsdir_file_control(&d, key.filename, ctrl); + if (r < 0) { + apk_err(out, PKG_VER_FMT": failed to commit " DIR_FILE_FMT ": %s", + PKG_VER_PRINTF(ipkg->pkg), + DIR_FILE_PRINTF(diri->dir, file), + apk_error_str(r)); + ipkg->broken_files = 1; + } + + // Claim ownership of the file in db if (ofile != file) { if (ofile != NULL) { hlist_del(&ofile->diri_files_list, diff --git a/src/extract.c b/src/extract.c deleted file mode 100644 index 00d871f..0000000 --- a/src/extract.c +++ /dev/null @@ -1,235 +0,0 @@ -/* extract.c - Alpine Package Keeper (APK) - * - * Copyright (C) 2005-2008 Natanael Copa - * Copyright (C) 2008-2011 Timo Teräs - * All rights reserved. - * - * SPDX-License-Identifier: GPL-2.0-only - */ - -#include -#include -#include -#include -#include - -#include "apk_context.h" -#include "apk_extract.h" - -static int uvol_run(struct apk_ctx *ac, char *action, const char *volname, char *arg1, char *arg2) -{ - struct apk_out *out = &ac->out; - pid_t pid; - int r, status; - char *argv[] = { (char*)apk_ctx_get_uvol(ac), action, (char*) volname, arg1, arg2, 0 }; - posix_spawn_file_actions_t act; - - posix_spawn_file_actions_init(&act); - posix_spawn_file_actions_addclose(&act, STDIN_FILENO); - r = posix_spawn(&pid, apk_ctx_get_uvol(ac), &act, 0, argv, environ); - posix_spawn_file_actions_destroy(&act); - if (r != 0) { - apk_err(out, "%s: uvol exec error: %s", volname, apk_error_str(r)); - return r; - } - while (waitpid(pid, &status, 0) < 0 && errno == EINTR); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - apk_err(out, "%s: uvol exited with error %d", volname, WEXITSTATUS(status)); - return -APKE_UVOL; - } - return 0; -} - -static int uvol_extract(struct apk_ctx *ac, const char *volname, char *arg1, off_t sz, - struct apk_istream *is, apk_progress_cb cb, void *cb_ctx) -{ - struct apk_out *out = &ac->out; - struct apk_ostream *os; - pid_t pid; - int r, status, pipefds[2]; - char *argv[] = { (char*)apk_ctx_get_uvol(ac), "write", (char*) volname, arg1, 0 }; - posix_spawn_file_actions_t act; - - if (pipe2(pipefds, O_CLOEXEC) != 0) return -errno; - - posix_spawn_file_actions_init(&act); - posix_spawn_file_actions_adddup2(&act, pipefds[0], STDIN_FILENO); - r = posix_spawn(&pid, apk_ctx_get_uvol(ac), &act, 0, argv, environ); - posix_spawn_file_actions_destroy(&act); - if (r != 0) { - apk_err(out, "%s: uvol exec error: %s", volname, apk_error_str(r)); - return r; - } - close(pipefds[0]); - os = apk_ostream_to_fd(pipefds[1]); - apk_stream_copy(is, os, sz, cb, cb_ctx, 0); - r = apk_ostream_close(os); - if (r != 0) { - if (r >= 0) r = -APKE_UVOL; - apk_err(out, "%s: uvol write error: %s", volname, apk_error_str(r)); - return r; - } - - while (waitpid(pid, &status, 0) < 0 && errno == EINTR); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - apk_err(out, "%s: uvol exited with error %d", volname, WEXITSTATUS(status)); - return -APKE_UVOL; - } - - return 0; -} - -static int apk_extract_volume(struct apk_ctx *ac, const struct apk_file_info *fi, - struct apk_istream *is, apk_progress_cb cb, void *cb_ctx) -{ - char size[64]; - int r; - - snprintf(size, sizeof size, "%ju", fi->size); - r = uvol_run(ac, "create", fi->uvol_name, size, "ro"); - if (r != 0) return r; - - r = uvol_extract(ac, fi->uvol_name, size, fi->size, is, cb, cb_ctx); - if (r == 0) r = uvol_run(ac, "up", fi->uvol_name, 0, 0); - if (r != 0) uvol_run(ac, "remove", fi->uvol_name, 0, 0); - return r; -} - -int apk_extract_file(int atfd, const struct apk_file_info *ae, - const char *extract_name, const char *link_target, - struct apk_istream *is, - apk_progress_cb cb, void *cb_ctx, - unsigned int extract_flags, struct apk_ctx *ac) -{ - struct apk_out *out = &ac->out; - struct apk_xattr *xattr; - const char *fn = extract_name ?: ae->name; - int fd, r = -1, atflags = 0, ret = 0; - - if (ae->uvol_name && is) { - if (extract_name || link_target) return -APKE_UVOL; - return apk_extract_volume(ac, ae, is, cb, cb_ctx); - } - - if (!S_ISDIR(ae->mode) && !(extract_flags & APK_EXTRACTF_NO_OVERWRITE)) { - if (unlinkat(atfd, fn, 0) != 0 && errno != ENOENT) return -errno; - } - - switch (ae->mode & S_IFMT) { - case S_IFDIR: - r = mkdirat(atfd, fn, ae->mode & 07777); - if (r < 0 && errno != EEXIST) - ret = -errno; - break; - case S_IFREG: - if (ae->link_target == NULL) { - int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC | O_EXCL; - int fd = openat(atfd, fn, flags, ae->mode & 07777); - if (fd < 0) { - ret = -errno; - break; - } - struct apk_ostream *os = apk_ostream_to_fd(fd); - if (IS_ERR(os)) { - ret = PTR_ERR(os); - break; - } - apk_stream_copy(is, os, ae->size, cb, cb_ctx, 0); - r = apk_ostream_close(os); - if (r < 0) { - unlinkat(atfd, fn, 0); - ret = r; - } - } else { - r = linkat(atfd, link_target ?: ae->link_target, atfd, fn, 0); - if (r < 0) ret = -errno; - } - break; - case S_IFLNK: - r = symlinkat(link_target ?: ae->link_target, atfd, fn); - if (r < 0) ret = -errno; - atflags |= AT_SYMLINK_NOFOLLOW; - break; - case S_IFBLK: - case S_IFCHR: - case S_IFIFO: - r = mknodat(atfd, fn, ae->mode, ae->device); - if (r < 0) ret = -errno; - break; - } - if (ret) { - apk_err(out, "Failed to create %s: %s", ae->name, strerror(-ret)); - return ret; - } - - if (!(extract_flags & APK_EXTRACTF_NO_CHOWN)) { - r = fchownat(atfd, fn, ae->uid, ae->gid, atflags); - if (r < 0) { - apk_err(out, "Failed to set ownership on %s: %s", - fn, strerror(errno)); - if (!ret) ret = -errno; - } - - /* chown resets suid bit so we need set it again */ - if (ae->mode & 07000) { - r = fchmodat(atfd, fn, ae->mode & 07777, atflags); - if (r < 0) { - apk_err(out, "Failed to set file permissions on %s: %s", - fn, strerror(errno)); - if (!ret) ret = -errno; - } - } - } - - /* extract xattrs */ - if (!S_ISLNK(ae->mode) && ae->xattrs && ae->xattrs->num) { - r = 0; - fd = openat(atfd, fn, O_RDWR); - if (fd >= 0) { - foreach_array_item(xattr, ae->xattrs) { - if (fsetxattr(fd, xattr->name, xattr->value.ptr, xattr->value.len, 0) < 0) { - r = -errno; - if (r != -ENOTSUP) break; - } - } - close(fd); - } else { - r = -errno; - } - if (r) { - if (r != -ENOTSUP) - apk_err(out, "Failed to set xattrs on %s: %s", - fn, strerror(-r)); - if (!ret) ret = r; - } - } - - if (!S_ISLNK(ae->mode)) { - /* preserve modification time */ - struct timespec times[2]; - - times[0].tv_sec = times[1].tv_sec = ae->mtime; - times[0].tv_nsec = times[1].tv_nsec = 0; - r = utimensat(atfd, fn, times, atflags); - if (r < 0) { - apk_err(out, "Failed to preserve modification time on %s: %s", - fn, strerror(errno)); - if (!ret || ret == -ENOTSUP) ret = -errno; - } - } - - return ret; -} - -int apk_extract(struct apk_extract_ctx *ectx, struct apk_istream *is) -{ - void *sig; - - if (IS_ERR(is)) return PTR_ERR(is); - - sig = apk_istream_peek(is, 4); - if (IS_ERR(sig)) return apk_istream_close_error(is, PTR_ERR(sig)); - - if (memcmp(sig, "ADB", 3) == 0) return apk_extract_v3(ectx, is); - return apk_extract_v2(ectx, is); -} diff --git a/src/extract_v3.c b/src/extract_v3.c index dcc5c24..c25b2ae 100644 --- a/src/extract_v3.c +++ b/src/extract_v3.c @@ -22,15 +22,6 @@ struct apk_extract_v3_ctx { struct apk_pathbuilder pb; }; -static const char *uvol_detect(struct apk_ctx *ac, const char *path) -{ - if (!apk_ctx_get_uvol(ac)) return 0; - if (strncmp(path, "uvol", 4) != 0) return 0; - if (path[4] == 0) return path; - if (path[4] == '/') return &path[5]; - return 0; -} - static void apk_extract_v3_acl(struct apk_file_info *fi, struct adb_obj *o, struct apk_id_cache *idc) { fi->mode = adb_ro_int(o, ADBI_ACL_MODE); @@ -41,11 +32,9 @@ static void apk_extract_v3_acl(struct apk_file_info *fi, struct adb_obj *o, stru static int apk_extract_v3_file(struct apk_extract_ctx *ectx, off_t sz, struct apk_istream *is) { struct apk_extract_v3_ctx *ctx = ectx->pctx; - struct apk_ctx *ac = ectx->ac; const char *path_name = apk_pathbuilder_cstr(&ctx->pb); struct apk_file_info fi = { .name = path_name, - .uvol_name = uvol_detect(ac, path_name), .size = adb_ro_int(&ctx->file, ADBI_FI_SIZE), .mtime = adb_ro_int(&ctx->file, ADBI_FI_MTIME), }; @@ -92,23 +81,18 @@ static int apk_extract_v3_file(struct apk_extract_ctx *ectx, off_t sz, struct ap if (fi.digest.alg == APK_DIGEST_NONE) return -APKE_ADB_SCHEMA; fi.mode |= S_IFREG; - r = ectx->ops->file(ectx, &fi, apk_istream_verify(&dis, is, &fi.digest)); - if (r == APK_EXTRACT_SKIP_FILE) - return 0; + r = ectx->ops->file(ectx, &fi, apk_istream_verify(&dis, is, fi.size, &fi.digest)); return apk_istream_close_error(&dis.is, r); } static int apk_extract_v3_directory(struct apk_extract_ctx *ectx) { struct apk_extract_v3_ctx *ctx = ectx->pctx; - struct apk_ctx *ac = ectx->ac; struct apk_file_info fi = { .name = apk_pathbuilder_cstr(&ctx->pb), }; struct adb_obj acl; - if (uvol_detect(ac, fi.name)) return 0; - apk_extract_v3_acl(&fi, adb_ro_obj(&ctx->path, ADBI_DI_ACL, &acl), apk_ctx_get_id_cache(ectx->ac)); fi.mode |= S_IFDIR; @@ -263,3 +247,16 @@ int apk_extract_v3(struct apk_extract_ctx *ectx, struct apk_istream *is) return r; } + +int apk_extract(struct apk_extract_ctx *ectx, struct apk_istream *is) +{ + void *sig; + + if (IS_ERR(is)) return PTR_ERR(is); + + sig = apk_istream_peek(is, 4); + if (IS_ERR(sig)) return apk_istream_close_error(is, PTR_ERR(sig)); + + if (memcmp(sig, "ADB", 3) == 0) return apk_extract_v3(ectx, is); + return apk_extract_v2(ectx, is); +} diff --git a/src/fs_fsys.c b/src/fs_fsys.c new file mode 100644 index 0000000..7615b79 --- /dev/null +++ b/src/fs_fsys.c @@ -0,0 +1,316 @@ +/* fsops_sys.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008-2011 Timo Teräs + * All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include +#include +#include + +#include "apk_fs.h" + +#define TMPNAME_MAX (PATH_MAX + 64) + +static int fsys_dir_create(struct apk_fsdir *d, mode_t mode) +{ + if (mkdirat(apk_ctx_fd_dest(d->ac), apk_pathbuilder_cstr(&d->pb), mode) < 0) + return -errno; + return 0; +} + +static int fsys_dir_delete(struct apk_fsdir *d) +{ + if (unlinkat(apk_ctx_fd_dest(d->ac), apk_pathbuilder_cstr(&d->pb), AT_REMOVEDIR) < 0) + return -errno; + return 0; +} + +static int fsys_dir_check(struct apk_fsdir *d, mode_t mode, uid_t uid, gid_t gid) +{ + struct stat st; + + if (fstatat(apk_ctx_fd_dest(d->ac), apk_pathbuilder_cstr(&d->pb), &st, AT_SYMLINK_NOFOLLOW) != 0) + return -errno; + + if ((st.st_mode & 07777) != (mode & 07777) || st.st_uid != uid || st.st_gid != gid) + return APK_FS_DIR_MODIFIED; + + return 0; +} + +static int fsys_dir_update_perms(struct apk_fsdir *d, mode_t mode, uid_t uid, gid_t gid) +{ + struct stat st; + int fd = apk_ctx_fd_dest(d->ac), rc = 0; + const char *dirname = apk_pathbuilder_cstr(&d->pb); + + if (fstatat(fd, dirname, &st, AT_SYMLINK_NOFOLLOW) != 0) + return -errno; + + if ((st.st_mode & 07777) != (mode & 07777)) { + if (fchmodat(fd, dirname, mode, 0) < 0) + rc = -errno; + } + if (st.st_uid != uid || st.st_gid != gid) { + if (fchownat(fd, dirname, uid, gid, 0) < 0) + rc = -errno; + } + return rc; +} + +static const char *format_tmpname(struct apk_digest_ctx *dctx, apk_blob_t pkgctx, + apk_blob_t dirname, apk_blob_t fullname, char tmpname[static TMPNAME_MAX]) +{ + struct apk_digest d; + apk_blob_t b = APK_BLOB_PTR_LEN(tmpname, TMPNAME_MAX); + + apk_digest_ctx_reset(dctx, APK_DIGEST_SHA256); + apk_digest_ctx_update(dctx, pkgctx.ptr, pkgctx.len); + apk_digest_ctx_update(dctx, fullname.ptr, fullname.len); + apk_digest_ctx_final(dctx, &d); + + apk_blob_push_blob(&b, dirname); + if (dirname.len > 0) { + apk_blob_push_blob(&b, APK_BLOB_STR("/.apk.")); + } else { + apk_blob_push_blob(&b, APK_BLOB_STR(".apk.")); + } + apk_blob_push_hexdump(&b, APK_BLOB_PTR_LEN((char *)d.data, 24)); + apk_blob_push_blob(&b, APK_BLOB_PTR_LEN("", 1)); + + return tmpname; +} + +static apk_blob_t get_dirname(const char *fullname) +{ + char *slash = strrchr(fullname, '/'); + if (!slash) return APK_BLOB_NULL; + return APK_BLOB_PTR_PTR((char*)fullname, slash); +} + +static int fsys_file_extract(struct apk_ctx *ac, const struct apk_file_info *fi, struct apk_istream *is, + apk_progress_cb cb, void *cb_ctx, unsigned int extract_flags, apk_blob_t pkgctx) +{ + char tmpname_file[TMPNAME_MAX], tmpname_linktarget[TMPNAME_MAX]; + struct apk_out *out = &ac->out; + struct apk_xattr *xattr; + int fd, r = -1, atflags = 0, ret = 0; + int atfd = apk_ctx_fd_dest(ac); + const char *fn = fi->name, *link_target = fi->link_target; + + if (pkgctx.ptr) { + fn = format_tmpname(&ac->dctx, pkgctx, get_dirname(fn), APK_BLOB_STR(fn), tmpname_file); + if (link_target) + link_target = format_tmpname(&ac->dctx, pkgctx, get_dirname(link_target), APK_BLOB_STR(link_target), tmpname_linktarget); + } + + if (!S_ISDIR(fi->mode) && !(extract_flags & APK_FSEXTRACTF_NO_OVERWRITE)) { + if (unlinkat(atfd, fn, 0) != 0 && errno != ENOENT) return -errno; + } + + switch (fi->mode & S_IFMT) { + case S_IFDIR: + r = mkdirat(atfd, fn, fi->mode & 07777); + if (r < 0 && errno != EEXIST) + ret = -errno; + break; + case S_IFREG: + if (fi->link_target == NULL) { + int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC | O_EXCL; + int fd = openat(atfd, fn, flags, fi->mode & 07777); + if (fd < 0) { + ret = -errno; + break; + } + struct apk_ostream *os = apk_ostream_to_fd(fd); + if (IS_ERR(os)) { + ret = PTR_ERR(os); + break; + } + apk_stream_copy(is, os, fi->size, cb, cb_ctx, 0); + r = apk_ostream_close(os); + if (r < 0) { + unlinkat(atfd, fn, 0); + ret = r; + } + } else { + r = linkat(atfd, link_target, atfd, fn, 0); + if (r < 0) ret = -errno; + } + break; + case S_IFLNK: + r = symlinkat(link_target, atfd, fn); + if (r < 0) ret = -errno; + atflags |= AT_SYMLINK_NOFOLLOW; + break; + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + r = mknodat(atfd, fn, fi->mode, fi->device); + if (r < 0) ret = -errno; + break; + } + if (ret) { + apk_err(out, "Failed to create %s: %s", fi->name, strerror(-ret)); + return ret; + } + + if (!(extract_flags & APK_FSEXTRACTF_NO_CHOWN)) { + r = fchownat(atfd, fn, fi->uid, fi->gid, atflags); + if (r < 0) { + apk_err(out, "Failed to set ownership on %s: %s", + fn, strerror(errno)); + if (!ret) ret = -errno; + } + + /* chown resets suid bit so we need set it again */ + if (fi->mode & 07000) { + r = fchmodat(atfd, fn, fi->mode & 07777, atflags); + if (r < 0) { + apk_err(out, "Failed to set file permissions on %s: %s", + fn, strerror(errno)); + if (!ret) ret = -errno; + } + } + } + + /* extract xattrs */ + if (!S_ISLNK(fi->mode) && fi->xattrs && fi->xattrs->num) { + r = 0; + fd = openat(atfd, fn, O_RDWR); + if (fd >= 0) { + foreach_array_item(xattr, fi->xattrs) { + if (fsetxattr(fd, xattr->name, xattr->value.ptr, xattr->value.len, 0) < 0) { + r = -errno; + if (r != -ENOTSUP) break; + } + } + close(fd); + } else { + r = -errno; + } + if (r) { + if (r != -ENOTSUP) + apk_err(out, "Failed to set xattrs on %s: %s", + fn, strerror(-r)); + if (!ret) ret = r; + } + } + + if (!S_ISLNK(fi->mode)) { + /* preserve modification time */ + struct timespec times[2]; + + times[0].tv_sec = times[1].tv_sec = fi->mtime; + times[0].tv_nsec = times[1].tv_nsec = 0; + r = utimensat(atfd, fn, times, atflags); + if (r < 0) { + apk_err(out, "Failed to preserve modification time on %s: %s", + fn, strerror(errno)); + if (!ret || ret == -ENOTSUP) ret = -errno; + } + } + + return ret; +} + +static int fsys_file_control(struct apk_fsdir *d, apk_blob_t filename, int ctrl) +{ + struct apk_ctx *ac = d->ac; + char tmpname[TMPNAME_MAX], apknewname[TMPNAME_MAX]; + const char *fn; + int rc = 0, atfd = apk_ctx_fd_dest(d->ac); + apk_blob_t dirname = apk_pathbuilder_get(&d->pb); + + apk_pathbuilder_pushb(&d->pb, filename); + fn = apk_pathbuilder_cstr(&d->pb); + + switch (ctrl) { + case APK_FS_CTRL_COMMIT: + // rename tmpname -> realname + if (renameat(atfd, format_tmpname(&ac->dctx, d->pkgctx, dirname, apk_pathbuilder_get(&d->pb), tmpname), + atfd, fn) < 0) + rc = -errno; + break; + case APK_FS_CTRL_APKNEW: + // rename tmpname -> realname.apk-new + snprintf(apknewname, sizeof apknewname, "%s%s", fn, ".apk-new"); + if (renameat(atfd, format_tmpname(&ac->dctx, d->pkgctx, dirname, apk_pathbuilder_get(&d->pb), tmpname), + atfd, apknewname) < 0) + rc = -errno; + break; + case APK_FS_CTRL_CANCEL: + // unlink tmpname + if (unlinkat(atfd, format_tmpname(&ac->dctx, d->pkgctx, dirname, apk_pathbuilder_get(&d->pb), tmpname), 0) < 0) + rc = -errno; + break; + case APK_FS_CTRL_DELETE: + // unlink realname + if (unlinkat(atfd, fn, 0) < 0) + rc = -errno; + break; + default: + rc = -ENOSYS; + break; + } + + apk_pathbuilder_pop(&d->pb); + return rc; +} + +static int fsys_file_digest(struct apk_fsdir *d, apk_blob_t filename, uint8_t alg, struct apk_digest *dgst) +{ + struct apk_ctx *ac = d->ac; + struct apk_istream *is; + apk_blob_t blob; + + apk_pathbuilder_pushb(&d->pb, filename); + is = apk_istream_from_file(apk_ctx_fd_dest(ac), apk_pathbuilder_cstr(&d->pb)); + apk_pathbuilder_pop(&d->pb); + if (IS_ERR(is)) return PTR_ERR(is); + + apk_digest_ctx_reset(&ac->dctx, alg); + while (apk_istream_get_all(is, &blob) == 0) + apk_digest_ctx_update(&ac->dctx, blob.ptr, blob.len); + apk_digest_ctx_final(&ac->dctx, dgst); + return apk_istream_close(is); +} + +static const struct apk_fsdir_ops fsdir_ops_fsys = { + .dir_create = fsys_dir_create, + .dir_delete = fsys_dir_delete, + .dir_check = fsys_dir_check, + .dir_update_perms = fsys_dir_update_perms, + .file_extract = fsys_file_extract, + .file_control = fsys_file_control, + .file_digest = fsys_file_digest, +}; + +static const struct apk_fsdir_ops *apk_fsops_get(apk_blob_t dir) +{ + if (dir.len >= 4 && memcmp(dir.ptr, "uvol", 4) == 0 && (dir.len == 4 || dir.ptr[4] == '/')) { + extern const struct apk_fsdir_ops fsdir_ops_uvol; + return &fsdir_ops_uvol; + } + + return &fsdir_ops_fsys; +} + +int apk_fs_extract(struct apk_ctx *ac, const struct apk_file_info *fi, struct apk_istream *is, + apk_progress_cb cb, void *cb_ctx, unsigned int extract_flags, apk_blob_t pkgctx) +{ + const struct apk_fsdir_ops *ops = apk_fsops_get(APK_BLOB_PTR_LEN((char*)fi->name, strnlen(fi->name, 5))); + return ops->file_extract(ac, fi, is, cb, cb_ctx, extract_flags, pkgctx); +} + +void apk_fsdir_get(struct apk_fsdir *d, apk_blob_t dir, struct apk_ctx *ac, apk_blob_t pkgctx) +{ + d->ac = ac; + d->pkgctx = pkgctx; + d->ops = apk_fsops_get(dir); + apk_pathbuilder_setb(&d->pb, dir); +} diff --git a/src/fs_uvol.c b/src/fs_uvol.c new file mode 100644 index 0000000..f2ba3f1 --- /dev/null +++ b/src/fs_uvol.c @@ -0,0 +1,162 @@ +/* fsops_uvol.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa + * Copyright (C) 2008-2011 Timo Teräs + * All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include +#include +#include +#include + +#include "apk_context.h" +#include "apk_fs.h" + +static int uvol_run(struct apk_ctx *ac, char *action, const char *volname, char *arg1, char *arg2) +{ + struct apk_out *out = &ac->out; + pid_t pid; + int r, status; + char *argv[] = { (char*)apk_ctx_get_uvol(ac), action, (char*) volname, arg1, arg2, 0 }; + posix_spawn_file_actions_t act; + + posix_spawn_file_actions_init(&act); + posix_spawn_file_actions_addclose(&act, STDIN_FILENO); + r = posix_spawn(&pid, apk_ctx_get_uvol(ac), &act, 0, argv, environ); + posix_spawn_file_actions_destroy(&act); + if (r != 0) { + apk_err(out, "%s: uvol exec error: %s", volname, apk_error_str(r)); + return r; + } + while (waitpid(pid, &status, 0) < 0 && errno == EINTR); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + apk_err(out, "%s: uvol exited with error %d", volname, WEXITSTATUS(status)); + return -APKE_UVOL_ERROR; + } + return 0; +} + +static int uvol_extract(struct apk_ctx *ac, const char *volname, char *arg1, off_t sz, + struct apk_istream *is, apk_progress_cb cb, void *cb_ctx) +{ + struct apk_out *out = &ac->out; + struct apk_ostream *os; + pid_t pid; + int r, status, pipefds[2]; + char *argv[] = { (char*)apk_ctx_get_uvol(ac), "write", (char*) volname, arg1, 0 }; + posix_spawn_file_actions_t act; + + if (pipe2(pipefds, O_CLOEXEC) != 0) return -errno; + + posix_spawn_file_actions_init(&act); + posix_spawn_file_actions_adddup2(&act, pipefds[0], STDIN_FILENO); + r = posix_spawn(&pid, apk_ctx_get_uvol(ac), &act, 0, argv, environ); + posix_spawn_file_actions_destroy(&act); + if (r != 0) { + apk_err(out, "%s: uvol exec error: %s", volname, apk_error_str(r)); + return r; + } + close(pipefds[0]); + os = apk_ostream_to_fd(pipefds[1]); + apk_stream_copy(is, os, sz, cb, cb_ctx, 0); + r = apk_ostream_close(os); + if (r != 0) { + if (r >= 0) r = -APKE_UVOL_ERROR; + apk_err(out, "%s: uvol write error: %s", volname, apk_error_str(r)); + return r; + } + + while (waitpid(pid, &status, 0) < 0 && errno == EINTR); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + apk_err(out, "%s: uvol exited with error %d", volname, WEXITSTATUS(status)); + return -APKE_UVOL_ERROR; + } + + return 0; +} + +static int uvol_dir_create(struct apk_fsdir *d, mode_t mode) +{ + return 0; +} + +static int uvol_dir_delete(struct apk_fsdir *d) +{ + return 0; +} + +static int uvol_dir_check(struct apk_fsdir *d, mode_t mode, uid_t uid, gid_t gid) +{ + return 0; +} + +static int uvol_dir_update_perms(struct apk_fsdir *d, mode_t mode, uid_t uid, gid_t gid) +{ + return 0; +} + +static int uvol_file_extract(struct apk_ctx *ac, const struct apk_file_info *fi, struct apk_istream *is, + apk_progress_cb cb, void *cb_ctx, unsigned int extract_flags, apk_blob_t pkgctx) +{ + char size[64]; + const char *uvol_name; + int r; + + if (IS_ERR(ac->uvol)) return PTR_ERR(ac->uvol); + + uvol_name = strrchr(fi->name, '/'); + uvol_name = uvol_name ? uvol_name + 1 : fi->name; + + snprintf(size, sizeof size, "%ju", fi->size); + r = uvol_run(ac, "create", uvol_name, size, "ro"); + if (r != 0) return r; + + r = uvol_extract(ac, uvol_name, size, fi->size, is, cb, cb_ctx); + if (r == 0 && !pkgctx.ptr) + r = uvol_run(ac, "up", uvol_name, 0, 0); + + if (r != 0) uvol_run(ac, "remove", uvol_name, 0, 0); + + return r; +} + +static int uvol_file_control(struct apk_fsdir *d, apk_blob_t filename, int ctrl) +{ + struct apk_ctx *ac = d->ac; + struct apk_pathbuilder pb; + const char *uvol_name; + + if (IS_ERR(ac->uvol)) return PTR_ERR(ac->uvol); + + apk_pathbuilder_setb(&pb, filename); + uvol_name = apk_pathbuilder_cstr(&pb); + + switch (ctrl) { + case APK_FS_CTRL_COMMIT: + return uvol_run(ac, "up", uvol_name, 0, 0); + case APK_FS_CTRL_APKNEW: + case APK_FS_CTRL_CANCEL: + case APK_FS_CTRL_DELETE: + return uvol_run(ac, "remove", uvol_name, 0, 0); + default: + return -APKE_UVOL_ERROR; + } +} + +static int uvol_file_digest(struct apk_fsdir *d, apk_blob_t filename, uint8_t alg, struct apk_digest *dgst) +{ + return -APKE_UVOL_ERROR; +} + +const struct apk_fsdir_ops fsdir_ops_uvol = { + .dir_create = uvol_dir_create, + .dir_delete = uvol_dir_delete, + .dir_check = uvol_dir_check, + .dir_update_perms = uvol_dir_update_perms, + .file_extract = uvol_file_extract, + .file_control = uvol_file_control, + .file_digest = uvol_file_digest, +}; diff --git a/src/io.c b/src/io.c index a8821e4..cf30bb6 100644 --- a/src/io.c +++ b/src/io.c @@ -314,7 +314,10 @@ static ssize_t digest_read(struct apk_istream *is, void *ptr, size_t size) ssize_t r; r = dis->pis->ops->read(dis->pis, ptr, size); - if (r > 0) apk_digest_ctx_update(&dis->dctx, ptr, r); + if (r > 0) { + apk_digest_ctx_update(&dis->dctx, ptr, r); + dis->size_left -= r; + } return r; } @@ -322,7 +325,7 @@ static int digest_close(struct apk_istream *is) { struct apk_digest_istream *dis = container_of(is, struct apk_digest_istream, is); - if (dis->digest) { + if (dis->digest && dis->size_left == 0) { struct apk_digest res; apk_digest_ctx_final(&dis->dctx, &res); if (apk_digest_cmp(&res, dis->digest) != 0) @@ -340,7 +343,7 @@ static const struct apk_istream_ops digest_istream_ops = { .close = digest_close, }; -struct apk_istream *apk_istream_verify(struct apk_digest_istream *dis, struct apk_istream *is, struct apk_digest *d) +struct apk_istream *apk_istream_verify(struct apk_digest_istream *dis, struct apk_istream *is, off_t size, struct apk_digest *d) { *dis = (struct apk_digest_istream) { .is.ops = &digest_istream_ops, @@ -350,10 +353,13 @@ struct apk_istream *apk_istream_verify(struct apk_digest_istream *dis, struct ap .is.end = is->end, .pis = is, .digest = d, + .size_left = size, }; apk_digest_ctx_init(&dis->dctx, d->alg); - if (dis->is.ptr != dis->is.end) + if (dis->is.ptr != dis->is.end) { apk_digest_ctx_update(&dis->dctx, dis->is.ptr, dis->is.end - dis->is.ptr); + dis->size_left -= dis->is.end - dis->is.ptr; + } return &dis->is; } diff --git a/src/meson.build b/src/meson.build index 3bdb477..9422ca6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,9 +14,10 @@ libapk_src = [ 'context.c', 'crypto_openssl.c', 'database.c', - 'extract.c', 'extract_v2.c', 'extract_v3.c', + 'fs_fsys.c', + 'fs_uvol.c', 'hash.c', 'io.c', 'io_url.c', @@ -38,6 +39,7 @@ libapk_headers = [ 'apk_database.h', 'apk_defines.h', 'apk_extract.h', + 'apk_fs.h', 'apk_hash.h', 'apk_io.h', 'apk_openssl.h', diff --git a/src/package.c b/src/package.c index cba999f..f08101f 100644 --- a/src/package.c +++ b/src/package.c @@ -591,7 +591,7 @@ void apk_pkg_from_adb(struct apk_database *db, struct apk_package *pkg, struct a adb_ro_obj(pkgo, ADBI_PKG_PKGINFO, &pkginfo); uid = adb_ro_blob(&pkginfo, ADBI_PI_UNIQUE_ID); - if (uid.len == APK_CHECKSUM_SHA1) { + if (uid.len >= APK_CHECKSUM_SHA1) { pkg->csum.type = APK_CHECKSUM_SHA1; memcpy(pkg->csum.data, uid.ptr, uid.len); } @@ -723,19 +723,16 @@ err: void apk_pkg_free(struct apk_package *pkg) { - if (pkg == NULL) - return; + if (!pkg) return; apk_pkg_uninstall(NULL, pkg); apk_dependency_array_free(&pkg->depends); apk_dependency_array_free(&pkg->provides); apk_dependency_array_free(&pkg->install_if); - if (pkg->url) - free(pkg->url); - if (pkg->description) - free(pkg->description); - if (pkg->commit) - free(pkg->commit); + if (pkg->url) free(pkg->url); + if (pkg->description) free(pkg->description); + if (pkg->commit) free(pkg->commit); + if (pkg->filename) free(pkg->filename); free(pkg); } diff --git a/src/print.c b/src/print.c index ea66b90..31a9fbb 100644 --- a/src/print.c +++ b/src/print.c @@ -58,7 +58,9 @@ const char *apk_error_str(int error) case APKE_INDEX_STALE: return "package mentioned in index not found (try 'apk update')"; case APKE_FILE_INTEGRITY: return "file integrity error"; case APKE_CACHE_NOT_AVAILABLE: return "cache not available"; - case APKE_UVOL: return "uvol error"; + case APKE_UVOL_NOT_AVAILABLE: return "uvol manager not available"; + case APKE_UVOL_ERROR: return "uvol error"; + case APKE_UVOL_ROOT: return "uvol not supported with --root"; default: return strerror(error); }