/*  This file is part of "reprepro"
 *  Copyright (C) 2003,2004,2005,2006,2007,2008,2009,2011,2012,2016 Bernhard R. Link
 *  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.
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02111-1301  USA
 */
#include <config.h>

#include <errno.h>
#include <assert.h>
#include <limits.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <signal.h>
#include "error.h"
#define DEFINE_IGNORE_VARIABLES
#include "ignore.h"
#include "mprintf.h"
#include "strlist.h"
#include "atoms.h"
#include "dirs.h"
#include "names.h"
#include "filecntl.h"
#include "files.h"
#include "filelist.h"
#include "target.h"
#include "reference.h"
#include "binaries.h"
#include "sources.h"
#include "release.h"
#include "aptmethod.h"
#include "updates.h"
#include "pull.h"
#include "upgradelist.h"
#include "signature.h"
#include "debfile.h"
#include "checkindeb.h"
#include "checkindsc.h"
#include "checkin.h"
#include "downloadcache.h"
#include "termdecide.h"
#include "tracking.h"
#include "optionsfile.h"
#include "dpkgversions.h"
#include "incoming.h"
#include "override.h"
#include "log.h"
#include "copypackages.h"
#include "uncompression.h"
#include "sourceextraction.h"
#include "pool.h"
#include "printlistformat.h"
#include "globmatch.h"
#include "needbuild.h"
#include "archallflood.h"
#include "sourcecheck.h"
#include "uploaderslist.h"
#include "sizes.h"
#include "filterlist.h"
#include "descriptions.h"
#include "outhook.h"
#include "package.h"

#ifndef STD_BASE_DIR
#define STD_BASE_DIR "."
#endif
#ifndef STD_METHOD_DIR
#define STD_METHOD_DIR "/usr/lib/apt/methods"
#endif

#ifndef LLONG_MAX
#define LLONG_MAX __LONG_LONG_MAX__
#endif

/* global options available to the rest */
struct global_config global;

/* global options */
static char /*@only@*/ /*@notnull@*/ // *g*
	*x_basedir = NULL,
	*x_outdir = NULL,
	*x_distdir = NULL,
	*x_dbdir = NULL,
	*x_listdir = NULL,
	*x_confdir = NULL,
	*x_logdir = NULL,
	*x_morguedir = NULL,
	*x_methoddir = NULL;
static char /*@only@*/ /*@null@*/
	*x_section = NULL,
	*x_priority = NULL,
	*x_component = NULL,
	*x_architecture = NULL,
	*x_packagetype = NULL;
static char /*@only@*/ /*@null@*/ *listformat = NULL;
static char /*@only@*/ /*@null@*/ *endhook = NULL;
static char /*@only@*/ /*@null@*/ *outhook = NULL;
static char /*@only@*/
	*gunzip = NULL,
	*bunzip2 = NULL,
	*unlzma = NULL,
	*unxz = NULL,
	*lunzip = NULL,
	*unzstd = NULL,
	*gnupghome = NULL;
static int 	listmax = -1;
static int 	listskip = 0;
static int	delete = D_COPY;
static bool	nothingiserror = false;
static bool	nolistsdownload = false;
static bool	keepunreferenced = false;
static bool	keepunusednew = false;
static bool	askforpassphrase = false;
static bool	guessgpgtty = true;
static bool	skipold = true;
static size_t   waitforlock = 0;
static enum exportwhen export = EXPORT_CHANGED;
int		verbose = 0;
static bool	fast = false;
static bool	verbosedatabase = false;
static enum spacecheckmode spacecheckmode = scm_FULL;
/* default: 100 MB for database to grow */
static off_t reserveddbspace = 1024*1024*100
/* 1MB safety margin for other filesystems */;
static off_t reservedotherspace = 1024*1024;

/* define for each config value an owner, and only higher owners are allowed
 * to change something owned by lower owners. */
static enum config_option_owner config_state,
#define O(x) owner_ ## x = CONFIG_OWNER_DEFAULT
O(fast), O(x_morguedir), O(x_outdir), O(x_basedir), O(x_distdir), O(x_dbdir), O(x_listdir), O(x_confdir), O(x_logdir), O(x_methoddir), O(x_section), O(x_priority), O(x_component), O(x_architecture), O(x_packagetype), O(nothingiserror), O(nolistsdownload), O(keepunusednew), O(keepunreferenced), O(keeptemporaries), O(keepdirectories), O(askforpassphrase), O(skipold), O(export), O(waitforlock), O(spacecheckmode), O(reserveddbspace), O(reservedotherspace), O(guessgpgtty), O(verbosedatabase), O(gunzip), O(bunzip2), O(unlzma), O(unxz), O(lunzip), O(unzstd), O(gnupghome), O(listformat), O(listmax), O(listskip), O(onlysmalldeletes), O(endhook), O(outhook);
#undef O

#define CONFIGSET(variable, value) if (owner_ ## variable <= config_state) { \
					owner_ ## variable = config_state; \
					variable = value; }
#define CONFIGGSET(variable, value) if (owner_ ## variable <= config_state) { \
					owner_ ## variable = config_state; \
					global.variable = value; }
#define CONFIGDUP(variable, value) if (owner_ ## variable <= config_state) { \
					owner_ ## variable = config_state; \
					free(variable); \
					variable = strdup(value); \
					if (FAILEDTOALLOC(variable)) { \
						(void)fputs("Out of Memory!", \
								stderr); \
						exit(EXIT_FAILURE); \
					} }

#define y(type, name) type name
#define n(type, name) UNUSED(type dummy_ ## name)

#define ACTION_N(act, sp, args, name) static retvalue action_n_ ## act ## _ ## sp ## _ ## name ( \
			UNUSED(struct distribution *dummy2),         \
			sp(const char *, section),                   \
			sp(const char *, priority),                  \
			act(const struct atomlist *, architectures), \
			act(const struct atomlist *, components),    \
			act(const struct atomlist *, packagetypes),  \
			int argc, args(const char *, argv[]))

#define ACTION_C(act, sp, a, name) static retvalue action_c_ ## act ## _ ## sp ## _ ## name ( \
			struct distribution *alldistributions,       \
			sp(const char *, section),                   \
			sp(const char *, priority),                  \
			act(const struct atomlist *, architectures), \
			act(const struct atomlist *, components),    \
			act(const struct atomlist *, packagetypes),  \
			a(int, argc), a(const char *, argv[]))

#define ACTION_B(act, sp, u, name) static retvalue action_b_ ## act ## _ ## sp ## _ ## name ( \
			u(struct distribution *, alldistributions),  \
			sp(const char *, section),                   \
			sp(const char *, priority),                  \
			act(const struct atomlist *, architectures), \
			act(const struct atomlist *, components),    \
			act(const struct atomlist *, packagetypes),  \
			int argc, const char *argv[])

#define ACTION_L(act, sp, u, args, name) static retvalue action_l_ ## act ## _ ## sp ## _ ## name ( \
			struct distribution *alldistributions,       \
			sp(const char *, section),                   \
			sp(const char *, priority),                  \
			act(const struct atomlist *, architectures), \
			act(const struct atomlist *, components),    \
			act(const struct atomlist *, packagetypes),  \
			int argc, args(const char *, argv[]))

#define ACTION_R(act, sp, d, a, name) static retvalue action_r_ ## act ## _ ## sp ## _ ## name ( \
			d(struct distribution *, alldistributions),  \
			sp(const char *, section),                   \
			sp(const char *, priority),                  \
			act(const struct atomlist *, architectures), \
			act(const struct atomlist *, components),    \
			act(const struct atomlist *, packagetypes),  \
			a(int, argc), a(const char *, argv[]))

#define ACTION_T(act, sp, name) static retvalue action_t_ ## act ## _ ## sp ## _ ## name ( \
			UNUSED(struct distribution *ddummy),         \
			sp(const char *, section),                   \
			sp(const char *, priority),                  \
			act(const struct atomlist *, architectures), \
			act(const struct atomlist *, components),    \
			act(const struct atomlist *, packagetypes),  \
			UNUSED(int argc), UNUSED(const char *dummy4[]))

#define ACTION_F(act, sp, d, a, name) static retvalue action_f_ ## act ## _ ## sp ## _ ## name ( \
			d(struct distribution *, alldistributions),  \
			sp(const char *, section),                   \
			sp(const char *, priority),                  \
			act(const struct atomlist *, architectures), \
			act(const struct atomlist *, components),    \
			act(const struct atomlist *, packagetypes),  \
			a(int, argc), a(const char *, argv[]))

#define ACTION_RF(act, sp, ud, u, name) static retvalue action_rf_ ## act ## _ ## sp ## _ ## name ( \
			ud(struct distribution *, alldistributions),  \
			sp(const char *, section),                   \
			sp(const char *, priority),                  \
			act(const struct atomlist *, architectures), \
			act(const struct atomlist *, components),    \
			act(const struct atomlist *, packagetypes),  \
			u(int, argc), u(const char *, argv[]))

#define ACTION_D(act, sp, u, name) static retvalue action_d_ ## act ## _ ## sp ## _ ## name ( \
			struct distribution *alldistributions,       \
			sp(const char *, section),                   \
			sp(const char *, priority),                  \
			act(const struct atomlist *, architectures), \
			act(const struct atomlist *, components),    \
			act(const struct atomlist *, packagetypes),  \
			u(int, argc), u(const char *, argv[]))

static retvalue splitnameandversion(const char *nameandversion, const char **name_p, const char **version_p) {
	char *version;
	retvalue r;

	version = index(nameandversion, '=');
	if (version != NULL) {
		if (index(version+1, '=') != NULL) {
			fprintf(stderr,
"Cannot parse '%s': more than one '='\n",
					nameandversion);
			*name_p = NULL;
			*version_p = NULL;
			r = RET_ERROR;
		} else if (version[1] == '\0') {
			fprintf(stderr,
"Cannot parse '%s': no version after '='\n",
					nameandversion);
			*name_p = NULL;
			*version_p = NULL;
			r = RET_ERROR;
		} else if (version == nameandversion) {
			fprintf(stderr,
"Cannot parse '%s': no source name found before the '='\n",
					nameandversion);
			*name_p = NULL;
			*version_p = NULL;
			r = RET_ERROR;
		} else {
			*name_p = strndup(nameandversion, version - nameandversion);
			if (FAILEDTOALLOC(*name_p))
				r = RET_ERROR_OOM;
			else
				r = RET_OK;
			*version_p = version + 1;
		}
	} else {
		r = RET_OK;
		*name_p = nameandversion;
		*version_p = NULL;
	}
	return r;
}

static inline void splitnameandversion_done(const char **name_p, const char **version_p) {
	// In case version_p points to a non-NULL value, name_p needs to be freed after usage.
	if (*version_p != NULL) {
		free((char*)*name_p);
		*name_p = NULL;
	}
}

ACTION_N(n, n, y, printargs) {
	int i;

	fprintf(stderr, "argc: %d\n", argc);
	for (i=0 ; i < argc ; i++) {
		fprintf(stderr, "%s\n", argv[i]);
	}
	return RET_OK;
}

ACTION_N(n, n, n, dumpuncompressors) {
	enum compression c;

	assert (argc == 1);
	for (c = 0 ; c < c_COUNT ; c++) {
		if (c == c_none)
			continue;
		printf("%s: ", uncompression_suffix[c]);
		if (uncompression_builtin(c)) {
			if (extern_uncompressors[c] != NULL)
				printf("built-in + '%s'\n",
						extern_uncompressors[c]);
			else
				printf("built-in\n");
		} else if (extern_uncompressors[c] != NULL)
			printf("'%s'\n", extern_uncompressors[c]);
		else switch (c) {
			case c_bzip2:
				printf(
"not supported (install bzip2 or use --bunzip2 to tell where bunzip2 is).\n");

				break;
			case c_lzma:
				printf(
"not supported (install lzma or use --unlzma to tell where unlzma is).\n");
				break;
			case c_xz:
				printf(
"not supported (install xz-utils or use --unxz to tell where unxz is).\n");
				break;
			case c_lunzip:
				printf(
"not supported (install lzip or use --lunzip to tell where lunzip is).\n");
				break;
			case c_zstd:
				printf(
"not supported (install unzstd or use --unzstd to tell where unzstd is).\n");
				break;
			default:
				printf("not supported\n");
		}
	}
	return RET_OK;
}
ACTION_N(n, n, y, uncompress) {
	enum compression c;

	assert (argc == 4);
	c = c_none + 1;
	while (c < c_COUNT && strcmp(argv[1], uncompression_suffix[c]) != 0)
		c++;
	if (c >= c_COUNT) {
		fprintf(stderr, "Unknown compression format '%s'\n", argv[1]);
		return RET_ERROR;
	}
	if (!uncompression_supported(c)) {
		fprintf(stderr,
"Cannot uncompress format '%s'\nCheck __dumpuncompressors for more details.\n",
				argv[1]);
		return RET_ERROR;
	}
	return uncompress_file(argv[2], argv[3], c);
}

ACTION_N(n, n, y, extractcontrol) {
	retvalue result;
	char *control;

	assert (argc == 2);

	result = extractcontrol(&control, argv[1]);

	if (RET_IS_OK(result)) {
		puts(control);
		free(control);
	}
	return result;
}

ACTION_N(n, n, y, extractfilelist) {
	retvalue result;
	char *filelist;
	size_t fls, len;
	size_t lengths[256];
	const unsigned char *dirs[256];
	int depth = 0, i, j;

	assert (argc == 2);

	result = getfilelist(&filelist, &fls, argv[1]);
	if (RET_IS_OK(result)) {
		const unsigned char *p = (unsigned char*)filelist;
		while (*p != '\0') {
			unsigned char c = *(p++);
			if (c > 2) {
				if (depth >= c)
					depth -= c;
				else
					depth = 0;
			} else if (c == 2) {
				len = 0;
				while (*p == 255) {
					len +=255;
					p++;
				}
				len += *(p++);
				lengths[depth] = len;
				dirs[depth++] = p;
				p += len;
			} else {
				len = 0;
				while (*p == 255) {
					len +=255;
					p++;
				}
				len += *(p++);
				(void)putchar('/');
				for (i = 0 ; i < depth ; i++) {
					const unsigned char *n = dirs[i];
					j = lengths[i];
					while (j-- > 0)
						(void)putchar(*(n++));
					(void)putchar('/');
				}
				while (len-- > 0)
					(void)putchar(*(p++));
				(void)putchar('\n');
			}
		}
		free(filelist);
	}
	return result;
}

ACTION_N(n, n, y, extractsourcesection) {
	struct dsc_headers dsc;
	struct sourceextraction *extraction;
	char *section = NULL, *priority = NULL, *directory, *filename;
	retvalue result, r;
	bool broken;
	int i;

	assert (argc == 2);

	r = sources_readdsc(&dsc, argv[1], argv[1], &broken);
	if (!RET_IS_OK(r))
		return r;
	if (broken && !IGNORING(brokensignatures,
"'%s' contains only broken signatures.\n"
"This most likely means the file was damaged or edited improperly\n",
				argv[1]))
		return RET_ERROR;
	r = dirs_getdirectory(argv[1], &directory);
	if (RET_WAS_ERROR(r)) {
		sources_done(&dsc);
		return r;
	}
	assert (RET_IS_OK(r));

	extraction = sourceextraction_init(&section, &priority);
	if (FAILEDTOALLOC(extraction)) {
		sources_done(&dsc);
		return RET_ERROR_OOM;
	}
	for (i = 0 ; i < dsc.files.names.count ; i ++)
		sourceextraction_setpart(extraction, i,
				dsc.files.names.values[i]);
	result = RET_OK;
	while (sourceextraction_needs(extraction, &i)) {
		filename = calc_dirconcat(directory, dsc.files.names.values[i]);
		if (FAILEDTOALLOC(filename)) {
			result = RET_ERROR_OOM;
			break;
		}
		r = sourceextraction_analyse(extraction, filename);
		free(filename);
		if (RET_WAS_ERROR(r)) {
			result = r;
			break;
		}
	}
	free(directory);
	if (RET_WAS_ERROR(result)) {
		sourceextraction_abort(extraction);
	} else {
		r = sourceextraction_finish(extraction);
		RET_UPDATE(result, r);
	}
	if (RET_IS_OK(result)) {
		if (section != NULL)
			printf("Section: %s\n", section);
		if (priority != NULL)
			printf("Priority: %s\n", priority);
	}
	sources_done(&dsc);
	free(section);
	free(priority);
	return result;
}

ACTION_F(n, n, n, y, fakeemptyfilelist) {
	assert (argc == 2);
	return fakefilelist(argv[1]);
}

ACTION_F(n, n, n, y, generatefilelists) {
	assert (argc == 2 || argc == 3);

	if (argc == 2)
		return files_regenerate_filelist(false);
	if (strcmp(argv[1], "reread") == 0)
		return files_regenerate_filelist(true);

	fprintf(stderr, "Error: Unrecognized second argument '%s'\n"
			"Syntax: reprepro generatefilelists [reread]\n",
				argv[1]);
	return RET_ERROR;
}

ACTION_T(n, n, translatefilelists) {
	return database_translate_filelists();
}

ACTION_N(n, n, n, translatelegacychecksums) {

	assert (argc == 1);

	return database_translate_legacy_checksums(
			verbosedatabase || verbose > 10);
}

ACTION_T(n, n, translatelegacyreferences) {
	return database_translate_legacy_references();
}

ACTION_F(n, n, n, n, addmd5sums) {
	char buffer[2000], *c, *m;
	retvalue result, r;

	result = RET_NOTHING;

	while (fgets(buffer, 1999, stdin) != NULL) {
		struct checksums *checksums;

		c = strchr(buffer, '\n');
		if (c == NULL) {
			fprintf(stderr, "Line too long\n");
			return RET_ERROR;
		}
		*c = '\0';
		m = strchr(buffer, ' ');
		if (m == NULL) {
			fprintf(stderr, "Malformed line\n");
			return RET_ERROR;
		}
		*m = '\0'; m++;
		if (*m == '\0') {
			fprintf(stderr, "Malformed line\n");
			return RET_ERROR;
		}
		r = checksums_setall(&checksums, m, strlen(m));
		if (RET_WAS_ERROR(r))
			return r;
		r = files_add_checksums(buffer, checksums);
		RET_UPDATE(result, r);
		checksums_free(checksums);

	}
	return result;
}


ACTION_R(n, n, n, y, removereferences) {
	assert (argc == 2);
	return references_remove(argv[1]);
}

ACTION_R(n, n, n, y, removereference) {
	assert (argc == 3);
	return references_decrement(argv[2], argv[1]);
}

ACTION_R(n, n, n, n, dumpreferences) {
	return references_dump();
}

static retvalue checkifreferenced(UNUSED(void *data), const char *filekey) {
	retvalue r;

	r = references_isused(filekey);
	if (r == RET_NOTHING) {
		printf("%s\n", filekey);
		return RET_OK;
	} else if (RET_IS_OK(r)) {
		return RET_NOTHING;
	} else
		return r;
}

ACTION_RF(n, n, n, n, dumpunreferenced) {
	retvalue result;

	result = files_foreach(checkifreferenced, NULL);
	return result;
}

static retvalue deleteifunreferenced(UNUSED(void *data), const char *filekey) {
	retvalue r;

	r = references_isused(filekey);
	if (r == RET_NOTHING) {
		r = pool_delete(filekey);
		return r;
	} else if (RET_IS_OK(r)) {
		return RET_NOTHING;
	} else
		return r;
}

ACTION_RF(n, n, n, n, deleteunreferenced) {
	retvalue result;

	if (keepunreferenced) {
		if (owner_keepunreferenced == CONFIG_OWNER_CMDLINE)
			fprintf(stderr,
"Calling deleteunreferenced with --keepunreferencedfiles does not really make sense, does it?\n");
		else
			fprintf(stderr,
"Error: deleteunreferenced called with option\n"
"'keepunreferencedfiles' activated. Please run\n"
"'reprepro --nokeepunreferencedfiles deleteunreferenced',\n"
"if you are sure you want to delete those files.\n");
		return RET_ERROR;
	}
	result = files_foreach(deleteifunreferenced, NULL);
	return result;
}

ACTION_RF(n, n, n, y, deleteifunreferenced) {
	char buffer[5000], *nl;
	int i;
	retvalue r, ret;

	ret = RET_NOTHING;
	if (argc > 1) {
		for (i = 1 ; i < argc ; i++) {
			r = deleteifunreferenced(NULL, argv[i]);
			RET_UPDATE(ret, r);
			if (r == RET_NOTHING && verbose >= 0)
				fprintf(stderr, "Not removing '%s'\n",
						argv[i]);
		}

	} else
		while (fgets(buffer, 4999, stdin) != NULL) {
			nl = strchr(buffer, '\n');
			if (nl == NULL) {
				return RET_ERROR;
			}
			*nl = '\0';
			r = deleteifunreferenced(NULL, buffer);
			RET_UPDATE(ret, r);
			if (r == RET_NOTHING && verbose >= 0)
				fprintf(stderr, "Not removing '%s'\n",
						buffer);
		}
	return ret;
}

