apk.c 9.86 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

35 36
char **apk_argv;

Timo Teräs's avatar
Timo Teräs committed
37 38 39
static struct apk_option generic_options[] = {
	{ 'h', "help",		"Show generic help or applet specific help" },
	{ 'p', "root",		"Install packages to DIR",
40
				required_argument, "DIR" },
Timo Teräs's avatar
Timo Teräs committed
41
	{ 'X', "repository",	"Use packages from REPO",
42
				required_argument, "REPO" },
Timo Teräs's avatar
Timo Teräs committed
43 44
	{ 'q', "quiet",		"Print less information" },
	{ 'v', "verbose",	"Print more information" },
45
	{ 'i', "interactive",	"Ask confirmation for certain operations" },
Timo Teräs's avatar
Timo Teräs committed
46 47
	{ 'V', "version",	"Print program version and exit" },
	{ 'f', "force",		"Do what was asked even if it looks dangerous" },
48
	{ 'U', "update-cache",	"Update the repository cache" },
Timo Teräs's avatar
Timo Teräs committed
49
	{ 0x101, "progress",	"Show a progress bar" },
50
	{ 0x110, "no-progress",	"Disable progress bar even for TTYs" },
51 52
	{ 0x102, "clean-protected", "Do not create .apk-new files to "
				"configuration dirs" },
53 54
	{ 0x106, "purge",	"Delete also modified configuration files on "
				"package removal" },
Timo Teräs's avatar
Timo Teräs committed
55 56
	{ 0x103, "allow-untrusted", "Blindly install packages with untrusted "
				"signatures or no signature at all" },
57 58 59 60 61
	{ 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" },
62 63 64
	{ 0x107, "keys-dir",	"Override directory of trusted keys",
				required_argument, "KEYSDIR" },
	{ 0x108, "repositories-file", "Override repositories file",
65 66
				required_argument, "REPOFILE" },
	{ 0x109, "no-network",	"Do not use network (cache is still used)" },
Timo Teräs's avatar
Timo Teräs committed
67
	{ 0x111, "overlay-from-stdin", "Read list of overlay files from stdin" },
68 69
	{ 0x112, "arch", 	"Use architecture with --root",
				required_argument, "ARCH" },
Timo Teräs's avatar
Timo Teräs committed
70 71 72
};

static int version(void)
Natanael Copa's avatar
Natanael Copa committed
73
{
74
	printf("apk-tools " APK_VERSION ", compiled for " APK_DEFAULT_ARCH ".\n");
Natanael Copa's avatar
Natanael Copa committed
75 76
	return 0;
}
77

Timo Teräs's avatar
Timo Teräs committed
78 79
static int format_option(char *buf, size_t len, struct apk_option *o,
			 const char *separator)
Natanael Copa's avatar
Natanael Copa committed
80
{
Timo Teräs's avatar
Timo Teräs committed
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
	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)
{
99
	struct apk_indent indent = { .indent = 11 };
Timo Teräs's avatar
Timo Teräs committed
100 101 102 103 104 105 106 107 108
	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
109
		apk_print_indented(&indent, APK_BLOB_PTR_LEN(word, j));
Timo Teräs's avatar
Timo Teräs committed
110 111
	}
	if (args != NULL)
Timo Teräs's avatar
Timo Teräs committed
112
		apk_print_indented(&indent, APK_BLOB_STR(args));
Timo Teräs's avatar
Timo Teräs committed
113 114 115 116 117
	printf("\n");
}

static void print_options(int num_opts, struct apk_option *opts)
{
118
	struct apk_indent indent = { .indent = 26 };
Timo Teräs's avatar
Timo Teräs committed
119 120 121 122 123 124
	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
125
		apk_print_indented_words(&indent, opts[i].help);
Timo Teräs's avatar
Timo Teräs committed
126 127 128 129 130 131
		printf("\n");
	}
}

static int usage(struct apk_applet *applet)
{
Natanael Copa's avatar
Natanael Copa committed
132
	version();
Timo Teräs's avatar
Timo Teräs committed
133 134 135 136 137 138
	if (applet == NULL) {
		struct apk_applet **a;

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

139 140
		printf("\nThe following commands are available:\n");
		for (a = &__start_apkapplets; a < &__stop_apkapplets; a++) {
141
			struct apk_indent indent = { .indent = 26 };
142

Timo Teräs's avatar
Timo Teräs committed
143 144
			indent.x = printf("  %-*s", indent.indent - 3, (*a)->name);
			apk_print_indented_words(&indent, (*a)->help);
145 146
			printf("\n");
		}
Timo Teräs's avatar
Timo Teräs committed
147
	} else {
148
		struct apk_indent indent = { .indent = 2 };
Timo Teräs's avatar
Timo Teräs committed
149

Timo Teräs's avatar
Timo Teräs committed
150 151
		print_usage(applet->name, applet->arguments,
			    applet->num_options, applet->options);
152
		printf("\nDescription:\n");
Timo Teräs's avatar
Timo Teräs committed
153
		apk_print_indented_words(&indent, applet->help);
Timo Teräs's avatar
Timo Teräs committed
154
		printf("\n");
Timo Teräs's avatar
Timo Teräs committed
155
	}
156
	printf("\nGeneric options:\n");
Timo Teräs's avatar
Timo Teräs committed
157 158 159
	print_options(ARRAY_SIZE(generic_options), generic_options);

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

165 166 167
	return 1;
}

