about summary refs log tree commit diff
path: root/src/download-via-ssh
diff options
context:
space:
mode:
authorShea Levy <shea@shealevy.com>2014-02-08T05·05-0500
committerShea Levy <shea@shealevy.com>2014-02-08T05·13-0500
commit64e23d0a38f316a07cef0960d0ed74a450214283 (patch)
tree5c1e75efbe703e4163f087cb05356bcaa27a9999 /src/download-via-ssh
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>
Diffstat (limited to 'src/download-via-ssh')
-rw-r--r--src/download-via-ssh/download-via-ssh.cc129
-rw-r--r--src/download-via-ssh/local.mk9
2 files changed, 138 insertions, 0 deletions
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 0000000000..7adb93e10d
--- /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 0000000000..92bf115946
--- /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