Exheredludis/paludis/broken_linkage_finder.cc
2016-12-07 20:05:31 -08:00

464 lines
17 KiB
C++

/* vim: set sw=4 sts=4 et foldmethod=syntax : */
/*
* Copyright (c) 2007 David Leverton
*
* This file is part of the Paludis package manager. Paludis is free software;
* you can redistribute it and/or modify it under the terms of the GNU General
* Public License version 2, as published by the Free Software Foundation.
*
* Paludis is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <paludis/broken_linkage_finder.hh>
#include <paludis/broken_linkage_configuration.hh>
#include <paludis/elf_linkage_checker.hh>
#include <paludis/libtool_linkage_checker.hh>
#include <paludis/linkage_checker.hh>
#include <paludis/util/realpath.hh>
#include <paludis/util/log.hh>
#include <paludis/util/pimp-impl.hh>
#include <paludis/util/set-impl.hh>
#include <paludis/util/sequence-impl.hh>
#include <paludis/util/visitor_cast.hh>
#include <paludis/util/wrapped_forward_iterator-impl.hh>
#include <paludis/util/member_iterator-impl.hh>
#include <paludis/util/indirect_iterator-impl.hh>
#include <paludis/util/fs_stat.hh>
#include <paludis/util/fs_iterator.hh>
#include <paludis/util/fs_error.hh>
#include <paludis/util/join.hh>
#include <paludis/contents.hh>
#include <paludis/environment.hh>
#include <paludis/metadata_key.hh>
#include <paludis/package_id.hh>
#include <paludis/generator.hh>
#include <paludis/filter.hh>
#include <paludis/filtered_generator.hh>
#include <paludis/selection.hh>
#include <paludis/notifier_callback.hh>
#include <functional>
#include <algorithm>
#include <iterator>
#include <map>
#include <set>
#include <vector>
#include <mutex>
using namespace paludis;
typedef std::multimap<FSPath, std::shared_ptr<const PackageID>, FSPathComparator> Files;
typedef std::map<FSPath, std::set<std::string>, FSPathComparator> PackageBreakage;
typedef std::map<std::shared_ptr<const PackageID>, PackageBreakage, PackageIDSetComparator> Breakage;
namespace paludis
{
template <>
struct Imp<BrokenLinkageFinder>
{
const Environment * env;
const BrokenLinkageConfiguration config;
std::shared_ptr<const Sequence<std::string>> libraries;
std::vector<std::shared_ptr<LinkageChecker> > checkers;
std::set<FSPath, FSPathComparator> extra_lib_dirs;
std::mutex mutex;
bool has_files;
Files files;
Breakage breakage;
PackageBreakage orphan_breakage;
void search_directory(const FSPath &);
void walk_directory(const FSPath &);
void check_file(const FSPath &);
void add_breakage(const FSPath &, const std::string &);
void gather_package(const std::shared_ptr<const PackageID> &);
Imp(const Environment * the_env, const std::shared_ptr<const Sequence<std::string>> & the_libraries) :
env(the_env),
config(the_env->preferred_root_key()->parse_value()),
libraries(the_libraries),
has_files(false)
{
}
};
template <>
struct WrappedForwardIteratorTraits<BrokenLinkageFinder::BrokenPackageConstIteratorTag>
{
typedef FirstIteratorTypes<Breakage::const_iterator>::Type UnderlyingIterator;
};
template <>
struct WrappedForwardIteratorTraits<BrokenLinkageFinder::BrokenFileConstIteratorTag>
{
typedef FirstIteratorTypes<PackageBreakage::const_iterator>::Type UnderlyingIterator;
};
template <>
struct WrappedForwardIteratorTraits<BrokenLinkageFinder::MissingRequirementConstIteratorTag>
{
typedef std::set<std::string>::const_iterator UnderlyingIterator;
};
}
namespace
{
const std::map<FSPath, std::set<std::string>, FSPathComparator> no_files;
const std::set<std::string> no_reqs;
struct ParentOf : std::unary_function<FSPath, bool>
{
const FSPath & _child;
ParentOf(const FSPath & child) :
_child(child)
{
}
bool operator() (const FSPath & parent)
{
std::string child_str(stringify(_child)), parent_str(stringify(parent));
return 0 == child_str.compare(0, parent_str.length(), parent_str) &&
(parent_str.length() == child_str.length() || '/' == child_str[parent_str.length()]);
}
};
}
BrokenLinkageFinder::BrokenLinkageFinder(const Environment * env, const std::shared_ptr<const Sequence<std::string>> & libraries) :
_imp(env, libraries)
{
using namespace std::placeholders;
Context ctx("When checking for broken linkage in '" + stringify(env->preferred_root_key()->parse_value()) + "':");
_imp->checkers.push_back(std::shared_ptr<LinkageChecker>(std::make_shared<ElfLinkageChecker>(env->preferred_root_key()->parse_value(), libraries)));
if (libraries->empty())
_imp->checkers.push_back(std::shared_ptr<LinkageChecker>(std::make_shared<LibtoolLinkageChecker>(env->preferred_root_key()->parse_value())));
std::vector<FSPath> search_dirs_nosyms, search_dirs_pruned;
std::transform(_imp->config.begin_search_dirs(), _imp->config.end_search_dirs(),
std::back_inserter(search_dirs_nosyms),
std::bind(realpath_with_current_and_root, _1, FSPath("/"), env->preferred_root_key()->parse_value()));
std::sort(search_dirs_nosyms.begin(), search_dirs_nosyms.end(), FSPathComparator());
for (const auto & dir : search_dirs_nosyms)
if (search_dirs_pruned.end() == std::find_if(search_dirs_pruned.begin(), search_dirs_pruned.end(), ParentOf(dir)))
search_dirs_pruned.push_back(dir);
Log::get_instance()->message("broken_linkage_finder.config",
ll_debug, lc_context) << "After resolving symlinks and pruning subdirectories, SEARCH_DIRS=\"" <<
join(search_dirs_pruned.begin(), search_dirs_pruned.end(), " ") << "\"";
std::transform(_imp->config.begin_ld_so_conf(), _imp->config.end_ld_so_conf(),
std::inserter(_imp->extra_lib_dirs, _imp->extra_lib_dirs.begin()),
std::bind(realpath_with_current_and_root, _1, FSPath("/"), env->preferred_root_key()->parse_value()));
std::for_each(search_dirs_pruned.begin(), search_dirs_pruned.end(),
std::bind(&Imp<BrokenLinkageFinder>::search_directory, _imp.get(), _1));
for (const auto & dir : _imp->extra_lib_dirs)
{
Log::get_instance()->message("broken_linkage_finder.config", ll_debug, lc_context)
<< "Need to check for extra libraries in '" << (env->preferred_root_key()->parse_value() / dir) << "'";
std::for_each(indirect_iterator(_imp->checkers.begin()), indirect_iterator(_imp->checkers.end()),
std::bind(&LinkageChecker::add_extra_lib_dir, _1, env->preferred_root_key()->parse_value() / dir));
}
std::function<void (const FSPath &, const std::string &)> callback(
std::bind(&Imp<BrokenLinkageFinder>::add_breakage, _imp.get(), _1, _2));
std::for_each(indirect_iterator(_imp->checkers.begin()), indirect_iterator(_imp->checkers.end()),
std::bind(&LinkageChecker::need_breakage_added, _1, callback));
_imp->checkers.clear();
}
BrokenLinkageFinder::~BrokenLinkageFinder() = default;
void
Imp<BrokenLinkageFinder>::search_directory(const FSPath & directory)
{
FSPath dir(directory);
env->trigger_notifier_callback(NotifierCallbackLinkageStepEvent(dir));
do
{
dir = dir.dirname();
if (config.dir_is_masked(dir))
{
Log::get_instance()->message("broken_linkage_finder.skipping", ll_debug, lc_context)
<< "Skipping '" << directory << "' because '" << dir << "' is search-masked";
return;
}
}
while (FSPath("/") != dir);
FSPath with_root(env->preferred_root_key()->parse_value() / directory);
if (with_root.stat().is_directory())
walk_directory(with_root);
else
Log::get_instance()->message("broken_linkage_finder.missing", ll_debug, lc_context)
<< "'" << directory << "' is missing or not a directory";
}
void
Imp<BrokenLinkageFinder>::walk_directory(const FSPath & directory)
{
using namespace std::placeholders;
FSPath without_root(directory.strip_leading(env->preferred_root_key()->parse_value()));
if (config.dir_is_masked(without_root))
{
Log::get_instance()->message("broken_linkage_finder.masked", ll_debug, lc_context)
<< "'" << directory << "' is search-masked";
return;
}
Log::get_instance()->message("broken_linkage_finder.entering", ll_debug, lc_context)
<< "Entering directory '" << directory << "'";
{
std::unique_lock<std::mutex> l(mutex);
extra_lib_dirs.erase(without_root);
}
try
{
std::for_each(FSIterator(directory, { fsio_include_dotfiles, fsio_inode_sort }), FSIterator(),
std::bind(&Imp<BrokenLinkageFinder>::check_file, this, _1));
}
catch (const FSError & ex)
{
Log::get_instance()->message("broken_linkage_finder.failure", ll_warning, lc_no_context) << ex.message();
}
}
void
Imp<BrokenLinkageFinder>::check_file(const FSPath & file)
{
using namespace std::placeholders;
try
{
FSStat file_stat(file);
if (file_stat.is_symlink())
{
FSPath target(dereference_with_root(file, env->preferred_root_key()->parse_value()));
if (target.stat().is_regular_file())
{
std::for_each(indirect_iterator(checkers.begin()), indirect_iterator(checkers.end()),
std::bind(&LinkageChecker::note_symlink, _1, file, target));
}
}
else if (file_stat.is_directory())
walk_directory(file);
else if (file_stat.is_regular_file())
{
env->trigger_notifier_callback(NotifierCallbackLinkageStepEvent(file));
if (indirect_iterator(checkers.end()) ==
std::find_if(indirect_iterator(checkers.begin()), indirect_iterator(checkers.end()),
std::bind(&LinkageChecker::check_file, _1, file)))
Log::get_instance()->message("broken_linkage_finder.unrecognised", ll_debug, lc_context)
<< "'" << file << "' is not a recognised file type";
}
}
catch (const FSError & ex)
{
Log::get_instance()->message("broken_linkage_finder.failure", ll_warning, lc_no_context) << ex.message();
}
}
void
Imp<BrokenLinkageFinder>::add_breakage(const FSPath & file, const std::string & req)
{
using namespace std::placeholders;
if (libraries->empty() && config.lib_is_masked(req))
return;
if (! has_files)
{
has_files = true;
Context ctx("When building map from files to packages:");
std::shared_ptr<const PackageIDSequence> pkgs((*env)[selection::AllVersionsUnsorted(
generator::All() | filter::InstalledAtRoot(env->preferred_root_key()->parse_value()))]);
std::for_each(pkgs->begin(), pkgs->end(),
std::bind(&Imp<BrokenLinkageFinder>::gather_package, this, _1));
}
FSPath without_root(file.strip_leading(env->preferred_root_key()->parse_value()));
std::pair<Files::const_iterator, Files::const_iterator> range(files.equal_range(without_root));
if (range.first == range.second)
orphan_breakage[without_root].insert(req);
else
while (range.first != range.second)
{
breakage[range.first->second][without_root].insert(req);
++range.first;
}
}
void
Imp<BrokenLinkageFinder>::gather_package(const std::shared_ptr<const PackageID> & pkg)
{
using namespace std::placeholders;
Context ctx("When gathering the contents of " + stringify(*pkg) + ":");
auto contents(pkg->contents());
if (! contents)
return;
for (const auto & content : *contents)
{
if (const auto *file = visitor_cast<const ContentsFileEntry>(*content))
{
std::unique_lock<std::mutex> l(mutex);
files.insert(std::make_pair(file->location_key()->parse_value(), pkg));
}
}
pkg->can_drop_in_memory_cache();
}
BrokenLinkageFinder::BrokenPackageConstIterator
BrokenLinkageFinder::begin_broken_packages() const
{
return BrokenPackageConstIterator(first_iterator(_imp->breakage.begin()));
}
BrokenLinkageFinder::BrokenPackageConstIterator
BrokenLinkageFinder::end_broken_packages() const
{
return BrokenPackageConstIterator(first_iterator(_imp->breakage.end()));
}
IteratorRange<BrokenLinkageFinder::BrokenPackageConstIterator>
BrokenLinkageFinder::broken_packages() const
{
return {begin_broken_packages(), end_broken_packages()};
}
BrokenLinkageFinder::BrokenFileConstIterator
BrokenLinkageFinder::begin_broken_files(const std::shared_ptr<const PackageID> & pkg) const
{
if (pkg)
{
Breakage::const_iterator it(_imp->breakage.find(pkg));
if (_imp->breakage.end() == it)
return BrokenFileConstIterator(no_files.begin());
return BrokenFileConstIterator(first_iterator(it->second.begin()));
}
return BrokenFileConstIterator(first_iterator(_imp->orphan_breakage.begin()));
}
BrokenLinkageFinder::BrokenFileConstIterator
BrokenLinkageFinder::end_broken_files(const std::shared_ptr<const PackageID> & pkg) const
{
if (pkg)
{
Breakage::const_iterator it(_imp->breakage.find(pkg));
if (_imp->breakage.end() == it)
return BrokenFileConstIterator(no_files.end());
return BrokenFileConstIterator(first_iterator(it->second.end()));
}
return BrokenFileConstIterator(first_iterator(_imp->orphan_breakage.end()));
}
IteratorRange<BrokenLinkageFinder::BrokenFileConstIterator>
BrokenLinkageFinder::broken_files(const std::shared_ptr<const PackageID> & package) const
{
return {begin_broken_files(package), end_broken_files(package)};
}
BrokenLinkageFinder::MissingRequirementConstIterator
BrokenLinkageFinder::begin_missing_requirements(const std::shared_ptr<const PackageID> & pkg,
const FSPath & file) const
{
if (pkg)
{
Breakage::const_iterator pkg_it(_imp->breakage.find(pkg));
if (_imp->breakage.end() == pkg_it)
return MissingRequirementConstIterator(no_reqs.begin());
PackageBreakage::const_iterator file_it(pkg_it->second.find(file));
if (pkg_it->second.end() == file_it)
return MissingRequirementConstIterator(no_reqs.begin());
return MissingRequirementConstIterator(file_it->second.begin());
}
else
{
PackageBreakage::const_iterator file_it(_imp->orphan_breakage.find(file));
if (_imp->orphan_breakage.end() == file_it)
return MissingRequirementConstIterator(no_reqs.begin());
return MissingRequirementConstIterator(file_it->second.begin());
}
}
BrokenLinkageFinder::MissingRequirementConstIterator
BrokenLinkageFinder::end_missing_requirements(const std::shared_ptr<const PackageID> & pkg,
const FSPath & file) const
{
if (pkg)
{
Breakage::const_iterator pkg_it(_imp->breakage.find(pkg));
if (_imp->breakage.end() == pkg_it)
return MissingRequirementConstIterator(no_reqs.end());
PackageBreakage::const_iterator file_it(pkg_it->second.find(file));
if (pkg_it->second.end() == file_it)
return MissingRequirementConstIterator(no_reqs.end());
return MissingRequirementConstIterator(file_it->second.end());
}
else
{
PackageBreakage::const_iterator file_it(_imp->orphan_breakage.find(file));
if (_imp->orphan_breakage.end() == file_it)
return MissingRequirementConstIterator(no_reqs.end());
return MissingRequirementConstIterator(file_it->second.end());
}
}
IteratorRange<BrokenLinkageFinder::MissingRequirementConstIterator>
BrokenLinkageFinder::missing_requirements(const std::shared_ptr<const PackageID> & package,
const FSPath & file) const
{
return {begin_missing_requirements(package, file), end_missing_requirements(package, file)};
}
namespace paludis
{
template class PALUDIS_VISIBLE WrappedForwardIterator<BrokenLinkageFinder::BrokenPackageConstIteratorTag, const std::shared_ptr<const PackageID>>;
template class PALUDIS_VISIBLE WrappedForwardIterator<BrokenLinkageFinder::BrokenFileConstIteratorTag, const FSPath>;
template class PALUDIS_VISIBLE WrappedForwardIterator<BrokenLinkageFinder::MissingRequirementConstIteratorTag, const std::string>;
}