1
0
Fork 0
apt/apt-private/private-sources.cc
Daniel Baumann 6810ba718b
Adding upstream version 3.0.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-20 21:10:43 +02:00

326 lines
10 KiB
C++

#include <config.h>
#include <apt-pkg/cachefile.h>
#include <apt-pkg/cmndline.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/error.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/hashes.h>
#include <apt-pkg/metaindex.h>
#include <apt-pkg/sourcelist.h>
#include <apt-pkg/strutl.h>
#include <apt-private/private-output.h>
#include <apt-private/private-sources.h>
#include <apt-private/private-utils.h>
#include <cstddef>
#include <iostream>
#include <regex>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <apti18n.h>
using namespace std::literals;
/* Interface discussion with donkult (for the future):
apt [add-{archive,release,component}|edit|change-release|disable]-sources
and be clever and work out stuff from the Release file
*/
// EditSource - EditSourcesList /*{{{*/
class APT_HIDDEN ScopedGetLock {
public:
int fd;
explicit ScopedGetLock(std::string const &filename) : fd(GetLock(filename)) {}
~ScopedGetLock() { close(fd); }
};
bool EditSources(CommandLine &CmdL)
{
std::string sourceslist;
if (CmdL.FileList[1] != NULL)
{
sourceslist = _config->FindDir("Dir::Etc::sourceparts") + CmdL.FileList[1];
} else {
sourceslist = _config->FindFile("Dir::Etc::sourcelist");
}
HashString before;
if (FileExists(sourceslist))
before.FromFile(sourceslist);
else
{
FileFd filefd;
if (filefd.Open(sourceslist, FileFd::Create | FileFd::WriteOnly, FileFd::None, 0644) == false)
return false;
}
ScopedGetLock lock(sourceslist);
if (lock.fd < 0)
return false;
bool res;
bool file_changed = false;
do {
if (EditFileInSensibleEditor(sourceslist) == false)
return false;
if (before.empty())
{
struct stat St;
if (stat(sourceslist.c_str(), &St) == 0 && St.st_size == 0)
RemoveFile("edit-sources", sourceslist);
}
else if (FileExists(sourceslist) && !before.VerifyFile(sourceslist))
{
file_changed = true;
pkgCacheFile::RemoveCaches();
}
pkgCacheFile CacheFile;
res = CacheFile.BuildCaches(nullptr);
if (res == false || _error->empty(GlobalError::WARNING) == false) {
std::string outs;
strprintf(outs, _("Failed to parse %s. Edit again? "), sourceslist.c_str());
// FIXME: should we add a "restore previous" option here?
if (YnPrompt(outs.c_str(), true) == false)
{
if (res == false && _error->PendingError() == false)
{
CacheFile.Close();
pkgCacheFile::RemoveCaches();
res = CacheFile.BuildCaches(nullptr);
}
break;
}
}
} while (res == false);
if (res == true && file_changed == true)
{
ioprintf(
std::cout, _("Your '%s' file changed, please run 'apt-get update'.\n"),
sourceslist.c_str());
}
return res;
}
/*}}}*/
template <typename T, typename V>
static auto contains(T const &container, V const &value) -> bool
{
return std::find(container.begin(), container.end(), value) != container.end();
}
static auto merge(std::vector<std::string> &a, std::vector<std::string> const &b)
{
for (auto const &e : b)
if (not contains(a, e))
a.push_back(e);
}
static bool Modernize(std::string const &filename) /*{{{*/
{
auto isMain = filename == _config->FindFile("Dir::Etc::SourceList");
auto simulate = _config->FindB("APT::Get::Simulate");
std::cerr << "Modernizing " << filename << "...\n";
pkgSourceList list;
if (not list.Read(filename))
return false;
struct Entry
{
std::set<std::string> types;
std::vector<std::string> uris;
std::vector<std::string> suites;
std::vector<std::string> components;
std::string signedBy;
std::map<std::string, std::string> options;
std::string origin;
bool merged = false;
auto Merge(Entry &other) -> bool
{
auto howManyDifferent = (types != other.types) + (uris != other.uris) + (suites != other.suites) + (components != other.components);
if (howManyDifferent > 1)
return false;
if (options != other.options)
return false;
if (origin != other.origin)
return false;
if (signedBy.empty() && not other.signedBy.empty() && (uris != other.uris || suites != other.suites))
return false;
if (not signedBy.empty() && not other.signedBy.empty() && signedBy != other.signedBy)
return false;
types.insert(other.types.begin(), other.types.end());
merge(uris, other.uris);
merge(suites, other.suites);
merge(components, other.components);
if (signedBy.empty())
signedBy = other.signedBy;
other.merged = true;
return true;
}
};
std::vector<Entry> entries;
for (auto const &meta : list)
{
Entry e;
e.uris.push_back(meta->GetURI());
e.suites.push_back(meta->GetDist());
for (auto const &t : meta->GetIndexTargets())
{
e.types.insert(t.Option(IndexTarget::TARGET_OF));
if (not contains(e.components, t.Option(IndexTarget::COMPONENT)))
e.components.push_back(t.Option(IndexTarget::COMPONENT));
}
if (meta->IsTrustedSet())
e.options["Trusted"] = "yes";
std::string err;
e.signedBy = meta->GetSignedBy();
meta->Load(&err);
if (e.signedBy.empty() && not meta->GetOrigin().empty())
{
std::string dir = _config->FindDir("Dir") + std::string{"usr/share/keyrings/"};
std::string keyring = std::regex_replace(meta->GetOrigin(), std::regex(" "), "-") + "-archive-keyring.gpg";
std::transform(keyring.begin(), keyring.end(), keyring.begin(), tolower);
if (FileExists(dir + keyring))
e.signedBy = dir + keyring;
}
if (auto k = _config->FindDir("Dir::Etc::trustedparts") += flNotDir(std::regex_replace(filename, std::regex("\\.list$"), ".gpg")); FileExists(k))
e.signedBy = k;
if (auto k = _config->FindDir("Dir::Etc::trustedparts") += flNotDir(std::regex_replace(filename, std::regex("\\.list$"), ".asc")); FileExists(k))
e.signedBy = k;
if (isMain && not meta->GetOrigin().empty())
{
constexpr auto bad = "\\|{}[]<>\"^~_=!@#$%^&*"sv;
e.origin = meta->GetOrigin();
std::transform(e.origin.begin(), e.origin.end(), e.origin.begin(), tolower);
std::transform(e.origin.begin(), e.origin.end(), e.origin.begin(), [](char c) -> char
{ return isspace(c) ? '-' : c; });
std::transform(e.origin.begin(), e.origin.end(), e.origin.begin(), [bad](char c) -> char
{ return bad.find(c) != bad.npos ? '-' : c; });
std::replace(e.origin.begin(), e.origin.end(), '/', '-');
}
entries.push_back(std::move(e));
}
for (bool merged = false; merged;)
{
merged = false;
for (auto it = entries.begin(); it != entries.end(); ++it)
{
for (auto it2 = it + 1; it2 != entries.end(); ++it2)
if (not it2->merged)
merged |= it->Merge(*it2);
}
}
std::map<std::string, std::ofstream> streams;
for (auto const &e : entries)
{
std::string outname;
if (not isMain)
outname = std::regex_replace(filename, std::regex("\\.list$"), ".sources");
else if (e.origin.empty())
outname = _config->FindDir("Dir::Etc::SourceParts") + "moved-from-main.sources";
else
outname = _config->FindDir("Dir::Etc::SourceParts") + (e.origin) + ".sources";
if (auto it = streams.find(outname); not simulate && it == streams.end())
{
std::cerr << "- Writing " << outname << "\n";
streams[outname].open(outname, std::ios::app);
}
auto &out = simulate ? std::cout : streams[outname];
if (not out)
_error->Warning("Cannot open %s for writing.", outname.c_str());
if (e.merged)
continue;
if (out.tellp() != 0)
out << "\n";
if (simulate)
out << "# Would write to: " << outname << "\n";
if (isMain)
out << "# Modernized from " << filename << "\n";
out << "Types:";
for (auto const &t : e.types)
out << " " << t;
out << "\n";
out << "URIs: " << APT::String::Join(e.uris, " ") << "\n";
out << "Suites: " << APT::String::Join(e.suites, " ") << "\n";
out << "Components: " << APT::String::Join(e.components, " ") << "\n";
out << "Signed-By: " << e.signedBy << "\n";
for (auto const &[key, value] : e.options)
out << key << ": " << value << "\n";
if (e.signedBy.empty())
_error->Warning("Could not determine Signed-By for URIs: %s, Suites: %s", APT::String::Join(e.uris, " ").c_str(), APT::String::Join(e.suites, " ").c_str());
}
if (not simulate && rename(filename.c_str(), (filename + ".bak").c_str()) != 0)
_error->WarningE("rename", "Could not rename %s", filename.c_str());
_error->DumpErrors();
std::cerr << "\n";
return true;
}
/*}}}*/
bool ModernizeSources(CommandLine &) /*{{{*/
{
auto main = _config->FindFile("Dir::Etc::SourceList");
auto parts = _config->FindDir("Dir::Etc::SourceParts");
std::vector<std::string> files;
if (FileExists(main))
files.push_back(main);
for (auto const &I : GetListOfFilesInDir(parts, std::vector<std::string>{"list", "sources"}, true))
if (APT::String::Endswith(I, ".list"))
files.push_back(I);
if (files.empty())
{
std::cout << "All sources are modern.\n";
return true;
}
// TRANSLATOR: "No" answer printed for a yes/no question if --assume-no is set
auto no = _("N");
std::cout << "The following files need modernizing:\n";
for (auto const &I : files)
std::cout << " - " << I << "\n";
std::cout << "\n"
<< "Modernizing will replace .list files with the new .sources format,\n"
<< "add Signed-By values where they can be determined automatically,\n"
<< "and save the old files into .list.bak files.\n"
<< "\n"
<< "This command supports the 'signed-by' and 'trusted' options. If you\n"
<< "have specified other options inside [] brackets, please transfer them\n"
<< "manually to the output files; see sources.list(5) for a mapping.\n"
<< "\n"
<< "For a simulation, respond " << no << " in the following prompt.\n";
std::string prompt;
strprintf(prompt, _("Rewrite %zu sources?"), files.size());
if (YnPrompt(prompt.c_str(), true) == false)
{
_config->Set("APT::Get::Simulate", true);
std::cout << "Simulating only..." << std::endl;
// Make it visible.
usleep(100 * 1000);
}
bool good = true;
for (auto const &I : files)
good = good && Modernize(I);
return good;
}
/*}}}*/