/ .. / / -> download
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <libgen.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/stat.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>

#include "s1kd_tools.h"

/* Initial maximum number of CSDB objects of each type. */
#define OBJECT_MAX 1
static unsigned DM_MAX  = OBJECT_MAX;
static unsigned PM_MAX  = OBJECT_MAX;
static unsigned COM_MAX = OBJECT_MAX;
static unsigned IMF_MAX = OBJECT_MAX;
static unsigned DDN_MAX = OBJECT_MAX;
static unsigned DML_MAX = OBJECT_MAX;
static unsigned ICN_MAX = OBJECT_MAX;
static unsigned SMC_MAX = OBJECT_MAX;
static unsigned UPF_MAX = OBJECT_MAX;
static unsigned NON_MAX = OBJECT_MAX;

#define PROG_NAME "s1kd-ls"
#define VERSION "1.15.0"

#define ERR_PREFIX PROG_NAME ": ERROR: "

#define EXIT_OBJECT_MAX 1 /* Cannot allocate memory for more objects. */

#define E_MAX_OBJECT ERR_PREFIX "Maximum CSDB objects reached: %d\n"
#define E_BAD_LIST ERR_PREFIX "Could not read list: %s\n"

/* Set of CSDB object types to list. */
#define SHOW_DM  0x001
#define SHOW_PM  0x002
#define SHOW_COM 0x004
#define SHOW_IMF 0x008
#define SHOW_DDN 0x010
#define SHOW_DML 0x020
#define SHOW_ICN 0x040
#define SHOW_SMC 0x080
#define SHOW_UPF 0x100
#define SHOW_NON 0x200

/* Lists of CSDB objects. */
static char (*dms)[PATH_MAX] = NULL;
static char (*pms)[PATH_MAX] = NULL;
static char (*smcs)[PATH_MAX] = NULL;
static char (*coms)[PATH_MAX] = NULL;
static char (*icns)[PATH_MAX] = NULL;
static char (*imfs)[PATH_MAX] = NULL;
static char (*ddns)[PATH_MAX] = NULL;
static char (*dmls)[PATH_MAX] = NULL;
static char (*upfs)[PATH_MAX] = NULL;
static char (*nons)[PATH_MAX] = NULL;
static int ndms = 0, npms = 0, ncoms = 0, nicns = 0, nimfs = 0, nddns = 0, ndmls = 0, nsmcs = 0, nupfs = 0, nnons = 0;

/* Separator between printed CSDB objects. */
static char sep = '\n';

/* Whether the CSDB objects were created with the -N option. */
static int no_issue = 0;

/* Command string to execute with the -e option. */
static char *execstr = NULL;

static void printfiles(char (*files)[PATH_MAX], int n)
{
	int i;
	if (execstr) {
		for (i = 0; i < n; ++i) execfile(execstr, files[i]);
	} else {
		for (i = 0; i < n; ++i) printf("%s%c", files[i], sep);
	}
}

/* Compare two ICN files, grouped by file extension. */
static int compare_icn(const void *a, const void *b)
{
	char *sa, *sb, *ba, *bb, *e, *ea, *eb;
	int d;

	sa = strdup((const char *) a);
	sb = strdup((const char *) b);
	ba = basename(sa);
	bb = basename(sb);

	/* Move the file extension to the front. */
	ea = malloc(strlen(ba) + 1);
	eb = malloc(strlen(bb) + 1);

	if ((e = strchr(ba, '.'))) {
		d = e - ba;
		sprintf(ea, "%s%.*s", e, d, ba);
	} else {
		strcpy(ea, ba);
	}

	if ((e = strchr(bb, '.'))) {
		d = e - bb;
		sprintf(eb, "%s%.*s", e, d, bb);
	} else {
		strcpy(eb, bb);
	}

	d = strcasecmp(ea, eb);

	free(ea);
	free(eb);
	free(sa);
	free(sb);

	return d;
}

