code:
/* GNU Guix --- Functional package management for GNU
   Copyright (C) 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
   Copyright (C) 2006, 2010, 2012, 2014 Eelco Dolstra <e.dolstra@tudelft.nl>

   This file is part of GNU Guix.

   GNU Guix is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or (at
   your option) any later version.

   GNU Guix 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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.  */

#include <config.h>

#include <types.hh>
#include "shared.hh"
#include <globals.hh>
#include <util.hh>

#include <gcrypt.h>

#include <stdlib.h>
#include <argp.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#include <strings.h>
#include <exception>
#include <iostream>

#include <libintl.h>
#include <locale.h>

/* Variables used by `nix-daemon.cc'.  */
volatile ::sig_atomic_t blockInt;
char **argvSaved;

using namespace nix;

/* Entry point in `nix-daemon.cc'.  */
extern void run (const std::vector<int> &);

/* 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[] =
  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.");

#define GUIX_OPT_SYSTEM 1
#define GUIX_OPT_DISABLE_CHROOT 2
#define GUIX_OPT_BUILD_USERS_GROUP 3
#define GUIX_OPT_CACHE_FAILURES 4
#define GUIX_OPT_LOSE_LOGS 5
#define GUIX_OPT_DISABLE_LOG_COMPRESSION 6
#define GUIX_OPT_DISABLE_DEDUPLICATION 7
#define GUIX_OPT_IMPERSONATE_LINUX_26 8
#define GUIX_OPT_DEBUG 9
#define GUIX_OPT_CHROOT_DIR 10
#define GUIX_OPT_LISTEN 11
#define GUIX_OPT_NO_SUBSTITUTES 12
#define GUIX_OPT_SUBSTITUTE_URLS 13
#define GUIX_OPT_NO_BUILD_HOOK 14
#define GUIX_OPT_GC_KEEP_OUTPUTS 15
#define GUIX_OPT_GC_KEEP_DERIVATIONS 16
#define GUIX_OPT_BUILD_ROUNDS 17
#define GUIX_OPT_TIMEOUT 18
#define GUIX_OPT_MAX_SILENT_TIME 19
#define GUIX_OPT_LOG_COMPRESSION 20

static const struct argp_option options[] =
  {
    { "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") },
    { "timeout", GUIX_OPT_TIMEOUT, n_("SECONDS"), 0,
      n_("mark builds as failed after SECONDS of activity") },
    { "max-silent-time", GUIX_OPT_MAX_SILENT_TIME, n_("SECONDS"), 0,
      n_("mark builds as failed after SECONDS of silence") },
    { "disable-chroot", GUIX_OPT_DISABLE_CHROOT, 0, 0,
      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,
      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-offload", GUIX_OPT_NO_BUILD_HOOK, 0, 0,
      n_("do not attempt to offload builds") },
    { "no-build-hook", GUIX_OPT_NO_BUILD_HOOK, 0,
      OPTION_HIDDEN,				  // deprecated
      n_("do not attempt to offload builds") },
    { "cache-failures", GUIX_OPT_CACHE_FAILURES, 0, 0,
      n_("cache build failures") },
    { "rounds", GUIX_OPT_BUILD_ROUNDS, "N", 0,
      n_("build each derivation N times in a row") },
    { "lose-logs", GUIX_OPT_LOSE_LOGS, 0, 0,
      n_("do not keep build logs") },
    { "disable-log-compression", GUIX_OPT_DISABLE_LOG_COMPRESSION, 0,
      OPTION_HIDDEN,				  // deprecated
      n_("disable compression of the build logs") },
    { "log-compression", GUIX_OPT_LOG_COMPRESSION, "TYPE", 0,
      n_("use the specified compression type for 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,
      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,
#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,
      n_("tell whether the GC must keep outputs of live derivations") },
    { "gc-keep-derivations", GUIX_OPT_GC_KEEP_DERIVATIONS,
      "yes/no", OPTION_ARG_OPTIONAL,
      n_("tell whether the GC must keep derivations corresponding \
to live outputs") },

    { "listen", GUIX_OPT_LISTEN, n_("SOCKET"), 0,
      n_("listen for connections on SOCKET") },
    { "debug", GUIX_OPT_DEBUG, 0, 0,
      n_("produce debugging output") },
    { 0, 0, 0, 0, 0 }
  };

/* Default port for '--listen' on TCP/IP.  */
#define DEFAULT_GUIX_PORT "44146"

/* List of '--listen' options.  */
static std::list<std::string> listen_options;

/* Convert ARG to a Boolean value, or throw an error if it does not denote a
   Boolean.  */
static bool
string_to_bool (const char *arg, bool dflt = true)
{
  if (arg == NULL)
    return dflt;
  else if (strcasecmp (arg, "yes") == 0)
    return true;
  else if (strcasecmp (arg, "no") == 0)
    return false;
  else
    throw nix::Error (format ("'%1%': invalid Boolean value") % arg);
}

/* Parse a single option. */
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
  switch (key)
    {
    case GUIX_OPT_DISABLE_CHROOT:
      settings.useChroot = false;
      break;
    case GUIX_OPT_CHROOT_DIR:
      {
	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_LOG_COMPRESSION:
      if (strcmp (arg, "none") == 0)
	settings.logCompression = COMPRESSION_NONE;
      else if (strcmp (arg, "gzip") == 0)
	settings.logCompression = COMPRESSION_GZIP;
#if HAVE_BZLIB_H
      else if (strcmp (arg, "bzip2") == 0)
	settings.logCompression = COMPRESSION_BZIP2;
#endif
      else
	{
	  fprintf (stderr, _("error: %s: unknown compression type\n"), arg);
	  exit (EXIT_FAILURE);
	}
      break;
    case GUIX_OPT_DISABLE_LOG_COMPRESSION:
      settings.logCompression = COMPRESSION_NONE;
      break;
    case GUIX_OPT_BUILD_USERS_GROUP:
      settings.buildUsersGroup = arg;
      break;
    case GUIX_OPT_DISABLE_DEDUPLICATION:
      settings.autoOptimiseStore = false;
      break;
    case GUIX_OPT_CACHE_FAILURES:
      settings.cacheFailure = true;
      break;
    case GUIX_OPT_BUILD_ROUNDS:
      {
	char *end;
	unsigned long n = strtoul (arg, &end, 10);
	if (end != arg + strlen (arg))
	  {
	    fprintf (stderr, _("error: %s: invalid number of rounds\n"), arg);
	    exit (EXIT_FAILURE);
	  }
	settings.set ("build-repeat", std::to_string (std::max (0UL, n - 1)));
	break;
      }
    case GUIX_OPT_IMPERSONATE_LINUX_26:
      settings.impersonateLinux26 = true;
      break;
    case GUIX_OPT_LOSE_LOGS:
      settings.keepLog = false;
      break;
    case GUIX_OPT_LISTEN:
      listen_options.push_back (arg);
      break;
    case GUIX_OPT_SUBSTITUTE_URLS:
      settings.set ("substitute-urls", arg);
      break;
    case GUIX_OPT_NO_SUBSTITUTES:
      settings.set ("build-use-substitutes", "false");
      break;
    case GUIX_OPT_NO_BUILD_HOOK:
      settings.useBuildHook = false;
      break;
    case GUIX_OPT_DEBUG:
      verbosity = lvlDebug;
      break;
    case GUIX_OPT_GC_KEEP_OUTPUTS:
      settings.gcKeepOutputs = string_to_bool (arg);
      break;
    case GUIX_OPT_GC_KEEP_DERIVATIONS:
      settings.gcKeepDerivations = string_to_bool (arg);
      break;
    case 'c':
      settings.set ("build-cores", arg);
      break;
    case 'M':
      settings.set ("build-max-jobs", arg);
      break;
    case GUIX_OPT_TIMEOUT:
      settings.set ("build-timeout", arg);
      break;
    case GUIX_OPT_MAX_SILENT_TIME:
      settings.set ("build-max-silent-time", arg);
      break;
    case GUIX_OPT_SYSTEM:
      settings.thisSystem = arg;
      break;
    default:
      return (error_t) ARGP_ERR_UNKNOWN;
    }

  return (error_t) 0;
}

/* Argument parsing.  */
static const struct argp argp =
  {
    options, parse_opt,
    NULL, doc,
    NULL, NULL,					  // children and help_filter
    guix_textdomain
  };

