list.c 5.96 KB
Newer Older
William Pitcock's avatar
William Pitcock committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/* list.c - Alpine Package Keeper (APK)
 *
 * Copyright (C) 2005-2009 Natanael Copa <n@tanael.org>
 * Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi>
 * Copyright (C) 2018 William Pitcock <nenolod@dereferenced.org>
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * 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>
#include <unistd.h>
#include <limits.h>
#include "apk_defines.h"
#include "apk_applet.h"
#include "apk_package.h"
#include "apk_database.h"
#include "apk_print.h"

struct list_ctx {
	unsigned int installed : 1;
	unsigned int orphaned : 1;
	unsigned int available : 1;
	unsigned int upgradable : 1;
	unsigned int match_origin : 1;
28
	unsigned int match_depends : 1;
29
	unsigned int match_providers : 1;
William Pitcock's avatar
William Pitcock committed
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

	struct apk_string_array *filters;
};

static int origin_matches(const struct list_ctx *ctx, const struct apk_package *pkg)
{
	char **pmatch;

	if (pkg->origin == NULL)
		return 0;

	foreach_array_item(pmatch, ctx->filters)
	{
		if (apk_blob_compare(APK_BLOB_STR(*pmatch), *pkg->origin) == 0)
			return 1;
	}

	return 0;
}

static int is_orphaned(const struct apk_name *name)
{
	struct apk_provider *p;
	unsigned int repos = 0;

	if (name == NULL)
		return 0;

	foreach_array_item(p, name->providers)
		repos |= p->pkg->repos;

	/* repo 1 is always installed-db, so if other bits are set it means the package is available somewhere
	 * (either cache or in a proper repo)
	 */
	return (repos & ~BIT(1)) == 0;
}

/* returns the currently installed package if there is a newer package that satisfies `name` */
static const struct apk_package *is_upgradable(struct apk_name *name, const struct apk_package *pkg0)
{
	struct apk_provider *p;
	struct apk_package *ipkg;
	apk_blob_t *latest = apk_blob_atomize(APK_BLOB_STR(""));

	if (name == NULL)
		return NULL;

	ipkg = apk_pkg_get_installed(name);
	if (ipkg == NULL)
		return NULL;

	if (pkg0 == NULL)
	{
		foreach_array_item(p, name->providers)
		{
			pkg0 = p->pkg;
			int r;

			if (pkg0 == ipkg)
				continue;

			r = apk_version_compare_blob(*pkg0->version, *latest);
			if (r == APK_VERSION_GREATER)
				latest = pkg0->version;
		}
	}
	else
		latest = pkg0->version;

	return apk_version_compare_blob(*ipkg->version, *latest) == APK_VERSION_LESS ? ipkg : NULL;
}

static void print_package(const struct apk_package *pkg, const struct list_ctx *ctx)
{
104 105 106 107 108 109 110 111 112
	printf(PKG_VER_FMT " " BLOB_FMT " ",
		PKG_VER_PRINTF(pkg), BLOB_PRINTF(*pkg->arch));

	if (pkg->origin != NULL)
		printf("{" BLOB_FMT "}", BLOB_PRINTF(*pkg->origin));
	else
		printf("{%s}", pkg->name->name);

	printf(" (" BLOB_FMT ")", BLOB_PRINTF(*pkg->license));
William Pitcock's avatar
William Pitcock committed
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

	if (pkg->ipkg)
		printf(" [installed]");
	else
	{
		const struct apk_package *u;

		u = is_upgradable(pkg->name, pkg);
		if (u != NULL)
			printf(" [upgradable from: " PKG_VER_FMT "]", PKG_VER_PRINTF(u));
	}


	if (apk_verbosity > 1)
	{
		printf("\n  %s\n", pkg->description);
		if (apk_verbosity > 2)
			printf("  <%s>\n", pkg->url);
	}

	printf("\n");
}

