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