/* opkg_cmd.c - the opkg package management system

   Carl D. Worth

   Copyright (C) 2001 University of Southern California

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2, or (at
   your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.
*/

#include <stdio.h>
#include <dirent.h>
#include <glob.h>
#include <fnmatch.h>
#include <signal.h>
#include <unistd.h>

#include "opkg_conf.h"
#include "opkg_cmd.h"
#include "opkg_message.h"
#include "pkg.h"
#include "pkg_dest.h"
#include "pkg_parse.h"
#include "sprintf_alloc.h"
#include "pkg.h"
#include "file_util.h"
#include "libbb/libbb.h"
#include "opkg_utils.h"
#include "opkg_defines.h"
#include "opkg_download.h"
#include "opkg_install.h"
#include "opkg_upgrade.h"
#include "opkg_remove.h"
#include "opkg_configure.h"
#include "xsystem.h"

int opkg_cli_argc = 0;
const char **opkg_cli_argv = NULL;

static void print_pkg(pkg_t * pkg)
{
	char *version = pkg_version_str_alloc(pkg);
	char *description = pkg_get_string(pkg, PKG_DESCRIPTION);
	const char *abiver;
	char *tmp, *tmpname = NULL;

	if (conf->strip_abi &&
	    (abiver = pkg_get_string(pkg, PKG_ABIVERSION)) &&
	    (strlen(pkg->name) > strlen(abiver))) {
		tmpname = strdup(pkg->name);
		tmp = &tmpname[strlen(tmpname) - strlen(abiver)];
		if (!strncmp(abiver, tmp, strlen(abiver)))
			*tmp = '\0';
	};

	printf("%s - %s", tmpname?tmpname:pkg->name, version);

	if (tmpname)
		free(tmpname);

	if (conf->size)
		printf(" - %lu", (unsigned long) pkg_get_int(pkg, PKG_SIZE));
	if (description)
		printf(" - %s", description);
	printf("\n");
	free(version);
}

int opkg_state_changed;

static void write_status_files_if_changed(void)
{
	if (opkg_state_changed && !conf->noaction) {
		opkg_msg(INFO, "Writing status file.\n");
		opkg_conf_write_status_files();
		pkg_write_changed_filelists();
	} else {
		opkg_msg(DEBUG, "Nothing to be done.\n");
	}
}

static void sigint_handler(int sig)
{
	signal(sig, SIG_DFL);
	opkg_msg(NOTICE, "Interrupted. Writing out status database.\n");
	write_status_files_if_changed();
	exit(128 + sig);
}

static int opkg_update_cmd(int argc, char **argv)
{
	char *tmp;
	int err;
	int failures;
	int pkglist_dl_error;
	char *lists_dir;
	pkg_src_list_elt_t *iter;
	pkg_src_t *src;

	sprintf_alloc(&lists_dir, "%s",
		      conf->restrict_to_default_dest ? conf->default_dest->
		      lists_dir : conf->lists_dir);

	if (!file_is_dir(lists_dir)) {
		if (file_exists(lists_dir)) {
			opkg_msg(ERROR, "%s exists, but is not a directory.\n",
				 lists_dir);
			free(lists_dir);
			return -1;
		}
		err = file_mkdir_hier(lists_dir, 0755);
		if (err) {
			free(lists_dir);
			return -1;
		}
	}

	failures = 0;

	sprintf_alloc(&tmp, "%s/update-XXXXXX", conf->tmp_dir);
	if (mkdtemp(tmp) == NULL) {
		opkg_perror(ERROR, "Failed to make temp dir %s", conf->tmp_dir);
		return -1;
	}

	for (iter = void_list_first(&conf->pkg_src_list); iter;
	     iter = void_list_next(&conf->pkg_src_list, iter)) {
		char *url, *list_file_name;

		src = (pkg_src_t *) iter->data;

		sprintf_alloc(&url, "%s/%s", src->value,
			      src->gzip ? "Packages.gz" : "Packages");

		sprintf_alloc(&list_file_name, "%s/%s", lists_dir, src->name);
		pkglist_dl_error = 0;
		if (opkg_download(url, list_file_name, 0)) {
			failures++;
			pkglist_dl_error = 1;
			opkg_msg(NOTICE,
				 "*** Failed to download the package list from %s\n\n",
				 url);
		} else {
			opkg_msg(NOTICE,
				 "Updated list of available packages in %s\n",
				 list_file_name);
		}
		free(url);
#if defined(HAVE_USIGN)
		if (pkglist_dl_error == 0 && conf->check_signature) {
			/* download detached signitures to verify the package lists */
			/* get the url for the sig file */
			sprintf_alloc(&url, "%s/%s", src->value,
				      "Packages.sig");

			/* create temporary file for it */
			char *tmp_file_name;

			/* Put the signature in the right place */
			sprintf_alloc(&tmp_file_name, "%s/%s.sig", lists_dir,
				      src->name);

			err = opkg_download(url, tmp_file_name, 0);
			if (err) {
				failures++;
				opkg_msg(NOTICE,
					 "Signature file download failed.\n");
			} else {
				err =
				    opkg_verify_file(list_file_name,
						     tmp_file_name);
				if (err == 0)
					opkg_msg(NOTICE,
						 "Signature check passed.\n");
				else
					opkg_msg(NOTICE,
						 "Signature check failed.\n");
			}
			if (err && !conf->force_signature) {
				/* The signature was wrong so delete it */
				opkg_msg(NOTICE,
					 "Remove wrong Signature file.\n");
				unlink(tmp_file_name);
				unlink(list_file_name);
			}
			/* We shouldn't unlink the signature ! */
			// unlink (tmp_file_name);
			free(tmp_file_name);
			free(url);
		}
#else
		// Do nothing
#endif
		free(list_file_name);
	}
	rmdir(tmp);
	free(tmp);
	free(lists_dir);

	return failures;
}

