initial autorollback feature.
authorjoden <devnull@localhost>
Fri, 5 Mar 2004 18:22:25 +0000 (18:22 +0000)
committerjoden <devnull@localhost>
Fri, 5 Mar 2004 18:22:25 +0000 (18:22 +0000)
CVS patchset: 7149
CVS date: 2004/03/05 18:22:25

lib/psm.c
lib/rpminstall.c
lib/rpmte.c
lib/rpmte.h
lib/rpmts.c
lib/rpmts.h
lib/transaction.c

index ea06008..c511055 100644 (file)
--- a/lib/psm.c
+++ b/lib/psm.c
@@ -38,6 +38,11 @@ int _psm_debug = _PSM_DEBUG;
 /*@unchecked@*/
 int _psm_threads = 0;
 
+/* Give access to the rpmte global tracking the last instance added
+ * to the database.
+ */
+extern unsigned int myinstall_instance;
+
 /*@access FD_t @*/             /* XXX void ptr args */
 /*@access rpmpsm @*/
 
@@ -1048,6 +1053,7 @@ static rpmRC runTriggers(rpmpsm psm)
 
     if (psm->te)       /* XXX can't happen */
        N = rpmteN(psm->te);
+/* ADJUST */
     if (N)             /* XXX can't happen */
        numPackage = rpmdbCountPackages(rpmtsGetRdb(ts), N)
                                + psm->countCorrection;
@@ -1300,6 +1306,48 @@ rpmRC rpmpsmStage(rpmpsm psm, pkgStage stage)
            break;
        }
 
+       /* If we have a score then autorollback is enabled.  If autorollback is
+        * enabled, and this is an autorollback transaction, then we may need to 
+        * adjust the pkgs installed count.
+        * 
+        * If all this is true, this adjustment should only be made if the PSM goal
+        * is an install.  No need to make this adjustment on the erase 
+        * component of the upgrade, or even more absurd to do this when doing a 
+        * PKGSAVE.
+        */
+       if(rpmtsGetScore(ts) != NULL &&
+           rpmtsGetType(ts) == RPMTRANS_TYPE_AUTOROLLBACK &&
+           (psm->goal & ~(PSM_PKGSAVE|PSM_PKGERASE))) {
+           /* Get the score, if its not NULL, get the appropriate 
+            * score entry.
+            */
+           rpmtsScore score = rpmtsGetScore(ts);
+           if(score != NULL) {
+               /* OK, we got a real score so lets get the appropriate
+                * score entry.
+                */
+               rpmtsScoreEntry se;
+               se = rpmtsScoreGetEntry(score, rpmteN(psm->te));
+
+               /* IF the header for the install element has been installed, 
+                * but the header for the erase element has not been erased,
+                * then decrement the instance count.  This is because in an 
+                * autorollback, if the header was added in the initial transaction
+                * then in the case of an upgrade the instance count will be 
+                * 2 instead of one when re-installing the old package, and 3 when
+                * erasing the new package.
+                * 
+                * Another wrinkle is we only want to make this adjustement
+                * if the thing we are rollback was an upgrade of package.  A pure
+                * install or erase does not need the adjustment
+                */
+               if(se && se->installed && 
+                   !se->erased &&
+                   (se->te_types & (TR_ADDED|TR_REMOVED)))
+                   psm->npkgs_installed--;
+          }
+       }
+
        if (psm->goal == PSM_PKGINSTALL) {
            int fc = rpmfiFC(fi);
 
@@ -1956,6 +2004,36 @@ assert(psm->mi == NULL);
        else
            rc = rpmdbAdd(rpmtsGetRdb(ts), rpmtsGetTid(ts), fi->h,
                                NULL, NULL);
+
+       /* Set the database instance so consumers (i.e. rpmtsRun())
+        * can add this to a rollback transaction.
+        */
+       rpmteSetDBInstance(psm->te, myinstall_instance);
+
+       /*
+        * If the score exists and this is not a rollback or autorollback
+        * then lets check off installed for this package.
+        */
+       if(rpmtsGetScore(ts) != NULL &&
+            rpmtsGetType(ts) != RPMTRANS_TYPE_ROLLBACK &&
+            rpmtsGetType(ts) != RPMTRANS_TYPE_AUTOROLLBACK) {
+            /* Get the score, if its not NULL, get the appropriate
+             * score entry.
+             */
+            rpmtsScore score = rpmtsGetScore(ts);
+            if(score != NULL) {
+               /* OK, we got a real score so lets get the appropriate
+                * score entry.
+                */
+               rpmMessage(RPMMESS_DEBUG,
+                   _("Attempting to mark %s as installed in score board(0x%x).\n"),
+                   rpmteN(psm->te), score);
+               rpmtsScoreEntry se;
+               se = rpmtsScoreGetEntry(score, rpmteN(psm->te));
+               if(se) se->installed = 1;
+           }
+       }
+
        (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_DBADD), 0);
        break;
     case PSM_RPMDB_REMOVE:
@@ -1963,6 +2041,31 @@ assert(psm->mi == NULL);
        (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_DBREMOVE), 0);
        rc = rpmdbRemove(rpmtsGetRdb(ts), rpmtsGetTid(ts), fi->record,
                                NULL, NULL);
+
+       /*
+        * If the score exists and this is not a rollback or autorollback
+        * then lets check off erased for this package.
+        */
+       if(rpmtsGetScore(ts) != NULL &&
+           rpmtsGetType(ts) != RPMTRANS_TYPE_ROLLBACK &&
+           rpmtsGetType(ts) != RPMTRANS_TYPE_AUTOROLLBACK) {
+           /* Get the score, if its not NULL, get the appropriate
+            * score entry.
+            */
+           rpmtsScore score = rpmtsGetScore(ts);
+           if(score != NULL) { /* XXX: Can't happen */
+               /* OK, we got a real score so lets get the appropriate
+                * score entry.
+                */
+               rpmMessage(RPMMESS_DEBUG,
+                   _("Attempting to mark %s as erased in score board(0x%x).\n"),
+                   rpmteN(psm->te), score);
+               rpmtsScoreEntry se;
+               se = rpmtsScoreGetEntry(score, rpmteN(psm->te));
+               if(se) se->erased = 1;
+           }
+       }
+
        (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_DBREMOVE), 0);
        break;
 
