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

24 25
#include <openssl/crypto.h>
#ifndef OPENSSL_NO_ENGINE
Timo Teräs's avatar
Timo Teräs committed
26
#include <openssl/engine.h>
27
#endif
Timo Teräs's avatar
Timo Teräs committed
28

29
#include "apk_defines.h"
30
#include "apk_database.h"
31
#include "apk_applet.h"
Timo Teräs's avatar
Timo Teräs committed
32
#include "apk_blob.h"
Natanael Copa's avatar
Natanael Copa committed
33
#include "apk_print.h"
34
#include "apk_io.h"
35

36 37
char **apk_argv;

Timo Teräs's avatar
Timo Teräs committed
38 39 40
static struct apk_option generic_options[] = {
	{ 'h', "help",		"Show generic help or applet specific help" },
	{ 'p', "root",		"Install packages to DIR",
41
				required_argument, "DIR" },
Timo Teräs's avatar
Timo Teräs committed
42
	{ 'X', "repository",	"Use packages from REPO",
43
				required_argument, "REPO" },
Timo Teräs's avatar
Timo Teräs committed
44 45
	{ 'q', "quiet",		"Print less information" },
	{ 'v', "verbose",	"Print more information" },
46
	{ 'i', "interactive",	"Ask confirmation for certain operations" },
Timo Teräs's avatar
Timo Teräs committed
47 48
	{ 'V', "version",	"Print program version and exit" },
	{ 'f', "force",		"Do what was asked even if it looks dangerous" },
49
	{ 'U', "update-cache",	"Update the repository cache" },
Timo Teräs's avatar
Timo Teräs committed
50
	{ 0x101, "progress",	"Show a progress bar" },
51
	{ 0x10f, "progress-fd",	"Write progress to fd", required_argument, "FD" },
52
	{ 0x110, "no-progress",	"Disable progress bar even for TTYs" },
53 54
	{ 0x102, "clean-protected", "Do not create .apk-new files to "
				"configuration dirs" },
55 56
	{ 0x106, "purge",	"Delete also modified configuration files on "
				"package removal" },
Timo Teräs's avatar
Timo Teräs committed
57 58
	{ 0x103, "allow-untrusted", "Blindly install packages with untrusted "
				"signatures or no signature at all" },
59 60 61 62 63
	{ 0x104, "simulate",	"Show what would be done without actually "
				"doing it" },
	{ 0x105, "wait",	"Wait for TIME seconds to get an exclusive "
				"repository lock before failing",
				required_argument, "TIME" },
64 65 66
	{ 0x107, "keys-dir",	"Override directory of trusted keys",
				required_argument, "KEYSDIR" },
	{ 0x108, "repositories-file", "Override repositories file",
67 68
				required_argument, "REPOFILE" },
	{ 0x109, "no-network",	"Do not use network (cache is still used)" },
Timo Teräs's avatar
Timo Teräs committed
69
	{ 0x111, "overlay-from-stdin", "Read list of overlay files from stdin" },
70 71
	{ 0x112, "arch", 	"Use architecture with --root",
				required_argument, "ARCH" },
72 73 74 75 76
#ifdef TEST_MODE
	{ 0x200, "test-repo",	"Repository", required_argument, "REPO" },
	{ 0x201, "test-instdb",	"Installed db", required_argument, "INSTALLED" },
	{ 0x202, "test-world",	"World", required_argument, "WORLD DEPS" },
#endif
Timo Teräs's avatar
Timo Teräs committed
77 78 79
};

static int version(void)
Natanael Copa's avatar
Natanael Copa committed
80
{
81
	printf("apk-tools " APK_VERSION ", compiled for " APK_DEFAULT_ARCH ".\n");
82 83 84
#ifdef TEST_MODE
	printf("TEST MODE BUILD. NOT FOR PRODUCTION USE.\n");
#endif
Natanael Copa's avatar
Natanael Copa committed
85 86
	return 0;
}
87

Timo Teräs's avatar
Timo Teräs committed
88 89
static int format_option(char *buf, size_t len, struct apk_option *o,
			 const char *separator)
Natanael Copa's avatar
Natanael Copa committed
90
{
Timo Teräs's avatar
Timo Teräs committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
	int i = 0;

	if (o->val <= 0xff && isalnum(o->val)) {
		i += snprintf(&buf[i], len - i, "-%c", o->val);
		if (o->name != NULL)
			i += snprintf(&buf[i], len - i, "%s", separator);
	}
	if (o->name != NULL)
		i += snprintf(&buf[i], len - i, "--%s", o->name);
	if (o->arg_name != NULL)
		i += snprintf(&buf[i], len - i, " %s", o->arg_name);

	return i;
}

