/* RPM - Copyright (C) 1995 Red Hat Software
 * 
 * pack.c - routines for packaging
 */

#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/signal.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <ftw.h>
#include <netinet/in.h>
#include <pwd.h>
#include <grp.h>
#include <netdb.h>
#include <glob.h>
#include <zlib.h>
#include <errno.h>

#include "header.h"
#include "specP.h"
#include "rpmerr.h"
#include "rpmlead.h"
#include "rpmlib.h"
#include "signature.h"
#include "misc.h"
#include "pack.h"
#include "build.h"
#include "messages.h"
#include "md5.h"
#include "myftw.h"

#define BINARY_HEADER 0
#define SOURCE_HEADER 1

struct file_entry {
    char file[1024];
    int isdoc;
    int isconf;
    int verify_flags;
    char *uname;  /* reference -- do not free */
    char *gname;  /* reference -- do not free */
    struct stat statbuf;
    struct file_entry *next;
};

static int cpio_gzip(Header header, int fd, char *tempdir, int *archiveSize);
static int writeMagic(int fd, char *name, unsigned short type,
		      unsigned short sigtype);
static int add_file(struct file_entry **festack, const char *name,
		    int isdoc, int isconf, int isdir, int verify_flags,
		    char *Pmode, char *Uname, char *Gname);
static int compare_fe(const void *ap, const void *bp);
static int process_filelist(Header header, struct PackageRec *pr, StringBuf sb,
			    int *size, char *name, char *version,
			    char *release, int type);
static char *buildHost(void);
static int add_file_aux(const char *file, struct stat *sb, int flag);
static char *getUname(uid_t uid);
static char *getUnameS(char *uname);
static char *getGname(gid_t gid);
static char *getGnameS(char *gname);
static int glob_error(const char *foo, int bar);
static int glob_pattern_p (char *pattern);
static int parseForVerify(char *buf, int *verify_flags);
static int parseForAttr(char *origbuf, char **currPmode,
			char **currUname, char **currGname);

static
int generateRPM(char *name,       /* name-version-release         */
		int type,         /* source or binary             */
		Header header,    /* the header                   */
		char *stempdir,   /* directory containing sources */
		char *passPhrase);

static void resetDocdir(void);
static void addDocdir(char *dirname);
static int isDoc(char *filename);

