diff --git a/src/adb.c b/src/adb.c
index b33799c147ac7930bddc7a8fe18777eee56d887a..03f50f3e9c5185573ae70f31a56eb1f8338426b9 100644
--- a/src/adb.c
+++ b/src/adb.c
@@ -13,13 +13,16 @@
 #include "apk_blob.h"
 #include "apk_trust.h"
 
+static char padding_zeroes[ADB_BLOCK_ALIGNMENT] = {0};
+
 /* Block enumeration */
 static inline struct adb_block *adb_block_validate(struct adb_block *blk, apk_blob_t b)
 {
 	size_t pos = (char *)blk - b.ptr;
 	if (pos == b.len) return NULL;
-	pos += sizeof(struct adb_block);
-	if (pos > b.len || ADB_BLOCK_SIZE(blk) > b.len - pos) return ERR_PTR(-EBADMSG);
+	if (sizeof(struct adb_block) > b.len - pos) return ERR_PTR(-EBADMSG);
+	if (adb_block_rawsize(blk) < sizeof(struct adb_block)) return ERR_PTR(-EBADMSG);
+	if (adb_block_size(blk) > b.len - pos) return ERR_PTR(-EBADMSG);
 	return blk;
 }
 
@@ -30,7 +33,7 @@ struct adb_block *adb_block_first(apk_blob_t b)
 
 struct adb_block *adb_block_next(struct adb_block *cur, apk_blob_t b)
 {
-	return adb_block_validate((struct adb_block*)((char*)cur + sizeof(struct adb_block) + ADB_BLOCK_SIZE(cur)), b);
+	return adb_block_validate((struct adb_block*)((char*)cur + adb_block_size(cur)), b);
 }
 
 /* Init stuff */
