database.c 46.8 KB
Newer Older
1 2 3
/* database.c - Alpine Package Keeper (APK)
 *
 * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
4
 * Copyright (C) 2008-2009 Timo Teräs <timo.teras@iki.fi>
5 6
 * All rights reserved.
 *
7
 * This program is free software; you can redistribute it and/or modify it
8 9 10 11 12 13 14
 * 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>
15
#include <limits.h>
16 17 18
#include <unistd.h>
#include <malloc.h>
#include <string.h>
19
#include <stdlib.h>
20
#include <signal.h>
21
#include <sys/file.h>
22 23 24 25 26

#include "apk_defines.h"
#include "apk_package.h"
#include "apk_database.h"
#include "apk_state.h"
27
#include "apk_applet.h"
28
#include "apk_archive.h"
29

30
const char * const apkindex_tar_gz = "APKINDEX.tar.gz";
Timo Teräs's avatar
Timo Teräs committed
31
const char * const apk_index_gz = "APK_INDEX.gz";
Timo Teräs's avatar
Timo Teräs committed
32 33 34
static const char * const apk_static_cache_dir = "var/lib/apk";
static const char * const apk_linked_cache_dir = "etc/apk/cache";

35 36 37 38 39
struct install_ctx {
	struct apk_database *db;
	struct apk_package *pkg;

	int script;
40
	int script_pending : 1;
41
	struct apk_db_dir_instance *diri;
Timo Teräs's avatar
Timo Teräs committed
42
	struct apk_checksum data_csum;
43
	struct apk_sign_ctx sctx;
44
	struct apk_name_array *replaces;
45

46 47 48 49 50
	apk_progress_cb cb;
	void *cb_ctx;
	size_t installed_size;
	size_t current_file_size;

51 52
	struct hlist_node **diri_node;
	struct hlist_node **file_diri_node;
53 54
};

55
static apk_blob_t pkg_name_get_key(apk_hash_item item)
56
{
57
	return APK_BLOB_STR(((struct apk_name *) item)->name);
58 59
}

60 61 62 63 64 65 66
static void pkg_name_free(struct apk_name *name)
{
	free(name->name);
	free(name->pkgs);
	free(name);
}

67 68 69
static const struct apk_hash_ops pkg_name_hash_ops = {
	.node_offset = offsetof(struct apk_name, hash_node),
	.get_key = pkg_name_get_key,
70 71
	.hash_key = apk_blob_hash,
	.compare = apk_blob_compare,
72
	.delete_item = (apk_hash_delete_f) pkg_name_free,
73 74
};

75
static apk_blob_t pkg_info_get_key(apk_hash_item item)
76
{
Timo Teräs's avatar
Timo Teräs committed
77
	return APK_BLOB_CSUM(((struct apk_package *) item)->csum);
78 79
}

80
static unsigned long csum_hash(apk_blob_t csum)
81
{
82 83
	/* Checksum's highest bits have the most "randomness", use that
	 * directly as hash */
84
	return *(unsigned long *) csum.ptr;
85 86 87 88 89
}

static const struct apk_hash_ops pkg_info_hash_ops = {
	.node_offset = offsetof(struct apk_package, hash_node),
	.get_key = pkg_info_get_key,
90 91
	.hash_key = csum_hash,
	.compare = apk_blob_compare,
92 93 94
	.delete_item = (apk_hash_delete_f) apk_pkg_free,
};

95
static apk_blob_t apk_db_dir_get_key(apk_hash_item item)
96
{
Timo Teräs's avatar
Timo Teräs committed
97 98
	struct apk_db_dir *dir = (struct apk_db_dir *) item;
	return APK_BLOB_PTR_LEN(dir->name, dir->namelen);
99 100 101 102 103
}

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,
104 105
	.hash_key = apk_blob_hash,
	.compare = apk_blob_compare,
106 107 108
	.delete_item = (apk_hash_delete_f) free,
};

109 110 111 112 113 114
struct apk_db_file_hash_key {
	apk_blob_t dirname;
	apk_blob_t filename;
};

static unsigned long apk_db_file_hash_key(apk_blob_t _key)
115
{
116 117
	struct apk_db_file_hash_key *key = (struct apk_db_file_hash_key *) _key.ptr;

Timo Teräs's avatar
Timo Teräs committed
118
	return apk_blob_hash_seed(key->filename, apk_blob_hash(key->dirname));
119 120 121 122 123 124
}

static unsigned long apk_db_file_hash_item(apk_hash_item item)
{
	struct apk_db_file *dbf = (struct apk_db_file *) item;

Timo Teräs's avatar
Timo Teräs committed
125 126
	return apk_blob_hash_seed(APK_BLOB_PTR_LEN(dbf->name, dbf->namelen),
				  dbf->diri->dir->hash);
127 128 129 130 131 132
}

static int apk_db_file_compare_item(apk_hash_item item, apk_blob_t _key)
{
	struct apk_db_file *dbf = (struct apk_db_file *) item;
	struct apk_db_file_hash_key *key = (struct apk_db_file_hash_key *) _key.ptr;
Timo Teräs's avatar
Timo Teräs committed
133
	struct apk_db_dir *dir = dbf->diri->dir;
134 135
	int r;

Timo Teräs's avatar
Timo Teräs committed
136 137
	r = apk_blob_compare(key->filename,
			     APK_BLOB_PTR_LEN(dbf->name, dbf->namelen));
138 139 140
	if (r != 0)
		return r;

Timo Teräs's avatar
Timo Teräs committed
141 142 143
	r = apk_blob_compare(key->dirname,
			     APK_BLOB_PTR_LEN(dir->name, dir->namelen));
	return r;
144 145 146 147
}

