librpmDb.cv3.cc

Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00012 #include "librpm.h"
00013 extern "C"
00014 {
00015 #ifndef _RPM_4_4_COMPAT
00016 #ifdef _RPM_5
00017 typedef rpmuint32_t rpm_count_t;
00018 #else
00019 typedef int32_t rpm_count_t;
00020 #endif
00021 #endif
00022 
00023 #if defined( _RPM_5 )
00024 #define HGEPtr_t void *
00025 #define headerGetEntryMinMemory headerGetEntry
00026 #define headerNVR(h,n,v,r) headerNEVRA(h,n,NULL,v,r,NULL)
00027 #elif defined( _RPM_4_4_COMPAT )
00028 #define HGEPtr_t void *
00029 #else
00030 #define HGEPtr_t const void *
00031 #endif
00032 }
00033 
00034 #include <iostream>
00035 
00036 #include "zypp/base/Logger.h"
00037 
00038 #include "zypp/target/rpm/librpmDb.h"
00039 #include "zypp/target/rpm/RpmCallbacks.h"
00040 #include "zypp/ZYppCallbacks.h"
00041 
00042 extern "C"
00043 {
00044 #include <string.h>
00045 
00046 #define FA_MAGIC      0x02050920
00047 
00048   struct faFileHeader
00049   {
00050     unsigned int magic;
00051     unsigned int firstFree;
00052   };
00053 
00054   struct faHeader
00055   {
00056     unsigned int size;
00057     unsigned int freeNext; /* offset of the next free block, 0 if none */
00058     unsigned int freePrev;
00059     unsigned int isFree;
00060 
00061     /* note that the u16's appear last for alignment/space reasons */
00062   };
00063 }
00064 
00065 namespace zypp
00066 {
00067 namespace target
00068 {
00069 namespace rpm
00070 {
00071 
00072 static int fadFileSize;
00073 
00074 static ssize_t Pread(FD_t fd, void * buf, size_t count, off_t offset)
00075 {
00076   if (Fseek(fd, offset, SEEK_SET) < 0)
00077     return -1;
00078   return Fread(buf, sizeof(char), count, fd);
00079 }
00080 
00081 static FD_t fadOpen(const char * path)
00082 {
00083   struct faFileHeader newHdr;
00084   FD_t fd;
00085   struct stat stb;
00086 
00087   fd = Fopen(path, "r.fdio");
00088   if (!fd || Ferror(fd))
00089     return NULL;
00090 
00091   if (fstat(Fileno(fd), &stb))
00092   {
00093     Fclose(fd);
00094     return NULL;
00095   }
00096   fadFileSize = stb.st_size;
00097 
00098   /* is this file brand new? */
00099   if (fadFileSize == 0)
00100   {
00101     Fclose(fd);
00102     return NULL;
00103   }
00104   if (Pread(fd, &newHdr, sizeof(newHdr), 0) != sizeof(newHdr))
00105   {
00106     Fclose(fd);
00107     return NULL;
00108   }
00109   if (newHdr.magic != FA_MAGIC)
00110   {
00111     Fclose(fd);
00112     return NULL;
00113   }
00114   /*@-refcounttrans@*/ return fd /*@=refcounttrans@*/ ;
00115 }
00116 
00117 static int fadNextOffset(FD_t fd, unsigned int lastOffset)
00118 {
00119   struct faHeader header;
00120   int offset;
00121 
00122   offset = (lastOffset)
00123            ? (lastOffset - sizeof(header))
00124            : sizeof(struct faFileHeader);
00125 
00126   if (offset >= fadFileSize)
00127     return 0;
00128 
00129   if (Pread(fd, &header, sizeof(header), offset) != sizeof(header))
00130     return 0;
00131 
00132   if (!lastOffset && !header.isFree)
00133     return (offset + sizeof(header));
00134 
00135   do
00136   {
00137     offset += header.size;
00138 
00139     if (Pread(fd, &header, sizeof(header), offset) != sizeof(header))
00140       return 0;
00141 
00142     if (!header.isFree) break;
00143   }
00144   while (offset < fadFileSize && header.isFree);
00145 
00146   if (offset < fadFileSize)
00147   {
00148     /* Sanity check this to make sure we're not going in loops */
00149     offset += sizeof(header);
00150 
00151     if (offset < 0 || (unsigned)offset <= lastOffset) return -1;
00152 
00153     return offset;
00154   }
00155   else
00156     return 0;
00157 }
00158 
00159 static int fadFirstOffset(FD_t fd)
00160 {
00161   return fadNextOffset(fd, 0);
00162 }
00163 
00164 /*@-boundsread@*/
00165 static int dncmp(const void * a, const void * b)
00166 /*@*/
00167 {
00168   const char *const * first = (const char *const *)a;
00169   const char *const * second = (const char *const *)b;
00170   return strcmp(*first, *second);
00171 }
00172 /*@=boundsread@*/
00173 
00174 /*@-bounds@*/
00175 static void compressFilelist(Header h)
00176 /*@*/
00177 {
00178   char ** fileNames;
00179   const char ** dirNames;
00180   const char ** baseNames;
00181   int_32 * dirIndexes;
00182   rpmTagType fnt;
00183   rpm_count_t count;
00184   int xx;
00185   int dirIndex = -1;
00186 
00187   /*
00188    * This assumes the file list is already sorted, and begins with a
00189    * single '/'. That assumption isn't critical, but it makes things go
00190    * a bit faster.
00191    */
00192 
00193   if (headerIsEntry(h, RPMTAG_DIRNAMES))
00194   {
00195     xx = headerRemoveEntry(h, RPMTAG_OLDFILENAMES);
00196     return;             /* Already converted. */
00197   }
00198 
00199   HGEPtr_t hgePtr = NULL;
00200   if (!headerGetEntryMinMemory(h, RPMTAG_OLDFILENAMES, hTYP_t(&fnt), &hgePtr, &count))
00201     return;             /* no file list */
00202   fileNames = (char **)hgePtr;
00203   if (fileNames == NULL || count <= 0)
00204     return;
00205 
00206   dirNames = (const char **)alloca(sizeof(*dirNames) * count);  /* worst case */
00207   baseNames = (const char **)alloca(sizeof(*dirNames) * count);
00208   dirIndexes = (int_32 *)alloca(sizeof(*dirIndexes) * count);
00209 
00210   if (fileNames[0][0] != '/')
00211   {
00212     /* HACK. Source RPM, so just do things differently */
00213     dirIndex = 0;
00214     dirNames[dirIndex] = "";
00215     for (rpm_count_t i = 0; i < count; i++)
00216     {
00217       dirIndexes[i] = dirIndex;
00218       baseNames[i] = fileNames[i];
00219     }
00220     goto exit;
00221   }
00222 
00223   /*@-branchstate@*/
00224   for (rpm_count_t i = 0; i < count; i++)
00225   {
00226     const char ** needle;
00227     char savechar;
00228     char * baseName;
00229     int len;
00230 
00231     if (fileNames[i] == NULL)   /* XXX can't happen */
00232       continue;
00233     baseName = strrchr(fileNames[i], '/') + 1;
00234     len = baseName - fileNames[i];
00235     needle = dirNames;
00236     savechar = *baseName;
00237     *baseName = '\0';
00238     /*@-compdef@*/
00239     if (dirIndex < 0 ||
00240         (needle = (const char **)bsearch(&fileNames[i], dirNames, dirIndex + 1, sizeof(dirNames[0]), dncmp)) == NULL)
00241     {
00242       char *s = (char *)alloca(len + 1);
00243       memcpy(s, fileNames[i], len + 1);
00244       s[len] = '\0';
00245       dirIndexes[i] = ++dirIndex;
00246       dirNames[dirIndex] = s;
00247     }
00248     else
00249       dirIndexes[i] = needle - dirNames;
00250     /*@=compdef@*/
00251 
00252     *baseName = savechar;
00253     baseNames[i] = baseName;
00254   }
00255   /*@=branchstate@*/
00256 
00257 exit:
00258   if (count > 0)
00259   {
00260     xx = headerAddEntry(h, RPMTAG_DIRINDEXES, RPM_INT32_TYPE, dirIndexes, count);
00261     xx = headerAddEntry(h, RPMTAG_BASENAMES, RPM_STRING_ARRAY_TYPE,
00262              baseNames, count);
00263     xx = headerAddEntry(h, RPMTAG_DIRNAMES, RPM_STRING_ARRAY_TYPE,
00264              dirNames, dirIndex + 1);
00265   }
00266 
00267   fileNames = (char**)headerFreeData(fileNames, fnt);
00268 
00269   xx = headerRemoveEntry(h, RPMTAG_OLDFILENAMES);
00270 }
00271 /*@=bounds@*/
00272 
00273 /*
00274  * Up to rpm 3.0.4, packages implicitly provided their own name-version-release.
00275  * Retrofit an explicit "Provides: name = epoch:version-release".
00276  */
00277 void providePackageNVR(Header h)
00278 {
00279   const char *name, *version, *release;
00280   HGEPtr_t hgePtr = NULL;
00281   int_32 * epoch;
00282   const char *pEVR;
00283   char *p;
00284   int_32 pFlags = RPMSENSE_EQUAL;
00285   const char ** provides = NULL;
00286   const char ** providesEVR = NULL;
00287   rpmTagType pnt, pvt;
00288   int_32 * provideFlags = NULL;
00289   rpm_count_t providesCount;
00290   int xx;
00291   int bingo = 1;
00292 
00293   /* Generate provides for this package name-version-release. */
00294   xx = headerNVR(h, &name, &version, &release);
00295   if (!(name && version && release))
00296     return;
00297   pEVR = p = (char *)alloca(21 + strlen(version) + 1 + strlen(release) + 1);
00298   *p = '\0';
00299   if (headerGetEntryMinMemory(h, RPMTAG_EPOCH, NULL, &hgePtr, NULL))
00300   {
00301     epoch = (int_32 *)hgePtr;
00302     sprintf(p, "%d:", *epoch);
00303     while (*p != '\0')
00304       p++;
00305   }
00306   (void) stpcpy( stpcpy( stpcpy(p, version) , "-") , release);
00307 
00308   /*
00309    * Rpm prior to 3.0.3 does not have versioned provides.
00310    * If no provides at all are available, we can just add.
00311    */
00312   if (!headerGetEntryMinMemory(h, RPMTAG_PROVIDENAME, hTYP_t(&pnt), &hgePtr, &providesCount))
00313     goto exit;
00314   provides = (const char **)hgePtr;
00315 
00316   /*
00317    * Otherwise, fill in entries on legacy packages.
00318    */
00319   if (!headerGetEntryMinMemory(h, RPMTAG_PROVIDEVERSION, hTYP_t(&pvt), &hgePtr, NULL))
00320   {
00321     providesEVR = (const char **)hgePtr;
00322     for (rpm_count_t i = 0; i < providesCount; i++)
00323     {
00324       const char * vdummy = "";
00325       int_32 fdummy = RPMSENSE_ANY;
00326       xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY_TYPE,
00327                                   &vdummy, 1);
00328       xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEFLAGS, RPM_INT32_TYPE,
00329                                   &fdummy, 1);
00330     }
00331     goto exit;
00332   }
00333 
00334   xx = headerGetEntryMinMemory(h, RPMTAG_PROVIDEFLAGS, NULL, &hgePtr, NULL);
00335   provideFlags = (int_32 *)hgePtr;
00336 
00337   /*@-nullderef@*/    /* LCL: providesEVR is not NULL */
00338   if (provides && providesEVR && provideFlags)
00339     for (rpm_count_t i = 0; i < providesCount; i++)
00340     {
00341       if (!(provides[i] && providesEVR[i]))
00342         continue;
00343       if (!(provideFlags[i] == RPMSENSE_EQUAL &&
00344             !strcmp(name, provides[i]) && !strcmp(pEVR, providesEVR[i])))
00345         continue;
00346       bingo = 0;
00347       break;
00348     }
00349   /*@=nullderef@*/
00350 
00351 exit:
00352   provides = (const char **)headerFreeData(provides, pnt);
00353   providesEVR = (const char **)headerFreeData(providesEVR, pvt);
00354 
00355   if (bingo)
00356   {
00357     xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDENAME, RPM_STRING_ARRAY_TYPE,
00358                                 &name, 1);
00359     xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEFLAGS, RPM_INT32_TYPE,
00360                                 &pFlags, 1);
00361     xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY_TYPE,
00362                                 &pEVR, 1);
00363   }
00364 }
00365 
00369 
00370 using namespace std;
00371 
00372 #undef Y2LOG
00373 #define Y2LOG "librpmDb"
00374 
00375 /******************************************************************
00376 **
00377 **
00378 **      FUNCTION NAME : internal_convertV3toV4
00379 **      FUNCTION TYPE : int
00380 */
00381 void internal_convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r,
00382                              callback::SendReport<ConvertDBReport> & report )
00383 {
00384 //  Timecount _t( "convert V3 to V4" );
00385   MIL << "Convert rpm3 database to rpm4" << endl;
00386 
00387   // Check arguments
00388   FD_t fd = fadOpen( v3db_r.asString().c_str() );
00389   if ( fd == 0 )
00390   {
00391     Fclose( fd );
00392     ZYPP_THROW(RpmDbOpenException(Pathname("/"), v3db_r));
00393   }
00394 
00395   if ( ! v4db_r )
00396   {
00397     Fclose( fd );
00398     INT << "NULL rpmV4 database passed as argument!" << endl;
00399     ZYPP_THROW(RpmNullDatabaseException());
00400   }
00401 
00402   shared_ptr<RpmException> err = v4db_r->error();
00403   if ( err )
00404   {
00405     Fclose( fd );
00406     INT << "Can't access rpmV4 database " << v4db_r << endl;
00407     ZYPP_THROW(*err);
00408   }
00409 
00410   // open rpmV4 database for writing. v4db_r is ok so librpm should
00411   // be properly initialized.
00412   rpmdb db = 0;
00413   string rootstr( v4db_r->root().asString() );
00414   const char * root = ( rootstr == "/" ? NULL : rootstr.c_str() );
00415 
00416   int res = ::rpmdbOpen( root, &db, O_RDWR, 0644 );
00417   if ( res || ! db )
00418   {
00419     if ( db )
00420     {
00421       ::rpmdbClose( db );
00422     }
00423     Fclose( fd );
00424     ZYPP_THROW(RpmDbOpenException(root, v4db_r->dbPath()));
00425   }
00426 
00427   // Check ammount of packages to process.
00428   int max = 0;
00429   for ( int offset = fadFirstOffset(fd); offset; offset = fadNextOffset(fd, offset) )
00430   {
00431     ++max;
00432   }
00433   MIL << "Packages in rpmV3 database " << v3db_r << ": " << max << endl;
00434 
00435   unsigned failed      = 0;
00436   unsigned ignored     = 0;
00437   unsigned alreadyInV4 = 0;
00438   report->progress( (100 * (failed + ignored + alreadyInV4) / max), v3db_r );
00439 
00440   if ( !max )
00441   {
00442     Fclose( fd );
00443     ::rpmdbClose( db );
00444     return;
00445   }
00446 
00447   // Start conversion.
00448 #warning Add CBSuggest handling if needed, also on lines below
00449 //  CBSuggest proceed;
00450   bool proceed = true;
00451   for ( int offset = fadFirstOffset(fd); offset && proceed ;
00452         offset = fadNextOffset(fd, offset),
00453         report->progress( (100 * (failed + ignored + alreadyInV4) / max), v3db_r ) )
00454   {
00455 
00456     // have to use lseek instead of Fseek because headerRead
00457     // uses low level IO
00458     if ( lseek( Fileno( fd ), (off_t)offset, SEEK_SET ) == -1 )
00459     {
00460       ostream * reportAs = &(ERR);
00461       /*      proceed = report->dbReadError( offset );
00462             if ( proceed == CBSuggest::SKIP ) {
00463         // ignore this error
00464         ++ignored;
00465         reportAs = &(WAR << "IGNORED: ");
00466             } else {*/
00467       // PROCEED will fail after conversion; CANCEL immediately stop loop
00468       ++failed;
00469 //      }
00470       (*reportAs) << "rpmV3 database entry: Can't seek to offset " << offset << " (errno " << errno << ")" << endl;
00471       continue;
00472     }
00473     Header h = headerRead(fd, HEADER_MAGIC_NO);
00474     if ( ! h )
00475     {
00476       ostream * reportAs = &(ERR);
00477       /*      proceed = report->dbReadError( offset );
00478             if ( proceed == CBSuggest::SKIP ) {
00479         // ignore this error
00480         ++ignored;
00481         reportAs = &(WAR << "IGNORED: ");
00482             } else {*/
00483       // PROCEED will fail after conversion; CANCEL immediately stop loop
00484       ++failed;
00485 //      }
00486       (*reportAs) << "rpmV3 database entry: No header at offset " << offset << endl;
00487       continue;
00488     }
00489     compressFilelist(h);
00490     providePackageNVR(h);
00491     const char *name = 0;
00492     const char *version = 0;
00493     const char *release = 0;
00494     headerNVR(h, &name, &version, &release);
00495     string nrv( string(name) + "-" +  version + "-" + release );
00496     rpmdbMatchIterator mi = rpmdbInitIterator(db, RPMTAG_NAME, name, 0);
00497     rpmdbSetIteratorRE(mi, RPMTAG_VERSION, RPMMIRE_DEFAULT, version);
00498     rpmdbSetIteratorRE(mi, RPMTAG_RELEASE, RPMMIRE_DEFAULT, release);
00499     if (rpmdbNextIterator(mi))
00500     {
00501 //      report.dbInV4( nrv );
00502       WAR << "SKIP: rpmV3 database entry: " << nrv << " is already in rpmV4 database" << endl;
00503       rpmdbFreeIterator(mi);
00504       headerFree(h);
00505       ++alreadyInV4;
00506       continue;
00507     }
00508     rpmdbFreeIterator(mi);
00509 #ifdef _RPM_5
00510     if (rpmdbAdd(db, -1, h, 0))
00511 #else
00512     if (rpmdbAdd(db, -1, h, 0, 0))
00513 #endif
00514     {
00515 //      report.dbWriteError( nrv );
00516       proceed = false;//CBSuggest::CANCEL; // immediately stop loop
00517       ++failed;
00518       ERR << "rpmV4 database error: could not add " << nrv << " to rpmV4 database" << endl;
00519       headerFree(h);
00520       continue;
00521     }
00522     headerFree(h);
00523   }
00524 
00525   Fclose(fd);
00526   ::rpmdbClose(db);
00527 
00528   if ( failed )
00529   {
00530     ERR << "Convert rpm3 database to rpm4: Aborted after "
00531     << alreadyInV4 << " package(s) and " << (failed+ignored) << " error(s)."
00532     << endl;
00533     ZYPP_THROW(RpmDbConvertException());
00534   }
00535   else
00536   {
00537     MIL << "Convert rpm3 database to rpm4: " << max << " package(s) processed";
00538     if ( alreadyInV4 )
00539     {
00540       MIL << "; " << alreadyInV4 << " already present in rpmV4 database";
00541     }
00542     if ( ignored )
00543     {
00544       MIL << "; IGNORED: " << ignored << " unconverted due to error";
00545     }
00546     MIL << endl;
00547   }
00548 }
00549 
00550 /******************************************************************
00551 *
00552 *
00553 *       FUNCTION NAME : convertV3toV4
00554 *
00555 * \throws RpmException
00556 *
00557 */
00558 void convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r )
00559 {
00560   // report
00561   callback::SendReport<ConvertDBReport> report;
00562   report->start(v3db_r);
00563   try
00564   {
00565     internal_convertV3toV4( v3db_r, v4db_r, report );
00566   }
00567   catch (RpmException & excpt_r)
00568   {
00569     report->finish(v3db_r, ConvertDBReport::FAILED,excpt_r.asUserString());
00570     ZYPP_RETHROW(excpt_r);
00571   }
00572   report->finish(v3db_r, ConvertDBReport::NO_ERROR, "");
00573 }
00574 
00575 } // namespace rpm
00576 } // namespace target
00577 } // namespace zypp

doxygen