/* 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

   $Id: keep.c 756 2009-02-19 08:10:54Z carsten-guest $

   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);

	strstripchr(line2, '\n');

	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;
    size_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'))) {
	for (i = 0; i < cnt; i++) {
	    if (strcmp(del[i], tok) == 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 = (size_t) (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)
{
    FILE *fp = fopen(kfile, "r");
    char s[100];

    if (!fp)
	return 0;

    while (fgets(s, 100, fp)) {
	if (*s != '\n') {
	    if (strchr(s, '\n')) {
		printf("%s", s);
	    } else {
		printf("%s\n", s);
	    }
	}
    }

    return 1;
}

/* This function handles the arguments to --add-keep and --del-keep.
 */
char **
parseargs(int argind, int argc, char **argv)
{
    char **ret = xcalloc(50, sizeof(char *));
    char t[100];
    int i = 0, s = 50;
    
    while (argind < argc) {
	if (*argv[argind] == '-' && *(argv[argind]+1) == '\0') {
	    while (fgets(t, 100, stdin)) {
		if (i == s) {
		    s *= 2;
		    ret = xrealloc(ret, s * sizeof(char *));
		}
		/* Allocate an extra byte, just to be on the safe side. */
		ret[i] = (char *) xmalloc((strlen(t)+1));
		strstripchr(t, ' ');
		strstripchr(t, '\r');
		strstripchr(t, '\n');
		strcpy(ret[i++], t);
	    }
	    argind++;
	    continue;
	}
	ret[i] = xstrdup(argv[argind]);
	argind++;
	i++;
    }

    return ret;
}
