Imported Upstream version 0.7.8
[platform/upstream/libsolv.git] / ext / repo_rpmdb_bdb.h
1 /*
2  * Copyright (c) 2018 SUSE Inc.
3  *
4  * This program is licensed under the BSD license, read LICENSE.BSD
5  * for further information
6  */
7
8 /*
9  * repo_rpmdb_bdb.h
10  *
11  * Use BerkeleyDB to access the rpm database
12  *
13  */
14
15
16 #if !defined(DB_CREATE) && !defined(ENABLE_RPMDB_LIBRPM)
17 # if defined(SUSE) || defined(HAVE_RPM_DB_H)
18 #  include <rpm/db.h>
19 # else
20 #  include <db.h>
21 # endif
22 #endif
23
24 #ifdef RPM5
25 # include <rpm/rpmversion.h>
26 # if RPMLIB_VERSION <  RPMLIB_VERSION_ENCODE(5,3,_,0,0,_)
27 #  define RPM_INDEX_SIZE 8      /* rpmdbid + array index */
28 # else
29 #  define RPM_INDEX_SIZE 4      /* just the rpmdbid */
30 #  define RPM5_BIG_ENDIAN_ID
31 #endif
32 #else
33 # define RPM_INDEX_SIZE 8       /* rpmdbid + array index */
34 #endif
35
36
37 /******************************************************************/
38 /*  Rpm Database stuff
39  */
40
41 struct rpmdbstate {
42   Pool *pool;
43   char *rootdir;
44
45   RpmHead *rpmhead;     /* header storage space */
46   int rpmheadsize;
47
48   int dbenvopened;      /* database environment opened */
49   int pkgdbopened;      /* package database openend */
50   int is_ostree;        /* read-only db that lives in /usr/share/rpm */
51
52   DB_ENV *dbenv;        /* database environment */
53   DB *db;               /* packages database */
54   int byteswapped;      /* endianess of packages database */
55   DBC *dbc;             /* iterator over packages database */
56 };
57
58
59 static int
60 stat_database_name(struct rpmdbstate *state, char *dbname, struct stat *statbuf, int seterror)
61 {
62   char *dbpath;
63   dbpath = solv_dupjoin(state->rootdir, state->is_ostree ? "/usr/share/rpm/" : "/var/lib/rpm/", dbname);
64   if (stat(dbpath, statbuf))
65     {
66       if (seterror)
67         pool_error(state->pool, -1, "%s: %s", dbpath, strerror(errno));
68       free(dbpath);
69       return -1;
70     }
71   free(dbpath);
72   return 0;
73 }
74
75 static int
76 stat_database(struct rpmdbstate *state, struct stat *statbuf)
77 {
78   return stat_database_name(state, "Packages", statbuf, 1);
79 }
80
81
82 static inline Id
83 db2rpmdbid(unsigned char *db, int byteswapped)
84 {
85 #ifdef RPM5_BIG_ENDIAN_ID
86   return db[0] << 24 | db[1] << 16 | db[2] << 8 | db[3];
87 #else
88 # if defined(WORDS_BIGENDIAN)
89   if (!byteswapped)
90 # else
91   if (byteswapped)
92 # endif
93     return db[0] << 24 | db[1] << 16 | db[2] << 8 | db[3];
94   else
95     return db[3] << 24 | db[2] << 16 | db[1] << 8 | db[0];
96 #endif
97 }
98
99 static inline void
100 rpmdbid2db(unsigned char *db, Id id, int byteswapped)
101 {
102 #ifdef RPM5_BIG_ENDIAN_ID
103   db[0] = id >> 24, db[1] = id >> 16, db[2] = id >> 8, db[3] = id;
104 #else
105 # if defined(WORDS_BIGENDIAN)
106   if (!byteswapped)
107 # else
108   if (byteswapped)
109 # endif
110     db[0] = id >> 24, db[1] = id >> 16, db[2] = id >> 8, db[3] = id;
111   else
112     db[3] = id >> 24, db[2] = id >> 16, db[1] = id >> 8, db[0] = id;
113 #endif
114 }
115
116 #if defined(FEDORA) || defined(MAGEIA)
117 static int
118 serialize_dbenv_ops(struct rpmdbstate *state)
119 {
120   char *lpath;
121   mode_t oldmask;
122   int fd;
123   struct flock fl;
124
125   lpath = solv_dupjoin(state->rootdir, "/var/lib/rpm/.dbenv.lock", 0);
126   oldmask = umask(022);
127   fd = open(lpath, (O_RDWR|O_CREAT), 0644);
128   free(lpath);
129   umask(oldmask);
130   if (fd < 0)
131     return -1;
132   memset(&fl, 0, sizeof(fl));
133   fl.l_type = F_WRLCK;
134   fl.l_whence = SEEK_SET;
135   for (;;)
136     {
137       if (fcntl(fd, F_SETLKW, &fl) != -1)
138         return fd;
139       if (errno != EINTR)
140         break;
141     }
142   close(fd);
143   return -1;
144 }
145
146 #endif
147
148 /* should look in /usr/lib/rpm/macros instead, but we want speed... */
149 static int
150 opendbenv(struct rpmdbstate *state)
151 {
152   const char *rootdir = state->rootdir;
153   char *dbpath;
154   DB_ENV *dbenv = 0;
155   int r;
156
157   if (db_env_create(&dbenv, 0))
158     return pool_error(state->pool, 0, "db_env_create: %s", strerror(errno));
159 #if (defined(FEDORA) || defined(MAGEIA)) && (DB_VERSION_MAJOR >= 5 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 5))
160   dbenv->set_thread_count(dbenv, 8);
161 #endif
162   dbpath = solv_dupjoin(rootdir, "/var/lib/rpm", 0);
163   if (access(dbpath, W_OK) == -1)
164     {
165       free(dbpath);
166       dbpath = solv_dupjoin(rootdir, "/usr/share/rpm/Packages", 0);
167       if (access(dbpath, R_OK) == 0)
168         state->is_ostree = 1;
169       free(dbpath);
170       dbpath = solv_dupjoin(rootdir, state->is_ostree ? "/usr/share/rpm" : "/var/lib/rpm", 0);
171       r = dbenv->open(dbenv, dbpath, DB_CREATE|DB_PRIVATE|DB_INIT_MPOOL, 0);
172     }
173   else
174     {
175 #if defined(FEDORA) || defined(MAGEIA)
176       int serialize_fd = serialize_dbenv_ops(state);
177       int eflags = DB_CREATE|DB_INIT_CDB|DB_INIT_MPOOL;
178       r = dbenv->open(dbenv, dbpath, eflags, 0644);
179       /* see rpm commit 2822ccbcdf3e898b960fafb23c4d571e26cef0a4 */
180       if (r == DB_VERSION_MISMATCH)
181         {
182           eflags |= DB_PRIVATE;
183           dbenv->errx(dbenv, "warning: DB_VERSION_MISMATCH, retrying with DB_PRIVATE");
184           r = dbenv->open(dbenv, dbpath, eflags, 0644);
185         }
186       if (serialize_fd >= 0)
187         close(serialize_fd);
188 #else
189       r = dbenv->open(dbenv, dbpath, DB_CREATE|DB_PRIVATE|DB_INIT_MPOOL, 0);
190 #endif
191     }
192   if (r)
193     {
194       pool_error(state->pool, 0, "dbenv->open: %s", strerror(errno));
195       free(dbpath);
196       dbenv->close(dbenv, 0);
197       return 0;
198     }
199   free(dbpath);
200   state->dbenv = dbenv;
201   state->dbenvopened = 1;
202   return 1;
203 }
204
205 static void
206 closedbenv(struct rpmdbstate *state)
207 {
208 #if defined(FEDORA) || defined(MAGEIA)
209   uint32_t eflags = 0;
210 #endif
211
212   if (!state->dbenv)
213     return;
214 #if defined(FEDORA) || defined(MAGEIA)
215   (void)state->dbenv->get_open_flags(state->dbenv, &eflags);
216   if (!(eflags & DB_PRIVATE))
217     {
218       int serialize_fd = serialize_dbenv_ops(state);
219       state->dbenv->close(state->dbenv, 0);
220       if (serialize_fd >= 0)
221         close(serialize_fd);
222     }
223   else
224     state->dbenv->close(state->dbenv, 0);
225 #else
226   state->dbenv->close(state->dbenv, 0);
227 #endif
228   state->dbenv = 0;
229   state->dbenvopened = 0;
230 }
231
232 static int
233 openpkgdb(struct rpmdbstate *state)
234 {
235   if (state->pkgdbopened)
236     return state->pkgdbopened > 0 ? 1 : 0;
237   state->pkgdbopened = -1;
238   if (state->dbenvopened != 1 && !opendbenv(state))
239     return 0;
240   if (db_create(&state->db, state->dbenv, 0))
241     {
242       pool_error(state->pool, 0, "db_create: %s", strerror(errno));
243       state->db = 0;
244       closedbenv(state);
245       return 0;
246     }
247   if (state->db->open(state->db, 0, "Packages", 0, DB_UNKNOWN, DB_RDONLY, 0664))
248     {
249       pool_error(state->pool, 0, "db->open Packages: %s", strerror(errno));
250       state->db->close(state->db, 0);
251       state->db = 0;
252       closedbenv(state);
253       return 0;
254     }
255   if (state->db->get_byteswapped(state->db, &state->byteswapped))
256     {
257       pool_error(state->pool, 0, "db->get_byteswapped: %s", strerror(errno));
258       state->db->close(state->db, 0);
259       state->db = 0;
260       closedbenv(state);
261       return 0;
262     }
263   state->pkgdbopened = 1;
264   return 1;
265 }
266
267 static void
268 closepkgdb(struct rpmdbstate *state)
269 {
270   if (!state->db)
271     return;
272   state->db->close(state->db, 0);
273   state->db = 0;
274   state->pkgdbopened = 0;
275 }
276
277 /* get the rpmdbids of all installed packages from the Name index database.
278  * This is much faster then querying the big Packages database */
279 static struct rpmdbentry *
280 getinstalledrpmdbids(struct rpmdbstate *state, const char *index, const char *match, int *nentriesp, char **namedatap, int keep_gpg_pubkey)
281 {
282   DB_ENV *dbenv = 0;
283   DB *db = 0;
284   DBC *dbc = 0;
285   int byteswapped;
286   DBT dbkey;
287   DBT dbdata;
288   unsigned char *dp;
289   int dl;
290   Id nameoff;
291
292   char *namedata = 0;
293   int namedatal = 0;
294   struct rpmdbentry *entries = 0;
295   int nentries = 0;
296
297   *nentriesp = 0;
298   if (namedatap)
299     *namedatap = 0;
300
301   if (state->dbenvopened != 1 && !opendbenv(state))
302     return 0;
303   dbenv = state->dbenv;
304   if (db_create(&db, dbenv, 0))
305     {
306       pool_error(state->pool, 0, "db_create: %s", strerror(errno));
307       return 0;
308     }
309   if (db->open(db, 0, index, 0, DB_UNKNOWN, DB_RDONLY, 0664))
310     {
311       pool_error(state->pool, 0, "db->open %s: %s", index, strerror(errno));
312       db->close(db, 0);
313       return 0;
314     }
315   if (db->get_byteswapped(db, &byteswapped))
316     {
317       pool_error(state->pool, 0, "db->get_byteswapped: %s", strerror(errno));
318       db->close(db, 0);
319       return 0;
320     }
321   if (db->cursor(db, NULL, &dbc, 0))
322     {
323       pool_error(state->pool, 0, "db->cursor: %s", strerror(errno));
324       db->close(db, 0);
325       return 0;
326     }
327   memset(&dbkey, 0, sizeof(dbkey));
328   memset(&dbdata, 0, sizeof(dbdata));
329   if (match)
330     {
331       dbkey.data = (void *)match;
332       dbkey.size = strlen(match);
333     }
334   while (dbc->c_get(dbc, &dbkey, &dbdata, match ? DB_SET : DB_NEXT) == 0)
335     {
336       if (!match && !keep_gpg_pubkey && dbkey.size == 10 && !memcmp(dbkey.data, "gpg-pubkey", 10))
337         continue;
338       dl = dbdata.size;
339       dp = dbdata.data;
340       nameoff = namedatal;
341       if (namedatap)
342         {
343           namedata = solv_extend(namedata, namedatal, dbkey.size + 1, 1, NAMEDATA_BLOCK);
344           memcpy(namedata + namedatal, dbkey.data, dbkey.size);
345           namedata[namedatal + dbkey.size] = 0;
346           namedatal += dbkey.size + 1;
347         }
348       while(dl >= RPM_INDEX_SIZE)
349         {
350           entries = solv_extend(entries, nentries, 1, sizeof(*entries), ENTRIES_BLOCK);
351           entries[nentries].rpmdbid = db2rpmdbid(dp, byteswapped);
352           entries[nentries].nameoff = nameoff;
353           nentries++;
354           dp += RPM_INDEX_SIZE;
355           dl -= RPM_INDEX_SIZE;
356         }
357       if (match)
358         break;
359     }
360   dbc->c_close(dbc);
361   db->close(db, 0);
362   /* make sure that enteries is != 0 if there was no error */
363   if (!entries)
364     entries = solv_extend(entries, 1, 1, sizeof(*entries), ENTRIES_BLOCK);
365   *nentriesp = nentries;
366   if (namedatap)
367     *namedatap = namedata;
368   return entries;
369 }
370
371 /* common code, return dbid on success, -1 on error */
372 static int
373 getrpm_dbdata(struct rpmdbstate *state, DBT *dbdata, int dbid)
374 {
375   unsigned int dsize, cnt, l;
376   RpmHead *rpmhead;
377
378   if (dbdata->size < 8)
379     return pool_error(state->pool, -1, "corrupt rpm database (size)");
380   cnt = getu32((const unsigned char *)dbdata->data);
381   dsize = getu32((const unsigned char *)dbdata->data + 4);
382   if (cnt >= MAX_HDR_CNT || dsize >= MAX_HDR_DSIZE)
383     return pool_error(state->pool, -1, "corrupt rpm database (cnt/dcnt)");
384   l = cnt * 16 + dsize;
385   if (8 + l > dbdata->size)
386     return pool_error(state->pool, -1, "corrupt rpm database (data size)");
387   if (l + 1 > state->rpmheadsize)
388     {
389       state->rpmheadsize = l + 128;
390       state->rpmhead = solv_realloc(state->rpmhead, sizeof(*rpmhead) + state->rpmheadsize);
391     }
392   rpmhead = state->rpmhead;
393   rpmhead->cnt = cnt;
394   rpmhead->dcnt = dsize;
395   memcpy(rpmhead->data, (unsigned char *)dbdata->data + 8, l);
396   rpmhead->data[l] = 0;
397   rpmhead->dp = rpmhead->data + cnt * 16;
398   return dbid;
399 }
400
401 /* retrive header by rpmdbid, returns 0 if not found, -1 on error */
402 static int
403 getrpm_dbid(struct rpmdbstate *state, Id dbid)
404 {
405   unsigned char buf[4];
406   DBT dbkey;
407   DBT dbdata;
408
409   if (dbid <= 0)
410     return pool_error(state->pool, -1, "illegal rpmdbid %d", dbid);
411   if (state->pkgdbopened != 1 && !openpkgdb(state))
412     return -1;
413   rpmdbid2db(buf, dbid, state->byteswapped);
414   memset(&dbkey, 0, sizeof(dbkey));
415   memset(&dbdata, 0, sizeof(dbdata));
416   dbkey.data = buf;
417   dbkey.size = 4;
418   dbdata.data = 0;
419   dbdata.size = 0;
420   if (state->db->get(state->db, NULL, &dbkey, &dbdata, 0))
421     return 0;
422   return getrpm_dbdata(state, &dbdata, dbid);
423 }
424
425 static int
426 count_headers(struct rpmdbstate *state)
427 {
428   Pool *pool = state->pool;
429   struct stat statbuf;
430   DB *db = 0;
431   DBC *dbc = 0;
432   int count = 0;
433   DBT dbkey;
434   DBT dbdata;
435
436   if (stat_database_name(state, "Name", &statbuf, 0))
437     return 0;
438   memset(&dbkey, 0, sizeof(dbkey));
439   memset(&dbdata, 0, sizeof(dbdata));
440   if (db_create(&db, state->dbenv, 0))
441     {
442       pool_error(pool, 0, "db_create: %s", strerror(errno));
443       return 0;
444     }
445   if (db->open(db, 0, "Name", 0, DB_UNKNOWN, DB_RDONLY, 0664))
446     {
447       pool_error(pool, 0, "db->open Name: %s", strerror(errno));
448       db->close(db, 0);
449       return 0;
450     }
451   if (db->cursor(db, NULL, &dbc, 0))
452     {
453       db->close(db, 0);
454       pool_error(pool, 0, "db->cursor: %s", strerror(errno));
455       return 0;
456     }
457   while (dbc->c_get(dbc, &dbkey, &dbdata, DB_NEXT) == 0)
458     count += dbdata.size / RPM_INDEX_SIZE;
459   dbc->c_close(dbc);
460   db->close(db, 0);
461   return count;
462 }
463
464 static int
465 pkgdb_cursor_open(struct rpmdbstate *state)
466 {
467   if (state->db->cursor(state->db, NULL, &state->dbc, 0))
468     return pool_error(state->pool, -1, "db->cursor failed");
469   return 0;
470 }
471
472 static void
473 pkgdb_cursor_close(struct rpmdbstate *state)
474 {
475   state->dbc->c_close(state->dbc);
476   state->dbc = 0;
477 }
478
479 /* retrive header by berkeleydb cursor, returns 0 on EOF, -1 on error */
480 static Id
481 pkgdb_cursor_getrpm(struct rpmdbstate *state)
482 {
483   DBT dbkey;
484   DBT dbdata;
485   Id dbid;
486
487   memset(&dbkey, 0, sizeof(dbkey));
488   memset(&dbdata, 0, sizeof(dbdata));
489   while (state->dbc->c_get(state->dbc, &dbkey, &dbdata, DB_NEXT) == 0)
490     {
491       if (dbkey.size != 4)
492         return pool_error(state->pool, -1, "corrupt Packages database (key size)");
493       dbid = db2rpmdbid(dbkey.data, state->byteswapped);
494       if (dbid)         /* ignore join key */
495         return getrpm_dbdata(state, &dbdata, dbid);
496     }
497   return 0;     /* no more entries */
498 }
499