static int
open_unix_domain_socket (const char *file)
{
  /* Create and bind to a Unix domain socket. */
  AutoCloseFD fdSocket = socket (PF_UNIX, SOCK_STREAM, 0);
  if (fdSocket == -1)
    throw SysError (_("cannot create Unix domain socket"));

  createDirs (dirOf (file));

  /* 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 (file).c_str ()) == -1)
    throw SysError (_("cannot change current directory"));
  Path fileRel = "./" + baseNameOf (file);

  struct sockaddr_un addr;
  addr.sun_family = AF_UNIX;
  if (fileRel.size () >= sizeof (addr.sun_path))
    throw Error (format (_("socket file name '%1%' is too long")) % fileRel);
  strcpy (addr.sun_path, fileRel.c_str ());

  unlink (file);

  /* 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%'")) % file);

  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%'")) % file);

  return fdSocket.borrow ();
}

/* Return a listening socket for ADDRESS, which has the given LENGTH.  */
static int
open_inet_socket (const struct sockaddr *address, socklen_t length)
{
  AutoCloseFD fd = socket (address->sa_family, SOCK_STREAM, 0);
  if (fd == -1)
    throw SysError (_("cannot create TCP socket"));

  int res = bind (fd, address, length);
  if (res == -1)
    throw SysError (_("cannot bind TCP socket"));

  if (listen (fd, 5) == -1)
    throw SysError (format (_("cannot listen on TCP socket")));

  return fd.borrow ();
}

/* Return a list of file descriptors of listening sockets.  */
static std::vector<int>
listening_sockets (const std::list<std::string> &options)
{
  std::vector<int> result;

  if (options.empty ())
    {
      /* Open the default Unix-domain socket.  */
      auto fd = open_unix_domain_socket (settings.nixDaemonSocketFile.c_str ());
      result.push_back (fd);
      return result;
    }

  /* Open the user-specified sockets.  */
  for (const std::string& option: options)
    {
      if (option[0] == '/')
	{
	  /* Assume OPTION is the file name of a Unix-domain socket.  */
	  settings.nixDaemonSocketFile = canonPath (option);
	  int fd =
	    open_unix_domain_socket (settings.nixDaemonSocketFile.c_str ());
	  result.push_back (fd);
	}
      else
	{
	  /* Assume OPTIONS has the form "HOST" or "HOST:PORT".  */
	  auto colon = option.find_last_of (":");
	  auto host = colon == std::string::npos
	    ? option : option.substr (0, colon);
	  auto port = colon == std::string::npos
	    ? DEFAULT_GUIX_PORT
	    : option.substr (colon + 1, option.size () - colon - 1);

	  struct addrinfo *res, hints;

	  memset (&hints, '\0', sizeof hints);
	  hints.ai_socktype = SOCK_STREAM;
	  hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG;

	  int err = getaddrinfo (host.c_str(), port.c_str (),
				 &hints, &res);

	  if (err != 0)
	    throw Error(format ("failed to look up '%1%': %2%")
			% option % gai_strerror (err));

	  printMsg (lvlDebug, format ("listening on '%1%', port '%2%'")
		    % host % port);

	  /* XXX: Pick the first result, RES.  */
	  result.push_back (open_inet_socket (res->ai_addr,
					      res->ai_addrlen));

	  freeaddrinfo (res);
	}
    }

  return result;
}

int
main (int argc, char *argv[])
{
  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"));
      exit (EXIT_FAILURE);
    }

  /* Tell Libgcrypt that initialization has completed, as per the Libgcrypt
     1.6.0 manual (although this does not appear to be strictly needed.)  */
  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);

  /* Set the umask so that the daemon does not end up creating group-writable
     files, which would lead to "suspicious ownership or permission" errors.
     See <http://lists.gnu.org/archive/html/bug-guix/2013-07/msg00033.html>.  */
  umask (S_IWGRP | S_IWOTH);

#ifndef HAVE_CHROOT
# error chroot is assumed to be available
#endif

  /* Always use chroots by default.  */
  settings.useChroot = true;

  /* Turn automatic deduplication on by default.  */
  settings.autoOptimiseStore = true;

  /* Default to using as many cores as possible and one job at a time.  */
  settings.buildCores = 0;
  settings.maxBuildJobs = 1;

  argvSaved = argv;

  try
    {
      settings.processEnvironment ();

      /* Enable substitutes by default.  */
      settings.set ("build-use-substitutes", "true");

      /* Use our substitute server by default.  */
      settings.set ("substitute-urls", GUIX_SUBSTITUTE_URLS);

#ifdef HAVE_DAEMON_OFFLOAD_HOOK
      /* Use 'guix offload' for distributed builds by default.  */
      settings.useBuildHook = true;
#else
      /* We are not installing any build hook, so disable it.  */
      settings.useBuildHook = false;
#endif

      argp_parse (&argp, argc, argv, 0, 0, 0);

      auto sockets = listening_sockets (listen_options);

      /* Effect all the changes made via 'settings.set'.  */
      settings.update ();
      printMsg(lvlDebug,
	       format ("build log compression: %1%") % settings.logCompression);

      if (geteuid () == 0 && settings.buildUsersGroup.empty ())
	fprintf (stderr, _("warning: daemon is running as root, so \
using `--build-users-group' is highly recommended\n"));

      if (settings.useChroot)
	{
	  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,
		format ("automatic deduplication set to %1%")
		% settings.autoOptimiseStore);

      printMsg (lvlDebug,
		format ("listening on `%1%'") % settings.nixDaemonSocketFile);

      run (sockets);
    }
  catch (std::exception &e)
    {
      fprintf (stderr, _("error: %s\n"), e.what ());
      return EXIT_FAILURE;
    }

  return EXIT_SUCCESS;				  /* never reached */
}


code:
#include "config.h"
#include "shared.hh"
#include "local-store.hh"
#include "util.hh"
#include "serialise.hh"
#include "worker-protocol.hh"
#include "archive.hh"
#include "affinity.hh"
#include "globals.hh"
#include "builtins.hh"

#include <algorithm>

#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#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>
#include <grp.h>

using namespace nix;

/* On platforms that have O_ASYNC, we can detect when a client
   disconnects and immediately kill any ongoing builds.  On platforms
   that lack it, we only notice the disconnection the next time we try
   to write to the client.  So if you have a builder that never
   generates output on stdout/stderr, the daemon will never notice
   that the client has disconnected until the builder terminates.

   GNU/Hurd does have O_ASYNC, but its Unix-domain socket translator
   (pflocal) does not implement F_SETOWN.  See
   <http://lists.gnu.org/archive/html/bug-guix/2013-07/msg00021.html> for
   details.*/
#if defined O_ASYNC && !defined __GNU__
#define HAVE_HUP_NOTIFICATION
#ifndef SIGPOLL
#define SIGPOLL SIGIO
#endif
#endif

static FdSource from(STDIN_FILENO);
static FdSink to(STDOUT_FILENO);

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.,
   when canSendStderr), send the message to the client over the
   socket. */
static void tunnelStderr(const unsigned char * buf, size_t count)
{
    if (canSendStderr) {
        try {
            writeInt(STDERR_NEXT, to);
            writeString(buf, count, to);
            to.flush();
        } catch (...) {
            /* Write failed; that means that the other side is
               gone. */
            canSendStderr = false;
            throw;
        }
    } else
        writeFull(STDERR_FILENO, buf, count);
}

/* Return true if the remote side has closed its end of the
   connection, false otherwise.  Should not be called on any socket on
   which we expect input! */
static bool isFarSideClosed(int socket)
{
    struct timeval timeout;
    timeout.tv_sec = timeout.tv_usec = 0;

    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(socket, &fds);

    while (select(socket + 1, &fds, 0, 0, &timeout) == -1)
        if (errno != EINTR) throw SysError("select()");

    if (!FD_ISSET(socket, &fds)) return false;

    /* Destructive read to determine whether the select() marked the
       socket as readable because there is actual input or because
       we've reached EOF (i.e., a read of size 0 is available). */
    char c;
    int rd;
    if ((rd = read(socket, &c, 1)) > 0)
        throw Error("EOF expected (protocol error?)");
    else if (rd == -1 && errno != ECONNRESET)
        throw SysError("expected connection reset or EOF");

    return true;
}

/* A SIGPOLL signal is received when data is available on the client
   communication socket, or when the client has closed its side of the
   socket.  This handler is enabled at precisely those moments in the
   protocol when we're doing work and the client is supposed to be
   quiet.  Thus, if we get a SIGPOLL signal, it means that the client
   has quit.  So we should quit as well.

   Too bad most operating systems don't support the POLL_HUP value for
   si_code in siginfo_t.  That would make most of the SIGPOLL
   complexity unnecessary, i.e., we could just enable SIGPOLL all the
   time and wouldn't have to worry about races. */
static void sigPollHandler(int sigNo)
{
    using namespace std;
    try {
        /* Check that the far side actually closed.  We're still
           getting spurious signals every once in a while.  I.e.,
           there is no input available, but we get a signal with
           POLL_IN set.  Maybe it's delayed or something. */
        if (isFarSideClosed(from.fd)) {
            if (!blockInt) {
                _isInterrupted = 1;
                blockInt = 1;
                canSendStderr = false;
                const char * s = "SIGPOLL\n";
                write(STDERR_FILENO, s, strlen(s));
            }
        } else {
            const char * s = "spurious SIGPOLL\n";
            write(STDERR_FILENO, s, strlen(s));
        }
    }
    catch (Error & e) {
        /* Shouldn't happen. */
        string s = "impossible: " + e.msg() + '\n';
        write(STDERR_FILENO, s.data(), s.size());
        throw;
    }
}

static void setSigPollAction(bool enable)
{
#ifdef HAVE_HUP_NOTIFICATION
    struct sigaction act, oact;
    act.sa_handler = enable ? sigPollHandler : SIG_IGN;
    sigfillset(&act.sa_mask);
    act.sa_flags = 0;
    if (sigaction(SIGPOLL, &act, &oact))
        throw SysError("setting handler for SIGPOLL");
#endif
}

/* startWork() means that we're starting an operation for which we
   want to send out stderr to the client. */
static void startWork()
{
    canSendStderr = true;

    /* Handle client death asynchronously. */
    setSigPollAction(true);

    /* Of course, there is a race condition here: the socket could
       have closed between when we last read from / wrote to it, and
       between the time we set the handler for SIGPOLL.  In that case
       we won't get the signal.  So do a non-blocking select() to find
       out if any input is available on the socket.  If there is, it
       has to be the 0-byte read that indicates that the socket has
       closed. */
    if (isFarSideClosed(from.fd)) {
        _isInterrupted = 1;
        checkInterrupt();
    }
}

/* stopWork() means that we're done; stop sending stderr to the
   client. */
static void stopWork(bool success = true, const string & msg = "", unsigned int status = 0)
{
    /* Stop handling async client death; we're going to a state where
       we're either sending or receiving from the client, so we'll be
       notified of client death anyway. */
    setSigPollAction(false);

    canSendStderr = false;

    if (success)
        writeInt(STDERR_LAST, to);
    else {
        writeInt(STDERR_ERROR, to);
        writeString(msg, to);
        if (status != 0) writeInt(status, to);
    }
}

