/*
 * Copyright 2005-2018 Gentoo Foundation
 * Distributed under the terms of the GNU General Public License v2
 *
 * Copyright 2006 Thomas A. Cort - <tcort@gentoo.org>
 */

#ifdef APPLET_qcache

/********************************************************************/
/* Required portage-utils stuff                                     */
/********************************************************************/

#define QCACHE_FLAGS "p:c:idtans" COMMON_FLAGS
static struct option const qcache_long_opts[] = {
	{"matchpkg", a_argument, NULL, 'p'},
	{"matchcat", a_argument, NULL, 'c'},
	{"imlate",  no_argument, NULL, 'i'},
	{"dropped", no_argument, NULL, 'd'},
	{"testing", no_argument, NULL, 't'},
	{"stats",   no_argument, NULL, 's'},
	{"all",     no_argument, NULL, 'a'},
	{"not",     no_argument, NULL, 'n'},
	COMMON_LONG_OPTS
};
static const char * const qcache_opts_help[] = {
	"match pkgname",
	"match catname",
	"list packages that can be marked stable on a given arch",
	"list packages that have dropped keywords on a version bump on a given arch",
	"list packages that have ~arch versions, but no stable versions on a given arch",
	"display statistics about the portage tree",
	"list packages that have at least one version keyworded for on a given arch",
	"list packages that aren't keyworded on a given arch.",
	COMMON_OPTS_HELP
};
#define qcache_usage(ret) usage(ret, QCACHE_FLAGS, qcache_long_opts, qcache_opts_help, NULL, lookup_applet_idx("qcache"))

/********************************************************************/
/* Structs                                                          */
/********************************************************************/

typedef struct {
	const char *overlay;
	const char *category;
	const char *package;
	const char *ebuild;
	portage_cache *cache_data;
	unsigned char cur;
	unsigned char num;
} qcache_data;

/********************************************************************/
/* Global Variables                                                 */
/********************************************************************/

static queue *arches;
static int archlist_count;
static size_t arch_longest_len;
const char status[3] = {'-', '~', '+'};
int qcache_skip, qcache_test_arch;
char *qcache_matchpkg = NULL, *qcache_matchcat = NULL;

/********************************************************************/
/* Enumerations                                                     */
/********************************************************************/

enum { none = 0, testing, stable, minus };

/********************************************************************/
/* Keyword functions                                                */
/********************************************************************/

/*
 * int decode_status(char c);
 *
 * Decode keyword status
 *
 * IN:
 *  char c - status to check
 * OUT:
 *  int - one of the following enum { none = 0, testing, stable, minus };
 */
static int
decode_status(char c)
{
	switch (c) {
		case '-': return minus;
		case '~': return testing;
		default:  return stable;
	}
}

/*
 * int decode_arch(const char *arch);
 *
 * Decode the architecture string
 *
 * IN:
 *  const char *arch - name of an arch (alpha, amd64, ...)
 * OUT:
 *  int pos - location of arch in archlist[]
 */
static int
decode_arch(const char *arch)
{
	queue *q = arches;
	int a;
	const char *p;

	p = arch;
	if (*p == '~' || *p == '-')
		p++;

	a = 0;
	while (q) {
		if (strcmp(q->name, p) == 0)
			return a;
		++a;
		q = q->next;
	}

	return -1;
}

/*
 * void print_keywords(char *category, char *ebuild, int *keywords);
 *
 * Prints the keywords to stdout
 *
 * IN:
 *  char *category - current category of the current package
 *  int *keywords - an array of keywords that coincides with archlist
 */
static void
print_keywords(const char *category, const char *ebuild, int *keywords)
{
	queue *arch = arches;
	int a;
	char *package;

	package = xstrdup(ebuild);
	package[strlen(ebuild)-7] = '\0';

	printf("%s%s/%s%s%s ", BOLD, category, BLUE, package, NORM);
	for (a = 0; a < archlist_count; ++a) {
		switch (keywords[a]) {
			case stable:
				printf("%s%c%s%s ", GREEN, status[keywords[a]], arch->name, NORM);
				break;
			case testing:
				printf("%s%c%s%s ", YELLOW, status[keywords[a]], arch->name, NORM);
				break;
		}
		arch = arch->next;
	}

	printf("\n");
	free(package);
}