struct opkg_intercept {
	char *oldpath;
	char *statedir;
};

typedef struct opkg_intercept *opkg_intercept_t;

static opkg_intercept_t opkg_prep_intercepts(void)
{
	opkg_intercept_t ctx;
	char *newpath;

	ctx = xcalloc(1, sizeof(*ctx));
	ctx->oldpath = xstrdup(getenv("PATH"));

	sprintf_alloc(&newpath, "%s/opkg/intercept:%s", DATADIR,
	              ctx->oldpath ? ctx->oldpath : PATH_SPEC);

	sprintf_alloc(&ctx->statedir, "%s/opkg-intercept-XXXXXX",
	              conf->tmp_dir);

	if (mkdtemp(ctx->statedir) == NULL) {
		opkg_perror(ERROR, "Failed to make temp dir %s", ctx->statedir);

		if (ctx->oldpath)
			free(ctx->oldpath);

		free(ctx->statedir);
		free(newpath);
		free(ctx);
		return NULL;
	}

	setenv("OPKG_INTERCEPT_DIR", ctx->statedir, 1);
	setenv("PATH", newpath, 1);
	free(newpath);

	return ctx;
}

static int opkg_finalize_intercepts(opkg_intercept_t ctx)
{
	DIR *dir;
	int err = 0;

	if (ctx->oldpath) {
		setenv("PATH", ctx->oldpath, 1);
		free(ctx->oldpath);
	}
	else {
		unsetenv("PATH");
	}

	dir = opendir(ctx->statedir);
	if (dir) {
		struct dirent *de;
		while (de = readdir(dir), de != NULL) {
			char *path;

			if (de->d_name[0] == '.')
				continue;

			sprintf_alloc(&path, "%s/%s", ctx->statedir,
				      de->d_name);
			if (access(path, X_OK) == 0) {
				const char *argv[] = { "/bin/sh", "-c", path, NULL };
				xsystem(argv);
			}
			free(path);
		}
		closedir(dir);
	} else
		opkg_perror(ERROR, "Failed to open dir %s", ctx->statedir);

	rm_r(ctx->statedir);
	free(ctx->statedir);
	free(ctx);

	return err;
}

/* For package pkg do the following: If it is already visited, return. If not,
   add it in visited list and recurse to its deps. Finally, add it to ordered
   list.
   pkg_vec all contains all available packages in repos.
   pkg_vec visited contains packages already visited by this function, and is
   used to end recursion and avoid an infinite loop on graph cycles.
   pkg_vec ordered will finally contain the ordered set of packages.
*/
static int
opkg_recurse_pkgs_in_order(pkg_t * pkg, pkg_vec_t * all,
			   pkg_vec_t * visited, pkg_vec_t * ordered)
{
	int j, k, l, m;
	pkg_t *dep;
	compound_depend_t *compound_depend;
	depend_t **possible_satisfiers;
	abstract_pkg_t *abpkg;
	abstract_pkg_t **dependents;

	/* If it's just an available package, that is, not installed and not even
	   unpacked, skip it */
	/* XXX: This is probably an overkill, since a state_status != SS_UNPACKED
	   would do here. However, if there is an intermediate node (pkg) that is
	   configured and installed between two unpacked packages, the latter
	   won't be properly reordered, unless all installed/unpacked pkgs are
	   checked */
	if (pkg->state_status == SS_NOT_INSTALLED)
		return 0;

	/* If the  package has already been visited (by this function), skip it */
	for (j = 0; j < visited->len; j++)
		if (!strcmp(visited->pkgs[j]->name, pkg->name)) {
			opkg_msg(DEBUG, "pkg %s already visited, skipping.\n",
				 pkg->name);
			return 0;
		}

	pkg_vec_insert(visited, pkg);

	opkg_msg(DEBUG, "pkg %s.\n", pkg->name);

	/* Iterate over all the dependencies of pkg. For each one, find a package
	   that is either installed or unpacked and satisfies this dependency.
	   (there should only be one such package per dependency installed or
	   unpacked). Then recurse to the dependency package */
	for (compound_depend = pkg_get_ptr(pkg, PKG_DEPENDS); compound_depend && compound_depend->type; compound_depend++) {
		possible_satisfiers = compound_depend->possibilities;
		for (k = 0; k < compound_depend->possibility_count; k++) {
			abpkg = possible_satisfiers[k]->pkg;
			dependents = abpkg->provided_by->pkgs;
			l = 0;
			if (dependents != NULL)
				while (l < abpkg->provided_by->len
				       && dependents[l] != NULL) {
					opkg_msg(DEBUG,
						 "Descending on pkg %s.\n",
						 dependents[l]->name);

					/* find whether dependent l is installed or unpacked,
					 * and then find which package in the list satisfies it */
					for (m = 0; m < all->len; m++) {
						dep = all->pkgs[m];
						if (dep->state_status !=
						    SS_NOT_INSTALLED)
							if (!strcmp
							    (dep->name,
							     dependents[l]->
							     name)) {
								opkg_recurse_pkgs_in_order
								    (dep, all,
								     visited,
								     ordered);
								/* Stop the outer loop */
								l = abpkg->
								    provided_by->
								    len;
								/* break from the inner loop */
								break;
							}
					}
					l++;
				}
		}
	}

	/* When all recursions from this node down, are over, and all
	   dependencies have been added in proper order in the ordered array, add
	   also the package pkg to ordered array */
	pkg_vec_insert(ordered, pkg);

	return 0;

}

