/** \ingroup rpmcli
 * \file lib/query.c
 * Display tag values from package metadata.
 */

#include "system.h"

#ifndef PATH_MAX
/*@-incondefs@*/	/* FIX: long int? */
# define PATH_MAX 255
/*@=incondefs@*/
#endif

#include <rpmio.h>
#include <poptIO.h>

#include <rpmtag.h>
#include "rpmdb.h"

#include "rpmfi.h"
#define	_RPMTS_INTERNAL		/* XXX for ts->rdb */
#include "rpmts.h"
#include "rpmgi.h"

#include "manifest.h"
#include "misc.h"	/* XXX for currentDirectory() */

#include <rpmcli.h>

#include "debug.h"

/*@access rpmts @*/	/* XXX cast */

/**
 */
static void printFileInfo(char * te, const char * name,
			  size_t size, unsigned short mode,
			  unsigned int mtime,
			  unsigned short rdev, unsigned int nlink,
			  const char * owner, const char * group,
			  const char * linkto)
	/*@modifies *te @*/
{
    char sizefield[15];
#if defined(RPM_VENDOR_OPENPKG) /* adjust-verbose-listing */
    /* In verbose file listing output, give the owner and group fields
       more width and at the same time reduce the nlink and size fields
       more to typical sizes within OpenPKG. */
    char ownerfield[13+1], groupfield[13+1];
#else
    char ownerfield[8+1], groupfield[8+1];
#endif
    char timefield[100];
    time_t when = mtime;  /* important if sizeof(uint32_t) ! sizeof(time_t) */
    struct tm * tm;
    static time_t now;
    static struct tm nowtm;
    const char * namefield = name;
    char * perms = rpmPermsString(mode);

    /* On first call, grab snapshot of now */
    if (now == 0) {
	now = time(NULL);
	tm = localtime(&now);
	if (tm) nowtm = *tm;	/* structure assignment */
    }

    strncpy(ownerfield, owner, sizeof(ownerfield));
    ownerfield[sizeof(ownerfield)-1] = '\0';

    strncpy(groupfield, group, sizeof(groupfield));
    groupfield[sizeof(groupfield)-1] = '\0';

    /* this is normally right */
#if defined(RPM_VENDOR_OPENPKG) /* adjust-verbose-listing */
    /* In verbose file listing output, give the owner and group fields
       more width and at the same time reduce the nlink and size fields
       more to typical sizes within OpenPKG. */
    sprintf(sizefield, "%8u", (unsigned)size);
#else
    sprintf(sizefield, "%12u", (unsigned)size);
#endif

    /* this knows too much about dev_t */

    if (S_ISLNK(mode)) {
	char *nf = alloca(strlen(name) + sizeof(" -> ") + strlen(linkto));
	sprintf(nf, "%s -> %s", name, linkto);
	namefield = nf;
    } else if (S_ISCHR(mode)) {
	perms[0] = 'c';
	sprintf(sizefield, "%3u, %3u", ((unsigned)(rdev >> 8) & 0xff),
			((unsigned)rdev & 0xff));
    } else if (S_ISBLK(mode)) {
	perms[0] = 'b';
	sprintf(sizefield, "%3u, %3u", ((unsigned)(rdev >> 8) & 0xff),
			((unsigned)rdev & 0xff));
    }

    /* Convert file mtime to display format */
    tm = localtime(&when);
    timefield[0] = '\0';
    if (tm != NULL)
    {	const char *fmt;
	if (now > when + 6L * 30L * 24L * 60L * 60L ||	/* Old. */
	    now < when - 60L * 60L)			/* In the future.  */
	{
	/* The file is fairly old or in the future.
	 * POSIX says the cutoff is 6 months old;
	 * approximate this by 6*30 days.
	 * Allow a 1 hour slop factor for what is considered "the future",
	 * to allow for NFS server/client clock disagreement.
	 * Show the year instead of the time of day.
	 */        
	    fmt = "%b %e  %Y";
	} else {
	    fmt = "%b %e %H:%M";
	}
	(void)strftime(timefield, sizeof(timefield) - 1, fmt, tm);
    }

#if defined(RPM_VENDOR_OPENPKG) /* adjust-verbose-listing */
    /* In verbose file listing output, give the owner and group fields
       more width and at the same time reduce the nlink and size fields
       more to typical sizes within OpenPKG. */
    sprintf(te, "%s %d %-13s %-13s %8s %s %s", perms,
#else
    sprintf(te, "%s %4d %-7s %-8s %10s %s %s", perms,
#endif
	(int)nlink, ownerfield, groupfield, sizefield, timefield, namefield);
    perms = _free(perms);
}

/**
 */
static inline /*@null@*/ const char * queryHeader(Header h, const char * qfmt)
	/*@globals internalState @*/
	/*@modifies h, internalState @*/
{
    const char * errstr = "(unkown error)";
    const char * str;

/*@-modobserver@*/
    str = headerSprintf(h, qfmt, NULL, rpmHeaderFormats, &errstr);
/*@=modobserver@*/
    if (str == NULL)
	rpmlog(RPMLOG_ERR, _("incorrect format: %s\n"), errstr);
    return str;
}

/**
 */
static void flushBuffer(char ** tp, char ** tep, int nonewline)
	/*@modifies *tp, **tp, *tep, **tep @*/
{
    char *t, *te;

    t = *tp;
    te = *tep;
    if (te > t) {
	if (!nonewline) {
	    *te++ = '\n';
	    *te = '\0';
	}
	rpmlog(RPMLOG_NOTICE, "%s", t);
	te = t;
	*t = '\0';
    }
    *tp = t;
    *tep = te;
}

int showQueryPackage(QVA_t qva, rpmts ts, Header h)
{
    int scareMem = 0;
    rpmfi fi = NULL;
    size_t tb = 2 * BUFSIZ;
    size_t sb;
    char * t, * te;
    char * prefix = NULL;
    int rc = 0;		/* XXX FIXME: need real return code */
    int i;

    te = t = xmalloc(tb);
    *te = '\0';

    if (qva->qva_queryFormat != NULL) {
	const char * str;

#ifdef	NOTYET	/* XXX necessary for --whatneeds/--needswhat */
/*@-type@*/	/* FIX rpmtsGetRDB()? */
	(void) headerSetRpmdb(h, ts->rdb);
/*@=type@*/
#endif

	str = queryHeader(h, qva->qva_queryFormat);

#ifdef	NOTYET	/* XXX necessary for --whatneeds/--needswhat */
	(void) headerSetRpmdb(h, NULL);
#endif

	if (str) {
	    size_t tx = (te - t);

	    sb = strlen(str);
	    if (sb) {
		tb += sb;
		t = xrealloc(t, tb);
		te = t + tx;
	    }
	    /*@-usereleased@*/
	    te = stpcpy(te, str);
	    /*@=usereleased@*/
	    str = _free(str);
	    flushBuffer(&t, &te, 1);
	}
    }

    if (!(qva->qva_flags & QUERY_FOR_LIST))
	goto exit;

    fi = rpmfiNew(ts, h, RPMTAG_BASENAMES, scareMem);
    if (rpmfiFC(fi) <= 0) {
	te = stpcpy(te, _("(contains no files)"));
	goto exit;
    }

    fi = rpmfiInit(fi, 0);
    if (fi != NULL)
    while ((i = rpmfiNext(fi)) >= 0) {
	rpmfileAttrs fflags;
	unsigned short fmode;
 	unsigned short frdev;
	unsigned int fmtime;
	rpmfileState fstate;
	size_t fsize;
	const char * fn;
	const char * fdigest;
	const char * fuser;
	const char * fgroup;
	const char * flink;
	uint32_t fnlink;

	fflags = rpmfiFFlags(fi);
	fmode = rpmfiFMode(fi);
	frdev = rpmfiFRdev(fi);
	fmtime = rpmfiFMtime(fi);
	fstate = rpmfiFState(fi);
	fsize = rpmfiFSize(fi);
	fn = rpmfiFN(fi);
	{   static char hex[] = "0123456789abcdef";
	    int dalgo = 0;
	    size_t dlen = 0;
	    const unsigned char * digest = rpmfiDigest(fi, &dalgo, &dlen);
	    char * p;
	    size_t j;
	    fdigest = p = xcalloc(1, ((2 * dlen) + 1));
	    for (j = 0; j < dlen; j++) {
		unsigned k = *digest++;
		*p++ = hex[ (k >> 4) & 0xf ];
		*p++ = hex[ (k     ) & 0xf ];
	    }
	    *p = '\0';
	}
	fuser = rpmfiFUser(fi);
	fgroup = rpmfiFGroup(fi);
	flink = rpmfiFLink(fi);
	fnlink = rpmfiFNlink(fi);
assert(fn != NULL);
assert(fdigest != NULL);

	/* If querying only docs, skip non-doc files. */
	if ((qva->qva_flags & QUERY_FOR_DOCS) && !(fflags & RPMFILE_DOC))
	    continue;

	/* If querying only configs, skip non-config files. */
	if ((qva->qva_flags & QUERY_FOR_CONFIG) && !(fflags & RPMFILE_CONFIG))
	    continue;

	/* If not querying %config, skip config files. */
	if ((qva->qva_fflags & RPMFILE_CONFIG) && (fflags & RPMFILE_CONFIG))
	    continue;

	/* If not querying %doc, skip doc files. */
	if ((qva->qva_fflags & RPMFILE_DOC) && (fflags & RPMFILE_DOC))
	    continue;

	/* If not querying %ghost, skip ghost files. */
	if ((qva->qva_fflags & RPMFILE_GHOST) && (fflags & RPMFILE_GHOST))
	    continue;

	/* Insure space for header derived data */
	sb = 0;
	if (fn)		sb += strlen(fn);
	if (fdigest)	sb += strlen(fdigest);
	if (fuser)	sb += strlen(fuser);
	if (fgroup)	sb += strlen(fgroup);
	if (flink)	sb += strlen(flink);
	if ((sb + BUFSIZ) > tb) {
	    size_t tx = (te - t);
	    tb += sb + BUFSIZ;
	    t = xrealloc(t, tb);
	    te = t + tx;
	}

	if (!rpmIsVerbose() && prefix)
	    te = stpcpy(te, prefix);

	if (qva->qva_flags & QUERY_FOR_STATE) {
	    switch (fstate) {
	    case RPMFILE_STATE_NORMAL:
		te = stpcpy(te, _("normal        "));
		/*@switchbreak@*/ break;
	    case RPMFILE_STATE_REPLACED:
		te = stpcpy(te, _("replaced      "));
		/*@switchbreak@*/ break;
	    case RPMFILE_STATE_NOTINSTALLED:
		te = stpcpy(te, _("not installed "));
		/*@switchbreak@*/ break;
	    case RPMFILE_STATE_NETSHARED:
		te = stpcpy(te, _("net shared    "));
		/*@switchbreak@*/ break;
	    case RPMFILE_STATE_WRONGCOLOR:
		te = stpcpy(te, _("wrong color   "));
		/*@switchbreak@*/ break;
	    case RPMFILE_STATE_MISSING:
		te = stpcpy(te, _("(no state)    "));
		/*@switchbreak@*/ break;
	    default:
		sprintf(te, _("(unknown %3d) "), fstate);
		te += strlen(te);
		/*@switchbreak@*/ break;
	    }
	}

	if (qva->qva_flags & QUERY_FOR_DUMPFILES) {
	    sprintf(te, "%s %d %d %s 0%o ",
				fn, (int)fsize, fmtime, fdigest, fmode);
	    te += strlen(te);

	    if (fuser && fgroup) {
/*@-nullpass@*/
		sprintf(te, "%s %s", fuser, fgroup);
/*@=nullpass@*/
		te += strlen(te);
	    } else {
		rpmlog(RPMLOG_CRIT, _("package without owner/group tags\n"));
	    }

	    sprintf(te, " %s %s %u ", 
				 fflags & RPMFILE_CONFIG ? "1" : "0",
				 fflags & RPMFILE_DOC ? "1" : "0",
				 frdev);
	    te += strlen(te);

	    sprintf(te, "%s", (flink && *flink ? flink : "X"));
	    te += strlen(te);
	} else
	if (!rpmIsVerbose()) {
	    te = stpcpy(te, fn);
	}
	else {

	    /* XXX Adjust directory link count and size for display output. */
	    if (S_ISDIR(fmode)) {
		fnlink++;
		fsize = 0;
	    }

	    if (fuser && fgroup) {
/*@-nullpass@*/
		printFileInfo(te, fn, fsize, fmode, fmtime, frdev, fnlink,
					fuser, fgroup, flink);
/*@=nullpass@*/
		te += strlen(te);
	    } else {
		rpmlog(RPMLOG_CRIT, _("package without owner/group tags\n"));
	    }
	}
	flushBuffer(&t, &te, 0);
	fdigest = _free(fdigest);
    }
	    
    rc = 0;

exit:
    flushBuffer(&t, &te, 0);
    t = _free(t);

    fi = rpmfiFree(fi);
    return rc;
}

static int rpmgiShowMatches(QVA_t qva, rpmts ts)
	/*@globals rpmGlobalMacroContext, h_errno, internalState @*/
        /*@modifies qva, rpmGlobalMacroContext, h_errno, internalState @*/
{
    rpmgi gi = qva->qva_gi;
    rpmRC rpmrc = RPMRC_NOTFOUND;
    int ec = 0;

    while ((rpmrc = rpmgiNext(gi)) == RPMRC_OK) {
	Header h;
	int rc;

#ifdef	NOTYET	/* XXX exiting here will leave stale locks. */
	(void) rpmdbCheckSignals();
#endif

	h = rpmgiHeader(gi);
	if (h == NULL)		/* XXX perhaps stricter break instead? */
	    continue;
	if ((rc = qva->qva_showPackage(qva, ts, h)) != 0)
	    ec = rc;
	if (qva->qva_source == RPMQV_DBOFFSET)
	    break;
    }
    if (ec == 0 && rpmrc == RPMRC_FAIL)
	ec++;
    return ec;
}

int rpmcliShowMatches(QVA_t qva, rpmts ts)
{
    Header h;
    int ec = 1;

    qva->qva_showFAIL = qva->qva_showOK = 0;
    while ((h = rpmdbNextIterator(qva->qva_mi)) != NULL) {
	ec = qva->qva_showPackage(qva, ts, h);
	if (ec)
	    qva->qva_showFAIL++;
	else
	    qva->qva_showOK++;
	if (qva->qva_source == RPMQV_DBOFFSET)
	    break;
    }
    qva->qva_mi = rpmdbFreeIterator(qva->qva_mi);
    return ec;
}

/**
 * Convert hex to binary nibble.
 * @param c            hex character
 * @return             binary nibble
 */
static inline unsigned char nibble(char c)
	/*@*/
{
    if (c >= '0' && c <= '9')
	return (c - '0');
    if (c >= 'A' && c <= 'F')
	return (c - 'A') + 10;
    if (c >= 'a' && c <= 'f')
	return (c - 'a') + 10;
    return 0;
}

int rpmQueryVerify(QVA_t qva, rpmts ts, const char * arg)
{
    int res = 0;
    const char * s;
    int i;
    int provides_checked = 0;

    (void) rpmdbCheckSignals();

    if (qva->qva_showPackage == NULL)
	return 1;

    switch (qva->qva_source) {
#ifdef	NOTYET
    default:
    case RPMQV_GROUP:
    case RPMQV_TRIGGEREDBY:
#endif
    case RPMQV_WHATCONFLICTS:
    case RPMQV_WHATOBSOLETES:
	qva->qva_mi = rpmtsInitIterator(ts, qva->qva_source, arg, 0);
	if (qva->qva_mi == NULL) {
	    rpmlog(RPMLOG_NOTICE, _("key \"%s\" not found in %s table\n"),
			arg, tagName((rpmTag)qva->qva_source));
	    res = 1;
	} else
	    res = rpmcliShowMatches(qva, ts);
	break;

    case RPMQV_RPM:
	res = rpmgiShowMatches(qva, ts);
	break;

    case RPMQV_ALL:
	res = rpmgiShowMatches(qva, ts);
	break;

    case RPMQV_HDLIST:
	res = rpmgiShowMatches(qva, ts);
	break;

    case RPMQV_FTSWALK:
	res = rpmgiShowMatches(qva, ts);
	break;

    case RPMQV_SPECSRPM:
    case RPMQV_SPECFILE:
	res = ((qva->qva_specQuery != NULL)
		? qva->qva_specQuery(ts, qva, arg) : 1);
	break;

    case RPMQV_GROUP:
	qva->qva_mi = rpmtsInitIterator(ts, RPMTAG_GROUP, arg, 0);
	if (qva->qva_mi == NULL) {
	    rpmlog(RPMLOG_ERR,
		_("group %s does not contain any packages\n"), arg);
	    res = 1;
	} else
	    res = rpmcliShowMatches(qva, ts);
	break;

    case RPMQV_TRIGGEREDBY:
	qva->qva_mi = rpmtsInitIterator(ts, RPMTAG_TRIGGERNAME, arg, 0);
	if (qva->qva_mi == NULL) {
	    rpmlog(RPMLOG_NOTICE, _("no package triggers %s\n"), arg);
	    res = 1;
	} else
	    res = rpmcliShowMatches(qva, ts);
	break;

    case RPMQV_SOURCEPKGID:
    case RPMQV_PKGID:
    {	unsigned char MD5[16];
	unsigned char * t;
	uint32_t tag;

	for (i = 0, s = arg; *s && isxdigit(*s); s++, i++)
	    {};
	if (i != 32) {
	    rpmlog(RPMLOG_NOTICE, _("malformed %s: %s\n"), "pkgid", arg);
	    return 1;
	}

	MD5[0] = '\0';
        for (i = 0, t = MD5, s = arg; i < 16; i++, t++, s += 2)
            *t = (nibble(s[0]) << 4) | nibble(s[1]);
	
	tag = (qva->qva_source == RPMQV_PKGID
		? RPMTAG_SOURCEPKGID : RPMTAG_PKGID);
	qva->qva_mi = rpmtsInitIterator(ts, tag, MD5, sizeof(MD5));
	if (qva->qva_mi == NULL) {
	    rpmlog(RPMLOG_NOTICE, _("no package matches %s: %s\n"),
			"pkgid", arg);
	    res = 1;
	} else
	    res = rpmcliShowMatches(qva, ts);
    }	break;

    case RPMQV_HDRID:
	for (i = 0, s = arg; *s && isxdigit(*s); s++, i++)
	    {};
	if (i != 40) {
	    rpmlog(RPMLOG_NOTICE, _("malformed %s: %s\n"), "hdrid", arg);
	    return 1;
	}

	qva->qva_mi = rpmtsInitIterator(ts, RPMTAG_SHA1HEADER, arg, 0);
	if (qva->qva_mi == NULL) {
	    rpmlog(RPMLOG_NOTICE, _("no package matches %s: %s\n"),
			"hdrid", arg);
	    res = 1;
	} else
	    res = rpmcliShowMatches(qva, ts);
	break;

    case RPMQV_FILEID:
    {	unsigned char * t;
	unsigned char * digest;
	size_t dlen;

	/* Insure even no. of digits and at least 8 digits. */
	for (dlen = 0, s = arg; *s && isxdigit(*s); s++, dlen++)
	    {};
	if ((dlen & 1) || dlen < 8) {
	    rpmlog(RPMLOG_ERR, _("malformed %s: %s\n"), "fileid", arg);
	    return 1;
	}

	dlen /= 2;
	digest = memset(alloca(dlen), 0, dlen);
        for (t = digest, s = arg; *s; t++, s += 2)
            *t = (nibble(s[0]) << 4) | nibble(s[1]);

	qva->qva_mi = rpmtsInitIterator(ts, RPMTAG_FILEDIGESTS, digest, dlen);
	if (qva->qva_mi == NULL) {
	    rpmlog(RPMLOG_NOTICE, _("no package matches %s: %s\n"),
			"fileid", arg);
	    res = 1;
	} else
	    res = rpmcliShowMatches(qva, ts);
    }	break;

    case RPMQV_TID:
    {	int mybase = 10;
	const char * myarg = arg;
	char * end = NULL;
	unsigned iid;

	/* XXX should be in strtoul */
	if (*myarg == '0') {
	    myarg++;
	    mybase = 8;
	    if (*myarg == 'x') {
		myarg++;
		mybase = 16;
	    }
	}
	iid = (unsigned) strtoul(myarg, &end, mybase);
	if ((*end) || (end == arg) || (iid == UINT_MAX)) {
	    rpmlog(RPMLOG_ERR, _("malformed %s: %s\n"), "tid", arg);
	    return 1;
	}
	qva->qva_mi = rpmtsInitIterator(ts, RPMTAG_INSTALLTID, &iid, sizeof(iid));
	if (qva->qva_mi == NULL) {
	    rpmlog(RPMLOG_NOTICE, _("no package matches %s: %s\n"),
			"tid", arg);
	    res = 1;
	} else
	    res = rpmcliShowMatches(qva, ts);
    }	break;

    case RPMQV_WHATNEEDS:
    case RPMQV_WHATREQUIRES:
	qva->qva_mi = rpmtsInitIterator(ts, RPMTAG_REQUIRENAME, arg, 0);
	if (qva->qva_mi == NULL) {
	    rpmlog(RPMLOG_NOTICE, _("no package requires %s\n"), arg);
	    res = 1;
	} else
	    res = rpmcliShowMatches(qva, ts);
	break;

    case RPMQV_WHATPROVIDES:
	if (arg[0] != '/') {
	    provides_checked = 1;
	    qva->qva_mi = rpmtsInitIterator(ts, RPMTAG_PROVIDENAME, arg, 0);
	    if (qva->qva_mi == NULL) {
		rpmlog(RPMLOG_NOTICE, _("no package provides %s\n"), arg);
		res = 1;
	    } else
		res = rpmcliShowMatches(qva, ts);
	    break;
	}
	/*@fallthrough@*/
    case RPMQV_PATH:
    {   char * fn;

	for (s = arg; *s != '\0'; s++)
	    if (!(*s == '.' || *s == '/'))
		/*@loopbreak@*/ break;

	if (*s == '\0') {
	    char fnbuf[PATH_MAX];
	    fn = realpath(arg, fnbuf);
	    fn = xstrdup( (fn != NULL ? fn : arg) );
	} else if (*arg != '/') {
	    const char *curDir = currentDirectory();
	    fn = (char *) rpmGetPath(curDir, "/", arg, NULL);
	    curDir = _free(curDir);
	} else
	    fn = xstrdup(arg);
assert(fn != NULL);
	(void) rpmCleanPath(fn);

	qva->qva_mi = rpmtsInitIterator(ts, RPMTAG_BASENAMES, fn, 0);
	if (qva->qva_mi == NULL && !provides_checked)
	    qva->qva_mi = rpmtsInitIterator(ts, RPMTAG_PROVIDENAME, fn, 0);

	if (qva->qva_mi == NULL) {
	    struct stat sb;
	    if (Lstat(fn, &sb) != 0)
		rpmlog(RPMLOG_NOTICE, _("file %s: %s\n"), fn, strerror(errno));
	    else
		rpmlog(RPMLOG_NOTICE,
			_("file %s is not owned by any package\n"), fn);
	    res = 1;
	} else
	    res = rpmcliShowMatches(qva, ts);

	fn = _free(fn);
    }	break;

    case RPMQV_DBOFFSET:
    {	int mybase = 10;
	const char * myarg = arg;
	char * end = NULL;
	unsigned recOffset;

	/* XXX should be in strtoul */
	if (*myarg == '0') {
	    myarg++;
	    mybase = 8;
	    if (*myarg == 'x') {
		myarg++;
		mybase = 16;
	    }
	}
	recOffset = (unsigned) strtoul(myarg, &end, mybase);
	if ((*end) || (end == arg) || (recOffset == UINT_MAX)) {
	    rpmlog(RPMLOG_NOTICE, _("invalid package number: %s\n"), arg);
	    return 1;
	}
	rpmlog(RPMLOG_DEBUG, D_("package record number: %u\n"), recOffset);
	qva->qva_mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, &recOffset, sizeof(recOffset));
	if (qva->qva_mi == NULL) {
	    rpmlog(RPMLOG_NOTICE,
		_("record %u could not be read\n"), recOffset);
	    res = 1;
	} else
	    res = rpmcliShowMatches(qva, ts);
    }	break;

    case RPMQV_PACKAGE:
	/* XXX HACK to get rpmdbFindByLabel out of the API */
	qva->qva_mi = rpmtsInitIterator(ts, RPMDBI_LABEL, arg, 0);
	if (qva->qva_mi == NULL) {
	    rpmlog(RPMLOG_NOTICE, _("package %s is not installed\n"), arg);
	    res = 1;
	} else {
	    res = rpmcliShowMatches(qva, ts);
	    /* detect foo.bogusarch empty iterations. */
	    if (qva->qva_showOK == 0 && qva->qva_showFAIL == 0) {
		rpmlog(RPMLOG_NOTICE, _("package %s is not installed\n"), arg);
		res = 1;
	    }
	}
	break;
    }
   
    return res;
}

