Added RPMv3 conversion
authorJiri Srain <jsrain@suse.cz>
Thu, 5 Jan 2006 11:42:30 +0000 (11:42 +0000)
committerJiri Srain <jsrain@suse.cz>
Thu, 5 Jan 2006 11:42:30 +0000 (11:42 +0000)
zypp/target/rpm/RpmDb.cc
zypp/target/rpm/RpmException.cc
zypp/target/rpm/RpmException.h
zypp/target/rpm/librpmDb.cv3.cc [new file with mode: 0644]

index bdfe4d5..9f48c21 100644 (file)
@@ -476,8 +476,7 @@ void RpmDb::internal_initDatabase( const Pathname & root_r, const Pathname & dbP
     MIL << "Found rpm3 database " << dbInfo.dbV3() << endl;
 
     if ( dbEmpty ) {
-      extern void convertV3toV4( const Pathname & v3db_r, const constlibrpmDbPtr & v4db_r );
-#warning FIXME exception handling for following function
+      extern void convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r );
       convertV3toV4( dbInfo.dbV3().path(), dbptr );
 
       // create a backup copy
@@ -858,6 +857,7 @@ void RpmDb::importPubkey( const Pathname & keyring_r, const string & keyname_r )
   }
   catch (RpmException & excpt_r)
   {
+    ZYPP_CAUGHT(excpt_r);
     close( tmpfd );
     filesystem::unlink( tmpname );
     ZYPP_RETHROW(excpt_r);
@@ -877,8 +877,15 @@ void RpmDb::importPubkey( const Pathname & keyring_r, const string & keyname_r )
   }
 
   MIL << "Exported '" << keyname_r << "' from '" << keyring_r << "' to " << tmpname << endl;
-#warning FIXME handle exception from line below
-  importPubkey( tmpname );
+  try {
+    importPubkey( tmpname );
+  }
+  catch (RpmException & excpt_r)
+  {
+    ZYPP_CAUGHT(excpt_r);
+    filesystem::unlink( tmpname );
+    ZYPP_RETHROW(excpt_r);
+  }
   filesystem::unlink( tmpname );
 }
 
index 341a4d6..c2a15a2 100644 (file)
@@ -64,10 +64,15 @@ namespace zypp
       {
         return str << "RPM database not open" << endl;
       }
-  
-      std::ostream & MediaCurlInitException::dumpOn( std::ostream & str) const
+
+      std::ostream & RpmDbConvertException::dumpOn( std::ostream & str) const
+      {
+        return str << "RPM database conversion failed" << endl;
+      }
+
+      std::ostream & RpmNullDatabaseException::dumpOn( std::ostream & str) const
       {
-        return str << "Curl init failed for: " << _url << endl;
+        return str << "NULL rpmV4 database passed as argument!" << endl;
       }
   
     /////////////////////////////////////////////////////////////////
index c5ef0cd..4d309e3 100644 (file)
@@ -190,7 +190,33 @@ namespace zypp
       private:
       };
   
-    /////////////////////////////////////////////////////////////////
+      class RpmDbConvertException : public RpmException
+      {
+      public:
+        RpmDbConvertException()
+        : RpmException()
+        {}
+        virtual ~RpmDbConvertException() throw() {};
+      protected:
+        virtual std::ostream & dumpOn( std::ostream & str ) const;
+      private:
+      };
+
+      class RpmNullDatabaseException : public RpmException
+      {
+      public:
+        RpmNullDatabaseException()
+        : RpmException()
+        {}
+        virtual ~RpmNullDatabaseException() throw() {};
+      protected:
+        virtual std::ostream & dumpOn( std::ostream & str ) const;
+      private:
+      };
+
+
+
+     /////////////////////////////////////////////////////////////////
     } // namespace rpm
   } // namespace target
 } // namespace zypp