static int opkg_configure_packages(char *pkg_name)
{
	pkg_vec_t *all, *ordered, *visited;
	int i;
	pkg_t *pkg;
	opkg_intercept_t ic;
	int r, err = 0;

	opkg_msg(INFO, "Configuring unpacked packages.\n");

	all = pkg_vec_alloc();

	pkg_hash_fetch_available(all);

	/* Reorder pkgs in order to be configured according to the Depends: tag
	   order */
	opkg_msg(INFO, "Reordering packages before configuring them...\n");
	ordered = pkg_vec_alloc();
	visited = pkg_vec_alloc();
	for (i = 0; i < all->len; i++) {
		pkg = all->pkgs[i];
		opkg_recurse_pkgs_in_order(pkg, all, visited, ordered);
	}

	ic = opkg_prep_intercepts();
	if (ic == NULL) {
		err = -1;
		goto error;
	}

	for (i = 0; i < ordered->len; i++) {
		pkg = ordered->pkgs[i];

		if (pkg_name && fnmatch(pkg_name, pkg->name, conf->nocase))
			continue;

		if (pkg->state_status == SS_UNPACKED) {
			opkg_msg(NOTICE, "Configuring %s.\n", pkg->name);
			r = opkg_configure(pkg);
			if (r == 0) {
				pkg->state_status = SS_INSTALLED;
				pkg->parent->state_status = SS_INSTALLED;
				pkg->state_flag &= ~SF_PREFER;
				opkg_state_changed++;
			} else {
				err = -1;
			}
		}
	}

	if (opkg_finalize_intercepts(ic))
		err = -1;

error:
	pkg_vec_free(all);
	pkg_vec_free(ordered);
	pkg_vec_free(visited);

	return err;
}

static int opkg_remove_cmd(int argc, char **argv);

static int opkg_install_cmd(int argc, char **argv)
{
	int i;
	char *arg;
	int err = 0;

	signal(SIGINT, sigint_handler);

	/*
	 * Now scan through package names and install
	 */
	for (i = 0; i < argc; i++) {
		arg = argv[i];

		opkg_msg(DEBUG2, "%s\n", arg);
		if (opkg_prepare_url_for_install(arg, &argv[i]))
			return -1;
	}

	pkg_hash_load_package_details();
	pkg_hash_load_status_files(NULL, NULL);

	if (conf->force_reinstall) {
		int saved_force_depends = conf->force_depends;
		conf->force_depends = 1;
		(void)opkg_remove_cmd(argc, argv);
		conf->force_depends = saved_force_depends;
		conf->force_reinstall = 0;
	}

	pkg_info_preinstall_check();

	for (i = 0; i < argc; i++) {
		arg = argv[i];
		if (opkg_install_by_name(arg)) {
			opkg_msg(ERROR, "Cannot install package %s.\n", arg);
			err = -1;
		}
	}

	if (opkg_configure_packages(NULL))
		err = -1;

	write_status_files_if_changed();

	return err;
}

static int opkg_upgrade_cmd(int argc, char **argv)
{
	int i;
	pkg_t *pkg;
	int err = 0;

	signal(SIGINT, sigint_handler);

	if (argc) {
		for (i = 0; i < argc; i++) {
			char *arg = argv[i];

			if (opkg_prepare_url_for_install(arg, &arg))
				return -1;
		}
		pkg_info_preinstall_check();

		for (i = 0; i < argc; i++) {
			char *arg = argv[i];
			if (conf->restrict_to_default_dest) {
				pkg =
				    pkg_hash_fetch_installed_by_name_dest(argv
									  [i],
									  conf->
									  default_dest);
				if (pkg == NULL) {
					opkg_msg(NOTICE,
						 "Package %s not installed in %s.\n",
						 argv[i],
						 conf->default_dest->name);
					continue;
				}
			} else {
				pkg = pkg_hash_fetch_installed_by_name(argv[i]);
			}
			if (pkg) {
				if (opkg_upgrade_pkg(pkg))
					err = -1;
			} else {
				if (opkg_install_by_name(arg))
					err = -1;
			}
		}
	}

	if (opkg_configure_packages(NULL))
		err = -1;

	write_status_files_if_changed();

	return err;
}

static int opkg_download_cmd(int argc, char **argv)
{
	int i, err = 0;
	char *arg;
	pkg_t *pkg;

	pkg_info_preinstall_check();
	for (i = 0; i < argc; i++) {
		arg = argv[i];

		pkg = pkg_hash_fetch_best_installation_candidate_by_name(arg);
		if (pkg == NULL) {
			opkg_msg(ERROR, "Cannot find package %s.\n", arg);
			continue;
		}

		if (opkg_download_pkg(pkg, "."))
			err = -1;

		if (err) {
			opkg_msg(ERROR, "Failed to download %s.\n", pkg->name);
		} else {
			opkg_msg(NOTICE, "Downloaded %s as %s.\n",
				 pkg->name, pkg_get_string(pkg, PKG_LOCAL_FILENAME));
		}
	}

	return err;
}

