summaryrefslogtreecommitdiff
path: root/src/fs_fsys.c
diff options
context:
space:
mode:
authorTimo Teräs <timo.teras@iki.fi>2021-11-05 13:20:19 +0200
committerTimo Teräs <timo.teras@iki.fi>2021-11-09 21:50:11 +0200
commita6736532001fd625f1ab3dd82abc2a4c5366c79c (patch)
tree47ead4bc00519f0b1e3ea52bf576da5bb8b66530 /src/fs_fsys.c
parentd441cf523cea6ab2d2ee1c0f50fab0bb620f2ea1 (diff)
downloadapk-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.c316
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);
+}