struct TunnelSink : BufferedSink
{
    Sink & to;
    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);
    }
};

struct TunnelSource : BufferedSource
{
    Source & from;
    TunnelSource(Source & from) : from(from) { }
    size_t readUnbuffered(unsigned char * data, size_t len)
    {
        /* Careful: we're going to receive data from the client now,
           so we have to disable the SIGPOLL handler. */
        setSigPollAction(false);
        canSendStderr = false;

        writeInt(STDERR_READ, to);
        writeInt(len, to);
        to.flush();
        size_t n = readString(data, len, from);

        startWork();
        if (n == 0) throw EndOfFile("unexpected end-of-file");
        return n;
    }
};

/* If the NAR archive contains a single file at top-level, then save
   the contents of the file to `s'.  Otherwise barf. */
struct RetrieveRegularNARSink : ParseSink
{
    bool regular;
    string s;

    RetrieveRegularNARSink() : regular(true) { }

    void createDirectory(const Path & path)
    {
        regular = false;
    }

    void receiveContents(unsigned char * data, unsigned int len)
    {
        s.append((const char *) data, len);
    }

    void createSymlink(const Path & path, const string & target)
    {
        regular = false;
    }
};

/* Adapter class of a Source that saves all data read to `s'. */
struct SavingSourceAdapter : Source
{
    Source & orig;
    string s;
    SavingSourceAdapter(Source & orig) : orig(orig) { }
    size_t read(unsigned char * data, size_t len)
    {
        size_t n = orig.read(data, len);
        s.append((const char *) data, n);
        return n;
    }
};

static void performOp(bool trusted, unsigned int clientVersion,
    Source & from, Sink & to, unsigned int op)
{
    switch (op) {

    case wopIsValidPath: {
        /* 'readStorePath' could raise an error leading to the connection
           being closed.  To be able to recover from an invalid path error,
           call 'startWork' early, and do 'assertStorePath' afterwards so
           that the 'Error' exception handler doesn't close the
           connection.  */
        Path path = readString(from);
        startWork();
        assertStorePath(path);
        bool result = store->isValidPath(path);
        stopWork();
        writeInt(result, to);
        break;
    }

    case wopQueryValidPaths: {
        PathSet paths = readStorePaths<PathSet>(from);
        startWork();
        PathSet res = store->queryValidPaths(paths);
        stopWork();
        writeStrings(res, to);
        break;
    }

    case wopHasSubstitutes: {
        Path path = readStorePath(from);
        startWork();
        PathSet res = store->querySubstitutablePaths(singleton<PathSet>(path));
        stopWork();
        writeInt(res.find(path) != res.end(), to);
        break;
    }

    case wopQuerySubstitutablePaths: {
        PathSet paths = readStorePaths<PathSet>(from);
        startWork();
        PathSet res = store->querySubstitutablePaths(paths);
        stopWork();
        writeStrings(res, to);
        break;
    }

    case wopQueryPathHash: {
        Path path = readStorePath(from);
        startWork();
        Hash hash = store->queryPathHash(path);
        stopWork();
        writeString(printHash(hash), to);
        break;
    }

    case wopQueryReferences:
    case wopQueryReferrers:
    case wopQueryValidDerivers:
    case wopQueryDerivationOutputs: {
        Path path = readStorePath(from);
        startWork();
        PathSet paths;
        if (op == wopQueryReferences)
            store->queryReferences(path, paths);
        else if (op == wopQueryReferrers)
            store->queryReferrers(path, paths);
        else if (op == wopQueryValidDerivers)
            paths = store->queryValidDerivers(path);
        else paths = store->queryDerivationOutputs(path);
        stopWork();
        writeStrings(paths, to);
        break;
    }

    case wopQueryDerivationOutputNames: {
        Path path = readStorePath(from);
        startWork();
        StringSet names;
        names = store->queryDerivationOutputNames(path);
        stopWork();
        writeStrings(names, to);
        break;
    }

    case wopQueryDeriver: {
        Path path = readStorePath(from);
        startWork();
        Path deriver = store->queryDeriver(path);
        stopWork();
        writeString(deriver, to);
        break;
    }

    case wopQueryPathFromHashPart: {
        string hashPart = readString(from);
        startWork();
        Path path = store->queryPathFromHashPart(hashPart);
        stopWork();
        writeString(path, to);
        break;
    }

    case wopAddToStore: {
        string baseName = readString(from);
        bool fixed = readInt(from) == 1; /* obsolete */
        bool recursive = readInt(from) == 1;
        string s = readString(from);
        /* Compatibility hack. */
        if (!fixed) {
            s = "sha256";
            recursive = true;
        }
        HashType hashAlgo = parseHashType(s);

        SavingSourceAdapter savedNAR(from);
        RetrieveRegularNARSink savedRegular;

        if (recursive) {
            /* Get the entire NAR dump from the client and save it to
               a string so that we can pass it to
               addToStoreFromDump(). */
            ParseSink sink; /* null sink; just parse the NAR */
            parseDump(sink, savedNAR);
        } else
            parseDump(savedRegular, from);

        startWork();
        if (!savedRegular.regular) throw Error("regular file expected");
        Path path = dynamic_cast<LocalStore *>(store.get())
            ->addToStoreFromDump(recursive ? savedNAR.s : savedRegular.s, baseName, recursive, hashAlgo);
        stopWork();

        writeString(path, to);
        break;
    }

    case wopAddTextToStore: {
        string suffix = readString(from);
        string s = readString(from);
        PathSet refs = readStorePaths<PathSet>(from);
        startWork();
        Path path = store->addTextToStore(suffix, s, refs);
        stopWork();
        writeString(path, to);
        break;
    }

    case wopExportPath: {
        Path path = readStorePath(from);
        bool sign = readInt(from) == 1;
        startWork();
        TunnelSink sink(to);
	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;
    }

    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);
        break;
    }

    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 a privileged operation");
        }
        startWork();
        store->buildPaths(drvs, mode);
        stopWork();
        writeInt(1, to);
        break;
    }

    case wopEnsurePath: {
        Path path = readStorePath(from);
        startWork();
        store->ensurePath(path);
        stopWork();
        writeInt(1, to);
        break;
    }

    case wopAddTempRoot: {
        Path path = readStorePath(from);
        startWork();
        store->addTempRoot(path);
        stopWork();
        writeInt(1, to);
        break;
    }

    case wopAddIndirectRoot: {
        Path path = absPath(readString(from));
        startWork();
        store->addIndirectRoot(path);
        stopWork();
        writeInt(1, to);
        break;
    }

    case wopSyncWithGC: {
        startWork();
        store->syncWithGC();
        stopWork();
        writeInt(1, to);
        break;
    }

    case wopFindRoots: {
        startWork();
        Roots roots = store->findRoots();
        stopWork();
        writeInt(roots.size(), to);
        for (Roots::iterator i = roots.begin(); i != roots.end(); ++i) {
            writeString(i->first, to);
            writeString(i->second, 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);
        options.ignoreLiveness = readInt(from);
        options.maxFreed = readLongLong(from);
        readInt(from); // obsolete field
        if (GET_PROTOCOL_MINOR(clientVersion) >= 5) {
            /* removed options */
            readInt(from);
            readInt(from);
        }

        GCResults results;

        startWork();
        if (options.ignoreLiveness)
            throw Error("you are not allowed to ignore liveness");
        store->collectGarbage(options, results);
        stopWork();

        writeStrings(results.paths, to);
        writeLongLong(results.bytesFreed, to);
        writeLongLong(0, to); // obsolete

        break;
    }

    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);

        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) {
#ifdef HAVE_DAEMON_OFFLOAD_HOOK
            settings.useBuildHook = readInt(from) != 0;
#else
	    readInt(from);			  // ignore the user's setting
#endif
	}

        if (GET_PROTOCOL_MINOR(clientVersion) >= 4) {
            settings.buildVerbosity = (Verbosity) readInt(from);
            logType = (LogType) readInt(from);
            settings.printBuildTrace = readInt(from) != 0;
        }
        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) {
	    if (settings.useSubstitutes)
		settings.set("build-use-substitutes", readInt(from) ? "true" : "false");
	    else
		readInt(from);			// substitutes remain disabled
	}
        if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
            unsigned int n = readInt(from);
            for (unsigned int i = 0; i < n; i++) {
                string name = readString(from);
                string value = readString(from);
                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 if (name == "user-name"
                         && settings.clientUid == (uid_t) -1) {
                    /* Create the user profile.  This is necessary if
                       clientUid = -1, for instance because the client
                       connected over TCP.  */
                    struct passwd *pw = getpwnam(value.c_str());
                    if (pw != NULL)
                        store->createUser(value, pw->pw_uid);
                    else
                        printMsg(lvlInfo, format("user name %1% not found") % value);
		}
                else
                    settings.set(trusted ? name : "untrusted-" + name, value);
            }
        }
        settings.update();
        startWork();
        stopWork();
        break;
    }

    case wopQuerySubstitutablePathInfo: {
        Path path = absPath(readString(from));
        startWork();
        SubstitutablePathInfos infos;
        store->querySubstitutablePathInfos(singleton<PathSet>(path), infos);
        stopWork();
        SubstitutablePathInfos::iterator i = infos.find(path);
        if (i == infos.end())
            writeInt(0, to);
        else {
            writeInt(1, to);
            writeString(i->second.deriver, to);
            writeStrings(i->second.references, to);
            writeLongLong(i->second.downloadSize, to);
            if (GET_PROTOCOL_MINOR(clientVersion) >= 7)
                writeLongLong(i->second.narSize, to);
        }
        break;
    }

    case wopQuerySubstitutablePathInfos: {
        PathSet paths = readStorePaths<PathSet>(from);
        startWork();
        SubstitutablePathInfos infos;
        store->querySubstitutablePathInfos(paths, infos);
        stopWork();
        writeInt(infos.size(), to);
        foreach (SubstitutablePathInfos::iterator, i, infos) {
            writeString(i->first, to);
            writeString(i->second.deriver, to);
            writeStrings(i->second.references, to);
            writeLongLong(i->second.downloadSize, to);
            writeLongLong(i->second.narSize, to);
        }
        break;
    }

    case wopQueryAllValidPaths: {
        startWork();
        PathSet paths = store->queryAllValidPaths();
        stopWork();
        writeStrings(paths, to);
        break;
    }

    case wopQueryFailedPaths: {
        startWork();
        PathSet paths = store->queryFailedPaths();
        stopWork();
        writeStrings(paths, to);
        break;
    }

    case wopClearFailedPaths: {
        PathSet paths = readStrings<PathSet>(from);
        startWork();
        store->clearFailedPaths(paths);
        stopWork();
        writeInt(1, to);
        break;
    }

    case wopQueryPathInfo: {
        Path path = readStorePath(from);
        startWork();
        ValidPathInfo info = store->queryPathInfo(path);
        stopWork();
        writeString(info.deriver, to);
        writeString(printHash(info.hash), to);
        writeStrings(info.references, to);
        writeInt(info.registrationTime, to);
        writeLongLong(info.narSize, to);
        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;
    }

    case wopBuiltinBuilders: {
	startWork();
	auto names = builtinBuilderNames();
	stopWork();
	writeStrings(names, to);
	break;
    }

    default:
        throw Error(format("invalid operation %1%") % op);
    }
}

