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