static const struct apk_hash_ops file_hash_ops = {
	.node_offset = offsetof(struct apk_db_file, hash_node),
148 149 150
	.hash_key = apk_db_file_hash_key,
	.hash_item = apk_db_file_hash_item,
	.compare_item = apk_db_file_compare_item,
151 152 153
	.delete_item = (apk_hash_delete_f) free,
};

Timo Teräs's avatar
Timo Teräs committed
154 155 156 157 158
struct apk_name *apk_db_query_name(struct apk_database *db, apk_blob_t name)
{
	return (struct apk_name *) apk_hash_get(&db->available.names, name);
}

159
struct apk_name *apk_db_get_name(struct apk_database *db, apk_blob_t name)
160 161
{
	struct apk_name *pn;
162
	unsigned long hash = apk_hash_from_key(&db->available.names, name);
163

164
	pn = (struct apk_name *) apk_hash_get_hashed(&db->available.names, name, hash);
165 166 167 168 169 170 171
	if (pn != NULL)
		return pn;

	pn = calloc(1, sizeof(struct apk_name));
	if (pn == NULL)
		return NULL;

172
	pn->name = apk_blob_cstr(name);
173
	pn->id = db->name_id++;
174
	apk_hash_insert_hashed(&db->available.names, pn, hash);
175 176 177 178

	return pn;
}

179
static void apk_db_dir_unref(struct apk_database *db, struct apk_db_dir *dir)
180 181 182 183 184 185 186 187
{
	dir->refs--;
	if (dir->refs > 0)
		return;

	db->installed.stats.dirs--;

	if (dir->parent != NULL)
188
		apk_db_dir_unref(db, dir->parent);
189 190
}

191
static struct apk_db_dir *apk_db_dir_ref(struct apk_db_dir *dir)
192 193 194 195 196
{
	dir->refs++;
	return dir;
}

197 198
struct apk_db_dir *apk_db_dir_query(struct apk_database *db,
				    apk_blob_t name)
199 200 201 202
{
	return (struct apk_db_dir *) apk_hash_get(&db->installed.dirs, name);
}

203 204
static struct apk_db_dir *apk_db_dir_get(struct apk_database *db,
					 apk_blob_t name)
205 206 207
{
	struct apk_db_dir *dir;
	apk_blob_t bparent;
208
	unsigned long hash = apk_hash_from_key(&db->installed.dirs, name);
209
	int i;
210

211
	if (name.len && name.ptr[name.len-1] == '/')
212 213
		name.len--;

214
	dir = (struct apk_db_dir *) apk_hash_get_hashed(&db->installed.dirs, name, hash);
215
	if (dir != NULL)
216
		return apk_db_dir_ref(dir);
217

218
	db->installed.stats.dirs++;
219 220
	dir = malloc(sizeof(*dir) + name.len + 1);
	memset(dir, 0, sizeof(*dir));
221
	dir->refs = 1;
Timo Teräs's avatar
Timo Teräs committed
222 223 224 225
	memcpy(dir->name, name.ptr, name.len);
	dir->name[name.len] = 0;
	dir->namelen = name.len;
	dir->hash = hash;
226
	apk_hash_insert_hashed(&db->installed.dirs, dir, hash);
227

228 229
	if (name.len == 0)
		dir->parent = NULL;
230
	else if (apk_blob_rsplit(name, '/', &bparent, NULL))
231
		dir->parent = apk_db_dir_get(db, bparent);
232
	else
233
		dir->parent = apk_db_dir_get(db, APK_BLOB_NULL);
234

235 236 237 238
	if (dir->parent != NULL)
		dir->flags = dir->parent->flags;

	for (i = 0; i < db->protected_paths->num; i++) {
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
		int flags = dir->flags, j;

		flags |= APK_DBDIRF_PROTECTED;
		for (j = 0; ; j++) {
			switch (db->protected_paths->item[i][j]) {
			case '-':
				flags &= ~(APK_DBDIRF_PROTECTED |
					   APK_DBDIRF_SYMLINKS_ONLY);
				continue;
			case '*':
				flags |= APK_DBDIRF_SYMLINKS_ONLY |
					 APK_DBDIRF_PROTECTED;
				continue;
			}
			break;
		}

		if (strcmp(&db->protected_paths->item[i][j], dir->name) == 0)
			dir->flags = flags;
258 259
	}

260 261 262
	return dir;
}

263 264 265
static struct apk_db_dir_instance *apk_db_diri_new(struct apk_database *db,
						   struct apk_package *pkg,
						   apk_blob_t name,
266
						   struct hlist_node ***after)
267 268 269 270
{
	struct apk_db_dir_instance *diri;

	diri = calloc(1, sizeof(struct apk_db_dir_instance));
271
	if (diri != NULL) {
272 273
		hlist_add_after(&diri->pkg_dirs_list, *after);
		*after = &diri->pkg_dirs_list.next;
274
		diri->dir = apk_db_dir_get(db, name);
275 276
		diri->pkg = pkg;
	}
277 278 279 280 281 282 283 284 285 286 287 288

	return diri;
}

static void apk_db_diri_set(struct apk_db_dir_instance *diri, mode_t mode,
			    uid_t uid, gid_t gid)
{
	diri->mode = mode;
	diri->uid = uid;
	diri->gid = gid;
}

289
static void apk_db_diri_mkdir(struct apk_database *db, struct apk_db_dir_instance *diri)
290
{
291 292
	if (mkdirat(db->root_fd, diri->dir->name, diri->mode) == 0)
		fchownat(db->root_fd, diri->dir->name, diri->uid, diri->gid, 0);
293 294
}

295
static void apk_db_diri_rmdir(struct apk_database *db, struct apk_db_dir_instance *diri)
296
{
Timo Teräs's avatar
Timo Teräs committed
297
	if (diri->dir->refs == 1)
298
		unlinkat(db->root_fd, diri->dir->name, 1);
299 300
}