static void processConnection(bool trusted, uid_t userId)
{
    canSendStderr = false;
    _writeToStderr = tunnelStderr;

#ifdef HAVE_HUP_NOTIFICATION
    /* Allow us to receive SIGPOLL for events on the client socket. */
    setSigPollAction(false);
    if (fcntl(from.fd, F_SETOWN, getpid()) == -1)
        throw SysError("F_SETOWN");
    if (fcntl(from.fd, F_SETFL, fcntl(from.fd, F_GETFL, 0) | O_ASYNC) == -1)
        throw SysError("F_SETFL");
#endif

    /* Exchange the greeting. */
    unsigned int magic = readInt(from);
    if (magic != WORKER_MAGIC_1) throw Error("protocol mismatch");
    writeInt(WORKER_MAGIC_2, to);
    writeInt(PROTOCOL_VERSION, to);
    to.flush();
    unsigned int clientVersion = readInt(from);

    if (GET_PROTOCOL_MINOR(clientVersion) >= 14 && readInt(from))
        setAffinityTo(readInt(from));

    bool reserveSpace = true;
    if (GET_PROTOCOL_MINOR(clientVersion) >= 11)
        reserveSpace = readInt(from) != 0;

    /* Send startup error messages to the client. */
    startWork();

    try {

        /* If we can't accept clientVersion, then throw an error
           *here* (not above). */

#if 0
        /* Prevent users from doing something very dangerous. */
        if (geteuid() == 0 &&
            querySetting("build-users-group", "") == "")
            throw Error("if you run `nix-daemon' as root, then you MUST set `build-users-group'!");
#endif

        /* Open the store. */
        store = std::shared_ptr<StoreAPI>(new LocalStore(reserveSpace));

	if (userId != (uid_t) -1) {
            /* Create the user profile.  */
            struct passwd *pw = getpwuid(userId);
            if (pw != NULL && pw->pw_name != NULL)
                store->createUser(pw->pw_name, userId);
            else
                printMsg(lvlInfo, format("user with UID %1% not found") % userId);
	}

        stopWork();
        to.flush();

    } catch (Error & e) {
        stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
        to.flush();
        return;
    }

    /* Process client requests. */
    unsigned int opCount = 0;

    while (true) {
        WorkerOp op;
        try {
            op = (WorkerOp) readInt(from);
        } catch (EndOfFile & e) {
            break;
        }

        opCount++;

        try {
            performOp(trusted, clientVersion, from, to, op);
        } catch (Error & e) {
            /* If we're not in a state where we can send replies, then
               something went wrong processing the input of the
               client.  This can happen especially if I/O errors occur
               during addTextToStore() / importPath().  If that
               happens, just send the error message and exit. */
            bool errorAllowed = canSendStderr;
            stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? e.status : 0);
            if (!errorAllowed) throw;
        } catch (std::bad_alloc & e) {
            stopWork(false, "build daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0);
            throw;
        }

        to.flush();

        assert(!canSendStderr);
    };

    canSendStderr = false;
    _isInterrupted = false;
    printMsg(lvlDebug, format("%1% operations") % opCount);
}

static void sigChldHandler(int sigNo)
{
    /* Reap all dead children. */
    while (waitpid(-1, 0, WNOHANG) > 0) ;
}

static void setSigChldAction(bool autoReap)
{
    struct sigaction act, oact;
    act.sa_handler = autoReap ? sigChldHandler : SIG_DFL;
    sigfillset(&act.sa_mask);
    act.sa_flags = 0;
    if (sigaction(SIGCHLD, &act, &oact))
        throw SysError("setting SIGCHLD handler");
}

/* Accept a connection on FDSOCKET and fork a server process to process the
   new connection.  */
static void acceptConnection(int fdSocket)
{
    uid_t clientUid = (uid_t) -1;
    gid_t clientGid = (gid_t) -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_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
	}

	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)
	    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;
	    clientUid = cred.uid;
	    clientGid = cred.gid;
	    trusted = clientUid == 0;

            struct passwd * pw = getpwuid(cred.uid);
            string user = pw ? pw->pw_name : std::to_string(cred.uid);

	    printMsg(lvlInfo,
		     format((string) "accepted connection from pid %1%, user %2%")
		     % clientPid % user);
#endif
	} 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)
                    throw SysError(format("creating a new session"));

                /* Restore normal handling of SIGCHLD. */
                setSigChldAction(false);

                /* For debugging, stuff the pid into argv[1]. */
                if (clientPid != -1 && argvSaved[1]) {
                    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;
                processConnection(trusted, clientUid);

                exit(0);
            }, false, "unexpected build daemon error: ", true);

    } catch (Interrupted & e) {
	throw;
    } catch (Error & e) {
	printMsg(lvlError, format("error processing connection: %1%") % e.msg());
    }
}

static void daemonLoop(const std::vector<int>& sockets)
{
    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);
    }

    /* 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);
}

void printHelp()
{
    showManPage("nix-daemon");
}

string programId = "nix-daemon";

license: mit


code:
/* GNU Guix --- Functional package management for GNU
   Copyright (C) 2012  Ludovic Courtès <ludo@gnu.org>

   This file is part of GNU Guix.

   GNU Guix is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or (at
   your option) any later version.

   GNU Guix 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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.  */

/* Replacement for Nix's libmain/shared.hh.  */

#pragma once

#include <string>

#include <stdlib.h>
#include <signal.h>

static inline void
showManPage (const char *name)
{
  /* This idea is evil.  Abort.  */
  abort ();
}

extern volatile ::sig_atomic_t blockInt;

extern char **argvSaved;


code:
# GNU Guix --- Functional package management for GNU
# Copyright © 2018 Julien Lepiller <julien@lepiller.eu>
#
# This file is part of GNU Guix.
#
# GNU Guix is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at
# your option) any later version.
#
# GNU Guix 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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

DOC_PO_FILES=					\
  %D%/guix-manual.es.po				\
  %D%/guix-manual.de.po				\
  %D%/guix-manual.fr.po				\
  %D%/guix-manual.ru.po				\
  %D%/guix-manual.zh_CN.po

DOC_COOKBOOK_PO_FILES=					\
  %D%/guix-cookbook.de.po

EXTRA_DIST = \
  %D%/guix-manual.pot \
  %D%/guix-cookbook.pot \
  $(DOC_PO_FILES) \
  $(DOC_COOKBOOK_PO_FILES)

POT_OPTIONS = --package-name "guix" --package-version "$(VERSION)" \
	          --copyright-holder "Ludovic Courtès" \
			  --msgid-bugs-address "ludo@gnu.org"