@@ -71,8 +74,8 @@ static int __adb_m_parse(struct adb *db, struct apk_trust *t)
 	int trusted = t ? 0 : 1;
 
 	adb_foreach_block(blk, db->data) {
-		apk_blob_t b = APK_BLOB_PTR_LEN((char*)(blk+1), ADB_BLOCK_SIZE(blk));
-		switch (ADB_BLOCK_TYPE(blk)) {
+		apk_blob_t b = adb_block_blob(blk);
+		switch (adb_block_type(blk)) {
 		case ADB_BLOCK_ADB:
 			if (!APK_BLOB_IS_NULL(db->adb)) break;
 			db->adb = b;
@@ -575,13 +578,12 @@ copy:
 adb_val_t adb_w_adb(struct adb *db, struct adb *valdb)
 {
 	uint32_t bsz;
-	struct adb_block blk = {
-		.type_size = htole32((ADB_BLOCK_ADB << 30) + valdb->adb.len)
-	};
+	struct adb_block blk = adb_block_init(ADB_BLOCK_ADB, valdb->adb.len);
 	struct iovec vec[] = {
 		{ .iov_base = &bsz, .iov_len = sizeof bsz },
 		{ .iov_base = &blk, .iov_len = sizeof blk },
 		{ .iov_base = valdb->adb.ptr, .iov_len = valdb->adb.len },
+		{ .iov_base = padding_zeroes, .iov_len = adb_block_padding(&blk) },
 	};
 	if (valdb->adb.len <= 4) return ADB_NULL;
 	bsz = htole32(iovec_len(vec, ARRAY_SIZE(vec)) - sizeof bsz);
@@ -817,9 +819,8 @@ int adb_c_header(struct apk_ostream *os, struct adb *db)
 
 int adb_c_block(struct apk_ostream *os, uint32_t type, apk_blob_t val)
 {
-	struct adb_block blk = {
-		.type_size = htole32((type << 30) + val.len)
-	};
+	struct adb_block blk = adb_block_init(type, val.len);
+	size_t padding = adb_block_padding(&blk);
 	int r;
 
 	r = apk_ostream_write(os, &blk, sizeof blk);
@@ -827,6 +828,39 @@ int adb_c_block(struct apk_ostream *os, uint32_t type, apk_blob_t val)
 
 	r = apk_ostream_write(os, val.ptr, val.len);
 	if (r < 0) return r;
+
+	if (padding) {
+		r = apk_ostream_write(os, padding_zeroes, padding);
+		if (r < 0) return r;
+	}
+
+	return 0;
+}
+
+int adb_c_block_data(struct apk_ostream *os, apk_blob_t hdr, uint32_t size, struct apk_istream *is)
+{
+	struct adb_block blk = adb_block_init(ADB_BLOCK_DATA, size + hdr.len);
+	size_t padding = adb_block_padding(&blk);
+	int r;
+
+	if (IS_ERR(os)) return PTR_ERR(os);
+	if (IS_ERR(is)) return apk_ostream_cancel(os, PTR_ERR(is));
+
+	r = apk_ostream_write(os, &blk, sizeof blk);
+	if (r < 0) return r;
+
+	r = apk_ostream_write(os, hdr.ptr, hdr.len);
+	if (r < 0) return r;
+
+	r = apk_stream_copy(is, os, size, 0, 0, 0);
+	if (r < 0) return r;
+
+	if (padding) {
+		r = apk_ostream_write(os, padding_zeroes, padding);
+		if (r < 0) return r;
+	}
+
+	apk_istream_close(is);
 	return 0;
 }
 
@@ -842,7 +876,7 @@ int adb_c_block_copy(struct apk_ostream *os, struct adb_block *b, struct apk_ist
 		mdctx = EVP_MD_CTX_new();
 		EVP_DigestInit_ex(mdctx, EVP_sha512(), 0);
 	}
-	r = apk_stream_copy(is, os, ADB_BLOCK_SIZE(b), 0, 0, mdctx);
+	r = apk_stream_copy(is, os, adb_block_size(b), 0, 0, mdctx);
 	if (vfy) {
 		EVP_DigestFinal_ex(mdctx, vfy->sha512, 0);
 		EVP_MD_CTX_free(mdctx);
@@ -851,17 +885,23 @@ int adb_c_block_copy(struct apk_ostream *os, struct adb_block *b, struct apk_ist
 	return r;
 }
 
-int adb_c_create(struct apk_ostream *os, struct adb *db, struct apk_trust *t)
+int adb_c_adb(struct apk_ostream *os, struct adb *db, struct apk_trust *t)
 {
-	if (IS_ERR(os)) return PTR_ERR(os);
-	if (db->hdr.magic != htole32(ADB_FORMAT_MAGIC)) {
-		apk_ostream_cancel(os, -EAPKFORMAT);
-		goto ret;
-	}
+	if (IS_ERR(os))
+		return apk_ostream_cancel(os, PTR_ERR(os));
+	if (db->hdr.magic != htole32(ADB_FORMAT_MAGIC))
+		return apk_ostream_cancel(os, -EAPKFORMAT);
+
 	adb_c_header(os, db);
 	adb_c_block(os, ADB_BLOCK_ADB, db->adb);
 	if (t) adb_trust_write_signatures(t, db, NULL, os);
-ret:
+
+	return apk_ostream_error(os);
+}
+
+int adb_c_create(struct apk_ostream *os, struct adb *db, struct apk_trust *t)
+{
+	adb_c_adb(os, db, t);
 	return apk_ostream_close(os);
 }
 
@@ -922,8 +962,7 @@ int adb_trust_write_signatures(struct apk_trust *trust, struct adb *db, struct a
 		EVP_MD_CTX_set_pkey_ctx(trust->mdctx, NULL);
 		if (EVP_DigestSignInit(trust->mdctx, NULL, EVP_sha512(), NULL, tkey->key.key) != 1 ||
 		    EVP_DigestUpdate(trust->mdctx, &db->hdr, sizeof db->hdr) != 1 ||
-		    EVP_DigestUpdate(trust->mdctx, &sig.hdr.sign_ver, sizeof sig.hdr.sign_ver) != 1 ||
-		    EVP_DigestUpdate(trust->mdctx, &sig.hdr.hash_alg, sizeof sig.hdr.hash_alg) != 1 ||
+		    EVP_DigestUpdate(trust->mdctx, &sig.v0, sizeof sig.v0) != 1 ||
 		    EVP_DigestUpdate(trust->mdctx, md.ptr, md.len) != 1 ||
 		    EVP_DigestSignFinal(trust->mdctx, sig.v0.sig, &siglen) != 1) {
 			ERR_print_errors_fp(stdout);
@@ -962,8 +1001,7 @@ int adb_trust_verify_signature(struct apk_trust *trust, struct adb *db, struct a
 		EVP_MD_CTX_set_pkey_ctx(trust->mdctx, NULL);
 		if (EVP_DigestVerifyInit(trust->mdctx, NULL, EVP_sha512(), NULL, tkey->key.key) != 1 ||
 		    EVP_DigestUpdate(trust->mdctx, &db->hdr, sizeof db->hdr) != 1 ||
-		    EVP_DigestUpdate(trust->mdctx, &sig->sign_ver, sizeof sig->sign_ver) != 1 ||
-		    EVP_DigestUpdate(trust->mdctx, &sig->hash_alg, sizeof sig->hash_alg) != 1 ||
+		    EVP_DigestUpdate(trust->mdctx, sig0, sizeof *sig0) != 1 ||
 		    EVP_DigestUpdate(trust->mdctx, md.ptr, md.len) != 1 ||
 		    EVP_DigestVerifyFinal(trust->mdctx, sig0->sig, sigb.len - sizeof(*sig0)) != 1) {
 			ERR_clear_error();
@@ -998,16 +1036,16 @@ int adb_c_xfrm(struct adb_xfrm *x, int (*cb)(struct adb_xfrm *, struct adb_block
 			goto err;
 		}
 
-		if ((block_no++ == 0) != (ADB_BLOCK_TYPE(&blk) == ADB_BLOCK_ADB)) goto bad_msg;
+		if ((block_no++ == 0) != (adb_block_type(&blk) == ADB_BLOCK_ADB)) goto bad_msg;
 
-		sz = ADB_BLOCK_SIZE(&blk);
+		sz = adb_block_size(&blk) - sizeof blk;
 		r = cb(x, &blk, apk_istream_segment(&seg, x->is, sz, 0));
 		if (r < 0) goto err;
 
 		if (r == 0 && seg.bytes_left == sz) {
 			r = apk_ostream_write(x->os, &blk, sizeof blk);
 			if (r < 0) goto err;
-			r = apk_stream_copy(x->is, x->os, seg.bytes_left, 0, 0, 0);
+			r = apk_stream_copy(x->is, x->os, sz, 0, 0, 0);
 			if (r < 0) goto err;
 		} else if (seg.bytes_left > 0) {
 			r = apk_istream_read(x->is, NULL, sz - seg.bytes_left);
diff --git a/src/adb.h b/src/adb.h
index a6bd121ad09f7016565fe246c71f242ffa3de696..5f0d7bd0edf5387165a5a8503e18063d07d5eb4c 100644
--- a/src/adb.h
+++ b/src/adb.h
@@ -51,17 +51,29 @@ struct adb_header {
 };
 
 /* Blocks */
+#define ADB_BLOCK_ALIGNMENT	8
 #define ADB_BLOCK_END		-1
 #define ADB_BLOCK_ADB		0
 #define ADB_BLOCK_SIG		2
-
-#define ADB_BLOCK_TYPE(b)	(le32toh((b)->type_size) >> 30)
-#define ADB_BLOCK_SIZE(b)	(le32toh((b)->type_size) & 0x3fffffff)
+#define ADB_BLOCK_DATA		3
 
 struct adb_block {
 	uint32_t type_size;
 };
 
+static inline struct adb_block adb_block_init(uint32_t type, uint32_t length) {
+	return (struct adb_block) { .type_size = htole32((type << 30) + sizeof(struct adb_block) + length)};
+}
+static inline uint32_t adb_block_type(struct adb_block *b) { return le32toh((b)->type_size) >> 30; }
+static inline uint32_t adb_block_rawsize(struct adb_block *b) { return le32toh((b)->type_size) & 0x3fffffff; }
+static inline uint32_t adb_block_size(struct adb_block *b) { return ROUND_UP(adb_block_rawsize(b), ADB_BLOCK_ALIGNMENT); }
+static inline uint32_t adb_block_length(struct adb_block *b) { return adb_block_rawsize(b) - sizeof(struct adb_block); }
+static inline uint32_t adb_block_padding(struct adb_block *b) { return adb_block_size(b) - adb_block_rawsize(b); }
+static inline void *adb_block_payload(struct adb_block *b) { return b + 1; }
+static inline apk_blob_t adb_block_blob(struct adb_block *b) {
+	return APK_BLOB_PTR_LEN(adb_block_payload(b), adb_block_length(b));
+}
+
 struct adb_sign_hdr {
 	uint8_t sign_ver, hash_alg;
 };
@@ -81,7 +93,6 @@ struct adb_sign_v0 {
 /* Block enumeration */
 struct adb_block *adb_block_first(apk_blob_t b);
 struct adb_block *adb_block_next(struct adb_block *cur, apk_blob_t b);
-
 #define adb_foreach_block(__blk, __adb) \
 	for (__blk = adb_block_first(__adb); !IS_ERR_OR_NULL(__blk); __blk = adb_block_next(__blk, __adb))
 
@@ -218,7 +229,9 @@ int adb_s_field_by_name(const struct adb_object_schema *, const char *);
 /* Creation */
 int adb_c_header(struct apk_ostream *os, struct adb *db);
 int adb_c_block(struct apk_ostream *os, uint32_t type, apk_blob_t);
+int adb_c_block_data(struct apk_ostream *os, apk_blob_t hdr, uint32_t size, struct apk_istream *is);
 int adb_c_block_copy(struct apk_ostream *os, struct adb_block *b, struct apk_istream *is, struct adb_verify_ctx *);
+int adb_c_adb(struct apk_ostream *os, struct adb *db, struct apk_trust *t);
 int adb_c_create(struct apk_ostream *os, struct adb *db, struct apk_trust *t);
 
 /* Trust */
@@ -279,6 +292,7 @@ struct adb_walk_gentext {
 struct adb_walk_genadb {
 	struct adb_walk d;
 	struct adb db;
+	adb_val_t stored_object;
 	struct adb idb[ADB_WALK_GENADB_MAX_IDB];
 	int nest, nestdb, num_vals;
 	struct adb_obj objs[ADB_WALK_GENADB_MAX_NESTING];
diff --git a/src/adb_walk_adb.c b/src/adb_walk_adb.c
index 4f99d85bd3d9a467fc3851725f39395f8793ef5f..b9f051c6eb3c69e81f322c9164d0bc423148d72d 100644
--- a/src/adb_walk_adb.c
+++ b/src/adb_walk_adb.c
@@ -107,11 +107,10 @@ static int dump_object(struct adb_walk_ctx *ctx, const struct adb_object_schema
 
 static int dump_adb(struct adb_walk_ctx *ctx)
 {
-	char tmp[256];
+	char tmp[512];
 	struct adb_block *blk;
 	struct adb_sign_hdr *s;
 	struct adb_verify_ctx vfy = {};
-	unsigned char *id;
 	uint32_t schema_magic = ctx->db->hdr.schema;
 	const struct adb_db_schema *ds;
 	struct adb_walk *d = ctx->d;
@@ -121,10 +120,10 @@ static int dump_adb(struct adb_walk_ctx *ctx)
 		if (ds->magic == schema_magic) break;
 
 	adb_foreach_block(blk, ctx->db->data) {
-		apk_blob_t b = APK_BLOB_PTR_LEN((char*)(blk+1), ADB_BLOCK_SIZE(blk));
-		switch (ADB_BLOCK_TYPE(blk)) {
+		apk_blob_t b = adb_block_blob(blk);
+		switch (adb_block_type(blk)) {
 		case ADB_BLOCK_ADB:
-			len = snprintf(tmp, sizeof tmp, "ADB block, size: %u", ADB_BLOCK_SIZE(blk));
+			len = snprintf(tmp, sizeof tmp, "ADB block, size: %u", adb_block_length(blk));
 			d->ops->comment(d, APK_BLOB_PTR_LEN(tmp, len));
 			if (ds->root) {
 				ctx->db->adb = b;
@@ -134,23 +133,19 @@ static int dump_adb(struct adb_walk_ctx *ctx)
 		case ADB_BLOCK_SIG:
 			s = (struct adb_sign_hdr*) b.ptr;
 			r = adb_trust_verify_signature(ctx->trust, ctx->db, &vfy, b);
-
-			len = snprintf(tmp, sizeof tmp, "signature: v%d ", s->sign_ver);
-			switch (s->sign_ver) {
-			case 0:
-				id = (unsigned char*)(s + 1);
-				for (size_t j = 0; j < 16; j++)
-					len += snprintf(&tmp[len], sizeof tmp - len, "%02x", id[j]);
-				break;
-			default:
-				break;
-			}
+			len = snprintf(tmp, sizeof tmp, "sig v%02x h%02x ", s->sign_ver, s->hash_alg);
+			for (size_t j = sizeof *s; j < b.len; j++)
+				len += snprintf(&tmp[len], sizeof tmp - len, "%02x", (uint8_t)b.ptr[j]);
 			len += snprintf(&tmp[len], sizeof tmp - len, ": %s", r ? apk_error_str(r) : "OK");
 			d->ops->comment(d, APK_BLOB_PTR_LEN(tmp, len));
 			break;
+		case ADB_BLOCK_DATA:
+			len = snprintf(tmp, sizeof tmp, "data block, size: %d", adb_block_length(blk));
+			d->ops->comment(d, APK_BLOB_PTR_LEN(tmp, len));
+			break;
 		default:
 			len = snprintf(tmp, sizeof tmp, "unknown block %d, size: %d",
-				ADB_BLOCK_TYPE(blk), ADB_BLOCK_SIZE(blk));
+				adb_block_type(blk), adb_block_length(blk));
 			d->ops->comment(d, APK_BLOB_PTR_LEN(tmp, len));
 		}
 	}
diff --git a/src/adb_walk_genadb.c b/src/adb_walk_genadb.c
index 06a3f9451933da273522322df14e1221b5a84e9e..4852eb60104b25d1b834afded6a30df4b76abaa2 100644
--- a/src/adb_walk_genadb.c
+++ b/src/adb_walk_genadb.c
@@ -74,7 +74,7 @@ static int adb_walk_genadb_end(struct adb_walk *d)
 	dt->num_vals -= dt->objs[dt->nest].schema->num_fields;
 
 	if (dt->nest == 0) {
-		adb_w_root(&dt->db, val);
+		dt->stored_object = val;
 		return 0;
 	}
 
diff --git a/src/apk_adb.c b/src/apk_adb.c
index 07854556142bfc4b00b465e01243c328c08d9330..9be0e5770fa17acd2393170063d5c836e5c9f846 100644
--- a/src/apk_adb.c
+++ b/src/apk_adb.c
@@ -422,17 +422,16 @@ const struct adb_object_schema schema_index = {
 	},
 };
 
-static uint32_t file_get_default_int(unsigned i)
-{
-	switch (i) {
-	case ADBI_FI_UID:
-	case ADBI_FI_GID:
-		return 0;
-	case ADBI_FI_MODE:
-		return 0644;
-	}
-	return -1;
-}
+const struct adb_object_schema schema_acl = {
+	.kind = ADB_KIND_OBJECT,
+	.num_fields = ADBI_ACL_MAX,
+	.fields = {
+		ADB_FIELD(ADBI_ACL_MODE,	"mode",		scalar_oct),
+		ADB_FIELD(ADBI_ACL_USER,	"user",		scalar_string),
+		ADB_FIELD(ADBI_ACL_GROUP,	"group",	scalar_string),
+		//ADB_FIELD(ADBI_ACL_XATTRS,	"xattr",	schema_string_array),
+	},
+};
 
 static int file_cmp(const struct adb_obj *o1, const struct adb_obj *o2)
 {
@@ -442,15 +441,14 @@ static int file_cmp(const struct adb_obj *o1, const struct adb_obj *o2)
 const struct adb_object_schema schema_file = {
 	.kind = ADB_KIND_OBJECT,
 	.num_fields = ADBI_FI_MAX,
-	.get_default_int = file_get_default_int,
 	.compare = file_cmp,
 	.fields = {
 		ADB_FIELD(ADBI_FI_NAME,		"name",		scalar_string),
+		ADB_FIELD(ADBI_FI_ACL,		"acl",		schema_acl),
+		ADB_FIELD(ADBI_FI_SIZE,		"size",		scalar_int),
+		ADB_FIELD(ADBI_FI_MTIME,	"mtime",	scalar_int),
 		ADB_FIELD(ADBI_FI_HASHES,	"hash",		scalar_hexblob),
-		ADB_FIELD(ADBI_FI_UID,		"uid",		scalar_int),
-		ADB_FIELD(ADBI_FI_GID,		"gid",		scalar_int),
-		ADB_FIELD(ADBI_FI_MODE,		"mode",		scalar_oct),
-		ADB_FIELD(ADBI_FI_XATTRS,	"xattr",	scalar_hexblob),
+		ADB_FIELD(ADBI_FI_TARGET,	"target",	scalar_string),
 	},
 };
 
@@ -461,38 +459,22 @@ const struct adb_object_schema schema_file_array = {
 	.fields = ADB_ARRAY_ITEM(schema_file),
 };
 
-static uint32_t path_get_default_int(unsigned i)
-{
-	switch (i) {
-	case ADBI_FI_UID:
-	case ADBI_FI_GID:
-		return 0;
-	case ADBI_FI_MODE:
-		return 0755;
-	}
-	return -1;
-}
-
-const struct adb_object_schema schema_path = {
+const struct adb_object_schema schema_dir = {
 	.kind = ADB_KIND_OBJECT,
-	.num_fields = ADBI_FI_MAX,
-	.get_default_int = path_get_default_int,
+	.num_fields = ADBI_DI_MAX,
 	.compare = file_cmp,
 	.fields = {
-		ADB_FIELD(ADBI_FI_NAME,		"name",		scalar_string),
-		ADB_FIELD(ADBI_FI_FILES,	"files",	schema_file_array),
-		ADB_FIELD(ADBI_FI_UID,		"uid",		scalar_int),
-		ADB_FIELD(ADBI_FI_GID,		"gid",		scalar_int),
-		ADB_FIELD(ADBI_FI_MODE,		"mode",		scalar_oct),
-		ADB_FIELD(ADBI_FI_XATTRS,	"xattr",	scalar_hexblob),
+		ADB_FIELD(ADBI_DI_NAME,		"name",		scalar_string),
+		ADB_FIELD(ADBI_DI_ACL,		"acl",		schema_acl),
+		ADB_FIELD(ADBI_DI_FILES,	"files",	schema_file_array),
 	},
 };
 
-const struct adb_object_schema schema_path_array = {
+const struct adb_object_schema schema_dir_array = {
 	.kind = ADB_KIND_ARRAY,
 	.pre_commit = adb_wa_sort,
 	.num_fields = APK_MAX_MANIFEST_PATHS,
-	.fields = ADB_ARRAY_ITEM(schema_path),
+	.fields = ADB_ARRAY_ITEM(schema_dir),
 };
 
 const struct adb_object_schema schema_scripts = {
@@ -520,7 +502,7 @@ const struct adb_object_schema schema_package = {
 	.compare = package_cmp,
 	.fields = {
 		ADB_FIELD(ADBI_PKG_PKGINFO,	"info",		schema_pkginfo),
-		ADB_FIELD(ADBI_PKG_PATHS,	"paths",	schema_path_array),
+		ADB_FIELD(ADBI_PKG_PATHS,	"paths",	schema_dir_array),
 		ADB_FIELD(ADBI_PKG_SCRIPTS,	"scripts",	schema_scripts),
 		ADB_FIELD(ADBI_PKG_TRIGGERS,	"triggers",	schema_string_array),
 		//ADB_FIELD(ADBI_PKG_PASSWD,	"passwd",	schema_string_array),
diff --git a/src/apk_adb.h b/src/apk_adb.h
index 557bc6eff526d6198b2b2350e71c00de436b8fee..1908cba6d6bba771e047be0cc49da56b15826815 100644
--- a/src/apk_adb.h
+++ b/src/apk_adb.h
@@ -33,16 +33,28 @@
 #define ADBI_PI_RECOMMENDS	0x13
 #define ADBI_PI_MAX		0x14
 
+/* ACL entries */
+#define ADBI_ACL_MODE		0x01
+#define ADBI_ACL_USER		0x02
+#define ADBI_ACL_GROUP		0x03
+#define ADBI_ACL_XATTRS		0x04
+#define ADBI_ACL_MAX		0x05
+
 /* File Info */
 #define ADBI_FI_NAME		0x01
-#define ADBI_FI_HASHES		0x02
-#define ADBI_FI_FILES		0x02
-#define ADBI_FI_MODE		0x03
-#define ADBI_FI_UID		0x04
-#define ADBI_FI_GID		0x05
-#define ADBI_FI_XATTRS		0x06
+#define ADBI_FI_ACL		0x02
+#define ADBI_FI_SIZE		0x03
+#define ADBI_FI_MTIME		0x04
+#define ADBI_FI_HASHES		0x05
+#define ADBI_FI_TARGET		0x06
 #define ADBI_FI_MAX		0x07
 
+/* Directory Info */
+#define ADBI_DI_NAME		0x01
+#define ADBI_DI_ACL		0x02
+#define ADBI_DI_FILES		0x03
+#define ADBI_DI_MAX		0x04
+
 /* Scripts */
 #define ADBI_SCRPT_TRIGGER	0x01
 #define ADBI_SCRPT_PREINST	0x02
@@ -81,7 +93,7 @@
 extern const struct adb_object_schema
 	schema_dependency, schema_dependency_array,
 	schema_pkginfo, schema_pkginfo_array,
-	schema_file, schema_file_array, schema_path, schema_path_array,
+	schema_acl, schema_file, schema_file_array, schema_dir, schema_dir_array,
 	schema_string_array, schema_scripts, schema_package, schema_package_adb_array,
 	schema_index, schema_idb;
 
diff --git a/src/apk_defines.h b/src/apk_defines.h
index 92e97d8a93c4b8b8622dcc8a353c2dbb441b72c8..321827791c53c702bf3383f664f2c986ab0b3f5f 100644
--- a/src/apk_defines.h
+++ b/src/apk_defines.h
@@ -191,14 +191,14 @@ APK_ARRAY(apk_string_array, char *);
 #define LIST_POISON1 (void *) 0xdeadbeef
 #define LIST_POISON2 (void *) 0xabbaabba
 
-struct hlist_head {
-	struct hlist_node *first;
-};
-
 struct hlist_node {
 	struct hlist_node *next;
 };
 
+struct hlist_head {
+	struct hlist_node *first;
+};
+
 static inline int hlist_empty(const struct hlist_head *h)
 {
 	return !h->first;
diff --git a/src/apk_io.h b/src/apk_io.h
index 8a243ac0f95fd5e18479b69fa8f990dbc9dd5a6a..030255e68cbe998a09e9e542c3703a010ea0d4be 100644
--- a/src/apk_io.h
+++ b/src/apk_io.h
@@ -15,14 +15,17 @@
 
 #include "apk_defines.h"
 #include "apk_blob.h"
-#include "apk_hash.h"
 #include "apk_atom.h"
 
+struct apk_id_hash {
+	int empty;
+	struct hlist_head by_id[16], by_name[16];
+};
+
 struct apk_id_cache {
 	int root_fd;
-	unsigned int genid;
-	struct apk_hash uid_cache;
-	struct apk_hash gid_cache;
+	struct apk_id_hash uid_cache;
+	struct apk_id_hash gid_cache;
 };
 
 struct apk_xattr {
@@ -148,6 +151,7 @@ struct apk_ostream *apk_ostream_to_fd(int fd);
 struct apk_ostream *apk_ostream_to_file(int atfd, const char *file, mode_t mode);
 struct apk_ostream *apk_ostream_to_file_gz(int atfd, const char *file, const char *tmpfile, mode_t mode);
 size_t apk_ostream_write_string(struct apk_ostream *ostream, const char *string);
+static inline int apk_ostream_error(struct apk_ostream *os) { return os->rc; }
 static inline int apk_ostream_cancel(struct apk_ostream *os, int rc) { if (!os->rc) os->rc = rc; return rc; }
 static inline ssize_t apk_ostream_write(struct apk_ostream *os, const void *buf, size_t size)
 {
@@ -181,7 +185,9 @@ const char *apk_url_local_file(const char *url);
 void apk_id_cache_init(struct apk_id_cache *idc, int root_fd);
 void apk_id_cache_free(struct apk_id_cache *idc);
 void apk_id_cache_reset(struct apk_id_cache *idc);
-uid_t apk_resolve_uid(struct apk_id_cache *idc, apk_blob_t username, uid_t default_uid);
-uid_t apk_resolve_gid(struct apk_id_cache *idc, apk_blob_t groupname, uid_t default_gid);
+uid_t apk_id_cache_resolve_uid(struct apk_id_cache *idc, apk_blob_t username, uid_t default_uid);
+gid_t apk_id_cache_resolve_gid(struct apk_id_cache *idc, apk_blob_t groupname, gid_t default_gid);
+apk_blob_t apk_id_cache_resolve_user(struct apk_id_cache *idc, uid_t uid);
+apk_blob_t apk_id_cache_resolve_group(struct apk_id_cache *idc, gid_t gid);
 
 #endif
diff --git a/src/apk_pathbuilder.h b/src/apk_pathbuilder.h
new file mode 100644
index 0000000000000000000000000000000000000000..34181dbe760dd631fe4e48c2fd3a726070c55ed7
--- /dev/null
+++ b/src/apk_pathbuilder.h
@@ -0,0 +1,44 @@
+/* apk_pathbuilder.h - Alpine Package Keeper (APK)
+ *
+ * Copyright (C) 2021 Timo Teräs <timo.teras@iki.fi>
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ */
+
+#ifndef APK_PATHBUILDER_H
+#define APK_PATHBUILDER_H
+
+#include "apk_blob.h"
+
+struct apk_pathbuilder {
+	uint16_t namelen;
+	char name[PATH_MAX];
+};
+
+int apk_pathbuilder_pushb(struct apk_pathbuilder *pb, apk_blob_t b);
+void apk_pathbuilder_pop(struct apk_pathbuilder *pb);
+
+
+static inline int apk_pathbuilder_setb(struct apk_pathbuilder *pb, apk_blob_t b)
+{
+	pb->namelen = 0;
+	return apk_pathbuilder_pushb(pb, b);
+}
+
+static inline int apk_pathbuilder_push(struct apk_pathbuilder *pb, const char *name)
+{
+	return apk_pathbuilder_pushb(pb, APK_BLOB_STR(name));
+}
+
+static inline const char *apk_pathbuilder_cstr(const struct apk_pathbuilder *pb)
+{
+	return pb->name;
+}
+
+static inline apk_blob_t apk_pathbuilder_get(const struct apk_pathbuilder *pb)
+{
+	return APK_BLOB_PTR_LEN((void*)pb->name, pb->namelen);
+}
+
+#endif
diff --git a/src/app_adbdump.c b/src/app_adbdump.c
index 958c90f2abb447fd6116ea709e51f4dda8380c45..5c1b69ce12530044aa9e829c10f3aa7524a716f9 100644
--- a/src/app_adbdump.c
+++ b/src/app_adbdump.c
@@ -68,8 +68,10 @@ static int adbgen_main(void *pctx, struct apk_ctx *ac, struct apk_string_array *
 	adb_w_init_alloca(&genadb.idb[0], 0, 100);
 	foreach_array_item(arg, args) {
 		adb_reset(&genadb.db);
+		adb_reset(&genadb.idb[0]);
 		r = adb_walk_istream(&genadb.d, apk_istream_from_file(AT_FDCWD, *arg));
 		if (!r) {
+			adb_w_root(&genadb.db, genadb.stored_object);
 			r = adb_c_create(apk_ostream_to_fd(STDOUT_FILENO), &genadb.db,
 				apk_ctx_get_trust(ac));
 		}
diff --git a/src/app_adbsign.c b/src/app_adbsign.c
index cdfd6e9aa51d11e85d648cd6a2a51231c04be9f5..bb489c057dcf970784128f72bb2f722a950ad014 100644
--- a/src/app_adbsign.c
+++ b/src/app_adbsign.c
@@ -44,7 +44,7 @@ static int update_signatures(struct adb_xfrm *xfrm, struct adb_block *blk, struc
 	struct apk_trust *trust = apk_ctx_get_trust(ctx->ac);
 	int r;
 
-	switch (blk ? ADB_BLOCK_TYPE(blk) : -1) {
+	switch (blk ? adb_block_type(blk) : -1) {
 	case ADB_BLOCK_ADB:
 		return adb_c_block_copy(xfrm->os, blk, is, &xfrm->vfy);
 	case ADB_BLOCK_SIG:
diff --git a/src/app_convdb.c b/src/app_convdb.c
index 4871f672d7c268c2a07c3a9da744fd84f4dd8d11..dfb426b8a2b0fce5e678cd0169c51fcfe2f81516 100644
--- a/src/app_convdb.c
+++ b/src/app_convdb.c
@@ -18,6 +18,7 @@ struct conv_script {
 };
 
 struct conv_ctx {
+	struct apk_ctx *ac;
 	struct apk_atom_pool atoms;
 	struct adb_obj pkgs;
 
@@ -100,8 +101,9 @@ static int read_triggers(struct conv_ctx *ctx, struct apk_istream *is)
 
 static void convert_idb(struct conv_ctx *ctx, struct apk_istream *is)
 {
+	struct apk_id_cache *idc = apk_ctx_get_id_cache(ctx->ac);
 	struct apk_checksum csum;
-	struct adb_obj pkg, pkginfo, files, file, paths, path, scripts, triggers;
+	struct adb_obj pkg, pkginfo, files, file, paths, path, scripts, triggers, acl;
 	apk_blob_t l, val, spc = APK_BLOB_STR(" "), nl = APK_BLOB_STR("\n");
 	struct conv_script *s;
 	int i;
@@ -111,14 +113,15 @@ static void convert_idb(struct conv_ctx *ctx, struct apk_istream *is)
 	adb_wo_alloca(&pkginfo, &schema_pkginfo, &ctx->dbp);
 	adb_wo_alloca(&files, &schema_file_array, &ctx->dbp);
 	adb_wo_alloca(&file, &schema_file, &ctx->dbp);
-	adb_wo_alloca(&paths, &schema_path_array, &ctx->dbp);
-	adb_wo_alloca(&path, &schema_path, &ctx->dbp);
+	adb_wo_alloca(&paths, &schema_dir_array, &ctx->dbp);
+	adb_wo_alloca(&path, &schema_dir, &ctx->dbp);
 	adb_wo_alloca(&pkg, &schema_package, &ctx->dbp);
+	adb_wo_alloca(&acl, &schema_acl, &ctx->dbp);
 
 	while (!APK_BLOB_IS_NULL(l = apk_istream_get_delim(is, nl))) {
 		if (l.len < 2) {
 			adb_wa_append_obj(&files, &file);
-			adb_wo_obj(&path, ADBI_FI_FILES, &files);
+			adb_wo_obj(&path, ADBI_DI_FILES, &files);
 			adb_wa_append_obj(&paths, &path);
 
 			adb_wo_obj(&pkg, ADBI_PKG_PKGINFO, &pkginfo);
@@ -152,28 +155,30 @@ static void convert_idb(struct conv_ctx *ctx, struct apk_istream *is)
 			break;
 		case 'F': // directory name
 			adb_wa_append_obj(&files, &file);
-			adb_wo_obj(&path, ADBI_FI_FILES, &files);
+			adb_wo_obj(&path, ADBI_DI_FILES, &files);
 			adb_wa_append_obj(&paths, &path);
 
-			adb_wo_blob(&path, ADBI_FI_NAME, val);
+			adb_wo_blob(&path, ADBI_DI_NAME, val);
 			break;
 		case 'M': // directory mode: uid:gid:mode:xattrcsum
-			adb_wo_int(&path, ADBI_FI_UID, apk_blob_pull_uint(&val, 10));
+			adb_wo_blob(&acl, ADBI_ACL_USER, apk_id_cache_resolve_user(idc, apk_blob_pull_uint(&val, 10)));
 			apk_blob_pull_char(&val, ':');
-			adb_wo_int(&path, ADBI_FI_GID, apk_blob_pull_uint(&val, 10));
+			adb_wo_blob(&acl, ADBI_ACL_GROUP, apk_id_cache_resolve_group(idc, apk_blob_pull_uint(&val, 10)));
 			apk_blob_pull_char(&val, ':');
-			adb_wo_int(&path, ADBI_FI_MODE, apk_blob_pull_uint(&val, 8));
+			adb_wo_int(&acl, ADBI_ACL_MODE, apk_blob_pull_uint(&val, 8));
+			adb_wo_obj(&path, ADBI_DI_ACL, &acl);
 			break;
 		case 'R': // file name
 			adb_wa_append_obj(&files, &file);
 			adb_wo_blob(&file, ADBI_FI_NAME, val);
 			break;
 		case 'a': // file mode: uid:gid:mode:xattrcsum
-			adb_wo_int(&file, ADBI_FI_UID, apk_blob_pull_uint(&val, 10));
+			adb_wo_blob(&acl, ADBI_ACL_USER, apk_id_cache_resolve_user(idc, apk_blob_pull_uint(&val, 10)));
 			apk_blob_pull_char(&val, ':');
-			adb_wo_int(&file, ADBI_FI_GID, apk_blob_pull_uint(&val, 10));
+			adb_wo_blob(&acl, ADBI_ACL_GROUP, apk_id_cache_resolve_group(idc, apk_blob_pull_uint(&val, 10)));
 			apk_blob_pull_char(&val, ':');
-			adb_wo_int(&file, ADBI_FI_MODE, apk_blob_pull_uint(&val, 8));
+			adb_wo_int(&acl, ADBI_ACL_MODE, apk_blob_pull_uint(&val, 8));
+			adb_wo_obj(&file, ADBI_FI_ACL, &acl);
 			break;
 		case 'Z': // file content hash
 			apk_blob_pull_csum(&val, &csum);
@@ -196,6 +201,7 @@ static int conv_main(void *pctx, struct apk_ctx *ac, struct apk_string_array *ar
 	int r;
 	int root_fd = apk_ctx_fd_root(ac);
 
+	ctx->ac = ac;
 	list_init(&ctx->script_head);
 	apk_atom_init(&ctx->atoms);
 
diff --git a/src/app_mkpkg.c b/src/app_mkpkg.c
new file mode 100644
index 0000000000000000000000000000000000000000..872b4dab72104310397e20029fe9ec3908f44542
--- /dev/null
+++ b/src/app_mkpkg.c
@@ -0,0 +1,284 @@
+/* app_mkpkg.c - Alpine Package Keeper (APK)
+ *
+ * Copyright (C) 2008-2021 Timo Teräs <timo.teras@iki.fi>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation. See http://www.gnu.org/ for details.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "apk_defines.h"
+#include "apk_adb.h"
+#include "apk_applet.h"
+#include "apk_database.h"
+#include "apk_pathbuilder.h"
+#include "apk_print.h"
+
+#define BLOCK_SIZE 4096
+
+struct mkpkg_ctx {
+	struct apk_ctx *ac;
+	const char *files_dir, *output;
+	struct adb db;
+	struct adb_obj paths, *files;
+	struct apk_sign_ctx sctx;
+	apk_blob_t info[ADBI_PI_MAX];
+	uint64_t installed_size;
+	struct apk_pathbuilder pb;
+};
+
+#define MKPKG_OPTIONS(OPT) \
+	OPT(OPT_MKPKG_files,	APK_OPT_ARG APK_OPT_SH("f") "files") \
+	OPT(OPT_MKPKG_info,	APK_OPT_ARG APK_OPT_SH("i") "info") \
+	OPT(OPT_MKPKG_output,	APK_OPT_ARG APK_OPT_SH("o") "output") \
+
+APK_OPT_APPLET(option_desc, MKPKG_OPTIONS);
+
+static int option_parse_applet(void *ctx, struct apk_ctx *ac, int optch, const char *optarg)
+{
+	struct apk_out *out = &ac->out;
+	struct mkpkg_ctx *ictx = ctx;
+	apk_blob_t l, r;
+	int i;
+
+	switch (optch) {
+	case OPT_MKPKG_info:
+		apk_blob_split(APK_BLOB_STR(optarg), APK_BLOB_STRLIT(":"), &l, &r);
+		i = adb_s_field_by_name_blob(&schema_pkginfo, l);
+		if (!i || i == ADBI_PI_FILE_SIZE || i == ADBI_PI_INSTALLED_SIZE) {
+			apk_err(out, "invalid pkginfo field: " BLOB_FMT, BLOB_PRINTF(l));
+			return -EINVAL;
+		}
+		ictx->info[i] = r;
+		break;
+	case OPT_MKPKG_files:
+		ictx->files_dir = optarg;
+		break;
+	case OPT_MKPKG_output:
+		ictx->output = optarg;
+		break;
+	default:
+		return -ENOTSUP;
+	}
+	return 0;
+}
+
+static const struct apk_option_group optgroup_applet = {
+	.desc = option_desc,
+	.parse = option_parse_applet,
+};
+
+static int mkpkg_process_dirent(void *pctx, int dirfd, const char *entry);
+
+static int mkpkg_process_directory(struct mkpkg_ctx *ctx, int dirfd, struct apk_file_info *fi)
+{
+	struct apk_ctx *ac = ctx->ac;
+	struct apk_id_cache *idc = apk_ctx_get_id_cache(ac);
+	struct apk_out *out = &ac->out;
+	struct adb_obj acl, fio, files, *prev_files;
+	apk_blob_t dirname = apk_pathbuilder_get(&ctx->pb);
+	int r;
+
+	adb_wo_alloca(&fio, &schema_dir, &ctx->db);
+	adb_wo_alloca(&acl, &schema_acl, &ctx->db);
+	adb_wo_blob(&fio, ADBI_DI_NAME, dirname);
+	adb_wo_int(&acl, ADBI_ACL_MODE, fi->mode & ~S_IFMT);
+	adb_wo_blob(&acl, ADBI_ACL_USER, apk_id_cache_resolve_user(idc, fi->uid));
+	adb_wo_blob(&acl, ADBI_ACL_GROUP, apk_id_cache_resolve_group(idc, fi->gid));
+	adb_wo_obj(&fio, ADBI_DI_ACL, &acl);
+
+	adb_wo_alloca(&files, &schema_file_array, &ctx->db);
+	prev_files = ctx->files;
+	ctx->files = &files;
+	r = apk_dir_foreach_file(dirfd, mkpkg_process_dirent, ctx);
+	ctx->files = prev_files;
+	if (r) {
+		apk_err(out, "failed to process directory '%s': %d",
+			apk_pathbuilder_cstr(&ctx->pb), r);
+		return r;
+	}
+
+	adb_wo_obj(&fio, ADBI_DI_FILES, &files);
+	adb_wa_append_obj(&ctx->paths, &fio);
+	return 0;
+}
+
+static int mkpkg_process_dirent(void *pctx, int dirfd, const char *entry)
+{
+	struct mkpkg_ctx *ctx = pctx;
+	struct apk_ctx *ac = ctx->ac;
+	struct apk_out *out = &ac->out;
+	struct apk_id_cache *idc = apk_ctx_get_id_cache(ac);
+	struct apk_file_info fi;
+	struct adb_obj fio, acl;
+	int r;
+
+	r = apk_fileinfo_get(dirfd, entry, APK_FI_NOFOLLOW | APK_FI_CSUM(APK_CHECKSUM_SHA1), &fi, NULL);
+	if (r) return r;
+
+	switch (fi.mode & S_IFMT) {
+	case S_IFDIR:
+		apk_pathbuilder_push(&ctx->pb, entry);
+		r = mkpkg_process_directory(ctx, openat(dirfd, entry, O_RDONLY), &fi);
+		apk_pathbuilder_pop(&ctx->pb);
+		break;
+	case S_IFREG:
+		adb_wo_alloca(&fio, &schema_file, &ctx->db);
+		adb_wo_alloca(&acl, &schema_acl, &ctx->db);
+		adb_wo_blob(&fio, ADBI_FI_NAME, APK_BLOB_STR(entry));
+		adb_wo_blob(&fio, ADBI_FI_HASHES, APK_BLOB_PTR_LEN((char*) fi.csum.data, fi.csum.type));
+		adb_wo_int(&fio, ADBI_FI_MTIME, fi.mtime);
+		adb_wo_int(&fio, ADBI_FI_SIZE, fi.size);
+		ctx->installed_size += (fi.size + BLOCK_SIZE - 1) & ~(BLOCK_SIZE-1);
+
+		adb_wo_int(&acl, ADBI_ACL_MODE, fi.mode & 07777);
+		adb_wo_blob(&acl, ADBI_ACL_USER, apk_id_cache_resolve_user(idc, fi.uid));
+		adb_wo_blob(&acl, ADBI_ACL_GROUP, apk_id_cache_resolve_group(idc, fi.gid));
+		adb_wo_obj(&fio, ADBI_FI_ACL, &acl);
+
+		adb_wa_append_obj(ctx->files, &fio);
+		break;
+	default:
+		apk_pathbuilder_push(&ctx->pb, entry);
+		apk_err(out, "special file '%s' not supported",
+			apk_pathbuilder_cstr(&ctx->pb), entry);
+		apk_pathbuilder_pop(&ctx->pb);
+		r = -EINVAL;
+		break;
+	}
+	return r;
+}
+
+static char *pkgi_filename(struct adb_obj *pkgi, char *buf, size_t n)
+{
+	apk_blob_t to = APK_BLOB_PTR_LEN(buf, n);
+	apk_blob_push_blob(&to, adb_ro_blob(pkgi, ADBI_PI_NAME));
+	apk_blob_push_blob(&to, APK_BLOB_STR("-"));
+	apk_blob_push_blob(&to, adb_ro_blob(pkgi, ADBI_PI_VERSION));
+	apk_blob_push_blob(&to, APK_BLOB_STR(".apk"));
+	apk_blob_push_blob(&to, APK_BLOB_PTR_LEN("", 1));
+	if (APK_BLOB_IS_NULL(to)) return 0;
+	return buf;
+}
+
+static int mkpkg_main(void *pctx, struct apk_ctx *ac, struct apk_string_array *args)
+{
+	struct apk_out *out = &ac->out;
+	struct apk_trust *trust = apk_ctx_get_trust(ac);
+	struct adb_obj pkg, pkgi;
+	int i, j, r;
+	struct mkpkg_ctx *ctx = pctx;
+	struct apk_ostream *os;
+	char outbuf[PATH_MAX];
+
+	ctx->ac = ac;
+	adb_w_init_alloca(&ctx->db, ADB_SCHEMA_PACKAGE, 40);
+	adb_wo_alloca(&pkg, &schema_package, &ctx->db);
+	adb_wo_alloca(&pkgi, &schema_pkginfo, &ctx->db);
+	adb_wo_alloca(&ctx->paths, &schema_dir_array, &ctx->db);
+
+	// prepare package info
+	for (i = 0; i < ARRAY_SIZE(ctx->info); i++) {
+		apk_blob_t val = ctx->info[i];
+		if (APK_BLOB_IS_NULL(val)) {
+			switch (i) {
+			case ADBI_PI_NAME:
+			case ADBI_PI_VERSION:
+				r = -EINVAL;
+				apk_err(out, "required pkginfo field '%s' not provided",
+					schema_pkginfo.fields[i-1].name);
+				goto err;
+			}
+			continue;
+		}
+		adb_wo_val_fromstring(&pkgi, i, val);
+	}
+	if (adb_ro_val(&pkgi, ADBI_PI_ARCH) == ADB_VAL_NULL)
+		adb_wo_blob(&pkgi, ADBI_PI_ARCH, APK_BLOB_STRLIT(APK_DEFAULT_ARCH));
+
+	// scan and add all files
+	if (ctx->files_dir) {
+		struct apk_file_info fi;
+		r = apk_fileinfo_get(AT_FDCWD, ctx->files_dir, APK_FI_NOFOLLOW, &fi, 0);
+		if (r) {
+			apk_err(out, "file directory '%s': %s",
+				ctx->files_dir, apk_error_str(r));
+			goto err;
+		}
+		r = mkpkg_process_directory(ctx, openat(AT_FDCWD, ctx->files_dir, O_RDONLY), &fi);
+		if (r) goto err;
+		if (!ctx->installed_size) ctx->installed_size = BLOCK_SIZE;
+	}
+
+	adb_wo_int(&pkgi, ADBI_PI_INSTALLED_SIZE, ctx->installed_size);
+	adb_wo_obj(&pkg, ADBI_PKG_PKGINFO, &pkgi);
+	adb_wo_obj(&pkg, ADBI_PKG_PATHS, &ctx->paths);
+	adb_w_rootobj(&pkg);
+
+	// re-read since object resets
+	adb_r_rootobj(&ctx->db, &pkg, &schema_package);
+	adb_ro_obj(&pkg, ADBI_PKG_PKGINFO, &pkgi);
+	adb_ro_obj(&pkg, ADBI_PKG_PATHS, &ctx->paths);
+
+	if (!ctx->output) {
+		ctx->output = pkgi_filename(&pkgi, outbuf, sizeof outbuf);
+	}
+
+	// construct package with ADB as header, and the file data in
+	// concatenated data blocks
+	os = apk_ostream_gzip(apk_ostream_to_file(AT_FDCWD, ctx->output, 0644));
+	adb_c_adb(os, &ctx->db, trust);
+	int files_fd = openat(AT_FDCWD, ctx->files_dir, O_RDONLY);
+	for (i = ADBI_FIRST; i <= adb_ra_num(&ctx->paths); i++) {
+		struct adb_obj path, files, file;
+		adb_ro_obj(&ctx->paths, i, &path);
+		adb_ro_obj(&path, ADBI_DI_FILES, &files);
+		apk_blob_t dirname = adb_ro_blob(&path, ADBI_DI_NAME);
+
+		apk_pathbuilder_setb(&ctx->pb, dirname);
+		for (j = ADBI_FIRST; j <= adb_ra_num(&files); j++) {
+			adb_ro_obj(&files, j, &file);
+			apk_blob_t filename = adb_ro_blob(&file, ADBI_FI_NAME);
+			apk_blob_t target = adb_ro_blob(&file, ADBI_FI_TARGET);
+			size_t sz = adb_ro_int(&file, ADBI_FI_SIZE);
+			if (!APK_BLOB_IS_NULL(target)) continue;
+			if (!sz) continue;
+			struct {
+				uint32_t path_idx;
+				uint32_t file_idx;
+			} hdr = { i, j };
+
+			apk_pathbuilder_pushb(&ctx->pb, filename);
+			adb_c_block_data(
+				os, APK_BLOB_STRUCT(hdr), sz,
+				apk_istream_from_fd(openat(files_fd,
+					apk_pathbuilder_cstr(&ctx->pb),
+					O_RDONLY)));
+			apk_pathbuilder_pop(&ctx->pb);
+		}
+	}
+	close(files_fd);
+	r = apk_ostream_close(os);
+
+err:
+	adb_free(&ctx->db);
+	if (r) apk_err(out, "failed to create package: %s", apk_error_str(r));
+	return r;
+}
+
+static struct apk_applet apk_mkpkg = {
+	.name = "mkpkg",
+	.context_size = sizeof(struct mkpkg_ctx),
+	.optgroups = { &optgroup_global, &optgroup_signing, &optgroup_applet },
+	.main = mkpkg_main,
+};
+
+APK_DEFINE_APPLET(apk_mkpkg);
diff --git a/src/io.c b/src/io.c
index a2c23aefd45d22a0fc33a4c342560abf850769e3..93ca9d635b59ebd9f5f44461eb1fe70851d04593 100644
--- a/src/io.c
+++ b/src/io.c
@@ -1033,72 +1033,86 @@ size_t apk_ostream_write_string(struct apk_ostream *os, const char *string)
 }
 
 struct cache_item {
-	apk_hash_node hash_node;
-	unsigned int genid;
-	union {
-		uid_t uid;
-		gid_t gid;
-	};
+	struct hlist_node by_id, by_name;
+	unsigned long id;
 	unsigned short len;
 	char name[];
 };
 
-static apk_blob_t cache_item_get_key(apk_hash_item item)
+static void idhash_init(struct apk_id_hash *idh)
 {
-	struct cache_item *ci = (struct cache_item *) item;
-	return APK_BLOB_PTR_LEN(ci->name, ci->len);
+	memset(idh, 0, sizeof *idh);
+	idh->empty = 1;
 }
 
-static const struct apk_hash_ops id_hash_ops = {
-	.node_offset = offsetof(struct cache_item, hash_node),
-	.get_key = cache_item_get_key,
-	.hash_key = apk_blob_hash,
-	.compare = apk_blob_compare,
-	.delete_item = (apk_hash_delete_f) free,
-};
+static void idhash_reset(struct apk_id_hash *idh)
+{
+	struct hlist_node *iter, *next;
+	struct cache_item *ci;
+	int i;
 
-static struct cache_item *resolve_cache_item(struct apk_hash *hash, apk_blob_t name)
+	for (i = 0; i < ARRAY_SIZE(idh->by_id); i++)
+		hlist_for_each_entry_safe(ci, iter, next, &idh->by_id[i], by_id)
+			free(ci);
+	idhash_init(idh);
+}
+
+static void idcache_add(struct apk_id_hash *hash, apk_blob_t name, unsigned long id)
 {
 	struct cache_item *ci;
 	unsigned long h;
 
-	h = id_hash_ops.hash_key(name);
-	ci = (struct cache_item *) apk_hash_get_hashed(hash, name, h);
-	if (ci != NULL)
-		return ci;
-
 	ci = calloc(1, sizeof(struct cache_item) + name.len);
-	if (ci == NULL)
-		return NULL;
+	if (!ci) return;
 
+	ci->id = id;
 	ci->len = name.len;
 	memcpy(ci->name, name.ptr, name.len);
-	apk_hash_insert_hashed(hash, ci, h);
 
-	return ci;
+	h = apk_blob_hash(name);
+	hlist_add_head(&ci->by_id, &hash->by_id[id % ARRAY_SIZE(hash->by_id)]);
+	hlist_add_head(&ci->by_name, &hash->by_name[h % ARRAY_SIZE(hash->by_name)]);
+}
+
+static struct cache_item *idcache_by_name(struct apk_id_hash *hash, apk_blob_t name)
+{
+	struct cache_item *ci;
+	struct hlist_node *pos;
+	unsigned long h = apk_blob_hash(name);
+
+	hlist_for_each_entry(ci, pos, &hash->by_name[h % ARRAY_SIZE(hash->by_name)], by_name)
+		if (apk_blob_compare(name, APK_BLOB_PTR_LEN(ci->name, ci->len)) == 0)
+			return ci;
+	return 0;
+}
+
+static struct cache_item *idcache_by_id(struct apk_id_hash *hash, unsigned long id)
+{
+	struct cache_item *ci;
+	struct hlist_node *pos;
+
+	hlist_for_each_entry(ci, pos, &hash->by_id[id % ARRAY_SIZE(hash->by_name)], by_id)
+		if (ci->id == id) return ci;
+	return 0;
 }
 
 void apk_id_cache_init(struct apk_id_cache *idc, int root_fd)
 {
 	idc->root_fd = root_fd;
-	idc->genid = 1;
-	apk_hash_init(&idc->uid_cache, &id_hash_ops, 256);
-	apk_hash_init(&idc->gid_cache, &id_hash_ops, 256);
+	idhash_init(&idc->uid_cache);
+	idhash_init(&idc->gid_cache);
 }
 
-void apk_id_cache_free(struct apk_id_cache *idc)
+void apk_id_cache_reset(struct apk_id_cache *idc)
 {
-	if (!idc->root_fd) return;
-	apk_hash_free(&idc->uid_cache);
-	apk_hash_free(&idc->gid_cache);
-	idc->root_fd = 0;
+	idhash_reset(&idc->uid_cache);
+	idhash_reset(&idc->gid_cache);
 }
 
-void apk_id_cache_reset(struct apk_id_cache *idc)
+void apk_id_cache_free(struct apk_id_cache *idc)
 {
-	idc->genid++;
-	if (idc->genid == 0)
-		idc->genid = 1;
+	apk_id_cache_reset(idc);
+	idc->root_fd = 0;
 }
 
 static FILE *fopenat(int dirfd, const char *pathname)
@@ -1114,86 +1128,94 @@ static FILE *fopenat(int dirfd, const char *pathname)
 	return f;
 }
 
-uid_t apk_resolve_uid(struct apk_id_cache *idc, apk_blob_t username, uid_t default_uid)
+static void idcache_load_users(int root_fd, struct apk_id_hash *idh)
 {
 #ifdef HAVE_FGETPWENT_R
 	char buf[1024];
 	struct passwd pwent;
 #endif
-	struct cache_item *ci;
 	struct passwd *pwd;
 	FILE *in;
 
-	ci = resolve_cache_item(&idc->uid_cache, username);
-	if (ci == NULL) return default_uid;
+	if (!idh->empty) return;
+	idh->empty = 0;
 
-	if (ci->genid != idc->genid) {
-		ci->genid = idc->genid;
-		ci->uid = -1;
+	in = fopenat(root_fd, "etc/passwd");
+	if (!in) return;
 
-		in = fopenat(idc->root_fd, "etc/passwd");
-		if (in) {
-			do {
+	do {
 #ifdef HAVE_FGETPWENT_R
-				fgetpwent_r(in, &pwent, buf, sizeof(buf), &pwd);
+		fgetpwent_r(in, &pwent, buf, sizeof(buf), &pwd);
 #else
-				pwd = fgetpwent(in);
+		pwd = fgetpwent(in);
+#endif
+		if (!pwd) break;
+		idcache_add(idh, APK_BLOB_STR(pwd->pw_name), pwd->pw_uid);
+	} while (1);
+	fclose(in);
+#ifndef HAVE_FGETPWENT_R
+	endpwent();
 #endif
-				if (pwd == NULL)
-					break;
-				if (apk_blob_compare(APK_BLOB_STR(pwd->pw_name), username) == 0) {
-					ci->uid = pwd->pw_uid;
-					break;
-				}
-			} while (1);
-			fclose(in);
-		}
-	}
-
-	if (ci->uid != -1)
-		return ci->uid;
-
-	return default_uid;
 }
 
-uid_t apk_resolve_gid(struct apk_id_cache *idc, apk_blob_t groupname, uid_t default_gid)
+static void idcache_load_groups(int root_fd, struct apk_id_hash *idh)
 {
 #ifdef HAVE_FGETGRENT_R
 	char buf[1024];
 	struct group grent;
 #endif
-	struct cache_item *ci;
 	struct group *grp;
 	FILE *in;
 
-	ci = resolve_cache_item(&idc->gid_cache, groupname);
-	if (ci == NULL) return default_gid;
+	if (!idh->empty) return;
+	idh->empty = 0;
 
-	if (ci->genid != idc->genid) {
-		ci->genid = idc->genid;
-		ci->gid = -1;
+	in = fopenat(root_fd, "etc/group");
+	if (!in) return;
 
-		in = fopenat(idc->root_fd, "etc/group");
-		if (in) {
-			do {
+	do {
 #ifdef HAVE_FGETGRENT_R
-				fgetgrent_r(in, &grent, buf, sizeof(buf), &grp);
+		fgetgrent_r(in, &grent, buf, sizeof(buf), &grp);
 #else
-				grp = fgetgrent(in);
+		grp = fgetgrent(in);
 #endif
-				if (grp == NULL)
-					break;
-				if (apk_blob_compare(APK_BLOB_STR(grp->gr_name), groupname) == 0) {
-					ci->gid = grp->gr_gid;
-					break;
-				}
-			} while (1);
-			fclose(in);
-		}
-	}
+		if (!grp) break;
+		idcache_add(idh, APK_BLOB_STR(grp->gr_name), grp->gr_gid);
+	} while (1);
+	fclose(in);
+#ifndef HAVE_FGETGRENT_R
+	endgrent();
+#endif
+}
 
-	if (ci->gid != -1)
-		return ci->gid;
+uid_t apk_id_cache_resolve_uid(struct apk_id_cache *idc, apk_blob_t username, uid_t default_uid)
+{
+	struct cache_item *ci;
+	idcache_load_users(idc->root_fd, &idc->uid_cache);
+	ci = idcache_by_name(&idc->uid_cache, username);
+	return ci ? ci->id : default_uid;
+}
+
+gid_t apk_id_cache_resolve_gid(struct apk_id_cache *idc, apk_blob_t groupname, gid_t default_gid)
+{
+	struct cache_item *ci;
+	idcache_load_groups(idc->root_fd, &idc->gid_cache);
+	ci = idcache_by_name(&idc->gid_cache, groupname);
+	return ci ? ci->id : default_gid;
+}
 
-	return default_gid;
+apk_blob_t apk_id_cache_resolve_user(struct apk_id_cache *idc, uid_t uid)
+{
+	struct cache_item *ci;
+	idcache_load_users(idc->root_fd, &idc->uid_cache);
+	ci = idcache_by_id(&idc->uid_cache, uid);
+	return ci ? APK_BLOB_PTR_LEN(ci->name, ci->len) : APK_BLOB_STRLIT("nobody");
+}
+
+apk_blob_t apk_id_cache_resolve_group(struct apk_id_cache *idc, gid_t gid)
+{
+	struct cache_item *ci;
+	idcache_load_groups(idc->root_fd, &idc->gid_cache);
+	ci = idcache_by_id(&idc->gid_cache, gid);
+	return ci ? APK_BLOB_PTR_LEN(ci->name, ci->len) : APK_BLOB_STRLIT("nobody");
 }
diff --git a/src/io_archive.c b/src/io_archive.c
index 303fb93549cf43c66d8daeaff9bae6bc037d6dfd..ad4f62113dddeaabd828f2e59aaa28b1b41ca4dd 100644
--- a/src/io_archive.c
+++ b/src/io_archive.c
@@ -150,8 +150,8 @@ int apk_tar_parse(struct apk_istream *is, apk_archive_entry_parser parser,
 
 		entry = (struct apk_file_info){
 			.size  = GET_OCTAL(buf.size),
-			.uid   = apk_resolve_uid(idc, TAR_BLOB(buf.uname), GET_OCTAL(buf.uid)),
-			.gid   = apk_resolve_gid(idc, TAR_BLOB(buf.gname), GET_OCTAL(buf.gid)),
+			.uid   = apk_id_cache_resolve_uid(idc, TAR_BLOB(buf.uname), GET_OCTAL(buf.uid)),
+			.gid   = apk_id_cache_resolve_gid(idc, TAR_BLOB(buf.gname), GET_OCTAL(buf.gid)),
 			.mode  = GET_OCTAL(buf.mode) & 07777,
 			.mtime = GET_OCTAL(buf.mtime),
 			.name  = entry.name,
diff --git a/src/meson.build b/src/meson.build
index 6c5f6caa322db935d2694a5440d57c1eaab81e16..4a76d4c7c5095c1b9bcf3e9301043518ff3f5a23 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -18,6 +18,7 @@ libapk_src = [
 	'io_url.c',
 	'io_gunzip.c',
 	'package.c',
+	'pathbuilder.c',
 	'print.c',
 	'solver.c',
 	'trust.c',
@@ -35,6 +36,7 @@ libapk_headers = [
 	'apk_io.h',
 	'apk_openssl.h',
 	'apk_package.h',
+	'apk_pathbuilder.h',
 	'apk_print.h',
 	'apk_provider_data.h',
 	'apk_solver_data.h',
@@ -60,6 +62,7 @@ apk_src = [
 	'app_list.c',
 	'app_manifest.c',
 	'app_mkndx.c',
+	'app_mkpkg.c',
 	'app_policy.c',
 	'app_update.c',
 	'app_upgrade.c',
diff --git a/src/pathbuilder.c b/src/pathbuilder.c
new file mode 100644
index 0000000000000000000000000000000000000000..166aa225f025667309d5eab887acbec198ae3235
--- /dev/null
+++ b/src/pathbuilder.c
@@ -0,0 +1,29 @@
+/* apk_pathbuilder.h - Alpine Package Keeper (APK)
+ *
+ * Copyright (C) 2021 Timo Teräs <timo.teras@iki.fi>
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ */
+
+#include <errno.h>
+#include "apk_pathbuilder.h"
+
+int apk_pathbuilder_pushb(struct apk_pathbuilder *pb, apk_blob_t b)
+{
+	size_t i = pb->namelen;
+	if (i + b.len + 2 >= ARRAY_SIZE(pb->name)) return -ENAMETOOLONG;
+	if (i) pb->name[i++] = '/';
+	memcpy(&pb->name[i], b.ptr, b.len);
+	pb->namelen = i + b.len;
+	pb->name[pb->namelen] = 0;
+	return 0;
+}
+
+void apk_pathbuilder_pop(struct apk_pathbuilder *pb)
+{
+	char *slash = memrchr(pb->name, '/', pb->namelen);
+	if (slash) pb->namelen = slash - pb->name;
+	else pb->namelen = 0;
+	pb->name[pb->namelen] = 0;
+}