projects/guix·blob master · refs

591 lines · 17.6 KiB

1/* GNU Guix --- Functional package management for GNU
2 Copyright (C) 2012-2019, 2021-2022 Ludovic Courtès <[email protected]>
3 Copyright (C) 2006, 2010, 2012, 2014 Eelco Dolstra <[email protected]>
4
5 This file is part of GNU Guix.
6
7 GNU Guix is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or (at
10 your option) any later version.
11
12 GNU Guix is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. */
19
20#include "config.h"
21
22#include "types.hh"
23#include "nix-daemon.hh"
24#include "globals.hh"
25#include "util.hh"
26
27#include <gcrypt.h>
28
29#include <stdlib.h>
30#include <argp.h>
31#include <unistd.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <sys/socket.h>
35#include <sys/un.h>
36#include <netdb.h>
37#include <strings.h>
38#include <exception>
39#include <format>
40
41#include <libintl.h>
42#include <locale.h>
43
44using namespace nix;
45
46
47
48/* Command-line options. */
49
50#define n_(str) str
51#define _(str) gettext (str)
52static const char guix_textdomain[] = "guix";
53
54
55const char *argp_program_version =
56 "guix-daemon (" PACKAGE_NAME ") " PACKAGE_VERSION;
57const char *argp_program_bug_address = PACKAGE_BUGREPORT;
58
59static char doc[] =
60 n_("guix-daemon -- perform derivation builds and store accesses")
61 "\v\n"
62 n_("This program is a daemon meant to run in the background. It serves \
63requests sent over a Unix-domain socket. It accesses the store, and \
64builds derivations on behalf of its clients.");
65
66#define GUIX_OPT_SYSTEM 1
67#define GUIX_OPT_DISABLE_CHROOT 2
68#define GUIX_OPT_BUILD_USERS_GROUP 3
69#define GUIX_OPT_CACHE_FAILURES 4
70#define GUIX_OPT_LOSE_LOGS 5
71#define GUIX_OPT_DISABLE_LOG_COMPRESSION 6
72#define GUIX_OPT_DISABLE_DEDUPLICATION 7
73#define GUIX_OPT_IMPERSONATE_LINUX_26 8
74#define GUIX_OPT_DEBUG 9
75#define GUIX_OPT_CHROOT_DIR 10
76#define GUIX_OPT_LISTEN 11
77#define GUIX_OPT_NO_SUBSTITUTES 12
78#define GUIX_OPT_SUBSTITUTE_URLS 13
79#define GUIX_OPT_NO_BUILD_HOOK 14
80#define GUIX_OPT_GC_KEEP_OUTPUTS 15
81#define GUIX_OPT_GC_KEEP_DERIVATIONS 16
82#define GUIX_OPT_BUILD_ROUNDS 17
83#define GUIX_OPT_TIMEOUT 18
84#define GUIX_OPT_MAX_SILENT_TIME 19
85#define GUIX_OPT_LOG_COMPRESSION 20
86#define GUIX_OPT_DISCOVER 21
87#define GUIX_OPT_ISOLATE_HOST_LOOPBACK 22
88#define GUIX_OPT_ALLOW_ASLR 23
89
90static const struct argp_option options[] =
91 {
92 { "system", GUIX_OPT_SYSTEM, n_("SYSTEM"), 0,
93 n_("assume SYSTEM as the current system type") },
94 { "cores", 'c', n_("N"), 0,
95 n_("use N CPU cores to build each derivation; 0 means as many as available")
96 },
97 { "max-jobs", 'M', n_("N"), 0,
98 n_("allow at most N build jobs") },
99 { "timeout", GUIX_OPT_TIMEOUT, n_("SECONDS"), 0,
100 n_("mark builds as failed after SECONDS of activity") },
101 { "max-silent-time", GUIX_OPT_MAX_SILENT_TIME, n_("SECONDS"), 0,
102 n_("mark builds as failed after SECONDS of silence") },
103 { "disable-chroot", GUIX_OPT_DISABLE_CHROOT, 0, 0,
104 n_("disable chroot builds") },
105 { "chroot-directory", GUIX_OPT_CHROOT_DIR, n_("DIR"), 0,
106 n_("add DIR to the build chroot") },
107 { "build-users-group", GUIX_OPT_BUILD_USERS_GROUP, n_("GROUP"), 0,
108 n_("perform builds as a user of GROUP") },
109 { "no-substitutes", GUIX_OPT_NO_SUBSTITUTES, 0, 0,
110 n_("do not use substitutes") },
111 { "substitute-urls", GUIX_OPT_SUBSTITUTE_URLS, n_("URLS"), 0,
112 n_("use URLS as the default list of substitute providers") },
113 { "no-offload", GUIX_OPT_NO_BUILD_HOOK, 0, 0,
114 n_("do not attempt to offload builds") },
115 { "no-build-hook", GUIX_OPT_NO_BUILD_HOOK, 0,
116 OPTION_HIDDEN, // deprecated
117 n_("do not attempt to offload builds") },
118 { "cache-failures", GUIX_OPT_CACHE_FAILURES, 0, 0,
119 n_("cache build failures") },
120 { "rounds", GUIX_OPT_BUILD_ROUNDS, "N", 0,
121 n_("build each derivation N times in a row") },
122 { "lose-logs", GUIX_OPT_LOSE_LOGS, 0, 0,
123 n_("do not keep build logs") },
124 { "disable-log-compression", GUIX_OPT_DISABLE_LOG_COMPRESSION, 0,
125 OPTION_HIDDEN, // deprecated
126 n_("disable compression of the build logs") },
127 { "log-compression", GUIX_OPT_LOG_COMPRESSION, "TYPE", 0,
128 n_("use the specified compression type for build logs") },
129 { "discover", GUIX_OPT_DISCOVER, "yes/no", OPTION_ARG_OPTIONAL,
130 n_("use substitute servers discovered on the local network") },
131
132 /* '--disable-deduplication' was known as '--disable-store-optimization'
133 up to Guix 0.7 included, so keep the alias around. */
134 { "disable-deduplication", GUIX_OPT_DISABLE_DEDUPLICATION, 0, 0,
135 n_("disable automatic file \"deduplication\" in the store") },
136 { "disable-store-optimization", GUIX_OPT_DISABLE_DEDUPLICATION, 0,
137 OPTION_ALIAS | OPTION_HIDDEN, NULL },
138
139 { "impersonate-linux-2.6", GUIX_OPT_IMPERSONATE_LINUX_26, 0,
140#ifdef HAVE_SYS_PERSONALITY_H
141 0,
142#else
143 OPTION_HIDDEN,
144#endif
145 n_("impersonate Linux 2.6")
146 },
147 { "gc-keep-outputs", GUIX_OPT_GC_KEEP_OUTPUTS,
148 "yes/no", OPTION_ARG_OPTIONAL,
149 n_("tell whether the GC must keep outputs of live derivations") },
150 { "gc-keep-derivations", GUIX_OPT_GC_KEEP_DERIVATIONS,
151 "yes/no", OPTION_ARG_OPTIONAL,
152 n_("tell whether the GC must keep derivations corresponding \
153to live outputs") },
154
155 { "listen", GUIX_OPT_LISTEN, n_("SOCKET"), 0,
156 n_("listen for connections on SOCKET") },
157 { "debug", GUIX_OPT_DEBUG, 0, 0,
158 n_("produce debugging output") },
159 { "isolate-host-loopback", GUIX_OPT_ISOLATE_HOST_LOOPBACK, 0, 0,
160 n_("do not allow fixed-output chroot builds to access the host loopback") },
161 { "allow-aslr", GUIX_OPT_ALLOW_ASLR, 0,
162#ifdef HAVE_SYS_PERSONALITY_H
163 0,
164#else
165 OPTION_HIDDEN,
166#endif
167 n_("allow builds to start even if address space layout \
168randomization (ASLR) cannot be disabled")},
169 { 0, 0, 0, 0, 0 }
170 };
171
172
173/* Default port for '--listen' on TCP/IP. */
174#define DEFAULT_GUIX_PORT "44146"
175
176/* List of '--listen' options. */
177static std::list<std::string> listen_options;
178
179static bool useDiscover = false;
180
181/* Convert ARG to a Boolean value, or throw an error if it does not denote a
182 Boolean. */
183static bool
184string_to_bool (const char *arg, bool dflt = true)
185{
186 if (arg == NULL)
187 return dflt;
188 else if (strcasecmp (arg, "yes") == 0)
189 return true;
190 else if (strcasecmp (arg, "no") == 0)
191 return false;
192 else
193 throw nix::Error(std::format("'{}': invalid Boolean value", arg));
194}
195
196/* Parse a single option. */
197static error_t
198parse_opt (int key, char *arg, struct argp_state *state)
199{
200 switch (key)
201 {
202 case GUIX_OPT_DISABLE_CHROOT:
203 settings.useChroot = false;
204 break;
205 case GUIX_OPT_CHROOT_DIR:
206 {
207 std::string chroot_dirs {settings.get("build-extra-chroot-dirs", "")};
208
209 if (chroot_dirs == "")
210 chroot_dirs = arg;
211 else
212 chroot_dirs = chroot_dirs + " " + arg;
213 settings.set("build-extra-chroot-dirs", chroot_dirs);
214 break;
215 }
216 case GUIX_OPT_LOG_COMPRESSION:
217 if (strcmp (arg, "none") == 0)
218 settings.logCompression = COMPRESSION_NONE;
219 else if (strcmp (arg, "gzip") == 0)
220 settings.logCompression = COMPRESSION_GZIP;
221#if HAVE_BZLIB_H
222 else if (strcmp (arg, "bzip2") == 0)
223 settings.logCompression = COMPRESSION_BZIP2;
224#endif
225 else
226 {
227 fprintf (stderr, _("error: %s: unknown compression type\n"), arg);
228 exit (EXIT_FAILURE);
229 }
230 break;
231 case GUIX_OPT_DISABLE_LOG_COMPRESSION:
232 settings.logCompression = COMPRESSION_NONE;
233 break;
234 case GUIX_OPT_BUILD_USERS_GROUP:
235 settings.buildUsersGroup = arg;
236 break;
237 case GUIX_OPT_DISABLE_DEDUPLICATION:
238 settings.autoOptimiseStore = false;
239 break;
240 case GUIX_OPT_CACHE_FAILURES:
241 settings.cacheFailure = true;
242 break;
243 case GUIX_OPT_BUILD_ROUNDS:
244 {
245 char *end;
246 unsigned long n = strtoul (arg, &end, 10);
247 if (end != arg + strlen (arg))
248 {
249 fprintf (stderr, _("error: %s: invalid number of rounds\n"), arg);
250 exit (EXIT_FAILURE);
251 }
252 settings.set ("build-repeat", std::to_string (std::max (0UL, n - 1)));
253 break;
254 }
255 case GUIX_OPT_IMPERSONATE_LINUX_26:
256 settings.impersonateLinux26 = true;
257 break;
258 case GUIX_OPT_LOSE_LOGS:
259 settings.keepLog = false;
260 break;
261 case GUIX_OPT_LISTEN:
262 listen_options.push_back (arg);
263 break;
264 case GUIX_OPT_SUBSTITUTE_URLS:
265 settings.set ("substitute-urls", arg);
266 break;
267 case GUIX_OPT_NO_SUBSTITUTES:
268 settings.set ("build-use-substitutes", "false");
269 break;
270 case GUIX_OPT_NO_BUILD_HOOK:
271 settings.useBuildHook = false;
272 break;
273 case GUIX_OPT_DISCOVER:
274 useDiscover = string_to_bool (arg);
275 settings.set ("discover", useDiscover ? "true" : "false");
276 break;
277 case GUIX_OPT_DEBUG:
278 verbosity = lvlDebug;
279 break;
280 case GUIX_OPT_GC_KEEP_OUTPUTS:
281 settings.gcKeepOutputs = string_to_bool (arg);
282 break;
283 case GUIX_OPT_GC_KEEP_DERIVATIONS:
284 settings.gcKeepDerivations = string_to_bool (arg);
285 break;
286 case 'c':
287 settings.set ("build-cores", arg);
288 break;
289 case 'M':
290 settings.set ("build-max-jobs", arg);
291 break;
292 case GUIX_OPT_TIMEOUT:
293 settings.set ("build-timeout", arg);
294 break;
295 case GUIX_OPT_MAX_SILENT_TIME:
296 settings.set ("build-max-silent-time", arg);
297 break;
298 case GUIX_OPT_SYSTEM:
299 settings.thisSystem = arg;
300 break;
301 case GUIX_OPT_ISOLATE_HOST_LOOPBACK:
302 settings.useHostLoopback = false;
303 break;
304 case GUIX_OPT_ALLOW_ASLR:
305 settings.allowASLR = true;
306 break;
307 default:
308 return (error_t) ARGP_ERR_UNKNOWN;
309 }
310
311 return (error_t) 0;
312}
313
314/* Argument parsing. */
315static const struct argp argp =
316 {
317 options, parse_opt,
318 NULL, doc,
319 NULL, NULL, // children and help_filter
320 guix_textdomain
321 };
322
323
324static int
325open_unix_domain_socket (const char *file)
326{
327 /* Create and bind to a Unix domain socket. */
328 AutoCloseFD fdSocket = socket (PF_UNIX, SOCK_STREAM, 0);
329 if (fdSocket == -1)
330 throw SysError (_("cannot create Unix domain socket"));
331
332 createDirs (dirOf (file));
333
334 /* Urgh, sockaddr_un allows path names of only 108 characters.
335 So chdir to the socket directory so that we can pass a
336 relative path name. */
337 if (chdir (dirOf (file).c_str ()) == -1)
338 throw SysError (_("cannot change current directory"));
339 Path fileRel = "./" + baseNameOf (file);
340
341 struct sockaddr_un addr;
342 addr.sun_family = AF_UNIX;
343 if (fileRel.size () >= sizeof (addr.sun_path))
344 throw Error (std::vformat ((_("socket file name '{}' is too long")),
345 std::make_format_args (fileRel)));
346 strcpy (addr.sun_path, fileRel.c_str ());
347
348 unlink (file);
349
350 /* Make sure that the socket is created with 0666 permission
351 (everybody can connect --- provided they have access to the
352 directory containing the socket). */
353 mode_t oldMode = umask (0111);
354 int res = bind (fdSocket, (struct sockaddr *) &addr, sizeof addr);
355 umask (oldMode);
356 if (res == -1)
357 throw SysError (std::vformat( (_("cannot bind to socket '{}'")),
358 std::make_format_args(file)));
359
360 if (chdir ("/") == -1) /* back to the root */
361 throw SysError (_("cannot change current directory"));
362
363 if (listen (fdSocket, 5) == -1)
364 throw SysError (std::vformat( (_("cannot listen on socket '{}'")),
365 std::make_format_args(file)));
366
367 return fdSocket.borrow ();
368}
369
370/* Return a listening socket for ADDRESS, which has the given LENGTH. */
371static int
372open_inet_socket (const struct sockaddr *address, socklen_t length)
373{
374 AutoCloseFD fd = socket (address->sa_family, SOCK_STREAM, 0);
375 if (fd == -1)
376 throw SysError (_("cannot create TCP socket"));
377
378 int res = bind (fd, address, length);
379 if (res == -1)
380 throw SysError (_("cannot bind TCP socket"));
381
382 if (listen (fd, 5) == -1)
383 throw SysError (_("cannot listen on TCP socket"));
384
385 return fd.borrow ();
386}
387
388/* Return a list of file descriptors of listening sockets. */
389static std::vector<int>
390listening_sockets (const std::list<std::string> &options)
391{
392 std::vector<int> result;
393
394 if (options.empty ())
395 {
396 /* Open the default Unix-domain socket. */
397 auto fd = open_unix_domain_socket (settings.nixDaemonSocketFile.c_str ());
398 result.push_back (fd);
399 return result;
400 }
401
402 /* Open the user-specified sockets. */
403 for (const std::string& option: options)
404 {
405 if (option[0] == '/')
406 {
407 /* Assume OPTION is the file name of a Unix-domain socket. */
408 settings.nixDaemonSocketFile = canonPath (option);
409 int fd =
410 open_unix_domain_socket (settings.nixDaemonSocketFile.c_str ());
411 result.push_back (fd);
412 }
413 else
414 {
415 /* Assume OPTIONS has the form "HOST" or "HOST:PORT". */
416 auto colon = option.find_last_of (":");
417 auto host = colon == std::string::npos
418 ? option : option.substr (0, colon);
419 auto port = colon == std::string::npos
420 ? DEFAULT_GUIX_PORT
421 : option.substr (colon + 1, option.size () - colon - 1);
422
423 struct addrinfo *res, hints;
424
425 memset (&hints, '\0', sizeof hints);
426 hints.ai_socktype = SOCK_STREAM;
427 hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG;
428
429 int err = getaddrinfo (host.c_str(), port.c_str (),
430 &hints, &res);
431
432 if (err != 0)
433 throw Error(std::format("failed to look up '{}': {}", option, gai_strerror(err)));
434
435 printMsg(lvlDebug, std::format("listening on '{}', port '{}'", host, port));
436
437 /* XXX: Pick the first result, RES. */
438 result.push_back (open_inet_socket (res->ai_addr,
439 res->ai_addrlen));
440
441 freeaddrinfo (res);
442 }
443 }
444
445 return result;
446}
447
448/* First file descriptor provided at startup using systemd-style socket
449 activation. */
450#define SD_LISTEN_FDS_START 3
451
452/* Return a list of file descriptors of listening sockets provided following
453 the systemd "socket activation" protocol. Return the empty list if we are
454 not being socket-activated. */
455static std::vector<int>
456systemd_activation_sockets ()
457{
458 std::vector<int> result;
459
460 if (getEnv ("LISTEN_PID") == std::to_string (getpid ()))
461 {
462 unsigned int fdCount;
463 if (string2Int (getEnv ("LISTEN_FDS"), fdCount))
464 {
465 for (unsigned int i = 0; i < fdCount; i++)
466 result.push_back (SD_LISTEN_FDS_START + i);
467 }
468 }
469
470 return result;
471}
472
473
474int
475main (int argc, char *argv[])
476{
477 setlocale (LC_ALL, "");
478 bindtextdomain (guix_textdomain, LOCALEDIR);
479 textdomain (guix_textdomain);
480
481 /* Initialize libgcrypt. */
482 if (!gcry_check_version (GCRYPT_VERSION))
483 {
484 fprintf (stderr, _("error: libgcrypt version mismatch\n"));
485 exit (EXIT_FAILURE);
486 }
487
488 /* Tell Libgcrypt that initialization has completed, as per the Libgcrypt
489 1.6.0 manual (although this does not appear to be strictly needed.) */
490 gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
491
492 /* Set the umask so that the daemon does not end up creating group-writable
493 files, which would lead to "suspicious ownership or permission" errors.
494 See <http://lists.gnu.org/archive/html/bug-guix/2013-07/msg00033.html>. */
495 umask (S_IWGRP | S_IWOTH);
496
497#ifndef HAVE_CHROOT
498# error chroot is assumed to be available
499#endif
500
501 /* Always use chroots by default. */
502 settings.useChroot = true;
503
504 /* Turn automatic deduplication on by default. */
505 settings.autoOptimiseStore = true;
506
507 /* Default to using as many cores as possible and one job at a time. */
508 settings.buildCores = 0;
509 settings.maxBuildJobs = 1;
510
511 argvSaved = argv;
512
513 try
514 {
515 settings.processEnvironment ();
516
517 /* Enable substitutes by default. */
518 settings.set ("build-use-substitutes", "true");
519
520 /* Use our substitute server by default. */
521 settings.set ("substitute-urls", GUIX_SUBSTITUTE_URLS);
522
523#ifdef HAVE_DAEMON_OFFLOAD_HOOK
524 /* Use 'guix offload' for distributed builds by default. */
525 settings.useBuildHook = true;
526#else
527 /* We are not installing any build hook, so disable it. */
528 settings.useBuildHook = false;
529#endif
530
531 argp_parse (&argp, argc, argv, 0, 0, 0);
532
533 auto sockets = systemd_activation_sockets ();
534 if (sockets.empty ())
535 /* We were not "socket-activated" so open the sockets specified by
536 LISTEN_OPTIONS. */
537 sockets = listening_sockets (listen_options);
538 else
539 {
540 auto size = sockets.size();
541 printMsg (lvlInfo,
542 std::vformat((ngettext ("socket-activated with {} socket",
543 "socket-activated with {} sockets",
544 size)),
545 std::make_format_args(size)));
546 }
547
548 /* Effect all the changes made via 'settings.set'. */
549 settings.update ();
550 printMsg(lvlDebug,
551 std::format("build log compression: {}", int(settings.logCompression)));
552
553 if (geteuid () == 0 && settings.buildUsersGroup.empty ())
554 fprintf (stderr, _("warning: daemon is running as root, so \
555using `--build-users-group' is highly recommended\n"));
556
557 if (settings.useChroot)
558 {
559 std::string chroot_dirs;
560
561 chroot_dirs = settings.get ("build-extra-chroot-dirs",
562 (std::string) "");
563 printMsg (lvlDebug,
564 std::format("extra chroot directories: '{}'", chroot_dirs));
565 }
566
567 if (useDiscover)
568 {
569 Strings args;
570
571 args.push_back("guix");
572 args.push_back("discover");
573
574 startProcess([&]() {
575 execv(settings.guixProgram.c_str(), stringsToCharPtrs(args).data());
576 });
577 }
578
579 printMsg(lvlDebug,
580 std::format("automatic deduplication set to {}", settings.autoOptimiseStore));
581
582 run (sockets);
583 }
584 catch (std::exception &e)
585 {
586 fprintf (stderr, _("error: %s\n"), e.what ());
587 return EXIT_FAILURE;
588 }
589
590 return EXIT_SUCCESS; /* never reached */
591}