r5232@piscolita: duncan | 2007-05-01 17:51:47 +0200
[platform/upstream/libzypp.git] / zypp / target / rpm / librpmDb.cv3.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/target/rpm/librpmDb.cv3.cc
10  *
11 */
12 #include "librpm.h"
13
14 #include <iostream>
15
16 #include "zypp/base/Logger.h"
17
18 #include "zypp/target/rpm/librpmDb.h"
19 #include "zypp/target/rpm/RpmCallbacks.h"
20 #include "zypp/ZYppCallbacks.h"
21
22 extern "C"
23 {
24 #include <string.h>
25
26 #define FA_MAGIC      0x02050920
27
28   struct faFileHeader
29   {
30     unsigned int magic;
31     unsigned int firstFree;
32   };
33
34   struct faHeader
35   {
36     unsigned int size;
37     unsigned int freeNext; /* offset of the next free block, 0 if none */
38     unsigned int freePrev;
39     unsigned int isFree;
40
41     /* note that the u16's appear last for alignment/space reasons */
42   };
43 }
44
45 namespace zypp
46 {
47 namespace target
48 {
49 namespace rpm
50 {
51
52 static int fadFileSize;
53
54 static ssize_t Pread(FD_t fd, void * buf, size_t count, off_t offset)
55 {
56   if (Fseek(fd, offset, SEEK_SET) < 0)
57     return -1;
58   return Fread(buf, sizeof(char), count, fd);
59 }
60
61 static FD_t fadOpen(const char * path)
62 {
63   struct faFileHeader newHdr;
64   FD_t fd;
65   struct stat stb;
66
67   fd = Fopen(path, "r.fdio");
68   if (!fd || Ferror(fd))
69     return NULL;
70
71   if (fstat(Fileno(fd), &stb))
72   {
73     Fclose(fd);
74     return NULL;
75   }
76   fadFileSize = stb.st_size;
77
78   /* is this file brand new? */
79   if (fadFileSize == 0)
80   {
81     Fclose(fd);
82     return NULL;
83   }
84   if (Pread(fd, &newHdr, sizeof(newHdr), 0) != sizeof(newHdr))
85   {
86     Fclose(fd);
87     return NULL;
88   }
89   if (newHdr.magic != FA_MAGIC)
90   {
91     Fclose(fd);
92     return NULL;
93   }
94   /*@-refcounttrans@*/ return fd /*@=refcounttrans@*/ ;
95 }
96
97 static int fadNextOffset(FD_t fd, unsigned int lastOffset)
98 {
99   struct faHeader header;
100   int offset;
101
102   offset = (lastOffset)
103            ? (lastOffset - sizeof(header))
104            : sizeof(struct faFileHeader);
105
106   if (offset >= fadFileSize)
107     return 0;
108
109   if (Pread(fd, &header, sizeof(header), offset) != sizeof(header))
110     return 0;
111
112   if (!lastOffset && !header.isFree)
113     return (offset + sizeof(header));
114
115   do
116   {
117     offset += header.size;
118
119     if (Pread(fd, &header, sizeof(header), offset) != sizeof(header))
120       return 0;
121
122     if (!header.isFree) break;
123   }
124   while (offset < fadFileSize && header.isFree);
125
126   if (offset < fadFileSize)
127   {
128     /* Sanity check this to make sure we're not going in loops */
129     offset += sizeof(header);
130
131     if (offset < 0 || (unsigned)offset <= lastOffset) return -1;
132
133     return offset;
134   }
135   else
136     return 0;
137 }
138
139 static int fadFirstOffset(FD_t fd)
140 {
141   return fadNextOffset(fd, 0);
142 }
143
144 /*@-boundsread@*/
145 static int dncmp(const void * a, const void * b)
146 /*@*/
147 {
148   const char *const * first = (const char *const *)a;
149   const char *const * second = (const char *const *)b;
150   return strcmp(*first, *second);
151 }
152 /*@=boundsread@*/
153
154 /*@-bounds@*/
155 static void compressFilelist(Header h)
156 /*@*/
157 {
158   HGE_t hge = (HGE_t)headerGetEntryMinMemory;
159   HAE_t hae = (HAE_t)headerAddEntry;
160   HRE_t hre = (HRE_t)headerRemoveEntry;
161   HFD_t hfd = headerFreeData;
162   char ** fileNames;
163   const char ** dirNames;
164   const char ** baseNames;
165   int_32 * dirIndexes;
166   rpmTagType fnt;
167   int count;
168   int i, xx;
169   int dirIndex = -1;
170
171   /*
172    * This assumes the file list is already sorted, and begins with a
173    * single '/'. That assumption isn't critical, but it makes things go
174    * a bit faster.
175    */
176
177   if (headerIsEntry(h, RPMTAG_DIRNAMES))
178   {
179     xx = hre(h, RPMTAG_OLDFILENAMES);
180     return;             /* Already converted. */
181   }
182
183   void *hgePtr = NULL;
184   if (!hge(h, RPMTAG_OLDFILENAMES, &fnt, &hgePtr, &count))
185     return;             /* no file list */
186   fileNames = (char **)hgePtr;
187   if (fileNames == NULL || count <= 0)
188     return;
189
190   dirNames = (const char **)alloca(sizeof(*dirNames) * count);  /* worst case */
191   baseNames = (const char **)alloca(sizeof(*dirNames) * count);
192   dirIndexes = (int_32 *)alloca(sizeof(*dirIndexes) * count);
193
194   if (fileNames[0][0] != '/')
195   {
196     /* HACK. Source RPM, so just do things differently */
197     dirIndex = 0;
198     dirNames[dirIndex] = "";
199     for (i = 0; i < count; i++)
200     {
201       dirIndexes[i] = dirIndex;
202       baseNames[i] = fileNames[i];
203     }
204     goto exit;
205   }
206
207   /*@-branchstate@*/
208   for (i = 0; i < count; i++)
209   {
210     const char ** needle;
211     char savechar;
212     char * baseName;
213     int len;
214
215     if (fileNames[i] == NULL)   /* XXX can't happen */
216       continue;
217     baseName = strrchr(fileNames[i], '/') + 1;
218     len = baseName - fileNames[i];
219     needle = dirNames;
220     savechar = *baseName;
221     *baseName = '\0';
222     /*@-compdef@*/
223     if (dirIndex < 0 ||
224         (needle = (const char **)bsearch(&fileNames[i], dirNames, dirIndex + 1, sizeof(dirNames[0]), dncmp)) == NULL)
225     {
226       char *s = (char *)alloca(len + 1);
227       memcpy(s, fileNames[i], len + 1);
228       s[len] = '\0';
229       dirIndexes[i] = ++dirIndex;
230       dirNames[dirIndex] = s;
231     }
232     else
233       dirIndexes[i] = needle - dirNames;
234     /*@=compdef@*/
235
236     *baseName = savechar;
237     baseNames[i] = baseName;
238   }
239   /*@=branchstate@*/
240
241 exit:
242   if (count > 0)
243   {
244     xx = hae(h, RPMTAG_DIRINDEXES, RPM_INT32_TYPE, dirIndexes, count);
245     xx = hae(h, RPMTAG_BASENAMES, RPM_STRING_ARRAY_TYPE,
246              baseNames, count);
247     xx = hae(h, RPMTAG_DIRNAMES, RPM_STRING_ARRAY_TYPE,
248              dirNames, dirIndex + 1);
249   }
250
251   fileNames = (char**)hfd(fileNames, fnt);
252
253   xx = hre(h, RPMTAG_OLDFILENAMES);
254 }
255 /*@=bounds@*/
256
257 /*
258  * Up to rpm 3.0.4, packages implicitly provided their own name-version-release.
259  * Retrofit an explicit "Provides: name = epoch:version-release".
260  */
261 void providePackageNVR(Header h)
262 {
263   HGE_t hge = (HGE_t)headerGetEntryMinMemory;
264   HFD_t hfd = headerFreeData;
265   const char *name, *version, *release;
266   void *hgePtr = NULL;
267   int_32 * epoch;
268   const char *pEVR;
269   char *p;
270   int_32 pFlags = RPMSENSE_EQUAL;
271   const char ** provides = NULL;
272   const char ** providesEVR = NULL;
273   rpmTagType pnt, pvt;
274   int_32 * provideFlags = NULL;
275   int providesCount;
276   int i, xx;
277   int bingo = 1;
278
279   /* Generate provides for this package name-version-release. */
280   xx = headerNVR(h, &name, &version, &release);
281   if (!(name && version && release))
282     return;
283   pEVR = p = (char *)alloca(21 + strlen(version) + 1 + strlen(release) + 1);
284   *p = '\0';
285   if (hge(h, RPMTAG_EPOCH, NULL, &hgePtr, NULL))
286   {
287     epoch = (int_32 *)hgePtr;
288     sprintf(p, "%d:", *epoch);
289     while (*p != '\0')
290       p++;
291   }
292   (void) stpcpy( stpcpy( stpcpy(p, version) , "-") , release);
293
294   /*
295    * Rpm prior to 3.0.3 does not have versioned provides.
296    * If no provides at all are available, we can just add.
297    */
298   if (!hge(h, RPMTAG_PROVIDENAME, &pnt, &hgePtr, &providesCount))
299     goto exit;
300   provides = (const char **)hgePtr;
301
302   /*
303    * Otherwise, fill in entries on legacy packages.
304    */
305   if (!hge(h, RPMTAG_PROVIDEVERSION, &pvt, &hgePtr, NULL))
306   {
307     providesEVR = (const char **)hgePtr;
308     for (i = 0; i < providesCount; i++)
309     {
310       char * vdummy = "";
311       int_32 fdummy = RPMSENSE_ANY;
312       xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY_TYPE,
313                                   &vdummy, 1);
314       xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEFLAGS, RPM_INT32_TYPE,
315                                   &fdummy, 1);
316     }
317     goto exit;
318   }
319
320   xx = hge(h, RPMTAG_PROVIDEFLAGS, NULL, &hgePtr, NULL);
321   provideFlags = (int_32 *)hgePtr;
322
323   /*@-nullderef@*/    /* LCL: providesEVR is not NULL */
324   if (provides && providesEVR && provideFlags)
325     for (i = 0; i < providesCount; i++)
326     {
327       if (!(provides[i] && providesEVR[i]))
328         continue;
329       if (!(provideFlags[i] == RPMSENSE_EQUAL &&
330             !strcmp(name, provides[i]) && !strcmp(pEVR, providesEVR[i])))
331         continue;
332       bingo = 0;
333       break;
334     }
335   /*@=nullderef@*/
336
337 exit:
338   provides = (const char **)hfd(provides, pnt);
339   providesEVR = (const char **)hfd(providesEVR, pvt);
340
341   if (bingo)
342   {
343     xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDENAME, RPM_STRING_ARRAY_TYPE,
344                                 &name, 1);
345     xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEFLAGS, RPM_INT32_TYPE,
346                                 &pFlags, 1);
347     xx = headerAddOrAppendEntry(h, RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY_TYPE,
348                                 &pEVR, 1);
349   }
350 }
351
352 ///////////////////////////////////////////////////////////////////
353 ///////////////////////////////////////////////////////////////////
354 ///////////////////////////////////////////////////////////////////
355
356 using namespace std;
357
358 #undef Y2LOG
359 #define Y2LOG "librpmDb"
360
361 /******************************************************************
362 **
363 **
364 **      FUNCTION NAME : internal_convertV3toV4
365 **      FUNCTION TYPE : int
366 */
367 void internal_convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r,
368                              callback::SendReport<ConvertDBReport> & report )
369 {
370 //  Timecount _t( "convert V3 to V4" );
371   MIL << "Convert rpm3 database to rpm4" << endl;
372
373   // Check arguments
374   FD_t fd = fadOpen( v3db_r.asString().c_str() );
375   if ( fd == 0 )
376   {
377     Fclose( fd );
378     ZYPP_THROW(RpmDbOpenException(Pathname("/"), v3db_r));
379   }
380
381   if ( ! v4db_r )
382   {
383     Fclose( fd );
384     INT << "NULL rpmV4 database passed as argument!" << endl;
385     ZYPP_THROW(RpmNullDatabaseException());
386   }
387
388   shared_ptr<RpmException> err = v4db_r->error();
389   if ( err )
390   {
391     Fclose( fd );
392     INT << "Can't access rpmV4 database " << v4db_r << endl;
393     ZYPP_THROW(*err);
394   }
395
396   // open rpmV4 database for writing. v4db_r is ok so librpm should
397   // be properly initialized.
398   rpmdb db = 0;
399   string rootstr( v4db_r->root().asString() );
400   const char * root = ( rootstr == "/" ? NULL : rootstr.c_str() );
401
402   int res = ::rpmdbOpen( root, &db, O_RDWR, 0644 );
403   if ( res || ! db )
404   {
405     if ( db )
406     {
407       ::rpmdbClose( db );
408     }
409     Fclose( fd );
410     ZYPP_THROW(RpmDbOpenException(root, Pathname(string(db->db_root))));
411   }
412
413   // Check ammount of packages to process.
414   int max = 0;
415   for ( int offset = fadFirstOffset(fd); offset; offset = fadNextOffset(fd, offset) )
416   {
417     ++max;
418   }
419   MIL << "Packages in rpmV3 database " << v3db_r << ": " << max << endl;
420
421   unsigned failed      = 0;
422   unsigned ignored     = 0;
423   unsigned alreadyInV4 = 0;
424   report->progress( (100 * (failed + ignored + alreadyInV4) / max), v3db_r );
425
426   if ( !max )
427   {
428     Fclose( fd );
429     ::rpmdbClose( db );
430     return;
431   }
432
433   // Start conversion.
434 #warning Add CBSuggest handling if needed, also on lines below
435 //  CBSuggest proceed;
436   bool proceed = true;
437   for ( int offset = fadFirstOffset(fd); offset && proceed /*!= CBSuggest::CANCEL*/;
438         offset = fadNextOffset(fd, offset),
439         report->progress( (100 * (failed + ignored + alreadyInV4) / max), v3db_r ) )
440   {
441
442     // have to use lseek instead of Fseek because headerRead
443     // uses low level IO
444     if ( lseek( Fileno( fd ), (off_t)offset, SEEK_SET ) == -1 )
445     {
446       ostream * reportAs = &(ERR);
447       /*      proceed = report->dbReadError( offset );
448             if ( proceed == CBSuggest::SKIP ) {
449         // ignore this error
450         ++ignored;
451         reportAs = &(WAR << "IGNORED: ");
452             } else {*/
453       // PROCEED will fail after conversion; CANCEL immediately stop loop
454       ++failed;
455 //      }
456       (*reportAs) << "rpmV3 database entry: Can't seek to offset " << offset << " (errno " << errno << ")" << endl;
457       continue;
458     }
459     Header h = headerRead(fd, HEADER_MAGIC_NO);
460     if ( ! h )
461     {
462       ostream * reportAs = &(ERR);
463       /*      proceed = report->dbReadError( offset );
464             if ( proceed == CBSuggest::SKIP ) {
465         // ignore this error
466         ++ignored;
467         reportAs = &(WAR << "IGNORED: ");
468             } else {*/
469       // PROCEED will fail after conversion; CANCEL immediately stop loop
470       ++failed;
471 //      }
472       (*reportAs) << "rpmV3 database entry: No header at offset " << offset << endl;
473       continue;
474     }
475     compressFilelist(h);
476     providePackageNVR(h);
477     const char *name = 0;
478     const char *version = 0;
479     const char *release = 0;
480     headerNVR(h, &name, &version, &release);
481     string nrv( string(name) + "-" +  version + "-" + release );
482     rpmdbMatchIterator mi = rpmdbInitIterator(db, RPMTAG_NAME, name, 0);
483     rpmdbSetIteratorRE(mi, RPMTAG_VERSION, RPMMIRE_DEFAULT, version);
484     rpmdbSetIteratorRE(mi, RPMTAG_RELEASE, RPMMIRE_DEFAULT, release);
485     if (rpmdbNextIterator(mi))
486     {
487 //      report.dbInV4( nrv );
488       WAR << "SKIP: rpmV3 database entry: " << nrv << " is already in rpmV4 database" << endl;
489       rpmdbFreeIterator(mi);
490       headerFree(h);
491       ++alreadyInV4;
492       continue;
493     }
494     rpmdbFreeIterator(mi);
495     if (rpmdbAdd(db, -1, h, 0, 0))
496     {
497 //      report.dbWriteError( nrv );
498       proceed = false;//CBSuggest::CANCEL; // immediately stop loop
499       ++failed;
500       ERR << "rpmV4 database error: could not add " << nrv << " to rpmV4 database" << endl;
501       headerFree(h);
502       continue;
503     }
504     headerFree(h);
505   }
506
507   Fclose(fd);
508   ::rpmdbClose(db);
509
510   if ( failed )
511   {
512     ERR << "Convert rpm3 database to rpm4: Aborted after "
513     << alreadyInV4 << " package(s) and " << (failed+ignored) << " error(s)."
514     << endl;
515     ZYPP_THROW(RpmDbConvertException());
516   }
517   else
518   {
519     MIL << "Convert rpm3 database to rpm4: " << max << " package(s) processed";
520     if ( alreadyInV4 )
521     {
522       MIL << "; " << alreadyInV4 << " already present in rpmV4 database";
523     }
524     if ( ignored )
525     {
526       MIL << "; IGNORED: " << ignored << " unconverted due to error";
527     }
528     MIL << endl;
529   }
530 }
531
532 /******************************************************************
533 *
534 *
535 *       FUNCTION NAME : convertV3toV4
536 *
537 * \throws RpmException
538 *
539 */
540 void convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r )
541 {
542   // report
543   callback::SendReport<ConvertDBReport> report;
544   report->start(v3db_r);
545   try
546   {
547     internal_convertV3toV4( v3db_r, v4db_r, report );
548   }
549   catch (RpmException & excpt_r)
550   {
551     report->finish(v3db_r, ConvertDBReport::FAILED,excpt_r.asUserString());
552     ZYPP_RETHROW(excpt_r);
553   }
554   report->finish(v3db_r, ConvertDBReport::NO_ERROR, "");
555 }
556
557 } // namespace rpm
558 } // namespace target
559 } // namespace zypp