/* Show usage message. */
static void show_help(void)
{
	puts("Usage: " PROG_NAME " [-0CDGIiLlMNnoPRrSUwX7] [<object>|<dir> ...]");
	puts("");
	puts("Options:");
	puts("  -0, --null        Output null-delimited list.");
	puts("  -C, --com         List comments.");
	puts("  -D, --dm          List data modules.");
	puts("  -e, --exec <cmd>  Execute <cmd> for each CSDB object.");
	puts("  -G, --icn         List ICN files.");
	puts("  -I, --inwork      Show only inwork issues.");
	puts("  -i, --official    Show only official issues.");
	puts("  -h, -?, --help    Show this help message.");
	puts("  -L, --dml         List DMLs.");
	puts("  -l, --latest      Show only latest official/inwork issue.");
	puts("  -M, --imf         List ICN metadata files.");
	puts("  -N, --omit-issue  Assume issue/inwork numbers are omitted.");
	puts("  -n, --other       List non-S1000D files.");
	puts("  -o, --old         Show only old official/inwork issues.");
	puts("  -P, --pm          List publication modules.");
	puts("  -R, --read-only   Show only non-writable object files.");
	puts("  -r, --recursive   Recursively search directories.");
	puts("  -S, --smc         List SCORM content packages.");
	puts("  -U, --upf         List data update files.");
	puts("  -w, --writable    Show only writable object files.");
	puts("  -X, --ddn         List DDNs.");
	puts("  -7, --list        Treat input as list of CSDB objects.");
	puts("  --version         Show version information.");
	LIBXML2_PARSE_LONGOPT_HELP
}

/* Show version information. */
static void show_version(void)
{
	printf("%s (s1kd-tools) %s\n", PROG_NAME, VERSION);
	printf("Using libxml %s\n", xmlParserVersion);
}

/* Resize CSDB object lists when it is full. */
static void resize(char (**list)[PATH_MAX], unsigned *max)
{
	if (!(*list = realloc(*list, (*max *= 2) * PATH_MAX))) {
		fprintf(stderr, E_MAX_OBJECT,
			ndms + npms + ncoms + nimfs + nicns + nddns + ndmls + nsmcs);
		exit(EXIT_OBJECT_MAX);
	}
}

/* Determine if file is not a CSDB object. */
static int is_non(const char *base)
{
	return !(base[0] == '.' || is_com(base) || is_ddn(base) || is_dm(base) || is_dml(base) || is_icn(base) || is_imf(base) || is_pm(base) || is_smc(base) || is_upf(base));
}

/* Find CSDB objects in a given directory. */
static void list_dir(const char *path, int only_writable, int only_readonly, int recursive)
{
	DIR *dir;
	struct dirent *cur;

	int len = strlen(path);
	char fpath[PATH_MAX];
	char cpath[PATH_MAX];

	if (strcmp(path, ".") == 0) {
		strcpy(fpath, "");
	} else if (path[len - 1] != '/') {
		strcpy(fpath, path);
		strcat(fpath, "/");
	} else {
		strcpy(fpath, path);
	}

	dir = opendir(path);

	while ((cur = readdir(dir))) {
		strcpy(cpath, fpath);
		strcat(cpath, cur->d_name);

		if (access(cpath, R_OK) != 0) {
			continue;
		} else if (only_writable && access(cpath, W_OK) != 0) {
			continue;
		} else if (only_readonly && access(cpath, W_OK) == 0) {
			continue;
		} else if (dms && is_dm(cur->d_name)) {
			if (ndms == DM_MAX) {
				resize(&dms, &DM_MAX);
			}
			strcpy(dms[(ndms)++], cpath);
		} else if (pms && is_pm(cur->d_name)) {
			if (npms == PM_MAX) {
				resize(&pms, &PM_MAX);
			}
			strcpy(pms[(npms)++], cpath);
		} else if (coms && is_com(cur->d_name)) {
			if (ncoms == COM_MAX) {
				resize(&coms, &COM_MAX);
			}
			strcpy(coms[(ncoms)++], cpath);
		} else if (imfs && is_imf(cur->d_name)) {
			if (nimfs == IMF_MAX) {
				resize(&imfs, &IMF_MAX);
			}
			strcpy(imfs[(nimfs)++], cpath);
		} else if (icns && is_icn(cur->d_name)) {
			if (nicns == ICN_MAX) {
				resize(&icns, &ICN_MAX);
			}
			strcpy(icns[(nicns)++], cpath);
		} else if (ddns && is_ddn(cur->d_name)) {
			if (nddns == DDN_MAX) {
				resize(&ddns, &DDN_MAX);
			}
			strcpy(ddns[(nddns)++], cpath);
		} else if (dmls && is_dml(cur->d_name)) {
			if (ndmls == DML_MAX) {
				resize(&dmls, &DML_MAX);
			}
			strcpy(dmls[(ndmls)++], cpath);
		} else if (smcs && is_smc(cur->d_name)) {
			if (nsmcs == SMC_MAX) {
				resize(&smcs, &SMC_MAX);
			}
			strcpy(smcs[(nsmcs)++], cpath);
		} else if (upfs && is_upf(cur->d_name)) {
			if (nupfs == UPF_MAX) {
				resize(&upfs, &UPF_MAX);
			}
			strcpy(upfs[(nupfs)++], cpath);
		} else if (recursive && isdir(cpath, recursive)) {
			list_dir(cpath, only_writable, only_readonly, recursive);
		} else if (nons && is_non(cur->d_name)) {
			if (nnons == NON_MAX) {
				resize(&nons, &NON_MAX);
			}
			strcpy(nons[nnons++], cpath);
		}
	}

	closedir(dir);
}

