app_info.c 12.7 KB
Newer Older
1
/* app_info.c - Alpine Package Keeper (APK)
Natanael Copa's avatar
Natanael Copa committed
2 3
 *
 * Copyright (C) 2005-2009 Natanael Copa <n@tanael.org>
4
 * Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi>
Natanael Copa's avatar
Natanael Copa committed
5 6
 * All rights reserved.
 *
7
 * SPDX-License-Identifier: GPL-2.0-only
Natanael Copa's avatar
Natanael Copa committed
8 9 10
 */

#include <stdio.h>
11
#include <unistd.h>
12
#include <limits.h>
Natanael Copa's avatar
Natanael Copa committed
13 14
#include "apk_defines.h"
#include "apk_applet.h"
Timo Teräs's avatar
Timo Teräs committed
15
#include "apk_package.h"
Natanael Copa's avatar
Natanael Copa committed
16
#include "apk_database.h"
Natanael Copa's avatar
Natanael Copa committed
17
#include "apk_print.h"
Natanael Copa's avatar
Natanael Copa committed
18

Timo Teräs's avatar
Timo Teräs committed
19
struct info_ctx {
20
	struct apk_database *db;
21
	void (*action)(struct info_ctx *ctx, struct apk_database *db, struct apk_string_array *args);
22
	int subaction_mask;
23
	int errors;
24 25
};

26 27 28 29 30 31
/* These need to stay in sync with the function pointer array in
 * info_subaction() */
#define APK_INFO_DESC		0x01
#define APK_INFO_URL		0x02
#define APK_INFO_SIZE		0x04
#define APK_INFO_DEPENDS	0x08
32 33 34 35 36 37 38
#define APK_INFO_PROVIDES	0x10
#define APK_INFO_RDEPENDS	0x20
#define APK_INFO_CONTENTS	0x40
#define APK_INFO_TRIGGERS	0x80
#define APK_INFO_INSTALL_IF	0x100
#define APK_INFO_RINSTALL_IF	0x200
#define APK_INFO_REPLACES	0x400
39
#define APK_INFO_LICENSE	0x800
40

41 42 43 44 45 46 47 48 49 50 51
static void verbose_print_pkg(struct apk_package *pkg, int minimal_verbosity)
{
	int verbosity = apk_verbosity;
	if (verbosity < minimal_verbosity)
		verbosity = minimal_verbosity;

	if (pkg == NULL || verbosity < 1)
		return;

	printf("%s", pkg->name->name);
	if (apk_verbosity > 1)
52
		printf("-" BLOB_FMT, BLOB_PRINTF(*pkg->version));
53 54 55 56 57
	if (apk_verbosity > 2)
		printf(" - %s", pkg->description);
	printf("\n");
}

58 59
static void info_exists(struct info_ctx *ctx, struct apk_database *db,
			struct apk_string_array *args)
Timo Teräs's avatar
Timo Teräs committed
60 61
{
	struct apk_name *name;
62
	struct apk_dependency dep;
63
	struct apk_provider *p;
64
	char **parg;
65
	int ok;
Timo Teräs's avatar
Timo Teräs committed
66

67 68
	foreach_array_item(parg, args) {
		apk_blob_t b = APK_BLOB_STR(*parg);
69

70
		apk_blob_pull_dep(&b, db, &dep);
71 72 73
		if (APK_BLOB_IS_NULL(b) || b.len > 0)
			continue;

74 75
		name = dep.name;
		if (name == NULL)
76
			continue;
Timo Teräs's avatar
Timo Teräs committed
77

78
		ok = apk_dep_is_provided(&dep, NULL);
79
		foreach_array_item(p, name->providers) {
80 81 82 83
			if (!p->pkg->ipkg) continue;
			ok = apk_dep_is_provided(&dep, p);
			if (ok) verbose_print_pkg(p->pkg, 0);
			break;
Timo Teräs's avatar
Timo Teräs committed
84
		}
85
		if (!ok) ctx->errors++;
Timo Teräs's avatar
Timo Teräs committed
86
	}
Natanael Copa's avatar
Natanael Copa committed
87 88
}

89 90
static void info_who_owns(struct info_ctx *ctx, struct apk_database *db,
			  struct apk_string_array *args)
