aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAvatar Ciaran McCreesh <ciaran.mccreesh@googlemail.com> 2009-09-05 18:24:41 +0100
committerAvatar Ciaran McCreesh <ciaran.mccreesh@googlemail.com> 2009-09-05 18:24:41 +0100
commit1a25ef47bf3a2ebd26f4ffacdcf705de0ce872c4 (patch)
treeee0c43735aaf534467140ff4214942e51e5f157f
parentc7586cd9db07a36c7a8e69567ee2c268b9b4035c (diff)
parentb1422adf39fa146714e4177c795beae5f12fd4a0 (diff)
downloadpaludis-1a25ef47bf3a2ebd26f4ffacdcf705de0ce872c4.tar.gz
paludis-1a25ef47bf3a2ebd26f4ffacdcf705de0ce872c4.tar.xz
Merge branch 'execute-resolution'
-rw-r--r--.gitignore1
-rw-r--r--paludis/util/Makefile.am.m46
-rw-r--r--paludis/util/system.cc322
-rw-r--r--paludis/util/system.hh12
-rw-r--r--paludis/util/system_TEST.cc17
-rw-r--r--paludis/util/system_TEST_become_child.cc36
-rw-r--r--src/clients/cave/Makefile.am2
-rw-r--r--src/clients/cave/cmd_execute_resolution.cc276
-rw-r--r--src/clients/cave/cmd_execute_resolution.hh45
-rw-r--r--src/clients/cave/cmd_resolve.cc52
-rw-r--r--src/clients/cave/cmd_resolve_cmdline.cc62
-rw-r--r--src/clients/cave/cmd_resolve_cmdline.hh24
-rw-r--r--src/clients/cave/command_factory.cc2
13 files changed, 749 insertions, 108 deletions
diff --git a/.gitignore b/.gitignore
index e097f60..d14dba4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -393,6 +393,7 @@ paludis-*.*.*.tar.bz2
/paludis/util/stringify_TEST
/paludis/util/strip_TEST
/paludis/util/system_TEST
+/paludis/util/system_TEST_become_child
/paludis/util/tail_output_stream_TEST
/paludis/util/thread_TEST
/paludis/util/thread_pool_TEST
diff --git a/paludis/util/Makefile.am.m4 b/paludis/util/Makefile.am.m4
index 18a9efd..f79d383 100644
--- a/paludis/util/Makefile.am.m4
+++ b/paludis/util/Makefile.am.m4
@@ -90,9 +90,13 @@ TESTS_ENVIRONMENT = env \
TEST_SCRIPT_DIR="$(srcdir)/" \
bash $(top_srcdir)/test/run_test.sh
-check_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS) system_TEST_become_child
check_SCRIPTS = testscriptlist
+system_TEST_become_child_SOURCES = system_TEST_become_child.cc
+system_TEST_become_child_LDADD = \
+ libpaludisutil_@PALUDIS_PC_SLOT@.la
+
lib_LTLIBRARIES = libpaludisutil_@PALUDIS_PC_SLOT@.la
paludis_util_includedir = $(includedir)/paludis-$(PALUDIS_PC_SLOT)/paludis/util/
diff --git a/paludis/util/system.cc b/paludis/util/system.cc
index 04c4179..a720799 100644
--- a/paludis/util/system.cc
+++ b/paludis/util/system.cc
@@ -41,6 +41,7 @@
#include <grp.h>
#include <pwd.h>
#include <signal.h>
+#include <fcntl.h>
#include <map>
#include <iostream>
#include <cstring>
@@ -391,30 +392,94 @@ namespace
{
std::cerr << "Caught signal " << sig << " in run_command child process" << std::endl;
}
-}
-int
-paludis::run_command(const Command & cmd)
-{
- Context context("When running command '" + stringify(cmd.command()) + "':");
+ std::string build_extras(const Command & cmd)
+ {
+ std::string extras;
+
+ if (! cmd.chdir().empty())
+ extras.append(" [chdir " + cmd.chdir() + "]");
+
+ if (cmd.clearenv())
+ extras.append(" [clearenv]");
+
+ for (Command::ConstIterator s(cmd.begin_setenvs()), s_end(cmd.end_setenvs()) ; s != s_end ; ++s)
+ extras.append(" [setenv " + s->first + "=" + s->second + "]");
+
+ if (cmd.gid() && *cmd.gid() != getgid())
+ extras.append(" [setgid " + stringify(*cmd.gid()) + "]");
+
+ if (cmd.uid() && *cmd.uid() != getuid())
+ extras.append(" [setuid " + stringify(*cmd.uid()) + "]");
+
+ return extras;
+ }
+
+ void do_chdir(const Command & cmd)
+ {
+ if (! cmd.chdir().empty())
+ if (-1 == chdir(stringify(cmd.chdir()).c_str()))
+ throw RunCommandError("chdir failed: " + stringify(strerror(errno)));
+ }
+
+ void do_env(const Command & cmd)
+ {
+ if (cmd.clearenv())
+ {
+ std::map<std::string, std::string> setenvs;
+ for (const char * const * it(environ); 0 != *it; ++it)
+ {
+ std::string var(*it);
+ if (std::string::npos != var.find('=') &&
+ ("PALUDIS_" == var.substr(0, 8) ||
+ "PATH=" == var.substr(0, 5) ||
+ "HOME=" == var.substr(0, 5) ||
+ "LD_LIBRARY_PATH=" == var.substr(0, 16)))
+ setenvs.insert(std::make_pair(var.substr(0, var.find('=')), var.substr(var.find('=') + 1)));
+ }
+ clearenv();
+ for (std::map<std::string, std::string>::const_iterator it(setenvs.begin()),
+ it_end(setenvs.end()); it_end != it; ++it)
+ setenv(it->first.c_str(), it->second.c_str(), 1);
+ }
- std::string extras;
+ for (Command::ConstIterator s(cmd.begin_setenvs()), s_end(cmd.end_setenvs()) ; s != s_end ; ++s)
+ setenv(s->first.c_str(), s->second.c_str(), 1);
- if (! cmd.chdir().empty())
- extras.append(" [chdir " + cmd.chdir() + "]");
+ /* This is what happens when people are allowed to play with
+ * things they don't understand. Yay Gentoo! */
+ setenv("PACKAGE_MANAGER", "paludis", 1);
+ setenv("ESELECT_PACKAGE_MANAGER", "paludis", 1);
+ }
- if (cmd.clearenv())
- extras.append(" [clearenv]");
+ void do_uid_gid(const Command & cmd)
+ {
+ std::string command(cmd.command());
+
+ if (cmd.gid() && *cmd.gid() != getgid())
+ {
+ gid_t g(*cmd.gid());
- for (Command::ConstIterator s(cmd.begin_setenvs()), s_end(cmd.end_setenvs()) ; s != s_end ; ++s)
- extras.append(" [setenv " + s->first + "=" + s->second + "]");
+ if (0 != ::setgid(*cmd.gid()))
+ std::cerr << "setgid(" << *cmd.uid() << ") failed for exec of '" << command << "': "
+ << strerror(errno) << std::endl;
+ else if (0 != ::setgroups(1, &g))
+ std::cerr << "setgroups failed for exec of '" << command << "': " << strerror(errno) << std::endl;
+ }
- if (cmd.gid() && *cmd.gid() != getgid())
- extras.append(" [setgid " + stringify(*cmd.gid()) + "]");
+ if (cmd.uid() && *cmd.uid() != getuid())
+ if (0 != ::setuid(*cmd.uid()))
+ std::cerr << "setuid(" << *cmd.uid() << ") failed for exec of '" << command << "': "
+ << strerror(errno) << std::endl;
+ }
+}
- if (cmd.uid() && *cmd.uid() != getuid())
- extras.append(" [setuid " + stringify(*cmd.uid()) + "]");
+int
+paludis::run_command(const Command & cmd)
+{
+ Context context("When running command '" + stringify(cmd.command()) + "':");
+ std::string extras(build_extras(cmd));
std::string command(cmd.command());
if ((! cmd.stdout_prefix().empty()) || (! cmd.stderr_prefix().empty()))
@@ -441,7 +506,16 @@ paludis::run_command(const Command & cmd)
if (cmd.captured_stderr_stream())
captured_stderr.reset(cmd.ptys() ? static_cast<Channel *>(new Pty) : new Pipe);
if (cmd.input_stream())
+ {
input_stream.reset(new Pipe);
+ int arg(fcntl(input_stream->write_fd(), F_GETFL, NULL));
+ if (-1 == arg)
+ throw RunCommandError("fcntl F_GETFL failed: " + stringify(strerror(errno)));
+ arg |= O_NONBLOCK;
+ if (-1 == fcntl(input_stream->write_fd(), F_SETFL, arg))
+ throw RunCommandError("fcntl F_SETFL failed: " + stringify(strerror(errno)));
+ }
+
/* Why do we fork twice, rather than install a SIGCHLD handler that writes to a pipe that
* our pselect watches? Simple: Unix is retarded. APUE 12.8 says "Each thread has its own signal
@@ -507,31 +581,8 @@ paludis::run_command(const Command & cmd)
close(internal_command_reader->write_fd());
internal_command_reader->clear_write_fd();
- if (! cmd.chdir().empty())
- if (-1 == chdir(stringify(cmd.chdir()).c_str()))
- throw RunCommandError("chdir failed: " + stringify(strerror(errno)));
-
- if (cmd.clearenv())
- {
- std::map<std::string, std::string> setenvs;
- for (const char * const * it(environ); 0 != *it; ++it)
- {
- std::string var(*it);
- if (std::string::npos != var.find('=') &&
- ("PALUDIS_" == var.substr(0, 8) ||
- "PATH=" == var.substr(0, 5) ||
- "HOME=" == var.substr(0, 5) ||
- "LD_LIBRARY_PATH=" == var.substr(0, 16)))
- setenvs.insert(std::make_pair(var.substr(0, var.find('=')), var.substr(var.find('=') + 1)));
- }
- clearenv();
- for (std::map<std::string, std::string>::const_iterator it(setenvs.begin()),
- it_end(setenvs.end()); it_end != it; ++it)
- setenv(it->first.c_str(), it->second.c_str(), 1);
- }
-
- for (Command::ConstIterator s(cmd.begin_setenvs()), s_end(cmd.end_setenvs()) ; s != s_end ; ++s)
- setenv(s->first.c_str(), s->second.c_str(), 1);
+ do_chdir(cmd);
+ do_env(cmd);
if (cmd.pipe_command_handler())
{
@@ -539,11 +590,6 @@ paludis::run_command(const Command & cmd)
setenv("PALUDIS_PIPE_COMMAND_READ_FD", stringify(pipe_command_response->read_fd()).c_str(), 1);
}
- /* This is what happens when people are allowed to play with
- * things they don't understand. Yay Gentoo! */
- setenv("PACKAGE_MANAGER", "paludis", 1);
- setenv("ESELECT_PACKAGE_MANAGER", "paludis", 1);
-
if (cmd.captured_stdout_stream())
{
if (-1 == dup2(captured_stdout->write_fd(), 1))
@@ -588,21 +634,7 @@ paludis::run_command(const Command & cmd)
setenv(cmd.input_fd_env_var().c_str(), stringify(cmd_input_fd).c_str(), 1);
}
- if (cmd.gid() && *cmd.gid() != getgid())
- {
- gid_t g(*cmd.gid());
-
- if (0 != ::setgid(*cmd.gid()))
- std::cerr << "setgid(" << *cmd.uid() << ") failed for exec of '" << command << "': "
- << strerror(errno) << std::endl;
- else if (0 != ::setgroups(1, &g))
- std::cerr << "setgroups failed for exec of '" << command << "': " << strerror(errno) << std::endl;
- }
-
- if (cmd.uid() && *cmd.uid() != getuid())
- if (0 != ::setuid(*cmd.uid()))
- std::cerr << "setuid(" << *cmd.uid() << ") failed for exec of '" << command << "': "
- << strerror(errno) << std::endl;
+ do_uid_gid(cmd);
execl("/bin/sh", "sh", "-c", command.c_str(), static_cast<char *>(0));
throw RunCommandError("execl /bin/sh -c '" + command + "' failed:"
@@ -861,13 +893,13 @@ paludis::run_command(const Command & cmd)
if (cmd.input_stream()->get(c).good())
{
int w(write(input_stream->write_fd(), &c, 1));
- if (-1 == w)
- throw RunCommandError("write failed: " + stringify(strerror(errno)));
- else if (0 == w)
+ if (0 == w || (-1 == w && (errno == EAGAIN || errno == EWOULDBLOCK)))
{
cmd.input_stream()->unget();
break;
}
+ else if (-1 == w)
+ throw RunCommandError("write failed: " + stringify(strerror(errno)));
}
else
eof = true;
@@ -951,6 +983,168 @@ paludis::run_command(const Command & cmd)
throw InternalError(PALUDIS_HERE, "should never be reached");
}
+void
+paludis::become_command(const Command & cmd)
+{
+ Context context("When becoming command '" + stringify(cmd.command()) + "':");
+
+ std::string extras(build_extras(cmd));
+ std::string command(cmd.command());
+
+ cmd.echo_to_stderr();
+ Log::get_instance()->message("util.system.execl_parent", ll_debug, lc_no_context) << "execl /bin/sh -c " << command
+ << " " << extras;
+
+ /* The double fork with the ignoring CLD in the middle may or may not be
+ * necessary, and it probably isn't, but POSIX appears to suggest that
+ * doing this will guarantee that the feeding process won't be a zombie,
+ * whereas it doesn't if we don't. Unless it doesn't. */
+
+ /* Temporarily disable SIGINT and SIGTERM to this thread, so that we can set up signal
+ * handlers. */
+ sigset_t intandterm;
+ sigemptyset(&intandterm);
+ sigaddset(&intandterm, SIGINT);
+ sigaddset(&intandterm, SIGTERM);
+ if (0 != pthread_sigmask(SIG_BLOCK, &intandterm, 0))
+ throw InternalError(PALUDIS_HERE, "pthread_sigmask failed");
+
+ std::tr1::shared_ptr<Pipe> input_stream;
+ if (cmd.input_stream())
+ {
+ input_stream.reset(new Pipe);
+
+ int cmd_input_fd;
+
+ if (-1 == cmd.input_fd())
+ cmd_input_fd = dup(input_stream->read_fd());
+ else
+ cmd_input_fd = dup2(input_stream->read_fd(), cmd.input_fd());
+
+ if (-1 == cmd_input_fd)
+ throw RunCommandError("input dup2 failed: " + stringify(strerror(errno)));
+
+ if (! cmd.input_fd_env_var().empty())
+ setenv(cmd.input_fd_env_var().c_str(), stringify(cmd_input_fd).c_str(), 1);
+ }
+
+ pid_t child(fork());
+ if (0 == child)
+ {
+ if (cmd.input_stream())
+ {
+ close(input_stream->read_fd());
+ input_stream->clear_read_fd();
+ }
+
+ /* Ignore CLD. POSIX may or may not say that if we do this, our child will
+ * not become a zombie. */
+ struct sigaction act;
+ act.sa_handler = SIG_IGN;
+ act.sa_flags = 0;
+ sigaction(SIGTERM, &act, 0);
+
+ pid_t child_child(fork());
+ if (0 == child_child)
+ {
+ /* Restore SIGINT and SIGTERM handling */
+ if (0 != pthread_sigmask(SIG_UNBLOCK, &intandterm, 0))
+ throw InternalError(PALUDIS_HERE, "pthread_sigmask failed");
+
+ /* Feed in any input things */
+ if (cmd.input_stream())
+ {
+ while (true)
+ {
+ char c;
+ if (cmd.input_stream()->get(c).good())
+ {
+ int w(write(input_stream->write_fd(), &c, 1));
+ if (w != 1)
+ throw RunCommandError("write failed: " + stringify(strerror(errno)));
+ }
+ else
+ break;
+ }
+
+ if (0 != close(input_stream->write_fd()))
+ throw RunCommandError("close failed: " + stringify(strerror(errno)));
+ input_stream->clear_write_fd();
+ }
+
+ /* we're done */
+ _exit(0);
+ }
+ else
+ {
+ _exit(0);
+ }
+ }
+ else if (-1 == child)
+ throw RunCommandError("fork failed: " + stringify(strerror(errno)));
+ else
+ {
+ /* Our original pid, which gets exec()ed */
+
+ if (cmd.input_stream())
+ {
+ close(input_stream->write_fd());
+ input_stream->clear_write_fd();
+ }
+
+ /* clear any SIGINT or SIGTERM handlers we inherit, and unblock signals */
+ struct sigaction act;
+ act.sa_handler = SIG_DFL;
+ act.sa_flags = 0;
+ sigaction(SIGINT, &act, 0);
+ sigaction(SIGTERM, &act, 0);
+ if (0 != pthread_sigmask(SIG_UNBLOCK, &intandterm, 0))
+ std::cerr << "pthread_sigmask failed: " + stringify(strerror(errno)) + "'" << std::endl;
+
+ /* wait until the child is done */
+ while (true)
+ {
+ int status(-1);
+ if (-1 == waitpid(child, &status, 0))
+ {
+ if (errno == EINTR)
+ std::cerr << "wait failed: '" + stringify(strerror(errno)) + "', trying once more" << std::endl;
+ else
+ {
+ std::cerr << "wait failed: '" + stringify(strerror(errno)) + "'" << std::endl;
+ break;
+ }
+ }
+ else
+ break;
+ }
+
+ try
+ {
+ do_chdir(cmd);
+ do_env(cmd);
+ do_uid_gid(cmd);
+
+ execl("/bin/sh", "sh", "-c", command.c_str(), static_cast<char *>(0));
+ throw RunCommandError("execl /bin/sh -c '" + command + "' failed:"
+ + stringify(strerror(errno)));
+ }
+ catch (const Exception & e)
+ {
+ std::cerr << "exec of '" << command << "' failed due to exception '" << e.message()
+ << "' (" << e.what() << ")" << std::endl;
+ std::exit(123);
+ }
+ catch (...)
+ {
+ std::cerr << "exec of '" << command << "' failed due to unknown exception" << std::endl;
+ std::exit(124);
+ }
+ }
+
+ throw InternalError(PALUDIS_HERE, "should never be reached");
+}
+
std::string
Command::command() const
{
diff --git a/paludis/util/system.hh b/paludis/util/system.hh
index 20fae26..1ab5745 100644
--- a/paludis/util/system.hh
+++ b/paludis/util/system.hh
@@ -350,6 +350,18 @@ namespace paludis
PALUDIS_ATTRIBUTE((warn_unused_result));
/**
+ * Become another command.
+ *
+ * Actions that change the initial state (uid / gid, env) work, as do input
+ * streams, but output redirection does not. Pipe commands don't work, but
+ * could be made to.
+ *
+ * \ingroup g_system
+ * \since 0.40.1
+ */
+ void become_command(const Command & cmd) PALUDIS_VISIBLE PALUDIS_ATTRIBUTE((noreturn));
+
+ /**
* Set the stderr and close for stdout fds used by run_command and
* run_command_in_directory.
*
diff --git a/paludis/util/system_TEST.cc b/paludis/util/system_TEST.cc
index 538b3b6..16f7e8a 100644
--- a/paludis/util/system_TEST.cc
+++ b/paludis/util/system_TEST.cc
@@ -350,5 +350,22 @@ namespace test_cases
TEST_CHECK(! std::getline(os, line));
}
} test_input;
+
+ struct BecomeChildCommandTest : TestCase
+ {
+ BecomeChildCommandTest() : TestCase("become child") { }
+
+ void run()
+ {
+ std::stringstream os;
+ Command cmd("./system_TEST_become_child");
+ cmd
+ .with_captured_stdout_stream(&os)
+ ;
+
+ TEST_CHECK_EQUAL(123, run_command(cmd));
+ TEST_CHECK_EQUAL(os.str(), "giant space monkey");
+ }
+ } test_become_child;
}
diff --git a/paludis/util/system_TEST_become_child.cc b/paludis/util/system_TEST_become_child.cc
new file mode 100644
index 0000000..eaf9841
--- /dev/null
+++ b/paludis/util/system_TEST_become_child.cc
@@ -0,0 +1,36 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2009 Ciaran McCreesh
+ *
+ * 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/util/system.hh>
+#include <sstream>
+#include <cstdlib>
+
+using namespace paludis;
+
+int main(int, char *[])
+{
+ std::stringstream is("giant space monkey");
+ Command cmd("cat ; exit 123");
+ cmd
+ .with_input_stream(&is, 0, "");
+
+ become_command(cmd);
+ return 27;
+}
+
diff --git a/src/clients/cave/Makefile.am b/src/clients/cave/Makefile.am
index 55458b5..b332c3c 100644
--- a/src/clients/cave/Makefile.am
+++ b/src/clients/cave/Makefile.am
@@ -17,6 +17,7 @@ noinst_PROGRAMS = man-cave
command_MANS = \
cave-display-resolution.1 \
+ cave-execute-resolution.1 \
cave-help.1 \
cave-perform.1 \
cave-print-categories.1 \
@@ -79,6 +80,7 @@ libcave_a_SOURCES = \
command_command_line.cc command_command_line.hh \
command_factory.cc command_factory.hh \
cmd_display_resolution.cc cmd_display_resolution.hh \
+ cmd_execute_resolution.cc cmd_execute_resolution.hh \
cmd_help.cc cmd_help.hh \
cmd_perform.cc cmd_perform.hh \
cmd_print_categories.cc cmd_print_categories.hh \
diff --git a/src/clients/cave/cmd_execute_resolution.cc b/src/clients/cave/cmd_execute_resolution.cc
new file mode 100644
index 0000000..ebfa68b
--- /dev/null
+++ b/src/clients/cave/cmd_execute_resolution.cc
@@ -0,0 +1,276 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2009 Ciaran McCreesh
+ *
+ * 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 "cmd_execute_resolution.hh"
+#include "cmd_resolve_cmdline.hh"
+#include "exceptions.hh"
+#include "command_command_line.hh"
+#include "formats.hh"
+#include "colour_formatter.hh"
+#include <paludis/args/do_help.hh>
+#include <paludis/util/make_shared_ptr.hh>
+#include <paludis/util/safe_ifstream.hh>
+#include <paludis/util/system.hh>
+#include <paludis/util/destringify.hh>
+#include <paludis/util/stringify.hh>
+#include <paludis/util/join.hh>
+#include <paludis/util/iterator_funcs.hh>
+#include <paludis/util/options.hh>
+#include <paludis/resolver/resolutions.hh>
+#include <paludis/resolver/serialise.hh>
+#include <paludis/resolver/reason.hh>
+#include <paludis/resolver/sanitised_dependencies.hh>
+#include <paludis/resolver/resolution.hh>
+#include <paludis/resolver/decision.hh>
+#include <paludis/resolver/destinations.hh>
+#include <paludis/resolver/constraint.hh>
+#include <paludis/resolver/resolver.hh>
+#include <paludis/resolver/qpn_s.hh>
+#include <paludis/package_id.hh>
+#include <paludis/version_spec.hh>
+#include <paludis/metadata_key.hh>
+#include <paludis/choice.hh>
+#include <paludis/user_dep_spec.hh>
+#include <paludis/match_package.hh>
+
+#include <set>
+#include <iterator>
+#include <iostream>
+#include <cstdlib>
+
+using namespace paludis;
+using namespace cave;
+using namespace paludis::resolver;
+
+using std::cout;
+using std::endl;
+
+namespace
+{
+ struct ExecuteResolutionCommandLine :
+ CaveCommandCommandLine
+ {
+ args::ArgsGroup g_general_options;
+ args::SwitchArg a_pretend;
+
+ ResolveCommandLineExecutionOptions execution_options;
+ ResolveCommandLineProgramOptions program_options;
+
+ ExecuteResolutionCommandLine() :
+ g_general_options(main_options_section(), "General Options", "General options."),
+ a_pretend(&g_general_options, "pretend", '\0', "Only carry out the pretend action", false),
+ execution_options(this),
+ program_options(this)
+ {
+ add_environment_variable("PALUDIS_SERIALISED_RESOLUTION_FD",
+ "The file descriptor on which the serialised resolution can be found.");
+ }
+
+ virtual std::string app_name() const
+ {
+ return "cave execute-resolution";
+ }
+
+ virtual std::string app_synopsis() const
+ {
+ return "Executes a dependency resolution created using 'cave execute'.";
+ }
+
+ virtual std::string app_description() const
+ {
+ return "Execute a dependency resolution created using 'cave resolve'. Mostly for "
+ "internal use; most users will not use this command directly.";
+ }
+ };
+
+ int do_pretend(
+ const std::tr1::shared_ptr<Environment> &,
+ const ExecuteResolutionCommandLine & cmdline,
+ const std::tr1::shared_ptr<const Decision> & c)
+ {
+ const std::tr1::shared_ptr<const PackageID> id(c->if_package_id());
+ Context context("When pretending for '" + stringify(*id) + "':");
+
+ std::string command(cmdline.program_options.a_perform_program.argument());
+ if (command.empty())
+ command = "$CAVE perform";
+
+ command.append(" pretend --hooks ");
+ command.append(stringify(id->uniquely_identifying_spec()));
+
+ paludis::Command cmd(command);
+ return run_command(cmd);
+ }
+
+ void starting_action(
+ const std::string & action,
+ const std::tr1::shared_ptr<const Decision> & c)
+ {
+ cout << endl;
+ cout << c::bold_blue() << "Starting " << action << " for "
+ << *c->if_package_id() << "..." << c::normal() << endl;
+ cout << endl;
+ }
+
+ void done_action(
+ const std::string & action,
+ const std::tr1::shared_ptr<const Decision> & c,
+ const bool success)
+ {
+ cout << endl;
+ if (success)
+ cout << c::bold_green() << "Done " << action << " for "
+ << *c->if_package_id() << c::normal() << endl;
+ else
+ cout << c::bold_red() << "Failed " << action << " for "
+ << *c->if_package_id() << c::normal() << endl;
+ cout << endl;
+ }
+
+ int do_fetch(
+ const std::tr1::shared_ptr<Environment> &,
+ const ExecuteResolutionCommandLine & cmdline,
+ const std::tr1::shared_ptr<const Decision> & c)
+ {
+ const std::tr1::shared_ptr<const PackageID> id(c->if_package_id());
+ Context context("When fetching for '" + stringify(*id) + "':");
+
+ starting_action("fetch", c);
+
+ std::string command(cmdline.program_options.a_perform_program.argument());
+ if (command.empty())
+ command = "$CAVE perform";
+
+ command.append(" fetch --hooks ");
+ command.append(stringify(id->uniquely_identifying_spec()));
+
+ paludis::Command cmd(command);
+ int retcode(run_command(cmd));
+
+ done_action("fetch", c, 0 == retcode);
+ return retcode;
+ }
+
+ int do_install_slash(
+ const std::tr1::shared_ptr<Environment> &,
+ const ExecuteResolutionCommandLine & cmdline,
+ const std::tr1::shared_ptr<const Resolution> & r)
+ {
+ const std::tr1::shared_ptr<const PackageID> id(r->decision()->if_package_id());
+ Context context("When installing to / for '" + stringify(*id) + "':");
+
+ starting_action("install to /", r->decision());
+
+ std::string command(cmdline.program_options.a_perform_program.argument());
+ if (command.empty())
+ command = "$CAVE perform";
+
+ command.append(" install --hooks ");
+ command.append(stringify(id->uniquely_identifying_spec()));
+ command.append(" --destination " + stringify(r->destinations()->slash()->repository()));
+ for (PackageIDSequence::ConstIterator i(r->destinations()->slash()->replacing()->begin()),
+ i_end(r->destinations()->slash()->replacing()->end()) ;
+ i != i_end ; ++i)
+ command.append(" --replacing " + stringify((*i)->uniquely_identifying_spec()));
+
+ paludis::Command cmd(command);
+ int retcode(run_command(cmd));
+
+ done_action("install to /", r->decision(), 0 == retcode);
+ return retcode;
+ }
+
+ int execute_resolution(
+ const std::tr1::shared_ptr<Environment> & env,
+ const ResolutionLists & lists,
+ const ExecuteResolutionCommandLine & cmdline)
+ {
+ Context context("When executing chosen resolution:");
+
+ int retcode(0);
+
+ for (Resolutions::ConstIterator c(lists.ordered()->begin()), c_end(lists.ordered()->end()) ;
+ c != c_end ; ++c)
+ retcode |= do_pretend(env, cmdline, (*c)->decision());
+
+ if (0 != retcode || cmdline.a_pretend.specified())
+ return retcode;
+
+ for (Resolutions::ConstIterator c(lists.ordered()->begin()), c_end(lists.ordered()->end()) ;
+ c != c_end ; ++c)
+ {
+ retcode = do_fetch(env, cmdline, (*c)->decision());
+ if (0 != retcode)
+ return retcode;
+
+ if ((*c)->destinations()->slash())
+ {
+ retcode = do_install_slash(env, cmdline, *c);
+ if (0 != retcode)
+ return retcode;
+ }
+ else
+ throw InternalError(PALUDIS_HERE, "destination != / not done yet");
+ }
+
+ return retcode;
+ }
+}
+
+bool
+ExecuteResolutionCommand::important() const
+{
+ return false;
+}
+
+int
+ExecuteResolutionCommand::run(
+ const std::tr1::shared_ptr<Environment> & env,
+ const std::tr1::shared_ptr<const Sequence<std::string > > & args
+ )
+{
+ ExecuteResolutionCommandLine cmdline;
+ cmdline.run(args, "CAVE", "CAVE_EXECUTE_RESOLUTION_OPTIONS", "CAVE_EXECUTE_RESOLUTION_CMDLINE");
+
+ if (cmdline.a_help.specified())
+ {
+ cout << cmdline;
+ return EXIT_SUCCESS;
+ }
+
+ if (getenv_with_default("PALUDIS_SERIALISED_RESOLUTION_FD", "").empty())
+ throw args::DoHelp("PALUDIS_SERIALISED_RESOLUTION_FD must be provided");
+
+ int fd(destringify<int>(getenv_with_default("PALUDIS_SERIALISED_RESOLUTION_FD", "")));
+ SafeIFStream deser_stream(fd);
+ const std::string deser_str((std::istreambuf_iterator<char>(deser_stream)), std::istreambuf_iterator<char>());
+ Deserialiser deserialiser(env.get(), deser_str);
+ Deserialisation deserialisation("ResolutionLists", deserialiser);
+ ResolutionLists lists(ResolutionLists::deserialise(deserialisation));
+
+ return execute_resolution(env, lists, cmdline);
+}
+
+std::tr1::shared_ptr<args::ArgsHandler>
+ExecuteResolutionCommand::make_doc_cmdline()
+{
+ return make_shared_ptr(new ExecuteResolutionCommandLine);
+}
+
+
diff --git a/src/clients/cave/cmd_execute_resolution.hh b/src/clients/cave/cmd_execute_resolution.hh
new file mode 100644
index 0000000..da7ca1c
--- /dev/null
+++ b/src/clients/cave/cmd_execute_resolution.hh
@@ -0,0 +1,45 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2009 Ciaran McCreesh
+ *
+ * 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
+ */
+
+#ifndef PALUDIS_GUARD_SRC_CLIENTS_CAVE_CMD_EXECUTE_RESOLUTION_HH
+#define PALUDIS_GUARD_SRC_CLIENTS_CAVE_CMD_EXECUTE_RESOLUTION_HH 1
+
+#include "command.hh"
+
+namespace paludis
+{
+ namespace cave
+ {
+ class PALUDIS_VISIBLE ExecuteResolutionCommand :
+ public Command
+ {
+ public:
+ bool important() const;
+
+ int run(
+ const std::tr1::shared_ptr<Environment> &,
+ const std::tr1::shared_ptr<const Sequence<std::string > > & args
+ );
+
+ std::tr1::shared_ptr<args::ArgsHandler> make_doc_cmdline();
+ };
+ }
+}
+
+#endif
diff --git a/src/clients/cave/cmd_resolve.cc b/src/clients/cave/cmd_resolve.cc
index f362951..af0ea0b 100644
--- a/src/clients/cave/cmd_resolve.cc
+++ b/src/clients/cave/cmd_resolve.cc
@@ -541,6 +541,56 @@ namespace
return run_command(cmd);
}
+
+ void perform_resolution(
+ const std::tr1::shared_ptr<Environment> &,
+ const ResolutionLists & resolution_lists,
+ const ResolveCommandLine & cmdline) PALUDIS_ATTRIBUTE((noreturn));
+
+ void perform_resolution(
+ const std::tr1::shared_ptr<Environment> &,
+ const ResolutionLists & resolution_lists,
+ const ResolveCommandLine & cmdline)
+ {
+ Context context("When performing chosen resolution:");
+
+ std::stringstream ser_stream;
+ Serialiser ser(ser_stream);
+ resolution_lists.serialise(ser);
+
+ std::string command(cmdline.program_options.a_execute_resolution_program.argument());
+ if (command.empty())
+ command = "$CAVE execute-resolution";
+
+ for (args::ArgsSection::GroupsConstIterator g(cmdline.execution_options.begin()),
+ g_end(cmdline.execution_options.end()) ;
+ g != g_end ; ++g)
+ {
+ for (args::ArgsGroup::ConstIterator o(g->begin()), o_end(g->end()) ;
+ o != o_end ; ++o)
+ if ((*o)->specified())
+ command = command + " " + (*o)->forwardable_string();
+ }
+
+ for (args::ArgsSection::GroupsConstIterator g(cmdline.program_options.begin()),
+ g_end(cmdline.program_options.end()) ;
+ g != g_end ; ++g)
+ {
+ for (args::ArgsGroup::ConstIterator o(g->begin()), o_end(g->end()) ;
+ o != o_end ; ++o)
+ if ((*o)->specified())
+ command = command + " " + (*o)->forwardable_string();
+ }
+
+ if (! cmdline.resolution_options.a_execute.specified())
+ command = command + " --pretend";
+
+ paludis::Command cmd(command);
+ cmd
+ .with_input_stream(&ser_stream, -1, "PALUDIS_SERIALISED_RESOLUTION_FD");
+
+ become_command(cmd);
+ }
}
@@ -651,6 +701,8 @@ ResolveCommand::run(
}
retcode |= display_resolution(env, *resolver->resolution_lists(), cmdline);
+ if (0 == retcode)
+ perform_resolution(env, *resolver->resolution_lists(), cmdline);
}
catch (...)
{
diff --git a/src/clients/cave/cmd_resolve_cmdline.cc b/src/clients/cave/cmd_resolve_cmdline.cc
index 467dc59..d35d446 100644
--- a/src/clients/cave/cmd_resolve_cmdline.cc
+++ b/src/clients/cave/cmd_resolve_cmdline.cc
@@ -24,10 +24,9 @@ using namespace cave;
ResolveCommandLineResolutionOptions::ResolveCommandLineResolutionOptions(args::ArgsHandler * const h) :
ArgsSection(h, "Resolution Options"),
-// g_execution_options(this, "Execution Options", "Control execution."),
-// a_execute(&g_execution_options, "execute", 'x', "Execute the suggested actions", true),
-// a_preserve_world(&g_execution_options, "preserve-world", '1', "Do not modify the 'world' set", true),
-//
+ g_execution_options(this, "Execution Options", "Control execution."),
+ a_execute(&g_execution_options, "execute", 'x', "Execute the suggested actions", true),
+
g_convenience_options(this, "Convenience Options", "Broad behaviour options."),
a_lazy(&g_convenience_options, "lazy", 'z', "Do as little work as possible.", true),
a_complete(&g_convenience_options, "complete", 'c', "Do all optional work.", true),
@@ -48,17 +47,6 @@ ResolveCommandLineResolutionOptions::ResolveCommandLineResolutionOptions(args::A
// a_purge_unused_packages(&g_cleanup_options, "purge-unused-packages", '\0',
// "Purge packages that are no longer used after an uninstall or clean", true),
//
-// g_failure_options(this, "Failure Options", "Failure handling options."),
-// a_continue_on_failure(&g_failure_options, "continue-on-failure", '\0',
-// "Whether to continue after an error occurs",
-// args::EnumArg::EnumArgOptions
-// ("if-fetching", "Only if we are just fetching packages")
-// ("never", "Never")
-// ("if-satisfied", "If remaining packages' dependencies are satisfied")
-// ("if-independent", "If remaining packages do not depend upon any failing package")
-// ("always", "Always (dangerous)"),
-// "if-fetching"),
-//
// g_display_options(this, "Display Options", "Options relating to the resolution display."),
// a_show_option_descriptions(&g_display_options, "show-option-descriptions", '\0',
// "Whether to display descriptions for package options",
@@ -78,20 +66,6 @@ ResolveCommandLineResolutionOptions::ResolveCommandLineResolutionOptions(args::A
// "new"
// ),
-// g_phase_options(this, "Phase Options", "Options controlling which phases to execute. No sanity checking "
-// "is done, allowing you to shoot as many feet off as you desire. Phase names do not have the "
-// "src_, pkg_ or builtin_ prefix, so 'init', 'preinst', 'unpack', 'merge', 'strip' etc."),
-// a_skip_phase(&g_phase_options, "skip-phase", '\0', "Skip the named phases"),
-// a_abort_at_phase(&g_phase_options, "abort-at-phase", '\0', "Abort when a named phase is encounted"),
-// a_skip_until_phase(&g_phase_options, "skip-until-phase", '\0', "Skip every phase until a named phase is encounted"),
-// a_change_phases_for(&g_phase_options, "change-phases-for", '\0',
-// "Control to which package or packages these phase options apply",
-// args::EnumArg::EnumArgOptions
-// ("all", "All packages")
-// ("first", "Only the first package on the list")
-// ("last", "Only the last package on the list"),
-// "all"),
-//
g_keep_options(this, "Reinstall Options", "Control whether installed packages are kept."),
a_keep_targets(&g_keep_options, "keep-targets", 'K',
"Select whether to keep target packages",
@@ -232,7 +206,35 @@ ResolveCommandLineDisplayOptions::ResolveCommandLineDisplayOptions(args::ArgsHan
}
ResolveCommandLineExecutionOptions::ResolveCommandLineExecutionOptions(args::ArgsHandler * const h) :
- ArgsSection(h, "Execution Options")
+ ArgsSection(h, "Execution Options"),
+
+ g_world_options(this, "World Options", "Options controlling how the 'world' set is modified"),
+ a_preserve_world(&g_world_options, "preserve-world", '1', "Do not modify the 'world' set", true),
+
+ g_failure_options(this, "Failure Options", "Failure handling options."),
+ a_continue_on_failure(&g_failure_options, "continue-on-failure", '\0',
+ "Whether to continue after an error occurs",
+ args::EnumArg::EnumArgOptions
+ ("if-fetching", "Only if we are just fetching packages")
+ ("never", "Never")
+ ("if-satisfied", "If remaining packages' dependencies are satisfied")
+ ("if-independent", "If remaining packages do not depend upon any failing package")
+ ("always", "Always (dangerous)"),
+ "if-fetching"),
+
+ g_phase_options(this, "Phase Options", "Options controlling which phases to execute. No sanity checking "
+ "is done, allowing you to shoot as many feet off as you desire. Phase names do not have the "
+ "src_, pkg_ or builtin_ prefix, so 'init', 'preinst', 'unpack', 'merge', 'strip' etc."),
+ a_skip_phase(&g_phase_options, "skip-phase", '\0', "Skip the named phases"),
+ a_abort_at_phase(&g_phase_options, "abort-at-phase", '\0', "Abort when a named phase is encounted"),
+ a_skip_until_phase(&g_phase_options, "skip-until-phase", '\0', "Skip every phase until a named phase is encounted"),
+ a_change_phases_for(&g_phase_options, "change-phases-for", '\0',
+ "Control to which package or packages these phase options apply",
+ args::EnumArg::EnumArgOptions
+ ("all", "All packages")
+ ("first", "Only the first package on the list")
+ ("last", "Only the last package on the list"),
+ "all")
{
}
diff --git a/src/clients/cave/cmd_resolve_cmdline.hh b/src/clients/cave/cmd_resolve_cmdline.hh
index 5c4725c..f1147ee 100644
--- a/src/clients/cave/cmd_resolve_cmdline.hh
+++ b/src/clients/cave/cmd_resolve_cmdline.hh
@@ -31,8 +31,8 @@ namespace paludis
{
ResolveCommandLineResolutionOptions(args::ArgsHandler * const);
- // args::ArgsGroup g_execution_options;
- // args::SwitchArg a_execute;
+ args::ArgsGroup g_execution_options;
+ args::SwitchArg a_execute;
args::ArgsGroup g_convenience_options;
args::SwitchArg a_lazy;
@@ -105,19 +105,17 @@ namespace paludis
{
ResolveCommandLineExecutionOptions(args::ArgsHandler * const);
- // args::ArgsGroup g_world_options;
- // args::SwitchArg a_preserve_world;
-
- // args::ArgsGroup g_failure_options;
- // args::EnumArg a_continue_on_failure;
-
- // args::ArgsGroup g_phase_options;
- // args::StringSetArg a_skip_phase;
- // args::StringSetArg a_abort_at_phase;
- // args::StringSetArg a_skip_until_phase;
- // args::EnumArg a_change_phases_for;
+ args::ArgsGroup g_world_options;
+ args::SwitchArg a_preserve_world;
+ args::ArgsGroup g_failure_options;
+ args::EnumArg a_continue_on_failure;
+ args::ArgsGroup g_phase_options;
+ args::StringSetArg a_skip_phase;
+ args::StringSetArg a_abort_at_phase;
+ args::StringSetArg a_skip_until_phase;
+ args::EnumArg a_change_phases_for;
};
struct ResolveCommandLineDisplayOptions :
diff --git a/src/clients/cave/command_factory.cc b/src/clients/cave/command_factory.cc
index 843cc2c..26b6518 100644
--- a/src/clients/cave/command_factory.cc
+++ b/src/clients/cave/command_factory.cc
@@ -27,6 +27,7 @@
#include <map>
#include "cmd_display_resolution.hh"
+#include "cmd_execute_resolution.hh"
#include "cmd_help.hh"
#include "cmd_perform.hh"
#include "cmd_print_categories.hh"
@@ -74,6 +75,7 @@ CommandFactory::CommandFactory() :
PrivateImplementationPattern<CommandFactory>(new Implementation<CommandFactory>)
{
_imp->handlers.insert(std::make_pair("display-resolution", make_command<DisplayResolutionCommand>));
+ _imp->handlers.insert(std::make_pair("execute-resolution", make_command<ExecuteResolutionCommand>));
_imp->handlers.insert(std::make_pair("help", make_command<HelpCommand>));
_imp->handlers.insert(std::make_pair("perform", make_command<PerformCommand>));
_imp->handlers.insert(std::make_pair("print-categories", make_command<PrintCategoriesCommand>));