/* Return the first node matching an XPath expression. */
static xmlNodePtr first_xpath_node(xmlDocPtr doc, xmlNodePtr node, const char *xpath)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr first;

	ctx = xmlXPathNewContext(doc ? doc : node->doc);
	ctx->node = node;

	obj = xmlXPathEvalExpression(BAD_CAST xpath, ctx);

	if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		first = NULL;
	} else {
		first = obj->nodesetval->nodeTab[0];
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return first;
}

/* Return the content of the first node matching an XPath expression. */
static xmlChar *first_xpath_value(xmlDocPtr doc, xmlNodePtr node, const char *xpath)
{
	return xmlNodeGetContent(first_xpath_node(doc, node, xpath));
}

/* Checks if a CSDB object is in the official state (inwork = 00). */
static int is_official_issue(const char *fname, const char *path)
{
	if (no_issue) {
		xmlDocPtr doc;
		xmlChar *inwork;
		int official;

		doc = read_xml_doc(path);

		if (!doc) {
			return 1;
		}

		inwork = first_xpath_value(doc, NULL, "//@inWork|//@inwork");

		official = !inwork || xmlStrcmp(inwork, BAD_CAST "00") == 0;

		xmlFree(inwork);
		xmlFreeDoc(doc);

		return official;
	} else {
		char inwork[3] = "";
		int n;
		n = sscanf(fname, "%*[^_]_%*3s-%2s", inwork);
		return n < 1 || strcmp(inwork, "00") == 0;
	}
}

static int extract_latest_icns(char (*latest)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
	int i, nlatest = 0;
	for (i = 0; i < nfiles; ++i) {
		char *name1, *name2, *base1, *base2, *s;
		int n;

		name1 = strdup(files[i]);
		base1 = basename(name1);
		if (i > 0) {
			name2 = strdup(files[i - 1]);
			base2 = basename(name2);
		} else {
			name2 = NULL;
		}

		s = strrchr(base1, '-');
		n = s - base1;

		if (i == 0 || strncmp(base1, base2, n - 3) != 0 || strcmp(s, base2 + n) != 0) {
			strcpy(latest[nlatest++], files[i]);
		} else {
			strcpy(latest[nlatest - 1], files[i]);
		}

		free(name1);
		free(name2);
	}
	return nlatest;
}

/* Copy only old issues of CSDB objects. */
static int remove_latest(char (*latest)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
	int i, nlatest = 0;
	for (i = 0; i < nfiles; ++i) {
		char *name1, *name3, *base1, *base3, *s;

		name1 = strdup(files[i]);
		base1 = basename(name1);

		s = strchr(base1, '_');
		if (!s || !strchr(s + 1, '_')) {
			free(name1);
			continue;
		}

		if (i < nfiles - 1) {
			name3 = strdup(files[i + 1]);
			base3 = basename(name3);
		} else {
			name3 = NULL;
		}

		if (name3 && strncmp(base1, base3, s - base1) == 0) {
			strcpy(latest[nlatest++], files[i]);
		}

		free(name1);
		free(name3);
	}
	return nlatest;
}
static int remove_latest_icns(char (*latest)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
	int i, nlatest = 0;
	for (i = 0; i < nfiles; ++i) {
		char *name1, *name3, *base1, *base3, *s;
		int n;

		name1 = strdup(files[i]);
		base1 = basename(name1);

		s = strrchr(base1, '-');
		if (!s) {
			free(name1);
			continue;
		}

		if (i < nfiles - 1) {
			name3 = strdup(files[i + 1]);
			base3 = basename(name3);
		} else {
			name3 = NULL;
		}

		n = s - base1;

		if (name3 && strncmp(base1, base3, n - 3) == 0 && strcmp(s, base3 + n) == 0) {
			strcpy(latest[nlatest++], files[i]);
		}

		free(name1);
		free(name3);
	}
	return nlatest;
}