ACTION_R(n, n, n, y, addreference) {
	assert (argc == 2 || argc == 3);
	return references_increment(argv[1], argv[2]);
}

ACTION_R(n, n, n, y, addreferences) {
	char buffer[5000], *nl;
	int i;
	retvalue r, ret;

	ret = RET_NOTHING;

	if (argc > 2) {
		for (i = 2 ; i < argc ; i++) {
			const char *filename = argv[i];
			r = references_increment(filename, argv[1]);
			RET_UPDATE(ret, r);
		}
	} else {
		while (fgets(buffer, 4999, stdin) != NULL) {
			nl = strchr(buffer, '\n');
			if (nl == NULL) {
				return RET_ERROR;
			}
			*nl = '\0';
			r = references_increment(buffer, argv[1]);
			RET_UPDATE(ret, r);
		}
	}

	return ret;
}

static retvalue remove_from_target(struct distribution *distribution, struct trackingdata *trackingdata, struct target *target, int count, struct nameandversion *nameandversion, int *remaining) {
	retvalue result, r;
	int i;

	result = RET_NOTHING;
	for (i = 0 ; i < count ; i++){
		r = target_removepackage(target, distribution->logger,
				nameandversion[i].name, nameandversion[i].version, trackingdata);
		RET_UPDATE(distribution->status, r);
		if (RET_IS_OK(r)) {
			if (!nameandversion[i].found)
				(*remaining)--;
			nameandversion[i].found = true;
		}
		RET_UPDATE(result, r);
	}
	return result;
}

ACTION_D(y, n, y, remove) {
	retvalue result, r;
	struct distribution *distribution;
	struct nameandversion data[argc-2];
	struct target *t;
	char *delimiter;
	int remaining;

	trackingdb tracks;
	struct trackingdata trackingdata;

	r = distribution_get(alldistributions, argv[1], true, &distribution);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r))
		return r;

	if (distribution->readonly) {
		fprintf(stderr,
"Cannot remove packages from read-only distribution '%s'\n",
				distribution->codename);
		return RET_ERROR;
	}

	r = distribution_prepareforwriting(distribution);
	if (RET_WAS_ERROR(r))
		return r;

	if (distribution->tracking != dt_NONE) {
		r = tracking_initialize(&tracks, distribution, false);
		if (RET_WAS_ERROR(r)) {
			return r;
		}
		r = trackingdata_new(tracks, &trackingdata);
		if (RET_WAS_ERROR(r)) {
			(void)tracking_done(tracks, distribution);
			return r;
		}
	}

	for (int i = 0 ; i < argc-2 ; i++) {
		data[i].found = false;
		r = splitnameandversion(argv[2 + i], &data[i].name, &data[i].version);
		if (RET_WAS_ERROR(r)) {
			for (i-- ; i >= 0 ; i--) {
				splitnameandversion_done(&data[i].name, &data[i].version);
			}
			return r;
		}
	}

	remaining = argc-2;
	result = RET_NOTHING;
	for (t = distribution->targets ; t != NULL ; t = t->next) {
		 if (!target_matches(t, components, architectures, packagetypes))
			 continue;
		 r = target_initpackagesdb(t, READWRITE);
		 RET_UPDATE(result, r);
		 if (RET_WAS_ERROR(r))
			 break;
		 r = remove_from_target(distribution,
				 (distribution->tracking != dt_NONE)
				 	? &trackingdata
					: NULL,
				 t, argc-2, data,
				 &remaining);
		 RET_UPDATE(result, r);
		 r = target_closepackagesdb(t);
		 RET_UPDATE(distribution->status, r);
		 RET_UPDATE(result, r);
		 if (RET_WAS_ERROR(result))
			 break;
	}

	if (distribution->tracking != dt_NONE) {
		if (RET_WAS_ERROR(result))
			trackingdata_done(&trackingdata);
		else
			trackingdata_finish(tracks, &trackingdata);
		r = tracking_done(tracks, distribution);
		RET_ENDUPDATE(result, r);
	}
	if (verbose >= 0 && !RET_WAS_ERROR(result) && remaining > 0) {
		int i = argc - 2;

		(void)fputs("Not removed as not found: ", stderr);
		delimiter = "";
		for (i = 0; i < argc - 2; i++) {
			if (!data[i].found) {
				if (data[i].version == NULL) {
					fprintf(stderr, "%s%s", delimiter, data[i].name);
				} else {
					fprintf(stderr, "%s%s=%s", delimiter, data[i].name, data[i].version);
				}
				remaining--;
				delimiter = ", ";
			}
		}
		(void)fputc('\n', stderr);
	}
	for (int i = 0; i < argc - 2; i++) {
		splitnameandversion_done(&data[i].name, &data[i].version);
	}
	return result;
}

struct removesrcdata {
	const char *sourcename;
	const char /*@null@*/ *sourceversion;
	bool found;
};

static retvalue package_source_fits(struct package *package, void *data) {
	struct removesrcdata *d = data;
	retvalue r;

	r = package_getsource(package);
	if (!RET_IS_OK(r))
		return r;
	for (; d->sourcename != NULL ; d++) {
		if (strcmp(package->source, d->sourcename) != 0)
			continue;
		if (d->sourceversion == NULL)
			break;
		if (strcmp(package->sourceversion, d->sourceversion) == 0)
			break;
	}
	if (d->sourcename == NULL)
		return RET_NOTHING;
	else {
		d->found = true;
		return RET_OK;
	}
}

static retvalue remove_packages(struct distribution *distribution, struct removesrcdata *toremove) {
	trackingdb tracks;
	retvalue result, r;

	r = distribution_prepareforwriting(distribution);
	if (RET_WAS_ERROR(r))
		return r;

	if (distribution->tracking != dt_NONE) {
		r = tracking_initialize(&tracks, distribution, false);
		if (RET_WAS_ERROR(r)) {
			return r;
		}
		if (r == RET_NOTHING)
			tracks = NULL;
	} else
		tracks = NULL;
	result = RET_NOTHING;
	if (tracks != NULL) {
		result = RET_NOTHING;
		for (; toremove->sourcename != NULL ; toremove++) {
			r = tracking_removepackages(tracks, distribution,
					toremove->sourcename,
					toremove->sourceversion);
			RET_UPDATE(result, r);
			if (r == RET_NOTHING) {
				if (verbose >= -2) {
					if (toremove->sourceversion == NULL)
						fprintf(stderr,
"Nothing about source package '%s' found in the tracking data of '%s'!\n"
"This either means nothing from this source in this version is there,\n"
"or the tracking information might be out of date.\n",
							toremove->sourcename,
							distribution->codename);
					else
						fprintf(stderr,
"Nothing about '%s' version '%s' found in the tracking data of '%s'!\n"
"This either means nothing from this source in this version is there,\n"
"or the tracking information might be out of date.\n",
							toremove->sourcename,
							toremove->sourceversion,
							distribution->codename);
				}
			}
		}
		r = tracking_done(tracks, distribution);
		RET_ENDUPDATE(result, r);
		return result;
	}
	return package_remove_each(distribution,
			// TODO: why not arch comp pt here?
			NULL, NULL, NULL,
			package_source_fits, NULL,
			toremove);
}

ACTION_D(n, n, y, removesrc) {
	retvalue r;
	struct distribution *distribution;
	struct removesrcdata data[2];

	r = distribution_get(alldistributions, argv[1], true, &distribution);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r))
		return r;

	if (distribution->readonly) {
		fprintf(stderr,
"Error: Cannot remove packages from read-only distribution '%s'\n",
				distribution->codename);
		return RET_ERROR;
	}

	data[0].found = false;
	data[0].sourcename = argv[2];
	if (argc <= 3)
		data[0].sourceversion = NULL;
	else
		data[0].sourceversion = argv[3];
	if (index(data[0].sourcename, '=') != NULL && verbose >= 0) {
		fputs(
"Warning: removesrc treats '=' as normal character.\n"
"Did you want to use removesrcs?\n",
			stderr);
	}
	data[1].sourcename = NULL;
	data[1].sourceversion = NULL;
	return remove_packages(distribution, data);
}

ACTION_D(n, n, y, removesrcs) {
	retvalue r;
	struct distribution *distribution;
	struct removesrcdata data[argc-1];
	int i;

	r = distribution_get(alldistributions, argv[1], true, &distribution);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r))
		return r;

	if (distribution->readonly) {
		fprintf(stderr,
"Error: Cannot remove packages from read-only distribution '%s'\n",
				distribution->codename);
		return RET_ERROR;
	}
	for (i = 0 ; i < argc-2 ; i++) {
		data[i].found = false;
		r = splitnameandversion(argv[2 + i], &data[i].sourcename, &data[i].sourceversion);
		if (RET_WAS_ERROR(r)) {
			for (i--; i >= 0; i--) {
				splitnameandversion_done(&data[i].sourcename, &data[i].sourceversion);
			}
			return r;
		}
	}
	data[i].sourcename = NULL;
	data[i].sourceversion= NULL;
	r = remove_packages(distribution, data);
	for (i = 0 ; i < argc-2 ; i++) {
		if (verbose >= 0 && !data[i].found) {
			if (data[i].sourceversion != NULL)
				fprintf(stderr,
"No package from source '%s', version '%s' found.\n",
						data[i].sourcename,
						data[i].sourceversion);
			else
				fprintf(stderr,
"No package from source '%s' (any version) found.\n",
						data[i].sourcename);
		}
		splitnameandversion_done(&data[i].sourcename, &data[i].sourceversion);
	}
	return r;
}

static retvalue package_matches_condition(struct package *package, void *data) {
	term *condition = data;

	return term_decidepackage(condition, package, package->target);
}

ACTION_D(y, n, y, removefilter) {
	retvalue result, r;
	struct distribution *distribution;
	trackingdb tracks;
	struct trackingdata trackingdata;
	term *condition;

	assert (argc == 3);

	r = distribution_get(alldistributions, argv[1], true, &distribution);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r))
		return r;

	if (distribution->readonly) {
		fprintf(stderr,
"Error: Cannot remove packages from read-only distribution '%s'\n",
				distribution->codename);
		return RET_ERROR;
	}

	result = term_compilefortargetdecision(&condition, argv[2]);
	if (RET_WAS_ERROR(result))
		return result;

	r = distribution_prepareforwriting(distribution);
	if (RET_WAS_ERROR(r)) {
		term_free(condition);
		return r;
	}

	if (distribution->tracking != dt_NONE) {
		r = tracking_initialize(&tracks, distribution, false);
		if (RET_WAS_ERROR(r)) {
			term_free(condition);
			return r;
		}
		if (r == RET_NOTHING)
			tracks = NULL;
		else {
			r = trackingdata_new(tracks, &trackingdata);
			if (RET_WAS_ERROR(r)) {
				(void)tracking_done(tracks, distribution);
				term_free(condition);
				return r;
			}
		}
	} else
		tracks = NULL;

	result = package_remove_each(distribution,
			components, architectures, packagetypes,
			package_matches_condition,
			(tracks != NULL)?&trackingdata:NULL,
			condition);
	if (tracks != NULL) {
		trackingdata_finish(tracks, &trackingdata);
		r = tracking_done(tracks, distribution);
		RET_ENDUPDATE(result, r);
	}
	term_free(condition);
	return result;
}

static retvalue package_matches_glob(struct package *package, void *data) {
	if (globmatch(package->name, data))
		return RET_OK;
	else
		return RET_NOTHING;
}

ACTION_D(y, n, y, removematched) {
	retvalue result, r;
	struct distribution *distribution;
	trackingdb tracks;
	struct trackingdata trackingdata;

	assert (argc == 3);

	r = distribution_get(alldistributions, argv[1], true, &distribution);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r))
		return r;

	if (distribution->readonly) {
		fprintf(stderr,
"Error: Cannot remove packages from read-only distribution '%s'\n",
				distribution->codename);
		return RET_ERROR;
	}

	r = distribution_prepareforwriting(distribution);
	if (RET_WAS_ERROR(r))
		return r;

	if (distribution->tracking != dt_NONE) {
		r = tracking_initialize(&tracks, distribution, false);
		if (RET_WAS_ERROR(r))
			return r;
		if (r == RET_NOTHING)
			tracks = NULL;
		else {
			r = trackingdata_new(tracks, &trackingdata);
			if (RET_WAS_ERROR(r)) {
				(void)tracking_done(tracks, distribution);
				return r;
			}
		}
	} else
		tracks = NULL;

	result = package_remove_each(distribution,
			components, architectures, packagetypes,
			package_matches_glob,
			(tracks != NULL)?&trackingdata:NULL,
			(void*)argv[2]);
	if (tracks != NULL) {
		trackingdata_finish(tracks, &trackingdata);
		r = tracking_done(tracks, distribution);
		RET_ENDUPDATE(result, r);
	}
	return result;
}

ACTION_B(y, n, y, buildneeded) {
	retvalue r;
	struct distribution *distribution;
	const char *glob;
	architecture_t arch;
	bool anyarchitecture;

	if (architectures != NULL) {
		fprintf(stderr,
"Error: build-needing cannot be used with --architecture!\n");
		return RET_ERROR;
	}
	if (packagetypes != NULL) {
		fprintf(stderr,
"Error: build-needing cannot be used with --packagetype!\n");
		return RET_ERROR;
	}

	if (argc == 4)
		glob = argv[3];
	else
		glob = NULL;

	if (strcmp(argv[2], "any") == 0) {
		anyarchitecture = true;
	} else {
		anyarchitecture = false;
		arch = architecture_find(argv[2]);
		if (!atom_defined(arch)) {
			fprintf(stderr,
"Error: Architecture '%s' is not known!\n", argv[2]);
			return RET_ERROR;
		}
		if (arch == architecture_source) {
			fprintf(stderr,
"Error: Architecture '%s' makes no sense for build-needing!\n", argv[2]);
			return RET_ERROR;
		}
	}
	r = distribution_get(alldistributions, argv[1], false, &distribution);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r))
		return r;

	if (!atomlist_in(&distribution->architectures, architecture_source)) {
		fprintf(stderr,
"Error: Architecture '%s' does not contain sources. build-needing cannot be used!\n",
				distribution->codename);
		return RET_ERROR;
	}
	if (anyarchitecture) {
		retvalue result;
		int i;

		result = find_needs_build(distribution,
				architecture_all,
				components, glob, true);

		for (i = 0 ; i < distribution->architectures.count ; i++) {
			architecture_t a = distribution->architectures.atoms[i];

			if (a == architecture_source || a == architecture_all)
				continue;
			r = find_needs_build(distribution, a,
					components, glob, true);
			RET_UPDATE(result, r);
		}
		return result;
	} else {
		if (!atomlist_in(&distribution->architectures, arch) &&
				arch != architecture_all) {
			fprintf(stderr,
"Error: Architecture '%s' not found in distribution '%s'!\n", argv[2],
					distribution->codename);
			return RET_ERROR;
		}

		return find_needs_build(distribution, arch, components,
				glob, false);
	}
}

ACTION_C(n, n, n, listcodenames) {
	retvalue r = RET_NOTHING;
	struct distribution *d;

	for (d = alldistributions ; d != NULL ; d = d->next) {
		puts(d->codename);
		r = RET_OK;
	}
	return r;
}

static retvalue list_in_target(struct target *target, const char *packagename) {
	retvalue r, result = RET_NOTHING;
	struct package_cursor iterator;

	if (listmax == 0)
		return RET_NOTHING;

	r = package_openduplicateiterator(target, packagename, 0, &iterator);
	if (!RET_IS_OK(r))
		return r;

	do {
		if (listskip <= 0) {
			r = listformat_print(listformat, &iterator.current);
			RET_UPDATE(result, r);
			if (listmax > 0)
				listmax--;
		} else
			listskip--;
	} while (package_next(&iterator));
	r = package_closeiterator(&iterator);
	RET_ENDUPDATE(result, r);
	return result;
}

static retvalue list_package(struct package *package, UNUSED(void *dummy3)) {
	if (listmax == 0)
		return RET_NOTHING;

	if (listskip <= 0) {
		if (listmax > 0)
			listmax--;
		return listformat_print(listformat, package);
	} else {
		listskip--;
		return RET_NOTHING;
	}
}

ACTION_B(y, n, y, list) {
	retvalue result = RET_NOTHING, r;
	struct distribution *distribution;
	struct target *t;

	assert (argc >= 2);

	r = distribution_get(alldistributions, argv[1], false, &distribution);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r))
		return r;

	if (argc == 2)
		return package_foreach(distribution,
			components, architectures, packagetypes,
			list_package, NULL, NULL);
	else for (t = distribution->targets ; t != NULL ; t = t->next) {
		if (!target_matches(t, components, architectures, packagetypes))
			continue;
		r = list_in_target(t, argv[2]);
		if (RET_WAS_ERROR(r))
			return r;
		RET_UPDATE(result, r);
	}
	return result;
}

struct lsversion {
	/*@null@*/struct lsversion *next;
	char *version;
	struct atomlist architectures;
};
struct lspart {
	struct lspart *next;
	const char *codename;
	const char *component;
	struct lsversion *versions;
};

static retvalue newlsversion(struct lsversion **versions_p, struct package *package, architecture_t architecture) {
	struct lsversion *v, **v_p;

	for (v_p = versions_p ; (v = *v_p) != NULL ; v_p = &v->next) {
		if (strcmp(v->version, package->version) != 0)
			continue;
		return atomlist_add_uniq(&v->architectures, architecture);
	}
	v = zNEW(struct lsversion);
	if (FAILEDTOALLOC(v))
		return RET_ERROR_OOM;
	*v_p = v;
	v->version = package_dupversion(package);
	if (FAILEDTOALLOC(v->version))
		return RET_ERROR_OOM;
	return atomlist_add(&v->architectures, architecture);
}

static retvalue ls_in_target(struct target *target, const char *packagename, struct lsversion **versions_p) {
	retvalue r, result;
	struct package_cursor iterator;

	result = package_openduplicateiterator(target, packagename, 0, &iterator);
	if (!RET_IS_OK(result))
		return result;
	do {
		r = package_getversion(&iterator.current);
		if (RET_IS_OK(r))
			r = newlsversion(versions_p, &iterator.current,
					target->architecture);
		RET_UPDATE(result, r);
	} while (package_next(&iterator));
	r = package_closeiterator(&iterator);
	RET_ENDUPDATE(result, r);
	return result;
}

static inline retvalue printlsparts(const char *pkgname, struct lspart *parts) {
	int versionlen, codenamelen, componentlen;
	struct lspart *p;
	retvalue result = RET_NOTHING;

	versionlen = 0; codenamelen = 0; componentlen = 0;
	for (p = parts ; p->codename != NULL ; p = p->next) {
		struct lsversion *v;
		int l;

		l = strlen(p->codename);
		if (l > codenamelen)
			codenamelen = l;
		if (p->component != NULL) {
			l = strlen(p->component);
			if (l > componentlen)
				componentlen = l;
		}
		for (v = p->versions ; v != NULL ; v = v->next) {
			l = strlen(v->version);
			if (l > versionlen)
				versionlen = l;
		}
	}
	while (parts->codename != NULL) {
		p = parts;
		parts = parts->next;
		while (p->versions != NULL) {
			architecture_t a; int i;
			struct lsversion *v;

			v = p->versions;
			p->versions = v->next;

			result = RET_OK;
			printf("%s | %*s | %*s | ", pkgname,
					versionlen, v->version,
					codenamelen, p->codename);
			if (componentlen > 0 && p->component != NULL)
				printf("%*s | ", componentlen, p->component);
			for (i = 0 ; i + 1 < v->architectures.count ; i++) {
				a = v->architectures.atoms[i];
				printf("%s, ", atoms_architectures[a]);
			}
			a = v->architectures.atoms[i];
			puts(atoms_architectures[a]);

			free(v->version);
			atomlist_done(&v->architectures);
			free(v);
		}
		free(p);
	}
	free(parts);
	return result;
}

ACTION_B(y, n, y, ls) {
	retvalue r;
	struct distribution *d;
	struct target *t;
	struct lspart *first, *last;

	assert (argc == 2);

	first = zNEW(struct lspart);
	last = first;

	for (d = alldistributions ; d != NULL ; d = d->next) {
		for (t = d->targets ; t != NULL ; t = t->next) {
			if (!target_matches(t, components, architectures,
						packagetypes))
				continue;
			r = ls_in_target(t, argv[1], &last->versions);
			if (RET_WAS_ERROR(r))
				return r;
		}
		if (last->versions != NULL) {
			last->codename = d->codename;
			last->next = zNEW(struct lspart);
			last = last->next;
		}
	}
	return printlsparts(argv[1], first);
}

