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