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

#include "miscfn.h"

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>

#include "spec.h"
#include "specP.h"
#include "stringbuf.h"
#include "build.h"
#include "messages.h"
#include "md5.h"
#include "myftw.h"
#include "header.h"
#include "files.h"
#include "names.h"
#include "rpmlead.h"
#include "rpmlib.h"
#include "misc.h"
#include "macro.h"

#define BINARY_HEADER 0
#define SOURCE_HEADER 1

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

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, char *prefix);
static int compare_fe(const void *ap, const void *bp);
static int add_file_aux(const char *file, struct stat *sb, int flag);
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 parseForConfig(char *buf, int *conf);
static int parseForAttr(char *origbuf, char **currPmode,
			char **currUname, char **currGname);

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

static int processFileListFailed;

static void parseForDocFiles(struct PackageRec *package, char *line)
{
    if (! (line = strstr(line, "%doc"))) {
	return;
    }

    line += 4;
    if ((*line != ' ') && (*line != '\t')) {
	return;
    }
    line += strspn(line, " \t\n");
    if ((! *line) || (*line == '/')) {
	return;
    }
    
    appendLineStringBuf(package->doc, "mkdir -p $DOCDIR");
    appendStringBuf(package->doc, "cp -pr ");
    appendStringBuf(package->doc, line);
    appendLineStringBuf(package->doc, " $DOCDIR");
}

int finish_filelists(Spec spec)
{
    char buf[1024];
    FILE *file;
    struct PackageRec *pr = spec->packages;
    char *s, **files, **line;
    char *version, *release, *packageVersion, *packageRelease, *docs, *name;

    headerGetEntry(spec->packages->header, RPMTAG_VERSION, NULL,
	     (void *) &version, NULL);
    headerGetEntry(spec->packages->header, RPMTAG_RELEASE, NULL,
	     (void *) &release, NULL);
    
    while (pr) {
	if (pr->fileFile) {
	    sprintf(buf, "%s/%s/%s", rpmGetVar(RPMVAR_BUILDDIR),
		    build_subdir, pr->fileFile);
	    rpmMessage(RPMMESS_DEBUG, "Reading file names from: %s\n", buf);
	    if ((file = fopen(buf, "r")) == NULL) {
		rpmError(RPMERR_BADSPEC, "unable to open filelist: %s\n", buf);
		return(RPMERR_BADSPEC);
	    }
	    while (fgets(buf, sizeof(buf), file)) {
		expandMacros(buf);
		appendStringBuf(pr->filelist, buf);
	    }
	    fclose(file);
	}

	/* parse for %doc wierdness */
	s = getStringBuf(pr->filelist);
	files = splitString(s, strlen(s), '\n');
	line = files;
	while (*line) {
	    parseForDocFiles(pr, *line);
	    line++;
	}
	freeSplitString(files);

	/* Handle subpackage version/release overrides */
        if (!headerGetEntry(pr->header, RPMTAG_VERSION, NULL,
		      (void *) &packageVersion, NULL)) {
            packageVersion = version;
	}
        if (!headerGetEntry(pr->header, RPMTAG_RELEASE, NULL,
		      (void *) &packageRelease, NULL)) {
            packageRelease = release;
	}

	/* Generate the doc script */
	appendStringBuf(spec->doc, "DOCDIR=$RPM_ROOT_DIR/$RPM_DOC_DIR/");
	headerGetEntry(pr->header, RPMTAG_NAME, NULL, (void *) &name, NULL);
	sprintf(buf, "%s-%s-%s", name, packageVersion, packageRelease);
	appendLineStringBuf(spec->doc, buf);
	docs = getStringBuf(pr->doc);
	if (*docs) {
	    appendLineStringBuf(spec->doc, "rm -rf $DOCDIR");
	    appendLineStringBuf(spec->doc, docs);
	}

	pr = pr->next;
    }

    return 0;
}