ACTION_B(y, n, y, lsbycomponent) {
	retvalue r;
	struct distribution *d;
	struct target *t;
	struct lspart *first, *last;
	int i;

	assert (argc == 2);

	first = zNEW(struct lspart);
	last = first;

	for (d = alldistributions ; d != NULL ; d = d->next) {
		for (i = 0 ; i < d->components.count ; i ++) {
			component_t component = d->components.atoms[i];

			if (limitations_missed(components, component))
				continue;
			for (t = d->targets ; t != NULL ; t = t->next) {
				if (t->component != component)
					continue;
				if (limitations_missed(architectures,
							t->architecture))
					continue;
				if (limitations_missed(packagetypes,
							t->packagetype))
					continue;
				r = ls_in_target(t, argv[1], &last->versions);
				if (RET_WAS_ERROR(r))
					return r;
			}
			if (last->versions != NULL) {
				last->codename = d->codename;
				last->component = atoms_components[component];
				last->next = zNEW(struct lspart);
				last = last->next;
			}
		}
	}
	return printlsparts(argv[1], first);
}

static retvalue listfilterprint(struct package *package, void *data) {
	term *condition = data;
	retvalue r;

	if (listmax == 0)
		return RET_NOTHING;

	r = term_decidepackage(condition, package, package->target);
	if (RET_IS_OK(r)) {
		if (listskip <= 0) {
			if (listmax > 0)
				listmax--;
			r = listformat_print(listformat, package);
		} else {
			listskip--;
			r = RET_NOTHING;
		}
	}
	return r;
}

ACTION_B(y, n, y, listfilter) {
	retvalue r, result;
	struct distribution *distribution;
	term *condition;

	assert (argc == 3);

	r = distribution_get(alldistributions, argv[1], false, &distribution);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r)) {
		return r;
	}
	result = term_compilefortargetdecision(&condition, argv[2]);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	result = package_foreach(distribution,
			components, architectures, packagetypes,
			listfilterprint, NULL, condition);
	term_free(condition);
	return result;
}

static retvalue listmatchprint(struct package *package, void *data) {
	const char *glob = data;

	if (listmax == 0)
		return RET_NOTHING;

	if (globmatch(package->name, glob)) {
		if (listskip <= 0) {
			if (listmax > 0)
				listmax--;
			return listformat_print(listformat, package);
		} else {
			listskip--;
			return RET_NOTHING;
		}
	} else
		return RET_NOTHING;
}

ACTION_B(y, n, y, listmatched) {
	retvalue r, result;
	struct distribution *distribution;

	assert (argc == 3);

	r = distribution_get(alldistributions, argv[1], false, &distribution);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r)) {
		return r;
	}
	result = package_foreach(distribution,
			components, architectures, packagetypes,
			listmatchprint, NULL, (void*)argv[2]);
	return result;
}

ACTION_F(n, n, n, y, detect) {
	char buffer[5000], *nl;
	int i;
	retvalue r, ret;

	ret = RET_NOTHING;
	if (argc > 1) {
		for (i = 1 ; i < argc ; i++) {
			r = files_detect(argv[i]);
			RET_UPDATE(ret, r);
		}

	} else
		while (fgets(buffer, 4999, stdin) != NULL) {
			nl = strchr(buffer, '\n');
			if (nl == NULL) {
				return RET_ERROR;
			}
			*nl = '\0';
			r = files_detect(buffer);
			RET_UPDATE(ret, r);
		}
	return ret;
}

ACTION_F(n, n, n, y, forget) {
	char buffer[5000], *nl;
	int i;
	retvalue r, ret;

	ret = RET_NOTHING;
	if (argc > 1) {
		for (i = 1 ; i < argc ; i++) {
			r = files_remove(argv[i]);
			RET_UPDATE(ret, r);
		}

	} else
		while (fgets(buffer, 4999, stdin) != NULL) {
			nl = strchr(buffer, '\n');
			if (nl == NULL) {
				return RET_ERROR;
			}
			*nl = '\0';
			r = files_remove(buffer);
			RET_UPDATE(ret, r);
		}
	return ret;
}

ACTION_F(n, n, n, n, listmd5sums) {
	return files_printmd5sums();
}

ACTION_F(n, n, n, n, listchecksums) {
	return files_printchecksums();
}

ACTION_B(n, n, n, dumpcontents) {
	retvalue result, r;
	struct table *packages;
	const char *package, *chunk;
	struct cursor *cursor;

	assert (argc == 2);

	result = database_openpackages(argv[1], true, &packages);
	if (RET_WAS_ERROR(result))
		return result;
	r = table_newglobalcursor(packages, true, &cursor);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r)) {
		(void)table_close(packages);
		return r;
	}
	result = RET_NOTHING;
	while (cursor_nexttempdata(packages, cursor, &package, &chunk, NULL)) {
		printf("'%s' -> '%s'\n", package, chunk);
		result = RET_OK;
	}
	r = cursor_close(packages, cursor);
	RET_ENDUPDATE(result, r);
	r = table_close(packages);
	RET_ENDUPDATE(result, r);
	return result;
}

ACTION_F(n, n, y, y, export) {
	retvalue result, r;
	struct distribution *d;

	if (export == EXPORT_NEVER || export == EXPORT_SILENT_NEVER) {
		fprintf(stderr,
"Error: reprepro export incompatible with --export=never\n");
		return RET_ERROR;
	}

	result = distribution_match(alldistributions, argc-1, argv+1, true, READWRITE);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		if (!d->selected)
			continue;

		if (d->exportoptions[deo_noexport]) {
			/* if explicitly selected, warn if not used: */
			if (argc > 1 && verbose >= 0 ) {
				printf("No exporting %s (as it has the noexport option set).\n", d->codename);
			}
			continue;
		}

		if (verbose > 0) {
			printf("Exporting %s...\n", d->codename);
		}
		r = distribution_fullexport(d);
		if (RET_IS_OK(r))
			/* avoid being exported again */
			d->lookedat = false;
		RET_UPDATE(result, r);
		if (RET_WAS_ERROR(r) && export != EXPORT_FORCE) {
			return r;
		}
	}
	return result;
}

/***********************update********************************/

ACTION_D(y, n, y, update) {
	retvalue result;
	struct update_pattern *patterns;
	struct update_distribution *u_distributions;

	result = dirs_make_recursive(global.listdir);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	result = distribution_match(alldistributions, argc-1, argv+1, true, READWRITE);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	result = updates_getpatterns(&patterns);
	if (RET_WAS_ERROR(result))
		return result;
	assert (RET_IS_OK(result));

	result = updates_calcindices(patterns, alldistributions,
			components, architectures, packagetypes,
			&u_distributions);
	if (!RET_IS_OK(result)) {
		if (result == RET_NOTHING) {
			if (argc == 1)
				fputs(
"Nothing to do, because no distribution has an Update: field.\n", stderr);
			else
				fputs(
"Nothing to do, because none of the selected distributions has an Update: field.\n",
					stderr);
		}
		updates_freepatterns(patterns);
		return result;
	}
	assert (RET_IS_OK(result));

	if (!RET_WAS_ERROR(result))
		result = updates_update(u_distributions,
				nolistsdownload, skipold,
				spacecheckmode, reserveddbspace,
				reservedotherspace);
	updates_freeupdatedistributions(u_distributions);
	updates_freepatterns(patterns);
	return result;
}

ACTION_D(y, n, y, predelete) {
	retvalue result;
	struct update_pattern *patterns;
	struct update_distribution *u_distributions;

	result = dirs_make_recursive(global.listdir);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	result = distribution_match(alldistributions, argc-1, argv+1, true, READWRITE);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	result = updates_getpatterns(&patterns);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	assert (RET_IS_OK(result));

	result = updates_calcindices(patterns, alldistributions,
			components, architectures, packagetypes,
			&u_distributions);
	if (!RET_IS_OK(result)) {
		if (result == RET_NOTHING) {
			if (argc == 1)
				fputs(
"Nothing to do, because no distribution has an Update: field.\n", stderr);
			else
				fputs(
"Nothing to do, because none of the selected distributions has an Update: field.\n",
					stderr);
		}
		updates_freepatterns(patterns);
		return result;
	}
	assert (RET_IS_OK(result));

	if (!RET_WAS_ERROR(result))
		result = updates_predelete(u_distributions,
				nolistsdownload, skipold);
	updates_freeupdatedistributions(u_distributions);
	updates_freepatterns(patterns);
	return result;
}