/*
 * int read_keywords(char *s, int *keywords);
 *
 * Read the KEYWORDS string and decode the values
 *
 * IN:
 *  char *s - a keywords string (ex: "alpha ~amd64 -x86")
 *  int *keywords - the output
 * ERR:
 *  int rc - -1 is returned on error (if !s || !keywords)
 */
static int
read_keywords(char *s, int *keywords)
{
	char *arch, delim[2] = { ' ', '\0' };
	size_t slen;
	int a;

	if (!s)
		return -1;

	memset(keywords, 0, sizeof(*keywords) * archlist_count);

	slen = strlen(s);
	if (slen >= 2 && s[0] == '-' && s[1] == '*')
		for (a = 0; a < archlist_count; ++a)
			keywords[a] = minus;

	if (!slen)
		return 0;

	arch = strtok(s, delim);
	do {
		a = decode_arch(arch);
		if (a == -1)
			continue;
		keywords[a] = decode_status(arch[0]);
	} while ((arch = strtok(NULL, delim)));

	return 0;
}

/*
 * portage_cache *qcache_read_cache_file(const char *file);
 *
 * Read a file from the edb cache and store data in portage_cache.
 *
 * IN:
 *  const char *filename - cache file to read
 * OUT:
 *  portage_cache *pkg - cache data
 * ERR:
 *  NULL is returned when an error occurs.
 */
static portage_cache *
qcache_read_cache_file(const char *filename)
{
	struct stat s;
	char *buf;
	FILE *f;
	portage_cache *ret = NULL;
	int linelen;
	size_t len, buflen;

	if ((f = fopen(filename, "r")) == NULL)
		goto err;

	if (fstat(fileno(f), &s) != 0) {
		fclose(f);
		goto err;
	}

	buf = NULL;
	len = sizeof(*ret) + s.st_size + 1;
	ret = xzalloc(len);

	while ((linelen = getline(&buf, &buflen, f)) >= 0) {
		rmspace_len(buf, (size_t)linelen);

		if (strncmp(buf, "DEPEND=", 7) == 0)
			ret->DEPEND = xstrdup(buf + 7);

		if (strncmp(buf, "DESCRIPTION=", 12) == 0)
			ret->DESCRIPTION = xstrdup(buf + 12);

		if (strncmp(buf, "HOMEPAGE=", 9) == 0)
			ret->HOMEPAGE = xstrdup(buf + 9);

		if (strncmp(buf, "INHERITED=", 10) == 0)
			ret->INHERITED = xstrdup(buf + 10);

		if (strncmp(buf, "IUSE=", 4) == 0)
			ret->IUSE = xstrdup(buf + 4);

		if (strncmp(buf, "KEYWORDS=", 9) == 0)
			ret->KEYWORDS = xstrdup(buf + 9);

		if (strncmp(buf, "LICENSE=", 8) == 0)
			ret->LICENSE = xstrdup(buf + 8);

		if (strncmp(buf, "PDEPEND=", 8) == 0)
			ret->PDEPEND = xstrdup(buf + 8);

		if (strncmp(buf, "PROVIDE=", 8) == 0)
			ret->PROVIDE = xstrdup(buf + 8);

		if (strncmp(buf, "RDEPEND=", 8) == 0)
			ret->RDEPEND = xstrdup(buf + 8);

		if (strncmp(buf, "RESTRICT=", 9) == 0)
			ret->RESTRICT = xstrdup(buf + 9);

		if (strncmp(buf, "SLOT=", 5) == 0)
			ret->SLOT = xstrdup(buf + 5);

		if (strncmp(buf, "SRC_URI=", 8) == 0)
			ret->SRC_URI = xstrdup(buf + 8);
	}

	free(buf);
	ret->atom = atom_explode(filename);
	fclose(f);

	return ret;

 err:
	if (ret)
		cache_free(ret);
	return NULL;
}