doc-po-update-%:
	@lang=`echo "$@" | sed -e's/^doc-po-update-//'` ; \
	output="$(srcdir)/po/doc/guix-manual.$$lang.po" ; \
	input="$(srcdir)/po/doc/guix-manual.pot" ; \
	if test -f "$$output"; then \
	  test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
	  echo "$${cdcmd}$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $$output $$input"; \
	  cd $(srcdir) \
	    && { case `$(MSGMERGE_UPDATE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
	        '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \
	          $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) "$$output" "$$input";; \
	        *) \
	          $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} "$$output" "$$input";; \
	      esac; \
	    }; \
	  touch "$$output"; \
	else \
	     echo "File $$output does not exist.  If you are a translator, you can create it with 'msginit'." 1>&2; \
	     exit 1; \
	fi

doc-po-update-cookbook-%:
	@lang=`echo "$@" | sed -e's/^doc-po-update-cookbook-//'` ; \
	output="$(srcdir)/po/doc/guix-cookbook.$$lang.po" ; \
	input="$(srcdir)/po/doc/guix-cookbook.pot" ; \
	if test -f "$$output"; then \
	  test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
	  echo "$${cdcmd}$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $$output $$input"; \
	  cd $(srcdir) \
	    && { case `$(MSGMERGE_UPDATE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
	        '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \
	          $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) "$$output" "$$input";; \
	        *) \
	          $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} "$$output" "$$input";; \
	      esac; \
	    }; \
	  touch "$$output"; \
	else \
	     echo "File $$output does not exist.  If you are a translator, you can create it with 'msginit'." 1>&2; \
	     exit 1; \
	fi

$(srcdir)/po/doc/%.pot-update: doc/%.texi
	$(AM_V_PO4A)$(PO4A_UPDATEPO) -M UTF-8 -f texinfo -m "$<" \
		-p "$$(echo $@ | sed 's|-update||')" $(POT_OPTIONS)
	@touch "$$(echo $@ | sed 's|-update||')"

TMP_POT_FILES = contributing.pot guix.pot

doc-pot-update:
	for f in $(TMP_POT_FILES); do \
		$(MAKE) $(srcdir)/po/doc/guix.pot-update; \
		$(MAKE) $(srcdir)/po/doc/contributing.pot-update; \
	done
	$(MAKE) $(srcdir)/po/doc/guix-cookbook.pot-update;
	msgcat $(addprefix $(srcdir)/po/doc/, $(TMP_POT_FILES)) > $(srcdir)/po/doc/guix-manual.pot
	rm -f $(addprefix $(srcdir)/po/doc/, $(TMP_POT_FILES))

doc-po-update: doc-pot-update
	for f in $(DOC_PO_FILES); do						\
	  lang="`echo "$$f" | $(SED) -es'|.*/guix-manual\.\(.*\)\.po$$|\1|g'`";	\
	  $(MAKE) "doc-po-update-$$lang";					\
	done
	for f in $(DOC_COOKBOOK_PO_FILES); do						\
	  lang="`echo "$$f" | $(SED) -es'|.*/guix-cookbook\.\(.*\)\.po$$|\1|g'`";	\
	  $(MAKE) "doc-po-update-cookbook-$$lang";						\
	done

.PHONY: doc-po-update doc-pot-update


code:
# Set of available languages.

cs
da
de
en@boldquot
en@quot
eo
es
fr
hu
pl
pt_BR
sr
sv
ta
vi
zh_CN

path: po/guix/LINGUAS


code:
# Makefile variables for PO directory in any package using GNU gettext.

DOMAIN = guix

subdir = po/guix
top_builddir = ../..

# These options get passed to xgettext.  We want to catch standard
# gettext uses, and SRFI-35 error condition messages.  In C++ code
# we use 'n_' instead of the more usual 'N_' for no-ops.
XGETTEXT_OPTIONS =				\
  --from-code=UTF-8				\
  --keyword=G_ --keyword=N_:1,2			\
  --keyword=message				\
  --keyword=description				\
  --keyword=n_

COPYRIGHT_HOLDER = Ludovic Courtès

MSGID_BUGS_ADDRESS = ludo@gnu.org

EXTRA_LOCALE_CATEGORIES =

# Disable PO update upon "make dist", which sometimes fails when doing
# "make distcheck -j4".
# See <https://lists.gnu.org/archive/html/bug-gettext/2014-08/msg00005.html>.
DIST_DEPENDS_ON_UPDATE_PO = no

path: po/guix/Makevars


code:
# List of source files which contain translatable strings.
# This should be source files of the various tools, and not package modules.
gnu.scm
gnu/packages.scm
gnu/services.scm
gnu/system.scm
gnu/services/shepherd.scm
gnu/system/mapped-devices.scm
gnu/system/shadow.scm
guix/import/opam.scm
gnu/installer.scm
gnu/installer/connman.scm
gnu/installer/hostname.scm
gnu/installer/keymap.scm
gnu/installer/locale.scm
gnu/installer/newt.scm
gnu/installer/newt/ethernet.scm
gnu/installer/newt/final.scm
gnu/installer/newt/parameters.scm
gnu/installer/newt/hostname.scm
gnu/installer/newt/keymap.scm
gnu/installer/newt/locale.scm
gnu/installer/newt/menu.scm
gnu/installer/newt/network.scm
gnu/installer/newt/page.scm
gnu/installer/newt/partition.scm
gnu/installer/newt/services.scm
gnu/installer/newt/timezone.scm
gnu/installer/newt/user.scm
gnu/installer/newt/utils.scm
gnu/installer/newt/welcome.scm
gnu/installer/newt/wifi.scm
gnu/installer/parted.scm
gnu/installer/proxy.scm
gnu/installer/record.scm
gnu/installer/services.scm
gnu/installer/steps.scm
gnu/installer/timezone.scm
gnu/installer/user.scm
gnu/installer/utils.scm
gnu/machine/ssh.scm
gnu/packages/bootstrap.scm
guix/build/utils.scm
guix/scripts.scm
guix/scripts/build.scm
guix/discovery.scm
guix/lint.scm
guix/scripts/download.scm
guix/scripts/package.scm
guix/scripts/install.scm
guix/scripts/remove.scm
guix/scripts/upgrade.scm
guix/scripts/search.scm
guix/scripts/show.scm
guix/scripts/gc.scm
guix/scripts/git.scm
guix/scripts/git/authenticate.scm
guix/scripts/hash.scm
guix/scripts/import.scm
guix/scripts/import/cran.scm
guix/scripts/import/elpa.scm
guix/scripts/pull.scm
guix/scripts/substitute.scm
guix/scripts/authenticate.scm
guix/scripts/system.scm
guix/scripts/system/search.scm
guix/scripts/lint.scm
guix/scripts/publish.scm
guix/scripts/edit.scm
guix/scripts/size.scm
guix/scripts/graph.scm
guix/scripts/challenge.scm
guix/scripts/copy.scm
guix/scripts/pack.scm
guix/scripts/weather.scm
guix/scripts/describe.scm
guix/scripts/processes.scm
guix/scripts/deploy.scm
guix/gnu-maintenance.scm
guix/scripts/container.scm
guix/scripts/container/exec.scm
guix/upstream.scm
guix/ui.scm
guix/status.scm
guix/http-client.scm
guix/nar.scm
guix/channels.scm
guix/profiles.scm
guix/git.scm
guix/deprecation.scm
guix/derivations.scm
nix/nix-daemon/guix-daemon.cc


code:
# German translation of guix.
# Copyright (C) 2014 Free Software Foundation, Inc.
# This file is distributed under the same license as the guix package.
#
# Pavel Fric <pavelfric@seznam.cz>, 2014.
msgid ""
msgstr ""
"Project-Id-Version: guix 0.7-pre1\n"
"Report-Msgid-Bugs-To: ludo@gnu.org\n"
"POT-Creation-Date: 2014-07-13 17:06+0200\n"
"PO-Revision-Date: 2014-07-26 18:06+0200\n"
"Last-Translator: Pavel Fric <pavelfric@seznam.cz>\n"
"Language-Team: Czech <translation-team-cs@lists.sourceforge.net>\n"
"Language: cs\n"
"X-Bugs: Report translation errors to the Language-Team address.\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 1.5\n"

#: gnu/packages.scm:95
#, scheme-format
msgid "cannot access `~a': ~a~%"
msgstr "Nelze přistupovat k »~a«: ~a~%"

#: guix/scripts/build.scm:54 guix/scripts/package.scm:349
#, scheme-format
msgid "ambiguous package specification `~a'~%"
msgstr ""

#: guix/scripts/build.scm:55 guix/scripts/package.scm:351
#, scheme-format
msgid "choosing ~a from ~a~%"
msgstr ""

#: guix/scripts/build.scm:61
#, scheme-format
msgid "~A: package not found for version ~a~%"
msgstr "~A: Balíček nenalezen pro verzi ~a~%"

#: guix/scripts/build.scm:63
#, scheme-format
msgid "~A: unknown package~%"
msgstr "~A: Neznámý balíček~%"

#: guix/scripts/build.scm:86
#, scheme-format
msgid "failed to create GC root `~a': ~a~%"
msgstr "Nepodařilo se vytvořit GC root `~a': ~a~%"

#: guix/scripts/build.scm:123
msgid ""
"\n"
"  -K, --keep-failed      keep build tree of failed builds"
msgstr ""
"\n"
"  -K, --keep-failed      Zachovat sestavovací strom nezdařených sestavení"

#: guix/scripts/build.scm:125
msgid ""
"\n"
"  -n, --dry-run          do not build the derivations"
msgstr ""

#: guix/scripts/build.scm:127
msgid ""
"\n"
"      --fallback         fall back to building when the substituter fails"
msgstr ""

#: guix/scripts/build.scm:129
msgid ""
"\n"
"      --no-substitutes   build instead of resorting to pre-built substitutes"
msgstr ""

#: guix/scripts/build.scm:131
msgid ""
"\n"
"      --no-build-hook    do not attempt to offload builds via the build hook"
msgstr ""

#: guix/scripts/build.scm:133
msgid ""
"\n"
"      --max-silent-time=SECONDS\n"
"                         mark the build as failed after SECONDS of silence"
msgstr ""

#: guix/scripts/build.scm:136
msgid ""
"\n"
"      --timeout=SECONDS  mark the build as failed after SECONDS of activity"
msgstr ""

#: guix/scripts/build.scm:138
msgid ""
"\n"
"      --verbosity=LEVEL  use the given verbosity LEVEL"
msgstr ""

#: guix/scripts/build.scm:140
msgid ""
"\n"
"  -c, --cores=N          allow the use of up to N CPU cores for the build"
msgstr ""

#: guix/scripts/build.scm:206
#, scheme-format
msgid "~a: not a number~%"
msgstr "~a: Není číslo~%"

#: guix/scripts/build.scm:223
msgid ""
"Usage: guix build [OPTION]... PACKAGE-OR-DERIVATION...\n"
"Build the given PACKAGE-OR-DERIVATION and return their output paths.\n"
msgstr ""

#: guix/scripts/build.scm:225
msgid ""
"\n"
"  -e, --expression=EXPR  build the package or derivation EXPR evaluates to"
msgstr ""

#: guix/scripts/build.scm:227
msgid ""
"\n"
"  -S, --source           build the packages' source derivations"
msgstr ""

#: guix/scripts/build.scm:229
msgid ""
"\n"
"  -s, --system=SYSTEM    attempt to build for SYSTEM--e.g., \"i686-linux\""
msgstr ""

#: guix/scripts/build.scm:231
msgid ""
"\n"
"      --target=TRIPLET   cross-build for TRIPLET--e.g., \"armel-linux-gnu\""
msgstr ""

#: guix/scripts/build.scm:233
msgid ""
"\n"
"      --with-source=SOURCE\n"
"                         use SOURCE when building the corresponding package"
msgstr ""

#: guix/scripts/build.scm:236
msgid ""
"\n"
"  -d, --derivations      return the derivation paths of the given packages"
msgstr ""

#: guix/scripts/build.scm:238
msgid ""
"\n"
"  -r, --root=FILE        make FILE a symlink to the result, and register it\n"
"                         as a garbage collector root"
msgstr ""

#: guix/scripts/build.scm:241
msgid ""
"\n"
"      --log-file         return the log file names for the given derivations"
msgstr ""

#: guix/scripts/build.scm:246 guix/scripts/download.scm:53
#: guix/scripts/package.scm:523 guix/scripts/gc.scm:58
#: guix/scripts/hash.scm:55 guix/scripts/pull.scm:82
#: guix/scripts/substitute-binary.scm:564 guix/scripts/system.scm:334
msgid ""
"\n"
"  -h, --help             display this help and exit"
msgstr ""
"\n"
"  -h, --help             Ukázat tuto nápovědu a ukončit"

#: guix/scripts/build.scm:248 guix/scripts/download.scm:55
#: guix/scripts/package.scm:525 guix/scripts/gc.scm:60
#: guix/scripts/hash.scm:57 guix/scripts/pull.scm:84
#: guix/scripts/substitute-binary.scm:566 guix/scripts/system.scm:336
msgid ""
"\n"
"  -V, --version          display version information and exit"
msgstr ""
"\n"
"  -V, --version         Ukázat informace o verzi a ukončit"

#: guix/scripts/build.scm:368
#, scheme-format
msgid "sources do not match any package:~{ ~a~}~%"
msgstr ""

#: guix/scripts/build.scm:397 guix/scripts/download.scm:96
#: guix/scripts/package.scm:756 guix/scripts/gc.scm:122
#: guix/scripts/pull.scm:115 guix/scripts/system.scm:386
#, scheme-format
msgid "~A: unrecognized option~%"
msgstr "~A: Nerozpoznaná volba~%"

#: guix/scripts/build.scm:425
#, scheme-format
msgid "no build log for '~a'~%"
msgstr "Žádný záznam o sestavení pro »~a«~%"

#: guix/scripts/download.scm:44
msgid ""
"Usage: guix download [OPTION] URL\n"
"Download the file at URL, add it to the store, and print its store path\n"
"and the hash of its contents.\n"
"\n"
"Supported formats: 'nix-base32' (default), 'base32', and 'base16'\n"
"('hex' and 'hexadecimal' can be used as well).\n"
msgstr ""

#: guix/scripts/download.scm:50 guix/scripts/hash.scm:50
msgid ""
"\n"
"  -f, --format=FMT       write the hash in the given format"
msgstr ""

#: guix/scripts/download.scm:73 guix/scripts/hash.scm:75
#, scheme-format
msgid "unsupported hash format: ~a~%"
msgstr "Nepodporovaný formát kontrolního součtu: ~a~%"

#: guix/scripts/download.scm:106
#, scheme-format
msgid "~a: failed to parse URI~%"
msgstr "~a: URI~% se nepodařilo zpracovat"

#: guix/scripts/download.scm:117
#, scheme-format
msgid "~a: download failed~%"
msgstr "~a: Stažení se nezdařilo~%"

#: guix/scripts/package.scm:88
#, scheme-format
msgid "failed to build the empty profile~%"
msgstr "Nepodařilo se vytvořit prázdný profil~%"

#: guix/scripts/package.scm:97
#, scheme-format
msgid "switching from generation ~a to ~a~%"
msgstr ""

#: guix/scripts/package.scm:108 guix/scripts/package.scm:866
#: guix/scripts/package.scm:978
#, scheme-format
msgid "profile '~a' does not exist~%"
msgstr "Profil »~a« neexistuje~%"

#: guix/scripts/package.scm:112
#, scheme-format
msgid "nothing to do: already at the empty profile~%"
msgstr ""

#: guix/scripts/package.scm:197
#, scheme-format
msgid "The following package would be removed:~%~{~a~%~}~%"
msgstr ""

#: guix/scripts/package.scm:202
#, scheme-format
msgid "The following package will be removed:~%~{~a~%~}~%"
msgstr ""

#: guix/scripts/package.scm:214
#, scheme-format
msgid "The following package would be installed:~%~{~a~%~}~%"
msgstr ""

#: guix/scripts/package.scm:219
#, scheme-format
msgid "The following package will be installed:~%~{~a~%~}~%"
msgstr "Bude nainstalován následující balíček:~%~{~a~%~}~%"

#: guix/scripts/package.scm:339
#, scheme-format
msgid "package `~a' lacks output `~a'~%"
msgstr ""

#: guix/scripts/package.scm:356
#, scheme-format
msgid "~a: package not found~%"
msgstr "~a: Balíček nenalezen~%"

#: guix/scripts/package.scm:391
#, scheme-format
msgid "looking for the latest release of GNU ~a..."
msgstr "Hledá se poslední vydání GNU ~a …"

#: guix/scripts/package.scm:395
#, scheme-format
msgid "~a: note: using ~a but ~a is available upstream~%"
msgstr ""

#: guix/scripts/package.scm:467
#, scheme-format
msgid "The following environment variable definitions may be needed:~%"
msgstr ""

#: guix/scripts/package.scm:483
msgid ""
"Usage: guix package [OPTION]... PACKAGES...\n"
"Install, remove, or upgrade PACKAGES in a single transaction.\n"
msgstr ""

#: guix/scripts/package.scm:485
msgid ""
"\n"
"  -i, --install=PACKAGE  install PACKAGE"
msgstr ""
"\n"
"  -i, --install=PAKET    Instalovat BALÍČEK"

#: guix/scripts/package.scm:487
msgid ""
"\n"
"  -e, --install-from-expression=EXP\n"
"                         install the package EXP evaluates to"
msgstr ""

#: guix/scripts/package.scm:490
msgid ""
"\n"
"  -r, --remove=PACKAGE   remove PACKAGE"
msgstr ""
"\n"
"  -r, --remove=PAKET     Odstranit BALÍČEK"

#: guix/scripts/package.scm:492
msgid ""
"\n"
"  -u, --upgrade[=REGEXP] upgrade all the installed packages matching REGEXP"
msgstr ""

#: guix/scripts/package.scm:494
msgid ""
"\n"
"      --roll-back        roll back to the previous generation"
msgstr ""

#: guix/scripts/package.scm:496
msgid ""
"\n"
"      --search-paths     display needed environment variable definitions"
msgstr ""

#: guix/scripts/package.scm:498
msgid ""
"\n"
"  -l, --list-generations[=PATTERN]\n"
"                         list generations matching PATTERN"
msgstr ""

#: guix/scripts/package.scm:501
msgid ""
"\n"
"  -d, --delete-generations[=PATTERN]\n"
"                         delete generations matching PATTERN"
msgstr ""

#: guix/scripts/package.scm:504
msgid ""
"\n"
"  -p, --profile=PROFILE  use PROFILE instead of the user's default profile"
msgstr ""

#: guix/scripts/package.scm:507
msgid ""
"\n"
"      --bootstrap        use the bootstrap Guile to build the profile"
msgstr ""

#: guix/scripts/package.scm:509 guix/scripts/pull.scm:75
msgid ""
"\n"
"      --verbose          produce verbose output"
msgstr ""

#: guix/scripts/package.scm:512
msgid ""
"\n"
"  -s, --search=REGEXP    search in synopsis and description using REGEXP"
msgstr ""

#: guix/scripts/package.scm:514
msgid ""
"\n"
"  -I, --list-installed[=REGEXP]\n"
"                         list installed packages matching REGEXP"
msgstr ""

#: guix/scripts/package.scm:517
msgid ""
"\n"
"  -A, --list-available[=REGEXP]\n"
"                         list available packages matching REGEXP"
msgstr ""

#: guix/scripts/package.scm:760
#, scheme-format
msgid "~A: extraneous argument~%"
msgstr ""

#: guix/scripts/package.scm:775
#, scheme-format
msgid "Try \"info '(guix) Invoking guix package'\" for more information.~%"
msgstr ""

#: guix/scripts/package.scm:797
#, scheme-format
msgid "error: while creating directory `~a': ~a~%"
msgstr ""

#: guix/scripts/package.scm:801
#, scheme-format
msgid "Please create the `~a' directory, with you as the owner.~%"
msgstr ""

#: guix/scripts/package.scm:808
#, scheme-format
msgid "error: directory `~a' is not owned by you~%"
msgstr ""

#: guix/scripts/package.scm:811
#, scheme-format
msgid "Please change the owner of `~a' to user ~s.~%"
msgstr ""

#: guix/scripts/package.scm:836
#, scheme-format
msgid "deleting ~a~%"
msgstr ""

#: guix/scripts/package.scm:889 guix/scripts/package.scm:994
#, scheme-format
msgid "invalid syntax: ~a~%"
msgstr "Neplatná skladba: ~a~%"

#: guix/scripts/package.scm:918
#, scheme-format
msgid "nothing to be done~%"
msgstr "Nic k dělání~%"

#: guix/scripts/package.scm:941
#, scheme-format
msgid "~a package in profile~%"
msgstr "~a balíček v profilu~%"

#: guix/scripts/package.scm:956
#, scheme-format
msgid "Generation ~a\t~a"
msgstr ""

#: guix/scripts/package.scm:963
#, scheme-format
msgid "~a\t(current)~%"
msgstr "~a\t(aktuální)~%"

#: guix/scripts/gc.scm:39
msgid ""
"Usage: guix gc [OPTION]... PATHS...\n"
"Invoke the garbage collector.\n"
msgstr ""

#: guix/scripts/gc.scm:41
msgid ""
"\n"
"  -C, --collect-garbage[=MIN]\n"
"                         collect at least MIN bytes of garbage"
msgstr ""

#: guix/scripts/gc.scm:44
msgid ""
"\n"
"  -d, --delete           attempt to delete PATHS"
msgstr ""

#: guix/scripts/gc.scm:46
msgid ""
"\n"
"      --list-dead        list dead paths"
msgstr ""

#: guix/scripts/gc.scm:48
msgid ""
"\n"
"      --list-live        list live paths"
msgstr ""

#: guix/scripts/gc.scm:51
msgid ""
"\n"
"      --references       list the references of PATHS"
msgstr ""

#: guix/scripts/gc.scm:53
msgid ""
"\n"
"  -R, --requisites       list the requisites of PATHS"
msgstr ""

#: guix/scripts/gc.scm:55
msgid ""
"\n"
"      --referrers        list the referrers of PATHS"
msgstr ""

#: guix/scripts/gc.scm:84
#, scheme-format
msgid "invalid amount of storage: ~a~%"
msgstr "Neplatná hodnota paměti: ~a~%"

#: guix/scripts/hash.scm:45
msgid ""
"Usage: guix hash [OPTION] FILE\n"
"Return the cryptographic hash of FILE.\n"
"\n"
"Supported formats: 'nix-base32' (default), 'base32', and 'base16' ('hex'\n"
"and 'hexadecimal' can be used as well).\n"
msgstr ""

#: guix/scripts/hash.scm:52
msgid ""
"\n"
"  -r, --recursive        compute the hash on FILE recursively"
msgstr ""
"\n"
"  -r, --recursive        Spočítat kontrolní součet SOUBORU rekursivně"

#: guix/scripts/hash.scm:103
#, scheme-format
msgid "unrecognized option: ~a~%"
msgstr "Nerozpoznaná volba: ~a~%"

#: guix/scripts/hash.scm:134 guix/ui.scm:233
#, scheme-format
msgid "~a~%"
msgstr "~a~%"

#: guix/scripts/hash.scm:137
#, scheme-format
msgid "wrong number of arguments~%"
msgstr "Nesprávný počet argumentů~%"

#: guix/scripts/pull.scm:73
msgid ""
"Usage: guix pull [OPTION]...\n"
"Download and deploy the latest version of Guix.\n"
msgstr ""

#: guix/scripts/pull.scm:77
msgid ""
"\n"
"      --url=URL          download the Guix tarball from URL"
msgstr ""

#: guix/scripts/pull.scm:79
msgid ""
"\n"
"      --bootstrap        use the bootstrap Guile to build the new Guix"
msgstr ""

#: guix/scripts/pull.scm:117
#, scheme-format
msgid "~A: unexpected argument~%"
msgstr "~A: Neočekávaný argument~%"

#: guix/scripts/pull.scm:126
msgid "failed to download up-to-date source, exiting\n"
msgstr ""

#: guix/scripts/pull.scm:145
#, scheme-format
msgid "updated ~a successfully deployed under `~a'~%"
msgstr ""

#: guix/scripts/pull.scm:148
#, scheme-format
msgid "failed to update Guix, check the build log~%"
msgstr ""

#: guix/scripts/pull.scm:150
msgid "Guix already up to date\n"
msgstr "Guix je již aktuální\n"

#: guix/scripts/substitute-binary.scm:80
#, scheme-format
msgid "authentication and authorization of substitutes disabled!~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:163
#, scheme-format
msgid "download from '~a' failed: ~a, ~s~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:178
#, scheme-format
msgid "while fetching ~a: server is unresponsive~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:180
#, scheme-format
msgid "try `--no-substitutes' if the problem persists~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:244
#, scheme-format
msgid "signature version must be a number: ~a~%"
msgstr "Verze podpisu musí být číslo: ~a~%"

#: guix/scripts/substitute-binary.scm:248
#, scheme-format
msgid "unsupported signature version: ~a~%"
msgstr "Verze podpisu není podporována: ~a~%"

#: guix/scripts/substitute-binary.scm:256
#, scheme-format
msgid "signature is not a valid s-expression: ~s~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:260
#, scheme-format
msgid "invalid format of the signature field: ~a~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:295
#, scheme-format
msgid "invalid signature for '~a'~%"
msgstr "Neplatný podpis »~a«~%"

#: guix/scripts/substitute-binary.scm:297
#, scheme-format
msgid "hash mismatch for '~a'~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:299
#, scheme-format
msgid "'~a' is signed with an unauthorized key~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:301
#, scheme-format
msgid "signature on '~a' is corrupt~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:338
#, scheme-format
msgid "substitute at '~a' lacks a signature~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:526
#, scheme-format
msgid "Downloading, please wait...~%"
msgstr "Stahuje se. Počkejte, prosím …~%"

#: guix/scripts/substitute-binary.scm:528
#, scheme-format
msgid "(Please consider upgrading Guile to get proper progress report.)~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:545
#, scheme-format
msgid "host name lookup error: ~a~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:554
msgid ""
"Usage: guix substitute-binary [OPTION]...\n"
"Internal tool to substitute a pre-built binary to a local build.\n"
msgstr ""

#: guix/scripts/substitute-binary.scm:556
msgid ""
"\n"
"      --query            report on the availability of substitutes for the\n"
"                         store file names passed on the standard input"
msgstr ""

#: guix/scripts/substitute-binary.scm:559
msgid ""
"\n"
"      --substitute STORE-FILE DESTINATION\n"
"                         download STORE-FILE and store it as a Nar in file\n"
"                         DESTINATION"
msgstr ""

#: guix/scripts/substitute-binary.scm:604
msgid "ACL for archive imports seems to be uninitialized, substitutes may be unavailable\n"
msgstr ""

#: guix/scripts/substitute-binary.scm:625
#, scheme-format
msgid "failed to look up host '~a' (~a), substituter disabled~%"
msgstr ""

#: guix/scripts/substitute-binary.scm:732
#, scheme-format
msgid "~a: unrecognized options~%"
msgstr "~a: Nerozpoznané volby~%"

#: guix/scripts/authenticate.scm:58
#, scheme-format
msgid "cannot find public key for secret key '~a'~%"
msgstr ""

#: guix/scripts/authenticate.scm:78
#, scheme-format
msgid "error: invalid signature: ~a~%"
msgstr "Chyba: Neplatný podpis: ~a~%"

#: guix/scripts/authenticate.scm:80
#, scheme-format
msgid "error: unauthorized public key: ~a~%"
msgstr "Chyba: Neautorizovaný veřejný klíč: ~a~%"

#: guix/scripts/authenticate.scm:82
#, scheme-format
msgid "error: corrupt signature data: ~a~%"
msgstr "Chyba: Data podpisu poškozena: ~a~%"

#: guix/scripts/authenticate.scm:126
msgid ""
"Usage: guix authenticate OPTION...\n"
"Sign or verify the signature on the given file.  This tool is meant to\n"
"be used internally by 'guix-daemon'.\n"
msgstr ""

#: guix/scripts/authenticate.scm:132
msgid "wrong arguments"
msgstr "Nesprávné argumenty"

#: guix/scripts/system.scm:74
#, scheme-format
msgid "failed to open operating system file '~a': ~a~%"
msgstr ""

#: guix/scripts/system.scm:78 guix/ui.scm:238
#, scheme-format
msgid "~a: ~a~%"
msgstr "~a: ~a~%"

#: guix/scripts/system.scm:81
#, scheme-format
msgid "failed to load operating system file '~a': ~s~%"
msgstr ""

#: guix/scripts/system.scm:111
#, scheme-format
msgid "failed to register '~a' under '~a'~%"
msgstr ""

#: guix/scripts/system.scm:127
#, scheme-format
msgid "initializing the current root file system~%"
msgstr ""

#: guix/scripts/system.scm:151 guix/scripts/system.scm:291
#, scheme-format
msgid "failed to install GRUB on device '~a'~%"
msgstr ""

#: guix/scripts/system.scm:176
#, scheme-format
msgid "activating system...~%"
msgstr "Systém se zapíná …~%"

#: guix/scripts/system.scm:211
#, scheme-format
msgid "unrecognized boot parameters for '~a'~%"
msgstr "Nerozpoznané spouštěcí parametry pro »~a«~%"

#: guix/scripts/system.scm:295
#, scheme-format
msgid "initializing operating system under '~a'...~%"
msgstr ""

#: guix/scripts/system.scm:311
msgid ""
"Usage: guix system [OPTION] ACTION FILE\n"
"Build the operating system declared in FILE according to ACTION.\n"
msgstr ""

#: guix/scripts/system.scm:314
msgid "The valid values for ACTION are:\n"
msgstr "Platné hodnoty pro ČINNOST jsou:\n"

#: guix/scripts/system.scm:315
msgid "  - 'reconfigure', switch to a new operating system configuration\n"
msgstr ""

#: guix/scripts/system.scm:317
msgid "  - 'build', build the operating system without installing anything\n"
msgstr ""

#: guix/scripts/system.scm:319
msgid "  - 'vm', build a virtual machine image that shares the host's store\n"
msgstr ""

#: guix/scripts/system.scm:321
msgid "  - 'vm-image', build a freestanding virtual machine image\n"
msgstr ""

#: guix/scripts/system.scm:323
msgid "  - 'disk-image', build a disk image, suitable for a USB stick\n"
msgstr ""

#: guix/scripts/system.scm:325
msgid "  - 'init', initialize a root file system to run GNU.\n"
msgstr ""

#: guix/scripts/system.scm:329
msgid ""
"\n"
"      --image-size=SIZE  for 'vm-image', produce an image of SIZE"
msgstr ""

#: guix/scripts/system.scm:331
msgid ""
"\n"
"      --no-grub          for 'init', do not install GRUB"
msgstr ""

#: guix/scripts/system.scm:394
#, scheme-format
msgid "~a: unknown action~%"
msgstr "~a: Neznámá činnost~%"

#: guix/scripts/system.scm:411
#, scheme-format
msgid "wrong number of arguments for action '~a'~%"
msgstr "Nesprávný počet argumentů pro činnost »~a«~%"

#: guix/scripts/system.scm:431
#, scheme-format
msgid "no configuration file specified~%"
msgstr "Nezadán žádný soubor s nastavením~%"

#: guix/gnu-maintenance.scm:373
#, scheme-format
msgid "signature verification failed for `~a'~%"
msgstr "Ověření podpisu se nezdařilo pro »~a«~%"

#: guix/gnu-maintenance.scm:375
#, scheme-format
msgid "(could be because the public key is not in your keyring)~%"
msgstr ""

#: guix/gnu-maintenance.scm:450
#, scheme-format
msgid "~a: could not locate source file"
msgstr "~a: Zdrojový soubor se nepodařilo najít"

#: guix/gnu-maintenance.scm:455
#, scheme-format
msgid "~a: ~a: no `version' field in source; skipping~%"
msgstr ""

#: guix/ui.scm:131
#, scheme-format
msgid "failed to install locale: ~a~%"
msgstr "Jazyk se nepodařilo nainstalovat: ~a~%"

#: guix/ui.scm:150
msgid ""
"Copyright (C) 2014 the Guix authors\n"
"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n"
msgstr ""
"Autorské právo (C) 2014 autoři Guixu\n"
"Povolení GPLv3+: GNU GPL verze 3 nebo pozdější <http://gnu.org/licenses/gpl.html>\n"
"Toto je svobodný software: Můžete jej volně měnit a dál šířit.\n"
"Není tu ŽÁDNÁ ZÁRUKA, ve výši povolené zákonem.\n"

#: guix/ui.scm:158
#, scheme-format
msgid ""
"\n"
"Report bugs to: ~a."
msgstr ""
"\n"
"Chyby hlašte na: ~a."

#: guix/ui.scm:160
#, scheme-format
msgid ""
"\n"
"~a home page: <~a>"
msgstr ""
"\n"
"~a stránky: <~a>"

#: guix/ui.scm:162
msgid ""
"\n"
"General help using GNU software: <http://www.gnu.org/gethelp/>"
msgstr ""
"\n"
"Všeobecná nápověda k software GNU: <http://www.gnu.org/gethelp/>"

#: guix/ui.scm:169
#, scheme-format
msgid "~a: invalid number~%"
msgstr "~a: Neplatné číslo~%"

#: guix/ui.scm:186
#, scheme-format
msgid "invalid number: ~a~%"
msgstr "Neplatné číslo: ~a~%"

#: guix/ui.scm:201
#, scheme-format
msgid "unknown unit: ~a~%"
msgstr "Neznámá jednotka: ~a~%"

#: guix/ui.scm:212
#, scheme-format
msgid "~a:~a:~a: package `~a' has an invalid input: ~s~%"
msgstr "~a:~a:~a: Balíček `~a' má neplatný vstup: ~s~%"

#: guix/ui.scm:219
#, scheme-format
msgid "~a: ~a: build system `~a' does not support cross builds~%"
msgstr "~a: ~a: Sestavovací systém `~a' nepodporuje křížová sestavení~%"

#: guix/ui.scm:224
#, scheme-format
msgid "failed to connect to `~a': ~a~%"
msgstr "Nepodařilo se spojit s »~a«: ~a~%"

#: guix/ui.scm:229
#, scheme-format
msgid "build failed: ~a~%"
msgstr "Nepodařilo se sestavit: ~a~%"

#: guix/ui.scm:257
#, scheme-format
msgid "failed to read expression ~s: ~s~%"
msgstr "Výraz ~s se nepodařilo přečíst: ~s~%"

#: guix/ui.scm:263
#, scheme-format
msgid "failed to evaluate expression `~a': ~s~%"
msgstr ""

#: guix/ui.scm:272
#, scheme-format
msgid "expression ~s does not evaluate to a package~%"
msgstr ""

#: guix/ui.scm:319
#, scheme-format
msgid "~:[The following derivation would be built:~%~{   ~a~%~}~;~]"
msgstr ""

#: guix/ui.scm:324
#, scheme-format
msgid "~:[The following file would be downloaded:~%~{   ~a~%~}~;~]"
msgstr ""

#: guix/ui.scm:330
#, scheme-format
msgid "~:[The following derivation will be built:~%~{   ~a~%~}~;~]"
msgstr ""

#: guix/ui.scm:335
#, scheme-format
msgid "~:[The following file will be downloaded:~%~{   ~a~%~}~;~]"
msgstr "~:[Bude stažen následující soubor:~%~{   ~a~%~}~;~]"

#: guix/ui.scm:352
msgid "<unknown location>"
msgstr "<neznámé místo>"

#: guix/ui.scm:380
#, scheme-format
msgid "failed to create configuration directory `~a': ~a~%"
msgstr "Adresář s nastavením »~a« se nepodařilo vytvořit: ~a~%"

#: guix/ui.scm:461 guix/ui.scm:475
msgid "unknown"
msgstr "Neznámý"

#: guix/ui.scm:584
#, scheme-format
msgid "invalid argument: ~a~%"
msgstr "Neplatný argument: ~a~%"

#: guix/ui.scm:589
#, scheme-format
msgid "Try `guix --help' for more information.~%"
msgstr "vyvolejte »guix --help« pro obdržení dalších informací.~%"

#: guix/ui.scm:619
msgid ""
"Usage: guix COMMAND ARGS...\n"
"Run COMMAND with ARGS.\n"
msgstr ""
"Použití: guix PŘÍKAZ ARGUMENTY …\n"
"Spustit PŘÍKAZ s ARGUMENTY.\n"

#: guix/ui.scm:622
msgid "COMMAND must be one of the sub-commands listed below:\n"
msgstr "PŘÍKAZ musí být jeden z níže uvedených podpříkazů:\n"

#: guix/ui.scm:642
#, scheme-format
msgid "guix: ~a: command not found~%"
msgstr "guix: ~a: Příkaz nenalezen~%"

#: guix/ui.scm:660
#, scheme-format
msgid "guix: missing command name~%"
msgstr "guix: Chybí název příkazu~%"

#: guix/ui.scm:668
#, scheme-format
msgid "guix: unrecognized option '~a'~%"
msgstr "guix: Nerozpoznaná volba »~a«~%"

#: guix/http-client.scm:217
#, scheme-format
msgid "using Guile ~a, which does not support ~s encoding~%"
msgstr ""

#: guix/http-client.scm:220
#, scheme-format
msgid "download failed; use a newer Guile~%"
msgstr "Stažení se nezdařilo. Použijte novější Guile~%"

#: guix/http-client.scm:232
#, scheme-format
msgid "following redirection to `~a'...~%"
msgstr ""

#: guix/http-client.scm:241
msgid "download failed"
msgstr "Stažení se nezdařilo"

#: guix/nar.scm:134
msgid "unexpected executable file marker"
msgstr ""

#: guix/nar.scm:141
msgid "unsupported nar file type"
msgstr ""

#: guix/nar.scm:209
msgid "unsupported file type"
msgstr "Nepodporovaný typ souboru"

#: guix/nar.scm:219
msgid "invalid nar signature"
msgstr ""

#: guix/nar.scm:230
msgid "invalid nar end-of-file marker"
msgstr ""

#: guix/nar.scm:244
msgid "invalid symlink tokens"
msgstr ""

#: guix/nar.scm:263
msgid "unexpected directory entry termination"
msgstr ""

#: guix/nar.scm:272
msgid "unexpected directory inter-entry marker"
msgstr ""

#: guix/nar.scm:277
msgid "unsupported nar entry type"
msgstr ""

#: guix/nar.scm:376
msgid "signature is not a valid s-expression"
msgstr "Podpis není platný s-výraz"

#: guix/nar.scm:385
msgid "invalid signature"
msgstr "Neplatný podpis"

#: guix/nar.scm:389
msgid "invalid hash"
msgstr "Neplatný kontrolní součet"

#: guix/nar.scm:397
msgid "unauthorized public key"
msgstr "Neautorizovaný veřejný klíč"

#: guix/nar.scm:402
msgid "corrupt signature data"
msgstr "data podpisu poškozena"

#: guix/nar.scm:422
msgid "corrupt file set archive"
msgstr ""

#: guix/nar.scm:432
#, scheme-format
msgid "importing file or directory '~a'...~%"
msgstr "Zavádí se soubor nebo adresář »~a« …~%"

#: guix/nar.scm:441
#, scheme-format
msgid "found valid signature for '~a'~%"
msgstr "Nalezen platný podpis pro »~a« ~%"

#: guix/nar.scm:448
msgid "imported file lacks a signature"
msgstr "Zavedenému souboru chybí podpis"

#: guix/nar.scm:487
msgid "invalid inter-file archive mark"
msgstr ""