ACTION_B(y, n, y, checkupdate) {
	retvalue result;
	struct update_pattern *patterns;
	struct update_distribution *u_distributions;

	result = dirs_make_recursive(global.listdir);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	result = distribution_match(alldistributions, argc-1, argv+1, false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	result = updates_getpatterns(&patterns);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	result = updates_calcindices(patterns, alldistributions,
			components, architectures, packagetypes,
			&u_distributions);
	if (!RET_IS_OK(result)) {
		if (result == RET_NOTHING) {
			if (argc == 1)
				fputs(
"Nothing to do, because no distribution has an Updates: field.\n", stderr);
			else
				fputs(
"Nothing to do, because none of the selected distributions has an Update: field.\n",
					stderr);
		}
		updates_freepatterns(patterns);
		return result;
	}

	result = updates_checkupdate(u_distributions,
			nolistsdownload, skipold);

	updates_freeupdatedistributions(u_distributions);
	updates_freepatterns(patterns);

	return result;
}

ACTION_B(y, n, y, dumpupdate) {
	retvalue result;
	struct update_pattern *patterns;
	struct update_distribution *u_distributions;

	result = dirs_make_recursive(global.listdir);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	result = distribution_match(alldistributions, argc-1, argv+1, false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	result = updates_getpatterns(&patterns);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	result = updates_calcindices(patterns, alldistributions,
			components, architectures, packagetypes,
			&u_distributions);
	if (!RET_IS_OK(result)) {
		if (result == RET_NOTHING) {
			if (argc == 1)
				fputs(
"Nothing to do, because no distribution has an Updates: field.\n", stderr);
			else
				fputs(
"Nothing to do, because none of the selected distributions has an Update: field.\n",
					stderr);
		}
		updates_freepatterns(patterns);
		return result;
	}

	result = updates_dumpupdate(u_distributions,
			nolistsdownload, skipold);

	updates_freeupdatedistributions(u_distributions);
	updates_freepatterns(patterns);

	return result;
}

ACTION_L(n, n, n, n, cleanlists) {
	retvalue result;
	struct update_pattern *patterns;

	assert (argc == 1);

	if (!isdirectory(global.listdir))
		return RET_NOTHING;

	result = updates_getpatterns(&patterns);
	if (RET_WAS_ERROR(result))
		return result;

	result = updates_cleanlists(alldistributions, patterns);
	updates_freepatterns(patterns);
	return result;
}

/***********************migrate*******************************/

ACTION_D(y, n, y, pull) {
	retvalue result;
	struct pull_rule *rules;
	struct pull_distribution *p;

	result = distribution_match(alldistributions, argc-1, argv+1,
			true, READWRITE);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	result = pull_getrules(&rules);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	assert (RET_IS_OK(result));

	result = pull_prepare(alldistributions, rules, fast,
			components, architectures, packagetypes, &p);
	if (RET_WAS_ERROR(result)) {
		pull_freerules(rules);
		return result;
	}
	result = pull_update(p);

	pull_freerules(rules);
	pull_freedistributions(p);
	return result;
}

ACTION_B(y, n, y, checkpull) {
	retvalue result;
	struct pull_rule *rules;
	struct pull_distribution *p;

	result = distribution_match(alldistributions, argc-1, argv+1,
			false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	result = pull_getrules(&rules);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	assert (RET_IS_OK(result));

	result = pull_prepare(alldistributions, rules, fast,
			components, architectures, packagetypes, &p);
	if (RET_WAS_ERROR(result)) {
		pull_freerules(rules);
		return result;
	}
	result = pull_checkupdate(p);

	pull_freerules(rules);
	pull_freedistributions(p);

	return result;
}

ACTION_B(y, n, y, dumppull) {
	retvalue result;
	struct pull_rule *rules;
	struct pull_distribution *p;

	result = distribution_match(alldistributions, argc-1, argv+1,
			false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	result = pull_getrules(&rules);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	assert (RET_IS_OK(result));

	result = pull_prepare(alldistributions, rules, fast,
			components, architectures, packagetypes, &p);
	if (RET_WAS_ERROR(result)) {
		pull_freerules(rules);
		return result;
	}
	result = pull_dumpupdate(p);

	pull_freerules(rules);
	pull_freedistributions(p);

	return result;
}

static retvalue copy_or_move(struct distribution *alldistributions, const struct atomlist * architectures, const struct atomlist * components,
                             const struct atomlist * packagetypes, int argc, const char * argv[], bool remove_source) {
	struct distribution *destination, *source;
	struct nameandversion data[argc-2];
	int i;
	retvalue result;

	result = distribution_get(alldistributions, argv[1], true, &destination);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	result = distribution_get(alldistributions, argv[2], false, &source);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	if (destination->readonly) {
		fprintf(stderr,
"Cannot %s packages to read-only distribution '%s'.\n",
				remove_source ? "move" : "copy", destination->codename);
		return RET_ERROR;
	}
	result = distribution_prepareforwriting(destination);
	if (RET_WAS_ERROR(result))
		return result;

	for (i = 0; i < argc-3; i++) {
		result = splitnameandversion(argv[3 + i], &data[i].name, &data[i].version);
		if (RET_WAS_ERROR(result)) {
			for (i-- ; i >= 0 ; i--) {
				splitnameandversion_done(&data[i].name, &data[i].version);
			}
			return result;
		}
	}
	data[i].name = NULL;
	data[i].version = NULL;

	result = copy_by_name(destination, source, data,
			components, architectures, packagetypes, remove_source);
	for (i = 0; i < argc - 3; i++) {
		splitnameandversion_done(&data[i].name, &data[i].version);
	}
	return result;
}

ACTION_D(y, n, y, copy) {
	return copy_or_move(alldistributions, architectures, components, packagetypes, argc, argv, false);
}

ACTION_D(y, n, y, move) {
	return copy_or_move(alldistributions, architectures, components, packagetypes, argc, argv, true);
}

static retvalue copysrc_or_movesrc(struct distribution *alldistributions, const struct atomlist * architectures, const struct atomlist * components,
                                   const struct atomlist * packagetypes, int argc, const char * argv[], bool remove_source) {
	struct distribution *destination, *source;
	retvalue result;

	result = distribution_get(alldistributions, argv[1], true, &destination);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	result = distribution_get(alldistributions, argv[2], false, &source);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (destination->readonly) {
		fprintf(stderr,
"Cannot %s packages to read-only distribution '%s'.\n",
				remove_source ? "move" : "copy", destination->codename);
		return RET_ERROR;
	}
	result = distribution_prepareforwriting(destination);
	if (RET_WAS_ERROR(result))
		return result;

	return copy_by_source(destination, source, argc-3, argv+3,
			components, architectures, packagetypes, remove_source);
	return result;
}

ACTION_D(y, n, y, copysrc) {
	return copysrc_or_movesrc(alldistributions, architectures, components, packagetypes, argc, argv, false);
}

ACTION_D(y, n, y, movesrc) {
	return copysrc_or_movesrc(alldistributions, architectures, components, packagetypes, argc, argv, true);
}

static retvalue copy_or_move_filter(struct distribution *alldistributions, const struct atomlist * architectures, const struct atomlist * components,
                                    const struct atomlist * packagetypes, int argc, const char * argv[], bool remove_source) {
	struct distribution *destination, *source;
	retvalue result;

	assert (argc == 4);

	result = distribution_get(alldistributions, argv[1], true, &destination);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	result = distribution_get(alldistributions, argv[2], false, &source);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (destination->readonly) {
		fprintf(stderr,
"Cannot %s packages to read-only distribution '%s'.\n",
				remove_source ? "move" : "copy", destination->codename);
		return RET_ERROR;
	}
	result = distribution_prepareforwriting(destination);
	if (RET_WAS_ERROR(result))
		return result;

	return copy_by_formula(destination, source, argv[3],
			components, architectures, packagetypes, remove_source);
}

ACTION_D(y, n, y, copyfilter) {
	return copy_or_move_filter(alldistributions, architectures, components, packagetypes, argc, argv, false);
}

ACTION_D(y, n, y, movefilter) {
	return copy_or_move_filter(alldistributions, architectures, components, packagetypes, argc, argv, true);
}

static retvalue copy_or_move_matched(struct distribution *alldistributions, const struct atomlist * architectures, const struct atomlist * components,
                                     const struct atomlist * packagetypes, int argc, const char * argv[], bool remove_source) {
	struct distribution *destination, *source;
	retvalue result;

	assert (argc == 4);

	result = distribution_get(alldistributions, argv[1], true, &destination);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	result = distribution_get(alldistributions, argv[2], false, &source);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (destination->readonly) {
		fprintf(stderr,
"Cannot %s packages to read-only distribution '%s'.\n",
				remove_source ? "move" : "copy", destination->codename);
		return RET_ERROR;
	}
	result = distribution_prepareforwriting(destination);
	if (RET_WAS_ERROR(result))
		return result;

	return copy_by_glob(destination, source, argv[3],
			components, architectures, packagetypes, remove_source);
}

ACTION_D(y, n, y, copymatched) {
	return copy_or_move_matched(alldistributions, architectures, components, packagetypes, argc, argv, false);
}

ACTION_D(y, n, y, movematched) {
	return copy_or_move_matched(alldistributions, architectures, components, packagetypes, argc, argv, true);
}

ACTION_D(y, n, y, restore) {
	struct distribution *destination;
	retvalue result;

	result = distribution_get(alldistributions, argv[1], true, &destination);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (destination->readonly) {
		fprintf(stderr,
"Cannot copy packages to read-only distribution '%s'.\n",
				destination->codename);
		return RET_ERROR;
	}
	result = distribution_prepareforwriting(destination);
	if (RET_WAS_ERROR(result))
		return result;

	return restore_by_name(destination,
			components, architectures, packagetypes, argv[2],
			argc-3, argv+3);
}

ACTION_D(y, n, y, restoresrc) {
	struct distribution *destination;
	retvalue result;

	result = distribution_get(alldistributions, argv[1], true, &destination);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (destination->readonly) {
		fprintf(stderr,
"Cannot copy packages to read-only distribution '%s'.\n",
				destination->codename);
		return RET_ERROR;
	}
	result = distribution_prepareforwriting(destination);
	if (RET_WAS_ERROR(result))
		return result;

	return restore_by_source(destination,
			components, architectures, packagetypes, argv[2],
			argc-3, argv+3);
}

ACTION_D(y, n, y, restorematched) {
	struct distribution *destination;
	retvalue result;

	assert (argc == 4);

	result = distribution_get(alldistributions, argv[1], true, &destination);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (destination->readonly) {
		fprintf(stderr,
"Cannot copy packages to read-only distribution '%s'.\n",
				destination->codename);
		return RET_ERROR;
	}
	result = distribution_prepareforwriting(destination);
	if (RET_WAS_ERROR(result))
		return result;

	return restore_by_glob(destination,
			components, architectures, packagetypes, argv[2],
			argv[3]);
}

ACTION_D(y, n, y, restorefilter) {
	struct distribution *destination;
	retvalue result;

	assert (argc == 4);

	result = distribution_get(alldistributions, argv[1], true, &destination);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (destination->readonly) {
		fprintf(stderr,
"Cannot copy packages to read-only distribution '%s'.\n",
				destination->codename);
		return RET_ERROR;
	}
	result = distribution_prepareforwriting(destination);
	if (RET_WAS_ERROR(result))
		return result;

	return restore_by_formula(destination,
			components, architectures, packagetypes, argv[2],
			argv[3]);
}

ACTION_D(y, n, y, addpackage) {
	struct distribution *destination;
	retvalue result;
	architecture_t architecture = atom_unknown;
	component_t component = atom_unknown;
	packagetype_t packagetype = atom_unknown;

	if (packagetypes != NULL) {
		if (packagetypes->count > 1) {
			fprintf(stderr,
"_addpackage can only cope with one packagetype at a time!\n");
			return RET_ERROR;
		}
		packagetype = packagetypes->atoms[0];
	}
	if (architectures != NULL) {
		if (architectures->count > 1) {
			fprintf(stderr,
"_addpackage can only cope with one architecture at a time!\n");
			return RET_ERROR;
		}
		architecture = architectures->atoms[0];
	}
	if (components != NULL) {
		if (components->count > 1) {
			fprintf(stderr,
"_addpackage can only cope with one component at a time!\n");
			return RET_ERROR;
		}
		component = components->atoms[0];
	}

	if (!atom_defined(packagetype) && atom_defined(architecture) &&
			architecture == architecture_source)
		packagetype = pt_dsc;
	if (atom_defined(packagetype) && !atom_defined(architecture) &&
			packagetype == pt_dsc)
		architecture = architecture_source;
	// TODO: some more guesses based on components and udebcomponents

	if (!atom_defined(architecture) || !atom_defined(component) ||
			!atom_defined(packagetype)) {
		fprintf(stderr, "_addpackage needs -C and -A and -T set!\n");
		return RET_ERROR;
	}

	result = distribution_get(alldistributions, argv[1], true, &destination);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (destination->readonly) {
		fprintf(stderr,
"Cannot add packages to read-only distribution '%s'.\n",
				destination->codename);
		return RET_ERROR;
	}
	result = distribution_prepareforwriting(destination);
	if (RET_WAS_ERROR(result))
		return result;

	return copy_from_file(destination,
			component, architecture, packagetype, argv[2],
			argc-3, argv+3);
}

/***********************rereferencing*************************/
ACTION_R(n, n, y, y, rereference) {
	retvalue result, r;
	struct distribution *d;
	struct target *t;

	result = distribution_match(alldistributions, argc-1, argv+1, false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		if (!d->selected)
			continue;

		if (verbose > 0) {
			printf("Referencing %s...\n", d->codename);
		}
		for (t = d->targets ; t != NULL ; t = t->next) {
			r = target_rereference(t);
			RET_UPDATE(result, r);
		}
		r = tracking_rereference(d);
		RET_UPDATE(result, r);
		if (RET_WAS_ERROR(r))
			break;
	}

	return result;
}
/***************************retrack****************************/
ACTION_D(n, n, y, retrack) {
	retvalue result, r;
	struct distribution *d;

	result = distribution_match(alldistributions, argc-1, argv+1, false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		if (!d->selected)
			continue;
		if (d->tracking == dt_NONE) {
			if (argc > 1) {
				fprintf(stderr,
"Cannot retrack %s: Tracking not activated for this distribution!\n",
					d->codename);
				RET_UPDATE(result, RET_ERROR);
			}
			continue;
		}
		r = tracking_retrack(d, true);
		RET_ENDUPDATE(result, r);
		if (RET_WAS_ERROR(result))
			break;
	}
	return result;
}

ACTION_D(n, n, y, removetrack) {
	retvalue result, r;
	struct distribution *distribution;
	trackingdb tracks;

	assert (argc == 4);

	result = distribution_get(alldistributions, argv[1], false, &distribution);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	r = tracking_initialize(&tracks, distribution, false);
	if (RET_WAS_ERROR(r)) {
		return r;
	}

	result = tracking_remove(tracks, argv[2], argv[3]);

	r = tracking_done(tracks, distribution);
	RET_ENDUPDATE(result, r);
	return result;
}

ACTION_D(n, n, y, removealltracks) {
	retvalue result, r;
	struct distribution *d;
	const char *codename;
	int i;

	if (delete <= 0)
		for (i = 1 ; i < argc ; i ++) {
			codename = argv[i];

			d = alldistributions;
			while (d != NULL && strcmp(codename, d->codename) != 0)
				d = d->next;
			if (d != NULL && d->tracking != dt_NONE) {
				fprintf(stderr,
"Error: Requested removing of all tracks of distribution '%s',\n"
"which still has tracking enabled. Use --delete to delete anyway.\n",
						codename);
				return RET_ERROR;
			}
		}
	result = RET_NOTHING;
	for (i = 1 ; i < argc ; i ++) {
		codename = argv[i];

		if (verbose >= 0) {
			printf("Deleting all tracks for %s...\n", codename);
		}

		r = tracking_drop(codename);
		RET_UPDATE(result, r);
		if (RET_WAS_ERROR(result))
			break;
		if (r == RET_NOTHING) {
			d = alldistributions;
			while (d != NULL && strcmp(codename, d->codename) != 0)
				d = d->next;
			if (d == NULL) {
				fprintf(stderr,
"Warning: There was no tracking information to delete for '%s',\n"
"which is also not found in conf/distributions. Either this was already\n"
"deleted earlier, or you might have mistyped.\n", codename);
			}
		}
	}
	return result;
}

ACTION_D(n, n, y, tidytracks) {
	retvalue result, r;
	struct distribution *d;

	result = distribution_match(alldistributions, argc-1, argv+1,
			false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		trackingdb tracks;

		if (!d->selected)
			continue;

		if (d->tracking == dt_NONE) {
			r = tracking_drop(d->codename);
			RET_UPDATE(result, r);
			if (RET_WAS_ERROR(r))
				break;
			continue;
		}

		if (verbose >= 0) {
			printf("Looking for old tracks in %s...\n",
					d->codename);
		}
		r = tracking_initialize(&tracks, d, false);
		if (RET_WAS_ERROR(r)) {
			RET_UPDATE(result, r);
			if (RET_WAS_ERROR(r))
				break;
			continue;
		}
		r = tracking_tidyall(tracks);
		RET_UPDATE(result, r);
		r = tracking_done(tracks, d);
		RET_ENDUPDATE(result, r);
		if (RET_WAS_ERROR(result))
			break;
	}
	return result;
}

ACTION_B(n, n, y, dumptracks) {
	retvalue result, r;
	struct distribution *d;

	result = distribution_match(alldistributions, argc-1, argv+1,
			false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		trackingdb tracks;

		if (!d->selected)
			continue;

		r = tracking_initialize(&tracks, d, true);
		if (RET_WAS_ERROR(r)) {
			RET_UPDATE(result, r);
			if (RET_WAS_ERROR(r))
				break;
			continue;
		}
		if (r == RET_NOTHING)
			continue;
		r = tracking_printall(tracks);
		RET_UPDATE(result, r);
		r = tracking_done(tracks, d);
		RET_ENDUPDATE(result, r);
		if (RET_WAS_ERROR(result))
			break;
	}
	return result;
}

/***********************checking*************************/

ACTION_RF(y, n, y, y, check) {
	retvalue result, r;
	struct distribution *d;

	result = distribution_match(alldistributions, argc-1, argv+1,
			false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		if (!d->selected)
			continue;

		if (verbose > 0) {
			printf("Checking %s...\n", d->codename);
		}

		r = package_foreach(d,
				components, architectures, packagetypes,
				package_check, NULL, NULL);
		RET_UPDATE(result, r);
		if (RET_WAS_ERROR(r))
			break;
	}
	return result;
}

ACTION_F(n, n, n, y, checkpool) {

	if (argc == 2 && strcmp(argv[1], "fast") != 0) {
		fprintf(stderr, "Error: Unrecognized second argument '%s'\n"
				"Syntax: reprepro checkpool [fast]\n",
				argv[1]);
		return RET_ERROR;
	}

	return files_checkpool(argc == 2);
}

/* Update checksums of existing files */

ACTION_F(n, n, n, n, collectnewchecksums) {

	return files_collectnewchecksums();
}
/*****************reapplying override info***************/

ACTION_F(y, n, y, y, reoverride) {
	retvalue result, r;
	struct distribution *d;

	result = distribution_match(alldistributions, argc-1, argv+1,
			true, READWRITE);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {

		if (!d->selected)
			continue;

		if (verbose > 0) {
			fprintf(stderr, "Reapplying override to %s...\n",
					d->codename);
		}

		r = distribution_loadalloverrides(d);
		if (RET_IS_OK(r)) {
			struct target *t;

			for (t = d->targets ; t != NULL ; t = t->next) {
				if (!target_matches(t,
				      components, architectures, packagetypes))
					continue;
				r = target_reoverride(t, d);
				RET_UPDATE(result, r);
				// TODO: how to separate this in those
				// affecting d and those that do not?
				RET_UPDATE(d->status, r);
			}
			distribution_unloadoverrides(d);
		} else if (r == RET_NOTHING) {
			fprintf(stderr,
"No override files, thus nothing to do for %s.\n",
					d->codename);
		} else {
			RET_UPDATE(result, r);
		}
		if (RET_WAS_ERROR(result))
			break;
	}
	return result;
}

/*****************retrieving Description data from .deb files***************/

static retvalue repair_descriptions(struct target *target) {
	struct package_cursor iterator;
	retvalue result, r;

	assert(target->packages == NULL);
	assert(target->packagetype == pt_deb ||
			target->packagetype == pt_udeb ||
			target->packagetype == pt_ddeb);

	if (verbose > 2) {
		printf(
"Redoing checksum information for packages in '%s'...\n",
				target->identifier);
	}

	r = package_openiterator(target, READWRITE, true, &iterator);
	if (!RET_IS_OK(r))
		return r;
	result = RET_NOTHING;
	while (package_next(&iterator)) {
		char *newcontrolchunk = NULL;

		if (interrupted()) {
			result = RET_ERROR_INTERRUPTED;
			break;
		}
		/* replace it by itself to normalize the Description field */
		r = description_addpackage(target, iterator.current.name,
				iterator.current.control,
				&newcontrolchunk);
		RET_UPDATE(result, r);
		if (RET_WAS_ERROR(r))
			break;
		if (RET_IS_OK(r)) {
			if (verbose >= 0) {
				printf(
"Fixing description for '%s'...\n", iterator.current.name);
			}
			r = package_newcontrol_by_cursor(&iterator,
				newcontrolchunk, strlen(newcontrolchunk));
			free(newcontrolchunk);
			if (RET_WAS_ERROR(r)) {
				result = r;
				break;
			}
			target->wasmodified = true;
		}
	}
	r = package_closeiterator(&iterator);
	RET_ENDUPDATE(result, r);
	return result;
}

ACTION_F(y, n, y, y, repairdescriptions) {
	retvalue result, r;
	struct distribution *d;

	result = distribution_match(alldistributions, argc-1, argv+1,
			true, READWRITE);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		struct target *t;

		if (!d->selected)
			continue;

		if (verbose > 0) {
			printf(
"Looking for 'Description's to repair in %s...\n", d->codename);
		}

		for (t = d->targets ; t != NULL ; t = t->next) {
			if (interrupted()) {
				result = RET_ERROR_INTERRUPTED;
				break;
			}
			if (!target_matches(t, components, architectures, packagetypes))
				continue;
			if (t->packagetype == pt_dsc)
				continue;
			r = repair_descriptions(t);
			RET_UPDATE(result, r);
			RET_UPDATE(d->status, r);
			if (RET_WAS_ERROR(r))
				break;
		}
	}
	return result;
}

/*****************adding checksums of files again*****************/

ACTION_F(y, n, y, y, redochecksums) {
	retvalue result, r;
	struct distribution *d;

	result = distribution_match(alldistributions, argc-1, argv+1,
			true, READWRITE);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		struct target *t;

		if (!d->selected)
			continue;

		if (verbose > 0) {
			fprintf(stderr,
"Readding checksum information to packages in %s...\n", d->codename);
		}

		for (t = d->targets ; t != NULL ; t = t->next) {
			if (!target_matches(t,
			      components, architectures, packagetypes))
				continue;
			r = target_redochecksums(t, d);
			RET_UPDATE(result, r);
			RET_UPDATE(d->status, r);
			if (RET_WAS_ERROR(r))
				break;
		}
		if (RET_WAS_ERROR(result))
			break;
	}
	return result;
}

/*******************sizes of distributions***************/

ACTION_RF(n, n, y, y, sizes) {
	retvalue result;

	result = distribution_match(alldistributions, argc-1, argv+1,
			false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	return sizes_distributions(alldistributions, argc > 1);
}

/***********************include******************************************/

ACTION_D(y, y, y, includedeb) {
	retvalue result, r;
	struct distribution *distribution;
	packagetype_t packagetype;
	trackingdb tracks;
	int i = 0;
	component_t component = atom_unknown;

	if (components != NULL) {
		if (components->count > 1) {
			fprintf(stderr,
"Error: Only one component is allowed with %s!\n",
					argv[0]);
			return RET_ERROR;
		}
		assert(components->count > 0);
		component = components->atoms[0];
	}

	if (architectures != NULL)
		if (!atomlist_hasexcept(architectures, architecture_source)) {
			fprintf(stderr,
"Error: -A source is not possible with includedeb!\n");
			return RET_ERROR;
		}
	if (strcmp(argv[0], "includeudeb") == 0) {
		packagetype = pt_udeb;
		if (limitations_missed(packagetypes, pt_udeb)) {
			fprintf(stderr,
"Calling includeudeb with a -T not containing udeb makes no sense!\n");
			return RET_ERROR;
		}
	} else if (strcmp(argv[0], "includeddeb") == 0) {
		packagetype = pt_ddeb;
		if (limitations_missed(packagetypes, pt_ddeb)) {
			fprintf(stderr,
"Calling includeddeb with a -T not containing ddeb makes no sense!\n");
			return RET_ERROR;
		}
	} else if (strcmp(argv[0], "includedeb") == 0) {
		packagetype = pt_deb;
		if (limitations_missed(packagetypes, pt_deb)) {
			fprintf(stderr,
"Calling includedeb with a -T not containing deb makes no sense!\n");
			return RET_ERROR;
		}

	} else {
		fprintf(stderr, "Internal error while parding command!\n");
		return RET_ERROR;
	}

	for (i = 2 ; i < argc ; i++) {
		const char *filename = argv[i];

		if (packagetype == pt_udeb) {
			if (!endswith(filename, ".udeb") && !IGNORING(extension,
"includeudeb called with file '%s' not ending with '.udeb'\n", filename))
				return RET_ERROR;
		} else if (packagetype == pt_ddeb) {
			if (!endswith(filename, ".ddeb") && !IGNORING(extension,
"includeddeb called with file '%s' not ending with '.ddeb'\n", filename))
				return RET_ERROR;
		} else {
			if (!endswith(filename, ".deb") && !IGNORING(extension,
"includedeb called with file '%s' not ending with '.deb'\n", filename))
				return RET_ERROR;
		}
	}

	result = distribution_get(alldistributions, argv[1], true, &distribution);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	if (distribution->readonly) {
		fprintf(stderr, "Cannot add packages to read-only distribution '%s'.\n",
				distribution->codename);
		return RET_ERROR;
	}

	if (packagetype == pt_udeb)
		result = override_read(distribution->udeb_override,
				&distribution->overrides.udeb, false);
	else
		/* we use the normal deb overrides for ddebs too -
		 * they're not meant to have overrides anyway */
		result = override_read(distribution->deb_override,
				&distribution->overrides.deb, false);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	// TODO: same for component? (depending on type?)
	if (architectures != NULL) {
		architecture_t missing = atom_unknown;

		if (!atomlist_subset(&distribution->architectures,
				architectures, &missing)){
			fprintf(stderr,
"Cannot force into the architecture '%s' not available in '%s'!\n",
				atoms_architectures[missing],
				distribution->codename);
			return RET_ERROR;
		}
	}

	r = distribution_prepareforwriting(distribution);
	if (RET_WAS_ERROR(r)) {
		return RET_ERROR;
	}

	if (distribution->tracking != dt_NONE) {
		result = tracking_initialize(&tracks, distribution, false);
		if (RET_WAS_ERROR(result)) {
			return result;
		}
	} else {
		tracks = NULL;
	}
	result = RET_NOTHING;
	for (i = 2 ; i < argc ; i++) {
		const char *filename = argv[i];

		r = deb_add(component, architectures,
				section, priority, packagetype,
				distribution, filename,
				delete, tracks);
		RET_UPDATE(result, r);
	}

	distribution_unloadoverrides(distribution);

	r = tracking_done(tracks, distribution);
	RET_ENDUPDATE(result, r);
	return result;
}


ACTION_D(y, y, y, includedsc) {
	retvalue result, r;
	struct distribution *distribution;
	trackingdb tracks;
	component_t component = atom_unknown;

	if (components != NULL) {
		if (components->count > 1) {
			fprintf(stderr,
"Error: Only one component is allowed with %s!\n",
					argv[0]);
			return RET_ERROR;
		}
		assert(components->count > 0);
		component = components->atoms[0];
	}


	assert (argc == 3);

	if (limitations_missed(architectures, architecture_source)) {
		fprintf(stderr,
"Cannot put a source package anywhere else than in architecture 'source'!\n");
		return RET_ERROR;
	}
	if (limitations_missed(packagetypes, pt_dsc)) {
		fprintf(stderr,
"Cannot put a source package anywhere else than in type 'dsc'!\n");
		return RET_ERROR;
	}
	if (!endswith(argv[2], ".dsc") && !IGNORING(extension,
"includedsc called with a file not ending with '.dsc'\n"))
		return RET_ERROR;

	result = distribution_get(alldistributions, argv[1], true, &distribution);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (distribution->readonly) {
		fprintf(stderr,
"Cannot add packages to read-only distribution '%s'.\n",
				distribution->codename);
		return RET_ERROR;
	}
	result = override_read(distribution->dsc_override,
			&distribution->overrides.dsc, true);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	result = distribution_prepareforwriting(distribution);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	if (distribution->tracking != dt_NONE) {
		result = tracking_initialize(&tracks, distribution, false);
		if (RET_WAS_ERROR(result)) {
			return result;
		}
	} else {
		tracks = NULL;
	}

	result = dsc_add(component, section, priority,
			distribution, argv[2], delete, tracks);
	logger_wait();

	distribution_unloadoverrides(distribution);
	r = tracking_done(tracks, distribution);
	RET_ENDUPDATE(result, r);
	return result;
}

ACTION_D(y, y, y, include) {
	retvalue result, r;
	struct distribution *distribution;
	trackingdb tracks;
	component_t component = atom_unknown;

	if (components != NULL) {
		if (components->count > 1) {
			fprintf(stderr,
"Error: Only one component is allowed with %s!\n",
					argv[0]);
			return RET_ERROR;
		}
		assert(components->count > 0);
		component = components->atoms[0];
	}

	assert (argc == 3);

	if (!endswith(argv[2], ".changes") && !IGNORING(extension,
"include called with a file not ending with '.changes'\n"
"(Did you mean includedeb or includedsc?)\n"))
		return RET_ERROR;

	result = distribution_get(alldistributions, argv[1], true, &distribution);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (distribution->readonly) {
		fprintf(stderr,
"Cannot add packages to read-only distribution '%s'.\n",
				distribution->codename);
		return RET_ERROR;
	}

	result = distribution_loadalloverrides(distribution);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	if (distribution->tracking != dt_NONE) {
		result = tracking_initialize(&tracks, distribution, false);
		if (RET_WAS_ERROR(result)) {
			return result;
		}
	} else {
		tracks = NULL;
	}
	result = distribution_loaduploaders(distribution);
	if (RET_WAS_ERROR(result)) {
		r = tracking_done(tracks, distribution);
		RET_ENDUPDATE(result, r);
		return result;
	}
	result = changes_add(tracks, packagetypes, component, architectures,
			section, priority, distribution,
			argv[2], delete);
	if (RET_WAS_ERROR(result))
		RET_UPDATE(distribution->status, result);

	distribution_unloadoverrides(distribution);
	distribution_unloaduploaders(distribution);
	r = tracking_done(tracks, distribution);
	RET_ENDUPDATE(result, r);
	return result;
}

/***********************createsymlinks***********************************/

static bool mayaliasas(const struct distribution *alldistributions, const char *part, const char *cnpart) {
	const struct distribution *d;

	/* here it is only checked whether there is something that could
	 * cause this link to exist. No tests whether this really will
	 * cause it to be created (or already existing). */

	for (d = alldistributions ; d != NULL ; d = d->next) {
		if (d->suite == NULL)
			continue;
		if (strcmp(d->suite, part) == 0 &&
				strcmp(d->codename, cnpart) == 0)
			return true;
		if (strcmp(d->codename, part) == 0 &&
				strcmp(d->suite, cnpart) == 0)
			return true;
	}
	return false;
}

ACTION_C(n, n, y, createsymlinks) {
	retvalue result, r;
	struct distribution *d, *d2;
	bool warned_slash = false;

	r = dirs_make_recursive(global.distdir);
	if (RET_WAS_ERROR(r))
		return r;

	result = distribution_match(alldistributions, argc-1, argv+1, false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result)) {
		return result;
	}
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		char *linkname, *buffer;
		size_t bufsize;
		int ret;
		const char *separator_in_suite;

		if (!d->selected)
			continue;

		if (d->suite == NULL || strcmp(d->suite, d->codename) == 0)
			continue;
		r = RET_NOTHING;
		for (d2 = alldistributions ; d2 != NULL ; d2 = d2->next) {
			if (!d2->selected)
				continue;
			if (d!=d2 && d2->suite!=NULL &&
					strcmp(d->suite, d2->suite)==0) {
				fprintf(stderr,
"Not linking %s->%s due to conflict with %s->%s\n",
					d->suite, d->codename,
					d2->suite, d2->codename);
				r = RET_ERROR;
			} else if (strcmp(d->suite, d2->codename)==0) {
				fprintf(stderr,
"Not linking %s->%s due to conflict with %s\n",
					d->suite, d->codename, d2->codename);
				r = RET_ERROR;
			}
		}
		if (RET_WAS_ERROR(r)) {
			RET_UPDATE(result, r);
			continue;
		}

		separator_in_suite = strchr(d->suite, '/');
		if (separator_in_suite != NULL) {
			/* things with / in it are tricky:
			 * relative symbolic links are hard,
			 * perhaps something else already moved
			 * the earlier ones, ... */
			const char *separator_in_codename;
			size_t ofs_in_suite = separator_in_suite - d->suite;
			char *part = strndup(d->suite, ofs_in_suite);

			if (FAILEDTOALLOC(part))
				return RET_ERROR_OOM;

			/* check if this is some case we do not want to warn about: */

			separator_in_codename = strchr(d->codename, '/');
			if (separator_in_codename != NULL &&
			    strcmp(separator_in_codename,
			           separator_in_suite) == 0) {
				/* all but the first is common: */
				size_t cnofs = separator_in_codename - d->codename;
				char *cnpart = strndup(d->codename, cnofs);
				if (FAILEDTOALLOC(cnpart)) {
					free(part);
					return RET_ERROR_OOM;
				}
				if (mayaliasas(alldistributions, part, cnpart)) {
					if (verbose > 1)
					fprintf(stderr,
"Not creating '%s' -> '%s' because of the '/' in it.\n"
"Hopefully something else will link '%s' -> '%s' then this is not needed.\n",
						d->suite, d->codename,
						part, cnpart);
					free(part);
					free(cnpart);
					continue;
				}
				free(cnpart);
			}
			free(part);
			if (verbose >= 0 && !warned_slash) {
				fprintf(stderr,
"Creating symlinks with '/' in them is not yet supported:\n");
				warned_slash = true;
			}
			if (verbose >= 0)
				fprintf(stderr,
"Not creating '%s' -> '%s' because of '/'.\n", d->suite, d->codename);
				continue;
		}

		linkname = calc_dirconcat(global.distdir, d->suite);
		bufsize = strlen(d->codename)+10;
		buffer = calloc(1, bufsize);
		if (FAILEDTOALLOC(linkname) || FAILEDTOALLOC(buffer)) {
			free(linkname); free(buffer);
			(void)fputs("Out of Memory!\n", stderr);
			return RET_ERROR_OOM;
		}

		ret = readlink(linkname, buffer, bufsize - 4);
		if (ret < 0 && errno == ENOENT) {
			ret = symlink(d->codename, linkname);
			if (ret != 0) {
				int e = errno;
				r = RET_ERRNO(e);
				fprintf(stderr,
"Error %d creating symlink %s->%s: %s\n", e, linkname, d->codename, strerror(e));
				RET_UPDATE(result, r);
			} else {
				if (verbose > 0) {
					printf("Created %s->%s\n", linkname,
							d->codename);
				}
				RET_UPDATE(result, RET_OK);
			}
		} else if (ret >= 0) {
			buffer[ret] = '\0';
			if (ret >= ((int)bufsize) - 4) {
				buffer[bufsize-4]='.';
				buffer[bufsize-3]='.';
				buffer[bufsize-2]='.';
				buffer[bufsize-1]='\0';
			}
			if (strcmp(buffer, d->codename) == 0) {
				if (verbose > 2) {
					printf("Already ok: %s->%s\n",
							linkname, d->codename);
				}
				RET_UPDATE(result, RET_OK);
			} else {
				if (delete <= 0) {
					fprintf(stderr,
"Cannot create %s as already pointing to %s instead of %s,\n"
" use --delete to delete the old link before creating an new one.\n",
						linkname, buffer, d->codename);
					RET_UPDATE(result, RET_ERROR);
				} else {
					unlink(linkname);
					ret = symlink(d->codename, linkname);
					if (ret != 0) {
						int e = errno;
						r = RET_ERRNO(e);
						fprintf(stderr,
"Error %d creating symlink %s->%s: %s\n", e, linkname, d->codename, strerror(e));
						RET_UPDATE(result, r);
					} else {
						if (verbose > 0) {
							printf(
"Replaced %s->%s\n", linkname, d->codename);
						}
						RET_UPDATE(result, RET_OK);
					}

				}
			}
		} else {
			int e = errno;
			r = RET_ERRNO(e);
			fprintf(stderr,
"Error %d checking %s, perhaps not a symlink?: %s\n", e, linkname, strerror(e));
			RET_UPDATE(result, r);
		}
		free(linkname); free(buffer);

		RET_UPDATE(result, r);
	}
	return result;
}

/***********************checkuploaders***********************************/

/* Read a fake package description from stdin */
static inline retvalue read_package_description(char **sourcename, struct strlist *sections, struct strlist *binaries, struct strlist *byhands, struct atomlist *architectures, struct signatures **signatures, char **buffer_p, size_t *bufferlen_p) {
	retvalue r;
	ssize_t got;
	char *buffer, *v, *p;
	struct strlist *l;
	struct signatures *s;
	struct signature *sig;
	architecture_t architecture;

	if (isatty(0)) {
		puts(
"Please input the simulated package data to test.\n"
"Format: (source|section|binary|byhand|architecture|signature) <value>\n"
"some keys may be given multiple times");
	}
	while ((got = getline(buffer_p, bufferlen_p, stdin)) >= 0) {
		buffer = *buffer_p;
		if (got == 0 || buffer[got - 1] != '\n') {
			fputs("stdin is not text\n", stderr);
			return RET_ERROR;
		}
		buffer[--got] = '\0';
		if (strncmp(buffer, "source ", 7) == 0) {
			if (*sourcename != NULL) {
				fprintf(stderr,
"Source name only allowed once!\n");
				return RET_ERROR;
			}
			*sourcename = strdup(buffer + 7);
			if (FAILEDTOALLOC(*sourcename))
				return RET_ERROR_OOM;
			continue;
		} else if (strncmp(buffer, "signature ", 10) == 0) {
			v = buffer + 10;
			if (*signatures == NULL) {
				s = calloc(1, sizeof(struct signatures)
						+sizeof(struct signature));
				if (FAILEDTOALLOC(s))
					return RET_ERROR_OOM;
			} else {
				s = realloc(*signatures,
						sizeof(struct signatures)
						+ (s->count+1)
						  * sizeof(struct signature));
				if (FAILEDTOALLOC(s))
					return RET_ERROR_OOM;
			}
			*signatures = s;
			sig = s->signatures + s->count;
			s->count++;
			s->validcount++;
			sig->expired_key = false;
			sig->expired_signature = false;
			sig->revoced_key = false;
			sig->state = sist_valid;
			switch (*v) {
				case 'b':
					sig->state = sist_bad;
					s->validcount--;
					v++;
					break;
				case 'e':
					sig->state = sist_mostly;
					sig->expired_signature = true;
					s->validcount--;
					v++;
					break;
				case 'i':
					sig->state = sist_invalid;
					s->validcount--;
					v++;
					break;
			}
			p = v;
			while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f'))
				p++;
			sig->keyid = strndup(v, p-v);
			sig->primary_keyid = NULL;
			if (FAILEDTOALLOC(sig->keyid))
				return RET_ERROR_OOM;
			if (*p == ':') {
				p++;
				v = p;
				while ((*p >= '0' && *p <= '9')
						|| (*p >= 'a' && *p <= 'f'))
					p++;
				if (*p != '\0') {
					fprintf(stderr,
"Invalid character in key id: '%c'!\n",
						*p);
					return RET_ERROR;
				}
				sig->primary_keyid = strdup(v);
			} else if (*p != '\0') {
				fprintf(stderr,
"Invalid character in key id: '%c'!\n",
						*p);
				return RET_ERROR;
			} else
				sig->primary_keyid = strdup(sig->keyid);
			if (FAILEDTOALLOC(sig->primary_keyid))
				return RET_ERROR_OOM;
			continue;
		} else if (strncmp(buffer, "section ", 8) == 0) {
			v = buffer + 8;
			l = sections;
		} else if (strncmp(buffer, "binary ", 7) == 0) {
			v = buffer + 7;
			l = binaries;
		} else if (strncmp(buffer, "byhand ", 7) == 0) {
			v = buffer + 7;
			l = byhands;
		} else if (strncmp(buffer, "architecture ", 13) == 0) {
			v = buffer + 13;
			r = architecture_intern(v, &architecture);
			if (RET_WAS_ERROR(r))
				return r;
			r = atomlist_add(architectures, architecture);
			if (RET_WAS_ERROR(r))
				return r;
			continue;
		} else if (strcmp(buffer, "finished") == 0) {
			break;
		} else {
			fprintf(stderr, "Unparseable line '%s'\n", buffer);
			return RET_ERROR;
		}
		r = strlist_add_dup(l, v);
		if (RET_WAS_ERROR(r))
			return r;
	}
	if (ferror(stdin)) {
		int e = errno;
		fprintf(stderr, "Error %d reading data from stdin: %s\n",
				e, strerror(e));
		return RET_ERRNO(e);
	}
	if (*sourcename == NULL) {
		fprintf(stderr, "No source name specified!\n");
		return RET_ERROR;
	}
	return RET_OK;
}

static inline void verifystrlist(struct upload_conditions *conditions, const struct strlist *list) {
	int i;
	for (i = 0 ; i < list->count ; i++) {
		if (!uploaders_verifystring(conditions, list->values[i]))
			break;
	}
}
static inline void verifyatomlist(struct upload_conditions *conditions, const struct atomlist *list) {
	int i;
	for (i = 0 ; i < list->count ; i++) {
		if (!uploaders_verifyatom(conditions, list->atoms[i]))
			break;
	}
}


ACTION_C(n, n, y, checkuploaders) {
	retvalue result, r;
	struct distribution *d;
	char *sourcename = NULL;
	struct strlist sections, binaries, byhands;
	struct atomlist architectures;
	struct signatures *signatures = NULL;
	struct upload_conditions *conditions;
	bool accepted, rejected;
	char *buffer = NULL;
	size_t bufferlen = 0;
	int i;

	r = distribution_match(alldistributions, argc-1, argv+1, false, READONLY);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r)) {
		return r;
	}
	for (d = alldistributions ; d != NULL ; d = d->next) {
		if (!d->selected)
			continue;
		r = distribution_loaduploaders(d);
		if (RET_WAS_ERROR(r))
			return r;
	}

	strlist_init(&sections);
	strlist_init(&binaries);
	strlist_init(&byhands);
	atomlist_init(&architectures);

	r = read_package_description(&sourcename, &sections, &binaries,
			&byhands, &architectures, &signatures,
			&buffer, &bufferlen);
	free(buffer);
	if (RET_WAS_ERROR(r)) {
		free(sourcename);
		strlist_done(&sections);
		strlist_done(&byhands);
		atomlist_done(&architectures);
		signatures_free(signatures);
		return r;
	}

	result = RET_NOTHING;
	accepted = false;
	for (i = 1 ; !accepted && i < argc ; i++) {
		r = distribution_get(alldistributions, argv[i], false, &d);
		if (RET_WAS_ERROR(r)) {
			result = r;
			break;
		}
		r = distribution_loaduploaders(d);
		if (RET_WAS_ERROR(r)) {
			result = r;
			break;
		}
		if (d->uploaderslist == NULL) {
			printf(
"'%s' would have been accepted by '%s' (as it has no uploader restrictions)\n",
				sourcename, d->codename);
			accepted = true;
			break;
		}
		r = uploaders_permissions(d->uploaderslist, signatures,
				&conditions);
		if (RET_WAS_ERROR(r)) {
			result = r;
			break;
		}
		rejected = false;
		do switch (uploaders_nextcondition(conditions)) {
			case uc_ACCEPTED:
				accepted = true;
				break;
			case uc_REJECTED:
				rejected = true;
				break;
			case uc_CODENAME:
				uploaders_verifystring(conditions, d->codename);
				break;
			case uc_SOURCENAME:
				uploaders_verifystring(conditions, sourcename);
				break;
			case uc_SECTIONS:
				verifystrlist(conditions, &sections);
				break;
			case uc_ARCHITECTURES:
				verifyatomlist(conditions, &architectures);
				break;
			case uc_BYHAND:
				verifystrlist(conditions, &byhands);
				break;
			case uc_BINARIES:
				verifystrlist(conditions, &byhands);
				break;
		} while (!accepted && !rejected);
		free(conditions);

		if (accepted) {
			printf("'%s' would have been accepted by '%s'\n",
					sourcename, d->codename);
			break;
		}
	}
	if (!accepted)
		printf(
"'%s' would NOT have been accepted by any of the distributions selected.\n",
			sourcename);
	free(sourcename);
	strlist_done(&sections);
	strlist_done(&byhands);
	atomlist_done(&architectures);
	signatures_free(signatures);
	if (RET_WAS_ERROR(result))
		return result;
	else if (accepted)
		return RET_OK;
	else
		return RET_NOTHING;
}