int generateRPM(char *name,       /* name-version-release         */
		int type,         /* source or binary             */
		Header header,    /* the header                   */
		char *stempdir,   /* directory containing sources */
		char *passPhrase)
{
    unsigned short sigtype;
    char *archName;
    char filename[1024];
    char *sigtarget, *archiveTemp;
    int fd, ifd, count, archiveSize;
    unsigned char buffer[8192];

    /* Figure out the signature type */
    if ((sigtype = sigLookupType()) == RPMSIG_BAD) {
	error(RPMERR_BADSIGTYPE, "Bad signature type in rpmrc");
	return RPMERR_BADSIGTYPE;
    }

    /* Make the output RPM filename */
    if (type == RPMLEAD_SOURCE) {
	sprintf(filename, "%s/%s.src.rpm", getVar(RPMVAR_SRPMDIR), name);
    } else {
	archName = getArchName();
	sprintf(filename, "%s/%s/%s.%s.rpm", getVar(RPMVAR_RPMDIR),
		getVar(RPMVAR_ARCHSENSITIVE) ? archName : "", name, archName);
    }

    /* Write the archive to a temp file so we can get the size */
    archiveTemp = tempnam("/var/tmp", "rpmbuild");
    if ((fd = open(archiveTemp, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) {
	fprintf(stderr, "Could not open %s\n", archiveTemp);
	return 1;
    }
    if (cpio_gzip(header, fd, stempdir, &archiveSize)) {
	close(fd);
	unlink(archiveTemp);
	return 1;
    }
    close(fd);

    /* Add the archive size to the Header */
    addEntry(header, RPMTAG_ARCHIVESIZE, INT32_TYPE, &archiveSize, 1);
    
    /* Now write the header and append the archive */
    sigtarget = tempnam("/var/tmp", "rpmbuild");
    if ((fd = open(sigtarget, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) {
	fprintf(stderr, "Could not open %s\n", sigtarget);
	unlink(archiveTemp);
	return 1;
    }
    writeHeader(fd, header);
    ifd = open(archiveTemp, O_RDONLY, 0644);
    while ((count = read(ifd, buffer, sizeof(buffer))) > 0) {
        if (count == -1) {
	    perror("Couldn't read archiveTemp");
	    close(fd);
	    close(ifd);
	    unlink(archiveTemp);
	    unlink(sigtarget);
	    return 1;
        }
        if (write(fd, buffer, count) < 0) {
	    perror("Couldn't write package to temp file");
	    close(fd);
	    close(ifd);
	    unlink(archiveTemp);
	    unlink(sigtarget);
	    return 1;
        }
    }
    close(ifd);
    close(fd);
    unlink(archiveTemp);

    /* Now write the lead */
    if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) {
	fprintf(stderr, "Could not open %s\n", filename);
	unlink(sigtarget);
	unlink(filename);
	return 1;
    }
    if (writeMagic(fd, name, type, sigtype)) {
	close(fd);
	unlink(sigtarget);
	unlink(filename);
	return 1;
    }

    /* Generate the signature */
    message(MESS_VERBOSE, "Generating signature: %d\n", sigtype);
    fflush(stdout);
    if (makeSignature(sigtarget, sigtype, fd, passPhrase)) {
	close(fd);
	unlink(sigtarget);
	unlink(filename);
	return 1;
    }

    /* Append the header and archive */
    ifd = open(sigtarget, O_RDONLY);
    while ((count = read(ifd, buffer, sizeof(buffer))) > 0) {
        if (count == -1) {
	    perror("Couldn't read sigtarget");
	    close(ifd);
	    close(fd);
	    unlink(sigtarget);
	    unlink(filename);
	    return 1;
        }
        if (write(fd, buffer, count) < 0) {
	    perror("Couldn't write package");
	    close(ifd);
	    close(fd);
	    unlink(sigtarget);
	    unlink(filename);
	    return 1;
        }
    }
    close(ifd);
    close(fd);
    unlink(sigtarget);
    
    return 0;
}

static int writeMagic(int fd, char *name,
		      unsigned short type,
		      unsigned short sigtype)
{
    struct rpmlead lead;

    lead.major = 2;
    lead.minor = 0;
    lead.type = type;
    lead.archnum = getArchNum();
    lead.osnum = getOsNum();
    lead.signature_type = sigtype;
    strncpy(lead.name, name, sizeof(lead.name));

    writeLead(fd, &lead);

    return 0;
}

static int cpio_gzip(Header header, int fd, char *tempdir, int *archiveSize)
{
    char **f, *s;
    int count;
    int cpioPID, gzipPID;
    int cpioDead, gzipDead;
    int toCpio[2];
    int fromCpio[2];
    int toGzip[2];

    StringBuf writeBuff;
    char *writePtr;
    int writeBytesLeft, bytesWritten;

    int bytes;
    unsigned char buf[8192];

    int status;
    void *oldhandler;

    *archiveSize = 0;
    
    pipe(toCpio);
    pipe(fromCpio);
    
    oldhandler = signal(SIGPIPE, SIG_IGN);

    /* CPIO */
    if (!(cpioPID = fork())) {
	close(0);
	close(1);
	close(toCpio[1]);
	close(fromCpio[0]);
	close(fd);
	
	dup2(toCpio[0], 0);   /* Make stdin the in pipe */
	dup2(fromCpio[1], 1); /* Make stdout the out pipe */

	if (tempdir) {
	    chdir(tempdir);
	} else if (getVar(RPMVAR_ROOT)) {
	    if (chdir(getVar(RPMVAR_ROOT))) {
		error(RPMERR_EXEC, "Couldn't chdir to %s",
		      getVar(RPMVAR_ROOT));
		exit(RPMERR_EXEC);
	    }
	} else {
	    chdir("/");
	}

	execlp("cpio", "cpio",
	       (isVerbose()) ? "-ov" : "-o",
	       (tempdir) ? "-LH" : "-H",
	       "crc", NULL);
	error(RPMERR_EXEC, "Couldn't exec cpio");
	exit(RPMERR_EXEC);
    }
    if (cpioPID < 0) {
	error(RPMERR_FORK, "Couldn't fork cpio");
	return RPMERR_FORK;
    }

    pipe(toGzip);
    
    /* GZIP */
    if (!(gzipPID = fork())) {
	close(0);
	close(1);
	close(toGzip[1]);
	close(toCpio[0]);
	close(toCpio[1]);
	close(fromCpio[0]);
	close(fromCpio[1]);
	
	dup2(toGzip[0], 0);  /* Make stdin the in pipe       */
	dup2(fd, 1);         /* Make stdout the passed-in fd */

	execlp("gzip", "gzip", "-c9fn", NULL);
	error(RPMERR_EXEC, "Couldn't exec gzip");
	exit(RPMERR_EXEC);
    }
    if (gzipPID < 0) {
	error(RPMERR_FORK, "Couldn't fork gzip");
	return RPMERR_FORK;
    }

    close(toCpio[0]);
    close(fromCpio[1]);
    close(toGzip[0]);

    /* It is OK to block writing to gzip.  But it is not OK */
    /* to block reading or writing from/to cpio.            */
    fcntl(fromCpio[0], F_SETFL, O_NONBLOCK);
    fcntl(toCpio[1], F_SETFL, O_NONBLOCK);

    if (!getEntry(header, RPMTAG_FILENAMES, NULL, (void **) &f, &count)) {
	/* count may already be 0, but this is safer */
	count = 0;
    }

    writeBuff = newStringBuf();
    writeBytesLeft = 0;
    while (count--) {
        s = *f++;
        if (!tempdir) {
	    s++;
	}
        writeBytesLeft += strlen(s) + 1;
        appendLineStringBuf(writeBuff, s);
    }
    writePtr = getStringBuf(writeBuff);
   
    cpioDead = 0;
    gzipDead = 0;
    do {
	if (waitpid(cpioPID, &status, WNOHANG)) {
	    cpioDead = 1;
	}
	if (waitpid(gzipPID, &status, WNOHANG)) {
	    gzipDead = 1;
	}

	/* Write some stuff to the cpio process if possible */
        if (writeBytesLeft) {
	    if ((bytesWritten =
		  write(toCpio[1], writePtr,
		    (1024<writeBytesLeft) ? 1024 : writeBytesLeft)) < 0) {
	        if (errno != EAGAIN) {
		    perror("Damn!");
	            exit(1);
		}
	        bytesWritten = 0;
	    }
	    writeBytesLeft -= bytesWritten;
	    writePtr += bytesWritten;
	} else {
	    close(toCpio[1]);
	}
	
	/* Read any data from cpio, write it to gzip */
	bytes = read(fromCpio[0], buf, sizeof(buf));
	while (bytes > 0) {
	    *archiveSize += bytes;
	    write(toGzip[1], buf, bytes);
	    bytes = read(fromCpio[0], buf, sizeof(buf));
	}

	/* while cpio is running, or we are writing to gzip */
	/* terminate if gzip dies on us in the middle       */
    } while (((!cpioDead) || bytes) && (!gzipDead));

    if (gzipDead) {
	error(RPMERR_GZIP, "gzip died");
	return 1;
    }
    
    close(toGzip[1]); /* Terminates the gzip process */
    close(toCpio[1]);
    close(fromCpio[0]);
    
    signal(SIGPIPE, oldhandler);

    if (writeBytesLeft) {
	error(RPMERR_CPIO, "failed to write all data to cpio");
	return 1;
    }
    waitpid(cpioPID, &status, 0);
    if (!WIFEXITED(status) || WEXITSTATUS(status)) {
	error(RPMERR_CPIO, "cpio failed");
	return 1;
    }
    waitpid(gzipPID, &status, 0);
    if (!WIFEXITED(status) || WEXITSTATUS(status)) {
	error(RPMERR_GZIP, "gzip failed");
	return 1;
    }

    freeStringBuf(writeBuff);
    return 0;
}

/* XXX hard coded limit -- only 1024 %docdir allowed */
static char *docdirs[1024];
static int docdir_count;

static void resetDocdir(void)
{
    while (docdir_count--) {
        free(docdirs[docdir_count]);
    }
    docdir_count = 0;
    docdirs[docdir_count++] = strdup("/usr/doc");
    docdirs[docdir_count++] = strdup("/usr/man");
    docdirs[docdir_count++] = strdup("/usr/info");
}

static void addDocdir(char *dirname)
{
    if (docdir_count == 1024) {
	fprintf(stderr, "RPMERR_INTERNAL: Hit limit in addDocdir()\n");
	exit(RPMERR_INTERNAL);
    }
    docdirs[docdir_count++] = strdup(dirname);
}

static int isDoc(char *filename)
{
    int x = 0;

    while (x < docdir_count) {
        if (strstr(filename, docdirs[x]) == filename) {
	    return 1;
        }
	x++;
    }
    return 0;
}

/* These should only be accessed by getUname() and getUnameS() */
static uid_t uids[1024];
static char *unames[1024];
static int uid_used = 0;

/*
 * getUname() takes a uid, gets the username, and creates an entry in the
 * table to hold a string containing the user name.
 */
static char *getUname(uid_t uid)
{
    struct passwd *pw;
    int x;

    x = 0;
    while (x < uid_used) {
	if (uids[x] == uid) {
	    return unames[x];
	}
	x++;
    }

    /* XXX - This is the other hard coded limit */
    if (x == 1024) {
	fprintf(stderr, "RPMERR_INTERNAL: Hit limit in getUname()\n");
	exit(RPMERR_INTERNAL);
    }
    
    pw = getpwuid(uid);
    uids[x] = uid;
    uid_used++;
    if (pw) {
	unames[x] = strdup(pw->pw_name);
    } else {
	unames[x] = "";
    }
    return unames[x];
}

/*
 * getUnameS() takes a username, gets the uid, and creates an entry in the
 * table to hold a string containing the user name.
 */
static char *getUnameS(char *uname)
{
    struct passwd *pw;
    int x;

    x = 0;
    while (x < uid_used) {
	if (!strcmp(unames[x],uname)) {
	    return unames[x];
	}
	x++;
    }

    /* XXX - This is the other hard coded limit */
    if (x == 1024) {
	fprintf(stderr, "RPMERR_INTERNAL: Hit limit in getUname()\n");
	exit(RPMERR_INTERNAL);
    }
    
    pw = getpwnam(uname);
    uid_used++;
    if (pw) {
        uids[x] = pw->pw_uid;
	unames[x] = strdup(pw->pw_name);
    } else {
        uids[x] = -1;
	unames[x] = strdup(uname);
    }
    return unames[x];
}

/* These should only be accessed by getGname() and getGnameS() */
static gid_t gids[1024];
static char *gnames[1024];
static int gid_used = 0;
    
/*
 * getGname() takes a gid, gets the group name, and creates an entry in the
 * table to hold a string containing the group name.
 */
static char *getGname(gid_t gid)
{
    struct group *gr;
    int x;

    x = 0;
    while (x < gid_used) {
	if (gids[x] == gid) {
	    return gnames[x];
	}
	x++;
    }

    /* XXX - This is the other hard coded limit */
    if (x == 1024) {
	fprintf(stderr, "RPMERR_INTERNAL: Hit limit in getGname()\n");
	exit(RPMERR_INTERNAL);
    }
    
    gr = getgrgid(gid);
    gids[x] = gid;
    gid_used++;
    if (gr) {
	gnames[x] = strdup(gr->gr_name);
    } else {
	gnames[x] = "";
    }
    return gnames[x];
}

/*
 * getGnameS() takes a group name, gets the gid, and creates an entry in the
 * table to hold a string containing the group name.
 */
static char *getGnameS(char *gname)
{
    struct group *gr;
    int x;

    x = 0;
    while (x < gid_used) {
	if (!strcmp(gnames[x], gname)) {
	    return gnames[x];
	}
	x++;
    }

    /* XXX - This is the other hard coded limit */
    if (x == 1024) {
	fprintf(stderr, "RPMERR_INTERNAL: Hit limit in getGname()\n");
	exit(RPMERR_INTERNAL);
    }
    
    gr = getgrnam(gname);
    gid_used++;
    if (gr) {
    	gids[x] = gr->gr_gid;
	gnames[x] = strdup(gr->gr_name);
    } else {
    	gids[x] = -1;
	gnames[x] = strdup(gname);
    }
    return gnames[x];
}

/* Need three globals to keep track of things in ftw() */
static int Gisdoc;
static int Gisconf;
static int Gverify_flags;
static int Gcount;
static char *GPmode;
static char *GUname;
static char *GGname;
static struct file_entry **Gfestack;

static int add_file(struct file_entry **festack, const char *name,
		    int isdoc, int isconf, int isdir, int verify_flags,
		    char *Pmode, char *Uname, char *Gname)
{
    struct file_entry *p;
    char fullname[1024];
    int mode;

    /* Set these up for ftw() */
    Gfestack = festack;
    Gisdoc = isdoc;
    Gisconf = isconf;
    Gverify_flags = verify_flags;
    GPmode = Pmode;
    GUname = Uname;
    GGname = Gname;

    p = malloc(sizeof(struct file_entry));
    strcpy(p->file, name);
    p->isdoc = isdoc;
    p->isconf = isconf;
    p->verify_flags = verify_flags;
    if (getVar(RPMVAR_ROOT)) {
	sprintf(fullname, "%s%s", getVar(RPMVAR_ROOT), name);
    } else {
	strcpy(fullname, name);
    }
    if (lstat(fullname, &p->statbuf)) {
	return 0;
    }

    /*
     * If %attr() was specified, then use those values instead of
     * what lstat() returned.
     */
    if (Pmode && strcmp(Pmode, "-")) {
 	sscanf(Pmode, "%o", &mode);
 	mode |= p->statbuf.st_mode & S_IFMT;
 	p->statbuf.st_mode = (unsigned short)mode;
    }
    
    if (Uname && strcmp(Uname, "-")) {
 	p->uname = getUnameS(Uname);
    } else {
 	p->uname = getUname(p->statbuf.st_uid);
    }
    
    if (Gname && strcmp(Gname, "-")) {
 	p->gname = getGnameS(Gname);
    } else {
 	p->gname = getGname(p->statbuf.st_gid);
    }
    
    if ((! isdir) && S_ISDIR(p->statbuf.st_mode)) {
	/* This means we need to descend with ftw() */
	Gcount = 0;
	
	/* We use our own ftw() call, because ftw() uses stat()    */
	/* instead of lstat(), which causes it to follow symlinks! */
	myftw(fullname, add_file_aux, 16);
	
	free(p);

	return Gcount;
    } else {
	/* Link it in */
	p->next = *festack;
	*festack = p;

	message(MESS_DEBUG, "ADDING: %s\n", name);

	/* return number of entries added */
	return 1;
    }
}

static int add_file_aux(const char *file, struct stat *sb, int flag)
{
    const char *name = file;

    if (getVar(RPMVAR_ROOT)) {
	name += strlen(getVar(RPMVAR_ROOT));
    }

    /* The 1 will cause add_file() to *not* descend */
    /* directories -- ftw() is already doing it!    */
    Gcount += add_file(Gfestack, name, Gisdoc, Gisconf, 1, Gverify_flags,
			GPmode, GUname, GGname);

    return 0; /* for ftw() */
}

static int compare_fe(const void *ap, const void *bp)
{
    char *a, *b;

    a = (*(struct file_entry **)ap)->file;
    b = (*(struct file_entry **)bp)->file;

    return strcmp(a, b);
}

/* glob_pattern_p() taken from bash
 * Copyright (C) 1985, 1988, 1989 Free Software Foundation, Inc.
 */

/* Return nonzero if PATTERN has any special globbing chars in it.  */
static int glob_pattern_p (char *pattern)
{
    register char *p = pattern;
    register char c;
    int open = 0;
  
    while ((c = *p++) != '\0')
	switch (c) {
	case '?':
	case '*':
	    return (1);
	case '[':      /* Only accept an open brace if there is a close */
	    open++;    /* brace to match it.  Bracket expressions must be */
	    continue;  /* complete, according to Posix.2 */
	case ']':
	    if (open)
		return (1);
	    continue;      
	case '\\':
	    if (*p++ == '\0')
		return (0);
	}

    return (0);
}

static int glob_error(const char *foo, int bar)
{
    return 1;
}

static int parseForAttr(char *buf, char **currPmode,
			char **currUname, char **currGname)
{
    char *p, *start, *end;
    char ourbuf[1024];
    int mode, x;

    if (!(p = start = strstr(buf, "%attr"))) {
	return 1;
    }

    *currPmode = *currUname = *currGname = NULL;

    p += 5;
    while (*p && (*p == ' ' || *p == '\t')) {
	p++;
    }

    if (*p != '(') {
	error(RPMERR_BADSPEC, "Bad %%attr() syntax: %s", buf);
	return 0;
    }
    p++;

    end = p;
    while (*end && *end != ')') {
	end++;
    }

    if (! *end) {
	error(RPMERR_BADSPEC, "Bad %%attr() syntax: %s", buf);
	return 0;
    }

    strncpy(ourbuf, p, end-p);
    ourbuf[end-p] = '\0';

    *currPmode = strtok(ourbuf, ", \n\t");
    *currUname = strtok(NULL, ", \n\t");
    *currGname = strtok(NULL, ", \n\t");

    if (! (*currPmode && *currUname && *currGname)) {
	error(RPMERR_BADSPEC, "Bad %%attr() syntax: %s", buf);
	*currPmode = *currUname = *currGname = NULL;
	return 0;
    }

    /* Do a quick test on the mode argument */
    if (strcmp(*currPmode, "-")) {
	x = sscanf(*currPmode, "%o", &mode);
	if ((x == 0) || (mode >> 12)) {
	    error(RPMERR_BADSPEC, "Bad %%attr() mode spec: %s", buf);
	    *currPmode = *currUname = *currGname = NULL;
	    return 0;
	}
    }
    
    *currPmode = strdup(*currPmode);
    *currUname = strdup(*currUname);
    *currGname = strdup(*currGname);
    
    /* Set everything we just parsed to blank spaces */
    while (start <= end) {
	*start++ = ' ';
    }

    return 1;
}

static int parseForVerify(char *buf, int *verify_flags)
{
    char *p, *start, *end;
    char ourbuf[1024];
    int not;

    if (!(p = start = strstr(buf, "%verify"))) {
	return 1;
    }

    p += 7;
    while (*p && (*p == ' ' || *p == '\t')) {
	p++;
    }

    if (*p != '(') {
	error(RPMERR_BADSPEC, "Bad %%verify() syntax: %s", buf);
	return 0;
    }
    p++;

    end = p;
    while (*end && *end != ')') {
	end++;
    }

    if (! *end) {
	error(RPMERR_BADSPEC, "Bad %%verify() syntax: %s", buf);
	return 0;
    }

    strncpy(ourbuf, p, end-p);
    ourbuf[end-p] = '\0';
    while (start <= end) {
	*start++ = ' ';
    }

    p = strtok(ourbuf, ", \n\t");
    not = 0;
    *verify_flags = VERIFY_NONE;
    while (p) {
	if (!strcmp(p, "not")) {
	    not = 1;
	} else if (!strcmp(p, "md5")) {
	    *verify_flags |= VERIFY_MD5;
	} else if (!strcmp(p, "size")) {
	    *verify_flags |= VERIFY_FILESIZE;
	} else if (!strcmp(p, "link")) {
	    *verify_flags |= VERIFY_LINKTO;
	} else if (!strcmp(p, "user")) {
	    *verify_flags |= VERIFY_USER;
	} else if (!strcmp(p, "group")) {
	    *verify_flags |= VERIFY_GROUP;
	} else if (!strcmp(p, "mtime")) {
	    *verify_flags |= VERIFY_MTIME;
	} else if (!strcmp(p, "mode")) {
	    *verify_flags |= VERIFY_MODE;
	} else if (!strcmp(p, "rdev")) {
	    *verify_flags |= VERIFY_RDEV;
	} else {
	    error(RPMERR_BADSPEC, "Invalid %%verify token: %s", p);
	    return 0;
	}
	p = strtok(NULL, ", \n\t");
    }

    if (not) {
	*verify_flags = ~(*verify_flags);
    }

    return 1;
}

static int process_filelist(Header header, struct PackageRec *pr,
			    StringBuf sb, int *size, char *name,
			    char *version, char *release, int type)
{
    char buf[1024];
    char **files, **fp;
    struct file_entry *fes, *fest;
    struct file_entry **file_entry_array;
    int isdoc, isconf, isdir, verify_flags;
    char *currPmode=NULL;	/* hold info from %attr() */
    char *currUname=NULL;	/* hold info from %attr() */
    char *currGname=NULL;	/* hold info from %attr() */
    char *filename, *s;
    char *str;
    int count = 0;
    int c, x;
    glob_t glob_result;
    int special_doc;
    int passed_special_doc = 0;
    FILE *file;

    fes = NULL;
    *size = 0;

    resetDocdir();

    if (type == RPMLEAD_BINARY && pr->fileFile) {
	sprintf(buf, "%s/%s/%s", getVar(RPMVAR_BUILDDIR),
		build_subdir, pr->fileFile);
	message(MESS_DEBUG, "Reading file names from: %sXX\n", buf);
	if ((file = fopen(buf, "r")) == NULL) {
	    perror("open fileFile");
	    exit(1);
	}
	while (fgets(buf, sizeof(buf), file)) {
	    appendStringBuf(sb, buf);
	}
	fclose(file);
    }
    
    str = getStringBuf(sb);
    files = splitString(str, strlen(str), '\n');
    fp = files;

    while (*fp) {
	strcpy(buf, *fp);  /* temp copy */
	isdoc = 0;
	special_doc = 0;
	isconf = 0;
	isdir = 0;
	if (currPmode) {
	    free (currPmode);
	    currPmode = NULL;
	}
	if (currUname) {
	    free (currUname);
	    currUname = NULL;
	}
	if (currGname) {
	    free (currGname);
	    currGname = NULL;
	}
	verify_flags = VERIFY_ALL;
	filename = NULL;

	/* First preparse buf for %verify() */
	if (!parseForVerify(buf, &verify_flags)) {
	    return(RPMERR_BADSPEC);
	}
	
	/* Next parse for for %attr() */
	if (!parseForAttr(buf, &currPmode, &currUname, &currGname)) {
	    return(RPMERR_BADSPEC);
	}

	s = strtok(buf, " \t\n");
	while (s) {
	    if (!strcmp(s, "%docdir")) {
	        s = strtok(NULL, " \t\n");
		addDocdir(s);
		break;
	    } else if (!strcmp(s, "%doc")) {
		isdoc = 1;
	    } else if (!strcmp(s, "%config")) {
		isconf = 1;
	    } else if (!strcmp(s, "%dir")) {
		isdir = 1;
	    } else {
		if (isdoc && (*s != '/')) {
		    /* This is a special %doc macro */
		    special_doc = 1;
		} else {
		    filename = s;
		}
	    }
	    s = strtok(NULL, " \t\n");
	}
	if (special_doc) {
	    if (passed_special_doc) {
		fp++;
		continue;
	    } else {
		if (filename || isconf || isdir) {
		    error(RPMERR_BADSPEC,
			  "Can't mix special %%doc with other forms: %s", fp);
		    return(RPMERR_BADSPEC);
		}
		sprintf(buf, "%s/%s-%s-%s", getVar(RPMVAR_DOCDIR),
			name, version, release);
		filename = buf;
		passed_special_doc = 1;
	    }
	}
	if (! filename) {
	    fp++;
	    continue;
	}

	if (type == RPMLEAD_BINARY) {
	    /* check that file starts with leading "/" */
	    if (*filename != '/') {
		error(RPMERR_BADSPEC,
		      "File needs leading \"/\": %s", filename);
		return(RPMERR_BADSPEC);
	    }

	    if (glob_pattern_p(filename)) {
		char fullname[1024];

		if (getVar(RPMVAR_ROOT)) {
		    sprintf(fullname, "%s%s", getVar(RPMVAR_ROOT), filename);
		} else {
		    strcpy(fullname, filename);
		}

	        if (glob(fullname, 0, glob_error, &glob_result)) {
		    error(RPMERR_BADSPEC, "No matches: %s", fullname);
		    return(RPMERR_BADSPEC);
		}
		if (glob_result.gl_pathc < 1) {
		    error(RPMERR_BADSPEC, "No matches: %s", fullname);
		    return(RPMERR_BADSPEC);
		}
		x = 0;
		c = 0;
		while (x < glob_result.gl_pathc) {
		    int offset = strlen(getVar(RPMVAR_ROOT) ? : "");
		    c += add_file(&fes, &(glob_result.gl_pathv[x][offset]),
				  isdoc, isconf, isdir, verify_flags,
				  currPmode, currUname, currGname);
		    x++;
		}
		globfree(&glob_result);
	    } else {
	        c = add_file(&fes, filename, isdoc, isconf, isdir,
			     verify_flags, currPmode, currUname, currGname);
	    }
	} else {
	    /* Source package are the simple case */
	    fest = malloc(sizeof(struct file_entry));
	    fest->isdoc = 0;
	    fest->isconf = 0;
	    fest->verify_flags = 0;  /* XXX - something else? */
	    stat(filename, &fest->statbuf);
	    fest->uname = getUname(fest->statbuf.st_uid);
	    fest->gname = getGname(fest->statbuf.st_gid);
	    strcpy(fest->file, filename);
	    fest->next = fes;
	    fes = fest;
	    c = 1;
	}
	    
	if (! c) {
	    error(RPMERR_BADSPEC, "File not found: %s", filename);
	    return(RPMERR_BADSPEC);
	}
	count += c;
	
	fp++;
    }

    /* If there are no files, don't add anything to the header */
    if (count) {
	char ** fileList;
	char ** fileMD5List;
	char ** fileLinktoList;
	int_32 * fileSizeList;
	int_32 * fileUIDList;
	int_32 * fileGIDList;
	char ** fileUnameList;
	char ** fileGnameList;
	int_32 * fileMtimesList;
	int_32 * fileFlagsList;
	int_16 * fileModesList;
	int_16 * fileRDevsList;
 	int_32 * fileVerifyFlagsList;

	fileList = malloc(sizeof(char *) * count);
	fileLinktoList = malloc(sizeof(char *) * count);
	fileMD5List = malloc(sizeof(char *) * count);
	fileSizeList = malloc(sizeof(int_32) * count);
	fileUIDList = malloc(sizeof(int_32) * count);
	fileGIDList = malloc(sizeof(int_32) * count);
	fileUnameList = malloc(sizeof(char *) * count);
	fileGnameList = malloc(sizeof(char *) * count);
	fileMtimesList = malloc(sizeof(int_32) * count);
	fileFlagsList = malloc(sizeof(int_32) * count);
	fileModesList = malloc(sizeof(int_16) * count);
	fileRDevsList = malloc(sizeof(int_16) * count);
	fileVerifyFlagsList = malloc(sizeof(int_32) * count);

	/* Build a reverse sorted file array.  */
	/* This makes uninstalls a lot easier. */
	file_entry_array = malloc(sizeof(struct file_entry *) * count);
	c = 0;
	fest = fes;
	while (fest) {
	    file_entry_array[c++] = fest;
	    fest = fest->next;
	}
	qsort(file_entry_array, count, sizeof(struct file_entry *),
	      compare_fe);
	
	c = 0;
	while (c < count) {
	    fest = file_entry_array[c];
	    if (type == RPMLEAD_BINARY) {
		fileList[c] = fest->file;
	    } else {
		fileList[c] = strrchr(fest->file, '/') + 1;
	    }
	    fileUnameList[c] = fest->uname;
	    fileGnameList[c] = fest->gname;
	    *size += fest->statbuf.st_size;
	    if (S_ISREG(fest->statbuf.st_mode)) {
		if (getVar(RPMVAR_ROOT)) {
		    sprintf(buf, "%s%s", getVar(RPMVAR_ROOT), fest->file);
		} else {
		    strcpy(buf, fest->file);
		}
		mdfile(buf, buf);
		fileMD5List[c] = strdup(buf);
		message(MESS_DEBUG, "md5(%s) = %s\n", fest->file, buf);
	    } else {
		/* This is stupid */
		fileMD5List[c] = strdup("");
	    }
	    fileSizeList[c] = fest->statbuf.st_size;
	    fileUIDList[c] = fest->statbuf.st_uid;
	    fileGIDList[c] = fest->statbuf.st_gid;
	    fileMtimesList[c] = fest->statbuf.st_mtime;
	    fileFlagsList[c] = 0;
	    if (isDoc(fest->file))
	        fileFlagsList[c] |= RPMFILE_DOC;
	    if (fest->isdoc) 
		fileFlagsList[c] |= RPMFILE_DOC;
	    if (fest->isconf)
		fileFlagsList[c] |= RPMFILE_CONFIG;

	    fileModesList[c] = fest->statbuf.st_mode;
	    fileRDevsList[c] = fest->statbuf.st_rdev;
	    fileVerifyFlagsList[c] = fest->verify_flags;

	    if (S_ISLNK(fest->statbuf.st_mode)) {
		if (getVar(RPMVAR_ROOT)) {
		    sprintf(buf, "%s%s", getVar(RPMVAR_ROOT), fest->file);
		} else {
		    strcpy(buf, fest->file);
		}
	        buf[readlink(buf, buf, 1024)] = '\0';
		fileLinktoList[c] = strdup(buf);
	    } else {
		/* This is stupid */
		fileLinktoList[c] = strdup("");
	    }
	    c++;
	}

	/* Add the header entries */
	c = count;
	addEntry(header, RPMTAG_FILENAMES, STRING_ARRAY_TYPE, fileList, c);
	addEntry(header, RPMTAG_FILELINKTOS, STRING_ARRAY_TYPE,
		 fileLinktoList, c);
	addEntry(header, RPMTAG_FILEMD5S, STRING_ARRAY_TYPE, fileMD5List, c);
	addEntry(header, RPMTAG_FILESIZES, INT32_TYPE, fileSizeList, c);
	addEntry(header, RPMTAG_FILEUIDS, INT32_TYPE, fileUIDList, c);
	addEntry(header, RPMTAG_FILEGIDS, INT32_TYPE, fileGIDList, c);
	addEntry(header, RPMTAG_FILEUSERNAME, STRING_ARRAY_TYPE,
		 fileUnameList, c);
	addEntry(header, RPMTAG_FILEGROUPNAME, STRING_ARRAY_TYPE,
		 fileGnameList, c);
	addEntry(header, RPMTAG_FILEMTIMES, INT32_TYPE, fileMtimesList, c);
	addEntry(header, RPMTAG_FILEFLAGS, INT32_TYPE, fileFlagsList, c);
	addEntry(header, RPMTAG_FILEMODES, INT16_TYPE, fileModesList, c);
	addEntry(header, RPMTAG_FILERDEVS, INT16_TYPE, fileRDevsList, c);
	addEntry(header, RPMTAG_FILEVERIFYFLAGS, INT32_TYPE,
		 fileVerifyFlagsList, c);
	
	/* Free the allocated strings */
	c = count;
	while (c--) {
	    free(fileMD5List[c]);
	    free(fileLinktoList[c]);
	}

	/* Free all those lists */
	free(fileList);
	free(fileLinktoList);
	free(fileMD5List);
	free(fileSizeList);
	free(fileUIDList);
	free(fileGIDList);
	free(fileUnameList);
	free(fileGnameList);
	free(fileMtimesList);
	free(fileFlagsList);
	free(fileModesList);
	free(fileRDevsList);
	free(fileVerifyFlagsList);
	
	/* Free the file entry array */
	free(file_entry_array);
	
	/* Free the file entry stack */
	fest = fes;
	while (fest) {
	    fes = fest->next;
	    free(fest);
	    fest = fes;
	}
    }
    
    freeSplitString(files);
    return 0;
}

static time_t buildtime;
void markBuildTime(void)
{
    buildtime = time(NULL);
}

static char *buildHost(void)
{
    static char hostname[1024];
    static int gotit = 0;
    struct hostent *hbn;

    if (! gotit) {
        gethostname(hostname, sizeof(hostname));
	hbn = gethostbyname(hostname);
	strcpy(hostname, hbn->h_name);
	gotit = 1;
    }
    return(hostname);
}

int packageBinaries(Spec s, char *passPhrase)
{
    char name[1024];
    char *nametmp;
    char filename[1024];
    char sourcerpm[1024];
    char *icon;
    int iconFD;
    struct stat statbuf;
    struct PackageRec *pr;
    Header outHeader;
    HeaderIterator headerIter;
    int_32 tag, type, c;
    void *ptr;
    char *version;
    char *release;
    char *vendor;
    char *dist;
    char *packageVersion, *packageRelease;
    int size;
    int_8 os, arch;
    
    if (!getEntry(s->packages->header, RPMTAG_VERSION, NULL,
		  (void *) &version, NULL)) {
	error(RPMERR_BADSPEC, "No version field");
	return RPMERR_BADSPEC;
    }
    if (!getEntry(s->packages->header, RPMTAG_RELEASE, NULL,
		  (void *) &release, NULL)) {
	error(RPMERR_BADSPEC, "No release field");
	return RPMERR_BADSPEC;
    }

    sprintf(sourcerpm, "%s-%s-%s.src.rpm", s->name, version, release);

    vendor = NULL;
    if (!isEntry(s->packages->header, RPMTAG_VENDOR)) {
	vendor = getVar(RPMVAR_VENDOR);
    }
    dist = NULL;
    if (!isEntry(s->packages->header, RPMTAG_DISTRIBUTION)) {
	dist = getVar(RPMVAR_DISTRIBUTION);
    }
    
    /* Look through for each package */
    pr = s->packages;
    while (pr) {
	/* A file count of -1 means no package */
	if (pr->files == -1) {
	    pr = pr->next;
	    continue;
	}

	/* Handle subpackage version/release overrides */
	if (!getEntry(pr->header, RPMTAG_VERSION, NULL,
		      (void *) &packageVersion, NULL)) {
	    packageVersion = version;
	}
	if (!getEntry(pr->header, RPMTAG_RELEASE, NULL,
		      (void *) &packageRelease, NULL)) {
	    packageRelease = release;
	}
	
	/* Figure out the name of this package */
	if (!getEntry(pr->header, RPMTAG_NAME, NULL, (void *)&nametmp, NULL)) {
	    error(RPMERR_INTERNAL, "Package has no name!");
	    return RPMERR_INTERNAL;
	}
	sprintf(name, "%s-%s-%s", nametmp, packageVersion, packageRelease);

        message(MESS_VERBOSE, "Binary Packaging: %s\n", name);
       
	/**** Generate the Header ****/
	
	/* Here's the plan: copy the package's header,  */
	/* then add entries from the primary header     */
	/* that don't already exist.                    */
	outHeader = copyHeader(pr->header);
	headerIter = initIterator(s->packages->header);
	while (nextIterator(headerIter, &tag, &type, &ptr, &c)) {
	    /* Some tags we don't copy */
	    switch (tag) {
	      case RPMTAG_PREIN:
	      case RPMTAG_POSTIN:
	      case RPMTAG_PREUN:
	      case RPMTAG_POSTUN:
		  continue;
		  break;  /* Shouldn't need this */
	      default:
		  if (! isEntry(outHeader, tag)) {
		      addEntry(outHeader, tag, type, ptr, c);
		  }
	    }
	}
	freeIterator(headerIter);
	
	/* Add some final entries to the header */
	os = getArchNum();
	arch = getArchNum();
	addEntry(outHeader, RPMTAG_OS, INT8_TYPE, &os, 1);
	addEntry(outHeader, RPMTAG_ARCH, INT8_TYPE, &arch, 1);
	addEntry(outHeader, RPMTAG_BUILDTIME, INT32_TYPE, &buildtime, 1);
	addEntry(outHeader, RPMTAG_BUILDHOST, STRING_TYPE, buildHost(), 1);
	addEntry(outHeader, RPMTAG_SOURCERPM, STRING_TYPE, sourcerpm, 1);
	if (pr->icon) {
	    sprintf(filename, "%s/%s", getVar(RPMVAR_SOURCEDIR), pr->icon);
	    stat(filename, &statbuf);
	    icon = malloc(statbuf.st_size);
	    iconFD = open(filename, O_RDONLY, 0644);
	    read(iconFD, icon, statbuf.st_size);
	    close(iconFD);
	    if (! strncmp(icon, "GIF", 3)) {
		addEntry(outHeader, RPMTAG_GIF, BIN_TYPE,
			 icon, statbuf.st_size);
	    } else if (! strncmp(icon, "/* XPM", 6)) {
		addEntry(outHeader, RPMTAG_XPM, BIN_TYPE,
			 icon, statbuf.st_size);
	    } else {
	       error(RPMERR_BADSPEC, "Unknown icon type");
	       return 1;
	    }
	    free(icon);
	}
	if (vendor) {
	    addEntry(outHeader, RPMTAG_VENDOR, STRING_TYPE, vendor, 1);
	}
	if (dist) {
	    addEntry(outHeader, RPMTAG_DISTRIBUTION, STRING_TYPE, dist, 1);
	}
	
	/**** Process the file list ****/
	
	if (process_filelist(outHeader, pr, pr->filelist, &size, nametmp,
			     packageVersion, packageRelease, RPMLEAD_BINARY)) {
	    return 1;
	}

	/* Add any require/provide entries */
	addReqProvHeaderEntry(outHeader, pr);
	
	/* And add the final Header entry */
	addEntry(outHeader, RPMTAG_SIZE, INT32_TYPE, &size, 1);

	/**** Make the RPM ****/

	if (generateRPM(name, RPMLEAD_BINARY, outHeader, NULL, passPhrase)) {
	    /* Build failed */
	    return 1;
	}

	freeHeader(outHeader);
	pr = pr->next;
    }
	
    return 0;
}

/**************** SOURCE PACKAGING ************************/

int packageSource(Spec s, char *passPhrase)
{
    struct sources *source;
    struct PackageRec *package;
    char *tempdir;
    char src[1024], dest[1024], fullname[1024];
    char *version;
    char *release;
    char *vendor;
    char *dist;
    Header outHeader;
    StringBuf filelist;
    int size;
    int_8 os, arch;
    char **sources;
    char **patches;
    int scount, pcount;

    /**** Create links for all the sources ****/
    
    tempdir = tempnam("/var/tmp", "rpmbuild");
    mkdir(tempdir, 0700);

    filelist = newStringBuf();

    sources = malloc((s->numSources+1) * sizeof(char *));
    patches = malloc((s->numPatches+1) * sizeof(char *));
    
    /* Link in the spec file and all the sources */
    sprintf(dest, "%s%s", tempdir, strrchr(s->specfile, '/'));
    symlink(s->specfile, dest);
    appendLineStringBuf(filelist, dest);
    source = s->sources;
    scount = 0;
    pcount = 0;
    while (source) {
	sprintf(src, "%s/%s", getVar(RPMVAR_SOURCEDIR), source->source);
	sprintf(dest, "%s/%s", tempdir, source->source);
	symlink(src, dest);
	appendLineStringBuf(filelist, dest);
	if (source->ispatch) {
	    patches[pcount++] = source->fullSource;
	} else {
	    sources[scount++] = source->fullSource;
	}
	source = source->next;
    }
    /* ... and icons */
    package = s->packages;
    while (package) {
	if (package->icon) {
	    sprintf(src, "%s/%s", getVar(RPMVAR_SOURCEDIR), package->icon);
	    sprintf(dest, "%s/%s", tempdir, package->icon);
	    appendLineStringBuf(filelist, dest);
	    symlink(src, dest);
	}
	package = package->next;
    }

    /**** Generate the Header ****/
    
    if (!getEntry(s->packages->header, RPMTAG_VERSION, NULL,
		  (void *) &version, NULL)) {
	error(RPMERR_BADSPEC, "No version field");
	return RPMERR_BADSPEC;
    }
    if (!getEntry(s->packages->header, RPMTAG_RELEASE, NULL,
		  (void *) &release, NULL)) {
	error(RPMERR_BADSPEC, "No release field");
	return RPMERR_BADSPEC;
    }

    outHeader = copyHeader(s->packages->header);
    os = getArchNum();
    arch = getArchNum();
    addEntry(outHeader, RPMTAG_OS, INT8_TYPE, &os, 1);
    addEntry(outHeader, RPMTAG_ARCH, INT8_TYPE, &arch, 1);
    addEntry(outHeader, RPMTAG_BUILDTIME, INT32_TYPE, &buildtime, 1);
    addEntry(outHeader, RPMTAG_BUILDHOST, STRING_TYPE, buildHost(), 1);
    if (scount) 
        addEntry(outHeader, RPMTAG_SOURCE, STRING_ARRAY_TYPE, sources, scount);
    if (pcount)
        addEntry(outHeader, RPMTAG_PATCH, STRING_ARRAY_TYPE, patches, pcount);
    if (!isEntry(s->packages->header, RPMTAG_VENDOR)) {
	if ((vendor = getVar(RPMVAR_VENDOR))) {
	    addEntry(outHeader, RPMTAG_VENDOR, STRING_TYPE, vendor, 1);
	}
    }
    if (!isEntry(s->packages->header, RPMTAG_DISTRIBUTION)) {
	if ((dist = getVar(RPMVAR_DISTRIBUTION))) {
	    addEntry(outHeader, RPMTAG_DISTRIBUTION, STRING_TYPE, dist, 1);
	}
    }

    /* Process the file list */
    if (process_filelist(outHeader, NULL, filelist, &size,
			 s->name, version, release, RPMLEAD_SOURCE)) {
	return 1;
    }
    /* And add the final Header entry */
    addEntry(outHeader, RPMTAG_SIZE, INT32_TYPE, &size, 1);

    /**** Make the RPM ****/

    sprintf(fullname, "%s-%s-%s", s->name, version, release);
    message(MESS_VERBOSE, "Source Packaging: %s\n", fullname);
   
    if (generateRPM(fullname, RPMLEAD_SOURCE, outHeader,
		    tempdir, passPhrase)) {
	return 1;
    }
    
    /**** Now clean up ****/

    freeStringBuf(filelist);
    
    source = s->sources;
    while (source) {
	sprintf(dest, "%s/%s", tempdir, source->source);
	unlink(dest);
	source = source->next;
    }
    package = s->packages;
    while (package) {
	if (package->icon) {
	    sprintf(dest, "%s/%s", tempdir, package->icon);
	    unlink(dest);
	}
	package = package->next;
    }
    sprintf(dest, "%s%s", tempdir, strrchr(s->specfile, '/'));
    unlink(dest);
    rmdir(tempdir);
    
    return 0;
}

/****************** Source Removal ********************/

int doRmSource(Spec s)
{
    char filename[1024];
    struct sources *source;
    struct PackageRec *package;

    /* spec file */
    sprintf(filename, "%s%s", getVar(RPMVAR_SPECDIR),
	    strrchr(s->specfile, '/'));
    unlink(filename);

    /* sources and patches */
    source = s->sources;
    while (source) {
	sprintf(filename, "%s/%s", getVar(RPMVAR_SOURCEDIR), source->source);
	unlink(filename);
	source = source->next;
    }

    /* icons */
    package = s->packages;
    while (package) {
	if (package->icon) {
	    sprintf(filename, "%s/%s", getVar(RPMVAR_SOURCEDIR),
		    package->icon);
	    unlink(filename);
	}
	package = package->next;
    }
    
    return 0;
}
