diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000000000000000000000000000000000000..644949357f3a597951008457b3d31472fce6a706
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+Natanael Copa <n@tanael.org>
+Timo Teräs <timo.teras@iki.fi>
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..563f0666423fd6abf224eb4c409cac779aadac6a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,52 @@
+# 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.
+
+VERSION := 2.0-pre0
+
+SVN_REV := $(shell svn info 2> /dev/null | grep ^Revision | cut -d ' ' -f 2)
+ifneq ($(SVN_REV),)
+FULL_VERSION := $(VERSION)-r$(SVN_REV)
+else
+FULL_VERSION := $(VERSION)
+endif
+
+CC=gcc
+INSTALL=install
+INSTALLDIR=$(INSTALL) -d
+
+CFLAGS=-O2 -g -D_GNU_SOURCE -Werror -Wall -Wstrict-prototypes -std=gnu99 \
+	-DAPK_VERSION=\"$(FULL_VERSION)\"
+LDFLAGS=-g -lpthread
+
+DESTDIR=
+SBINDIR=/usr/sbin
+CONFDIR=/etc/apk
+MANDIR=/usr/share/man
+DOCDIR=/usr/share/doc/apk
+
+SUBDIRS=src
+
+.PHONY: compile install clean all
+
+all: compile
+
+compile install clean::
+	@for i in $(SUBDIRS); do $(MAKE) $(MFLAGS) -C $$i $(MAKECMDGOALS); done
+
+install::
+	$(INSTALLDIR) $(DESTDIR)$(DOCDIR)
+	$(INSTALL) README $(DESTDIR)$(DOCDIR)
+
+dist:
+	svn-clean
+	(TOP=`pwd` && cd .. && ln -s $$TOP apk-tools-$(VERSION) && \
+	 tar --exclude '*/.svn*' -cjvf apk-tools-$(VERSION).tar.bz2 apk-tools-$(VERSION)/* && \
+	 rm apk-tools-$(VERSION))
+
+.EXPORT_ALL_VARIABLES:
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/README b/README
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/TODO b/TODO
new file mode 100644
index 0000000000000000000000000000000000000000..5c67bdb7b1a1d59becae7981874be08434fcdb48
--- /dev/null
+++ b/TODO
@@ -0,0 +1,42 @@
+- Command line parsing
+- Get repositories/root from command line
+- Repository index/package fetching from URLs
+- Installation of local files
+
+- Implement lbu stuff
+
+- Error handling and rollback
+- Dependency manipulation API: deletion, overwrite, check compatibility
+
+- File ownership chowning
+- New user/group creation
+
+- Non-trivial solution finder
+	- Versioned dependencies
+	- Conflicts
+	- Provides
+
+- Order removal of packages to honour dependencies
+	- Create reverse dependencies for installed pkgs
+
+- Remember counts for hash table creation
+
+- Possibly create a token hash for package names, versions and licenses, etc.
+- Calculate changeset installed-size change
+- Compress databases
+- Option to not read fs entry cache
+- Essentials(?)
+
+- Oldies:
+  add, delete: read (pkgs+fs), modify DEPs, recalc+commit+write (pkgs+fs)
+  fetch: read (pkgs), download remote packages
+  fetch -u: read (pkgs), download indexes, write (pkgs)
+  glob: read (pkgs), operate on package db
+  info: read (pkgs+fs), mostly on package db, might need .apks
+  version: read (pkgs), compare all installed pkg versions
+
+- New:
+  deps: show master dependencies
+  index: new TARGET, scan packages, write INDEX (pkgs)
+  upgrade: read TARGET, mark upgrade flags, recalculate, commit (pkgs+fs)
+
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0964012ab9080617b1bf01e70fa7c6d58fc3dae2
--- /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 0000000000000000000000000000000000000000..28dcb9b1ea0d5ab5a52a98fba169c7ca2868fc31
--- /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 0000000000000000000000000000000000000000..f8f13dd3af5abb7d1616533dcdae9293b1e758df
--- /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 0000000000000000000000000000000000000000..d243d658c10e692886c1c9f78014e30066f6cd21
--- /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 0000000000000000000000000000000000000000..2e8d27410d38624c30a908b17d83a6176a921470
--- /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 0000000000000000000000000000000000000000..033d3312a419c9da153f15fd4cefc393163af821
--- /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 0000000000000000000000000000000000000000..96ff36151d976b93cca009101c6fcc883229697c
--- /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 0000000000000000000000000000000000000000..ecd08e30d0b117b609ceb35080c98413c620ec01
--- /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 0000000000000000000000000000000000000000..f2f4cedaebb42002422d1072b578bc7aaeecf321
--- /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 0000000000000000000000000000000000000000..cdcce49b38ee04e4ad680ee6bd87a78132785d91
--- /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 0000000000000000000000000000000000000000..0e954751fae0ef8d2af88e456e0cdae038172c89
--- /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 0000000000000000000000000000000000000000..e0b5a3709f9e76974e2bb86fd5e56a336c5f8c62
--- /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 0000000000000000000000000000000000000000..e5092dc6b4b5a3ef967c99a5b270d5ca553bd568
--- /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 0000000000000000000000000000000000000000..eecf64233435626fb84663bd0637a16b007be130
--- /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 0000000000000000000000000000000000000000..f98be64f4ae4f21acbf94c2590a705f9f7090b9f
--- /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 0000000000000000000000000000000000000000..c4d7539342c115dc88a09fb7dd8e0b273f7caf5b
--- /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 0000000000000000000000000000000000000000..447601e6f61e942de2dfffe26a1580e580a1a466
--- /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 0000000000000000000000000000000000000000..4234002af6cfc9a3b22de7628091bfe4f7e48d02
--- /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 0000000000000000000000000000000000000000..e165724783c1f793a5a276581a50c1799980b117
--- /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 0000000000000000000000000000000000000000..6996c4dc9d7371a193584638938d1468e24ee4bd
--- /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 0000000000000000000000000000000000000000..88aef70ef913f379c517c09071f54b8454b3ece3
--- /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 0000000000000000000000000000000000000000..119bebc8974fa09eee5a12b4932cc1433f6042e8
--- /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 0000000000000000000000000000000000000000..8578444f7b00fa138b111589ba5fbfdd1daeb522
--- /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 0000000000000000000000000000000000000000..c8b2e2baac000fe745f677c68e0bc9c7757af413
--- /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 0000000000000000000000000000000000000000..13f3c1a3b2959b50b975249738b15062b12127b4
--- /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;
+}