/***********************clearvanished***********************************/

ACTION_D(n, n, n, clearvanished) {
	retvalue result, r;
	struct distribution *d;
	struct strlist identifiers, codenames;
	bool *inuse;
	int i;

	result = database_listpackages(&identifiers);
	if (!RET_IS_OK(result)) {
		return result;
	}

	inuse = nzNEW(identifiers.count, bool);
	if (FAILEDTOALLOC(inuse)) {
		strlist_done(&identifiers);
		return RET_ERROR_OOM;
	}
	for (d = alldistributions; d != NULL ; d = d->next) {
		struct target *t;
		for (t = d->targets; t != NULL ; t = t->next) {
			int ofs = strlist_ofs(&identifiers, t->identifier);
			if (ofs >= 0) {
				inuse[ofs] = true;
				if (verbose > 6)
					printf(
"Marking '%s' as used.\n", t->identifier);
			} else if (verbose > 3){
				fprintf(stderr,
"Strange, '%s' does not appear in packages.db yet.\n", t->identifier);
			}
		}
	}
	for (i = 0 ; i < identifiers.count ; i ++) {
		const char *identifier = identifiers.values[i];
		const char *p, *q;

		if (inuse[i])
			continue;
		if (interrupted())
			return RET_ERROR_INTERRUPTED;
		if (delete <= 0) {
			r = database_haspackages(identifier);
			if (RET_IS_OK(r)) {
				fprintf(stderr,
"There are still packages in '%s', not removing (give --delete to do so)!\n", identifier);
				continue;
			}
		}
		if (interrupted())
			return RET_ERROR_INTERRUPTED;
		// TODO: if delete, check what is removed, so that tracking
		// information can be updated.
		printf(
"Deleting vanished identifier '%s'.\n", identifier);
		/* intern component and architectures, so parsing
		 * has no problems (actually only need component now) */
		p = identifier;
		if (strncmp(p, "u|", 2) == 0)
			p += 2;
		p = strchr(p, '|');
		if (p != NULL) {
			p++;
			q = strchr(p, '|');
			if (q != NULL) {
				atom_t dummy;

				char *component = strndup(p, q-p);
				q++;
				char *architecture = strdup(q);
				if (FAILEDTOALLOC(component) ||
						FAILEDTOALLOC(architecture)) {
					free(component);
					free(architecture);
					return RET_ERROR_OOM;
				}
				r = architecture_intern(architecture, &dummy);
				free(architecture);
				if (RET_WAS_ERROR(r)) {
					free(component);
					return r;
				}
				r = component_intern(component, &dummy);
				free(component);
				if (RET_WAS_ERROR(r))
					return r;
			}
		}
		/* derference anything left */
		references_remove(identifier);
		/* remove the database */
		database_droppackages(identifier);
	}
	free(inuse);
	strlist_done(&identifiers);
	if (interrupted())
		return RET_ERROR_INTERRUPTED;

	r = tracking_listdistributions(&codenames);
	RET_UPDATE(result, r);
	if (RET_IS_OK(r)) {
		for (d = alldistributions; d != NULL ; d = d->next) {
			strlist_remove(&codenames, d->codename);
		}
		for (i = 0 ; i < codenames.count ; i ++) {
			printf(
"Deleting tracking data for vanished distribution '%s'.\n",
					codenames.values[i]);
			r = tracking_drop(codenames.values[i]);
			RET_UPDATE(result, r);
		}
		strlist_done(&codenames);
	}

	return result;
}

ACTION_B(n, n, y, listdbidentifiers) {
	retvalue result;
	struct strlist identifiers;
	const struct distribution *d;
	int i;

	result = database_listpackages(&identifiers);
	if (!RET_IS_OK(result)) {
		return result;
	}
	result = distribution_match(alldistributions, argc-1, argv+1, false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	result = RET_NOTHING;
	for (i = 0 ; i < identifiers.count ; i++) {
		const char *p, *q, *identifier = identifiers.values[i];

		if (argc <= 1) {
			puts(identifier);
			result = RET_OK;
			continue;
		}
		p = identifier;
		if (strncmp(p, "u|", 2) == 0)
			p += 2;
		q = strchr(p, '|');
		if (q == NULL)
			q = strchr(p, '\0');
		for (d = alldistributions ; d != NULL ; d = d->next) {
			if (!d->selected)
				continue;
			if (strncmp(p, d->codename, q - p) == 0
			    && d->codename[q-p] == '\0') {
				puts(identifier);
				result = RET_OK;
				break;
			}
		}
	}
	strlist_done(&identifiers);
	return result;
}

ACTION_C(n, n, y, listconfidentifiers) {
	struct target *t;
	const struct distribution *d;
	retvalue result;

	result = distribution_match(alldistributions, argc-1, argv+1, false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		if (!d->selected)
			continue;

		for (t = d->targets; t != NULL ; t = t->next) {
			puts(t->identifier);
			result = RET_OK;
		}
	}
	return result;
}

ACTION_N(n, n, y, versioncompare) {
	retvalue r;
	int i;

	assert (argc == 3);

	r = properversion(argv[1]);
	if (RET_WAS_ERROR(r))
		fprintf(stderr, "'%s' is not a proper version!\n", argv[1]);
	r = properversion(argv[2]);
	if (RET_WAS_ERROR(r))
		fprintf(stderr, "'%s' is not a proper version!\n", argv[2]);
	r = dpkgversions_cmp(argv[1], argv[2], &i);
	if (RET_IS_OK(r)) {
		if (i < 0) {
			printf("'%s' is smaller than '%s'.\n",
						argv[1], argv[2]);
		} else if (i > 0) {
			printf("'%s' is larger than '%s'.\n",
					argv[1], argv[2]);
		} else
			printf("'%s' is the same as '%s'.\n",
					argv[1], argv[2]);
	}
	return r;
}
/***********************processincoming********************************/
ACTION_D(n, n, y, processincoming) {
	struct distribution *d;

	for (d = alldistributions ; d != NULL ; d = d->next)
		d->selected = true;

	return process_incoming(alldistributions, argv[1],
			(argc==3) ? argv[2] : NULL);
}
/***********************gensnapshot********************************/
ACTION_R(n, n, y, y, gensnapshot) {
	retvalue result;
	struct distribution *distribution;

	assert (argc == 3);

	result = distribution_get(alldistributions, argv[1], false, &distribution);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	return distribution_snapshot(distribution, argv[2]);
}

ACTION_R(n, n, n, y, unreferencesnapshot) {
	retvalue result;
	char *id;

	assert (argc == 3);

	id = mprintf("s=%s=%s", argv[1], argv[2]);
	if (FAILEDTOALLOC(id))
		return RET_ERROR_OOM;

	result = references_remove(id);

	free(id);

	return result;
}

/***********************rerunnotifiers********************************/
static retvalue rerunnotifiersintarget(struct target *target, UNUSED(void *dummy)) {
	if (!logger_rerun_needs_target(target->distribution->logger, target))
		return RET_NOTHING;
	return RET_OK;
}

ACTION_B(y, n, y, rerunnotifiers) {
	retvalue result, r;
	struct distribution *d;

	result = distribution_match(alldistributions, argc-1, argv+1, false, READONLY);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;

	result = RET_NOTHING;
	for (d = alldistributions ; d != NULL ; d = d->next) {
		if (!d->selected)
			continue;

		if (d->logger == NULL)
			continue;

		if (verbose > 0) {
			printf("Processing %s...\n", d->codename);
		}
		r = logger_prepare(d->logger);
		RET_UPDATE(result, r);
		if (RET_WAS_ERROR(r))
			break;

		r = package_foreach(d,
				components, architectures, packagetypes,
				package_rerunnotifiers,
				rerunnotifiersintarget, NULL);
		logger_wait();

		RET_UPDATE(result, r);
		if (RET_WAS_ERROR(r))
			break;
	}
	return result;
}

/*********************** flood ****************************/

ACTION_D(y, n, y, flood) {
	retvalue result, r;
	struct distribution *distribution;
	trackingdb tracks;
	component_t architecture = atom_unknown;

	result = distribution_get(alldistributions, argv[1], true, &distribution);
	assert (result != RET_NOTHING);
	if (RET_WAS_ERROR(result))
		return result;
	if (distribution->readonly) {
		fprintf(stderr,
"Cannot add packages to read-only distribution '%s'.\n",
				distribution->codename);
		return RET_ERROR;
	}

	if (argc == 3) {
		architecture = architecture_find(argv[2]);
		if (!atom_defined(architecture)) {
			fprintf(stderr, "Error: Unknown architecture '%s'!\n",
					argv[2]);
			return RET_ERROR;
		}
		if (architecture == architecture_source) {
			fprintf(stderr,
"Error: Architecture 'source' does not make sense with 'flood'!\n");
			return RET_ERROR;
		}
		if (!atomlist_in(&distribution->architectures, architecture)) {
			fprintf(stderr,
"Error: Architecture '%s' not part of '%s'!\n",
					argv[2], distribution->codename);
			return RET_ERROR;
		}
	}

	result = distribution_prepareforwriting(distribution);
	if (RET_WAS_ERROR(result)) {
		return result;
	}

	if (distribution->tracking != dt_NONE) {
		result = tracking_initialize(&tracks, distribution, false);
		if (RET_WAS_ERROR(result)) {
			return result;
		}
	} else
		tracks = NULL;
	result = flood(distribution, components, architectures, packagetypes,
			architecture, tracks);

	if (RET_WAS_ERROR(result))
		RET_UPDATE(distribution->status, result);

	if (tracks != NULL) {
		r = tracking_done(tracks, distribution);
		RET_ENDUPDATE(result, r);
	}
	return result;
}

/*********************** unusedsources ****************************/
ACTION_B(n, n, y, unusedsources) {
	retvalue r;

	r = distribution_match(alldistributions, argc-1, argv+1,
			false, READONLY);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r))
		return r;
	return unusedsources(alldistributions);
}

