aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShea Levy <shea@shealevy.com>2014-02-08 00:05:46 -0500
committerShea Levy <shea@shealevy.com>2014-02-08 00:13:33 -0500
commit64e23d0a38f316a07cef0960d0ed74a450214283 (patch)
tree5c1e75efbe703e4163f087cb05356bcaa27a9999
parent5671188eb2822b7392a6affa5ebe2f1eb8f521a0 (diff)
Add download-via-ssh substituter
This substituter connects to a remote host, runs nix-store --serve there, and then forwards substituter commands on to the remote host and sends their results to the calling program. The ssh-substituter-hosts option can be specified as a list of hosts to try. This is an initial implementation and, while it works, it has some limitations: * Only the first host is used * There is no caching of query results (all queries are sent to the remote machine) * There is no informative output (such as progress bars) * Some failure modes may cause unhelpful error messages * There is no concept of trusted-ssh-substituter-hosts Signed-off-by: Shea Levy <shea@shealevy.com>
-rw-r--r--Makefile1
-rw-r--r--src/download-via-ssh/download-via-ssh.cc129
-rw-r--r--src/download-via-ssh/local.mk9
-rw-r--r--src/libmain/shared.cc3
-rw-r--r--src/libstore/globals.cc20
-rw-r--r--src/libstore/globals.hh6
6 files changed, 168 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index 0ad6bb236..78d85ebc5 100644
--- a/Makefile
+++ b/Makefile
@@ -10,6 +10,7 @@ makefiles = \
src/nix-instantiate/local.mk \
src/nix-env/local.mk \
src/nix-daemon/local.mk \
+ src/download-via-ssh/local.mk \
src/nix-log2xml/local.mk \
src/bsdiff-4.3/local.mk \
perl/local.mk \
diff --git a/src/download-via-ssh/download-via-ssh.cc b/src/download-via-ssh/download-via-ssh.cc
new file mode 100644
index 000000000..7adb93e10
--- /dev/null
+++ b/src/download-via-ssh/download-via-ssh.cc
@@ -0,0 +1,129 @@
+#include "shared.hh"
+#include "util.hh"
+#include "serialise.hh"
+#include "archive.hh"
+#include "affinity.hh"
+#include "globals.hh"
+
+#include <iostream>
+#include <unistd.h>
+
+using namespace nix;
+using std::pair;
+using std::cout;
+using std::endl;
+
+// !!! TODO:
+// * Respect more than the first host
+// * use a database
+// * show progress
+
+static pair<FdSink, FdSource> connect(string conn) {
+ Pipe to, from;
+ to.create();
+ from.create();
+ pid_t child = fork();
+ switch (child) {
+ case -1:
+ throw SysError("unable to fork");
+ case 0:
+ try {
+ restoreAffinity();
+ if (dup2(to.readSide, STDIN_FILENO) == -1)
+ throw SysError("dupping stdin");
+ if (dup2(from.writeSide, STDOUT_FILENO) == -1)
+ throw SysError("dupping stdout");
+ execlp("ssh"
+ , "ssh"
+ , "-x"
+ , "-T"
+ , conn.c_str()
+ , "nix-store --serve"
+ , NULL);
+ throw SysError("executing ssh");
+ } catch (std::exception & e) {
+ std::cerr << "error: " << e.what() << std::endl;
+ }
+ _exit(1);
+ }
+ // If child exits unexpectedly, we'll EPIPE. If we exit unexpectedly, child will
+ // So no need to keep track of it.
+
+ return pair<FdSink, FdSource>(to.writeSide.borrow(), from.readSide.borrow());
+}
+
+static void substitute(pair<FdSink, FdSource> & pipes, Path storePath, Path destPath) {
+ writeString("substitute", pipes.first);
+ writeString(storePath, pipes.first);
+ pipes.first.flush();
+ restorePath(destPath, pipes.second);
+ cout << endl;
+}
+
+static void query(pair<FdSink, FdSource> & pipes) {
+ using std::cin;
+ writeString("query", pipes.first);
+ for (string line; getline(cin, line);) {
+ Strings tokenized = tokenizeString<Strings>(line);
+ string cmd = tokenized.front();
+ writeString(cmd, pipes.first);
+ tokenized.pop_front();
+ foreach (Strings::iterator, i, tokenized)
+ writeStrings(tokenized, pipes.first);
+ pipes.first.flush();
+ if (cmd == "have") {
+ PathSet paths = readStrings<PathSet>(pipes.second);
+ foreach (PathSet::iterator, i, paths)
+ cout << *i << endl;
+ } else if (cmd == "info") {
+ for (Path path = readString(pipes.second); !path.empty(); path = readString(pipes.second)) {
+ cout << path << endl;
+ cout << readString(pipes.second) << endl;
+ PathSet references = readStrings<PathSet>(pipes.second);
+ cout << references.size() << endl;
+ foreach (PathSet::iterator, i, references)
+ cout << *i << endl;
+ cout << readLongLong(pipes.second) << endl;
+ cout << readLongLong(pipes.second) << endl;
+ }
+ } else
+ throw Error(format("Unknown substituter query `%1%'") % cmd);
+ cout << endl;
+ }
+ writeString("", pipes.first);
+}
+
+void run(Strings args)
+{
+ if (args.empty())
+ throw UsageError("download-via-ssh requires an argument");
+
+ if (settings.sshSubstituterHosts.empty())
+ return;
+
+ cout << endl;
+
+ pair<FdSink, FdSource> pipes = connect(settings.sshSubstituterHosts.front());
+
+ Strings::iterator i = args.begin();
+ if (*i == "--query")
+ query(pipes);
+ else if (*i == "--substitute")
+ if (args.size() != 3)
+ throw UsageError("download-via-ssh: --substitute takes exactly two arguments");
+ else {
+ Path storePath = *++i;
+ Path destPath = *++i;
+ substitute(pipes, storePath, destPath);
+ }
+ else
+ throw UsageError(format("download-via-ssh: unknown command `%1%'") % *i);
+}
+
+void printHelp()
+{
+ std::cerr << "Usage: download-via-ssh --query|--substitute store-path dest-path" << std::endl;
+}
+
+
+string programId = "download-via-ssh";
diff --git a/src/download-via-ssh/local.mk b/src/download-via-ssh/local.mk
new file mode 100644
index 000000000..92bf11594
--- /dev/null
+++ b/src/download-via-ssh/local.mk
@@ -0,0 +1,9 @@
+programs += download-via-ssh
+
+download-via-ssh_DIR := $(d)
+
+download-via-ssh_SOURCES := $(d)/download-via-ssh.cc
+
+download-via-ssh_INSTALL_DIR := $(libexecdir)/nix/substituters
+
+download-via-ssh_LIBS = libmain libstore libutil libformat
diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc
index fb70cb076..30238c7fb 100644
--- a/src/libmain/shared.cc
+++ b/src/libmain/shared.cc
@@ -223,6 +223,9 @@ static void initAndRun(int argc, char * * argv)
else remaining.push_back(arg);
}
+ if (char *pack = getenv("_NIX_OPTIONS"))
+ settings.unpack(pack);
+
settings.update();
run(remaining);
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index 68add1982..c1ffc26fa 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -79,6 +79,7 @@ void Settings::processEnvironment()
#endif
substituters.push_back(nixLibexecDir + "/nix/substituters/download-using-manifests.pl");
substituters.push_back(nixLibexecDir + "/nix/substituters/download-from-binary-cache.pl");
+ substituters.push_back(nixLibexecDir + "/nix/substituters/download-via-ssh");
} else
substituters = tokenizeString<Strings>(subs, ":");
}
@@ -151,6 +152,7 @@ void Settings::update()
get(gcKeepDerivations, "gc-keep-derivations");
get(autoOptimiseStore, "auto-optimise-store");
get(envKeepDerivations, "env-keep-derivations");
+ get(sshSubstituterHosts, "ssh-substituter-hosts");
}
@@ -182,6 +184,13 @@ void Settings::get(StringSet & res, const string & name)
res.insert(ss.begin(), ss.end());
}
+void Settings::get(Strings & res, const string & name)
+{
+ SettingsMap::iterator i = settings.find(name);
+ if (i == settings.end()) return;
+ res = tokenizeString<Strings>(i->second);
+}
+
template<class N> void Settings::get(N & res, const string & name)
{
@@ -206,6 +215,17 @@ string Settings::pack()
}
+void Settings::unpack(string pack) {
+ Strings lines = tokenizeString<Strings>(pack, "\n");
+ foreach (Strings::iterator, i, lines) {
+ string::size_type eq = i->find('=');
+ if (eq == string::npos)
+ throw Error("illegal option name/value");
+ set(i->substr(0, eq), i->substr(eq + 1));
+ }
+}
+
+
Settings::SettingsMap Settings::getOverrides()
{
return overrides;
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index 9300edbe9..5e7cfda9a 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -25,6 +25,8 @@ struct Settings {
string pack();
+ void unpack(string pack);
+
SettingsMap getOverrides();
/* The directory where we store sources and derived files. */
@@ -144,6 +146,9 @@ struct Settings {
chroot. */
StringSet dirsInChroot;
+ /* Set of ssh connection strings for the ssh substituter */
+ Strings sshSubstituterHosts;
+
/* Whether to impersonate a Linux 2.6 machine on newer kernels. */
bool impersonateLinux26;
@@ -195,6 +200,7 @@ private:
void get(string & res, const string & name);
void get(bool & res, const string & name);
void get(StringSet & res, const string & name);
+ void get(Strings & res, const string & name);
template<class N> void get(N & res, const string & name);
};