301 302 303
static void apk_db_diri_free(struct apk_database *db,
			     struct apk_db_dir_instance *diri)
{
304
	apk_db_dir_unref(db, diri->dir);
305 306 307
	free(diri);
}

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
struct apk_db_file *apk_db_file_query(struct apk_database *db,
				      apk_blob_t dir,
				      apk_blob_t name)
{
	struct apk_db_file_hash_key key;

	key = (struct apk_db_file_hash_key) {
		.dirname = dir,
		.filename = name,
	};

	return (struct apk_db_file *) apk_hash_get(&db->installed.files,
						   APK_BLOB_BUF(&key));
}

Timo Teräs's avatar
Timo Teräs committed
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
static struct apk_db_file *apk_db_file_new(struct apk_db_dir_instance *diri,
					   apk_blob_t name,
					   struct hlist_node ***after)
{
	struct apk_db_file *file;

	file = malloc(sizeof(*file) + name.len + 1);
	if (file == NULL)
		return NULL;

	memset(file, 0, sizeof(*file));
	memcpy(file->name, name.ptr, name.len);
	file->name[name.len] = 0;
	file->namelen = name.len;

	file->diri = diri;
	hlist_add_after(&file->diri_files_list, *after);
	*after = &file->diri_files_list.next;

	return file;
}

345
static struct apk_db_file *apk_db_file_get(struct apk_database *db,
346
					   struct apk_db_dir_instance *diri,
347 348
					   apk_blob_t name,
					   struct hlist_node ***after)
349 350
{
	struct apk_db_file *file;
351
	struct apk_db_file_hash_key key;
Timo Teräs's avatar
Timo Teräs committed
352 353
	struct apk_db_dir *dir = diri->dir;
	unsigned long hash;
354 355

	key = (struct apk_db_file_hash_key) {
Timo Teräs's avatar
Timo Teräs committed
356
		.dirname = APK_BLOB_PTR_LEN(dir->name, dir->namelen),
357 358
		.filename = name,
	};
359

Timo Teräs's avatar
Timo Teräs committed
360 361 362
	hash = apk_blob_hash_seed(name, dir->hash);
	file = (struct apk_db_file *) apk_hash_get_hashed(
		&db->installed.files, APK_BLOB_BUF(&key), hash);
363 364 365
	if (file != NULL)
		return file;

Timo Teräs's avatar
Timo Teräs committed
366
	file = apk_db_file_new(diri, name, after);
Timo Teräs's avatar
Timo Teräs committed
367
	apk_hash_insert_hashed(&db->installed.files, file, hash);
368
	db->installed.stats.files++;
369 370 371 372

	return file;
}

Timo Teräs's avatar
Timo Teräs committed
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
static void apk_db_pkg_rdepends(struct apk_database *db, struct apk_package *pkg)
{
	int i, j;

	if (pkg->depends == NULL)
		return;

	for (i = 0; i < pkg->depends->num; i++) {
		struct apk_name *rname = pkg->depends->item[i].name;

		if (rname->rdepends) {
			for (j = 0; j < rname->rdepends->num; j++)
				if (rname->rdepends->item[j] == pkg->name)
					return;
		}
		*apk_name_array_add(&rname->rdepends) = pkg->name;
	}
}

392
struct apk_package *apk_db_pkg_add(struct apk_database *db, struct apk_package *pkg)
393 394 395
{
	struct apk_package *idb;

Timo Teräs's avatar
Timo Teräs committed
396
	idb = apk_hash_get(&db->available.packages, APK_BLOB_CSUM(pkg->csum));
397 398 399 400
	if (idb == NULL) {
		idb = pkg;
		apk_hash_insert(&db->available.packages, pkg);
		*apk_package_array_add(&pkg->name->pkgs) = pkg;
Timo Teräs's avatar
Timo Teräs committed
401
		apk_db_pkg_rdepends(db, pkg);
402 403
	} else {
		idb->repos |= pkg->repos;
404 405 406 407
		if (idb->filename == NULL && pkg->filename != NULL) {
			idb->filename = pkg->filename;
			pkg->filename = NULL;
		}
408 409 410 411 412
		apk_pkg_free(pkg);
	}
	return idb;
}

413
void apk_cache_format_index(apk_blob_t to, struct apk_repository *repo, int ver)
414
{
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
	/* APKINDEX.12345678.tar.gz */
	/* APK_INDEX.12345678.gz */
	if (ver == 0)
		apk_blob_push_blob(&to, APK_BLOB_STR("APKINDEX."));
	else
		apk_blob_push_blob(&to, APK_BLOB_STR("APK_INDEX."));
	apk_blob_push_hexdump(&to, APK_BLOB_PTR_LEN((char *) repo->csum.data,
						    APK_CACHE_CSUM_BYTES));
	if (ver == 0)
		apk_blob_push_blob(&to, APK_BLOB_STR(".tar.gz"));
	else
		apk_blob_push_blob(&to, APK_BLOB_STR(".gz"));
	apk_blob_push_blob(&to, APK_BLOB_PTR_LEN("", 1));
}