/*********************** missingsource ****************************/
ACTION_B(n, n, y, sourcemissing) {
	retvalue r;

	r = distribution_match(alldistributions, argc-1, argv+1,
			false, READONLY);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r))
		return r;
	return sourcemissing(alldistributions);
}
/*********************** reportcruft ****************************/
ACTION_B(n, n, y, reportcruft) {
	retvalue r;

	r = distribution_match(alldistributions, argc-1, argv+1,
			false, READONLY);
	assert (r != RET_NOTHING);
	if (RET_WAS_ERROR(r))
		return r;
	return reportcruft(alldistributions);
}

/*********************/
/* argument handling */
/*********************/

// TODO: this has become an utter mess and needs some serious cleaning...
#define NEED_REFERENCES 1
/* FILESDB now includes REFERENCED... */
#define NEED_FILESDB 2
#define NEED_DEREF 4
#define NEED_DATABASE 8
#define NEED_CONFIG 16
#define NEED_NO_PACKAGES 32
#define IS_RO 64
#define MAY_UNUSED 128
#define NEED_ACT 256
#define NEED_SP 512
#define NEED_DELNEW 1024
#define NEED_RESTRICT 2048
#define A_N(w) action_n_n_n_ ## w, 0
#define A_C(w) action_c_n_n_ ## w, NEED_CONFIG
#define A_ROB(w) action_b_n_n_ ## w, NEED_DATABASE|IS_RO
#define A_ROBact(w) action_b_y_n_ ## w, NEED_ACT|NEED_DATABASE|IS_RO
#define A_L(w) action_l_n_n_ ## w, NEED_DATABASE
#define A_B(w) action_b_n_n_ ## w, NEED_DATABASE
#define A_Bact(w) action_b_y_n_ ## w, NEED_ACT|NEED_DATABASE
#define A_F(w) action_f_n_n_ ## w, NEED_DATABASE|NEED_FILESDB
#define A_Fact(w) action_f_y_n_ ## w, NEED_ACT|NEED_DATABASE|NEED_FILESDB
#define A_R(w) action_r_n_n_ ## w, NEED_DATABASE|NEED_REFERENCES
#define A__F(w) action_f_n_n_ ## w, NEED_DATABASE|NEED_FILESDB|NEED_NO_PACKAGES
#define A__R(w) action_r_n_n_ ## w, NEED_DATABASE|NEED_REFERENCES|NEED_NO_PACKAGES
#define A__T(w) action_t_n_n_ ## w, NEED_DATABASE|NEED_NO_PACKAGES|MAY_UNUSED
#define A_RF(w) action_rf_n_n_ ## w, NEED_DATABASE|NEED_FILESDB|NEED_REFERENCES
#define A_RFact(w) action_rf_y_n_ ## w, NEED_ACT|NEED_DATABASE|NEED_FILESDB|NEED_REFERENCES
/* to dereference files, one needs files and references database: */
#define A_D(w) action_d_n_n_ ## w, NEED_DATABASE|NEED_FILESDB|NEED_REFERENCES|NEED_DEREF
#define A_Dact(w) action_d_y_n_ ## w, NEED_ACT|NEED_DATABASE|NEED_FILESDB|NEED_REFERENCES|NEED_DEREF
#define A_Dactsp(w) action_d_y_y_ ## w, NEED_ACT|NEED_SP|NEED_DATABASE|NEED_FILESDB|NEED_REFERENCES|NEED_DEREF

static const struct action {
	const char *name;
	retvalue (*start)(
			/*@null@*/struct distribution *,
			/*@null@*/const char *priority,
			/*@null@*/const char *section,
			/*@null@*/const struct atomlist *,
			/*@null@*/const struct atomlist *,
			/*@null@*/const struct atomlist *,
			int argc, const char *argv[]);
	int needs;
	int minargs, maxargs;
	const char *wrongargmessage;
} all_actions[] = {
	{"__d", 		A_N(printargs),
		-1, -1, NULL},
	{"__dumpuncompressors",	A_N(dumpuncompressors),
		0, 0, "__dumpuncompressors"},
	{"__uncompress",	A_N(uncompress),
		3, 3, "__uncompress .gz|.bz2|.lzma|.xz|.lz <compressed-filename> <into-filename>"},
	{"__extractsourcesection", A_N(extractsourcesection),
		1, 1, "__extractsourcesection <.dsc-file>"},
	{"__extractcontrol",	A_N(extractcontrol),
		1, 1, "__extractcontrol <.deb-file>"},
	{"__extractfilelist",	A_N(extractfilelist),
		1, 1, "__extractfilelist <.deb-file>"},
	{"__checkuploaders",	A_C(checkuploaders),
		1, -1,	"__checkuploaders <codenames>"},
	{"_versioncompare",	A_N(versioncompare),
		2, 2, "_versioncompare <version> <version>"},
	{"_detect", 		A__F(detect),
		-1, -1, NULL},
	{"_forget", 		A__F(forget),
		-1, -1, NULL},
	{"_listmd5sums",	A__F(listmd5sums),
		0, 0, "_listmd5sums"},
	{"_listchecksums",	A__F(listchecksums),
		0, 0, "_listchecksums"},
	{"_addchecksums",	A__F(addmd5sums),
		0, 0, "_addchecksums < data"},
	{"_addmd5sums",		A__F(addmd5sums),
		0, 0, "_addmd5sums < data"},
	{"_dumpcontents", 	A_ROB(dumpcontents)|MAY_UNUSED,
		1, 1, "_dumpcontents <identifier>"},
	{"_removereferences", 	A__R(removereferences),
		1, 1, "_removereferences <identifier>"},
	{"_removereference", 	A__R(removereference),
		2, 2, "_removereferences <identifier>"},
	{"_addreference", 	A__R(addreference),
		2, 2, "_addreference <reference> <referee>"},
	{"_addreferences", 	A__R(addreferences),
		1, -1, "_addreferences <referee> <references>"},
	{"_fakeemptyfilelist",	A__F(fakeemptyfilelist),
		1, 1, "_fakeemptyfilelist <filekey>"},
	{"_addpackage",		A_Dact(addpackage),
		3, -1, "-C <component> -A <architecture> -T <packagetype> _addpackage <distribution> <filename> <package-names>"},
	{"remove", 		A_Dact(remove),
		2, -1, "[-C <component>] [-A <architecture>] [-T <type>] remove <codename> <package-names>"},
	{"removesrc", 		A_D(removesrc),
		2, 3, "removesrc <codename> <source-package-names> [<source-version>]"},
	{"removesrcs", 		A_D(removesrcs),
		2, -1, "removesrcs <codename> (<source-package-name>[=<source-version>])+"},
	{"ls", 		A_ROBact(ls),
		1, 1, "[-C <component>] [-A <architecture>] [-T <type>] ls <package-name>"},
	{"lsbycomponent",	A_ROBact(lsbycomponent),
		1, 1, "[-C <component>] [-A <architecture>] [-T <type>] lsbycomponent <package-name>"},
	{"list", 		A_ROBact(list),
		1, 2, "[-C <component>] [-A <architecture>] [-T <type>] list <codename> [<package-name>]"},
	{"listfilter", 		A_ROBact(listfilter),
		2, 2, "[-C <component>] [-A <architecture>] [-T <type>] listfilter <codename> <term to describe which packages to list>"},
	{"removefilter", 	A_Dact(removefilter),
		2, 2, "[-C <component>] [-A <architecture>] [-T <type>] removefilter <codename> <term to describe which packages to remove>"},
	{"listmatched", 	A_ROBact(listmatched),
		2, 2, "[-C <component>] [-A <architecture>] [-T <type>] listmatched <codename> <glob to describe packages>"},
	{"removematched", 	A_Dact(removematched),
		2, 2, "[-C <component>] [-A <architecture>] [-T <type>] removematched <codename> <glob to describe packages>"},
	{"createsymlinks", 	A_C(createsymlinks),
		0, -1, "createsymlinks [<distributions>]"},
	{"export", 		A_F(export),
		0, -1, "export [<distributions>]"},
	{"check", 		A_RFact(check),
		0, -1, "check [<distributions>]"},
	{"sizes", 		A_RF(sizes),
		0, -1, "check [<distributions>]"},
	{"reoverride", 		A_Fact(reoverride),
		0, -1, "[-T ...] [-C ...] [-A ...] reoverride [<distributions>]"},
	{"repairdescriptions", 	A_Fact(repairdescriptions),
		0, -1, "[-C ...] [-A ...] repairdescriptions [<distributions>]"},
	{"forcerepairdescriptions", 	A_Fact(repairdescriptions),
		0, -1, "[-C ...] [-A ...] [force]repairdescriptions [<distributions>]"},
	{"redochecksums", 	A_Fact(redochecksums),
		0, -1, "[-T ...] [-C ...] [-A ...] redo [<distributions>]"},
	{"collectnewchecksums", A_F(collectnewchecksums),
		0, 0, "collectnewchecksums"},
	{"checkpool", 		A_F(checkpool),
		0, 1, "checkpool [fast]"},
	{"rereference", 	A_R(rereference),
		0, -1, "rereference [<distributions>]"},
	{"dumpreferences", 	A_R(dumpreferences)|MAY_UNUSED,
		0, 0, "dumpreferences", },
	{"dumpunreferenced", 	A_RF(dumpunreferenced),
		0, 0, "dumpunreferenced", },
	{"deleteifunreferenced", A_RF(deleteifunreferenced),
		0, -1, "deleteifunreferenced"},
	{"deleteunreferenced", 	A_RF(deleteunreferenced),
		0, 0, "deleteunreferenced", },
	{"retrack",	 	A_D(retrack),
		0, -1, "retrack [<distributions>]"},
	{"dumptracks",	 	A_ROB(dumptracks)|MAY_UNUSED,
		0, -1, "dumptracks [<distributions>]"},
	{"removealltracks",	A_D(removealltracks)|MAY_UNUSED,
		1, -1, "removealltracks <distributions>"},
	{"tidytracks",		A_D(tidytracks),
		0, -1, "tidytracks [<distributions>]"},
	{"removetrack",		A_D(removetrack),
		3, 3, "removetrack <distribution> <sourcename> <version>"},
	{"update",		A_Dact(update)|NEED_RESTRICT,
		0, -1, "update [<distributions>]"},
	{"checkupdate",		A_Bact(checkupdate)|NEED_RESTRICT,
		0, -1, "checkupdate [<distributions>]"},
	{"dumpupdate",		A_Bact(dumpupdate)|NEED_RESTRICT,
		0, -1, "dumpupdate [<distributions>]"},
	{"predelete",		A_Dact(predelete),
		0, -1, "predelete [<distributions>]"},
	{"pull",		A_Dact(pull)|NEED_RESTRICT,
		0, -1, "pull [<distributions>]"},
	{"copy",		A_Dact(copy),
		3, -1, "[-C <component> ] [-A <architecture>] [-T <packagetype>] copy <destination-distribution> <source-distribution> <package-names to pull>"},
	{"copysrc",		A_Dact(copysrc),
		3, -1, "[-C <component> ] [-A <architecture>] [-T <packagetype>] copysrc <destination-distribution> <source-distribution> <source-package-name> [<source versions>]"},
	{"copymatched",		A_Dact(copymatched),
		3, 3, "[-C <component> ] [-A <architecture>] [-T <packagetype>] copymatched <destination-distribution> <source-distribution> <glob>"},
	{"copyfilter",		A_Dact(copyfilter),
		3, 3, "[-C <component> ] [-A <architecture>] [-T <packagetype>] copyfilter <destination-distribution> <source-distribution> <formula>"},
	{"move",		A_Dact(move),
		3, -1, "[-C <component> ] [-A <architecture>] [-T <packagetype>] move <destination-distribution> <source-distribution> <package-names to move>"},
	{"movesrc",		A_Dact(movesrc),
		3, -1, "[-C <component> ] [-A <architecture>] [-T <packagetype>] movesrc <destination-distribution> <source-distribution> <source-package-name> [<source versions>]"},
	{"movematched",		A_Dact(movematched),
		3, 3, "[-C <component> ] [-A <architecture>] [-T <packagetype>] movematched <destination-distribution> <source-distribution> <glob>"},
	{"movefilter",		A_Dact(movefilter),
		3, 3, "[-C <component> ] [-A <architecture>] [-T <packagetype>] movefilter <destination-distribution> <source-distribution> <formula>"},
	{"restore",		A_Dact(restore),
		3, -1, "[-C <component> ] [-A <architecture>] [-T <packagetype>] restore <distribution> <snapshot-name> <package-names to restore>"},
	{"restoresrc",		A_Dact(restoresrc),
		3, -1, "[-C <component> ] [-A <architecture>] [-T <packagetype>] restoresrc <distribution> <snapshot-name> <source-package-name> [<source versions>]"},
	{"restorematched",		A_Dact(restorematched),
		3, 3, "[-C <component> ] [-A <architecture>] [-T <packagetype>] restorematched <distribution> <snapshot-name> <glob>"},
	{"restorefilter",		A_Dact(restorefilter),
		3, 3, "[-C <component> ] [-A <architecture>] [-T <packagetype>] restorefilter <distribution> <snapshot-name> <formula>"},
	{"dumppull",		A_Bact(dumppull)|NEED_RESTRICT,
		0, -1, "dumppull [<distributions>]"},
	{"checkpull",		A_Bact(checkpull)|NEED_RESTRICT,
		0, -1, "checkpull [<distributions>]"},
	{"includedeb",		A_Dactsp(includedeb)|NEED_DELNEW,
		2, -1, "[--delete] includedeb <distribution> <.deb-file>"},
	{"includeudeb",		A_Dactsp(includedeb)|NEED_DELNEW,
		2, -1, "[--delete] includeudeb <distribution> <.udeb-file>"},
	{"includeddeb",		A_Dactsp(includedeb)|NEED_DELNEW,
		2, -1, "[--delete] includeddeb <distribution> <.ddeb-file>"},
	{"includedsc",		A_Dactsp(includedsc)|NEED_DELNEW,
		2, 2, "[--delete] includedsc <distribution> <package>"},
	{"include",		A_Dactsp(include)|NEED_DELNEW,
		2, 2, "[--delete] include <distribution> <.changes-file>"},
	{"generatefilelists",	A_F(generatefilelists),
		0, 1, "generatefilelists [reread]"},
	{"translatefilelists",	A__T(translatefilelists),
		0, 0, "translatefilelists"},
	{"translatelegacychecksums",	A_N(translatelegacychecksums),
		0, 0, "translatelegacychecksums"},
	{"translatelegacyreferences",	A__T(translatelegacyreferences),
		0, 0, "translatelegacyreferences"},
	{"_listconfidentifiers",	A_C(listconfidentifiers),
		0, -1, "_listconfidentifiers"},
	{"_listdbidentifiers",	A_ROB(listdbidentifiers)|MAY_UNUSED,
		0, -1, "_listdbidentifiers"},
	{"_listcodenames", 		A_C(listcodenames),
		0, 0, "_listcodenames"},
	{"clearvanished",	A_D(clearvanished)|MAY_UNUSED,
		0, 0, "[--delete] clearvanished"},
	{"processincoming",	A_D(processincoming)|NEED_DELNEW,
		1, 2, "processincoming <rule-name> [<.changes file>]"},
	{"gensnapshot",		A_R(gensnapshot),
		2, 2, "gensnapshot <distribution> <date or other name>"},
	{"unreferencesnapshot",	A__R(unreferencesnapshot),
		2, 2, "gensnapshot <distribution> <name of snapshot>"},
	{"rerunnotifiers",	A_Bact(rerunnotifiers),
		0, -1, "rerunnotifiers [<distributions>]"},
	{"cleanlists",		A_L(cleanlists),
		0, 0,  "cleanlists"},
	{"build-needing", 	A_ROBact(buildneeded),
		2, 3, "[-C <component>] build-needing <codename> <architecture> [<glob>]"},
	{"flood", 		A_Dact(flood)|MAY_UNUSED,
		1, 2, "[-C <component> ] [-A <architecture>] [-T <packagetype>] flood <codename> [<architecture>]"},
	{"unusedsources",	A_B(unusedsources),
		0, -1, "unusedsources [<codenames>]"},
	{"sourcemissing",	A_B(sourcemissing),
		0, -1, "sourcemissing [<codenames>]"},
	{"reportcruft",		A_B(reportcruft),
		0, -1, "reportcruft [<codenames>]"},
	{NULL, NULL , 0, 0, 0, NULL}
};
#undef A_N
#undef A_B
#undef A_ROB
#undef A_C
#undef A_F
#undef A_R
#undef A_RF
#undef A_F
#undef A__T