91 92
{
	struct apk_package *pkg;
Timo Teräs's avatar
Timo Teräs committed
93
	struct apk_dependency_array *deps;
94
	struct apk_dependency dep;
95
	struct apk_ostream *os;
96
	const char *via;
97 98
	char **parg, fnbuf[PATH_MAX], buf[PATH_MAX];
	apk_blob_t fn;
99
	ssize_t r;
100

Timo Teräs's avatar
Timo Teräs committed
101
	apk_dependency_array_init(&deps);
102
	foreach_array_item(parg, args) {
103 104 105 106 107
		if (*parg[0] != '/' && realpath(*parg, fnbuf))
			fn = APK_BLOB_STR(fnbuf);
		else
			fn = APK_BLOB_STR(*parg);

108
		via = "";
109
		pkg = apk_db_get_file_owner(db, fn);
110 111 112 113 114 115 116 117
		if (pkg == NULL) {
			r = readlinkat(db->root_fd, *parg, buf, sizeof(buf));
			if (r > 0 && r < PATH_MAX && buf[0] == '/') {
				pkg = apk_db_get_file_owner(db, APK_BLOB_STR(buf));
				via = "symlink target ";
			}
		}

118
		if (pkg == NULL) {
119 120
			apk_error(BLOB_FMT ": Could not find owner package",
				  BLOB_PRINTF(fn));
121
			ctx->errors++;
122
			continue;
123
		}
124

125
		if (apk_verbosity < 1) {
126 127
			dep = (struct apk_dependency) {
				.name = pkg->name,
128
				.version = apk_blob_atomize(APK_BLOB_NULL),
129
				.result_mask = APK_DEPMASK_ANY,
130 131 132
			};
			apk_deps_add(&deps, &dep);
		} else {
133 134
			printf(BLOB_FMT " %sis owned by " PKG_VER_FMT "\n",
			       BLOB_PRINTF(fn), via, PKG_VER_PRINTF(pkg));
135 136
		}
	}
Timo Teräs's avatar
Timo Teräs committed
137
	if (apk_verbosity < 1 && deps->num != 0) {
138
		os = apk_ostream_to_fd(STDOUT_FILENO);
139 140
		if (!IS_ERR_OR_NULL(os)) {
			apk_deps_write(db, deps, os, APK_BLOB_PTR_LEN(" ", 1));
141 142
			apk_ostream_write(os, "\n", 1);
			apk_ostream_close(os);
143
		}
144
	}
Timo Teräs's avatar
Timo Teräs committed
145
	apk_dependency_array_free(&deps);
146 147
}

148
static void info_print_description(struct apk_database *db, struct apk_package *pkg)
149
{
150 151 152
	if (apk_verbosity > 1)
		printf("%s: %s", pkg->name->name, pkg->description);
	else
153 154 155
		printf(PKG_VER_FMT " description:\n%s\n",
		       PKG_VER_PRINTF(pkg),
		       pkg->description);
156 157
}

158
static void info_print_url(struct apk_database *db, struct apk_package *pkg)
159
{
160 161 162
	if (apk_verbosity > 1)
		printf("%s: %s", pkg->name->name, pkg->url);
	else
163 164
		printf(PKG_VER_FMT " webpage:\n%s\n",
		       PKG_VER_PRINTF(pkg),
165
		       pkg->url);
166 167
}

168 169 170 171 172 173 174 175 176 177
static void info_print_license(struct apk_database *db, struct apk_package *pkg)
{
	if (apk_verbosity > 1)
		printf("%s: " BLOB_FMT , pkg->name->name, BLOB_PRINTF(*pkg->license));
	else
		printf(PKG_VER_FMT " license:\n" BLOB_FMT "\n",
		       PKG_VER_PRINTF(pkg),
		       BLOB_PRINTF(*pkg->license));
}

178
static void info_print_size(struct apk_database *db, struct apk_package *pkg)
Natanael Copa's avatar
Natanael Copa committed
179
{
180 181 182 183
	off_t size;
	const char *size_unit;

	size_unit = apk_get_human_size(pkg->installed_size, &size);
184
	if (apk_verbosity > 1)
185 186
		printf("%s: %lld %s", pkg->name->name,
		       (long long)size, size_unit);
187
	else
188 189
		printf(PKG_VER_FMT " installed size:\n%lld %s\n",
		       PKG_VER_PRINTF(pkg), (long long)size, size_unit);
Natanael Copa's avatar
Natanael Copa committed
190 191
}

