apk.c 9.88 KB
Newer Older
1 2 3 4 5 6
/* apk.c - Alpine Package Keeper (APK)
 *
 * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
 * Copyright (C) 2008 Timo Teräs <timo.teras@iki.fi>
 * 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
char **apk_argv;
Timo Teräs's avatar
Timo Teräs committed
36
int apk_screen_width;
37

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
	{ 0x110, "no-progress",	"Disable progress bar even for TTYs" },
52 53
	{ 0x102, "clean-protected", "Do not create .apk-new files to "
				"configuration dirs" },
54 55
	{ 0x106, "purge",	"Delete also modified configuration files on "
				"package removal" },
Timo Teräs's avatar
Timo Teräs committed
56 57
	{ 0x103, "allow-untrusted", "Blindly install packages with untrusted "
				"signatures or no signature at all" },
58 59 60 61 62
	{ 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" },
63 64 65
	{ 0x107, "keys-dir",	"Override directory of trusted keys",
				required_argument, "KEYSDIR" },
	{ 0x108, "repositories-file", "Override repositories file",
66 67
				required_argument, "REPOFILE" },
	{ 0x109, "no-network",	"Do not use network (cache is still used)" },
Timo Teräs's avatar
Timo Teräs committed
68
	{ 0x111, "overlay-from-stdin", "Read list of overlay files from stdin" },
69 70
	{ 0x112, "arch", 	"Use architecture with --root",
				required_argument, "ARCH" },
Timo Teräs's avatar
Timo Teräs committed
71 72 73
};

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

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

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

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

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

140 141
		printf("\nThe following commands are available:\n");
		for (a = &__start_apkapplets; a < &__stop_apkapplets; a++) {
Timo Teräs's avatar
Timo Teräs committed
142
			struct apk_indent indent = { 0, 26 };
143

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

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

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

167 168 169
	return 1;
}

170 171 172 173 174 175 176 177
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
178

179 180 181
	return NULL;
}

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

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

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

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

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

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

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

Timo Teräs's avatar
Timo Teräs committed
257 258
static void setup_terminal(void)
{
Timo Teräs's avatar
Timo Teräs committed
259 260 261
	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
262
	signal(SIGWINCH, on_sigwinch);
Timo Teräs's avatar
Timo Teräs committed
263 264
}

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

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

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

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

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

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

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

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

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

403
	r = apk_db_open(&db, &dbopts);
404 405 406 407 408 409 410 411 412
	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
413
	if (r == -EINVAL)
Timo Teräs's avatar
Timo Teräs committed
414 415
		return usage(applet);
	return r;
416
}