int apk_cache_download(struct apk_database *db, const char *url,
		       const char *item, const char *cacheitem, int verify)
{
	char fullurl[PATH_MAX];
	int r;

	snprintf(fullurl, sizeof(fullurl), "%s%s%s",
		 url, url[strlen(url)-1] == '/' ? "" : "/", item);
	apk_message("fetch %s", fullurl);

	if (apk_flags & APK_SIMULATE)
		return 0;

	r = apk_url_download(fullurl, db->cachetmp_fd, cacheitem);
	if (r < 0)
		return r;

	if (verify != APK_SIGN_NONE) {
		struct apk_istream *is;
		struct apk_sign_ctx sctx;

		apk_sign_ctx_init(&sctx, APK_SIGN_VERIFY, NULL, db->keys_fd);
		is = apk_bstream_gunzip_mpart(
			apk_bstream_from_file(db->cachetmp_fd, cacheitem),
			apk_sign_ctx_mpart_cb, &sctx);

		r = apk_tar_parse(is, apk_sign_ctx_verify_tar, &sctx, FALSE);
		is->close(is);
		apk_sign_ctx_free(&sctx);
		if (r != 0) {
			unlinkat(db->cachetmp_fd, cacheitem, 0);
			return r;
		}
	}
464

465 466
	if (renameat(db->cachetmp_fd, cacheitem, db->cache_fd, cacheitem) < 0)
		return -errno;
467

468
	return 0;
469 470
}

471
int apk_db_index_read(struct apk_database *db, struct apk_bstream *bs, int repo)
472 473
{
	struct apk_package *pkg = NULL;
474
	struct apk_db_dir_instance *diri = NULL;
475
	struct apk_db_file *file = NULL;
476 477
	struct hlist_node **diri_node = NULL;
	struct hlist_node **file_diri_node = NULL;
478 479
	apk_blob_t token = APK_BLOB_STR("\n"), l;
	int field;
480

481 482 483 484
	while (!APK_BLOB_IS_NULL(l = bs->read(bs, token))) {
		if (l.len < 2 || l.ptr[1] != ':') {
			if (pkg == NULL)
				continue;
485

486
			if (repo >= 0)
487
				pkg->repos |= BIT(repo);
488
			else if (repo == -1)
489
				apk_pkg_set_state(db, pkg, APK_PKG_INSTALLED);
490

491 492 493
			if (apk_db_pkg_add(db, pkg) != pkg && repo == -1) {
				apk_error("Installed database load failed");
				return -1;
494
			}
495 496 497
			pkg = NULL;
			continue;
		}
498

499 500 501 502 503 504 505 506 507 508 509 510
		/* Get field */
		field = l.ptr[0];
		l.ptr += 2;
		l.len -= 2;

		/* If no package, create new */
		if (pkg == NULL) {
			pkg = apk_pkg_new();
			diri = NULL;
			diri_node = hlist_tail_ptr(&pkg->owned_dirs);
			file_diri_node = NULL;
		}
511

512 513 514 515 516 517 518 519
		/* Standard index line? */
		if (apk_pkg_add_info(db, pkg, field, l) == 0)
			continue;

		if (repo != -1) {
			apk_error("Invalid index entry '%c'", field);
			return -1;
		}
520

521 522 523 524 525
		/* Check FDB special entries */
		switch (field) {
		case 'F':
			if (pkg->name == NULL) {
				apk_error("FDB directory entry before package entry");
526 527
				return -1;
			}
528 529 530 531 532 533 534 535
			diri = apk_db_diri_new(db, pkg, l, &diri_node);
			file_diri_node = &diri->owned_files.first;
			break;
		case 'M':
			if (diri == NULL) {
				apk_error("FDB directory metadata entry before directory entry");
				return -1;
			}
536 537 538 539 540
			diri->uid = apk_blob_pull_uint(&l, 10);
			apk_blob_pull_char(&l, ':');
			diri->gid = apk_blob_pull_uint(&l, 10);
			apk_blob_pull_char(&l, ':');
			diri->mode = apk_blob_pull_uint(&l, 8);
541 542 543 544 545 546
			break;
		case 'R':
			if (diri == NULL) {
				apk_error("FDB file entry before directory entry");
				return -1;
			}
Timo Teräs's avatar
Timo Teräs committed
547
			file = apk_db_file_get(db, diri, l, &file_diri_node);
548 549 550 551 552 553
			break;
		case 'Z':
			if (file == NULL) {
				apk_error("FDB checksum entry before file entry");
				return -1;
			}
Timo Teräs's avatar
Timo Teräs committed
554
			apk_blob_pull_csum(&l, &file->csum);
555 556 557 558
			break;
		default:
			apk_error("FDB entry '%c' unsupported", field);
			return -1;
559
		}
560 561 562 563
		if (APK_BLOB_IS_NULL(l)) {
			apk_error("FDB format error in entry '%c'", field);
			return -1;
		}
564 565
	}

566
	return 0;
567 568
}

Timo Teräs's avatar
Timo Teräs committed
569
static int apk_db_write_fdb(struct apk_database *db, struct apk_ostream *os)
570 571
{
	struct apk_package *pkg;
572
	struct apk_db_dir_instance *diri;
573
	struct apk_db_file *file;
574
	struct hlist_node *c1, *c2;
575
	char buf[1024];
576 577
	apk_blob_t bbuf = APK_BLOB_BUF(buf);
	int r;
578

579
	list_for_each_entry(pkg, &db->installed.packages, installed_pkgs_list) {
580 581 582
		r = apk_pkg_write_index_entry(pkg, os);
		if (r < 0)
			return r;
583

584
		hlist_for_each_entry(diri, c1, &pkg->owned_dirs, pkg_dirs_list) {
585 586 587 588 589 590 591 592 593
			apk_blob_push_blob(&bbuf, APK_BLOB_STR("F:"));
			apk_blob_push_blob(&bbuf, APK_BLOB_PTR_LEN(diri->dir->name, diri->dir->namelen));
			apk_blob_push_blob(&bbuf, APK_BLOB_STR("\nM:"));
			apk_blob_push_uint(&bbuf, diri->uid, 10);
			apk_blob_push_blob(&bbuf, APK_BLOB_STR(":"));
			apk_blob_push_uint(&bbuf, diri->gid, 10);
			apk_blob_push_blob(&bbuf, APK_BLOB_STR(":"));
			apk_blob_push_uint(&bbuf, diri->mode, 8);
			apk_blob_push_blob(&bbuf, APK_BLOB_STR("\n"));
594

595
			hlist_for_each_entry(file, c2, &diri->owned_files, diri_files_list) {
596 597
				apk_blob_push_blob(&bbuf, APK_BLOB_STR("R:"));
				apk_blob_push_blob(&bbuf, APK_BLOB_PTR_LEN(file->name, file->namelen));
Timo Teräs's avatar
Timo Teräs committed
598
				if (file->csum.type != APK_CHECKSUM_NONE) {
599
					apk_blob_push_blob(&bbuf, APK_BLOB_STR("\nZ:"));
Timo Teräs's avatar
Timo Teräs committed
600
					apk_blob_push_csum(&bbuf, &file->csum);
601
				}
602
				apk_blob_push_blob(&bbuf, APK_BLOB_STR("\n"));
603

604
				if (os->write(os, buf, bbuf.ptr - buf) != bbuf.ptr - buf)
605
					return -1;
606
				bbuf = APK_BLOB_BUF(buf);
607
			}
608
			if (os->write(os, buf, bbuf.ptr - buf) != bbuf.ptr - buf)
609
				return -1;
610
			bbuf = APK_BLOB_BUF(buf);
611
		}
Timo Teräs's avatar
Timo Teräs committed
612
		os->write(os, "\n", 1);
613 614 615 616 617
	}

	return 0;
}

