/* 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 (!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 (!link_target) {
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 {
// Hardlink needs to be done against the temporary name
if (pkgctx.ptr)
link_target = format_tmpname(&ac->dctx, pkgctx, get_dirname(link_target),
APK_BLOB_STR(link_target), tmpname_linktarget);
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)
{
if (S_ISDIR(fi->mode)) {
struct apk_fsdir fsd;
apk_fsdir_get(&fsd, APK_BLOB_STR((char*)fi->name), ac, pkgctx);
return apk_fsdir_create(&fsd, fi->mode);
} else {
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);
}