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