Timo Teräs's avatar
Timo Teräs committed
618
static int apk_db_scriptdb_write(struct apk_database *db, struct apk_ostream *os)
619 620 621
{
	struct apk_package *pkg;
	struct apk_script *script;
622
	struct hlist_node *c2;
Timo Teräs's avatar
Timo Teräs committed
623 624 625
	struct apk_file_info fi;
	char filename[256];
	apk_blob_t bfn;
626
	int r;
627

628
	list_for_each_entry(pkg, &db->installed.packages, installed_pkgs_list) {
629
		hlist_for_each_entry(script, c2, &pkg->scripts, script_list) {
Timo Teräs's avatar
Timo Teräs committed
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
			fi = (struct apk_file_info) {
				.name = filename,
				.size = script->size,
				.mode = 0755 | S_IFREG,
			};
			/* The scripts db expects file names in format:
			 * pkg-version.<hexdump of package checksum>.action */
			bfn = APK_BLOB_BUF(filename);
			apk_blob_push_blob(&bfn, APK_BLOB_STR(pkg->name->name));
			apk_blob_push_blob(&bfn, APK_BLOB_STR("-"));
			apk_blob_push_blob(&bfn, APK_BLOB_STR(pkg->version));
			apk_blob_push_blob(&bfn, APK_BLOB_STR("."));
			apk_blob_push_csum(&bfn, &pkg->csum);
			apk_blob_push_blob(&bfn, APK_BLOB_STR("."));
			apk_blob_push_blob(&bfn, APK_BLOB_STR(apk_script_types[script->type]));
			apk_blob_push_blob(&bfn, APK_BLOB_PTR_LEN("", 1));

647
			r = apk_tar_write_entry(os, &fi, script->script);
Timo Teräs's avatar
Timo Teräs committed
648 649
			if (r < 0)
				return r;
650 651 652
		}
	}

653
	return apk_tar_write_entry(os, NULL, NULL);
654 655
}

Timo Teräs's avatar
Timo Teräs committed
656
static int apk_db_scriptdb_read_v1(struct apk_database *db, struct apk_istream *is)
657 658
{
	struct apk_package *pkg;
Timo Teräs's avatar
Timo Teräs committed
659 660 661 662 663 664
	struct {
		unsigned char md5sum[16];
		unsigned int type;
		unsigned int size;
	} hdr;
	struct apk_checksum csum;
665

666
	while (is->read(is, &hdr, sizeof(hdr)) == sizeof(hdr)) {
Timo Teräs's avatar
Timo Teräs committed
667 668 669 670
		memcpy(csum.data, hdr.md5sum, sizeof(hdr.md5sum));
		csum.type = APK_CHECKSUM_MD5;

		pkg = apk_db_get_pkg(db, &csum);
671 672
		if (pkg != NULL)
			apk_pkg_add_script(pkg, is, hdr.type, hdr.size);
Timo Teräs's avatar
Timo Teräs committed
673 674
		else
			apk_istream_skip(is, hdr.size);
675 676 677 678 679
	}

	return 0;
}

Timo Teräs's avatar
Timo Teräs committed
680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
static int apk_read_script_archive_entry(void *ctx,
					 const struct apk_file_info *ae,
					 struct apk_istream *is)
{
	struct apk_database *db = (struct apk_database *) ctx;
	struct apk_package *pkg;
	char *fncsum, *fnaction;
	struct apk_checksum csum;
	apk_blob_t blob;
	int type;

	if (!S_ISREG(ae->mode))
		return 0;

	/* The scripts db expects file names in format:
	 * pkgname-version.<hexdump of package checksum>.action */
	fnaction = memrchr(ae->name, '.', strlen(ae->name));
	if (fnaction == NULL || fnaction == ae->name)
		return 0;
	fncsum = memrchr(ae->name, '.', fnaction - ae->name - 1);
	if (fncsum == NULL)
		return 0;
	fnaction++;
	fncsum++;

	/* Parse it */
	type = apk_script_type(fnaction);
	if (type == APK_SCRIPT_INVALID)
		return 0;
	blob = APK_BLOB_PTR_PTR(fncsum, fnaction - 2);
	apk_blob_pull_csum(&blob, &csum);

	/* Attach script */
	pkg = apk_db_get_pkg(db, &csum);
	if (pkg != NULL)
		apk_pkg_add_script(pkg, is, type, ae->size);