struct opkg_list_find_cmd_item {
	int size;
	char *name;
	char *version;
	char *description;
};

struct opkg_list_find_cmd_args {
	int use_desc;
	int set_status;
	char *pkg_name;
	struct opkg_list_find_cmd_item **items;
	size_t n_items;
};

static void opkg_list_find_cmd_cb(pkg_t *pkg, void *priv)
{
	struct opkg_list_find_cmd_args *args = priv;
	char *description = pkg_get_string(pkg, PKG_DESCRIPTION);
	char *version = pkg_version_str_alloc(pkg);
	struct opkg_list_find_cmd_item *item;
	char *nameptr, *versionptr, *descriptionptr, *tmp;
	const char *abiver;
	int i, found = 0;

	/* if we have package name or pattern and pkg does not match, then skip it */
	if (args->pkg_name && fnmatch(args->pkg_name, pkg->name, conf->nocase) &&
	    (!args->use_desc || !description
	     || fnmatch(args->pkg_name, description, conf->nocase)))
		goto out;

	if (args->set_status) {
		for (i = 0; i < args->n_items; i++) {
			if (!strcmp(args->items[i]->name, pkg->name)) {
				found = 1;
				break;
			}
		}
	}

	if (!found) {
		item = calloc_a(sizeof(*item),
		                &nameptr, strlen(pkg->name) + 1,
		                &versionptr, strlen(version) + 1,
		                &descriptionptr, description ? strlen(description) + 1 : 0);

		item->name = strcpy(nameptr, pkg->name);

		if (conf->strip_abi &&
		    (abiver = pkg_get_string(pkg, PKG_ABIVERSION)) &&
		    (strlen(item->name) > strlen(abiver))) {
			tmp = &item->name[strlen(item->name) - strlen(abiver)];
			if (!strncmp(abiver, tmp, strlen(abiver)))
				*tmp = '\0';
		};

		item->size = pkg_get_int(pkg, PKG_SIZE);
		item->version = strcpy(versionptr, version);
		item->description = description ? strcpy(descriptionptr, description) : NULL;

		args->items = xrealloc(args->items, sizeof(item) * (args->n_items + 1));
		args->items[args->n_items++] = item;
	}

out:
	pkg_deinit(pkg);
	free(pkg);
	free(version);
}

static int opkg_list_find_cmd_sort(const void *a, const void *b)
{
	const struct opkg_list_find_cmd_item *pkg_a = *(const struct opkg_list_find_cmd_item **)a;
	const struct opkg_list_find_cmd_item *pkg_b = *(const struct opkg_list_find_cmd_item **)b;
	return strcmp(pkg_a->name, pkg_b->name);
}

static int opkg_list_find_cmd(int argc, char **argv, int use_desc)
{
	int i;
	struct opkg_list_find_cmd_args args = {
		.use_desc = use_desc,
		.pkg_name = (argc > 0) ? argv[0] : NULL
	};

	args.set_status = 0;
	pkg_hash_load_feeds(SF_NEED_DETAIL, opkg_list_find_cmd_cb, &args);

	args.set_status = 1;
	pkg_hash_load_status_files(opkg_list_find_cmd_cb, &args);

	if (args.n_items > 1)
		qsort(args.items, args.n_items, sizeof(args.items[0]),
		      opkg_list_find_cmd_sort);

	for (i = 0; i < args.n_items; i++) {
		printf("%s - %s",
		       args.items[i]->name,
		       args.items[i]->version);

		if (conf->size)
			printf(" - %lu", (unsigned long) args.items[i]->size);

		if (args.items[i]->description)
			printf(" - %s", args.items[i]->description);

		printf("\n");

		free(args.items[i]);
	}

	free(args.items);

	return 0;
}

static int opkg_list_cmd(int argc, char **argv)
{
	return opkg_list_find_cmd(argc, argv, 0);
}

static int opkg_find_cmd(int argc, char **argv)
{
	return opkg_list_find_cmd(argc, argv, 1);
}

static int opkg_list_installed_cmd(int argc, char **argv)
{
	int i;
	pkg_vec_t *available;
	pkg_t *pkg;
	char *pkg_name = NULL;

	if (argc > 0) {
		pkg_name = argv[0];
	}
	available = pkg_vec_alloc();
	pkg_hash_fetch_all_installed(available);
	pkg_vec_sort(available, pkg_compare_names);
	for (i = 0; i < available->len; i++) {
		pkg = available->pkgs[i];
		/* if we have package name or pattern and pkg does not match, then skip it */
		if (pkg_name && fnmatch(pkg_name, pkg->name, conf->nocase))
			continue;
		print_pkg(pkg);
	}

	pkg_vec_free(available);

	return 0;
}