/* Copy only official issues of CSDB objects. */
static int extract_official(char (*official)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
	int i, nofficial = 0;
	for (i = 0; i < nfiles; ++i) {
		char *name = strdup(files[i]);
		char *base = basename(name);

		if (is_official_issue(base, files[i])) {
			strcpy(official[nofficial++], files[i]);
		}

		free(name);
	}
	return nofficial;
}

/* Copy a list, removing official CSDB objects. */
static int remove_official(char (*official)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
	int i, nofficial = 0;
	for (i = 0; i < nfiles; ++i) {
		char *name = strdup(files[i]);
		char *base = basename(name);

		if (!is_official_issue(base, files[i])) {
			strcpy(official[nofficial++], files[i]);
		}

		free(name);
	}
	return nofficial;
}

/* Add a CSDB object to the appropriate list. */
static void list_path(const char *path, int only_writable, int only_readonly, int recursive)
{
	char tmp[PATH_MAX], *base;

	strcpy(tmp, path);
	base = basename(tmp);

	if (access(path, R_OK) != 0) {
		return;
	} else if (only_writable && access(path, W_OK) != 0) {
		return;
	} else if (only_readonly && access(path, W_OK) == 0) {
		return;
	} else if (dms && is_dm(base)) {
		if (ndms == DM_MAX) {
			resize(&dms, &DM_MAX);
		}
		strcpy(dms[ndms++], path);
	} else if (pms && is_pm(base)) {
		if (npms == PM_MAX) {
			resize(&pms, &PM_MAX);
		}
		strcpy(pms[npms++], path);
	} else if (coms && is_com(base)) {
		if (ncoms == COM_MAX) {
			resize(&coms, &COM_MAX);
		}
		strcpy(coms[ncoms++], path);
	} else if (icns && is_icn(base)) {
		if (nicns == ICN_MAX) {
			resize(&icns, &ICN_MAX);
		}
		strcpy(icns[nicns++], path);
	} else if (imfs && is_imf(base)) {
		if (nimfs == IMF_MAX) {
			resize(&imfs, &IMF_MAX);
		}
		strcpy(imfs[nimfs++], path);
	} else if (ddns && is_ddn(base)) {
		if (nddns == DDN_MAX) {
			resize(&ddns, &DDN_MAX);
		}
		strcpy(ddns[nddns++], path);
	} else if (dmls && is_dml(base)) {
		if (ndmls == DML_MAX) {
			resize(&dmls, &DML_MAX);
		}
		strcpy(dmls[ndmls++], path);
	} else if (smcs && is_smc(base)) {
		if (nsmcs == SMC_MAX) {
			resize(&smcs, &SMC_MAX);
		}
		strcpy(smcs[nsmcs++], path);
	} else if (upfs && is_upf(base)) {
		if (nupfs == UPF_MAX) {
			resize(&upfs, &UPF_MAX);
		}
		strcpy(upfs[nupfs++], path);
	} else if (isdir(path, 0)) {
		list_dir(path, only_writable, only_readonly, recursive);
	} else if (nons && is_non(base)) {
		if (nnons == NON_MAX) {
			resize(&nons, &NON_MAX);
		}
		strcpy(nons[nnons++], path);
	}
}

static void read_list(const char *path, int only_writable, int only_readonly, int recursive)
{
	FILE *f;
	char line[PATH_MAX];

	if (path) {
		if (!(f = fopen(path, "r"))) {
			fprintf(stderr, E_BAD_LIST, path);
			return;
		}
	} else {
		f = stdin;
	}

	while (fgets(line, PATH_MAX, f)) {
		strtok(line, "\t\r\n");
		list_path(line, only_writable, only_readonly, recursive);
	}

	if (path) {
		fclose(f);
	}
}

