1 /*
2 * Copyright (C) 2017-2018 Red Hat, Inc.
3 *
4 * Licensed under the GNU Lesser General Public License Version 2.1
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include <cstdio>
22 #include <solv/bitmap.h>
23 #include <solv/solvable.h>
24
25 #include "../hy-query-private.hpp"
26 #include "../hy-subject.h"
27 #include "../nevra.hpp"
28
29 #include "../sack/packageset.hpp"
30
31 #include "../log.hpp"
32 #include "../utils/bgettext/bgettext-lib.h"
33 #include "../utils/filesystem.hpp"
34 #include "../utils/sqlite3/Sqlite3.hpp"
35 #include "../utils/tinyformat/tinyformat.hpp"
36
37 #include "RPMItem.hpp"
38 #include "Swdb.hpp"
39 #include "Transformer.hpp"
40
41 namespace libdnf {
42
Swdb(SQLite3Ptr conn)43 Swdb::Swdb(SQLite3Ptr conn)
44 : conn{conn}
45 , autoClose(true)
46 {
47 Transformer::migrateSchema(conn);
48 }
49
Swdb(SQLite3Ptr conn,bool autoClose)50 Swdb::Swdb(SQLite3Ptr conn, bool autoClose)
51 : conn{conn}
52 , autoClose(autoClose)
53 {
54 Transformer::migrateSchema(conn);
55 }
56
Swdb(const std::string & path)57 Swdb::Swdb(const std::string &path)
58 : conn(nullptr)
59 , autoClose(true)
60 {
61 auto logger(libdnf::Log::getLogger());
62
63 if (path == ":memory:") {
64 // connect to an in-memory database as requested
65 conn = std::make_shared<SQLite3>(path);
66 Transformer::createDatabase(conn);
67 return;
68 }
69
70 bool path_exists;
71 try {
72 path_exists = pathExistsOrException(path.c_str());
73 } catch (Error & e) {
74 // pathExistsOrException() threw an exception, most likely a permission error
75 // -> use an in-memory fallback
76 conn = std::make_shared<SQLite3>(":memory:");
77 Transformer::createDatabase(conn);
78 logger->error(tfm::format(
79 "History database is not readable, using in-memory database instead: %s",
80 e.what())
81 );
82 return;
83 }
84
85 if (path_exists) {
86 if (geteuid() == 0) {
87 // database exists, running under root
88 try {
89 conn = std::make_shared<SQLite3>(path);
90 // execute an update to detect if the database is writable
91 conn->exec("BEGIN; UPDATE config SET value='test' WHERE key='test'; ROLLBACK;");
92 } catch (SQLite3::Error & ex) {
93 // root must have the database writable -> log and re-throw the exception
94 logger->error(tfm::format("History database is not writable: %s", ex.what()));
95 throw;
96 }
97 } else {
98 // database exists, running under unprivileged user
99 try {
100 conn = std::make_shared<SQLite3>(path);
101 // execute a select to detect if the database is readable
102 conn->exec("SELECT * FROM config WHERE key='test'");
103 } catch (SQLite3::Error & ex) {
104 // unpriviledged user may have insufficient permissions to open the database -> in-memory fallback
105 conn = std::make_shared<SQLite3>(":memory:");
106 Transformer::createDatabase(conn);
107 logger->error(tfm::format("History database is not readable, using in-memory database instead: %s", ex.what()));
108 }
109 }
110 } else {
111 if (geteuid() == 0) {
112 // database doesn't exist, running under root
113 // create a new database and migrate old data
114
115 // extract persistdir from path - "/var/lib/dnf/"
116 auto found = path.find_last_of("/");
117 try {
118 Transformer transformer(path.substr(0, found), path);
119 transformer.transform();
120 conn = std::make_shared<SQLite3>(path);
121 } catch (SQLite3::Error & ex) {
122 // root must have the database writable -> log and re-throw the exception
123 logger->error(tfm::format("History database cannot be created: %s", ex.what()));
124 throw;
125 }
126 } else {
127 try {
128 // database doesn't exist, running under unprivileged user
129 // connect to a new database and initialize it; old data is not migrated
130 conn = std::make_shared<SQLite3>(path);
131 Transformer::createDatabase(conn);
132 } catch (SQLite3::Error & ex) {
133 // unpriviledged user may have insufficient permissions to create the database -> in-memory fallback
134 conn = std::make_shared<SQLite3>(":memory:");
135 Transformer::createDatabase(conn);
136 logger->error(tfm::format("History database cannot be created, using in-memory database instead: %s", ex.what()));
137 }
138 }
139 }
140 Transformer::migrateSchema(conn);
141 }
142
143 void
resetDatabase()144 Swdb::resetDatabase()
145 {
146 conn->close();
147 if (pathExists(getPath().c_str())) {
148 remove(getPath().c_str());
149 }
150 conn->open();
151 Transformer::createDatabase(conn);
152 }
153
154 void
closeDatabase()155 Swdb::closeDatabase()
156 {
157 conn->close();
158 }
159
~Swdb()160 Swdb::~Swdb()
161 {
162 if (autoClose) {
163 try {
164 closeDatabase();
165 } catch(const std::exception &){}
166 }
167 }
168
169 void
initTransaction()170 Swdb::initTransaction()
171 {
172 if (transactionInProgress) {
173 throw std::logic_error(_("In progress"));
174 }
175 transactionInProgress = std::make_shared< swdb_private::Transaction >(conn);
176 itemsInProgress.clear();
177 }
178
179 int64_t
beginTransaction(int64_t dtBegin,std::string rpmdbVersionBegin,std::string cmdline,uint32_t userId,std::string comment)180 Swdb::beginTransaction(int64_t dtBegin,
181 std::string rpmdbVersionBegin,
182 std::string cmdline,
183 uint32_t userId,
184 std::string comment)
185 {
186 if (!transactionInProgress) {
187 throw std::logic_error(_("Not in progress"));
188 }
189
190 // begin transaction
191 transactionInProgress->setDtBegin(dtBegin);
192 transactionInProgress->setRpmdbVersionBegin(rpmdbVersionBegin);
193 transactionInProgress->setCmdline(cmdline);
194 transactionInProgress->setUserId(userId);
195 transactionInProgress->setComment(comment);
196 transactionInProgress->begin();
197
198 // save rpm items to map to resolve RPM callbacks
199 for (auto item : transactionInProgress->getItems()) {
200 auto transItem = item->getItem();
201 if (transItem->getItemType() != ItemType::RPM) {
202 continue;
203 }
204 auto rpmItem = std::dynamic_pointer_cast< RPMItem >(transItem);
205 itemsInProgress[rpmItem->getNEVRA()] = item;
206 }
207
208 return transactionInProgress->getId();
209 }
210
211 int64_t
endTransaction(int64_t dtEnd,std::string rpmdbVersionEnd,TransactionState state)212 Swdb::endTransaction(int64_t dtEnd, std::string rpmdbVersionEnd, TransactionState state)
213 {
214 if (!transactionInProgress) {
215 throw std::logic_error(_("Not in progress"));
216 }
217 transactionInProgress->setDtEnd(dtEnd);
218 transactionInProgress->setRpmdbVersionEnd(rpmdbVersionEnd);
219 transactionInProgress->finish(state);
220 return transactionInProgress->getId();
221 }
222
223 int64_t
closeTransaction()224 Swdb::closeTransaction()
225 {
226 if (!transactionInProgress) {
227 throw std::logic_error(_("Not in progress"));
228 }
229 int64_t result = transactionInProgress->getId();
230 transactionInProgress = std::unique_ptr< swdb_private::Transaction >(nullptr);
231 itemsInProgress.clear();
232 return result;
233 }
234
235
236 TransactionItemPtr
addItem(std::shared_ptr<Item> item,const std::string & repoid,TransactionItemAction action,TransactionItemReason reason)237 Swdb::addItem(std::shared_ptr< Item > item,
238 const std::string &repoid,
239 TransactionItemAction action,
240 TransactionItemReason reason)
241 // std::shared_ptr<TransactionItem> replacedBy)
242 {
243 if (!transactionInProgress) {
244 throw std::logic_error(_("Not in progress"));
245 }
246 // auto replacedBy = std::make_shared<TransactionItem>(nullptr);
247 return transactionInProgress->addItem(item, repoid, action, reason);
248 }
249
250 void
setItemDone(const std::string & nevra)251 Swdb::setItemDone(const std::string &nevra)
252 {
253 if (!transactionInProgress) {
254 throw std::logic_error(_("No transaction in progress"));
255 }
256 auto item = itemsInProgress[nevra];
257 item->setState(TransactionItemState::DONE);
258 item->saveState();
259 }
260
261 TransactionItemReason
resolveRPMTransactionItemReason(const std::string & name,const std::string & arch,int64_t maxTransactionId)262 Swdb::resolveRPMTransactionItemReason(const std::string &name,
263 const std::string &arch,
264 int64_t maxTransactionId)
265 {
266 // TODO:
267 // -1: latest
268 // -2: latest and lastTransaction data in memory
269 if (maxTransactionId == -2 && transactionInProgress != nullptr) {
270 for (auto i : transactionInProgress->getItems()) {
271 auto rpm = std::dynamic_pointer_cast< RPMItem >(i->getItem());
272 if (!rpm) {
273 continue;
274 }
275 if (rpm->getName() == name && rpm->getArch() == arch) {
276 return i->getReason();
277 }
278 }
279 }
280
281 return RPMItem::resolveTransactionItemReason(conn, name, arch, maxTransactionId);
282 }
283
284 const std::string
getRPMRepo(const std::string & nevra)285 Swdb::getRPMRepo(const std::string &nevra)
286 {
287 Nevra nevraObject;
288 if (!nevraObject.parse(nevra.c_str(), HY_FORM_NEVRA)) {
289 return "";
290 }
291 // TODO: hy_nevra_possibility should set epoch to 0 if epoch is not specified
292 // and HY_FORM_NEVRA is used
293 if (nevraObject.getEpoch() < 0) {
294 nevraObject.setEpoch(0);
295 }
296
297 const char *sql = R"**(
298 SELECT
299 repo.repoid as repoid
300 FROM
301 trans_item ti
302 JOIN
303 rpm USING (item_id)
304 JOIN
305 repo ON ti.repo_id == repo.id
306 WHERE
307 ti.action not in (3, 5, 7, 10)
308 AND rpm.name = ?
309 AND rpm.epoch = ?
310 AND rpm.version = ?
311 AND rpm.release = ?
312 AND rpm.arch = ?
313 ORDER BY
314 ti.id DESC
315 LIMIT 1;
316 )**";
317 // TODO: where trans.done != 0
318 SQLite3::Query query(*conn, sql);
319 query.bindv(nevraObject.getName(),
320 nevraObject.getEpoch(),
321 nevraObject.getVersion(),
322 nevraObject.getRelease(),
323 nevraObject.getArch());
324 if (query.step() == SQLite3::Statement::StepResult::ROW) {
325 auto repoid = query.get< std::string >("repoid");
326 return repoid;
327 }
328 return "";
329 }
330
331 TransactionItemPtr
getRPMTransactionItem(const std::string & nevra)332 Swdb::getRPMTransactionItem(const std::string &nevra)
333 {
334 return RPMItem::getTransactionItem(conn, nevra);
335 }
336
337 TransactionPtr
getLastTransaction()338 Swdb::getLastTransaction()
339 {
340 const char *sql = R"**(
341 SELECT
342 id
343 FROM
344 trans
345 ORDER BY
346 id DESC
347 LIMIT 1
348 )**";
349 SQLite3::Statement query(*conn, sql);
350 if (query.step() == SQLite3::Statement::StepResult::ROW) {
351 auto transId = query.get< int64_t >(0);
352 auto transaction = std::make_shared< Transaction >(conn, transId);
353 return transaction;
354 }
355 return nullptr;
356 }
357
358 std::vector< TransactionPtr >
listTransactions()359 Swdb::listTransactions()
360 {
361 const char *sql = R"**(
362 SELECT
363 id
364 FROM
365 trans
366 ORDER BY
367 id
368 )**";
369 SQLite3::Statement query(*conn, sql);
370 std::vector< TransactionPtr > result;
371 while (query.step() == SQLite3::Statement::StepResult::ROW) {
372 auto transId = query.get< int64_t >(0);
373 auto transaction = std::make_shared< Transaction >(conn, transId);
374 result.push_back(transaction);
375 }
376 return result;
377 }
378
379 void
setReleasever(std::string value)380 Swdb::setReleasever(std::string value)
381 {
382 if (!transactionInProgress) {
383 throw std::logic_error(_("Not in progress"));
384 }
385 transactionInProgress->setReleasever(value);
386 }
387
388
389 void
addConsoleOutputLine(int fileDescriptor,std::string line)390 Swdb::addConsoleOutputLine(int fileDescriptor, std::string line)
391 {
392 if (!transactionInProgress) {
393 throw std::logic_error(_("Not in progress"));
394 }
395 transactionInProgress->addConsoleOutputLine(fileDescriptor, line);
396 }
397
398 TransactionItemPtr
getCompsGroupItem(const std::string & groupid)399 Swdb::getCompsGroupItem(const std::string &groupid)
400 {
401 return CompsGroupItem::getTransactionItem(conn, groupid);
402 }
403
404 std::vector< TransactionItemPtr >
getCompsGroupItemsByPattern(const std::string & pattern)405 Swdb::getCompsGroupItemsByPattern(const std::string &pattern)
406 {
407 return CompsGroupItem::getTransactionItemsByPattern(conn, pattern);
408 }
409
410 std::vector< std::string >
getPackageCompsGroups(const std::string & packageName)411 Swdb::getPackageCompsGroups(const std::string &packageName)
412 {
413 const char *sql_all_groups = R"**(
414 SELECT DISTINCT
415 g.groupid
416 FROM
417 comps_group g
418 JOIN
419 comps_group_package p ON p.group_id = g.item_id
420 WHERE
421 p.name = ?
422 AND p.installed = 1
423 ORDER BY
424 g.groupid
425 )**";
426
427 const char *sql_trans_items = R"**(
428 SELECT
429 ti.action as action,
430 ti.reason as reason,
431 i.item_id as group_id
432 FROM
433 trans_item ti
434 JOIN
435 comps_group i USING (item_id)
436 JOIN
437 trans t ON ti.trans_id = t.id
438 WHERE
439 t.state = 1
440 AND ti.action not in (3, 5, 7)
441 AND i.groupid = ?
442 ORDER BY
443 ti.trans_id DESC
444 LIMIT 1
445 )**";
446
447 const char *sql_group_package = R"**(
448 SELECT
449 p.name
450 FROM
451 comps_group_package p
452 WHERE
453 p.group_id = ?
454 AND p.installed = 1
455 )**";
456
457 std::vector< std::string > result;
458
459 // list all relevant groups
460 SQLite3::Query query_all_groups(*conn, sql_all_groups);
461 query_all_groups.bindv(packageName);
462
463 while (query_all_groups.step() == SQLite3::Statement::StepResult::ROW) {
464 auto groupid = query_all_groups.get< std::string >("groupid");
465 SQLite3::Query query_trans_items(*conn, sql_trans_items);
466 query_trans_items.bindv(groupid);
467 if (query_trans_items.step() == SQLite3::Statement::StepResult::ROW) {
468 auto action =
469 static_cast< TransactionItemAction >(query_trans_items.get< int64_t >("action"));
470 // if the last record is group removal, skip
471 if (action == TransactionItemAction::REMOVE) {
472 continue;
473 }
474 auto groupId = query_trans_items.get< int64_t >("group_id");
475 SQLite3::Query query_group_package(*conn, sql_group_package);
476 query_group_package.bindv(groupId);
477 if (query_group_package.step() == SQLite3::Statement::StepResult::ROW) {
478 result.push_back(groupid);
479 }
480 }
481 }
482 return result;
483 }
484
485 std::vector< std::string >
getCompsGroupEnvironments(const std::string & groupId)486 Swdb::getCompsGroupEnvironments(const std::string &groupId)
487 {
488 const char *sql_all_environments = R"**(
489 SELECT DISTINCT
490 e.environmentid
491 FROM
492 comps_environment e
493 JOIN
494 comps_environment_group g ON g.environment_id = e.item_id
495 WHERE
496 g.groupid = ?
497 AND g.installed = 1
498 ORDER BY
499 e.environmentid
500 )**";
501
502 const char *sql_trans_items = R"**(
503 SELECT
504 ti.action as action,
505 ti.reason as reason,
506 i.item_id as environment_id
507 FROM
508 trans_item ti
509 JOIN
510 comps_environment i USING (item_id)
511 JOIN
512 trans t ON ti.trans_id = t.id
513 WHERE
514 t.state = 1
515 AND ti.action not in (3, 5, 7)
516 AND i.environmentid = ?
517 ORDER BY
518 ti.trans_id DESC
519 LIMIT 1
520 )**";
521
522 const char *sql_environment_group = R"**(
523 SELECT
524 g.groupid
525 FROM
526 comps_environment_group g
527 WHERE
528 g.environment_id = ?
529 AND g.installed = 1
530 )**";
531
532 std::vector< std::string > result;
533
534 // list all relevant groups
535 SQLite3::Query query_all_environments(*conn, sql_all_environments);
536 query_all_environments.bindv(groupId);
537
538 while (query_all_environments.step() == SQLite3::Statement::StepResult::ROW) {
539 auto envid = query_all_environments.get< std::string >("environmentid");
540 SQLite3::Query query_trans_items(*conn, sql_trans_items);
541 query_trans_items.bindv(envid);
542 if (query_trans_items.step() == SQLite3::Statement::StepResult::ROW) {
543 auto action =
544 static_cast< TransactionItemAction >(query_trans_items.get< int64_t >("action"));
545 // if the last record is group removal, skip
546 if (action == TransactionItemAction::REMOVE) {
547 continue;
548 }
549 auto envId = query_trans_items.get< int64_t >("environment_id");
550 SQLite3::Query query_environment_group(*conn, sql_environment_group);
551 query_environment_group.bindv(envId);
552 if (query_environment_group.step() == SQLite3::Statement::StepResult::ROW) {
553 result.push_back(envid);
554 }
555 }
556 }
557 return result;
558 }
559
560 TransactionItemPtr
getCompsEnvironmentItem(const std::string & envid)561 Swdb::getCompsEnvironmentItem(const std::string &envid)
562 {
563 return CompsEnvironmentItem::getTransactionItem(conn, envid);
564 }
565
566 std::vector< TransactionItemPtr >
getCompsEnvironmentItemsByPattern(const std::string & pattern)567 Swdb::getCompsEnvironmentItemsByPattern(const std::string &pattern)
568 {
569 return CompsEnvironmentItem::getTransactionItemsByPattern(conn, pattern);
570 }
571
572 RPMItemPtr
createRPMItem()573 Swdb::createRPMItem()
574 {
575 return std::make_shared< RPMItem >(conn);
576 }
577
578 CompsEnvironmentItemPtr
createCompsEnvironmentItem()579 Swdb::createCompsEnvironmentItem()
580 {
581 return std::make_shared< CompsEnvironmentItem >(conn);
582 }
583
584 CompsGroupItemPtr
createCompsGroupItem()585 Swdb::createCompsGroupItem()
586 {
587 return std::make_shared< CompsGroupItem >(conn);
588 }
589
590 /**
591 * Filter unneeded packages from pool
592 *
593 * \return list of user installed package IDs
594 */
595 void
filterUserinstalled(PackageSet & installed) const596 Swdb::filterUserinstalled(PackageSet & installed) const
597 {
598 Pool * pool = dnf_sack_get_pool(installed.getSack());
599
600 // iterate over solvables
601 Id id = -1;
602 while ((id = installed.next(id)) != -1) {
603
604 Solvable *s = pool_id2solvable(pool, id);
605 const char *name = pool_id2str(pool, s->name);
606 const char *arch = pool_id2str(pool, s->arch);
607
608 auto reason = RPMItem::resolveTransactionItemReason(conn, name, arch, -1);
609 // if not dep or weak, than consider it user installed
610 if (reason == TransactionItemReason::DEPENDENCY ||
611 reason == TransactionItemReason::WEAK_DEPENDENCY) {
612 installed.remove(id);
613 }
614 }
615 }
616
617 std::vector< int64_t >
searchTransactionsByRPM(const std::vector<std::string> & patterns)618 Swdb::searchTransactionsByRPM(const std::vector< std::string > &patterns)
619 {
620 return RPMItem::searchTransactions(conn, patterns);
621 }
622
623 } // namespace libdnf
624