summaryrefslogtreecommitdiffstats
path: root/src/package.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/package.c')
-rw-r--r--src/package.c483
1 files changed, 483 insertions, 0 deletions
diff --git a/src/package.c b/src/package.c
new file mode 100644
index 0000000..88aef70
--- /dev/null
+++ b/src/package.c
@@ -0,0 +1,483 @@
+/* package.c - Alpine Package Keeper (APK)
+ *
+ * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
+ * Copyright (C) 2008 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 <fcntl.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <malloc.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/wait.h>
+
+#include "apk_defines.h"
+#include "apk_archive.h"
+#include "apk_package.h"
+#include "apk_database.h"
+#include "apk_state.h"
+
+int apk_pkg_parse_name(apk_blob_t apkname,
+ apk_blob_t *name,
+ apk_blob_t *version)
+{
+ int i, dash = 0;
+
+ for (i = apkname.len - 2; i >= 0; i--) {
+ if (apkname.ptr[i] != '-')
+ continue;
+ if (isdigit(apkname.ptr[i+1]))
+ break;
+ if (++dash >= 2)
+ return -1;
+ }
+ if (name != NULL)
+ *name = APK_BLOB_PTR_LEN(apkname.ptr, i);
+ if (version != NULL)
+ *version = APK_BLOB_PTR_PTR(&apkname.ptr[i+1],
+ &apkname.ptr[apkname.len-1]);
+
+ return 0;
+}
+
+static char *trim(apk_blob_t str)
+{
+ if (str.ptr == NULL || str.len < 1)
+ return NULL;
+
+ if (str.ptr[str.len-2] == '\n')
+ str.ptr[str.len-2] = 0;
+
+ return str.ptr;
+}
+
+static void parse_depend(struct apk_database *db,
+ struct apk_dependency_array **depends,
+ apk_blob_t blob)
+{
+ struct apk_dependency *dep;
+ struct apk_name *name;
+ char *cname;
+
+ while (blob.len && blob.ptr[0] == ' ')
+ blob.ptr++, blob.len--;
+ while (blob.len && (blob.ptr[blob.len-1] == ' ' ||
+ blob.ptr[blob.len-1] == 0))
+ blob.len--;
+
+ if (blob.len == 0)
+ return;
+
+ cname = apk_blob_cstr(blob);
+ name = apk_db_get_name(db, cname);
+ free(cname);
+
+ dep = apk_dependency_array_add(depends);
+ *dep = (struct apk_dependency){
+ .prefer_upgrade = 0,
+ .version_mask = 0,
+ .name = name,
+ .version = NULL,
+ };
+}
+
+int apk_deps_add(struct apk_dependency_array **depends,
+ struct apk_dependency *dep)
+{
+ struct apk_dependency_array *deps = *depends;
+ int i;
+
+ if (deps != NULL) {
+ for (i = 0; i < deps->num; i++) {
+ if (deps->item[i].name == dep->name)
+ return 0;
+ }
+ }
+
+ *apk_dependency_array_add(depends) = *dep;
+ return 0;
+}
+void apk_deps_parse(struct apk_database *db,
+ struct apk_dependency_array **depends,
+ apk_blob_t blob)
+{
+ char *start;
+ int i;
+
+ start = blob.ptr;
+ for (i = 0; i < blob.len; i++) {
+ if (blob.ptr[i] != ',' && blob.ptr[i] != '\n')
+ continue;
+
+ parse_depend(db, depends,
+ APK_BLOB_PTR_PTR(start, &blob.ptr[i-1]));
+ start = &blob.ptr[i+1];
+ }
+ parse_depend(db, depends,
+ APK_BLOB_PTR_PTR(start, &blob.ptr[i-1]));
+}
+
+int apk_deps_format(char *buf, int size,
+ struct apk_dependency_array *depends)
+{
+ int i, n = 0;
+
+ if (depends == NULL)
+ return 0;
+
+ for (i = 0; i < depends->num - 1; i++)
+ n += snprintf(&buf[n], size-n,
+ "%s, ",
+ depends->item[i].name->name);
+ n += snprintf(&buf[n], size-n,
+ "%s\n",
+ depends->item[i].name->name);
+ return n;
+}
+
+static const char *script_types[] = {
+ [APK_SCRIPT_PRE_INSTALL] = "pre-install",
+ [APK_SCRIPT_POST_INSTALL] = "post-install",
+ [APK_SCRIPT_PRE_DEINSTALL] = "pre-deinstall",
+ [APK_SCRIPT_POST_DEINSTALL] = "post-deinstall",
+ [APK_SCRIPT_PRE_UPGRADE] = "pre-upgrade",
+ [APK_SCRIPT_POST_UPGRADE] = "post-upgrade",
+};
+
+int apk_script_type(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(script_types); i++)
+ if (script_types[i] &&
+ strcmp(script_types[i], name) == 0)
+ return i;
+
+ return -1;
+}
+
+struct read_info_ctx {
+ struct apk_database *db;
+ struct apk_package *pkg;
+ int has_install;
+};
+
+static int read_info_entry(struct apk_archive_entry *ae, void *ctx)
+{
+ struct read_info_ctx *ri = (struct read_info_ctx *) ctx;
+ struct apk_database *db = ri->db;
+ struct apk_package *pkg = ri->pkg;
+ const int bsize = 4 * 1024;
+ apk_blob_t name, version;
+ char *slash, *str;
+
+ if (strncmp(ae->name, "var/db/apk/", 11) != 0) {
+ pkg->installed_size += (ae->size + bsize - 1) & ~(bsize - 1);
+ return 0;
+ }
+
+ if (!S_ISREG(ae->mode))
+ return 0;
+
+ slash = strchr(&ae->name[11], '/');
+ if (slash == NULL)
+ return 0;
+
+ if (apk_pkg_parse_name(APK_BLOB_PTR_PTR(&ae->name[11], slash-1),
+ &name, &version) < 0)
+ return -1;
+
+ if (pkg->name == NULL) {
+ str = apk_blob_cstr(name);
+ pkg->name = apk_db_get_name(db, str);
+ free(str);
+ }
+ if (pkg->version == NULL)
+ pkg->version = apk_blob_cstr(version);
+
+ if (strcmp(slash, "/DEPEND") == 0) {
+ apk_blob_t blob = apk_archive_entry_read(ae);
+ if (blob.ptr) {
+ apk_deps_parse(db, &pkg->depends, blob);
+ free(blob.ptr);
+ }
+ } else if (strcmp(slash, "/DESC") == 0) {
+ pkg->description = trim(apk_archive_entry_read(ae));
+ } else if (strcmp(slash, "/WWW") == 0) {
+ pkg->url = trim(apk_archive_entry_read(ae));
+ } else if (strcmp(slash, "/LICENSE") == 0) {
+ pkg->license = trim(apk_archive_entry_read(ae));
+ } else if (apk_script_type(slash+1) == APK_SCRIPT_POST_INSTALL ||
+ apk_script_type(slash+1) == APK_SCRIPT_PRE_INSTALL)
+ ri->has_install = 1;
+
+ return 0;
+}
+
+struct apk_package *apk_pkg_read(struct apk_database *db, const char *file)
+{
+ struct read_info_ctx ctx;
+ struct stat st;
+ pthread_t tid;
+ int fd;
+
+ ctx.pkg = calloc(1, sizeof(struct apk_package));
+ if (ctx.pkg == NULL)
+ return NULL;
+
+ fd = open(file, O_RDONLY);
+ if (fd < 0)
+ goto err;
+
+ fstat(fd, &st);
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+
+ tid = apk_checksum_and_tee(&fd, ctx.pkg->csum);
+ if (fd < 0)
+ goto err;
+
+ ctx.db = db;
+ ctx.pkg->size = st.st_size;
+ ctx.has_install = 0;
+ if (apk_parse_tar_gz(fd, read_info_entry, &ctx) != 0) {
+ pthread_join(tid, NULL);
+ goto err;
+ }
+ pthread_join(tid, NULL);
+
+ if (ctx.pkg->name == NULL)
+ goto err;
+
+ close(fd);
+
+ /* Add implicit busybox dependency if there is scripts */
+ if (ctx.has_install) {
+ struct apk_dependency dep = {
+ .name = apk_db_get_name(db, "busybox"),
+ };
+ apk_deps_add(&ctx.pkg->depends, &dep);
+ }
+
+ return ctx.pkg;
+err:
+ apk_pkg_free(ctx.pkg);
+ return NULL;
+}
+
+void apk_pkg_free(struct apk_package *pkg)
+{
+ struct apk_script *script;
+ struct hlist_node *c, *n;
+
+ if (pkg == NULL)
+ return;
+
+ hlist_for_each_entry_safe(script, c, n, &pkg->scripts, script_list)
+ free(script);
+
+ if (pkg->version)
+ free(pkg->version);
+ if (pkg->url)
+ free(pkg->url);
+ if (pkg->description)
+ free(pkg->description);
+ if (pkg->license)
+ free(pkg->license);
+ free(pkg);
+}
+
+int apk_pkg_get_state(struct apk_package *pkg)
+{
+ if (hlist_hashed(&pkg->installed_pkgs_list))
+ return APK_STATE_INSTALL;
+ return APK_STATE_NO_INSTALL;
+}
+
+int apk_pkg_add_script(struct apk_package *pkg, int fd,
+ unsigned int type, unsigned int size)
+{
+ struct apk_script *script;
+ int r;
+
+ script = malloc(sizeof(struct apk_script) + size);
+ script->type = type;
+ script->size = size;
+ r = read(fd, script->script, size);
+ if (r < 0) {
+ free(script);
+ return r;
+ }
+
+ hlist_add_head(&script->script_list, &pkg->scripts);
+ return r;
+}
+
+int apk_pkg_run_script(struct apk_package *pkg, const char *root,
+ unsigned int type)
+{
+ struct apk_script *script;
+ struct hlist_node *c;
+ int fd, status;
+ pid_t pid;
+ char fn[1024];
+
+ hlist_for_each_entry(script, c, &pkg->scripts, script_list) {
+ if (script->type != type)
+ continue;
+
+ snprintf(fn, sizeof(fn),
+ "tmp/%s-%s.%s",
+ pkg->name->name, pkg->version,
+ script_types[script->type]);
+ fd = creat(fn, 0777);
+ if (fd < 0)
+ return fd;
+ write(fd, script->script, script->size);
+ close(fd);
+
+ apk_message("Executing %s", &fn[4]);
+
+ pid = fork();
+ if (pid == -1)
+ return -1;
+ if (pid == 0) {
+ chroot(root);
+ fn[2] = '.';
+ execl(&fn[2], script_types[script->type],
+ pkg->version, "", NULL);
+ exit(1);
+ }
+ waitpid(pid, &status, 0);
+ unlink(fn);
+ if (WIFEXITED(status))
+ return WEXITSTATUS(status);
+ return -1;
+ }
+
+ /* FIXME: Remove this ugly kludge */
+ if (strcmp(pkg->name->name, "busybox") == 0 &&
+ type == APK_SCRIPT_POST_INSTALL) {
+ apk_message("Create busybox links");
+
+ pid = fork();
+ if (pid == -1)
+ return -1;
+ if (pid == 0) {
+ chroot(root);
+ execl("/bin/busybox", "busybox", "--install", "-s", NULL);
+ exit(1);
+ }
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status))
+ return WEXITSTATUS(status);
+ return -1;
+
+ }
+
+ return 0;
+}
+
+static int parse_index_line(struct apk_database *db, struct apk_package *pkg,
+ apk_blob_t blob)
+{
+ apk_blob_t d;
+ char *str;
+
+ if (blob.len < 2 || blob.ptr[1] != ':')
+ return -1;
+
+ d = APK_BLOB_PTR_LEN(blob.ptr+2, blob.len-2);
+ switch (blob.ptr[0]) {
+ case 'P':
+ str = apk_blob_cstr(d);
+ pkg->name = apk_db_get_name(db, str);
+ free(str);
+ break;
+ case 'V':
+ pkg->version = apk_blob_cstr(d);
+ break;
+ case 'T':
+ pkg->description = apk_blob_cstr(d);
+ break;
+ case 'U':
+ pkg->url = apk_blob_cstr(d);
+ break;
+ case 'L':
+ pkg->license = apk_blob_cstr(d);
+ break;
+ case 'D':
+ apk_deps_parse(db, &pkg->depends, d);
+ break;
+ case 'C':
+ apk_hexdump_parse(APK_BLOB_BUF(pkg->csum), d);
+ break;
+ case 'S':
+ pkg->size = apk_blob_uint(d, 10);
+ break;
+ case 'I':
+ pkg->installed_size = apk_blob_uint(d, 10);
+ break;
+ }
+ return 0;
+}
+
+struct apk_package *apk_pkg_parse_index_entry(struct apk_database *db, apk_blob_t blob)
+{
+ struct apk_package *pkg;
+ apk_blob_t l, r;
+
+ pkg = calloc(1, sizeof(struct apk_package));
+ if (pkg == NULL)
+ return NULL;
+
+ r = blob;
+ while (apk_blob_splitstr(r, "\n", &l, &r))
+ parse_index_line(db, pkg, l);
+ parse_index_line(db, pkg, r);
+
+ if (pkg->name == NULL) {
+ apk_pkg_free(pkg);
+ printf("%.*s\n", blob.len, blob.ptr);
+ pkg = NULL;
+ }
+
+ return pkg;
+}
+
+apk_blob_t apk_pkg_format_index_entry(struct apk_package *info, int size,
+ char *buf)
+{
+ int n = 0;
+
+ n += snprintf(&buf[n], size-n,
+ "P:%s\n"
+ "V:%s\n"
+ "S:%u\n"
+ "I:%u\n"
+ "T:%s\n"
+ "U:%s\n"
+ "L:%s\n",
+ info->name->name, info->version,
+ info->size, info->installed_size,
+ info->description, info->url, info->license);
+
+ if (info->depends != NULL) {
+ n += snprintf(&buf[n], size-n, "D:");
+ n += apk_deps_format(&buf[n], size-n, info->depends);
+ }
+ n += snprintf(&buf[n], size-n, "C:");
+ n += apk_hexdump_format(size-n, &buf[n],
+ APK_BLOB_BUF(info->csum));
+ n += snprintf(&buf[n], size-n,
+ "\n\n");
+
+ return APK_BLOB_PTR_LEN(buf, n);
+}