	return 0;
}

Timo Teräs's avatar
Timo Teräs committed
720
static int apk_db_read_state(struct apk_database *db, int flags)
721
{
722
	struct apk_istream *is;
723
	struct apk_bstream *bs;
Timo Teräs's avatar
Timo Teräs committed
724
	apk_blob_t blob;
725
	int i;
726 727 728 729 730 731 732 733 734

	/* Read:
	 * 1. installed repository
	 * 2. source repositories
	 * 3. master dependencies
	 * 4. package statuses
	 * 5. files db
	 * 6. script db
	 */
Timo Teräs's avatar
Timo Teräs committed
735
	if (!(flags & APK_OPENF_NO_WORLD)) {
736
		blob = apk_blob_from_file(db->root_fd, "var/lib/apk/world");
Timo Teräs's avatar
Timo Teräs committed
737 738 739 740
		if (APK_BLOB_IS_NULL(blob))
			return -ENOENT;
		apk_deps_parse(db, &db->world, blob);
		free(blob.ptr);
741

Timo Teräs's avatar
Timo Teräs committed
742
		for (i = 0; db->world != NULL && i < db->world->num; i++)
Timo Teräs's avatar
Timo Teräs committed
743 744
			db->world->item[i].name->flags |= APK_NAME_TOPLEVEL;
	}
745

Timo Teräs's avatar
Timo Teräs committed
746
	if (!(flags & APK_OPENF_NO_INSTALLED)) {
747
		bs = apk_bstream_from_file(db->root_fd, "var/lib/apk/installed");
748 749 750
		if (bs != NULL) {
			apk_db_index_read(db, bs, -1);
			bs->close(bs, NULL);
Timo Teräs's avatar
Timo Teräs committed
751
		}
752

753 754 755 756 757 758
		if (apk_db_cache_active(db)) {
			bs = apk_bstream_from_file(db->cache_fd, "installed");
			if (bs != NULL) {
				apk_db_index_read(db, bs, -2);
				bs->close(bs, NULL);
			}
759
		}
760 761
	}

Timo Teräs's avatar
Timo Teräs committed
762
	if (!(flags & APK_OPENF_NO_SCRIPTS)) {
763
		is = apk_istream_from_file(db->root_fd, "var/lib/apk/scripts.tar");
Timo Teräs's avatar
Timo Teräs committed
764
		if (is != NULL) {
765 766
			apk_tar_parse(is, apk_read_script_archive_entry, db,
				      FALSE);
Timo Teräs's avatar
Timo Teräs committed
767
		} else {
768
			is = apk_istream_from_file(db->root_fd, "var/lib/apk/scripts");
Timo Teräs's avatar
Timo Teräs committed
769 770
			if (is != NULL)
				apk_db_scriptdb_read_v1(db, is);
Timo Teräs's avatar
Timo Teräs committed
771
		}
Timo Teräs's avatar
Timo Teräs committed
772 773
		if (is != NULL)
			is->close(is);
774 775 776 777 778
	}

	return 0;
}

779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
struct index_write_ctx {
	struct apk_ostream *os;
	int count;
	int force;
};

static int write_index_entry(apk_hash_item item, void *ctx)
{
	struct index_write_ctx *iwctx = (struct index_write_ctx *) ctx;
	struct apk_package *pkg = (struct apk_package *) item;
	int r;

	if (!iwctx->force && pkg->filename == NULL)
		return 0;

	r = apk_pkg_write_index_entry(pkg, iwctx->os);
	if (r < 0)
		return r;

	if (iwctx->os->write(iwctx->os, "\n", 1) != 1)
		return -1;

	iwctx->count++;
	return 0;
}

static int apk_db_index_write_nr_cache(struct apk_database *db)
{
	struct index_write_ctx ctx = { NULL, 0, TRUE };
	struct apk_package *pkg;
	struct apk_ostream *os;
	int r;

	if (!apk_db_cache_active(db))
		return 0;

	/* Write list of installed non-repository packages to
	 * cached index file */
817 818 819 820
	os = apk_ostream_to_file(db->cache_fd,
				 "installed",
				 "installed.new",
				 0644);
821 822 823
	if (os == NULL)
		return -1;

824
	ctx.os = os;
825 826 827 828 829 830 831
	list_for_each_entry(pkg, &db->installed.packages, installed_pkgs_list) {
		if (pkg->repos != 0)
			continue;
		r = write_index_entry(pkg, &ctx);
		if (r != 0)
			return r;
	}
832 833 834
	r = os->close(os);
	if (r < 0)
		return r;
835 836 837 838 839 840 841 842 843 844 845 846 847

	return ctx.count;
}

int apk_db_index_write(struct apk_database *db, struct apk_ostream *os)
{
	struct index_write_ctx ctx = { os, 0, FALSE };

	apk_hash_foreach(&db->available.packages, write_index_entry, &ctx);

	return ctx.count;
}

848 849 850 851 852 853 854 855
static int add_protected_path(void *ctx, apk_blob_t blob)
{
	struct apk_database *db = (struct apk_database *) ctx;

	*apk_string_array_add(&db->protected_paths) = apk_blob_cstr(blob);
	return 0;
}

856 857 858 859
static int apk_db_create(struct apk_database *db)
{
	int fd;

860 861 862 863 864 865
	mkdirat(db->root_fd, "tmp", 01777);
	mkdirat(db->root_fd, "dev", 0755);
	mknodat(db->root_fd, "dev/null", 0666, makedev(1, 3));
	mkdirat(db->root_fd, "var", 0755);
	mkdirat(db->root_fd, "var/lib", 0755);
	mkdirat(db->root_fd, "var/lib/apk", 0755);
866 867
	mkdirat(db->root_fd, "var/cache", 0755);
	mkdirat(db->root_fd, "var/cache/misc", 0755);
868

869
	fd = openat(db->root_fd, "var/lib/apk/world", O_CREAT|O_RDWR|O_TRUNC, 0644);
870 871 872 873 874 875 876
	if (fd < 0)
		return -errno;
	close(fd);

	return 0;
}