static int opkg_list_changed_conffiles_cmd(int argc, char **argv)
{
	int i;
	pkg_vec_t *available;
	pkg_t *pkg;
	char *pkg_name = NULL;
	conffile_list_elt_t *iter;
	conffile_list_t *cl;
	conffile_t *cf;

	if (argc > 0) {
		pkg_name = argv[0];
	}
	available = pkg_vec_alloc();
	pkg_hash_fetch_all_installed(available);
	pkg_vec_sort(available, pkg_compare_names);
	for (i = 0; i < available->len; i++) {
		pkg = available->pkgs[i];
		cl = pkg_get_ptr(pkg, PKG_CONFFILES);
		/* if we have package name or pattern and pkg does not match, then skip it */
		if (pkg_name && fnmatch(pkg_name, pkg->name, conf->nocase))
			continue;
		if (!cl || nv_pair_list_empty(cl))
			continue;
		for (iter = nv_pair_list_first(cl); iter;
		     iter = nv_pair_list_next(cl, iter)) {
			cf = (conffile_t *) iter->data;
			if (cf->name && cf->value
			    && conffile_has_been_modified(cf))
				printf("%s\n", cf->name);
		}
	}
	pkg_vec_free(available);
	return 0;
}

static int opkg_list_upgradable_cmd(int argc, char **argv)
{
	struct active_list *head = prepare_upgrade_list();
	struct active_list *node = NULL;
	pkg_t *_old_pkg, *_new_pkg;
	char *old_v, *new_v;
	for (node = active_list_next(head, head); node;
	     node = active_list_next(head, node)) {
		_old_pkg = node->pkg;
		_new_pkg =
		    pkg_hash_fetch_best_installation_candidate_by_name
		    (_old_pkg->name);
		if (_new_pkg == NULL)
			continue;
		old_v = pkg_version_str_alloc(_old_pkg);
		new_v = pkg_version_str_alloc(_new_pkg);
		printf("%s - %s - %s\n", _old_pkg->name, old_v, new_v);
		free(old_v);
		free(new_v);
	}
	active_list_head_delete(head);
	return 0;
}

static int opkg_info_status_cmd(int argc, char **argv, int installed_only)
{
	int i;
	pkg_vec_t *available;
	pkg_t *pkg;
	char *pkg_name = NULL;
	conffile_list_t *cl;

	if (argc > 0) {
		pkg_name = argv[0];
	}

	available = pkg_vec_alloc();
	if (installed_only)
		pkg_hash_fetch_all_installed(available);
	else
		pkg_hash_fetch_available(available);

	for (i = 0; i < available->len; i++) {
		pkg = available->pkgs[i];
		if (pkg_name && fnmatch(pkg_name, pkg->name, conf->nocase)) {
			continue;
		}

		pkg_formatted_info(stdout, pkg);

		cl = pkg_get_ptr(pkg, PKG_CONFFILES);

		if (conf->verbosity >= NOTICE && cl) {
			conffile_list_elt_t *iter;
			for (iter = nv_pair_list_first(cl); iter;
			     iter = nv_pair_list_next(cl, iter)) {
				conffile_t *cf = (conffile_t *) iter->data;
				int modified = conffile_has_been_modified(cf);
				if (cf->value)
					opkg_msg(INFO,
						 "conffile=%s md5sum=%s modified=%d.\n",
						 cf->name, cf->value, modified);
			}
		}
	}
	pkg_vec_free(available);

	return 0;
}

static int opkg_info_cmd(int argc, char **argv)
{
	return opkg_info_status_cmd(argc, argv, 0);
}

static int opkg_status_cmd(int argc, char **argv)
{
	return opkg_info_status_cmd(argc, argv, 1);
}

static int opkg_configure_cmd(int argc, char **argv)
{
	int err;
	char *pkg_name = NULL;

	if (argc > 0)
		pkg_name = argv[0];

	err = opkg_configure_packages(pkg_name);

	write_status_files_if_changed();

	return err;
}

static int opkg_remove_cmd(int argc, char **argv)
{
	int i, a, done, err = 0;
	pkg_t *pkg;
	pkg_t *pkg_to_remove;
	pkg_vec_t *available;

	done = 0;

	signal(SIGINT, sigint_handler);

	pkg_info_preinstall_check();

	available = pkg_vec_alloc();
	pkg_hash_fetch_all_installed(available);

	for (i = 0; i < argc; i++) {
		for (a = 0; a < available->len; a++) {
			pkg = available->pkgs[a];
			if (fnmatch(argv[i], pkg->name, conf->nocase)) {
				continue;
			}
			if (conf->restrict_to_default_dest) {
				pkg_to_remove =
				    pkg_hash_fetch_installed_by_name_dest(pkg->
									  name,
									  conf->
									  default_dest);
			} else {
				pkg_to_remove =
				    pkg_hash_fetch_installed_by_name(pkg->name);
			}

			if (pkg_to_remove == NULL) {
				opkg_msg(ERROR,
					 "Package %s is not installed.\n",
					 pkg->name);
				continue;
			}
			if (pkg->state_status == SS_NOT_INSTALLED) {
				opkg_msg(ERROR, "Package %s not installed.\n",
					 pkg->name);
				continue;
			}

			if (opkg_remove_pkg(pkg_to_remove, 0))
				err = -1;
			else
				done = 1;
		}
	}

	pkg_vec_free(available);

	if (done == 0)
		opkg_msg(NOTICE, "No packages removed.\n");

	write_status_files_if_changed();
	return err;
}

