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