static retvalue callaction(command_t command, const struct action *action, int argc, const char *argv[]) {
	retvalue result, r;
	struct distribution *alldistributions = NULL;
	bool deletederef, deletenew;
	int needs;
	struct atomlist as, *architectures = NULL;
	struct atomlist cs, *components = NULL;
	struct atomlist ps, *packagetypes = NULL;

	assert(action != NULL);

	causingcommand = command;

	if (action->minargs >= 0 && argc < 1 + action->minargs) {
		fprintf(stderr,
"Error: Too few arguments for command '%s'!\nSyntax: reprepro %s\n",
				argv[0], action->wrongargmessage);
		return RET_ERROR;
	}
	if (action->maxargs >= 0 && argc > 1 + action->maxargs) {
		fprintf(stderr,
"Error: Too many arguments for command '%s'!\nSyntax: reprepro %s\n",
				argv[0], action->wrongargmessage);
		return RET_ERROR;
	}
	needs = action->needs;

	if (!ISSET(needs, NEED_ACT) && (x_architecture != NULL)) {
		if (!IGNORING(unusedoption,
"Action '%s' cannot be restricted to an architecture!\n"
"neither --archiecture nor -A make sense here.\n",
				action->name))
			return RET_ERROR;
	}
	if (!ISSET(needs, NEED_ACT) && (x_component != NULL)) {
		if (!IGNORING(unusedoption,
"Action '%s' cannot be restricted to a component!\n"
"neither --component nor -C make sense here.\n",
				action->name))
			return RET_ERROR;
	}
	if (!ISSET(needs, NEED_ACT) && (x_packagetype != NULL)) {
		if (!IGNORING(unusedoption,
"Action '%s' cannot be restricted to a packagetype!\n"
"neither --packagetype nor -T make sense here.\n",
				action->name))
			return RET_ERROR;
	}

	if (!ISSET(needs, NEED_SP) && (x_section != NULL)) {
		if (!IGNORING(unusedoption,
"Action '%s' cannot take a section option!\n"
"neither --section nor -S make sense here.\n",
				action->name))
			return RET_ERROR;
	}
	if (!ISSET(needs, NEED_SP) && (x_priority != NULL)) {
		if (!IGNORING(unusedoption,
"Action '%s' cannot take a priority option!\n"
"neither --priority nor -P make sense here.\n",
				action->name))
			return RET_ERROR;
	}
	if (!ISSET(needs, NEED_RESTRICT) && (cmdline_bin_filter.set
				|| cmdline_src_filter.set)) {
		if (!IGNORING(unusedoption,
"Action '%s' cannot take a --restrict-* option!\n",
				action->name))
			return RET_ERROR;
	}

	if (ISSET(needs, NEED_DATABASE))
		needs |= NEED_CONFIG;
	if (ISSET(needs, NEED_CONFIG)) {
		r = distribution_readall(&alldistributions);
		if (RET_WAS_ERROR(r))
			return r;
	}

	if (!ISSET(needs, NEED_DATABASE)) {
		assert ((needs & ~NEED_CONFIG) == 0);

		result = action->start(alldistributions,
				x_section, x_priority,
				NULL, NULL, NULL,
				argc, argv);
		logger_wait();

		if (!RET_WAS_ERROR(result)) {
			r = distribution_exportlist(export, alldistributions);
			RET_ENDUPDATE(result, r);
		}

		r = distribution_freelist(alldistributions);
		RET_ENDUPDATE(result, r);
		return result;
	}

	if (ISSET(needs, NEED_ACT)) {
		const char *unknownitem;
		if (x_architecture != NULL) {
			r = atomlist_filllist(at_architecture, &as,
					x_architecture, &unknownitem);
			if (r == RET_NOTHING) {
				fprintf(stderr,
"Error: Architecture '%s' as given to --architecture is not know.\n"
"(it does not appear as architecture in %s/distributions (did you mistype?))\n",
					unknownitem, global.confdir);
				r = RET_ERROR;
			}
			if (RET_WAS_ERROR(r)) {
				(void)distribution_freelist(alldistributions);
				return r;
			}
			architectures = &as;
		} else {
			atomlist_init(&as);
		}
		if (x_component != NULL) {
			r = atomlist_filllist(at_component, &cs,
					x_component, &unknownitem);
			if (r == RET_NOTHING) {
				fprintf(stderr,
"Error: Component '%s' as given to --component is not know.\n"
"(it does not appear as component in %s/distributions (did you mistype?))\n",
					unknownitem, global.confdir);
				r = RET_ERROR;
			}
			if (RET_WAS_ERROR(r)) {
				(void)distribution_freelist(alldistributions);
				return r;
			}
			components = &cs;
		} else {
			atomlist_init(&cs);
		}
		if (x_packagetype != NULL) {
			r = atomlist_filllist(at_packagetype, &ps,
					x_packagetype, &unknownitem);
			if (r == RET_NOTHING) {
				fprintf(stderr,
"Error: Packagetype '%s' as given to --packagetype is not know.\n"
"(only dsc, deb, udeb, ddeb and combinations of those are allowed)\n",
					unknownitem);
				r = RET_ERROR;
			}
			if (RET_WAS_ERROR(r)) {
				(void)distribution_freelist(alldistributions);
				return r;
			}
			packagetypes = &ps;
		} else {
			atomlist_init(&ps);
		}
		if (ps.count == 1 && ps.atoms[0] == pt_dsc &&
				limitations_missed(architectures,
					architecture_source)) {
			fprintf(stderr,
"Error: -T dsc is not possible with -A not including source!\n");
			return RET_ERROR;
		}
		if (as.count == 1 && as.atoms[0] == architecture_source &&
				limitations_missed(packagetypes, pt_dsc)) {
			fprintf(stderr,
"Error: -A source is not possible with -T not including dsc!\n");
			return RET_ERROR;
		}
	}

	deletederef = ISSET(needs, NEED_DEREF) && !keepunreferenced;
	deletenew = ISSET(needs, NEED_DELNEW) && !keepunusednew;

	result = database_create(alldistributions,
			fast, ISSET(needs, NEED_NO_PACKAGES),
			ISSET(needs, MAY_UNUSED), ISSET(needs, IS_RO),
			waitforlock, verbosedatabase || (verbose >= 30));
	if (!RET_IS_OK(result)) {
		(void)distribution_freelist(alldistributions);
		return result;
	}

	/* adding files may check references to see if they were added */
	if (ISSET(needs, NEED_FILESDB))
		needs |= NEED_REFERENCES;

	if (ISSET(needs, NEED_REFERENCES))
		result = database_openreferences();

	assert (result != RET_NOTHING);
	if (RET_IS_OK(result)) {

		if (ISSET(needs, NEED_FILESDB))
			result = database_openfiles();

		if (RET_IS_OK(result)) {
			if (outhook != NULL) {
				r = outhook_start();
				RET_UPDATE(result, r);
			}
		}

		assert (result != RET_NOTHING);
		if (RET_IS_OK(result)) {

			if (deletederef) {
				assert (ISSET(needs, NEED_REFERENCES));
			}

			if (!interrupted()) {
				result = action->start(alldistributions,
					x_section, x_priority,
					architectures, components, packagetypes,
					argc, argv);
				/* wait for package specific loggers */
				logger_wait();

				/* remove files added but not used */
				pool_tidyadded(deletenew);

				/* tell an outhook about added files */
				if (outhook != NULL)
					pool_sendnewfiles();
				/* export changed/lookedat distributions */
				if (!RET_WAS_ERROR(result)) {
					r = distribution_exportlist(export,
							alldistributions);
					RET_ENDUPDATE(result, r);
				}

				/* delete files losing references, or
				 * tell how many lost their references */

				// TODO: instead check if any distribution that
				// was not exported lost files
				// (and in a far future do not remove references
				// before the index is written)
				if (deletederef && RET_WAS_ERROR(result)) {
					deletederef = false;
					if (pool_havedereferenced) {
						fprintf(stderr,
"Not deleting possibly left over files due to previous errors.\n"
"(To keep the files in the still existing index files from vanishing)\n"
"Use dumpunreferenced/deleteunreferenced to show/delete files without references.\n");
					}
				}
				r = pool_removeunreferenced(deletederef);
				RET_ENDUPDATE(result, r);

				if (outhook != NULL) {
					if (interrupted())
						r = RET_ERROR_INTERRUPTED;
					else
						r = outhook_call(outhook);
					RET_ENDUPDATE(result, r);
				}
			}
		}
	}
	if (!interrupted()) {
		logger_wait();
	}
	if (ISSET(needs, NEED_ACT)) {
		atomlist_done(&as);
		atomlist_done(&cs);
		atomlist_done(&ps);
	}
	logger_warn_waiting();
	r = database_close();
	RET_ENDUPDATE(result, r);
	r = distribution_freelist(alldistributions);
	RET_ENDUPDATE(result, r);
	return result;
}

enum { LO_DELETE=1,
LO_KEEPUNREFERENCED,
LO_KEEPUNUSEDNEW,
LO_KEEPUNNEEDEDLISTS,
LO_NOTHINGISERROR,
LO_NOLISTDOWNLOAD,
LO_ASKPASSPHRASE,
LO_ONLYSMALLDELETES,
LO_KEEPDIRECTORIES,
LO_KEEPTEMPORARIES,
LO_FAST,
LO_SKIPOLD,
LO_GUESSGPGTTY,
LO_NODELETE,
LO_NOKEEPUNREFERENCED,
LO_NOKEEPUNUSEDNEW,
LO_NOKEEPUNNEEDEDLISTS,
LO_NONOTHINGISERROR,
LO_LISTDOWNLOAD,
LO_NOASKPASSPHRASE,
LO_NOONLYSMALLDELETES,
LO_NOKEEPDIRECTORIES,
LO_NOKEEPTEMPORARIES,
LO_NOFAST,
LO_NOSKIPOLD,
LO_NOGUESSGPGTTY,
LO_VERBOSEDB,
LO_NOVERBOSEDB,
LO_EXPORT,
LO_OUTDIR,
LO_DISTDIR,
LO_DBDIR,
LO_LOGDIR,
LO_LISTDIR,
LO_OVERRIDEDIR,
LO_CONFDIR,
LO_METHODDIR,
LO_VERSION,
LO_WAITFORLOCK,
LO_SPACECHECK,
LO_SAFETYMARGIN,
LO_DBSAFETYMARGIN,
LO_GUNZIP,
LO_BUNZIP2,
LO_UNLZMA,
LO_UNXZ,
LO_LZIP,
LO_UNZSTD,
LO_GNUPGHOME,
LO_LISTFORMAT,
LO_LISTSKIP,
LO_LISTMAX,
LO_MORGUEDIR,
LO_SHOWPERCENT,
LO_RESTRICT_BIN,
LO_RESTRICT_SRC,
LO_RESTRICT_FILE_BIN,
LO_RESTRICT_FILE_SRC,
LO_ENDHOOK,
LO_OUTHOOK,
LO_UNIGNORE};
static int longoption = 0;
static const char *programname;

static void setexport(const char *argument) {
	if (strcasecmp(argument, "silent-never") == 0) {
		CONFIGSET(export, EXPORT_SILENT_NEVER);
		return;
	}
	if (strcasecmp(argument, "never") == 0) {
		CONFIGSET(export, EXPORT_NEVER);
		return;
	}
	if (strcasecmp(argument, "changed") == 0) {
		CONFIGSET(export, EXPORT_CHANGED);
		return;
	}
	if (strcasecmp(argument, "normal") == 0) {
		CONFIGSET(export, EXPORT_NORMAL);
		return;
	}
	if (strcasecmp(argument, "lookedat") == 0) {
		CONFIGSET(export, EXPORT_NORMAL);
		return;
	}
	if (strcasecmp(argument, "force") == 0) {
		CONFIGSET(export, EXPORT_FORCE);
		return;
	}
	fprintf(stderr,
"Error: --export needs an argument of 'silenv-never', 'never', 'changed', 'lookedat' or 'force', but got '%s'\n",
			argument);
	exit(EXIT_FAILURE);
}

static unsigned long long parse_number(const char *name, const char *argument, long long max) {
	long long l;
	char *p;

	l = strtoll(argument, &p, 10);
	if (p==NULL || *p != '\0' || l < 0) {
		fprintf(stderr, "Invalid argument to %s: '%s'\n", name, argument);
		exit(EXIT_FAILURE);
	}
	if (l == LLONG_MAX  || l > max) {
		fprintf(stderr, "Too large argument for to %s: '%s'\n", name, argument);
		exit(EXIT_FAILURE);
	}
	return l;
}

static void handle_option(int c, const char *argument) {
	retvalue r;
	int i;

	switch (c) {
		case 'h':
			printf(
"reprepro - Produce and Manage a Debian package repository\n\n"
"options:\n"
" -h, --help:                        Show this help\n"
" -i  --ignore <flag>:               Ignore errors of type <flag>.\n"
"     --keepunreferencedfiles:       Do not delete files no longer needed.\n"
"     --delete:                      Delete included files if reasonable.\n"
" -b, --basedir <dir>:               Base directory\n"
"     --outdir <dir>:                Set pool and dists base directory\n"
"     --distdir <dir>:               Override dists directory.\n"
"     --dbdir <dir>:                 Directory to place the database in.\n"
"     --listdir <dir>:               Directory to place downloaded lists in.\n"
"     --confdir <dir>:               Directory to search configuration in.\n"
"     --logdir <dir>:                Directory to put requeted log files in.\n"
"     --methodir <dir>:              Use instead of /usr/lib/apt/methods/\n"
" -S, --section <section>:           Force include* to set section.\n"
" -P, --priority <priority>:         Force include* to set priority.\n"
" -C, --component <component>: 	     Add,list or delete only in component.\n"
" -A, --architecture <architecture>: Add,list or delete only to architecture.\n"
" -T, --type <type>:                 Add,list or delete only type (dsc,deb,udeb,ddeb).\n"
"\n"
"actions (selection, for more see manpage):\n"
" dumpreferences:    Print all saved references\n"
" dumpunreferenced:   Print registered files without reference\n"
" deleteunreferenced: Delete and forget all unreferenced files\n"
" checkpool:          Check if all files in the pool are still in proper shape.\n"
" check [<distributions>]\n"
"       Check for all needed files to be registered properly.\n"
" export [<distributions>]\n"
"	Force (re)generation of Packages.gz/Packages/Sources.gz/Release\n"
" update [<distributions>]\n"
"	Update the given distributions from the configured sources.\n"
" remove <distribution> <packagename>\n"
"       Remove the given package from the specified distribution.\n"
" include <distribution> <.changes-file>\n"
"       Include the given upload.\n"
" includedeb <distribution> <.deb-file>\n"
"       Include the given binary package.\n"
" includeddeb <distribution> <.ddeb-file>\n"
"       Include the given debug binary package.\n"
" includeudeb <distribution> <.udeb-file>\n"
"       Include the given installer binary package.\n"
" includedsc <distribution> <.dsc-file>\n"
"       Include the given source package.\n"
" list <distribution> <package-name>\n"
"       List all packages by the given name occurring in the given distribution.\n"
" listfilter <distribution> <condition>\n"
"       List all packages in the given distribution matching the condition.\n"
" clearvanished\n"
"       Remove everything no longer referenced in the distributions config file.\n"
"\n");
			exit(EXIT_SUCCESS);
		case '\0':
			switch (longoption) {
				case LO_UNIGNORE:
					r = set_ignore(argument, false, config_state);
					if (RET_WAS_ERROR(r)) {
						exit(EXIT_FAILURE);
					}
					break;
				case LO_SHOWPERCENT:
					global.showdownloadpercent++;
					break;
				case LO_DELETE:
					delete++;
					break;
				case LO_NODELETE:
					delete--;
					break;
				case LO_KEEPUNREFERENCED:
					CONFIGSET(keepunreferenced, true);
					break;
				case LO_NOKEEPUNREFERENCED:
					CONFIGSET(keepunreferenced, false);
					break;
				case LO_KEEPUNUSEDNEW:
					CONFIGSET(keepunusednew, true);
					break;
				case LO_NOKEEPUNUSEDNEW:
					CONFIGSET(keepunusednew, false);
					break;
				case LO_KEEPUNNEEDEDLISTS:
					/* this is the only option now and ignored
					 * for compatibility reasond */
					break;
				case LO_NOKEEPUNNEEDEDLISTS:
					fprintf(stderr,
"Warning: --nokeepuneededlists no longer exists.\n"
"Use cleanlists to clean manually.\n");
					break;
				case LO_KEEPTEMPORARIES:
					CONFIGGSET(keeptemporaries, true);
					break;
				case LO_NOKEEPTEMPORARIES:
					CONFIGGSET(keeptemporaries, false);
					break;
				case LO_ONLYSMALLDELETES:
					CONFIGGSET(onlysmalldeletes, true);
					break;
				case LO_NOONLYSMALLDELETES:
					CONFIGGSET(onlysmalldeletes, false);
					break;
				case LO_KEEPDIRECTORIES:
					CONFIGGSET(keepdirectories, true);
					break;
				case LO_NOKEEPDIRECTORIES:
					CONFIGGSET(keepdirectories, false);
					break;
				case LO_NOTHINGISERROR:
					CONFIGSET(nothingiserror, true);
					break;
				case LO_NONOTHINGISERROR:
					CONFIGSET(nothingiserror, false);
					break;
				case LO_NOLISTDOWNLOAD:
					CONFIGSET(nolistsdownload, true);
					break;
				case LO_LISTDOWNLOAD:
					CONFIGSET(nolistsdownload, false);
					break;
				case LO_ASKPASSPHRASE:
					CONFIGSET(askforpassphrase, true);
					break;
				case LO_NOASKPASSPHRASE:
					CONFIGSET(askforpassphrase, false);
					break;
				case LO_GUESSGPGTTY:
					CONFIGSET(guessgpgtty, true);
					break;
				case LO_NOGUESSGPGTTY:
					CONFIGSET(guessgpgtty, false);
					break;
				case LO_SKIPOLD:
					CONFIGSET(skipold, true);
					break;
				case LO_NOSKIPOLD:
					CONFIGSET(skipold, false);
					break;
				case LO_FAST:
					CONFIGSET(fast, true);
					break;
				case LO_NOFAST:
					CONFIGSET(fast, false);
					break;
				case LO_VERBOSEDB:
					CONFIGSET(verbosedatabase, true);
					break;
				case LO_NOVERBOSEDB:
					CONFIGSET(verbosedatabase, false);
					break;
				case LO_EXPORT:
					setexport(argument);
					break;
				case LO_OUTDIR:
					CONFIGDUP(x_outdir, argument);
					break;
				case LO_DISTDIR:
					CONFIGDUP(x_distdir, argument);
					break;
				case LO_DBDIR:
					CONFIGDUP(x_dbdir, argument);
					break;
				case LO_LISTDIR:
					CONFIGDUP(x_listdir, argument);
					break;
				case LO_CONFDIR:
					CONFIGDUP(x_confdir, argument);
					break;
				case LO_LOGDIR:
					CONFIGDUP(x_logdir, argument);
					break;
				case LO_METHODDIR:
					CONFIGDUP(x_methoddir, argument);
					break;
				case LO_MORGUEDIR:
					CONFIGDUP(x_morguedir, argument);
					break;
				case LO_VERSION:
					fprintf(stderr,
"%s: This is " PACKAGE " version " VERSION "\n",
						programname);
					exit(EXIT_SUCCESS);
				case LO_WAITFORLOCK:
					CONFIGSET(waitforlock, parse_number(
							"--waitforlock",
							argument, LONG_MAX));
					break;
				case LO_SPACECHECK:
					if (strcasecmp(argument, "none") == 0) {
						CONFIGSET(spacecheckmode, scm_NONE);
					} else if (strcasecmp(argument, "full") == 0) {
						CONFIGSET(spacecheckmode, scm_FULL);
					} else {
						fprintf(stderr,
"Unknown --spacecheck argument: '%s'!\n", argument);
						exit(EXIT_FAILURE);
					}
					break;
				case LO_SAFETYMARGIN:
					CONFIGSET(reservedotherspace, parse_number(
							"--safetymargin",
							argument, LONG_MAX));
					break;
				case LO_DBSAFETYMARGIN:
					CONFIGSET(reserveddbspace, parse_number(
							"--dbsafetymargin",
							argument, LONG_MAX));
					break;
				case LO_GUNZIP:
					CONFIGDUP(gunzip, argument);
					break;
				case LO_BUNZIP2:
					CONFIGDUP(bunzip2, argument);
					break;
				case LO_UNLZMA:
					CONFIGDUP(unlzma, argument);
					break;
				case LO_UNXZ:
					CONFIGDUP(unxz, argument);
					break;
				case LO_LZIP:
					CONFIGDUP(lunzip, argument);
					break;
				case LO_UNZSTD:
					CONFIGDUP(unzstd, argument);
					break;
				case LO_GNUPGHOME:
					CONFIGDUP(gnupghome, argument);
					break;
				case LO_ENDHOOK:
					CONFIGDUP(endhook, argument);
					break;
				case LO_OUTHOOK:
					CONFIGDUP(outhook, argument);
					break;
				case LO_LISTMAX:
					i = parse_number("--list-max",
							argument, INT_MAX);
					if (i == 0)
						i = -1;
					CONFIGSET(listmax, i);
					break;
				case LO_LISTSKIP:
					i = parse_number("--list-skip",
							argument, INT_MAX);
					CONFIGSET(listskip, i);
					break;
				case LO_LISTFORMAT:
					if (strcmp(argument, "NONE") == 0) {
						CONFIGSET(listformat, NULL);
					} else
						CONFIGDUP(listformat, argument);
					break;
				case LO_RESTRICT_BIN:
					r = filterlist_cmdline_add_pkg(false,
							argument);
					if (RET_WAS_ERROR(r))
						exit(EXIT_FAILURE);
					break;
				case LO_RESTRICT_SRC:
					r = filterlist_cmdline_add_pkg(true,
							argument);
					if (RET_WAS_ERROR(r))
						exit(EXIT_FAILURE);
					break;
				case LO_RESTRICT_FILE_BIN:
					r = filterlist_cmdline_add_file(false,
							argument);
					if (RET_WAS_ERROR(r))
						exit(EXIT_FAILURE);
					break;
				case LO_RESTRICT_FILE_SRC:
					r = filterlist_cmdline_add_file(true,
							argument);
					if (RET_WAS_ERROR(r))
						exit(EXIT_FAILURE);
					break;
				default:
					fputs(
"Error parsing arguments!\n", stderr);
					exit(EXIT_FAILURE);
			}
			longoption = 0;
			break;
		case 's':
			verbose--;
			break;
		case 'v':
			verbose++;
			break;
		case 'V':
			verbose+=5;
			break;
		case 'f':
			fprintf(stderr,
"Ignoring no longer existing option -f/--force!\n");
			break;
		case 'b':
			CONFIGDUP(x_basedir, argument);
			break;
		case 'i':
			r = set_ignore(argument, true, config_state);
			if (RET_WAS_ERROR(r)) {
				exit(EXIT_FAILURE);
			}
			break;
		case 'C':
			if (x_component != NULL &&
					strcmp(x_component, argument) != 0) {
				fprintf(stderr,
"Multiple '-%c' are not supported!\n", 'C');
				exit(EXIT_FAILURE);
			}
			CONFIGDUP(x_component, argument);
			break;
		case 'A':
			if (x_architecture != NULL &&
					strcmp(x_architecture, argument) != 0) {
				fprintf(stderr,
"Multiple '-%c' are not supported!\n", 'A');
				exit(EXIT_FAILURE);
			}
			CONFIGDUP(x_architecture, argument);
			break;
		case 'T':
			if (x_packagetype != NULL &&
					strcmp(x_packagetype, argument) != 0) {
				fprintf(stderr,
"Multiple '-%c' are not supported!\n", 'T');
				exit(EXIT_FAILURE);
			}
			CONFIGDUP(x_packagetype, argument);
			break;
		case 'S':
			if (x_section != NULL &&
					strcmp(x_section, argument) != 0) {
				fprintf(stderr,
"Multiple '-%c' are not supported!\n", 'S');
				exit(EXIT_FAILURE);
			}
			CONFIGDUP(x_section, argument);
			break;
		case 'P':
			if (x_priority != NULL &&
					strcmp(x_priority, argument) != 0) {
				fprintf(stderr,
"Multiple '-%c' are not supported!\n", 'P');
				exit(EXIT_FAILURE);
			}
			CONFIGDUP(x_priority, argument);
			break;
		case '?':
			/* getopt_long should have already given an error msg */
			exit(EXIT_FAILURE);
		default:
			fprintf(stderr, "Not supported option '-%c'\n", c);
			exit(EXIT_FAILURE);
	}
}