/*
 * void qcache_free_data(portage_cache *cache);
 *
 * free()'s a portage_cache
 *
 * IN:
 *  portage_cache *cache - the portage_cache to be free()'d
 */
static void
qcache_free_data(portage_cache *cache)
{
	int i;
	char **c;

	if (!cache)
		errf("Cache is empty !");

	for (i = 0, c = (char**) cache; i < 15; i++)
		if (c[i])
			free(c[i]);

	atom_implode(cache->atom);
	free(cache);
}

/********************************************************************/
/* Comparison functions                                             */
/********************************************************************/

/*
 * int qcache_vercmp(const void *x, const void *y);
 *
 * Compare 2 struct dirent d_name strings based with atom_compare_str().
 * Used with dirscan() to sort ebuild filenames by version.
 *
 * IN:
 *  2 (const struct dirent **) with d_name filled in
 * OUT:
 *  -1 (NEWER)
 *   1 (OLDER)
 *   0 (SAME)
 */
static int
qcache_vercmp(const struct dirent **x, const struct dirent **y)
{
	switch (atom_compare_str((*x)->d_name, (*y)->d_name)) {
		case NEWER: return -1;
		case OLDER: return  1;
		default:    return  0;
	}
}

/********************************************************************/
/* Selection functions                                              */
/********************************************************************/

/*
 * int qcache_file_select(const struct dirent *entry);
 *
 * Selects filenames that do not begin with '.' and are not name "metadata.xml"
 *  or that file matches ".cpickle"
 *
 * IN:
 *  const struct dirent *entry - entry to check
 * OUT:
 *  int - 0 if filename begins with '.' or is "metadata.xml", otherwise 1
 */
static int
qcache_file_select(const struct dirent *entry)
{
	return !(entry->d_name[0] == '.' || (strcmp(entry->d_name, "metadata.xml") == 0) || (strstr(entry->d_name, ".cpickle") != 0));
}

/*
 * int qcache_ebuild_select(const struct dirent *entry);
 *
 * Select filenames that end in ".ebuild"
 *
 * IN:
 *  const struct dirent *entry - entry to check
 * OUT:
 *  int - 1 if the filename ends in ".ebuild", otherwise 0
 */
static int
qcache_ebuild_select(const struct dirent *entry)
{
	return ((strlen(entry->d_name) > 7) && !strcmp(entry->d_name+strlen(entry->d_name)-7, ".ebuild"));
}

/********************************************************************/
/* Traversal function                                               */
/********************************************************************/

static void qcache_load_arches(const char *overlay);

/*
 * int qcache_traverse(void (*func)(qcache_data*));
 *
 * visit every version of every package of every category in the tree
 *
 * IN:
 *  void (*func)(qcache_data*) - function to call
 * OUT:
 *  int - 0 on success.
 * ERR:
 *  exit or return -1 on failure.
 */