static void print_usage(const char *cmd, const char *args, int num_opts,
		        struct apk_option *opts)
{
109
	struct apk_indent indent = { .indent = 11 };
Timo Teräs's avatar
Timo Teräs committed
110 111 112 113 114 115 116 117 118
	char word[128];
	int i, j;

	indent.x = printf("\nusage: apk %s", cmd) - 1;
	for (i = 0; i < num_opts; i++) {
		j = 0;
		word[j++] = '[';
		j += format_option(&word[j], sizeof(word) - j, &opts[i], "|");
		word[j++] = ']';
Timo Teräs's avatar
Timo Teräs committed
119
		apk_print_indented(&indent, APK_BLOB_PTR_LEN(word, j));
Timo Teräs's avatar
Timo Teräs committed
120 121
	}
	if (args != NULL)
Timo Teräs's avatar
Timo Teräs committed
122
		apk_print_indented(&indent, APK_BLOB_STR(args));
Timo Teräs's avatar
Timo Teräs committed
123 124 125 126 127
	printf("\n");
}

static void print_options(int num_opts, struct apk_option *opts)
{
128
	struct apk_indent indent = { .indent = 26 };
Timo Teräs's avatar
Timo Teräs committed
129 130 131 132 133 134
	char word[128];
	int i;

	for (i = 0; i < num_opts; i++) {
		format_option(word, sizeof(word), &opts[i], ", ");
		indent.x = printf("  %-*s", indent.indent - 3, word);
Timo Teräs's avatar
Timo Teräs committed
135
		apk_print_indented_words(&indent, opts[i].help);
Timo Teräs's avatar
Timo Teräs committed
136 137 138 139 140 141
		printf("\n");
	}
}

static int usage(struct apk_applet *applet)
{
Natanael Copa's avatar
Natanael Copa committed
142
	version();
Timo Teräs's avatar
Timo Teräs committed
143 144 145 146 147 148
	if (applet == NULL) {
		struct apk_applet **a;

		print_usage("COMMAND", "[ARGS]...",
			    ARRAY_SIZE(generic_options), generic_options);

149 150
		printf("\nThe following commands are available:\n");
		for (a = &__start_apkapplets; a < &__stop_apkapplets; a++) {
151
			struct apk_indent indent = { .indent = 26 };
152

Timo Teräs's avatar
Timo Teräs committed
153 154
			indent.x = printf("  %-*s", indent.indent - 3, (*a)->name);
			apk_print_indented_words(&indent, (*a)->help);
155 156
			printf("\n");
		}
Timo Teräs's avatar
Timo Teräs committed
157
	} else {
158
		struct apk_indent indent = { .indent = 2 };
Timo Teräs's avatar
Timo Teräs committed
159

Timo Teräs's avatar
Timo Teräs committed
160 161
		print_usage(applet->name, applet->arguments,
			    applet->num_options, applet->options);
162
		printf("\nDescription:\n");
Timo Teräs's avatar
Timo Teräs committed
163
		apk_print_indented_words(&indent, applet->help);
Timo Teräs's avatar
Timo Teräs committed
164
		printf("\n");
Timo Teräs's avatar
Timo Teräs committed
165
	}
166
	printf("\nGeneric options:\n");
Timo Teräs's avatar
Timo Teräs committed
167 168 169
	print_options(ARRAY_SIZE(generic_options), generic_options);

	if (applet != NULL && applet->num_options > 0) {
Timo Teräs's avatar
Timo Teräs committed
170
		printf("\nOptions for %s command:\n", applet->name);
Timo Teräs's avatar
Timo Teräs committed
171 172
		print_options(applet->num_options, applet->options);
	}
173
	printf("\nThis apk has coffee making abilities.\n");
Timo Teräs's avatar
Timo Teräs committed
174

175 176 177
	return 1;
}

178 179 180 181 182 183 184 185
static struct apk_applet *find_applet(const char *name)
{
	struct apk_applet **a;

	for (a = &__start_apkapplets; a < &__stop_apkapplets; a++) {
		if (strcmp(name, (*a)->name) == 0)
			return *a;
	}
Timo Teräs's avatar
Timo Teräs committed
186

187 188 189
	return NULL;
}

