#include "../config.h"

#ifdef HAVE_RPM

#include <fcntl.h>
#include <iostream.h>

#include "rpmMessages.h"

/* stupid RPM headers! */
typedef __off64_t off64_t;
#include <rpm/rpmlib.h>
#include "rpmInstall.h"
#include "rpmVerify.h"
#include "rpmutils.h"
#include <qlist.h>
#include <klocale.h>
 

#include "kpackage.h"

#define EMAX 30

static int verifyHeader(const char * prefix, Header h, int verifyFlags,
                        QStringList &list);

static int verifyDependencies(rpmdb db, Header h,
			      QStringList &list);

static int verifyHeader(const char * prefix, Header h, int verifyFlags,
                        QStringList &list)
{
    int type; // need with some versions of RPM
    const char ** fileList;
    int count;
#if RPM_V1 < 4 || (RPM_V1 == 4 && RPM_V2 == 0 && RPM_V3 < 3)
    int verifyResult;
#else
    rpmVerifyAttrs verifyResult;
#endif
    int i, ec, rc;
    int_32 * fileFlagsList;
#if RPM_V1 < 4 || (RPM_V1 == 4 && RPM_V2 == 0 && RPM_V3 < 3)
    int omitMask = 0;
#else
    rpmVerifyAttrs omitMask = RPMVERIFY_NONE;
#endif
    int enumber = 0;
    QString buff, errors;
 
    ec = 0;
    fileList = 0;
    fileFlagsList = 0;
    if (!(verifyFlags & VERIFY_MD5)) omitMask = RPMVERIFY_MD5;


#if RPM_V1 >= 4 || (RPM_V1 == 3 && RPM_V3 >= 4)
// Code for RPM 3.0.4 or greater
#if RPM_V1 == 3  && RPM_V3 >= 4
    // hopefully will compile with all version of rpm-3.0.4
    rpmBuildFileList(h, &fileList, &count);
    if (headerGetEntry(h, RPMTAG_FILEFLAGS, NULL, (void **) &fileFlagsList, NULL)
	&& count)
#else
    if (headerGetEntry(h, RPMTAG_FILEFLAGS, NULL, (void **) &fileFlagsList, NULL)
	&& rpmHeaderGetEntry(h, RPMTAG_OLDFILENAMES, &type, (void **) &fileList, &count))
#endif

#else
    if (headerGetEntry(h, RPMTAG_FILEFLAGS, NULL, (void **) &fileFlagsList, NULL)
	&& headerGetEntry(h, RPMTAG_FILENAMES, &type, (void **) &fileList, &count))
#endif

      {

        int oldpercentage=0;
	for (i = 0; i < count; i++) {
            int percentage = (int)(count
                                ? ((float) ((((float) i) / count) * 100))
                                : 100.0);
            if(percentage-oldpercentage > 5) {
               kpackage->setPercent(percentage);
               oldpercentage = percentage;
            }
	    if ((rc = rpmVerifyFile(prefix, h, i, &verifyResult, omitMask)) != 0) {
		cout << i18n("missing    ").local8Bit() << fileList[i] << endl;
                list.append(fileList[i]);
	    } else {
		const char * size, * md5, * link, * mtime, * mode;
		const char * group, * user, * rdev;
		static const char * aok = ".";
		static const char * unknown = "?";

		if (!verifyResult) continue;
	    
		rc = 1;

#define	_verify(_RPMVERIFY_F, _C)	\
	((verifyResult & _RPMVERIFY_F) ? _C : aok)
#define	_verifylink(_RPMVERIFY_F, _C)	\
	((verifyResult & RPMVERIFY_READLINKFAIL) ? unknown : \
	 (verifyResult & _RPMVERIFY_F) ? _C : aok)
#define	_verifyfile(_RPMVERIFY_F, _C)	\
	((verifyResult & RPMVERIFY_READFAIL) ? unknown : \
	 (verifyResult & _RPMVERIFY_F) ? _C : aok)
	
		md5 = _verifyfile(RPMVERIFY_MD5, "5");
		size = _verify(RPMVERIFY_FILESIZE, "S");
		link = _verifylink(RPMVERIFY_LINKTO, "L");
		mtime = _verify(RPMVERIFY_MTIME, "T");
		rdev = _verify(RPMVERIFY_RDEV, "D");
		user = _verify(RPMVERIFY_USER, "U");
		group = _verify(RPMVERIFY_GROUP, "G");
		mode = _verify(RPMVERIFY_MODE, "M");

#undef _verify
#undef _verifylink
#undef _verifyfile

               buff.sprintf("%s%s%s%s%s%s%s%s %c %s\n",
		       size, mode, md5, rdev, link, user, group, mtime, 
		       fileFlagsList[i] & RPMFILE_CONFIG ? 'c' : ' ', 
		       fileList[i]);
               cout << buff.local8Bit();
               if (++enumber <= EMAX)
                  errors += buff;
 	    }
	    if (rc)
		ec = rc;
	}
        free(fileList);
      }

    if (!errors.isEmpty()) {
      KpMsg(i18n("File attribute differences"),errors,FALSE); //remove 
      if (enumber >= EMAX)
       errors += "...\n";
    }
    return ec;
}