877 878 879 880
static void handle_alarm(int sig)
{
}

881
int apk_db_open(struct apk_database *db, struct apk_db_options *dbopts)
882
{
883 884
	const char *msg = NULL;
	struct apk_repository_list *repo = NULL;
885
	struct stat64 st;
Timo Teräs's avatar
Timo Teräs committed
886
	apk_blob_t blob;
887
	int r, rr = 0;
888

889
	memset(db, 0, sizeof(*db));
890 891 892 893 894 895
	if (dbopts->open_flags == 0) {
		msg = "Invalid open flags (internal error)";
		r = -1;
		goto ret_r;
	}

896 897
	apk_hash_init(&db->available.names, &pkg_name_hash_ops, 1000);
	apk_hash_init(&db->available.packages, &pkg_info_hash_ops, 4000);
Timo Teräs's avatar
Timo Teräs committed
898 899
	apk_hash_init(&db->installed.dirs, &dir_hash_ops, 2000);
	apk_hash_init(&db->installed.files, &file_hash_ops, 10000);
900
	list_init(&db->installed.packages);
Timo Teräs's avatar
Timo Teräs committed
901
	db->cache_dir = apk_static_cache_dir;
902
	db->permanent = 1;
903

904
	db->root = strdup(dbopts->root ?: "/");
905
	db->root_fd = openat(AT_FDCWD, db->root, O_RDONLY);
906
	if (db->root_fd < 0 && (dbopts->open_flags & APK_OPENF_CREATE)) {
907
		mkdirat(AT_FDCWD, db->root, 0755);
908
		db->root_fd = openat(AT_FDCWD, db->root, O_RDONLY);
909 910 911 912 913 914 915 916 917 918 919 920
	}
	if (db->root_fd < 0) {
		msg = "Unable to open root";
		goto ret_errno;
	}
	if (fstat64(db->root_fd, &st) != 0 || major(st.st_dev) == 0)
		db->permanent = 0;

	if (fstatat64(db->root_fd, apk_linked_cache_dir, &st, 0) == 0 &&
	    S_ISDIR(st.st_mode))
		db->cache_dir = apk_linked_cache_dir;

921
	if (dbopts->open_flags & APK_OPENF_WRITE) {
922 923 924
		db->lock_fd = openat(db->root_fd, "var/lib/apk/lock",
				     O_CREAT | O_RDWR, 0400);
		if (db->lock_fd < 0 && errno == ENOENT &&
925
		    (dbopts->open_flags & APK_OPENF_CREATE)) {
926 927 928 929 930
			r = apk_db_create(db);
			if (r != 0) {
				msg = "Unable to create database";
				goto ret_r;
			}
931 932
			db->lock_fd = openat(db->root_fd, "var/lib/apk/lock",
					     O_CREAT | O_RDWR, 0400);
933 934 935 936 937 938 939 940 941 942 943 944 945 946 947
		}
		if (db->lock_fd < 0 ||
		    flock(db->lock_fd, LOCK_EX | LOCK_NB) < 0) {
			msg = "Unable to lock database";
			if (apk_wait) {
				struct sigaction sa, old_sa;

				apk_message("Waiting for repository lock");
				memset(&sa, 0, sizeof sa);
				sa.sa_handler = handle_alarm;
				sa.sa_flags   = SA_ONESHOT;
				sigaction(SIGALRM, &sa, &old_sa);

				alarm(apk_wait);
				if (flock(db->lock_fd, LOCK_EX) < 0)
Timo Teräs's avatar
Timo Teräs committed
948
					goto ret_errno;
949 950 951 952 953

				alarm(0);
				sigaction(SIGALRM, &old_sa, NULL);
			} else
				goto ret_errno;
954 955
		}
	}
956

957
	blob = APK_BLOB_STR("etc:*etc/init.d");
958 959
	apk_blob_for_each_segment(blob, ":", add_protected_path, db);

960 961 962
	db->cache_fd = openat(db->root_fd, db->cache_dir, O_RDONLY);
	mkdirat(db->cache_fd, "tmp", 0644);
	db->cachetmp_fd = openat(db->cache_fd, "tmp", O_RDONLY);
963 964 965
	db->keys_fd = openat(db->root_fd,
			     dbopts->keys_dir ?: "etc/apk/keys",
			     O_RDONLY);
966

967 968
	r = apk_db_read_state(db, dbopts->open_flags);
	if (r == -ENOENT && (dbopts->open_flags & APK_OPENF_CREATE)) {
969
		r = apk_db_create(db);
Timo Teräs's avatar
Timo Teräs committed
970
		if (r != 0) {
971
			msg = "Unable to create database";
Timo Teräs's avatar
Timo Teräs committed
972
			goto ret_r;
973
		}
974
		r = apk_db_read_state(db, dbopts->open_flags);
975 976 977 978
	}
	if (r != 0) {
		msg = "Unable to read database state";
		goto ret_r;
979 980
	}

981 982
	if (!(dbopts->open_flags & APK_OPENF_NO_REPOS)) {
		list_for_each_entry(repo, &dbopts->repository_list, list) {
983 984 985
			r = apk_db_add_repository(db, APK_BLOB_STR(repo->url));
			rr = r ?: rr;
		}
986 987 988
		blob = apk_blob_from_file(
			db->root_fd,
			dbopts->repositories_file ?: "etc/apk/repositories");
989 990 991 992 993 994 995
		if (!APK_BLOB_IS_NULL(blob)) {
			r = apk_blob_for_each_segment(
				blob, "\n",
				apk_db_add_repository, db);
			rr = r ?: rr;
			free(blob.ptr);
		}
996 997
		if (apk_flags & APK_UPDATE_CACHE)
			apk_db_index_write_nr_cache(db);
998
	}
999 1000 1001 1002
	if (rr != 0) {
		r = rr;
		goto ret_r;
	}
1003

1004
	return rr;
1005 1006 1007 1008

ret_errno:
	r = -errno;
ret_r:
Natanael Copa's avatar
Natanael Copa committed
1009 1010
	if (msg != NULL)
		apk_error("%s: %s", msg, strerror(-r));
1011
	apk_db_close(db);
1012

1013
	return r;
1014 1015
}

