/* keep.c - Maintain a list of packages to keep for deborphan.
   Copyright (C) 2000, 2001, 2002, 2003 Cris van Pelt
   Copyright (C) 2003, 2004 Peter Palfrader
   Copyright (C) 2008 Andrej Tatarenkow
   Copyright (C) 2008 Carsten Hey

   Distributed under the terms of the Artistic License.

   The general idea was borrowed from Wessel Danker's debfoster program.
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <deborphan.h>
#include <xalloc.h>

/* If package is a valid package, this function returns 0. On error, it
 * returns -1, if it is not a valid package, it returns the position in
 * the 'args' list +1.
 */
int
pkggrep(const char *sfile, char **pkgnames)
{
    FILE *fp;
    char *s, *t = NULL;
    int i = 0;
    s = (char *) xmalloc (200);

    if (!(fp = fopen(sfile, "r")))
	return -1;

    while (*pkgnames) {
	t = (char *) xmalloc (9+strlen(*pkgnames));
	rewind(fp);

	strcpy(t, "ackage:");
	strcat(t, *pkgnames);
	strcat(t, "\n");
    
	while (fgets(s, 200, fp)) {
	    if (upcase(*s) != 'P')
		continue;

	    strstripchr(s, ' ');
	    if (strcmp(s+1, t) == 0) {
		free(s);
		free(t);
		return 0;
	    }
	}

	i++;
	pkgnames++;
    }

    free(s);
    free(t);
    return i;
}

/* Read the entire keep file into memory as an array of `dep's.
 */
dep *
readkeep(const char *kfile)
{
#ifdef LOW_MEM
    FILE *f;
#else
    char *f, *bak;
#endif
    char *line, *line2;
    dep *d, *ret;
    int i = 1;

    d = ret = xmalloc(10 * sizeof(dep));

    if (!(f = debopen(kfile)))
	return NULL;

#ifndef LOW_MEM
    bak = f;
#endif

    while ((line = nextline(&f))) {
	/* We'll be modifying line with strstripchr, which nextline does
	 * not like. Guess that's the price of speed.
	 */
	line2 = xstrdup(line);

	line2 += strspn(line2, " \t");             /* skip over blanks */

	strstripchr(line2, '\n');    /* line not removed due to point release */

	line2[strcspn(line2, " \t\r\n:#")] = '\0'; /* strip arch suffix et al.*/

	if (line2[0] == '\0') {                    /* skip empty lines */
	    free(line2);
	    continue;
	}

	d->name = line2;
	d->namehash = strhash(d->name);

	d++;
	i++;
	if (i % 10 == 0) {
	    ret = xrealloc(ret, (i+10) * sizeof(dep));
	    d = ret + i - 1;
	}
    }
    d->name = NULL;

#ifdef LOW_MEM
    fclose(f);
    free(line);
#else
    free(bak);
#endif
    return ret;
}

int
mustkeep(const dep d)
{
    dep *c;

    for (c = keep; c && c->name; c++) {
	if (pkgcmp(*c, d))
	    return 1;
    }

    return 0;
}

int
addkeep(const char *kfile, char **add)
{
    FILE *fd;
    int i = 0;
    char c;

    if (!(fd = fopen(kfile, "a+")))
	return -1;

    fseek (fd, -1L, SEEK_END);
    c = getc(fd);
    fseek (fd, 1, SEEK_CUR);

    if (c != '\n')
	fputc('\n', fd);

    while (*add) {
	if (**add && **add != '\n') {
	    fputs(*add, fd);
	    fputc('\n', fd);
	}
	add++;
	i++;
    }

    return i;
}

/* Sure, this function will never win the Function of the Year award, but
 * at least we got rid of the temporary files. Now all I/O is done in
 * memory.
 */