Timo Teräs's avatar
Timo Teräs committed
190
static struct apk_applet *deduce_applet(int argc, char **argv)
191
{
Timo Teräs's avatar
Timo Teräs committed
192
	struct apk_applet *a;
193
	const char *prog;
Timo Teräs's avatar
Timo Teräs committed
194
	int i;
195 196 197 198 199 200 201

	prog = strrchr(argv[0], '/');
	if (prog == NULL)
		prog = argv[0];
	else
		prog++;

202
	if (strncmp(prog, "apk_", 4) == 0)
Timo Teräs's avatar
Timo Teräs committed
203 204 205 206 207 208 209 210 211 212 213 214 215 216
		return find_applet(prog + 4);

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-')
			continue;

		a = find_applet(argv[i]);
		if (a != NULL)
			return a;
	}

	return NULL;
}

217
static struct apk_repository_list *apk_repository_new(const char *url)
218
{
219 220
	struct apk_repository_list *r = calloc(1,
			sizeof(struct apk_repository_list));
221 222 223 224 225 226 227
	if (r) {
		r->url = url;
		list_init(&r->list);
	}
	return r;
}

Timo Teräs's avatar
Timo Teräs committed
228 229 230 231 232 233 234 235 236 237 238 239
static void merge_options(struct option *opts, struct apk_option *ao, int num)
{
	int i;

	for (i = 0; i < num; i++, opts++, ao++) {
		opts->name = ao->name;
		opts->has_arg = ao->has_arg;
		opts->flag = NULL;
		opts->val = ao->val;
	}
	opts->name = NULL;
}
Timo Teräs's avatar
Timo Teräs committed
240

Timo Teräs's avatar
Timo Teräs committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
static void fini_openssl(void)
{
	EVP_cleanup();
#ifndef OPENSSL_NO_ENGINE
	ENGINE_cleanup();
#endif
	CRYPTO_cleanup_all_ex_data();
}

static void init_openssl(void)
{
	atexit(fini_openssl);
	OpenSSL_add_all_algorithms();
#ifndef OPENSSL_NO_ENGINE
	ENGINE_load_builtin_engines();
256
	ENGINE_register_all_complete();
Timo Teräs's avatar
Timo Teräs committed
257 258 259
#endif
}

Timo Teräs's avatar
Timo Teräs committed
260
static void on_sigwinch(int s)
Timo Teräs's avatar
Timo Teräs committed
261
{
Timo Teräs's avatar
Timo Teräs committed
262 263
	apk_reset_screen_width();
}
Timo Teräs's avatar
Timo Teräs committed
264

Timo Teräs's avatar
Timo Teräs committed
265 266
static void setup_terminal(void)
{
Timo Teräs's avatar
Timo Teräs committed
267
	setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
Timo Teräs's avatar
Timo Teräs committed
268
	signal(SIGWINCH, on_sigwinch);
269
	signal(SIGPIPE, SIG_IGN);
Timo Teräs's avatar
Timo Teräs committed
270 271
}

272 273 274 275 276 277 278 279 280 281 282 283
static void setup_automatic_flags(void)
{
	if (!isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO) ||
	    !isatty(STDIN_FILENO))
		return;

	apk_flags |= APK_PROGRESS;
	if (!(apk_flags & APK_SIMULATE) &&
	    access("/etc/apk/interactive", F_OK) == 0)
		apk_flags |= APK_INTERACTIVE;
}