192 193
static void info_print_dep_array(struct apk_database *db, struct apk_package *pkg,
				 struct apk_dependency_array *deps, const char *dep_text)
194
{
195
	struct apk_dependency *d;
196
	apk_blob_t separator = APK_BLOB_STR(apk_verbosity > 1 ? " " : "\n");
197
	char buf[256];
198

Timo Teräs's avatar
Timo Teräs committed
199
	if (apk_verbosity == 1)
200
		printf(PKG_VER_FMT " %s:\n", PKG_VER_PRINTF(pkg), dep_text);
201 202
	if (apk_verbosity > 1)
		printf("%s: ", pkg->name->name);
203 204 205
	foreach_array_item(d, deps) {
		apk_blob_t b = APK_BLOB_BUF(buf);
		apk_blob_push_dep(&b, db, d);
206
		apk_blob_push_blob(&b, separator);
207
		b = apk_blob_pushed(APK_BLOB_BUF(buf), b);
208 209
		fwrite(b.ptr, b.len, 1, stdout);
	}
210 211
}

212 213 214 215 216
static void info_print_depends(struct apk_database *db, struct apk_package *pkg)
{
	info_print_dep_array(db, pkg, pkg->depends, "depends on");
}

217 218
static void info_print_provides(struct apk_database *db, struct apk_package *pkg)
{
219 220
	info_print_dep_array(db, pkg, pkg->provides, "provides");
}
221

222 223 224
static void print_rdep_pkg(struct apk_package *pkg0, struct apk_dependency *dep0, struct apk_package *pkg, void *pctx)
{
	printf(PKG_VER_FMT "%s", PKG_VER_PRINTF(pkg0), apk_verbosity > 1 ? " " : "\n");
225 226
}

227
static void info_print_required_by(struct apk_database *db, struct apk_package *pkg)
228
{
229
	if (apk_verbosity == 1)
230
		printf(PKG_VER_FMT " is required by:\n", PKG_VER_PRINTF(pkg));
231 232
	if (apk_verbosity > 1)
		printf("%s: ", pkg->name->name);
233 234 235 236
	apk_pkg_foreach_reverse_dependency(
		pkg,
		APK_FOREACH_INSTALLED | APK_DEP_SATISFIES | apk_foreach_genid(),
		print_rdep_pkg, NULL);
237 238
}

239
static void info_print_install_if(struct apk_database *db, struct apk_package *pkg)
240
{
241
	info_print_dep_array(db, pkg, pkg->install_if, "has auto-install rule");
242 243
}

244
static void info_print_rinstall_if(struct apk_database *db, struct apk_package *pkg)
245
{
246
	int i, j;
247 248 249 250 251 252 253 254
	char *separator = apk_verbosity > 1 ? " " : "\n";

	if (apk_verbosity == 1)
		printf(PKG_VER_FMT " affects auto-installation of:\n",
		       PKG_VER_PRINTF(pkg));
	if (apk_verbosity > 1)
		printf("%s: ", pkg->name->name);
	for (i = 0; i < pkg->name->rinstall_if->num; i++) {
255 256
		struct apk_name *name0;
		struct apk_package *pkg0;
257 258 259

		/* Check only the package that is installed, and that
		 * it actually has this package in install_if. */
260 261 262 263
		name0 = pkg->name->rinstall_if->item[i];
		pkg0 = apk_pkg_get_installed(name0);
		if (pkg0 == NULL)
			continue;
264

265 266
		for (j = 0; j < pkg0->install_if->num; j++) {
			if (pkg0->install_if->item[j].name != pkg->name)
267
				continue;
268 269 270 271
			printf(PKG_VER_FMT "%s",
			       PKG_VER_PRINTF(pkg0),
			       separator);
			break;
272 273 274 275
		}
	}
}

276
static void info_print_contents(struct apk_database *db, struct apk_package *pkg)
277
{
278 279 280 281 282 283
	struct apk_installed_package *ipkg = pkg->ipkg;
	struct apk_db_dir_instance *diri;
	struct apk_db_file *file;
	struct hlist_node *dc, *dn, *fc, *fn;

	if (apk_verbosity == 1)
284 285
		printf(PKG_VER_FMT " contains:\n",
		       PKG_VER_PRINTF(pkg));
286 287 288 289 290 291 292

	hlist_for_each_entry_safe(diri, dc, dn, &ipkg->owned_dirs,
				  pkg_dirs_list) {
		hlist_for_each_entry_safe(file, fc, fn, &diri->owned_files,
					  diri_files_list) {
			if (apk_verbosity > 1)
				printf("%s: ", pkg->name->name);
293
			printf(DIR_FILE_FMT "\n", DIR_FILE_PRINTF(diri->dir, file));
294 295
		}
	}
296 297
}