int main(int argc, char **argv)
{
	DIR *dir = NULL;

	char (*latest_dms)[PATH_MAX] = NULL;
	char (*latest_pms)[PATH_MAX] = NULL;
	char (*latest_smcs)[PATH_MAX] = NULL;
	char (*latest_imfs)[PATH_MAX] = NULL;
	char (*latest_dmls)[PATH_MAX] = NULL;
	char (*latest_icns)[PATH_MAX] = NULL;
	char (*latest_upfs)[PATH_MAX] = NULL;
	int nlatest_dms = 0, nlatest_pms = 0, nlatest_imfs = 0, nlatest_dmls = 0, nlatest_icns = 0, nlatest_smcs = 0, nlatest_upfs = 0;

	char (*issue_dms)[PATH_MAX] = NULL;
	char (*issue_pms)[PATH_MAX] = NULL;
	char (*issue_smcs)[PATH_MAX] = NULL;
	char (*issue_imfs)[PATH_MAX] = NULL;
	char (*issue_dmls)[PATH_MAX] = NULL;
	char (*issue_upfs)[PATH_MAX] = NULL;
	int nissue_dms = 0, nissue_pms = 0, nissue_imfs = 0, nissue_dmls = 0, nissue_smcs = 0, nissue_upfs = 0;

	int only_latest = 0;
	int only_official_issue = 0;
	int only_writable = 0;
	int only_readonly = 0;
	int only_old = 0;
	int only_inwork = 0;
	int recursive = 0;
	int show = 0;
	int list = 0;

	int i;

	const char *sopts = "0CDe:GiLlMPRrSwXoINnU7h?";
	struct option lopts[] = {
		{"version"   , no_argument      , 0, 0},
		{"help"      , no_argument      , 0, 'h'},
		{"null"      , no_argument      , 0, '0'},
		{"com"       , no_argument      , 0, 'C'},
		{"dm"        , no_argument      , 0, 'D'},
		{"exec"      , required_argument, 0, 'e'},
		{"icn"       , no_argument      , 0, 'G'},
		{"official"  , no_argument      , 0, 'i'},
		{"dml"       , no_argument      , 0, 'L'},
		{"latest"    , no_argument      , 0, 'l'},
		{"imf"       , no_argument      , 0, 'M'},
		{"pm"        , no_argument      , 0, 'P'},
		{"read-only" , no_argument      , 0, 'R'},
		{"recursive" , no_argument      , 0, 'r'},
		{"smc"       , no_argument      , 0, 'S'},
		{"writable"  , no_argument      , 0, 'w'},
		{"ddn"       , no_argument      , 0, 'X'},
		{"old"       , no_argument      , 0, 'o'},
		{"inwork"    , no_argument      , 0, 'I'},
		{"omit-issue", no_argument      , 0, 'N'},
		{"upf"       , no_argument      , 0, 'U'},
		{"other"     , no_argument      , 0, 'n'},
		{"list"      , no_argument      , 0, '7'},
		LIBXML2_PARSE_LONGOPT_DEFS
		{0, 0, 0, 0}
	};
	int loptind = 0;

	while ((i = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) {
		switch (i) {
			case 0:
				if (strcmp(lopts[loptind].name, "version") == 0) {
					show_version();
					return 0;
				}
				LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg)
				break;
			case '0': sep = '\0'; break;
			case 'C': show |= SHOW_COM; break;
			case 'D': show |= SHOW_DM; break;
			case 'e': execstr = strdup(optarg); break;
			case 'G': show |= SHOW_ICN; break;
			case 'i': only_official_issue = 1; break;
			case 'L': show |= SHOW_DML; break;
			case 'l': only_latest = 1; break;
			case 'M': show |= SHOW_IMF; break;
			case 'P': show |= SHOW_PM; break;
			case 'R': only_readonly = 1; break;
			case 'r': recursive = 1; break;
			case 'S': show |= SHOW_SMC; break;
			case 'w': only_writable = 1; break;
			case 'X': show |= SHOW_DDN; break;
			case 'o': only_old = 1; break;
			case 'I': only_inwork = 1; break;
			case 'N': no_issue = 1; break;
			case 'n': show |= SHOW_NON; break;
			case 'U': show |= SHOW_UPF; break;
			case '7': list = 1; break;
			case 'h':
			case '?': show_help();
				  return 0;
		}
	}

	if (!show) show = SHOW_DM | SHOW_PM | SHOW_COM | SHOW_ICN | SHOW_IMF | SHOW_DDN | SHOW_DML | SHOW_SMC | SHOW_UPF;

	if (optset(show, SHOW_DM)) {
		dms = malloc(DM_MAX * PATH_MAX);
	}
	if (optset(show, SHOW_PM)) {
		pms = malloc(PM_MAX * PATH_MAX);
	}
	if (optset(show, SHOW_COM)) {
		coms = malloc(COM_MAX * PATH_MAX);
	}
	if (optset(show, SHOW_ICN)) {
		icns = malloc(ICN_MAX * PATH_MAX);
	}
	if (optset(show, SHOW_IMF)) {
		imfs = malloc(IMF_MAX * PATH_MAX);
	}
	if (optset(show, SHOW_DDN)) {
		ddns = malloc(DDN_MAX * PATH_MAX);
	}
	if (optset(show, SHOW_DML)) {
		dmls = malloc(DML_MAX * PATH_MAX);
	}
	if (optset(show, SHOW_SMC)) {
		smcs = malloc(SMC_MAX * PATH_MAX);
	}
	if (optset(show, SHOW_UPF)) {
		upfs = malloc(UPF_MAX * PATH_MAX);
	}
	if (optset(show, SHOW_NON)) {
		nons = malloc(NON_MAX * PATH_MAX);
	}

	if (optind < argc) {
		for (i = optind; i < argc; ++i) {
			if (list) {
				read_list(argv[i], only_writable, only_readonly, recursive);
			} else {
				list_path(argv[i], only_writable, only_readonly, recursive);
			}
		}
	} else if (list) {
		read_list(NULL, only_writable, only_readonly, recursive);
	} else {
		list_dir(".", only_writable, only_readonly, recursive);
	}

	if (ndms) {
		qsort(dms, ndms, PATH_MAX, compare_basename);
		if (only_latest || only_old) latest_dms = malloc(ndms * PATH_MAX);
		if (only_official_issue || only_inwork) issue_dms = malloc(ndms * PATH_MAX);
	} else {
		free(dms);
	}
	if (npms) {
		qsort(pms, npms, PATH_MAX, compare_basename);
		if (only_latest || only_old) latest_pms = malloc(npms * PATH_MAX);
		if (only_official_issue || only_inwork) issue_pms = malloc(npms * PATH_MAX);
	} else {
		free(pms);
	}
	if (nsmcs) {
		qsort(smcs, nsmcs, PATH_MAX, compare_basename);
		if (only_latest || only_old) latest_smcs = malloc(nsmcs * PATH_MAX);
		if (only_official_issue || only_inwork) issue_smcs = malloc(nsmcs * PATH_MAX);
	} else {
		free(smcs);
	}
	if (nupfs) {
		qsort(upfs, nupfs, PATH_MAX, compare_basename);
		if (only_latest || only_old) latest_upfs = malloc(nupfs * PATH_MAX);
		if (only_official_issue || only_inwork) issue_upfs = malloc(nupfs * PATH_MAX);
	} else {
		free(upfs);
	}
	if (nimfs) {
		qsort(imfs, nimfs, PATH_MAX, compare_basename);
		if (only_latest || only_old) latest_imfs = malloc(nimfs * PATH_MAX);
		if (only_official_issue || only_inwork) issue_imfs = malloc(nimfs * PATH_MAX);
	} else {
		free(imfs);
	}
	if (ndmls) {
		qsort(dmls, ndmls, PATH_MAX, compare_basename);
		if (only_latest || only_old) latest_dmls = malloc(ndmls * PATH_MAX);
		if (only_official_issue || only_inwork) issue_dmls = malloc(ndmls * PATH_MAX);
	} else {
		free(dmls);
	}
	if (nicns) {
		qsort(icns, nicns, PATH_MAX, compare_icn);
		if (only_latest || only_old) latest_icns = malloc(nicns * PATH_MAX);
	} else {
		free(icns);
	}

	if (!ncoms) {
		free(coms);
	}
	if (!nddns) {
		free(ddns);
	}

	if (only_official_issue || only_inwork) {
		if (only_old) {
			int (*f)(char (*)[PATH_MAX], char (*)[PATH_MAX], int);

			if (ndms) {
				nissue_dms = remove_latest(issue_dms, dms, ndms);
				free(dms);
			}
			if (npms) {
				nissue_pms = remove_latest(issue_pms, pms, npms);
				free(pms);
			}
			if (nsmcs) {
				nissue_smcs = remove_latest(issue_smcs, smcs, nsmcs);
				free(smcs);
			}
			if (nupfs) {
				nissue_upfs = remove_latest(issue_upfs, upfs, nupfs);
				free(upfs);
			}
			if (nimfs) {
				nissue_imfs = remove_latest(issue_imfs, imfs, nimfs);
				free(imfs);
			}
			if (ndmls) {
				nissue_dmls = remove_latest(issue_dmls, dmls, ndmls);
				free(dmls);
			}
			if (nicns) {
				nlatest_icns = remove_latest_icns(latest_icns, icns, nicns);
				free(icns);
			}

			if (only_official_issue) {
				f = extract_official;
			} else {
				f = remove_official;
			}

			if (nissue_dms) {
				nlatest_dms = f(latest_dms, issue_dms, nissue_dms);
			}
			if (nissue_pms) {
				nlatest_pms = f(latest_pms, issue_pms, nissue_pms);
			}
			if (nissue_smcs) {
				nlatest_smcs = f(latest_smcs, issue_smcs, nissue_smcs);
			}
			if (nissue_upfs) {
				nlatest_upfs = f(latest_upfs, issue_upfs, nissue_upfs);
			}
			if (nissue_imfs) {
				nlatest_imfs = f(latest_imfs, issue_imfs, nissue_imfs);
			}
			if (nissue_dmls) {
				nlatest_dmls = f(latest_dmls, issue_dmls, nissue_dmls);
			}

			free(issue_dms);
			free(issue_pms);
			free(issue_smcs);
			free(issue_upfs);
			free(issue_imfs);
			free(issue_dmls);
		} else {
			int (*f)(char (*)[PATH_MAX], char (*)[PATH_MAX], int);

			if (only_official_issue) {
				f = extract_official;
			} else {
				f = remove_official;
			}

			if (ndms) {
				nissue_dms = f(issue_dms, dms, ndms);
				free(dms);
			}
			if (npms) {
				nissue_pms = f(issue_pms, pms, npms);
				free(pms);
			}
			if (nsmcs) {
				nissue_smcs = f(issue_smcs, smcs, nsmcs);
				free(smcs);
			}
			if (nupfs) {
				nissue_upfs = f(issue_upfs, upfs, nupfs);
				free(upfs);
			}
			if (nimfs) {
				nissue_imfs = f(issue_imfs, imfs, nimfs);
				free(imfs);
			}
			if (ndmls) {
				nissue_dmls = f(issue_dmls, dmls, ndmls);
				free(dmls);
			}

			if (only_latest) {
				if (nissue_dms) {
					nlatest_dms = extract_latest_csdb_objects(latest_dms, issue_dms, nissue_dms);
				}
				if (nissue_pms) {
					nlatest_pms = extract_latest_csdb_objects(latest_pms, issue_pms, nissue_pms);
				}
				if (nissue_smcs) {
					nlatest_smcs = extract_latest_csdb_objects(latest_smcs, issue_smcs, nissue_smcs);
				}
				if (nissue_upfs) {
					nlatest_upfs = extract_latest_csdb_objects(latest_upfs, issue_upfs, nissue_upfs);
				}
				if (nissue_imfs) {
					nlatest_imfs = extract_latest_csdb_objects(latest_imfs, issue_imfs, nissue_imfs);
				}
				if (nissue_dmls) {
					nlatest_dmls = extract_latest_csdb_objects(latest_dmls, issue_dmls, nissue_dmls);
				}
				if (nicns) {
					nlatest_icns = extract_latest_icns(latest_icns, icns, nicns);
					free(icns);
				}

				free(issue_dms);
				free(issue_pms);
				free(issue_smcs);
				free(issue_upfs);
				free(issue_imfs);
				free(issue_dmls);
			}
		}
	} else if (only_latest || only_old) {
		int (*f)(char (*)[PATH_MAX], char (*)[PATH_MAX], int);
		int (*icnf)(char (*)[PATH_MAX], char (*)[PATH_MAX], int);

		if (only_latest) {
			f = extract_latest_csdb_objects;
			icnf = extract_latest_icns;
		} else {
			f = remove_latest;
			icnf = remove_latest_icns;
		}
		if (ndms) {
			nlatest_dms = f(latest_dms, dms, ndms);
			free(dms);
		}
		if (npms) {
			nlatest_pms = f(latest_pms, pms, npms);
			free(pms);
		}
		if (nsmcs) {
			nlatest_smcs = f(latest_smcs, smcs, nsmcs);
			free(smcs);
		}
		if (nupfs) {
			nlatest_upfs = f(latest_upfs, upfs, nupfs);
			free(upfs);
		}
		if (nimfs) {
			nlatest_imfs = f(latest_imfs, imfs, nimfs);
			free(imfs);
		}
		if (ndmls) {
			nlatest_dmls = f(latest_dmls, dmls, ndmls);
			free(dmls);
		}
		if (nicns) {
			nlatest_icns = icnf(latest_icns, icns, nicns);
			free(icns);
		}
	}

	if (ncoms) {
		if (!only_old) {
			printfiles(coms, ncoms);
		}
		free(coms);
	}

	if (nddns) {
		if (!only_old) {
			printfiles(ddns, nddns);
		}
		free(ddns);
	}

	if (ndms) {
		if (only_latest || only_old) {
			printfiles(latest_dms, nlatest_dms);
			free(latest_dms);
		} else if (only_official_issue || only_inwork) {
			printfiles(issue_dms, nissue_dms);
			free(issue_dms);
		} else {
			printfiles(dms, ndms);
			free(dms);
		}
	}

	if (ndmls) {
		if (only_latest || only_old) {
			printfiles(latest_dmls, nlatest_dmls);
			free(latest_dmls);
		} else if (only_official_issue || only_inwork) {
			printfiles(issue_dmls, nissue_dmls);
			free(issue_dmls);
		} else {
			printfiles(dmls, ndmls);
			free(dmls);
		}
	}

	if (nicns) {
		if (only_inwork) {
			if (only_latest || only_old) {
				free(latest_icns);
			} else {
				free(icns);
			}
		} else {
			if (only_latest || only_old) {
				printfiles(latest_icns, nlatest_icns);
				free(latest_icns);
			} else {
				printfiles(icns, nicns);
				free(icns);
			}
		}
	}

	if (nimfs) {
		if (only_latest || only_old) {
			printfiles(latest_imfs, nlatest_imfs);
			free(latest_imfs);
		} else if (only_official_issue || only_inwork) {
			printfiles(issue_imfs, nissue_imfs);
			free(issue_imfs);
		} else {
			printfiles(imfs, nimfs);
			free(imfs);
		}
	}

	if (npms) {
		if (only_latest || only_old) {
			printfiles(latest_pms, nlatest_pms);
			free(latest_pms);
		} else if (only_official_issue || only_inwork) {
			printfiles(issue_pms, nissue_pms);
			free(issue_pms);
		} else {
			printfiles(pms, npms);
			free(pms);
		}
	}

	if (nsmcs) {
		if (only_latest || only_old) {
			printfiles(latest_smcs, nlatest_smcs);
			free(latest_smcs);
		} else if (only_official_issue || only_inwork) {
			printfiles(issue_smcs, nissue_smcs);
			free(issue_smcs);
		} else {
			printfiles(smcs, nsmcs);
			free(smcs);
		}
	}

	if (nupfs) {
		if (only_latest || only_old) {
			printfiles(latest_upfs, nlatest_upfs);
			free(latest_upfs);
		} else if (only_official_issue || only_inwork) {
			printfiles(issue_upfs, nissue_upfs);
			free(issue_upfs);
		} else {
			printfiles(upfs, nupfs);
			free(upfs);
		}
	}

	if (nnons) {
		printfiles(nons, nnons);
		free(nons);
	}

	if (dir) {
		closedir(dir);
	}

	free(execstr);

	xmlCleanupParser();

	return 0;
}


/ gopher://khzae.net/0/s1000d/s1kd-tools/src/tools/s1kd-ls/s1kd-ls.c
Styles: Light Dark Classic