int rpmcliArgIter(rpmts ts, QVA_t qva, ARGV_t argv)
	/*@globals rpmioFtsOpts @*/
	/*@modifies rpmioFtsOpts @*/
{
    rpmRC rpmrc = RPMRC_NOTFOUND;
    int ec = 0;

    switch (qva->qva_source) {
    case RPMQV_ALL:
	qva->qva_gi = rpmgiNew(ts, RPMDBI_PACKAGES, NULL, 0);
	qva->qva_rc = rpmgiSetArgs(qva->qva_gi, argv, rpmioFtsOpts, RPMGI_NONE);

	if (rpmgiGetFlags(qva->qva_gi) & RPMGI_TSADD)	/* Load the ts with headers. */
	while ((rpmrc = rpmgiNext(qva->qva_gi)) == RPMRC_OK)
	    {};
	if (rpmrc != RPMRC_NOTFOUND)
	    return 1;	/* XXX should be no. of failures. */
	
	/*@-nullpass@*/ /* FIX: argv can be NULL, cast to pass argv array */
	ec = rpmQueryVerify(qva, ts, (const char *) argv);
	/*@=nullpass@*/
	rpmtsEmpty(ts);
	break;
    case RPMQV_RPM:
	qva->qva_gi = rpmgiNew(ts, RPMDBI_ARGLIST, NULL, 0);
	qva->qva_rc = rpmgiSetArgs(qva->qva_gi, argv, rpmioFtsOpts, giFlags);

	if (rpmgiGetFlags(qva->qva_gi) & RPMGI_TSADD)	/* Load the ts with headers. */
	while ((rpmrc = rpmgiNext(qva->qva_gi)) == RPMRC_OK)
	    {};
	if (rpmrc != RPMRC_NOTFOUND)
	    return 1;	/* XXX should be no. of failures. */
	
	/*@-nullpass@*/ /* FIX: argv can be NULL, cast to pass argv array */
	ec = rpmQueryVerify(qva, ts, NULL);
	/*@=nullpass@*/
	rpmtsEmpty(ts);
	break;
    case RPMQV_HDLIST:
	qva->qva_gi = rpmgiNew(ts, RPMDBI_HDLIST, NULL, 0);
	qva->qva_rc = rpmgiSetArgs(qva->qva_gi, argv, rpmioFtsOpts, giFlags);

	if (rpmgiGetFlags(qva->qva_gi) & RPMGI_TSADD)	/* Load the ts with headers. */
	while ((rpmrc = rpmgiNext(qva->qva_gi)) == RPMRC_OK)
	    {};
	if (rpmrc != RPMRC_NOTFOUND)
	    return 1;	/* XXX should be no. of failures. */
	
	/*@-nullpass@*/ /* FIX: argv can be NULL, cast to pass argv array */
	ec = rpmQueryVerify(qva, ts, NULL);
	/*@=nullpass@*/
	rpmtsEmpty(ts);
	break;
    case RPMQV_FTSWALK:
	if (rpmioFtsOpts == 0)
	    rpmioFtsOpts = (FTS_COMFOLLOW | FTS_LOGICAL | FTS_NOSTAT);
	qva->qva_gi = rpmgiNew(ts, RPMDBI_FTSWALK, NULL, 0);
	qva->qva_rc = rpmgiSetArgs(qva->qva_gi, argv, rpmioFtsOpts, giFlags);

	if (rpmgiGetFlags(qva->qva_gi) & RPMGI_TSADD)	/* Load the ts with headers. */
	while ((rpmrc = rpmgiNext(qva->qva_gi)) == RPMRC_OK)
	    {};
	if (rpmrc != RPMRC_NOTFOUND)
	    return 1;	/* XXX should be no. of failures. */
	
	/*@-nullpass@*/ /* FIX: argv can be NULL, cast to pass argv array */
	ec = rpmQueryVerify(qva, ts, NULL);
	/*@=nullpass@*/
	rpmtsEmpty(ts);
	break;
    default:
      if (giFlags & RPMGI_TSADD) {
	qva->qva_gi = rpmgiNew(ts, RPMDBI_LABEL, NULL, 0);
	qva->qva_rc = rpmgiSetArgs(qva->qva_gi, argv, rpmioFtsOpts,
		(giFlags | (RPMGI_NOGLOB               )));
	if (rpmgiGetFlags(qva->qva_gi) & RPMGI_TSADD)	/* Load the ts with headers. */
	while ((rpmrc = rpmgiNext(qva->qva_gi)) == RPMRC_OK)
	    {};
	if (rpmrc != RPMRC_NOTFOUND)
	    return 1;	/* XXX should be no. of failures. */
	qva->qva_source = RPMQV_ALL;
	/*@-nullpass@*/ /* FIX: argv can be NULL, cast to pass argv array */
	ec = rpmQueryVerify(qva, ts, NULL);
	/*@=nullpass@*/
	rpmtsEmpty(ts);
      } else {
	qva->qva_gi = rpmgiNew(ts, RPMDBI_ARGLIST, NULL, 0);
	qva->qva_rc = rpmgiSetArgs(qva->qva_gi, argv, rpmioFtsOpts,
		(giFlags | (RPMGI_NOGLOB|RPMGI_NOHEADER)));
	while ((rpmrc = rpmgiNext(qva->qva_gi)) == RPMRC_OK) {
	    const char * path;
	    path = rpmgiHdrPath(qva->qva_gi);
assert(path != NULL);
	    ec += rpmQueryVerify(qva, ts, path);
	    rpmtsEmpty(ts);
	}
      }
	break;
    }

    qva->qva_gi = rpmgiFree(qva->qva_gi);

    return ec;
}