static int verifyDependencies(rpmdb db, Header h, QStringList &list) {
    rpmTransactionSet rpmdep;
#if RPM_V1 < 4 || (RPM_V1 == 4 && RPM_V2 == 0 && RPM_V3 < 3)
    struct rpmDependencyConflict * conflicts;
#else
    rpmDependencyConflict conflicts;
#endif
    int numConflicts;
    const char * name, * version, * release;
    int type, count, i;
    QString ctmp;
 
    rpmdep = rpmtransCreateSet(db, NULL);
    rpmtransAddPackage(rpmdep, h, NULL, NULL, 0, NULL);

    rpmdepCheck(rpmdep, &conflicts, &numConflicts);
    rpmtransFree(rpmdep);

    if (numConflicts) {
	headerGetEntry(h, RPMTAG_NAME, &type, (void **) &name, &count);
	headerGetEntry(h, RPMTAG_VERSION, &type, (void **) &version, &count);
	headerGetEntry(h, RPMTAG_RELEASE, &type, (void **) &release, &count);
        kdDebug() <<  i18n("Unsatisfied dependencies for "
                    "%1-%2-%3:").arg(name).arg(version).arg(release) << endl;
        for (i = 0; i < numConflicts; i++)
         {
           ctmp =  conflicts[i].needsName;
           if (conflicts[i].needsFlags)
             {
               ctmp += " ";
               if (conflicts[i].needsFlags & RPMSENSE_LESS)
                 ctmp += "<";
               if (conflicts[i].needsFlags & RPMSENSE_GREATER)
                 ctmp += ">";
               if (conflicts[i].needsFlags & RPMSENSE_EQUAL)
                 ctmp += "=";
               if (conflicts[i].needsFlags & RPMSENSE_SERIAL)
                 ctmp += "S";
  
               ctmp += " ";
               ctmp +=  conflicts[i].needsVersion;
             }
           list.append(ctmp);
           kdDebug() <<  ctmp << endl;
        }
	rpmdepFreeConflicts(conflicts, numConflicts);
	return 1;
    }
    return 0;
}

static int verifyPackage(const char * root, rpmdb db, Header h, int 
verifyFlags,
                     QStringList &list)
{
    int ec, rc;
    FD_t fdo;
    ec = 0;
    if ((verifyFlags & VERIFY_DEPS) &&
	(rc = verifyDependencies(db, h, list)) != 0)
	    ec = rc;
    if (verifyFlags & VERIFY_FILES) {
         list.clear();
         if ((rc = verifyHeader(root, h, verifyFlags, list)) != 0)
	    ec = rc;
    }
    fdo = fdDup(STDOUT_FILENO);
    if ((verifyFlags & VERIFY_SCRIPT) &&
	(rc = rpmVerifyScript(root, h, fdo)) != 0)
	    ec = rc;
// Code for RPM 3.0.4 or greater
#if ( RPM_V1 == 3 && RPM_V2 == 0 && RPM_V3 > 4 ) || RPM_V1 >= 4
    Fclose(fdo);
#else
    fdClose(fdo);
#endif
    return ec;
}

