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