298
static void info_print_triggers(struct apk_database *db, struct apk_package *pkg)
299
{
300
	struct apk_installed_package *ipkg = pkg->ipkg;
301
	char **trigger;
302 303

	if (apk_verbosity == 1)
304 305
		printf(PKG_VER_FMT " triggers:\n",
		       PKG_VER_PRINTF(pkg));
306

307
	foreach_array_item(trigger, ipkg->triggers) {
308 309
		if (apk_verbosity > 1)
			printf("%s: trigger ", pkg->name->name);
310
		printf("%s\n", *trigger);
311
	}
312 313
}

314
static void info_print_replaces(struct apk_database *db, struct apk_package *pkg)
315
{
316
	info_print_dep_array(db, pkg, pkg->ipkg->replaces, "replaces");
317 318
}

319
static void info_subaction(struct info_ctx *ctx, struct apk_package *pkg)
320
{
321
	typedef void (*subaction_t)(struct apk_database *, struct apk_package *);
322 323 324 325 326
	static subaction_t subactions[] = {
		info_print_description,
		info_print_url,
		info_print_size,
		info_print_depends,
327
		info_print_provides,
328 329 330
		info_print_required_by,
		info_print_contents,
		info_print_triggers,
331 332
		info_print_install_if,
		info_print_rinstall_if,
333
		info_print_replaces,
334
		info_print_license,
335 336
	};
	const int requireipkg =
337
		APK_INFO_CONTENTS | APK_INFO_TRIGGERS | APK_INFO_RDEPENDS |
338
		APK_INFO_RINSTALL_IF | APK_INFO_REPLACES;
339 340 341 342 343 344 345 346 347
	int i;

	for (i = 0; i < ARRAY_SIZE(subactions); i++) {
		if (!(BIT(i) & ctx->subaction_mask))
			continue;

		if (pkg->ipkg == NULL && (BIT(i) & requireipkg))
			continue;

348
		subactions[i](ctx->db, pkg);
349 350
		puts("");
	}
351
}
352

353
static void print_name_info(struct apk_database *db, const char *match, struct apk_name *name, void *pctx)
354
{
355
	struct info_ctx *ctx = (struct info_ctx *) pctx;
356
	struct apk_provider *p;
357

358 359 360 361 362
	if (name == NULL) {
		ctx->errors++;
		return;
	}

363 364
	foreach_array_item(p, name->providers)
		info_subaction(ctx, p->pkg);
365 366
}

367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
enum {
	OPT_INFO_all,
	OPT_INFO_contents,
	OPT_INFO_depends,
	OPT_INFO_description,
	OPT_INFO_install_if,
	OPT_INFO_installed,
	OPT_INFO_license,
	OPT_INFO_provides,
	OPT_INFO_rdepends,
	OPT_INFO_replaces,
	OPT_INFO_rinstall_if,
	OPT_INFO_size,
	OPT_INFO_triggers,
	OPT_INFO_webpage,
	OPT_INFO_who_owns,
};

static const char option_desc[] =
	APK_OPTAPPLET
	APK_OPT2n("all", "a")
	APK_OPT2n("contents", "L")
	APK_OPT2n("depends", "R")
	APK_OPT2n("description", "d")
	APK_OPT1n("install-if")
	APK_OPT2n("installed", "e")
	APK_OPT1n("license")
	APK_OPT2n("provides", "P")
	APK_OPT2n("rdepends", "r")
	APK_OPT1n("replaces")
	APK_OPT1n("rinstall-if")
	APK_OPT2n("size", "s")
	APK_OPT2n("triggers", "t")
	APK_OPT2n("webpage", "w")
	APK_OPT2n("who-owns", "W");

