diff options
author | Timo Teräs <timo.teras@iki.fi> | 2021-11-05 13:20:19 +0200 |
---|---|---|
committer | Timo Teräs <timo.teras@iki.fi> | 2021-11-09 21:50:11 +0200 |
commit | a6736532001fd625f1ab3dd82abc2a4c5366c79c (patch) | |
tree | 47ead4bc00519f0b1e3ea52bf576da5bb8b66530 /src/fs_fsys.c | |
parent | d441cf523cea6ab2d2ee1c0f50fab0bb620f2ea1 (diff) | |
download | apk-tools-a6736532001fd625f1ab3dd82abc2a4c5366c79c.tar.gz apk-tools-a6736532001fd625f1ab3dd82abc2a4c5366c79c.tar.bz2 apk-tools-a6736532001fd625f1ab3dd82abc2a4c5366c79c.tar.xz apk-tools-a6736532001fd625f1ab3dd82abc2a4c5366c79c.zip |
database: implement uvol support
by adding an abstraction layer to the file system
Diffstat (limited to 'src/fs_fsys.c')
-rw-r--r-- | src/fs_fsys.c | 316 |
1 files changed, 316 insertions, 0 deletions
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 <n@tanael.org> + * Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi> + * All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include <unistd.h> +#include <sys/stat.h> +#include <sys/xattr.h> + +#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); +} |