136
static void filter_package(const struct apk_package *pkg, const struct list_ctx *ctx)
William Pitcock's avatar
William Pitcock committed
137
{
138 139
	if (ctx->match_origin && !origin_matches(ctx, pkg))
		return;
William Pitcock's avatar
William Pitcock committed
140

141
	if (ctx->installed && pkg->ipkg == NULL)
William Pitcock's avatar
William Pitcock committed
142 143
		return;

144 145
	if (ctx->orphaned && !is_orphaned(pkg->name))
		return;
William Pitcock's avatar
William Pitcock committed
146

147 148
	if (ctx->available && pkg->repos == BIT(1))
		return;
William Pitcock's avatar
William Pitcock committed
149

150 151
	if (ctx->upgradable && !is_upgradable(pkg->name, pkg))
		return;
William Pitcock's avatar
William Pitcock committed
152

153 154
	print_package(pkg, ctx);
}
William Pitcock's avatar
William Pitcock committed
155

156 157 158 159 160 161
static void iterate_providers(const struct apk_name *name, const struct list_ctx *ctx)
{
	struct apk_provider *p;

	foreach_array_item(p, name->providers)
	{
162
		if (!ctx->match_providers && p->pkg->name != name)
163 164
			continue;

165 166 167
		if (ctx->match_providers)
			printf("<%s> ", name->name);

168 169 170 171
		filter_package(p->pkg, ctx);
	}
}

172 173 174
static void print_result(struct apk_database *db, const char *match, struct apk_name *name, void *pctx)
{
	struct list_ctx *ctx = pctx;
William Pitcock's avatar
William Pitcock committed
175

176 177
	if (name == NULL)
		return;
William Pitcock's avatar
William Pitcock committed
178

179 180 181 182 183 184 185 186 187
	if (ctx->match_depends)
	{
		struct apk_name **pname;

		foreach_array_item(pname, name->rdepends)
			iterate_providers(*pname, ctx);
	}
	else
		iterate_providers(name, ctx);
William Pitcock's avatar
William Pitcock committed
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
}

static int option_parse_applet(void *pctx, struct apk_db_options *dbopts, int optch, const char *optarg)
{
	struct list_ctx *ctx = pctx;

	switch (optch)
	{
	case 'I':
		ctx->installed = 1;
		break;
	case 'O':
		ctx->installed = 1;
		ctx->orphaned = 1;
		break;
	case 'u':
		ctx->available = 1;
		ctx->orphaned = 0;
		ctx->installed = 0;
		ctx->upgradable = 1;
		break;
	case 'a':
		ctx->available = 1;
		ctx->orphaned = 0;
		break;
	case 'o':
		ctx->match_origin = 1;
		break;
216 217
	case 'd':
		ctx->match_depends = 1;
218
		break;
219 220
	case 'P':
		ctx->match_providers = 1;
221
		break;
William Pitcock's avatar
William Pitcock committed
222 223 224 225 226 227 228 229 230 231 232 233 234
	default:
		return -1;
	}

	return 0;
}

static const struct apk_option options_applet[] = {
	{ 'I', "installed", "List installed packages only" },
	{ 'O', "orphaned", "List orphaned packages only" },
	{ 'a', "available", "List available packages only" },
	{ 'u', "upgradable", "List upgradable packages only" },
	{ 'o', "origin", "List packages by origin" },
235
	{ 'd', "depends", "List packages by dependency" },
236
	{ 'P', "providers", "List packages by provider" },
William Pitcock's avatar
William Pitcock committed
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
};

static const struct apk_option_group optgroup_applet = {
	.name = "List",
	.options = options_applet,
	.num_options = ARRAY_SIZE(options_applet),
	.parse = option_parse_applet,
};

static int list_main(void *pctx, struct apk_database *db, struct apk_string_array *args)
{
	struct list_ctx *ctx = pctx;

	ctx->filters = args;

	if (ctx->match_origin)
		args = NULL;

	apk_name_foreach_matching(
		db, args, APK_FOREACH_NULL_MATCHES_ALL | apk_foreach_genid(),
		print_result, ctx);

	return 0;
}

static struct apk_applet apk_list = {
	.name = "list",
	.help = "List packages by PATTERN and other criteria",
	.arguments = "PATTERN",
	.open_flags = APK_OPENF_READ,
	.command_groups = APK_COMMAND_GROUP_QUERY,
	.context_size = sizeof(struct list_ctx),
	.optgroups = { &optgroup_global, &optgroup_applet },
	.main = list_main,
};

APK_DEFINE_APPLET(apk_list);