#if RPM_V1 < 4
static int verifyMatches(const char * prefix, rpmdb db, dbiIndexSet matches,
                         int verifyFlags, QStringList &list);
static int verifyMatches(const char * prefix, rpmdb db, dbiIndexSet matches,
			  int verifyFlags, QStringList &list) {
    int ec, rc;
    unsigned int i;
    Header h;

    ec = 0;
    for (i = 0; i < dbiIndexSetCount(matches); i++)  {
	unsigned int recOffset = dbiIndexRecordOffset(matches, i);
	if (recOffset == 0)
	    continue;
	kdDebug() << i18n("verifying record number %1\n").arg(recOffset) << endl;
	    
	h = rpmdbGetRecord(db, recOffset);
	if (h == NULL) {
		KpMsgE(i18n("error: could not read database record\n"), TRUE);
		ec = 1;
	} else {
		if ((rc = verifyPackage(prefix, db, h, verifyFlags, list)) != 0)
		    ec = rc;
		headerFree(h);
	}
    }
    return ec;
}
#else
static int verifyMatches(const char * prefix, rpmdb db, rpmdbMatchIterator mi,
                         int verifyFlags, QStringList &list);
static int verifyMatches(const char * prefix, rpmdb db, rpmdbMatchIterator mi,
                         int verifyFlags, QStringList &list) {
    int ec, rc;
    Header h;

    ec = 0;
    while ((h = rpmdbNextIterator(mi)) != NULL) {
	unsigned int recOffset = rpmdbGetIteratorOffset(mi);
	kdDebug() << i18n("verifying record number %1\n").arg(recOffset) << endl;
	    
	if ((rc = verifyPackage(prefix, db, h, verifyFlags, list)) != 0)
	    ec = rc;
    }
    return ec;
}
#endif