int process_filelist(Header header, struct PackageRec *pr,
		     StringBuf sb, int *size, char *name,
		     char *version, char *release, int type,
		     char *prefix, char *specFile)
{
    char buf[1024];
    char **files, **fp;
    struct file_entry *fes, *fest;
    struct file_entry **file_entry_array;
    int isdoc, conf, 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;
    int tc;
    char *tcs;
    int currentTime;

    processFileListFailed = 0;
    fes = NULL;
    *size = 0;

    resetDocdir();

    str = getStringBuf(sb);
    files = splitString(str, strlen(str), '\n');
    fp = files;

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

	/* First preparse buf for %verify() */
	if (parseForVerify(buf, &verify_flags)) {
	    processFileListFailed = 1;
	    fp++; continue;
	}
	
	/* Next parse for %attr() */
	if (parseForAttr(buf, &currPmode, &currUname, &currGname)) {
	    processFileListFailed = 1;
	    fp++; continue;
	}

	/* Then parse for %config or %config() */
	if (parseForConfig(buf, &conf)) {
	    processFileListFailed = 1;
	    fp++; continue;
	}

	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, "%dir")) {
		isdir = 1;
	    } else {
		if (filename) {
		    /* We already got a file -- error */
		    rpmError(RPMERR_BADSPEC,
			  "Two files on one line: %s", filename);
		    processFileListFailed = 1;
		}
		if (*s != '/') {
		    if (isdoc) {
			special_doc = 1;
		    } else {
			/* not in %doc, does not begin with / -- error */
			rpmError(RPMERR_BADSPEC,
			      "File must begin with \"/\": %s", s);
			processFileListFailed = 1;
		    }
		} else {
		    filename = s;
		}
	    }
	    s = strtok(NULL, " \t\n");
	}
	if (special_doc) {
	    if (passed_special_doc) {
		fp++;
		continue;
	    } else {
		if (filename || conf || isdir) {
		    rpmError(RPMERR_BADSPEC,
			  "Can't mix special %%doc with other forms: %s", fp);
		    processFileListFailed = 1;
		    fp++; continue;
		}
		sprintf(buf, "%s/%s-%s-%s", rpmGetVar(RPMVAR_DEFAULTDOCDIR), 
			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 != '/') {
		rpmError(RPMERR_BADSPEC,
		      "File needs leading \"/\": %s", filename);
		processFileListFailed = 1;
		fp++; continue;
	    }

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

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

	        if (glob(fullname, 0, glob_error, &glob_result) ||
		    (glob_result.gl_pathc < 1)) {
		    rpmError(RPMERR_BADSPEC, "No matches: %s", fullname);
		    processFileListFailed = 1;
		    globfree(&glob_result);
		    fp++; continue;
		}
		
		c = 0;
		x = 0;
		while (x < glob_result.gl_pathc) {
		    int offset = strlen(rpmGetVar(RPMVAR_ROOT) ? : "");
		    c += add_file(&fes, &(glob_result.gl_pathv[x][offset]),
				  isdoc, conf, isdir, verify_flags,
				  currPmode, currUname, currGname, prefix);
		    x++;
		}
		globfree(&glob_result);
	    } else {
	        c = add_file(&fes, filename, isdoc, conf, isdir,
			     verify_flags, currPmode, currUname,
			     currGname, prefix);
	    }
	} else {
	    /* Source package are the simple case */
	    fest = malloc(sizeof(struct file_entry));
	    fest->isdoc = 0;
	    fest->conf = 0;
	    if (!strcmp(filename, specFile)) {
		fest->isspecfile = 1;
	    } else {
		fest->isspecfile = 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);
	    if (! (fest->uname && fest->gname)) {
		rpmError(RPMERR_BADSPEC, "Bad owner/group: %s", filename);
		fest->uname = "";
		fest->gname = "";
		processFileListFailed = 1;
	    }
	    strcpy(fest->file, filename);
	    fest->next = fes;
	    fes = fest;
	    c = 1;
	}
	    
	if (! c) {
	    rpmError(RPMERR_BADSPEC, "File not found: %s", filename);
	    processFileListFailed = 1;
	}
	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);
		
	/* Do timecheck */
	tc = 0;
	currentTime = time(NULL);
	if ((tcs = rpmGetVar(RPMVAR_TIMECHECK))) {
	    tc = strtoul(tcs, NULL, 10);
	}
    
	c = 0;
	while (c < count) {
	    fest = file_entry_array[c];
	    if (type == RPMLEAD_BINARY) {
		x = strlen(fest->file) - 1;
		if (x && fest->file[x] == '/') {
		    fest->file[x] = '\0';
		}
		fileList[c] = fest->file;
	    } else {
		fileList[c] = strrchr(fest->file, '/') + 1;
	    }
	    if ((c > 0) && !strcmp(fileList[c], fileList[c-1])) {
		rpmError(RPMERR_BADSPEC, "File listed twice: %s", fileList[c]);
		processFileListFailed = 1;
	    }
	    
	    fileUnameList[c] = fest->uname;
	    fileGnameList[c] = fest->gname;
	    *size += fest->statbuf.st_size;
	    if (S_ISREG(fest->statbuf.st_mode)) {
		if ((type == RPMLEAD_BINARY) &&
		    rpmGetVar(RPMVAR_ROOT)) {
		    sprintf(buf, "%s%s", rpmGetVar(RPMVAR_ROOT), fest->file);
		} else {
		    strcpy(buf, fest->file);
		}
		mdfile(buf, buf);
		fileMD5List[c] = strdup(buf);
		rpmMessage(RPMMESS_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;

	    /* Do timecheck */
	    if (tc && (type == RPMLEAD_BINARY)) {
		if (currentTime - fest->statbuf.st_mtime > tc) {
		    rpmMessage(RPMMESS_WARNING, "TIMECHECK failure: %s\n",
			    fest->file);
		}
	    }
    
	    fileFlagsList[c] = 0;
	    if (isDoc(fest->file))
	        fileFlagsList[c] |= RPMFILE_DOC;
	    if (fest->isdoc) 
		fileFlagsList[c] |= RPMFILE_DOC;
	    if (fest->conf && !(fest->statbuf.st_mode & S_IFDIR))
		fileFlagsList[c] |= fest->conf;
	    if (fest->isspecfile)
		fileFlagsList[c] |= RPMFILE_SPECFILE;

	    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 (rpmGetVar(RPMVAR_ROOT)) {
		    sprintf(buf, "%s%s", rpmGetVar(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;
	headerAddEntry(header, RPMTAG_FILENAMES, RPM_STRING_ARRAY_TYPE, fileList, c);
	headerAddEntry(header, RPMTAG_FILELINKTOS, RPM_STRING_ARRAY_TYPE,
		 fileLinktoList, c);
	headerAddEntry(header, RPMTAG_FILEMD5S, RPM_STRING_ARRAY_TYPE, fileMD5List, c);
	headerAddEntry(header, RPMTAG_FILESIZES, RPM_INT32_TYPE, fileSizeList, c);
	headerAddEntry(header, RPMTAG_FILEUIDS, RPM_INT32_TYPE, fileUIDList, c);
	headerAddEntry(header, RPMTAG_FILEGIDS, RPM_INT32_TYPE, fileGIDList, c);
	headerAddEntry(header, RPMTAG_FILEUSERNAME, RPM_STRING_ARRAY_TYPE,
		 fileUnameList, c);
	headerAddEntry(header, RPMTAG_FILEGROUPNAME, RPM_STRING_ARRAY_TYPE,
		 fileGnameList, c);
	headerAddEntry(header, RPMTAG_FILEMTIMES, RPM_INT32_TYPE, fileMtimesList, c);
	headerAddEntry(header, RPMTAG_FILEFLAGS, RPM_INT32_TYPE, fileFlagsList, c);
	headerAddEntry(header, RPMTAG_FILEMODES, RPM_INT16_TYPE, fileModesList, c);
	headerAddEntry(header, RPMTAG_FILERDEVS, RPM_INT16_TYPE, fileRDevsList, c);
	headerAddEntry(header, RPMTAG_FILEVERIFYFLAGS, RPM_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 processFileListFailed;
}

/*************************************************************/
/*                                                           */
/* misc                                                      */
/*                                                           */
/*************************************************************/

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

/*************************************************************/
/*                                                           */
/* Doc dir stuff                                             */
/*                                                           */
/*************************************************************/

/* 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");
    docdirs[docdir_count++] = strdup("/usr/X11R6/man");
}

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

/*************************************************************/
/*                                                           */
/* File stating / tree walk                                  */
/*                                                           */
/*************************************************************/

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

static int add_file(struct file_entry **festack, const char *name,
		    int isdoc, int conf, int isdir, int verify_flags,
		    char *Pmode, char *Uname, char *Gname, char *prefix)
{
    struct file_entry *p;
    char *copyTo, copied;
    const char *prefixTest, *prefixPtr;
    const char *copyFrom;
    char fullname[1024];
    int mode;

    /* Set these up for ftw() */
    Gfestack = festack;
    Gisdoc = isdoc;
    Gconf = conf;
    Gverify_flags = verify_flags;
    GPmode = Pmode;
    GUname = Uname;
    GGname = Gname;
    Gprefix = prefix;

    p = malloc(sizeof(struct file_entry));

    copyTo = p->file;
    copied = '\0';
    copyFrom = name;
    while (*copyFrom) {
	if (*copyFrom != '/' || copied != '/') {
	    *copyTo++ = copied = *copyFrom;
	}
	copyFrom++;
    }
    *copyTo = '\0';

    p->isdoc = isdoc;
    p->conf = conf;
    p->isspecfile = 0;  /* source packages are done by hand */
    p->verify_flags = verify_flags;
    if (rpmGetVar(RPMVAR_ROOT)) {
	sprintf(fullname, "%s%s", rpmGetVar(RPMVAR_ROOT), name);
    } else {
	strcpy(fullname, name);
    }

    /* If we are using a prefix, validate the file */
    if (prefix) {
	prefixTest = name;
	prefixPtr = prefix;
	while (*prefixTest == '/') {
	    prefixTest++;  /* Skip leading "/" */
	}
	while (*prefixPtr && *prefixTest && (*prefixTest == *prefixPtr)) {
	    prefixPtr++;
	    prefixTest++;
	}
	if (*prefixPtr) {
	    rpmError(RPMERR_BADSPEC, "File doesn't match prefix (%s): %s",
		  prefix, name);
	    return 0;
	}
    }

    /* OK, finally stat() the file */
    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 (! (p->uname && p->gname)) {
	rpmError(RPMERR_BADSPEC, "Bad owner/group: %s\n", fullname);
	p->uname = "";
	p->gname = "";
	return 0;
    }
    
    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;

	rpmMessage(RPMMESS_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 (rpmGetVar(RPMVAR_ROOT)) {
	name += strlen(rpmGetVar(RPMVAR_ROOT));
    }

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

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

/*************************************************************/
/*                                                           */
/* globbing                                                  */
/*                                                           */
/*************************************************************/

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

/*************************************************************/
/*                                                           */
/* %attr parsing                                             */
/*                                                           */
/*************************************************************/

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 0;
    }

    *currPmode = *currUname = *currGname = NULL;

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

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

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

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

    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)) {
	rpmError(RPMERR_BADSPEC, "Bad %%attr() syntax: %s", buf);
	*currPmode = *currUname = *currGname = NULL;
	return RPMERR_BADSPEC;
    }

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

    return 0;
}

/*************************************************************/
/*                                                           */
/* %verify parsing                                           */
/*                                                           */
/*************************************************************/

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

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

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

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

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

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

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

    p = strtok(ourbuf, ", \n\t");
    not = 0;
    *verify_flags = RPMVERIFY_NONE;
    while (p) {
	if (!strcmp(p, "not")) {
	    not = 1;
	} else if (!strcmp(p, "md5")) {
	    *verify_flags |= RPMVERIFY_MD5;
	} else if (!strcmp(p, "size")) {
	    *verify_flags |= RPMVERIFY_FILESIZE;
	} else if (!strcmp(p, "link")) {
	    *verify_flags |= RPMVERIFY_LINKTO;
	} else if (!strcmp(p, "user")) {
	    *verify_flags |= RPMVERIFY_USER;
	} else if (!strcmp(p, "group")) {
	    *verify_flags |= RPMVERIFY_GROUP;
	} else if (!strcmp(p, "mtime")) {
	    *verify_flags |= RPMVERIFY_MTIME;
	} else if (!strcmp(p, "mode")) {
	    *verify_flags |= RPMVERIFY_MODE;
	} else if (!strcmp(p, "rdev")) {
	    *verify_flags |= RPMVERIFY_RDEV;
	} else {
	    rpmError(RPMERR_BADSPEC, "Invalid %%verify token: %s", p);
	    return RPMERR_BADSPEC;
	}
	p = strtok(NULL, ", \n\t");
    }

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

    return 0;
}

static int parseForConfig(char *buf, int *conf)
{
    char *p, *start, *end;
    char ourbuf[1024];

    if (!(p = start = strstr(buf, "%config"))) {
	return 0;
    }
    *conf = RPMFILE_CONFIG;

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

    if (*p != '(') {
	while (start < p) {
	    *start++ = ' ';
	}
	return 0;
    }
    p++;

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

    if (! *end) {
	rpmError(RPMERR_BADSPEC, "Bad %%config() syntax: %s", buf);
	return RPMERR_BADSPEC;
    }

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

    p = strtok(ourbuf, ", \n\t");
    while (p) {
	if (!strcmp(p, "missingok")) {
	    *conf |= RPMFILE_MISSINGOK;
	} else if (!strcmp(p, "noreplace")) {
	    *conf |= RPMFILE_NOREPLACE;
	} else {
	    rpmError(RPMERR_BADSPEC, "Invalid %%config token: %s", p);
	    return RPMERR_BADSPEC;
	}
	p = strtok(NULL, ", \n\t");
    }

    return 0;
}