int rpmcliQuery(rpmts ts, QVA_t qva, const char ** argv)
{
    rpmdepFlags depFlags = qva->depFlags, odepFlags;
    rpmtransFlags transFlags = qva->transFlags, otransFlags;
    rpmVSFlags vsflags, ovsflags;
    int ec = 0;

    if (qva->qva_showPackage == NULL)
	qva->qva_showPackage = showQueryPackage;

    /* If --queryformat unspecified, then set default now. */
    if (!(qva->qva_flags & _QUERY_FOR_BITS) && qva->qva_queryFormat == NULL) {
	qva->qva_queryFormat = rpmExpand("%{?_query_all_fmt}\n", NULL);
	if (!(qva->qva_queryFormat != NULL && *qva->qva_queryFormat != '\0')) {
	    qva->qva_queryFormat = _free(qva->qva_queryFormat);
	    qva->qva_queryFormat = xstrdup("%{name}-%{version}-%{release}.%{arch}\n");
	}
    }

    vsflags = rpmExpandNumeric("%{?_vsflags_query}");
    if (qva->qva_flags & VERIFY_DIGEST)
	vsflags |= _RPMVSF_NODIGESTS;
    if (qva->qva_flags & VERIFY_SIGNATURE)
	vsflags |= _RPMVSF_NOSIGNATURES;
    if (qva->qva_flags & VERIFY_HDRCHK)
	vsflags |= RPMVSF_NOHDRCHK;

    odepFlags = rpmtsSetDFlags(ts, depFlags);
    otransFlags = rpmtsSetFlags(ts, transFlags);
    ovsflags = rpmtsSetVSFlags(ts, vsflags);
    ec = rpmcliArgIter(ts, qva, argv);
    vsflags = rpmtsSetVSFlags(ts, ovsflags);
    transFlags = rpmtsSetFlags(ts, otransFlags);
    depFlags = rpmtsSetDFlags(ts, odepFlags);

    if (qva->qva_showPackage == showQueryPackage)
	qva->qva_showPackage = NULL;

    return ec;
}