int doVerify(const char * prefix, enum verifysources source, const QStringList 
&argv,
	      int verifyFlags, QStringList &result) {
#if RPM_V1 >= 4
    rpmdbMatchIterator mi = NULL;
#endif
    Header h;
    int ec, rc;
    int isSource;
    rpmdb db = NULL;
#if RPM_V1 < 4
    int offset;
    dbiIndexSet matches;
#endif

    ec = 0;
    if (source == VERIFY_RPM && !(verifyFlags & VERIFY_DEPS)) {
	db = NULL;
    } else {
	if (rpmdbOpen(const_cast<char*>(prefix), &db, O_RDONLY, 0644)) {
	    return 1;	/* XXX was exit(EXIT_FAILURE) */
	}
    }

    if (source == VERIFY_EVERY) {
#if RPM_V1 < 4
	for (offset = rpmdbFirstRecNum(db);
	     offset != 0;
	     offset = rpmdbNextRecNum(db, offset)) {
		h = rpmdbGetRecord(db, offset);
		if (h == NULL) {
		    KpMsgE(i18n("could not read database record!\n"), TRUE);
		    return 1;	/* XXX was exit(EXIT_FAILURE) */
		}
		
		if ((rc = verifyPackage(prefix, db, h, verifyFlags, result)) != 0)
		    ec = rc;
		headerFree(h);
	}
#else
	mi = rpmdbInitIterator(db, RPMDBI_PACKAGES, NULL, 0);
	while ((h = rpmdbNextIterator(mi)) != NULL) {
	    if ((rc = verifyPackage(prefix, db, h, verifyFlags, result)) != 0)
		ec = rc;
	}
	rpmdbFreeIterator(mi);
#endif
    } else {
        for ( QStringList::ConstIterator it = argv.begin();
              ( it != argv.end());
              it++)
        {
	    const char *arg = (*it).ascii();

	    rc = 0;
	    switch (source) {
	    case VERIFY_RPM:
	      { FD_t fd;

// Code for RPM 3.0.4 or greater
#if ( RPM_V1 == 3 && RPM_V2 == 0 && RPM_V3 > 4 ) || RPM_V1 >= 4
		fd = Fopen(arg, "r.ufdio");
		if (fd == NULL || Ferror(fd)) {
		    KpMsgE(i18n("open of %1 failed\n").arg(*it), TRUE);
		    if (fd)
			Fclose(fd);
		    break;
		}
		rc = rpmReadPackageHeader(fd, &h, &isSource, NULL, NULL);
		Fclose(fd);
#else
		fd = fdOpen(arg, O_RDONLY, 0);
		if (fd == NULL) {
		    KpMsgE(i18n("open of %1 failed\n").arg(*it), TRUE);
		    break;
		}

		if (fdFileno(fd) >= 0)
		    rc = rpmReadPackageHeader(fd, &h, &isSource, NULL, NULL);

		fdClose(fd);
#endif

		switch (rc) {
		case 0:
			rc = verifyPackage(prefix, db, h, verifyFlags, result);
			headerFree(h);
			break;
		case 1:
			KpMsgE(i18n("%1 is not an RPM\n").arg(*it), FALSE);
			break;
		}
	      }	break;

	    case VERIFY_GRP:
#if RPM_V1 < 4
		if (rpmdbFindByGroup(db, const_cast<char*>(arg), &matches)) {
		    KpMsgE(i18n("group %1 does not contain any packages\n").arg(*it), FALSE);
		} else {
		    rc = verifyMatches(prefix, db, matches, verifyFlags, result);
		    dbiFreeIndexRecord(matches);
		}
#else
		mi = rpmdbInitIterator(db, RPMTAG_GROUP, arg, 0);
		if (mi == NULL)
		    KpMsgE(i18n("group %1 does not contain any packages\n").arg(*it), FALSE);
		else
		    rc = verifyMatches(prefix, db, mi, verifyFlags, result);
		rpmdbFreeIterator(mi);
#endif
		break;

	    case VERIFY_PATH:
#if RPM_V1 < 4
		if (rpmdbFindByFile(db, const_cast<char*>(arg), &matches)) {
		    KpMsgE(i18n("file %1 is not owned by any package\n").arg(*it), false);
		} else {
		    rc = verifyMatches(prefix, db, matches, verifyFlags, result);
		    dbiFreeIndexRecord(matches);
		}
#else
		mi = rpmdbInitIterator(db, RPMTAG_BASENAMES, arg, 0);
		if (mi == NULL)
		    KpMsgE(i18n("file %1 is not owned by any package\n").arg(*it), false);
		else
		    rc = verifyMatches(prefix, db, mi, verifyFlags, result);
		rpmdbFreeIterator(mi);
#endif
		break;

	    case VERIFY_PACKAGE:
#if RPM_V1 < 4
		rc = rpmdbFindByLabel(db, const_cast<char*>(arg), &matches);
		if (rc == 1) 
		    KpMsgE(i18n("package %1 is not installed\n").arg(*it), FALSE);
		else if (rc == 2) {
		    KpMsgE(i18n("error looking for package %1\n").arg(*it), FALSE);
		} else {
		    rc = verifyMatches(prefix, db, matches, verifyFlags, result);
		    dbiFreeIndexRecord(matches);
		}
#else
		mi = rpmdbInitIterator(db, RPMDBI_LABEL, arg, 0);
		if (mi == NULL)
		    KpMsgE(i18n("package %1 is not installed\n").arg(*it), FALSE);
		else
		    rc = verifyMatches(prefix, db, mi, verifyFlags, result);
		rpmdbFreeIterator(mi);
#endif
		break;

	    case VERIFY_EVERY:
		break;
	    }
	    if (rc)
		ec = rc;
	}
    }
   
    if (db)
	rpmdbClose(db);
    return ec;
}

#endif /*HAVE_RPM*/
