359 lines
11 KiB
C
359 lines
11 KiB
C
/* app_mkpkg.c - Alpine Package Keeper (APK)
|
|
*
|
|
* Copyright (C) 2008-2021 Timo Teräs <timo.teras@iki.fi>
|
|
* All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published
|
|
* by the Free Software Foundation. See http://www.gnu.org/ for details.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "apk_defines.h"
|
|
#include "apk_adb.h"
|
|
#include "apk_applet.h"
|
|
#include "apk_database.h"
|
|
#include "apk_pathbuilder.h"
|
|
#include "apk_extract.h"
|
|
#include "apk_print.h"
|
|
|
|
#define BLOCK_SIZE 4096
|
|
|
|
struct mkpkg_ctx {
|
|
struct apk_ctx *ac;
|
|
const char *files_dir, *output;
|
|
struct adb db;
|
|
struct adb_obj paths, *files;
|
|
struct apk_extract_ctx ectx;
|
|
apk_blob_t info[ADBI_PI_MAX];
|
|
apk_blob_t script[ADBI_SCRPT_MAX];
|
|
struct apk_string_array *triggers;
|
|
uint64_t installed_size;
|
|
struct apk_pathbuilder pb;
|
|
unsigned has_scripts : 1;
|
|
};
|
|
|
|
#define MKPKG_OPTIONS(OPT) \
|
|
OPT(OPT_MKPKG_files, APK_OPT_ARG APK_OPT_SH("f") "files") \
|
|
OPT(OPT_MKPKG_info, APK_OPT_ARG APK_OPT_SH("i") "info") \
|
|
OPT(OPT_MKPKG_output, APK_OPT_ARG APK_OPT_SH("o") "output") \
|
|
OPT(OPT_MKPKG_script, APK_OPT_ARG APK_OPT_SH("s") "script") \
|
|
OPT(OPT_MKPKG_trigger, APK_OPT_ARG APK_OPT_SH("t") "trigger") \
|
|
|
|
APK_OPT_APPLET(option_desc, MKPKG_OPTIONS);
|
|
|
|
static int option_parse_applet(void *ctx, struct apk_ctx *ac, int optch, const char *optarg)
|
|
{
|
|
struct apk_out *out = &ac->out;
|
|
struct mkpkg_ctx *ictx = ctx;
|
|
apk_blob_t l, r;
|
|
int i;
|
|
|
|
switch (optch) {
|
|
case OPT_MKPKG_info:
|
|
apk_blob_split(APK_BLOB_STR(optarg), APK_BLOB_STRLIT(":"), &l, &r);
|
|
i = adb_s_field_by_name_blob(&schema_pkginfo, l);
|
|
if (!i || i == ADBI_PI_FILE_SIZE || i == ADBI_PI_INSTALLED_SIZE) {
|
|
apk_err(out, "invalid pkginfo field: " BLOB_FMT, BLOB_PRINTF(l));
|
|
return -EINVAL;
|
|
}
|
|
ictx->info[i] = r;
|
|
break;
|
|
case OPT_MKPKG_files:
|
|
ictx->files_dir = optarg;
|
|
break;
|
|
case OPT_MKPKG_output:
|
|
ictx->output = optarg;
|
|
break;
|
|
case OPT_MKPKG_script:
|
|
apk_blob_split(APK_BLOB_STR(optarg), APK_BLOB_STRLIT(":"), &l, &r);
|
|
i = adb_s_field_by_name_blob(&schema_scripts, l);
|
|
if (!i) {
|
|
apk_err(out, "invalid script type: " BLOB_FMT, BLOB_PRINTF(l));
|
|
return -EINVAL;
|
|
}
|
|
ictx->script[i] = apk_blob_from_file(AT_FDCWD, r.ptr);
|
|
if (APK_BLOB_IS_NULL(ictx->script[i])) {
|
|
apk_err(out, "failed to load script: " BLOB_FMT, BLOB_PRINTF(r));
|
|
return -ENOENT;
|
|
}
|
|
ictx->has_scripts = 1;
|
|
break;
|
|
case OPT_MKPKG_trigger:
|
|
*apk_string_array_add(&ictx->triggers) = (char*) optarg;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct apk_option_group optgroup_applet = {
|
|
.desc = option_desc,
|
|
.parse = option_parse_applet,
|
|
};
|
|
|
|
static int mkpkg_process_dirent(void *pctx, int dirfd, const char *entry);
|
|
|
|
static int mkpkg_process_directory(struct mkpkg_ctx *ctx, int dirfd, struct apk_file_info *fi)
|
|
{
|
|
struct apk_ctx *ac = ctx->ac;
|
|
struct apk_id_cache *idc = apk_ctx_get_id_cache(ac);
|
|
struct apk_out *out = &ac->out;
|
|
struct adb_obj acl, fio, files, *prev_files;
|
|
apk_blob_t dirname = apk_pathbuilder_get(&ctx->pb);
|
|
int r;
|
|
|
|
adb_wo_alloca(&fio, &schema_dir, &ctx->db);
|
|
adb_wo_alloca(&acl, &schema_acl, &ctx->db);
|
|
adb_wo_blob(&fio, ADBI_DI_NAME, dirname);
|
|
adb_wo_int(&acl, ADBI_ACL_MODE, fi->mode & ~S_IFMT);
|
|
adb_wo_blob(&acl, ADBI_ACL_USER, apk_id_cache_resolve_user(idc, fi->uid));
|
|
adb_wo_blob(&acl, ADBI_ACL_GROUP, apk_id_cache_resolve_group(idc, fi->gid));
|
|
adb_wo_obj(&fio, ADBI_DI_ACL, &acl);
|
|
|
|
adb_wo_alloca(&files, &schema_file_array, &ctx->db);
|
|
prev_files = ctx->files;
|
|
ctx->files = &files;
|
|
r = apk_dir_foreach_file(dirfd, mkpkg_process_dirent, ctx);
|
|
ctx->files = prev_files;
|
|
if (r) {
|
|
apk_err(out, "failed to process directory '%s': %d",
|
|
apk_pathbuilder_cstr(&ctx->pb), r);
|
|
return r;
|
|
}
|
|
|
|
adb_wo_obj(&fio, ADBI_DI_FILES, &files);
|
|
adb_wa_append_obj(&ctx->paths, &fio);
|
|
return 0;
|
|
}
|
|
|
|
static int mkpkg_process_dirent(void *pctx, int dirfd, const char *entry)
|
|
{
|
|
struct mkpkg_ctx *ctx = pctx;
|
|
struct apk_ctx *ac = ctx->ac;
|
|
struct apk_out *out = &ac->out;
|
|
struct apk_id_cache *idc = apk_ctx_get_id_cache(ac);
|
|
struct apk_file_info fi;
|
|
struct adb_obj fio, acl;
|
|
apk_blob_t target = APK_BLOB_NULL;
|
|
union {
|
|
uint16_t mode;
|
|
struct {
|
|
uint16_t mode;
|
|
uint64_t dev;
|
|
} __attribute__((packed)) dev;
|
|
struct {
|
|
uint16_t mode;
|
|
char target[1022];
|
|
} symlink;
|
|
} ft;
|
|
int r, n;
|
|
|
|
r = apk_fileinfo_get(dirfd, entry, APK_FI_NOFOLLOW | APK_FI_DIGEST(APK_DIGEST_SHA256), &fi, NULL);
|
|
if (r) return r;
|
|
|
|
switch (fi.mode & S_IFMT) {
|
|
case S_IFREG:
|
|
ctx->installed_size += (fi.size + BLOCK_SIZE - 1) & ~(BLOCK_SIZE-1);
|
|
break;
|
|
case S_IFBLK:
|
|
case S_IFCHR:
|
|
case S_IFIFO:
|
|
ft.dev.mode = fi.mode & S_IFMT;
|
|
ft.dev.dev = fi.device;
|
|
target = APK_BLOB_STRUCT(ft.dev);
|
|
break;
|
|
case S_IFLNK:
|
|
ft.symlink.mode = fi.mode & S_IFMT;
|
|
r = readlinkat(dirfd, entry, ft.symlink.target, sizeof ft.symlink.target);
|
|
if (r < 0) return r;
|
|
target = APK_BLOB_PTR_LEN((void*)&ft.symlink, sizeof(ft.symlink.mode) + r);
|
|
r = 0;
|
|
break;
|
|
case S_IFDIR:
|
|
n = apk_pathbuilder_push(&ctx->pb, entry);
|
|
r = mkpkg_process_directory(ctx, openat(dirfd, entry, O_RDONLY), &fi);
|
|
apk_pathbuilder_pop(&ctx->pb, n);
|
|
return r;
|
|
default:
|
|
n = apk_pathbuilder_push(&ctx->pb, entry);
|
|
apk_out(out, "%s: special file ignored", apk_pathbuilder_cstr(&ctx->pb));
|
|
apk_pathbuilder_pop(&ctx->pb, n);
|
|
return 0;
|
|
}
|
|
|
|
adb_wo_alloca(&fio, &schema_file, &ctx->db);
|
|
adb_wo_alloca(&acl, &schema_acl, &ctx->db);
|
|
adb_wo_blob(&fio, ADBI_FI_NAME, APK_BLOB_STR(entry));
|
|
if (APK_BLOB_IS_NULL(target))
|
|
adb_wo_blob(&fio, ADBI_FI_HASHES, APK_DIGEST_BLOB(fi.digest));
|
|
else
|
|
adb_wo_blob(&fio, ADBI_FI_TARGET, target);
|
|
adb_wo_int(&fio, ADBI_FI_MTIME, fi.mtime);
|
|
adb_wo_int(&fio, ADBI_FI_SIZE, fi.size);
|
|
|
|
adb_wo_int(&acl, ADBI_ACL_MODE, fi.mode & 07777);
|
|
adb_wo_blob(&acl, ADBI_ACL_USER, apk_id_cache_resolve_user(idc, fi.uid));
|
|
adb_wo_blob(&acl, ADBI_ACL_GROUP, apk_id_cache_resolve_group(idc, fi.gid));
|
|
adb_wo_obj(&fio, ADBI_FI_ACL, &acl);
|
|
|
|
adb_wa_append_obj(ctx->files, &fio);
|
|
|
|
return r;
|
|
}
|
|
|
|
static char *pkgi_filename(struct adb_obj *pkgi, char *buf, size_t n)
|
|
{
|
|
apk_blob_t to = APK_BLOB_PTR_LEN(buf, n);
|
|
apk_blob_push_blob(&to, adb_ro_blob(pkgi, ADBI_PI_NAME));
|
|
apk_blob_push_blob(&to, APK_BLOB_STR("-"));
|
|
apk_blob_push_blob(&to, adb_ro_blob(pkgi, ADBI_PI_VERSION));
|
|
apk_blob_push_blob(&to, APK_BLOB_STR(".apk"));
|
|
apk_blob_push_blob(&to, APK_BLOB_PTR_LEN("", 1));
|
|
if (APK_BLOB_IS_NULL(to)) return 0;
|
|
return buf;
|
|
}
|
|
|
|
static int mkpkg_main(void *pctx, struct apk_ctx *ac, struct apk_string_array *args)
|
|
{
|
|
struct apk_out *out = &ac->out;
|
|
struct apk_trust *trust = apk_ctx_get_trust(ac);
|
|
struct adb_obj pkg, pkgi;
|
|
int i, j, r;
|
|
struct mkpkg_ctx *ctx = pctx;
|
|
struct apk_ostream *os;
|
|
struct apk_digest d = {};
|
|
char outbuf[PATH_MAX];
|
|
const int uid_len = apk_digest_alg_len(APK_DIGEST_SHA1);
|
|
apk_blob_t uid = APK_BLOB_PTR_LEN((char*)d.data, uid_len);
|
|
|
|
ctx->ac = ac;
|
|
adb_w_init_alloca(&ctx->db, ADB_SCHEMA_PACKAGE, 40);
|
|
adb_wo_alloca(&pkg, &schema_package, &ctx->db);
|
|
adb_wo_alloca(&pkgi, &schema_pkginfo, &ctx->db);
|
|
adb_wo_alloca(&ctx->paths, &schema_dir_array, &ctx->db);
|
|
|
|
// prepare package info
|
|
for (i = 0; i < ARRAY_SIZE(ctx->info); i++) {
|
|
apk_blob_t val = ctx->info[i];
|
|
if (APK_BLOB_IS_NULL(val)) {
|
|
switch (i) {
|
|
case ADBI_PI_NAME:
|
|
case ADBI_PI_VERSION:
|
|
r = -EINVAL;
|
|
apk_err(out, "required pkginfo field '%s' not provided",
|
|
schema_pkginfo.fields[i-1].name);
|
|
goto err;
|
|
}
|
|
continue;
|
|
}
|
|
adb_wo_val_fromstring(&pkgi, i, val);
|
|
}
|
|
if (adb_ro_val(&pkgi, ADBI_PI_ARCH) == ADB_VAL_NULL)
|
|
adb_wo_blob(&pkgi, ADBI_PI_ARCH, APK_BLOB_STRLIT(APK_DEFAULT_ARCH));
|
|
|
|
// scan and add all files
|
|
if (ctx->files_dir) {
|
|
struct apk_file_info fi;
|
|
r = apk_fileinfo_get(AT_FDCWD, ctx->files_dir, APK_FI_NOFOLLOW, &fi, 0);
|
|
if (r) {
|
|
apk_err(out, "file directory '%s': %s",
|
|
ctx->files_dir, apk_error_str(r));
|
|
goto err;
|
|
}
|
|
r = mkpkg_process_directory(ctx, openat(AT_FDCWD, ctx->files_dir, O_RDONLY), &fi);
|
|
if (r) goto err;
|
|
if (!ctx->installed_size) ctx->installed_size = BLOCK_SIZE;
|
|
}
|
|
|
|
adb_wo_int(&pkgi, ADBI_PI_INSTALLED_SIZE, ctx->installed_size);
|
|
adb_wo_blob(&pkgi, ADBI_PI_UNIQUE_ID, uid);
|
|
adb_wo_obj(&pkg, ADBI_PKG_PKGINFO, &pkgi);
|
|
adb_wo_obj(&pkg, ADBI_PKG_PATHS, &ctx->paths);
|
|
if (ctx->has_scripts) {
|
|
struct adb_obj scripts;
|
|
adb_wo_alloca(&scripts, &schema_scripts, &ctx->db);
|
|
for (i = ADBI_FIRST; i < APK_SCRIPT_MAX; i++)
|
|
adb_wo_blob(&scripts, i, ctx->script[i]);
|
|
adb_wo_obj(&pkg, ADBI_PKG_SCRIPTS, &scripts);
|
|
}
|
|
if (ctx->triggers) {
|
|
struct adb_obj triggers;
|
|
adb_wo_alloca(&triggers, &schema_string_array, &ctx->db);
|
|
for (i = 0; i < ctx->triggers->num; i++)
|
|
adb_wa_append_fromstring(&triggers, APK_BLOB_STR(ctx->triggers->item[i]));
|
|
adb_wo_obj(&pkg, ADBI_PKG_TRIGGERS, &triggers);
|
|
}
|
|
adb_w_rootobj(&pkg);
|
|
|
|
// re-read since object resets
|
|
adb_r_rootobj(&ctx->db, &pkg, &schema_package);
|
|
adb_ro_obj(&pkg, ADBI_PKG_PKGINFO, &pkgi);
|
|
adb_ro_obj(&pkg, ADBI_PKG_PATHS, &ctx->paths);
|
|
|
|
// fill in unique id
|
|
apk_digest_calc(&d, APK_DIGEST_SHA256, ctx->db.adb.ptr, ctx->db.adb.len);
|
|
uid = adb_ro_blob(&pkgi, ADBI_PI_UNIQUE_ID);
|
|
memcpy(uid.ptr, d.data, uid.len);
|
|
|
|
if (!ctx->output) {
|
|
ctx->output = pkgi_filename(&pkgi, outbuf, sizeof outbuf);
|
|
}
|
|
|
|
// construct package with ADB as header, and the file data in
|
|
// concatenated data blocks
|
|
os = adb_compress(apk_ostream_to_file(AT_FDCWD, ctx->output, 0644), ADB_COMP_DEFLATE);
|
|
adb_c_adb(os, &ctx->db, trust);
|
|
int files_fd = openat(AT_FDCWD, ctx->files_dir, O_RDONLY);
|
|
for (i = ADBI_FIRST; i <= adb_ra_num(&ctx->paths); i++) {
|
|
struct adb_obj path, files, file;
|
|
adb_ro_obj(&ctx->paths, i, &path);
|
|
adb_ro_obj(&path, ADBI_DI_FILES, &files);
|
|
apk_blob_t dirname = adb_ro_blob(&path, ADBI_DI_NAME);
|
|
|
|
apk_pathbuilder_setb(&ctx->pb, dirname);
|
|
for (j = ADBI_FIRST; j <= adb_ra_num(&files); j++) {
|
|
adb_ro_obj(&files, j, &file);
|
|
apk_blob_t filename = adb_ro_blob(&file, ADBI_FI_NAME);
|
|
apk_blob_t target = adb_ro_blob(&file, ADBI_FI_TARGET);
|
|
size_t sz = adb_ro_int(&file, ADBI_FI_SIZE);
|
|
if (!APK_BLOB_IS_NULL(target)) continue;
|
|
if (!sz) continue;
|
|
struct adb_data_package hdr = {
|
|
.path_idx = i,
|
|
.file_idx = j,
|
|
};
|
|
int n = apk_pathbuilder_pushb(&ctx->pb, filename);
|
|
adb_c_block_data(
|
|
os, APK_BLOB_STRUCT(hdr), sz,
|
|
apk_istream_from_fd(openat(files_fd,
|
|
apk_pathbuilder_cstr(&ctx->pb),
|
|
O_RDONLY)));
|
|
apk_pathbuilder_pop(&ctx->pb, n);
|
|
}
|
|
}
|
|
close(files_fd);
|
|
r = apk_ostream_close(os);
|
|
|
|
err:
|
|
adb_free(&ctx->db);
|
|
if (r) apk_err(out, "failed to create package: %s", apk_error_str(r));
|
|
return r;
|
|
}
|
|
|
|
static struct apk_applet apk_mkpkg = {
|
|
.name = "mkpkg",
|
|
.context_size = sizeof(struct mkpkg_ctx),
|
|
.optgroups = { &optgroup_global, &optgroup_signing, &optgroup_applet },
|
|
.main = mkpkg_main,
|
|
};
|
|
|
|
APK_DEFINE_APPLET(apk_mkpkg);
|