static int
qcache_traverse_overlay(void (*func)(qcache_data*), const char *overlay)
{
	qcache_data data = {
		.overlay = overlay,
	};
	char *catpath, *pkgpath, *ebuildpath, *cachepath;
	int i, j, k, len, num_cat, num_pkg, num_ebuild;
	struct dirent **categories, **packages, **ebuilds;

	xasprintf(&catpath, "%s/metadata/md5-cache", overlay);

	if (-1 == (num_cat = scandir(catpath, &categories,
					qcache_file_select, alphasort)))
	{
		errp("%s", catpath);
		free(catpath);
	}

	if (!num_cat)
		warn("%s is empty!", catpath);

	/* traverse categories */
	for (i = 0; i < num_cat; i++) {
		xasprintf(&pkgpath, "%s/%s", overlay, categories[i]->d_name);

		if (-1 == (num_pkg = scandir(pkgpath, &packages,
						qcache_file_select, alphasort)))
		{
			if (errno != ENOENT)
				warnp("Found a cache dir, but unable to process %s", pkgpath);
			free(categories[i]);
			free(pkgpath);
			continue;
		}

		if (qcache_matchcat) {
			if (strcmp(categories[i]->d_name, qcache_matchcat) != 0) {
				scandir_free(packages, num_pkg);
				free(categories[i]);
				free(pkgpath);
				continue;
			}
		}

		/* traverse packages */
		for (j = 0; j < num_pkg; j++) {
			xasprintf(&ebuildpath, "%s/%s/%s",
					overlay, categories[i]->d_name, packages[j]->d_name);

			if (-1 == (num_ebuild = scandir(ebuildpath, &ebuilds,
							qcache_ebuild_select, qcache_vercmp)))
			{
				/* Do not complain about spurious files */
				if (errno != ENOTDIR)
					warnp("%s", ebuildpath);
				free(packages[j]);
				free(ebuildpath);
				continue;
			}

			if (qcache_matchpkg) {
				if (strcmp(packages[j]->d_name, qcache_matchpkg) != 0) {
					scandir_free(ebuilds, num_ebuild);
					free(packages[j]);
					free(ebuildpath);
					continue;
				}
			}

			qcache_skip = 0;

			/* traverse ebuilds */
			data.num = num_ebuild;
			for (k = 0; k < num_ebuild; k++) {
				len = xasprintf(&cachepath, "%s/%s/%s",
						catpath, categories[i]->d_name, ebuilds[k]->d_name);
				cachepath[len - 7] = '\0'; /* remove ".ebuild" */

				data.category = categories[i]->d_name;
				data.package = packages[j]->d_name;
				data.ebuild = ebuilds[k]->d_name;
				data.cur = k + 1;
				data.cache_data = qcache_read_cache_file(cachepath);

				if (data.cache_data != NULL) {
					if (!qcache_skip)
						func(&data);

					qcache_free_data(data.cache_data);
				} else {
					static bool warned = false;
					if (!warned) {
						warned = true;
						warnp("unable to read cache '%s'\n"
						      "\tperhaps you need to `egencache`?", cachepath);
					}
				}

				free(ebuilds[k]);
				free(cachepath);
			}

			free(packages[j]);
			free(ebuilds);
			free(ebuildpath);
		}

		free(categories[i]);
		free(packages);
		free(pkgpath);
	}

	free(categories);
	free(catpath);

	return 0;
}

static int
qcache_traverse(void (*func)(qcache_data*))
{
	int ret;
	size_t n;
	const char *overlay;

	/* Preload all the arches. Not entirely correctly (as arches are bound
	 * to overlays if set), but oh well. */
	array_for_each(overlays, n, overlay)
		qcache_load_arches(overlay);

	ret = 0;
	array_for_each(overlays, n, overlay)
		ret |= qcache_traverse_overlay(func, overlay);

	func(NULL);

	return ret;
}

/********************************************************************/
/* functors                                                         */
/********************************************************************/

static void
qcache_imlate(qcache_data *data)
{
	int *keywords;
	int a;

	if (!data)
		return;

	keywords = xmalloc(sizeof(*keywords) * archlist_count);

	if (read_keywords(data->cache_data->KEYWORDS, keywords) < 0) {
		if (verbose)
			warn("Failed to read keywords for %s%s/%s%s%s",
				BOLD, data->category, BLUE, data->ebuild, NORM);
		free(keywords);
		return;
	}

	switch (keywords[qcache_test_arch]) {
		case stable:
			qcache_skip = 1;
		case none:
		case minus:
			break;

		default:
			for (a = 0; a < archlist_count && !qcache_skip; ++a) {
				if (keywords[a] != stable)
					continue;
				qcache_skip = 1;
				print_keywords(data->category, data->ebuild, keywords);
			}
	}
	free(keywords);
}

