//////////////////////////////////////////////////////////////
//     $Id: rpmInstall.cpp,v 1.41 2002/05/16 14:58:06 toivo Exp $
#include "../config.h"

#ifdef HAVE_RPM

#include <fcntl.h>
#include <stdio.h>  
#include <unistd.h>

#include "rpmMessages.h"

/* stupid RPM headers! */
typedef __off64_t off64_t;
#include <rpm/rpmlib.h>
#undef	rpmErrorString
#include "rpmInstall.h"

#include <klocale.h>

#include "kpackage.h"
#include "rpmutils.h"

extern "C" { extern int kpackage_rpmIsVerbose(void); };

#if RPM_V1 < 4 || (RPM_V1 == 4 && RPM_V2 == 0 && RPM_V3 == 0)
typedef int rpmtransFlags;
typedef int rpmprobFilterFlags;
#else
typedef enum rpmtransFlags_e rpmtransFlags;
typedef enum rpmprobFilterFlags_e rpmprobFilterFlags;
#endif

extern "C"
{
#include <rpm/rpmmacro.h>
}

static void printHash(const unsigned long amount, const unsigned long total);
#if RPM_V1 < 4
static void * showProgress(const Header h, const rpmCallbackType what, 
			   const unsigned long amount, 
			   const unsigned long total,
			   const void * pkgKey, void * data);
#else
static void * showProgress(const void *hd, const rpmCallbackType what,
			   const unsigned long amount,
			   const unsigned long total,
			   const void * pkgKey, void * data);
#endif

static int hashesPrinted = 0;

static void printHash(const unsigned long amount, const unsigned long total)
{
    int hashesNeeded = -1;

    if (hashesPrinted != 50) {
	hashesNeeded = (int)( 50 * (total ? (((float) amount) / total) : 1));
    }
    kpackage->setPercent(hashesNeeded*2);   
}