Timo Teräs's avatar
Timo Teräs committed
284 285 286 287
int main(int argc, char **argv)
{
	struct apk_applet *applet;
	char short_options[256], *sopt;
Timo Teräs's avatar
Timo Teräs committed
288
	struct option *opt, *all_options;
289
	int r, optindex, num_options;
Timo Teräs's avatar
Timo Teräs committed
290
	void *ctx = NULL;
291 292 293
	struct apk_repository_list *repo = NULL;
	struct apk_database db;
	struct apk_db_options dbopts;
294
	struct apk_string_array *args;
295 296 297 298 299 300 301 302
#ifdef TEST_MODE
	const char *test_installed_db = NULL;
	const char *test_world = NULL;
	struct apk_string_array *test_repos;
	int i;

	apk_string_array_init(&test_repos);
#endif
303

304
	apk_argv = malloc(sizeof(char*[argc+2]));
305 306
	memcpy(apk_argv, argv, sizeof(char*[argc]));
	apk_argv[argc] = NULL;
307
	apk_argv[argc+1] = NULL;
308

309 310
	memset(&dbopts, 0, sizeof(dbopts));
	list_init(&dbopts.repository_list);
311
	apk_atom_init();
Timo Teräs's avatar
Timo Teräs committed
312
	umask(0);
Timo Teräs's avatar
Timo Teräs committed
313
	setup_terminal();
314

Timo Teräs's avatar
Timo Teräs committed
315
	applet = deduce_applet(argc, argv);
Timo Teräs's avatar
Timo Teräs committed
316 317 318 319 320 321
	num_options = ARRAY_SIZE(generic_options) + 1;
	if (applet != NULL)
		num_options += applet->num_options;
	all_options = alloca(sizeof(struct option) * num_options);
	merge_options(&all_options[0], generic_options,
	              ARRAY_SIZE(generic_options));
Natanael Copa's avatar
Natanael Copa committed
322
	if (applet != NULL) {
Timo Teräs's avatar
Timo Teräs committed
323 324
		merge_options(&all_options[ARRAY_SIZE(generic_options)],
			      applet->options, applet->num_options);
Natanael Copa's avatar
Natanael Copa committed
325 326
		if (applet->context_size != 0)
			ctx = calloc(1, applet->context_size);
327 328
		dbopts.open_flags = applet->open_flags;
		apk_flags |= applet->forced_flags;
Timo Teräs's avatar
Timo Teräs committed
329
	}
Natanael Copa's avatar
Natanael Copa committed
330

Timo Teräs's avatar
Timo Teräs committed
331
	for (opt = all_options, sopt = short_options; opt->name != NULL; opt++) {
332 333
		if (opt->flag == NULL &&
		    opt->val <= 0xff && isalnum(opt->val)) {
Timo Teräs's avatar
Timo Teräs committed
334 335 336 337 338 339
			*(sopt++) = opt->val;
			if (opt->has_arg != no_argument)
				*(sopt++) = ':';
		}
	}

Timo Teräs's avatar
Timo Teräs committed
340
	init_openssl();
341
	setup_automatic_flags();
Timo Teräs's avatar
Timo Teräs committed
342

Timo Teräs's avatar
Timo Teräs committed
343 344
	optindex = 0;
	while ((r = getopt_long(argc, argv, short_options,
Timo Teräs's avatar
Timo Teräs committed
345
				all_options, &optindex)) != -1) {
346
		switch (r) {
Timo Teräs's avatar
Timo Teräs committed
347 348
		case 0:
			break;
Natanael Copa's avatar
Natanael Copa committed
349
		case 'h':
350 351
			r = usage(applet);
			goto err;
Natanael Copa's avatar
Natanael Copa committed
352
		case 'p':
353 354 355 356 357 358 359
			dbopts.root = optarg;
			break;
		case 0x107:
			dbopts.keys_dir = optarg;
			break;
		case 0x108:
			dbopts.repositories_file = optarg;
360 361
			break;
		case 'X':
362 363
			repo = apk_repository_new(optarg);
			if (repo)
364
				list_add(&repo->list, &dbopts.repository_list);
365
			break;
366
		case 'q':
367 368 369 370
			apk_verbosity--;
			break;
		case 'v':
			apk_verbosity++;
371
			break;
Natanael Copa's avatar
Natanael Copa committed
372
		case 'V':
373 374
			r = version();
			goto err;
375 376 377
		case 'f':
			apk_flags |= APK_FORCE;
			break;
378 379 380
		case 'i':
			apk_flags |= APK_INTERACTIVE;
			break;
381 382 383
		case 'U':
			apk_flags |= APK_UPDATE_CACHE;
			break;
384 385 386
		case 0x101:
			apk_flags |= APK_PROGRESS;
			break;
387 388 389
		case 0x110:
			apk_flags &= ~APK_PROGRESS;
			break;
390
		case 0x10f:
391
			apk_progress_fd = atoi(optarg);
392
			break;
393 394 395
		case 0x102:
			apk_flags |= APK_CLEAN_PROTECTED;
			break;
Timo Teräs's avatar
Timo Teräs committed
396 397 398
		case 0x103:
			apk_flags |= APK_ALLOW_UNTRUSTED;
			break;
399 400
		case 0x104:
			apk_flags |= APK_SIMULATE;
Natanael Copa's avatar
Natanael Copa committed
401
			break;
402 403 404
		case 0x106:
			apk_flags |= APK_PURGE;
			break;
405
		case 0x105:
406
			dbopts.lock_wait = atoi(optarg);
407
			break;
408 409 410
		case 0x109:
			apk_flags |= APK_NO_NETWORK;
			break;
Timo Teräs's avatar
Timo Teräs committed
411 412 413
		case 0x111:
			apk_flags |= APK_OVERLAY_FROM_STDIN;
			break;
414 415 416
		case 0x112:
			dbopts.arch = optarg;
			break;
417 418 419 420 421 422 423 424 425 426 427
#ifdef TEST_MODE
		case 0x200:
			*apk_string_array_add(&test_repos) = (char*) optarg;
			break;
		case 0x201:
			test_installed_db = optarg;
			break;
		case 0x202:
			test_world = optarg;
			break;
#endif
428
		default:
Natanael Copa's avatar
Natanael Copa committed
429
			if (applet == NULL || applet->parse == NULL ||
430
			    applet->parse(ctx, &dbopts, r,
Timo Teräs's avatar
Timo Teräs committed
431
					  optindex - ARRAY_SIZE(generic_options),
432 433 434 435
					  optarg) != 0) {
				r = usage(applet);
				goto err;
			}
Timo Teräs's avatar
Timo Teräs committed
436
			break;
437 438 439
		}
	}

440 441 442 443
	if (applet == NULL) {
		r = usage(NULL);
		goto err;
	}
Natanael Copa's avatar
Natanael Copa committed
444

Timo Teräs's avatar
Timo Teräs committed
445 446 447
	argc -= optind;
	argv += optind;
	if (argc >= 1 && strcmp(argv[0], applet->name) == 0) {
448 449 450 451
		argc--;
		argv++;
	}

452 453 454 455 456 457
#ifdef TEST_MODE
	dbopts.open_flags &= ~(APK_OPENF_WRITE | APK_OPENF_CACHE_WRITE | APK_OPENF_CREATE);
	dbopts.open_flags |= APK_OPENF_READ | APK_OPENF_NO_STATE | APK_OPENF_NO_REPOS;
	apk_flags |= APK_SIMULATE;
	apk_flags &= ~APK_INTERACTIVE;
#endif
458
	r = apk_db_open(&db, &dbopts);
459 460 461
	if (r != 0) {
		apk_error("Failed to open apk database: %s",
			  apk_error_str(r));
462
		goto err;
463 464
	}

465 466 467 468 469 470 471 472 473 474 475 476 477 478
#ifdef TEST_MODE
	if (test_world != NULL) {
		apk_blob_t b = APK_BLOB_STR(test_world);
		apk_blob_pull_deps(&b, &db, &db.world);
	}
	if (test_installed_db != NULL) {
		struct apk_bstream *bs = apk_bstream_from_file(AT_FDCWD, test_installed_db);
		if (bs != NULL) {
			apk_db_index_read(&db, bs, -1);
			bs->close(bs, NULL);
		}
	}
	for (i = 0; i < test_repos->num; i++) {
		struct apk_bstream *bs;
Timo Teräs's avatar
Timo Teräs committed
479
		apk_blob_t spec = APK_BLOB_STR(test_repos->item[i]), name, tag;
480
		int repo_tag = 0, repo = APK_REPOSITORY_FIRST_CONFIGURED + i;
Timo Teräs's avatar
Timo Teräs committed
481 482 483 484 485

		if (apk_blob_split(spec, APK_BLOB_STR(":"), &tag, &name)) {
			repo_tag = apk_db_get_tag_id(&db, tag);
		} else {
			name = spec;
486
		}
Timo Teräs's avatar
Timo Teräs committed
487 488

		bs = apk_bstream_from_file(AT_FDCWD, name.ptr);
489
		if (bs != NULL) {
490
			apk_db_index_read(&db, bs, repo);
491
			bs->close(bs, NULL);
492 493 494
			if (!(apk_flags & APK_NO_NETWORK))
				db.available_repos |= BIT(repo);
			db.repo_tags[repo_tag].allowed_repos |= BIT(repo);
495 496 497 498
		}
	}
#endif

499 500 501 502 503
	apk_string_array_init(&args);
	apk_string_array_resize(&args, argc);
	memcpy(args->item, argv, argc * sizeof(*argv));

	r = applet->main(ctx, &db, args);
504 505
	apk_db_close(&db);

Timo Teräs's avatar
Timo Teräs committed
506
	if (r == -EINVAL)
507 508 509 510
		r = usage(applet);
err:
	if (ctx)
		free(ctx);
Timo Teräs's avatar
Timo Teräs committed
511
	return r;
512
}