static volatile bool was_interrupted = false;
static bool interruption_printed = false;

bool interrupted(void) {
	if (was_interrupted) {
		if (!interruption_printed) {
			interruption_printed = true;
			fprintf(stderr,
"\n\nInterruption in progress, interrupt again to force-stop it (and risking database corruption!)\n\n");
		}
		return true;
	} else
		return false;
}

static void interrupt_signaled(int) /*__attribute__((signal))*/;
static void interrupt_signaled(UNUSED(int s)) {
	was_interrupted = true;
}

static void myexit(int) __attribute__((__noreturn__));
static void myexit(int status) {
	free(x_dbdir);
	free(x_distdir);
	free(x_listdir);
	free(x_logdir);
	free(x_confdir);
	free(x_basedir);
	free(x_outdir);
	free(x_methoddir);
	free(x_component);
	free(x_architecture);
	free(x_packagetype);
	free(x_section);
	free(x_priority);
	free(x_morguedir);
	free(gnupghome);
	free(endhook);
	free(outhook);
	pool_free();
	exit(status);
}

static void disallow_plus_prefix(const char *dir, const char *name, const char *allowed) {
	if (dir[0] != '+')
		return;
	if (dir[1] == '\0' || dir[2] != '/') {
		fprintf(stderr,
"Error: %s starts with +, but does not continue with '+b/'.\n",
				name);
		myexit(EXIT_FAILURE);
	}
	if (strchr(allowed, dir[1]) != NULL)
		return;
	fprintf(stderr, "Error: %s is not allowed to start with '+%c/'.\n"
"(if your directory is named like that, set it to './+%c/')\n",
			name, dir[1], dir[1]);
	myexit(EXIT_FAILURE);
}

static char *expand_plus_prefix(/*@only@*/char *dir, const char *name, const char *allowed, bool freedir) {
	const char *fromdir;
	char *newdir;

	disallow_plus_prefix(dir, name, allowed);

	if (dir[0] == '/' || (dir[0] == '.' && dir[1] == '/'))
		return dir;
	if (dir[0] != '+') {
		fprintf(stderr,
"Warning: %s '%s'  does not start with '/', './', or '+'.\n"
"This currently means it is relative to the current working directory,\n"
"but that might change in the future or cause an error instead!\n",
				name, dir);
		return dir;
	}
	if (dir[1] == 'b') {
		fromdir = x_basedir;
	} else if (dir[1] == 'o') {
		fromdir = x_outdir;
	} else if (dir[1] == 'c') {
		fromdir = x_confdir;
	} else {
		abort();
		return dir;
	}
	if (dir[3] == '\0')
		newdir = strdup(fromdir);
	else
		newdir = calc_dirconcat(fromdir, dir + 3);
	if (FAILEDTOALLOC(newdir)) {
		(void)fputs("Out of Memory!\n", stderr);
		exit(EXIT_FAILURE);
	}
	if (freedir)
		free(dir);
	return newdir;
}

static inline int callendhook(int status, char *argv[]) {
	char exitcode[4];

	/* Try to close all open fd but 0,1,2 */
	closefrom(3);

	if (snprintf(exitcode, 4, "%u", ((unsigned int)status)&255U) > 3)
		memcpy(exitcode, "255", 4);
	sethookenvironment(causingfile, NULL, NULL, exitcode);
	fflush(stdout);
	fflush(stderr);
	argv[0] = endhook,
	(void)execv(endhook, argv);
	fprintf(stderr, "Error executing '%s': %s\n", endhook,
				strerror(errno));
	return EXIT_RET(RET_ERROR);
}

int main(int argc, char *argv[]) {
	static struct option longopts[] = {
		{"delete", no_argument, &longoption, LO_DELETE},
		{"nodelete", no_argument, &longoption, LO_NODELETE},
		{"basedir", required_argument, NULL, 'b'},
		{"ignore", required_argument, NULL, 'i'},
		{"unignore", required_argument, &longoption, LO_UNIGNORE},
		{"noignore", required_argument, &longoption, LO_UNIGNORE},
		{"methoddir", required_argument, &longoption, LO_METHODDIR},
		{"outdir", required_argument, &longoption, LO_OUTDIR},
		{"distdir", required_argument, &longoption, LO_DISTDIR},
		{"dbdir", required_argument, &longoption, LO_DBDIR},
		{"listdir", required_argument, &longoption, LO_LISTDIR},
		{"confdir", required_argument, &longoption, LO_CONFDIR},
		{"logdir", required_argument, &longoption, LO_LOGDIR},
		{"section", required_argument, NULL, 'S'},
		{"priority", required_argument, NULL, 'P'},
		{"component", required_argument, NULL, 'C'},
		{"architecture", required_argument, NULL, 'A'},
		{"type", required_argument, NULL, 'T'},
		{"help", no_argument, NULL, 'h'},
		{"verbose", no_argument, NULL, 'v'},
		{"silent", no_argument, NULL, 's'},
		{"version", no_argument, &longoption, LO_VERSION},
		{"nothingiserror", no_argument, &longoption, LO_NOTHINGISERROR},
		{"nolistsdownload", no_argument, &longoption, LO_NOLISTDOWNLOAD},
		{"keepunreferencedfiles", no_argument, &longoption, LO_KEEPUNREFERENCED},
		{"keepunusednewfiles", no_argument, &longoption, LO_KEEPUNUSEDNEW},
		{"keepunneededlists", no_argument, &longoption, LO_KEEPUNNEEDEDLISTS},
		{"onlysmalldeletes", no_argument, &longoption, LO_ONLYSMALLDELETES},
		{"keepdirectories", no_argument, &longoption, LO_KEEPDIRECTORIES},
		{"keeptemporaries", no_argument, &longoption, LO_KEEPTEMPORARIES},
		{"ask-passphrase", no_argument, &longoption, LO_ASKPASSPHRASE},
		{"nonothingiserror", no_argument, &longoption, LO_NONOTHINGISERROR},
		{"nonolistsdownload", no_argument, &longoption, LO_LISTDOWNLOAD},
		{"listsdownload", no_argument, &longoption, LO_LISTDOWNLOAD},
		{"nokeepunreferencedfiles", no_argument, &longoption, LO_NOKEEPUNREFERENCED},
		{"nokeepunusednewfiles", no_argument, &longoption, LO_NOKEEPUNUSEDNEW},
		{"nokeepunneededlists", no_argument, &longoption, LO_NOKEEPUNNEEDEDLISTS},
		{"noonlysmalldeletes", no_argument, &longoption, LO_NOONLYSMALLDELETES},
		{"nokeepdirectories", no_argument, &longoption, LO_NOKEEPDIRECTORIES},
		{"nokeeptemporaries", no_argument, &longoption, LO_NOKEEPTEMPORARIES},
		{"noask-passphrase", no_argument, &longoption, LO_NOASKPASSPHRASE},
		{"guessgpgtty", no_argument, &longoption, LO_GUESSGPGTTY},
		{"noguessgpgtty", no_argument, &longoption, LO_NOGUESSGPGTTY},
		{"nonoguessgpgtty", no_argument, &longoption, LO_GUESSGPGTTY},
		{"fast", no_argument, &longoption, LO_FAST},
		{"nofast", no_argument, &longoption, LO_NOFAST},
		{"verbosedb", no_argument, &longoption, LO_VERBOSEDB},
		{"noverbosedb", no_argument, &longoption, LO_NOVERBOSEDB},
		{"verbosedatabase", no_argument, &longoption, LO_VERBOSEDB},
		{"noverbosedatabase", no_argument, &longoption, LO_NOVERBOSEDB},
		{"skipold", no_argument, &longoption, LO_SKIPOLD},
		{"noskipold", no_argument, &longoption, LO_NOSKIPOLD},
		{"nonoskipold", no_argument, &longoption, LO_SKIPOLD},
		{"force", no_argument, NULL, 'f'},
		{"export", required_argument, &longoption, LO_EXPORT},
		{"waitforlock", required_argument, &longoption, LO_WAITFORLOCK},
		{"checkspace", required_argument, &longoption, LO_SPACECHECK},
		{"spacecheck", required_argument, &longoption, LO_SPACECHECK},
		{"safetymargin", required_argument, &longoption, LO_SAFETYMARGIN},
		{"dbsafetymargin", required_argument, &longoption, LO_DBSAFETYMARGIN},
		{"gunzip", required_argument, &longoption, LO_GUNZIP},
		{"bunzip2", required_argument, &longoption, LO_BUNZIP2},
		{"unlzma", required_argument, &longoption, LO_UNLZMA},
		{"unxz", required_argument, &longoption, LO_UNXZ},
		{"lunzip", required_argument, &longoption, LO_LZIP},
		{"unzstd", required_argument, &longoption, LO_UNZSTD},
		{"gnupghome", required_argument, &longoption, LO_GNUPGHOME},
		{"list-format", required_argument, &longoption, LO_LISTFORMAT},
		{"list-skip", required_argument, &longoption, LO_LISTSKIP},
		{"list-max", required_argument, &longoption, LO_LISTMAX},
		{"morguedir", required_argument, &longoption, LO_MORGUEDIR},
		{"show-percent", no_argument, &longoption, LO_SHOWPERCENT},
		{"restrict", required_argument, &longoption, LO_RESTRICT_SRC},
		{"restrict-source", required_argument, &longoption, LO_RESTRICT_SRC},
		{"restrict-src", required_argument, &longoption, LO_RESTRICT_SRC},
		{"restrict-binary", required_argument, &longoption, LO_RESTRICT_BIN},
		{"restrict-file", required_argument, &longoption, LO_RESTRICT_FILE_SRC},
		{"restrict-file-source", required_argument, &longoption, LO_RESTRICT_FILE_SRC},
		{"restrict-file-src", required_argument, &longoption, LO_RESTRICT_FILE_SRC},
		{"restrict-file-binary", required_argument, &longoption, LO_RESTRICT_FILE_BIN},
		{"endhook", required_argument, &longoption, LO_ENDHOOK},
		{"outhook", required_argument, &longoption, LO_OUTHOOK},
		{NULL, 0, NULL, 0}
	};
	const struct action *a;
	retvalue r;
	int c;
	struct sigaction sa;
	char *tempconfdir;

	sigemptyset(&sa.sa_mask);
#if defined(SA_ONESHOT)
	sa.sa_flags = SA_ONESHOT;
#elif defined(SA_RESETHAND)
	sa.sa_flags = SA_RESETHAND;
#elif !defined(SPLINT)
#       error "missing argument to sigaction!"
#endif
	sa.sa_handler = interrupt_signaled;
	(void)sigaction(SIGTERM, &sa, NULL);
	(void)sigaction(SIGABRT, &sa, NULL);
	(void)sigaction(SIGINT, &sa, NULL);
	(void)sigaction(SIGQUIT, &sa, NULL);

	(void)signal(SIGPIPE, SIG_IGN);

	programname = argv[0];

	config_state = CONFIG_OWNER_DEFAULT;
	CONFIGDUP(x_basedir, STD_BASE_DIR);
	CONFIGDUP(x_confdir, "+b/conf");
	CONFIGDUP(x_methoddir, STD_METHOD_DIR);
	CONFIGDUP(x_outdir, "+b/");
	CONFIGDUP(x_distdir, "+o/dists");
	CONFIGDUP(x_dbdir, "+b/db");
	CONFIGDUP(x_logdir, "+b/logs");
	CONFIGDUP(x_listdir, "+b/lists");

	config_state = CONFIG_OWNER_CMDLINE;
	if (interrupted())
		exit(EXIT_RET(RET_ERROR_INTERRUPTED));

	while ((c = getopt_long(argc, argv, "+fVvshb:P:i:A:C:S:T:", longopts, NULL)) != -1) {
		handle_option(c, optarg);
	}
	if (optind >= argc) {
		fputs(
"No action given. (see --help for available options and actions)\n", stderr);
		exit(EXIT_FAILURE);
	}
	if (interrupted())
		exit(EXIT_RET(RET_ERROR_INTERRUPTED));

	/* only for this CONFIG_OWNER_ENVIRONMENT is a bit stupid,
	 * but perhaps it gets more... */
	config_state = CONFIG_OWNER_ENVIRONMENT;
	if (getenv("REPREPRO_BASE_DIR") != NULL) {
		CONFIGDUP(x_basedir, getenv("REPREPRO_BASE_DIR"));
	}
	if (getenv("REPREPRO_CONFIG_DIR") != NULL) {
		CONFIGDUP(x_confdir, getenv("REPREPRO_CONFIG_DIR"));
	}

	disallow_plus_prefix(x_basedir, "basedir", "");
	tempconfdir = expand_plus_prefix(x_confdir, "confdir", "b", false);

	config_state = CONFIG_OWNER_FILE;
	optionsfile_parse(tempconfdir, longopts, handle_option);
	if (tempconfdir != x_confdir)
		free(tempconfdir);

	disallow_plus_prefix(x_basedir, "basedir", "");
	disallow_plus_prefix(x_methoddir, "methoddir", "");
	x_confdir = expand_plus_prefix(x_confdir, "confdir", "b", true);
	x_outdir = expand_plus_prefix(x_outdir, "outdir", "bc", true);
	x_logdir = expand_plus_prefix(x_logdir, "logdir", "boc", true);
	x_dbdir = expand_plus_prefix(x_dbdir, "dbdir", "boc", true);
	x_distdir = expand_plus_prefix(x_distdir, "distdir", "boc", true);
	x_listdir = expand_plus_prefix(x_listdir, "listdir", "boc", true);
	if (x_morguedir != NULL)
		x_morguedir = expand_plus_prefix(x_morguedir, "morguedir",
				"boc", true);
	if (endhook != NULL) {
		if (endhook[0] == '+' || endhook[0] == '/' ||
				(endhook[0] == '.' && endhook[1] == '/')) {
			endhook = expand_plus_prefix(endhook, "endhook", "boc",
					true);
		} else {
			char *h;

			h = calc_dirconcat(x_confdir, endhook);
			free(endhook);
			endhook = h;
			if (endhook == NULL)
				exit(EXIT_RET(RET_ERROR_OOM));
		}
	}
	if (outhook != NULL) {
		if (outhook[0] == '+' || outhook[0] == '/' ||
				(outhook[0] == '.' && outhook[1] == '/')) {
			outhook = expand_plus_prefix(outhook, "outhook", "boc",
					true);
		} else {
			char *h;

			h = calc_dirconcat(x_confdir, outhook);
			free(outhook);
			outhook = h;
			if (outhook == NULL)
				exit(EXIT_RET(RET_ERROR_OOM));
		}
	}

	if (guessgpgtty && (getenv("GPG_TTY")==NULL) && isatty(0)) {
		static char terminalname[1024];
		ssize_t len;

		len = readlink("/proc/self/fd/0", terminalname, 1023);
		if (len > 0 && len < 1024) {
			terminalname[len] = '\0';
			setenv("GPG_TTY", terminalname, 0);
		} else if (verbose > 10) {
			fprintf(stderr,
"Could not readlink /proc/self/fd/0 (error was %s), not setting GPG_TTY.\n",
					strerror(errno));
		}
	}

	if (delete < D_COPY)
		delete = D_COPY;
	if (interrupted())
		exit(EXIT_RET(RET_ERROR_INTERRUPTED));
	global.basedir = x_basedir;
	global.dbdir = x_dbdir;
	global.outdir = x_outdir;
	global.confdir = x_confdir;
	global.distdir = x_distdir;
	global.logdir = x_logdir;
	global.methoddir = x_methoddir;
	global.listdir = x_listdir;
	global.morguedir = x_morguedir;

	if (gunzip != NULL && gunzip[0] == '+')
		gunzip = expand_plus_prefix(gunzip, "gunzip", "boc", true);
	if (bunzip2 != NULL && bunzip2[0] == '+')
		bunzip2 = expand_plus_prefix(bunzip2, "bunzip2", "boc", true);
	if (unlzma != NULL && unlzma[0] == '+')
		unlzma = expand_plus_prefix(unlzma, "unlzma", "boc", true);
	if (unxz != NULL && unxz[0] == '+')
		unxz = expand_plus_prefix(unxz, "unxz", "boc", true);
	if (lunzip != NULL && lunzip[0] == '+')
		lunzip = expand_plus_prefix(lunzip, "lunzip", "boc", true);
	if (unzstd != NULL && lunzip[0] == '+')
		lunzip = expand_plus_prefix(unzstd, "unzstd", "boc", true);
	uncompressions_check(gunzip, bunzip2, unlzma, unxz, lunzip, unzstd);
	free(gunzip);
	free(bunzip2);
	free(unlzma);
	free(unxz);
	free(lunzip);
	free(unzstd);

	a = all_actions;
	while (a->name != NULL) {
		a++;
	}
	r = atoms_init(a - all_actions);
	if (r == RET_ERROR_OOM)
		(void)fputs("Out of Memory!\n", stderr);
	if (RET_WAS_ERROR(r))
		exit(EXIT_RET(r));
	for (a = all_actions; a->name != NULL ; a++) {
		atoms_commands[1 + (a - all_actions)] = a->name;
	}

	if (gnupghome != NULL) {
		gnupghome = expand_plus_prefix(gnupghome,
				"gnupghome", "boc", true);
		if (setenv("GNUPGHOME", gnupghome, 1) != 0) {
			int e = errno;

			fprintf(stderr,
"Error %d setting GNUPGHOME to '%s': %s\n",
					e, gnupghome, strerror(e));
			myexit(EXIT_FAILURE);
		}
	}

	a = all_actions;
	while (a->name != NULL) {
		if (strcasecmp(a->name, argv[optind]) == 0) {
			signature_init(askforpassphrase);
			r = callaction(1 + (a - all_actions), a,
					argc-optind, (const char**)argv+optind);
			/* yeah, freeing all this stuff before exiting is
			 * stupid, but it makes valgrind logs easier
			 * readable */
			signatures_done();
			free_known_keys();
			if (RET_WAS_ERROR(r)) {
				if (r == RET_ERROR_OOM)
					(void)fputs("Out of Memory!\n", stderr);
				else if (verbose >= 0)
					(void)fputs(
"There have been errors!\n",
						stderr);
			}
			if (endhook != NULL) {
				assert (optind > 0);
				/* only returns upon error: */
				r = callendhook(EXIT_RET(r), argv + optind - 1);
			}
			myexit(EXIT_RET(r));
		} else
			a++;
	}

	fprintf(stderr,
"Unknown action '%s'. (see --help for available options and actions)\n",
			argv[optind]);
	signatures_done();
	myexit(EXIT_FAILURE);
}

retvalue package_newcontrol_by_cursor(struct package_cursor *cursor, const char *newcontrol, size_t newcontrollen) {
	return cursor_replace(cursor->target->packages, cursor->cursor,
			newcontrol, newcontrollen);
}