#if RPM_V1 < 4
static void * showProgress(const Header h, const rpmCallbackType what, 
			   const unsigned long amount, 
			   const unsigned long total,
			   const void * pkgKey, void * data) {
#else
static void * showProgress(const void *hd, const rpmCallbackType what, 
			   const unsigned long amount, 
			   const unsigned long total,
			   const void * pkgKey, void * data) {
    Header h=(Header)hd;
#endif
    char * s;
    int flags = (int) data;
    void * rc = NULL;
    const char * filename = (const char *)pkgKey;
    static FD_t fd;

    switch (what) {
      case RPMCALLBACK_INST_OPEN_FILE:
#if RPM_V1 < 4
	fd = fdOpen(filename, O_RDONLY, 0);
#else /* RPM 4 */
	fd = Fopen(filename, "r.ufdio");
#endif
	return fd;

      case RPMCALLBACK_INST_CLOSE_FILE:
#if RPM_V1 < 4
	fdClose(fd);
#else /* RPM 4 */
	Fclose(fd);
#endif
	break;

      case RPMCALLBACK_INST_START:
	hashesPrinted = 0;
	if (flags & INSTALL_LABEL) {
	    if (flags & INSTALL_HASH) {
		s = headerSprintf(h, "%{NAME}",
				  rpmTagTable, rpmHeaderFormats, NULL);
		//		KpMsgE(QString("%1\n").arg(s,28));
		kdDebug() << "RPMCALLBACK_INST_START:" << s << "\n";
	    } else {
		s = headerSprintf(h, "%{NAME}-%{VERSION}-%{RELEASE}", 
				  rpmTagTable, rpmHeaderFormats, NULL);
		//		KpMsgE(QString("%1\n").arg(s));
		kdDebug() << "RPMCALLBACK_INST_START : " << s << "\n";
	    }
	    free(s);
	}
	break;

      case RPMCALLBACK_INST_PROGRESS:
	printHash(amount, total);
	break;

      case RPMCALLBACK_TRANS_PROGRESS:
      case RPMCALLBACK_TRANS_START:
      case RPMCALLBACK_TRANS_STOP:
      case RPMCALLBACK_UNINST_PROGRESS:
      case RPMCALLBACK_UNINST_START:
      case RPMCALLBACK_UNINST_STOP:
	/* ignore */
	break;
    }

    return rc;
}	

int doInstal(const char * rootdir, const QStringList &argv, int transFlags, 
	      int interfaceFlags, int probFilter, 
	      rpmRelocation * relocations, QString &errs) {
    rpmdb db = NULL;
    FD_t fd;
    int i = 0;
    int mode, rc, major;
    const char ** packages, ** tmpPackages;
    const char ** filename;
    int numPackages;
    int numTmpPackages = 0, numBinaryPackages = 0, numSourcePackages = 0;
    int numFailed = 0;
    Header h;
    int isSource;
    rpmTransactionSet rpmdep = NULL;
#if RPM_V1 < 4 || (RPM_V1 == 4 && RPM_V2 == 0 && RPM_V3 < 3)
    struct rpmDependencyConflict * conflicts;
#else
    rpmDependencyConflict conflicts;
#endif
    int numConflicts;
    int stopInstall = 0;
    size_t nb;
    int notifyFlags = interfaceFlags | (kpackage_rpmIsVerbose() ? INSTALL_LABEL : 0 );
    int dbIsOpen = 0;
    const char ** sourcePackages;
    rpmRelocation * defaultReloc;

    if (transFlags & RPMTRANS_FLAG_TEST) 
	mode = O_RDONLY;
    else
	mode = O_RDWR | O_CREAT;

    for (defaultReloc = relocations; defaultReloc && defaultReloc->oldPath;
	 defaultReloc++);
    if (defaultReloc && !defaultReloc->newPath) defaultReloc = NULL;

    //    errs +=  i18n("counting packages to install\n");

    numPackages = argv.count();

    //    errs += i18n("found %1 packages\n").arg(numPackages);

    nb = (numPackages + 1) * sizeof(char *);
    packages = (const char **) alloca(nb);
    memset(packages, 0, nb);
    tmpPackages = (const char **)alloca(nb);
    memset(tmpPackages, 0, nb);
    nb = (numPackages + 1) * sizeof(Header);

    for (QStringList::ConstIterator it = argv.begin();
         (it != argv.end());
         it++) {
       packages[i++] = (*it).ascii();
    }

    sourcePackages = (const char **)alloca(sizeof(*sourcePackages) * i);

    //    errs += i18n("retrieved %1 packages").arg(numTmpPackages); 

    /* Build up the transaction set. As a special case, v1 source packages
       are installed right here, only because they don't have headers and
       would create all sorts of confusion later. */

    for (filename = packages; *filename; filename++) {
#if RPM_V1 < 4
	fd = fdOpen(*filename, O_RDONLY, 0);
	if (fdFileno(fd) < 0) {
	  //	    KpMsgE(i18n("cannot open file %1").arg(*filename));
	  errs += i18n("cannot open file %1\n").arg(*filename);
	    numFailed++;
	    packages[i] = NULL;
	    continue;
	}
#else /* RPM 4 */
	fd = Fopen(*filename, "r.ufdio");
	if (Fileno(fd) < 0) {
	  //KpMsgE(i18n("cannot open file %1").arg(*filename));
	    errs += i18n("cannot open file %1\n").arg(*filename);
	    numFailed++;
	    packages[i] = NULL;
	    continue;
	}
#endif

	rc = rpmReadPackageHeader(fd, &h, &isSource, &major, NULL);

	switch (rc) {
	case 1:
#if RPM_V1 < 4
	    fdClose(fd);
#else /* RPM 4 */
	    Fclose(fd);
#endif
	    //KpMsgE(i18n("%1 does not appear to be a RPM package").arg(*filename));
	    errs += i18n("%1 does not appear to be a RPM package\n").arg(*filename);
	    break;
	default:
	  //KpMsgE(i18n("%1 cannot be installed").arg(*filename));
	    errs += i18n("%1 cannot be installed\n").arg(*filename);
	    numFailed++;
	    packages[i] = NULL;
	    break;
	case 0:
	    if (isSource) {
		sourcePackages[numSourcePackages++] = *filename;
#if RPM_V1 < 4
		fdClose(fd);
#else /* RPM 4 */
		Fclose(fd);
#endif
	    } else {
		if (!dbIsOpen) {
		    if (rpmdbOpen(rootdir, &db, mode, 0644)) {
		      //	const char *dn;
		      //	dn = rpmGetPath( (rootdir ? rootdir : ""), 
		      //			"%{_dbpath}", NULL);
		      if (getuid() != 0)
			errs += i18n("KPACKAGE has to run as ROOT\n");
			//			free((void*)dn);
			return(EXIT_FAILURE);
		    }
		    rpmdep = rpmtransCreateSet(db, rootdir);
		    dbIsOpen = 1;
		}

		if (defaultReloc) {
		    char ** paths;
		    char * name;
		    int c;

		    if (headerGetEntry(h, RPMTAG_PREFIXES, NULL,
				       (void **) &paths, &c) && (c == 1)) {
			defaultReloc->oldPath = paths[0];
			free(paths);
		    } else {
			headerGetEntry(h, RPMTAG_NAME, NULL, (void **) &name,
				       NULL);
			//KpMsgE(i18n("package %1 is not relocateable").arg(name));
			errs += i18n("package %1 is not relocateable\n").arg(name);

			return numPackages;
		    }
		}

		rc = rpmtransAddPackage(rpmdep, h, NULL, *filename,
			       (interfaceFlags & INSTALL_UPGRADE) != 0,
			       relocations);
		if (rc) {
		    if (rc == 1)
		      //KpMsgE(i18n("error reading from file %1").arg(*filename));
			errs += i18n("error reading from file %1\n").arg(*filename);
		    else if (rc == 2)
		      //KpMsgE(i18n("file %1 requires a newer version of RPM").arg(*filename));
			errs += i18n("file %1 requires a newer version of RPM\n").arg(*filename);
		    return numPackages;
		}

		if (defaultReloc)
		    defaultReloc->oldPath = NULL;

#if RPM_V1 < 4
		fdClose(fd);
#else /* RPM 4 */
		Fclose(fd);
#endif
		numBinaryPackages++;
	    }
	    break;
	}
    }

    errs +=  i18n("Found %1 source and %2 binary packages\n").arg(numSourcePackages).arg(numBinaryPackages);

    if (numBinaryPackages && !(interfaceFlags & INSTALL_NODEPS)) {
	kdDebug() <<  conflicts << " rpmdepCheck() failed.\n";
	if (!rpmdepCheck(rpmdep, &conflicts, &numConflicts)) {
	    if (conflicts) {
   	        stopInstall = 1;
	        numFailed = numPackages;
	        errs += KprintDepProblems(stderr, conflicts, numConflicts);
	        rpmdepFreeConflicts(conflicts, numConflicts);
	    } else {
	        kdDebug() << " Dependency check problem\n";
	    }
	} 
    }

    if (numBinaryPackages && !(interfaceFlags & INSTALL_NOORDER)) {
	if (rpmdepOrder(rpmdep)) {
	    numFailed = numPackages;
	    stopInstall = 1;
	}
    }

    if (numBinaryPackages && !stopInstall) {
	rpmProblemSet probs = NULL;

	errs += i18n("installing binary packages\n");
	rc = rpmRunTransactions(rpmdep, showProgress, (void *) notifyFlags, 
				    NULL, &probs, (rpmtransFlags)transFlags,
				    (rpmprobFilterFlags)probFilter);

	if (rc < 0) {
	    numFailed += numBinaryPackages;
	} else if (rc) {
	    numFailed += rc;
	    for (i = 0; i < probs->numProblems; i++) {
		if (!probs->probs[i].ignoreProblem) {
#if RPM_V1 < 4 || (RPM_V1 == 4 && RPM_V2 == 0 && RPM_V3 == 0)
		    char *msg = (char *) rpmProblemString(probs->probs[i]);
#else
		    char *msg = (char *) rpmProblemString(&probs->probs[i]);
#endif
		    //KpMsgE(msg);
		    errs += msg;
		    free(msg);
		}
	    }
	}

	if (probs) rpmProblemSetFree(probs);
    }

    if (numBinaryPackages) rpmtransFree(rpmdep);


    if (numSourcePackages && !stopInstall) {
	for (i = 0; i < numSourcePackages; i++) {
#if RPM_V1 < 4
	    fd = fdOpen(sourcePackages[i], O_RDONLY, 0);
	    if (fdFileno(fd) < 0) {
	      //KpMsgE(i18n("cannot open file %1").arg(sourcePackages[i]));
		errs += i18n("cannot open file %1\n").arg(sourcePackages[i]);
		continue;
	    }
#else /* RPM 4 */
	    fd = Fopen(sourcePackages[i], "r.ufdio");
	    if (Fileno(fd) < 0) {
	      //KpMsgE(i18n("cannot open file %1").arg(sourcePackages[i]));
		errs += i18n("cannot open file %1\n").arg(sourcePackages[i]);
		continue;
	    }
#endif

	    if (!(transFlags & RPMTRANS_FLAG_TEST))
		numFailed += rpmInstallSourcePackage(rootdir, fd, NULL,
				showProgress, (void *) notifyFlags, NULL);

#if RPM_V1 < 4
            fdClose(fd);
#else /* RPM 4 */
            Fclose(fd);
#endif
	}
    }

    for (i = 0; i < numTmpPackages; i++) {
	unlink(tmpPackages[i]);
	free((void*)tmpPackages[i]);
    }

    /* FIXME how do we close our various fd's? */

    if (dbIsOpen) rpmdbClose(db);

    return numFailed;
}

int doUninstal(const char * rootdir, const QStringList &argv, int transFlags,
		 int interfaceFlags, QString &errs) {
    rpmdb db;
#if RPM_V1 < 4
    dbiIndexSet matches;
    unsigned int i;
    int rc;
    int count;
#else
    rpmdbMatchIterator mi;
    Header h;
#endif
    int j;
    int mode;
    int numFailed = 0;
    rpmTransactionSet rpmdep;
#if RPM_V1 < 4 || (RPM_V1 == 4 && RPM_V2 == 0 && RPM_V3 < 3)
    struct rpmDependencyConflict * conflicts;
#else
    rpmDependencyConflict conflicts;
#endif
    int numConflicts;
    int stopUninstall = 0;
    int numPackages = 0;
    rpmProblemSet probs;

    if (transFlags & RPMTRANS_FLAG_TEST) 
	mode = O_RDONLY;
    else
	mode = O_RDWR | O_EXCL;
	
    if (rpmdbOpen(rootdir, &db, mode, 0644)) {
	const char *dn;
	dn = rpmGetPath( (rootdir ? rootdir : ""), "%{_dbpath}", NULL);
        if (getuid() != 0)
           errs += i18n("KPACKAGE has to run as ROOT\n");
	free((void*)dn);
	return(EXIT_FAILURE);
    }

    j = 0;
    rpmdep = rpmtransCreateSet(db, rootdir);
    for(QStringList::ConstIterator it = argv.begin();
        (it != argv.end());
        it++)
    {
        QString arg = (*it);
#if RPM_V1 < 4
        rc = rpmdbFindByLabel(db, QFile::encodeName(arg), &matches);
	switch (rc) {
	case 1:
	  //            KpMsgE(i18n("package %1 is not installed").arg(arg));
            errs += i18n("package %1 is not installed\n").arg(arg);
	    numFailed++;
	    break;
	case 2:
	  //            KpMsgE(i18n("searching for package %1").arg(arg));
            errs += i18n("searching for package %1\n").arg(arg);
	    numFailed++;
	    break;
	default:
	    count = 0;
	    for (i = 0; i < dbiIndexSetCount(matches); i++)
		if (dbiIndexRecordOffset(matches, i)) count++;

	    if (count > 1 && !(interfaceFlags & UNINSTALL_ALLMATCHES)) {
	      //		KpMsgE(i18n("\"%1\" specifies multiple packages").arg(arg));
		errs += i18n("\"%1\" specifies multiple packages\n").arg(arg);
		numFailed++;
	    }
	    else { 
		for (i = 0; i < dbiIndexSetCount(matches); i++) {
		    unsigned int recOffset = dbiIndexRecordOffset(matches, i);
		    if (recOffset) {
			rpmtransRemovePackage(rpmdep, recOffset);
			numPackages++;
		    }
		}
	    }

	    dbiFreeIndexRecord(matches);
	    break;
	}
#else /* RPM v4 */
	{
	bool found=false;
	mi=rpmdbInitIterator(db, RPMDBI_LABEL, QFile::encodeName(arg), 0);
	while((h=rpmdbNextIterator(mi))!=NULL) {
		found=true;
		rpmtransRemovePackage(rpmdep, rpmdbGetIteratorOffset(mi));
		numPackages++;
	}
	if(!found) {
	  //            KpMsgE(i18n("package %1 is not installed").arg(arg));
            errs += i18n("package %1 is not installed\n").arg(arg);
	    numFailed++;
	}
	rpmdbFreeIterator(mi);
	}
#endif
    }

    if (!(interfaceFlags & UNINSTALL_NODEPS)) {
	if (!rpmdepCheck(rpmdep, &conflicts, &numConflicts)) {
	    kdDebug() << numConflicts << " rpmdepCheck() failed.\n";
	    if (conflicts) {
	        numFailed = numPackages;
	        stopUninstall = 1;
	        errs += KprintDepProblems(stderr, conflicts, numConflicts);
	        rpmdepFreeConflicts(conflicts, numConflicts);
	        numFailed += numPackages;
	    } else {
	        kdDebug() << " Dependency check problem\n";
	    }
	} 
    }

    if (!stopUninstall) {
	numFailed += rpmRunTransactions(rpmdep, NULL, NULL, NULL, &probs,
					(rpmtransFlags)transFlags,
				        (rpmprobFilterFlags)0);
    }

    rpmtransFree(rpmdep);
    rpmdbClose(db);

    return numFailed;
}

int doSourceInstall(const char * rootdir, const char * arg, const char ** specFile,
		    char ** cookie) {
    FD_t fd;
    int rc;

#if RPM_V1 < 4
    fd = fdOpen(arg, O_RDONLY, 0);
    if (fdFileno(fd) < 0) {
	KpMsgE(i18n("cannot open %1").arg(arg));
	return 1;
    }
#else /* RPM 4 */
    fd = Fopen(arg, "r.ufdio");
    if (Fileno(fd) < 0) {
	KpMsgE(i18n("cannot open %1").arg(arg));
	return 1;
    }
#endif

    if (kpackage_rpmIsVerbose())
      kdDebug() << i18n("Installing %1\n").arg(arg) << endl;;

    rc = rpmInstallSourcePackage(rootdir, fd, specFile, NULL, NULL, 
				 cookie);
    if (rc == 1) {
	KpMsgE(i18n("%1 cannot be installed").arg(arg),TRUE);
	if (specFile) FREE(*specFile);
	if (cookie) FREE(*cookie);
    }

#if RPM_V1 < 4
    fdClose(fd);
#else /* RPM 4 */
    Fclose(fd);
#endif

    return rc;
}

void printDepFlags(FILE * f, QString s, char const * version, int flags) {
 
  if (flags) {
    fprintf(f, " ");
    s += " ";
  }
 
  if (flags & RPMSENSE_LESS) {
    fprintf(f, "<");
    s += "<";
  }
  if (flags & RPMSENSE_GREATER) {
    fprintf(f, ">");
    s += ">";
  }
  if (flags & RPMSENSE_EQUAL) {
    fprintf(f, "=");
    s += "=";
  }
  if (flags & RPMSENSE_SERIAL) {
    fprintf(f, "S");
    s += "S";
  }
  if (flags) {
    fprintf(f, " %s", version);
    s += " ";
    s += version;
  }
}
 
#if RPM_V1 < 4 || (RPM_V1 == 4 && RPM_V2 == 0 && RPM_V3 < 3)
QString KprintDepProblems(FILE * f, struct rpmDependencyConflict * conflicts,
		       int numConflicts) {
#else
QString KprintDepProblems(FILE * f, rpmDependencyConflict conflicts, int numConflicts) {
#endif
  
  int i;
  QString s, st;
 
  st += i18n("Dependency Problem:\n");
#if RPM_V1 < 4 || (RPM_V1 == 4 && RPM_V2 == 0 && RPM_V3 < 3)
  for (i = 0; i < numConflicts; i++) {
#else
  for (i = 0; i < numConflicts; i++, conflicts++) {
#endif
    fprintf(f, "\t%s", conflicts[i].needsName);
    st += " ";
#if RPM_V1 < 4 || (RPM_V1 == 4 && RPM_V2 == 0 && RPM_V3 < 3)
    st += conflicts[i].needsName;
    if (conflicts[i].needsFlags) {
      printDepFlags(stderr, st, conflicts[i].needsVersion, 
		    conflicts[i].needsFlags);
    }
    if (conflicts[i].sense == (rpmDependencyConflict::RPMDEP_SENSE_REQUIRES)) { 
      st += s = i18n(" is needed by %1-%2-%3\n")
	.arg(conflicts[i].byName)
	.arg(conflicts[i].byVersion).arg(conflicts[i].byRelease);
    } else {
      st += s = i18n(" conflicts with %1-%2-%3\n")
	.arg(conflicts[i].byName)
	.arg(conflicts[i].byVersion).arg(conflicts[i].byRelease);
    }
#else
    st += conflicts->needsName;
    if (conflicts->needsFlags) {
      printDepFlags(stderr, st, conflicts->needsVersion, conflicts->needsFlags);
    }
    if (conflicts->sense == (rpmDependencyConflict_s::RPMDEP_SENSE_REQUIRES)) { 
      st += s = i18n(" is needed by %1-%2-%3\n")
	.arg(conflicts->byName)
	.arg(conflicts->byVersion).arg(conflicts->byRelease);
    } else {
      st += s = i18n(" conflicts with %1-%2-%3\n")
	.arg(conflicts->byName)
	.arg(conflicts->byVersion).arg(conflicts->byRelease);
    }
#endif
  }
  return st;
}

extern "C"
{

  void rpmBuff(char *buff) {
    kpackage->setStatus(buff);
  }

  void rpmMess(char *buff) {
    KpMsgE(buff,TRUE);
  } 

  QString *rpmEBuf = 0;

  void rpmErr(void) {
    if (rpmEBuf) {
      *rpmEBuf += rpmErrorString();
      *rpmEBuf += "\n";
    } else {
      KpMsgE(rpmErrorString(),TRUE);
    }
  }

}

#endif

