1 /*
2  * Copyright (C) 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 <algorithm>
22 #include <set>
23 #include <sstream>
24 
25 extern "C" {
26 #include <solv/poolarch.h>
27 #include <solv/solver.h>
28 }
29 
30 #include "ModulePackageContainer.hpp"
31 #include "libdnf/utils/filesystem.hpp"
32 #include "libdnf/utils/utils.hpp"
33 #include "libdnf/utils/File.hpp"
34 #include "libdnf/dnf-sack-private.hpp"
35 #include "libdnf/hy-query.h"
36 #include "libdnf/hy-types.h"
37 #include <functional>
38 #include <../sack/query.hpp>
39 #include "../log.hpp"
40 #include "libdnf/conf/ConfigParser.hpp"
41 #include "libdnf/conf/OptionStringList.hpp"
42 #include "libdnf/goal/Goal.hpp"
43 #include "libdnf/repo/Repo-private.hpp"
44 #include "libdnf/sack/selector.hpp"
45 #include "libdnf/conf/Const.hpp"
46 
47 #include "bgettext/bgettext-lib.h"
48 #include "tinyformat/tinyformat.hpp"
49 #include "modulemd/ModuleMetadata.hpp"
50 #include "modulemd/ModuleProfile.hpp"
51 
52 
53 namespace {
54 
55 /// Requires resolved goal
56 /// Takes listInstalls() from goal and keep solvables with the solvable-name (<name>:<stream>:<context>) in query
goal2name_query(libdnf::Goal & goal,libdnf::Query & query)57 void goal2name_query(libdnf::Goal & goal, libdnf::Query & query)
58 {
59     auto pool = dnf_sack_get_pool(goal.getSack());
60     auto installList = goal.listInstalls();
61     std::vector<const char *> module_names;
62     Id id = -1;
63     while ((id = installList.next(id)) != -1) {
64         Solvable * s = pool_id2solvable(pool, id);
65         const char * name = pool_id2str(pool, s->name);
66         module_names.push_back(name);
67     }
68     module_names.push_back(nullptr);
69     query.addFilter(HY_PKG_NAME, HY_EQ, module_names.data());
70 }
71 
72 /**
73  * @brief In python => ";".join(list.sort())
74  */
concentrateVectorString(std::vector<std::string> & list)75 std::string concentrateVectorString(std::vector<std::string> & list)
76 {
77     if (list.empty()) {
78         return {};
79     }
80     std::sort(list.begin(), list.end());
81     std::ostringstream ss;
82     ss << *list.begin();
83     for (auto require = std::next(list.begin()); require != list.end(); ++require) {
84         ss << ";" << *require;
85     }
86     return ss.str();
87 }
88 
89 }
90 
91 namespace std {
92 
93 template<>
94 struct default_delete<DIR> {
operator ()std::default_delete95     void operator()(DIR * ptr) noexcept { closedir(ptr); }
96 };
97 
98 }
99 
100 namespace libdnf {
101 
102 static constexpr auto EMPTY_STREAM = "";
103 static constexpr auto EMPTY_PROFILES = "";
104 static constexpr auto DEFAULT_STATE = "";
105 
106 static const char * ENABLE_MULTIPLE_STREAM_EXCEPTION =
107 _("Cannot enable multiple streams for module '%s'");
108 
109 static const std::string EMPTY_RESULT;
110 
111 static std::string
stringFormater(std::string imput)112 stringFormater(std::string imput)
113 {
114     return imput.empty() ? "*" : imput;
115 }
116 
117 static ModulePackageContainer::ModuleState
fromString(const std::string & str)118 fromString(const std::string &str) {
119     if (str == "1" || str == "true" || str == "enabled")
120         return ModulePackageContainer::ModuleState::ENABLED;
121     if (str == "0" || str == "false" || str == "disabled")
122         return ModulePackageContainer::ModuleState::DISABLED;
123 
124     return ModulePackageContainer::ModuleState::UNKNOWN;
125 }
126 
127 static std::string
toString(const ModulePackageContainer::ModuleState & state)128 toString(const ModulePackageContainer::ModuleState &state) {
129     switch (state) {
130         case ModulePackageContainer::ModuleState::ENABLED:
131             return "enabled";
132         case ModulePackageContainer::ModuleState::DISABLED:
133             return "disabled";
134         case ModulePackageContainer::ModuleState::DEFAULT:
135             return "";
136         default:
137             return "";
138     }
139 }
140 
getFileContent(const std::string & filePath)141 static std::string getFileContent(const std::string &filePath)
142 {
143     auto yaml = File::newFile(filePath);
144 
145     yaml->open("r");
146     const auto &yamlContent = yaml->getContent();
147     yaml->close();
148 
149     return yamlContent;
150 }
151 
152 /**
153  * @brief Get names in given directory with surfix ".yaml"
154  *
155  * @param dirPath p_dirPath: Directory
156  * @return std::vector< std::string > Sorted vector with all entries in directory that ends ".yaml"
157  */
getYamlFilenames(const char * dirPath)158 static std::vector<std::string> getYamlFilenames(const char * dirPath)
159 {
160     struct dirent * ent;
161     std::unique_ptr<DIR> dir(opendir(dirPath));
162     std::vector<std::string> fileNames;
163     if (dir) {
164         DIR * dirPtr = dir.get();
165         while ((ent = readdir(dirPtr)) != NULL) {
166             auto filename = ent->d_name;
167             auto fileNameLen = strlen(filename);
168             if (fileNameLen < 10 || strcmp(filename + fileNameLen - 5, ".yaml")) {
169                 continue;
170             }
171             fileNames.push_back(filename);
172         }
173     }
174     std::sort(fileNames.begin(), fileNames.end());
175     return fileNames;
176 }
177 
178 static bool
stringStartWithLowerComparator(const std::string & first,const std::string & pattern)179 stringStartWithLowerComparator(const std::string & first, const std::string & pattern)
180 {
181     return first.compare(0, pattern.size(), pattern) < 0;
182 }
183 
184 class ModulePackageContainer::Impl {
185 public:
186     Impl();
187     ~Impl();
188     std::pair<std::vector<std::vector<std::string>>, ModulePackageContainer::ModuleErrorType> moduleSolve(
189         const std::vector<ModulePackage *> & modules, bool debugSolver);
190     bool insert(const std::string &moduleName, const char *path);
191     std::vector<ModulePackage *> getLatestActiveEnabledModules();
192     /// Required to call after all modules v3 are in metadata
193     void addVersion2Modules();
194 
195 private:
196     friend struct ModulePackageContainer;
197     class ModulePersistor;
198     std::unique_ptr<ModulePersistor> persistor;
199     std::map<Id, std::unique_ptr<ModulePackage>> modules;
200     /// Internal sack with module solvables
201     /// resolveContext = <moduleContext> if moduleMdVersion > 2, else generated from requires
202     /// solvable.name = <moduleName>:<moduleStream>:<resolveContext>
203     /// solvable.evr = <moduleVersion>
204     /// solvable.arch = <moduleArch>
205     /// solvable.summary = <moduleContext>
206     /// solvable.description = <moduleName>:<moduleStream>
207     /// solvable.conflicts = module(<moduleName>)
208     DnfSack * moduleSack;
209     std::unique_ptr<PackageSet> activatedModules;
210     std::string installRoot;
211     std::string persistDir;
212     ModuleMetadata moduleMetadata;
213 
214     std::map<std::string, std::string> moduleDefaults;
215     std::vector<std::tuple<LibsolvRepo *, ModulemdModuleStream *, std::string>> modulesV2;
216 
217     bool isEnabled(const std::string &name, const std::string &stream);
218 };
219 
220 class ModulePackageContainer::Impl::ModulePersistor {
221 public:
222     ~ModulePersistor() = default;
223 
224     const std::string & getStream(const std::string &name);
225     const std::vector<std::string> & getProfiles(const std::string &name);
226     /**
227      * @brief Can throw NoModuleException
228      */
229     const ModuleState & getState(const std::string &name);
230 
231     std::map<std::string, std::string> getEnabledStreams();
232     std::vector<std::string> getDisabledModules();
233     std::map<std::string, std::string> getDisabledStreams();
234     std::vector<std::string> getResetModules();
235 
236     std::map<std::string, std::string> getResetStreams();
237     std::map<std::string, std::pair<std::string, std::string>> getSwitchedStreams();
238     std::map<std::string, std::vector<std::string>> getInstalledProfiles();
239     std::map<std::string, std::vector<std::string>> getRemovedProfiles();
240 
241     std::vector<std::string> getAllModuleNames();
242     bool changeStream(const std::string &name, const std::string &stream);
243     bool addProfile(const std::string &name, const std::string &profile);
244     bool removeProfile(const std::string &name, const std::string &profile);
245     bool changeState(const std::string &name, ModuleState state);
246 
247     bool insert(const std::string &moduleName, const char *path);
248     void rollback();
249     void save(const std::string &installRoot, const std::string &modulesPath);
250 
251 private:
252     friend class Impl;
253     friend struct ModulePackageContainer;
254     struct Config {
255         std::string stream;
256         std::vector<std::string> profiles;
257         ModuleState state;
258         bool locked;
259         int streamChangesNum;
260     };
261     std::pair<ConfigParser, struct Config> & getEntry(const std::string & moduleName);
262     bool update(const std::string &name);
263     void reset(const std::string &name);
264 
265     std::map<std::string, std::pair<ConfigParser, struct Config>> configs;
266 };
267 
EnableMultipleStreamsException(const std::string & moduleName)268 ModulePackageContainer::EnableMultipleStreamsException::EnableMultipleStreamsException(
269     const std::string & moduleName)
270 : Exception(tfm::format(ENABLE_MULTIPLE_STREAM_EXCEPTION, moduleName)) {}
271 
ModulePackageContainer(bool allArch,std::string installRoot,const char * arch,const char * persistDir)272 ModulePackageContainer::ModulePackageContainer(bool allArch, std::string installRoot,
273     const char * arch, const char * persistDir) : pImpl(new Impl)
274 {
275     if (allArch) {
276         dnf_sack_set_all_arch(pImpl->moduleSack, TRUE);
277     } else {
278         dnf_sack_set_arch(pImpl->moduleSack, arch, NULL);
279     }
280     if (persistDir) {
281         g_autofree gchar * dir = g_build_filename(persistDir, "modulefailsafe", NULL);
282         pImpl->persistDir = dir;
283     } else {
284         g_autofree gchar * dir = g_build_filename(installRoot.c_str(), PERSISTDIR, "modulefailsafe",
285                                                   NULL);
286         pImpl->persistDir = dir;
287     }
288 
289     Pool * pool = dnf_sack_get_pool(pImpl->moduleSack);
290     HyRepo hrepo = hy_repo_create("available");
291     auto repoImpl = libdnf::repoGetImpl(hrepo);
292     LibsolvRepo *repo = repo_create(pool, "available");
293     repo->appdata = hrepo;
294     repoImpl->libsolvRepo = repo;
295     repoImpl->needs_internalizing = 1;
296     pImpl->installRoot = installRoot;
297     g_autofree gchar * path = g_build_filename(pImpl->installRoot.c_str(),
298                                               "/etc/dnf/modules.d", NULL);
299     std::unique_ptr<DIR> dir(opendir(path));
300     if (dir) {
301         struct dirent * ent;
302         /* Load "*.module" files into module persistor */
303         DIR * dirPtr = dir.get();
304         while ((ent = readdir(dirPtr)) != NULL) {
305             auto filename = ent->d_name;
306             auto fileNameLen = strlen(filename);
307             if (fileNameLen < 8 || strcmp(filename + fileNameLen - 7, ".module")) {
308                 continue;
309             }
310             std::string name(filename, fileNameLen - 7);
311             pImpl->persistor->insert(name, path);
312         }
313     }
314 }
315 
316 ModulePackageContainer::~ModulePackageContainer() = default;
317 
Impl()318 ModulePackageContainer::Impl::Impl() : persistor(new ModulePersistor), moduleSack(dnf_sack_new()) {}
319 
~Impl()320 ModulePackageContainer::Impl::~Impl()
321 {
322     g_object_unref(moduleSack);
323 }
324 
325 void
add(DnfSack * sack)326 ModulePackageContainer::add(DnfSack * sack)
327 {
328     Pool * pool = dnf_sack_get_pool(sack);
329     LibsolvRepo * r;
330     Id id;
331 
332     FOR_REPOS(id, r) {
333         HyRepo hyRepo = static_cast<HyRepo>(r->appdata);
334         auto modules_fn = hyRepo->getMetadataPath(MD_TYPE_MODULES);
335         if (modules_fn.empty()) {
336             continue;
337         }
338         std::string yamlContent = getFileContent(modules_fn);
339         auto repoName = hyRepo->getId();
340         add(yamlContent, repoName);
341         // update defaults from repo
342         try {
343             pImpl->moduleMetadata.addMetadataFromString(yamlContent, 0);
344         } catch (const ModulePackageContainer::ResolveException & exception) {
345             throw ModulePackageContainer::ConflictException(
346                 tfm::format(_("Conflicting defaults with repo '%s': %s"), repoName,
347                             exception.what()));
348         }
349     }
350 }
351 
addDefaultsFromDisk()352 void ModulePackageContainer::addDefaultsFromDisk()
353 {
354     g_autofree gchar * dirPath = g_build_filename(
355             pImpl->installRoot.c_str(), "/etc/dnf/modules.defaults.d/", NULL);
356 
357     for (const auto &file : filesystem::getDirContent(dirPath)) {
358         std::string yamlContent = getFileContent(file);
359         pImpl->moduleMetadata.addMetadataFromString(yamlContent, 1000);
360     }
361 }
362 
moduleDefaultsResolve()363 void ModulePackageContainer::moduleDefaultsResolve()
364 {
365     pImpl->moduleMetadata.resolveAddedMetadata();
366     pImpl->moduleDefaults = pImpl->moduleMetadata.getDefaultStreams();
367 }
368 
369 void
add(const std::string & fileContent,const std::string & repoID)370 ModulePackageContainer::add(const std::string &fileContent, const std::string & repoID)
371 {
372     Pool * pool = dnf_sack_get_pool(pImpl->moduleSack);
373 
374     ModuleMetadata md;
375     md.addMetadataFromString(fileContent, 0);
376     md.resolveAddedMetadata();
377 
378     LibsolvRepo * r;
379     Id id;
380 
381     FOR_REPOS(id, r) {
382         if (strcmp(r->name, "available") == 0) {
383             g_autofree gchar * path = g_build_filename(pImpl->installRoot.c_str(),
384                                                       "/etc/dnf/modules.d", NULL);
385             auto packages = md.getAllModulePackages(pImpl->moduleSack, r, repoID, pImpl->modulesV2);
386             for(auto const& modulePackagePtr: packages) {
387                 std::unique_ptr<ModulePackage> modulePackage(modulePackagePtr);
388                 pImpl->modules.insert(std::make_pair(modulePackage->getId(), std::move(modulePackage)));
389                 pImpl->persistor->insert(modulePackagePtr->getName(), path);
390             }
391 
392             return;
393         }
394     }
395 }
396 
397 Id
addPlatformPackage(const std::string & osReleasePath,const char * platformModule)398 ModulePackageContainer::addPlatformPackage(const std::string& osReleasePath,
399     const char* platformModule)
400 {
401     return ModulePackage::createPlatformSolvable(pImpl->moduleSack, osReleasePath,
402         pImpl->installRoot, platformModule);
403 }
404 
405 Id
addPlatformPackage(DnfSack * sack,const std::vector<std::string> & osReleasePath,const char * platformModule)406 ModulePackageContainer::addPlatformPackage(DnfSack * sack,
407     const std::vector<std::string> & osReleasePath,
408     const char* platformModule)
409 {
410     return ModulePackage::createPlatformSolvable(sack, pImpl->moduleSack, osReleasePath,
411         pImpl->installRoot, platformModule);
412 }
413 
createConflictsBetweenStreams()414 void ModulePackageContainer::createConflictsBetweenStreams()
415 {
416     // TODO Use Query for filtering
417     for (const auto &iter : pImpl->modules) {
418         const auto &modulePackage = iter.second;
419 
420         for (const auto &innerIter : pImpl->modules) {
421             if (modulePackage->getName() == innerIter.second->getName()
422                 && modulePackage->getStream() != innerIter.second->getStream()) {
423                 modulePackage->addStreamConflict(innerIter.second.get());
424             }
425         }
426     }
427 }
428 
empty() const429 bool ModulePackageContainer::empty() const noexcept
430 {
431     pImpl->addVersion2Modules();
432     return pImpl->modules.empty();
433 }
434 
getModulePackage(Id id)435 ModulePackage * ModulePackageContainer::getModulePackage(Id id)
436 {
437     return pImpl->modules.at(id).get();
438 }
439 
440 std::vector<ModulePackage *>
requiresModuleEnablement(const PackageSet & packages)441 ModulePackageContainer::requiresModuleEnablement(const PackageSet & packages)
442 {
443     auto activatedModules = pImpl->activatedModules.get();
444     if (!activatedModules) {
445         return {};
446     }
447     std::vector<ModulePackage *> output;
448     Query baseQuery(packages.getSack());
449     baseQuery.addFilter(HY_PKG, HY_EQ, &packages);
450     baseQuery.apply();
451     Query testQuery(baseQuery);
452     Id moduleId = -1;
453     while ((moduleId = activatedModules->next(moduleId)) != -1) {
454         auto module = getModulePackage(moduleId);
455         if (isEnabled(module)) {
456             continue;
457         }
458         auto includeNEVRAs = module->getArtifacts();
459         std::vector<const char *> includeNEVRAsCString(includeNEVRAs.size() + 1);
460         transform(includeNEVRAs.begin(), includeNEVRAs.end(), includeNEVRAsCString.begin(),
461                   std::mem_fn(&std::string::c_str));
462         testQuery.queryUnion(baseQuery);
463         testQuery.addFilter(HY_PKG_NEVRA_STRICT, HY_EQ, includeNEVRAsCString.data());
464         if (testQuery.empty()) {
465             continue;
466         }
467         output.push_back(module);
468     }
469     return output;
470 }
471 
472 
473 /**
474  * @brief Is a ModulePackage part of an enabled stream?
475  *
476  * @return bool
477  */
isEnabled(const std::string & name,const std::string & stream)478 bool ModulePackageContainer::Impl::isEnabled(const std::string &name, const std::string &stream)
479 {
480     try {
481         return persistor->getState(name) == ModuleState::ENABLED &&
482             persistor->getStream(name) == stream;
483     } catch (NoModuleException &) {
484         return false;
485     }
486 }
487 
488 /**
489  * @brief Is a ModulePackage part of an enabled stream?
490  *
491  * @return bool
492  */
isEnabled(const std::string & name,const std::string & stream)493 bool ModulePackageContainer::isEnabled(const std::string &name, const std::string &stream)
494 {
495     return pImpl->isEnabled(name, stream);
496 }
497 
isEnabled(const ModulePackage * module)498 bool ModulePackageContainer::isEnabled(const ModulePackage * module)
499 {
500     return pImpl->isEnabled(module->getName(), module->getStream());
501 }
502 
503 /**
504  * @brief Is a ModulePackage part of a disabled module?
505  *
506  * @return bool
507  */
isDisabled(const std::string & name)508 bool ModulePackageContainer::isDisabled(const std::string &name)
509 {
510     try {
511         return pImpl->persistor->getState(name) == ModuleState::DISABLED;
512     } catch (NoModuleException &) {
513         return false;
514     }
515 }
516 
isDisabled(const ModulePackage * module)517 bool ModulePackageContainer::isDisabled(const ModulePackage * module)
518 {
519     return isDisabled(module->getName());
520 }
521 
getDefaultProfiles(std::string moduleName,std::string moduleStream)522 std::vector<std::string> ModulePackageContainer::getDefaultProfiles(std::string moduleName,
523     std::string moduleStream)
524 {
525     pImpl->addVersion2Modules();
526     return pImpl->moduleMetadata.getDefaultProfiles(moduleName, moduleStream);
527 }
528 
getDefaultStream(const std::string & name) const529 const std::string & ModulePackageContainer::getDefaultStream(const std::string &name) const
530 {
531     pImpl->addVersion2Modules();
532     auto it = pImpl->moduleDefaults.find(name);
533     if (it == pImpl->moduleDefaults.end()) {
534         return EMPTY_RESULT;
535     }
536     return it->second;
537 }
538 
getEnabledStream(const std::string & name)539 const std::string & ModulePackageContainer::getEnabledStream(const std::string &name)
540 {
541     pImpl->addVersion2Modules();
542     return pImpl->persistor->getStream(name);
543 }
544 
545 /**
546  * @brief Mark ModulePackage as part of an enabled stream.
547  */
548 bool
enable(const std::string & name,const std::string & stream,const bool count)549 ModulePackageContainer::enable(const std::string &name, const std::string & stream, const bool count)
550 {
551     pImpl->addVersion2Modules();
552     if (count) {
553         pImpl->persistor->getEntry(name).second.streamChangesNum++;
554     }
555     bool changed = pImpl->persistor->changeStream(name, stream);
556     if (pImpl->persistor->changeState(name, ModuleState::ENABLED)) {
557         changed = true;
558     }
559     if (changed) {
560         auto & profiles = pImpl->persistor->getEntry(name).second.profiles;
561         profiles.clear();
562     }
563     return changed;
564 }
565 
566 bool
enable(const ModulePackage * module,const bool count)567 ModulePackageContainer::enable(const ModulePackage * module, const bool count)
568 {
569     return enable(module->getName(), module->getStream(), count);
570 }
571 
572 /**
573  * @brief Mark module as not part of an enabled stream.
574  */
disable(const std::string & name,const bool count)575 void ModulePackageContainer::disable(const std::string & name, const bool count)
576 {
577     pImpl->addVersion2Modules();
578     if (count) {
579         pImpl->persistor->getEntry(name).second.streamChangesNum++;
580     }
581 
582     pImpl->persistor->changeState(name, ModuleState::DISABLED);
583     pImpl->persistor->changeStream(name, "");
584     auto & profiles = pImpl->persistor->getEntry(name).second.profiles;
585     profiles.clear();
586 }
587 
disable(const ModulePackage * module,const bool count)588 void ModulePackageContainer::disable(const ModulePackage * module, const bool count)
589 {
590     disable(module->getName(), count);
591 }
592 
593 /**
594  * @brief Reset module state so it's no longer enabled or disabled.
595  */
reset(const std::string & name,const bool count)596 void ModulePackageContainer::reset(const std::string & name, const bool count)
597 {
598     pImpl->addVersion2Modules();
599     if (count) {
600         pImpl->persistor->getEntry(name).second.streamChangesNum++;
601     }
602     pImpl->persistor->changeState(name, ModuleState::UNKNOWN);
603     pImpl->persistor->changeStream(name, "");
604     auto & profiles = pImpl->persistor->getEntry(name).second.profiles;
605     profiles.clear();
606 }
607 
reset(const ModulePackage * module,const bool count)608 void ModulePackageContainer::reset(const ModulePackage * module, const bool count)
609 {
610     reset(module->getName(), count);
611 }
612 
613 /**
614  * @brief Are there any changes to be saved?
615  */
isChanged()616 bool ModulePackageContainer::isChanged()
617 {
618     if (!getEnabledStreams().empty()) {
619         return true;
620     }
621     if (!getDisabledModules().empty()) {
622         return true;
623     }
624     if (!getResetModules().empty()) {
625         return true;
626     }
627     if (!getSwitchedStreams().empty()) {
628         return true;
629     }
630     if (!getInstalledProfiles().empty()) {
631         return true;
632     }
633     if (!getRemovedProfiles().empty()) {
634         return true;
635     }
636     return false;
637 }
638 
install(const std::string & name,const std::string & stream,const std::string & profile)639 void ModulePackageContainer::install(const std::string &name, const std::string &stream,
640     const std::string &profile)
641 {
642     pImpl->addVersion2Modules();
643     for (const auto &iter : pImpl->modules) {
644         auto modulePackage = iter.second.get();
645         if (modulePackage->getName() == name && modulePackage->getStream() == stream) {
646             install(modulePackage, profile);
647         }
648     }
649 }
650 
install(const ModulePackage * module,const std::string & profile)651 void ModulePackageContainer::install(const ModulePackage * module, const std::string &profile)
652 {
653     if (pImpl->persistor->getStream(module->getName()) == module->getStream())
654         pImpl->persistor->addProfile(module->getName(), profile);
655 }
656 
uninstall(const std::string & name,const std::string & stream,const std::string & profile)657 void ModulePackageContainer::uninstall(const std::string &name, const std::string &stream,
658     const std::string &profile)
659 {
660     pImpl->addVersion2Modules();
661     for (const auto &iter : pImpl->modules) {
662         auto modulePackage = iter.second.get();
663         if (modulePackage->getName() == name && modulePackage->getStream() == stream) {
664             uninstall(modulePackage, profile);
665         }
666     }
667 }
668 
uninstall(const ModulePackage * module,const std::string & profile)669 void ModulePackageContainer::uninstall(const ModulePackage * module, const std::string &profile)
670 {
671     if (pImpl->persistor->getStream(module->getName()) == module->getStream())
672         pImpl->persistor->removeProfile(module->getName(), profile);
673 }
674 
675 std::pair<std::vector<std::vector<std::string>>, ModulePackageContainer::ModuleErrorType>
moduleSolve(const std::vector<ModulePackage * > & modules,bool debugSolver)676 ModulePackageContainer::Impl::moduleSolve(const std::vector<ModulePackage *> & modules,
677     bool debugSolver)
678 {
679     if (modules.empty()) {
680         activatedModules.reset();
681         return std::make_pair(std::vector<std::vector<std::string>>(),
682                               ModulePackageContainer::ModuleErrorType::NO_ERROR);
683     }
684     dnf_sack_recompute_considered(moduleSack);
685     dnf_sack_make_provides_ready(moduleSack);
686     Goal goal(moduleSack);
687     Goal goalWeak(moduleSack);
688     for (const auto &module : modules) {
689         std::ostringstream ss;
690         auto name = module->getName();
691         ss << "module(" << name << ":" << module->getStream() << ")";
692         Selector selector(moduleSack);
693         bool optional = persistor->getState(name) == ModuleState::DEFAULT;
694         selector.set(HY_PKG_PROVIDES, HY_EQ, ss.str().c_str());
695         goal.install(&selector, optional);
696         goalWeak.install(&selector, true);
697     }
698     auto ret = goal.run(static_cast<DnfGoalActions>(DNF_IGNORE_WEAK | DNF_FORCE_BEST));
699     if (debugSolver) {
700         goal.writeDebugdata("debugdata/modules");
701     }
702     std::vector<std::vector<std::string>> problems;
703     auto problemType = ModulePackageContainer::ModuleErrorType::NO_ERROR;
704     if (ret) {
705         // Goal run ignor problem in defaults
706         problems = goal.describeAllProblemRules(false);
707         ret = goal.run(DNF_FORCE_BEST);
708         if (ret) {
709             // Goal run ignor problem in defaults and in latest
710             ret = goal.run(DNF_NONE);
711             if (ret) {
712                 // Conflicting modules has to be removed otherwice it could result than one of them will
713                 // be active
714                 auto conflictingPkgs = goal.listConflictPkgs(DNF_PACKAGE_STATE_AVAILABLE);
715                 dnf_sack_add_excludes(moduleSack, conflictingPkgs.get());
716                 ret = goalWeak.run(DNF_NONE);
717                 if (ret) {
718                     auto logger(Log::getLogger());
719                     logger->critical("Modularity filtering totally broken\n");
720                     problemType = ModulePackageContainer::ModuleErrorType::CANNOT_RESOLVE_MODULES;
721                     activatedModules.reset();
722                 } else {
723                     problemType = ModulePackageContainer::ModuleErrorType::ERROR;
724                     Query query(moduleSack, Query::ExcludeFlags::IGNORE_EXCLUDES);
725                     goal2name_query(goalWeak, query);
726                     activatedModules.reset(new PackageSet(*query.runSet()));
727                 }
728                 return make_pair(problems, problemType);
729             }
730             problemType = ModulePackageContainer::ModuleErrorType::ERROR_IN_LATEST;
731         } else {
732             problemType = ModulePackageContainer::ModuleErrorType::ERROR_IN_DEFAULTS;
733         }
734     }
735     Query query(moduleSack, Query::ExcludeFlags::IGNORE_EXCLUDES);
736     goal2name_query(goal, query);
737     activatedModules.reset(new PackageSet(*query.runSet()));
738     return make_pair(problems, problemType);
739 }
740 
741 std::vector<ModulePackage *>
query(Nsvcap & moduleNevra)742 ModulePackageContainer::query(Nsvcap& moduleNevra)
743 {
744     return query(moduleNevra.getName(), moduleNevra.getStream(), moduleNevra.getVersion(),
745                  moduleNevra.getContext(), moduleNevra.getArch());
746 }
747 
748 std::vector<ModulePackage *>
query(std::string subject)749 ModulePackageContainer::query(std::string subject)
750 {
751     pImpl->addVersion2Modules();
752     // Alternatively a search using module provides could be performed
753     std::vector<ModulePackage *> result;
754     Query query(pImpl->moduleSack, Query::ExcludeFlags::IGNORE_EXCLUDES);
755     // platform modules are installed and not in modules std::Map.
756     query.available();
757     std::ostringstream ss;
758     ss << subject << "*";
759     query.addFilter(HY_PKG_NAME, HY_GLOB, ss.str().c_str());
760     auto pset = query.runSet();
761     Id moduleId = -1;
762     while ((moduleId = pset->next(moduleId)) != -1) {
763         result.push_back(pImpl->modules.at(moduleId).get());
764     }
765     return result;
766 }
767 
768 std::vector<ModulePackage *>
query(std::string name,std::string stream,std::string version,std::string context,std::string arch)769 ModulePackageContainer::query(std::string name, std::string stream, std::string version,
770     std::string context, std::string arch)
771 {
772     pImpl->addVersion2Modules();
773     // Alternatively a search using module provides could be performed
774     std::vector<ModulePackage *> result;
775     Query query(pImpl->moduleSack, Query::ExcludeFlags::IGNORE_EXCLUDES);
776     // platform modules are installed and not in modules std::Map.
777     query.available();
778     if (!name.empty() || !stream.empty()) {
779         std::ostringstream ss;
780         ss << stringFormater(name) << ":" << stringFormater(stream);
781         query.addFilter(HY_PKG_DESCRIPTION, HY_GLOB, ss.str().c_str());
782     }
783     if (!context.empty()) {
784         query.addFilter(HY_PKG_SUMMARY, HY_GLOB, context.c_str());
785     }
786     if (!arch.empty()) {
787         query.addFilter(HY_PKG_ARCH, HY_GLOB, arch.c_str());
788     }
789     if (!version.empty()) {
790         query.addFilter(HY_PKG_VERSION, HY_GLOB, version.c_str());
791     }
792     auto pset = query.runSet();
793     Id moduleId = -1;
794     while ((moduleId = pset->next(moduleId)) != -1) {
795         result.push_back(pImpl->modules.at(moduleId).get());
796     }
797     return result;
798 }
799 
enableDependencyTree(std::vector<ModulePackage * > & modulePackages)800 void ModulePackageContainer::enableDependencyTree(std::vector<ModulePackage *> & modulePackages)
801 {
802     if (!pImpl->activatedModules) {
803         return;
804     }
805     PackageSet toEnable(pImpl->moduleSack);
806     PackageSet enabled(pImpl->moduleSack);
807     for (auto & modulePackage: modulePackages) {
808         if (!isModuleActive(modulePackage)) {
809             continue;
810         }
811         Query query(pImpl->moduleSack);
812         query.addFilter(HY_PKG, HY_EQ, pImpl->activatedModules.get());
813         auto pkg = dnf_package_new(pImpl->moduleSack, modulePackage->getId());
814         auto requires = dnf_package_get_requires(pkg);
815         query.addFilter(HY_PKG_PROVIDES, requires);
816         auto set = query.runSet();
817         toEnable += *set;
818         delete requires;
819         g_object_unref(pkg);
820         enable(modulePackage);
821         enabled.set(modulePackage->getId());
822     }
823     toEnable -= enabled;
824     while (!toEnable.empty()) {
825         Id moduleId = -1;
826         while ((moduleId = toEnable.next(moduleId)) != -1) {
827             enable(pImpl->modules.at(moduleId).get());
828             enabled.set(moduleId);
829             Query query(pImpl->moduleSack);
830             query.addFilter(HY_PKG, HY_EQ, pImpl->activatedModules.get());
831             query.addFilter(HY_PKG, HY_NEQ, &enabled);
832             auto pkg = dnf_package_new(pImpl->moduleSack, moduleId);
833             auto requires = dnf_package_get_requires(pkg);
834             query.addFilter(HY_PKG_PROVIDES, requires);
835             auto set = query.runSet();
836             toEnable += *set;
837             delete requires;
838             g_object_unref(pkg);
839         }
840         toEnable -= enabled;
841     }
842 }
843 
844 ModulePackageContainer::ModuleState
getModuleState(const std::string & name)845 ModulePackageContainer::getModuleState(const std::string& name)
846 {
847     try {
848         return pImpl->persistor->getState(name);
849     } catch (NoModuleException &) {
850         return ModuleState::UNKNOWN;
851     }
852 }
853 
getLatestModule(std::vector<ModulePackage * > modulePackages,bool activeOnly)854 ModulePackage * ModulePackageContainer::getLatestModule(std::vector<ModulePackage *> modulePackages, bool activeOnly)
855 {
856     ModulePackage * latest = nullptr;
857     for (ModulePackage * module: modulePackages) {
858         if (!activeOnly || isModuleActive(module->getId())) {
859             if (!latest) {
860                 latest = module;
861             } else {
862                 if (module->getVersionNum() > latest->getVersionNum()) {
863                     latest = module;
864                 }
865             }
866         }
867     }
868 
869     return latest;
870 }
871 
getInstalledPkgNames()872 std::set<std::string> ModulePackageContainer::getInstalledPkgNames()
873 {
874     pImpl->addVersion2Modules();
875     auto moduleNames = pImpl->persistor->getAllModuleNames();
876     std::set<std::string> pkgNames;
877     for (auto & moduleName: moduleNames) {
878         auto stream = getEnabledStream(moduleName);
879         if (stream.empty()) {
880             continue;
881         }
882         auto profilesInstalled = getInstalledProfiles(moduleName);
883         if (profilesInstalled.empty()) {
884             continue;
885         }
886         std::string nameStream(moduleName);
887         nameStream += ":";
888         nameStream += stream;
889         auto modules = query(nameStream);
890         const ModulePackage * latest = getLatestModule(modules, true);
891         if (!latest) {
892             latest = getLatestModule(modules, false);
893         }
894         if (!latest) {
895             continue;
896         }
897         for (auto & profile: profilesInstalled) {
898             auto profiles = latest->getProfiles(profile);
899             for (auto & profile: profiles) {
900                 auto pkgs = profile.getContent();
901                 for (auto pkg: pkgs) {
902                     pkgNames.insert(pkg);
903                 }
904             }
905         }
906     }
907     return pkgNames;
908 }
909 
910 std::string
getReport()911 ModulePackageContainer::getReport()
912 {
913     std::string report;
914 
915     auto installedProfiles = getInstalledProfiles();
916     if (!installedProfiles.empty()) {
917         report += _("Installing module profiles:\n");
918         for (auto & item: installedProfiles) {
919             for (auto & profile:item.second) {
920                 report += "    ";
921                 report += item.first;
922                 report += ":";
923                 report += profile;
924                 report += "\n";
925             }
926         }
927         report += "\n";
928     }
929 
930     auto removedProfiles = getRemovedProfiles();
931     if (!removedProfiles.empty()) {
932         report += _("Disabling module profiles:\n");
933         for (auto & item: removedProfiles) {
934             for (auto & profile:item.second) {
935                 report += "    ";
936                 report += item.first;
937                 report += ":";
938                 report += profile;
939                 report += "\n";
940             }
941         }
942         report += "\n";
943     }
944 
945     auto enabled = getEnabledStreams();
946     if (!enabled.empty()) {
947         report += _("Enabling module streams:\n");
948         for (auto & item: enabled) {
949             report += "    ";
950             report += item.first;
951             report += ":";
952             report += item.second;
953             report += "\n";
954         }
955         report += "\n";
956     }
957 
958     auto switchedStreams = getSwitchedStreams();
959     if (!switchedStreams.empty()) {
960         std::string switchedReport;
961         switchedReport += _("Switching module streams:\n");
962         for (auto & item: switchedStreams) {
963             switchedReport += "    ";
964             switchedReport += item.first;
965             switchedReport += ":";
966             switchedReport += item.second.first;
967             switchedReport += " > ";
968             switchedReport += item.first;
969             switchedReport += ":";
970             switchedReport += item.second.second;
971             switchedReport += "\n";
972         }
973         report += switchedReport;
974         report += "\n";
975     }
976 
977     auto disabled = getDisabledModules();
978     if (!disabled.empty()) {
979         report += _("Disabling modules:\n");
980         for (auto & name: disabled) {
981             report += "    ";
982             report += name;
983             report += "\n";
984         }
985         report += "\n";
986     }
987 
988     auto reset = getResetModules();
989     if (!reset.empty()) {
990         report += _("Resetting modules:\n");
991         for (auto & name: reset) {
992             report += "    ";
993             report += name;
994             report += "\n";
995         }
996         report += "\n";
997     }
998     return report;
999 }
1000 
1001 static bool
modulePackageLatestPerRepoSorter(DnfSack * sack,const ModulePackage * first,const ModulePackage * second)1002 modulePackageLatestPerRepoSorter(DnfSack * sack, const ModulePackage * first, const ModulePackage * second)
1003 {
1004     if (first->getRepoID() != second->getRepoID())
1005         return first->getRepoID() < second->getRepoID();
1006     int cmp = g_strcmp0(first->getNameCStr(), second->getNameCStr());
1007     if (cmp != 0)
1008         return cmp < 0;
1009     cmp = dnf_sack_evr_cmp(sack, first->getStreamCStr(), second->getStreamCStr());
1010     if (cmp != 0)
1011         return cmp < 0;
1012     cmp = g_strcmp0(first->getArchCStr(), second->getArchCStr());
1013     if (cmp != 0)
1014         return cmp < 0;
1015     return first->getVersionNum() > second->getVersionNum();
1016 }
1017 
1018 std::vector<std::vector<std::vector<ModulePackage *>>>
getLatestModulesPerRepo(ModuleState moduleFilter,std::vector<ModulePackage * > modulePackages)1019 ModulePackageContainer::getLatestModulesPerRepo(ModuleState moduleFilter,
1020     std::vector<ModulePackage *> modulePackages)
1021 {
1022     pImpl->addVersion2Modules();
1023     if (modulePackages.empty()) {
1024         return {};
1025     }
1026     if (moduleFilter == ModuleState::ENABLED) {
1027         std::vector<ModulePackage *> enabled;
1028         for (auto package: modulePackages) {
1029             if (isEnabled(package)) {
1030                 enabled.push_back(package);
1031             }
1032         }
1033         modulePackages = enabled;
1034     } else if (moduleFilter == ModuleState::DISABLED) {
1035         std::vector<ModulePackage *> disabled;
1036         for (auto package: modulePackages) {
1037             if (isDisabled(package)) {
1038                 disabled.push_back(package);
1039             }
1040         }
1041         modulePackages = disabled;
1042     } else if (moduleFilter == ModuleState::INSTALLED) {
1043         std::vector<ModulePackage *> installed;
1044         for (auto package: modulePackages) {
1045             if ((!getInstalledProfiles(package->getName()).empty()) && isEnabled(package)) {
1046                 installed.push_back(package);
1047             }
1048         }
1049         modulePackages = installed;
1050     }
1051     if (modulePackages.empty()) {
1052         return {};
1053     }
1054 
1055     std::vector<std::vector<std::vector<ModulePackage *>>> output;
1056     auto sack = pImpl->moduleSack;
1057     std::sort(modulePackages.begin(), modulePackages.end(),
1058               [sack](const ModulePackage * first, const ModulePackage * second)
1059               {return modulePackageLatestPerRepoSorter(sack, first, second);});
1060     auto vectorSize = modulePackages.size();
1061 
1062     auto & packageFirst = modulePackages[0];
1063     output.push_back(
1064         std::vector<std::vector<ModulePackage *>>{std::vector<ModulePackage *> {packageFirst}});
1065     int repoIndex = 0;
1066     int nameStreamArchIndex = 0;
1067     auto repoID = packageFirst->getRepoID();
1068     auto name = packageFirst->getNameCStr();
1069     auto stream = packageFirst->getStreamCStr();
1070     auto arch = packageFirst->getArchCStr();
1071     auto version = packageFirst->getVersionNum();
1072 
1073     for (unsigned int index = 1; index < vectorSize; ++index) {
1074         auto & package = modulePackages[index];
1075         if (repoID != package->getRepoID()) {
1076             repoID = package->getRepoID();
1077             name = package->getNameCStr();
1078             stream = package->getStreamCStr();
1079             arch = package->getArchCStr();
1080             version = package->getVersionNum();
1081             output.push_back(std::vector<std::vector<ModulePackage *>>{std::vector<ModulePackage *> {package}});
1082             ++repoIndex;
1083             nameStreamArchIndex = 0;
1084             continue;
1085         }
1086         if (g_strcmp0(package->getNameCStr(), name) != 0 ||
1087             g_strcmp0(package->getStreamCStr(), stream) != 0 ||
1088             g_strcmp0(package->getArchCStr(), arch) != 0) {
1089             name = package->getNameCStr();
1090             stream = package->getStreamCStr();
1091             arch = package->getArchCStr();
1092             version = package->getVersionNum();
1093             output[repoIndex].push_back(std::vector<ModulePackage *> {package});
1094             ++nameStreamArchIndex;
1095             continue;
1096         }
1097         if (version == package->getVersionNum()) {
1098             output[repoIndex][nameStreamArchIndex].push_back(package);
1099         }
1100     }
1101     return output;
1102 }
1103 
1104 std::vector<ModulePackage *>
getLatestModules(const std::vector<ModulePackage * > modulePackages,bool activeOnly)1105 ModulePackageContainer::getLatestModules(const std::vector<ModulePackage *> modulePackages, bool activeOnly)
1106 {
1107     // Because modular sovables uses as name combination of module $name:$stream:$context, we can use to get the lates
1108     // Query
1109     std::vector<ModulePackage *> latestModules;
1110     Query query(pImpl->moduleSack, Query::ExcludeFlags::IGNORE_EXCLUDES);
1111     if (activeOnly) {
1112         // When no active module return
1113         if (!pImpl->activatedModules) {
1114             return latestModules;
1115         }
1116         query.addFilter(HY_PKG, HY_EQ, pImpl->activatedModules.get());
1117     }
1118 
1119     PackageSet inputModulePackages(pImpl->moduleSack);
1120     for (auto modulePackage : modulePackages) {
1121         inputModulePackages.set(modulePackage->getId());
1122     }
1123     query.addFilter(HY_PKG, HY_EQ, &inputModulePackages);
1124     query.addFilter(HY_PKG_LATEST_PER_ARCH, HY_EQ, 1);
1125     auto set = query.runSet();
1126 
1127     Id moduleId = -1;
1128     while ((moduleId = set->next(moduleId)) != -1) {
1129         latestModules.push_back(pImpl->modules.at(moduleId).get());
1130     }
1131     return latestModules;
1132 }
1133 
1134 std::pair<std::vector<std::vector<std::string>>, ModulePackageContainer::ModuleErrorType>
resolveActiveModulePackages(bool debugSolver)1135 ModulePackageContainer::resolveActiveModulePackages(bool debugSolver)
1136 {
1137     pImpl->addVersion2Modules();
1138     dnf_sack_reset_excludes(pImpl->moduleSack);
1139     std::vector<ModulePackage *> packages;
1140 
1141     PackageSet excludes(pImpl->moduleSack);
1142     // Use only Enabled or Default modules for transaction
1143     for (const auto &iter : pImpl->modules) {
1144         auto module = iter.second.get();
1145         auto moduleState = pImpl->persistor->getState(module->getName());
1146         if (moduleState == ModuleState::DISABLED) {
1147             excludes.set(module->getId());
1148             continue;
1149         }
1150 
1151         bool hasDefaultStream;
1152         hasDefaultStream = getDefaultStream(module->getName()) == module->getStream();
1153         if (isDisabled(module)) {
1154             // skip disabled modules
1155             continue;
1156         } else if (isEnabled(module)) {
1157             packages.push_back(module);
1158         } else if (hasDefaultStream) {
1159             if (moduleState != ModuleState::ENABLED) {
1160                 pImpl->persistor->changeState(module->getName(), ModuleState::DEFAULT);
1161                 packages.push_back(module);
1162             }
1163         }
1164     }
1165     dnf_sack_add_excludes(pImpl->moduleSack, &excludes);
1166     auto problems = pImpl->moduleSolve(packages, debugSolver);
1167     return problems;
1168 }
1169 
isModuleActive(Id id)1170 bool ModulePackageContainer::isModuleActive(Id id)
1171 {
1172     if (pImpl->activatedModules) {
1173         return pImpl->activatedModules->has(id);
1174     }
1175     return false;
1176 }
1177 
isModuleActive(const ModulePackage * modulePackage)1178 bool ModulePackageContainer::isModuleActive(const ModulePackage * modulePackage)
1179 {
1180     if (pImpl->activatedModules) {
1181         return pImpl->activatedModules->has(modulePackage->getId());
1182     }
1183     return false;
1184 }
1185 
getModulePackages()1186 std::vector<ModulePackage *> ModulePackageContainer::getModulePackages()
1187 {
1188     pImpl->addVersion2Modules();
1189     std::vector<ModulePackage *> values;
1190     const auto & modules = pImpl->modules;
1191     std::transform(
1192         std::begin(modules), std::end(modules), std::back_inserter(values),
1193         [](const std::map<Id, std::unique_ptr<ModulePackage>>::value_type & pair){ return pair.second.get(); });
1194 
1195     return values;
1196 }
1197 
save()1198 void ModulePackageContainer::save()
1199 {
1200     pImpl->persistor->save(pImpl->installRoot, "/etc/dnf/modules.d");
1201 }
1202 
rollback()1203 void ModulePackageContainer::rollback()
1204 {
1205     pImpl->persistor->rollback();
1206 }
1207 
getEnabledStreams()1208 std::map<std::string, std::string> ModulePackageContainer::getEnabledStreams()
1209 {
1210     pImpl->addVersion2Modules();
1211     return pImpl->persistor->getEnabledStreams();
1212 }
1213 
getDisabledModules()1214 std::vector<std::string> ModulePackageContainer::getDisabledModules()
1215 {
1216     pImpl->addVersion2Modules();
1217     return pImpl->persistor->getDisabledModules();
1218 }
1219 
getDisabledStreams()1220 std::map<std::string, std::string> ModulePackageContainer::getDisabledStreams()
1221 {
1222     pImpl->addVersion2Modules();
1223     return pImpl->persistor->getDisabledStreams();
1224 }
1225 
getResetModules()1226 std::vector<std::string> ModulePackageContainer::getResetModules()
1227 {
1228     pImpl->addVersion2Modules();
1229     return pImpl->persistor->getResetModules();
1230 }
1231 
getResetStreams()1232 std::map<std::string, std::string> ModulePackageContainer::getResetStreams()
1233 {
1234     pImpl->addVersion2Modules();
1235     return pImpl->persistor->getResetStreams();
1236 }
1237 
1238 std::map<std::string, std::pair<std::string, std::string>>
getSwitchedStreams()1239 ModulePackageContainer::getSwitchedStreams()
1240 {
1241     pImpl->addVersion2Modules();
1242     return pImpl->persistor->getSwitchedStreams();
1243 }
1244 
getInstalledProfiles()1245 std::map<std::string, std::vector<std::string>> ModulePackageContainer::getInstalledProfiles()
1246 {
1247     pImpl->addVersion2Modules();
1248     return pImpl->persistor->getInstalledProfiles();
1249 }
1250 
getInstalledProfiles(std::string moduleName)1251 std::vector<std::string> ModulePackageContainer::getInstalledProfiles(std::string moduleName)
1252 {
1253     pImpl->addVersion2Modules();
1254     return pImpl->persistor->getProfiles(moduleName);
1255 }
1256 
getRemovedProfiles()1257 std::map<std::string, std::vector<std::string>> ModulePackageContainer::getRemovedProfiles()
1258 {
1259     pImpl->addVersion2Modules();
1260     return pImpl->persistor->getRemovedProfiles();
1261 }
1262 const std::string &
getStream(const std::string & name)1263 ModulePackageContainer::Impl::ModulePersistor::getStream(const std::string & name)
1264 {
1265     return getEntry(name).second.stream;
1266 }
1267 
1268 inline std::pair<ConfigParser, struct ModulePackageContainer::Impl::ModulePersistor::Config> &
getEntry(const std::string & moduleName)1269 ModulePackageContainer::Impl::ModulePersistor::getEntry(const std::string & moduleName)
1270 {
1271     try {
1272         auto & entry = configs.at(moduleName);
1273         return entry;
1274     } catch (std::out_of_range &) {
1275         throw NoModuleException(moduleName);
1276     }
1277 }
1278 
getAllModuleNames()1279 std::vector<std::string> ModulePackageContainer::Impl::ModulePersistor::getAllModuleNames()
1280 {
1281     std::vector<std::string> output;
1282     output.reserve(configs.size());
1283     for (auto & item: configs) {
1284         output.push_back(item.first);
1285     }
1286     return output;
1287 }
1288 
1289 bool
changeStream(const std::string & name,const std::string & stream)1290 ModulePackageContainer::Impl::ModulePersistor::changeStream(const std::string &name,
1291     const std::string &stream)
1292 {
1293     const auto &updatedValue = configs.at(name).second.stream;
1294     if (updatedValue == stream)
1295         return false;
1296     const auto &originValue = configs.at(name).first.getValue(name, "stream");
1297     if (originValue != updatedValue && configs.at(name).second.streamChangesNum > 1) {
1298         throw EnableMultipleStreamsException(name);
1299     }
1300     getEntry(name).second.stream = stream;
1301     return true;
1302 }
1303 
1304 const std::vector<std::string> &
getProfiles(const std::string & name)1305 ModulePackageContainer::Impl::ModulePersistor::getProfiles(const std::string &name)
1306 {
1307     return getEntry(name).second.profiles;
1308 }
1309 
addProfile(const std::string & name,const std::string & profile)1310 bool ModulePackageContainer::Impl::ModulePersistor::addProfile(
1311     const std::string &name, const std::string &profile)
1312 {
1313     auto & profiles = getEntry(name).second.profiles;
1314     const auto &it = std::find(std::begin(profiles), std::end(profiles), profile);
1315     if (it != std::end(profiles))
1316         return false;
1317 
1318     profiles.push_back(profile);
1319     return true;
1320 }
1321 
removeProfile(const std::string & name,const std::string & profile)1322 bool ModulePackageContainer::Impl::ModulePersistor::removeProfile(
1323     const std::string &name, const std::string &profile)
1324 {
1325     auto &profiles = getEntry(name).second.profiles;
1326 
1327     for (auto it = profiles.begin(); it != profiles.end(); it++) {
1328         if (*it == profile) {
1329             profiles.erase(it);
1330             return true;
1331         }
1332     }
1333 
1334     return false;
1335 }
1336 
1337 const ModulePackageContainer::ModuleState &
getState(const std::string & name)1338 ModulePackageContainer::Impl::ModulePersistor::getState(const std::string &name)
1339 {
1340     return getEntry(name).second.state;
1341 }
1342 
changeState(const std::string & name,ModuleState state)1343 bool ModulePackageContainer::Impl::ModulePersistor::changeState(
1344     const std::string &name, ModuleState state)
1345 {
1346     if (getEntry(name).second.state == state)
1347         return false;
1348 
1349     getEntry(name).second.state = state;
1350     return true;
1351 }
1352 
1353 static inline void
initConfig(ConfigParser & parser,const std::string & name)1354 initConfig(ConfigParser & parser, const std::string & name)
1355 {
1356     parser.addSection(name);
1357     parser.setValue(name, "name", name);
1358     parser.setValue(name, "stream", EMPTY_STREAM);
1359     parser.setValue(name, "profiles", EMPTY_PROFILES);
1360     parser.setValue(name, "state", DEFAULT_STATE);
1361 }
1362 
1363 static inline void
parseConfig(ConfigParser & parser,const std::string & name,const char * path)1364 parseConfig(ConfigParser &parser, const std::string &name, const char *path)
1365 {
1366     auto logger(Log::getLogger());
1367 
1368     try {
1369         const auto fname = name + ".module";
1370         g_autofree gchar * cfn = g_build_filename(path, fname.c_str(), NULL);
1371         parser.read(cfn);
1372 
1373         /* FIXME: init empty config or throw error? */
1374         if (!parser.hasOption(name, "stream") ||    /* stream = <stream_name> */
1375             !parser.hasOption(name, "profiles") ||  /* profiles = <list of profiles> */
1376             (!parser.hasOption(name, "state") && !parser.hasOption(name, "enabled"))) {
1377             logger->debug("Invalid config file for " + name);
1378             initConfig(parser, name);
1379             return;
1380         }
1381 
1382         /* Old config files might not have an option 'name' */
1383         parser.setValue(name, "name", name);
1384 
1385         /* Replace old 'enabled' format by 'state' */
1386         if (parser.hasOption(name, "enabled")) {
1387             parser.setValue(name, "state", parser.getValue(name, "enabled"));
1388             parser.removeOption(name, "enabled");
1389         }
1390     } catch (const ConfigParser::CantOpenFile &) {
1391         /* No module config file present. Fill values in */
1392         initConfig(parser, name);
1393         return;
1394     }
1395 }
1396 
insert(const std::string & moduleName,const char * path)1397 bool ModulePackageContainer::Impl::ModulePersistor::insert(
1398     const std::string &moduleName, const char *path)
1399 {
1400     /* There can only be one config file per module */
1401     if (configs.find(moduleName) != configs.end()) {
1402         return false;
1403     }
1404 
1405     auto newEntry = configs.emplace(moduleName, std::make_pair(ConfigParser{}, Config()));
1406     auto & parser = newEntry.first->second.first;
1407     auto & newConfig = newEntry.first->second.second;
1408 
1409     parseConfig(parser, moduleName, path);
1410 
1411     OptionStringList slist{std::vector<std::string>()};
1412     const auto &plist = parser.getValue(moduleName, "profiles");
1413     newConfig.profiles = std::move(slist.fromString(plist));
1414 
1415     newConfig.state = fromString(parser.getValue(moduleName, "state"));
1416     newConfig.stream = parser.getValue(moduleName, "stream");
1417     newConfig.streamChangesNum = 0;
1418 
1419     return true;
1420 }
1421 
update(const std::string & name)1422 bool ModulePackageContainer::Impl::ModulePersistor::update(const std::string & name)
1423 {
1424     bool changed = false;
1425     auto & parser = getEntry(name).first;
1426 
1427     const auto & state = toString(getState(name));
1428     if (!parser.hasOption(name, "state") || parser.getValue(name, "state") != state) {
1429         parser.setValue(name, "state", state);
1430         changed = true;
1431     }
1432 
1433     const auto & stream = getStream(name);
1434     if (!parser.hasOption(name, "stream") || parser.getValue(name, "stream") != stream) {
1435         parser.setValue(name, "stream", stream);
1436         changed = true;
1437     }
1438 
1439     OptionStringList profiles{getProfiles(name)};
1440     if (!parser.hasOption(name, "profiles") ||
1441         OptionStringList(parser.getValue(name, "profiles")).getValue() != profiles.getValue()) {
1442         parser.setValue(name, "profiles", profiles.getValueString());
1443         changed = true;
1444     }
1445 
1446     return changed;
1447 }
1448 
reset(const std::string & name)1449 void ModulePackageContainer::Impl::ModulePersistor::reset(const std::string & name)
1450 {
1451     auto & entry = getEntry(name);
1452     auto & parser = entry.first;
1453 
1454     entry.second.stream = parser.getValue(name, "stream");
1455     entry.second.state = fromString(parser.getValue(name, "state"));
1456     OptionStringList slist{std::vector<std::string>()};
1457     entry.second.profiles = slist.fromString(parser.getValue(name, "profiles"));
1458 }
1459 
save(const std::string & installRoot,const std::string & modulesPath)1460 void ModulePackageContainer::Impl::ModulePersistor::save(
1461     const std::string &installRoot, const std::string &modulesPath)
1462 {
1463     g_autofree gchar * dirname = g_build_filename(
1464         installRoot.c_str(), modulesPath.c_str(), "/", NULL);
1465     makeDirPath(std::string(dirname));
1466 
1467     for (auto &iter : configs) {
1468         const auto &name = iter.first;
1469 
1470         if (update(name)) {
1471             g_autofree gchar * fname = g_build_filename(installRoot.c_str(),
1472                     modulesPath.c_str(), (name + ".module").c_str(), NULL);
1473             iter.second.first.write(std::string(fname), false);
1474         }
1475     }
1476 }
1477 
rollback(void)1478 void ModulePackageContainer::Impl::ModulePersistor::rollback(void)
1479 {
1480     for (auto &iter : configs) {
1481         const auto &name = iter.first;
1482         reset(name);
1483     }
1484 }
1485 
1486 std::map<std::string, std::string>
getEnabledStreams()1487 ModulePackageContainer::Impl::ModulePersistor::getEnabledStreams()
1488 {
1489     std::map<std::string, std::string> enabled;
1490 
1491     for (const auto &it : configs) {
1492         const auto &name = it.first;
1493         const auto &newVal = it.second.second.state;
1494         const auto &oldVal = fromString(it.second.first.getValue(name, "state"));
1495 
1496         if (oldVal != ModuleState::ENABLED && newVal == ModuleState::ENABLED) {
1497             enabled.emplace(name, it.second.second.stream);
1498         }
1499     }
1500 
1501     return enabled;
1502 }
1503 
1504 std::vector<std::string>
getDisabledModules()1505 ModulePackageContainer::Impl::ModulePersistor::getDisabledModules()
1506 {
1507     std::vector<std::string> disabled;
1508 
1509     for (const auto & it : configs) {
1510         const auto & name = it.first;
1511         const auto & newVal = it.second.second.state;
1512         const auto & oldVal = fromString(it.second.first.getValue(name, "state"));
1513         if (oldVal != ModuleState::DISABLED && newVal == ModuleState::DISABLED) {
1514             disabled.emplace_back(name);
1515         }
1516     }
1517 
1518     return disabled;
1519 }
1520 
1521 std::map<std::string, std::string>
getDisabledStreams()1522 ModulePackageContainer::Impl::ModulePersistor::getDisabledStreams()
1523 {
1524     std::map<std::string, std::string> disabled;
1525 
1526     for (const auto &it : configs) {
1527         const auto &name = it.first;
1528         const auto &newVal = it.second.second.state;
1529         const auto &oldVal = fromString(it.second.first.getValue(name, "state"));
1530         if (oldVal != ModuleState::DISABLED && newVal == ModuleState::DISABLED) {
1531             disabled.emplace(name, it.second.first.getValue(name, "stream"));
1532         }
1533     }
1534 
1535     return disabled;
1536 }
1537 
1538 
1539 std::vector<std::string>
getResetModules()1540 ModulePackageContainer::Impl::ModulePersistor::getResetModules()
1541 {
1542     std::vector<std::string> result;
1543 
1544     for (const auto & it : configs) {
1545         const auto & name = it.first;
1546         const auto & newVal = it.second.second.state;
1547         const auto & oldVal = fromString(it.second.first.getValue(name, "state"));
1548         // when resetting module state, UNKNOWN and DEFAULT are treated equally,
1549         // because they are both represented as 'state=' in the config file
1550         // and the only difference is internal state based on module defaults
1551         if (oldVal == ModuleState::UNKNOWN || oldVal == ModuleState::DEFAULT) {
1552             continue;
1553         }
1554         if (newVal == ModuleState::UNKNOWN || newVal == ModuleState::DEFAULT) {
1555             result.emplace_back(name);
1556         }
1557     }
1558 
1559     return result;
1560 }
1561 
1562 std::map<std::string, std::string>
getResetStreams()1563 ModulePackageContainer::Impl::ModulePersistor::getResetStreams()
1564 {
1565     std::map<std::string, std::string> result;
1566 
1567     for (const auto &it : configs) {
1568         const auto &name = it.first;
1569         const auto &newVal = it.second.second.state;
1570         const auto &oldVal = fromString(it.second.first.getValue(name, "state"));
1571         // when resetting module state, UNKNOWN and DEFAULT are treated equally,
1572         // because they are both represented as 'state=' in the config file
1573         // and the only difference is internal state based on module defaults
1574         if (oldVal == ModuleState::UNKNOWN || oldVal == ModuleState::DEFAULT) {
1575             continue;
1576         }
1577         if (newVal == ModuleState::UNKNOWN || newVal == ModuleState::DEFAULT) {
1578             result.emplace(name, it.second.first.getValue(name, "stream"));
1579         }
1580     }
1581 
1582     return result;
1583 }
1584 
1585 std::map<std::string, std::pair<std::string, std::string>>
getSwitchedStreams()1586 ModulePackageContainer::Impl::ModulePersistor::getSwitchedStreams()
1587 {
1588     std::map<std::string, std::pair<std::string, std::string>> switched;
1589 
1590     for (const auto &it : configs) {
1591         const auto &name = it.first;
1592         const auto &oldVal = it.second.first.getValue(name, "stream");
1593         const auto &newVal = it.second.second.stream;
1594         // Do not report enabled stream as switched
1595         if (oldVal.empty()) {
1596             continue;
1597         }
1598         // Do not report disabled stream as switched
1599         if (newVal.empty()) {
1600             continue;
1601         }
1602         if (oldVal != newVal) {
1603             switched.emplace(name, std::make_pair(oldVal, newVal));
1604         }
1605     }
1606 
1607     return switched;
1608 }
1609 
1610 std::map<std::string, std::vector<std::string>>
getInstalledProfiles()1611 ModulePackageContainer::Impl::ModulePersistor::getInstalledProfiles()
1612 {
1613     std::map<std::string, std::vector<std::string>> profiles;
1614     for (auto & it : configs) {
1615         OptionStringList slist{std::vector<std::string>()};
1616         const auto & name = it.first;
1617         const auto & parser = it.second.first;
1618         auto & newProfiles = it.second.second.profiles;
1619 
1620         auto vprof = slist.fromString(parser.getValue(name, "profiles"));
1621         std::sort(vprof.begin(), vprof.end());
1622         std::sort(newProfiles.begin(), newProfiles.end());
1623         std::vector<std::string> profDiff;
1624         std::set_difference(newProfiles.begin(), newProfiles.end(),
1625                             vprof.begin(), vprof.end(),
1626                             std::back_inserter(profDiff));
1627 
1628         if (profDiff.size() > 0) {
1629             profiles.emplace(name, std::move(profDiff));
1630         }
1631     }
1632 
1633     return profiles;
1634 }
1635 
1636 std::map<std::string, std::vector<std::string>>
getRemovedProfiles()1637 ModulePackageContainer::Impl::ModulePersistor::getRemovedProfiles()
1638 {
1639     std::map<std::string, std::vector<std::string>> profiles;
1640 
1641     for (auto & it : configs) {
1642         OptionStringList slist{std::vector<std::string>()};
1643         const auto & name = it.first;
1644         const auto & parser = it.second.first;
1645         auto & newProfiles = it.second.second.profiles;
1646 
1647         auto vprof = slist.fromString(parser.getValue(name, "profiles"));
1648         std::sort(vprof.begin(), vprof.end());
1649         std::sort(newProfiles.begin(), newProfiles.end());
1650         std::vector<std::string> profDiff;
1651         std::set_difference(vprof.begin(), vprof.end(),
1652                             newProfiles.begin(), newProfiles.end(),
1653                             std::back_inserter(profDiff));
1654         if (profDiff.size() > 0) {
1655             profiles.emplace(name, std::move(profDiff));
1656         }
1657     }
1658 
1659     return profiles;
1660 }
1661 
loadFailSafeData()1662 void ModulePackageContainer::loadFailSafeData()
1663 {
1664     pImpl->addVersion2Modules();
1665     auto persistor = pImpl->persistor->configs;
1666 
1667     std::map<std::string, std::pair<std::string, bool>> enabledStreams;
1668     for (auto & nameConfig: persistor) {
1669         if (nameConfig.second.second.state == ModuleState::ENABLED) {
1670             auto & stream = nameConfig.second.second.stream;
1671             if (!stream.empty()) {
1672                 enabledStreams.emplace(nameConfig.first, std::make_pair(stream, false));
1673             }
1674         }
1675     }
1676     for (auto & modulePair: pImpl->modules) {
1677         auto module = modulePair.second.get();
1678         auto it = enabledStreams.find(module->getName());
1679         if (it != enabledStreams.end() && it->second.first == module->getStream()) {
1680             it->second.second = true;
1681         }
1682     }
1683     auto fileNames = getYamlFilenames(pImpl->persistDir.c_str());
1684     auto begin = fileNames.begin();
1685     auto end = fileNames.end();
1686     for (auto & pair: enabledStreams) {
1687         if (!pair.second.second) {
1688             // load from disk
1689             std::ostringstream ss;
1690             ss << pair.first << ":" << pair.second.first << ":";
1691             bool loaded = false;
1692             auto searchPrefix = ss.str();
1693             auto low = std::lower_bound(begin, end, searchPrefix, stringStartWithLowerComparator);
1694             for (; low != end && string::startsWith((*low), searchPrefix); ++low) {
1695                 g_autofree gchar * file = g_build_filename(
1696                     pImpl->persistDir.c_str(), low->c_str(), NULL);
1697                 try {
1698                     auto yamlContent = getFileContent(file);
1699                     add(yamlContent, LIBDNF_MODULE_FAIL_SAFE_REPO_NAME);
1700                     loaded = true;
1701                 } catch (const std::exception &) {
1702                     auto logger(Log::getLogger());
1703                     logger->debug(tfm::format(
1704                         _("Unable to load modular Fail-Safe data at '%s'"), file));
1705                 }
1706             }
1707             if (!loaded) {
1708                 auto logger(Log::getLogger());
1709                 logger->debug(tfm::format(
1710                             _("Unable to load modular Fail-Safe data for module '%s:%s'"),
1711                                           pair.first, pair.second.first));
1712             }
1713         }
1714     }
1715 }
1716 
getLatestActiveEnabledModules()1717 std::vector<ModulePackage *> ModulePackageContainer::Impl::getLatestActiveEnabledModules()
1718 {
1719     Query query(moduleSack, Query::ExcludeFlags::IGNORE_EXCLUDES);
1720     query.addFilter(HY_PKG, HY_EQ, activatedModules.get());
1721     query.addFilter(HY_PKG_REPONAME, HY_NEQ, HY_SYSTEM_REPO_NAME);
1722     query.addFilter(HY_PKG_LATEST_PER_ARCH, HY_EQ, 1);
1723     auto set = query.runSet();
1724 
1725     std::vector<ModulePackage *> activeModules;
1726     Id moduleId = -1;
1727     while ((moduleId = set->next(moduleId)) != -1) {
1728         auto modulePackage = modules.at(moduleId).get();
1729         if (isEnabled(modulePackage->getName(), modulePackage->getStream())) {
1730             activeModules.push_back(modulePackage);
1731         }
1732     }
1733     return activeModules;
1734 }
1735 
addVersion2Modules()1736 void ModulePackageContainer::Impl::addVersion2Modules()
1737 {
1738     if (modulesV2.empty()) {
1739         return;
1740     }
1741     std::map<std::string, std::map<std::string, std::vector<ModulePackage *>>> v3_context_map;
1742     for (auto const & module_pair : modules) {
1743         auto * module = module_pair.second.get();
1744         auto requires = module->getRequires(true);
1745         auto concentratedRequires = concentrateVectorString(requires);
1746         v3_context_map[module->getNameStream()][concentratedRequires].push_back(module);
1747     }
1748     libdnf::LibsolvRepo * repo;
1749     ModulemdModuleStream * mdStream;
1750     std::string repoID;
1751     g_autofree gchar * path = g_build_filename(installRoot.c_str(), "/etc/dnf/modules.d", NULL);
1752     for (auto & module_tuple : modulesV2) {
1753         std::tie(repo, mdStream, repoID) = module_tuple;
1754         auto nameStream = ModulePackage::getNameStream(mdStream);
1755         auto requires = ModulePackage::getRequires(mdStream, true);
1756         auto concentratedRequires = concentrateVectorString(requires);
1757         auto streamIterator = v3_context_map.find(nameStream);
1758         if (streamIterator != v3_context_map.end()) {
1759             auto contextIterator = streamIterator->second.find(concentratedRequires);
1760             if (contextIterator != streamIterator->second.end()) {
1761                 auto v3_context = contextIterator->second[0]->getContext();
1762                 std::unique_ptr<ModulePackage> modulePackage(new ModulePackage(moduleSack, repo, mdStream, repoID, v3_context));
1763                 persistor->insert(modulePackage->getName(), path);
1764                 modules.insert(std::make_pair(modulePackage->getId(), std::move(modulePackage)));
1765                 g_object_unref(mdStream);
1766                 continue;
1767             }
1768         }
1769         if (concentratedRequires.empty()) {
1770             concentratedRequires.append("NoRequires");
1771         }
1772         std::unique_ptr<ModulePackage> modulePackage(new ModulePackage(moduleSack, repo, mdStream, repoID, concentratedRequires));
1773         persistor->insert(modulePackage->getName(), path);
1774         modules.insert(std::make_pair(modulePackage->getId(), std::move(modulePackage)));
1775         g_object_unref(mdStream);
1776     }
1777     modulesV2.clear();
1778 }
1779 
updateFailSafeData()1780 void ModulePackageContainer::updateFailSafeData()
1781 {
1782     auto fileNames = getYamlFilenames(pImpl->persistDir.c_str());
1783 
1784     if (pImpl->activatedModules) {
1785         std::vector<ModulePackage *> latest = pImpl->getLatestActiveEnabledModules();
1786 
1787         if (g_mkdir_with_parents(pImpl->persistDir.c_str(), 0755) == -1) {
1788             const char * errTxt = strerror(errno);
1789             auto logger(Log::getLogger());
1790             logger->debug(tfm::format(
1791                 _("Unable to create directory \"%s\" for modular Fail Safe data: %s"),
1792                                       pImpl->persistDir.c_str(), errTxt));
1793         }
1794 
1795         // Update FailSafe data
1796         for (auto modulePackage: latest) {
1797             std::ostringstream ss;
1798             ss << modulePackage->getNameStream();
1799             ss << ":" << modulePackage->getArch() << ".yaml";
1800             auto fileName = ss.str();
1801             if (modulePackage->getRepoID() == LIBDNF_MODULE_FAIL_SAFE_REPO_NAME) {
1802                 continue;
1803             }
1804             g_autofree gchar * filePath = g_build_filename(pImpl->persistDir.c_str(), fileName.c_str(), NULL);
1805             if (!updateFile(filePath, modulePackage->getYaml().c_str())) {
1806                 auto logger(Log::getLogger());
1807                 logger->debug(tfm::format(_("Unable to save a modular Fail Safe data to '%s'"), filePath));
1808             }
1809         }
1810     }
1811 
1812     // Remove files from not enabled modules
1813     for (unsigned int index = 0; index < fileNames.size(); ++index) {
1814         auto fileName = fileNames[index];
1815         auto first = fileName.find(":");
1816         if (first == std::string::npos || first == 0) {
1817             continue;
1818         }
1819         std::string moduleName = fileName.substr(0, first);
1820         auto second = fileName.find(":", ++first);
1821         if (second == std::string::npos || first == second) {
1822             continue;
1823         }
1824         std::string moduleStream = fileName.substr(first, second - first);
1825 
1826         if (!isEnabled(moduleName, moduleStream)) {
1827             g_autofree gchar * file = g_build_filename(pImpl->persistDir.c_str(), fileNames[index].c_str(), NULL);
1828             if (remove(file)) {
1829                 auto logger(Log::getLogger());
1830                 logger->debug(tfm::format(_("Unable to remove a modular Fail Safe data in '%s'"), file));
1831             }
1832         }
1833     }
1834 }
1835 
applyObsoletes()1836 void ModulePackageContainer::applyObsoletes(){
1837     for (const auto &iter : pImpl->modules) {
1838         auto modulePkg = iter.second.get();
1839         if (!isEnabled(modulePkg)) {
1840             continue;
1841         }
1842         /* We cannot access the eol through the ModulemdModuleStream which is
1843          * wrapped in modulePkg because it was created with metedata from only
1844          * one repository but other repositories can have new eols which affect
1845          * this module stream. We have to use merged modular metadata from all
1846          * available repositories.
1847          */
1848         ModulemdObsoletes *modulePkgObsoletes = pImpl->moduleMetadata.getNewestActiveObsolete(modulePkg);
1849         if (modulePkgObsoletes) {
1850             auto moduleName = modulemd_obsoletes_get_obsoleted_by_module_name(modulePkgObsoletes);
1851             auto moduleStream = modulemd_obsoletes_get_obsoleted_by_module_stream(modulePkgObsoletes);
1852 
1853             if (moduleName && moduleStream) {
1854                 if (!isDisabled(moduleName)) {
1855                     enable(moduleName, moduleStream, false);
1856                     if (std::string(moduleName) != modulePkg->getName()) {
1857                         reset(modulePkg, false);
1858                     }
1859                 } else {
1860                     auto logger(Log::getLogger());
1861                     logger->debug(tfm::format(
1862                         _("Unable to apply modular obsoletes to '%s:%s' because target module '%s' is disabled"),
1863                         modulePkg->getName(), modulePkg->getStream(), moduleName));
1864                 }
1865             } else {
1866                 reset(modulePkg, false);
1867             }
1868         }
1869     }
1870 }
1871 
1872 }
1873