int
delkeep(const char *kfile, char **del)
{
    int fd, i, cnt, j, tmp = 0, shrunk = 0;
    struct stat sbuf;
    char *fcont, *tok, *pt, *orig;
    ssize_t len;

    if ((fd = open(kfile, O_RDONLY | O_CREAT, 0666 /* let umask handle -w */ )) < 0)
	return -1;

    if (fstat(fd, &sbuf) < 0)
	return -1;

    for (cnt = 0; del[cnt]; cnt++)
	;

    orig = fcont = xmalloc((size_t) (sbuf.st_size + 1));
    
    if (read(fd, fcont, (size_t) sbuf.st_size) < sbuf.st_size) {
	free(fcont);
	close(fd);
	return -1;
    } 

    if (fcont[strlen(fcont) - 1] != '\n') {
	fcont[strlen(fcont)] = '\n';
	tmp = 1;
    }

    while ((tok = sstrsep(&fcont, '\n'))) {
	char *substr = tok;
	size_t substrlen;
	substr += strspn(substr, " \t");
	substrlen = strcspn(substr, " \t\r\n:#");
	if (substrlen == 0)
	    continue;
	for (i = 0; i < cnt; i++) {
	    if (strlen(del[i]) != substrlen)
		continue;
	    if (strncmp(del[i], substr, substrlen) == 0) {
		pt = fcont - (strlen(tok)+1);
		shrunk += (strlen(tok)+1);

		for (j = 0; *(fcont + j); j++)
		    *(pt + j) = *(fcont + j);
	    }
	}
  	free(tok);
    }
    close(fd);
    len = sbuf.st_size - shrunk;

    if (tmp != 0)
	len += 1;

    if ((fd = open(kfile, O_WRONLY | O_TRUNC)) < 0) {
	free(orig);
	return -1;
    }
    
    if (write(fd, orig, len) < len) {
	free(orig);
	return -1;
    }
    
    return 0;
}

/* If something in list is found in keep, this function returns its 
 * position in the list +1, else it returns 0.
 */
int
hasduplicate(char **list)
{
    int i;
    dep d;

    for (i = 0; list[i]; i++) {
	d.name = list[i];
	d.namehash = strhash(list[i]);
	if (mustkeep(d))
	    return i+1;
    }

    return 0;
}

dep *
mergekeep(const dep *a, const dep *b)
{
    unsigned int i = 0, j = 0, x = 0, y = 0;
    dep *ret;

    if (!(a || b))
	return NULL;

    if (a)
	while (a[i++].name) {};
    if (b)
	while (b[j++].name) {};

    ret = (dep *) xmalloc (sizeof(dep) * (j+i));

    for (i = 0; a && a[i].name; i++)
	ret[i] = a[i];

    for (j = 0; b && b[j].name; j++) {
	for (x = 0, y = 0; ret[x].name; x++) {
	    if (pkgcmp(ret[x], b[j]))
		y = 1;
	}
	if (!y)
	    ret[i++] = b[j];
    }

    return ret;
}

int
listkeep(const char *kfile)
{
    size_t n = 2048;
    char *s = (char *)xmalloc(n);
    FILE *fp = fopen(kfile, "r");

    if (!fp)
	return 0;

    while (getline(&s, &n, fp) > 0) {      /* getline() never returns 0 */
	char *t = s;
	t += strspn(t, " \t");             /* skip over leading blanks */
	t[strcspn(t, " \t\r\n:#")] = '\0'; /* cutting at ':' strips ":arch" */
	if (t[0] != '\0')
	    printf("%s\n", t);
    }

    /* Errors whilst reading from fp are ignored since a decade,
     * don't change this for a point release ... */
    /* deborphan exits after this function returns, so there is
     * no need to free s. */

    return 1;
}

/* This function handles the arguments to --add-keep and --del-keep.
 */
char **
parseargs(int argind, int argc, char **argv)
{
    int i = 0;
    int s = 50;
    char **ret = (char **)xcalloc(s, sizeof(char *));
    char t[100];

    for ( ; argind < argc; argind++) {
	if (i == s - 1) {
	    s *= 2;
	    ret = (char **)xrealloc(ret, s * sizeof(char *));
	}
	if (argv[argind][0] == '-' && argv[argind][1] == '\0') {
	    while (fgets(t, 100, stdin)) {
		if (i == s - 1) {
		    s *= 2;
		    ret = (char **)xrealloc(ret, s * sizeof(char *));
		}
		ret[i] = (char *)xmalloc((strlen(t)+1));
		strstripchr(t, ' ');
		strstripchr(t, '\r');
		strstripchr(t, '\n');
		strcpy(ret[i++], t);
	    }
	}
	else {
	    ret[i++] = xstrdup(argv[argind]);
	}
    }

    ret[i] = NULL;

    return ret;
}

dep *
parseargs_as_dep(int argind, int argc, char **argv)
{
    dep *rv;
    char *t;
    char **a;
    size_t i, n;

    a = parseargs(argind, argc, argv);

    for (n = 0; a[n]; n++)
	;

    rv = (dep *)xmalloc((n + 1) * sizeof(dep));

    for (i = 0; i < n; i++) {
	rv[i].name = a[i];

	t = strchr(rv[i].name, ':');
	if (t) {
	    rv[i].arch = t + 1;
	    *t = '\0';
	}
	else {
	    rv[i].arch = NULL;
	}

	rv[i].namehash = strhash(rv[i].name);
    }

    rv[n].name = rv[n].arch = NULL;

    free(a);

    return rv;
}