static int opkg_flag_cmd(int argc, char **argv)
{
	int i;
	pkg_t *pkg;
	const char *flags = argv[0];

	signal(SIGINT, sigint_handler);

	for (i = 1; i < argc; i++) {
		if (conf->restrict_to_default_dest) {
			pkg = pkg_hash_fetch_installed_by_name_dest(argv[i],
								    conf->
								    default_dest);
		} else {
			pkg = pkg_hash_fetch_installed_by_name(argv[i]);
		}

		if (pkg == NULL) {
			opkg_msg(ERROR, "Package %s is not installed.\n",
				 argv[i]);
			continue;
		}
		if ((strcmp(flags, "hold") == 0)
		    || (strcmp(flags, "noprune") == 0)
		    || (strcmp(flags, "user") == 0)
		    || (strcmp(flags, "ok") == 0)) {
			pkg->state_flag = pkg_state_flag_from_str(flags);
		}

		/*
		 * Useful if a package is installed in an offline_root, and
		 * should be configured by opkg-cl configure at a later date.
		 */
		if ((strcmp(flags, "installed") == 0)
		    || (strcmp(flags, "unpacked") == 0)) {
			pkg->state_status = pkg_state_status_from_str(flags);
		}

		opkg_state_changed++;
		opkg_msg(NOTICE, "Setting flags for package %s to %s.\n",
			 pkg->name, flags);
	}

	write_status_files_if_changed();
	return 0;
}

static int opkg_files_cmd(int argc, char **argv)
{
	pkg_t *pkg;
	str_list_t *files;
	str_list_elt_t *iter;
	char *pkg_version;

	if (argc < 1) {
		return -1;
	}

	pkg = pkg_hash_fetch_installed_by_name(argv[0]);
	if (pkg == NULL) {
		opkg_msg(ERROR, "Package %s not installed.\n", argv[0]);
		return 0;
	}

	files = pkg_get_installed_files(pkg);
	pkg_version = pkg_version_str_alloc(pkg);

	printf
	    ("Package %s (%s) is installed on %s and has the following files:\n",
	     pkg->name, pkg_version, pkg->dest->name);

	for (iter = str_list_first(files); iter;
	     iter = str_list_next(files, iter))
		printf("%s\n", (char *)iter->data);

	free(pkg_version);
	pkg_free_installed_files(pkg);

	return 0;
}

static int opkg_depends_cmd(int argc, char **argv)
{
	int i, j, k;
	pkg_vec_t *available_pkgs;
	compound_depend_t *cdep;
	pkg_t *pkg;
	char *str;

	pkg_info_preinstall_check();

	available_pkgs = pkg_vec_alloc();
	if (conf->query_all)
		pkg_hash_fetch_available(available_pkgs);
	else
		pkg_hash_fetch_all_installed(available_pkgs);

	for (i = 0; i < argc; i++) {
		for (j = 0; j < available_pkgs->len; j++) {
			pkg = available_pkgs->pkgs[j];

			if (fnmatch(argv[i], pkg->name, conf->nocase) != 0)
				continue;

			opkg_msg(NOTICE, "%s depends on:\n", pkg->name);

			for (k = 0, cdep = pkg_get_ptr(pkg, PKG_DEPENDS); cdep && cdep->type; k++, cdep++) {
				if (cdep->type != DEPEND)
					continue;

				str = pkg_depend_str(pkg, k);
				opkg_msg(NOTICE, "\t%s\n", str);
				free(str);
			}

		}
	}

	pkg_vec_free(available_pkgs);
	return 0;
}

static int pkg_mark_provides(pkg_t * pkg)
{
	abstract_pkg_t **provider = pkg_get_ptr(pkg, PKG_PROVIDES);

	pkg->parent->state_flag |= SF_MARKED;

	while (provider && *provider) {
		(*provider)->state_flag |= SF_MARKED;
		provider++;
	}

	return 0;
}

enum what_field_type {
	WHATDEPENDS,
	WHATCONFLICTS,
	WHATPROVIDES,
	WHATREPLACES,
	WHATRECOMMENDS,
	WHATSUGGESTS
};

