#include "archive.hh"
#include "affinity.hh"
#include "globals.hh"
+#include "builtins.hh"
#include <algorithm>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
#include <fcntl.h>
#include <errno.h>
#include <pwd.h>
bool canSendStderr;
-
+/* This variable is used to keep track of whether a connection
+ comes from a host other than the host running guix-daemon. */
+static bool isRemoteConnection;
/* This function is called anytime we want to write something to
stderr. If we're in a state where the protocol allows it (i.e.,
}
-struct TunnelSink : Sink
+struct TunnelSink : BufferedSink
{
Sink & to;
- TunnelSink(Sink & to) : to(to) { }
- virtual void operator () (const unsigned char * data, size_t len)
+ TunnelSink(Sink & to) : BufferedSink(64 * 1024), to(to) { }
+ virtual void write(const unsigned char * data, size_t len)
{
writeInt(STDERR_WRITE, to);
writeString(data, len, to);
bool sign = readInt(from) == 1;
startWork();
TunnelSink sink(to);
- store->exportPath(path, sign, sink);
+ try {
+ store->exportPath(path, sign, sink);
+ }
+ catch (Error &e) {
+ /* Flush SINK beforehand or its destructor will rightfully trigger
+ an assertion failure. */
+ sink.flush();
+ throw e;
+ }
+ sink.flush();
stopWork();
writeInt(1, to);
break;
startWork();
TunnelSource source(from);
- /* Unlike Nix, always require a signature, even for "trusted"
- users. */
+ /* Unlike Nix, always require a signature, even for "trusted"
+ users. */
Paths paths = store->importPaths(true, source);
stopWork();
writeStrings(paths, to);
case wopBuildPaths: {
PathSet drvs = readStorePaths<PathSet>(from);
+ BuildMode mode = bmNormal;
+ if (GET_PROTOCOL_MINOR(clientVersion) >= 15) {
+ mode = (BuildMode)readInt(from);
+
+ /* Repairing is not atomic, so disallowed for "untrusted"
+ clients. */
+ if (mode == bmRepair && !trusted)
+ throw Error("repairing is not supported when building through the Nix daemon");
+ }
startWork();
- store->buildPaths(drvs);
+ store->buildPaths(drvs, mode);
stopWork();
writeInt(1, to);
break;
}
case wopCollectGarbage: {
+ if (isRemoteConnection) {
+ throw Error("Garbage collection is disabled for remote hosts.");
+ break;
+ }
+
GCOptions options;
options.action = (GCOptions::GCAction) readInt(from);
options.pathsToDelete = readStorePaths<PathSet>(from);
case wopSetOptions: {
settings.keepFailed = readInt(from) != 0;
+ if (isRemoteConnection)
+ /* When the client is remote, don't keep the failed build tree as
+ it is presumably inaccessible to the client and could fill up
+ our disk. */
+ settings.keepFailed = 0;
+
settings.keepGoing = readInt(from) != 0;
settings.set("build-fallback", readInt(from) ? "true" : "false");
verbosity = (Verbosity) readInt(from);
- settings.set("build-max-jobs", int2String(readInt(from)));
- settings.set("build-max-silent-time", int2String(readInt(from)));
+
+ if (GET_PROTOCOL_MINOR(clientVersion) < 0x61) {
+ settings.set("build-max-jobs", std::to_string(readInt(from)));
+ settings.set("build-max-silent-time", std::to_string(readInt(from)));
+ }
+
if (GET_PROTOCOL_MINOR(clientVersion) >= 2)
settings.useBuildHook = readInt(from) != 0;
if (GET_PROTOCOL_MINOR(clientVersion) >= 4) {
logType = (LogType) readInt(from);
settings.printBuildTrace = readInt(from) != 0;
}
- if (GET_PROTOCOL_MINOR(clientVersion) >= 6)
- settings.set("build-cores", int2String(readInt(from)));
+ if (GET_PROTOCOL_MINOR(clientVersion) >= 6
+ && GET_PROTOCOL_MINOR(clientVersion) < 0x61)
+ settings.set("build-cores", std::to_string(readInt(from)));
if (GET_PROTOCOL_MINOR(clientVersion) >= 10)
settings.set("build-use-substitutes", readInt(from) ? "true" : "false");
if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
for (unsigned int i = 0; i < n; i++) {
string name = readString(from);
string value = readString(from);
- if (name == "build-timeout" || name == "use-ssh-substituter")
+ if (name == "build-timeout" || name == "build-max-silent-time"
+ || name == "build-max-jobs" || name == "build-cores"
+ || name == "build-repeat"
+ || name == "multiplexed-build-output")
settings.set(name, value);
else
settings.set(trusted ? name : "untrusted-" + name, value);
break;
}
+ case wopBuiltinBuilders: {
+ startWork();
+ auto names = builtinBuilderNames();
+ stopWork();
+ writeStrings(names, to);
+ break;
+ }
+
default:
throw Error(format("invalid operation %1%") % op);
}
}
-bool matchUser(const string & user, const string & group, const Strings & users)
-{
- if (find(users.begin(), users.end(), "*") != users.end())
- return true;
-
- if (find(users.begin(), users.end(), user) != users.end())
- return true;
-
- for (auto & i : users)
- if (string(i, 0, 1) == "@") {
- if (group == string(i, 1)) return true;
- struct group * gr = getgrnam(i.c_str() + 1);
- if (!gr) continue;
- for (char * * mem = gr->gr_mem; *mem; mem++)
- if (user == string(*mem)) return true;
- }
-
- return false;
-}
-
-
-#define SD_LISTEN_FDS_START 3
-
-
-static void daemonLoop()
+/* Accept a connection on FDSOCKET and fork a server process to process the
+ new connection. */
+static void acceptConnection(int fdSocket)
{
- if (chdir("/") == -1)
- throw SysError("cannot change current directory");
-
- /* Get rid of children automatically; don't let them become
- zombies. */
- setSigChldAction(true);
-
- AutoCloseFD fdSocket;
-
- /* Handle socket-based activation by systemd. */
- if (getEnv("LISTEN_FDS") != "") {
- if (getEnv("LISTEN_PID") != int2String(getpid()) || getEnv("LISTEN_FDS") != "1")
- throw Error("unexpected systemd environment variables");
- fdSocket = SD_LISTEN_FDS_START;
- }
-
- /* Otherwise, create and bind to a Unix domain socket. */
- else {
-
- /* Create and bind to a Unix domain socket. */
- fdSocket = socket(PF_UNIX, SOCK_STREAM, 0);
- if (fdSocket == -1)
- throw SysError("cannot create Unix domain socket");
-
- string socketPath = settings.nixDaemonSocketFile;
-
- createDirs(dirOf(socketPath));
-
- /* Urgh, sockaddr_un allows path names of only 108 characters.
- So chdir to the socket directory so that we can pass a
- relative path name. */
- if (chdir(dirOf(socketPath).c_str()) == -1)
- throw SysError("cannot change current directory");
- Path socketPathRel = "./" + baseNameOf(socketPath);
-
- struct sockaddr_un addr;
- addr.sun_family = AF_UNIX;
- if (socketPathRel.size() >= sizeof(addr.sun_path))
- throw Error(format("socket path `%1%' is too long") % socketPathRel);
- strcpy(addr.sun_path, socketPathRel.c_str());
-
- unlink(socketPath.c_str());
-
- /* Make sure that the socket is created with 0666 permission
- (everybody can connect --- provided they have access to the
- directory containing the socket). */
- mode_t oldMode = umask(0111);
- int res = bind(fdSocket, (struct sockaddr *) &addr, sizeof(addr));
- umask(oldMode);
- if (res == -1)
- throw SysError(format("cannot bind to socket `%1%'") % socketPath);
-
- if (chdir("/") == -1) /* back to the root */
- throw SysError("cannot change current directory");
+ uid_t clientUid = (uid_t) -1;
+ gid_t clientGid = (gid_t) -1;
- if (listen(fdSocket, 5) == -1)
- throw SysError(format("cannot listen on socket `%1%'") % socketPath);
- }
-
- closeOnExec(fdSocket);
-
- /* Loop accepting connections. */
- while (1) {
-
- try {
- /* Important: the server process *cannot* open the SQLite
- database, because it doesn't like forks very much. */
- assert(!store);
-
- /* Accept a connection. */
- struct sockaddr_un remoteAddr;
- socklen_t remoteAddrLen = sizeof(remoteAddr);
-
- AutoCloseFD remote = accept(fdSocket,
- (struct sockaddr *) &remoteAddr, &remoteAddrLen);
- checkInterrupt();
- if (remote == -1) {
- if (errno == EINTR)
- continue;
- else
- throw SysError("accepting connection");
- }
-
- closeOnExec(remote);
+ try {
+ /* Important: the server process *cannot* open the SQLite
+ database, because it doesn't like forks very much. */
+ assert(!store);
+
+ /* Accept a connection. */
+ struct sockaddr_storage remoteAddr;
+ socklen_t remoteAddrLen = sizeof(remoteAddr);
+
+ try_again:
+ AutoCloseFD remote = accept(fdSocket,
+ (struct sockaddr *) &remoteAddr, &remoteAddrLen);
+ checkInterrupt();
+ if (remote == -1) {
+ if (errno == EINTR)
+ goto try_again;
+ else
+ throw SysError("accepting connection");
+ }
+
+ closeOnExec(remote);
+
+ {
+ int enabled = 1;
+
+ /* If we're on a TCP connection, disable Nagle's algorithm so that
+ data is sent as soon as possible. */
+ (void) setsockopt(remote, SOL_TCP, TCP_NODELAY,
+ &enabled, sizeof enabled);
+
+#if defined(TCP_QUICKACK)
+ /* Enable TCP quick-ack if applicable; this might help a little. */
+ (void) setsockopt(remote, SOL_TCP, TCP_QUICKACK,
+ &enabled, sizeof enabled);
+#endif
+ }
- bool trusted = false;
- pid_t clientPid = -1;
+ pid_t clientPid = -1;
+ bool trusted = false;
+ /* Get the identity of the caller, if possible. */
+ if (remoteAddr.ss_family == AF_UNIX) {
#if defined(SO_PEERCRED)
- /* Get the identity of the caller, if possible. */
- ucred cred;
- socklen_t credLen = sizeof(cred);
- if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1)
- throw SysError("getting peer credentials");
+ ucred cred;
+ socklen_t credLen = sizeof(cred);
+ if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED,
+ &cred, &credLen) == -1)
+ throw SysError("getting peer credentials");
- clientPid = cred.pid;
+ clientPid = cred.pid;
+ clientUid = cred.uid;
+ clientGid = cred.gid;
+ trusted = clientUid == 0;
struct passwd * pw = getpwuid(cred.uid);
- string user = pw ? pw->pw_name : int2String(cred.uid);
-
- struct group * gr = getgrgid(cred.gid);
- string group = gr ? gr->gr_name : int2String(cred.gid);
-
- Strings trustedUsers = settings.get("trusted-users", Strings({"root"}));
- Strings allowedUsers = settings.get("allowed-users", Strings({"*"}));
-
- if (matchUser(user, group, trustedUsers))
- trusted = true;
+ string user = pw ? pw->pw_name : std::to_string(cred.uid);
- if (!trusted && !matchUser(user, group, allowedUsers))
- throw Error(format("user `%1%' is not allowed to connect to the Nix daemon") % user);
-
- printMsg(lvlInfo, format((string) "accepted connection from pid %1%, user %2%"
- + (trusted ? " (trusted)" : "")) % clientPid % user);
+ printMsg(lvlInfo,
+ format((string) "accepted connection from pid %1%, user %2%")
+ % clientPid % user);
#endif
-
- /* Fork a child to handle the connection. */
- startProcess([&]() {
- fdSocket.close();
+ } else {
+ char address_str[128];
+ const char *result;
+
+ if (remoteAddr.ss_family == AF_INET) {
+ struct sockaddr_in *addr = (struct sockaddr_in *) &remoteAddr;
+ result = inet_ntop(AF_INET, &addr->sin_addr,
+ address_str, sizeof address_str);
+ } else if (remoteAddr.ss_family == AF_INET6) {
+ struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &remoteAddr;
+ result = inet_ntop(AF_INET6, &addr->sin6_addr,
+ address_str, sizeof address_str);
+ } else {
+ result = NULL;
+ }
+
+ if (result != NULL) {
+ printMsg(lvlInfo,
+ format("accepted connection from %1%")
+ % address_str);
+ }
+ }
+
+ /* Fork a child to handle the connection. */
+ startProcess([&]() {
+ close(fdSocket);
/* Background the daemon. */
if (setsid() == -1)
/* For debugging, stuff the pid into argv[1]. */
if (clientPid != -1 && argvSaved[1]) {
- string processName = int2String(clientPid);
+ string processName = std::to_string(clientPid);
strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1]));
}
+ /* Store the client's user and group for this connection. This
+ has to be done in the forked process since it is per
+ connection. Setting these to -1 means: do not change. */
+ settings.clientUid = clientUid;
+ settings.clientGid = clientGid;
+ isRemoteConnection = (remoteAddr.ss_family != AF_UNIX);
+
/* Handle the connection. */
from.fd = remote;
to.fd = remote;
exit(0);
}, false, "unexpected Nix daemon error: ", true);
- } catch (Interrupted & e) {
- throw;
- } catch (Error & e) {
- printMsg(lvlError, format("error processing connection: %1%") % e.msg());
- }
+ } catch (Interrupted & e) {
+ throw;
+ } catch (Error & e) {
+ printMsg(lvlError, format("error processing connection: %1%") % e.msg());
}
}
-
-void run(Strings args)
+static void daemonLoop(const std::vector<int>& sockets)
{
- for (Strings::iterator i = args.begin(); i != args.end(); ) {
- string arg = *i++;
- if (arg == "--daemon") /* ignored for backwards compatibility */;
+ if (chdir("/") == -1)
+ throw SysError("cannot change current directory");
+
+ /* Get rid of children automatically; don't let them become
+ zombies. */
+ setSigChldAction(true);
+
+ /* Mark sockets as close-on-exec. */
+ for(int fd: sockets) {
+ closeOnExec(fd);
}
- daemonLoop();
+ /* Prepare the FD set corresponding to SOCKETS. */
+ auto initializeFDSet = [&](fd_set *set) {
+ FD_ZERO(set);
+ for (int fd: sockets) {
+ FD_SET(fd, set);
+ }
+ };
+
+ /* Loop accepting connections. */
+ while (1) {
+ fd_set readfds;
+
+ initializeFDSet(&readfds);
+ int count =
+ select(*std::max_element(sockets.begin(), sockets.end()) + 1,
+ &readfds, NULL, NULL,
+ NULL);
+ if (count < 0) {
+ int err = errno;
+ if (err == EINTR)
+ continue;
+ throw SysError(format("select error: %1%") % strerror(err));
+ }
+
+ for (unsigned int i = 0; i < sockets.size(); i++) {
+ if (FD_ISSET(sockets[i], &readfds)) {
+ acceptConnection(sockets[i]);
+ }
+ }
+ }
+}
+
+
+void run(const std::vector<int>& sockets)
+{
+ daemonLoop(sockets);
}