326 lines
10 KiB
C++
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;
|
|
}
|
|
/*}}}*/
|