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