index b75a958..523771a 100644 (file)
@@ -9,6 +9,7 @@
 #include "rpmdb.h"
 #include "rpmds.h"
 
+#include "rpmte.h"     /* XXX: rpmts.h needs this for rpmtsScoreEntries */
 #define        _RPMTS_INTERNAL         /* ts->goal, ts->dbmode, ts->suggests */
 #include "rpmts.h"
 
@@ -1120,6 +1121,11 @@ int rpmRollback(rpmts ts, struct rpmInstallArguments_s * ia, const char ** argv)
 
     (void) rpmtsSetFlags(ts, transFlags);
 
+    /*  Make the transaction a rollback transaction.  In a rollback
+     *  a best effort is what we want 
+     */
+    rpmtsSetType(ts, RPMTRANS_TYPE_ROLLBACK);
+
     itids = IDTXload(ts, RPMTAG_INSTALLTID);
     if (itids != NULL) {
        ip = itids->idt;
index fe8eff6..b70de69 100644 (file)
@@ -104,6 +104,11 @@ static void addTE(rpmts ts, rpmte p, Header h,
     if ((p->version = strrchr(p->name, '-')) != NULL)
        *p->version++ = '\0';
 
+    /* Set db_instance to 0 as it has not been installed
+     * necessarily yet.
+     */
+    p->db_instance = 0;
+
     arch = NULL;
     xx = hge(h, RPMTAG_ARCH, NULL, (void **)&arch, NULL);
     if (arch != NULL) {
@@ -225,6 +230,19 @@ rpmte rpmteNew(const rpmts ts, Header h,
     return p;
 }
 
+/* Get the DB Instance value */
+unsigned int rpmteDBInstance(rpmte te) 
+{
+    return (te != NULL ? te->db_instance : 0);
+}
+
+/* Set the DB Instance value */
+void rpmteSetDBInstance(rpmte te, unsigned int instance) 
+{
+    if(te != NULL) 
+       te->db_instance = instance;
+}
+
 rpmElementType rpmteType(rpmte te)
 {
     return (te != NULL ? te->type : -1);
index 5648c58..f91bf6a 100644 (file)
@@ -85,6 +85,7 @@ struct rpmte_s {
     int depth;                 /*!< Max. depth in dependency tree. */
     int npreds;                        /*!< No. of predecessors. */
     int tree;                  /*!< Tree index. */
+    unsigned int db_instance;   /*!< Database Instance after add */
 /*@owned@*/
     tsortInfo tsi;             /*!< Dependency ordering chains. */
 
@@ -254,6 +255,21 @@ uint_32 rpmteSetColor(rpmte te, uint_32 color)
        /*@modifies te @*/;
 
 /**
+ * Retrieve last instance installed to the database.
+ * @param te           transaction element
+ * @return             last install instance.
+ */
+unsigned int rpmteDBInstance(rpmte te);
+
+/**
+ * Set last instance installed to the database.
+ * @param te           transaction element
+ * @param instance     Database instance of last install element.
+ * @return             last install instance.
+ */
+void rpmteSetDBInstance(rpmte te, unsigned int instance);
+
+/**
  * Retrieve size in bytes of package file.
  * @todo Signature header is estimated at 256b.
  * @param te           transaction element
index dc2bb8d..005718a 100644 (file)
@@ -833,6 +833,9 @@ rpmts rpmtsFree(rpmts ts)
     if (_rpmts_stats)
        rpmtsPrintStats(ts);
 
+    /* Free up the memory used by the rpmtsScore */
+    rpmtsScoreFree(ts->score);
+
     (void) rpmtsUnlink(ts, "tsCreate");
 
     /*@-refcounttrans -usereleased @*/
@@ -860,6 +863,36 @@ rpmVSFlags rpmtsSetVSFlags(rpmts ts, rpmVSFlags vsflags)
     return ovsflags;
 }
 
+/* 
+ * This allows us to mark transactions as being of a certain type.
+ * The three types are:
+ * 
+ *     RPM_TRANS_NORMAL        
+ *     RPM_TRANS_ROLLBACK
+ *     RPM_TRANS_AUTOROLLBACK
+ * 
+ * ROLLBACK and AUTOROLLBACK transactions should always be ran as
+ * a best effort.  In particular this important to the autorollback 
+ * feature to avoid rolling back a rollback (otherwise known as 
+ * dueling rollbacks (-;).  AUTOROLLBACK's additionally need instance 
+ * counts passed to scriptlets to be altered.
+ */
+void rpmtsSetType(rpmts ts, rpmtsType type)
+{
+    if(ts != NULL) {
+       ts->type = type;
+    }   
+}
+
+/* Let them know what type of transaction we are */
+rpmtsType rpmtsGetType(rpmts ts) 
+{
+    if(ts != NULL) 
+       return ts->type;
+    else
+       return 0;
+}
+
 int rpmtsUnorderedSuccessors(rpmts ts, int first)
 {
     int unorderedSuccessors = 0;
@@ -1419,6 +1452,7 @@ rpmts rpmtsCreate(void)
     ts = xcalloc(1, sizeof(*ts));
     memset(&ts->ops, 0, sizeof(ts->ops));
     (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_TOTAL), -1);
+       ts->type = RPMTRANS_TYPE_NORMAL;
     ts->goal = TSM_UNKNOWN;
     ts->filesystemCount = 0;
     ts->filesystems = NULL;
@@ -1469,7 +1503,187 @@ rpmts rpmtsCreate(void)
     memset(ts->pksignid, 0, sizeof(ts->pksignid));
     ts->dig = NULL;
 
+    /* 
+       We only use the score in an autorollback.  So set this to
+       NULL by default.
+     */
+    ts->score = NULL;
+
     ts->nrefs = 0;
 
     return rpmtsLink(ts, "tsCreate");
 }
+
+/**********************
+ * Transaction Scores *
+ **********************/
+
+
+rpmRC rpmtsScoreInit(rpmts runningTS, rpmts rollbackTS) 
+{
+    rpmtsScore score;
+    rpmtsi     pi;
+    rpmte      p;
+    int        i;
+    int        tranElements;  /* Number of transaction elements in runningTS */
+    int        found = 0;
+    rpmRC      rc = RPMRC_OK; /* Assume success */
+    rpmtsScoreEntry se;
+
+    rpmMessage(RPMMESS_DEBUG, _("Creating transaction score board(0x%x, 0x%x)\n"),
+       runningTS, rollbackTS); 
+
+    /* Allocate space for score board */
+    score = xcalloc(1, sizeof(*score));
+    rpmMessage(RPMMESS_DEBUG, _("\tScore board address:  0x%x\n"), score);
+
+    /* 
+     * Determine the maximum size needed for the entry list.
+     * XXX: Today, I just get the count of rpmts elements, and allocate
+     *      an array that big.  Yes this is guaranteed to waste memory.
+     *      Future updates will hopefully make this more efficient,
+     *      but for now it will work.
+     */
+    tranElements  = rpmtsNElements(runningTS);
+    rpmMessage(RPMMESS_DEBUG, _("\tAllocating space for %d entries\n"), tranElements);
+    score->scores = xcalloc(tranElements, sizeof(score->scores));
+
+    /* Initialize score entry count */
+    score->entries = 0;
+    score->nrefs   = 0;
+
+    /*
+     * Increment through transaction elements and make sure for every 
+     * N there is an rpmtsScoreEntry.
+     */
+    pi = rpmtsiInit(runningTS); 
+    while ((p = rpmtsiNext(pi, TR_ADDED|TR_REMOVED)) != NULL) {
+       found  = 0;
+
+       /* Try to find the entry in the score list */
+       for(i = 0; i < score->entries; i++) {
+           se = score->scores[i]; 
+           if(strcmp(rpmteN(p), se->N) == 0) {
+               found = 1;
+               break;
+           }
+       }
+
+       /* If we did not find the entry then allocate space for it */
+       if(!found) {
+           rpmMessage(RPMMESS_DEBUG, _("\tAdding entry for %s to score board.\n"),
+               rpmteN(p));
+           se = xcalloc(1, sizeof(*(*(score->scores))));
+           rpmMessage(RPMMESS_DEBUG, _("\t\tEntry address:  0x%x\n"), se);
+           se->N         = xstrdup(rpmteN(p));
+           se->te_types  = rpmteType(p); 
+           se->installed = 0;
+           se->erased    = 0; 
+           score->scores[score->entries] = se;
+           score->entries++;
+       } else {
+           /* We found this one, so just add the element type to the one 
+            * already there.
+            */
+           rpmMessage(RPMMESS_DEBUG, _("\tUpdating entry for %s in score board.\n"),
+               rpmteN(p));
+           score->scores[i]->te_types |= rpmteType(p);
+       }
+        
+    }
+    pi = rpmtsiFree(pi);
+    /* 
+     * Attach the score to the running transaction and the autorollback
+     * transaction.
+     */
+    runningTS->score  = score;
+    score->nrefs++;
+    rollbackTS->score = score;
+    score->nrefs++;
+
+    return rc;
+}
+
+rpmRC rpmtsScoreFree(rpmtsScore score) 
+{
+    int i;
+    rpmtsScoreEntry se = NULL;
+
+    rpmMessage(RPMMESS_DEBUG, _("May free Score board(0x%x)\n"), score);
+
+    /* If score is not initialized, then just return.
+     * This is likely the case if autorollbacks are not enabled.
+     */
+    if(score == NULL) return RPMRC_OK;
+
+    /* Decrement the reference count */
+    score->nrefs--;
+
+    /* Do we have any more references?  If so
+     * just return.
+     */
+    if(score->nrefs > 0) return RPMRC_OK;
+
+    rpmMessage(RPMMESS_DEBUG, _("\tRefcount is zero...will free\n"), score);
+    /* No more references, lets clean up  */
+    /* First deallocate the score entries */
+    for(i = 0; i < score->entries; i++) {
+       /* Get the score for the ith entry */
+       se = score->scores[i]; 
+       
+       /* Deallocate the score entries name */
+       _free(se->N);
+
+       /* Deallocate the score entry itself */
+       _free(se);
+    }
+
+    /* Next deallocate the score entry table */
+    _free(score->scores);
+
+    /* Finally deallocate the score itself */
+    _free(score);
+
+    return RPMRC_OK;
+}
+
+/* 
+ * XXX: Do not get the score and then store it aside for later use.
+ *      we will delete it out from under you.  There is not rpmtsScoreLink()
+ *      as this may be a very temporary fix for autorollbacks.
+ */
+rpmtsScore rpmtsGetScore(rpmts ts) 
+{
+    if(ts == NULL) return NULL;
+    return ts->score;
+}
+
+/* 
+ * XXX: Do not get the score entry and then store it aside for later use.
+ *      we will delete it out from under you.  There is not an 
+ *      rpmtsScoreEntryLink() as this may be a very temporary fix 
+ *      for autorollbacks.
+ * XXX: The scores are not sorted.  This should be fixed at earliest
+ *      opportunity (i.e. when we have the whole autorollback working).
+ */
+rpmtsScoreEntry rpmtsScoreGetEntry(rpmtsScore score, const char *N) 
+{
+    int i;
+    rpmtsScoreEntry se;
+    rpmtsScoreEntry ret = NULL; /* Assume we don't find it */
+
+    rpmMessage(RPMMESS_DEBUG, _("Looking in score board(0x%x) for %s\n"), score, N);
+
+    /* Try to find the entry in the score list */
+    for(i = 0; i < score->entries; i++) {
+       se = score->scores[i]; 
+       if(strcmp(N, se->N) == 0) {
+           rpmMessage(RPMMESS_DEBUG, _("\tFound entry at address:  0x%x\n"), se);
+           ret = se;
+           break;
+       }
+    }
+       
+    return ret;        
+}
index 7e488e4..b5e8984 100644 (file)
@@ -1,10 +1,6 @@
 #ifndef H_RPMTS
 #define H_RPMTS
 
-/** \ingroup rpmts
- * \file lib/rpmts.h
- * Structures and prototypes used for an "rpmts" transaction set.
- */
 
 #include "rpmps.h"
 #include "rpmsw.h"
@@ -39,6 +35,15 @@ typedef enum rpmVSFlags_e {
     /* bit(s) 16-31 unused */
 } rpmVSFlags;
 
+/**
+ * Transaction Types
+ */
+typedef enum rpmtsType_e {
+       RPMTRANS_TYPE_NORMAL       = 0,
+       RPMTRANS_TYPE_ROLLBACK     = (1 << 0),
+       RPMTRANS_TYPE_AUTOROLLBACK = (1 << 1)
+} rpmtsType;
+
 #define        _RPMVSF_NODIGESTS       \
   ( RPMVSF_NOSHA1HEADER |      \
     RPMVSF_NOMD5HEADER |       \
@@ -92,6 +97,84 @@ typedef      enum rpmtsOpX_e {
 #include "rpmhash.h"   /* XXX hashTable */
 #include "rpmal.h"     /* XXX availablePackage/relocateFileList ,*/
 
+/**********************
+ * Transaction Scores *
+ **********************
+ * 
+ * In order to allow instance counts to be adjusted properly when an
+ * autorollback transaction is ran, we keep a list that is indexed
+ * by rpm name of whether the rpm has been installed or erased.  This listed
+ * is only updated:
+ *
+ *     iif autorollbacks are enabled.
+ *     iif this is not a rollback or autorollback transaction.
+ *
+ * When creating an autorollback transaction, its rpmts points to the same
+ * rpmtsScore object as the running transaction.  So when the autorollback
+ * transaction runs it can see where each package was in the running transaction
+ * at the point the running transaction failed, and thus on a per package
+ * basis make adjustments to the instance counts.
+ *
+ * XXX: Jeff, I am not convinced that this does not need to be in its own file 
+ *      (i.e. rpmtsScore.{h,c}), but I first wanted to get it working.  
+ */
+struct rpmtsScoreEntry_s {
+       char *         N;               /*!<Name of package                */
+       rpmElementType te_types;        /*!<te types this entry represents */
+       int            installed;       /*!<Was the new header installed   */
+       int            erased;          /*!<Was the old header removed     */
+};
+
+typedef struct rpmtsScoreEntry_s * rpmtsScoreEntry;
+
+struct rpmtsScore_s {
+       int entries;                    /*!< Number of scores       */
+       rpmtsScoreEntry * scores;       /*!< Array of score entries */
+       int nrefs;                      /*!< Reference count.       */
+};
+
+typedef struct rpmtsScore_s * rpmtsScore;
+
+
+/** \ingroup rpmts
+ * initialize rpmtsScore for running transaction and autorollback 
+ * transaction.
+ * @param runningTS    Running Transaction.
+ * @param rollbackTS   Rollback Transaction.
+ * @return             RPMRC_OK
+ */
+rpmRC rpmtsScoreInit(rpmts runningTS, rpmts rollbackTS); 
+
+/** \ingroup rpmts
+ * Free rpmtsScore provided no more references exist against it.
+ * @param score                rpmtsScore to free
+ * @return             RPMRC_OK
+ */
+rpmRC rpmtsScoreFree(rpmtsScore score);
+
+/** \ingroup rpmts
+ * Get rpmtsScore from transaction.
+ * @param ts   RPM Transaction.
+ * @return     rpmtsScore or NULL.
+ */
+rpmtsScore rpmtsGetScore(rpmts ts);
+
+/** \ingroup rpmts
+ * Get rpmtsScoreEntry from rpmtsScore.
+ * @param score   RPM Transaction Score.
+ * @return       rpmtsScoreEntry or NULL.
+ */
+rpmtsScoreEntry rpmtsScoreGetEntry(rpmtsScore score, const char *N);
+
+/** \ingroup rpmts
+ * \file lib/rpmts.h
+ * Structures and prototypes used for an "rpmts" transaction set.
+ */
+
+/**************************
+ * END Transaction Scores *
+ **************************/
+
 /*@unchecked@*/
 /*@-exportlocal@*/
 extern int _cacheDependsRC;
@@ -135,6 +218,7 @@ typedef enum tsStage_e {
 struct rpmts_s {
     rpmtransFlags transFlags;  /*!< Bit(s) to control operation. */
     tsmStage goal;             /*!< Transaction goal (i.e. mode) */
+    rpmtsType type;             /*!< default, rollback, autorollback */
 
 /*@refcounted@*/ /*@null@*/
     rpmdb sdb;                 /*!< Solve database handle. */
@@ -233,9 +317,9 @@ struct rpmts_s {
 /*@null@*/
     Spec spec;                 /*!< Spec file control structure. */
 
+    rpmtsScore score;          /*!< Transaction Score (autorollback). */
 /*@refs@*/
     int nrefs;                 /*!< Reference count. */
-
 };
 #endif /* _RPMTS_INTERNAL */
 
@@ -452,6 +536,26 @@ int rpmtsSetSolveCallback(rpmts ts,
        /*@modifies ts @*/;
 
 /**
+ * Set transaction type.   Allowed types are:
+ * 
+ *     RPMTRANS_TYPE_NORMAL
+ *     RPMTRANS_TYPE_ROLLBACK
+ *     RPMTRANS_TYPE_AUTOROLLBACK
+ * 
+ * @param ts           transaction set
+ * @param rollback     rpmtsType
+ * @return             void
+ */
+void rpmtsSetType(rpmts ts, rpmtsType type);
+
+/**
+ * Return the type of a transaction.
+ * @param ts           transaction set
+ * @return             0 it is not, 1 it is.
+ */
+rpmtsType rpmtsGetType(rpmts ts);
+
+/**
  * Return current transaction set problems.
  * @param ts           transaction set
  * @return             current problem set (or NULL)
@@ -959,4 +1063,5 @@ uint_32 hGetColor(Header h)
 }
 #endif
 
+
 #endif /* H_RPMTS */
index 0b2f06c..faa02fe 100644 (file)
 
 #include "debug.h"
 
+/*
+ * This is needed for the IDTX definitions.  I think probably those need
+ * to be moved into a different source file (idtx.{c,h}), but that is up
+ * Jeff Johnson.
+ */
+#include "rpmcli.h"
+
 /*@access Header @*/           /* XXX ts->notify arg1 is void ptr */
 /*@access rpmps @*/    /* XXX need rpmProblemSetOK() */
 /*@access dbiIndexSet @*/
 /*@access rpmtsi @*/
 /*@access rpmts @*/
 
+/* Internal function to rollback a transaction
+ * This is not declared in the header because we not
+ * want others calling this directly (or at all).
+ */
+rpmRC _rpmtsRollback(rpmts rollbackTransaction);
+
+/* Internal function to add an element to a rollback transaction
+ * This is not declared in the header because we not want others 
+ * calling this directly (or at all).
+ */
+rpmRC _rpmtsAddRollbackElement(rpmts rollbackTransaction, rpmts runningTransaction, rpmte te);
+
+/* You might want to move this into the header Jeff (or even 
+ * to a different file altogether (i.e. the prototype and the 
+ * the function).
+ */
+rpmRC getRepackageHeaderFromTE(rpmte te, rpmts ts, Header *hdrp, char **fn); 
+/* XXX: This is a hack.  I needed a to setup a notify callback
+ * for the rollback transaction, but I did not want to create
+ * a header for rpminstall.c.
+ */
+extern void * rpmShowProgress(/*@null@*/ const void * arg,
+                        const rpmCallbackType what,
+                        const unsigned long amount,
+                        const unsigned long total,
+                        /*@null@*/ fnpyKey key,
+                        /*@null@*/ void * data);
+
 /**
  */
 static int archOkay(/*@null@*/ const char * pkgArch)
@@ -953,13 +989,28 @@ int rpmtsRun(rpmts ts, rpmps okProbs, rpmprobFilterFlags ignoreSet)
     rpmtsi qi; rpmte q;
     int numAdded;
     int numRemoved;
+    rpmts rollbackTransaction = NULL;
+    int rollbackOnFailure = 0;
     void * lock;
     int xx;
 
+
     /* XXX programmer error segfault avoidance. */
     if (rpmtsNElements(ts) <= 0)
        return -1;
 
+    /* See if we need to rollback on failure */
+    rollbackOnFailure = rpmExpandNumeric(
+       "%{?_rollback_transaction_on_failure}");
+    if(rpmtsGetType(ts) & (RPMTRANS_TYPE_ROLLBACK 
+       | RPMTRANS_TYPE_AUTOROLLBACK)) {
+       rollbackOnFailure = 0;
+    }
+    /* If we are in test mode, there is no need to rollback on 
+     * failure (-;
+     */
+    if(rpmtsFlags(ts) & RPMTRANS_FLAG_TEST) rollbackOnFailure = 0;
+
     lock = rpmtsAcquireLock(ts);
     if (lock == NULL)
        return -1;      /* XXX W2DO? */
@@ -1346,6 +1397,56 @@ rpmMessage(RPMMESS_DEBUG, _("computing file dispositions\n"));
     }
 
     /* ===============================================
+     * If we were requested to rollback this transaction
+     * if an error occurs, then we need to create a 
+     * a rollback transaction.
+     */
+     if(rollbackOnFailure) {
+       rpmtransFlags xx;
+       rpmVSFlags ovsflags;
+       rpmVSFlags vsflags;
+
+       rpmMessage(RPMMESS_DEBUG, 
+           _("Creating auto-rollback transaction\n"));
+
+       rollbackTransaction = rpmtsCreate();
+
+       /* Set the verify signature flags:
+        *      - can't verify digests on repackaged packages.  Other than 
+        *        they are wrong, this will cause segfaults down stream.
+        *      - signatures are out too.
+        *      - header check are out.
+        */     
+       vsflags = rpmExpandNumeric("%{?_vsflags_erase}");
+       vsflags |= _RPMVSF_NODIGESTS;       
+       vsflags |= _RPMVSF_NOSIGNATURES;
+       vsflags |= RPMVSF_NOHDRCHK;
+       vsflags |= RPMVSF_NEEDPAYLOAD;      /* XXX no legacy signatures */
+       ovsflags = rpmtsSetVSFlags(ts, vsflags);
+
+       /* 
+        *  If we run this thing its imperitive that it be known that it
+        *  is an autorollback transaction.  This will affect the instance
+        *  counts passed to the scriptlets in the psm.
+        */
+       rpmtsSetType(rollbackTransaction, RPMTRANS_TYPE_AUTOROLLBACK);
+
+       /* Set transaction flags to be the same as the running transaction */
+       xx = rpmtsSetFlags(rollbackTransaction, rpmtsFlags(ts)); 
+
+       /* Set root dir to be the same as the running transaction */
+       rpmtsSetRootDir(rollbackTransaction, rpmtsRootDir(ts));
+    
+       /* Setup the notify of the call back to be the same as the running
+        * transaction 
+        */ 
+       xx = rpmtsSetNotifyCallback(rollbackTransaction, ts->notify, ts->notifyData);
+
+       /* Create rpmtsScore for running transaction and rollback transaction */
+       xx = rpmtsScoreInit(ts, rollbackTransaction);
+     }
+
+    /* ===============================================
      * Save removed files before erasing.
      */
     if (rpmtsFlags(ts) & (RPMTRANS_FLAG_DIRSTASH | RPMTRANS_FLAG_REPACKAGE)) {
@@ -1454,6 +1555,23 @@ assert(psm != NULL);
                        /*@=noeffectuncon@*/
                        p->fd = NULL;
                        ourrc++;
+
+                       /* If we should rollback this transaction 
+                          on failure, lets do it.                 */
+                       if(rollbackOnFailure) {
+                           rpmMessage(RPMMESS_ERROR, 
+                               _("Add failed.  Could not read package header.\n"));
+                           /* Clean up the current transaction */
+                           p->h = headerFree(p->h);
+                           xx = rpmdbSync(rpmtsGetRdb(ts));
+                           psm = rpmpsmFree(psm);
+                           p->fi = rpmfiFree(p->fi);
+                           pi = rpmtsiFree(pi);
+
+                           /* Run the rollback transaction */
+                           xx = _rpmtsRollback(rollbackTransaction);
+                           return -1;
+                       }
                        /*@innerbreak@*/ break;
                    case RPMRC_NOTTRUSTED:
                    case RPMRC_NOKEY:
@@ -1501,11 +1619,67 @@ assert(psm != NULL);
                if (rpmpsmStage(psm, PSM_PKGINSTALL)) {
                    ourrc++;
                    lastFailKey = pkgKey;
+                   
+                   /* If we should rollback this transaction 
+                      on failure, lets do it.                 */
+                   if(rollbackOnFailure) {
+                       rpmMessage(RPMMESS_ERROR, 
+                           _("Add failed in rpmpsmStage().\n"));
+                       /* Clean up the current transaction */
+                       p->h = headerFree(p->h);
+                       xx = rpmdbSync(rpmtsGetRdb(ts));
+                       psm = rpmpsmFree(psm);
+                       p->fi = rpmfiFree(p->fi);
+                       pi = rpmtsiFree(pi);
+
+                       /* Run the rollback transaction */
+                       xx = _rpmtsRollback(rollbackTransaction);
+                       return -1;
+                   }
+               }
+               
+               /* If we should rollback on failure lets add
+                * this element to the rollback transaction
+                * as an erase element as it has installed succesfully.
+                */
+               if(rollbackOnFailure) {
+                   int rc;
+                   rc = _rpmtsAddRollbackElement(rollbackTransaction, ts, p);
+                   if(rc != RPMRC_OK) {
+                       /* Clean up the current transaction */
+                       p->h = headerFree(p->h);
+                       xx = rpmdbSync(rpmtsGetRdb(ts));
+                       psm = rpmpsmFree(psm);
+                       p->fi = rpmfiFree(p->fi);
+                       pi = rpmtsiFree(pi);
+                       
+                       /* Clean up rollback transaction */
+                       rpmtsFree(rollbackTransaction);
+                       return -1;
+                   }
                }
 /*@=nullstate@*/
            } else {
                ourrc++;
                lastFailKey = pkgKey;
+               
+               /* If we should rollback this transaction 
+                * on failure, lets do it.                 
+                */
+               if(rollbackOnFailure) {
+                   rpmMessage(RPMMESS_ERROR, _("Add failed.  Could not get file list.\n"));
+                   /* Clean up the current transaction */
+                   p->h = headerFree(p->h);
+                   xx = rpmdbSync(rpmtsGetRdb(ts));
+                   psm = rpmpsmFree(psm);
+                   p->fi = rpmfiFree(p->fi);
+                   pi = rpmtsiFree(pi);
+
+                   /* Run the rollback transaction */
+                   xx = _rpmtsRollback(rollbackTransaction);
+                   return -1;
+               }
            }
 
            if (gotfd) {
@@ -1535,8 +1709,48 @@ assert(psm != NULL);
             * If install failed, then we shouldn't erase.
             */
            if (rpmteDependsOnKey(p) != lastFailKey) {
-               if (rpmpsmStage(psm, PSM_PKGERASE))
+               if (rpmpsmStage(psm, PSM_PKGERASE)) {
                    ourrc++;
+                   
+                   /* If we should rollback this transaction 
+                    * on failure, lets do it.                
+                    */ 
+                   if(rollbackOnFailure) {
+                       rpmMessage(RPMMESS_ERROR, 
+                           _("Erase failed failed in rpmpsmStage().\n"));
+                       /* Clean up the current transaction */
+                       xx = rpmdbSync(rpmtsGetRdb(ts));
+                       psm = rpmpsmFree(psm);
+                       p->fi = rpmfiFree(p->fi);
+                       pi = rpmtsiFree(pi);
+
+                       /* Run the rollback transaction */
+                       xx = _rpmtsRollback(rollbackTransaction);
+                       return -1;
+                   }
+               }
+
+               /* If we should rollback on failure lets add
+                * this element to the rollback transaction
+                * as an install element as it has erased succesfully.
+                */
+               if(rollbackOnFailure) {
+                   int rc;
+
+                   rc = _rpmtsAddRollbackElement(rollbackTransaction, ts, p);
+
+                   if(rc != RPMRC_OK) {
+                       /* Clean up the current transaction */
+                       xx = rpmdbSync(rpmtsGetRdb(ts));
+                       psm = rpmpsmFree(psm);
+                       p->fi = rpmfiFree(p->fi);
+                       pi = rpmtsiFree(pi);
+               
+                       /* Clean up rollback transaction */
+                       rpmtsFree(rollbackTransaction);
+                       return -1;
+                   }
+               }
            }
 
            (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_ERASE), 0);
@@ -1557,6 +1771,11 @@ assert(psm != NULL);
     /*@=branchstate@*/
     pi = rpmtsiFree(pi);
 
+    /* If we created a rollback transaction lets get rid of it */
+    if(rollbackOnFailure && rollbackTransaction != NULL) {
+       rpmtsFree(rollbackTransaction);
+    }
+
     rpmtsFreeLock(lock);
 
     /*@-nullstate@*/ /* FIX: ts->flList may be NULL */
@@ -1566,3 +1785,369 @@ assert(psm != NULL);
        return 0;
     /*@=nullstate@*/
 }
+
+/**
+ * Get the repackaged header and filename from the repackage directory.
+ * @todo Find a suitable home for this function.
+ * @todo This function creates an IDTX everytime it is called.  Needs to 
+ *       be made more efficient (only create on per running transaction).
+ * @param te           transaction element
+ * @param rpmts                rpm transaction
+ * @return hdrp                Repackaged header
+ * @return fn          Repackaged package's path (transaction key)
+ * @return             RPMRC_NOTFOUND or RPMRC_OK
+ */
+rpmRC getRepackageHeaderFromTE(rpmte te, rpmts ts, Header *hdrp, char **fn) 
+{
+    int_32 tid;
+    const char * name; 
+    const char * rpname = NULL;
+    const char * _repackage_dir = NULL;
+    const char * globStr = "-*.rpm";
+    char * rp = NULL;          /* Rollback package name */
+    IDTX rtids = NULL;
+    IDT rpIDT;
+    int nrids = 0;
+    int nb;                    /* Number of bytes */
+    Header h = NULL;
+    int rc   = RPMRC_NOTFOUND; /* Assume we do not find it*/
+  
+    rpmMessage(RPMMESS_DEBUG, 
+       _("Getting repackaged header from transaction element\n"));
+
+    /* Set header pointer to null if its not already */
+    if(hdrp)
+       *hdrp = NULL;
+    if(fn)
+       *fn = NULL;
+
+    /* Get the TID of the current transaction */
+    tid = rpmtsGetTid(ts);
+    /* Need the repackage dir if the user want to
+     * rollback on a failure.
+     */
+    _repackage_dir = rpmExpand("%{?_repackage_dir}", NULL);
+    if(_repackage_dir == NULL) goto exit;
+
+    /* Build the glob string to find the possible repackaged 
+     * packages for this package.
+     */
+    name = rpmteN(te); 
+    nb = strlen(_repackage_dir) + strlen(name) + strlen(globStr) + 2;
+    rp = memset((char *) malloc(nb), 0, nb);
+    snprintf(rp, nb, "%s/%s%s.rpm", _repackage_dir, name, globStr);
+
+    /* Get the index of possible repackaged packages */
+    rpmMessage(RPMMESS_DEBUG, _("\tLooking for %s...\n"), rp);
+    rtids = IDTXglob(ts, rp, RPMTAG_REMOVETID);
+    rp = _free(rp);
+    if (rtids != NULL) {
+       rpmMessage(RPMMESS_DEBUG, _("\tMatches found.\n"));
+       rpIDT = rtids->idt;
+       nrids = rtids->nidt;
+    } else {
+       rpmMessage(RPMMESS_DEBUG, _("\tNo matches found.\n"));
+       goto exit;
+    }
+
+    /* Now walk through index until we find the package (or we have
+     * exhausted the index.
+     */
+    do {
+       /* If index is null we have exhausted the list and need to 
+        * get out of here...the repackaged package was not found.
+        */
+       if(rpIDT == NULL) {
+           rpmMessage(RPMMESS_DEBUG, _("\tRepackaged package not found!.\n"));
+           break;
+       }
+
+       /* Is this the same tid.  If not decrement the list and continue */
+       if(rpIDT->val.u32 != tid) {
+           nrids--;
+           if(nrids > 0)
+               rpIDT++;
+           else
+               rpIDT = NULL;
+           continue;
+       }
+
+       /* OK, the tid matches.  Now lets see if the name is the same.
+        * If I could not get the name from the package, I will go onto
+        * the next one.  Perhaps I should return an error at this
+        * point, but if this was not the correct one, at least the correct one
+        * would be found.
+        * XXX:  Should I be matching name and arch?
+        */
+       rpmMessage(RPMMESS_DEBUG, _("\tREMOVETID matched INSTALLTID.\n"));
+       if(headerGetEntry(rpIDT->h, RPMTAG_NAME, NULL, (void **) &rpname, NULL)) {
+           rpmMessage(RPMMESS_DEBUG, _("\t\tName:  %s.\n"), rpname);
+           if(!strcmp(name,rpname)) {
+               /* It matched we have a canidate */
+               h  = headerLink(rpIDT->h);
+               nb = strlen(rpIDT->key) + 1;
+               rp = memset((char *) malloc(nb), 0, nb);
+               rp = strncat(rp, rpIDT->key, nb);
+               rc = RPMRC_OK;
+               break;
+           }
+       }
+
+       /* Decrement list */    
+       nrids--;
+       if(nrids > 0)
+           rpIDT++;
+       else
+           rpIDT = NULL;
+    } while(1);
+exit:
+    if(rc != RPMRC_NOTFOUND && h != NULL && hdrp != NULL) {
+       rpmMessage(RPMMESS_DEBUG, _("\tRepackaged Package was %s...\n"), rp); 
+       *hdrp = headerLink(h);
+       *fn   = rp;
+    }
+    if(h != NULL) {
+       h = headerFree(h);
+    }
+    rtids = IDTXfree(rtids); 
+    return rc; 
+}
+
+/**
+ * This is not a generalized function to be called from outside 
+ * librpm.  It is called internally by rpmtsRun() to add elements
+ * to its rollback transaction.
+ * @param rollbackTransaction          rollback transaction
+ * @param runningTransaction           running transaction (the one you want to rollback)
+ * @param te                           Transaction element.
+ * @return                             RPMRC_OK, or RPMRC_FAIL
+ */
+rpmRC _rpmtsAddRollbackElement(rpmts rollbackTransaction, rpmts runningTransaction, rpmte te)
+{
+    Header h   = NULL;
+    Header rph = NULL;
+    char * rpn;        
+    unsigned int db_instance = 0;
+    rpmtsi pi;         
+    rpmte p;
+    int rc  = RPMRC_FAIL;      /* Assume Failure */
+    switch(rpmteType(te)) {
+    case TR_ADDED:
+       rpmMessage(RPMMESS_DEBUG, 
+           _("Adding install element to auto-rollback transaction.\n"));
+    
+       /* Get the header for this package from the database 
+        * First get the database instance (the key).
+        */
+       db_instance = rpmteDBInstance(te);
+       if(db_instance <= 0) {
+           /* Could not get the db instance: WTD! */
+           rpmMessage(RPMMESS_FATALERROR, 
+               _("Could not get install element database instance!\n"));
+           break;
+       }
+
+       /* Now suck the header out of the database */
+       rpmdbMatchIterator mi = rpmtsInitIterator(rollbackTransaction, 
+           RPMDBI_PACKAGES, &db_instance, sizeof(db_instance));
+       h = rpmdbNextIterator(mi);
+       if(h != NULL) h = headerLink(h); 
+       mi = rpmdbFreeIterator(mi);
+       if(h == NULL) {
+           /* Header was not there??? */
+           rpmMessage(RPMMESS_FATALERROR, 
+               _("Could not get header for auto-rollback transaction!\n"));
+           break;
+       }
+                  
+       /* Now see if there is a repackaged package for this */
+       rc = getRepackageHeaderFromTE(te, runningTransaction, &rph, &rpn);
+       switch(rc) {
+       case RPMRC_OK:
+           /* Add the install element, as we had a repackaged package */
+           rpmMessage(RPMMESS_DEBUG, 
+               _("\tAdded repackaged package header: %s.\n"), rpn);
+           rc = rpmtsAddInstallElement(rollbackTransaction, headerLink(rph), 
+               (fnpyKey) rpn, 1, te->relocs);
+           break;
+
+       case RPMRC_NOTFOUND:
+           /* Add the header as an erase element, we did not
+            * have a repackaged package
+            */
+           rpmMessage(RPMMESS_DEBUG, _("\tAdded erase element.\n"));
+           rc = rpmtsAddEraseElement(rollbackTransaction, h, db_instance);    
+           break;
+                       
+       default:
+           /* Not sure what to do on failure...just give up */ 
+           rpmMessage(RPMMESS_FATALERROR, 
+               _("Could not get repackaged header for auto-rollback transaction!\n"));
+           break;
+       }
+       break;
+
+   case TR_REMOVED:
+       rpmMessage(RPMMESS_DEBUG, 
+           _("Add erase element to auto-rollback transaction.\n"));
+
+       /* See if this element has already been added as an upgrade.
+        * If so we want to do nothing.
+        */
+       pi = rpmtsiInit(rollbackTransaction);
+       while ((p = rpmtsiNext(pi, TR_ADDED)) != NULL) {
+           if(rpmteType(p) == TR_ADDED) continue;
+           if(!strcmp(rpmteN(p), rpmteN(te))) {
+               rpmMessage(RPMMESS_DEBUG, _("\tFound existing upgrade element.\n"));
+               rpmMessage(RPMMESS_DEBUG, _("\tNot adding erase element for %s.\n"),
+                       rpmteN(te));
+               rc = RPMRC_OK;  
+               pi = rpmtsiFree(pi);
+               break;
+           }
+       }
+       pi = rpmtsiFree(pi);
+
+
+       /* Get the repackage header from the current transaction
+       * element.
+       */
+       rc = getRepackageHeaderFromTE(te, runningTransaction, &rph, &rpn); 
+       switch(rc) {
+       case RPMRC_OK:
+           /* Add the install element */
+           rpmMessage(RPMMESS_DEBUG, 
+               _("\tAdded repackaged package %s.\n"), rpn);
+           rc = rpmtsAddInstallElement(rollbackTransaction, rph, 
+               (fnpyKey) rpn, 1, te->relocs);    
+           if(rc != RPMRC_OK) 
+               rpmMessage(RPMMESS_FATALERROR, 
+                   _("Could not add erase element to auto-rollback transaction.\n"));
+           break;
+
+       case RPMRC_NOTFOUND:
+           /* Just did not have a repackaged package */
+           rpmMessage(RPMMESS_DEBUG, 
+               _("\tNo repackaged package...nothing to do.\n"));
+           rc = RPMRC_OK;
+           break;
+
+       default:
+           rpmMessage(RPMMESS_FATALERROR, 
+               _("Failure reading repackaged package!\n"));
+           break;
+       }
+       break;
+
+    default:
+       break;
+    }
+
+/* XXX:  I want to free this, but if I do then the consumers of
+ *       are hosed.  Just leaving you a little note Jeff, so you
+ *       know that this does introduce a memory leak.  I wanted
+ *       keep the patch as simple as possible so I am not fixxing
+ *       the leak.
+ *   if(rpn != NULL) 
+ *     free(rpn); 
+ */
+
+    /* Clean up */
+    if(h != NULL)   
+       h = headerFree(h);
+    if(rph != NULL) 
+       rph = headerFree(rph);
+    return rc;
+}
+
+/**
+ * This is not a generalized function to be called from outside 
+ * librpm.  It is called internally by rpmtsRun() to rollback
+ * a failed transaction.
+ * @param rollbackTransaction          rollback transaction
+ * @return                             RPMRC_OK, or RPMRC_FAIL
+ */
+rpmRC _rpmtsRollback(rpmts rollbackTransaction)
+{
+    int    rc         = 0;
+    int    numAdded   = 0;
+    int    numRemoved = 0;
+    int_32 tid;
+    rpmtsi tsi;
+    rpmte  te;
+    rpmps  ps;
+
+    /* 
+     * Gather information about this rollback transaction for reporting.
+     *    1) Get tid
+     */
+    tid = rpmtsGetTid(rollbackTransaction);
+    /*    
+     *    2) Get number of install elments and erase elements 
+     */
+    tsi = rpmtsiInit(rollbackTransaction);
+    while((te = rpmtsiNext(tsi, 0)) != NULL) {
+       switch (rpmteType(te)) {
+       case TR_ADDED:
+          numAdded++;
+          break;
+       case TR_REMOVED:
+          numRemoved++;
+          break;
+       default:
+          break;
+       }       
+    }
+    tsi = rpmtsiFree(tsi);
+
+    rpmMessage(RPMMESS_NORMAL, _("Transaction failed...rolling back\n"));
+    rpmMessage(RPMMESS_NORMAL, 
+       _("Rollback packages (+%d/-%d) to %-24.24s (0x%08x):\n"),
+                        numAdded, numRemoved, ctime(&tid), tid);
+
+    /* Check the transaction to see if it is doable */
+    rc = rpmtsCheck(rollbackTransaction);
+    ps = rpmtsProblems(rollbackTransaction);
+    if (rc != 0 && rpmpsNumProblems(ps) > 0) {
+       rpmMessage(RPMMESS_ERROR, _("Failed dependencies:\n"));
+       rpmpsPrint(NULL, ps);
+       ps = rpmpsFree(ps);
+       return -1;
+    }
+    ps = rpmpsFree(ps);
+
+    /* Order the transaction */
+    rc = rpmtsOrder(rollbackTransaction);
+    if (rc != 0) {
+       rpmMessage(RPMMESS_ERROR, 
+           _("Could not order auto-rollback transaction!\n"));
+       return -1;
+    }
+
+                  
+
+    /* Run the transaction and print any problems 
+     * We want to stay with the original transactions flags except
+     * that we want to add what is essentially a force.
+     * This handles two things in particular:
+     * 
+     * 1.  We we want to upgrade over a newer package.
+     *         2.  If a header for the old package is there we
+     *      we want to replace it.  No questions asked.
+     */
+    rc = rpmtsRun(rollbackTransaction, NULL, 
+         RPMPROB_FILTER_REPLACEPKG
+       | RPMPROB_FILTER_REPLACEOLDFILES
+       | RPMPROB_FILTER_REPLACENEWFILES
+       | RPMPROB_FILTER_OLDPACKAGE 
+    );
+    ps = rpmtsProblems(rollbackTransaction);
+    if (rc > 0 && rpmpsNumProblems(ps) > 0)
+       rpmpsPrint(stderr, ps);
+    ps = rpmpsFree(ps);
+    rollbackTransaction = rpmtsFree(rollbackTransaction);
+
+    return rc;
+}