168 169 170 171 172 173 174 175
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
176

177 178 179
	return NULL;
}

Timo Teräs's avatar
Timo Teräs committed
180
static struct apk_applet *deduce_applet(int argc, char **argv)
181
{
Timo Teräs's avatar
Timo Teräs committed
182
	struct apk_applet *a;
183
	const char *prog;
Timo Teräs's avatar
Timo Teräs committed
184
	int i;
185 186 187 188 189 190 191

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

192
	if (strncmp(prog, "apk_", 4) == 0)
Timo Teräs's avatar
Timo Teräs committed
193 194 195 196 197 198 199 200 201 202 203 204 205 206
		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;
}

207
static struct apk_repository_list *apk_repository_new(const char *url)
208
{
209 210
	struct apk_repository_list *r = calloc(1,
			sizeof(struct apk_repository_list));
211 212 213 214 215 216 217
	if (r) {
		r->url = url;
		list_init(&r->list);
	}
	return r;
}

Timo Teräs's avatar
Timo Teräs committed
218 219 220 221 222 223 224 225 226 227 228 229
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
230

Timo Teräs's avatar
Timo Teräs committed
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
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();
246
	ENGINE_register_all_complete();
Timo Teräs's avatar
Timo Teräs committed
247 248 249
#endif
}

Timo Teräs's avatar
Timo Teräs committed
250
static void on_sigwinch(int s)
Timo Teräs's avatar
Timo Teräs committed
251
{
Timo Teräs's avatar
Timo Teräs committed
252 253
	apk_reset_screen_width();
}
Timo Teräs's avatar
Timo Teräs committed
254

Timo Teräs's avatar
Timo Teräs committed
255 256
static void setup_terminal(void)
{
Timo Teräs's avatar
Timo Teräs committed
257 258 259
	setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
	if (isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) && isatty(STDIN_FILENO))
		apk_flags |= APK_PROGRESS;
Timo Teräs's avatar
Timo Teräs committed
260
	signal(SIGWINCH, on_sigwinch);
Timo Teräs's avatar
Timo Teräs committed
261 262
}

