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
	{ 'q', "quiet",		"Print less information" },
45
	{ 'v', "verbose",	"Print more information (can be doubled)" },
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
	{ 0x102, "clean-protected", "Do not create .apk-new files in "
54
				"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
	{ 0x112, "arch",	"Use architecture with --root",
71
				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
	char word[128];
	int i, j;

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

static void print_options(int num_opts, struct apk_option *opts)
{
129
	struct apk_indent indent = { .indent = 26 };
Timo Teräs's avatar
Timo Teräs committed
130 131 132 133 134 135
	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
136
		apk_print_indented_words(&indent, opts[i].help);
Timo Teräs's avatar
Timo Teräs committed
137 138 139 140 141 142
		printf("\n");
	}
}

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

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

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

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

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

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

176 177 178
	return 1;
}

179 180 181 182 183 184 185 186
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
187

188 189 190
	return NULL;
}

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

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

203
	if (strncmp(prog, "apk_", 4) == 0)
Timo Teräs's avatar
Timo Teräs committed
204 205 206 207 208 209 210 211 212 213 214 215 216 217
		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;
}

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

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

	for (i = 0; i < num; i++, opts++, ao++) {
234
		opts->name = ao->name ?: "";
Timo Teräs's avatar
Timo Teräs committed
235 236 237 238 239 240
		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
241

Timo Teräs's avatar
Timo Teräs committed
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
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();
257
	ENGINE_register_all_complete();
Timo Teräs's avatar
Timo Teräs committed
258 259 260
#endif
}

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

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

273 274 275 276 277 278 279 280 281 282 283 284
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
285 286 287 288
int main(int argc, char **argv)
{
	struct apk_applet *applet;
	char short_options[256], *sopt;
Timo Teräs's avatar
Timo Teräs committed
289
	struct option *opt, *all_options;
290
	int r, optindex, num_options;
Timo Teräs's avatar
Timo Teräs committed
291
	void *ctx = NULL;
292 293 294
	struct apk_repository_list *repo = NULL;
	struct apk_database db;
	struct apk_db_options dbopts;
295
	struct apk_string_array *args;
296 297 298 299 300 301 302 303
#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
304

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

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

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

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

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

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

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

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

453 454 455 456 457 458
#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
459
	r = apk_db_open(&db, &dbopts);
460 461 462
	if (r != 0) {
		apk_error("Failed to open apk database: %s",
			  apk_error_str(r));
463
		goto err;
464 465
	}

466 467 468 469 470 471 472 473 474 475 476 477 478 479
#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
480
		apk_blob_t spec = APK_BLOB_STR(test_repos->item[i]), name, tag;
481
		int repo_tag = 0, repo = APK_REPOSITORY_FIRST_CONFIGURED + i;
Timo Teräs's avatar
Timo Teräs committed
482 483 484 485 486

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

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

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

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

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