static void
qcache_not(qcache_data *data)
{
	int *keywords;

	if (!data)
		return;

	keywords = xmalloc(sizeof(*keywords) * archlist_count);

	if (read_keywords(data->cache_data->KEYWORDS, keywords) < 0) {
		if (verbose)
			warn("Failed to read keywords for %s%s/%s%s%s",
				BOLD, data->category, BLUE, data->ebuild, NORM);
		free(keywords);
		return;
	}

	if (keywords[qcache_test_arch] == testing || keywords[qcache_test_arch] == stable) {
		qcache_skip = 1;
	} else if (data->cur == data->num) {
		printf("%s%s/%s%s%s\n", BOLD, data->category, BLUE, data->package, NORM);
	}

	free(keywords);
}

static void
qcache_all(qcache_data *data)
{
	int *keywords;

	if (!data)
		return;

	keywords = xmalloc(sizeof(*keywords) * archlist_count);

	if (read_keywords(data->cache_data->KEYWORDS, keywords) < 0) {
		if (verbose)
			warn("Failed to read keywords for %s%s/%s%s%s",
				BOLD, data->category, BLUE, data->ebuild, NORM);
		free(keywords);
		return;
	}

	if (keywords[qcache_test_arch] == stable || keywords[qcache_test_arch] == testing) {
		qcache_skip = 1;
		printf("%s%s/%s%s%s\n", BOLD, data->category, BLUE, data->package, NORM);
	}

	free(keywords);
}

static void
qcache_dropped(qcache_data *data)
{
	static int possible = 0;
	int *keywords, i;

	if (!data)
		return;

	if (data->cur == 1)
		possible = 0;

	keywords = xmalloc(sizeof(*keywords) * archlist_count);

	if (read_keywords(data->cache_data->KEYWORDS, keywords) < 0) {
		if (verbose)
			warn("Failed to read keywords for %s%s/%s%s%s",
				BOLD, data->category, BLUE, data->ebuild, NORM);
		free(keywords);
		return;
	}

	if (keywords[qcache_test_arch] == testing || keywords[qcache_test_arch] == stable) {
		qcache_skip = 1;

		if (possible) {
			printf("%s%s/%s%s%s\n", BOLD, data->category, BLUE, data->package, NORM);
		}

		free(keywords);
		return;
	}

	if (!possible) {
		/* don't count newer versions with "-*" keywords */
		for (i = 0; i < archlist_count; ++i) {
			if (keywords[i] == stable || keywords[i] == testing) {
				possible = 1;
				break;
			}
		}
	}

	free(keywords);
}