Timo Teräs's avatar
Timo Teräs committed
263 264 265 266
int main(int argc, char **argv)
{
	struct apk_applet *applet;
	char short_options[256], *sopt;
Timo Teräs's avatar
Timo Teräs committed
267
	struct option *opt, *all_options;
268
	int r, optindex, num_options;
Timo Teräs's avatar
Timo Teräs committed
269
	void *ctx = NULL;
270 271 272
	struct apk_repository_list *repo = NULL;
	struct apk_database db;
	struct apk_db_options dbopts;
273

274
	apk_argv = malloc(sizeof(char*[argc+2]));
275 276
	memcpy(apk_argv, argv, sizeof(char*[argc]));
	apk_argv[argc] = NULL;
277
	apk_argv[argc+1] = NULL;
278

279 280
	memset(&dbopts, 0, sizeof(dbopts));
	list_init(&dbopts.repository_list);
281
	apk_atom_init();
Timo Teräs's avatar
Timo Teräs committed
282
	umask(0);
Timo Teräs's avatar
Timo Teräs committed
283
	setup_terminal();
284

Timo Teräs's avatar
Timo Teräs committed
285
	applet = deduce_applet(argc, argv);
Timo Teräs's avatar
Timo Teräs committed
286 287 288 289 290 291
	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
292
	if (applet != NULL) {
Timo Teräs's avatar
Timo Teräs committed
293 294
		merge_options(&all_options[ARRAY_SIZE(generic_options)],
			      applet->options, applet->num_options);
Natanael Copa's avatar
Natanael Copa committed
295 296
		if (applet->context_size != 0)
			ctx = calloc(1, applet->context_size);
297 298
		dbopts.open_flags = applet->open_flags;
		apk_flags |= applet->forced_flags;
Timo Teräs's avatar
Timo Teräs committed
299
	}
Natanael Copa's avatar
Natanael Copa committed
300

Timo Teräs's avatar
Timo Teräs committed
301
	for (opt = all_options, sopt = short_options; opt->name != NULL; opt++) {
302 303
		if (opt->flag == NULL &&
		    opt->val <= 0xff && isalnum(opt->val)) {
Timo Teräs's avatar
Timo Teräs committed
304 305 306 307 308 309
			*(sopt++) = opt->val;
			if (opt->has_arg != no_argument)
				*(sopt++) = ':';
		}
	}

Timo Teräs's avatar
Timo Teräs committed
310 311
	init_openssl();

Timo Teräs's avatar
Timo Teräs committed
312 313
	optindex = 0;
	while ((r = getopt_long(argc, argv, short_options,
Timo Teräs's avatar
Timo Teräs committed
314
				all_options, &optindex)) != -1) {
315
		switch (r) {
Timo Teräs's avatar
Timo Teräs committed
316 317
		case 0:
			break;
Natanael Copa's avatar
Natanael Copa committed
318 319 320
		case 'h':
			return usage(applet);
			break;
Natanael Copa's avatar
Natanael Copa committed
321
		case 'p':
322 323 324 325 326 327 328
			dbopts.root = optarg;
			break;
		case 0x107:
			dbopts.keys_dir = optarg;
			break;
		case 0x108:
			dbopts.repositories_file = optarg;
329 330
			break;
		case 'X':
331 332
			repo = apk_repository_new(optarg);
			if (repo)
333
				list_add(&repo->list, &dbopts.repository_list);
334
			break;
335
		case 'q':
336 337 338 339
			apk_verbosity--;
			break;
		case 'v':
			apk_verbosity++;
340
			break;
Natanael Copa's avatar
Natanael Copa committed
341 342
		case 'V':
			return version();
343 344 345
		case 'f':
			apk_flags |= APK_FORCE;
			break;
346 347 348
		case 'i':
			apk_flags |= APK_INTERACTIVE;
			break;
349 350 351
		case 'U':
			apk_flags |= APK_UPDATE_CACHE;
			break;
352 353 354
		case 0x101:
			apk_flags |= APK_PROGRESS;
			break;
355 356 357
		case 0x110:
			apk_flags &= ~APK_PROGRESS;
			break;
358 359 360
		case 0x102:
			apk_flags |= APK_CLEAN_PROTECTED;
			break;
Timo Teräs's avatar
Timo Teräs committed
361 362 363
		case 0x103:
			apk_flags |= APK_ALLOW_UNTRUSTED;
			break;
364 365
		case 0x104:
			apk_flags |= APK_SIMULATE;
Natanael Copa's avatar
Natanael Copa committed
366
			break;
367 368 369
		case 0x106:
			apk_flags |= APK_PURGE;
			break;
370
		case 0x105:
371
			dbopts.lock_wait = atoi(optarg);
372
			break;
373 374 375
		case 0x109:
			apk_flags |= APK_NO_NETWORK;
			break;
Timo Teräs's avatar
Timo Teräs committed
376 377 378
		case 0x111:
			apk_flags |= APK_OVERLAY_FROM_STDIN;
			break;
379 380 381
		case 0x112:
			dbopts.arch = optarg;
			break;
382
		default:
Natanael Copa's avatar
Natanael Copa committed
383
			if (applet == NULL || applet->parse == NULL ||
384
			    applet->parse(ctx, &dbopts, r,
Timo Teräs's avatar
Timo Teräs committed
385
					  optindex - ARRAY_SIZE(generic_options),
Timo Teräs's avatar
Timo Teräs committed
386
					  optarg) != 0)
Natanael Copa's avatar
Natanael Copa committed
387
				return usage(applet);
Timo Teräs's avatar
Timo Teräs committed
388
			break;
389 390 391
		}
	}

Natanael Copa's avatar
Natanael Copa committed
392
	if (applet == NULL)
Natanael Copa's avatar
Natanael Copa committed
393
		return usage(NULL);
Natanael Copa's avatar
Natanael Copa committed
394

Timo Teräs's avatar
Timo Teräs committed
395 396 397
	argc -= optind;
	argv += optind;
	if (argc >= 1 && strcmp(argv[0], applet->name) == 0) {
398 399 400 401
		argc--;
		argv++;
	}

402
	r = apk_db_open(&db, &dbopts);
403 404 405 406 407 408 409 410 411
	if (r != 0) {
		apk_error("Failed to open apk database: %s",
			  apk_error_str(r));
		return r;
	}

	r = applet->main(ctx, &db, argc, argv);
	apk_db_close(&db);

Timo Teräs's avatar
Timo Teräs committed
412
	if (r == -EINVAL)
Timo Teräs's avatar
Timo Teräs committed
413 414
		return usage(applet);
	return r;
415
}