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