static void
qcache_stats(qcache_data *data)
{
	static time_t runtime;
	static queue *allcats;
	static const char *last_overlay;
	static int numpkg  = 0;
	static int numebld = 0;
	static int numcat;
	static int *packages_stable;
	static int *packages_testing;
	static int *current_package_keywords;
	static int *keywords;
	int a;

	/* Is this the last time we'll be called? */
	if (!data) {
		const char border[] = "------------------------------------------------------------------";

		printf("+%.*s+\n", 25, border);
		printf("|   general statistics    |\n");
		printf("+%.*s+\n", 25, border);
		printf("| %s%13s%s | %s%7d%s |\n", GREEN, "architectures", NORM, BLUE, archlist_count, NORM);
		printf("| %s%13s%s | %s%7d%s |\n", GREEN, "categories", NORM, BLUE, numcat, NORM);
		printf("| %s%13s%s | %s%7d%s |\n", GREEN, "packages", NORM, BLUE, numpkg, NORM);
		printf("| %s%13s%s | %s%7d%s |\n", GREEN, "ebuilds", NORM, BLUE, numebld, NORM);
		printf("+%.*s+\n\n", 25, border);

		printf("+%.*s+\n", (int)(arch_longest_len + 46), border);
		printf("|%*skeyword distribution                          |\n",
			(int)arch_longest_len, "");
		printf("+%.*s+\n", (int)(arch_longest_len + 46), border);
		printf("| %s%*s%s |%s%8s%s |%s%8s%s |%s%8s%s | %s%8s%s |\n",
			RED, (int)arch_longest_len, "architecture", NORM, RED, "stable", NORM,
			RED, "~arch", NORM, RED, "total", NORM, RED, "total/#pkgs", NORM);
		printf("| %*s |         |%s%8s%s |         |             |\n",
			(int)arch_longest_len, "", RED, "only", NORM);
		printf("+%.*s+\n", (int)(arch_longest_len + 46), border);

		queue *arch = arches;
		for (a = 0; a < archlist_count; ++a) {
			printf("| %s%*s%s |", GREEN, (int)arch_longest_len, arch->name, NORM);
			printf("%s%8d%s |", BLUE, packages_stable[a], NORM);
			printf("%s%8d%s |", BLUE, packages_testing[a], NORM);
			printf("%s%8d%s |", BLUE, packages_testing[a]+packages_stable[a], NORM);
			printf("%s%11.2f%s%% |\n", BLUE, (100.0*(packages_testing[a]+packages_stable[a]))/numpkg, NORM);
			arch = arch->next;
		}

		printf("+%.*s+\n\n", (int)(arch_longest_len + 46), border);

		printf("Completed in ");
		print_seconds_for_earthlings(time(NULL) - runtime);
		printf("\n");

		free(packages_stable);
		free(packages_testing);
		free(keywords);
		free(current_package_keywords);
		free_sets(allcats);
		return;
	}

	if (last_overlay != data->overlay) {
		DIR *dir;
		struct dirent *de;
		char *catpath;

		runtime = time(NULL);

		xasprintf(&catpath, "%s/metadata/md5-cache", data->overlay);
		dir = opendir(catpath);
		while ((de = readdir(dir))) {
			/* Look for all the directories in this path. */
#if defined(DT_UNKNOWN) && defined(DT_DIR)
			if (de->d_type == DT_UNKNOWN)
#endif
			{
				struct stat s;
				char spath[_Q_PATH_MAX];
				snprintf(spath, sizeof(spath), "%s/%s", catpath, de->d_name);
				if (lstat(spath, &s) != 0)
					continue;
				if (!S_ISDIR(s.st_mode))
					continue;
			}
#if defined(DT_UNKNOWN) && defined(DT_DIR)
			else if (de->d_type != DT_DIR)
				continue;
#endif

			if (de->d_name[0] != '.') {
				bool ok;
				allcats = add_set_unique(de->d_name, allcats, &ok);
				if (ok)
					++numcat;
			}
		}
		closedir(dir);
		free(catpath);

		last_overlay = data->overlay;
	}

	if (!numpkg) {
		packages_stable          = xcalloc(archlist_count, sizeof(*packages_stable));
		packages_testing         = xcalloc(archlist_count, sizeof(*packages_testing));
		keywords                 = xcalloc(archlist_count, sizeof(*keywords));
		current_package_keywords = xcalloc(archlist_count, sizeof(*current_package_keywords));
	}

	if (data->cur == 1) {
		numpkg++;
		memset(current_package_keywords, 0, archlist_count * sizeof(*current_package_keywords));
	}
	++numebld;

	memset(keywords, 0, archlist_count * sizeof(*keywords));
	if (read_keywords(data->cache_data->KEYWORDS, keywords) < 0) {
		if (verbose)
			warn("Failed to read keywords for %s%s/%s%s%s",
				BOLD, data->category, BLUE, data->ebuild, NORM);
		return;
	}

	for (a = 0; a < archlist_count; ++a) {
		switch (keywords[a]) {
			case stable:
				current_package_keywords[a] = stable;
				break;
			case testing:
				if (current_package_keywords[a] != stable)
					current_package_keywords[a] = testing;
			default:
				break;
		}
	}

	if (data->cur == data->num) {
		for (a = 0; a < archlist_count; ++a) {
			switch (current_package_keywords[a]) {
				case stable:
					packages_stable[a]++;
					break;
				case testing:
					packages_testing[a]++;
				default:
					break;
			}
		}
	}
}

