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
}