static int
opkg_what_depends_conflicts_cmd(enum depend_type what_field_type, int recursive,
				int argc, char **argv)
{
	depend_t *possibility;
	compound_depend_t *cdep, *deps;
	pkg_vec_t *available_pkgs;
	pkg_t *pkg;
	int i, j, l;
	int changed;
	const char *rel_str = NULL;
	char *ver;

	switch (what_field_type) {
	case DEPEND:
		rel_str = "depends on";
		break;
	case CONFLICTS:
		rel_str = "conflicts with";
		break;
	case SUGGEST:
		rel_str = "suggests";
		break;
	case RECOMMEND:
		rel_str = "recommends";
		break;
	default:
		return -1;
	}

	available_pkgs = pkg_vec_alloc();

	if (conf->query_all)
		pkg_hash_fetch_available(available_pkgs);
	else
		pkg_hash_fetch_all_installed(available_pkgs);

	/* mark the root set */
	pkg_vec_clear_marks(available_pkgs);
	opkg_msg(NOTICE, "Root set:\n");
	for (i = 0; i < argc; i++)
		pkg_vec_mark_if_matches(available_pkgs, argv[i]);

	for (i = 0; i < available_pkgs->len; i++) {
		pkg = available_pkgs->pkgs[i];
		if (pkg->state_flag & SF_MARKED) {
			/* mark the parent (abstract) package */
			pkg_mark_provides(pkg);
			opkg_msg(NOTICE, "  %s\n", pkg->name);
		}
	}

	opkg_msg(NOTICE, "What %s root set\n", rel_str);
	do {
		changed = 0;

		for (j = 0; j < available_pkgs->len; j++) {

			pkg = available_pkgs->pkgs[j];
			/*
			count = ((what_field_type == CONFLICTS)
				 ? pkg->conflicts_count
				 : pkg->pre_depends_count +
				 pkg->depends_count +
				 pkg->recommends_count + pkg->suggests_count);
				 */

			/* skip this package if it is already marked */
			if (pkg->parent->state_flag & SF_MARKED)
				continue;

			deps = pkg_get_ptr(pkg, (what_field_type == CONFLICTS) ? PKG_CONFLICTS : PKG_DEPENDS);

			for (cdep = deps; cdep && cdep->type; cdep++) {
				if (what_field_type != cdep->type)
					continue;

				for (l = 0; l < cdep->possibility_count; l++) {
					possibility = cdep->possibilities[l];

					if ((possibility->pkg->state_flag
					     & SF_MARKED)
					    != SF_MARKED)
						continue;

					/* mark the depending package so we
					 * won't visit it again */
					pkg->state_flag |= SF_MARKED;
					pkg_mark_provides(pkg);
					changed++;

					ver = pkg_version_str_alloc(pkg);
					opkg_msg(NOTICE, "\t%s %s\t%s %s",
						 pkg->name,
						 ver,
						 rel_str,
						 possibility->pkg->name);
					free(ver);
					if (possibility->version) {
						opkg_msg(NOTICE, " (%s%s)",
							 constraint_to_str
							 (possibility->
							  constraint),
							 possibility->version);
					}
					if (!pkg_dependence_satisfiable
					    (possibility))
						opkg_msg(NOTICE,
							 " unsatisfiable");
					opkg_message(NOTICE, "\n");
					goto next_package;
				}
			}
next_package:
			;
		}
	} while (changed && recursive);

	pkg_vec_free(available_pkgs);

	return 0;
}

static int opkg_whatdepends_recursively_cmd(int argc, char **argv)
{
	return opkg_what_depends_conflicts_cmd(DEPEND, 1, argc, argv);
}

static int opkg_whatdepends_cmd(int argc, char **argv)
{
	return opkg_what_depends_conflicts_cmd(DEPEND, 0, argc, argv);
}

static int opkg_whatsuggests_cmd(int argc, char **argv)
{
	return opkg_what_depends_conflicts_cmd(SUGGEST, 0, argc, argv);
}

static int opkg_whatrecommends_cmd(int argc, char **argv)
{
	return opkg_what_depends_conflicts_cmd(RECOMMEND, 0, argc, argv);
}

static int opkg_whatconflicts_cmd(int argc, char **argv)
{
	return opkg_what_depends_conflicts_cmd(CONFLICTS, 0, argc, argv);
}

static int
opkg_what_provides_replaces_cmd(enum what_field_type what_field_type, int argc,
				char **argv)
{
	abstract_pkg_t *apkg, **abpkgs;

	if (argc > 0) {
		pkg_vec_t *available_pkgs = pkg_vec_alloc();
		const char *rel_str =
		    (what_field_type == WHATPROVIDES ? "provides" : "replaces");
		int i;

		pkg_info_preinstall_check();

		if (conf->query_all)
			pkg_hash_fetch_available(available_pkgs);
		else
			pkg_hash_fetch_all_installed(available_pkgs);
		for (i = 0; i < argc; i++) {
			const char *target = argv[i];
			int j;

			opkg_msg(NOTICE, "What %s %s\n", rel_str, target);
			for (j = 0; j < available_pkgs->len; j++) {
				pkg_t *pkg = available_pkgs->pkgs[j];
				abpkgs = pkg_get_ptr(pkg, (what_field_type == WHATPROVIDES) ? PKG_PROVIDES : PKG_REPLACES);

				while (abpkgs && *abpkgs) {
					apkg = *abpkgs++;

					if (fnmatch(target, apkg->name, conf->nocase))
						continue;

					opkg_msg(NOTICE, "    %s", pkg->name);

					if ((conf->nocase ? strcasecmp(target, apkg->name)
					    : strcmp(target, apkg->name)))
						opkg_msg(NOTICE, "\t%s %s\n", rel_str, apkg->name);

					opkg_message(NOTICE, "\n");
				}
			}
		}
		pkg_vec_free(available_pkgs);
	}
	return 0;
}

static int opkg_whatprovides_cmd(int argc, char **argv)
{
	return opkg_what_provides_replaces_cmd(WHATPROVIDES, argc, argv);
}

static int opkg_whatreplaces_cmd(int argc, char **argv)
{
	return opkg_what_provides_replaces_cmd(WHATREPLACES, argc, argv);
}

static int opkg_search_cmd(int argc, char **argv)
{
	int i;

	pkg_vec_t *installed;
	pkg_t *pkg;
	str_list_t *installed_files;
	str_list_elt_t *iter;
	char *installed_file;

	if (argc < 1) {
		return -1;
	}

	installed = pkg_vec_alloc();
	pkg_hash_fetch_all_installed(installed);
	pkg_vec_sort(installed, pkg_compare_names);

	for (i = 0; i < installed->len; i++) {
		pkg = installed->pkgs[i];

		installed_files = pkg_get_installed_files(pkg);

		for (iter = str_list_first(installed_files); iter;
		     iter = str_list_next(installed_files, iter)) {
			installed_file = (char *)iter->data;
			if (fnmatch(argv[0], installed_file, conf->nocase) == 0)
				print_pkg(pkg);
		}

		pkg_free_installed_files(pkg);
	}

	pkg_vec_free(installed);

	return 0;
}