static void
qcache_testing_only(qcache_data *data)
{
	static int possible = 0;
	int *keywords;

	if (!data)
		return;

	if (data->cur == 1)
		possible = 0;

	keywords = xmalloc(sizeof(*keywords) * archlist_count);

	if (read_keywords(data->cache_data->KEYWORDS, keywords) < 0) {
		if (verbose)
			warn("Failed to read keywords for %s%s/%s%s%s",
				BOLD, data->category, BLUE, data->ebuild, NORM);
		free(keywords);
		return;
	}

	if (keywords[qcache_test_arch] == stable) {
		qcache_skip = 1;
		free(keywords);
		return;
	}

	/* the qcache_test_arch must have at least 1 ~arch keyword */
	if (keywords[qcache_test_arch] == testing)
		possible = 1;

	if (data->cur == data->num && possible) {
		printf("%s%s/%s%s%s\n", BOLD, data->category, BLUE, data->package, NORM);
	}

	free(keywords);
}

/********************************************************************/
/* Misc functions                                                   */
/********************************************************************/

static void
qcache_load_arches(const char *overlay)
{
	FILE *fp;
	char *filename, *s;
	int linelen;
	size_t buflen;
	char *buf;

	xasprintf(&filename, "%s/profiles/arch.list", overlay);
	fp = fopen(filename, "re");
	if (!fp)
		goto done;

	archlist_count = 0;
	arch_longest_len = 0;
	buf = NULL;
	while ((linelen = getline(&buf, &buflen, fp)) >= 0) {
		rmspace_len(buf, (size_t)linelen);

		if ((s = strchr(buf, '#')) != NULL)
			*s = '\0';
		if (buf[0] == '\0')
			continue;

		bool ok;
		arches = add_set_unique(buf, arches, &ok);
		if (ok) {
			++archlist_count;
			arch_longest_len = MAX(arch_longest_len, strlen(buf));
		}
	}
	free(buf);

	fclose(fp);
 done:
	free(filename);
}

/*
 * int qcache_free();
 *
 * Deallocate variables (archlist)
 */
static void
qcache_free(void)
{
	free_sets(arches);
}

/********************************************************************/
/* main                                                             */
/********************************************************************/

int qcache_main(int argc, char **argv)
{
	int i, action = 0;

	while ((i = GETOPT_LONG(QCACHE, qcache, "")) != -1) {
		switch (i) {
			case 'p': qcache_matchpkg = optarg; break;
			case 'c': qcache_matchcat = optarg; break;
			case 'i':
			case 'd':
			case 't':
			case 's':
			case 'a':
			case 'n':
				if (action)
					qcache_usage(EXIT_FAILURE); /* trying to use more than 1 action */
				action = i;
				break;

			COMMON_GETOPTS_CASES(qcache)
		}
	}

	if (optind < argc)
		qcache_test_arch = decode_arch(argv[optind]);

	if ((qcache_test_arch == -1 && action != 's') || optind + 1 < argc)
		qcache_usage(EXIT_FAILURE);

	switch (action) {
		case 'i': return qcache_traverse(qcache_imlate);
		case 'd': return qcache_traverse(qcache_dropped);
		case 't': return qcache_traverse(qcache_testing_only);
		case 's': return qcache_traverse(qcache_stats);
		case 'a': return qcache_traverse(qcache_all);
		case 'n': return qcache_traverse(qcache_not);
	}

	qcache_free();
	qcache_usage(EXIT_FAILURE);
	return EXIT_FAILURE;
}

#else
DEFINE_APPLET_STUB(qcache)
#endif