diff --git a/zypp/target/rpm/librpmDb.cv3.cc b/zypp/target/rpm/librpmDb.cv3.cc
new file mode 100644 (file)
index 0000000..451ae09
--- /dev/null
@@ -0,0 +1,514 @@
+/*---------------------------------------------------------------------\
+|                          ____ _   __ __ ___                          |
+|                         |__  / \ / / . \ . \                         |
+|                           / / \ V /|  _/  _/                         |
+|                          / /__ | | | | | |                           |
+|                         /_____||_| |_| |_|                           |
+|                                                                      |
+\---------------------------------------------------------------------*/
+/** \file zypp/target/rpm/librpmDb.cv3.cc
+ *
+*/
+#include "librpm.h"
+
+#include <iostream>
+
+#include "zypp/base/Logger.h"
+
+#include "zypp/target/rpm/librpmDb.h"
+#include "zypp/target/rpm/RpmCallbacks.h"
+
+extern "C" {
+#include <string.h>
+
+#define FA_MAGIC      0x02050920
+
+  struct faFileHeader{
+    unsigned int magic;
+    unsigned int firstFree;
+  };
+
+  struct faHeader {
+    unsigned int size;
+    unsigned int freeNext; /* offset of the next free block, 0 if none */
+    unsigned int freePrev;
+    unsigned int isFree;
+
+    /* note that the u16's appear last for alignment/space reasons */
+  };
+}
+
+namespace zypp {
+  namespace target {
+    namespace rpm {
+
+static int fadFileSize;
+
+static ssize_t Pread(FD_t fd, void * buf, size_t count, off_t offset) {
+    if (Fseek(fd, offset, SEEK_SET) < 0)
+        return -1;
+    return Fread(buf, sizeof(char), count, fd);
+}
+
+static FD_t fadOpen(const char * path)
+{
+    struct faFileHeader newHdr;
+    FD_t fd;
+    struct stat stb;
+
+    fd = Fopen(path, "r.fdio");
+    if (!fd || Ferror(fd))
+       return NULL;
+
+    if (fstat(Fileno(fd), &stb)) {
+       Fclose(fd);
+        return NULL;
+    }
+    fadFileSize = stb.st_size;
+
+    /* is this file brand new? */
+    if (fadFileSize == 0) {
+       Fclose(fd);
+       return NULL;
+    }
+    if (Pread(fd, &newHdr, sizeof(newHdr), 0) != sizeof(newHdr)) {
+       Fclose(fd);
+       return NULL;
+    }
+    if (newHdr.magic != FA_MAGIC) {
+       Fclose(fd);
+       return NULL;
+    }
+    /*@-refcounttrans@*/ return fd /*@=refcounttrans@*/ ;
+}
+
+static int fadNextOffset(FD_t fd, unsigned int lastOffset)
+{
+    struct faHeader header;
+    int offset;
+
+    offset = (lastOffset)
+       ? (lastOffset - sizeof(header))
+       : sizeof(struct faFileHeader);
+
+    if (offset >= fadFileSize)
+       return 0;
+
+    if (Pread(fd, &header, sizeof(header), offset) != sizeof(header))
+       return 0;
+
+    if (!lastOffset && !header.isFree)
+       return (offset + sizeof(header));
+
+    do {
+       offset += header.size;
+
+       if (Pread(fd, &header, sizeof(header), offset) != sizeof(header))
+           return 0;
+
+       if (!header.isFree) break;
+    } while (offset < fadFileSize && header.isFree);
+
+    if (offset < fadFileSize) {
+       /* Sanity check this to make sure we're not going in loops */
+       offset += sizeof(header);
+
+       if (offset < 0 || (unsigned)offset <= lastOffset) return -1;
+
+       return offset;
+    } else
+       return 0;
+}
+
+static int fadFirstOffset(FD_t fd)
+{
+    return fadNextOffset(fd, 0);
+}
+
+/*@-boundsread@*/
+static int dncmp(const void * a, const void * b)
+       /*@*/
+{
+    const char *const * first = (const char *const *)a;
+    const char *const * second = (const char *const *)b;
+    return strcmp(*first, *second);
+}
+/*@=boundsread@*/
+
+/*@-bounds@*/
+static void compressFilelist(Header h)
+       /*@*/
+{
+    HGE_t hge = (HGE_t)headerGetEntryMinMemory;
+    HAE_t hae = (HAE_t)headerAddEntry;
+    HRE_t hre = (HRE_t)headerRemoveEntry;
+    HFD_t hfd = headerFreeData;
+    char ** fileNames;
+    const char ** dirNames;
+    const char ** baseNames;
+    int_32 * dirIndexes;
+    rpmTagType fnt;
+    int count;
+    int i, xx;
+    int dirIndex = -1;
+
+    /*
+     * This assumes the file list is already sorted, and begins with a
+     * single '/'. That assumption isn't critical, but it makes things go
+     * a bit faster.
+     */
+
+    if (headerIsEntry(h, RPMTAG_DIRNAMES)) {
+       xx = hre(h, RPMTAG_OLDFILENAMES);
+       return;         /* Already converted. */
+    }
+
+    void *hgePtr = NULL;
+    if (!hge(h, RPMTAG_OLDFILENAMES, &fnt, &hgePtr, &count))
+       return;         /* no file list */
+    fileNames = (char **)hgePtr;
+    if (fileNames == NULL || count <= 0)
+       return;
+
+    dirNames = (const char **)alloca(sizeof(*dirNames) * count);       /* worst case */
+    baseNames = (const char **)alloca(sizeof(*dirNames) * count);
+    dirIndexes = (int_32 *)alloca(sizeof(*dirIndexes) * count);
+
+    if (fileNames[0][0] != '/') {
+       /* HACK. Source RPM, so just do things differently */
+       dirIndex = 0;
+       dirNames[dirIndex] = "";
+       for (i = 0; i < count; i++) {
+           dirIndexes[i] = dirIndex;
+           baseNames[i] = fileNames[i];
+       }
+       goto exit;
+    }
+
+    /*@-branchstate@*/
+    for (i = 0; i < count; i++) {
+       const char ** needle;
+       char savechar;
+       char * baseName;
+       int len;
+
+       if (fileNames[i] == NULL)       /* XXX can't happen */
+           continue;
+       baseName = strrchr(fileNames[i], '/') + 1;
+       len = baseName - fileNames[i];
+       needle = dirNames;
+       savechar = *baseName;
+       *baseName = '\0';
+/*@-compdef@*/
+       if (dirIndex < 0 ||
+           (needle = (const char **)bsearch(&fileNames[i], dirNames, dirIndex + 1, sizeof(dirNames[0]), dncmp)) == NULL) {
+           char *s = (char *)alloca(len + 1);
+           memcpy(s, fileNames[i], len + 1);
+           s[len] = '\0';
+           dirIndexes[i] = ++dirIndex;
+           dirNames[dirIndex] = s;
+       } else
+           dirIndexes[i] = needle - dirNames;
+/*@=compdef@*/
+
+       *baseName = savechar;
+       baseNames[i] = baseName;
+    }
+    /*@=branchstate@*/
+
+exit:
+    if (count > 0) {
+       xx = hae(h, RPMTAG_DIRINDEXES, RPM_INT32_TYPE, dirIndexes, count);
+       xx = hae(h, RPMTAG_BASENAMES, RPM_STRING_ARRAY_TYPE,
+                       baseNames, count);
+       xx = hae(h, RPMTAG_DIRNAMES, RPM_STRING_ARRAY_TYPE,
+                       dirNames, dirIndex + 1);
+    }
+
+    fileNames = (char**)hfd(fileNames, fnt);
+
+    xx = hre(h, RPMTAG_OLDFILENAMES);
+}
+/*@=bounds@*/
+
+/*
+ * Up to rpm 3.0.4, packages implicitly provided their own name-version-release.
+ * Retrofit an explicit "Provides: name = epoch:version-release".
+ */
+void providePackageNVR(Header h)
+{
+    HGE_t hge = (HGE_t)headerGetEntryMinMemory;
+    HFD_t hfd = headerFreeData;
+    const char *name, *version, *release;
+    void *hgePtr = NULL;
+    int_32 * epoch;
+    const char *pEVR;
+    char *p;
+    int_32 pFlags = RPMSENSE_EQUAL;
+    const char ** provides = NULL;
+    const char ** providesEVR = NULL;
+    rpmTagType pnt, pvt;
+    int_32 * provideFlags = NULL;
+    int providesCount;
+    int i, xx;
+    int bingo = 1;
+
+    /* Generate provides for this package name-version-release. */
+    xx = headerNVR(h, &name, &version, &release);
+    if (!(name && version && release))
+        return;
+    pEVR = p = (char *)alloca(21 + strlen(version) + 1 + strlen(release) + 1);
+    *p = '\0';
+    if (hge(h, RPMTAG_EPOCH, NULL, &hgePtr, NULL)) {
+       epoch = (int_32 *)hgePtr;
+        sprintf(p, "%d:", *epoch);
+        while (*p != '\0')
+            p++;
+    }
+    (void) stpcpy( stpcpy( stpcpy(p, version) , "-") , release);
+
+    /*
+     * Rpm prior to 3.0.3 does not have versioned provides.
+     * If no provides at all are available, we can just add.
+     */
+    if (!hge(h, RPMTAG_PROVIDENAME, &pnt, &hgePtr, &providesCount))
+        goto exit;
+    provides = (const char **)hgePtr;
+
+    /*
+     * Otherwise, fill in entries on legacy packages.
+     */
+    if (!hge(h, RPMTAG_PROVIDEVERSION, &pvt, &hgePtr, NULL)) {
+       providesEVR = (const char **)hgePtr;
+        for (i = 0; i < providesCount; i++) {
+            char * vdummy = "";
+            int_32 fdummy = RPMSENSE_ANY;
+            xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY_TYPE,
+                        &vdummy, 1);
+            xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEFLAGS, RPM_INT32_TYPE,
+                        &fdummy, 1);
+        }
+        goto exit;
+    }
+
+    xx = hge(h, RPMTAG_PROVIDEFLAGS, NULL, &hgePtr, NULL);
+    provideFlags = (int_32 *)hgePtr;
+
+    /*@-nullderef@*/    /* LCL: providesEVR is not NULL */
+    if (provides && providesEVR && provideFlags)
+    for (i = 0; i < providesCount; i++) {
+        if (!(provides[i] && providesEVR[i]))
+            continue;
+        if (!(provideFlags[i] == RPMSENSE_EQUAL &&
+            !strcmp(name, provides[i]) && !strcmp(pEVR, providesEVR[i])))
+            continue;
+        bingo = 0;
+        break;
+    }
+    /*@=nullderef@*/
+
+exit:
+    provides = (const char **)hfd(provides, pnt);
+    providesEVR = (const char **)hfd(providesEVR, pvt);
+
+    if (bingo) {
+        xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDENAME, RPM_STRING_ARRAY_TYPE,
+                &name, 1);
+        xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEFLAGS, RPM_INT32_TYPE,
+                &pFlags, 1);
+        xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY_TYPE,
+                &pEVR, 1);
+    }
+}
+
+///////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////
+
+using namespace std;
+
+#undef Y2LOG
+#define Y2LOG "librpmDb"
+
+/******************************************************************
+**
+**
+**     FUNCTION NAME : internal_convertV3toV4
+**     FUNCTION TYPE : int
+*/
+void internal_convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r,
+                               ConvertDbReport & report )
+{
+//  Timecount _t( "convert V3 to V4" );
+  MIL << "Convert rpm3 database to rpm4" << endl;
+
+  // Check arguments
+  FD_t fd = fadOpen( v3db_r.asString().c_str() );
+  if ( fd == 0 ) {
+    Fclose( fd );
+    ZYPP_THROW(RpmDbOpenException(Pathname("/"), v3db_r));
+  }
+
+  if ( ! v4db_r ) {
+    Fclose( fd );
+    INT << "NULL rpmV4 database passed as argument!" << endl;
+    ZYPP_THROW(RpmNullDatabaseException());
+  }
+
+  shared_ptr<RpmException> err = v4db_r->error();
+  if ( err ) {
+    Fclose( fd );
+    INT << "Can't access rpmV4 database " << v4db_r << endl;
+    ZYPP_THROW(*err);
+  }
+
+  // open rpmV4 database for writing. v4db_r is ok so librpm should
+  // be properly initialized.
+  rpmdb db = 0;
+  string rootstr( v4db_r->root().asString() );
+  const char * root = ( rootstr == "/" ? NULL : rootstr.c_str() );
+
+  int res = ::rpmdbOpen( root, &db, O_RDWR, 0644 );
+  if ( res || ! db ) {
+    if ( db ) {
+      ::rpmdbClose( db );
+    }
+    Fclose( fd );
+    ZYPP_THROW(RpmDbOpenException(root, Pathname(string(db->db_root))));
+  }
+
+  // Check ammount of packages to process.
+  int max = 0;
+  for ( int offset = fadFirstOffset(fd); offset; offset = fadNextOffset(fd, offset) ) {
+    ++max;
+  }
+  MIL << "Packages in rpmV3 database " << v3db_r << ": " << max << endl;
+
+  unsigned failed      = 0;
+  unsigned ignored     = 0;
+  unsigned alreadyInV4 = 0;
+  report.progress( (100 * (failed + ignored + alreadyInV4) / max) );
+
+  if ( !max ) {
+    Fclose( fd );
+    ::rpmdbClose( db );
+    return;
+  }
+
+  // Start conversion.
+#warning Add CBSuggest handling if needed, also on lines below
+//  CBSuggest proceed;
+  bool proceed = true;
+  for ( int offset = fadFirstOffset(fd); offset && proceed /*!= CBSuggest::CANCEL*/;
+       offset = fadNextOffset(fd, offset),
+       report.progress( (100 * (failed + ignored + alreadyInV4) / max) ) )
+  {
+
+    // have to use lseek instead of Fseek because headerRead
+    // uses low level IO
+    if ( lseek( Fileno( fd ), (off_t)offset, SEEK_SET ) == -1 ) {
+      ostream * reportAs = &(ERR);
+/*      proceed = report->dbReadError( offset );
+      if ( proceed == CBSuggest::SKIP ) {
+       // ignore this error
+       ++ignored;
+       reportAs = &(WAR << "IGNORED: ");
+      } else {*/
+       // PROCEED will fail after conversion; CANCEL immediately stop loop
+       ++failed;
+//      }
+      (*reportAs) << "rpmV3 database entry: Can't seek to offset " << offset << " (errno " << errno << ")" << endl;
+      continue;
+    }
+    Header h = headerRead(fd, HEADER_MAGIC_NO);
+    if ( ! h ) {
+      ostream * reportAs = &(ERR);
+/*      proceed = report->dbReadError( offset );
+      if ( proceed == CBSuggest::SKIP ) {
+       // ignore this error
+       ++ignored;
+       reportAs = &(WAR << "IGNORED: ");
+      } else {*/
+       // PROCEED will fail after conversion; CANCEL immediately stop loop
+       ++failed;
+//      }
+      (*reportAs) << "rpmV3 database entry: No header at offset " << offset << endl;
+      continue;
+    }
+    compressFilelist(h);
+    providePackageNVR(h);
+    const char *name = 0;
+    const char *version = 0;
+    const char *release = 0;
+    headerNVR(h, &name, &version, &release);
+    string nrv( string(name) + "-" +  version + "-" + release );
+    rpmdbMatchIterator mi = rpmdbInitIterator(db, RPMTAG_NAME, name, 0);
+    rpmdbSetIteratorRE(mi, RPMTAG_VERSION, RPMMIRE_DEFAULT, version);
+    rpmdbSetIteratorRE(mi, RPMTAG_RELEASE, RPMMIRE_DEFAULT, release);
+    if (rpmdbNextIterator(mi)) {
+//      report.dbInV4( nrv );
+      WAR << "SKIP: rpmV3 database entry: " << nrv << " is already in rpmV4 database" << endl;
+      rpmdbFreeIterator(mi);
+      headerFree(h);
+      ++alreadyInV4;
+      continue;
+    }
+    rpmdbFreeIterator(mi);
+    if (rpmdbAdd(db, -1, h, 0, 0)) {
+//      report.dbWriteError( nrv );
+      proceed = false;//CBSuggest::CANCEL; // immediately stop loop
+      ++failed;
+      ERR << "rpmV4 database error: could not add " << nrv << " to rpmV4 database" << endl;
+      headerFree(h);
+      continue;
+    }
+    headerFree(h);
+  }
+
+  Fclose(fd);
+  ::rpmdbClose(db);
+
+  if ( failed ) {
+    ERR << "Convert rpm3 database to rpm4: Aborted after "
+      << alreadyInV4 << " package(s) and " << (failed+ignored) << " error(s)."
+       << endl;
+    ZYPP_THROW(RpmDbConvertException());
+  } else {
+    MIL << "Convert rpm3 database to rpm4: " << max << " package(s) processed";
+    if ( alreadyInV4 ) {
+      MIL << "; " << alreadyInV4 << " already present in rpmV4 database";
+    }
+    if ( ignored ) {
+      MIL << "; IGNORED: " << ignored << " unconverted due to error";
+    }
+    MIL << endl;
+  }
+}
+
+      /******************************************************************
+      *
+      *
+      *        FUNCTION NAME : convertV3toV4
+      *
+      * \throws RpmException
+      *
+      */
+      void convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r )
+      {
+        // report
+        ConvertDbReport report;
+        report.start();
+       try { 
+         internal_convertV3toV4( v3db_r, v4db_r, report );
+       }
+       catch (RpmException & excpt_r)
+       {
+         report.end(excpt_r);
+         ZYPP_RETHROW(excpt_r);
+       }
+        report.end();
+      }
+
+    } // namespace rpm
+  } // namespace target
+} // namespace zypp