summaryrefslogblamecommitdiff
path: root/src/fs_fsys.c
blob: 09936071c6c36b4c8276b30e0d18ad0205ec0b76 (plain) (tree)
































































































































































































































                                                                                                                                                 
                                                     

                                                         
                                                    






























                                                                                                                               
                                       







                                                                                                           
              
 
                                                    
                                                                                      
                                       










































                                                                                                                 
/* 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 n, rc = 0, atfd = apk_ctx_fd_dest(d->ac);
	apk_blob_t dirname = apk_pathbuilder_get(&d->pb);

	n = 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, n);
	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;
	int n;

	n = 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, n);
	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);
}