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