static int opkg_compare_versions_cmd(int argc, char **argv)
{
	int rc;
	pkg_t *p1, *p2;

	if (argc == 3) {
		/* this is a bit gross */
		p1 = pkg_new();
		p2 = pkg_new();
		parse_version(p1, argv[0]);
		parse_version(p2, argv[2]);
		rc = pkg_version_satisfied(p1, p2, argv[1]);
		pkg_deinit(p1);
		pkg_deinit(p2);
		free(p1);
		free(p2);
		return rc ? 0 : 1;
	} else {
		opkg_msg(ERROR,
			 "opkg compare_versions <v1> <op> <v2>\n"
			 "<op> is one of <= >= << >> =\n");
		return -1;
	}
}

static int opkg_print_architecture_cmd(int argc, char **argv)
{
	nv_pair_list_elt_t *l;

	list_for_each_entry(l, &conf->arch_list.head, node) {
		nv_pair_t *nv = (nv_pair_t *) l->data;
		printf("arch %s %s\n", nv->name, nv->value);
	}
	return 0;
}

/* XXX: CLEANUP: The usage strings should be incorporated into this
   array for easier maintenance */
static opkg_cmd_t cmds[] = {
	{"update", 0, (opkg_cmd_fun_t) opkg_update_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"upgrade", 1, (opkg_cmd_fun_t) opkg_upgrade_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"list", 0, (opkg_cmd_fun_t) opkg_list_cmd, PFM_SOURCE},
	{"list_installed", 0, (opkg_cmd_fun_t) opkg_list_installed_cmd,
	 PFM_SOURCE},
	{"list-installed", 0, (opkg_cmd_fun_t) opkg_list_installed_cmd,
	 PFM_SOURCE},
	{"list_upgradable", 0, (opkg_cmd_fun_t) opkg_list_upgradable_cmd,
	 PFM_SOURCE},
	{"list-upgradable", 0, (opkg_cmd_fun_t) opkg_list_upgradable_cmd,
	 PFM_SOURCE},
	{"list_changed_conffiles", 0,
	 (opkg_cmd_fun_t) opkg_list_changed_conffiles_cmd, PFM_SOURCE},
	{"list-changed-conffiles", 0,
	 (opkg_cmd_fun_t) opkg_list_changed_conffiles_cmd, PFM_SOURCE},
	{"info", 0, (opkg_cmd_fun_t) opkg_info_cmd, 0},
	{"flag", 1, (opkg_cmd_fun_t) opkg_flag_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"status", 0, (opkg_cmd_fun_t) opkg_status_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"install", 1, (opkg_cmd_fun_t) opkg_install_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"remove", 1, (opkg_cmd_fun_t) opkg_remove_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"configure", 0, (opkg_cmd_fun_t) opkg_configure_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"files", 1, (opkg_cmd_fun_t) opkg_files_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"search", 1, (opkg_cmd_fun_t) opkg_search_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"find", 1, (opkg_cmd_fun_t) opkg_find_cmd, PFM_SOURCE},
	{"download", 1, (opkg_cmd_fun_t) opkg_download_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"compare_versions", 1, (opkg_cmd_fun_t) opkg_compare_versions_cmd, 0},
	{"compare-versions", 1, (opkg_cmd_fun_t) opkg_compare_versions_cmd, 0},
	{"print-architecture", 0, (opkg_cmd_fun_t) opkg_print_architecture_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"print_architecture", 0, (opkg_cmd_fun_t) opkg_print_architecture_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"print-installation-architecture", 0,
	 (opkg_cmd_fun_t) opkg_print_architecture_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"print_installation_architecture", 0,
	 (opkg_cmd_fun_t) opkg_print_architecture_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"depends", 1, (opkg_cmd_fun_t) opkg_depends_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"whatdepends", 1, (opkg_cmd_fun_t) opkg_whatdepends_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"whatdependsrec", 1, (opkg_cmd_fun_t) opkg_whatdepends_recursively_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"whatrecommends", 1, (opkg_cmd_fun_t) opkg_whatrecommends_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"whatsuggests", 1, (opkg_cmd_fun_t) opkg_whatsuggests_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"whatprovides", 1, (opkg_cmd_fun_t) opkg_whatprovides_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"whatreplaces", 1, (opkg_cmd_fun_t) opkg_whatreplaces_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
	{"whatconflicts", 1, (opkg_cmd_fun_t) opkg_whatconflicts_cmd,
	 PFM_DESCRIPTION | PFM_SOURCE},
};

opkg_cmd_t *opkg_cmd_find(const char *name)
{
	int i;
	opkg_cmd_t *cmd;
	int num_cmds = sizeof(cmds) / sizeof(opkg_cmd_t);

	for (i = 0; i < num_cmds; i++) {
		cmd = &cmds[i];
		if (strcmp(name, cmd->name) == 0)
			return cmd;
	}

	return NULL;
}

int opkg_cmd_exec(opkg_cmd_t * cmd, int argc, const char **argv)
{
	opkg_cli_argc = argc;
	opkg_cli_argv = argv;
	return (cmd->fun) (argc, argv);
}
