64ba7d5be8
Convert a number of destructors to default synthesized functions. Try to inline a few instances into the header. It should be possible to inline all of them, however, gcc seems to emit a number of warnings. Furthermore, some of the destructors are pure-virtualed, but provide an implementation. Placing the definition into the header causes ODR violations.
1164 lines
39 KiB
C++
1164 lines
39 KiB
C++
/* vim: set sw=4 sts=4 et foldmethod=syntax : */
|
|
|
|
/*
|
|
* Copyright (c) 2007, 2008, 2009, 2010, 2011 Ciaran McCreesh
|
|
* Copyright (c) 2008 Fernando J. Pereda
|
|
*
|
|
* 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/fs_merger.hh>
|
|
#include <paludis/util/enum_iterator.hh>
|
|
#include <paludis/util/stringify.hh>
|
|
#include <paludis/util/fd_holder.hh>
|
|
#include <paludis/util/log.hh>
|
|
#include <paludis/util/options.hh>
|
|
#include <paludis/util/hashes.hh>
|
|
#include <paludis/util/pimp-impl.hh>
|
|
#include <paludis/util/set.hh>
|
|
#include <paludis/util/timestamp.hh>
|
|
#include <paludis/util/make_named_values.hh>
|
|
#include <paludis/util/fs_stat.hh>
|
|
#include <paludis/util/fs_iterator.hh>
|
|
#include <paludis/util/fs_error.hh>
|
|
#include <paludis/selinux/security_context.hh>
|
|
#include <paludis/environment.hh>
|
|
#include <paludis/hook.hh>
|
|
#include <paludis/partitioning.hh>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include <list>
|
|
#include <set>
|
|
#include <unordered_map>
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef HAVE_XATTRS
|
|
# include <sys/xattr.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_FALLOCATE
|
|
# include <linux/falloc.h>
|
|
#endif
|
|
|
|
using namespace paludis;
|
|
|
|
#include <paludis/fs_merger-se.cc>
|
|
|
|
typedef std::unordered_map<std::pair<dev_t, ino_t>, std::string, Hash<std::pair<dev_t, ino_t> > > MergedMap;
|
|
|
|
namespace paludis
|
|
{
|
|
template <>
|
|
struct Imp<FSMerger>
|
|
{
|
|
MergedMap merged_ids;
|
|
FSMergerParams params;
|
|
std::set<FSPath, FSPathComparator> elided_paths;
|
|
|
|
Imp(const FSMergerParams & p) :
|
|
params(p)
|
|
{
|
|
}
|
|
|
|
bool is_elided_directory(const FSPath & dir) const
|
|
{
|
|
for (FSIterator dentry(dir, { fsio_include_dotfiles }), invalid;
|
|
dentry != invalid; ++dentry)
|
|
{
|
|
const auto path = dentry->strip_leading(params.image());
|
|
const auto part = params.parts()->classify(path);
|
|
|
|
if (path.stat().is_directory())
|
|
if (! is_elided_directory(*dentry))
|
|
return false;
|
|
|
|
if (params.should_merge()(path))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
}
|
|
|
|
FSMergerError::FSMergerError(const std::string & s) noexcept :
|
|
MergerError(s)
|
|
{
|
|
}
|
|
|
|
FSMerger::FSMerger(const FSMergerParams & p) :
|
|
Merger(make_named_values<MergerParams>(
|
|
n::environment() = p.environment(),
|
|
n::fix_mtimes_before() = p.fix_mtimes_before(),
|
|
n::get_new_ids_or_minus_one() = p.get_new_ids_or_minus_one(),
|
|
n::image() = p.image(),
|
|
n::install_under() = p.install_under(),
|
|
n::maybe_output_manager() = p.maybe_output_manager(),
|
|
n::merged_entries() = p.merged_entries(),
|
|
n::no_chown() = p.no_chown(),
|
|
n::options() = p.options(),
|
|
n::permit_destination() = p.permit_destination(),
|
|
n::root() = p.root()
|
|
)),
|
|
_imp(p)
|
|
{
|
|
}
|
|
|
|
FSMerger::~FSMerger() = default;
|
|
|
|
void
|
|
FSMerger::merge()
|
|
{
|
|
struct SaveUmask
|
|
{
|
|
mode_t m;
|
|
|
|
SaveUmask(mode_t mm) :
|
|
m(mm)
|
|
{
|
|
}
|
|
|
|
~SaveUmask()
|
|
{
|
|
::umask(m);
|
|
}
|
|
} old_umask(::umask(0000));
|
|
|
|
Merger::merge();
|
|
}
|
|
|
|
void
|
|
FSMerger::prepare_install_under()
|
|
{
|
|
Context context("When preparing install_under directory '" + stringify(_imp->params.install_under()) + "' under root '"
|
|
+ stringify(_imp->params.root()) + "':");
|
|
|
|
std::list<FSPath> dd;
|
|
for (FSPath d(_imp->params.root().realpath() / _imp->params.install_under()), d_end(_imp->params.root().realpath()) ;
|
|
d != d_end ; d = d.dirname())
|
|
dd.push_front(d);
|
|
for (auto & d : dd)
|
|
if (! d.stat().exists())
|
|
{
|
|
d.mkdir(0755, { });
|
|
track_install_under_dir(d, FSMergerStatusFlags());
|
|
}
|
|
else
|
|
track_install_under_dir(d, FSMergerStatusFlags() + msi_used_existing);
|
|
}
|
|
|
|
void
|
|
FSMerger::on_file_over_nothing(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
track_install_file(src, dst, src.basename(), install_file(src, dst, src.basename()));
|
|
}
|
|
|
|
void
|
|
FSMerger::on_file_over_file(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
if (config_protected(src, dst))
|
|
{
|
|
std::string cfgpro_name(make_config_protect_name(src, dst));
|
|
track_install_file(src, dst, cfgpro_name, install_file(src, dst, cfgpro_name));
|
|
}
|
|
else
|
|
track_install_file(src, dst, src.basename(), install_file(src, dst, src.basename()) + msi_unlinked_first);
|
|
}
|
|
|
|
void
|
|
FSMerger::on_file_over_dir(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
on_error(is_check, "Cannot overwrite directory '" + stringify(dst / src.basename()) + "' with file '"
|
|
+ stringify(src) + "'");
|
|
}
|
|
|
|
void
|
|
FSMerger::on_file_over_sym(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
track_install_file(src, dst, src.basename(), install_file(src, dst, src.basename()) + msi_unlinked_first);
|
|
}
|
|
|
|
void
|
|
FSMerger::on_file_over_misc(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
track_install_file(src, dst, src.basename(), install_file(src, dst, src.basename()) + msi_unlinked_first);
|
|
}
|
|
|
|
void
|
|
FSMerger::on_dir_over_nothing(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
track_install_dir(src, dst, install_dir(src, dst));
|
|
}
|
|
|
|
void
|
|
FSMerger::on_dir_over_file(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
on_error(is_check, "Cannot overwrite file '" + stringify(dst / src.basename()) + "' with directory '"
|
|
+ stringify(src) + "'");
|
|
}
|
|
|
|
void
|
|
FSMerger::on_dir_over_dir(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
track_install_dir(src, dst, { msi_used_existing });
|
|
}
|
|
|
|
void
|
|
FSMerger::on_dir_over_sym(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
EntryType m;
|
|
try
|
|
{
|
|
m = entry_type((dst / src.basename()).realpath());
|
|
}
|
|
catch (const FSError &)
|
|
{
|
|
m = et_nothing;
|
|
}
|
|
|
|
if (m == et_dir)
|
|
{
|
|
if (_imp->params.fs_merger_options()[fsmo_dir_over_sym_dir_is_error])
|
|
on_error(is_check, "Expected '" + stringify(dst / src.basename()) +
|
|
"' to be a directory but found a symlink to a directory");
|
|
else
|
|
on_warn(is_check, "Expected '" + stringify(dst / src.basename()) +
|
|
"' to be a directory but found a symlink to a directory");
|
|
|
|
if (! is_check)
|
|
track_install_dir(src, dst, { msi_used_existing });
|
|
}
|
|
else
|
|
on_error(is_check, "Expected '" + stringify(dst / src.basename()) +
|
|
"' to be a directory but found a symlink to a non-directory");
|
|
}
|
|
|
|
void
|
|
FSMerger::on_dir_over_misc(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
unlink_misc(dst / src.basename());
|
|
track_install_dir(src, dst, install_dir(src, dst) + msi_unlinked_first);
|
|
}
|
|
|
|
void
|
|
FSMerger::on_sym_over_nothing(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
track_install_sym(src, dst, install_sym(src, dst));
|
|
}
|
|
|
|
void
|
|
FSMerger::on_sym_over_file(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
unlink_file(dst / src.basename());
|
|
track_install_sym(src, dst, install_sym(src, dst) + msi_unlinked_first);
|
|
}
|
|
|
|
void
|
|
FSMerger::on_sym_over_dir(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
on_error(is_check, "Cannot overwrite directory '" + stringify(dst / src.basename()) + "' with symlink '"
|
|
+ stringify(src) + "'");
|
|
}
|
|
|
|
void
|
|
FSMerger::on_sym_over_sym(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
unlink_sym(dst / src.basename());
|
|
track_install_sym(src, dst, install_sym(src, dst) + msi_unlinked_first);
|
|
}
|
|
|
|
void
|
|
FSMerger::on_sym_over_misc(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
if (is_check)
|
|
return;
|
|
|
|
unlink_misc(dst / src.basename());
|
|
track_install_sym(src, dst, install_sym(src, dst) + msi_unlinked_first);
|
|
}
|
|
|
|
FSMergerStatusFlags
|
|
FSMerger::install_file(const FSPath & src, const FSPath & dst_dir, const std::string & dst_name)
|
|
{
|
|
Context context("When installing file '" + stringify(src) + "' to '" + stringify(dst_dir) + "' with protection '"
|
|
+ stringify(dst_name) + "':");
|
|
|
|
FSPath dst_real(dst_dir / dst_name);
|
|
|
|
if (_imp->params.should_merge() &&
|
|
! _imp->params.should_merge()(dst_real.strip_leading(_imp->params.root().realpath())))
|
|
return { msi_unselected_part };
|
|
|
|
FSMergerStatusFlags result;
|
|
FSStat src_stat(src);
|
|
FSStat dst_real_stat(dst_real);
|
|
FSPath dst(dst_dir / (stringify(dst_name) + "|paludis-midmerge"));
|
|
|
|
if (dst_real_stat.is_regular_file())
|
|
dst_real.chmod(0);
|
|
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_install_file_pre")
|
|
("INSTALL_SOURCE", stringify(src))
|
|
("INSTALL_DESTINATION", stringify(dst_dir / src.basename()))
|
|
("REAL_DESTINATION", stringify(dst_real))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.file.pre_hooks.failure", ll_warning, lc_context) <<
|
|
"Merge of '" << src << "' to '" << dst_dir << "' pre hooks returned non-zero";
|
|
|
|
std::shared_ptr<const SecurityContext> secctx(MatchPathCon::get_instance()->match(stringify(dst_real), src_stat.permissions()));
|
|
FSCreateCon createcon(secctx);
|
|
if (0 != paludis::setfilecon(src, secctx))
|
|
throw FSMergerError("Could not set SELinux context on '"
|
|
+ stringify(src) + "': " + stringify(::strerror(errno)));
|
|
|
|
mode_t src_perms(src_stat.permissions());
|
|
if (0 != (src_perms & (S_ISVTX | S_ISUID | S_ISGID)))
|
|
result += msi_setid_bits;
|
|
|
|
bool do_copy(false);
|
|
|
|
if ((! _imp->params.options()[mo_nondestructive]) &&
|
|
0 == std::rename(stringify(src).c_str(), stringify(dst_real).c_str()))
|
|
{
|
|
result += msi_rename;
|
|
|
|
bool touch(_imp->merged_ids.end() == _imp->merged_ids.find(src_stat.lowlevel_id()));
|
|
_imp->merged_ids.insert(make_pair(src_stat.lowlevel_id(), stringify(dst_real)));
|
|
|
|
FSPath d(stringify(dst_real));
|
|
if (touch && ! _imp->params.options()[mo_preserve_mtimes])
|
|
if (! d.utime(Timestamp::now()))
|
|
throw FSMergerError("utime(" + stringify(dst_real) + ", 0) failed: " + stringify(::strerror(errno)));
|
|
|
|
/* set*id bits get partially clobbered on a rename on linux */
|
|
dst_real.chmod(src_perms);
|
|
}
|
|
else
|
|
{
|
|
do_copy = true;
|
|
std::pair<MergedMap::const_iterator, MergedMap::const_iterator> ii(_imp->merged_ids.equal_range(src_stat.lowlevel_id()));
|
|
for (MergedMap::const_iterator i = ii.first ; i != ii.second ; ++i)
|
|
{
|
|
if (0 == ::link(i->second.c_str(), stringify(dst).c_str()))
|
|
{
|
|
if (0 != std::rename(stringify(dst).c_str(), stringify(dst_real).c_str()))
|
|
throw FSMergerError("rename(" + stringify(dst) + ", " + stringify(dst_real) + ") failed: " + stringify(::strerror(errno)));
|
|
do_copy = false;
|
|
result += msi_as_hardlink;
|
|
break;
|
|
}
|
|
Log::get_instance()->message("merger.file.link_failed", ll_debug, lc_context)
|
|
<< "link(" << i->second << ", " << dst_real << ") failed: "
|
|
<< ::strerror(errno);
|
|
}
|
|
}
|
|
|
|
if (do_copy)
|
|
{
|
|
Log::get_instance()->message("merger.file.will_copy", ll_debug, lc_context) <<
|
|
"rename/link failed: " << ::strerror(errno) << ". Falling back to regular read/write copy";
|
|
|
|
FDHolder input_fd(::open(stringify(src).c_str(), O_RDONLY), false);
|
|
if (-1 == input_fd)
|
|
throw FSMergerError("Cannot read '" + stringify(src) + "': " + stringify(::strerror(errno)));
|
|
|
|
FDHolder output_fd(::open(stringify(dst).c_str(), O_WRONLY | O_CREAT, src_perms), false);
|
|
if (-1 == output_fd)
|
|
throw FSMergerError("Cannot write '" + stringify(dst) + "': " + stringify(::strerror(errno)));
|
|
|
|
if (! _imp->params.no_chown())
|
|
if (0 != ::fchown(output_fd, src_stat.owner(), src_stat.group()))
|
|
throw FSMergerError("Cannot fchown '" + stringify(dst) + "': " + stringify(::strerror(errno)));
|
|
|
|
#ifdef HAVE_FALLOCATE
|
|
if (0 != ::fallocate(output_fd, FALLOC_FL_KEEP_SIZE, 0, src_stat.file_size()))
|
|
switch (errno)
|
|
{
|
|
case EOPNOTSUPP:
|
|
case ENOSYS:
|
|
break;
|
|
|
|
case ENOSPC:
|
|
throw FSMergerError("fallocate '" + stringify(dst) + "' returned " + stringify(::strerror(errno)));
|
|
|
|
default:
|
|
Log::get_instance()->message("merger.file.fallocate_failed", ll_debug, lc_context) <<
|
|
"fallocate '" + stringify(dst) + "' returned " + stringify(::strerror(errno));
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
/* set*id bits, after fallocate because xfs is weird */
|
|
if (0 != ::fchmod(output_fd, src_perms))
|
|
throw FSMergerError("Cannot fchmod '" + stringify(dst) + "': " + stringify(::strerror(errno)));
|
|
try_to_copy_xattrs(src, output_fd, result);
|
|
|
|
char buf[4096];
|
|
ssize_t count;
|
|
while ((count = read(input_fd, buf, 4096)) > 0)
|
|
if (-1 == write(output_fd, buf, count))
|
|
throw FSMergerError("write failed: " + stringify(::strerror(errno)));
|
|
if (-1 == count)
|
|
throw FSMergerError("read failed: " + stringify(::strerror(errno)));
|
|
|
|
/* might need to copy mtime */
|
|
if (_imp->params.options()[mo_preserve_mtimes])
|
|
{
|
|
Timestamp timestamp(src_stat.mtim());
|
|
struct timespec ts[2];
|
|
ts[0] = ts[1] = timestamp.as_timespec();
|
|
if (0 != ::futimens(output_fd, ts))
|
|
throw FSMergerError("Cannot futimens '" + stringify(dst) + "': " + stringify(::strerror(errno)));
|
|
}
|
|
|
|
if (0 != std::rename(stringify(dst).c_str(), stringify(dst_real).c_str()))
|
|
throw FSMergerError(
|
|
"rename(" + stringify(dst) + ", " + stringify(dst_real) + ") failed: " + stringify(::strerror(errno)));
|
|
|
|
_imp->merged_ids.insert(make_pair(src_stat.lowlevel_id(), stringify(dst_real)));
|
|
}
|
|
|
|
if (fixed_ownership_for(src))
|
|
result += msi_fixed_ownership;
|
|
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_install_file_post")
|
|
("INSTALL_SOURCE", stringify(src))
|
|
("INSTALL_DESTINATION", stringify(dst_dir / src.basename()))
|
|
("REAL_DESTINATION", stringify(dst_real))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.file.post_hooks.failed", ll_warning, lc_context) <<
|
|
"Merge of '" << src << "' to '" << dst_dir << "' post hooks returned non-zero";
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
FSMerger::track_renamed_dir_recursive(const FSPath & dst)
|
|
{
|
|
for (FSIterator d(dst, { fsio_include_dotfiles, fsio_inode_sort }), d_end ; d != d_end ; ++d)
|
|
{
|
|
FSMergerStatusFlags merged_how({ msi_parent_rename });
|
|
if (fixed_ownership_for(_imp->params.image() / *d))
|
|
merged_how += msi_fixed_ownership;
|
|
EntryType m(entry_type(*d));
|
|
switch (m)
|
|
{
|
|
case et_sym:
|
|
rewrite_symlink_as_needed(*d, dst);
|
|
track_install_sym(*d, dst, merged_how);
|
|
_imp->merged_ids.insert(make_pair(d->stat().lowlevel_id(), stringify(*d)));
|
|
continue;
|
|
|
|
case et_file:
|
|
{
|
|
FSStat d_star_stat(*d);
|
|
bool touch(_imp->merged_ids.end() == _imp->merged_ids.find(d_star_stat.lowlevel_id()));
|
|
_imp->merged_ids.insert(make_pair(d_star_stat.lowlevel_id(), stringify(*d)));
|
|
|
|
if (touch && ! _imp->params.options()[mo_preserve_mtimes])
|
|
if (! d->utime(Timestamp::now()))
|
|
throw FSMergerError("utime(" + stringify(*d) + ", 0) failed: " + stringify(::strerror(errno)));
|
|
track_install_file(*d, dst, stringify(d->basename()), merged_how);
|
|
}
|
|
continue;
|
|
|
|
case et_dir:
|
|
track_install_dir(*d, d->dirname(), merged_how);
|
|
track_renamed_dir_recursive(*d);
|
|
continue;
|
|
|
|
case et_misc:
|
|
throw FSMergerError("Unexpected 'et_misc' entry found at: " + stringify(*d));
|
|
|
|
case et_nothing:
|
|
case last_et:
|
|
break;
|
|
}
|
|
|
|
throw InternalError(PALUDIS_HERE, "Unexpected entry_type '" + stringify(m) + "'");
|
|
}
|
|
}
|
|
|
|
void
|
|
FSMerger::relabel_dir_recursive(const FSPath & src, const FSPath & dst)
|
|
{
|
|
for (FSIterator d(src, { fsio_include_dotfiles, fsio_inode_sort }), d_end ; d != d_end ; ++d)
|
|
{
|
|
FSStat d_star_stat(*d);
|
|
|
|
mode_t mode(d_star_stat.permissions());
|
|
std::shared_ptr<const SecurityContext> secctx(
|
|
MatchPathCon::get_instance()->match(stringify(dst / d->basename()), mode));
|
|
if (0 != paludis::setfilecon(*d, secctx))
|
|
throw FSMergerError("Could not set SELinux context on '"
|
|
+ stringify(*d) + "' : " + stringify(::strerror(errno)));
|
|
if (d_star_stat.is_directory())
|
|
relabel_dir_recursive(*d, dst / d->basename());
|
|
}
|
|
}
|
|
|
|
FSMergerStatusFlags
|
|
FSMerger::install_dir(const FSPath & src, const FSPath & dst_dir)
|
|
{
|
|
Context context("When installing dir '" + stringify(src) + "' to '" + stringify(dst_dir) + "':");
|
|
|
|
const FSPath dst(dst_dir / src.basename());
|
|
FSMergerStatusFlags result;
|
|
FSStat src_stat(src);
|
|
|
|
if (! _imp->elided_paths.empty())
|
|
{
|
|
const auto path = dst.strip_leading(_imp->params.root());
|
|
if (_imp->elided_paths.find(path) != _imp->elided_paths.end())
|
|
{
|
|
set_skipped_dir(true);
|
|
return { msi_unselected_part };
|
|
}
|
|
}
|
|
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_install_dir_pre")
|
|
("INSTALL_SOURCE", stringify(src))
|
|
("INSTALL_DESTINATION", stringify(dst_dir / src.basename()))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.dir.pre_hooks.failure", ll_warning, lc_context)
|
|
<< "Merge of '" << src << "' to '" << dst_dir << "' pre hooks returned non-zero";
|
|
|
|
mode_t mode(src_stat.permissions());
|
|
if (0 != (mode & (S_ISVTX | S_ISUID | S_ISGID)))
|
|
result += msi_setid_bits;
|
|
|
|
std::shared_ptr<const SecurityContext> secctx(MatchPathCon::get_instance()->match(stringify(dst), mode));
|
|
FSCreateCon createcon(secctx);
|
|
if (0 != paludis::setfilecon(src, secctx))
|
|
throw FSMergerError("Could not set SELinux context on '"
|
|
+ stringify(src) + "': " + stringify(::strerror(errno)));
|
|
|
|
if (is_selinux_enabled())
|
|
relabel_dir_recursive(src, dst);
|
|
|
|
const bool partitioned = _imp->params.parts() &&
|
|
_imp->params.parts()->is_partitioned(dst.strip_leading(_imp->params.root()));
|
|
|
|
if (! partitioned && ! _imp->params.options()[mo_nondestructive] &&
|
|
0 == std::rename(stringify(src).c_str(), stringify(dst).c_str()))
|
|
{
|
|
result += msi_rename;
|
|
track_renamed_dir_recursive(dst);
|
|
set_skipped_dir(true);
|
|
}
|
|
else
|
|
{
|
|
if (! partitioned && ! _imp->params.options()[mo_nondestructive])
|
|
Log::get_instance()->message("merger.dir.rename_failed", ll_debug, lc_context)
|
|
<< "rename failed. Falling back to recursive copy.";
|
|
|
|
dst.mkdir(mode, { fspmkdo_ok_if_exists });
|
|
FDHolder dst_fd(::open(stringify(dst).c_str(), O_RDONLY));
|
|
struct stat sb;
|
|
if (-1 == dst_fd)
|
|
throw FSMergerError("Could not get an FD for the directory '"
|
|
+ stringify(dst) + "' that we just created: " + stringify(::strerror(errno)));
|
|
if (-1 == ::fstat(dst_fd, &sb))
|
|
throw FSMergerError("Could not fstat the directory '"
|
|
+ stringify(dst) + "' that we just created: " + stringify(::strerror(errno)));
|
|
if ( !S_ISDIR(sb.st_mode))
|
|
throw FSMergerError("The directory that we just created is not a directory anymore");
|
|
if (! _imp->params.no_chown())
|
|
if (-1 == ::fchown(dst_fd, src_stat.owner(), src_stat.group()))
|
|
throw FSMergerError("Could not fchown the directory '" + stringify(dst) + "' that we just created: "
|
|
+ stringify(::strerror(errno)));
|
|
/* pick up set*id bits */
|
|
::fchmod(dst_fd, mode);
|
|
try_to_copy_xattrs(src, dst_fd, result);
|
|
}
|
|
|
|
if (fixed_ownership_for(src))
|
|
result += msi_fixed_ownership;
|
|
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_install_dir_post")
|
|
("INSTALL_SOURCE", stringify(src))
|
|
("INSTALL_DESTINATION", stringify(dst_dir / src.basename()))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.dir.post_hooks.failure", ll_warning, lc_context)
|
|
<< "Merge of '" << src << "' to '" << dst_dir << "' post hooks returned non-zero";
|
|
|
|
return result;
|
|
}
|
|
|
|
FSMergerStatusFlags
|
|
FSMerger::install_sym(const FSPath & src, const FSPath & dst_dir)
|
|
{
|
|
Context context("When installing sym '" + stringify(src) + "' to '" + stringify(dst_dir) + "':");
|
|
|
|
FSPath dst(dst_dir / src.basename());
|
|
|
|
if (_imp->params.should_merge() &&
|
|
! _imp->params.should_merge()(dst.strip_leading(_imp->params.root().realpath())))
|
|
return { msi_unselected_part };
|
|
|
|
FSMergerStatusFlags result;
|
|
FSStat src_stat(src);
|
|
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_install_sym_pre")
|
|
("INSTALL_SOURCE", stringify(src))
|
|
("INSTALL_DESTINATION", stringify(dst))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.sym.pre_hooks.failure", ll_warning, lc_context)
|
|
<< "Merge of '" << src << "' to '" << dst_dir << "' pre hooks returned non-zero";
|
|
|
|
if (0 != (src_stat.permissions() & (S_ISVTX | S_ISUID | S_ISGID)))
|
|
result += msi_setid_bits;
|
|
|
|
bool do_sym(true);
|
|
|
|
FSCreateCon createcon(MatchPathCon::get_instance()->match(stringify(dst), S_IFLNK));
|
|
std::pair<MergedMap::const_iterator, MergedMap::const_iterator> ii(
|
|
_imp->merged_ids.equal_range(src_stat.lowlevel_id()));
|
|
for (MergedMap::const_iterator i = ii.first ; i != ii.second ; ++i)
|
|
{
|
|
if (0 == ::link(i->second.c_str(), stringify(dst).c_str()))
|
|
{
|
|
do_sym = false;
|
|
result += msi_as_hardlink;
|
|
break;
|
|
}
|
|
Log::get_instance()->message("merger.sym.link_failed", ll_debug, lc_context)
|
|
<< "link(" << i->second + ", " << stringify(dst) << ") failed: "
|
|
<< ::strerror(errno);
|
|
}
|
|
|
|
if (do_sym)
|
|
{
|
|
if (0 != ::symlink(stringify(src.readlink()).c_str(), stringify(dst).c_str()))
|
|
throw FSMergerError("Couldn't create symlink at '" + stringify(dst) + "': "
|
|
+ stringify(::strerror(errno)));
|
|
_imp->merged_ids.insert(make_pair(src_stat.lowlevel_id(), stringify(dst)));
|
|
}
|
|
|
|
if (! _imp->params.no_chown())
|
|
{
|
|
dst.lchown(src_stat.owner(), src_stat.group());
|
|
if (fixed_ownership_for(src))
|
|
result += msi_fixed_ownership;
|
|
}
|
|
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_install_sym_post")
|
|
("INSTALL_SOURCE", stringify(src))
|
|
("INSTALL_DESTINATION", stringify(dst))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.sym.post_hooks.failure", ll_warning, lc_context) <<
|
|
"Merge of '" << src << "' to '" << dst_dir << "' post hooks returned non-zero";
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
FSMerger::unlink_file(FSPath d)
|
|
{
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_unlink_file_pre")
|
|
("UNLINK_TARGET", stringify(d))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.unlink_file.pre_hooks.failure", ll_warning, lc_context) <<
|
|
"Unmerge of '" << d << "' pre hooks returned non-zero";
|
|
|
|
d.chmod(0);
|
|
d.unlink();
|
|
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_unlink_file_post")
|
|
("UNLINK_TARGET", stringify(d))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.unlink_file.post_hooks.failure", ll_warning, lc_context) <<
|
|
"Unmerge of '" << d << "' post hooks returned non-zero";
|
|
}
|
|
|
|
void
|
|
FSMerger::unlink_sym(FSPath d)
|
|
{
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_unlink_sym_pre")
|
|
("UNLINK_TARGET", stringify(d))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.unlink_sym.pre_hooks.failure", ll_warning, lc_context) <<
|
|
"Unmerge of '" << d << "' pre hooks returned non-zero";
|
|
|
|
d.unlink();
|
|
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_unlink_sym_post")
|
|
("UNLINK_TARGET", stringify(d))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.unlink_sym.post_hooks.failure", ll_warning, lc_context) <<
|
|
"Unmerge of '" << d << "' post hooks returned non-zero";
|
|
}
|
|
|
|
void
|
|
FSMerger::unlink_dir(FSPath d)
|
|
{
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_unlink_dir_pre")
|
|
("UNLINK_TARGET", stringify(d))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.unlink_dir.pre_hooks.failure", ll_warning, lc_context) <<
|
|
"Unmerge of '" << d << "' pre hooks returned non-zero";
|
|
|
|
d.rmdir();
|
|
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_unlink_dir_post")
|
|
("UNLINK_TARGET", stringify(d))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.unlink_dir.post_hooks.failure", ll_warning, lc_context) <<
|
|
"Unmerge of '" << d << "' post hooks returned non-zero";
|
|
}
|
|
|
|
void
|
|
FSMerger::unlink_misc(FSPath d)
|
|
{
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_unlink_misc_pre")
|
|
("UNLINK_TARGET", stringify(d))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.unlink_misc.pre_hooks.failure", ll_warning, lc_context) <<
|
|
"Unmerge of '" << d << "' pre hooks returned non-zero";
|
|
|
|
d.unlink();
|
|
|
|
if (0 != _imp->params.environment()->perform_hook(extend_hook(
|
|
Hook("merger_unlink_misc_post")
|
|
("UNLINK_TARGET", stringify(d))),
|
|
_imp->params.maybe_output_manager()).max_exit_status())
|
|
Log::get_instance()->message("merger.unlink_misc.post_hooks.failure", ll_warning, lc_context) <<
|
|
"Unmerge of '" << d << "' post hooks returned non-zero";
|
|
}
|
|
|
|
Hook
|
|
FSMerger::extend_hook(const Hook & h)
|
|
{
|
|
return h
|
|
("ROOT", stringify(_imp->params.root()))
|
|
("IMAGE", stringify(_imp->params.image()));
|
|
}
|
|
|
|
#ifdef HAVE_XATTRS
|
|
|
|
void
|
|
FSMerger::try_to_copy_xattrs(const FSPath & src, int dst_fd, FSMergerStatusFlags & flags)
|
|
{
|
|
FDHolder src_fd(::open(stringify(src).c_str(), O_RDONLY));
|
|
|
|
ssize_t list_sz(flistxattr(src_fd, nullptr, 0));
|
|
if (-1 == list_sz)
|
|
{
|
|
if (ENOTSUP != errno)
|
|
Log::get_instance()->message("merger.xattrs.failure", ll_warning, lc_context) <<
|
|
"Got error '" << ::strerror(errno) << "' when trying to find extended attributes size for '" << src << "'";
|
|
return;
|
|
}
|
|
else if (list_sz > (2 << 29))
|
|
{
|
|
Log::get_instance()->message("merger.xattrs.your_fs_sucks", ll_warning, lc_context) <<
|
|
"flistxattr returned " << list_sz << ", which clearly isn't right. Are you using some crazy "
|
|
"ricer filesystem or kernel?";
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<char> list_holder(static_cast<char *>(::operator new(list_sz)));
|
|
list_sz = flistxattr(src_fd, list_holder.get(), list_sz);
|
|
if (-1 == list_sz)
|
|
{
|
|
Log::get_instance()->message("merger.xattrs.failure", ll_warning, lc_context) <<
|
|
"Got error '" << ::strerror(errno) << "' when trying to find extended attributes for '" << src << "'";
|
|
return;
|
|
}
|
|
|
|
for (int offset(0) ; list_sz > 0 ; )
|
|
{
|
|
std::string key(list_holder.get() + offset);
|
|
do
|
|
{
|
|
ssize_t value_sz(fgetxattr(src_fd, key.c_str(), nullptr, 0));
|
|
if (-1 == value_sz)
|
|
{
|
|
Log::get_instance()->message("merger.xattrs.failure", ll_warning, lc_context) <<
|
|
"Got error '" << ::strerror(errno) << "' when trying to read size of extended attribute '" <<
|
|
key << "' for '" << src << "'";
|
|
break;
|
|
}
|
|
|
|
std::shared_ptr<char> value_holder(static_cast<char *>(::operator new(value_sz)));
|
|
|
|
if (key == "security.selinux")
|
|
{
|
|
/* we handle selinux stuff specially */
|
|
continue;
|
|
}
|
|
|
|
value_sz = fgetxattr(src_fd, key.c_str(), value_holder.get(), value_sz);
|
|
if (-1 == value_sz)
|
|
{
|
|
Log::get_instance()->message("merger.xattrs.failure", ll_warning, lc_context) <<
|
|
"Got error '" << ::strerror(errno) << "' when trying to read extended attribute '" <<
|
|
key << "' for '" << src << "'";
|
|
}
|
|
|
|
if (-1 == fsetxattr(dst_fd, key.c_str(), value_holder.get(), value_sz, 0))
|
|
{
|
|
if (ENOTSUP == errno)
|
|
{
|
|
Log::get_instance()->message("merger.xattrs.failure", ll_warning, lc_context) <<
|
|
"Could not copy extended attributes from source file '" << src << "', discarding attribute '" <<
|
|
key << "' = '" << std::string(value_holder.get(), value_sz) << "'";
|
|
continue;
|
|
}
|
|
else
|
|
Log::get_instance()->message("merger.xattrs.failure", ll_warning, lc_context) <<
|
|
"Got error '" << ::strerror(errno) << "' when trying to set extended attribute '" <<
|
|
key << "' taken from source file '" << src << "'";
|
|
}
|
|
|
|
flags += msi_xattr;
|
|
|
|
} while (false);
|
|
|
|
list_sz -= (key.length() + 1);
|
|
offset += (key.length() + 1);
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
void
|
|
FSMerger::try_to_copy_xattrs(const FSPath &, int, FSMergerStatusFlags &)
|
|
{
|
|
}
|
|
|
|
#endif
|
|
|
|
void
|
|
FSMerger::track_install_file(const FSPath & src, const FSPath & dst_dir, const std::string & dst_name, const FSMergerStatusFlags & flags)
|
|
{
|
|
if (flags[msi_unselected_part])
|
|
return display_merge(et_file, dst_dir / src.basename(), flags,
|
|
src.basename() == dst_name ? "" : dst_name);
|
|
|
|
_imp->params.merged_entries()->insert(dst_dir / dst_name);
|
|
record_install_file(src, dst_dir, dst_name, flags);
|
|
}
|
|
|
|
void
|
|
FSMerger::track_install_dir(const FSPath & src, const FSPath & dst_dir, const FSMergerStatusFlags & flags)
|
|
{
|
|
if (flags[msi_unselected_part])
|
|
return display_merge(et_dir, dst_dir / src.basename(), flags);
|
|
|
|
_imp->params.merged_entries()->insert(dst_dir / src.basename());
|
|
record_install_dir(src, dst_dir, flags);
|
|
}
|
|
|
|
void
|
|
FSMerger::track_install_under_dir(const FSPath & dst, const FSMergerStatusFlags & flags)
|
|
{
|
|
_imp->params.merged_entries()->insert(dst);
|
|
record_install_under_dir(dst, flags);
|
|
}
|
|
|
|
void
|
|
FSMerger::track_install_sym(const FSPath & src, const FSPath & dst_dir, const FSMergerStatusFlags & flags)
|
|
{
|
|
if (flags[msi_unselected_part])
|
|
return display_merge(et_sym, dst_dir / src.basename(), flags);
|
|
|
|
_imp->params.merged_entries()->insert(dst_dir / src.basename());
|
|
record_install_sym(src, dst_dir, flags);
|
|
}
|
|
|
|
void
|
|
FSMerger::on_file_main(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
EntryType m(entry_type(dst / src.basename()));
|
|
|
|
do
|
|
{
|
|
switch (m)
|
|
{
|
|
case et_nothing:
|
|
on_file_over_nothing(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_sym:
|
|
on_file_over_sym(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_dir:
|
|
on_file_over_dir(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_misc:
|
|
on_file_over_misc(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_file:
|
|
on_file_over_file(is_check, src, dst);
|
|
continue;
|
|
|
|
case last_et:
|
|
;
|
|
}
|
|
|
|
throw InternalError(PALUDIS_HERE, "Unexpected entry_type '" + stringify(m) + "'");
|
|
} while (false);
|
|
}
|
|
|
|
void
|
|
FSMerger::on_dir_main(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
EntryType m(entry_type(dst / src.basename()));
|
|
|
|
do
|
|
{
|
|
switch (m)
|
|
{
|
|
case et_nothing:
|
|
on_dir_over_nothing(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_sym:
|
|
on_dir_over_sym(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_dir:
|
|
on_dir_over_dir(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_misc:
|
|
on_dir_over_misc(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_file:
|
|
on_dir_over_file(is_check, src, dst);
|
|
continue;
|
|
|
|
case last_et:
|
|
;
|
|
}
|
|
|
|
throw InternalError(PALUDIS_HERE, "Unexpected entry_type '" + stringify(m) + "'");
|
|
|
|
} while (false);
|
|
}
|
|
|
|
void
|
|
FSMerger::on_enter_dir(bool is_check, const FSPath src)
|
|
{
|
|
if (is_check || ! _imp->params.parts() || ! _imp->params.should_merge())
|
|
return;
|
|
|
|
for (FSIterator dentry(src, { fsio_want_directories }), invalid;
|
|
dentry != invalid; ++dentry)
|
|
if (_imp->is_elided_directory(*dentry))
|
|
_imp->elided_paths.insert(dentry->strip_leading(_imp->params.image()));
|
|
}
|
|
|
|
void
|
|
FSMerger::on_sym_main(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
EntryType m(entry_type(dst / src.basename()));
|
|
|
|
do
|
|
{
|
|
switch (m)
|
|
{
|
|
case et_nothing:
|
|
on_sym_over_nothing(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_sym:
|
|
on_sym_over_sym(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_dir:
|
|
on_sym_over_dir(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_misc:
|
|
on_sym_over_misc(is_check, src, dst);
|
|
continue;
|
|
|
|
case et_file:
|
|
on_sym_over_file(is_check, src, dst);
|
|
continue;
|
|
|
|
case last_et:
|
|
;
|
|
}
|
|
|
|
throw InternalError(PALUDIS_HERE, "Unexpected entry_type '" + stringify(m) + "'");
|
|
} while (false);
|
|
}
|
|
|
|
FSPath
|
|
FSMerger::canonicalise_root_path(const FSPath & f)
|
|
{
|
|
return f.realpath();
|
|
}
|
|
|
|
void
|
|
FSMerger::do_dir_recursive(bool is_check, const FSPath & src, const FSPath & dst)
|
|
{
|
|
FSStat dst_stat(dst);
|
|
|
|
if ((! is_check) && (! dst_stat.is_directory()))
|
|
throw MergerError("Destination directory '" + stringify(dst) + "' is not a directory");
|
|
|
|
Merger::do_dir_recursive(is_check, src, dst);
|
|
}
|
|
|
|
std::string
|
|
FSMerger::make_arrows(const FSMergerStatusFlags & flags) const
|
|
{
|
|
std::string result(">>>");
|
|
|
|
for (EnumIterator<FSMergerStatusFlag> m, m_end(last_msi); m != m_end; ++m)
|
|
{
|
|
if (! flags[*m])
|
|
continue;
|
|
|
|
switch (*m)
|
|
{
|
|
case msi_unlinked_first:
|
|
result[0] = '<';
|
|
continue;
|
|
|
|
case msi_used_existing:
|
|
result[0] = '=';
|
|
continue;
|
|
|
|
case msi_unselected_part:
|
|
result[0] = '%';
|
|
continue;
|
|
|
|
case msi_parent_rename:
|
|
result[1] = '^';
|
|
continue;
|
|
|
|
case msi_rename:
|
|
result[1] = '-';
|
|
continue;
|
|
|
|
case msi_as_hardlink:
|
|
result[1] = '&';
|
|
continue;
|
|
|
|
case msi_fixed_ownership:
|
|
result[2] = '~';
|
|
continue;
|
|
|
|
case msi_setid_bits:
|
|
result[2] = '*';
|
|
continue;
|
|
|
|
case msi_xattr:
|
|
result[2] = '+';
|
|
continue;
|
|
|
|
case last_msi:
|
|
break;
|
|
}
|
|
|
|
throw InternalError(PALUDIS_HERE, "Unhandled MergeStatusFlag '" + stringify(static_cast<long>(*m)) + "'");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
FSMerger::display_merge(const EntryType & type, const FSPath & path,
|
|
const FSMergerStatusFlags & flags,
|
|
const std::string & renamed) const
|
|
{
|
|
const auto real_path(path.strip_leading(_imp->params.root().realpath()));
|
|
std::ostringstream message;
|
|
|
|
message << make_arrows(flags);
|
|
|
|
switch (type)
|
|
{
|
|
case et_dir:
|
|
message << " [dir] ";
|
|
break;
|
|
case et_file:
|
|
message << " [obj] ";
|
|
break;
|
|
case et_sym:
|
|
message << " [sym] ";
|
|
break;
|
|
|
|
case et_misc:
|
|
case et_nothing:
|
|
case last_et:
|
|
throw FSMergerError("Unexpected entry type merged at: " + stringify(real_path));
|
|
}
|
|
|
|
message << stringify(real_path);
|
|
if (!renamed.empty())
|
|
message << " (" << renamed << ")";
|
|
|
|
display_override(message.str());
|
|
}
|
|
|