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