1016 1017 1018 1019 1020
struct write_ctx {
	struct apk_database *db;
	int fd;
};

1021
int apk_db_write_config(struct apk_database *db)
1022
{
Timo Teräs's avatar
Timo Teräs committed
1023
	struct apk_ostream *os;
1024
	int r;
1025 1026

	if (db->root == NULL)
1027
		return 0;
1028

1029 1030 1031 1032 1033
	if (db->lock_fd == 0) {
		apk_error("Refusing to write db without write lock!");
		return -1;
	}

1034 1035 1036 1037
	os = apk_ostream_to_file(db->root_fd,
				 "var/lib/apk/world",
				 "var/lib/apk/world.new",
				 0644);
Timo Teräs's avatar
Timo Teräs committed
1038
	if (os == NULL)
1039
		return -1;
1040

1041 1042
	apk_deps_write(db->world, os);
	os->write(os, "\n", 1);
1043 1044 1045
	r = os->close(os);
	if (r < 0)
		return r;
1046

1047 1048 1049 1050
	os = apk_ostream_to_file(db->root_fd,
				 "var/lib/apk/installed",
				 "var/lib/apk/installed.new",
				 0644);
Timo Teräs's avatar
Timo Teräs committed
1051
	if (os == NULL)
1052
		return -1;
Timo Teräs's avatar
Timo Teräs committed
1053
	apk_db_write_fdb(db, os);
1054 1055 1056
	r = os->close(os);
	if (r < 0)
		return r;
1057

1058 1059 1060 1061
	os = apk_ostream_to_file(db->root_fd,
				 "var/lib/apk/scripts.tar",
				 "var/lib/apk/scripts.tar.new",
				 0644);
Timo Teräs's avatar
Timo Teräs committed
1062
	if (os == NULL)
1063
		return -1;
Timo Teräs's avatar
Timo Teräs committed
1064
	apk_db_scriptdb_write(db, os);
1065 1066 1067
	r = os->close(os);
	if (r < 0)
		return r;
Timo Teräs's avatar
Timo Teräs committed
1068

1069
	unlinkat(db->root_fd, "var/lib/apk/scripts", 0);
1070
	apk_db_index_write_nr_cache(db);
1071 1072 1073 1074

	return 0;
}

1075
void apk_db_close(struct apk_database *db)
1076
{
1077 1078
	struct apk_package *pkg;
	struct apk_db_dir_instance *diri;
1079
	struct hlist_node *dc, *dn;
1080 1081 1082 1083 1084 1085 1086 1087
	int i;

	list_for_each_entry(pkg, &db->installed.packages, installed_pkgs_list) {
		hlist_for_each_entry_safe(diri, dc, dn, &pkg->owned_dirs, pkg_dirs_list) {
			apk_db_diri_free(db, diri);
		}
	}

1088
	for (i = 0; i < db->num_repos; i++) {
1089
		free(db->repos[i].url);
1090
	}
1091 1092 1093 1094 1095 1096 1097
	if (db->protected_paths) {
		for (i = 0; i < db->protected_paths->num; i++)
			free(db->protected_paths->item[i]);
		free(db->protected_paths);
	}
	if (db->world)
		free(db->world);
1098

1099 1100
	apk_hash_free(&db->available.names);
	apk_hash_free(&db->available.packages);
1101
	apk_hash_free(&db->installed.files);
1102
	apk_hash_free(&db->installed.dirs);
1103

1104 1105 1106 1107 1108 1109
	if (db->keys_fd)
		close(db->keys_fd);
	if (db->cachetmp_fd)
		close(db->cachetmp_fd);
	if (db->cache_fd)
		close(db->cache_fd);
1110
	if (db->root_fd)
1111
		close(db->root_fd);
1112 1113 1114
	if (db->lock_fd)
		close(db->lock_fd);
	if (db->root != NULL)
1115 1116 1117
		free(db->root);
}

Timo Teräs's avatar
Timo Teräs committed
1118 1119 1120 1121 1122
int apk_db_cache_active(struct apk_database *db)
{
	return db->cache_dir != apk_static_cache_dir;
}

1123 1124
int apk_db_permanent(struct apk_database *db)
{
1125
	return db->permanent;
1126 1127
}

Timo Teräs's avatar
Timo Teräs committed
1128 1129
struct apk_package *apk_db_get_pkg(struct apk_database *db,
				   struct apk_checksum *csum)
1130
{
Timo Teräs's avatar
Timo Teräs committed
1131
	return apk_hash_get(&db->available.packages, APK_BLOB_CSUM(*csum));
1132 1133
}

1134 1135 1136
struct apk_package *apk_db_get_file_owner(struct apk_database *db,
					  apk_blob_t filename)
{
1137
	struct apk_db_file *dbf;
1138
	struct apk_db_file_hash_key key;
1139

1140 1141
	if (filename.len && filename.ptr[0] == '/')
		filename.len--, filename.ptr++;
1142

1143 1144 1145 1146 1147
	if (!apk_blob_rsplit(filename, '/', &key.dirname,