diff options
Diffstat (limited to 'nix/nix-daemon')
-rw-r--r-- | nix/nix-daemon/guix-daemon.cc | 126 | ||||
-rw-r--r-- | nix/nix-daemon/nix-daemon.cc | 159 |
2 files changed, 178 insertions, 107 deletions
diff --git a/nix/nix-daemon/guix-daemon.cc b/nix/nix-daemon/guix-daemon.cc index f096ed5a97..1934487d24 100644 --- a/nix/nix-daemon/guix-daemon.cc +++ b/nix/nix-daemon/guix-daemon.cc @@ -33,6 +33,9 @@ #include <strings.h> #include <exception> +#include <libintl.h> +#include <locale.h> + /* Variables used by `nix-daemon.cc'. */ volatile ::sig_atomic_t blockInt; char **argvSaved; @@ -45,16 +48,21 @@ extern void run (Strings args); /* Command-line options. */ +#define n_(str) str +#define _(str) gettext (str) +static const char guix_textdomain[] = "guix"; + + const char *argp_program_version = "guix-daemon (" PACKAGE_NAME ") " PACKAGE_VERSION; const char *argp_program_bug_address = PACKAGE_BUGREPORT; static char doc[] = -"guix-daemon -- perform derivation builds and store accesses\ -\v\ -This program is a daemon meant to run in the background. It serves \ + n_("guix-daemon -- perform derivation builds and store accesses") + "\v\n" + n_("This program is a daemon meant to run in the background. It serves \ requests sent over a Unix-domain socket. It accesses the store, and \ -builds derivations on behalf of its clients."; +builds derivations on behalf of its clients."); #define GUIX_OPT_SYSTEM 1 #define GUIX_OPT_DISABLE_CHROOT 2 @@ -75,56 +83,59 @@ builds derivations on behalf of its clients."; static const struct argp_option options[] = { - { "system", GUIX_OPT_SYSTEM, "SYSTEM", 0, - "Assume SYSTEM as the current system type" }, - { "cores", 'c', "N", 0, - "Use N CPU cores to build each derivation; 0 means as many as available" }, - { "max-jobs", 'M', "N", 0, - "Allow at most N build jobs" }, + { "system", GUIX_OPT_SYSTEM, n_("SYSTEM"), 0, + n_("assume SYSTEM as the current system type") }, + { "cores", 'c', n_("N"), 0, + n_("use N CPU cores to build each derivation; 0 means as many as available") + }, + { "max-jobs", 'M', n_("N"), 0, + n_("allow at most N build jobs") }, { "disable-chroot", GUIX_OPT_DISABLE_CHROOT, 0, 0, - "Disable chroot builds" }, - { "chroot-directory", GUIX_OPT_CHROOT_DIR, "DIR", 0, - "Add DIR to the build chroot" }, - { "build-users-group", GUIX_OPT_BUILD_USERS_GROUP, "GROUP", 0, - "Perform builds as a user of GROUP" }, + n_("disable chroot builds") }, + { "chroot-directory", GUIX_OPT_CHROOT_DIR, n_("DIR"), 0, + n_("add DIR to the build chroot") }, + { "build-users-group", GUIX_OPT_BUILD_USERS_GROUP, n_("GROUP"), 0, + n_("perform builds as a user of GROUP") }, { "no-substitutes", GUIX_OPT_NO_SUBSTITUTES, 0, 0, - "Do not use substitutes" }, - { "substitute-urls", GUIX_OPT_SUBSTITUTE_URLS, "URLS", 0, - "Use URLS as the default list of substitute providers" }, + n_("do not use substitutes") }, + { "substitute-urls", GUIX_OPT_SUBSTITUTE_URLS, n_("URLS"), 0, + n_("use URLS as the default list of substitute providers") }, { "no-build-hook", GUIX_OPT_NO_BUILD_HOOK, 0, 0, - "Do not use the 'build hook'" }, + n_("do not use the 'build hook'") }, { "cache-failures", GUIX_OPT_CACHE_FAILURES, 0, 0, - "Cache build failures" }, + n_("cache build failures") }, { "lose-logs", GUIX_OPT_LOSE_LOGS, 0, 0, - "Do not keep build logs" }, + n_("do not keep build logs") }, { "disable-log-compression", GUIX_OPT_DISABLE_LOG_COMPRESSION, 0, 0, - "Disable compression of the build logs" }, + n_("disable compression of the build logs") }, /* '--disable-deduplication' was known as '--disable-store-optimization' up to Guix 0.7 included, so keep the alias around. */ { "disable-deduplication", GUIX_OPT_DISABLE_DEDUPLICATION, 0, 0, - "Disable automatic file \"deduplication\" in the store" }, + n_("disable automatic file \"deduplication\" in the store") }, { "disable-store-optimization", GUIX_OPT_DISABLE_DEDUPLICATION, 0, OPTION_ALIAS | OPTION_HIDDEN, NULL }, - { "impersonate-linux-2.6", GUIX_OPT_IMPERSONATE_LINUX_26, 0, 0, - "Impersonate Linux 2.6" -#ifndef HAVE_SYS_PERSONALITY_H - " (this option has no effect in this configuration)" + { "impersonate-linux-2.6", GUIX_OPT_IMPERSONATE_LINUX_26, 0, +#ifdef HAVE_SYS_PERSONALITY_H + 0, +#else + OPTION_HIDDEN, #endif + n_("impersonate Linux 2.6") }, { "gc-keep-outputs", GUIX_OPT_GC_KEEP_OUTPUTS, "yes/no", OPTION_ARG_OPTIONAL, - "Tell whether the GC must keep outputs of live derivations" }, + n_("tell whether the GC must keep outputs of live derivations") }, { "gc-keep-derivations", GUIX_OPT_GC_KEEP_DERIVATIONS, "yes/no", OPTION_ARG_OPTIONAL, - "Tell whether the GC must keep derivations corresponding \ -to live outputs" }, + n_("tell whether the GC must keep derivations corresponding \ +to live outputs") }, - { "listen", GUIX_OPT_LISTEN, "SOCKET", 0, - "Listen for connections on SOCKET" }, + { "listen", GUIX_OPT_LISTEN, n_("SOCKET"), 0, + n_("listen for connections on SOCKET") }, { "debug", GUIX_OPT_DEBUG, 0, 0, - "Produce debugging output" }, + n_("produce debugging output") }, { 0, 0, 0, 0, 0 } }; @@ -154,8 +165,18 @@ parse_opt (int key, char *arg, struct argp_state *state) settings.useChroot = false; break; case GUIX_OPT_CHROOT_DIR: - settings.dirsInChroot.insert (arg); - break; + { + std::string chroot_dirs; + + chroot_dirs = settings.get ("build-extra-chroot-dirs", + (std::string) ""); + if (chroot_dirs == "") + chroot_dirs = arg; + else + chroot_dirs = chroot_dirs + " " + arg; + settings.set("build-extra-chroot-dirs", chroot_dirs); + break; + } case GUIX_OPT_DISABLE_LOG_COMPRESSION: settings.compressLog = false; break; @@ -181,7 +202,7 @@ parse_opt (int key, char *arg, struct argp_state *state) } catch (std::exception &e) { - fprintf (stderr, "error: %s\n", e.what ()); + fprintf (stderr, _("error: %s\n"), e.what ()); exit (EXIT_FAILURE); } break; @@ -220,19 +241,29 @@ parse_opt (int key, char *arg, struct argp_state *state) } /* Argument parsing. */ -static struct argp argp = { options, parse_opt, 0, doc }; +static const struct argp argp = + { + options, parse_opt, + NULL, doc, + NULL, NULL, // children and help_filter + guix_textdomain + }; int main (int argc, char *argv[]) { - Strings nothing; + static const Strings nothing; + + setlocale (LC_ALL, ""); + bindtextdomain (guix_textdomain, LOCALEDIR); + textdomain (guix_textdomain); /* Initialize libgcrypt. */ if (!gcry_check_version (GCRYPT_VERSION)) { - fprintf (stderr, "error: libgcrypt version mismatch\n"); + fprintf (stderr, _("error: libgcrypt version mismatch\n")); exit (EXIT_FAILURE); } @@ -323,16 +354,17 @@ main (int argc, char *argv[]) settings.update (); if (geteuid () == 0 && settings.buildUsersGroup.empty ()) - fprintf (stderr, "warning: daemon is running as root, so " - "using `--build-users-group' is highly recommended\n"); + fprintf (stderr, _("warning: daemon is running as root, so \ +using `--build-users-group' is highly recommended\n")); if (settings.useChroot) { - foreach (PathSet::iterator, i, settings.dirsInChroot) - { - printMsg (lvlDebug, - format ("directory `%1%' added to the chroot") % *i); - } + std::string chroot_dirs; + + chroot_dirs = settings.get ("build-extra-chroot-dirs", + (std::string) ""); + printMsg (lvlDebug, + format ("extra chroot directories: '%1%'") % chroot_dirs); } printMsg (lvlDebug, @@ -346,7 +378,7 @@ main (int argc, char *argv[]) } catch (std::exception &e) { - fprintf (stderr, "error: %s\n", e.what ()); + fprintf (stderr, _("error: %s\n"), e.what ()); return EXIT_FAILURE; } diff --git a/nix/nix-daemon/nix-daemon.cc b/nix/nix-daemon/nix-daemon.cc index 8814fe3155..10159db62e 100644 --- a/nix/nix-daemon/nix-daemon.cc +++ b/nix/nix-daemon/nix-daemon.cc @@ -7,6 +7,8 @@ #include "affinity.hh" #include "globals.hh" +#include <algorithm> + #include <cstring> #include <unistd.h> #include <signal.h> @@ -17,6 +19,8 @@ #include <sys/un.h> #include <fcntl.h> #include <errno.h> +#include <pwd.h> +#include <grp.h> using namespace nix; @@ -44,7 +48,6 @@ static FdSource from(STDIN_FILENO); static FdSink to(STDOUT_FILENO); bool canSendStderr; -pid_t myPid; @@ -54,11 +57,7 @@ pid_t myPid; socket. */ static void tunnelStderr(const unsigned char * buf, size_t count) { - /* Don't send the message to the client if we're a child of the - process handling the connection. Otherwise we could screw up - the protocol. It's up to the parent to redirect stderr and - send it to the client somehow (e.g., as in build.cc). */ - if (canSendStderr && myPid == getpid()) { + if (canSendStderr) { try { writeInt(STDERR_NEXT, to); writeString(buf, count, to); @@ -284,15 +283,6 @@ static void performOp(bool trusted, unsigned int clientVersion, { switch (op) { -#if 0 - case wopQuit: { - /* Close the database. */ - store.reset((StoreAPI *) 0); - writeInt(1, to); - break; - } -#endif - case wopIsValidPath: { /* 'readStorePath' could raise an error leading to the connection being closed. To be able to recover from an invalid path error, @@ -450,6 +440,9 @@ static void performOp(bool trusted, unsigned int clientVersion, case wopImportPaths: { startWork(); TunnelSource source(from); + + /* Unlike Nix, always require a signature, even for "trusted" + users. */ Paths paths = store->importPaths(true, source); stopWork(); writeStrings(paths, to); @@ -650,6 +643,25 @@ static void performOp(bool trusted, unsigned int clientVersion, break; } + case wopOptimiseStore: + startWork(); + store->optimiseStore(); + stopWork(); + writeInt(1, to); + break; + + case wopVerifyStore: { + bool checkContents = readInt(from) != 0; + bool repair = readInt(from) != 0; + startWork(); + if (repair && !trusted) + throw Error("you are not privileged to repair paths"); + bool errors = store->verifyStore(checkContents, repair); + stopWork(); + writeInt(errors, to); + break; + } + default: throw Error(format("invalid operation %1%") % op); } @@ -659,7 +671,6 @@ static void performOp(bool trusted, unsigned int clientVersion, static void processConnection(bool trusted) { canSendStderr = false; - myPid = getpid(); _writeToStderr = tunnelStderr; #ifdef HAVE_HUP_NOTIFICATION @@ -708,7 +719,7 @@ static void processConnection(bool trusted) to.flush(); } catch (Error & e) { - stopWork(false, e.msg()); + stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0); to.flush(); return; } @@ -735,12 +746,10 @@ static void processConnection(bool trusted) during addTextToStore() / importPath(). If that happens, just send the error message and exit. */ bool errorAllowed = canSendStderr; - if (!errorAllowed) printMsg(lvlError, format("error processing client input: %1%") % e.msg()); stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? e.status : 0); - if (!errorAllowed) break; + if (!errorAllowed) throw; } catch (std::bad_alloc & e) { - if (canSendStderr) - stopWork(false, "Nix daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0); + stopWork(false, "Nix daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0); throw; } @@ -749,7 +758,9 @@ static void processConnection(bool trusted) assert(!canSendStderr); }; - printMsg(lvlError, format("%1% operations") % opCount); + canSendStderr = false; + _isInterrupted = false; + printMsg(lvlDebug, format("%1% operations") % opCount); } @@ -771,11 +782,35 @@ static void setSigChldAction(bool autoReap) } +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() { + if (chdir("/") == -1) + throw SysError("cannot change current directory"); + /* Get rid of children automatically; don't let them become zombies. */ setSigChldAction(true); @@ -804,7 +839,8 @@ static void daemonLoop() /* 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. */ - chdir(dirOf(socketPath).c_str()); + if (chdir(dirOf(socketPath).c_str()) == -1) + throw SysError("cannot change current directory"); Path socketPathRel = "./" + baseNameOf(socketPath); struct sockaddr_un addr; @@ -824,7 +860,8 @@ static void daemonLoop() if (res == -1) throw SysError(format("cannot bind to socket `%1%'") % socketPath); - chdir("/"); /* back to the root */ + if (chdir("/") == -1) /* back to the root */ + throw SysError("cannot change current directory"); if (listen(fdSocket, 5) == -1) throw SysError(format("cannot listen on socket `%1%'") % socketPath); @@ -856,58 +893,61 @@ static void daemonLoop() closeOnExec(remote); - /* Get the identity of the caller, if possible. */ - uid_t clientUid = -1; - pid_t clientPid = -1; bool trusted = false; + pid_t clientPid = -1; #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) { - clientPid = cred.pid; - clientUid = cred.uid; - if (clientUid == 0) trusted = true; - } -#endif + if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1) + throw SysError("getting peer credentials"); - printMsg(lvlInfo, format("accepted connection from pid %1%, uid %2%") % clientPid % clientUid); + clientPid = cred.pid; - /* Fork a child to handle the connection. */ - pid_t child; - child = fork(); + struct passwd * pw = getpwuid(cred.uid); + string user = pw ? pw->pw_name : int2String(cred.uid); - switch (child) { + struct group * gr = getgrgid(cred.gid); + string group = gr ? gr->gr_name : int2String(cred.gid); - case -1: - throw SysError("unable to fork"); + Strings trustedUsers = settings.get("trusted-users", Strings({"root"})); + Strings allowedUsers = settings.get("allowed-users", Strings({"*"})); - case 0: - try { /* child */ + if (matchUser(user, group, trustedUsers)) + trusted = true; - /* Background the daemon. */ - if (setsid() == -1) - throw SysError(format("creating a new session")); + if (!trusted && !matchUser(user, group, allowedUsers)) + throw Error(format("user `%1%' is not allowed to connect to the Nix daemon") % user); - /* Restore normal handling of SIGCHLD. */ - setSigChldAction(false); + printMsg(lvlInfo, format((string) "accepted connection from pid %1%, user %2%" + + (trusted ? " (trusted)" : "")) % clientPid % user); +#endif - /* For debugging, stuff the pid into argv[1]. */ - if (clientPid != -1 && argvSaved[1]) { - string processName = int2String(clientPid); - strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1])); - } + /* Fork a child to handle the connection. */ + startProcess([&]() { + fdSocket.close(); - /* Handle the connection. */ - from.fd = remote; - to.fd = remote; - processConnection(trusted); + /* Background the daemon. */ + if (setsid() == -1) + throw SysError(format("creating a new session")); - } catch (std::exception & e) { - writeToStderr("unexpected Nix daemon error: " + string(e.what()) + "\n"); + /* Restore normal handling of SIGCHLD. */ + setSigChldAction(false); + + /* For debugging, stuff the pid into argv[1]. */ + if (clientPid != -1 && argvSaved[1]) { + string processName = int2String(clientPid); + strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1])); } + + /* Handle the connection. */ + from.fd = remote; + to.fd = remote; + processConnection(trusted); + exit(0); - } + }, false, "unexpected Nix daemon error: ", true); } catch (Interrupted & e) { throw; @@ -925,7 +965,6 @@ void run(Strings args) if (arg == "--daemon") /* ignored for backwards compatibility */; } - chdir("/"); daemonLoop(); } |