summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile39
-rw-r--r--src/add.c42
-rw-r--r--src/apk.c81
-rw-r--r--src/apk_applet.h27
-rw-r--r--src/apk_archive.h42
-rw-r--r--src/apk_blob.h37
-rw-r--r--src/apk_database.h97
-rw-r--r--src/apk_defines.h147
-rw-r--r--src/apk_hash.h55
-rw-r--r--src/apk_package.h84
-rw-r--r--src/apk_state.h50
-rw-r--r--src/apk_version.h26
-rw-r--r--src/archive.c349
-rw-r--r--src/blob.c125
-rw-r--r--src/database.c802
-rw-r--r--src/del.c53
-rw-r--r--src/hash.c97
-rw-r--r--src/index.c70
-rw-r--r--src/md5.c488
-rw-r--r--src/md5.h60
-rw-r--r--src/package.c483
-rw-r--r--src/state.c267
-rw-r--r--src/template.c15
-rw-r--r--src/ver.c76
-rw-r--r--src/version.c165
25 files changed, 3777 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..0964012
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,39 @@
+# Makefile - one file to rule them all, one file to bind them
+#
+# Copyright (C) 2007 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 3 as published
+# by the Free Software Foundation. See http://www.gnu.org/ for details.
+
+TARGETS = apk
+
+apk_OBJS = \
+ state.o \
+ database.o \
+ package.o \
+ archive.o \
+ version.o \
+ blob.o \
+ hash.o \
+ md5.o \
+ add.o \
+ del.o \
+ ver.o \
+ index.o \
+ apk.o
+
+ALL_OBJS = $(apk_OBJS)
+
+all: $(TARGETS)
+
+apk: $(apk_OBJS)
+
+clean::
+ @rm -f $(TARGETS) $(ALL_OBJS)
+
+install::
+ $(INSTALLDIR) $(DESTDIR)$(SBINDIR)
+ $(INSTALL) $(TARGETS) $(DESTDIR)$(SBINDIR)
+
diff --git a/src/add.c b/src/add.c
new file mode 100644
index 0000000..28dcb9b
--- /dev/null
+++ b/src/add.c
@@ -0,0 +1,42 @@
+/* add.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 <stdio.h>
+#include "apk_applet.h"
+#include "apk_database.h"
+
+static int add_main(int argc, char **argv)
+{
+ struct apk_database db;
+ int i;
+
+ apk_db_init(&db, "/home/fabled/tmproot/");
+ apk_db_read_config(&db);
+ for (i = 0; i < argc; i++) {
+ struct apk_dependency dep = {
+ .name = apk_db_get_name(&db, argv[i]),
+ };
+ apk_deps_add(&db.world, &dep);
+ }
+ apk_db_recalculate_and_commit(&db);
+ apk_db_free(&db);
+
+ return 0;
+}
+
+static struct apk_applet apk_add = {
+ .name = "add",
+ .usage = "apkname...",
+ .main = add_main,
+};
+
+APK_DEFINE_APPLET(apk_add);
+
diff --git a/src/apk.c b/src/apk.c
new file mode 100644
index 0000000..f8f13dd
--- /dev/null
+++ b/src/apk.c
@@ -0,0 +1,81 @@
+/* apk.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 <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "apk_defines.h"
+#include "apk_applet.h"
+
+void apk_log(const char *prefix, const char *format, ...)
+{
+ va_list va;
+
+ if (prefix != NULL)
+ fprintf(stderr, prefix);
+ va_start(va, format);
+ vfprintf(stderr, format, va);
+ va_end(va);
+ fprintf(stderr, "\n");
+}
+
+int usage(void)
+{
+ struct apk_applet **a, *applet;
+
+ printf("apk-tools " APK_VERSION "\n"
+ "\n"
+ "Usage:\n");
+
+ for (a = &__start_apkapplets; a < &__stop_apkapplets; a++) {
+ applet = *a;
+ printf(" apk %s %s\n",
+ applet->name, applet->usage);
+ }
+ printf("\n");
+
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ struct apk_applet **a, *applet;
+ char *prog;
+
+ prog = strrchr(argv[0], '/');
+ if (prog == NULL)
+ prog = argv[0];
+ else
+ prog++;
+
+ if (strcmp(prog, "apk") == 0) {
+ if (argc < 2)
+ return usage();
+ prog = argv[1];
+ argv++;
+ argc--;
+ } else if (strncmp(prog, "apk_", 4) == 0) {
+ prog += 4;
+ } else
+ return usage();
+
+ for (a = &__start_apkapplets; a < &__stop_apkapplets; a++) {
+ applet = *a;
+ if (strcmp(prog, applet->name) == 0) {
+ argv++;
+ argc--;
+ return applet->main(argc, argv);
+ }
+ }
+
+ return usage();
+}
diff --git a/src/apk_applet.h b/src/apk_applet.h
new file mode 100644
index 0000000..d243d65
--- /dev/null
+++ b/src/apk_applet.h
@@ -0,0 +1,27 @@
+/* apk_applet.h - 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.
+ */
+
+#ifndef APK_APPLET_H
+#define APK_APPLET_H
+
+struct apk_applet {
+ const char *name;
+ const char *usage;
+ int (*main)(int argc, char **argv);
+};
+
+extern struct apk_applet *__start_apkapplets, *__stop_apkapplets;
+
+#define APK_DEFINE_APPLET(x) \
+ static struct apk_applet *__applet_##x \
+ __attribute__((__section__("apkapplets") used)) = &x;
+
+#endif
diff --git a/src/apk_archive.h b/src/apk_archive.h
new file mode 100644
index 0000000..2e8d274
--- /dev/null
+++ b/src/apk_archive.h
@@ -0,0 +1,42 @@
+/* apk_archive.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.
+ */
+
+#ifndef APK_ARCHIVE
+#define APK_ARCHIVE
+
+#include <sys/types.h>
+#include <pthread.h>
+#include "apk_blob.h"
+
+struct apk_archive_entry {
+ char *name;
+ char *link_target;
+ char *uname;
+ char *gname;
+ off_t size;
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+ time_t mtime;
+ dev_t device;
+ int read_fd;
+};
+
+typedef int (*apk_archive_entry_parser)(struct apk_archive_entry *entry, void *ctx);
+
+pid_t apk_open_gz(int *fd);
+int apk_parse_tar(int fd, apk_archive_entry_parser parser, void *ctx);
+int apk_parse_tar_gz(int fd, apk_archive_entry_parser parser, void *ctx);
+apk_blob_t apk_archive_entry_read(struct apk_archive_entry *ae);
+int apk_archive_entry_extract(struct apk_archive_entry *ae, const char *to);
+pthread_t apk_checksum_and_tee(int *fd, void *ptr);
+
+#endif
diff --git a/src/apk_blob.h b/src/apk_blob.h
new file mode 100644
index 0000000..033d331
--- /dev/null
+++ b/src/apk_blob.h
@@ -0,0 +1,37 @@
+/* apk_blob.h - 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.
+ */
+
+#ifndef APK_BLOB_H
+#define APK_BLOB_H
+
+#include <string.h>
+
+struct apk_blob {
+ unsigned int len;
+ char *ptr;
+};
+typedef struct apk_blob apk_blob_t;
+
+#define APK_BLOB_NULL ((apk_blob_t){0, NULL})
+#define APK_BLOB_STR(str) ((apk_blob_t){strlen(str), (str)})
+#define APK_BLOB_BUF(buf) ((apk_blob_t){sizeof(buf), (char *)(buf)})
+#define APK_BLOB_PTR_LEN(beg,len) ((apk_blob_t){(len), (beg)})
+#define APK_BLOB_PTR_PTR(beg,end) APK_BLOB_PTR_LEN((beg),(end)-(beg)+1)
+
+char *apk_blob_cstr(apk_blob_t str);
+int apk_blob_splitstr(apk_blob_t blob, char *split, apk_blob_t *l, apk_blob_t *r);
+int apk_blob_rsplit(apk_blob_t blob, char split, apk_blob_t *l, apk_blob_t *r);
+unsigned apk_blob_uint(apk_blob_t blob, int base);
+
+int apk_hexdump_parse(apk_blob_t to, apk_blob_t from);
+int apk_hexdump_format(int tolen, char *to, apk_blob_t from);
+
+#endif
diff --git a/src/apk_database.h b/src/apk_database.h
new file mode 100644
index 0000000..96ff361
--- /dev/null
+++ b/src/apk_database.h
@@ -0,0 +1,97 @@
+/* apk_database.h - 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.
+ */
+
+#ifndef APK_PKGDB_H
+#define APK_PKGDB_H
+
+#include "apk_version.h"
+#include "apk_hash.h"
+#include "apk_archive.h"
+#include "apk_package.h"
+
+#define APK_MAX_REPOS 32
+
+struct apk_db_file {
+ struct hlist_node dir_files_list;
+ struct hlist_node pkg_files_list;
+
+ struct apk_db_dir *dir;
+ struct apk_package *owner;
+ char filename[];
+};
+
+struct apk_db_dir {
+ apk_hash_node hash_node;
+
+ struct hlist_head files;
+ struct apk_db_dir *parent;
+
+ unsigned refs;
+ mode_t mode;
+ char dirname[];
+};
+
+struct apk_name {
+ apk_hash_node hash_node;
+
+ char *name;
+ struct apk_package_array *pkgs;
+};
+
+struct apk_repository {
+ char *url;
+};
+
+struct apk_database {
+ char *root;
+ unsigned pkg_id, num_repos;
+
+ struct apk_dependency_array *world;
+ struct apk_repository repos[APK_MAX_REPOS];
+
+ struct {
+ struct apk_hash names;
+ struct apk_hash packages;
+ } available;
+
+ struct {
+ struct hlist_head packages;
+ struct apk_hash dirs;
+ struct {
+ unsigned files;
+ unsigned dirs;
+ unsigned packages;
+ } stats;
+ } installed;
+};
+
+struct apk_name *apk_db_get_name(struct apk_database *db, const char *name);
+void apk_name_free(struct apk_name *pkgname);
+
+void apk_db_init(struct apk_database *db, const char *root);
+void apk_db_free(struct apk_database *db);
+int apk_db_read_config(struct apk_database *db);
+int apk_db_write_config(struct apk_database *db);
+
+int apk_db_pkg_add_file(struct apk_database *db, const char *file);
+struct apk_package *apk_db_get_pkg(struct apk_database *db, csum_t sum);
+
+int apk_db_index_read(struct apk_database *db, int fd, int repo);
+void apk_db_index_write(struct apk_database *db, int fd);
+
+int apk_db_add_repository(struct apk_database *db, const char *repo);
+int apk_db_recalculate_and_commit(struct apk_database *db);
+
+int apk_db_install_pkg(struct apk_database *db,
+ struct apk_package *oldpkg,
+ struct apk_package *newpkg);
+
+#endif
diff --git a/src/apk_defines.h b/src/apk_defines.h
new file mode 100644
index 0000000..ecd08e3
--- /dev/null
+++ b/src/apk_defines.h
@@ -0,0 +1,147 @@
+/* apk_defines.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.
+ */
+
+#ifndef APK_DEFINES_H
+#define APK_DEFINES_H
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define BIT(x) (1 << (x))
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef NULL
+#define NULL 0L
+#endif
+
+#ifndef container_of
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+#endif
+
+#if 1
+#include "md5.h"
+
+typedef md5sum_t csum_t;
+typedef struct md5_ctx csum_ctx_t;
+
+#define csum_init(ctx) md5_init(ctx)
+#define csum_process(ctx, buf, len) md5_process(ctx, buf, len)
+#define csum_finish(ctx, buf) md5_finish(ctx, buf)
+#endif
+
+#define apk_error(args...) apk_log("ERROR: ", args)
+#define apk_warning(args...) apk_log("WARNING: ", args)
+#define apk_message(args...) apk_log(NULL, args)
+
+void apk_log(const char *prefix, const char *format, ...);
+
+#define APK_ARRAY(array_type_name, elem_type_name) \
+ struct array_type_name { \
+ int num; \
+ elem_type_name item[]; \
+ }; \
+ static inline struct array_type_name * \
+ array_type_name##_resize(struct array_type_name *a, int size) \
+ { \
+ struct array_type_name *tmp; \
+ tmp = (struct array_type_name *) \
+ realloc(a, sizeof(struct array_type_name) + \
+ size * sizeof(elem_type_name)); \
+ tmp->num = size; \
+ return tmp; \
+ } \
+ static inline elem_type_name * \
+ array_type_name##_add(struct array_type_name **a) \
+ { \
+ int size = 1; \
+ if (*a != NULL) size += (*a)->num; \
+ *a = array_type_name##_resize(*a, size); \
+ return &(*a)->item[size-1]; \
+ }
+
+#define LIST_END (void *) 0xe01
+#define LIST_POISON1 (void *) 0xdeadbeef
+#define LIST_POISON2 (void *) 0xabbaabba
+
+struct hlist_head {
+ struct hlist_node *first;
+};
+
+struct hlist_node {
+ struct hlist_node *next;
+};
+
+static inline int hlist_empty(const struct hlist_head *h)
+{
+ return !h->first;
+}
+
+static inline int hlist_hashed(const struct hlist_node *n)
+{
+ return n->next != NULL;
+}
+
+static inline void __hlist_del(struct hlist_node *n, struct hlist_node **pprev)
+{
+ *pprev = n->next;
+}
+
+static inline void hlist_del(struct hlist_node *n, struct hlist_node **pprev)
+{
+ __hlist_del(n, pprev);
+ n->next = LIST_POISON1;
+}
+
+static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
+{
+ struct hlist_node *first = h->first;
+
+ n->next = first ? first : LIST_END;
+ h->first = n;
+}
+
+static inline void hlist_add_after(struct hlist_node *n, struct hlist_node **prev)
+{
+ n->next = *prev ? *prev : LIST_END;
+ *prev = n;
+}
+
+#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
+
+#define hlist_for_each(pos, head) \
+ for (pos = (head)->first; pos && pos != LIST_END; \
+ pos = pos->next)
+
+#define hlist_for_each_safe(pos, n, head) \
+ for (pos = (head)->first; pos && pos != LIST_END && \
+ ({ n = pos->next; 1; }); \
+ pos = n)
+
+#define hlist_for_each_entry(tpos, pos, head, member) \
+ for (pos = (head)->first; \
+ pos && pos != LIST_END && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next)
+
+#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \
+ for (pos = (head)->first; \
+ pos && pos != LIST_END && ({ n = pos->next; 1; }) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = n)
+
+#endif
diff --git a/src/apk_hash.h b/src/apk_hash.h
new file mode 100644
index 0000000..f2f4ced
--- /dev/null
+++ b/src/apk_hash.h
@@ -0,0 +1,55 @@
+/* apk_hash.h - 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.
+ */
+
+#ifndef APK_HASH_H
+#define APK_HASH_H
+
+#include <malloc.h>
+#include "apk_defines.h"
+
+typedef void *apk_hash_item;
+typedef const void *apk_hash_key;
+
+typedef unsigned long (*apk_hash_f)(apk_hash_key);
+typedef int (*apk_hash_compare_f)(apk_hash_key, apk_hash_key);
+typedef void (*apk_hash_delete_f)(apk_hash_item);
+typedef int (*apk_hash_enumerator_f)(apk_hash_item, void *ctx);
+
+struct apk_hash_ops {
+ ptrdiff_t node_offset;
+ apk_hash_key (*get_key)(apk_hash_item item);
+ unsigned long (*hash_key)(apk_hash_key key);
+ int (*compare)(apk_hash_key key, apk_hash_key item);
+ void (*delete_item)(apk_hash_item item);
+};
+
+typedef struct hlist_node apk_hash_node;
+APK_ARRAY(apk_hash_array, struct hlist_head);
+
+struct apk_hash {
+ const struct apk_hash_ops *ops;
+ struct apk_hash_array *buckets;
+ int num_items;
+};
+
+unsigned long apk_hash_string(const char *string);
+unsigned long apk_hash_csum(const void *);
+
+void apk_hash_init(struct apk_hash *h, const struct apk_hash_ops *ops,
+ int num_buckets);
+void apk_hash_free(struct apk_hash *h);
+
+int apk_hash_foreach(struct apk_hash *h, apk_hash_enumerator_f e, void *ctx);
+apk_hash_item apk_hash_get(struct apk_hash *h, apk_hash_key key);
+void apk_hash_insert(struct apk_hash *h, apk_hash_item item);
+void apk_hash_delete(struct apk_hash *h, apk_hash_key key);
+
+#endif
diff --git a/src/apk_package.h b/src/apk_package.h
new file mode 100644
index 0000000..cdcce49
--- /dev/null
+++ b/src/apk_package.h
@@ -0,0 +1,84 @@
+/* apk_database.h - 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.
+ */
+
+#ifndef APK_PKG_H
+#define APK_PKG_H
+
+#include "apk_version.h"
+#include "apk_hash.h"
+
+struct apk_database;
+struct apk_name;
+
+#define APK_SCRIPT_PRE_INSTALL 0
+#define APK_SCRIPT_POST_INSTALL 1
+#define APK_SCRIPT_PRE_DEINSTALL 2
+#define APK_SCRIPT_POST_DEINSTALL 3
+#define APK_SCRIPT_PRE_UPGRADE 4
+#define APK_SCRIPT_POST_UPGRADE 5
+
+struct apk_script {
+ struct hlist_node script_list;
+ unsigned int type;
+ unsigned int size;
+ char script[];
+};
+
+struct apk_dependency {
+ unsigned conflict : 1;
+ unsigned prefer_upgrade : 1;
+ unsigned version_mask : 3;
+
+ struct apk_name *name;
+ char *version;
+};
+APK_ARRAY(apk_dependency_array, struct apk_dependency);
+
+struct apk_package {
+ apk_hash_node hash_node;
+
+ csum_t csum;
+ unsigned id, repos;
+ struct apk_name *name;
+ char *version;
+ char *url, *description, *license;
+ struct apk_dependency_array *depends;
+ unsigned int installed_size, size;
+
+ /* for installed packages only */
+ struct hlist_node installed_pkgs_list;
+ struct hlist_head owned_files;
+ struct hlist_head scripts;
+};
+APK_ARRAY(apk_package_array, struct apk_package *);
+
+int apk_deps_add(struct apk_dependency_array **depends,
+ struct apk_dependency *dep);
+void apk_deps_parse(struct apk_database *db,
+ struct apk_dependency_array **depends,
+ apk_blob_t blob);
+int apk_deps_format(char *buf, int size,
+ struct apk_dependency_array *depends);
+int apk_script_type(const char *name);
+
+struct apk_package *apk_pkg_read(struct apk_database *db, const char *name);
+void apk_pkg_free(struct apk_package *pkg);
+
+int apk_pkg_get_state(struct apk_package *pkg);
+int apk_pkg_add_script(struct apk_package *pkg, int fd,
+ unsigned int type, unsigned int size);
+int apk_pkg_run_script(struct apk_package *pkg, const char *root,
+ unsigned int type);
+
+struct apk_package *apk_pkg_parse_index_entry(struct apk_database *db, apk_blob_t entry);
+apk_blob_t apk_pkg_format_index_entry(struct apk_package *pkg, int size, char *buf);
+
+#endif
diff --git a/src/apk_state.h b/src/apk_state.h
new file mode 100644
index 0000000..0e95475
--- /dev/null
+++ b/src/apk_state.h
@@ -0,0 +1,50 @@
+/* apk_state.h - 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.
+ */
+
+#ifndef APK_STATE_H
+#define APK_STATE_H
+
+#include "apk_database.h"
+
+#define APK_STATE_NOT_CONSIDERED 0
+#define APK_STATE_PREFER_UPGRADE 1
+#define APK_STATE_INSTALL 2
+#define APK_STATE_NO_INSTALL 3
+
+struct apk_state {
+ int refs;
+ unsigned char bitarray[];
+};
+
+struct apk_deferred_state {
+ unsigned int preference;
+ struct apk_package *deferred_install;
+ /* struct apk_pkg_name_queue *install_queue; */
+ struct apk_state *state;
+};
+
+struct apk_state *apk_state_new(struct apk_database *db);
+struct apk_state *apk_state_dup(struct apk_state *state);
+void apk_state_unref(struct apk_state *state);
+
+int apk_state_commit(struct apk_state *state, struct apk_database *db);
+
+int apk_state_satisfy_deps(struct apk_state *state,
+ struct apk_dependency_array *deps);
+
+void apk_state_pkg_set(struct apk_state *state,
+ struct apk_package *pkg);
+int apk_state_pkg_install(struct apk_state *state,
+ struct apk_package *pkg);
+int apk_state_pkg_is_installed(struct apk_state *state,
+ struct apk_package *pkg);
+
+#endif
diff --git a/src/apk_version.h b/src/apk_version.h
new file mode 100644
index 0000000..e0b5a37
--- /dev/null
+++ b/src/apk_version.h
@@ -0,0 +1,26 @@
+/* apk_version.h - 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.
+ */
+
+#ifndef APK_VERSION_H
+#define APK_VERSION_H
+
+#include "apk_blob.h"
+
+#define APK_VERSION_LESS -1
+#define APK_VERSION_EQUAL 0
+#define APK_VERSION_GREATER 1
+
+#define APK_VERSION_RESULT_MASK(r) (1 << ((r)+1))
+
+int apk_version_validate(apk_blob_t ver);
+int apk_version_compare(apk_blob_t a, apk_blob_t b);
+
+#endif
diff --git a/src/archive.c b/src/archive.c
new file mode 100644
index 0000000..e5092dc
--- /dev/null
+++ b/src/archive.c
@@ -0,0 +1,349 @@
+/* archive.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 <stdio.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc.h>
+#include <string.h>
+#include <unistd.h>
+#include <sysexits.h>
+#include <sys/wait.h>
+
+#include "apk_defines.h"
+#include "apk_archive.h"
+
+#ifndef GUNZIP_BINARY
+#define GUNZIP_BINARY "/bin/gunzip"
+#endif
+
+struct tar_header {
+ /* ustar header, Posix 1003.1 */
+ char name[100]; /* 0-99 */
+ char mode[8]; /* 100-107 */
+ char uid[8]; /* 108-115 */
+ char gid[8]; /* 116-123 */
+ char size[12]; /* 124-135 */
+ char mtime[12]; /* 136-147 */
+ char chksum[8]; /* 148-155 */
+ char typeflag; /* 156-156 */
+ char linkname[100]; /* 157-256 */
+ char magic[8]; /* 257-264 */
+ char uname[32]; /* 265-296 */
+ char gname[32]; /* 297-328 */
+ char devmajor[8]; /* 329-336 */
+ char devminor[8]; /* 337-344 */
+ char prefix[155]; /* 345-499 */
+ char padding[12]; /* 500-512 */
+};
+
+static int get_dev_null(void)
+{
+ static int fd_null = 0;
+
+ if (fd_null == 0) {
+ fd_null = open("/dev/null", O_WRONLY);
+ if (fd_null < 0)
+ err(EX_OSFILE, "/dev/null");
+ }
+ return fd_null;
+
+}
+
+pid_t apk_open_gz(int *fd)
+{
+ int pipe_fd[2];
+ pid_t child_pid;
+
+ if (pipe(pipe_fd) < 0)
+ err(EX_OSERR, "pipe");
+
+ child_pid = fork();
+ if (child_pid < 0)
+ err(EX_OSERR, "fork");
+
+ if (child_pid == 0) {
+ close(pipe_fd[0]);
+ dup2(pipe_fd[1], STDOUT_FILENO);
+ dup2(*fd, STDIN_FILENO);
+ dup2(get_dev_null(), STDERR_FILENO);
+ close(pipe_fd[1]);
+ execl(GUNZIP_BINARY, "gunzip", "-c", NULL);
+ err(EX_UNAVAILABLE, GUNZIP_BINARY);
+ }
+
+ close(pipe_fd[1]);
+ *fd = pipe_fd[0];
+
+ return child_pid;
+}
+
+#define GET_OCTAL(s) apk_blob_uint(APK_BLOB_PTR_LEN(s, sizeof(s)), 8)
+
+static int do_splice(int from_fd, int to_fd, int len)
+{
+ int i = 0, r;
+
+ while (i != len) {
+ r = splice(from_fd, NULL, to_fd, NULL, len - i, SPLICE_F_MOVE);
+ if (r == -1)
+ return i;
+ i += r;
+ }
+
+ return i;
+}
+
+int apk_parse_tar(int fd, apk_archive_entry_parser parser, void *ctx)
+{
+ struct apk_archive_entry entry;
+ struct tar_header buf;
+ unsigned long offset = 0;
+ int end = 0, r;
+
+ memset(&entry, 0, sizeof(entry));
+ while (read(fd, &buf, 512) == 512) {
+ offset += 512;
+ if (buf.name[0] == '\0') {
+ if (end)
+ break;
+ end++;
+ continue;
+ }
+
+ entry = (struct apk_archive_entry){
+ .size = GET_OCTAL(buf.size),
+ .uid = GET_OCTAL(buf.uid),
+ .gid = GET_OCTAL(buf.gid),
+ .mode = GET_OCTAL(buf.mode) & 0777,
+ .mtime = GET_OCTAL(buf.mtime),
+ .name = entry.name,
+ .uname = buf.uname,
+ .gname = buf.gname,
+ .device = makedev(GET_OCTAL(buf.devmajor),
+ GET_OCTAL(buf.devminor)),
+ .read_fd = fd,
+ };
+
+ switch (buf.typeflag) {
+ case 'L':
+ if (entry.name != NULL)
+ free(entry.name);
+ entry.name = malloc(entry.size+1);
+ read(fd, entry.name, entry.size);
+ offset += entry.size;
+ entry.size = 0;
+ break;
+ case '0':
+ case '7': /* regular file */
+ entry.mode |= S_IFREG;
+ break;
+ case '1': /* hard link */
+ entry.mode |= S_IFREG;
+ entry.link_target = buf.linkname;
+ break;
+ case '2': /* symbolic link */
+ entry.mode |= S_IFLNK;
+ entry.link_target = buf.linkname;
+ break;
+ case '3': /* char device */
+ entry.mode |= S_IFCHR;
+ break;
+ case '4': /* block devicek */
+ entry.mode |= S_IFBLK;
+ break;
+ case '5': /* directory */
+ entry.mode |= S_IFDIR;
+ break;
+ default:
+ break;
+ }
+
+ if (entry.mode & S_IFMT) {
+ if (entry.name == NULL)
+ entry.name = strdup(buf.name);
+
+ /* callback parser function */
+ offset += entry.size;
+ r = parser(&entry, ctx);
+ if (r != 0)
+ return r;
+ offset -= entry.size;
+
+ free(entry.name);
+ entry.name = NULL;
+ }
+
+ if (entry.size)
+ offset += do_splice(fd, get_dev_null(), entry.size);
+
+ /* align to next 512 block */
+ if (offset & 511)
+ offset += do_splice(fd, get_dev_null(),
+ 512 - (offset & 511));
+ }
+
+ return 0;
+}
+
+int apk_parse_tar_gz(int fd, apk_archive_entry_parser parser, void *ctx)
+{
+ pid_t pid;
+ int r, status;
+
+ pid = apk_open_gz(&fd);
+ if (pid < 0)
+ return pid;
+
+ r = apk_parse_tar(fd, parser, ctx);
+ close(fd);
+ waitpid(pid, &status, 0);
+
+ return r;
+}
+
+apk_blob_t apk_archive_entry_read(struct apk_archive_entry *ae)
+{
+ char *str;
+ int pos = 0;
+ ssize_t r;
+
+ str = malloc(ae->size + 1);
+ pos = 0;
+ while (ae->size) {
+ r = read(ae->read_fd, &str[pos], ae->size);
+ if (r < 0) {
+ free(str);
+ return APK_BLOB_NULL;
+ }
+ pos += r;
+ ae->size -= r;
+ }
+ str[pos] = 0;
+
+ return APK_BLOB_PTR_LEN(str, pos+1);
+}
+
+int apk_archive_entry_extract(struct apk_archive_entry *ae, const char *fn)
+{
+ int r = -1;
+
+ if (fn == NULL)
+ fn = ae->name;
+
+ /* BIG HONKING FIXME */
+ unlink(fn);
+
+ switch (ae->mode & S_IFMT) {
+ case S_IFDIR:
+ r = mkdir(fn, ae->mode & 0777);
+ if (r < 0 && errno == EEXIST)
+ r = 0;
+ break;
+ case S_IFREG:
+ if (ae->link_target == NULL) {
+ r = open(fn, O_WRONLY | O_CREAT, ae->mode & 0777);
+ if (r < 0)
+ break;
+ ae->size -= do_splice(ae->read_fd, r, ae->size);
+ close(r);
+ r = ae->size ? -1 : 0;
+ } else {
+ r = link(ae->link_target, fn);
+ }
+ break;
+ case S_IFLNK:
+ r = symlink(ae->link_target, fn);
+ break;
+ case S_IFSOCK:
+ case S_IFBLK:
+ case S_IFCHR:
+ case S_IFIFO:
+ r = mknod(fn, ae->mode, ae->device);
+ break;
+ }
+ if (r != 0)
+ apk_error("Failed to extract %s\n", ae->name);
+ return r;
+}
+
+struct checksum_and_tee {
+ int in_fd, tee_fd;
+ void *ptr;
+};
+
+static void *__apk_checksum_and_tee(void *arg)
+{
+ struct checksum_and_tee *args = (struct checksum_and_tee *) arg;
+ char buf[2*1024];
+ int r, w, wt;
+ __off64_t offset;
+ csum_ctx_t ctx;
+ int dosplice = 1;
+
+ offset = lseek(args->in_fd, 0, SEEK_CUR);
+ csum_init(&ctx);
+ do {
+ r = read(args->in_fd, buf, sizeof(buf));
+ if (r <= 0)
+ break;
+
+ wt = 0;
+ do {
+ if (dosplice) {
+ w = splice(args->in_fd, &offset, args->tee_fd, NULL,
+ r - wt, SPLICE_F_MOVE);
+ if (w < 0) {
+ dosplice = 0;
+ continue;
+ }
+ } else {
+ w = write(args->tee_fd, &buf[wt], r - wt);
+ if (w < 0)
+ break;
+ }
+ wt += w;
+ } while (wt != r);
+
+ csum_process(&ctx, buf, r);
+ } while (r == sizeof(buf));
+
+ csum_finish(&ctx, args->ptr);
+ close(args->tee_fd);
+ close(args->in_fd);
+ free(args);
+
+ return NULL;
+}
+
+pthread_t apk_checksum_and_tee(int *fd, void *ptr)
+{
+ struct checksum_and_tee *args;
+ int fds[2];
+ pthread_t tid;
+
+ if (pipe(fds) < 0)
+ return -1;
+
+ fcntl(fds[0], F_SETFD, FD_CLOEXEC);
+ fcntl(fds[1], F_SETFD, FD_CLOEXEC);
+
+ args = malloc(sizeof(*args));
+ *args = (struct checksum_and_tee){ *fd, fds[1], ptr };
+ if (pthread_create(&tid, NULL, __apk_checksum_and_tee, args) < 0)
+ return -1;
+
+ *fd = fds[0];
+ return tid;
+}
+
diff --git a/src/blob.c b/src/blob.c
new file mode 100644
index 0000000..eecf642
--- /dev/null
+++ b/src/blob.c
@@ -0,0 +1,125 @@
+/* blob.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 <malloc.h>
+#include <string.h>
+
+#include "apk_blob.h"
+
+char *apk_blob_cstr(apk_blob_t blob)
+{
+ char *cstr;
+
+ if (blob.ptr[blob.len-1] == 0)
+ return strdup(blob.ptr);
+
+ cstr = malloc(blob.len + 1);
+ memcpy(cstr, blob.ptr, blob.len);
+ cstr[blob.len] = 0;
+
+ return cstr;
+}
+
+int apk_blob_rsplit(apk_blob_t blob, char split, apk_blob_t *l, apk_blob_t *r)
+{
+ char *sep;
+
+ sep = memrchr(blob.ptr, split, blob.len);
+ if (sep == NULL)
+ return 0;
+
+ if (l != NULL)
+ *l = APK_BLOB_PTR_PTR(blob.ptr, sep - 1);
+ if (r != NULL)
+ *r = APK_BLOB_PTR_PTR(sep + 1, blob.ptr + blob.len);
+
+ return 1;
+}
+
+int apk_blob_splitstr(apk_blob_t blob, char *split, apk_blob_t *l, apk_blob_t *r)
+{
+ int splitlen = strlen(split);
+ char *pos = blob.ptr, *end = blob.ptr + blob.len - splitlen + 1;
+
+ if (end < pos)
+ return 0;
+
+ while (1) {
+ pos = memchr(pos, split[0], end - pos);
+ if (pos == NULL)
+ return 0;
+
+ if (memcmp(pos, split, splitlen) != 0) {
+ pos++;
+ continue;
+ }
+
+ *l = APK_BLOB_PTR_PTR(blob.ptr, pos-1);
+ *r = APK_BLOB_PTR_PTR(pos+splitlen, blob.ptr+blob.len-1);
+ return 1;
+ }
+}
+
+static int dx(int c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 0xa;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 0xa;
+ return -1;
+}
+
+unsigned apk_blob_uint(apk_blob_t blob, int base)
+{
+ unsigned val;
+ int i, ch;
+
+ val = 0;
+ for (i = 0; i < blob.len; i++) {
+ if (blob.ptr[i] == 0)
+ break;
+ ch = dx(blob.ptr[i]);
+ if (ch < 0 || ch >= base)
+ return 0;
+ val *= base;
+ val += ch;
+ }
+ return val;
+}
+
+int apk_hexdump_parse(apk_blob_t to, apk_blob_t from)
+{
+ int i;
+
+ if (to.len * 2 != from.len)
+ return -1;
+
+ for (i = 0; i < from.len / 2; i++)
+ to.ptr[i] = (dx(from.ptr[i*2]) << 4) + dx(from.ptr[i*2+1]);
+
+ return 0;
+}
+
+int apk_hexdump_format(int tolen, char *to, apk_blob_t from)
+{
+ static const char *xd = "0123456789abcdef";
+ int i;
+
+ for (i = 0; i < from.len && i*2+2 < tolen; i++) {
+ to[i*2+0] = xd[(from.ptr[i] >> 4) & 0xf];
+ to[i*2+1] = xd[from.ptr[i] & 0xf];
+ }
+ to[i*2] = 0;
+
+ return i*2;
+}
diff --git a/src/database.c b/src/database.c
new file mode 100644
index 0000000..f98be64
--- /dev/null
+++ b/src/database.c
@@ -0,0 +1,802 @@
+/* database.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 <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <string.h>
+
+#include "apk_defines.h"
+#include "apk_package.h"
+#include "apk_database.h"
+#include "apk_state.h"
+
+struct install_ctx {
+ struct apk_database *db;
+ struct apk_package *pkg;
+
+ int script;
+ struct apk_db_dir *dircache;
+ struct hlist_node **file_dir_node;
+ struct hlist_node **file_pkg_node;
+};
+
+static apk_hash_key pkg_name_get_key(apk_hash_item item)
+{
+ return ((struct apk_name *) item)->name;
+}
+
+static const struct apk_hash_ops pkg_name_hash_ops = {
+ .node_offset = offsetof(struct apk_name, hash_node),
+ .get_key = pkg_name_get_key,
+ .hash_key = (apk_hash_f) apk_hash_string,
+ .compare = (apk_hash_compare_f) strcmp,
+ .delete_item = (apk_hash_delete_f) apk_name_free,
+};
+
+static apk_hash_key pkg_info_get_key(apk_hash_item item)
+{
+ return ((struct apk_package *) item)->csum;
+}
+
+static int cmpcsum(apk_hash_key a, apk_hash_key b)
+{
+ return memcmp(a, b, sizeof(csum_t));
+}
+
+static const struct apk_hash_ops pkg_info_hash_ops = {
+ .node_offset = offsetof(struct apk_package, hash_node),
+ .get_key = pkg_info_get_key,
+ .hash_key = (apk_hash_f) apk_hash_csum,
+ .compare = cmpcsum,
+ .delete_item = (apk_hash_delete_f) apk_pkg_free,
+};
+
+static apk_hash_key apk_db_dir_get_key(apk_hash_item item)
+{
+ return ((struct apk_db_dir *) item)->dirname;
+}
+
+static const struct apk_hash_ops dir_hash_ops = {
+ .node_offset = offsetof(struct apk_db_dir, hash_node),
+ .get_key = apk_db_dir_get_key,
+ .hash_key = (apk_hash_f) apk_hash_string,
+ .compare = (apk_hash_compare_f) strcmp,
+ .delete_item = (apk_hash_delete_f) free,
+};
+
+struct apk_name *apk_db_get_name(struct apk_database *db, const char *name)
+{
+ struct apk_name *pn;
+
+ pn = (struct apk_name *) apk_hash_get(&db->available.names, name);
+ if (pn != NULL)
+ return pn;
+
+ pn = calloc(1, sizeof(struct apk_name));
+ if (pn == NULL)
+ return NULL;
+
+ pn->name = strdup(name);
+ apk_hash_insert(&db->available.names, pn);
+
+ return pn;
+}
+
+void apk_name_free(struct apk_name *name)
+{
+ free(name->name);
+ free(name);
+}
+
+static struct apk_db_dir *apk_db_dir_ref(struct apk_database *db,
+ struct apk_db_dir *dir,
+ int create_dir)
+{
+ if (dir->refs == 0) {
+ if (dir->parent != NULL)
+ apk_db_dir_ref(db, dir->parent, create_dir);
+ db->installed.stats.dirs++;
+ if (create_dir)
+ mkdir(dir->dirname, dir->mode);
+ }
+ dir->refs++;
+
+ return dir;
+}
+
+static void apk_db_dir_unref(struct apk_database *db, struct apk_db_dir *dir)
+{
+ dir->refs--;
+ if (dir->refs > 0)
+ return;
+
+ db->installed.stats.dirs--;
+ rmdir(dir->dirname);
+
+ if (dir->parent != NULL)
+ apk_db_dir_unref(db, dir->parent);
+}
+
+static struct apk_db_dir *apk_db_dir_get(struct apk_database *db,
+ apk_blob_t name)
+{
+ struct apk_db_dir *dir;
+ apk_blob_t bparent;
+ char *cstr;
+
+ if (name.ptr[name.len-1] == '/')
+ name.len--;
+
+ cstr = apk_blob_cstr(name);
+ dir = (struct apk_db_dir *) apk_hash_get(&db->installed.dirs, cstr);
+ free(cstr);
+ if (dir != NULL)
+ return dir;
+
+ dir = calloc(1, sizeof(*dir) + name.len + 1);
+ memcpy(dir->dirname, name.ptr, name.len);
+ dir->dirname[name.len] = 0;
+ apk_hash_insert(&db->installed.dirs, dir);
+
+ if (apk_blob_rsplit(name, '/', &bparent, NULL))
+ dir->parent = apk_db_dir_get(db, bparent);
+
+ return dir;
+}
+
+static struct apk_db_file *apk_db_file_new(struct apk_db_dir *dir,
+ apk_blob_t name,
+ struct hlist_node **after)
+{
+ struct apk_db_file *file;
+
+ file = calloc(1, sizeof(*file) + name.len + 1);
+ hlist_add_after(&file->dir_files_list, after);
+ file->dir = dir;
+ memcpy(file->filename, name.ptr, name.len);
+ file->filename[name.len] = 0;
+
+ return file;
+}
+
+static void apk_db_file_set_owner(struct apk_database *db,
+ struct apk_db_file *file,
+ struct apk_package *owner,
+ int create_dir,
+ struct hlist_node **after)
+{
+ if (file->owner != NULL)
+ return;
+
+ db->installed.stats.files++;
+ file->dir = apk_db_dir_ref(db, file->dir, create_dir);
+ file->owner = owner;
+ hlist_add_after(&file->pkg_files_list, after);
+}
+
+static struct apk_db_file *apk_db_file_get(struct apk_database *db,
+ apk_blob_t name,
+ struct install_ctx *ctx)
+{
+ struct apk_db_dir *dir;
+ struct apk_db_file *file;
+ struct hlist_node *cur;
+ apk_blob_t bdir, bfile;
+
+ if (!apk_blob_rsplit(name, '/', &bdir, &bfile))
+ return NULL;
+
+ dir = NULL;
+ if (ctx != NULL && ctx->dircache != NULL) {
+ dir = ctx->dircache;
+ if (strncmp(dir->dirname, bdir.ptr, bdir.len) != 0 ||
+ dir->dirname[bdir.len] != 0)
+ dir = NULL;
+ }
+ if (dir == NULL) {
+ dir = apk_db_dir_get(db, bdir);
+ if (ctx != NULL) {
+ ctx->dircache = dir;
+ ctx->file_dir_node = &dir->files.first;
+ }
+ }
+
+ hlist_for_each_entry(file, cur, &dir->files, dir_files_list) {
+ if (strncmp(file->filename, bfile.ptr, bfile.len) == 0 &&
+ file->filename[bfile.len] == 0)
+ return file;
+ }
+
+ file = apk_db_file_new(dir, bfile, ctx->file_dir_node);
+ ctx->file_dir_node = &file->dir_files_list.next;
+
+ return file;
+}
+
+static int apk_db_read_fdb(struct apk_database *db, int fd)
+{
+ struct apk_package *pkg = NULL;
+ struct apk_db_dir *dir = NULL;
+ struct apk_db_file *file = NULL;
+ struct hlist_node **pkg_node = &db->installed.packages.first;
+ struct hlist_node **file_dir_node = NULL;
+ struct hlist_node **file_pkg_node = NULL;
+
+ char buf[1024];
+ apk_blob_t l, r;
+ csum_t csum;
+ int n;
+
+ r = APK_BLOB_PTR_LEN(buf, 0);
+ while (1) {
+ n = read(fd, &r.ptr[r.len], sizeof(buf) - r.len);
+ if (n <= 0)
+ break;
+ r.len += n;
+
+ while (apk_blob_splitstr(r, "\n", &l, &r)) {
+ n = l.ptr[0];
+ l.ptr++;
+ l.len--;
+ switch (n) {
+ case 'P':
+ if (apk_hexdump_parse(APK_BLOB_BUF(csum), l)) {
+ apk_error("Not a valid checksum");
+ return -1;
+ }
+ pkg = apk_db_get_pkg(db, csum);
+ if (pkg == NULL) {
+ apk_error("Package '%.*s' is installed, but not in any repository",
+ l.len, l.ptr);
+ return -1;
+ }
+ if (!hlist_hashed(&pkg->installed_pkgs_list)) {
+ db->installed.stats.packages++;
+ hlist_add_after(&pkg->installed_pkgs_list, pkg_node);
+ pkg_node = &pkg->installed_pkgs_list.next;
+ }
+ dir = NULL;
+ file_dir_node = NULL;
+ file_pkg_node = &pkg->owned_files.first;
+ break;
+ case 'D':
+ if (pkg == NULL) {
+ apk_error("FDB directory entry before package entry");
+ return -1;
+ }
+ dir = apk_db_dir_get(db, l);
+ file_dir_node = &dir->files.first;
+ break;
+ case 'F':
+ if (dir == NULL) {
+ apk_error("FDB file entry before directory entry");
+ return -1;
+ }
+ file = apk_db_file_new(dir, l, file_dir_node);
+ apk_db_file_set_owner(db, file, pkg, FALSE, file_pkg_node);
+ file_dir_node = &file->dir_files_list.next;
+ file_pkg_node = &file->pkg_files_list.next;
+ break;
+ default:
+ apk_error("FDB entry '%c' unsupported", n);
+ return -1;
+ }
+ }
+
+ memcpy(&buf[0], r.ptr, r.len);
+ r = APK_BLOB_PTR_LEN(buf, r.len);
+ }
+
+ return 0;
+}
+
+static int apk_db_write_fdb(struct apk_database *db, int fd)
+{
+ struct apk_package *pkg;
+ struct apk_db_dir *dir;
+ struct apk_db_file *file;
+ struct hlist_node *c1, *c2;
+ char buf[1024];
+ int n;
+
+ hlist_for_each_entry(pkg, c1, &db->installed.packages, installed_pkgs_list) {
+ n = 0;
+ buf[n++] = 'P';
+ n += apk_hexdump_format(sizeof(buf)-n, &buf[n],
+ APK_BLOB_BUF(pkg->csum));
+ buf[n++] = '\n';
+
+ dir = NULL;
+ hlist_for_each_entry(file, c2, &pkg->owned_files, pkg_files_list) {
+ if (file->owner == NULL)
+ continue;
+
+ if (dir != file->dir) {
+ n += snprintf(&buf[n], sizeof(buf)-n,
+ "D%s\n",
+ file->dir->dirname);
+ dir = file->dir;
+ }
+
+ n += snprintf(&buf[n], sizeof(buf)-n,
+ "F%s\n",
+ file->filename);
+
+ write(fd, buf, n);
+ n = 0;
+ }
+ }
+
+ return 0;
+}
+
+struct apk_script_header {
+ csum_t csum;
+ unsigned int type;
+ unsigned int size;
+};
+
+static int apk_db_write_scriptdb(struct apk_database *db, int fd)
+{
+ struct apk_package *pkg;
+ struct apk_script *script;
+ struct apk_script_header hdr;
+ struct hlist_node *c1, *c2;
+
+ hlist_for_each_entry(pkg, c1, &db->installed.packages,
+ installed_pkgs_list) {
+ hlist_for_each_entry(script, c2, &pkg->scripts, script_list) {
+ memcpy(hdr.csum, pkg->csum, sizeof(csum_t));
+ hdr.type = script->type;
+ hdr.size = script->size;
+
+ write(fd, &hdr, sizeof(hdr));
+ write(fd, script->script, script->size);
+ }
+ }
+
+ return 0;
+}
+
+static int apk_db_read_scriptdb(struct apk_database *db, int fd)
+{
+ struct apk_package *pkg;
+ struct apk_script_header hdr;
+
+ while (read(fd, &hdr, sizeof(hdr)) == sizeof(hdr)) {
+ pkg = apk_db_get_pkg(db, hdr.csum);
+ apk_pkg_add_script(pkg, fd, hdr.type, hdr.size);
+ }
+
+ return 0;
+}
+
+static const char *get_db_path(struct apk_database *db, const char *f)
+{
+ static char fn[1024];
+
+ snprintf(fn, sizeof(fn), "%s%s", db->root, f);
+
+ return fn;
+}
+
+void apk_db_init(struct apk_database *db, const char *root)
+{
+ memset(db, 0, sizeof(*db));
+ apk_hash_init(&db->available.names, &pkg_name_hash_ops, 1000);
+ apk_hash_init(&db->available.packages, &pkg_info_hash_ops, 4000);
+ apk_hash_init(&db->installed.dirs, &dir_hash_ops, 1000);
+
+ if (root != NULL) {
+ db->root = strdup(root);
+ apk_db_add_repository(db, "/home/fabled/foo/");
+ mkdir(get_db_path(db, "tmp"), 01777);
+ mkdir(get_db_path(db, "dev"), 0755);
+ mknod(get_db_path(db, "dev/null"), 0666, makedev(1, 3));
+ }
+}
+
+int apk_db_read_config(struct apk_database *db)
+{
+ struct stat st;
+ char *buf;
+ int fd;
+
+ if (db->root == NULL)
+ return -1;
+
+ /* Read:
+ * 1. installed repository
+ * 2. source repositories
+ * 3. master dependencies
+ * 4. package statuses
+ * 5. files db
+ * 6. script db
+ */
+ fd = open(get_db_path(db, "var/lib/apk/world"), O_RDONLY);
+ if (fd >= 0) {
+ fstat(fd, &st);
+ buf = malloc(st.st_size);
+ read(fd, buf, st.st_size);
+ apk_deps_parse(db, &db->world,
+ APK_BLOB_PTR_LEN(buf, st.st_size));
+ close(fd);
+ } else {
+ apk_deps_parse(db, &db->world,
+ APK_BLOB_STR("busybox, alpine-baselayout, "
+ "apk-tools, alpine-conf"));
+ }
+
+ fd = open(get_db_path(db, "var/lib/apk/files"), O_RDONLY);
+ if (fd >= 0) {
+ apk_db_read_fdb(db, fd);
+ close(fd);
+ }
+
+ fd = open(get_db_path(db, "var/lib/apk/scripts"), O_RDONLY);
+ if (fd >= 0) {
+ apk_db_read_scriptdb(db, fd);
+ close(fd);
+ }
+
+ return 0;
+}
+
+struct write_ctx {
+ struct apk_database *db;
+ int fd;
+};
+
+int apk_db_write_config(struct apk_database *db)
+{
+ char buf[1024];
+ int n, fd;
+
+ if (db->root == NULL)
+ return -1;
+
+ mkdir(get_db_path(db, "var"), 0755);
+ mkdir(get_db_path(db, "var/lib"), 0755);
+ mkdir(get_db_path(db, "var/lib/apk"), 0755);
+
+ fd = creat(get_db_path(db, "var/lib/apk/world"), 0600);
+ if (fd < 0)
+ return -1;
+ n = apk_deps_format(buf, sizeof(buf), db->world);
+ write(fd, buf, n);
+ close(fd);
+
+ fd = creat(get_db_path(db, "var/lib/apk/files"), 0600);
+ if (fd < 0)
+ return -1;
+ apk_db_write_fdb(db, fd);
+ close(fd);
+
+ fd = creat(get_db_path(db, "var/lib/apk/scripts"), 0600);
+ if (fd < 0)
+ return -1;
+ apk_db_write_scriptdb(db, fd);
+ close(fd);
+
+ return 0;
+}
+
+void apk_db_free(struct apk_database *db)
+{
+ apk_hash_free(&db->available.names);
+ apk_hash_free(&db->available.packages);
+ apk_hash_free(&db->installed.dirs);
+ if (db->root != NULL)
+ free(db->root);
+}
+
+static void apk_db_pkg_add(struct apk_database *db, struct apk_package *pkg)
+{
+ struct apk_package *idb;
+
+ idb = apk_hash_get(&db->available.packages, pkg->csum);
+ if (idb == NULL) {
+ pkg->id = db->pkg_id++;
+ apk_hash_insert(&db->available.packages, pkg);
+ *apk_package_array_add(&pkg->name->pkgs) = pkg;
+ } else {
+ idb->repos |= pkg->repos;
+ apk_pkg_free(pkg);
+ }
+}
+
+struct apk_package *apk_db_get_pkg(struct apk_database *db, csum_t sum)
+{
+ return apk_hash_get(&db->available.packages, sum);
+}
+
+int apk_db_pkg_add_file(struct apk_database *db, const char *file)
+{
+ struct apk_package *info;
+
+ info = apk_pkg_read(db, file);
+ if (info == NULL)
+ return FALSE;
+
+ apk_db_pkg_add(db, info);
+ return TRUE;
+}
+
+int apk_db_index_read(struct apk_database *db, int fd, int repo)
+{
+ struct apk_package *pkg;
+ char buf[1024];
+ int n;
+ apk_blob_t l, r;
+
+ r = APK_BLOB_PTR_LEN(buf, 0);
+ while (1) {
+ n = read(fd, &r.ptr[r.len], sizeof(buf) - r.len);
+ if (n <= 0)
+ break;
+ r.len += n;
+
+ while (apk_blob_splitstr(r, "\n\n", &l, &r)) {
+ pkg = apk_pkg_parse_index_entry(db, l);
+ if (pkg != NULL) {
+ pkg->repos |= BIT(repo);
+ apk_db_pkg_add(db, pkg);
+ }
+ }
+
+ memcpy(&buf[0], r.ptr, r.len);
+ r = APK_BLOB_PTR_LEN(buf, r.len);
+ }
+
+ return 0;
+}
+
+static int write_index_entry(apk_hash_item item, void *ctx)
+{
+ int fd = (int) ctx;
+ char buf[1024];
+ apk_blob_t blob;
+
+ blob = apk_pkg_format_index_entry(item, sizeof(buf), buf);
+ if (blob.ptr)
+ write(fd, blob.ptr, blob.len);
+
+ return 0;
+}
+
+void apk_db_index_write(struct apk_database *db, int fd)
+{
+ apk_hash_foreach(&db->available.packages, write_index_entry, (void *) fd);
+}
+
+int apk_db_add_repository(struct apk_database *db, const char *repo)
+{
+ char tmp[256];
+ int fd, r;
+
+ if (db->num_repos >= APK_MAX_REPOS)
+ return -1;
+
+ r = db->num_repos++;
+ db->repos[r] = (struct apk_repository){
+ .url = strdup(repo)
+ };
+
+ snprintf(tmp, sizeof(tmp), "%sAPK_INDEX", repo);
+ fd = open(tmp, O_RDONLY);
+ if (fd < 0) {
+ apk_error("Failed to open index file %s", tmp);
+ return -1;
+ }
+ apk_db_index_read(db, fd, r);
+ close(fd);
+
+ return 0;
+}
+
+int apk_db_recalculate_and_commit(struct apk_database *db)
+{
+ struct apk_state *state;
+ int r;
+
+ state = apk_state_new(db);
+ r = apk_state_satisfy_deps(state, db->world);
+ if (r == 0) {
+ r = apk_state_commit(state, db);
+ if (r != 0) {
+ apk_error("Failed to commit changes");
+ return r;
+ }
+ apk_db_write_config(db);
+
+ apk_message("OK: %d packages, %d dirs, %d files",
+ db->installed.stats.packages,
+ db->installed.stats.dirs,
+ db->installed.stats.files);
+ } else {
+ apk_error("Failed to build installation graph");
+ }
+ apk_state_unref(state);
+
+ return r;
+}
+
+static int apk_db_install_archive_entry(struct apk_archive_entry *ae,
+ struct install_ctx *ctx)
+{
+ struct apk_database *db = ctx->db;
+ struct apk_package *pkg = ctx->pkg;
+ apk_blob_t name = APK_BLOB_STR(ae->name);
+ struct apk_db_file *file;
+ const char *p;
+ int r = 0, type;
+
+ if (strncmp(ae->name, "var/db/apk/", 11) == 0) {
+ p = &ae->name[11];
+ if (strncmp(p, pkg->name->name, strlen(pkg->name->name)) != 0)
+ return 0;
+ p += strlen(pkg->name->name) + 1;
+ if (strncmp(p, pkg->version, strlen(pkg->version)) != 0)
+ return 0;
+ p += strlen(pkg->version) + 1;
+
+ type = apk_script_type(p);
+ if (type < 0)
+ return 0;
+
+ ae->size -= apk_pkg_add_script(pkg, ae->read_fd,
+ type, ae->size);
+
+ if (type == ctx->script) {
+ r = apk_pkg_run_script(pkg, db->root, type);
+ if (r != 0)
+ apk_error("%s-%s: Failed to execute pre-install/upgrade script",
+ pkg->name->name, pkg->version);
+ }
+
+ return r;
+ }
+
+ if (ctx->file_pkg_node == NULL)
+ ctx->file_pkg_node = &pkg->owned_files.first;
+
+ if (!S_ISDIR(ae->mode)) {
+ file = apk_db_file_get(db, name, ctx);
+ if (file == NULL)
+ return -1;
+
+ if (file->owner != NULL &&
+ file->owner->name != pkg->name) {
+ apk_error("%s: Trying to overwrite %s owned by %s.\n",
+ pkg->name->name, ae->name,
+ file->owner->name->name);
+ return -1;
+ }
+
+ apk_db_file_set_owner(db, file, pkg, TRUE, ctx->file_pkg_node);
+ ctx->file_pkg_node = &file->pkg_files_list.next;
+
+ if (strncmp(file->filename, ".keep_", 6) == 0)
+ return 0;
+
+ r = apk_archive_entry_extract(ae, NULL);
+ } else {
+ if (name.ptr[name.len-1] == '/')
+ name.len--;
+ apk_db_dir_get(db, name)->mode = 0777 & ae->mode;
+ }
+
+ return r;
+}
+
+static void apk_db_purge_pkg(struct apk_database *db,
+ struct apk_package *pkg)
+{
+ struct apk_db_file *file;
+ struct hlist_node *c, *n;
+ char fn[1024];
+
+ hlist_for_each_entry_safe(file, c, n, &pkg->owned_files, pkg_files_list) {
+ file->owner = NULL;
+ snprintf(fn, sizeof(fn), "%s/%s",
+ file->dir->dirname,
+ file->filename);
+ unlink(fn);
+
+ apk_db_dir_unref(db, file->dir);
+ hlist_del(c, &pkg->owned_files.first);
+
+ db->installed.stats.files--;
+ }
+ db->installed.stats.packages--;
+}
+
+int apk_db_install_pkg(struct apk_database *db,
+ struct apk_package *oldpkg,
+ struct apk_package *newpkg)
+{
+ struct install_ctx ctx;
+ csum_t csum;
+ char file[256];
+ pthread_t tid = 0;
+ int fd, r;
+
+ if (chdir(db->root) < 0)
+ return errno;
+
+ /* Purge the old package if there */
+ if (oldpkg != NULL) {
+ if (newpkg == NULL) {
+ r = apk_pkg_run_script(oldpkg, db->root,
+ APK_SCRIPT_PRE_DEINSTALL);
+ if (r != 0)
+ return r;
+ }
+ apk_db_purge_pkg(db, oldpkg);
+ if (newpkg == NULL) {
+ apk_pkg_run_script(oldpkg, db->root,
+ APK_SCRIPT_POST_DEINSTALL);
+ return 0;
+ }
+ }
+
+ /* Install the new stuff */
+ snprintf(file, sizeof(file),
+ "%s%s-%s.apk",
+ db->repos[0].url, newpkg->name->name, newpkg->version);
+
+ fd = open(file, O_RDONLY);
+ if (fd < 0)
+ return errno;
+
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+
+ tid = apk_checksum_and_tee(&fd, csum);
+ if (tid < 0)
+ goto err_close;
+
+ ctx = (struct install_ctx) {
+ .db = db,
+ .pkg = newpkg,
+ .script = (oldpkg == NULL) ?
+ APK_SCRIPT_PRE_INSTALL : APK_SCRIPT_PRE_UPGRADE,
+ };
+ if (apk_parse_tar_gz(fd, (apk_archive_entry_parser)
+ apk_db_install_archive_entry, &ctx) != 0)
+ goto err_close;
+
+ pthread_join(tid, NULL);
+ close(fd);
+
+ db->installed.stats.packages++;
+ hlist_add_head(&newpkg->installed_pkgs_list, &db->installed.packages);
+
+ if (memcmp(csum, newpkg->csum, sizeof(csum)) != 0)
+ apk_warning("%s-%s: checksum does not match",
+ newpkg->name->name, newpkg->version);
+
+ r = apk_pkg_run_script(newpkg, db->root,
+ (oldpkg == NULL) ?
+ APK_SCRIPT_POST_INSTALL : APK_SCRIPT_POST_UPGRADE);
+ if (r != 0)
+ apk_error("%s-%s: Failed to execute post-install/upgrade script",
+ newpkg->name->name, newpkg->version);
+ return r;
+
+err_close:
+ close(fd);
+ if (tid != 0)
+ pthread_join(tid, NULL);
+ return -1;
+}
diff --git a/src/del.c b/src/del.c
new file mode 100644
index 0000000..c4d7539
--- /dev/null
+++ b/src/del.c
@@ -0,0 +1,53 @@
+/* del.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 <stdio.h>
+#include "apk_applet.h"
+#include "apk_database.h"
+
+static int del_main(int argc, char **argv)
+{
+ struct apk_database db;
+ int i, j;
+
+ apk_db_init(&db, "/home/fabled/tmproot/");
+ apk_db_read_config(&db);
+
+ if (db.world == NULL)
+ goto out;
+
+ for (i = 0; i < argc; i++) {
+ for (j = 0; j < db.world->num; j++) {
+ if (strcmp(db.world->item[j].name->name,
+ argv[i]) == 0) {
+ db.world->item[j] =
+ db.world->item[db.world->num-1];
+ db.world =
+ apk_dependency_array_resize(db.world, db.world->num-1);
+ }
+ }
+ }
+
+ apk_db_recalculate_and_commit(&db);
+out:
+ apk_db_free(&db);
+
+ return 0;
+}
+
+static struct apk_applet apk_del = {
+ .name = "del",
+ .usage = "apkname...",
+ .main = del_main,
+};
+
+APK_DEFINE_APPLET(apk_del);
+
diff --git a/src/hash.c b/src/hash.c
new file mode 100644
index 0000000..447601e
--- /dev/null
+++ b/src/hash.c
@@ -0,0 +1,97 @@
+/* hash.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 "apk_defines.h"
+#include "apk_hash.h"
+
+unsigned long apk_hash_string(const char *str)
+{
+ unsigned long hash = 5381;
+ int c;
+
+ while ((c = *str++) != 0)
+ hash = hash * 33 + c;
+
+ return hash;
+}
+
+unsigned long apk_hash_csum(const void *ptr)
+{
+ return *(const unsigned long *) ptr;
+}
+
+void apk_hash_init(struct apk_hash *h, const struct apk_hash_ops *ops,
+ int num_buckets)
+{
+ h->ops = ops;
+ h->buckets = apk_hash_array_resize(NULL, num_buckets);
+ h->num_items = 0;
+}
+
+void apk_hash_free(struct apk_hash *h)
+{
+ apk_hash_foreach(h, (apk_hash_enumerator_f) h->ops->delete_item, NULL);
+ free(h->buckets);
+}
+
+int apk_hash_foreach(struct apk_hash *h, apk_hash_enumerator_f e, void *ctx)
+{
+ apk_hash_node *pos, *n;
+ ptrdiff_t offset = h->ops->node_offset;
+ int i, r;
+
+ for (i = 0; i < h->buckets->num; i++) {
+ hlist_for_each_safe(pos, n, &h->buckets->item[i]) {
+ r = e(((void *) pos) - offset, ctx);
+ if (r != 0)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+apk_hash_item apk_hash_get(struct apk_hash *h, apk_hash_key key)
+{
+ ptrdiff_t offset = h->ops->node_offset;
+ unsigned long hash;
+ apk_hash_node *pos;
+ apk_hash_item item;
+ apk_hash_key itemkey;
+
+ hash = h->ops->hash_key(key) % h->buckets->num;
+ hlist_for_each(pos, &h->buckets->item[hash]) {
+ item = ((void *) pos) - offset;
+ itemkey = h->ops->get_key(item);
+ if (h->ops->compare(key, itemkey) == 0)
+ return item;
+ }
+
+ return NULL;
+}
+
+void apk_hash_insert(struct apk_hash *h, apk_hash_item item)
+{
+ apk_hash_key key;
+ unsigned long hash;
+ apk_hash_node *node;
+
+ key = h->ops->get_key(item);
+ hash = h->ops->hash_key(key) % h->buckets->num;
+ node = (apk_hash_node *) (item + h->ops->node_offset);
+ hlist_add_head(node, &h->buckets->item[hash]);
+ h->num_items++;
+}
+
+void apk_hash_delete(struct apk_hash *h, apk_hash_key key)
+{
+}
+
diff --git a/src/index.c b/src/index.c
new file mode 100644
index 0000000..4234002
--- /dev/null
+++ b/src/index.c
@@ -0,0 +1,70 @@
+/* index.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 <stdio.h>
+#include <unistd.h>
+
+#include "apk_applet.h"
+#include "apk_database.h"
+
+struct counts {
+ int total;
+ int unsatisfied;
+};
+
+static int warn_if_no_providers(apk_hash_item item, void *ctx)
+{
+ struct counts *counts = (struct counts *) ctx;
+ struct apk_name *name = (struct apk_name *) item;
+
+ if (name->pkgs == NULL) {
+ if (++counts->unsatisfied < 10)
+ apk_warning("No provider for dependency '%s'",
+ name->name);
+ else if (counts->unsatisfied == 10)
+ apk_warning("Too many unsatisfiable dependencies, "
+ "not reporting the rest.");
+ }
+ counts->total++;
+
+ return 0;
+}
+
+static int index_main(int argc, char **argv)
+{
+ struct apk_database db;
+ struct counts counts = {0,0};
+ int i;
+
+ apk_db_init(&db, NULL);
+ for (i = 0; i < argc; i++)
+ apk_db_pkg_add_file(&db, argv[i]);
+ apk_db_index_write(&db, STDOUT_FILENO);
+ apk_hash_foreach(&db.available.names, warn_if_no_providers, &counts);
+ apk_db_free(&db);
+
+ if (counts.unsatisfied != 0)
+ apk_warning("Total of %d unsatisfiable package "
+ "names. Your repository maybe broken.",
+ counts.unsatisfied);
+ apk_message("Index has %d packages", counts.total);
+
+ return 0;
+}
+
+static struct apk_applet apk_index = {
+ .name = "index",
+ .usage = "apkname...",
+ .main = index_main,
+};
+
+APK_DEFINE_APPLET(apk_index);
+
diff --git a/src/md5.c b/src/md5.c
new file mode 100644
index 0000000..e165724
--- /dev/null
+++ b/src/md5.c
@@ -0,0 +1,488 @@
+/* md5.c - Compute MD5 checksum of files or strings according to the
+ * definition of MD5 in RFC 1321 from April 1992.
+ * Copyright (C) 1995-1999 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu> */
+/* Hacked to work with BusyBox by Alfred M. Szmidt <ams@trillian.itslinux.org> */
+
+/* Sucked directly into ipkg since the md5sum functions aren't in libbb
+ Dropped a few functions since ipkg only needs md5_stream.
+ Got rid of evil, twisted defines of FALSE=1 and TRUE=0
+ 6 March 2002 Carl Worth <cworth@east.isi.edu>
+*/
+
+/*
+ * June 29, 2001 Manuel Novoa III
+ *
+ * Added MD5SUM_SIZE_VS_SPEED configuration option.
+ *
+ * Current valid values, with data from my system for comparison, are:
+ * (using uClibc and running on linux-2.4.4.tar.bz2)
+ * user times (sec) text size (386)
+ * 0 (fastest) 1.1 6144
+ * 1 1.4 5392
+ * 2 3.0 5088
+ * 3 (smallest) 5.1 4912
+ */
+
+#define MD5SUM_SIZE_VS_SPEED 0
+
+/**********************************************************************/
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <endian.h>
+#include <sys/types.h>
+#if defined HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+#include "md5.h"
+
+/* Handle endian-ness */
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define SWAP(n) (n)
+#else
+#define SWAP(n) ((n << 24) | ((n&65280)<<8) | ((n&16711680)>>8) | (n>>24))
+#endif
+
+#if MD5SUM_SIZE_VS_SPEED == 0
+/* This array contains the bytes used to pad the buffer to the next
+ 64-byte boundary. (RFC 1321, 3.1: Step 1) */
+static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
+#endif
+
+/* These are the four functions used in the four steps of the MD5 algorithm
+ and defined in the RFC 1321. The first function is a little bit optimized
+ (as found in Colin Plumbs public domain implementation). */
+/* #define FF(b, c, d) ((b & c) | (~b & d)) */
+#define FF(b, c, d) (d ^ (b & (c ^ d)))
+#define FG(b, c, d) FF (d, b, c)
+#define FH(b, c, d) (b ^ c ^ d)
+#define FI(b, c, d) (c ^ (b | ~d))
+
+#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
+
+/* Process LEN bytes of BUFFER, accumulating context into CTX.
+ It is assumed that LEN % 64 == 0. */
+static void md5_process_block(struct md5_ctx *ctx,
+ const void *buffer, size_t len)
+{
+ md5_uint32 correct_words[16];
+ const md5_uint32 *words = buffer;
+ size_t nwords = len / sizeof(md5_uint32);
+ const md5_uint32 *endp = words + nwords;
+#if MD5SUM_SIZE_VS_SPEED > 0
+ static const md5_uint32 C_array[] = {
+ /* round 1 */
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
+ 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+ 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+ 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+ /* round 2 */
+ 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
+ 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8,
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
+ 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+ /* round 3 */
+ 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+ 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+ 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05,
+ 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+ /* round 4 */
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
+ 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+ 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+ };
+
+ static const char P_array[] = {
+#if MD5SUM_SIZE_VS_SPEED > 1
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 1 */
+#endif
+ 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, /* 2 */
+ 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, /* 3 */
+ 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 /* 4 */
+ };
+
+#if MD5SUM_SIZE_VS_SPEED > 1
+ static const char S_array[] = {
+ 7, 12, 17, 22,
+ 5, 9, 14, 20,
+ 4, 11, 16, 23,
+ 6, 10, 15, 21
+ };
+#endif
+#endif
+
+ md5_uint32 A = ctx->A;
+ md5_uint32 B = ctx->B;
+ md5_uint32 C = ctx->C;
+ md5_uint32 D = ctx->D;
+
+ /* First increment the byte count. RFC 1321 specifies the possible
+ length of the file up to 2^64 bits. Here we only compute the
+ number of bytes. Do a double word increment. */
+ ctx->total[0] += len;
+ if (ctx->total[0] < len)
+ ++ctx->total[1];
+
+ /* Process all bytes in the buffer with 64 bytes in each round of
+ the loop. */
+ while (words < endp) {
+ md5_uint32 *cwp = correct_words;
+ md5_uint32 A_save = A;
+ md5_uint32 B_save = B;
+ md5_uint32 C_save = C;
+ md5_uint32 D_save = D;
+
+#if MD5SUM_SIZE_VS_SPEED > 1
+ const md5_uint32 *pc;
+ const char *pp;
+ const char *ps;
+ int i;
+ md5_uint32 temp;
+
+ for ( i=0 ; i < 16 ; i++ ) {
+ cwp[i] = SWAP(words[i]);
+ }
+ words += 16;
+
+#if MD5SUM_SIZE_VS_SPEED > 2
+ pc = C_array; pp = P_array; ps = S_array - 4;
+
+ for ( i = 0 ; i < 64 ; i++ ) {
+ if ((i&0x0f) == 0) ps += 4;
+ temp = A;
+ switch (i>>4) {
+ case 0:
+ temp += FF(B,C,D);
+ break;
+ case 1:
+ temp += FG(B,C,D);
+ break;
+ case 2:
+ temp += FH(B,C,D);
+ break;
+ case 3:
+ temp += FI(B,C,D);
+ }
+ temp += cwp[(int)(*pp++)] + *pc++;
+ temp = CYCLIC(temp, ps[i&3]);
+ temp += B;
+ A = D; D = C; C = B; B = temp;
+ }
+#else
+ pc = C_array; pp = P_array; ps = S_array;
+
+ for ( i = 0 ; i < 16 ; i++ ) {
+ temp = A + FF(B,C,D) + cwp[(int)(*pp++)] + *pc++;
+ temp = CYCLIC (temp, ps[i&3]);
+ temp += B;
+ A = D; D = C; C = B; B = temp;
+ }
+
+ ps += 4;
+ for ( i = 0 ; i < 16 ; i++ ) {
+ temp = A + FG(B,C,D) + cwp[(int)(*pp++)] + *pc++;
+ temp = CYCLIC (temp, ps[i&3]);
+ temp += B;
+ A = D; D = C; C = B; B = temp;
+ }
+ ps += 4;
+ for ( i = 0 ; i < 16 ; i++ ) {
+ temp = A + FH(B,C,D) + cwp[(int)(*pp++)] + *pc++;
+ temp = CYCLIC (temp, ps[i&3]);
+ temp += B;
+ A = D; D = C; C = B; B = temp;
+ }
+ ps += 4;
+ for ( i = 0 ; i < 16 ; i++ ) {
+ temp = A + FI(B,C,D) + cwp[(int)(*pp++)] + *pc++;
+ temp = CYCLIC (temp, ps[i&3]);
+ temp += B;
+ A = D; D = C; C = B; B = temp;
+ }
+
+#endif
+#else
+ /* First round: using the given function, the context and a constant
+ the next context is computed. Because the algorithms processing
+ unit is a 32-bit word and it is determined to work on words in
+ little endian byte order we perhaps have to change the byte order
+ before the computation. To reduce the work for the next steps
+ we store the swapped words in the array CORRECT_WORDS. */
+
+#define OP(a, b, c, d, s, T) \
+ do \
+ { \
+ a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \
+ ++words; \
+ CYCLIC (a, s); \
+ a += b; \
+ } \
+ while (0)
+
+ /* Before we start, one word to the strange constants.
+ They are defined in RFC 1321 as
+
+ T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
+ */
+
+#if MD5SUM_SIZE_VS_SPEED == 1
+ const md5_uint32 *pc;
+ const char *pp;
+ int i;
+#endif
+
+ /* Round 1. */
+#if MD5SUM_SIZE_VS_SPEED == 1
+ pc = C_array;
+ for ( i=0 ; i < 4 ; i++ ) {
+ OP(A, B, C, D, 7, *pc++);
+ OP(D, A, B, C, 12, *pc++);
+ OP(C, D, A, B, 17, *pc++);
+ OP(B, C, D, A, 22, *pc++);
+ }
+#else
+ OP(A, B, C, D, 7, 0xd76aa478);
+ OP(D, A, B, C, 12, 0xe8c7b756);
+ OP(C, D, A, B, 17, 0x242070db);
+ OP(B, C, D, A, 22, 0xc1bdceee);
+ OP(A, B, C, D, 7, 0xf57c0faf);
+ OP(D, A, B, C, 12, 0x4787c62a);
+ OP(C, D, A, B, 17, 0xa8304613);
+ OP(B, C, D, A, 22, 0xfd469501);
+ OP(A, B, C, D, 7, 0x698098d8);
+ OP(D, A, B, C, 12, 0x8b44f7af);
+ OP(C, D, A, B, 17, 0xffff5bb1);
+ OP(B, C, D, A, 22, 0x895cd7be);
+ OP(A, B, C, D, 7, 0x6b901122);
+ OP(D, A, B, C, 12, 0xfd987193);
+ OP(C, D, A, B, 17, 0xa679438e);
+ OP(B, C, D, A, 22, 0x49b40821);
+#endif
+
+ /* For the second to fourth round we have the possibly swapped words
+ in CORRECT_WORDS. Redefine the macro to take an additional first
+ argument specifying the function to use. */
+#undef OP
+#define OP(f, a, b, c, d, k, s, T) \
+ do \
+ { \
+ a += f (b, c, d) + correct_words[k] + T; \
+ CYCLIC (a, s); \
+ a += b; \
+ } \
+ while (0)
+
+ /* Round 2. */
+#if MD5SUM_SIZE_VS_SPEED == 1
+ pp = P_array;
+ for ( i=0 ; i < 4 ; i++ ) {
+ OP(FG, A, B, C, D, (int)(*pp++), 5, *pc++);
+ OP(FG, D, A, B, C, (int)(*pp++), 9, *pc++);
+ OP(FG, C, D, A, B, (int)(*pp++), 14, *pc++);
+ OP(FG, B, C, D, A, (int)(*pp++), 20, *pc++);
+ }
+#else
+ OP(FG, A, B, C, D, 1, 5, 0xf61e2562);
+ OP(FG, D, A, B, C, 6, 9, 0xc040b340);
+ OP(FG, C, D, A, B, 11, 14, 0x265e5a51);
+ OP(FG, B, C, D, A, 0, 20, 0xe9b6c7aa);
+ OP(FG, A, B, C, D, 5, 5, 0xd62f105d);
+ OP(FG, D, A, B, C, 10, 9, 0x02441453);
+ OP(FG, C, D, A, B, 15, 14, 0xd8a1e681);
+ OP(FG, B, C, D, A, 4, 20, 0xe7d3fbc8);
+ OP(FG, A, B, C, D, 9, 5, 0x21e1cde6);
+ OP(FG, D, A, B, C, 14, 9, 0xc33707d6);
+ OP(FG, C, D, A, B, 3, 14, 0xf4d50d87);
+ OP(FG, B, C, D, A, 8, 20, 0x455a14ed);
+ OP(FG, A, B, C, D, 13, 5, 0xa9e3e905);
+ OP(FG, D, A, B, C, 2, 9, 0xfcefa3f8);
+ OP(FG, C, D, A, B, 7, 14, 0x676f02d9);
+ OP(FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
+#endif
+
+ /* Round 3. */
+#if MD5SUM_SIZE_VS_SPEED == 1
+ for ( i=0 ; i < 4 ; i++ ) {
+ OP(FH, A, B, C, D, (int)(*pp++), 4, *pc++);
+ OP(FH, D, A, B, C, (int)(*pp++), 11, *pc++);
+ OP(FH, C, D, A, B, (int)(*pp++), 16, *pc++);
+ OP(FH, B, C, D, A, (int)(*pp++), 23, *pc++);
+ }
+#else
+ OP(FH, A, B, C, D, 5, 4, 0xfffa3942);
+ OP(FH, D, A, B, C, 8, 11, 0x8771f681);
+ OP(FH, C, D, A, B, 11, 16, 0x6d9d6122);
+ OP(FH, B, C, D, A, 14, 23, 0xfde5380c);
+ OP(FH, A, B, C, D, 1, 4, 0xa4beea44);
+ OP(FH, D, A, B, C, 4, 11, 0x4bdecfa9);
+ OP(FH, C, D, A, B, 7, 16, 0xf6bb4b60);
+ OP(FH, B, C, D, A, 10, 23, 0xbebfbc70);
+ OP(FH, A, B, C, D, 13, 4, 0x289b7ec6);
+ OP(FH, D, A, B, C, 0, 11, 0xeaa127fa);
+ OP(FH, C, D, A, B, 3, 16, 0xd4ef3085);
+ OP(FH, B, C, D, A, 6, 23, 0x04881d05);
+ OP(FH, A, B, C, D, 9, 4, 0xd9d4d039);
+ OP(FH, D, A, B, C, 12, 11, 0xe6db99e5);
+ OP(FH, C, D, A, B, 15, 16, 0x1fa27cf8);
+ OP(FH, B, C, D, A, 2, 23, 0xc4ac5665);
+#endif
+
+ /* Round 4. */
+#if MD5SUM_SIZE_VS_SPEED == 1
+ for ( i=0 ; i < 4 ; i++ ) {
+ OP(FI, A, B, C, D, (int)(*pp++), 6, *pc++);
+ OP(FI, D, A, B, C, (int)(*pp++), 10, *pc++);
+ OP(FI, C, D, A, B, (int)(*pp++), 15, *pc++);
+ OP(FI, B, C, D, A, (int)(*pp++), 21, *pc++);
+ }
+#else
+ OP(FI, A, B, C, D, 0, 6, 0xf4292244);
+ OP(FI, D, A, B, C, 7, 10, 0x432aff97);
+ OP(FI, C, D, A, B, 14, 15, 0xab9423a7);
+ OP(FI, B, C, D, A, 5, 21, 0xfc93a039);
+ OP(FI, A, B, C, D, 12, 6, 0x655b59c3);
+ OP(FI, D, A, B, C, 3, 10, 0x8f0ccc92);
+ OP(FI, C, D, A, B, 10, 15, 0xffeff47d);
+ OP(FI, B, C, D, A, 1, 21, 0x85845dd1);
+ OP(FI, A, B, C, D, 8, 6, 0x6fa87e4f);
+ OP(FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
+ OP(FI, C, D, A, B, 6, 15, 0xa3014314);
+ OP(FI, B, C, D, A, 13, 21, 0x4e0811a1);
+ OP(FI, A, B, C, D, 4, 6, 0xf7537e82);
+ OP(FI, D, A, B, C, 11, 10, 0xbd3af235);
+ OP(FI, C, D, A, B, 2, 15, 0x2ad7d2bb);
+ OP(FI, B, C, D, A, 9, 21, 0xeb86d391);
+#endif
+#endif
+
+ /* Add the starting values of the context. */
+ A += A_save;
+ B += B_save;
+ C += C_save;
+ D += D_save;
+ }
+
+ /* Put checksum in context given as argument. */
+ ctx->A = A;
+ ctx->B = B;
+ ctx->C = C;
+ ctx->D = D;
+}
+
+/* Initialize structure containing state of computation.
+ (RFC 1321, 3.3: Step 3) */
+void md5_init(struct md5_ctx *ctx)
+{
+ ctx->A = 0x67452301;
+ ctx->B = 0xefcdab89;
+ ctx->C = 0x98badcfe;
+ ctx->D = 0x10325476;
+
+ ctx->total[0] = ctx->total[1] = 0;
+ ctx->buflen = 0;
+}
+
+void md5_process(struct md5_ctx *ctx, const void *buffer, size_t len)
+{
+ /* When we already have some bits in our internal buffer concatenate
+ both inputs first. */
+ if (ctx->buflen != 0) {
+ size_t left_over = ctx->buflen;
+ size_t add = 128 - left_over > len ? len : 128 - left_over;
+
+ memcpy(&ctx->buffer[left_over], buffer, add);
+ ctx->buflen += add;
+
+ if (left_over + add > 64) {
+ md5_process_block(ctx, ctx->buffer, (left_over + add) & ~63);
+ /* The regions in the following copy operation cannot overlap. */
+ memcpy(ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
+ (left_over + add) & 63);
+ ctx->buflen = (left_over + add) & 63;
+ }
+
+ buffer = (const char *) buffer + add;
+ len -= add;
+ }
+
+ /* Process available complete blocks. */
+ if (len > 64) {
+ md5_process_block(ctx, buffer, len & ~63);
+ buffer = (const char *) buffer + (len & ~63);
+ len &= 63;
+ }
+
+ /* Move remaining bytes in internal buffer. */
+ if (len > 0) {
+ memcpy(ctx->buffer, buffer, len);
+ ctx->buflen = len;
+ }
+}
+
+/* Process the remaining bytes in the internal buffer and the usual
+ prolog according to the standard and write the result to RESBUF.
+
+ IMPORTANT: On some systems it is required that RESBUF is correctly
+ aligned for a 32 bits value. */
+void md5_finish(struct md5_ctx *ctx, md5sum_t resbuf)
+{
+ /* Take yet unprocessed bytes into account. */
+ md5_uint32 bytes = ctx->buflen;
+ size_t pad;
+
+ /* Now count remaining bytes. */
+ ctx->total[0] += bytes;
+ if (ctx->total[0] < bytes)
+ ++ctx->total[1];
+
+ pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
+#if MD5SUM_SIZE_VS_SPEED > 0
+ memset(&ctx->buffer[bytes], 0, pad);
+ ctx->buffer[bytes] = 0x80;
+#else
+ memcpy(&ctx->buffer[bytes], fillbuf, pad);
+#endif
+
+ /* Put the 64-bit file length in *bits* at the end of the buffer. */
+ *(md5_uint32 *) & ctx->buffer[bytes + pad] = SWAP(ctx->total[0] << 3);
+ *(md5_uint32 *) & ctx->buffer[bytes + pad + 4] =
+ SWAP( ((ctx->total[1] << 3) | (ctx->total[0] >> 29)) );
+
+ /* Process last bytes. */
+ md5_process_block(ctx, ctx->buffer, bytes + pad + 8);
+
+ /* Put result from CTX in first 16 bytes following RESBUF. The result is
+ always in little endian byte order, so that a byte-wise output yields
+ to the wanted ASCII representation of the message digest.
+
+ IMPORTANT: On some systems it is required that RESBUF is correctly
+ aligned for a 32 bits value. */
+ ((md5_uint32 *) resbuf)[0] = SWAP(ctx->A);
+ ((md5_uint32 *) resbuf)[1] = SWAP(ctx->B);
+ ((md5_uint32 *) resbuf)[2] = SWAP(ctx->C);
+ ((md5_uint32 *) resbuf)[3] = SWAP(ctx->D);
+}
+
diff --git a/src/md5.h b/src/md5.h
new file mode 100644
index 0000000..6996c4d
--- /dev/null
+++ b/src/md5.h
@@ -0,0 +1,60 @@
+/* md5.h - Compute MD5 checksum of files or strings according to the
+ * definition of MD5 in RFC 1321 from April 1992.
+ * Copyright (C) 1995-1999 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MD5_H
+#define MD5_H
+
+#include <sys/types.h>
+
+typedef unsigned char md5sum_t[16];
+typedef u_int32_t md5_uint32;
+
+struct md5_ctx
+{
+ md5_uint32 A;
+ md5_uint32 B;
+ md5_uint32 C;
+ md5_uint32 D;
+
+ md5_uint32 total[2];
+ md5_uint32 buflen;
+ char buffer[128];
+};
+
+/* Initialize structure containing state of computation.
+ (RFC 1321, 3.3: Step 3) */
+void md5_init(struct md5_ctx *ctx);
+
+/* Starting with the result of former calls of this function (or the
+ initialization function update the context for the next LEN bytes
+ starting at BUFFER.
+ It is NOT required that LEN is a multiple of 64. */
+void md5_process(struct md5_ctx *ctx, const void *buffer, size_t len);
+
+/* Process the remaining bytes in the buffer and put result from CTX
+ in first 16 bytes following RESBUF. The result is always in little
+ endian byte order, so that a byte-wise output yields to the wanted
+ ASCII representation of the message digest.
+
+ IMPORTANT: On some systems it is required that RESBUF is correctly
+ aligned for a 32 bits value. */
+void md5_finish(struct md5_ctx *ctx, md5sum_t resbuf);
+
+#endif
+
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);
+}
diff --git a/src/state.c b/src/state.c
new file mode 100644
index 0000000..119bebc
--- /dev/null
+++ b/src/state.c
@@ -0,0 +1,267 @@
+/* state.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 <stdio.h>
+#include <malloc.h>
+
+#include "apk_state.h"
+#include "apk_database.h"
+
+static int apk_state_commit_deps(struct apk_state *state,
+ struct apk_database *db,
+ struct apk_dependency_array *deps);
+
+struct apk_state *apk_state_new(struct apk_database *db)
+{
+ struct apk_state *state;
+ int num_bytes;
+
+ num_bytes = sizeof(struct apk_state) + (db->pkg_id * 2 + 7) / 8;
+ state = (struct apk_state*) calloc(1, num_bytes);
+ state->refs = 1;
+
+ return state;
+}
+
+struct apk_state *apk_state_dup(struct apk_state *state)
+{
+ state->refs++;
+ return state;
+}
+
+void apk_state_unref(struct apk_state *state)
+{
+ if (--state->refs > 0)
+ return;
+
+ free(state);
+}
+
+static void apk_state_set(struct apk_state *state, int pos, int val)
+{
+ int byte = pos / 4, offs = pos % 4;
+
+ state->bitarray[byte] &= ~(0x3 << (offs * 2));
+ state->bitarray[byte] |= (val & 0x3) << (offs * 2);
+}
+
+static int apk_state_get(struct apk_state *state, int pos)
+{
+ int byte = pos / 4, offs = pos % 4;
+
+ if (state == NULL)
+ return APK_STATE_NOT_CONSIDERED;
+
+ return (state->bitarray[byte] >> (offs * 2)) & 0x3;
+}
+
+static int apk_state_commit_pkg(struct apk_state *state,
+ struct apk_database *db,
+ struct apk_name *name,
+ struct apk_package *oldpkg,
+ struct apk_package *newpkg)
+{
+ const char *msg = NULL;
+ int r, upgrade = 0;
+
+ if (newpkg != NULL) {
+ r = apk_state_commit_deps(state, db, newpkg->depends);
+ if (r != 0)
+ return r;
+ }
+
+ if (oldpkg == NULL) {
+ apk_message("Installing %s (%s)",
+ name->name, newpkg->version);
+ } else if (newpkg == NULL) {
+ apk_message("Purging %s (%s)",
+ name->name, oldpkg->version);
+ } else {
+ r = apk_version_compare(APK_BLOB_STR(newpkg->version),
+ APK_BLOB_STR(oldpkg->version));
+ switch (r) {
+ case APK_VERSION_LESS:
+ msg = "Downgrading";
+ upgrade = 1;
+ break;
+ case APK_VERSION_EQUAL:
+ msg = "Re-installing";
+ break;
+ case APK_VERSION_GREATER:
+ msg = "Upgrading";
+ upgrade = 1;
+ break;
+ }
+ apk_message("%s %s (%s -> %s)",
+ msg, name->name, oldpkg->version, newpkg->version);
+ }
+
+ return apk_db_install_pkg(db, oldpkg, newpkg);
+}
+
+static int apk_state_commit_name(struct apk_state *state,
+ struct apk_database *db,
+ struct apk_name *name)
+{
+ struct apk_package *oldpkg = NULL, *newpkg = NULL;
+ int i;
+
+ for (i = 0; i < name->pkgs->num; i++) {
+ if (apk_pkg_get_state(name->pkgs->item[i]) == APK_STATE_INSTALL)
+ oldpkg = name->pkgs->item[i];
+ if (apk_state_get(state, name->pkgs->item[i]->id) == APK_STATE_INSTALL)
+ newpkg = name->pkgs->item[i];
+ }
+
+ if (oldpkg == NULL && newpkg == NULL)
+ return 0;
+
+ /* No reinstallations for now */
+ if (newpkg == oldpkg)
+ return 0;
+
+ return apk_state_commit_pkg(state, db, name, oldpkg, newpkg);
+}
+
+static int apk_state_commit_deps(struct apk_state *state,
+ struct apk_database *db,
+ struct apk_dependency_array *deps)
+{
+ int r, i;
+
+ if (deps == NULL)
+ return 0;
+
+ for (i = 0; i < deps->num; i++) {
+ r = apk_state_commit_name(state, db, deps->item[i].name);
+ if (r != 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int apk_state_commit(struct apk_state *state,
+ struct apk_database *db)
+{
+ struct apk_package *pkg;
+ struct hlist_node *c, *n;
+ int r;
+
+ /* Check all dependencies */
+ r = apk_state_commit_deps(state, db, db->world);
+ if (r != 0)
+ return r;
+
+ /* And purge all installed packages that were not considered */
+ hlist_for_each_entry_safe(pkg, c, n, &db->installed.packages, installed_pkgs_list)
+ apk_state_commit_name(state, db, pkg->name);
+
+ return 0;
+}
+
+int apk_state_satisfy_name(struct apk_state *state,
+ struct apk_name *name)
+{
+ struct apk_package *preferred = NULL;
+ int i;
+ int upgrading = 1;
+
+ /* Is something already installed? Or figure out the preferred
+ * package. */
+ for (i = 0; i < name->pkgs->num; i++) {
+ if (apk_state_get(state, name->pkgs->item[i]->id) ==
+ APK_STATE_INSTALL)
+ return 0;
+
+ if (preferred == NULL) {
+ preferred = name->pkgs->item[i];
+ continue;
+ }
+
+ if (upgrading) {
+ if (apk_version_compare(APK_BLOB_STR(name->pkgs->item[i]->version),
+ APK_BLOB_STR(preferred->version)) ==
+ APK_VERSION_GREATER) {
+ preferred = name->pkgs->item[i];
+ continue;
+ }
+ } else {
+ if (apk_pkg_get_state(name->pkgs->item[i]) ==
+ APK_STATE_INSTALL) {
+ preferred = name->pkgs->item[i];
+ continue;
+ }
+ }
+ }
+
+ /* Mark conflicting names as no install */
+ for (i = 0; i < name->pkgs->num; i++) {
+ if (name->pkgs->item[i] != preferred)
+ apk_state_set(state, name->pkgs->item[i]->id,
+ APK_STATE_NO_INSTALL);
+ }
+
+ return apk_state_pkg_install(state, preferred);
+}
+
+int apk_state_satisfy_deps(struct apk_state *state,
+ struct apk_dependency_array *deps)
+{
+ struct apk_name *name;
+ int r, i;
+
+ if (deps == NULL)
+ return 0;
+
+ for (i = 0; i < deps->num; i++) {
+ name = deps->item[i].name;
+ if (name->pkgs == NULL) {
+ apk_error("No providers for '%s'", name->name);
+ return -1;
+ }
+ r = apk_state_satisfy_name(state, name);
+ if (r != 0)
+ return r;
+ }
+ return 0;
+}
+
+void apk_state_pkg_set(struct apk_state *state,
+ struct apk_package *pkg)
+{
+ apk_state_set(state, pkg->id, APK_STATE_INSTALL);
+}
+
+int apk_state_pkg_install(struct apk_state *state,
+ struct apk_package *pkg)
+{
+ switch (apk_state_get(state, pkg->id)) {
+ case APK_STATE_INSTALL:
+ return 0;
+ case APK_STATE_NO_INSTALL:
+ return -1;
+ }
+
+ apk_state_set(state, pkg->id, APK_STATE_INSTALL);
+
+ if (pkg->depends == NULL)
+ return 0;
+
+ return apk_state_satisfy_deps(state, pkg->depends);
+}
+
+int apk_state_pkg_is_installed(struct apk_state *state,
+ struct apk_package *pkg)
+{
+ return apk_state_get(state, pkg->id);
+}
+
diff --git a/src/template.c b/src/template.c
new file mode 100644
index 0000000..8578444
--- /dev/null
+++ b/src/template.c
@@ -0,0 +1,15 @@
+/* template.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 "apk_database.h"
+
+
+
diff --git a/src/ver.c b/src/ver.c
new file mode 100644
index 0000000..c8b2e2b
--- /dev/null
+++ b/src/ver.c
@@ -0,0 +1,76 @@
+/* ver.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 <stdio.h>
+#include "apk_defines.h"
+#include "apk_applet.h"
+#include "apk_database.h"
+#include "apk_version.h"
+
+static int res2char(int res)
+{
+ switch (res) {
+ case APK_VERSION_LESS:
+ return '<';
+ case APK_VERSION_GREATER:
+ return '>';
+ case APK_VERSION_EQUAL:
+ return '=';
+ default:
+ return '?';
+ }
+}
+
+static int ver_main(int argc, char **argv)
+{
+ struct apk_database db;
+ struct apk_name *name;
+ struct apk_package *pkg, *upg, *tmp;
+ struct hlist_node *c;
+ int i, r;
+
+ if (argc == 2) {
+ r = apk_version_compare(APK_BLOB_STR(argv[0]),
+ APK_BLOB_STR(argv[1]));
+ printf("%c\n", res2char(r));
+ return 0;
+ }
+
+ apk_db_init(&db, "/home/fabled/tmproot/");
+ apk_db_read_config(&db);
+
+ hlist_for_each_entry(pkg, c, &db.installed.packages, installed_pkgs_list) {
+ name = pkg->name;
+ upg = pkg;
+ for (i = 0; i < name->pkgs->num; i++) {
+ tmp = name->pkgs->item[i];
+ if (tmp->name != name)
+ continue;
+ r = apk_version_compare(APK_BLOB_STR(tmp->version),
+ APK_BLOB_STR(upg->version));
+ if (r == APK_VERSION_GREATER)
+ upg = tmp;
+ }
+ printf("%-40s%c\n", name->name, pkg != upg ? '<' : '=');
+ }
+ apk_db_free(&db);
+
+ return 0;
+}
+
+static struct apk_applet apk_ver = {
+ .name = "version",
+ .usage = "[version1 version2]",
+ .main = ver_main,
+};
+
+APK_DEFINE_APPLET(apk_ver);
+
diff --git a/src/version.c b/src/version.c
new file mode 100644
index 0000000..13f3c1a
--- /dev/null
+++ b/src/version.c
@@ -0,0 +1,165 @@
+/* version.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 <stdio.h>
+
+#include <ctype.h>
+#include "apk_defines.h"
+#include "apk_version.h"
+
+/* Gentoo version: {digit}{.digit}...{letter}{_suf{#}}...{-r#} */
+
+enum PARTS {
+ TOKEN_INVALID = -1,
+ TOKEN_DIGIT_OR_ZERO,
+ TOKEN_DIGIT,
+ TOKEN_LETTER,
+ TOKEN_SUFFIX,
+ TOKEN_SUFFIX_NO,
+ TOKEN_REVISION_NO,
+ TOKEN_END,
+};
+
+static void next_token(int *type, apk_blob_t *blob)
+{
+ int n = TOKEN_INVALID;
+
+ if (blob->len == 0 || blob->ptr[0] == 0) {
+ n = TOKEN_END;
+ } else if (islower(blob->ptr[0])) {
+ n = TOKEN_LETTER;
+ } else if (*type == TOKEN_SUFFIX && isdigit(blob->ptr[0])) {
+ n = TOKEN_SUFFIX_NO;
+ } else {
+ switch (blob->ptr[0]) {
+ case '.':
+ n = TOKEN_DIGIT_OR_ZERO;
+ break;
+ case '_':
+ n = TOKEN_SUFFIX;
+ break;
+ case '-':
+ if (blob->len > 1 && blob->ptr[1] == 'r') {
+ n = TOKEN_REVISION_NO;
+ blob->ptr++;
+ blob->len--;
+ } else
+ n = TOKEN_INVALID;
+ break;
+ }
+ blob->ptr++;
+ blob->len--;
+ }
+
+ if (n < *type) {
+ if (! ((n == TOKEN_DIGIT_OR_ZERO && *type == TOKEN_DIGIT) ||
+ (n == TOKEN_SUFFIX && *type == TOKEN_SUFFIX_NO)))
+ n = TOKEN_INVALID;
+ }
+ *type = n;
+}
+
+static int get_token(int *type, apk_blob_t *blob)
+{
+ static const char *pre_suffixes[] = { "alpha", "beta", "pre", "rc" };
+ int v = 0, i = 0, nt = TOKEN_INVALID;
+
+ switch (*type) {
+ case TOKEN_DIGIT_OR_ZERO:
+ /* Leading zero digits get a special treatment */
+ if (blob->ptr[i] == '0') {
+ while (blob->ptr[i] == '0' && i < blob->len)
+ i++;
+ nt = TOKEN_DIGIT;
+ v = -i;
+ break;
+ }
+ case TOKEN_DIGIT:
+ case TOKEN_SUFFIX_NO:
+ case TOKEN_REVISION_NO:
+ while (isdigit(blob->ptr[i]) && i < blob->len) {
+ v *= 10;
+ v += blob->ptr[i++] - '0';
+ }
+ break;
+ case TOKEN_LETTER:
+ v = blob->ptr[i++];
+ break;
+ case TOKEN_SUFFIX:
+ for (v = 0; v < ARRAY_SIZE(pre_suffixes); v++) {
+ i = strlen(pre_suffixes[v]);
+ if (i < blob->len &&
+ strncmp(pre_suffixes[v], blob->ptr, i) == 0)
+ break;
+ }
+ if (v < ARRAY_SIZE(pre_suffixes)) {
+ nt = TOKEN_SUFFIX_NO;
+ v = v - ARRAY_SIZE(pre_suffixes);
+ break;
+ }
+ if (strncmp("p", blob->ptr, 1) == 0) {
+ nt = TOKEN_SUFFIX_NO;
+ v = 1;
+ break;
+ }
+ /* fallthrough: invalid suffix */
+ default:
+ *type = TOKEN_INVALID;
+ return -1;
+ }
+ blob->ptr += i;
+ blob->len -= i;
+ if (nt != TOKEN_INVALID)
+ *type = nt;
+ else
+ next_token(type, blob);
+
+ return v;
+}
+
+int apk_version_validate(apk_blob_t ver)
+{
+ int t = TOKEN_DIGIT;
+
+ while (t != TOKEN_END && t != TOKEN_INVALID)
+ get_token(&t, &ver);
+
+ return t == TOKEN_END;
+}
+
+int apk_version_compare(apk_blob_t a, apk_blob_t b)
+{
+ int at = TOKEN_DIGIT, bt = TOKEN_DIGIT;
+ int av = 0, bv = 0;
+
+ while (at == bt && at != TOKEN_END && av == bv) {
+ av = get_token(&at, &a);
+ bv = get_token(&bt, &b);
+#if 0
+ fprintf(stderr,
+ "av=%d, at=%d, a.len=%d\n"
+ "bv=%d, bt=%d, b.len=%d\n",
+ av, at, a.len, bv, bt, b.len);
+#endif
+ }
+
+ /* value of this token differs? */
+ if (av < bv)
+ return APK_VERSION_LESS;
+ if (av > bv)
+ return APK_VERSION_GREATER;
+ if (at < bt)
+ return get_token(&at, &a) < 0 ?
+ APK_VERSION_LESS : APK_VERSION_GREATER;
+ if (bt < at)
+ return get_token(&bt, &b) > 0 ?
+ APK_VERSION_LESS : APK_VERSION_GREATER;
+ return APK_VERSION_EQUAL;
+}