static int option_parse_applet(void *pctx, struct apk_db_options *dbopts, int opt, const char *optarg)
Timo Teräs's avatar
Timo Teräs committed
404
{
405
	struct info_ctx *ctx = (struct info_ctx *) pctx;
Timo Teräs's avatar
Timo Teräs committed
406

407
	ctx->action = NULL;
408 409
	switch (opt) {
	case OPT_INFO_installed:
410
		ctx->action = info_exists;
411
		dbopts->open_flags |= APK_OPENF_NO_REPOS;
Timo Teräs's avatar
Timo Teräs committed
412
		break;
413
	case OPT_INFO_who_owns:
414
		ctx->action = info_who_owns;
415
		dbopts->open_flags |= APK_OPENF_NO_REPOS;
416
		break;
417
	case OPT_INFO_webpage:
418
		ctx->subaction_mask |= APK_INFO_URL;
419
		break;
420
	case OPT_INFO_depends:
421
		ctx->subaction_mask |= APK_INFO_DEPENDS;
422
		break;
423
	case OPT_INFO_provides:
424
		ctx->subaction_mask |= APK_INFO_PROVIDES;
425
		break;
426
	case OPT_INFO_rdepends:
427
		ctx->subaction_mask |= APK_INFO_RDEPENDS;
428
		break;
429
	case OPT_INFO_install_if:
430
		ctx->subaction_mask |= APK_INFO_INSTALL_IF;
431
		break;
432
	case OPT_INFO_rinstall_if:
433
		ctx->subaction_mask |= APK_INFO_RINSTALL_IF;
434
		break;
435
	case OPT_INFO_size:
436
		ctx->subaction_mask |= APK_INFO_SIZE;
437
		break;
438
	case OPT_INFO_description:
439
		ctx->subaction_mask |= APK_INFO_DESC;
440
		break;
441
	case OPT_INFO_contents:
442
		ctx->subaction_mask |= APK_INFO_CONTENTS;
443
		break;
444
	case OPT_INFO_triggers:
445
		ctx->subaction_mask |= APK_INFO_TRIGGERS;
446
		break;
447
	case OPT_INFO_replaces:
448
		ctx->subaction_mask |= APK_INFO_REPLACES;
449
		break;
450
	case OPT_INFO_license:
451 452
		ctx->subaction_mask |= APK_INFO_LICENSE;
		break;
453
	case OPT_INFO_all:
454
		ctx->subaction_mask = 0xffffffff;
455
		break;
Timo Teräs's avatar
Timo Teräs committed
456
	default:
457
		return -ENOTSUP;
Timo Teräs's avatar
Timo Teräs committed
458 459 460 461
	}
	return 0;
}

462
static int info_main(void *ctx, struct apk_database *db, struct apk_string_array *args)
Timo Teräs's avatar
Timo Teräs committed
463 464
{
	struct info_ctx *ictx = (struct info_ctx *) ctx;
465
	struct apk_installed_package *ipkg;
Timo Teräs's avatar
Timo Teräs committed
466

467
	ictx->db = db;
468 469
	if (ictx->subaction_mask == 0)
		ictx->subaction_mask = APK_INFO_DESC | APK_INFO_URL | APK_INFO_SIZE;
470 471 472
	if (ictx->action != NULL) {
		ictx->action(ictx, db, args);
	} else if (args->num > 0) {
473
		/* Print info on given names */
474 475 476
		apk_name_foreach_matching(
			db, args, APK_FOREACH_NULL_MATCHES_ALL | apk_foreach_genid(),
			print_name_info, ctx);
477 478 479 480 481
	} else {
		/* Print all installed packages */
		list_for_each_entry(ipkg, &db->installed.packages, installed_pkgs_list)
			verbose_print_pkg(ipkg->pkg, 1);
	}
482

483
	return ictx->errors;
Timo Teräs's avatar
Timo Teräs committed
484 485
}

486
static const struct apk_option_group optgroup_applet = {
487
	.desc = option_desc,
488 489 490
	.parse = option_parse_applet,
};

Natanael Copa's avatar
Natanael Copa committed
491 492
static struct apk_applet apk_info = {
	.name = "info",
493
	.open_flags = APK_OPENF_READ,
Timo Teräs's avatar
Timo Teräs committed
494
	.context_size = sizeof(struct info_ctx),
495
	.optgroups = { &optgroup_global, &optgroup_applet },
Natanael Copa's avatar
Natanael Copa committed
496 497 498 499 500
	.main = info_main,
};

APK_DEFINE_APPLET(apk_info);