diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index dcc4462652..0000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "nix-upstream"] - path = nix-upstream - url = https://github.com/NixOS/nix.git diff --git a/bootstrap b/bootstrap index f34c43dc52..cb774bc737 100755 --- a/bootstrap +++ b/bootstrap @@ -1,15 +1,5 @@ #!/bin/sh - -# Import missing source files and create the build system. +# Create the build system. set -e -x - -top_srcdir="$PWD" -export top_srcdir - -git submodule init -git submodule update - -./nix/sync-with-upstream - exec autoreconf -vfi diff --git a/doc/guix.texi b/doc/guix.texi index bb52cf713f..00737850fd 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -4177,10 +4177,91 @@ tool suite.) the ``message of the day''. @end deffn -@deffn {Monadic Procedure} nscd-service [#:glibc glibc] -Return a service that runs libc's name service cache daemon (nscd). +@cindex name service cache daemon +@cindex nscd +@deffn {Monadic Procedure} nscd-service [@var{config}] [#:glibc glibc] +Return a service that runs libc's name service cache daemon (nscd) with the +given @var{config}---an @code{} object. @end deffn +@defvr {Scheme Variable} %nscd-default-configuration +This is the default @code{} value (see below) used +by @code{nscd-service}. This uses the caches defined by +@var{%nscd-default-caches}; see below. +@end defvr + +@deftp {Data Type} nscd-configuration +This is the type representing the name service cache daemon (nscd) +configuration. + +@table @asis + +@item @code{log-file} (default: @code{"/var/log/nscd.log"}) +Name of nscd's log file. This is where debugging output goes when +@code{debug-level} is strictly positive. + +@item @code{debug-level} (default: @code{0}) +Integer denoting the debugging levels. Higher numbers mean more +debugging output is logged. + +@item @code{caches} (default: @var{%nscd-default-caches}) +List of @code{} objects denoting things to be cached; see +below. + +@end table +@end deftp + +@deftp {Data Type} nscd-cache +Data type representing a cache database of nscd and its parameters. + +@table @asis + +@item @code{database} +This is a symbol representing the name of the database to be cached. +Valid values are @code{passwd}, @code{group}, @code{hosts}, and +@code{services}, which designate the corresponding NSS database +(@pxref{NSS Basics,,, libc, The GNU C Library Reference Manual}). + +@item @code{positive-time-to-live} +@itemx @code{negative-time-to-live} (default: @code{20}) +A number representing the number of seconds during which a positive or +negative lookup result remains in cache. + +@item @code{check-files?} (default: @code{#t}) +Whether to check for updates of the files corresponding to +@var{database}. + +For instance, when @var{database} is @code{hosts}, setting this flag +instructs nscd to check for updates in @file{/etc/hosts} and to take +them into account. + +@item @code{persistent?} (default: @code{#t}) +Whether the cache should be stored persistently on disk. + +@item @code{shared?} (default: @code{#t}) +Whether the cache should be shared among users. + +@item @code{max-database-size} (default: 32@tie{}MiB) +Maximum size in bytes of the database cache. + +@c XXX: 'suggested-size' and 'auto-propagate?' seem to be expert +@c settings, so leave them out. + +@end table +@end deftp + +@defvr {Scheme Variable} %nscd-default-caches +List of @code{} objects used by default by +@code{nscd-configuration} (see above.) + +It enables persistent and aggressive caching of service and host name +lookups. The latter provides better host name lookup performance, +resilience in the face of unreliable name servers, and also better +privacy---often the result of host name lookups is in local cache, so +external name servers do not even need to be queried. +@end defvr + + @deffn {Monadic Procedure} syslog-service Return a service that runs @code{syslogd} with reasonable default settings. diff --git a/gnu-system.am b/gnu-system.am index f1ebe40703..0e912f280e 100644 --- a/gnu-system.am +++ b/gnu-system.am @@ -96,6 +96,7 @@ GNU_SYSTEM_MODULES = \ gnu/packages/freeipmi.scm \ gnu/packages/ftp.scm \ gnu/packages/fribidi.scm \ + gnu/packages/game-development.scm \ gnu/packages/games.scm \ gnu/packages/gawk.scm \ gnu/packages/gcal.scm \ @@ -377,6 +378,15 @@ dist_patch_DATA = \ gnu/packages/patches/guix-test-networking.patch \ gnu/packages/patches/gtkglext-disable-disable-deprecated.patch \ gnu/packages/patches/hop-bigloo-4.0b.patch \ + gnu/packages/patches/icecat-CVE-2014-1587-bug-1042567.patch \ + gnu/packages/patches/icecat-CVE-2014-1587-bug-1072847.patch \ + gnu/packages/patches/icecat-CVE-2014-1587-bug-1079729.patch \ + gnu/packages/patches/icecat-CVE-2014-1587-bug-1080312.patch \ + gnu/packages/patches/icecat-CVE-2014-1587-bug-1089207.patch \ + gnu/packages/patches/icecat-CVE-2014-1590.patch \ + gnu/packages/patches/icecat-CVE-2014-1592.patch \ + gnu/packages/patches/icecat-CVE-2014-1593.patch \ + gnu/packages/patches/icecat-CVE-2014-1594.patch \ gnu/packages/patches/inkscape-stray-comma.patch \ gnu/packages/patches/jbig2dec-ignore-testtest.patch \ gnu/packages/patches/kmod-module-directory.patch \ @@ -450,6 +460,7 @@ dist_patch_DATA = \ gnu/packages/patches/wmctrl-64-fix.patch \ gnu/packages/patches/xf86-input-synaptics-glibc-2.20.patch \ gnu/packages/patches/xf86-video-openchrome-includes.patch \ + gnu/packages/patches/xfce4-panel-plugins.patch \ gnu/packages/patches/xmodmap-asprintf.patch bootstrapdir = $(guilemoduledir)/gnu/packages/bootstrap diff --git a/gnu/packages.scm b/gnu/packages.scm index c9efd0d691..6109d1f896 100644 --- a/gnu/packages.scm +++ b/gnu/packages.scm @@ -105,24 +105,29 @@ (append environment `((,%distro-root-directory . "gnu/packages")))))) (define* (scheme-files directory) - "Return the list of Scheme files found under DIRECTORY." - (file-system-fold (const #t) ; enter? - (lambda (path stat result) ; leaf - (if (string-suffix? ".scm" path) - (cons path result) - result)) - (lambda (path stat result) ; down - result) - (lambda (path stat result) ; up - result) - (const #f) ; skip - (lambda (path stat errno result) - (warning (_ "cannot access `~a': ~a~%") - path (strerror errno)) - result) - '() - directory - stat)) + "Return the list of Scheme files found under DIRECTORY, recursively. The +returned list is sorted in alphabetical order." + + ;; Sort entries so that 'fold-packages' works in a deterministic fashion + ;; regardless of details of the underlying file system. + (sort (file-system-fold (const #t) ; enter? + (lambda (path stat result) ; leaf + (if (string-suffix? ".scm" path) + (cons path result) + result)) + (lambda (path stat result) ; down + result) + (lambda (path stat result) ; up + result) + (const #f) ; skip + (lambda (path stat errno result) + (warning (_ "cannot access `~a': ~a~%") + path (strerror errno)) + result) + '() + directory + stat) + stringmodule-name (let ((not-slash (char-set-complement (char-set #\/)))) diff --git a/gnu/packages/bioinformatics.scm b/gnu/packages/bioinformatics.scm index 6f6178a3ff..ff6c3379af 100644 --- a/gnu/packages/bioinformatics.scm +++ b/gnu/packages/bioinformatics.scm @@ -28,6 +28,113 @@ #:use-module (gnu packages pkg-config) #:use-module (gnu packages python)) +(define-public bedtools + (package + (name "bedtools") + (version "2.22.0") + (source (origin + (method url-fetch) + (uri (string-append "https://github.com/arq5x/bedtools2/archive/v" + version ".tar.gz")) + (sha256 + (base32 + "16aq0w3dmbd0853j32xk9jin4vb6v6fgakfyvrsmsjizzbn3fpfl")))) + (build-system gnu-build-system) + (native-inputs `(("python" ,python-2))) + (inputs `(("samtools" ,samtools) + ("zlib" ,zlib))) + (arguments + '(#:test-target "test" + #:phases + (alist-cons-after + 'unpack 'patch-makefile-SHELL-definition + (lambda _ + ;; patch-makefile-SHELL cannot be used here as it does not + ;; yet patch definitions with `:='. Since changes to + ;; patch-makefile-SHELL result in a full rebuild, features + ;; of patch-makefile-SHELL are reimplemented here. + (substitute* "Makefile" + (("^SHELL := .*$") (string-append "SHELL := " (which "bash") " -e \n")))) + (alist-delete + 'configure + (alist-replace + 'install + (lambda* (#:key outputs #:allow-other-keys) + (let ((bin (string-append (assoc-ref outputs "out") "/bin/"))) + (mkdir-p bin) + (for-each (lambda (file) + (copy-file file (string-append bin (basename file)))) + (find-files "bin" ".*")))) + %standard-phases))))) + (home-page "https://github.com/arq5x/bedtools2") + (synopsis "Tools for genome analysis and arithmetic") + (description + "Collectively, the bedtools utilities are a swiss-army knife of tools for +a wide-range of genomics analysis tasks. The most widely-used tools enable +genome arithmetic: that is, set theory on the genome. For example, bedtools +allows one to intersect, merge, count, complement, and shuffle genomic +intervals from multiple files in widely-used genomic file formats such as BAM, +BED, GFF/GTF, VCF.") + (license license:gpl2))) + +(define-public bowtie + (package + (name "bowtie") + (version "2.2.4") + (source (origin + (method url-fetch) + (uri (string-append "https://github.com/BenLangmead/bowtie2/archive/v" + version ".tar.gz")) + (sha256 + (base32 + "15dnbqippwvhyh9zqjhaxkabk7lm1xbh1nvar1x4b5kwm117zijn")) + (modules '((guix build utils))) + (snippet + '(substitute* "Makefile" + (("^CC = .*$") "CC = gcc") + (("^CPP = .*$") "CPP = g++") + ;; replace BUILD_HOST and BUILD_TIME for deterministic build + (("-DBUILD_HOST=.*") "-DBUILD_HOST=\"\\\"guix\\\"\"") + (("-DBUILD_TIME=.*") "-DBUILD_TIME=\"\\\"0\\\"\""))))) + (build-system gnu-build-system) + (inputs `(("perl" ,perl) + ("perl-clone" ,perl-clone) + ("perl-test-deep" ,perl-test-deep) + ("perl-test-simple" ,perl-test-simple) + ("python" ,python-2))) + (arguments + '(#:make-flags '("allall") + #:phases + (alist-delete + 'configure + (alist-replace + 'install + (lambda* (#:key outputs #:allow-other-keys) + (let ((bin (string-append (assoc-ref outputs "out") "/bin/"))) + (mkdir-p bin) + (for-each (lambda (file) + (copy-file file (string-append bin file))) + (find-files "." "bowtie2.*")))) + (alist-replace + 'check + (lambda* (#:key outputs #:allow-other-keys) + (system* "perl" + "scripts/test/simple_tests.pl" + "--bowtie2=./bowtie2" + "--bowtie2-build=./bowtie2-build")) + %standard-phases))))) + (home-page "http://bowtie-bio.sourceforge.net/bowtie2/index.shtml") + (synopsis "Fast and sensitive nucleotide sequence read aligner") + (description + "Bowtie 2 is a fast and memory-efficient tool for aligning sequencing +reads to long reference sequences. It is particularly good at aligning reads +of about 50 up to 100s or 1,000s of characters, and particularly good at +aligning to relatively long (e.g. mammalian) genomes. Bowtie 2 indexes the +genome with an FM Index to keep its memory footprint small: for the human +genome, its memory footprint is typically around 3.2 GB. Bowtie 2 supports +gapped, local, and paired-end alignment modes.") + (license license:gpl3+))) + (define-public samtools (package (name "samtools") @@ -43,7 +150,14 @@ "1y5p2hs4gif891b4ik20275a8xf3qrr1zh9wpysp4g8m0g1jckf2")))) (build-system gnu-build-system) (arguments - '(#:make-flags (list (string-append "prefix=" (assoc-ref %outputs "out"))) + `(;; There are 87 test failures when building on non-64-bit architectures + ;; due to invalid test data. This has since been fixed upstream (see + ;; ), but as there has + ;; not been a new release we disable the tests for all non-64-bit + ;; systems. + #:tests? ,(string=? (or (%current-system) (%current-target-system)) + "x86_64-linux") + #:make-flags (list (string-append "prefix=" (assoc-ref %outputs "out"))) #:phases (alist-cons-after 'unpack diff --git a/gnu/packages/compression.scm b/gnu/packages/compression.scm index 7c22300dd1..f2736b9eb3 100644 --- a/gnu/packages/compression.scm +++ b/gnu/packages/compression.scm @@ -70,6 +70,26 @@ independent of the input data and can be reduced, if necessary, at some cost in compression.") (license license:zlib))) +(define-public fastjar + (package + (name "fastjar") + (version "0.98") + (source (origin + (method url-fetch) + (uri (string-append "mirror://savannah/fastjar/fastjar-" + version ".tar.gz")) + (sha256 + (base32 + "0iginbz2m15hcsa3x4y7v3mhk54gr1r7m3ghx0pg4n46vv2snmpi")))) + (build-system gnu-build-system) + (inputs `(("zlib" ,zlib))) + (home-page "http://savannah.nongnu.org/projects/fastjar") + (synopsis "Replacement for Sun's 'jar' utility") + (description + "FastJar is an attempt to create a much faster replacement for Sun's 'jar' +utility. Instead of being written in Java, FastJar is written in C.") + (license license:gpl2+))) + (define-public gzip (package (name "gzip") diff --git a/gnu/packages/game-development.scm b/gnu/packages/game-development.scm new file mode 100644 index 0000000000..056b3681a7 --- /dev/null +++ b/gnu/packages/game-development.scm @@ -0,0 +1,48 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2014 Tomáš Čech +;;; +;;; 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 . + +(define-module (gnu packages game-development) + #:use-module (guix licenses) + #:use-module (guix packages) + #:use-module (guix download) + #:use-module (guix build-system cmake) + #:use-module (gnu packages)) + +(define-public bullet + (package + (name "bullet") + (version "2.82-r2704") + (source (origin + (method url-fetch) + (uri (string-append "https://bullet.googlecode.com/files/bullet-" + version ".tgz")) + (sha256 + (base32 + "1lnfksxa9b1slyfcxys313ymsllvbsnxh9np06azkbgpfvmwkr37")))) + (build-system cmake-build-system) + (arguments '(#:tests? #f ; no 'test' target + #:configure-flags (list + (string-append + "-DCMAKE_CXX_FLAGS=-fPIC " + (or (getenv "CXXFLAGS") ""))))) + (home-page "http://bulletphysics.org/") + (synopsis "3D physics engine library") + (description + "Bullet is a physics engine library usable for collision detection. It +is used in some video games and movies.") + (license zlib))) diff --git a/gnu/packages/ghostscript.scm b/gnu/packages/ghostscript.scm index f21eeadf45..405b4e744e 100644 --- a/gnu/packages/ghostscript.scm +++ b/gnu/packages/ghostscript.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2013 Andreas Enge +;;; Copyright © 2014 Mark H Weaver ;;; ;;; This file is part of GNU Guix. ;;; @@ -33,14 +34,14 @@ (define-public lcms (package (name "lcms") - (version "2.4") + (version "2.6") (source (origin (method url-fetch) (uri (string-append "http://downloads.sourceforge.net/project/lcms/lcms/" version "/lcms2-" version ".tar.gz")) (sha256 (base32 - "1s1ppvqaydf2yqc72mw6zfviwxccb311a6hrbi802sgjxw84sl9a")))) + "1c8lgq8gfs3nyplvbx9k8wzfj6r2bqi3f611vb1m8z3476454wji")))) (build-system gnu-build-system) (inputs `(("libjpeg-8" ,libjpeg-8) ("libtiff" ,libtiff) @@ -118,13 +119,13 @@ printing, and psresize, for adjusting page sizes.") (define-public ghostscript (package (name "ghostscript") - (version "9.06.0") + (version "9.14.0") (source (origin (method url-fetch) (uri (string-append "mirror://gnu/ghostscript/gnu-ghostscript-" version ".tar.xz")) (sha256 (base32 - "0bcg2203p7cm0f53f3s883xhj2c91xnaxakj2cy7kcdknfxplvs4")))) + "0q4jj41p0qbr4mgcc9q78f5zs8cm1g57wgryhsm2yq4lfslm3ib1")))) (build-system gnu-build-system) (inputs `(("freetype" ,freetype) ("lcms" ,lcms) @@ -160,7 +161,7 @@ printing, and psresize, for adjusting page sizes.") file format. It also includes a C library that implements the graphics capabilities of the PostScript language. It supports a wide variety of output file formats and printers.") - (license license:gpl3+) + (license license:agpl3+) (home-page "http://www.gnu.org/software/ghostscript/"))) (define-public gs-fonts diff --git a/gnu/packages/gnome.scm b/gnu/packages/gnome.scm index 1d3ce25421..d9a22b41bb 100644 --- a/gnu/packages/gnome.scm +++ b/gnu/packages/gnome.scm @@ -47,7 +47,8 @@ #:use-module (gnu packages gl) #:use-module (gnu packages compression) #:use-module (gnu packages xorg) - #:use-module (gnu packages xdisorg)) + #:use-module (gnu packages xdisorg) + #:use-module (gnu packages ncurses)) (define-public brasero (package @@ -1292,3 +1293,89 @@ engineering.") (description "The default GNOME 3 themes (Adwaita and some accessibility themes).") (license license:lgpl2.1+))) + +(define-public vala + (package + (name "vala") + (version "0.26.1") + (source (origin + (method url-fetch) + (uri (string-append "mirror://gnome/sources/" name "/" + (version-major+minor version) "/" + name "-" version ".tar.xz")) + (sha256 + (base32 + "0swyym2papln0f62ah05dpvq3vv6fssap26jq2zqp9dkkaqsn1w4")))) + (build-system gnu-build-system) + (arguments '(#:make-flags '("CC=gcc"))) + (native-inputs + `(("pkg-config" ,pkg-config) + ("flex" ,flex) + ("bison" ,bison) + ("xsltproc" ,libxslt) + ("dbus" ,dbus) ; for dbus tests + ("gobject-introspection" ,gobject-introspection))) ; for gir tests + (propagated-inputs + `(("glib" ,glib))) ; required by libvala-0.26.pc + (home-page "http://live.gnome.org/Vala/") + (synopsis "Compiler for the GObject type system") + (description + "Vala is a programming language that aims to bring modern programming +language features to GNOME developers without imposing any additional runtime +requirements and without using a different ABI compared to applications and +libraries written in C.") + (license license:lgpl2.1+))) + +(define-public vte + (package + (name "vte") + (version "0.38.2") + (source (origin + (method url-fetch) + (uri (string-append "mirror://gnome/sources/" name "/" + (version-major+minor version) "/" + name "-" version ".tar.xz")) + (sha256 + (base32 + "1rbxrigff9yszbgdw0gw4c2saz4d1hbbpz21phzxx14w49wvmnmj")))) + (build-system gnu-build-system) + (native-inputs + `(("pkg-config" ,pkg-config) + ("intltool" ,intltool) + ("vala" ,vala) + ("gobject-introspection" ,gobject-introspection) + ("glib" ,glib "bin") ; for glib-genmarshal, etc. + ("xmllint" ,libxml2))) + (propagated-inputs + `(("gtk+" ,gtk+))) ; required by libvte-2.91.pc + (home-page "http://www.gnome.org/") + (synopsis "Virtual Terminal Emulator") + (description + "VTE is a library (libvte) implementing a terminal emulator widget for +GTK+, and a minimal sample application (vte) using that. Vte is mainly used in +gnome-terminal, but can also be used to embed a console/terminal in games, +editors, IDEs, etc.") + (license license:lgpl2.1+))) + +;; stable version for gtk2, required by xfce4-terminal. +(define-public vte/gtk+-2 + (package (inherit vte) + (name "vte") + (version "0.28.2") + (source (origin + (method url-fetch) + (uri (string-append "mirror://gnome/sources/" name "/" + (version-major+minor version) "/" + name "-" version ".tar.xz")) + (sha256 + (base32 + "1bmhahkf8wdsra9whd3k5l5z4rv7r58ksr8mshzajgq2ma0hpkw6")))) + (arguments + '(#:configure-flags '("--disable-python"))) + (native-inputs + `(("pkg-config" ,pkg-config) + ("intltool" ,intltool) + ("glib" ,glib "bin"))) ; for glib-genmarshal, etc. + (propagated-inputs + `(("gtk+" ,gtk+-2) ; required by libvte.pc + ("ncurses" ,ncurses))))) ; required by libvte.la diff --git a/gnu/packages/gnuzilla.scm b/gnu/packages/gnuzilla.scm index 2781447685..3ebc20dffa 100644 --- a/gnu/packages/gnuzilla.scm +++ b/gnu/packages/gnuzilla.scm @@ -1,6 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2013 Andreas Enge -;;; Copyright © 2013 Ludovic Courtès +;;; Copyright © 2013, 2014 Ludovic Courtès ;;; Copyright © 2014 Mark H Weaver ;;; ;;; This file is part of GNU Guix. @@ -53,7 +53,17 @@ version "/" name "-" version ".tar.xz")) (sha256 (base32 - "02r9klfc0z26w270inq652249hq0wfzvwhzvwmk0n8v8nzkk5idh")))) + "02r9klfc0z26w270inq652249hq0wfzvwhzvwmk0n8v8nzkk5idh")) + (patches (map search-patch + '("icecat-CVE-2014-1587-bug-1042567.patch" + "icecat-CVE-2014-1587-bug-1072847.patch" + "icecat-CVE-2014-1587-bug-1079729.patch" + "icecat-CVE-2014-1587-bug-1080312.patch" + "icecat-CVE-2014-1587-bug-1089207.patch" + "icecat-CVE-2014-1590.patch" + "icecat-CVE-2014-1592.patch" + "icecat-CVE-2014-1593.patch" + "icecat-CVE-2014-1594.patch"))))) (build-system gnu-build-system) (inputs `(("alsa-lib" ,alsa-lib) @@ -90,6 +100,7 @@ "--disable-debug" "--disable-debug-symbols" + "--enable-pulseaudio" "--disable-webrtc" ; webrtc fails to build "--with-system-zlib" diff --git a/gnu/packages/groff.scm b/gnu/packages/groff.scm index ad7cff32e1..e7a0026d9e 100644 --- a/gnu/packages/groff.scm +++ b/gnu/packages/groff.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2013 Andreas Enge +;;; Copyright © 2014 Mark H Weaver ;;; ;;; This file is part of GNU Guix. ;;; @@ -31,20 +32,21 @@ (define-public groff (package (name "groff") - (version "1.22.2") + (version "1.22.3") (source (origin (method url-fetch) (uri (string-append "mirror://gnu/groff/groff-" version ".tar.gz")) (sha256 (base32 - "0xi07nhj5vdgax37rj25mwxzdmsz1ifx50hjgc6hqbkpqkd6821q")))) + "1998v2kcs288d3y7kfxpvl369nqi06zbbvjzafyvyl3pr7bajj1s")))) (build-system gnu-build-system) (inputs `(("ghostscript" ,ghostscript) ("netpbm" ,netpbm))) (native-inputs `(("bison" ,bison) - ("perl" ,perl) - ("psutils" ,psutils) - ("texinfo" ,texinfo))) + ("perl" ,perl) + ("psutils" ,psutils) + ("texinfo" ,texinfo))) + (arguments '(#:parallel-build? #f)) ; parallel build fails (synopsis "Typesetting from plain text mixed with formatting commands") (description "Groff is a typesetting package that reads plain text and produces diff --git a/gnu/packages/linux.scm b/gnu/packages/linux.scm index 3f83711f32..a2708a290f 100644 --- a/gnu/packages/linux.scm +++ b/gnu/packages/linux.scm @@ -192,7 +192,7 @@ for SYSTEM, or #f if there is no configuration for SYSTEM." #f))) (define-public linux-libre - (let* ((version "3.18") + (let* ((version "3.18.1") (build-phase '(lambda* (#:key system inputs #:allow-other-keys #:rest args) ;; Apply the neat patch. @@ -265,7 +265,7 @@ for SYSTEM, or #f if there is no configuration for SYSTEM." (uri (linux-libre-urls version)) (sha256 (base32 - "1kv03bhls9rya4sg3qixyjirc79pn2g5bcwldcj7hs4apa77sd0g")))) + "0yj6sz9cvsbhrc9jksr4wgg63crzmqh65903l7bq9k0gz1f3x1s8")))) (build-system gnu-build-system) (native-inputs `(("perl" ,perl) ("bc" ,bc) diff --git a/gnu/packages/ntp.scm b/gnu/packages/ntp.scm index 8e6ed4fd3c..b2c520605a 100644 --- a/gnu/packages/ntp.scm +++ b/gnu/packages/ntp.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright 2014 John Darrington +;;; Copyright © 2014 John Darrington +;;; Copyright © 2014 Mark H Weaver ;;; ;;; This file is part of GNU Guix. ;;; @@ -20,8 +21,11 @@ #:use-module (gnu packages) #:use-module (gnu packages which) #:use-module (gnu packages linux) - #:use-module (guix licenses) + #:use-module (gnu packages pkg-config) + #:use-module (gnu packages openssl) + #:use-module ((guix licenses) #:prefix l:) #:use-module (guix packages) + #:use-module (guix utils) #:use-module (guix download) #:use-module (guix build-system gnu) #:use-module (srfi srfi-1)) @@ -29,29 +33,31 @@ (define-public ntp (package (name "ntp") - (version "4.2.6p5") + (version "4.2.8") (source (origin (method url-fetch) (uri (string-append - "http://www.eecis.udel.edu/~ntp/ntp_spool/ntp4/ntp-" - (string-join (take (string-split version #\.) 2) ".") + "http://archive.ntp.org/ntp4/ntp-" + (version-major+minor version) "/ntp-" version ".tar.gz")) (sha256 (base32 - "077r69a41hasl8zf5c44km7cqgfhrkaj6a4jnr75j7nkz5qq7ayn")))) - (native-inputs `(("which" ,which))) + "1vnqa1542d01xmlkw8f3rq57y360b2j7yxkkg9b11955nvw0v4if")))) + (native-inputs `(("which" ,which) + ("pkg-config" ,pkg-config))) (inputs - ;; Build with POSIX capabilities support on GNU/Linux. This allows 'ntpd' - ;; to run as non-root (when invoked with '-u'.) - (if (string-suffix? "-linux" - (or (%current-target-system) (%current-system))) - `(("libcap" ,libcap)) - '())) + `(("openssl" ,openssl) + ;; Build with POSIX capabilities support on GNU/Linux. This allows 'ntpd' + ;; to run as non-root (when invoked with '-u'.) + ,@(if (string-suffix? "-linux" + (or (%current-target-system) (%current-system))) + `(("libcap" ,libcap)) + '()))) (build-system gnu-build-system) (synopsis "Real time clock synchonization system") (description "NTP is a system designed to synchronize the clocks of computers over a network.") - (license (x11-style + (license (l:x11-style "http://www.eecis.udel.edu/~mills/ntp/html/copyright.html" "A non-copyleft free licence from the University of Delaware")) (home-page "http://www.ntp.org"))) diff --git a/gnu/packages/patches/icecat-CVE-2014-1587-bug-1042567.patch b/gnu/packages/patches/icecat-CVE-2014-1587-bug-1042567.patch new file mode 100644 index 0000000000..4e45e3062f --- /dev/null +++ b/gnu/packages/patches/icecat-CVE-2014-1587-bug-1042567.patch @@ -0,0 +1,30 @@ +commit 60529fc02cf10482d8fecd699eea271ddc22bcb9 +Author: Jason Orendorff +Date: Thu Aug 28 15:43:57 2014 -0500 + + Bug 1042567 - Reflect JSPropertyOp properties more consistently as data properties. r=efaust, a=lmandel + + Modified js/src/jsobj.cpp +diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp +index 2745509..ad336f3 100644 +--- a/js/src/jsobj.cpp ++++ b/js/src/jsobj.cpp +@@ -235,11 +235,18 @@ js::GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, + if (pobj->isNative()) { + desc.setAttributes(GetShapeAttributes(pobj, shape)); + if (desc.hasGetterOrSetterObject()) { ++ MOZ_ASSERT(desc.isShared()); + doGet = false; + if (desc.hasGetterObject()) + desc.setGetterObject(shape->getterObject()); + if (desc.hasSetterObject()) + desc.setSetterObject(shape->setterObject()); ++ } else { ++ // This is either a straight-up data property or (rarely) a ++ // property with a JSPropertyOp getter/setter. The latter must be ++ // reported to the caller as a plain data property, so don't ++ // populate desc.getter/setter, and mask away the SHARED bit. ++ desc.attributesRef() &= ~JSPROP_SHARED; + } + } else { + if (!JSObject::getGenericAttributes(cx, pobj, id, &desc.attributesRef())) diff --git a/gnu/packages/patches/icecat-CVE-2014-1587-bug-1072847.patch b/gnu/packages/patches/icecat-CVE-2014-1587-bug-1072847.patch new file mode 100644 index 0000000000..448b096b81 --- /dev/null +++ b/gnu/packages/patches/icecat-CVE-2014-1587-bug-1072847.patch @@ -0,0 +1,19 @@ +commit 5d91f3b10f999e852e0392470198bd6aefc87e1e +Author: Jeff Muizelaar +Date: Tue Oct 28 10:08:25 2014 -0400 + + Bug 1072847 - Initialize mSurface. r=BenWa, a=bkerensa + + Modified gfx/2d/DrawTargetCairo.cpp +diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp +index 48c2c73..78d9e4f 100644 +--- a/gfx/2d/DrawTargetCairo.cpp ++++ b/gfx/2d/DrawTargetCairo.cpp +@@ -353,6 +353,7 @@ NeedIntermediateSurface(const Pattern& aPattern, const DrawOptions& aOptions) + + DrawTargetCairo::DrawTargetCairo() + : mContext(nullptr) ++ , mSurface(nullptr) + , mLockedBits(nullptr) + { + } diff --git a/gnu/packages/patches/icecat-CVE-2014-1587-bug-1079729.patch b/gnu/packages/patches/icecat-CVE-2014-1587-bug-1079729.patch new file mode 100644 index 0000000000..3ef60baaad --- /dev/null +++ b/gnu/packages/patches/icecat-CVE-2014-1587-bug-1079729.patch @@ -0,0 +1,191 @@ +commit 5de6730cc26744b9efcf4d4adb4a4c45023ef8a0 +Author: Randell Jesup +Date: Tue Oct 28 11:06:00 2014 -0400 + + Bug 1079729: Fix handling of increasing number of SCTP channels used by DataChannels r=tuexen a=lsblakk + + Modified media/webrtc/signaling/src/sipcc/core/gsm/h/fsm.h +diff --git a/media/webrtc/signaling/src/sipcc/core/gsm/h/fsm.h b/media/webrtc/signaling/src/sipcc/core/gsm/h/fsm.h +index ba8e1ff..8d964f1 100755 +--- a/media/webrtc/signaling/src/sipcc/core/gsm/h/fsm.h ++++ b/media/webrtc/signaling/src/sipcc/core/gsm/h/fsm.h +@@ -225,7 +225,7 @@ typedef struct fsmdef_media_t_ { + /* + * Data Channel properties + */ +-#define WEBRTC_DATACHANNEL_STREAMS_DEFAULT 16 ++#define WEBRTC_DATACHANNEL_STREAMS_DEFAULT 256 + uint32 datachannel_streams; + char datachannel_protocol[SDP_MAX_STRING_LEN + 1]; + + Modified netwerk/sctp/datachannel/DataChannel.cpp +diff --git a/netwerk/sctp/datachannel/DataChannel.cpp b/netwerk/sctp/datachannel/DataChannel.cpp +index 414e3db..a00d938 100644 +--- a/netwerk/sctp/datachannel/DataChannel.cpp ++++ b/netwerk/sctp/datachannel/DataChannel.cpp +@@ -910,10 +910,12 @@ DataChannelConnection::RequestMoreStreams(int32_t aNeeded) + uint32_t outStreamsNeeded; + socklen_t len; + +- if (aNeeded + mStreams.Length() > MAX_NUM_STREAMS) ++ if (aNeeded + mStreams.Length() > MAX_NUM_STREAMS) { + aNeeded = MAX_NUM_STREAMS - mStreams.Length(); +- if (aNeeded <= 0) ++ } ++ if (aNeeded <= 0) { + return false; ++ } + + len = (socklen_t)sizeof(struct sctp_status); + if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_STATUS, &status, &len) < 0) { +@@ -922,19 +924,25 @@ DataChannelConnection::RequestMoreStreams(int32_t aNeeded) + } + outStreamsNeeded = aNeeded; // number to add + +- memset(&sas, 0, sizeof(struct sctp_add_streams)); ++ // Note: if multiple channel opens happen when we don't have enough space, ++ // we'll call RequestMoreStreams() multiple times ++ memset(&sas, 0, sizeof(sas)); + sas.sas_instrms = 0; + sas.sas_outstrms = (uint16_t)outStreamsNeeded; /* XXX error handling */ + // Doesn't block, we get an event when it succeeds or fails + if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_ADD_STREAMS, &sas, + (socklen_t) sizeof(struct sctp_add_streams)) < 0) { +- if (errno == EALREADY) ++ if (errno == EALREADY) { ++ LOG(("Already have %u output streams", outStreamsNeeded)); + return true; ++ } + + LOG(("***failed: setsockopt ADD errno=%d", errno)); + return false; + } + LOG(("Requested %u more streams", outStreamsNeeded)); ++ // We add to mStreams when we get a SCTP_STREAM_CHANGE_EVENT and the ++ // values are larger than mStreams.Length() + return true; + } + +@@ -1050,6 +1058,13 @@ DataChannelConnection::SendDeferredMessages() + channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED, + channel->mPrPolicy, channel->mPrValue)) { + channel->mFlags &= ~DATA_CHANNEL_FLAGS_SEND_REQ; ++ ++ channel->mState = OPEN; ++ channel->mReady = true; ++ LOG(("%s: sending ON_CHANNEL_OPEN for %p", __FUNCTION__, channel.get())); ++ NS_DispatchToMainThread(new DataChannelOnMessageAvailable( ++ DataChannelOnMessageAvailable::ON_CHANNEL_OPEN, this, ++ channel)); + sent = true; + } else { + if (errno == EAGAIN || errno == EWOULDBLOCK) { +@@ -1177,6 +1192,7 @@ DataChannelConnection::HandleOpenRequestMessage(const struct rtcweb_datachannel_ + prPolicy = SCTP_PR_SCTP_TTL; + break; + default: ++ LOG(("Unknown channel type", req->channel_type)); + /* XXX error handling */ + return; + } +@@ -1203,6 +1219,10 @@ DataChannelConnection::HandleOpenRequestMessage(const struct rtcweb_datachannel_ + } + return; + } ++ if (stream >= mStreams.Length()) { ++ LOG(("%s: stream %u out of bounds (%u)", __FUNCTION__, stream, mStreams.Length())); ++ return; ++ } + + nsCString label(nsDependentCSubstring(&req->label[0], ntohs(req->label_length))); + nsCString protocol(nsDependentCSubstring(&req->label[ntohs(req->label_length)], +@@ -1220,8 +1240,8 @@ DataChannelConnection::HandleOpenRequestMessage(const struct rtcweb_datachannel_ + + channel->mState = DataChannel::WAITING_TO_OPEN; + +- LOG(("%s: sending ON_CHANNEL_CREATED for %s/%s: %u", __FUNCTION__, +- channel->mLabel.get(), channel->mProtocol.get(), stream)); ++ LOG(("%s: sending ON_CHANNEL_CREATED for %s/%s: %u (state %u)", __FUNCTION__, ++ channel->mLabel.get(), channel->mProtocol.get(), stream, channel->mState)); + NS_DispatchToMainThread(new DataChannelOnMessageAvailable( + DataChannelOnMessageAvailable::ON_CHANNEL_CREATED, + this, channel)); +@@ -1739,13 +1759,14 @@ DataChannelConnection::HandleStreamResetEvent(const struct sctp_stream_reset_eve + // 2. We sent our own reset (CLOSING); either they crossed on the + // wire, or this is a response to our Reset. + // Go to CLOSED +- // 3. We've sent a open but haven't gotten a response yet (OPENING) ++ // 3. We've sent a open but haven't gotten a response yet (CONNECTING) + // I believe this is impossible, as we don't have an input stream yet. + + LOG(("Incoming: Channel %u closed, state %d", + channel->mStream, channel->mState)); + ASSERT_WEBRTC(channel->mState == DataChannel::OPEN || + channel->mState == DataChannel::CLOSING || ++ channel->mState == DataChannel::CONNECTING || + channel->mState == DataChannel::WAITING_TO_OPEN); + if (channel->mState == DataChannel::OPEN || + channel->mState == DataChannel::WAITING_TO_OPEN) { +@@ -1791,20 +1812,21 @@ DataChannelConnection::HandleStreamChangeEvent(const struct sctp_stream_change_e + return; + } else { + if (strchg->strchange_instrms > mStreams.Length()) { +- LOG(("Other side increased streamds from %u to %u", ++ LOG(("Other side increased streams from %u to %u", + mStreams.Length(), strchg->strchange_instrms)); + } +- if (strchg->strchange_outstrms > mStreams.Length()) { ++ if (strchg->strchange_outstrms > mStreams.Length() || ++ strchg->strchange_instrms > mStreams.Length()) { + uint16_t old_len = mStreams.Length(); ++ uint16_t new_len = std::max(strchg->strchange_outstrms, ++ strchg->strchange_instrms); + LOG(("Increasing number of streams from %u to %u - adding %u (in: %u)", +- old_len, +- strchg->strchange_outstrms, +- strchg->strchange_outstrms - old_len, ++ old_len, new_len, new_len - old_len, + strchg->strchange_instrms)); + // make sure both are the same length +- mStreams.AppendElements(strchg->strchange_outstrms - old_len); ++ mStreams.AppendElements(new_len - old_len); + LOG(("New length = %d (was %d)", mStreams.Length(), old_len)); +- for (uint32_t i = old_len; i < mStreams.Length(); ++i) { ++ for (size_t i = old_len; i < mStreams.Length(); ++i) { + mStreams[i] = nullptr; + } + // Re-process any channels waiting for streams. +@@ -1815,13 +1837,17 @@ DataChannelConnection::HandleStreamChangeEvent(const struct sctp_stream_change_e + // Could make a more complex API for OpenXxxFinish() and avoid this loop + int32_t num_needed = mPending.GetSize(); + LOG(("%d of %d new streams already needed", num_needed, +- strchg->strchange_outstrms - old_len)); +- num_needed -= (strchg->strchange_outstrms - old_len); // number we added ++ new_len - old_len)); ++ num_needed -= (new_len - old_len); // number we added + if (num_needed > 0) { + if (num_needed < 16) + num_needed = 16; + LOG(("Not enough new streams, asking for %d more", num_needed)); + RequestMoreStreams(num_needed); ++ } else if (strchg->strchange_outstrms < strchg->strchange_instrms) { ++ LOG(("Requesting %d output streams to match partner", ++ strchg->strchange_instrms - strchg->strchange_outstrms)); ++ RequestMoreStreams(strchg->strchange_instrms - strchg->strchange_outstrms); + } + + ProcessQueuedOpens(); + Modified netwerk/sctp/datachannel/DataChannelProtocol.h +diff --git a/netwerk/sctp/datachannel/DataChannelProtocol.h b/netwerk/sctp/datachannel/DataChannelProtocol.h +index 549f74b..74fbe58 100644 +--- a/netwerk/sctp/datachannel/DataChannelProtocol.h ++++ b/netwerk/sctp/datachannel/DataChannelProtocol.h +@@ -17,7 +17,7 @@ + #endif + + // Duplicated in fsm.def +-#define WEBRTC_DATACHANNEL_STREAMS_DEFAULT 16 ++#define WEBRTC_DATACHANNEL_STREAMS_DEFAULT 256 + + #define DATA_CHANNEL_PPID_CONTROL 50 + #define DATA_CHANNEL_PPID_BINARY 52 diff --git a/gnu/packages/patches/icecat-CVE-2014-1587-bug-1080312.patch b/gnu/packages/patches/icecat-CVE-2014-1587-bug-1080312.patch new file mode 100644 index 0000000000..5efac49e12 --- /dev/null +++ b/gnu/packages/patches/icecat-CVE-2014-1587-bug-1080312.patch @@ -0,0 +1,308 @@ +commit d74bdb4589ad714e2a45e282974db075de2be673 +Author: Randell Jesup +Date: Wed Nov 12 22:59:53 2014 -0500 + + Bug 1080312 - Update iteration code from upstream. r=jesup, a=abillings + + Modified netwerk/sctp/src/moz.build +diff --git a/netwerk/sctp/src/moz.build b/netwerk/sctp/src/moz.build +index 1901a41..82103b9 100644 +--- a/netwerk/sctp/src/moz.build ++++ b/netwerk/sctp/src/moz.build +@@ -31,7 +31,6 @@ SOURCES += [ + 'user_environment.c', + 'user_mbuf.c', + 'user_recv_thread.c', +- 'user_sctp_timer_iterate.c', + 'user_socket.c', + ] + + Modified netwerk/sctp/src/netinet/sctp_callout.c +diff --git a/netwerk/sctp/src/netinet/sctp_callout.c b/netwerk/sctp/src/netinet/sctp_callout.c +index 67b7566..e8ac77f 100755 +--- a/netwerk/sctp/src/netinet/sctp_callout.c ++++ b/netwerk/sctp/src/netinet/sctp_callout.c +@@ -30,9 +30,27 @@ + * THE POSSIBILITY OF SUCH DAMAGE. + */ + ++#if defined(__Userspace__) ++#include ++#if !defined (__Userspace_os_Windows) ++#include ++#include ++#include ++#endif ++#if defined(__Userspace_os_NaCl) ++#include ++#endif ++#include ++#include ++#include ++#include ++#include ++#include ++#else + #include + #include + #include ++#endif + + /* + * Callout/Timer routines for OS that doesn't have them +@@ -117,24 +135,16 @@ sctp_os_timer_stop(sctp_os_timer_t *c) + return (1); + } + +-#if defined(__APPLE__) +-/* +- * For __APPLE__, use a single main timer at a faster resolution than +- * fastim. The timer just calls this existing callout infrastructure. +- */ +-#endif +-void +-sctp_timeout(void *arg SCTP_UNUSED) ++static void ++sctp_handle_tick(int delta) + { + sctp_os_timer_t *c; + void (*c_func)(void *); + void *c_arg; + + SCTP_TIMERQ_LOCK(); +-#if defined(__APPLE__) + /* update our tick count */ +- ticks += SCTP_BASE_VAR(sctp_main_timer_ticks); +-#endif ++ ticks += delta; + c = TAILQ_FIRST(&SCTP_BASE_INFO(callqueue)); + while (c) { + if (c->c_time <= ticks) { +@@ -155,9 +165,60 @@ sctp_timeout(void *arg SCTP_UNUSED) + } + sctp_os_timer_next = NULL; + SCTP_TIMERQ_UNLOCK(); ++} + + #if defined(__APPLE__) +- /* restart the main timer */ ++void ++sctp_timeout(void *arg SCTP_UNUSED) ++{ ++ sctp_handle_tick(SCTP_BASE_VAR(sctp_main_timer_ticks)); + sctp_start_main_timer(); ++} + #endif ++ ++#if defined(__Userspace__) ++#define TIMEOUT_INTERVAL 10 ++ ++void * ++user_sctp_timer_iterate(void *arg) ++{ ++ for (;;) { ++#if defined (__Userspace_os_Windows) ++ Sleep(TIMEOUT_INTERVAL); ++#else ++ struct timeval timeout; ++ ++ timeout.tv_sec = 0; ++ timeout.tv_usec = 1000 * TIMEOUT_INTERVAL; ++ select(0, NULL, NULL, NULL, &timeout); ++#endif ++ if (SCTP_BASE_VAR(timer_thread_should_exit)) { ++ break; ++ } ++ sctp_handle_tick(MSEC_TO_TICKS(TIMEOUT_INTERVAL)); ++ } ++ return (NULL); + } ++ ++void ++sctp_start_timer(void) ++{ ++ /* ++ * No need to do SCTP_TIMERQ_LOCK_INIT(); ++ * here, it is being done in sctp_pcb_init() ++ */ ++#if defined (__Userspace_os_Windows) ++ if ((SCTP_BASE_VAR(timer_thread) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)user_sctp_timer_iterate, NULL, 0, NULL)) == NULL) { ++ SCTP_PRINTF("ERROR; Creating ithread failed\n"); ++ } ++#else ++ int rc; ++ ++ rc = pthread_create(&SCTP_BASE_VAR(timer_thread), NULL, user_sctp_timer_iterate, NULL); ++ if (rc) { ++ SCTP_PRINTF("ERROR; return code from pthread_create() is %d\n", rc); ++ } ++#endif ++} ++ ++#endif + Modified netwerk/sctp/src/netinet/sctp_callout.h +diff --git a/netwerk/sctp/src/netinet/sctp_callout.h b/netwerk/sctp/src/netinet/sctp_callout.h +index 2782945..c53c5a4 100755 +--- a/netwerk/sctp/src/netinet/sctp_callout.h ++++ b/netwerk/sctp/src/netinet/sctp_callout.h +@@ -64,7 +64,6 @@ __FBSDID("$FreeBSD$"); + #endif + + extern int ticks; +-extern void sctp_start_timer(); + #endif + + TAILQ_HEAD(calloutlist, sctp_callout); +@@ -94,6 +93,11 @@ int sctp_os_timer_stop(sctp_os_timer_t *); + #define SCTP_OS_TIMER_ACTIVE(tmr) ((tmr)->c_flags & SCTP_CALLOUT_ACTIVE) + #define SCTP_OS_TIMER_DEACTIVATE(tmr) ((tmr)->c_flags &= ~SCTP_CALLOUT_ACTIVE) + ++#if defined(__Userspace__) ++void sctp_start_timer(void); ++#endif ++#if defined(__APPLE__) + void sctp_timeout(void *); ++#endif + + #endif + Modified netwerk/sctp/src/netinet/sctp_usrreq.c +diff --git a/netwerk/sctp/src/netinet/sctp_usrreq.c b/netwerk/sctp/src/netinet/sctp_usrreq.c +index d4115ad..c17ea04 100755 +--- a/netwerk/sctp/src/netinet/sctp_usrreq.c ++++ b/netwerk/sctp/src/netinet/sctp_usrreq.c +@@ -56,6 +56,9 @@ __FBSDID("$FreeBSD: head/sys/netinet/sctp_usrreq.c 259943 2013-12-27 13:07:00Z t + #include + #include + #include ++#if defined(__Userspace__) ++#include ++#endif + #if !defined(__Userspace_os_Windows) + #include + #endif + Deleted netwerk/sctp/src/user_sctp_timer_iterate.c +diff --git a/netwerk/sctp/src/user_sctp_timer_iterate.c b/netwerk/sctp/src/user_sctp_timer_iterate.c +deleted file mode 100755 +index 0a9dbce..0000000 +--- a/netwerk/sctp/src/user_sctp_timer_iterate.c ++++ /dev/null +@@ -1,119 +0,0 @@ +-/*- +- * Copyright (c) 2012 Michael Tuexen +- * All rights reserved. +- * +- * Redistribution and use in source and binary forms, with or without +- * modification, are permitted provided that the following conditions +- * are met: +- * 1. Redistributions of source code must retain the above copyright +- * notice, this list of conditions and the following disclaimer. +- * 2. Redistributions in binary form must reproduce the above copyright +- * notice, this list of conditions and the following disclaimer in the +- * documentation and/or other materials provided with the distribution. +- * +- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +- * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +- * SUCH DAMAGE. +- * +- */ +- +-#include +-#if !defined (__Userspace_os_Windows) +-#include +-#include +-#include +-#endif +-#include +-#include +-#include +-#include +-#include +-#include +-#include "netinet/sctp_callout.h" +- +-/* This is the polling time of callqueue in milliseconds +- * 10ms seems to work well. 1ms was giving erratic behavior +- */ +-#define TIMEOUT_INTERVAL 10 +- +-extern int ticks; +- +-void * +-user_sctp_timer_iterate(void *arg) +-{ +- sctp_os_timer_t *c; +- void (*c_func)(void *); +- void *c_arg; +- sctp_os_timer_t *sctp_os_timer_next; +- /* +- * The MSEC_TO_TICKS conversion depends on hz. The to_ticks in +- * sctp_os_timer_start also depends on hz. E.g. if hz=1000 then +- * for multiple INIT the to_ticks is 2000, 4000, 8000, 16000, 32000, 60000 +- * and further to_ticks level off at 60000 i.e. 60 seconds. +- * If hz=100 then for multiple INIT the to_ticks are 200, 400, 800 and so-on. +- */ +- for (;;) { +-#if defined (__Userspace_os_Windows) +- Sleep(TIMEOUT_INTERVAL); +-#else +- struct timeval timeout; +- +- timeout.tv_sec = 0; +- timeout.tv_usec = 1000 * TIMEOUT_INTERVAL; +- select(0, NULL, NULL, NULL, &timeout); +-#endif +- if (SCTP_BASE_VAR(timer_thread_should_exit)) { +- break; +- } +- SCTP_TIMERQ_LOCK(); +- /* update our tick count */ +- ticks += MSEC_TO_TICKS(TIMEOUT_INTERVAL); +- c = TAILQ_FIRST(&SCTP_BASE_INFO(callqueue)); +- while (c) { +- if (c->c_time <= ticks) { +- sctp_os_timer_next = TAILQ_NEXT(c, tqe); +- TAILQ_REMOVE(&SCTP_BASE_INFO(callqueue), c, tqe); +- c_func = c->c_func; +- c_arg = c->c_arg; +- c->c_flags &= ~SCTP_CALLOUT_PENDING; +- SCTP_TIMERQ_UNLOCK(); +- c_func(c_arg); +- SCTP_TIMERQ_LOCK(); +- c = sctp_os_timer_next; +- } else { +- c = TAILQ_NEXT(c, tqe); +- } +- } +- SCTP_TIMERQ_UNLOCK(); +- } +- return (NULL); +-} +- +-void +-sctp_start_timer(void) +-{ +- /* +- * No need to do SCTP_TIMERQ_LOCK_INIT(); +- * here, it is being done in sctp_pcb_init() +- */ +-#if defined (__Userspace_os_Windows) +- if ((SCTP_BASE_VAR(timer_thread) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)user_sctp_timer_iterate, NULL, 0, NULL)) == NULL) { +- SCTP_PRINTF("ERROR; Creating ithread failed\n"); +- } +-#else +- int rc; +- +- rc = pthread_create(&SCTP_BASE_VAR(timer_thread), NULL, user_sctp_timer_iterate, NULL); +- if (rc) { +- SCTP_PRINTF("ERROR; return code from pthread_create() is %d\n", rc); +- } +-#endif +-} diff --git a/gnu/packages/patches/icecat-CVE-2014-1587-bug-1089207.patch b/gnu/packages/patches/icecat-CVE-2014-1587-bug-1089207.patch new file mode 100644 index 0000000000..cd5602c86b --- /dev/null +++ b/gnu/packages/patches/icecat-CVE-2014-1587-bug-1089207.patch @@ -0,0 +1,119 @@ +commit 9df10fea93b483af6646ef2f7aab35598fbaab2f +Author: Nils Ohlmeier [:drno] +Date: Thu Nov 6 12:21:57 2014 -0500 + + Bug 1089207: fix parsing of invalid fmtp att r=drno,jesup a=lmandel + + Modified media/webrtc/signaling/src/sipcc/core/sdp/sdp_attr.c +diff --git a/media/webrtc/signaling/src/sipcc/core/sdp/sdp_attr.c b/media/webrtc/signaling/src/sipcc/core/sdp/sdp_attr.c +index fa5ca2e..33d26c0 100644 +--- a/media/webrtc/signaling/src/sipcc/core/sdp/sdp_attr.c ++++ b/media/webrtc/signaling/src/sipcc/core/sdp/sdp_attr.c +@@ -458,7 +458,6 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t *sdp_p, sdp_attr_t *attr_p, + char tmp[SDP_MAX_STRING_LEN]; + char *src_ptr; + char *temp_ptr = NULL; +- tinybool flag=FALSE; + char *tok=NULL; + char *temp=NULL; + u16 custom_x=0; +@@ -495,29 +494,11 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t *sdp_p, sdp_attr_t *attr_p, + fmtp_p->packetization_mode = 0; + fmtp_p->level_asymmetry_allowed = SDP_DEFAULT_LEVEL_ASYMMETRY_ALLOWED_VALUE; + +- /* BEGIN - a typical macro fn to replace '/' with ';' from fmtp line*/ +- /* This ugly replacement of '/' with ';' is only done because +- * econf/MS client sends in this wierd /illegal format. +- * fmtp parameters MUST be separated by ';' +- */ + temp_ptr = cpr_strdup(ptr); + if (temp_ptr == NULL) { + return (SDP_FAILURE); + } + fmtp_ptr = src_ptr = temp_ptr; +- while (flag == FALSE) { +- if (*src_ptr == '\n') { +- flag = TRUE; +- break; +- } +- if (*src_ptr == '/') { +- *src_ptr =';' ; +- } +- src_ptr++; +- } +- /* END */ +- /* Once we move to RFC compliant video codec implementations, the above +- * patch should be removed */ + + src_ptr = temp_ptr; + while (!done) { + Modified media/webrtc/signaling/src/sipcc/core/sdp/sdp_main.c +diff --git a/media/webrtc/signaling/src/sipcc/core/sdp/sdp_main.c b/media/webrtc/signaling/src/sipcc/core/sdp/sdp_main.c +index 0be02aa..9760d4e 100644 +--- a/media/webrtc/signaling/src/sipcc/core/sdp/sdp_main.c ++++ b/media/webrtc/signaling/src/sipcc/core/sdp/sdp_main.c +@@ -1002,7 +1002,12 @@ sdp_result_e sdp_parse (sdp_t *sdp_p, char **bufp, u16 len) + */ + ptr = next_ptr; + line_end = sdp_findchar(ptr, "\n"); +- if (line_end >= (*bufp + len)) { ++ if ((line_end >= (*bufp + len)) || ++ (*line_end == '\0')) { ++ /* As this does not update the result value the SDP up to this point ++ * is still accept as valid. So encountering this is not treated as ++ * an error. ++ */ + sdp_parse_error(sdp_p->peerconnection, + "%s End of line beyond end of buffer.", + sdp_p->debug_str); + Modified media/webrtc/signaling/test/sdp_unittests.cpp +diff --git a/media/webrtc/signaling/test/sdp_unittests.cpp b/media/webrtc/signaling/test/sdp_unittests.cpp +index 51df09b..9f98eed 100644 +--- a/media/webrtc/signaling/test/sdp_unittests.cpp ++++ b/media/webrtc/signaling/test/sdp_unittests.cpp +@@ -755,13 +755,13 @@ TEST_F(SdpTest, parseFmtpMaxFs) { + u32 val = 0; + ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n"); + ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS); +- ASSERT_EQ(val, 300); ++ ASSERT_EQ(val, 300U); + } + TEST_F(SdpTest, parseFmtpMaxFr) { + u32 val = 0; + ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n"); + ASSERT_EQ(sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS); +- ASSERT_EQ(val, 30); ++ ASSERT_EQ(val, 30U); + } + + TEST_F(SdpTest, addFmtpMaxFs) { +@@ -789,6 +789,29 @@ TEST_F(SdpTest, addFmtpMaxFsFr) { + std::string::npos); + } + ++static const std::string kBrokenFmtp = ++ "v=0\r\n" ++ "o=- 137331303 2 IN IP4 127.0.0.1\r\n" ++ "s=SIP Call\r\n" ++ "t=0 0\r\n" ++ "m=video 56436 RTP/SAVPF 120\r\n" ++ "c=IN IP4 198.51.100.7\r\n" ++ "a=rtpmap:120 VP8/90000\r\n" ++ /* Note: the \0 in this string triggered bz://1089207 ++ */ ++ "a=fmtp:120 max-fs=300;max\0fr=30"; ++ ++TEST_F(SdpTest, parseBrokenFmtp) { ++ u32 val = 0; ++ char *buf = const_cast(kBrokenFmtp.data()); ++ ResetSdp(); ++ /* We need to manually invoke the parser here to be able to specify the length ++ * of the string beyond the \0 in last line of the string. ++ */ ++ ASSERT_EQ(sdp_parse(sdp_ptr_, &buf, 165), SDP_SUCCESS); ++ ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val), SDP_INVALID_PARAMETER); ++} ++ + } // End namespace test. + + int main(int argc, char **argv) { diff --git a/gnu/packages/patches/icecat-CVE-2014-1590.patch b/gnu/packages/patches/icecat-CVE-2014-1590.patch new file mode 100644 index 0000000000..f8513980ad --- /dev/null +++ b/gnu/packages/patches/icecat-CVE-2014-1590.patch @@ -0,0 +1,33 @@ +commit 50c5ca4bacf7cda77c3a7ab1b8d82ded18fb3355 +Author: Olli Pettay +Date: Sun Nov 2 22:01:55 2014 +0200 + + Bug 1087633 - Filter out XPConnect wrapped input streams. r=bz, a=lmandel + + Modified content/base/src/nsXMLHttpRequest.h +diff --git a/content/base/src/nsXMLHttpRequest.h b/content/base/src/nsXMLHttpRequest.h +index b1fc4e3..4ab4f29 100644 +--- a/content/base/src/nsXMLHttpRequest.h ++++ b/content/base/src/nsXMLHttpRequest.h +@@ -28,7 +28,8 @@ + #include "nsIPrincipal.h" + #include "nsIScriptObjectPrincipal.h" + #include "nsISizeOfEventTarget.h" +- ++#include "nsIXPConnect.h" ++#include "nsIInputStream.h" + #include "mozilla/Assertions.h" + #include "mozilla/DOMEventTargetHelper.h" + #include "mozilla/MemoryReporting.h" +@@ -446,6 +447,11 @@ public: + void Send(nsIInputStream* aStream, ErrorResult& aRv) + { + NS_ASSERTION(aStream, "Null should go to string version"); ++ nsCOMPtr wjs = do_QueryInterface(aStream); ++ if (wjs) { ++ aRv.Throw(NS_ERROR_DOM_TYPE_ERR); ++ return; ++ } + aRv = Send(RequestBody(aStream)); + } + void SendAsBinary(const nsAString& aBody, ErrorResult& aRv); diff --git a/gnu/packages/patches/icecat-CVE-2014-1592.patch b/gnu/packages/patches/icecat-CVE-2014-1592.patch new file mode 100644 index 0000000000..6de1b6fe4a --- /dev/null +++ b/gnu/packages/patches/icecat-CVE-2014-1592.patch @@ -0,0 +1,400 @@ +commit 7efadbb03cdffa11ebfc2da3113377d2f33b893b +Author: Henri Sivonen +Date: Mon Nov 3 15:23:26 2014 +0200 + + Bug 1088635. r=smaug, a=bkerensa + + Modified content/base/src/nsDocument.cpp +diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp +index cbed38d..3493bce 100644 +--- a/content/base/src/nsDocument.cpp ++++ b/content/base/src/nsDocument.cpp +@@ -3916,7 +3916,7 @@ nsDocument::InsertChildAt(nsIContent* aKid, uint32_t aIndex, + bool aNotify) + { + if (aKid->IsElement() && GetRootElement()) { +- NS_ERROR("Inserting element child when we already have one"); ++ NS_WARNING("Inserting root element when we already have one"); + return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; + } + + Modified parser/html/nsHtml5Parser.cpp +diff --git a/parser/html/nsHtml5Parser.cpp b/parser/html/nsHtml5Parser.cpp +index a485be4..f28adb4 100644 +--- a/parser/html/nsHtml5Parser.cpp ++++ b/parser/html/nsHtml5Parser.cpp +@@ -237,7 +237,8 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer, + * WillBuildModel to be called before the document has had its + * script global object set. + */ +- mExecutor->WillBuildModel(eDTDMode_unknown); ++ rv = mExecutor->WillBuildModel(eDTDMode_unknown); ++ NS_ENSURE_SUCCESS(rv, rv); + } + + // Return early if the parser has processed EOF +@@ -255,7 +256,7 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer, + } + mDocumentClosed = true; + if (!mBlocked && !mInDocumentWrite) { +- ParseUntilBlocked(); ++ return ParseUntilBlocked(); + } + return NS_OK; + } +@@ -378,7 +379,8 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer, + + if (mTreeBuilder->HasScript()) { + mTreeBuilder->Flush(); // Move ops to the executor +- mExecutor->FlushDocumentWrite(); // run the ops ++ rv = mExecutor->FlushDocumentWrite(); // run the ops ++ NS_ENSURE_SUCCESS(rv, rv); + // Flushing tree ops can cause all sorts of things. + // Return early if the parser got terminated. + if (mExecutor->IsComplete()) { +@@ -437,7 +439,8 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer, + "Buffer wasn't tokenized to completion?"); + // Scripting semantics require a forced tree builder flush here + mTreeBuilder->Flush(); // Move ops to the executor +- mExecutor->FlushDocumentWrite(); // run the ops ++ rv = mExecutor->FlushDocumentWrite(); // run the ops ++ NS_ENSURE_SUCCESS(rv, rv); + } else if (stackBuffer.hasMore()) { + // The buffer wasn't tokenized to completion. Tokenize the untokenized + // content in order to preload stuff. This content will be retokenized +@@ -594,11 +597,13 @@ nsHtml5Parser::IsScriptCreated() + /* End nsIParser */ + + // not from interface +-void ++nsresult + nsHtml5Parser::ParseUntilBlocked() + { +- if (mBlocked || mExecutor->IsComplete() || NS_FAILED(mExecutor->IsBroken())) { +- return; ++ nsresult rv = mExecutor->IsBroken(); ++ NS_ENSURE_SUCCESS(rv, rv); ++ if (mBlocked || mExecutor->IsComplete()) { ++ return NS_OK; + } + NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle."); + NS_ASSERTION(!mInDocumentWrite, +@@ -611,7 +616,7 @@ nsHtml5Parser::ParseUntilBlocked() + if (mFirstBuffer == mLastBuffer) { + if (mExecutor->IsComplete()) { + // something like cache manisfests stopped the parse in mid-flight +- return; ++ return NS_OK; + } + if (mDocumentClosed) { + NS_ASSERTION(!GetStreamParser(), +@@ -620,8 +625,10 @@ nsHtml5Parser::ParseUntilBlocked() + mTreeBuilder->StreamEnded(); + mTreeBuilder->Flush(); + mExecutor->FlushDocumentWrite(); ++ // The below call does memory cleanup, so call it even if the ++ // parser has been marked as broken. + mTokenizer->end(); +- return; ++ return NS_OK; + } + // never release the last buffer. + NS_ASSERTION(!mLastBuffer->getStart() && !mLastBuffer->getEnd(), +@@ -643,14 +650,14 @@ nsHtml5Parser::ParseUntilBlocked() + NS_ASSERTION(mExecutor->IsInFlushLoop(), + "How did we come here without being in the flush loop?"); + } +- return; // no more data for now but expecting more ++ return NS_OK; // no more data for now but expecting more + } + mFirstBuffer = mFirstBuffer->next; + continue; + } + + if (mBlocked || mExecutor->IsComplete()) { +- return; ++ return NS_OK; + } + + // now we have a non-empty buffer +@@ -667,10 +674,11 @@ nsHtml5Parser::ParseUntilBlocked() + } + if (mTreeBuilder->HasScript()) { + mTreeBuilder->Flush(); +- mExecutor->FlushDocumentWrite(); ++ nsresult rv = mExecutor->FlushDocumentWrite(); ++ NS_ENSURE_SUCCESS(rv, rv); + } + if (mBlocked) { +- return; ++ return NS_OK; + } + } + continue; + Modified parser/html/nsHtml5Parser.h +diff --git a/parser/html/nsHtml5Parser.h b/parser/html/nsHtml5Parser.h +index aff79c7..e2ef2f8 100644 +--- a/parser/html/nsHtml5Parser.h ++++ b/parser/html/nsHtml5Parser.h +@@ -262,7 +262,7 @@ class nsHtml5Parser : public nsIParser, + /** + * Parse until pending data is exhausted or a script blocks the parser + */ +- void ParseUntilBlocked(); ++ nsresult ParseUntilBlocked(); + + private: + + Modified parser/html/nsHtml5StreamParser.cpp +diff --git a/parser/html/nsHtml5StreamParser.cpp b/parser/html/nsHtml5StreamParser.cpp +index 4790568..7e3917b 100644 +--- a/parser/html/nsHtml5StreamParser.cpp ++++ b/parser/html/nsHtml5StreamParser.cpp +@@ -796,7 +796,7 @@ nsHtml5StreamParser::WriteStreamBytes(const uint8_t* aFromSegment, + // NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE. + if (!mLastBuffer) { + NS_WARNING("mLastBuffer should not be null!"); +- MarkAsBroken(); ++ MarkAsBroken(NS_ERROR_NULL_POINTER); + return NS_ERROR_NULL_POINTER; + } + if (mLastBuffer->getEnd() == NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE) { +@@ -902,7 +902,8 @@ nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) + * WillBuildModel to be called before the document has had its + * script global object set. + */ +- mExecutor->WillBuildModel(eDTDMode_unknown); ++ rv = mExecutor->WillBuildModel(eDTDMode_unknown); ++ NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr newBuf = + nsHtml5OwningUTF16Buffer::FalliblyCreate( +@@ -1003,8 +1004,9 @@ nsHtml5StreamParser::DoStopRequest() + + if (!mUnicodeDecoder) { + uint32_t writeCount; +- if (NS_FAILED(FinalizeSniffing(nullptr, 0, &writeCount, 0))) { +- MarkAsBroken(); ++ nsresult rv; ++ if (NS_FAILED(rv = FinalizeSniffing(nullptr, 0, &writeCount, 0))) { ++ MarkAsBroken(rv); + return; + } + } else if (mFeedChardet) { +@@ -1076,7 +1078,7 @@ nsHtml5StreamParser::DoDataAvailable(const uint8_t* aBuffer, uint32_t aLength) + rv = SniffStreamBytes(aBuffer, aLength, &writeCount); + } + if (NS_FAILED(rv)) { +- MarkAsBroken(); ++ MarkAsBroken(rv); + return; + } + NS_ASSERTION(writeCount == aLength, "Wrong number of stream bytes written/sniffed."); +@@ -1662,13 +1664,13 @@ nsHtml5StreamParser::TimerFlush() + } + + void +-nsHtml5StreamParser::MarkAsBroken() ++nsHtml5StreamParser::MarkAsBroken(nsresult aRv) + { + NS_ASSERTION(IsParserThread(), "Wrong thread!"); + mTokenizerMutex.AssertCurrentThreadOwns(); + + Terminate(); +- mTreeBuilder->MarkAsBroken(); ++ mTreeBuilder->MarkAsBroken(aRv); + mozilla::DebugOnly hadOps = mTreeBuilder->Flush(false); + NS_ASSERTION(hadOps, "Should have had the markAsBroken op!"); + if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) { + Modified parser/html/nsHtml5StreamParser.h +diff --git a/parser/html/nsHtml5StreamParser.h b/parser/html/nsHtml5StreamParser.h +index c7dcbbe..476ef16 100644 +--- a/parser/html/nsHtml5StreamParser.h ++++ b/parser/html/nsHtml5StreamParser.h +@@ -218,7 +218,7 @@ class nsHtml5StreamParser : public nsICharsetDetectionObserver { + } + #endif + +- void MarkAsBroken(); ++ void MarkAsBroken(nsresult aRv); + + /** + * Marks the stream parser as interrupted. If you ever add calls to this + Modified parser/html/nsHtml5TreeBuilderCppSupplement.h +diff --git a/parser/html/nsHtml5TreeBuilderCppSupplement.h b/parser/html/nsHtml5TreeBuilderCppSupplement.h +index 4cd5c7c..1e65394 100644 +--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h ++++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h +@@ -949,14 +949,14 @@ nsHtml5TreeBuilder::DropHandles() + } + + void +-nsHtml5TreeBuilder::MarkAsBroken() ++nsHtml5TreeBuilder::MarkAsBroken(nsresult aRv) + { + if (MOZ_UNLIKELY(mBuilder)) { + MOZ_ASSUME_UNREACHABLE("Must not call this with builder."); + return; + } + mOpQueue.Clear(); // Previous ops don't matter anymore +- mOpQueue.AppendElement()->Init(eTreeOpMarkAsBroken); ++ mOpQueue.AppendElement()->Init(aRv); + } + + void + Modified parser/html/nsHtml5TreeBuilderHSupplement.h +diff --git a/parser/html/nsHtml5TreeBuilderHSupplement.h b/parser/html/nsHtml5TreeBuilderHSupplement.h +index a321e80..8d380eb 100644 +--- a/parser/html/nsHtml5TreeBuilderHSupplement.h ++++ b/parser/html/nsHtml5TreeBuilderHSupplement.h +@@ -223,4 +223,4 @@ + + void errEndWithUnclosedElements(nsIAtom* aName); + +- void MarkAsBroken(); ++ void MarkAsBroken(nsresult aRv); + Modified parser/html/nsHtml5TreeOpExecutor.cpp +diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp +index ebcafca..6c52e5f 100644 +--- a/parser/html/nsHtml5TreeOpExecutor.cpp ++++ b/parser/html/nsHtml5TreeOpExecutor.cpp +@@ -411,7 +411,11 @@ nsHtml5TreeOpExecutor::RunFlushLoop() + GetParser()->GetStreamParser(); + // Now parse content left in the document.write() buffer queue if any. + // This may generate tree ops on its own or dequeue a speculation. +- GetParser()->ParseUntilBlocked(); ++ nsresult rv = GetParser()->ParseUntilBlocked(); ++ if (NS_FAILED(rv)) { ++ MarkAsBroken(rv); ++ return; ++ } + } + + if (mOpQueue.IsEmpty()) { +@@ -496,21 +500,24 @@ nsHtml5TreeOpExecutor::RunFlushLoop() + } + } + +-void ++nsresult + nsHtml5TreeOpExecutor::FlushDocumentWrite() + { ++ nsresult rv = IsBroken(); ++ NS_ENSURE_SUCCESS(rv, rv); ++ + FlushSpeculativeLoads(); // Make sure speculative loads never start after the + // corresponding normal loads for the same URLs. + + if (MOZ_UNLIKELY(!mParser)) { + // The parse has ended. + mOpQueue.Clear(); // clear in order to be able to assert in destructor +- return; ++ return rv; + } + + if (mFlushState != eNotFlushing) { + // XXX Can this happen? In case it can, let's avoid crashing. +- return; ++ return rv; + } + + mFlushState = eInFlush; +@@ -545,7 +552,7 @@ nsHtml5TreeOpExecutor::FlushDocumentWrite() + } + NS_ASSERTION(mFlushState == eInDocUpdate, + "Tried to perform tree op outside update batch."); +- nsresult rv = iter->Perform(this, &scriptElement); ++ rv = iter->Perform(this, &scriptElement); + if (NS_FAILED(rv)) { + MarkAsBroken(rv); + break; +@@ -560,13 +567,14 @@ nsHtml5TreeOpExecutor::FlushDocumentWrite() + + if (MOZ_UNLIKELY(!mParser)) { + // Ending the doc update caused a call to nsIParser::Terminate(). +- return; ++ return rv; + } + + if (scriptElement) { + // must be tail call when mFlushState is eNotFlushing + RunScript(scriptElement); + } ++ return rv; + } + + // copied from HTML content sink + Modified parser/html/nsHtml5TreeOpExecutor.h +diff --git a/parser/html/nsHtml5TreeOpExecutor.h b/parser/html/nsHtml5TreeOpExecutor.h +index 9617dcb..1f81448 100644 +--- a/parser/html/nsHtml5TreeOpExecutor.h ++++ b/parser/html/nsHtml5TreeOpExecutor.h +@@ -173,7 +173,7 @@ class nsHtml5TreeOpExecutor : public nsHtml5DocumentBuilder, + + void RunFlushLoop(); + +- void FlushDocumentWrite(); ++ nsresult FlushDocumentWrite(); + + void MaybeSuspend(); + + Modified parser/html/nsHtml5TreeOperation.cpp +diff --git a/parser/html/nsHtml5TreeOperation.cpp b/parser/html/nsHtml5TreeOperation.cpp +index 48b71dc..7ad65247 100644 +--- a/parser/html/nsHtml5TreeOperation.cpp ++++ b/parser/html/nsHtml5TreeOperation.cpp +@@ -214,6 +214,9 @@ nsHtml5TreeOperation::AppendToDocument(nsIContent* aNode, + nsIDocument* doc = aBuilder->GetDocument(); + uint32_t childCount = doc->GetChildCount(); + rv = doc->AppendChildTo(aNode, false); ++ if (rv == NS_ERROR_DOM_HIERARCHY_REQUEST_ERR) { ++ return NS_OK; ++ } + NS_ENSURE_SUCCESS(rv, rv); + nsNodeUtils::ContentInserted(doc, aNode, childCount); + +@@ -739,8 +742,7 @@ nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder, + return NS_OK; + } + case eTreeOpMarkAsBroken: { +- aBuilder->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); +- return NS_OK; ++ return mOne.result; + } + case eTreeOpRunScript: { + nsIContent* node = *(mOne.node); + Modified parser/html/nsHtml5TreeOperation.h +diff --git a/parser/html/nsHtml5TreeOperation.h b/parser/html/nsHtml5TreeOperation.h +index 2727733..06d0274 100644 +--- a/parser/html/nsHtml5TreeOperation.h ++++ b/parser/html/nsHtml5TreeOperation.h +@@ -435,6 +435,15 @@ class nsHtml5TreeOperation { + mFour.integer = aInt; + } + ++ inline void Init(nsresult aRv) ++ { ++ NS_PRECONDITION(mOpCode == eTreeOpUninitialized, ++ "Op code must be uninitialized when initializing."); ++ NS_PRECONDITION(NS_FAILED(aRv), "Initialized tree op with non-failure."); ++ mOpCode = eTreeOpMarkAsBroken; ++ mOne.result = aRv; ++ } ++ + inline void InitAddClass(nsIContentHandle* aNode, const char16_t* aClass) + { + NS_PRECONDITION(mOpCode == eTreeOpUninitialized, +@@ -487,11 +496,12 @@ class nsHtml5TreeOperation { + nsIAtom* atom; + nsHtml5HtmlAttributes* attributes; + nsHtml5DocumentMode mode; +- char16_t* unicharPtr; ++ char16_t* unicharPtr; + char* charPtr; + nsHtml5TreeOperationStringPair* stringPair; + nsAHtml5TreeBuilderState* state; + int32_t integer; ++ nsresult result; + } mOne, mTwo, mThree, mFour; + }; + diff --git a/gnu/packages/patches/icecat-CVE-2014-1593.patch b/gnu/packages/patches/icecat-CVE-2014-1593.patch new file mode 100644 index 0000000000..446920a95f --- /dev/null +++ b/gnu/packages/patches/icecat-CVE-2014-1593.patch @@ -0,0 +1,154 @@ +commit a58cea744ac5b93b99a66554e1029b2c7aa3255d +Author: Matthew Gregan +Date: Tue Nov 11 08:58:52 2014 +1300 + + Bug 1085175. r=roc, a=dveditz + + Modified content/media/MediaCache.cpp +diff --git a/content/media/MediaCache.cpp b/content/media/MediaCache.cpp +index 598d905..c99f724 100644 +--- a/content/media/MediaCache.cpp ++++ b/content/media/MediaCache.cpp +@@ -1174,6 +1174,7 @@ MediaCache::Update() + // Figure out where we should be reading from. It's the first + // uncached byte after the current mStreamOffset. + int64_t dataOffset = stream->GetCachedDataEndInternal(stream->mStreamOffset); ++ MOZ_ASSERT(dataOffset >= 0); + + // Compute where we'd actually seek to to read at readOffset + int64_t desiredOffset = dataOffset; +@@ -1702,6 +1703,7 @@ MediaCacheStream::NotifyDataStarted(int64_t aOffset) + ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor()); + NS_WARN_IF_FALSE(aOffset == mChannelOffset, + "Server is giving us unexpected offset"); ++ MOZ_ASSERT(aOffset >= 0); + mChannelOffset = aOffset; + if (mStreamLength >= 0) { + // If we started reading at a certain offset, then for sure +@@ -2118,23 +2120,28 @@ MediaCacheStream::Seek(int32_t aWhence, int64_t aOffset) + return NS_ERROR_FAILURE; + + int64_t oldOffset = mStreamOffset; ++ int64_t newOffset = mStreamOffset; + switch (aWhence) { + case PR_SEEK_END: + if (mStreamLength < 0) + return NS_ERROR_FAILURE; +- mStreamOffset = mStreamLength + aOffset; ++ newOffset = mStreamLength + aOffset; + break; + case PR_SEEK_CUR: +- mStreamOffset += aOffset; ++ newOffset += aOffset; + break; + case PR_SEEK_SET: +- mStreamOffset = aOffset; ++ newOffset = aOffset; + break; + default: + NS_ERROR("Unknown whence"); + return NS_ERROR_FAILURE; + } + ++ if (newOffset < 0) ++ return NS_ERROR_FAILURE; ++ mStreamOffset = newOffset; ++ + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p Seek to %lld", this, (long long)mStreamOffset)); + gMediaCache->NoteSeek(this, oldOffset); + +@@ -2176,11 +2183,10 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) + break; + } + size = std::min(size, bytesRemaining); +- // Clamp size until 64-bit file size issues (bug 500784) are fixed. ++ // Clamp size until 64-bit file size issues are fixed. + size = std::min(size, int64_t(INT32_MAX)); + } + +- int32_t bytes; + int32_t cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1; + if (cacheBlock < 0) { + // We don't have a complete cached block here. +@@ -2208,7 +2214,10 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) + // We can just use the data in mPartialBlockBuffer. In fact we should + // use it rather than waiting for the block to fill and land in + // the cache. +- bytes = std::min(size, streamWithPartialBlock->mChannelOffset - mStreamOffset); ++ int64_t bytes = std::min(size, streamWithPartialBlock->mChannelOffset - mStreamOffset); ++ // Clamp bytes until 64-bit file size issues are fixed. ++ bytes = std::min(bytes, int64_t(INT32_MAX)); ++ NS_ABORT_IF_FALSE(bytes >= 0 && bytes <= aCount, "Bytes out of range."); + memcpy(aBuffer, + reinterpret_cast(streamWithPartialBlock->mPartialBlockBuffer.get()) + offsetInStreamBlock, bytes); + if (mCurrentMode == MODE_METADATA) { +@@ -2232,6 +2241,7 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) + gMediaCache->NoteBlockUsage(this, cacheBlock, mCurrentMode, TimeStamp::Now()); + + int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock; ++ int32_t bytes; + NS_ABORT_IF_FALSE(size >= 0 && size <= INT32_MAX, "Size out of range."); + nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, int32_t(size), &bytes); + if (NS_FAILED(rv)) { +@@ -2268,9 +2278,7 @@ MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer, + } + + nsresult +-MediaCacheStream::ReadFromCache(char* aBuffer, +- int64_t aOffset, +- int64_t aCount) ++MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, int64_t aCount) + { + ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor()); + if (mClosed) +@@ -2292,7 +2300,7 @@ MediaCacheStream::ReadFromCache(char* aBuffer, + return NS_ERROR_FAILURE; + } + size = std::min(size, bytesRemaining); +- // Clamp size until 64-bit file size issues (bug 500784) are fixed. ++ // Clamp size until 64-bit file size issues are fixed. + size = std::min(size, int64_t(INT32_MAX)); + } + +@@ -2303,7 +2311,10 @@ MediaCacheStream::ReadFromCache(char* aBuffer, + // We can just use the data in mPartialBlockBuffer. In fact we should + // use it rather than waiting for the block to fill and land in + // the cache. +- bytes = std::min(size, mChannelOffset - streamOffset); ++ // Clamp bytes until 64-bit file size issues are fixed. ++ int64_t toCopy = std::min(size, mChannelOffset - streamOffset); ++ bytes = std::min(toCopy, int64_t(INT32_MAX)); ++ NS_ABORT_IF_FALSE(bytes >= 0 && bytes <= toCopy, "Bytes out of range."); + memcpy(aBuffer + count, + reinterpret_cast(mPartialBlockBuffer.get()) + offsetInStreamBlock, bytes); + } else { + Modified media/libnestegg/include/nestegg-stdint.h +diff --git a/media/libnestegg/include/nestegg-stdint.h b/media/libnestegg/include/nestegg-stdint.h +index 599a7a5..c315991 100644 +--- a/media/libnestegg/include/nestegg-stdint.h ++++ b/media/libnestegg/include/nestegg-stdint.h +@@ -1,6 +1,9 @@ + #ifdef _WIN32 + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; ++#if !defined(INT64_MAX) ++#define INT64_MAX 9223372036854775807LL ++#endif + #else + #include + #endif + Modified media/libnestegg/src/nestegg.c +diff --git a/media/libnestegg/src/nestegg.c b/media/libnestegg/src/nestegg.c +index 8813cf2..56884d7 100644 +--- a/media/libnestegg/src/nestegg.c ++++ b/media/libnestegg/src/nestegg.c +@@ -1950,6 +1950,9 @@ nestegg_offset_seek(nestegg * ctx, uint64_t offset) + { + int r; + ++ if (offset > INT64_MAX) ++ return -1; ++ + /* Seek and set up parser state for segment-level element (Cluster). */ + r = ne_io_seek(ctx->io, offset, NESTEGG_SEEK_SET); + if (r != 0) diff --git a/gnu/packages/patches/icecat-CVE-2014-1594.patch b/gnu/packages/patches/icecat-CVE-2014-1594.patch new file mode 100644 index 0000000000..e5ce7b069b --- /dev/null +++ b/gnu/packages/patches/icecat-CVE-2014-1594.patch @@ -0,0 +1,34 @@ +commit 7a8497c0df722b1ed145b99a82c71ed1f7b1d6ce +Author: Markus Stange +Date: Thu Oct 9 21:26:27 2014 -0400 + + Bug 1074280 - Use AsContainerLayer() in order to avoid a bad cast. r=roc, a=bkerensa + + Modified gfx/layers/basic/BasicLayerManager.cpp +diff --git a/gfx/layers/basic/BasicLayerManager.cpp b/gfx/layers/basic/BasicLayerManager.cpp +index 5a3a1f6..ff42bc0 100644 +--- a/gfx/layers/basic/BasicLayerManager.cpp ++++ b/gfx/layers/basic/BasicLayerManager.cpp +@@ -901,18 +901,17 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget, + RenderTraceScope trace("BasicLayerManager::PaintLayer", "707070"); + + const nsIntRect* clipRect = aLayer->GetEffectiveClipRect(); +- // aLayer might not be a container layer, but if so we take care not to use +- // the container variable +- BasicContainerLayer* container = static_cast(aLayer); +- bool needsGroup = aLayer->GetFirstChild() && ++ BasicContainerLayer* container = ++ static_cast(aLayer->AsContainerLayer()); ++ bool needsGroup = container && + container->UseIntermediateSurface(); + BasicImplData* data = ToData(aLayer); + bool needsClipToVisibleRegion = + data->GetClipToVisibleRegion() && !aLayer->AsThebesLayer(); +- NS_ASSERTION(needsGroup || !aLayer->GetFirstChild() || ++ NS_ASSERTION(needsGroup || !container || + container->GetOperator() == CompositionOp::OP_OVER, + "non-OVER operator should have forced UseIntermediateSurface"); +- NS_ASSERTION(!aLayer->GetFirstChild() || !aLayer->GetMaskLayer() || ++ NS_ASSERTION(!container || !aLayer->GetMaskLayer() || + container->UseIntermediateSurface(), + "ContainerLayer with mask layer should force UseIntermediateSurface"); diff --git a/gnu/packages/patches/xfce4-panel-plugins.patch b/gnu/packages/patches/xfce4-panel-plugins.patch new file mode 100644 index 0000000000..df5a0a914d --- /dev/null +++ b/gnu/packages/patches/xfce4-panel-plugins.patch @@ -0,0 +1,115 @@ +Search for xfce4 panel plugins in the directories specified +in XDG_DATA_DIRS and X_XFCE4_LIB_DIRS. For discussion of the +relevant issues, see: + + https://bugzilla.xfce.org/show_bug.cgi?id=5455 + +Patch by Mark H Weaver + +--- xfce4-panel-4.10.0/panel/panel-module.c.orig 2012-04-28 16:31:35.000000000 -0400 ++++ xfce4-panel-4.10.0/panel/panel-module.c 2014-12-14 01:31:55.728107386 -0500 +@@ -35,8 +35,14 @@ + #include + #include + +-#define PANEL_PLUGINS_LIB_DIR (LIBDIR G_DIR_SEPARATOR_S "panel" G_DIR_SEPARATOR_S "plugins") +-#define PANEL_PLUGINS_LIB_DIR_OLD (LIBDIR G_DIR_SEPARATOR_S "panel-plugins") ++#define PANEL_PLUGINS_LIB_DIR_TAIL (G_DIR_SEPARATOR_S "panel" G_DIR_SEPARATOR_S "plugins") ++#define PANEL_PLUGINS_LIB_DIR_TAIL_OLD (G_DIR_SEPARATOR_S "panel-plugins") ++ ++static const gchar *plugins_lib_dir_tails[] = ++{ ++ PANEL_PLUGINS_LIB_DIR_TAIL, ++ PANEL_PLUGINS_LIB_DIR_TAIL_OLD ++}; + + + typedef enum _PanelModuleRunMode PanelModuleRunMode; +@@ -335,21 +341,39 @@ + /* show a messsage if the old module path key still exists */ + g_message ("Plugin %s: The \"X-XFCE-Module-Path\" key is " + "ignored in \"%s\", the panel will look for the " +- "module in %s. See bug #5455 why this decision was made", +- name, filename, PANEL_PLUGINS_LIB_DIR); ++ "module in DIR%s for each DIR in $X_XFCE4_LIB_DIRS " ++ "(%s by default). See bug #5455 for discussion.", ++ name, filename, PANEL_PLUGINS_LIB_DIR_TAIL, LIBDIR); + } + #endif + +- path = g_module_build_path (PANEL_PLUGINS_LIB_DIR, module_name); +- found = g_file_test (path, G_FILE_TEST_EXISTS); ++ /* search for module */ ++ { ++ gchar *dirs_string; ++ gchar **dirs; ++ int i, j; ++ ++ dirs_string = (gchar *) g_getenv ("X_XFCE4_LIB_DIRS"); ++ if (!dirs_string) ++ dirs_string = LIBDIR; ++ dirs = g_strsplit (dirs_string, G_SEARCHPATH_SEPARATOR_S, 0); ++ ++ found = FALSE; ++ path = NULL; ++ ++ for (i = 0; !found && dirs[i] != NULL; i++) ++ for (j = 0; !found && j < G_N_ELEMENTS (plugins_lib_dir_tails); j++) ++ { ++ gchar *dir = g_strconcat (dirs[i], plugins_lib_dir_tails[j], NULL); ++ ++ g_free (path); ++ path = g_module_build_path (dir, module_name); ++ found = g_file_test (path, G_FILE_TEST_EXISTS); ++ g_free (dir); ++ } + +- if (!found) +- { +- /* deprecated location for module plugin directories */ +- g_free (path); +- path = g_module_build_path (PANEL_PLUGINS_LIB_DIR_OLD, module_name); +- found = g_file_test (path, G_FILE_TEST_EXISTS); +- } ++ g_strfreev (dirs); ++ } + + if (G_LIKELY (found)) + { +--- xfce4-panel-4.10.0/panel/panel-module-factory.c.orig 2012-04-28 16:31:35.000000000 -0400 ++++ xfce4-panel-4.10.0/panel/panel-module-factory.c 2014-12-13 23:55:27.439404812 -0500 +@@ -42,6 +42,11 @@ + #define PANEL_PLUGINS_DATA_DIR (DATADIR G_DIR_SEPARATOR_S "panel" G_DIR_SEPARATOR_S "plugins") + #define PANEL_PLUGINS_DATA_DIR_OLD (DATADIR G_DIR_SEPARATOR_S "panel-plugins") + ++static const gchar *plugins_data_dir_tails[] = ++{ ++ (G_DIR_SEPARATOR_S "xfce4" G_DIR_SEPARATOR_S "panel" G_DIR_SEPARATOR_S "plugins"), ++ (G_DIR_SEPARATOR_S "xfce4" G_DIR_SEPARATOR_S "panel-plugins") ++}; + + + static void panel_module_factory_finalize (GObject *object); +@@ -223,8 +228,22 @@ + panel_module_factory_load_modules (PanelModuleFactory *factory, + gboolean warn_if_known) + { ++ const gchar * const * system_data_dirs; ++ int i, j; ++ + panel_return_if_fail (PANEL_IS_MODULE_FACTORY (factory)); + ++ system_data_dirs = g_get_system_data_dirs (); ++ for (i = 0; system_data_dirs[i] != NULL; i++) ++ for (j = 0; j < G_N_ELEMENTS (plugins_data_dir_tails); j++) ++ { ++ gchar *dir; ++ ++ dir = g_strconcat (system_data_dirs[i], plugins_data_dir_tails[j], NULL); ++ panel_module_factory_load_modules_dir (factory, dir, warn_if_known); ++ g_free (dir); ++ } ++ + /* load from the new and old location */ + panel_module_factory_load_modules_dir (factory, PANEL_PLUGINS_DATA_DIR, warn_if_known); + panel_module_factory_load_modules_dir (factory, PANEL_PLUGINS_DATA_DIR_OLD, warn_if_known); diff --git a/gnu/packages/perl.scm b/gnu/packages/perl.scm index a724a1b21f..03cad3e25f 100644 --- a/gnu/packages/perl.scm +++ b/gnu/packages/perl.scm @@ -74,6 +74,27 @@ (home-page "http://www.perl.org/") (license gpl1+))) ; or "Artistic" +(define-public perl-clone + (package + (name "perl-clone") + (version "0.37") + (source (origin + (method url-fetch) + (uri (string-append "mirror://cpan/authors/id/G/GA/GARU/" + "Clone-" version ".tar.gz")) + (sha256 + (base32 + "17fdhxpzrq2nwim3zkcrz4m9gjixp0i886yz54ysrshxy3k53wnr")))) + (build-system perl-build-system) + (synopsis "Recursively copy Perl datatypes") + (description + "This module provides a clone() method which makes recursive copies of +nested hash, array, scalar and reference types, including tied variables and +objects.") + (home-page (string-append "http://search.cpan.org/~garu/" + "Clone-" version)) + (license (package-license perl)))) + (define-public perl-file-list (package (name "perl-file-list") @@ -253,6 +274,54 @@ Perlish API and none of the bloat and rarely used features of IPC::Run.") ;; licenses, any version." (license (list bsd-3 gpl3+)))) +(define-public perl-test-deep + (package + (name "perl-test-deep") + (version "0.114") + (source (origin + (method url-fetch) + (uri (string-append "mirror://cpan/authors/id/R/RJ/RJBS/" + "Test-Deep-" version ".tar.gz")) + (sha256 + (base32 + "09yr47vw7vj27sdik312x08938higcij8ybyq8k67mlccx8cpqf0")))) + (build-system perl-build-system) + (inputs `(("perl-test-tester" ,perl-test-tester) + ("perl-test-nowarnings" ,perl-test-nowarnings))) + (synopsis "Flexible deep comparison for the Test::Builder framework") + (description + "Test::Deep compares two structures by going through each level, ensuring +that the values match, that arrays and hashes have the same elements and that +references are blessed into the correct class. It also handles circular data +structures without getting caught in an infinite loop.") + (home-page (string-append "http://search.cpan.org/~rjbs/" + "Test-Deep-" version)) + (license gpl1+))) ; or "Artistic License" + +(define-public perl-test-nowarnings + (package + (name "perl-test-nowarnings") + (version "1.04") + (source (origin + (method url-fetch) + (uri (string-append "mirror://cpan/authors/id/A/AD/ADAMK/" + "Test-NoWarnings-" version ".tar.gz")) + (sha256 + (base32 + "0v385ch0hzz9naqwdw2az3zdqi15gka76pmiwlgsy6diiijmg2k3")))) + (build-system perl-build-system) + (inputs `(("perl-test-tester" ,perl-test-tester))) + (synopsis "Ensure no warnings are produced while testing") + (description + "This modules causes any warnings during testing to be captured and +stored. It automatically adds an extra test that will run when your script +ends to check that there were no warnings. If there were any warings, the +test will fail and output diagnostics of where, when and what the warning was, +including a stack trace of what was going on when it occurred.") + (home-page (string-append "http://search.cpan.org/~adamk/" + "Test-NoWarnings-" version)) + (license lgpl2.1))) + (define-public perl-test-script (package (name "perl-test-script") @@ -277,6 +346,46 @@ bin as is also commonly used) paths of your Perl distribution.") "Test-Script-" version)) (license (package-license perl)))) +(define-public perl-test-simple + (package + (name "perl-test-simple") + (version "1.001009") + (source (origin + (method url-fetch) + (uri (string-append "mirror://cpan/authors/id/E/EX/EXODIST/" + "Test-Simple-" version ".tar.gz")) + (sha256 + (base32 + "1klxpy658aj1pmrw63j1hc16gilwh5rzhp9rb2d1iydi3hcm8xb5")))) + (build-system perl-build-system) + (synopsis "Basic utilities for writing tests") + (description + "Test::Simple contains basic utilities for writing tests.") + (home-page (string-append "http://search.cpan.org/~exodist/" + "Test-Simple-" version)) + (license (package-license perl)))) + +(define-public perl-test-tester + (package + (name "perl-test-tester") + (version "0.109") + (source (origin + (method url-fetch) + (uri (string-append "mirror://cpan/authors/id/F/FD/FDALY/" + "Test-Tester-" version ".tar.gz")) + (sha256 + (base32 + "0m9n28z09kq455r5nydj1bnr85lvmbfpcbjdkjfbpmfb5xgciiyk")))) + (build-system perl-build-system) + (synopsis "Simplify running Test::Builder tests") + (description + "Test::Tester allows testing of test modules based on Test::Builder with +a minimum of effort.") + (home-page (string-append "http://search.cpan.org/~fdaly/" + "Test-Tester-" version)) + ;; "Under the same license as Perl itself" + (license (package-license perl)))) + (define-public perl-file-which (package (name "perl-file-which") diff --git a/gnu/packages/python.scm b/gnu/packages/python.scm index dc7def5507..adb84fc5b7 100644 --- a/gnu/packages/python.scm +++ b/gnu/packages/python.scm @@ -37,6 +37,7 @@ #:use-module (gnu packages openssl) #:use-module (gnu packages elf) #:use-module (gnu packages maths) + #:use-module (gnu packages ncurses) #:use-module (gnu packages gcc) #:use-module (gnu packages pkg-config) #:use-module (gnu packages databases) @@ -50,6 +51,9 @@ #:use-module (gnu packages fontutils) #:use-module (gnu packages which) #:use-module (gnu packages perl) + #:use-module (gnu packages xorg) + #:use-module (gnu packages glib) + #:use-module (gnu packages gtk) #:use-module (guix packages) #:use-module (guix download) #:use-module (guix git-download) @@ -614,6 +618,43 @@ get the local timezone information, unless you know the zoneinfo name, and under several distributions that's hard or impossible to figure out.") (license cc0))) +(define-public python-pysam + (package + (name "python-pysam") + (version "0.8.1") + (source + (origin + (method url-fetch) + (uri (string-append "https://pypi.python.org/packages/source/p/pysam/pysam-" + version ".tar.gz")) + (sha256 + (base32 + "1fb6i6hbpzxaxb62kyyp5alaidwhj40f7c6gwbhr6njzlqd5l459")))) + (build-system python-build-system) + (arguments + `(#:tests? #f ; tests are excluded in the manifest + #:phases + (alist-cons-before + 'build 'set-flags + (lambda _ + (setenv "LDFLAGS" "-lncurses") + (setenv "CFLAGS" "-D_CURSES_LIB=1")) + %standard-phases))) + (inputs + `(("python-cython" ,python-cython) + ("python-setuptools" ,python-setuptools) + ("ncurses" ,ncurses) + ("zlib" ,zlib))) + (home-page "https://github.com/pysam-developers/pysam") + (synopsis "Python bindings to the SAMtools C API") + (description + "Pysam is a Python module for reading and manipulating files in the +SAM/BAM format. Pysam is a lightweight wrapper of the SAMtools C API. It +also includes an interface for tabix.") + (license expat))) + +(define-public python2-pysam + (package-with-python2 python-pysam)) (define-public python2-pysqlite (package @@ -2117,10 +2158,35 @@ that client code uses to construct the grammar directly in Python code.") "0m6v9nwdldlwk22gcd339zg6mny5m301fxgks7z8sb8m9wawg8qp")))) (build-system python-build-system) (outputs '("out" "doc")) + (propagated-inputs ; the following packages are all needed at run time + `(("python-pyparsing" ,python-pyparsing) + ("python-pygobject" ,python-pygobject) + ("gobject-introspection" ,gobject-introspection) + ;; The 'gtk+' package (and 'gdk-pixbuf', 'atk' and 'pango' propagated + ;; from 'gtk+') provides the required 'typelib' files used by + ;; 'gobject-introspection'. The location of these files is set with the + ;; help of the environment variable GI_TYPELIB_PATH. At build time this + ;; is done automatically by a 'native-search-path' procedure. However, + ;; at run-time the user must set this variable as follows: + ;; + ;; export GI_TYPELIB_PATH=~/.guix-profile/lib/girepository-1.0 + ;; + ;; 'typelib' files include references to dynamic libraries. Currently + ;; the references do not include the full path to the libraries. For + ;; this reason the user must set the LD_LIBRARY_PATH to the location of + ;; 'libgtk-3.so.0', 'libgdk-3.so.0' and 'libatk-1.0.so.0': + ;; + ;; export LD_LIBRARY_PATH=~/.guix-profile/lib + ("gtk+" ,gtk+) + ;; From version 1.4.0 'matplotlib' makes use of 'cairocffi' instead of + ;; 'pycairo'. However, 'pygobject' makes use of a 'pycairo' 'context' + ;; object. For this reason we need to import both libraries. + ;; https://pythonhosted.org/cairocffi/cffi_api.html#converting-pycairo + ("python-pycairo" ,python-pycairo) + ("python-cairocffi" ,python-cairocffi))) (inputs `(("python-setuptools" ,python-setuptools) ("python-dateutil" ,python-dateutil-2) - ("python-pyparsing" ,python-pyparsing) ("python-six" ,python-six) ("python-pytz" ,python-pytz) ("python-numpy" ,python-numpy-bootstrap) @@ -2131,10 +2197,10 @@ that client code uses to construct the grammar directly in Python code.") ("libpng" ,libpng) ("imagemagick" ,imagemagick) ("freetype" ,freetype) + ("cairo" ,cairo) + ("glib" ,glib) + ("python-pillow" ,python-pillow) ;; FIXME: Add backends when available. - ;("python-pygtk" ,python-pygtk) - ;("python-pycairo" ,python-pycairo) - ;("python-pygobject" ,python-pygobject) ;("python-wxpython" ,python-wxpython) ;("python-pyqt" ,python-pyqt) )) @@ -2144,40 +2210,51 @@ that client code uses to construct the grammar directly in Python code.") ("texinfo" ,texinfo))) (arguments `(#:phases - (alist-cons-after - 'install 'install-doc - (lambda* (#:key outputs #:allow-other-keys) - (let* ((data (string-append (assoc-ref outputs "doc") "/share")) - (doc (string-append data "/doc/" ,name "-" ,version)) - (info (string-append data "/info")) - (html (string-append doc "/html"))) - (with-directory-excursion "doc" - ;; Without setting this variable we get an encoding error. - (setenv "LANG" "en_US.UTF-8") - ;; Produce pdf in 'A4' format. - (substitute* (find-files "." "conf\\.py") - (("latex_paper_size = 'letter'") - "latex_paper_size = 'a4'")) - (mkdir-p html) - (mkdir-p info) - ;; The doc recommends to run the 'html' target twice. - (system* "python" "make.py" "html") - (system* "python" "make.py" "html") - (system* "python" "make.py" "latex") - (system* "python" "make.py" "texinfo") - (copy-file "build/texinfo/matplotlib.info" - (string-append info "/matplotlib.info")) - (copy-file "build/latex/Matplotlib.pdf" - (string-append doc "/Matplotlib.pdf")) - (with-directory-excursion "build/html" - (map (lambda (file) - (let* ((dir (dirname file)) - (tgt-dir (string-append html "/" dir))) - (unless (equal? "." dir) - (mkdir-p tgt-dir)) - (copy-file file (string-append html "/" file)))) - (find-files "." ".*")))))) - %standard-phases))) + (alist-cons-before + 'build 'configure-environment + (lambda* (#:key outputs inputs #:allow-other-keys) + (let ((cairo (assoc-ref inputs "cairo")) + (gtk+ (assoc-ref inputs "gtk+"))) + ;; Setting these directories in the 'basedirlist' of 'setup.cfg' + ;; has not effect. + ;; + ;; FIXME: setting LD_LIBRARY_PATH should be removed once we patch + ;; gobject-introspection to include the full path of shared + ;; libraries in 'typelib' files. + (setenv "LD_LIBRARY_PATH" + (string-append cairo "/lib:" gtk+ "/lib")) + (setenv "HOME" (getcwd)) + (call-with-output-file "setup.cfg" + (lambda (port) + (format port "[rc_options]~% +backend = GTK3Agg~%"))))) + (alist-cons-after + 'install 'install-doc + (lambda* (#:key outputs #:allow-other-keys) + (let* ((data (string-append (assoc-ref outputs "doc") "/share")) + (doc (string-append data "/doc/" ,name "-" ,version)) + (info (string-append data "/info")) + (html (string-append doc "/html"))) + (with-directory-excursion "doc" + ;; Without setting this variable we get an encoding error. + (setenv "LANG" "en_US.UTF-8") + ;; Produce pdf in 'A4' format. + (substitute* (find-files "." "conf\\.py") + (("latex_paper_size = 'letter'") + "latex_paper_size = 'a4'")) + (mkdir-p html) + (mkdir-p info) + ;; The doc recommends to run the 'html' target twice. + (system* "python" "make.py" "html") + (system* "python" "make.py" "html") + (system* "python" "make.py" "latex") + (system* "python" "make.py" "texinfo") + (copy-file "build/texinfo/matplotlib.info" + (string-append info "/matplotlib.info")) + (copy-file "build/latex/Matplotlib.pdf" + (string-append doc "/Matplotlib.pdf")) + (copy-recursively "build/html" html)))) + %standard-phases)))) (home-page "http://matplotlib.org") (synopsis "2D plotting library for Python") (description @@ -2193,9 +2270,17 @@ toolkits.") (package (inherit matplotlib) ;; Make sure we use exactly PYTHON2-NUMPYDOC, which is ;; customized for Python 2. - (inputs `(("python2-numpydoc" ,python2-numpydoc) - ,@(alist-delete "python-numpydoc" - (package-inputs matplotlib))))))) + (propagated-inputs + `(("python2-py2cairo" ,python2-py2cairo) + ("python2-pygobject-2" ,python2-pygobject-2) + ,@(alist-delete "python-pycairo" + (alist-delete "python-pygobject" + (package-propagated-inputs + matplotlib))))) + (inputs + `(("python2-numpydoc" ,python2-numpydoc) + ,@(alist-delete "python-numpydoc" + (package-inputs matplotlib))))))) ;; Scipy 0.14.0 with Numpy 0.19.X fails several tests. This is known and ;; planned to be fixed in 0.14.1. It is claimed that the failures can safely @@ -2542,3 +2627,102 @@ a front-end for C compilers or analysis tools.") (define-public python2-cffi (package-with-python2 python-cffi)) + +(define-public python-xcffib + (package + (name "python-xcffib") + (version "0.1.9") + (source + (origin + (method url-fetch) + (uri (string-append "https://pypi.python.org/packages/source/x/" + "xcffib/xcffib-" version ".tar.gz")) + (sha256 + (base32 + "0655hzxv57h1a9ja9kwp0ichbkhf3djw32k33d66xp0q37dq2y81")))) + (build-system python-build-system) + (inputs + `(("libxcb" ,libxcb) + ("python-six" ,python-six))) + (native-inputs + `(("python-setuptools" ,python-setuptools))) + (propagated-inputs + `(("python-cffi" ,python-cffi))) ; used at run time + (arguments + `(#:phases + (alist-cons-after + 'install 'install-doc + (lambda* (#:key outputs #:allow-other-keys) + (let ((doc (string-append (assoc-ref outputs "out") "/share" + "/doc/" ,name "-" ,version))) + (mkdir-p doc) + (copy-file "README.md" + (string-append doc "/README.md")))) + %standard-phases))) + (home-page "https://github.com/tych0/xcffib") + (synopsis "XCB Python bindings") + (description + "Xcffib is a replacement for xpyb, an XCB Python bindings. It adds +support for Python 3 and PyPy. It is based on cffi.") + (license expat))) + +(define-public python2-xcffib + (package-with-python2 python-xcffib)) + +(define-public python-cairocffi + (package + (name "python-cairocffi") + (version "0.6") + (source + (origin + (method url-fetch) + ;; The archive on pypi is missing the 'utils' directory! + (uri (string-append "https://github.com/SimonSapin/cairocffi/archive/v" + version ".tar.gz")) + (sha256 + (base32 + "03w5p62sp3nqiccx864sbq0jvh7946277jqx3rcc3dch5xwfvv51")))) + (build-system python-build-system) + (outputs '("out" "doc")) + (inputs + `(("gdk-pixbuf" ,gdk-pixbuf) + ("cairo" ,cairo))) + (native-inputs + `(("pkg-config" ,pkg-config) + ("python-sphinx" ,python-sphinx) + ("python-docutils" ,python-docutils) + ("python-setuptools" ,python-setuptools))) + (propagated-inputs + `(("python-xcffib" ,python-xcffib))) ; used at run time + (arguments + `(#:phases + (alist-cons-after + 'install 'install-doc + (lambda* (#:key inputs outputs #:allow-other-keys) + (let* ((data (string-append (assoc-ref outputs "doc") "/share")) + (doc (string-append data "/doc/" ,name "-" ,version)) + (html (string-append doc "/html"))) + (setenv "LD_LIBRARY_PATH" + (string-append (assoc-ref inputs "cairo") "/lib" ":" + (assoc-ref inputs "gdk-pixbuf") "/lib")) + (setenv "LANG" "en_US.UTF-8") + (mkdir-p html) + (for-each (lambda (file) + (copy-file (string-append "." file) + (string-append doc file))) + '("/README.rst" "/CHANGES" "/LICENSE")) + (system* "python" "setup.py" "build_sphinx") + (copy-recursively "docs/_build/html" html))) + %standard-phases))) + (home-page "https://github.com/SimonSapin/cairocffi") + (synopsis "Python bindings and object-oriented API for Cairo") + (description + "Cairocffi is a CFFI-based drop-in replacement for Pycairo, a set of +Python bindings and object-oriented API for cairo. Cairo is a 2D vector +graphics library with support for multiple backends including image buffers, +PNG, PostScript, PDF, and SVG file output.") + (license bsd-3))) + +(define-public python2-cairocffi + (package-with-python2 python-cairocffi)) + diff --git a/gnu/packages/qemu.scm b/gnu/packages/qemu.scm index 0a37a246bd..77aeecf40c 100644 --- a/gnu/packages/qemu.scm +++ b/gnu/packages/qemu.scm @@ -42,14 +42,14 @@ ;; This is QEMU without GUI support. (package (name "qemu-headless") - (version "2.0.0") + (version "2.2.0") (source (origin (method url-fetch) (uri (string-append "http://wiki.qemu-project.org/download/qemu-" version ".tar.bz2")) (sha256 (base32 - "0frsahiw56jr4cqr9m6s383lyj4ar9hfs2wp3y4yr76krah1mk30")))) + "1703c3scl5n07gmpilg7g2xzyxnr7jczxgx6nn4m8kv9gin9p35n")))) (build-system gnu-build-system) (arguments '(#:phases (alist-replace diff --git a/gnu/packages/tmux.scm b/gnu/packages/tmux.scm index 636b56e0db..9cb35bb4b2 100644 --- a/gnu/packages/tmux.scm +++ b/gnu/packages/tmux.scm @@ -28,7 +28,7 @@ (define-public tmux (package (name "tmux") - (version "1.7") + (version "1.9a") (source (origin (method url-fetch) (uri (string-append @@ -36,7 +36,7 @@ version "/tmux-" version ".tar.gz")) (sha256 (base32 - "0ywy1x2g905hmhkdz418ik42lcvnhnwr8fv63rcqczfg27d6nd38")))) + "1x9k4wfd4l5jg6fh7xkr3yyilizha6ka8m5b1nr0kw8wj0mv5qy5")))) (build-system gnu-build-system) (inputs `(("libevent" ,libevent) diff --git a/gnu/packages/video.scm b/gnu/packages/video.scm index 063f1dae43..984ba7e1f4 100644 --- a/gnu/packages/video.scm +++ b/gnu/packages/video.scm @@ -395,7 +395,7 @@ SVCD, DVD, 3ivx, DivX 3/4/5, WMV and H.264 movies.") (define-public youtube-dl (package (name "youtube-dl") - (version "2014.11.21.1") + (version "2014.12.15") (source (origin (method url-fetch) (uri (string-append "http://youtube-dl.org/downloads/" @@ -403,7 +403,7 @@ SVCD, DVD, 3ivx, DivX 3/4/5, WMV and H.264 movies.") version ".tar.gz")) (sha256 (base32 - "0rxpx8j4qhhsws6czlfji1x9igsinkbbwvld10qdylll7g9q1v7j")))) + "09z7v6jxs4a36kyy681mcypcqsxipplnbdy9s3rva1rpp5f74h2z")))) (build-system python-build-system) (inputs `(("setuptools" ,python-setuptools))) (home-page "http://youtube-dl.org") diff --git a/gnu/packages/xdisorg.scm b/gnu/packages/xdisorg.scm index 6820d018e3..6a84a45376 100644 --- a/gnu/packages/xdisorg.scm +++ b/gnu/packages/xdisorg.scm @@ -29,6 +29,7 @@ #:use-module (gnu packages image) #:use-module (gnu packages pkg-config) #:use-module (gnu packages glib) + #:use-module (gnu packages perl) #:use-module (gnu packages xorg)) ;; packages outside the x.org system proper @@ -57,6 +58,47 @@ can also be used for copying files, as an alternative to sftp/scp, thus avoiding password prompts when X11 forwarding has already been setup.") (license license:gpl2+))) +(define-public xdotool + (package + (name "xdotool") + (version "2.20110530.1") + (source + (origin + (method url-fetch) + (uri (string-append + "http://semicomplete.googlecode.com/files/" name "-" + version ".tar.gz")) + (sha256 + (base32 + "0rxggg1cy7nnkwidx8x2w3c5f3pk6dh2b6q0q7hp069r3n5jrd77")))) + (build-system gnu-build-system) + (arguments + '(#:tests? #f ; Test suite requires a lot of black magic + #:phases + (alist-replace 'configure + (lambda* (#:key outputs #:allow-other-keys #:rest args) + (setenv "PREFIX" (assoc-ref outputs "out")) + (setenv "LDFLAGS" (string-append "-Wl,-rpath=" + (assoc-ref + %outputs "out") "/lib")) + (setenv "CC" "gcc")) + %standard-phases))) + (native-inputs `(("perl" ,perl))) ; for pod2man + (inputs `(("libx11" ,libx11) + ("libxext" ,libxext) + ("libxi" ,libxi) + ("libxinerama" ,libxinerama) + ("libxtst" ,libxtst))) + (home-page "http://www.semicomplete.com/projects/xdotool") + (synopsis "Fake keyboard/mouse input, window management, and more") + (description "Xdotool lets you simulate keyboard input and mouse activity, +move and resize windows, etc. It does this using X11's XTEST extension and +other Xlib functions. Additionally, you can search for windows and move, +resize, hide, and modify window properties like the title. If your window +manager supports it, you can use xdotool to switch desktops, move windows +between desktops, and change the number of desktops.") + (license license:bsd-3))) + (define-public xeyes (package (name "xeyes") diff --git a/gnu/packages/xfce.scm b/gnu/packages/xfce.scm index 69776fc582..2b15c3e35c 100644 --- a/gnu/packages/xfce.scm +++ b/gnu/packages/xfce.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2014 Sou Bunnbu +;;; Copyright © 2014 Mark H Weaver ;;; ;;; This file is part of GNU Guix. ;;; @@ -22,6 +23,7 @@ #:use-module (guix download) #:use-module (guix utils) #:use-module (guix build-system gnu) + #:use-module (gnu packages) #:use-module (gnu packages pkg-config) #:use-module (gnu packages glib) #:use-module (gnu packages gtk) @@ -249,7 +251,8 @@ management D-Bus specification.") "/src/" name "-" version ".tar.bz2")) (sha256 (base32 - "1f8903nx6ivzircl8d8s9zna4vjgfy0qhjk5d2x19g9bmycgj89k")))) + "1f8903nx6ivzircl8d8s9zna4vjgfy0qhjk5d2x19g9bmycgj89k")) + (patches (list (search-patch "xfce4-panel-plugins.patch"))))) (build-system gnu-build-system) (native-inputs `(("pkg-config" ,pkg-config) @@ -261,6 +264,10 @@ management D-Bus specification.") ("garcon", garcon) ("libwnck" ,libwnck-1) ("libxfce4ui" ,libxfce4ui))) + (native-search-paths + (list (search-path-specification + (variable "X_XFCE4_LIB_DIRS") + (directories '("lib/xfce4"))))) (home-page "http://www.xfce.org/") (synopsis "Xfce desktop panel") (description @@ -269,6 +276,35 @@ applications menu, workspace switcher and more.") ;; Libraries are under LGPLv2.1+, and programs under GPLv2+. (license (list gpl2+ lgpl2.1+)))) +(define-public xfce4-battery-plugin + (package + (name "xfce4-battery-plugin") + (version "1.0.5") + (source (origin + (method url-fetch) + (uri (string-append "http://archive.xfce.org/src/panel-plugins/" + name "/" (version-major+minor version) "/" + name "-" version ".tar.bz2")) + (sha256 + (base32 + "04gbplcj8z4vg5xbks8cc2jjf62mmf9sdymg90scjwmb82pv2ngn")))) + (build-system gnu-build-system) + (native-inputs `(("pkg-config" ,pkg-config) + ("intltool" ,intltool))) + (inputs `(("glib" ,glib) + ("gtk+" ,gtk+-2) + ("libxfce4util" ,libxfce4util) + ("libxfce4ui" ,libxfce4ui) + ("xfce4-panel" ,xfce4-panel))) + (home-page + "http://goodies.xfce.org/projects/panel-plugins/xfce4-battery-plugin") + (synopsis "Battery monitor panel plugin for Xfce4") + (description + "A battery monitor panel plugin for Xfce4, compatible with APM and ACPI.") + ;; The main plugin code is covered by gpl2+, but the files containing code + ;; to read the battery state via ACPI or APM are covered by lgpl2.0+. + (license (list gpl2+ lgpl2.0+)))) + (define-public xfce4-appfinder (package (name "xfce4-appfinder") @@ -476,3 +512,33 @@ on the screen.") optional application menu or icons for minimized applications or launchers, devices and folders.") (license gpl2+))) + +(define-public xfce4-terminal + (package + (name "xfce4-terminal") + (version "0.6.3") + (source (origin + (method url-fetch) + (uri (string-append "http://archive.xfce.org/src/apps/" name "/" + (version-major+minor version) "/" + name "-" version ".tar.bz2")) + (sha256 + (base32 + "023y0lkfijifh05yz8grimxadqpi98mrivr00sl18nirq8b4fbwi")))) + (build-system gnu-build-system) + (native-inputs + `(("pkg-config" ,pkg-config) + ("intltool" ,intltool))) + (inputs + `(("libxfce4ui" ,libxfce4ui) + ("vte" ,vte/gtk+-2))) + (home-page "http://www.xfce.org/") + (synopsis "Xfce terminal emulator") + (description + "A lightweight and easy to use terminal emulator for Xfce. Features +include a simple configuration interface, the ability to use multiple tabs +with terminals within a single window, the possibility to have a +pseudo-transparent terminal background, and a compact mode (where both the +menubar and the window decorations are hidden) that helps you to save space +on your desktop.") + (license gpl2+))) diff --git a/gnu/services/base.scm b/gnu/services/base.scm index 712222bdde..95edba6e7c 100644 --- a/gnu/services/base.scm +++ b/gnu/services/base.scm @@ -33,8 +33,10 @@ #:select (mount-flags->bit-mask)) #:use-module (guix gexp) #:use-module (guix monads) + #:use-module (guix records) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) + #:use-module (ice-9 match) #:use-module (ice-9 format) #:export (root-file-system-service file-system-service @@ -46,6 +48,16 @@ console-font-service udev-service mingetty-service + + %nscd-default-caches + %nscd-default-configuration + + nscd-configuration + nscd-configuration? + + nscd-cache + nscd-cache? + nscd-service syslog-service guix-service @@ -374,9 +386,110 @@ the ``message of the day''." #:allow-empty-passwords? allow-empty-passwords? #:motd motd))))))) -(define* (nscd-service #:key (glibc (canonical-package glibc))) - "Return a service that runs libc's name service cache daemon (nscd)." - (with-monad %store-monad +(define-record-type* nscd-configuration + make-nscd-configuration + nscd-configuration? + (log-file nscd-configuration-log-file ;string + (default "/var/log/nscd.log")) + (debug-level nscd-debug-level ;integer + (default 0)) + ;; TODO: See nscd.conf in glibc for other options to add. + (caches nscd-configuration-caches ;list of + (default %nscd-default-caches))) + +(define-record-type* nscd-cache make-nscd-cache + nscd-cache? + (database nscd-cache-database) ;symbol + (positive-time-to-live nscd-cache-positive-time-to-live) ;integer + (negative-time-to-live nscd-cache-negative-time-to-live + (default 20)) ;integer + (suggested-size nscd-cache-suggested-size ;integer ("default module + ;of hash table") + (default 211)) + (check-files? nscd-cache-check-files? ;Boolean + (default #t)) + (persistent? nscd-cache-persistent? ;Boolean + (default #t)) + (shared? nscd-cache-shared? ;Boolean + (default #t)) + (max-database-size nscd-cache-max-database-size ;integer + (default (* 32 (expt 2 20)))) + (auto-propagate? nscd-cache-auto-propagate? ;Boolean + (default #t))) + +(define %nscd-default-caches + ;; Caches that we want to enable by default. Note that when providing an + ;; empty nscd.conf, all caches are disabled. + (list (nscd-cache (database 'hosts) + + ;; Aggressively cache the host name cache to improve + ;; privacy and resilience. + (positive-time-to-live (* 3600 12)) + (negative-time-to-live 20) + (persistent? #t)) + + (nscd-cache (database 'services) + + ;; Services are unlikely to change, so we can be even more + ;; aggressive. + (positive-time-to-live (* 3600 24)) + (negative-time-to-live 3600) + (check-files? #t) ;check /etc/services changes + (persistent? #t)))) + +(define %nscd-default-configuration + ;; Default nscd configuration. + (nscd-configuration)) + +(define (nscd.conf-file config) + "Return the @file{nscd.conf} configuration file for @var{config}, an +@code{} object." + (define cache->config + (match-lambda + (($ (= symbol->string database) + positive-ttl negative-ttl size check-files? + persistent? shared? max-size propagate?) + (string-append "\nenable-cache\t" database "\tyes\n" + + "positive-time-to-live\t" database "\t" + (number->string positive-ttl) "\n" + "negative-time-to-live\t" database "\t" + (number->string negative-ttl) "\n" + "suggested-size\t" database "\t" + (number->string size) "\n" + "check-files\t" database "\t" + (if check-files? "yes\n" "no\n") + "persistent\t" database "\t" + (if persistent? "yes\n" "no\n") + "shared\t" database "\t" + (if shared? "yes\n" "no\n") + "max-db-size\t" database "\t" + (number->string max-size) "\n" + "auto-propagate\t" database "\t" + (if propagate? "yes\n" "no\n"))))) + + (match config + (($ log-file debug-level caches) + (text-file "nscd.conf" + (string-append "\ +# Configuration of libc's name service cache daemon (nscd).\n\n" + (if log-file + (string-append "logfile\t" log-file) + "") + "\n" + (if debug-level + (string-append "debug-level\t" + (number->string debug-level)) + "") + "\n" + (string-concatenate + (map cache->config caches))))))) + +(define* (nscd-service #:optional (config %nscd-default-configuration) + #:key (glibc (canonical-package glibc))) + "Return a service that runs libc's name service cache daemon (nscd) with the +given @var{config}---an @code{} object." + (mlet %store-monad ((nscd.conf (nscd.conf-file config))) (return (service (documentation "Run libc's name service cache daemon (nscd).") (provision '(nscd)) @@ -388,7 +501,7 @@ the ``message of the day''." (start #~(make-forkexec-constructor (list (string-append #$glibc "/sbin/nscd") - "-f" "/dev/null" "--foreground"))) + "-f" #$nscd.conf "--foreground"))) (stop #~(make-kill-destructor)) (respawn? #f))))) diff --git a/gnu/services/networking.scm b/gnu/services/networking.scm index 1cb501bb7a..db9be8cfbd 100644 --- a/gnu/services/networking.scm +++ b/gnu/services/networking.scm @@ -80,60 +80,62 @@ fe80::1%lo0 apps.facebook.com\n") gateway (provision '(networking)) (name-servers '()) - (inetutils inetutils) (net-tools net-tools)) "Return a service that starts @var{interface} with address @var{ip}. If @var{gateway} is true, it must be a string specifying the default network gateway." + (define loopback? + (memq 'loopback provision)) - ;; TODO: Eventually we should do this using Guile's networking procedures, - ;; like 'configure-qemu-networking' does, but the patch that does this is - ;; not yet in stock Guile. + ;; TODO: Eventually replace 'route' with bindings for the appropriate + ;; ioctls. (with-monad %store-monad (return (service ;; Unless we're providing the loopback interface, wait for udev to be up ;; and running so that INTERFACE is actually usable. - (requirement (if (memq 'loopback provision) - '() - '(udev))) + (requirement (if loopback? '() '(udev))) (documentation "Bring up the networking interface using a static IP address.") (provision provision) (start #~(lambda _ ;; Return #t if successfully started. - (and (zero? (system* (string-append #$inetutils - "/bin/ifconfig") - "-i" #$interface "-A" #$ip - "-i" #$interface "--up")) - #$(if gateway - #~(zero? (system* (string-append #$net-tools - "/sbin/route") - "add" "-net" "default" - "gw" #$gateway)) - #t) - #$(if (pair? name-servers) - #~(call-with-output-file "/etc/resolv.conf" - (lambda (port) - (display - "# Generated by 'static-networking-service'.\n" - port) - (for-each (lambda (server) - (format port "nameserver ~a~%" - server)) - '#$name-servers))) - #t)))) + (let* ((addr (inet-pton AF_INET #$ip)) + (sockaddr (make-socket-address AF_INET addr 0))) + (configure-network-interface #$interface sockaddr + (logior IFF_UP + #$(if loopback? + #~IFF_LOOPBACK + 0)))) + #$(if gateway + #~(zero? (system* (string-append #$net-tools + "/sbin/route") + "add" "-net" "default" + "gw" #$gateway)) + #t) + #$(if (pair? name-servers) + #~(call-with-output-file "/etc/resolv.conf" + (lambda (port) + (display + "# Generated by 'static-networking-service'.\n" + port) + (for-each (lambda (server) + (format port "nameserver ~a~%" + server)) + '#$name-servers))) + #t))) (stop #~(lambda _ ;; Return #f is successfully stopped. - (not (and (system* (string-append #$inetutils "/bin/ifconfig") - #$interface "down") - #$(if gateway - #~(system* (string-append #$net-tools - "/sbin/route") - "del" "-net" "default") - #t))))) + (let ((sock (socket AF_INET SOCK_STREAM 0))) + (set-network-interface-flags sock #$interface 0) + (close-port sock)) + (not #$(if gateway + #~(system* (string-append #$net-tools + "/sbin/route") + "del" "-net" "default") + #t)))) (respawn? #f))))) (define* (dhcp-client-service #:key (dhcp isc-dhcp)) diff --git a/gnu/services/xorg.scm b/gnu/services/xorg.scm index fbf96c799b..27a72e8019 100644 --- a/gnu/services/xorg.scm +++ b/gnu/services/xorg.scm @@ -36,7 +36,7 @@ #:use-module (srfi srfi-26) #:use-module (ice-9 match) #:export (xorg-start-command - + %default-xsessions %default-slim-theme %default-slim-theme-name slim-service)) @@ -136,9 +136,10 @@ EndSection (define* (xinitrc #:key (guile (canonical-package guile-2.0)) - (ratpoison ratpoison) - (windowmaker windowmaker)) - "Return a system-wide xinitrc script that starts the specified X session." + fallback-session) + "Return a system-wide xinitrc script that starts the specified X session, +which should be passed to this script as the first argument. If not, the +@var{fallback-session} will be used." (define builder #~(begin (use-modules (ice-9 match)) @@ -155,20 +156,14 @@ EndSection (execl shell shell "--login" "-c" (string-join (cons command args)))))) - ;; First, try to run ~/.xsession. - (let* ((home (getenv "HOME")) - (xsession (string-append home "/.xsession"))) - (exec-from-login-shell xsession)) - - ;; Then try a pre-configured session type. - (let ((ratpoison (string-append #$ratpoison "/bin/ratpoison")) - (wmaker (string-append #$windowmaker "/bin/wmaker"))) - (match (command-line) - ((_ "ratpoison") - (exec-from-login-shell ratpoison)) - (_ - (exec-from-login-shell wmaker)))))) - + (let ((home (getenv "HOME")) + (session (match (command-line) + ((_ x) x) + (_ #$fallback-session)))) + ;; First, try to run ~/.xsession. + (exec-from-login-shell (string-append home "/.xsession")) + ;; Then try to start the specified session. + (exec-from-login-shell session)))) (gexp->script "xinitrc" builder)) @@ -176,6 +171,35 @@ EndSection ;;; SLiM log-in manager. ;;; +(define %default-xsessions + ;; Default xsessions available for log-in manager, representing as a list of + ;; monadic desktop entries. + (list (text-file* "wmaker.desktop" " +[Desktop Entry] +Name=Window Maker +Exec=" windowmaker "/bin/wmaker +Type=Application +") + (text-file* "ratpoison.desktop" " +[Desktop Entry] +Name=Ratpoison +Exec=" ratpoison "/bin/ratpoison +Type=Application +"))) + +(define (xsessions-directory sessions) + "Return a directory containing SESSIONS, which should be a list of monadic +desktop entries." + (mlet %store-monad ((sessions (sequence %store-monad sessions))) + (define builder + #~(begin + (mkdir #$output) + (for-each (lambda (session) + (symlink session (string-append #$output "/" + (basename session)))) + '#$sessions))) + (gexp->derivation "xsessions-dir" builder))) + (define %default-slim-theme ;; Theme based on work by Felipe López. #~(string-append #$%artwork-repository "/slim")) @@ -191,6 +215,9 @@ EndSection (theme %default-slim-theme) (theme-name %default-slim-theme-name) (xauth xauth) (dmd dmd) (bash bash) + (sessions %default-xsessions) + (auto-login-session #~(string-append #$windowmaker + "/bin/wmaker")) startx) "Return a service that spawns the SLiM graphical login manager, which in turn starts the X display server with @var{startx}, a command as returned by @@ -198,7 +225,7 @@ turn starts the X display server with @var{startx}, a command as returned by When @var{allow-empty-passwords?} is true, allow logins with an empty password. When @var{auto-login?} is true, log in automatically as -@var{default-user}. +@var{default-user} with @var{auto-login-session}. If @var{theme} is @code{#f}, the use the default log-in theme; otherwise @var{theme} must be a gexp denoting the name of a directory containing the @@ -207,7 +234,9 @@ theme." (define (slim.cfg) (mlet %store-monad ((startx (or startx (xorg-start-command))) - (xinitrc (xinitrc))) + (xinitrc (xinitrc #:fallback-session + auto-login-session)) + (sessiondir (xsessions-directory sessions))) (text-file* "slim.cfg" " default_path /run/current-system/profile/bin default_xserver " startx " @@ -218,7 +247,7 @@ authfile /var/run/slim.auth # The login command. '%session' is replaced by the chosen session name, one # of the names specified in the 'sessions' setting: 'wmaker', 'xfce', etc. login_cmd exec " xinitrc " %session -sessions wmaker,ratpoison +sessiondir " sessiondir " halt_cmd " dmd "/sbin/halt reboot_cmd " dmd "/sbin/reboot diff --git a/gnu/system/install.scm b/gnu/system/install.scm index 01e79480b1..ab3fe42ae1 100644 --- a/gnu/system/install.scm +++ b/gnu/system/install.scm @@ -145,6 +145,14 @@ configuration template file in the installation system." #~(unless (file-exists? #$local-template) (copy-file #$template #$local-template))))))) +(define %nscd-minimal-caches + ;; Minimal in-memory caching policy for nscd. + (list (nscd-cache (database 'hosts) + (positive-time-to-live (* 3600 12)) + (negative-time-to-live 20) + (persistent? #f) + (max-database-size (* 5 (expt 2 20)))))) ;5 MiB + (define (installation-services) "Return the list services for the installation image." (let ((motd (text-file "motd" " @@ -206,7 +214,10 @@ You have been warned. Thanks for being so brave. (console-font-service "tty5") (console-font-service "tty6") - (nscd-service)))) + ;; Since this is running on a USB stick with a unionfs as the root + ;; file system, use an appropriate cache configuration. + (nscd-service (nscd-configuration + (caches %nscd-minimal-caches)))))) (define %issue ;; Greeting. diff --git a/guix/build-system/python.scm b/guix/build-system/python.scm index 4bba7167ca..e8af9f8146 100644 --- a/guix/build-system/python.scm +++ b/guix/build-system/python.scm @@ -55,8 +55,7 @@ PYTHON-BUILD-SYSTEM, such that it is compiled with PYTHON instead. The inputs are changed recursively accordingly. If the name of P starts with OLD-PREFIX, this is replaced by NEW-PREFIX; otherwise, NEW-PREFIX is prepended to the name." - (let* ((build-system (package-build-system p)) - (rewrite-if-package + (let* ((rewrite-if-package (lambda (content) ;; CONTENT may be a file name, in which case it is returned, or a ;; package, which is rewritten with the new PYTHON and NEW-PREFIX. @@ -68,28 +67,23 @@ prepended to the name." (match-lambda ((name content . rest) (append (list name (rewrite-if-package content)) rest))))) - (package (inherit p) - (name - (let ((name (package-name p))) - (if (eq? build-system python-build-system) - (string-append new-prefix - (if (string-prefix? old-prefix name) - (substring name (string-length old-prefix)) - name)) - name))) - (arguments - (let ((arguments (package-arguments p))) - (if (eq? build-system python-build-system) - (if (member #:python arguments) - (substitute-keyword-arguments arguments ((#:python p) python)) - (append arguments `(#:python ,python))) - arguments))) - (inputs - (map rewrite (package-inputs p))) - (propagated-inputs - (map rewrite (package-propagated-inputs p))) - (native-inputs - (map rewrite (package-native-inputs p)))))) + + (if (eq? (package-build-system p) python-build-system) + (package (inherit p) + (name (let ((name (package-name p))) + (string-append new-prefix + (if (string-prefix? old-prefix name) + (substring name (string-length old-prefix)) + name)))) + (arguments + (let ((arguments (package-arguments p))) + (if (member #:python arguments) + (substitute-keyword-arguments arguments ((#:python p) python)) + (append arguments `(#:python ,python))))) + (inputs (map rewrite (package-inputs p))) + (propagated-inputs (map rewrite (package-propagated-inputs p))) + (native-inputs (map rewrite (package-native-inputs p)))) + p))) (define package-with-python2 (cut package-with-explicit-python <> (default-python2) "python-" "python2-")) diff --git a/guix/build/python-build-system.scm b/guix/build/python-build-system.scm index 2f3d04a5d8..74ba0c765d 100644 --- a/guix/build/python-build-system.scm +++ b/guix/build/python-build-system.scm @@ -105,19 +105,36 @@ files))) bindirs))) +(define* (rename-pth-file #:key name inputs outputs #:allow-other-keys) + "Rename easy-install.pth to NAME.pth to avoid conflicts between packages +installed with setuptools." + (let* ((out (assoc-ref outputs "out")) + (python (assoc-ref inputs "python")) + (site-packages (string-append out "/lib/python" + (get-python-version python) + "/site-packages")) + (easy-install-pth (string-append site-packages "/easy-install.pth")) + (new-pth (string-append site-packages "/" name ".pth"))) + (when (file-exists? easy-install-pth) + (rename-file easy-install-pth new-pth)) + #t)) + (define %standard-phases ;; 'configure' and 'build' phases are not needed. Everything is done during ;; 'install'. - (alist-cons-after - 'install 'wrap - wrap - (alist-replace - 'build build + (alist-cons-before + 'strip 'rename-pth-file + rename-pth-file + (alist-cons-after + 'install 'wrap + wrap (alist-replace - 'check check - (alist-replace 'install install - (alist-delete 'configure - gnu:%standard-phases)))))) + 'build build + (alist-replace + 'check check + (alist-replace 'install install + (alist-delete 'configure + gnu:%standard-phases))))))) (define* (python-build #:key inputs (phases %standard-phases) #:allow-other-keys #:rest args) diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm index e1fafe2266..b210f8faa8 100644 --- a/guix/build/syscalls.scm +++ b/guix/build/syscalls.scm @@ -42,7 +42,11 @@ all-network-interfaces network-interfaces network-interface-flags - loopback-network-interface?)) + loopback-network-interface? + network-interface-address + set-network-interface-flags + set-network-interface-address + configure-network-interface)) ;;; Commentary: ;;; @@ -228,6 +232,77 @@ user-land process." (scandir "/proc")) <)) + +;;; +;;; Packed structures. +;;; + +(define-syntax sizeof* + ;; XXX: This duplicates 'compile-time-value'. + (syntax-rules (int128) + ((_ int128) + 16) + ((_ type) + (let-syntax ((v (lambda (s) + (let ((val (sizeof type))) + (syntax-case s () + (_ val)))))) + v)))) + +(define-syntax type-size + (syntax-rules (~) + ((_ (type ~ order)) + (sizeof* type)) + ((_ type) + (sizeof* type)))) + +(define-syntax write-type + (syntax-rules (~) + ((_ bv offset (type ~ order) value) + (bytevector-uint-set! bv offset value + (endianness order) (sizeof* type))) + ((_ bv offset type value) + (bytevector-uint-set! bv offset value + (native-endianness) (sizeof* type))))) + +(define-syntax write-types + (syntax-rules () + ((_ bv offset () ()) + #t) + ((_ bv offset (type0 types ...) (field0 fields ...)) + (begin + (write-type bv offset type0 field0) + (write-types bv (+ offset (type-size type0)) + (types ...) (fields ...)))))) + +(define-syntax read-type + (syntax-rules (~) + ((_ bv offset (type ~ order)) + (bytevector-uint-ref bv offset + (endianness order) (sizeof* type))) + ((_ bv offset type) + (bytevector-uint-ref bv offset + (native-endianness) (sizeof* type))))) + +(define-syntax read-types + (syntax-rules () + ((_ bv offset ()) + '()) + ((_ bv offset (type0 types ...)) + (cons (read-type bv offset type0) + (read-types bv (+ offset (type-size type0)) (types ...)))))) + +(define-syntax define-c-struct + (syntax-rules () + "Define READ as an optimized serializer and WRITE! as a deserializer for +the C structure with the given TYPES." + ((_ name read write! (fields types) ...) + (begin + (define (write! bv offset fields ...) + (write-types bv offset (types ...) (fields ...))) + (define (read bv offset) + (read-types bv offset (types ...))))))) + ;;; ;;; Network interfaces. @@ -241,6 +316,18 @@ user-land process." (if (string-contains %host-type "linux") #x8913 ;GNU/Linux #xc4804191)) ;GNU/Hurd +(define SIOCSIFFLAGS + (if (string-contains %host-type "linux") + #x8914 ;GNU/Linux + -1)) ;FIXME: GNU/Hurd? +(define SIOCGIFADDR + (if (string-contains %host-type "linux") + #x8915 ;GNU/Linux + -1)) ;FIXME: GNU/Hurd? +(define SIOCSIFADDR + (if (string-contains %host-type "linux") + #x8916 ;GNU/Linux + -1)) ;FIXME: GNU/Hurd? ;; Flags and constants from . @@ -263,6 +350,56 @@ user-land process." 40 32)) +(define-c-struct sockaddr-in ; + read-sockaddr-in + write-sockaddr-in! + (family unsigned-short) + (port (int16 ~ big)) + (address (int32 ~ big))) + +(define-c-struct sockaddr-in6 ; + read-sockaddr-in6 + write-sockaddr-in6! + (family unsigned-short) + (port (int16 ~ big)) + (flowinfo (int32 ~ big)) + (address (int128 ~ big)) + (scopeid int32)) + +(define (write-socket-address! sockaddr bv index) + "Write SOCKADDR, a socket address as returned by 'make-socket-address', to +bytevector BV at INDEX." + (let ((family (sockaddr:fam sockaddr))) + (cond ((= family AF_INET) + (write-sockaddr-in! bv index + family + (sockaddr:port sockaddr) + (sockaddr:addr sockaddr))) + ((= family AF_INET6) + (write-sockaddr-in6! bv index + family + (sockaddr:port sockaddr) + (sockaddr:flowinfo sockaddr) + (sockaddr:addr sockaddr) + (sockaddr:scopeid sockaddr))) + (else + (error "unsupported socket address" sockaddr))))) + +(define (read-socket-address bv index) + "Read a socket address from bytevector BV at INDEX." + (let ((family (bytevector-u16-native-ref bv index))) + (cond ((= family AF_INET) + (match (read-sockaddr-in bv index) + ((family port address) + (make-socket-address family address port)))) + ((= family AF_INET6) + (match (read-sockaddr-in6 bv index) + ((family port flowinfo address scopeid) + (make-socket-address family address port + flowinfo scopeid)))) + (else + "unsupported socket address family" family)))) + (define %ioctl ;; The most terrible interface, live from Scheme. (pointer->procedure int @@ -354,4 +491,65 @@ interface NAME." (close-port sock) (not (zero? (logand flags IFF_LOOPBACK))))) +(define (set-network-interface-flags socket name flags) + "Set the flag of network interface NAME to FLAGS." + (let ((req (make-bytevector ifreq-struct-size))) + (bytevector-copy! (string->utf8 name) 0 req 0 + (min (string-length name) (- IF_NAMESIZE 1))) + ;; Set the 'ifr_flags' field. + (bytevector-uint-set! req IF_NAMESIZE flags (native-endianness) + (sizeof short)) + (let* ((ret (%ioctl (fileno socket) SIOCSIFFLAGS + (bytevector->pointer req))) + (err (errno))) + (unless (zero? ret) + (throw 'system-error "set-network-interface-flags" + "set-network-interface-flags on ~A: ~A" + (list name (strerror err)) + (list err)))))) + +(define (set-network-interface-address socket name sockaddr) + "Set the address of network interface NAME to SOCKADDR." + (let ((req (make-bytevector ifreq-struct-size))) + (bytevector-copy! (string->utf8 name) 0 req 0 + (min (string-length name) (- IF_NAMESIZE 1))) + ;; Set the 'ifr_addr' field. + (write-socket-address! sockaddr req IF_NAMESIZE) + (let* ((ret (%ioctl (fileno socket) SIOCSIFADDR + (bytevector->pointer req))) + (err (errno))) + (unless (zero? ret) + (throw 'system-error "set-network-interface-address" + "set-network-interface-address on ~A: ~A" + (list name (strerror err)) + (list err)))))) + +(define (network-interface-address socket name) + "Return the address of network interface NAME. The result is an object of +the same type as that returned by 'make-socket-address'." + (let ((req (make-bytevector ifreq-struct-size))) + (bytevector-copy! (string->utf8 name) 0 req 0 + (min (string-length name) (- IF_NAMESIZE 1))) + (let* ((ret (%ioctl (fileno socket) SIOCGIFADDR + (bytevector->pointer req))) + (err (errno))) + (if (zero? ret) + (read-socket-address req IF_NAMESIZE) + (throw 'system-error "network-interface-address" + "network-interface-address on ~A: ~A" + (list name (strerror err)) + (list err)))))) + +(define (configure-network-interface name sockaddr flags) + "Configure network interface NAME to use SOCKADDR, an address as returned by +'make-socket-address', and FLAGS, a bitwise-or of IFF_* constants." + (let ((sock (socket (sockaddr:fam sockaddr) SOCK_STREAM 0))) + (dynamic-wind + (const #t) + (lambda () + (set-network-interface-address sock name sockaddr) + (set-network-interface-flags sock name flags)) + (lambda () + (close-port sock))))) + ;;; syscalls.scm ends here diff --git a/guix/packages.scm b/guix/packages.scm index cf16a4730c..a25eab7699 100644 --- a/guix/packages.scm +++ b/guix/packages.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2012, 2013, 2014 Ludovic Courtès +;;; Copyright © 2014 Mark H Weaver ;;; ;;; This file is part of GNU Guix. ;;; @@ -546,40 +547,38 @@ for the host system (\"native inputs\"), and not target inputs." recursively." (transitive-inputs (package-propagated-inputs package))) -(define-syntax-rule (first-value exp) - "Truncate all but the first value returned by EXP." - (call-with-values (lambda () exp) - (lambda (result . _) - result))) +(define-syntax define-memoized/v + (lambda (form) + "Define a memoized single-valued unary procedure with docstring. +The procedure argument is compared to cached keys using `eqv?'." + (syntax-case form () + ((_ (proc arg) docstring body body* ...) + (string? (syntax->datum #'docstring)) + #'(define proc + (let ((cache (make-hash-table))) + (define (proc arg) + docstring + (match (hashv-get-handle cache arg) + ((_ . value) + value) + (_ + (let ((result (let () body body* ...))) + (hashv-set! cache arg result) + result)))) + proc)))))) -(define (package-transitive-supported-systems package) +(define-memoized/v (package-transitive-supported-systems package) "Return the intersection of the systems supported by PACKAGE and those supported by its dependencies." - (first-value - (let loop ((package package) - (systems (package-supported-systems package)) - (visited vlist-null)) - (match (vhash-assq package visited) - ((_ . result) - (values (lset-intersection string=? systems result) - visited)) - (#f - (call-with-values - (lambda () - (fold2 (lambda (input systems visited) - (match input - ((label (? package? package) . _) - (loop package systems visited)) - (_ - (values systems visited)))) - (lset-intersection string=? - systems - (package-supported-systems package)) - visited - (package-direct-inputs package))) - (lambda (systems visited) - (values systems - (vhash-consq package systems visited))))))))) + (fold (lambda (input systems) + (match input + ((label (? package? p) . _) + (lset-intersection + string=? systems (package-transitive-supported-systems p))) + (_ + systems))) + (package-supported-systems package) + (package-direct-inputs package))) (define (bag-transitive-inputs bag) "Same as 'package-transitive-inputs', but applied to a bag." diff --git a/guix/profiles.scm b/guix/profiles.scm index 2742ba8933..44d7a314a3 100644 --- a/guix/profiles.scm +++ b/guix/profiles.scm @@ -414,7 +414,13 @@ INFO-DIR? is #f." (return #f)))) (define inputs (if info-dir - (cons info-dir (manifest-inputs manifest)) + ;; XXX: Here we use the tuple (INFO-DIR "out") just so that the list + ;; is unambiguous for the gexp code when MANIFEST has a single input + ;; denoted as a string (the pattern (DRV STRING) is normally + ;; interpreted in a gexp as "the STRING output of DRV".). See + ;; . + (cons (list info-dir "out") + (manifest-inputs manifest)) (manifest-inputs manifest))) (define builder diff --git a/nix-upstream b/nix-upstream deleted file mode 160000 index e7720aa10a..0000000000 --- a/nix-upstream +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e7720aa10a1da63bb15a4587837d649268944943 diff --git a/nix/AUTHORS b/nix/AUTHORS new file mode 100644 index 0000000000..fc2279d628 --- /dev/null +++ b/nix/AUTHORS @@ -0,0 +1,2 @@ +Most of the code is this directory was written by several people for +the Nix project (http://nixos.org/nix). Thanks! diff --git a/nix/COPYING b/nix/COPYING new file mode 100644 index 0000000000..5ab7695ab8 --- /dev/null +++ b/nix/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/nix/boost/assert.hpp b/nix/boost/assert.hpp new file mode 100644 index 0000000000..754ebb954b --- /dev/null +++ b/nix/boost/assert.hpp @@ -0,0 +1,38 @@ +// +// boost/assert.hpp - BOOST_ASSERT(expr) +// +// Copyright (c) 2001, 2002 Peter Dimov and Multi Media Ltd. +// +// Permission to copy, use, modify, sell and distribute this software +// is granted provided this copyright notice appears in all copies. +// This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// Note: There are no include guards. This is intentional. +// +// See http://www.boost.org/libs/utility/assert.html for documentation. +// + +#undef BOOST_ASSERT + +#if defined(BOOST_DISABLE_ASSERTS) + +# define BOOST_ASSERT(expr) ((void)0) + +#elif defined(BOOST_ENABLE_ASSERT_HANDLER) + +#include + +namespace boost +{ + +void assertion_failed(char const * expr, char const * function, char const * file, long line); // user defined + +} // namespace boost + +#define BOOST_ASSERT(expr) ((expr)? ((void)0): ::boost::assertion_failed(#expr, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__)) + +#else +# include +# define BOOST_ASSERT(expr) assert(expr) +#endif diff --git a/nix/boost/format.hpp b/nix/boost/format.hpp new file mode 100644 index 0000000000..f965f0f33e --- /dev/null +++ b/nix/boost/format.hpp @@ -0,0 +1,64 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream + +// ---------------------------------------------------------------------------- +// format.hpp : primary header +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_HPP +#define BOOST_FORMAT_HPP + +#include +#include +#include +#include + +#if HAVE_LOCALE +#include +#else +#define BOOST_NO_STD_LOCALE +#define BOOST_NO_LOCALE_ISIDIGIT +#include +#endif + +#include + + +// **** Forward declarations ---------------------------------- +#include // basic_format, and other frontends +#include // misc forward declarations for internal use + + +// **** Auxiliary structs (stream_format_state , and format_item ) +#include + +// **** Format class interface -------------------------------- +#include + +// **** Exceptions ----------------------------------------------- +#include + +// **** Implementation ------------------------------------------- +//#include // member functions + +#include // class for grouping arguments + +#include // argument-feeding functions +//#include // format-string parsing (member-)functions + +// **** Implementation of the free functions ---------------------- +//#include + + +#endif // BOOST_FORMAT_HPP diff --git a/nix/boost/format/exceptions.hpp b/nix/boost/format/exceptions.hpp new file mode 100644 index 0000000000..79e452449e --- /dev/null +++ b/nix/boost/format/exceptions.hpp @@ -0,0 +1,96 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// exceptions.hpp +// ------------------------------------------------------------------------------ + + +#ifndef BOOST_FORMAT_EXCEPTIONS_HPP +#define BOOST_FORMAT_EXCEPTIONS_HPP + + +#include + + +namespace boost { + +namespace io { + +// **** exceptions ----------------------------------------------- + +class format_error : public std::exception +{ +public: + format_error() {} + virtual const char *what() const throw() + { + return "boost::format_error: " + "format generic failure"; + } +}; + +class bad_format_string : public format_error +{ +public: + bad_format_string() {} + virtual const char *what() const throw() + { + return "boost::bad_format_string: " + "format-string is ill-formed"; + } +}; + +class too_few_args : public format_error +{ +public: + too_few_args() {} + virtual const char *what() const throw() + { + return "boost::too_few_args: " + "format-string refered to more arguments than were passed"; + } +}; + +class too_many_args : public format_error +{ +public: + too_many_args() {} + virtual const char *what() const throw() + { + return "boost::too_many_args: " + "format-string refered to less arguments than were passed"; + } +}; + + +class out_of_range : public format_error +{ +public: + out_of_range() {} + virtual const char *what() const throw() + { + return "boost::out_of_range: " + "tried to refer to an argument (or item) number which is out of range, " + "according to the format string."; + } +}; + + +} // namespace io + +} // namespace boost + + +#endif // BOOST_FORMAT_EXCEPTIONS_HPP diff --git a/nix/boost/format/feed_args.hpp b/nix/boost/format/feed_args.hpp new file mode 100644 index 0000000000..3d0b47b4a1 --- /dev/null +++ b/nix/boost/format/feed_args.hpp @@ -0,0 +1,247 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream + +// ---------------------------------------------------------------------------- +// feed_args.hpp : functions for processing each argument +// (feed, feed_manip, and distribute) +// ---------------------------------------------------------------------------- + + +#ifndef BOOST_FORMAT_FEED_ARGS_HPP +#define BOOST_FORMAT_FEED_ARGS_HPP + +#include "boost/format/format_class.hpp" +#include "boost/format/group.hpp" + +#include "boost/throw_exception.hpp" + +namespace boost { +namespace io { +namespace detail { +namespace { + + inline + void empty_buf(BOOST_IO_STD ostringstream & os) { + static const std::string emptyStr; + os.str(emptyStr); + } + + void do_pad( std::string & s, + std::streamsize w, + const char c, + std::ios::fmtflags f, + bool center) + // applies centered / left / right padding to the string s. + // Effects : string s is padded. + { + std::streamsize n=w-s.size(); + if(n<=0) { + return; + } + if(center) + { + s.reserve(w); // allocate once for the 2 inserts + const std::streamsize n1 = n /2, n0 = n - n1; + s.insert(s.begin(), n0, c); + s.append(n1, c); + } + else + { + if(f & std::ios::left) { + s.append(n, c); + } + else { + s.insert(s.begin(), n, c); + } + } + } // -do_pad(..) + + + template inline + void put_head(BOOST_IO_STD ostream& , const T& ) { + } + + template inline + void put_head( BOOST_IO_STD ostream& os, const group1& x ) { + os << group_head(x.a1_); // send the first N-1 items, not the last + } + + template inline + void put_last( BOOST_IO_STD ostream& os, const T& x ) { + os << x ; + } + + template inline + void put_last( BOOST_IO_STD ostream& os, const group1& x ) { + os << group_last(x.a1_); // this selects the last element + } + +#ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + template inline + void put_head( BOOST_IO_STD ostream& , T& ) { + } + + template inline + void put_last( BOOST_IO_STD ostream& os, T& x ) { + os << x ; + } +#endif + + + + +template +void put( T x, + const format_item& specs, + std::string & res, + BOOST_IO_STD ostringstream& oss_ ) +{ + // does the actual conversion of x, with given params, into a string + // using the *supplied* strinstream. (the stream state is important) + + typedef std::string string_t; + typedef format_item format_item_t; + + stream_format_state prev_state(oss_); + + specs.state_.apply_on(oss_); + + // in case x is a group, apply the manip part of it, + // in order to find width + put_head( oss_, x ); + empty_buf( oss_); + + const std::streamsize w=oss_.width(); + const std::ios::fmtflags fl=oss_.flags(); + const bool internal = (fl & std::ios::internal) != 0; + const bool two_stepped_padding = internal + && ! ( specs.pad_scheme_ & format_item_t::spacepad ) + && specs.truncate_ < 0 ; + + + if(! two_stepped_padding) + { + if(w>0) // handle simple padding via do_pad, not natively in stream + oss_.width(0); + put_last( oss_, x); + res = oss_.str(); + + if (specs.truncate_ >= 0) + res.erase(specs.truncate_); + + // complex pads : + if(specs.pad_scheme_ & format_item_t::spacepad) + { + if( res.size()==0 || ( res[0]!='+' && res[0]!='-' )) + { + res.insert(res.begin(), 1, ' '); // insert 1 space at pos 0 + } + } + if(w > 0) // need do_pad + { + do_pad(res,w,oss_.fill(), fl, (specs.pad_scheme_ & format_item_t::centered) !=0 ); + } + } + else // 2-stepped padding + { + put_last( oss_, x); // oss_.width() may result in padding. + res = oss_.str(); + + if (specs.truncate_ >= 0) + res.erase(specs.truncate_); + + if( res.size() - w > 0) + { // length w exceeded + // either it was multi-output with first output padding up all width.. + // either it was one big arg and we are fine. + empty_buf( oss_); + oss_.width(0); + put_last(oss_, x ); + string_t tmp = oss_.str(); // minimal-length output + std::streamsize d; + if( (d=w - tmp.size()) <=0 ) + { + // minimal length is already >= w, so no padding (cool!) + res.swap(tmp); + } + else + { // hum.. we need to pad (it was necessarily multi-output) + typedef typename string_t::size_type size_type; + size_type i = 0; + while( i( d ), oss_.fill()); + res.swap( tmp ); + } + } + else + { // okay, only one thing was printed and padded, so res is fine. + } + } + + prev_state.apply_on(oss_); + empty_buf( oss_); + oss_.clear(); +} // end- put(..) + + +} // local namespace + + + + + +template +void distribute(basic_format& self, T x) + // call put(x, ..) on every occurence of the current argument : +{ + if(self.cur_arg_ >= self.num_args_) + { + if( self.exceptions() & too_many_args_bit ) + boost::throw_exception(too_many_args()); // too many variables have been supplied ! + else return; + } + for(unsigned long i=0; i < self.items_.size(); ++i) + { + if(self.items_[i].argN_ == self.cur_arg_) + { + put (x, self.items_[i], self.items_[i].res_, self.oss_ ); + } + } +} + +template +basic_format& feed(basic_format& self, T x) +{ + if(self.dumped_) self.clear(); + distribute (self, x); + ++self.cur_arg_; + if(self.bound_.size() != 0) + { + while( self.cur_arg_ < self.num_args_ && self.bound_[self.cur_arg_] ) + ++self.cur_arg_; + } + + // this arg is finished, reset the stream's format state + self.state0_.apply_on(self.oss_); + return self; +} + + +} // namespace detail +} // namespace io +} // namespace boost + + +#endif // BOOST_FORMAT_FEED_ARGS_HPP diff --git a/nix/boost/format/format_class.hpp b/nix/boost/format/format_class.hpp new file mode 100644 index 0000000000..6875623acb --- /dev/null +++ b/nix/boost/format/format_class.hpp @@ -0,0 +1,135 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// format_class.hpp : class interface +// ------------------------------------------------------------------------------ + + +#ifndef BOOST_FORMAT_CLASS_HPP +#define BOOST_FORMAT_CLASS_HPP + +#include +#include + +#include +#include + +#include + +namespace boost { + +class basic_format +{ +public: + typedef std::string string_t; + typedef BOOST_IO_STD ostringstream internal_stream_t; +private: + typedef BOOST_IO_STD ostream stream_t; + typedef io::detail::stream_format_state stream_format_state; + typedef io::detail::format_item format_item_t; + +public: + basic_format(const char* str); + basic_format(const string_t& s); +#ifndef BOOST_NO_STD_LOCALE + basic_format(const char* str, const std::locale & loc); + basic_format(const string_t& s, const std::locale & loc); +#endif // no locale + basic_format(const basic_format& x); + basic_format& operator= (const basic_format& x); + + basic_format& clear(); // empty the string buffers (except bound arguments, see clear_binds() ) + + // pass arguments through those operators : + template basic_format& operator%(const T& x) + { + return io::detail::feed(*this,x); + } + +#ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + template basic_format& operator%(T& x) + { + return io::detail::feed(*this,x); + } +#endif + + + // system for binding arguments : + template + basic_format& bind_arg(int argN, const T& val) + { + return io::detail::bind_arg_body(*this, argN, val); + } + basic_format& clear_bind(int argN); + basic_format& clear_binds(); + + // modify the params of a directive, by applying a manipulator : + template + basic_format& modify_item(int itemN, const T& manipulator) + { + return io::detail::modify_item_body(*this, itemN, manipulator) ; + } + + // Choosing which errors will throw exceptions : + unsigned char exceptions() const; + unsigned char exceptions(unsigned char newexcept); + + // final output + string_t str() const; + friend BOOST_IO_STD ostream& + operator<< ( BOOST_IO_STD ostream& , const basic_format& ); + + + template friend basic_format& + io::detail::feed(basic_format&, T); + + template friend + void io::detail::distribute(basic_format&, T); + + template friend + basic_format& io::detail::modify_item_body(basic_format&, int, const T&); + + template friend + basic_format& io::detail::bind_arg_body(basic_format&, int, const T&); + +// make the members private only if the friend templates are supported +private: + + // flag bits, used for style_ + enum style_values { ordered = 1, // set only if all directives are positional directives + special_needs = 4 }; + + // parse the format string : + void parse(const string_t&); + + int style_; // style of format-string : positional or not, etc + int cur_arg_; // keep track of wich argument will come + int num_args_; // number of expected arguments + mutable bool dumped_; // true only after call to str() or << + std::vector items_; // vector of directives (aka items) + string_t prefix_; // piece of string to insert before first item + + std::vector bound_; // stores which arguments were bound + // size = num_args OR zero + internal_stream_t oss_; // the internal stream. + stream_format_state state0_; // reference state for oss_ + unsigned char exceptions_; +}; // class basic_format + + +} // namespace boost + + +#endif // BOOST_FORMAT_CLASS_HPP diff --git a/nix/boost/format/format_fwd.hpp b/nix/boost/format/format_fwd.hpp new file mode 100644 index 0000000000..97c55f6684 --- /dev/null +++ b/nix/boost/format/format_fwd.hpp @@ -0,0 +1,49 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// format_fwd.hpp : forward declarations, for primary header format.hpp +// ------------------------------------------------------------------------------ + +#ifndef BOOST_FORMAT_FWD_HPP +#define BOOST_FORMAT_FWD_HPP + +#include +#include + +namespace boost { + +class basic_format; + +typedef basic_format format; + +namespace io { +enum format_error_bits { bad_format_string_bit = 1, + too_few_args_bit = 2, too_many_args_bit = 4, + out_of_range_bit = 8, + all_error_bits = 255, no_error_bits=0 }; + +// Convertion: format to string +std::string str(const basic_format& ) ; + +} // namespace io + + +BOOST_IO_STD ostream& +operator<<( BOOST_IO_STD ostream&, const basic_format&); + + +} // namespace boost + +#endif // BOOST_FORMAT_FWD_HPP diff --git a/nix/boost/format/format_implementation.cc b/nix/boost/format/format_implementation.cc new file mode 100644 index 0000000000..aa191afe11 --- /dev/null +++ b/nix/boost/format/format_implementation.cc @@ -0,0 +1,256 @@ +// -*- C++ -*- +// Boost general library format --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream + +// ---------------------------------------------------------------------------- +// format_implementation.hpp Implementation of the basic_format class +// ---------------------------------------------------------------------------- + + +#ifndef BOOST_FORMAT_IMPLEMENTATION_HPP +#define BOOST_FORMAT_IMPLEMENTATION_HPP + +#include +#include +#include + +namespace boost { + +// -------- format:: ------------------------------------------- +basic_format::basic_format(const char* str) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + items_(), oss_(), exceptions_(io::all_error_bits) +{ + state0_.set_by_stream(oss_); + string_t emptyStr; + if( !str) str = emptyStr.c_str(); + parse( str ); +} + +#ifndef BOOST_NO_STD_LOCALE +basic_format::basic_format(const char* str, const std::locale & loc) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + items_(), oss_(), exceptions_(io::all_error_bits) +{ + oss_.imbue( loc ); + state0_.set_by_stream(oss_); + string_t emptyStr; + if( !str) str = emptyStr.c_str(); + parse( str ); +} + +basic_format::basic_format(const string_t& s, const std::locale & loc) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + items_(), oss_(), exceptions_(io::all_error_bits) +{ + oss_.imbue( loc ); + state0_.set_by_stream(oss_); + parse(s); +} +#endif //BOOST_NO_STD_LOCALE + +basic_format::basic_format(const string_t& s) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + items_(), oss_(), exceptions_(io::all_error_bits) +{ + state0_.set_by_stream(oss_); + parse(s); +} + +basic_format:: basic_format(const basic_format& x) + : style_(x.style_), cur_arg_(x.cur_arg_), num_args_(x.num_args_), dumped_(false), + items_(x.items_), prefix_(x.prefix_), bound_(x.bound_), + oss_(), // <- we obviously can't copy x.oss_ + state0_(x.state0_), exceptions_(x.exceptions_) +{ + state0_.apply_on(oss_); +} + +basic_format& basic_format::operator= (const basic_format& x) +{ + if(this == &x) + return *this; + state0_ = x.state0_; + state0_.apply_on(oss_); + + // plus all the other (trivial) assignments : + exceptions_ = x.exceptions_; + items_ = x.items_; + prefix_ = x.prefix_; + bound_=x.bound_; + style_=x.style_; + cur_arg_=x.cur_arg_; + num_args_=x.num_args_; + dumped_=x.dumped_; + return *this; +} + + +unsigned char basic_format::exceptions() const +{ + return exceptions_; +} + +unsigned char basic_format::exceptions(unsigned char newexcept) +{ + unsigned char swp = exceptions_; + exceptions_ = newexcept; + return swp; +} + + +basic_format& basic_format ::clear() + // empty the string buffers (except bound arguments, see clear_binds() ) + // and make the format object ready for formatting a new set of arguments +{ + BOOST_ASSERT( bound_.size()==0 || num_args_ == static_cast(bound_.size()) ); + + for(unsigned long i=0; i num_args_ || bound_.size()==0 || !bound_[argN-1] ) + { + if( exceptions() & io::out_of_range_bit ) + boost::throw_exception(io::out_of_range()); // arg not in range. + else return *this; + } + bound_[argN-1]=false; + clear(); + return *this; +} + + + +std::string basic_format::str() const +{ + dumped_=true; + if(items_.size()==0) + return prefix_; + if( cur_arg_ < num_args_) + if( exceptions() & io::too_few_args_bit ) + boost::throw_exception(io::too_few_args()); // not enough variables have been supplied ! + + unsigned long sz = prefix_.size(); + unsigned long i; + for(i=0; i < items_.size(); ++i) + sz += items_[i].res_.size() + items_[i].appendix_.size(); + string_t res; + res.reserve(sz); + + res += prefix_; + for(i=0; i < items_.size(); ++i) + { + const format_item_t& item = items_[i]; + res += item.res_; + if( item.argN_ == format_item_t::argN_tabulation) + { + BOOST_ASSERT( item.pad_scheme_ & format_item_t::tabulation); + std::streamsize n = item.state_.width_ - res.size(); + if( n > 0 ) + res.append( n, item.state_.fill_ ); + } + res += item.appendix_; + } + return res; +} + +namespace io { +namespace detail { + +template +basic_format& bind_arg_body( basic_format& self, + int argN, + const T& val) + // bind one argument to a fixed value + // this is persistent over clear() calls, thus also over str() and << +{ + if(self.dumped_) self.clear(); // needed, because we will modify cur_arg_.. + if(argN<1 || argN > self.num_args_) + { + if( self.exceptions() & io::out_of_range_bit ) + boost::throw_exception(io::out_of_range()); // arg not in range. + else return self; + } + if(self.bound_.size()==0) + self.bound_.assign(self.num_args_,false); + else + BOOST_ASSERT( self.num_args_ == static_cast(self.bound_.size()) ); + int o_cur_arg = self.cur_arg_; + self.cur_arg_ = argN-1; // arrays begin at 0 + + self.bound_[self.cur_arg_]=false; // if already set, we unset and re-sets.. + self.operator%(val); // put val at the right place, because cur_arg is set + + + // Now re-position cur_arg before leaving : + self.cur_arg_ = o_cur_arg; + self.bound_[argN-1]=true; + if(self.cur_arg_ == argN-1 ) + // hum, now this arg is bound, so move to next free arg + { + while(self.cur_arg_ < self.num_args_ && self.bound_[self.cur_arg_]) ++self.cur_arg_; + } + // In any case, we either have all args, or are on a non-binded arg : + BOOST_ASSERT( self.cur_arg_ >= self.num_args_ || ! self.bound_[self.cur_arg_]); + return self; +} + +template +basic_format& modify_item_body( basic_format& self, + int itemN, + const T& manipulator) + // applies a manipulator to the format_item describing a given directive. + // this is a permanent change, clear or clear_binds won't cancel that. +{ + if(itemN<1 || itemN >= static_cast(self.items_.size() )) + { + if( self.exceptions() & io::out_of_range_bit ) + boost::throw_exception(io::out_of_range()); // item not in range. + else return self; + } + self.items_[itemN-1].ref_state_.apply_manip( manipulator ); + self.items_[itemN-1].state_ = self.items_[itemN-1].ref_state_; + return self; +} + +} // namespace detail + +} // namespace io + +} // namespace boost + + + +#endif // BOOST_FORMAT_IMPLEMENTATION_HPP diff --git a/nix/boost/format/free_funcs.cc b/nix/boost/format/free_funcs.cc new file mode 100644 index 0000000000..151db37a0a --- /dev/null +++ b/nix/boost/format/free_funcs.cc @@ -0,0 +1,71 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// free_funcs.hpp : implementation of the free functions declared in namespace format +// ------------------------------------------------------------------------------ + +#ifndef BOOST_FORMAT_FUNCS_HPP +#define BOOST_FORMAT_FUNCS_HPP + +#include "boost/format.hpp" +#include "boost/throw_exception.hpp" + +namespace boost { + +namespace io { + inline + std::string str(const basic_format& f) + // adds up all pieces of strings and converted items, and return the formatted string + { + return f.str(); + } +} // - namespace io + +BOOST_IO_STD ostream& +operator<<( BOOST_IO_STD ostream& os, + const boost::basic_format& f) + // effect: "return os << str(f);" but we can try to do it faster +{ + typedef boost::basic_format format_t; + if(f.items_.size()==0) + os << f.prefix_; + else { + if(f.cur_arg_ < f.num_args_) + if( f.exceptions() & io::too_few_args_bit ) + boost::throw_exception(io::too_few_args()); // not enough variables have been supplied ! + if(f.style_ & format_t::special_needs) + os << f.str(); + else { + // else we dont have to count chars output, so we dump directly to os : + os << f.prefix_; + for(unsigned long i=0; i +inline +BOOST_IO_STD ostream& +operator << ( BOOST_IO_STD ostream& os, + const group0& ) +{ + return os; +} + +template +struct group1 +{ + T1 a1_; + group1(T1 a1) + : a1_(a1) + {} +}; + +template +inline +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, + const group1& x) +{ + os << x.a1_; + return os; +} + + + + +template +struct group2 +{ + T1 a1_; + T2 a2_; + group2(T1 a1,T2 a2) + : a1_(a1),a2_(a2) + {} +}; + +template +inline +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, + const group2& x) +{ + os << x.a1_<< x.a2_; + return os; +} + +template +struct group3 +{ + T1 a1_; + T2 a2_; + T3 a3_; + group3(T1 a1,T2 a2,T3 a3) + : a1_(a1),a2_(a2),a3_(a3) + {} +}; + +template +inline +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, + const group3& x) +{ + os << x.a1_<< x.a2_<< x.a3_; + return os; +} + +template +struct group4 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + group4(T1 a1,T2 a2,T3 a3,T4 a4) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4) + {} +}; + +template +inline +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, + const group4& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_; + return os; +} + +template +struct group5 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + group5(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5) + {} +}; + +template +inline +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, + const group5& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_; + return os; +} + +template +struct group6 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + group6(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6) + {} +}; + +template +inline +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, + const group6& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_; + return os; +} + +template +struct group7 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + group7(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7) + {} +}; + +template +inline +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, + const group7& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_; + return os; +} + +template +struct group8 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + T8 a8_; + group8(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7),a8_(a8) + {} +}; + +template +inline +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, + const group8& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_; + return os; +} + +template +struct group9 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + T8 a8_; + T9 a9_; + group9(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7),a8_(a8),a9_(a9) + {} +}; + +template +inline +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, + const group9& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_<< x.a9_; + return os; +} + +template +struct group10 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + T8 a8_; + T9 a9_; + T10 a10_; + group10(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9,T10 a10) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7),a8_(a8),a9_(a9),a10_(a10) + {} +}; + +template +inline +BOOST_IO_STD ostream& +operator << (BOOST_IO_STD ostream& os, + const group10& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_<< x.a9_<< x.a10_; + return os; +} + + + + +template +inline +group1 +group_head( group2 const& x) +{ + return group1 (x.a1_); +} + +template +inline +group1 +group_last( group2 const& x) +{ + return group1 (x.a2_); +} + + + +template +inline +group2 +group_head( group3 const& x) +{ + return group2 (x.a1_,x.a2_); +} + +template +inline +group1 +group_last( group3 const& x) +{ + return group1 (x.a3_); +} + + + +template +inline +group3 +group_head( group4 const& x) +{ + return group3 (x.a1_,x.a2_,x.a3_); +} + +template +inline +group1 +group_last( group4 const& x) +{ + return group1 (x.a4_); +} + + + +template +inline +group4 +group_head( group5 const& x) +{ + return group4 (x.a1_,x.a2_,x.a3_,x.a4_); +} + +template +inline +group1 +group_last( group5 const& x) +{ + return group1 (x.a5_); +} + + + +template +inline +group5 +group_head( group6 const& x) +{ + return group5 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_); +} + +template +inline +group1 +group_last( group6 const& x) +{ + return group1 (x.a6_); +} + + + +template +inline +group6 +group_head( group7 const& x) +{ + return group6 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_); +} + +template +inline +group1 +group_last( group7 const& x) +{ + return group1 (x.a7_); +} + + + +template +inline +group7 +group_head( group8 const& x) +{ + return group7 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_,x.a7_); +} + +template +inline +group1 +group_last( group8 const& x) +{ + return group1 (x.a8_); +} + + + +template +inline +group8 +group_head( group9 const& x) +{ + return group8 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_,x.a7_,x.a8_); +} + +template +inline +group1 +group_last( group9 const& x) +{ + return group1 (x.a9_); +} + + + +template +inline +group9 +group_head( group10 const& x) +{ + return group9 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_,x.a7_,x.a8_,x.a9_); +} + +template +inline +group1 +group_last( group10 const& x) +{ + return group1 (x.a10_); +} + + + + + +} // namespace detail + + + +// helper functions + + +inline detail::group1< detail::group0 > +group() { return detail::group1< detail::group0 > ( detail::group0() ); } + +template +inline +detail::group1< detail::group2 > + group(T1 a1, Var const& var) +{ + return detail::group1< detail::group2 > + ( detail::group2 + (a1, var) + ); +} + +template +inline +detail::group1< detail::group3 > + group(T1 a1,T2 a2, Var const& var) +{ + return detail::group1< detail::group3 > + ( detail::group3 + (a1,a2, var) + ); +} + +template +inline +detail::group1< detail::group4 > + group(T1 a1,T2 a2,T3 a3, Var const& var) +{ + return detail::group1< detail::group4 > + ( detail::group4 + (a1,a2,a3, var) + ); +} + +template +inline +detail::group1< detail::group5 > + group(T1 a1,T2 a2,T3 a3,T4 a4, Var const& var) +{ + return detail::group1< detail::group5 > + ( detail::group5 + (a1,a2,a3,a4, var) + ); +} + +template +inline +detail::group1< detail::group6 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5, Var const& var) +{ + return detail::group1< detail::group6 > + ( detail::group6 + (a1,a2,a3,a4,a5, var) + ); +} + +template +inline +detail::group1< detail::group7 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6, Var const& var) +{ + return detail::group1< detail::group7 > + ( detail::group7 + (a1,a2,a3,a4,a5,a6, var) + ); +} + +template +inline +detail::group1< detail::group8 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7, Var const& var) +{ + return detail::group1< detail::group8 > + ( detail::group8 + (a1,a2,a3,a4,a5,a6,a7, var) + ); +} + +template +inline +detail::group1< detail::group9 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8, Var const& var) +{ + return detail::group1< detail::group9 > + ( detail::group9 + (a1,a2,a3,a4,a5,a6,a7,a8, var) + ); +} + +template +inline +detail::group1< detail::group10 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9, Var const& var) +{ + return detail::group1< detail::group10 > + ( detail::group10 + (a1,a2,a3,a4,a5,a6,a7,a8,a9, var) + ); +} + + +#ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + +template +inline +detail::group1< detail::group2 > + group(T1 a1, Var& var) +{ + return detail::group1< detail::group2 > + ( detail::group2 + (a1, var) + ); +} + +template +inline +detail::group1< detail::group3 > + group(T1 a1,T2 a2, Var& var) +{ + return detail::group1< detail::group3 > + ( detail::group3 + (a1,a2, var) + ); +} + +template +inline +detail::group1< detail::group4 > + group(T1 a1,T2 a2,T3 a3, Var& var) +{ + return detail::group1< detail::group4 > + ( detail::group4 + (a1,a2,a3, var) + ); +} + +template +inline +detail::group1< detail::group5 > + group(T1 a1,T2 a2,T3 a3,T4 a4, Var& var) +{ + return detail::group1< detail::group5 > + ( detail::group5 + (a1,a2,a3,a4, var) + ); +} + +template +inline +detail::group1< detail::group6 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5, Var& var) +{ + return detail::group1< detail::group6 > + ( detail::group6 + (a1,a2,a3,a4,a5, var) + ); +} + +template +inline +detail::group1< detail::group7 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6, Var& var) +{ + return detail::group1< detail::group7 > + ( detail::group7 + (a1,a2,a3,a4,a5,a6, var) + ); +} + +template +inline +detail::group1< detail::group8 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7, Var& var) +{ + return detail::group1< detail::group8 > + ( detail::group8 + (a1,a2,a3,a4,a5,a6,a7, var) + ); +} + +template +inline +detail::group1< detail::group9 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8, Var& var) +{ + return detail::group1< detail::group9 > + ( detail::group9 + (a1,a2,a3,a4,a5,a6,a7,a8, var) + ); +} + +template +inline +detail::group1< detail::group10 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9, Var& var) +{ + return detail::group1< detail::group10 > + ( detail::group10 + (a1,a2,a3,a4,a5,a6,a7,a8,a9, var) + ); +} + + +#endif //end- #ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + + +} // namespace io + +} // namespace boost + + +#endif // BOOST_FORMAT_GROUP_HPP diff --git a/nix/boost/format/internals.hpp b/nix/boost/format/internals.hpp new file mode 100644 index 0000000000..d25eb4c864 --- /dev/null +++ b/nix/boost/format/internals.hpp @@ -0,0 +1,167 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream + +// ---------------------------------------------------------------------------- +// internals.hpp : internal structs. included by format.hpp +// stream_format_state, and format_item +// ---------------------------------------------------------------------------- + + +#ifndef BOOST_FORMAT_INTERNALS_HPP +#define BOOST_FORMAT_INTERNALS_HPP + + +#include +#include + +namespace boost { +namespace io { +namespace detail { + + +// -------------- +// set of params that define the format state of a stream + +struct stream_format_state +{ + typedef std::ios basic_ios; + + std::streamsize width_; + std::streamsize precision_; + char fill_; + std::ios::fmtflags flags_; + + stream_format_state() : width_(-1), precision_(-1), fill_(0), flags_(std::ios::dec) {} + stream_format_state(basic_ios& os) {set_by_stream(os); } + + void apply_on(basic_ios & os) const; //- applies format_state to the stream + template void apply_manip(T manipulator) //- modifies state by applying manipulator. + { apply_manip_body( *this, manipulator) ; } + void reset(); //- sets to default state. + void set_by_stream(const basic_ios& os); //- sets to os's state. +}; + + + +// -------------- +// format_item : stores all parameters that can be defined by directives in the format-string + +struct format_item +{ + enum pad_values { zeropad = 1, spacepad =2, centered=4, tabulation = 8 }; + + enum arg_values { argN_no_posit = -1, // non-positional directive. argN will be set later. + argN_tabulation = -2, // tabulation directive. (no argument read) + argN_ignored = -3 // ignored directive. (no argument read) + }; + typedef BOOST_IO_STD ios basic_ios; + typedef detail::stream_format_state stream_format_state; + typedef std::string string_t; + typedef BOOST_IO_STD ostringstream internal_stream_t; + + + int argN_; //- argument number (starts at 0, eg : %1 => argN=0) + // negative values are used for items that don't process + // an argument + string_t res_; //- result of the formatting of this item + string_t appendix_; //- piece of string between this item and the next + + stream_format_state ref_state_;// set by parsing the format_string, is only affected by modify_item + stream_format_state state_; // always same as ref_state, _unless_ modified by manipulators 'group(..)' + + // non-stream format-state parameters + signed int truncate_; //- is >=0 for directives like %.5s (take 5 chars from the string) + unsigned int pad_scheme_; //- several possible padding schemes can mix. see pad_values + + format_item() : argN_(argN_no_posit), truncate_(-1), pad_scheme_(0) {} + + void compute_states(); // sets states according to truncate and pad_scheme. +}; + + + +// ----------------------------------------------------------- +// Definitions +// ----------------------------------------------------------- + +// --- stream_format_state:: ------------------------------------------- +inline +void stream_format_state::apply_on(basic_ios & os) const + // set the state of this stream according to our params +{ + if(width_ != -1) + os.width(width_); + if(precision_ != -1) + os.precision(precision_); + if(fill_ != 0) + os.fill(fill_); + os.flags(flags_); +} + +inline +void stream_format_state::set_by_stream(const basic_ios& os) + // set our params according to the state of this stream +{ + flags_ = os.flags(); + width_ = os.width(); + precision_ = os.precision(); + fill_ = os.fill(); +} + +template inline +void apply_manip_body( stream_format_state& self, + T manipulator) + // modify our params according to the manipulator +{ + BOOST_IO_STD stringstream ss; + self.apply_on( ss ); + ss << manipulator; + self.set_by_stream( ss ); +} + +inline +void stream_format_state::reset() + // set our params to standard's default state +{ + width_=-1; precision_=-1; fill_=0; + flags_ = std::ios::dec; +} + + +// --- format_items:: ------------------------------------------- +inline +void format_item::compute_states() + // reflect pad_scheme_ on state_ and ref_state_ + // because some pad_schemes has complex consequences on several state params. +{ + if(pad_scheme_ & zeropad) + { + if(ref_state_.flags_ & std::ios::left) + { + pad_scheme_ = pad_scheme_ & (~zeropad); // ignore zeropad in left alignment + } + else + { + ref_state_.fill_='0'; + ref_state_.flags_ |= std::ios::internal; + } + } + state_ = ref_state_; +} + + +} } } // namespaces boost :: io :: detail + + +#endif // BOOST_FORMAT_INTERNALS_HPP diff --git a/nix/boost/format/internals_fwd.hpp b/nix/boost/format/internals_fwd.hpp new file mode 100644 index 0000000000..a8ebf7c3ab --- /dev/null +++ b/nix/boost/format/internals_fwd.hpp @@ -0,0 +1,65 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// internals_fwd.hpp : forward declarations, for internal headers +// ------------------------------------------------------------------------------ + +#ifndef BOOST_FORMAT_INTERNAL_FWD_HPP +#define BOOST_FORMAT_INTERNAL_FWD_HPP + +#include "boost/format/format_fwd.hpp" + + +namespace boost { +namespace io { + +namespace detail { + struct stream_format_state; + struct format_item; +} + + +namespace detail { + + // these functions were intended as methods, + // but MSVC have problems with template member functions : + + // defined in format_implementation.hpp : + template + basic_format& modify_item_body( basic_format& self, + int itemN, const T& manipulator); + + template + basic_format& bind_arg_body( basic_format& self, + int argN, const T& val); + + template + void apply_manip_body( stream_format_state& self, + T manipulator); + + // argument feeding (defined in feed_args.hpp ) : + template + void distribute(basic_format& self, T x); + + template + basic_format& feed(basic_format& self, T x); + +} // namespace detail + +} // namespace io +} // namespace boost + + +#endif // BOOST_FORMAT_INTERNAL_FWD_HPP diff --git a/nix/boost/format/macros_default.hpp b/nix/boost/format/macros_default.hpp new file mode 100644 index 0000000000..4fd84a163f --- /dev/null +++ b/nix/boost/format/macros_default.hpp @@ -0,0 +1,48 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rdiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// macros_default.hpp : configuration for the format library +// provides default values for the stl workaround macros +// ------------------------------------------------------------------------------ + +#ifndef BOOST_FORMAT_MACROS_DEFAULT_HPP +#define BOOST_FORMAT_MACROS_DEFAULT_HPP + +// *** This should go to "boost/config/suffix.hpp". + +#ifndef BOOST_IO_STD +# define BOOST_IO_STD std:: +#endif + +// **** Workaround for io streams, stlport and msvc. +#ifdef BOOST_IO_NEEDS_USING_DECLARATION +namespace boost { + using std::char_traits; + using std::basic_ostream; + using std::basic_ostringstream; + namespace io { + using std::basic_ostream; + namespace detail { + using std::basic_ios; + using std::basic_ostream; + using std::basic_ostringstream; + } + } +} +#endif + +// ------------------------------------------------------------------------------ + +#endif // BOOST_FORMAT_MACROS_DEFAULT_HPP diff --git a/nix/boost/format/parsing.cc b/nix/boost/format/parsing.cc new file mode 100644 index 0000000000..34c36adeb7 --- /dev/null +++ b/nix/boost/format/parsing.cc @@ -0,0 +1,454 @@ +// -*- C++ -*- +// Boost general library 'format' --------------------------- +// See http://www.boost.org for updates, documentation, and revision history. + +// (C) Samuel Krempp 2001 +// krempp@crans.ens-cachan.fr +// Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. + +// ideas taken from Rudiger Loos's format class +// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing) + +// ------------------------------------------------------------------------------ +// parsing.hpp : implementation of the parsing member functions +// ( parse, parse_printf_directive) +// ------------------------------------------------------------------------------ + + +#ifndef BOOST_FORMAT_PARSING_HPP +#define BOOST_FORMAT_PARSING_HPP + + +#include +#include +#include + + +namespace boost { +namespace io { +namespace detail { + + template inline + bool wrap_isdigit(char c, Stream &os) + { +#ifndef BOOST_NO_LOCALE_ISIDIGIT + return std::isdigit(c, os.rdbuf()->getloc() ); +# else + using namespace std; + return isdigit(c); +#endif + } //end- wrap_isdigit(..) + + template inline + Res str2int(const std::string& s, + std::string::size_type start, + BOOST_IO_STD ios &os, + const Res = Res(0) ) + // Input : char string, with starting index + // a basic_ios& merely to call its widen/narrow member function in the desired locale. + // Effects : reads s[start:] and converts digits into an integral n, of type Res + // Returns : n + { + Res n = 0; + while(start= buf.size() ) return; + if(buf[ *pos_p]=='*') { + ++ (*pos_p); + while (*pos_p < buf.size() && wrap_isdigit(buf[*pos_p],os)) ++(*pos_p); + if(buf[*pos_p]=='$') ++(*pos_p); + } + } + + + inline void maybe_throw_exception( unsigned char exceptions) + // auxiliary func called by parse_printf_directive + // for centralising error handling + // it either throws if user sets the corresponding flag, or does nothing. + { + if(exceptions & io::bad_format_string_bit) + boost::throw_exception(io::bad_format_string()); + } + + + + bool parse_printf_directive(const std::string & buf, + std::string::size_type * pos_p, + detail::format_item * fpar, + BOOST_IO_STD ios &os, + unsigned char exceptions) + // Input : a 'printf-directive' in the format-string, starting at buf[ *pos_p ] + // a basic_ios& merely to call its widen/narrow member function in the desired locale. + // a bitset'excpetions' telling whether to throw exceptions on errors. + // Returns : true if parse somehow succeeded (possibly ignoring errors if exceptions disabled) + // false if it failed so bad that the directive should be printed verbatim + // Effects : - *pos_p is incremented so that buf[*pos_p] is the first char after the directive + // - *fpar is set with the parameters read in the directive + { + typedef format_item format_item_t; + BOOST_ASSERT( pos_p != 0); + std::string::size_type &i1 = *pos_p, + i0; + fpar->argN_ = format_item_t::argN_no_posit; // if no positional-directive + + bool in_brackets=false; + if(buf[i1]=='|') + { + in_brackets=true; + if( ++i1 >= buf.size() ) { + maybe_throw_exception(exceptions); + return false; + } + } + + // the flag '0' would be picked as a digit for argument order, but here it's a flag : + if(buf[i1]=='0') + goto parse_flags; + + // handle argument order (%2$d) or possibly width specification: %2d + i0 = i1; // save position before digits + while (i1 < buf.size() && wrap_isdigit(buf[i1], os)) + ++i1; + if (i1!=i0) + { + if( i1 >= buf.size() ) { + maybe_throw_exception(exceptions); + return false; + } + int n=str2int(buf,i0, os, int(0) ); + + // %N% case : this is already the end of the directive + if( buf[i1] == '%' ) + { + fpar->argN_ = n-1; + ++i1; + if( in_brackets) + maybe_throw_exception(exceptions); + // but don't return. maybe "%" was used in lieu of '$', so we go on. + else return true; + } + + if ( buf[i1]=='$' ) + { + fpar->argN_ = n-1; + ++i1; + } + else + { + // non-positionnal directive + fpar->ref_state_.width_ = n; + fpar->argN_ = format_item_t::argN_no_posit; + goto parse_precision; + } + } + + parse_flags: + // handle flags + while ( i1 ref_state_.flags_ |= std::ios::left; + break; + case '=': + fpar->pad_scheme_ |= format_item_t::centered; + break; + case ' ': + fpar->pad_scheme_ |= format_item_t::spacepad; + break; + case '+': + fpar->ref_state_.flags_ |= std::ios::showpos; + break; + case '0': + fpar->pad_scheme_ |= format_item_t::zeropad; + // need to know alignment before really setting flags, + // so just add 'zeropad' flag for now, it will be processed later. + break; + case '#': + fpar->ref_state_.flags_ |= std::ios::showpoint | std::ios::showbase; + break; + default: + goto parse_width; + } + ++i1; + } // loop on flag. + if( i1>=buf.size()) { + maybe_throw_exception(exceptions); + return true; + } + + parse_width: + // handle width spec + skip_asterisk(buf, &i1, os); // skips 'asterisk fields' : *, or *N$ + i0 = i1; // save position before digits + while (i1ref_state_.width_ = str2int( buf,i0, os, std::streamsize(0) ); } + + parse_precision: + if( i1>=buf.size()) { + maybe_throw_exception(exceptions); + return true; + } + // handle precision spec + if (buf[i1]=='.') + { + ++i1; + skip_asterisk(buf, &i1, os); + i0 = i1; // save position before digits + while (i1ref_state_.precision_ = 0; + else + fpar->ref_state_.precision_ = str2int(buf,i0, os, std::streamsize(0) ); + } + + // handle formatting-type flags : + while( i1=buf.size()) { + maybe_throw_exception(exceptions); + return true; + } + + if( in_brackets && buf[i1]=='|' ) + { + ++i1; + return true; + } + switch (buf[i1]) + { + case 'X': + fpar->ref_state_.flags_ |= std::ios::uppercase; + case 'p': // pointer => set hex. + case 'x': + fpar->ref_state_.flags_ &= ~std::ios::basefield; + fpar->ref_state_.flags_ |= std::ios::hex; + break; + + case 'o': + fpar->ref_state_.flags_ &= ~std::ios::basefield; + fpar->ref_state_.flags_ |= std::ios::oct; + break; + + case 'E': + fpar->ref_state_.flags_ |= std::ios::uppercase; + case 'e': + fpar->ref_state_.flags_ &= ~std::ios::floatfield; + fpar->ref_state_.flags_ |= std::ios::scientific; + + fpar->ref_state_.flags_ &= ~std::ios::basefield; + fpar->ref_state_.flags_ |= std::ios::dec; + break; + + case 'f': + fpar->ref_state_.flags_ &= ~std::ios::floatfield; + fpar->ref_state_.flags_ |= std::ios::fixed; + case 'u': + case 'd': + case 'i': + fpar->ref_state_.flags_ &= ~std::ios::basefield; + fpar->ref_state_.flags_ |= std::ios::dec; + break; + + case 'T': + ++i1; + if( i1 >= buf.size()) + maybe_throw_exception(exceptions); + else + fpar->ref_state_.fill_ = buf[i1]; + fpar->pad_scheme_ |= format_item_t::tabulation; + fpar->argN_ = format_item_t::argN_tabulation; + break; + case 't': + fpar->ref_state_.fill_ = ' '; + fpar->pad_scheme_ |= format_item_t::tabulation; + fpar->argN_ = format_item_t::argN_tabulation; + break; + + case 'G': + fpar->ref_state_.flags_ |= std::ios::uppercase; + break; + case 'g': // 'g' conversion is default for floats. + fpar->ref_state_.flags_ &= ~std::ios::basefield; + fpar->ref_state_.flags_ |= std::ios::dec; + + // CLEAR all floatield flags, so stream will CHOOSE + fpar->ref_state_.flags_ &= ~std::ios::floatfield; + break; + + case 'C': + case 'c': + fpar->truncate_ = 1; + break; + case 'S': + case 's': + fpar->truncate_ = fpar->ref_state_.precision_; + fpar->ref_state_.precision_ = -1; + break; + case 'n' : + fpar->argN_ = format_item_t::argN_ignored; + break; + default: + maybe_throw_exception(exceptions); + } + ++i1; + + if( in_brackets ) + { + if( i1= buf.size() ) { + if(exceptions() & io::bad_format_string_bit) + boost::throw_exception(io::bad_format_string()); // must not end in "bla bla %" + else break; // stop there, ignore last '%' + } + if(buf[i1+1] == buf[i1] ) { i1+=2; continue; } // escaped "%%" / "##" + ++i1; + + // in case of %N% directives, dont count it double (wastes allocations..) : + while(i1 < buf.size() && io::detail::wrap_isdigit(buf[i1],oss_)) ++i1; + if( i1 < buf.size() && buf[i1] == arg_mark ) ++ i1; + + ++num_items; + } + items_.assign( num_items, format_item_t() ); + + // B: Now the real parsing of the format string : + num_items=0; + i1 = 0; + string_t::size_type i0 = i1; + bool special_things=false; + int cur_it=0; + while( (i1=buf.find(arg_mark,i1)) != string::npos ) + { + string_t & piece = (cur_it==0) ? prefix_ : items_[cur_it-1].appendix_; + + if( buf[i1+1] == buf[i1] ) // escaped mark, '%%' + { + piece += buf.substr(i0, i1-i0) + buf[i1]; + i1+=2; i0=i1; + continue; + } + BOOST_ASSERT( static_cast(cur_it) < items_.size() || cur_it==0); + + if(i1!=i0) piece += buf.substr(i0, i1-i0); + ++i1; + + bool parse_ok; + parse_ok = io::detail::parse_printf_directive(buf, &i1, &items_[cur_it], oss_, exceptions()); + if( ! parse_ok ) continue; // the directive will be printed verbatim + + i0=i1; + items_[cur_it].compute_states(); // process complex options, like zeropad, into stream params. + + int argN=items_[cur_it].argN_; + if(argN == format_item_t::argN_ignored) + continue; + if(argN ==format_item_t::argN_no_posit) + ordered_args=false; + else if(argN == format_item_t::argN_tabulation) special_things=true; + else if(argN > max_argN) max_argN = argN; + ++num_items; + ++cur_it; + } // loop on %'s + BOOST_ASSERT(cur_it == num_items); + + // store the final piece of string + string_t & piece = (cur_it==0) ? prefix_ : items_[cur_it-1].appendix_; + piece += buf.substr(i0); + + if( !ordered_args) + { + if(max_argN >= 0 ) // dont mix positional with non-positionnal directives + { + if(exceptions() & io::bad_format_string_bit) + boost::throw_exception(io::bad_format_string()); + // else do nothing. => positionnal arguments are processed as non-positionnal + } + // set things like it would have been with positional directives : + int non_ordered_items = 0; + for(int i=0; i< num_items; ++i) + if(items_[i].argN_ == format_item_t::argN_no_posit) + { + items_[i].argN_ = non_ordered_items; + ++non_ordered_items; + } + max_argN = non_ordered_items-1; + } + + // C: set some member data : + items_.resize(num_items); + + if(special_things) style_ |= special_needs; + num_args_ = max_argN + 1; + if(ordered_args) style_ |= ordered; + else style_ &= ~ordered; +} + +} // namespace boost + + +#endif // BOOST_FORMAT_PARSING_HPP diff --git a/nix/boost/throw_exception.hpp b/nix/boost/throw_exception.hpp new file mode 100644 index 0000000000..07b4ae5cea --- /dev/null +++ b/nix/boost/throw_exception.hpp @@ -0,0 +1,47 @@ +#ifndef BOOST_THROW_EXCEPTION_HPP_INCLUDED +#define BOOST_THROW_EXCEPTION_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// boost/throw_exception.hpp +// +// Copyright (c) 2002 Peter Dimov and Multi Media Ltd. +// +// Permission to copy, use, modify, sell and distribute this software +// is granted provided this copyright notice appears in all copies. +// This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// http://www.boost.org/libs/utility/throw_exception.html +// + +//#include + +#ifdef BOOST_NO_EXCEPTIONS +# include +#endif + +namespace boost +{ + +#ifdef BOOST_NO_EXCEPTIONS + +void throw_exception(std::exception const & e); // user defined + +#else + +template void throw_exception(E const & e) +{ + throw e; +} + +#endif + +} // namespace boost + +#endif // #ifndef BOOST_THROW_EXCEPTION_HPP_INCLUDED diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc new file mode 100644 index 0000000000..2e2f92fadf --- /dev/null +++ b/nix/libstore/build.cc @@ -0,0 +1,3351 @@ +#include "config.h" + +#include "references.hh" +#include "pathlocks.hh" +#include "misc.hh" +#include "globals.hh" +#include "local-store.hh" +#include "util.hh" +#include "archive.hh" +#include "affinity.hh" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* Includes required for chroot support. */ +#if HAVE_SYS_PARAM_H +#include +#endif +#if HAVE_SYS_MOUNT_H +#include +#endif +#if HAVE_SCHED_H +#include +#endif + +/* In GNU libc 2.11, does not define `MS_PRIVATE', but + does. */ +#if !defined MS_PRIVATE && defined HAVE_LINUX_FS_H +#include +#endif + +#define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS) + +#if CHROOT_ENABLED +#include +#include +#include +#include +#endif + +#if HAVE_SYS_PERSONALITY_H +#include +#define CAN_DO_LINUX32_BUILDS +#endif + +#if HAVE_STATVFS +#include +#endif + + +namespace nix { + +using std::map; + + +static string pathNullDevice = "/dev/null"; + + +/* Forward definition. */ +class Worker; +struct HookInstance; + + +/* A pointer to a goal. */ +class Goal; +typedef std::shared_ptr GoalPtr; +typedef std::weak_ptr WeakGoalPtr; + +/* Set of goals. */ +typedef set Goals; +typedef list WeakGoals; + +/* A map of paths to goals (and the other way around). */ +typedef map WeakGoalMap; + + + +class Goal : public std::enable_shared_from_this +{ +public: + typedef enum {ecBusy, ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode; + +protected: + + /* Backlink to the worker. */ + Worker & worker; + + /* Goals that this goal is waiting for. */ + Goals waitees; + + /* Goals waiting for this one to finish. Must use weak pointers + here to prevent cycles. */ + WeakGoals waiters; + + /* Number of goals we are/were waiting for that have failed. */ + unsigned int nrFailed; + + /* Number of substitution goals we are/were waiting for that + failed because there are no substituters. */ + unsigned int nrNoSubstituters; + + /* Number of substitution goals we are/were waiting for that + failed because othey had unsubstitutable references. */ + unsigned int nrIncompleteClosure; + + /* Name of this goal for debugging purposes. */ + string name; + + /* Whether the goal is finished. */ + ExitCode exitCode; + + Goal(Worker & worker) : worker(worker) + { + nrFailed = nrNoSubstituters = nrIncompleteClosure = 0; + exitCode = ecBusy; + } + + virtual ~Goal() + { + trace("goal destroyed"); + } + +public: + virtual void work() = 0; + + void addWaitee(GoalPtr waitee); + + virtual void waiteeDone(GoalPtr waitee, ExitCode result); + + virtual void handleChildOutput(int fd, const string & data) + { + abort(); + } + + virtual void handleEOF(int fd) + { + abort(); + } + + void trace(const format & f); + + string getName() + { + return name; + } + + ExitCode getExitCode() + { + return exitCode; + } + + /* Cancel the goal. It should wake up its waiters, get rid of any + running child processes that are being monitored by the worker + (important!), etc. */ + virtual void cancel(bool timeout) = 0; + +protected: + void amDone(ExitCode result); +}; + + +/* A mapping used to remember for each child process to what goal it + belongs, and file descriptors for receiving log data and output + path creation commands. */ +struct Child +{ + WeakGoalPtr goal; + set fds; + bool respectTimeouts; + bool inBuildSlot; + time_t lastOutput; /* time we last got output on stdout/stderr */ + time_t timeStarted; +}; + +typedef map Children; + + +/* The worker class. */ +class Worker +{ +private: + + /* Note: the worker should only have strong pointers to the + top-level goals. */ + + /* The top-level goals of the worker. */ + Goals topGoals; + + /* Goals that are ready to do some work. */ + WeakGoals awake; + + /* Goals waiting for a build slot. */ + WeakGoals wantingToBuild; + + /* Child processes currently running. */ + Children children; + + /* Number of build slots occupied. This includes local builds and + substitutions but not remote builds via the build hook. */ + unsigned int nrLocalBuilds; + + /* Maps used to prevent multiple instantiations of a goal for the + same derivation / path. */ + WeakGoalMap derivationGoals; + WeakGoalMap substitutionGoals; + + /* Goals waiting for busy paths to be unlocked. */ + WeakGoals waitingForAnyGoal; + + /* Goals sleeping for a few seconds (polling a lock). */ + WeakGoals waitingForAWhile; + + /* Last time the goals in `waitingForAWhile' where woken up. */ + time_t lastWokenUp; + +public: + + /* Set if at least one derivation had a BuildError (i.e. permanent + failure). */ + bool permanentFailure; + + LocalStore & store; + + std::shared_ptr hook; + + Worker(LocalStore & store); + ~Worker(); + + /* Make a goal (with caching). */ + GoalPtr makeDerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, BuildMode buildMode = bmNormal); + GoalPtr makeSubstitutionGoal(const Path & storePath, bool repair = false); + + /* Remove a dead goal. */ + void removeGoal(GoalPtr goal); + + /* Wake up a goal (i.e., there is something for it to do). */ + void wakeUp(GoalPtr goal); + + /* Return the number of local build and substitution processes + currently running (but not remote builds via the build + hook). */ + unsigned int getNrLocalBuilds(); + + /* Registers a running child process. `inBuildSlot' means that + the process counts towards the jobs limit. */ + void childStarted(GoalPtr goal, pid_t pid, + const set & fds, bool inBuildSlot, bool respectTimeouts); + + /* Unregisters a running child process. `wakeSleepers' should be + false if there is no sense in waking up goals that are sleeping + because they can't run yet (e.g., there is no free build slot, + or the hook would still say `postpone'). */ + void childTerminated(pid_t pid, bool wakeSleepers = true); + + /* Put `goal' to sleep until a build slot becomes available (which + might be right away). */ + void waitForBuildSlot(GoalPtr goal); + + /* Wait for any goal to finish. Pretty indiscriminate way to + wait for some resource that some other goal is holding. */ + void waitForAnyGoal(GoalPtr goal); + + /* Wait for a few seconds and then retry this goal. Used when + waiting for a lock held by another process. This kind of + polling is inefficient, but POSIX doesn't really provide a way + to wait for multiple locks in the main select() loop. */ + void waitForAWhile(GoalPtr goal); + + /* Loop until the specified top-level goals have finished. */ + void run(const Goals & topGoals); + + /* Wait for input to become available. */ + void waitForInput(); + + unsigned int exitStatus(); +}; + + +////////////////////////////////////////////////////////////////////// + + +void addToWeakGoals(WeakGoals & goals, GoalPtr p) +{ + // FIXME: necessary? + foreach (WeakGoals::iterator, i, goals) + if (i->lock() == p) return; + goals.push_back(p); +} + + +void Goal::addWaitee(GoalPtr waitee) +{ + waitees.insert(waitee); + addToWeakGoals(waitee->waiters, shared_from_this()); +} + + +void Goal::waiteeDone(GoalPtr waitee, ExitCode result) +{ + assert(waitees.find(waitee) != waitees.end()); + waitees.erase(waitee); + + trace(format("waitee `%1%' done; %2% left") % + waitee->name % waitees.size()); + + if (result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure) ++nrFailed; + + if (result == ecNoSubstituters) ++nrNoSubstituters; + + if (result == ecIncompleteClosure) ++nrIncompleteClosure; + + if (waitees.empty() || (result == ecFailed && !settings.keepGoing)) { + + /* If we failed and keepGoing is not set, we remove all + remaining waitees. */ + foreach (Goals::iterator, i, waitees) { + GoalPtr goal = *i; + WeakGoals waiters2; + foreach (WeakGoals::iterator, j, goal->waiters) + if (j->lock() != shared_from_this()) waiters2.push_back(*j); + goal->waiters = waiters2; + } + waitees.clear(); + + worker.wakeUp(shared_from_this()); + } +} + + +void Goal::amDone(ExitCode result) +{ + trace("done"); + assert(exitCode == ecBusy); + assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure); + exitCode = result; + foreach (WeakGoals::iterator, i, waiters) { + GoalPtr goal = i->lock(); + if (goal) goal->waiteeDone(shared_from_this(), result); + } + waiters.clear(); + worker.removeGoal(shared_from_this()); +} + + +void Goal::trace(const format & f) +{ + debug(format("%1%: %2%") % name % f); +} + + + +////////////////////////////////////////////////////////////////////// + + +/* Common initialisation performed in child processes. */ +static void commonChildInit(Pipe & logPipe) +{ + restoreAffinity(); + + /* Put the child in a separate session (and thus a separate + process group) so that it has no controlling terminal (meaning + that e.g. ssh cannot open /dev/tty) and it doesn't receive + terminal signals. */ + if (setsid() == -1) + throw SysError(format("creating a new session")); + + /* Dup the write side of the logger pipe into stderr. */ + if (dup2(logPipe.writeSide, STDERR_FILENO) == -1) + throw SysError("cannot pipe standard error into log file"); + + /* Dup stderr to stdout. */ + if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) + throw SysError("cannot dup stderr into stdout"); + + /* Reroute stdin to /dev/null. */ + int fdDevNull = open(pathNullDevice.c_str(), O_RDWR); + if (fdDevNull == -1) + throw SysError(format("cannot open `%1%'") % pathNullDevice); + if (dup2(fdDevNull, STDIN_FILENO) == -1) + throw SysError("cannot dup null device into stdin"); + close(fdDevNull); +} + + +/* Convert a string list to an array of char pointers. Careful: the + string list should outlive the array. */ +const char * * strings2CharPtrs(const Strings & ss) +{ + const char * * arr = new const char * [ss.size() + 1]; + const char * * p = arr; + foreach (Strings::const_iterator, i, ss) *p++ = i->c_str(); + *p = 0; + return arr; +} + + +/* Restore default handling of SIGPIPE, otherwise some programs will + randomly say "Broken pipe". */ +static void restoreSIGPIPE() +{ + struct sigaction act, oact; + act.sa_handler = SIG_DFL; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + if (sigaction(SIGPIPE, &act, &oact)) throw SysError("resetting SIGPIPE"); +} + + +////////////////////////////////////////////////////////////////////// + + +class UserLock +{ +private: + /* POSIX locks suck. If we have a lock on a file, and we open and + close that file again (without closing the original file + descriptor), we lose the lock. So we have to be *very* careful + not to open a lock file on which we are holding a lock. */ + static PathSet lockedPaths; /* !!! not thread-safe */ + + Path fnUserLock; + AutoCloseFD fdUserLock; + + string user; + uid_t uid; + gid_t gid; + +public: + UserLock(); + ~UserLock(); + + void acquire(); + void release(); + + void kill(); + + string getUser() { return user; } + uid_t getUID() { return uid; } + uid_t getGID() { return gid; } + + bool enabled() { return uid != 0; } + +}; + + +PathSet UserLock::lockedPaths; + + +UserLock::UserLock() +{ + uid = gid = 0; +} + + +UserLock::~UserLock() +{ + release(); +} + + +void UserLock::acquire() +{ + assert(uid == 0); + + assert(settings.buildUsersGroup != ""); + + /* Get the members of the build-users-group. */ + struct group * gr = getgrnam(settings.buildUsersGroup.c_str()); + if (!gr) + throw Error(format("the group `%1%' specified in `build-users-group' does not exist") + % settings.buildUsersGroup); + gid = gr->gr_gid; + + /* Copy the result of getgrnam. */ + Strings users; + for (char * * p = gr->gr_mem; *p; ++p) { + debug(format("found build user `%1%'") % *p); + users.push_back(*p); + } + + if (users.empty()) + throw Error(format("the build users group `%1%' has no members") + % settings.buildUsersGroup); + + /* Find a user account that isn't currently in use for another + build. */ + foreach (Strings::iterator, i, users) { + debug(format("trying user `%1%'") % *i); + + struct passwd * pw = getpwnam(i->c_str()); + if (!pw) + throw Error(format("the user `%1%' in the group `%2%' does not exist") + % *i % settings.buildUsersGroup); + + createDirs(settings.nixStateDir + "/userpool"); + + fnUserLock = (format("%1%/userpool/%2%") % settings.nixStateDir % pw->pw_uid).str(); + + if (lockedPaths.find(fnUserLock) != lockedPaths.end()) + /* We already have a lock on this one. */ + continue; + + AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT, 0600); + if (fd == -1) + throw SysError(format("opening user lock `%1%'") % fnUserLock); + closeOnExec(fd); + + if (lockFile(fd, ltWrite, false)) { + fdUserLock = fd.borrow(); + lockedPaths.insert(fnUserLock); + user = *i; + uid = pw->pw_uid; + + /* Sanity check... */ + if (uid == getuid() || uid == geteuid()) + throw Error(format("the Nix user should not be a member of `%1%'") + % settings.buildUsersGroup); + + return; + } + } + + throw Error(format("all build users are currently in use; " + "consider creating additional users and adding them to the `%1%' group") + % settings.buildUsersGroup); +} + + +void UserLock::release() +{ + if (uid == 0) return; + fdUserLock.close(); /* releases lock */ + assert(lockedPaths.find(fnUserLock) != lockedPaths.end()); + lockedPaths.erase(fnUserLock); + fnUserLock = ""; + uid = 0; +} + + +void UserLock::kill() +{ + assert(enabled()); + killUser(uid); +} + + +////////////////////////////////////////////////////////////////////// + + +struct HookInstance +{ + /* Pipes for talking to the build hook. */ + Pipe toHook; + + /* Pipe for the hook's standard output/error. */ + Pipe fromHook; + + /* Pipe for the builder's standard output/error. */ + Pipe builderOut; + + /* The process ID of the hook. */ + Pid pid; + + HookInstance(); + + ~HookInstance(); +}; + + +HookInstance::HookInstance() +{ + debug("starting build hook"); + + Path buildHook = absPath(getEnv("NIX_BUILD_HOOK")); + + /* Create a pipe to get the output of the child. */ + fromHook.create(); + + /* Create the communication pipes. */ + toHook.create(); + + /* Create a pipe to get the output of the builder. */ + builderOut.create(); + + /* Fork the hook. */ + pid = maybeVfork(); + switch (pid) { + + case -1: + throw SysError("unable to fork"); + + case 0: + try { /* child */ + + commonChildInit(fromHook); + + if (chdir("/") == -1) throw SysError("changing into `/"); + + /* Dup the communication pipes. */ + if (dup2(toHook.readSide, STDIN_FILENO) == -1) + throw SysError("dupping to-hook read side"); + + /* Use fd 4 for the builder's stdout/stderr. */ + if (dup2(builderOut.writeSide, 4) == -1) + throw SysError("dupping builder's stdout/stderr"); + + execl(buildHook.c_str(), buildHook.c_str(), settings.thisSystem.c_str(), + (format("%1%") % settings.maxSilentTime).str().c_str(), + (format("%1%") % settings.printBuildTrace).str().c_str(), + (format("%1%") % settings.buildTimeout).str().c_str(), + NULL); + + throw SysError(format("executing `%1%'") % buildHook); + + } catch (std::exception & e) { + writeToStderr("build hook error: " + string(e.what()) + "\n"); + } + _exit(1); + } + + /* parent */ + pid.setSeparatePG(true); + pid.setKillSignal(SIGTERM); + fromHook.writeSide.close(); + toHook.readSide.close(); +} + + +HookInstance::~HookInstance() +{ + try { + pid.kill(); + } catch (...) { + ignoreException(); + } +} + + +////////////////////////////////////////////////////////////////////// + + +typedef map HashRewrites; + + +string rewriteHashes(string s, const HashRewrites & rewrites) +{ + foreach (HashRewrites::const_iterator, i, rewrites) { + assert(i->first.size() == i->second.size()); + size_t j = 0; + while ((j = s.find(i->first, j)) != string::npos) { + debug(format("rewriting @ %1%") % j); + s.replace(j, i->second.size(), i->second); + } + } + return s; +} + + +////////////////////////////////////////////////////////////////////// + + +typedef enum {rpAccept, rpDecline, rpPostpone} HookReply; + +class SubstitutionGoal; + +class DerivationGoal : public Goal +{ +private: + /* The path of the derivation. */ + Path drvPath; + + /* The specific outputs that we need to build. Empty means all of + them. */ + StringSet wantedOutputs; + + /* Whether additional wanted outputs have been added. */ + bool needRestart; + + /* Whether to retry substituting the outputs after building the + inputs. */ + bool retrySubstitution; + + /* The derivation stored at drvPath. */ + Derivation drv; + + /* The remainder is state held during the build. */ + + /* Locks on the output paths. */ + PathLocks outputLocks; + + /* All input paths (that is, the union of FS closures of the + immediate input paths). */ + PathSet inputPaths; + + /* Referenceable paths (i.e., input and output paths). */ + PathSet allPaths; + + /* Outputs that are already valid. If we're repairing, these are + the outputs that are valid *and* not corrupt. */ + PathSet validPaths; + + /* Outputs that are corrupt or not valid. */ + PathSet missingPaths; + + /* User selected for running the builder. */ + UserLock buildUser; + + /* The process ID of the builder. */ + Pid pid; + + /* The temporary directory. */ + Path tmpDir; + + /* File descriptor for the log file. */ + FILE * fLogFile; + BZFILE * bzLogFile; + AutoCloseFD fdLogFile; + + /* Number of bytes received from the builder's stdout/stderr. */ + unsigned long logSize; + + /* Pipe for the builder's standard output/error. */ + Pipe builderOut; + + /* The build hook. */ + std::shared_ptr hook; + + /* Whether we're currently doing a chroot build. */ + bool useChroot; + + Path chrootRootDir; + + /* RAII object to delete the chroot directory. */ + std::shared_ptr autoDelChroot; + + /* All inputs that are regular files. */ + PathSet regularInputPaths; + + /* Whether this is a fixed-output derivation. */ + bool fixedOutput; + + typedef void (DerivationGoal::*GoalState)(); + GoalState state; + + /* Stuff we need to pass to initChild(). */ + typedef map DirsInChroot; // maps target path to source path + DirsInChroot dirsInChroot; + typedef map Environment; + Environment env; + + /* Hash rewriting. */ + HashRewrites rewritesToTmp, rewritesFromTmp; + typedef map RedirectedOutputs; + RedirectedOutputs redirectedOutputs; + + BuildMode buildMode; + + /* If we're repairing without a chroot, there may be outputs that + are valid but corrupt. So we redirect these outputs to + temporary paths. */ + PathSet redirectedBadOutputs; + + /* Set of inodes seen during calls to canonicalisePathMetaData() + for this build's outputs. This needs to be shared between + outputs to allow hard links between outputs. */ + InodesSeen inodesSeen; + + /* Magic exit code denoting that setting up the child environment + failed. (It's possible that the child actually returns the + exit code, but ah well.) */ + const static int childSetupFailed = 189; + +public: + DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode = bmNormal); + ~DerivationGoal(); + + void cancel(bool timeout); + + void work(); + + Path getDrvPath() + { + return drvPath; + } + + /* Add wanted outputs to an already existing derivation goal. */ + void addWantedOutputs(const StringSet & outputs); + +private: + /* The states. */ + void init(); + void haveDerivation(); + void outputsSubstituted(); + void closureRepaired(); + void inputsRealised(); + void tryToBuild(); + void buildDone(); + + /* Is the build hook willing to perform the build? */ + HookReply tryBuildHook(); + + /* Start building a derivation. */ + void startBuilder(); + + /* Initialise the builder's process. */ + void initChild(); + + friend int childEntry(void *); + + /* Check that the derivation outputs all exist and register them + as valid. */ + void registerOutputs(); + + /* Open a log file and a pipe to it. */ + Path openLogFile(); + + /* Close the log file. */ + void closeLogFile(); + + /* Delete the temporary directory, if we have one. */ + void deleteTmpDir(bool force); + + /* Callback used by the worker to write to the log. */ + void handleChildOutput(int fd, const string & data); + void handleEOF(int fd); + + /* Return the set of (in)valid paths. */ + PathSet checkPathValidity(bool returnValid, bool checkHash); + + /* Abort the goal if `path' failed to build. */ + bool pathFailed(const Path & path); + + /* Forcibly kill the child process, if any. */ + void killChild(); + + Path addHashRewrite(const Path & path); + + void repairClosure(); +}; + + +DerivationGoal::DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode) + : Goal(worker) + , wantedOutputs(wantedOutputs) + , needRestart(false) + , retrySubstitution(false) + , fLogFile(0) + , bzLogFile(0) + , useChroot(false) + , buildMode(buildMode) +{ + this->drvPath = drvPath; + state = &DerivationGoal::init; + name = (format("building of `%1%'") % drvPath).str(); + trace("created"); +} + + +DerivationGoal::~DerivationGoal() +{ + /* Careful: we should never ever throw an exception from a + destructor. */ + try { + killChild(); + deleteTmpDir(false); + closeLogFile(); + } catch (...) { + ignoreException(); + } +} + + +void DerivationGoal::killChild() +{ + if (pid != -1) { + worker.childTerminated(pid); + + if (buildUser.enabled()) { + /* If we're using a build user, then there is a tricky + race condition: if we kill the build user before the + child has done its setuid() to the build user uid, then + it won't be killed, and we'll potentially lock up in + pid.wait(). So also send a conventional kill to the + child. */ + ::kill(-pid, SIGKILL); /* ignore the result */ + buildUser.kill(); + pid.wait(true); + } else + pid.kill(); + + assert(pid == -1); + } + + hook.reset(); +} + + +void DerivationGoal::cancel(bool timeout) +{ + if (settings.printBuildTrace && timeout) + printMsg(lvlError, format("@ build-failed %1% - timeout") % drvPath); + killChild(); + amDone(ecFailed); +} + + +void DerivationGoal::work() +{ + (this->*state)(); +} + + +void DerivationGoal::addWantedOutputs(const StringSet & outputs) +{ + /* If we already want all outputs, there is nothing to do. */ + if (wantedOutputs.empty()) return; + + if (outputs.empty()) { + wantedOutputs.clear(); + needRestart = true; + } else + foreach (StringSet::const_iterator, i, outputs) + if (wantedOutputs.find(*i) == wantedOutputs.end()) { + wantedOutputs.insert(*i); + needRestart = true; + } +} + + +void DerivationGoal::init() +{ + trace("init"); + + if (settings.readOnlyMode) + throw Error(format("cannot build derivation `%1%' - no write access to the Nix store") % drvPath); + + /* The first thing to do is to make sure that the derivation + exists. If it doesn't, it may be created through a + substitute. */ + addWaitee(worker.makeSubstitutionGoal(drvPath)); + + state = &DerivationGoal::haveDerivation; +} + + +void DerivationGoal::haveDerivation() +{ + trace("loading derivation"); + + if (nrFailed != 0) { + printMsg(lvlError, format("cannot build missing derivation `%1%'") % drvPath); + amDone(ecFailed); + return; + } + + /* `drvPath' should already be a root, but let's be on the safe + side: if the user forgot to make it a root, we wouldn't want + things being garbage collected while we're busy. */ + worker.store.addTempRoot(drvPath); + + assert(worker.store.isValidPath(drvPath)); + + /* Get the derivation. */ + drv = derivationFromPath(worker.store, drvPath); + + foreach (DerivationOutputs::iterator, i, drv.outputs) + worker.store.addTempRoot(i->second.path); + + /* Check what outputs paths are not already valid. */ + PathSet invalidOutputs = checkPathValidity(false, buildMode == bmRepair); + + /* If they are all valid, then we're done. */ + if (invalidOutputs.size() == 0 && buildMode == bmNormal) { + amDone(ecSuccess); + return; + } + + /* Check whether any output previously failed to build. If so, + don't bother. */ + foreach (PathSet::iterator, i, invalidOutputs) + if (pathFailed(*i)) return; + + /* We are first going to try to create the invalid output paths + through substitutes. If that doesn't work, we'll build + them. */ + if (settings.useSubstitutes && !willBuildLocally(drv)) + foreach (PathSet::iterator, i, invalidOutputs) + addWaitee(worker.makeSubstitutionGoal(*i, buildMode == bmRepair)); + + if (waitees.empty()) /* to prevent hang (no wake-up event) */ + outputsSubstituted(); + else + state = &DerivationGoal::outputsSubstituted; +} + + +void DerivationGoal::outputsSubstituted() +{ + trace("all outputs substituted (maybe)"); + + if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) + throw Error(format("some substitutes for the outputs of derivation `%1%' failed (usually happens due to networking issues); try `--fallback' to build derivation from source ") % drvPath); + + /* If the substitutes form an incomplete closure, then we should + build the dependencies of this derivation, but after that, we + can still use the substitutes for this derivation itself. */ + if (nrIncompleteClosure > 0 && !retrySubstitution) retrySubstitution = true; + + nrFailed = nrNoSubstituters = nrIncompleteClosure = 0; + + if (needRestart) { + needRestart = false; + haveDerivation(); + return; + } + + unsigned int nrInvalid = checkPathValidity(false, buildMode == bmRepair).size(); + if (buildMode == bmNormal && nrInvalid == 0) { + amDone(ecSuccess); + return; + } + if (buildMode == bmRepair && nrInvalid == 0) { + repairClosure(); + return; + } + if (buildMode == bmCheck && nrInvalid > 0) + throw Error(format("some outputs of `%1%' are not valid, so checking is not possible") % drvPath); + + /* Otherwise, at least one of the output paths could not be + produced using a substitute. So we have to build instead. */ + + /* Make sure checkPathValidity() from now on checks all + outputs. */ + wantedOutputs = PathSet(); + + /* The inputs must be built before we can build this goal. */ + foreach (DerivationInputs::iterator, i, drv.inputDrvs) + addWaitee(worker.makeDerivationGoal(i->first, i->second, buildMode == bmRepair ? bmRepair : bmNormal)); + + foreach (PathSet::iterator, i, drv.inputSrcs) + addWaitee(worker.makeSubstitutionGoal(*i)); + + if (waitees.empty()) /* to prevent hang (no wake-up event) */ + inputsRealised(); + else + state = &DerivationGoal::inputsRealised; +} + + +void DerivationGoal::repairClosure() +{ + /* If we're repairing, we now know that our own outputs are valid. + Now check whether the other paths in the outputs closure are + good. If not, then start derivation goals for the derivations + that produced those outputs. */ + + /* Get the output closure. */ + PathSet outputClosure; + foreach (DerivationOutputs::iterator, i, drv.outputs) + computeFSClosure(worker.store, i->second.path, outputClosure); + + /* Filter out our own outputs (which we have already checked). */ + foreach (DerivationOutputs::iterator, i, drv.outputs) + outputClosure.erase(i->second.path); + + /* Get all dependencies of this derivation so that we know which + derivation is responsible for which path in the output + closure. */ + PathSet inputClosure; + computeFSClosure(worker.store, drvPath, inputClosure); + std::map outputsToDrv; + foreach (PathSet::iterator, i, inputClosure) + if (isDerivation(*i)) { + Derivation drv = derivationFromPath(worker.store, *i); + foreach (DerivationOutputs::iterator, j, drv.outputs) + outputsToDrv[j->second.path] = *i; + } + + /* Check each path (slow!). */ + PathSet broken; + foreach (PathSet::iterator, i, outputClosure) { + if (worker.store.pathContentsGood(*i)) continue; + printMsg(lvlError, format("found corrupted or missing path `%1%' in the output closure of `%2%'") % *i % drvPath); + Path drvPath2 = outputsToDrv[*i]; + if (drvPath2 == "") + addWaitee(worker.makeSubstitutionGoal(*i, true)); + else + addWaitee(worker.makeDerivationGoal(drvPath2, PathSet(), bmRepair)); + } + + if (waitees.empty()) { + amDone(ecSuccess); + return; + } + + state = &DerivationGoal::closureRepaired; +} + + +void DerivationGoal::closureRepaired() +{ + trace("closure repaired"); + if (nrFailed > 0) + throw Error(format("some paths in the output closure of derivation `%1%' could not be repaired") % drvPath); + amDone(ecSuccess); +} + + +void DerivationGoal::inputsRealised() +{ + trace("all inputs realised"); + + if (nrFailed != 0) { + printMsg(lvlError, + format("cannot build derivation `%1%': %2% dependencies couldn't be built") + % drvPath % nrFailed); + amDone(ecFailed); + return; + } + + if (retrySubstitution) { + haveDerivation(); + return; + } + + /* Gather information necessary for computing the closure and/or + running the build hook. */ + + /* The outputs are referenceable paths. */ + foreach (DerivationOutputs::iterator, i, drv.outputs) { + debug(format("building path `%1%'") % i->second.path); + allPaths.insert(i->second.path); + } + + /* Determine the full set of input paths. */ + + /* First, the input derivations. */ + foreach (DerivationInputs::iterator, i, drv.inputDrvs) { + /* Add the relevant output closures of the input derivation + `*i' as input paths. Only add the closures of output paths + that are specified as inputs. */ + assert(worker.store.isValidPath(i->first)); + Derivation inDrv = derivationFromPath(worker.store, i->first); + foreach (StringSet::iterator, j, i->second) + if (inDrv.outputs.find(*j) != inDrv.outputs.end()) + computeFSClosure(worker.store, inDrv.outputs[*j].path, inputPaths); + else + throw Error( + format("derivation `%1%' requires non-existent output `%2%' from input derivation `%3%'") + % drvPath % *j % i->first); + } + + /* Second, the input sources. */ + foreach (PathSet::iterator, i, drv.inputSrcs) + computeFSClosure(worker.store, *i, inputPaths); + + debug(format("added input paths %1%") % showPaths(inputPaths)); + + allPaths.insert(inputPaths.begin(), inputPaths.end()); + + /* Is this a fixed-output derivation? */ + fixedOutput = true; + foreach (DerivationOutputs::iterator, i, drv.outputs) + if (i->second.hash == "") fixedOutput = false; + + /* Okay, try to build. Note that here we don't wait for a build + slot to become available, since we don't need one if there is a + build hook. */ + state = &DerivationGoal::tryToBuild; + worker.wakeUp(shared_from_this()); +} + + +PathSet outputPaths(const DerivationOutputs & outputs) +{ + PathSet paths; + foreach (DerivationOutputs::const_iterator, i, outputs) + paths.insert(i->second.path); + return paths; +} + + +static string get(const StringPairs & map, const string & key) +{ + StringPairs::const_iterator i = map.find(key); + return i == map.end() ? (string) "" : i->second; +} + + +static bool canBuildLocally(const string & platform) +{ + return platform == settings.thisSystem +#ifdef CAN_DO_LINUX32_BUILDS + || (platform == "i686-linux" && settings.thisSystem == "x86_64-linux") +#endif + ; +} + + +bool willBuildLocally(const Derivation & drv) +{ + return get(drv.env, "preferLocalBuild") == "1" && canBuildLocally(drv.platform); +} + + +void DerivationGoal::tryToBuild() +{ + trace("trying to build"); + + /* Check for the possibility that some other goal in this process + has locked the output since we checked in haveDerivation(). + (It can't happen between here and the lockPaths() call below + because we're not allowing multi-threading.) If so, put this + goal to sleep until another goal finishes, then try again. */ + foreach (DerivationOutputs::iterator, i, drv.outputs) + if (pathIsLockedByMe(i->second.path)) { + debug(format("putting derivation `%1%' to sleep because `%2%' is locked by another goal") + % drvPath % i->second.path); + worker.waitForAnyGoal(shared_from_this()); + return; + } + + /* Obtain locks on all output paths. The locks are automatically + released when we exit this function or Nix crashes. If we + can't acquire the lock, then continue; hopefully some other + goal can start a build, and if not, the main loop will sleep a + few seconds and then retry this goal. */ + if (!outputLocks.lockPaths(outputPaths(drv.outputs), "", false)) { + worker.waitForAWhile(shared_from_this()); + return; + } + + /* Now check again whether the outputs are valid. This is because + another process may have started building in parallel. After + it has finished and released the locks, we can (and should) + reuse its results. (Strictly speaking the first check can be + omitted, but that would be less efficient.) Note that since we + now hold the locks on the output paths, no other process can + build this derivation, so no further checks are necessary. */ + validPaths = checkPathValidity(true, buildMode == bmRepair); + assert(buildMode != bmCheck || validPaths.size() == drv.outputs.size()); + if (buildMode != bmCheck && validPaths.size() == drv.outputs.size()) { + debug(format("skipping build of derivation `%1%', someone beat us to it") % drvPath); + outputLocks.setDeletion(true); + amDone(ecSuccess); + return; + } + + missingPaths = outputPaths(drv.outputs); + if (buildMode != bmCheck) + foreach (PathSet::iterator, i, validPaths) missingPaths.erase(*i); + + /* If any of the outputs already exist but are not valid, delete + them. */ + foreach (DerivationOutputs::iterator, i, drv.outputs) { + Path path = i->second.path; + if (worker.store.isValidPath(path)) continue; + if (!pathExists(path)) continue; + debug(format("removing invalid path `%1%'") % path); + deletePath(path); + } + + /* Check again whether any output previously failed to build, + because some other process may have tried and failed before we + acquired the lock. */ + foreach (DerivationOutputs::iterator, i, drv.outputs) + if (pathFailed(i->second.path)) return; + + /* Don't do a remote build if the derivation has the attribute + `preferLocalBuild' set. Also, check and repair modes are only + supported for local builds. */ + bool buildLocally = buildMode != bmNormal || willBuildLocally(drv); + + /* Is the build hook willing to accept this job? */ + if (!buildLocally) { + switch (tryBuildHook()) { + case rpAccept: + /* Yes, it has started doing so. Wait until we get + EOF from the hook. */ + state = &DerivationGoal::buildDone; + return; + case rpPostpone: + /* Not now; wait until at least one child finishes or + the wake-up timeout expires. */ + worker.waitForAWhile(shared_from_this()); + outputLocks.unlock(); + return; + case rpDecline: + /* We should do it ourselves. */ + break; + } + } + + /* Make sure that we are allowed to start a build. If this + derivation prefers to be done locally, do it even if + maxBuildJobs is 0. */ + unsigned int curBuilds = worker.getNrLocalBuilds(); + if (curBuilds >= settings.maxBuildJobs && !(buildLocally && curBuilds == 0)) { + worker.waitForBuildSlot(shared_from_this()); + outputLocks.unlock(); + return; + } + + try { + + /* Okay, we have to build. */ + startBuilder(); + + } catch (BuildError & e) { + printMsg(lvlError, e.msg()); + outputLocks.unlock(); + buildUser.release(); + if (settings.printBuildTrace) + printMsg(lvlError, format("@ build-failed %1% - %2% %3%") + % drvPath % 0 % e.msg()); + worker.permanentFailure = true; + amDone(ecFailed); + return; + } + + /* This state will be reached when we get EOF on the child's + log pipe. */ + state = &DerivationGoal::buildDone; +} + + +void replaceValidPath(const Path & storePath, const Path tmpPath) +{ + /* We can't atomically replace storePath (the original) with + tmpPath (the replacement), so we have to move it out of the + way first. We'd better not be interrupted here, because if + we're repairing (say) Glibc, we end up with a broken system. */ + Path oldPath = (format("%1%.old-%2%-%3%") % storePath % getpid() % rand()).str(); + if (pathExists(storePath)) + rename(storePath.c_str(), oldPath.c_str()); + if (rename(tmpPath.c_str(), storePath.c_str()) == -1) + throw SysError(format("moving `%1%' to `%2%'") % tmpPath % storePath); + if (pathExists(oldPath)) + deletePath(oldPath); +} + + +void DerivationGoal::buildDone() +{ + trace("build done"); + + /* Since we got an EOF on the logger pipe, the builder is presumed + to have terminated. In fact, the builder could also have + simply have closed its end of the pipe --- just don't do that + :-) */ + int status; + pid_t savedPid; + if (hook) { + savedPid = hook->pid; + status = hook->pid.wait(true); + } else { + /* !!! this could block! security problem! solution: kill the + child */ + savedPid = pid; + status = pid.wait(true); + } + + debug(format("builder process for `%1%' finished") % drvPath); + + /* So the child is gone now. */ + worker.childTerminated(savedPid); + + /* Close the read side of the logger pipe. */ + if (hook) { + hook->builderOut.readSide.close(); + hook->fromHook.readSide.close(); + } + else builderOut.readSide.close(); + + /* Close the log file. */ + closeLogFile(); + + /* When running under a build user, make sure that all processes + running under that uid are gone. This is to prevent a + malicious user from leaving behind a process that keeps files + open and modifies them after they have been chown'ed to + root. */ + if (buildUser.enabled()) buildUser.kill(); + + bool diskFull = false; + + try { + + /* Check the exit status. */ + if (!statusOk(status)) { + + /* Heuristically check whether the build failure may have + been caused by a disk full condition. We have no way + of knowing whether the build actually got an ENOSPC. + So instead, check if the disk is (nearly) full now. If + so, we don't mark this build as a permanent failure. */ +#if HAVE_STATVFS + unsigned long long required = 8ULL * 1024 * 1024; // FIXME: make configurable + struct statvfs st; + if (statvfs(settings.nixStore.c_str(), &st) == 0 && + (unsigned long long) st.f_bavail * st.f_bsize < required) + diskFull = true; + if (statvfs(tmpDir.c_str(), &st) == 0 && + (unsigned long long) st.f_bavail * st.f_bsize < required) + diskFull = true; +#endif + + deleteTmpDir(false); + + /* Move paths out of the chroot for easier debugging of + build failures. */ + if (useChroot && buildMode == bmNormal) + foreach (PathSet::iterator, i, missingPaths) + if (pathExists(chrootRootDir + *i)) + rename((chrootRootDir + *i).c_str(), i->c_str()); + + if (WIFEXITED(status) && WEXITSTATUS(status) == childSetupFailed) + throw Error(format("failed to set up the build environment for `%1%'") % drvPath); + + if (diskFull) + printMsg(lvlError, "note: build failure may have been caused by lack of free disk space"); + + throw BuildError(format("builder for `%1%' %2%") + % drvPath % statusToString(status)); + } + + /* Compute the FS closure of the outputs and register them as + being valid. */ + registerOutputs(); + + if (buildMode == bmCheck) { + amDone(ecSuccess); + return; + } + + /* Delete unused redirected outputs (when doing hash rewriting). */ + foreach (RedirectedOutputs::iterator, i, redirectedOutputs) + if (pathExists(i->second)) deletePath(i->second); + + /* Delete the chroot (if we were using one). */ + autoDelChroot.reset(); /* this runs the destructor */ + + deleteTmpDir(true); + + /* It is now safe to delete the lock files, since all future + lockers will see that the output paths are valid; they will + not create new lock files with the same names as the old + (unlinked) lock files. */ + outputLocks.setDeletion(true); + outputLocks.unlock(); + + } catch (BuildError & e) { + printMsg(lvlError, e.msg()); + outputLocks.unlock(); + buildUser.release(); + + /* When using a build hook, the hook will return a remote + build failure using exit code 100. Anything else is a hook + problem. */ + bool hookError = hook && + (!WIFEXITED(status) || WEXITSTATUS(status) != 100); + + if (settings.printBuildTrace) { + if (hook && hookError) + printMsg(lvlError, format("@ hook-failed %1% - %2% %3%") + % drvPath % status % e.msg()); + else + printMsg(lvlError, format("@ build-failed %1% - %2% %3%") + % drvPath % 1 % e.msg()); + } + + /* Register the outputs of this build as "failed" so we won't + try to build them again (negative caching). However, don't + do this for fixed-output derivations, since they're likely + to fail for transient reasons (e.g., fetchurl not being + able to access the network). Hook errors (like + communication problems with the remote machine) shouldn't + be cached either. */ + if (settings.cacheFailure && !hookError && !fixedOutput) + foreach (DerivationOutputs::iterator, i, drv.outputs) + worker.store.registerFailedPath(i->second.path); + + worker.permanentFailure = !hookError && !fixedOutput && !diskFull; + amDone(ecFailed); + return; + } + + /* Release the build user, if applicable. */ + buildUser.release(); + + if (settings.printBuildTrace) + printMsg(lvlError, format("@ build-succeeded %1% -") % drvPath); + + amDone(ecSuccess); +} + + +HookReply DerivationGoal::tryBuildHook() +{ + if (!settings.useBuildHook || getEnv("NIX_BUILD_HOOK") == "") return rpDecline; + + if (!worker.hook) + worker.hook = std::shared_ptr(new HookInstance); + + /* Tell the hook about system features (beyond the system type) + required from the build machine. (The hook could parse the + drv file itself, but this is easier.) */ + Strings features = tokenizeString(get(drv.env, "requiredSystemFeatures")); + foreach (Strings::iterator, i, features) checkStoreName(*i); /* !!! abuse */ + + /* Send the request to the hook. */ + writeLine(worker.hook->toHook.writeSide, (format("%1% %2% %3% %4%") + % (worker.getNrLocalBuilds() < settings.maxBuildJobs ? "1" : "0") + % drv.platform % drvPath % concatStringsSep(",", features)).str()); + + /* Read the first line of input, which should be a word indicating + whether the hook wishes to perform the build. */ + string reply; + while (true) { + string s = readLine(worker.hook->fromHook.readSide); + if (string(s, 0, 2) == "# ") { + reply = string(s, 2); + break; + } + s += "\n"; + writeToStderr(s); + } + + debug(format("hook reply is `%1%'") % reply); + + if (reply == "decline" || reply == "postpone") + return reply == "decline" ? rpDecline : rpPostpone; + else if (reply != "accept") + throw Error(format("bad hook reply `%1%'") % reply); + + printMsg(lvlTalkative, format("using hook to build path(s) %1%") % showPaths(missingPaths)); + + hook = worker.hook; + worker.hook.reset(); + + /* Tell the hook all the inputs that have to be copied to the + remote system. This unfortunately has to contain the entire + derivation closure to ensure that the validity invariant holds + on the remote system. (I.e., it's unfortunate that we have to + list it since the remote system *probably* already has it.) */ + PathSet allInputs; + allInputs.insert(inputPaths.begin(), inputPaths.end()); + computeFSClosure(worker.store, drvPath, allInputs); + + string s; + foreach (PathSet::iterator, i, allInputs) { s += *i; s += ' '; } + writeLine(hook->toHook.writeSide, s); + + /* Tell the hooks the missing outputs that have to be copied back + from the remote system. */ + s = ""; + foreach (PathSet::iterator, i, missingPaths) { s += *i; s += ' '; } + writeLine(hook->toHook.writeSide, s); + + hook->toHook.writeSide.close(); + + /* Create the log file and pipe. */ + Path logFile = openLogFile(); + + set fds; + fds.insert(hook->fromHook.readSide); + fds.insert(hook->builderOut.readSide); + worker.childStarted(shared_from_this(), hook->pid, fds, false, false); + + if (settings.printBuildTrace) + printMsg(lvlError, format("@ build-started %1% - %2% %3%") + % drvPath % drv.platform % logFile); + + return rpAccept; +} + + +void chmod_(const Path & path, mode_t mode) +{ + if (chmod(path.c_str(), mode) == -1) + throw SysError(format("setting permissions on `%1%'") % path); +} + + +int childEntry(void * arg) +{ + ((DerivationGoal *) arg)->initChild(); + return 1; +} + + +void DerivationGoal::startBuilder() +{ + startNest(nest, lvlInfo, format( + buildMode == bmRepair ? "repairing path(s) %1%" : + buildMode == bmCheck ? "checking path(s) %1%" : + "building path(s) %1%") % showPaths(missingPaths)); + + /* Right platform? */ + if (!canBuildLocally(drv.platform)) { + if (settings.printBuildTrace) + printMsg(lvlError, format("@ unsupported-platform %1% %2%") % drvPath % drv.platform); + throw Error( + format("a `%1%' is required to build `%3%', but I am a `%2%'") + % drv.platform % settings.thisSystem % drvPath); + } + + /* Construct the environment passed to the builder. */ + + /* Most shells initialise PATH to some default (/bin:/usr/bin:...) when + PATH is not set. We don't want this, so we fill it in with some dummy + value. */ + env["PATH"] = "/path-not-set"; + + /* Set HOME to a non-existing path to prevent certain programs from using + /etc/passwd (or NIS, or whatever) to locate the home directory (for + example, wget looks for ~/.wgetrc). I.e., these tools use /etc/passwd + if HOME is not set, but they will just assume that the settings file + they are looking for does not exist if HOME is set but points to some + non-existing path. */ + Path homeDir = "/homeless-shelter"; + env["HOME"] = homeDir; + + /* Tell the builder where the Nix store is. Usually they + shouldn't care, but this is useful for purity checking (e.g., + the compiler or linker might only want to accept paths to files + in the store or in the build directory). */ + env["NIX_STORE"] = settings.nixStore; + + /* The maximum number of cores to utilize for parallel building. */ + env["NIX_BUILD_CORES"] = (format("%d") % settings.buildCores).str(); + + /* Add all bindings specified in the derivation. */ + foreach (StringPairs::iterator, i, drv.env) + env[i->first] = i->second; + + /* Create a temporary directory where the build will take + place. */ + tmpDir = createTempDir("", "nix-build-" + storePathToName(drvPath), false, false, 0700); + + /* For convenience, set an environment pointing to the top build + directory. */ + env["NIX_BUILD_TOP"] = tmpDir; + + /* Also set TMPDIR and variants to point to this directory. */ + env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] = tmpDir; + + /* Explicitly set PWD to prevent problems with chroot builds. In + particular, dietlibc cannot figure out the cwd because the + inode of the current directory doesn't appear in .. (because + getdents returns the inode of the mount point). */ + env["PWD"] = tmpDir; + + /* Compatibility hack with Nix <= 0.7: if this is a fixed-output + derivation, tell the builder, so that for instance `fetchurl' + can skip checking the output. On older Nixes, this environment + variable won't be set, so `fetchurl' will do the check. */ + if (fixedOutput) env["NIX_OUTPUT_CHECKED"] = "1"; + + /* *Only* if this is a fixed-output derivation, propagate the + values of the environment variables specified in the + `impureEnvVars' attribute to the builder. This allows for + instance environment variables for proxy configuration such as + `http_proxy' to be easily passed to downloaders like + `fetchurl'. Passing such environment variables from the caller + to the builder is generally impure, but the output of + fixed-output derivations is by definition pure (since we + already know the cryptographic hash of the output). */ + if (fixedOutput) { + Strings varNames = tokenizeString(get(drv.env, "impureEnvVars")); + foreach (Strings::iterator, i, varNames) env[*i] = getEnv(*i); + } + + /* The `exportReferencesGraph' feature allows the references graph + to be passed to a builder. This attribute should be a list of + pairs [name1 path1 name2 path2 ...]. The references graph of + each `pathN' will be stored in a text file `nameN' in the + temporary build directory. The text files have the format used + by `nix-store --register-validity'. However, the deriver + fields are left empty. */ + string s = get(drv.env, "exportReferencesGraph"); + Strings ss = tokenizeString(s); + if (ss.size() % 2 != 0) + throw BuildError(format("odd number of tokens in `exportReferencesGraph': `%1%'") % s); + for (Strings::iterator i = ss.begin(); i != ss.end(); ) { + string fileName = *i++; + checkStoreName(fileName); /* !!! abuse of this function */ + + /* Check that the store path is valid. */ + Path storePath = *i++; + if (!isInStore(storePath)) + throw BuildError(format("`exportReferencesGraph' contains a non-store path `%1%'") + % storePath); + storePath = toStorePath(storePath); + if (!worker.store.isValidPath(storePath)) + throw BuildError(format("`exportReferencesGraph' contains an invalid path `%1%'") + % storePath); + + /* If there are derivations in the graph, then include their + outputs as well. This is useful if you want to do things + like passing all build-time dependencies of some path to a + derivation that builds a NixOS DVD image. */ + PathSet paths, paths2; + computeFSClosure(worker.store, storePath, paths); + paths2 = paths; + + foreach (PathSet::iterator, j, paths2) { + if (isDerivation(*j)) { + Derivation drv = derivationFromPath(worker.store, *j); + foreach (DerivationOutputs::iterator, k, drv.outputs) + computeFSClosure(worker.store, k->second.path, paths); + } + } + + /* Write closure info to `fileName'. */ + writeFile(tmpDir + "/" + fileName, + worker.store.makeValidityRegistration(paths, false, false)); + } + + + /* If `build-users-group' is not empty, then we have to build as + one of the members of that group. */ + if (settings.buildUsersGroup != "") { + buildUser.acquire(); + assert(buildUser.getUID() != 0); + assert(buildUser.getGID() != 0); + + /* Make sure that no other processes are executing under this + uid. */ + buildUser.kill(); + + /* Change ownership of the temporary build directory. */ + if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1) + throw SysError(format("cannot change ownership of `%1%'") % tmpDir); + + /* Check that the Nix store has the appropriate permissions, + i.e., owned by root and mode 1775 (sticky bit on so that + the builder can create its output but not mess with the + outputs of other processes). */ + struct stat st; + if (stat(settings.nixStore.c_str(), &st) == -1) + throw SysError(format("cannot stat `%1%'") % settings.nixStore); + if (!(st.st_mode & S_ISVTX) || + ((st.st_mode & S_IRWXG) != S_IRWXG) || + (st.st_gid != buildUser.getGID())) + throw Error(format( + "builder does not have write permission to `%2%'; " + "try `chgrp %1% %2%; chmod 1775 %2%'") + % buildUser.getGID() % settings.nixStore); + } + + + /* Are we doing a chroot build? Note that fixed-output + derivations are never done in a chroot, mainly so that + functions like fetchurl (which needs a proper /etc/resolv.conf) + work properly. Purity checking for fixed-output derivations + is somewhat pointless anyway. */ + useChroot = settings.useChroot; + + if (fixedOutput) useChroot = false; + + /* Hack to allow derivations to disable chroot builds. */ + if (get(drv.env, "__noChroot") == "1") useChroot = false; + + if (useChroot) { +#if CHROOT_ENABLED + /* Create a temporary directory in which we set up the chroot + environment using bind-mounts. We put it in the Nix store + to ensure that we can create hard-links to non-directory + inputs in the fake Nix store in the chroot (see below). */ + chrootRootDir = drvPath + ".chroot"; + if (pathExists(chrootRootDir)) deletePath(chrootRootDir); + + /* Clean up the chroot directory automatically. */ + autoDelChroot = std::shared_ptr(new AutoDelete(chrootRootDir)); + + printMsg(lvlChatty, format("setting up chroot environment in `%1%'") % chrootRootDir); + + /* Create a writable /tmp in the chroot. Many builders need + this. (Of course they should really respect $TMPDIR + instead.) */ + Path chrootTmpDir = chrootRootDir + "/tmp"; + createDirs(chrootTmpDir); + chmod_(chrootTmpDir, 01777); + + /* Create a /etc/passwd with entries for the build user and the + nobody account. The latter is kind of a hack to support + Samba-in-QEMU. */ + createDirs(chrootRootDir + "/etc"); + + writeFile(chrootRootDir + "/etc/passwd", + (format( + "nixbld:x:%1%:%2%:Nix build user:/:/noshell\n" + "nobody:x:65534:65534:Nobody:/:/noshell\n") + % (buildUser.enabled() ? buildUser.getUID() : getuid()) + % (buildUser.enabled() ? buildUser.getGID() : getgid())).str()); + + /* Declare the build user's group so that programs get a consistent + view of the system (e.g., "id -gn"). */ + writeFile(chrootRootDir + "/etc/group", + (format("nixbld:!:%1%:\n") + % (buildUser.enabled() ? buildUser.getGID() : getgid())).str()); + + /* Create /etc/hosts with localhost entry. */ + writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n"); + + /* Bind-mount a user-configurable set of directories from the + host file system. */ + foreach (StringSet::iterator, i, settings.dirsInChroot) { + size_t p = i->find('='); + if (p == string::npos) + dirsInChroot[*i] = *i; + else + dirsInChroot[string(*i, 0, p)] = string(*i, p + 1); + } + dirsInChroot[tmpDir] = tmpDir; + + /* Make the closure of the inputs available in the chroot, + rather than the whole Nix store. This prevents any access + to undeclared dependencies. Directories are bind-mounted, + while other inputs are hard-linked (since only directories + can be bind-mounted). !!! As an extra security + precaution, make the fake Nix store only writable by the + build user. */ + createDirs(chrootRootDir + settings.nixStore); + chmod_(chrootRootDir + settings.nixStore, 01777); + + foreach (PathSet::iterator, i, inputPaths) { + struct stat st; + if (lstat(i->c_str(), &st)) + throw SysError(format("getting attributes of path `%1%'") % *i); + if (S_ISDIR(st.st_mode)) + dirsInChroot[*i] = *i; + else { + Path p = chrootRootDir + *i; + if (link(i->c_str(), p.c_str()) == -1) { + /* Hard-linking fails if we exceed the maximum + link count on a file (e.g. 32000 of ext3), + which is quite possible after a `nix-store + --optimise'. */ + if (errno != EMLINK) + throw SysError(format("linking `%1%' to `%2%'") % p % *i); + StringSink sink; + dumpPath(*i, sink); + StringSource source(sink.s); + restorePath(p, source); + } + + regularInputPaths.insert(*i); + } + } + + /* If we're repairing or checking, it's possible that we're + rebuilding a path that is in settings.dirsInChroot + (typically the dependencies of /bin/sh). Throw them + out. */ + if (buildMode != bmNormal) + foreach (DerivationOutputs::iterator, i, drv.outputs) + dirsInChroot.erase(i->second.path); + +#else + throw Error("chroot builds are not supported on this platform"); +#endif + } + + else { + + if (pathExists(homeDir)) + throw Error(format("directory `%1%' exists; please remove it") % homeDir); + + /* We're not doing a chroot build, but we have some valid + output paths. Since we can't just overwrite or delete + them, we have to do hash rewriting: i.e. in the + environment/arguments passed to the build, we replace the + hashes of the valid outputs with unique dummy strings; + after the build, we discard the redirected outputs + corresponding to the valid outputs, and rewrite the + contents of the new outputs to replace the dummy strings + with the actual hashes. */ + if (validPaths.size() > 0) + foreach (PathSet::iterator, i, validPaths) + addHashRewrite(*i); + + /* If we're repairing, then we don't want to delete the + corrupt outputs in advance. So rewrite them as well. */ + if (buildMode == bmRepair) + foreach (PathSet::iterator, i, missingPaths) + if (worker.store.isValidPath(*i) && pathExists(*i)) { + addHashRewrite(*i); + redirectedBadOutputs.insert(*i); + } + } + + + /* Run the builder. */ + printMsg(lvlChatty, format("executing builder `%1%'") % drv.builder); + + /* Create the log file. */ + Path logFile = openLogFile(); + + /* Create a pipe to get the output of the builder. */ + builderOut.create(); + + /* Fork a child to build the package. Note that while we + currently use forks to run and wait for the children, it + shouldn't be hard to use threads for this on systems where + fork() is unavailable or inefficient. + + If we're building in a chroot, then also set up private + namespaces for the build: + + - The PID namespace causes the build to start as PID 1. + Processes outside of the chroot are not visible to those on + the inside, but processes inside the chroot are visible from + the outside (though with different PIDs). + + - The private mount namespace ensures that all the bind mounts + we do will only show up in this process and its children, and + will disappear automatically when we're done. + + - The private network namespace ensures that the builder cannot + talk to the outside world (or vice versa). It only has a + private loopback interface. + + - The IPC namespace prevents the builder from communicating + with outside processes using SysV IPC mechanisms (shared + memory, message queues, semaphores). It also ensures that + all IPC objects are destroyed when the builder exits. + + - The UTS namespace ensures that builders see a hostname of + localhost rather than the actual hostname. + */ +#if CHROOT_ENABLED + if (useChroot) { + char stack[32 * 1024]; + pid = clone(childEntry, stack + sizeof(stack) - 8, + CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, this); + } else +#endif + { + pid = fork(); + if (pid == 0) initChild(); + } + + if (pid == -1) throw SysError("unable to fork"); + + /* parent */ + pid.setSeparatePG(true); + builderOut.writeSide.close(); + worker.childStarted(shared_from_this(), pid, + singleton >(builderOut.readSide), true, true); + + if (settings.printBuildTrace) { + printMsg(lvlError, format("@ build-started %1% - %2% %3%") + % drvPath % drv.platform % logFile); + } +} + + +void DerivationGoal::initChild() +{ + /* Warning: in the child we should absolutely not make any SQLite + calls! */ + + bool inSetup = true; + + try { /* child */ + +#if CHROOT_ENABLED + if (useChroot) { + /* Initialise the loopback interface. */ + AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)); + if (fd == -1) throw SysError("cannot open IP socket"); + + struct ifreq ifr; + strcpy(ifr.ifr_name, "lo"); + ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING; + if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) + throw SysError("cannot set loopback interface flags"); + + fd.close(); + + /* Set the hostname etc. to fixed values. */ + char hostname[] = "localhost"; + sethostname(hostname, sizeof(hostname)); + char domainname[] = "(none)"; // kernel default + setdomainname(domainname, sizeof(domainname)); + + /* Make all filesystems private. This is necessary + because subtrees may have been mounted as "shared" + (MS_SHARED). (Systemd does this, for instance.) Even + though we have a private mount namespace, mounting + filesystems on top of a shared subtree still propagates + outside of the namespace. Making a subtree private is + local to the namespace, though, so setting MS_PRIVATE + does not affect the outside world. */ + Strings mounts = tokenizeString(readFile("/proc/self/mountinfo", true), "\n"); + foreach (Strings::iterator, i, mounts) { + vector fields = tokenizeString >(*i, " "); + string fs = decodeOctalEscaped(fields.at(4)); + if (mount(0, fs.c_str(), 0, MS_PRIVATE, 0) == -1) + throw SysError(format("unable to make filesystem `%1%' private") % fs); + } + + /* Set up a nearly empty /dev, unless the user asked to + bind-mount the host /dev. */ + if (dirsInChroot.find("/dev") == dirsInChroot.end()) { + createDirs(chrootRootDir + "/dev/shm"); + createDirs(chrootRootDir + "/dev/pts"); + Strings ss; + ss.push_back("/dev/full"); +#ifdef __linux__ + if (pathExists("/dev/kvm")) + ss.push_back("/dev/kvm"); +#endif + ss.push_back("/dev/null"); + ss.push_back("/dev/random"); + ss.push_back("/dev/tty"); + ss.push_back("/dev/urandom"); + ss.push_back("/dev/zero"); + foreach (Strings::iterator, i, ss) dirsInChroot[*i] = *i; + createSymlink("/proc/self/fd", chrootRootDir + "/dev/fd"); + createSymlink("/proc/self/fd/0", chrootRootDir + "/dev/stdin"); + createSymlink("/proc/self/fd/1", chrootRootDir + "/dev/stdout"); + createSymlink("/proc/self/fd/2", chrootRootDir + "/dev/stderr"); + } + + /* Bind-mount all the directories from the "host" + filesystem that we want in the chroot + environment. */ + foreach (DirsInChroot::iterator, i, dirsInChroot) { + struct stat st; + Path source = i->second; + Path target = chrootRootDir + i->first; + if (source == "/proc") continue; // backwards compatibility + debug(format("bind mounting `%1%' to `%2%'") % source % target); + if (stat(source.c_str(), &st) == -1) + throw SysError(format("getting attributes of path `%1%'") % source); + if (S_ISDIR(st.st_mode)) + createDirs(target); + else { + createDirs(dirOf(target)); + writeFile(target, ""); + } + if (mount(source.c_str(), target.c_str(), "", MS_BIND, 0) == -1) + throw SysError(format("bind mount from `%1%' to `%2%' failed") % source % target); + } + + /* Bind a new instance of procfs on /proc to reflect our + private PID namespace. */ + createDirs(chrootRootDir + "/proc"); + if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1) + throw SysError("mounting /proc"); + + /* Mount a new tmpfs on /dev/shm to ensure that whatever + the builder puts in /dev/shm is cleaned up automatically. */ + if (pathExists("/dev/shm") && mount("none", (chrootRootDir + "/dev/shm").c_str(), "tmpfs", 0, 0) == -1) + throw SysError("mounting /dev/shm"); + + /* Mount a new devpts on /dev/pts. Note that this + requires the kernel to be compiled with + CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is the case + if /dev/ptx/ptmx exists). */ + if (pathExists("/dev/pts/ptmx") && + !pathExists(chrootRootDir + "/dev/ptmx") + && dirsInChroot.find("/dev/pts") == dirsInChroot.end()) + { + if (mount("none", (chrootRootDir + "/dev/pts").c_str(), "devpts", 0, "newinstance,mode=0620") == -1) + throw SysError("mounting /dev/pts"); + createSymlink("/dev/pts/ptmx", chrootRootDir + "/dev/ptmx"); + + /* Make sure /dev/pts/ptmx is world-writable. With some + Linux versions, it is created with permissions 0. */ + chmod_(chrootRootDir + "/dev/pts/ptmx", 0666); + } + + /* Do the chroot(). Below we do a chdir() to the + temporary build directory to make sure the current + directory is in the chroot. (Actually the order + doesn't matter, since due to the bind mount tmpDir and + tmpRootDit/tmpDir are the same directories.) */ + if (chroot(chrootRootDir.c_str()) == -1) + throw SysError(format("cannot change root directory to `%1%'") % chrootRootDir); + } +#endif + + commonChildInit(builderOut); + + if (chdir(tmpDir.c_str()) == -1) + throw SysError(format("changing into `%1%'") % tmpDir); + + /* Close all other file descriptors. */ + closeMostFDs(set()); + +#ifdef CAN_DO_LINUX32_BUILDS + /* Change the personality to 32-bit if we're doing an + i686-linux build on an x86_64-linux machine. */ + struct utsname utsbuf; + uname(&utsbuf); + if (drv.platform == "i686-linux" && + (settings.thisSystem == "x86_64-linux" || + (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) { + if (personality(0x0008 | 0x8000000 /* == PER_LINUX32_3GB */) == -1) + throw SysError("cannot set i686-linux personality"); + } + + /* Impersonate a Linux 2.6 machine to get some determinism in + builds that depend on the kernel version. */ + if ((drv.platform == "i686-linux" || drv.platform == "x86_64-linux") && settings.impersonateLinux26) { + int cur = personality(0xffffffff); + if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */); + } +#endif + + /* Fill in the environment. */ + Strings envStrs; + foreach (Environment::const_iterator, i, env) + envStrs.push_back(rewriteHashes(i->first + "=" + i->second, rewritesToTmp)); + const char * * envArr = strings2CharPtrs(envStrs); + + Path program = drv.builder.c_str(); + std::vector args; /* careful with c_str()! */ + string user; /* must be here for its c_str()! */ + + /* If we are running in `build-users' mode, then switch to the + user we allocated above. Make sure that we drop all root + privileges. Note that above we have closed all file + descriptors except std*, so that's safe. Also note that + setuid() when run as root sets the real, effective and + saved UIDs. */ + if (buildUser.enabled()) { + printMsg(lvlChatty, format("switching to user `%1%'") % buildUser.getUser()); + + if (setgroups(0, 0) == -1) + throw SysError("cannot clear the set of supplementary groups"); + + if (setgid(buildUser.getGID()) == -1 || + getgid() != buildUser.getGID() || + getegid() != buildUser.getGID()) + throw SysError("setgid failed"); + + if (setuid(buildUser.getUID()) == -1 || + getuid() != buildUser.getUID() || + geteuid() != buildUser.getUID()) + throw SysError("setuid failed"); + } + + /* Fill in the arguments. */ + string builderBasename = baseNameOf(drv.builder); + args.push_back(builderBasename.c_str()); + foreach (Strings::iterator, i, drv.args) + args.push_back(rewriteHashes(*i, rewritesToTmp).c_str()); + args.push_back(0); + + restoreSIGPIPE(); + + /* Execute the program. This should not return. */ + inSetup = false; + execve(program.c_str(), (char * *) &args[0], (char * *) envArr); + + throw SysError(format("executing `%1%'") % drv.builder); + + } catch (std::exception & e) { + writeToStderr("build error: " + string(e.what()) + "\n"); + _exit(inSetup ? childSetupFailed : 1); + } + + abort(); /* never reached */ +} + + +/* Parse a list of reference specifiers. Each element must either be + a store path, or the symbolic name of the output of the derivation + (such as `out'). */ +PathSet parseReferenceSpecifiers(const Derivation & drv, string attr) +{ + PathSet result; + Paths paths = tokenizeString(attr); + foreach (Strings::iterator, i, paths) { + if (isStorePath(*i)) + result.insert(*i); + else if (drv.outputs.find(*i) != drv.outputs.end()) + result.insert(drv.outputs.find(*i)->second.path); + else throw BuildError( + format("derivation contains an illegal reference specifier `%1%'") + % *i); + } + return result; +} + + +void DerivationGoal::registerOutputs() +{ + /* When using a build hook, the build hook can register the output + as valid (by doing `nix-store --import'). If so we don't have + to do anything here. */ + if (hook) { + bool allValid = true; + foreach (DerivationOutputs::iterator, i, drv.outputs) + if (!worker.store.isValidPath(i->second.path)) allValid = false; + if (allValid) return; + } + + ValidPathInfos infos; + + /* Check whether the output paths were created, and grep each + output path to determine what other paths it references. Also make all + output paths read-only. */ + foreach (DerivationOutputs::iterator, i, drv.outputs) { + Path path = i->second.path; + if (missingPaths.find(path) == missingPaths.end()) continue; + + Path actualPath = path; + if (useChroot) { + actualPath = chrootRootDir + path; + if (pathExists(actualPath)) { + /* Move output paths from the chroot to the Nix store. */ + if (buildMode == bmRepair) + replaceValidPath(path, actualPath); + else + if (buildMode != bmCheck && rename(actualPath.c_str(), path.c_str()) == -1) + throw SysError(format("moving build output `%1%' from the chroot to the Nix store") % path); + } + if (buildMode != bmCheck) actualPath = path; + } else { + Path redirected = redirectedOutputs[path]; + if (buildMode == bmRepair + && redirectedBadOutputs.find(path) != redirectedBadOutputs.end() + && pathExists(redirected)) + replaceValidPath(path, redirected); + if (buildMode == bmCheck) + actualPath = redirected; + } + + struct stat st; + if (lstat(actualPath.c_str(), &st) == -1) { + if (errno == ENOENT) + throw BuildError( + format("builder for `%1%' failed to produce output path `%2%'") + % drvPath % path); + throw SysError(format("getting attributes of path `%1%'") % actualPath); + } + +#ifndef __CYGWIN__ + /* Check that the output is not group or world writable, as + that means that someone else can have interfered with the + build. Also, the output should be owned by the build + user. */ + if ((!S_ISLNK(st.st_mode) && (st.st_mode & (S_IWGRP | S_IWOTH))) || + (buildUser.enabled() && st.st_uid != buildUser.getUID())) + throw BuildError(format("suspicious ownership or permission on `%1%'; rejecting this build output") % path); +#endif + + /* Apply hash rewriting if necessary. */ + bool rewritten = false; + if (!rewritesFromTmp.empty()) { + printMsg(lvlError, format("warning: rewriting hashes in `%1%'; cross fingers") % path); + + /* Canonicalise first. This ensures that the path we're + rewriting doesn't contain a hard link to /etc/shadow or + something like that. */ + canonicalisePathMetaData(actualPath, buildUser.enabled() ? buildUser.getUID() : -1, inodesSeen); + + /* FIXME: this is in-memory. */ + StringSink sink; + dumpPath(actualPath, sink); + deletePath(actualPath); + sink.s = rewriteHashes(sink.s, rewritesFromTmp); + StringSource source(sink.s); + restorePath(actualPath, source); + + rewritten = true; + } + + startNest(nest, lvlTalkative, + format("scanning for references inside `%1%'") % path); + + /* Check that fixed-output derivations produced the right + outputs (i.e., the content hash should match the specified + hash). */ + if (i->second.hash != "") { + + bool recursive; HashType ht; Hash h; + i->second.parseHashInfo(recursive, ht, h); + + if (!recursive) { + /* The output path should be a regular file without + execute permission. */ + if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0) + throw BuildError( + format("output path `%1% should be a non-executable regular file") % path); + } + + /* Check the hash. */ + Hash h2 = recursive ? hashPath(ht, actualPath).first : hashFile(ht, actualPath); + if (h != h2) + throw BuildError( + format("output path `%1%' should have %2% hash `%3%', instead has `%4%'") + % path % i->second.hashAlgo % printHash16or32(h) % printHash16or32(h2)); + } + + /* Get rid of all weird permissions. This also checks that + all files are owned by the build user, if applicable. */ + canonicalisePathMetaData(actualPath, + buildUser.enabled() && !rewritten ? buildUser.getUID() : -1, inodesSeen); + + /* For this output path, find the references to other paths + contained in it. Compute the SHA-256 NAR hash at the same + time. The hash is stored in the database so that we can + verify later on whether nobody has messed with the store. */ + HashResult hash; + PathSet references = scanForReferences(actualPath, allPaths, hash); + + if (buildMode == bmCheck) { + ValidPathInfo info = worker.store.queryPathInfo(path); + if (hash.first != info.hash) + throw Error(format("derivation `%2%' may not be deterministic: hash mismatch in output `%1%'") % drvPath % path); + continue; + } + + /* For debugging, print out the referenced and unreferenced + paths. */ + foreach (PathSet::iterator, i, inputPaths) { + PathSet::iterator j = references.find(*i); + if (j == references.end()) + debug(format("unreferenced input: `%1%'") % *i); + else + debug(format("referenced input: `%1%'") % *i); + } + + /* If the derivation specifies an `allowedReferences' + attribute (containing a list of paths that the output may + refer to), check that all references are in that list. !!! + allowedReferences should really be per-output. */ + if (drv.env.find("allowedReferences") != drv.env.end()) { + PathSet allowed = parseReferenceSpecifiers(drv, get(drv.env, "allowedReferences")); + foreach (PathSet::iterator, i, references) + if (allowed.find(*i) == allowed.end()) + throw BuildError(format("output is not allowed to refer to path `%1%'") % *i); + } + + worker.store.optimisePath(path); // FIXME: combine with scanForReferences() + + worker.store.markContentsGood(path); + + ValidPathInfo info; + info.path = path; + info.hash = hash.first; + info.narSize = hash.second; + info.references = references; + info.deriver = drvPath; + infos.push_back(info); + } + + if (buildMode == bmCheck) return; + + /* Register each output path as valid, and register the sets of + paths referenced by each of them. If there are cycles in the + outputs, this will fail. */ + worker.store.registerValidPaths(infos); +} + + +string drvsLogDir = "drvs"; + + +Path DerivationGoal::openLogFile() +{ + logSize = 0; + + if (!settings.keepLog) return ""; + + string baseName = baseNameOf(drvPath); + + /* Create a log file. */ + Path dir = (format("%1%/%2%/%3%/") % settings.nixLogDir % drvsLogDir % string(baseName, 0, 2)).str(); + createDirs(dir); + + if (settings.compressLog) { + + Path logFileName = (format("%1%/%2%.bz2") % dir % string(baseName, 2)).str(); + AutoCloseFD fd = open(logFileName.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (fd == -1) throw SysError(format("creating log file `%1%'") % logFileName); + closeOnExec(fd); + + if (!(fLogFile = fdopen(fd.borrow(), "w"))) + throw SysError(format("opening file `%1%'") % logFileName); + + int err; + if (!(bzLogFile = BZ2_bzWriteOpen(&err, fLogFile, 9, 0, 0))) + throw Error(format("cannot open compressed log file `%1%'") % logFileName); + + return logFileName; + + } else { + Path logFileName = (format("%1%/%2%") % dir % string(baseName, 2)).str(); + fdLogFile = open(logFileName.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (fdLogFile == -1) throw SysError(format("creating log file `%1%'") % logFileName); + closeOnExec(fdLogFile); + return logFileName; + } +} + + +void DerivationGoal::closeLogFile() +{ + if (bzLogFile) { + int err; + BZ2_bzWriteClose(&err, bzLogFile, 0, 0, 0); + bzLogFile = 0; + if (err != BZ_OK) throw Error(format("cannot close compressed log file (BZip2 error = %1%)") % err); + } + + if (fLogFile) { + fclose(fLogFile); + fLogFile = 0; + } + + fdLogFile.close(); +} + + +void DerivationGoal::deleteTmpDir(bool force) +{ + if (tmpDir != "") { + if (settings.keepFailed && !force) { + printMsg(lvlError, + format("note: keeping build directory `%2%'") + % drvPath % tmpDir); + chmod(tmpDir.c_str(), 0755); + } + else + deletePath(tmpDir); + tmpDir = ""; + } +} + + +void DerivationGoal::handleChildOutput(int fd, const string & data) +{ + if ((hook && fd == hook->builderOut.readSide) || + (!hook && fd == builderOut.readSide)) + { + logSize += data.size(); + if (settings.maxLogSize && logSize > settings.maxLogSize) { + printMsg(lvlError, + format("%1% killed after writing more than %2% bytes of log output") + % getName() % settings.maxLogSize); + cancel(true); // not really a timeout, but close enough + return; + } + if (verbosity >= settings.buildVerbosity) + writeToStderr(data); + if (bzLogFile) { + int err; + BZ2_bzWrite(&err, bzLogFile, (unsigned char *) data.data(), data.size()); + if (err != BZ_OK) throw Error(format("cannot write to compressed log file (BZip2 error = %1%)") % err); + } else if (fdLogFile != -1) + writeFull(fdLogFile, (unsigned char *) data.data(), data.size()); + } + + if (hook && fd == hook->fromHook.readSide) + writeToStderr(data); +} + + +void DerivationGoal::handleEOF(int fd) +{ + worker.wakeUp(shared_from_this()); +} + + +PathSet DerivationGoal::checkPathValidity(bool returnValid, bool checkHash) +{ + PathSet result; + foreach (DerivationOutputs::iterator, i, drv.outputs) { + if (!wantOutput(i->first, wantedOutputs)) continue; + bool good = + worker.store.isValidPath(i->second.path) && + (!checkHash || worker.store.pathContentsGood(i->second.path)); + if (good == returnValid) result.insert(i->second.path); + } + return result; +} + + +bool DerivationGoal::pathFailed(const Path & path) +{ + if (!settings.cacheFailure) return false; + + if (!worker.store.hasPathFailed(path)) return false; + + printMsg(lvlError, format("builder for `%1%' failed previously (cached)") % path); + + if (settings.printBuildTrace) + printMsg(lvlError, format("@ build-failed %1% - cached") % drvPath); + + worker.permanentFailure = true; + amDone(ecFailed); + + return true; +} + + +Path DerivationGoal::addHashRewrite(const Path & path) +{ + string h1 = string(path, settings.nixStore.size() + 1, 32); + string h2 = string(printHash32(hashString(htSHA256, "rewrite:" + drvPath + ":" + path)), 0, 32); + Path p = settings.nixStore + "/" + h2 + string(path, settings.nixStore.size() + 33); + if (pathExists(p)) deletePath(p); + assert(path.size() == p.size()); + rewritesToTmp[h1] = h2; + rewritesFromTmp[h2] = h1; + redirectedOutputs[path] = p; + return p; +} + + +////////////////////////////////////////////////////////////////////// + + +class SubstitutionGoal : public Goal +{ + friend class Worker; + +private: + /* The store path that should be realised through a substitute. */ + Path storePath; + + /* The remaining substituters. */ + Paths subs; + + /* The current substituter. */ + Path sub; + + /* Whether any substituter can realise this path */ + bool hasSubstitute; + + /* Path info returned by the substituter's query info operation. */ + SubstitutablePathInfo info; + + /* Pipe for the substituter's standard output. */ + Pipe outPipe; + + /* Pipe for the substituter's standard error. */ + Pipe logPipe; + + /* The process ID of the builder. */ + Pid pid; + + /* Lock on the store path. */ + std::shared_ptr outputLock; + + /* Whether to try to repair a valid path. */ + bool repair; + + /* Location where we're downloading the substitute. Differs from + storePath when doing a repair. */ + Path destPath; + + typedef void (SubstitutionGoal::*GoalState)(); + GoalState state; + +public: + SubstitutionGoal(const Path & storePath, Worker & worker, bool repair = false); + ~SubstitutionGoal(); + + void cancel(bool timeout); + + void work(); + + /* The states. */ + void init(); + void tryNext(); + void gotInfo(); + void referencesValid(); + void tryToRun(); + void finished(); + + /* Callback used by the worker to write to the log. */ + void handleChildOutput(int fd, const string & data); + void handleEOF(int fd); + + Path getStorePath() { return storePath; } +}; + + +SubstitutionGoal::SubstitutionGoal(const Path & storePath, Worker & worker, bool repair) + : Goal(worker) + , hasSubstitute(false) + , repair(repair) +{ + this->storePath = storePath; + state = &SubstitutionGoal::init; + name = (format("substitution of `%1%'") % storePath).str(); + trace("created"); +} + + +SubstitutionGoal::~SubstitutionGoal() +{ + if (pid != -1) worker.childTerminated(pid); +} + + +void SubstitutionGoal::cancel(bool timeout) +{ + if (settings.printBuildTrace && timeout) + printMsg(lvlError, format("@ substituter-failed %1% timeout") % storePath); + if (pid != -1) { + pid_t savedPid = pid; + pid.kill(); + worker.childTerminated(savedPid); + } + amDone(ecFailed); +} + + +void SubstitutionGoal::work() +{ + (this->*state)(); +} + + +void SubstitutionGoal::init() +{ + trace("init"); + + worker.store.addTempRoot(storePath); + + /* If the path already exists we're done. */ + if (!repair && worker.store.isValidPath(storePath)) { + amDone(ecSuccess); + return; + } + + if (settings.readOnlyMode) + throw Error(format("cannot substitute path `%1%' - no write access to the Nix store") % storePath); + + subs = settings.substituters; + + tryNext(); +} + + +void SubstitutionGoal::tryNext() +{ + trace("trying next substituter"); + + if (subs.size() == 0) { + /* None left. Terminate this goal and let someone else deal + with it. */ + debug(format("path `%1%' is required, but there is no substituter that can build it") % storePath); + /* Hack: don't indicate failure if there were no substituters. + In that case the calling derivation should just do a + build. */ + amDone(hasSubstitute ? ecFailed : ecNoSubstituters); + return; + } + + sub = subs.front(); + subs.pop_front(); + + SubstitutablePathInfos infos; + PathSet dummy(singleton(storePath)); + worker.store.querySubstitutablePathInfos(sub, dummy, infos); + SubstitutablePathInfos::iterator k = infos.find(storePath); + if (k == infos.end()) { tryNext(); return; } + info = k->second; + hasSubstitute = true; + + /* To maintain the closure invariant, we first have to realise the + paths referenced by this one. */ + foreach (PathSet::iterator, i, info.references) + if (*i != storePath) /* ignore self-references */ + addWaitee(worker.makeSubstitutionGoal(*i)); + + if (waitees.empty()) /* to prevent hang (no wake-up event) */ + referencesValid(); + else + state = &SubstitutionGoal::referencesValid; +} + + +void SubstitutionGoal::referencesValid() +{ + trace("all references realised"); + + if (nrFailed > 0) { + debug(format("some references of path `%1%' could not be realised") % storePath); + amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed); + return; + } + + foreach (PathSet::iterator, i, info.references) + if (*i != storePath) /* ignore self-references */ + assert(worker.store.isValidPath(*i)); + + state = &SubstitutionGoal::tryToRun; + worker.wakeUp(shared_from_this()); +} + + +void SubstitutionGoal::tryToRun() +{ + trace("trying to run"); + + /* Make sure that we are allowed to start a build. Note that even + is maxBuildJobs == 0 (no local builds allowed), we still allow + a substituter to run. This is because substitutions cannot be + distributed to another machine via the build hook. */ + if (worker.getNrLocalBuilds() >= (settings.maxBuildJobs == 0 ? 1 : settings.maxBuildJobs)) { + worker.waitForBuildSlot(shared_from_this()); + return; + } + + /* Maybe a derivation goal has already locked this path + (exceedingly unlikely, since it should have used a substitute + first, but let's be defensive). */ + outputLock.reset(); // make sure this goal's lock is gone + if (pathIsLockedByMe(storePath)) { + debug(format("restarting substitution of `%1%' because it's locked by another goal") + % storePath); + worker.waitForAnyGoal(shared_from_this()); + return; /* restart in the tryToRun() state when another goal finishes */ + } + + /* Acquire a lock on the output path. */ + outputLock = std::shared_ptr(new PathLocks); + if (!outputLock->lockPaths(singleton(storePath), "", false)) { + worker.waitForAWhile(shared_from_this()); + return; + } + + /* Check again whether the path is invalid. */ + if (!repair && worker.store.isValidPath(storePath)) { + debug(format("store path `%1%' has become valid") % storePath); + outputLock->setDeletion(true); + amDone(ecSuccess); + return; + } + + printMsg(lvlInfo, format("fetching path `%1%'...") % storePath); + + outPipe.create(); + logPipe.create(); + + destPath = repair ? storePath + ".tmp" : storePath; + + /* Remove the (stale) output path if it exists. */ + if (pathExists(destPath)) + deletePath(destPath); + + worker.store.setSubstituterEnv(); + + /* Fill in the arguments. */ + Strings args; + args.push_back(baseNameOf(sub)); + args.push_back("--substitute"); + args.push_back(storePath); + args.push_back(destPath); + const char * * argArr = strings2CharPtrs(args); + + /* Fork the substitute program. */ + pid = maybeVfork(); + + switch (pid) { + + case -1: + throw SysError("unable to fork"); + + case 0: + try { /* child */ + + commonChildInit(logPipe); + + if (dup2(outPipe.writeSide, STDOUT_FILENO) == -1) + throw SysError("cannot dup output pipe into stdout"); + + execv(sub.c_str(), (char * *) argArr); + + throw SysError(format("executing `%1%'") % sub); + + } catch (std::exception & e) { + writeToStderr("substitute error: " + string(e.what()) + "\n"); + } + _exit(1); + } + + /* parent */ + pid.setSeparatePG(true); + pid.setKillSignal(SIGTERM); + outPipe.writeSide.close(); + logPipe.writeSide.close(); + worker.childStarted(shared_from_this(), + pid, singleton >(logPipe.readSide), true, true); + + state = &SubstitutionGoal::finished; + + if (settings.printBuildTrace) + printMsg(lvlError, format("@ substituter-started %1% %2%") % storePath % sub); +} + + +void SubstitutionGoal::finished() +{ + trace("substitute finished"); + + /* Since we got an EOF on the logger pipe, the substitute is + presumed to have terminated. */ + pid_t savedPid = pid; + int status = pid.wait(true); + + /* So the child is gone now. */ + worker.childTerminated(savedPid); + + /* Close the read side of the logger pipe. */ + logPipe.readSide.close(); + + /* Get the hash info from stdout. */ + string dummy = readLine(outPipe.readSide); + string expectedHashStr = statusOk(status) ? readLine(outPipe.readSide) : ""; + outPipe.readSide.close(); + + /* Check the exit status and the build result. */ + HashResult hash; + try { + + if (!statusOk(status)) + throw SubstError(format("fetching path `%1%' %2%") + % storePath % statusToString(status)); + + if (!pathExists(destPath)) + throw SubstError(format("substitute did not produce path `%1%'") % destPath); + + hash = hashPath(htSHA256, destPath); + + /* Verify the expected hash we got from the substituer. */ + if (expectedHashStr != "") { + size_t n = expectedHashStr.find(':'); + if (n == string::npos) + throw Error(format("bad hash from substituter: %1%") % expectedHashStr); + HashType hashType = parseHashType(string(expectedHashStr, 0, n)); + if (hashType == htUnknown) + throw Error(format("unknown hash algorithm in `%1%'") % expectedHashStr); + Hash expectedHash = parseHash16or32(hashType, string(expectedHashStr, n + 1)); + Hash actualHash = hashType == htSHA256 ? hash.first : hashPath(hashType, destPath).first; + if (expectedHash != actualHash) + throw SubstError(format("hash mismatch in downloaded path `%1%': expected %2%, got %3%") + % storePath % printHash(expectedHash) % printHash(actualHash)); + } + + } catch (SubstError & e) { + + printMsg(lvlInfo, e.msg()); + + if (settings.printBuildTrace) { + printMsg(lvlError, format("@ substituter-failed %1% %2% %3%") + % storePath % status % e.msg()); + } + + /* Try the next substitute. */ + state = &SubstitutionGoal::tryNext; + worker.wakeUp(shared_from_this()); + return; + } + + if (repair) replaceValidPath(storePath, destPath); + + canonicalisePathMetaData(storePath, -1); + + worker.store.optimisePath(storePath); // FIXME: combine with hashPath() + + ValidPathInfo info2; + info2.path = storePath; + info2.hash = hash.first; + info2.narSize = hash.second; + info2.references = info.references; + info2.deriver = info.deriver; + worker.store.registerValidPath(info2); + + outputLock->setDeletion(true); + outputLock.reset(); + + worker.store.markContentsGood(storePath); + + printMsg(lvlChatty, + format("substitution of path `%1%' succeeded") % storePath); + + if (settings.printBuildTrace) + printMsg(lvlError, format("@ substituter-succeeded %1%") % storePath); + + amDone(ecSuccess); +} + + +void SubstitutionGoal::handleChildOutput(int fd, const string & data) +{ + assert(fd == logPipe.readSide); + if (verbosity >= settings.buildVerbosity) writeToStderr(data); + /* Don't write substitution output to a log file for now. We + probably should, though. */ +} + + +void SubstitutionGoal::handleEOF(int fd) +{ + if (fd == logPipe.readSide) worker.wakeUp(shared_from_this()); +} + + + +////////////////////////////////////////////////////////////////////// + + +static bool working = false; + + +Worker::Worker(LocalStore & store) + : store(store) +{ + /* Debugging: prevent recursive workers. */ + if (working) abort(); + working = true; + nrLocalBuilds = 0; + lastWokenUp = 0; + permanentFailure = false; +} + + +Worker::~Worker() +{ + working = false; + + /* Explicitly get rid of all strong pointers now. After this all + goals that refer to this worker should be gone. (Otherwise we + are in trouble, since goals may call childTerminated() etc. in + their destructors). */ + topGoals.clear(); +} + + +GoalPtr Worker::makeDerivationGoal(const Path & path, const StringSet & wantedOutputs, BuildMode buildMode) +{ + GoalPtr goal = derivationGoals[path].lock(); + if (!goal) { + goal = GoalPtr(new DerivationGoal(path, wantedOutputs, *this, buildMode)); + derivationGoals[path] = goal; + wakeUp(goal); + } else + (dynamic_cast(goal.get()))->addWantedOutputs(wantedOutputs); + return goal; +} + + +GoalPtr Worker::makeSubstitutionGoal(const Path & path, bool repair) +{ + GoalPtr goal = substitutionGoals[path].lock(); + if (!goal) { + goal = GoalPtr(new SubstitutionGoal(path, *this, repair)); + substitutionGoals[path] = goal; + wakeUp(goal); + } + return goal; +} + + +static void removeGoal(GoalPtr goal, WeakGoalMap & goalMap) +{ + /* !!! inefficient */ + for (WeakGoalMap::iterator i = goalMap.begin(); + i != goalMap.end(); ) + if (i->second.lock() == goal) { + WeakGoalMap::iterator j = i; ++j; + goalMap.erase(i); + i = j; + } + else ++i; +} + + +void Worker::removeGoal(GoalPtr goal) +{ + nix::removeGoal(goal, derivationGoals); + nix::removeGoal(goal, substitutionGoals); + if (topGoals.find(goal) != topGoals.end()) { + topGoals.erase(goal); + /* If a top-level goal failed, then kill all other goals + (unless keepGoing was set). */ + if (goal->getExitCode() == Goal::ecFailed && !settings.keepGoing) + topGoals.clear(); + } + + /* Wake up goals waiting for any goal to finish. */ + foreach (WeakGoals::iterator, i, waitingForAnyGoal) { + GoalPtr goal = i->lock(); + if (goal) wakeUp(goal); + } + + waitingForAnyGoal.clear(); +} + + +void Worker::wakeUp(GoalPtr goal) +{ + goal->trace("woken up"); + addToWeakGoals(awake, goal); +} + + +unsigned Worker::getNrLocalBuilds() +{ + return nrLocalBuilds; +} + + +void Worker::childStarted(GoalPtr goal, + pid_t pid, const set & fds, bool inBuildSlot, + bool respectTimeouts) +{ + Child child; + child.goal = goal; + child.fds = fds; + child.timeStarted = child.lastOutput = time(0); + child.inBuildSlot = inBuildSlot; + child.respectTimeouts = respectTimeouts; + children[pid] = child; + if (inBuildSlot) nrLocalBuilds++; +} + + +void Worker::childTerminated(pid_t pid, bool wakeSleepers) +{ + assert(pid != -1); /* common mistake */ + + Children::iterator i = children.find(pid); + assert(i != children.end()); + + if (i->second.inBuildSlot) { + assert(nrLocalBuilds > 0); + nrLocalBuilds--; + } + + children.erase(pid); + + if (wakeSleepers) { + + /* Wake up goals waiting for a build slot. */ + foreach (WeakGoals::iterator, i, wantingToBuild) { + GoalPtr goal = i->lock(); + if (goal) wakeUp(goal); + } + + wantingToBuild.clear(); + } +} + + +void Worker::waitForBuildSlot(GoalPtr goal) +{ + debug("wait for build slot"); + if (getNrLocalBuilds() < settings.maxBuildJobs) + wakeUp(goal); /* we can do it right away */ + else + addToWeakGoals(wantingToBuild, goal); +} + + +void Worker::waitForAnyGoal(GoalPtr goal) +{ + debug("wait for any goal"); + addToWeakGoals(waitingForAnyGoal, goal); +} + + +void Worker::waitForAWhile(GoalPtr goal) +{ + debug("wait for a while"); + addToWeakGoals(waitingForAWhile, goal); +} + + +void Worker::run(const Goals & _topGoals) +{ + foreach (Goals::iterator, i, _topGoals) topGoals.insert(*i); + + startNest(nest, lvlDebug, format("entered goal loop")); + + while (1) { + + checkInterrupt(); + + /* Call every wake goal. */ + while (!awake.empty() && !topGoals.empty()) { + WeakGoals awake2(awake); + awake.clear(); + foreach (WeakGoals::iterator, i, awake2) { + checkInterrupt(); + GoalPtr goal = i->lock(); + if (goal) goal->work(); + if (topGoals.empty()) break; + } + } + + if (topGoals.empty()) break; + + /* Wait for input. */ + if (!children.empty() || !waitingForAWhile.empty()) + waitForInput(); + else { + if (awake.empty() && settings.maxBuildJobs == 0) throw Error( + "unable to start any build; either increase `--max-jobs' " + "or enable distributed builds"); + assert(!awake.empty()); + } + } + + /* If --keep-going is not set, it's possible that the main goal + exited while some of its subgoals were still active. But if + --keep-going *is* set, then they must all be finished now. */ + assert(!settings.keepGoing || awake.empty()); + assert(!settings.keepGoing || wantingToBuild.empty()); + assert(!settings.keepGoing || children.empty()); +} + + +void Worker::waitForInput() +{ + printMsg(lvlVomit, "waiting for children"); + + /* Process output from the file descriptors attached to the + children, namely log output and output path creation commands. + We also use this to detect child termination: if we get EOF on + the logger pipe of a build, we assume that the builder has + terminated. */ + + bool useTimeout = false; + struct timeval timeout; + timeout.tv_usec = 0; + time_t before = time(0); + + /* If we're monitoring for silence on stdout/stderr, or if there + is a build timeout, then wait for input until the first + deadline for any child. */ + assert(sizeof(time_t) >= sizeof(long)); + time_t nearest = LONG_MAX; // nearest deadline + foreach (Children::iterator, i, children) { + if (!i->second.respectTimeouts) continue; + if (settings.maxSilentTime != 0) + nearest = std::min(nearest, i->second.lastOutput + settings.maxSilentTime); + if (settings.buildTimeout != 0) + nearest = std::min(nearest, i->second.timeStarted + settings.buildTimeout); + } + if (nearest != LONG_MAX) { + timeout.tv_sec = std::max((time_t) 1, nearest - before); + useTimeout = true; + printMsg(lvlVomit, format("sleeping %1% seconds") % timeout.tv_sec); + } + + /* If we are polling goals that are waiting for a lock, then wake + up after a few seconds at most. */ + if (!waitingForAWhile.empty()) { + useTimeout = true; + if (lastWokenUp == 0) + printMsg(lvlError, "waiting for locks or build slots..."); + if (lastWokenUp == 0 || lastWokenUp > before) lastWokenUp = before; + timeout.tv_sec = std::max((time_t) 1, (time_t) (lastWokenUp + settings.pollInterval - before)); + } else lastWokenUp = 0; + + using namespace std; + /* Use select() to wait for the input side of any logger pipe to + become `available'. Note that `available' (i.e., non-blocking) + includes EOF. */ + fd_set fds; + FD_ZERO(&fds); + int fdMax = 0; + foreach (Children::iterator, i, children) { + foreach (set::iterator, j, i->second.fds) { + FD_SET(*j, &fds); + if (*j >= fdMax) fdMax = *j + 1; + } + } + + if (select(fdMax, &fds, 0, 0, useTimeout ? &timeout : 0) == -1) { + if (errno == EINTR) return; + throw SysError("waiting for input"); + } + + time_t after = time(0); + + /* Process all available file descriptors. */ + + /* Since goals may be canceled from inside the loop below (causing + them go be erased from the `children' map), we have to be + careful that we don't keep iterators alive across calls to + cancel(). */ + set pids; + foreach (Children::iterator, i, children) pids.insert(i->first); + + foreach (set::iterator, i, pids) { + checkInterrupt(); + Children::iterator j = children.find(*i); + if (j == children.end()) continue; // child destroyed + GoalPtr goal = j->second.goal.lock(); + assert(goal); + + set fds2(j->second.fds); + foreach (set::iterator, k, fds2) { + if (FD_ISSET(*k, &fds)) { + unsigned char buffer[4096]; + ssize_t rd = read(*k, buffer, sizeof(buffer)); + if (rd == -1) { + if (errno != EINTR) + throw SysError(format("reading from %1%") + % goal->getName()); + } else if (rd == 0) { + debug(format("%1%: got EOF") % goal->getName()); + goal->handleEOF(*k); + j->second.fds.erase(*k); + } else { + printMsg(lvlVomit, format("%1%: read %2% bytes") + % goal->getName() % rd); + string data((char *) buffer, rd); + j->second.lastOutput = after; + goal->handleChildOutput(*k, data); + } + } + } + + if (goal->getExitCode() == Goal::ecBusy && + settings.maxSilentTime != 0 && + j->second.respectTimeouts && + after - j->second.lastOutput >= (time_t) settings.maxSilentTime) + { + printMsg(lvlError, + format("%1% timed out after %2% seconds of silence") + % goal->getName() % settings.maxSilentTime); + goal->cancel(true); + } + + else if (goal->getExitCode() == Goal::ecBusy && + settings.buildTimeout != 0 && + j->second.respectTimeouts && + after - j->second.timeStarted >= (time_t) settings.buildTimeout) + { + printMsg(lvlError, + format("%1% timed out after %2% seconds") + % goal->getName() % settings.buildTimeout); + goal->cancel(true); + } + } + + if (!waitingForAWhile.empty() && lastWokenUp + settings.pollInterval <= after) { + lastWokenUp = after; + foreach (WeakGoals::iterator, i, waitingForAWhile) { + GoalPtr goal = i->lock(); + if (goal) wakeUp(goal); + } + waitingForAWhile.clear(); + } +} + + +unsigned int Worker::exitStatus() +{ + return permanentFailure ? 100 : 1; +} + + +////////////////////////////////////////////////////////////////////// + + +void LocalStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode) +{ + startNest(nest, lvlDebug, + format("building %1%") % showPaths(drvPaths)); + + Worker worker(*this); + + Goals goals; + foreach (PathSet::const_iterator, i, drvPaths) { + DrvPathWithOutputs i2 = parseDrvPathWithOutputs(*i); + if (isDerivation(i2.first)) + goals.insert(worker.makeDerivationGoal(i2.first, i2.second, buildMode)); + else + goals.insert(worker.makeSubstitutionGoal(*i, buildMode)); + } + + worker.run(goals); + + PathSet failed; + foreach (Goals::iterator, i, goals) + if ((*i)->getExitCode() == Goal::ecFailed) { + DerivationGoal * i2 = dynamic_cast(i->get()); + if (i2) failed.insert(i2->getDrvPath()); + else failed.insert(dynamic_cast(i->get())->getStorePath()); + } + + if (!failed.empty()) + throw Error(format("build of %1% failed") % showPaths(failed), worker.exitStatus()); +} + + +void LocalStore::ensurePath(const Path & path) +{ + /* If the path is already valid, we're done. */ + if (isValidPath(path)) return; + + Worker worker(*this); + GoalPtr goal = worker.makeSubstitutionGoal(path); + Goals goals = singleton(goal); + + worker.run(goals); + + if (goal->getExitCode() != Goal::ecSuccess) + throw Error(format("path `%1%' does not exist and cannot be created") % path, worker.exitStatus()); +} + + +void LocalStore::repairPath(const Path & path) +{ + Worker worker(*this); + GoalPtr goal = worker.makeSubstitutionGoal(path, true); + Goals goals = singleton(goal); + + worker.run(goals); + + if (goal->getExitCode() != Goal::ecSuccess) + throw Error(format("cannot repair path `%1%'") % path, worker.exitStatus()); +} + + +} diff --git a/nix/libstore/derivations.cc b/nix/libstore/derivations.cc new file mode 100644 index 0000000000..d91e42784c --- /dev/null +++ b/nix/libstore/derivations.cc @@ -0,0 +1,278 @@ +#include "derivations.hh" +#include "store-api.hh" +#include "globals.hh" +#include "util.hh" +#include "misc.hh" + + +namespace nix { + + +void DerivationOutput::parseHashInfo(bool & recursive, HashType & hashType, Hash & hash) const +{ + recursive = false; + string algo = hashAlgo; + + if (string(algo, 0, 2) == "r:") { + recursive = true; + algo = string(algo, 2); + } + + hashType = parseHashType(algo); + if (hashType == htUnknown) + throw Error(format("unknown hash algorithm `%1%'") % algo); + + hash = parseHash(hashType, this->hash); +} + + +Path writeDerivation(StoreAPI & store, + const Derivation & drv, const string & name, bool repair) +{ + PathSet references; + references.insert(drv.inputSrcs.begin(), drv.inputSrcs.end()); + foreach (DerivationInputs::const_iterator, i, drv.inputDrvs) + references.insert(i->first); + /* Note that the outputs of a derivation are *not* references + (that can be missing (of course) and should not necessarily be + held during a garbage collection). */ + string suffix = name + drvExtension; + string contents = unparseDerivation(drv); + return settings.readOnlyMode + ? computeStorePathForText(suffix, contents, references) + : store.addTextToStore(suffix, contents, references, repair); +} + + +static Path parsePath(std::istream & str) +{ + string s = parseString(str); + if (s.size() == 0 || s[0] != '/') + throw Error(format("bad path `%1%' in derivation") % s); + return s; +} + + +static StringSet parseStrings(std::istream & str, bool arePaths) +{ + StringSet res; + while (!endOfList(str)) + res.insert(arePaths ? parsePath(str) : parseString(str)); + return res; +} + + +Derivation parseDerivation(const string & s) +{ + Derivation drv; + std::istringstream str(s); + expect(str, "Derive(["); + + /* Parse the list of outputs. */ + while (!endOfList(str)) { + DerivationOutput out; + expect(str, "("); string id = parseString(str); + expect(str, ","); out.path = parsePath(str); + expect(str, ","); out.hashAlgo = parseString(str); + expect(str, ","); out.hash = parseString(str); + expect(str, ")"); + drv.outputs[id] = out; + } + + /* Parse the list of input derivations. */ + expect(str, ",["); + while (!endOfList(str)) { + expect(str, "("); + Path drvPath = parsePath(str); + expect(str, ",["); + drv.inputDrvs[drvPath] = parseStrings(str, false); + expect(str, ")"); + } + + expect(str, ",["); drv.inputSrcs = parseStrings(str, true); + expect(str, ","); drv.platform = parseString(str); + expect(str, ","); drv.builder = parseString(str); + + /* Parse the builder arguments. */ + expect(str, ",["); + while (!endOfList(str)) + drv.args.push_back(parseString(str)); + + /* Parse the environment variables. */ + expect(str, ",["); + while (!endOfList(str)) { + expect(str, "("); string name = parseString(str); + expect(str, ","); string value = parseString(str); + expect(str, ")"); + drv.env[name] = value; + } + + expect(str, ")"); + return drv; +} + + +static void printString(string & res, const string & s) +{ + res += '"'; + for (const char * i = s.c_str(); *i; i++) + if (*i == '\"' || *i == '\\') { res += "\\"; res += *i; } + else if (*i == '\n') res += "\\n"; + else if (*i == '\r') res += "\\r"; + else if (*i == '\t') res += "\\t"; + else res += *i; + res += '"'; +} + + +template +static void printStrings(string & res, ForwardIterator i, ForwardIterator j) +{ + res += '['; + bool first = true; + for ( ; i != j; ++i) { + if (first) first = false; else res += ','; + printString(res, *i); + } + res += ']'; +} + + +string unparseDerivation(const Derivation & drv) +{ + string s; + s.reserve(65536); + s += "Derive(["; + + bool first = true; + foreach (DerivationOutputs::const_iterator, i, drv.outputs) { + if (first) first = false; else s += ','; + s += '('; printString(s, i->first); + s += ','; printString(s, i->second.path); + s += ','; printString(s, i->second.hashAlgo); + s += ','; printString(s, i->second.hash); + s += ')'; + } + + s += "],["; + first = true; + foreach (DerivationInputs::const_iterator, i, drv.inputDrvs) { + if (first) first = false; else s += ','; + s += '('; printString(s, i->first); + s += ','; printStrings(s, i->second.begin(), i->second.end()); + s += ')'; + } + + s += "],"; + printStrings(s, drv.inputSrcs.begin(), drv.inputSrcs.end()); + + s += ','; printString(s, drv.platform); + s += ','; printString(s, drv.builder); + s += ','; printStrings(s, drv.args.begin(), drv.args.end()); + + s += ",["; + first = true; + foreach (StringPairs::const_iterator, i, drv.env) { + if (first) first = false; else s += ','; + s += '('; printString(s, i->first); + s += ','; printString(s, i->second); + s += ')'; + } + + s += "])"; + + return s; +} + + +bool isDerivation(const string & fileName) +{ + return hasSuffix(fileName, drvExtension); +} + + +bool isFixedOutputDrv(const Derivation & drv) +{ + return drv.outputs.size() == 1 && + drv.outputs.begin()->first == "out" && + drv.outputs.begin()->second.hash != ""; +} + + +DrvHashes drvHashes; + + +/* Returns the hash of a derivation modulo fixed-output + subderivations. A fixed-output derivation is a derivation with one + output (`out') for which an expected hash and hash algorithm are + specified (using the `outputHash' and `outputHashAlgo' + attributes). We don't want changes to such derivations to + propagate upwards through the dependency graph, changing output + paths everywhere. + + For instance, if we change the url in a call to the `fetchurl' + function, we do not want to rebuild everything depending on it + (after all, (the hash of) the file being downloaded is unchanged). + So the *output paths* should not change. On the other hand, the + *derivation paths* should change to reflect the new dependency + graph. + + That's what this function does: it returns a hash which is just the + hash of the derivation ATerm, except that any input derivation + paths have been replaced by the result of a recursive call to this + function, and that for fixed-output derivations we return a hash of + its output path. */ +Hash hashDerivationModulo(StoreAPI & store, Derivation drv) +{ + /* Return a fixed hash for fixed-output derivations. */ + if (isFixedOutputDrv(drv)) { + DerivationOutputs::const_iterator i = drv.outputs.begin(); + return hashString(htSHA256, "fixed:out:" + + i->second.hashAlgo + ":" + + i->second.hash + ":" + + i->second.path); + } + + /* For other derivations, replace the inputs paths with recursive + calls to this function.*/ + DerivationInputs inputs2; + foreach (DerivationInputs::const_iterator, i, drv.inputDrvs) { + Hash h = drvHashes[i->first]; + if (h.type == htUnknown) { + assert(store.isValidPath(i->first)); + Derivation drv2 = parseDerivation(readFile(i->first)); + h = hashDerivationModulo(store, drv2); + drvHashes[i->first] = h; + } + inputs2[printHash(h)] = i->second; + } + drv.inputDrvs = inputs2; + + return hashString(htSHA256, unparseDerivation(drv)); +} + + +DrvPathWithOutputs parseDrvPathWithOutputs(const string & s) +{ + size_t n = s.find("!"); + return n == s.npos + ? DrvPathWithOutputs(s, std::set()) + : DrvPathWithOutputs(string(s, 0, n), tokenizeString >(string(s, n + 1), ",")); +} + + +Path makeDrvPathWithOutputs(const Path & drvPath, const std::set & outputs) +{ + return outputs.empty() + ? drvPath + : drvPath + "!" + concatStringsSep(",", outputs); +} + + +bool wantOutput(const string & output, const std::set & wanted) +{ + return wanted.empty() || wanted.find(output) != wanted.end(); +} + + +} diff --git a/nix/libstore/derivations.hh b/nix/libstore/derivations.hh new file mode 100644 index 0000000000..703410b925 --- /dev/null +++ b/nix/libstore/derivations.hh @@ -0,0 +1,93 @@ +#pragma once + +#include "types.hh" +#include "hash.hh" + +#include + + +namespace nix { + + +/* Extension of derivations in the Nix store. */ +const string drvExtension = ".drv"; + + +/* Abstract syntax of derivations. */ + +struct DerivationOutput +{ + Path path; + string hashAlgo; /* hash used for expected hash computation */ + string hash; /* expected hash, may be null */ + DerivationOutput() + { + } + DerivationOutput(Path path, string hashAlgo, string hash) + { + this->path = path; + this->hashAlgo = hashAlgo; + this->hash = hash; + } + void parseHashInfo(bool & recursive, HashType & hashType, Hash & hash) const; +}; + +typedef std::map DerivationOutputs; + +/* For inputs that are sub-derivations, we specify exactly which + output IDs we are interested in. */ +typedef std::map DerivationInputs; + +typedef std::map StringPairs; + +struct Derivation +{ + DerivationOutputs outputs; /* keyed on symbolic IDs */ + DerivationInputs inputDrvs; /* inputs that are sub-derivations */ + PathSet inputSrcs; /* inputs that are sources */ + string platform; + Path builder; + Strings args; + StringPairs env; +}; + + +class StoreAPI; + + +/* Write a derivation to the Nix store, and return its path. */ +Path writeDerivation(StoreAPI & store, + const Derivation & drv, const string & name, bool repair = false); + +/* Parse a derivation. */ +Derivation parseDerivation(const string & s); + +/* Print a derivation. */ +string unparseDerivation(const Derivation & drv); + +/* Check whether a file name ends with the extensions for + derivations. */ +bool isDerivation(const string & fileName); + +/* Return true iff this is a fixed-output derivation. */ +bool isFixedOutputDrv(const Derivation & drv); + +Hash hashDerivationModulo(StoreAPI & store, Derivation drv); + +/* Memoisation of hashDerivationModulo(). */ +typedef std::map DrvHashes; + +extern DrvHashes drvHashes; + +/* Split a string specifying a derivation and a set of outputs + (/nix/store/hash-foo!out1,out2,...) into the derivation path and + the outputs. */ +typedef std::pair > DrvPathWithOutputs; +DrvPathWithOutputs parseDrvPathWithOutputs(const string & s); + +Path makeDrvPathWithOutputs(const Path & drvPath, const std::set & outputs); + +bool wantOutput(const string & output, const std::set & wanted); + + +} diff --git a/nix/libstore/gc.cc b/nix/libstore/gc.cc new file mode 100644 index 0000000000..f90edac1cd --- /dev/null +++ b/nix/libstore/gc.cc @@ -0,0 +1,748 @@ +#include "globals.hh" +#include "misc.hh" +#include "local-store.hh" + +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace nix { + + +static string gcLockName = "gc.lock"; +static string tempRootsDir = "temproots"; +static string gcRootsDir = "gcroots"; + + +/* Acquire the global GC lock. This is used to prevent new Nix + processes from starting after the temporary root files have been + read. To be precise: when they try to create a new temporary root + file, they will block until the garbage collector has finished / + yielded the GC lock. */ +int LocalStore::openGCLock(LockType lockType) +{ + Path fnGCLock = (format("%1%/%2%") + % settings.nixStateDir % gcLockName).str(); + + debug(format("acquiring global GC lock `%1%'") % fnGCLock); + + AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT, 0600); + if (fdGCLock == -1) + throw SysError(format("opening global GC lock `%1%'") % fnGCLock); + closeOnExec(fdGCLock); + + if (!lockFile(fdGCLock, lockType, false)) { + printMsg(lvlError, format("waiting for the big garbage collector lock...")); + lockFile(fdGCLock, lockType, true); + } + + /* !!! Restrict read permission on the GC root. Otherwise any + process that can open the file for reading can DoS the + collector. */ + + return fdGCLock.borrow(); +} + + +static void makeSymlink(const Path & link, const Path & target) +{ + /* Create directories up to `gcRoot'. */ + createDirs(dirOf(link)); + + /* Create the new symlink. */ + Path tempLink = (format("%1%.tmp-%2%-%3%") + % link % getpid() % rand()).str(); + createSymlink(target, tempLink); + + /* Atomically replace the old one. */ + if (rename(tempLink.c_str(), link.c_str()) == -1) + throw SysError(format("cannot rename `%1%' to `%2%'") + % tempLink % link); +} + + +void LocalStore::syncWithGC() +{ + AutoCloseFD fdGCLock = openGCLock(ltRead); +} + + +void LocalStore::addIndirectRoot(const Path & path) +{ + string hash = printHash32(hashString(htSHA1, path)); + Path realRoot = canonPath((format("%1%/%2%/auto/%3%") + % settings.nixStateDir % gcRootsDir % hash).str()); + makeSymlink(realRoot, path); +} + + +Path addPermRoot(StoreAPI & store, const Path & _storePath, + const Path & _gcRoot, bool indirect, bool allowOutsideRootsDir) +{ + Path storePath(canonPath(_storePath)); + Path gcRoot(canonPath(_gcRoot)); + assertStorePath(storePath); + + if (isInStore(gcRoot)) + throw Error(format( + "creating a garbage collector root (%1%) in the Nix store is forbidden " + "(are you running nix-build inside the store?)") % gcRoot); + + if (indirect) { + /* Don't clobber the the link if it already exists and doesn't + point to the Nix store. */ + if (pathExists(gcRoot) && (!isLink(gcRoot) || !isInStore(readLink(gcRoot)))) + throw Error(format("cannot create symlink `%1%'; already exists") % gcRoot); + makeSymlink(gcRoot, storePath); + store.addIndirectRoot(gcRoot); + } + + else { + if (!allowOutsideRootsDir) { + Path rootsDir = canonPath((format("%1%/%2%") % settings.nixStateDir % gcRootsDir).str()); + + if (string(gcRoot, 0, rootsDir.size() + 1) != rootsDir + "/") + throw Error(format( + "path `%1%' is not a valid garbage collector root; " + "it's not in the directory `%2%'") + % gcRoot % rootsDir); + } + + makeSymlink(gcRoot, storePath); + } + + /* Check that the root can be found by the garbage collector. + !!! This can be very slow on machines that have many roots. + Instead of reading all the roots, it would be more efficient to + check if the root is in a directory in or linked from the + gcroots directory. */ + if (settings.checkRootReachability) { + Roots roots = store.findRoots(); + if (roots.find(gcRoot) == roots.end()) + printMsg(lvlError, + format( + "warning: `%1%' is not in a directory where the garbage collector looks for roots; " + "therefore, `%2%' might be removed by the garbage collector") + % gcRoot % storePath); + } + + /* Grab the global GC root, causing us to block while a GC is in + progress. This prevents the set of permanent roots from + increasing while a GC is in progress. */ + store.syncWithGC(); + + return gcRoot; +} + + +/* The file to which we write our temporary roots. */ +static Path fnTempRoots; +static AutoCloseFD fdTempRoots; + + +void LocalStore::addTempRoot(const Path & path) +{ + /* Create the temporary roots file for this process. */ + if (fdTempRoots == -1) { + + while (1) { + Path dir = (format("%1%/%2%") % settings.nixStateDir % tempRootsDir).str(); + createDirs(dir); + + fnTempRoots = (format("%1%/%2%") + % dir % getpid()).str(); + + AutoCloseFD fdGCLock = openGCLock(ltRead); + + if (pathExists(fnTempRoots)) + /* It *must* be stale, since there can be no two + processes with the same pid. */ + unlink(fnTempRoots.c_str()); + + fdTempRoots = openLockFile(fnTempRoots, true); + + fdGCLock.close(); + + debug(format("acquiring read lock on `%1%'") % fnTempRoots); + lockFile(fdTempRoots, ltRead, true); + + /* Check whether the garbage collector didn't get in our + way. */ + struct stat st; + if (fstat(fdTempRoots, &st) == -1) + throw SysError(format("statting `%1%'") % fnTempRoots); + if (st.st_size == 0) break; + + /* The garbage collector deleted this file before we could + get a lock. (It won't delete the file after we get a + lock.) Try again. */ + } + + } + + /* Upgrade the lock to a write lock. This will cause us to block + if the garbage collector is holding our lock. */ + debug(format("acquiring write lock on `%1%'") % fnTempRoots); + lockFile(fdTempRoots, ltWrite, true); + + string s = path + '\0'; + writeFull(fdTempRoots, (const unsigned char *) s.data(), s.size()); + + /* Downgrade to a read lock. */ + debug(format("downgrading to read lock on `%1%'") % fnTempRoots); + lockFile(fdTempRoots, ltRead, true); +} + + +void removeTempRoots() +{ + if (fdTempRoots != -1) { + fdTempRoots.close(); + unlink(fnTempRoots.c_str()); + } +} + + +/* Automatically clean up the temporary roots file when we exit. */ +struct RemoveTempRoots +{ + ~RemoveTempRoots() + { + removeTempRoots(); + } +}; + +static RemoveTempRoots autoRemoveTempRoots __attribute__((unused)); + + +typedef std::shared_ptr FDPtr; +typedef list FDs; + + +static void readTempRoots(PathSet & tempRoots, FDs & fds) +{ + /* Read the `temproots' directory for per-process temporary root + files. */ + Strings tempRootFiles = readDirectory( + (format("%1%/%2%") % settings.nixStateDir % tempRootsDir).str()); + + foreach (Strings::iterator, i, tempRootFiles) { + Path path = (format("%1%/%2%/%3%") % settings.nixStateDir % tempRootsDir % *i).str(); + + debug(format("reading temporary root file `%1%'") % path); + FDPtr fd(new AutoCloseFD(open(path.c_str(), O_RDWR, 0666))); + if (*fd == -1) { + /* It's okay if the file has disappeared. */ + if (errno == ENOENT) continue; + throw SysError(format("opening temporary roots file `%1%'") % path); + } + + /* This should work, but doesn't, for some reason. */ + //FDPtr fd(new AutoCloseFD(openLockFile(path, false))); + //if (*fd == -1) continue; + + /* Try to acquire a write lock without blocking. This can + only succeed if the owning process has died. In that case + we don't care about its temporary roots. */ + if (lockFile(*fd, ltWrite, false)) { + printMsg(lvlError, format("removing stale temporary roots file `%1%'") % path); + unlink(path.c_str()); + writeFull(*fd, (const unsigned char *) "d", 1); + continue; + } + + /* Acquire a read lock. This will prevent the owning process + from upgrading to a write lock, therefore it will block in + addTempRoot(). */ + debug(format("waiting for read lock on `%1%'") % path); + lockFile(*fd, ltRead, true); + + /* Read the entire file. */ + string contents = readFile(*fd); + + /* Extract the roots. */ + string::size_type pos = 0, end; + + while ((end = contents.find((char) 0, pos)) != string::npos) { + Path root(contents, pos, end - pos); + debug(format("got temporary root `%1%'") % root); + assertStorePath(root); + tempRoots.insert(root); + pos = end + 1; + } + + fds.push_back(fd); /* keep open */ + } +} + + +static void foundRoot(StoreAPI & store, + const Path & path, const Path & target, Roots & roots) +{ + Path storePath = toStorePath(target); + if (store.isValidPath(storePath)) + roots[path] = storePath; + else + printMsg(lvlInfo, format("skipping invalid root from `%1%' to `%2%'") % path % storePath); +} + + +static void findRoots(StoreAPI & store, const Path & path, Roots & roots) +{ + try { + + struct stat st = lstat(path); + + if (S_ISDIR(st.st_mode)) { + Strings names = readDirectory(path); + foreach (Strings::iterator, i, names) + findRoots(store, path + "/" + *i, roots); + } + + else if (S_ISLNK(st.st_mode)) { + Path target = readLink(path); + if (isInStore(target)) + foundRoot(store, path, target, roots); + + /* Handle indirect roots. */ + else { + target = absPath(target, dirOf(path)); + if (!pathExists(target)) { + if (isInDir(path, settings.nixStateDir + "/" + gcRootsDir + "/auto")) { + printMsg(lvlInfo, format("removing stale link from `%1%' to `%2%'") % path % target); + unlink(path.c_str()); + } + } else { + struct stat st2 = lstat(target); + if (!S_ISLNK(st2.st_mode)) return; + Path target2 = readLink(target); + if (isInStore(target2)) foundRoot(store, target, target2, roots); + } + } + } + + } + + catch (SysError & e) { + /* We only ignore permanent failures. */ + if (e.errNo == EACCES || e.errNo == ENOENT || e.errNo == ENOTDIR) + printMsg(lvlInfo, format("cannot read potential root `%1%'") % path); + else + throw; + } +} + + +Roots LocalStore::findRoots() +{ + Roots roots; + + /* Process direct roots in {gcroots,manifests,profiles}. */ + nix::findRoots(*this, settings.nixStateDir + "/" + gcRootsDir, roots); + nix::findRoots(*this, settings.nixStateDir + "/manifests", roots); + nix::findRoots(*this, settings.nixStateDir + "/profiles", roots); + + return roots; +} + + +static void addAdditionalRoots(StoreAPI & store, PathSet & roots) +{ + Path rootFinder = getEnv("NIX_ROOT_FINDER", + settings.nixLibexecDir + "/guix/list-runtime-roots"); + + if (rootFinder.empty()) return; + + debug(format("executing `%1%' to find additional roots") % rootFinder); + + string result = runProgram(rootFinder); + + StringSet paths = tokenizeString(result, "\n"); + + foreach (StringSet::iterator, i, paths) { + if (isInStore(*i)) { + Path path = toStorePath(*i); + if (roots.find(path) == roots.end() && store.isValidPath(path)) { + debug(format("got additional root `%1%'") % path); + roots.insert(path); + } + } + } +} + + +struct GCLimitReached { }; + + +struct LocalStore::GCState +{ + GCOptions options; + GCResults & results; + PathSet roots; + PathSet tempRoots; + PathSet dead; + PathSet alive; + bool gcKeepOutputs; + bool gcKeepDerivations; + unsigned long long bytesInvalidated; + Path trashDir; + bool shouldDelete; + GCState(GCResults & results_) : results(results_), bytesInvalidated(0) { } +}; + + +bool LocalStore::isActiveTempFile(const GCState & state, + const Path & path, const string & suffix) +{ + return hasSuffix(path, suffix) + && state.tempRoots.find(string(path, 0, path.size() - suffix.size())) != state.tempRoots.end(); +} + + +void LocalStore::deleteGarbage(GCState & state, const Path & path) +{ + unsigned long long bytesFreed; + deletePath(path, bytesFreed); + state.results.bytesFreed += bytesFreed; +} + + +void LocalStore::deletePathRecursive(GCState & state, const Path & path) +{ + checkInterrupt(); + + unsigned long long size = 0; + + if (isValidPath(path)) { + PathSet referrers; + queryReferrers(path, referrers); + foreach (PathSet::iterator, i, referrers) + if (*i != path) deletePathRecursive(state, *i); + size = queryPathInfo(path).narSize; + invalidatePathChecked(path); + } + + struct stat st; + if (lstat(path.c_str(), &st)) { + if (errno == ENOENT) return; + throw SysError(format("getting status of %1%") % path); + } + + printMsg(lvlInfo, format("deleting `%1%'") % path); + + state.results.paths.insert(path); + + /* If the path is not a regular file or symlink, move it to the + trash directory. The move is to ensure that later (when we're + not holding the global GC lock) we can delete the path without + being afraid that the path has become alive again. Otherwise + delete it right away. */ + if (S_ISDIR(st.st_mode)) { + // Estimate the amount freed using the narSize field. FIXME: + // if the path was not valid, need to determine the actual + // size. + state.bytesInvalidated += size; + // Mac OS X cannot rename directories if they are read-only. + if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) + throw SysError(format("making `%1%' writable") % path); + Path tmp = state.trashDir + "/" + baseNameOf(path); + if (rename(path.c_str(), tmp.c_str())) + throw SysError(format("unable to rename `%1%' to `%2%'") % path % tmp); + } else + deleteGarbage(state, path); + + if (state.results.bytesFreed + state.bytesInvalidated > state.options.maxFreed) { + printMsg(lvlInfo, format("deleted or invalidated more than %1% bytes; stopping") % state.options.maxFreed); + throw GCLimitReached(); + } +} + + +bool LocalStore::canReachRoot(GCState & state, PathSet & visited, const Path & path) +{ + if (visited.find(path) != visited.end()) return false; + + if (state.alive.find(path) != state.alive.end()) { + return true; + } + + if (state.dead.find(path) != state.dead.end()) { + return false; + } + + if (state.roots.find(path) != state.roots.end()) { + printMsg(lvlDebug, format("cannot delete `%1%' because it's a root") % path); + state.alive.insert(path); + return true; + } + + visited.insert(path); + + if (!isValidPath(path)) return false; + + PathSet incoming; + + /* Don't delete this path if any of its referrers are alive. */ + queryReferrers(path, incoming); + + /* If gc-keep-derivations is set and this is a derivation, then + don't delete the derivation if any of the outputs are alive. */ + if (state.gcKeepDerivations && isDerivation(path)) { + PathSet outputs = queryDerivationOutputs(path); + foreach (PathSet::iterator, i, outputs) + if (isValidPath(*i) && queryDeriver(*i) == path) + incoming.insert(*i); + } + + /* If gc-keep-outputs is set, then don't delete this path if there + are derivers of this path that are not garbage. */ + if (state.gcKeepOutputs) { + PathSet derivers = queryValidDerivers(path); + foreach (PathSet::iterator, i, derivers) + incoming.insert(*i); + } + + foreach (PathSet::iterator, i, incoming) + if (*i != path) + if (canReachRoot(state, visited, *i)) { + state.alive.insert(path); + return true; + } + + return false; +} + + +void LocalStore::tryToDelete(GCState & state, const Path & path) +{ + checkInterrupt(); + + if (path == linksDir || path == state.trashDir) return; + + startNest(nest, lvlDebug, format("considering whether to delete `%1%'") % path); + + if (!isValidPath(path)) { + /* A lock file belonging to a path that we're building right + now isn't garbage. */ + if (isActiveTempFile(state, path, ".lock")) return; + + /* Don't delete .chroot directories for derivations that are + currently being built. */ + if (isActiveTempFile(state, path, ".chroot")) return; + } + + PathSet visited; + + if (canReachRoot(state, visited, path)) { + printMsg(lvlDebug, format("cannot delete `%1%' because it's still reachable") % path); + } else { + /* No path we visited was a root, so everything is garbage. + But we only delete ‘path’ and its referrers here so that + ‘nix-store --delete’ doesn't have the unexpected effect of + recursing into derivations and outputs. */ + state.dead.insert(visited.begin(), visited.end()); + if (state.shouldDelete) + deletePathRecursive(state, path); + } +} + + +/* Unlink all files in /nix/store/.links that have a link count of 1, + which indicates that there are no other links and so they can be + safely deleted. FIXME: race condition with optimisePath(): we + might see a link count of 1 just before optimisePath() increases + the link count. */ +void LocalStore::removeUnusedLinks(const GCState & state) +{ + AutoCloseDir dir = opendir(linksDir.c_str()); + if (!dir) throw SysError(format("opening directory `%1%'") % linksDir); + + long long actualSize = 0, unsharedSize = 0; + + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir)) { + checkInterrupt(); + string name = dirent->d_name; + if (name == "." || name == "..") continue; + Path path = linksDir + "/" + name; + + struct stat st; + if (lstat(path.c_str(), &st) == -1) + throw SysError(format("statting `%1%'") % path); + + if (st.st_nlink != 1) { + unsigned long long size = st.st_blocks * 512ULL; + actualSize += size; + unsharedSize += (st.st_nlink - 1) * size; + continue; + } + + printMsg(lvlTalkative, format("deleting unused link `%1%'") % path); + + if (unlink(path.c_str()) == -1) + throw SysError(format("deleting `%1%'") % path); + + state.results.bytesFreed += st.st_blocks * 512; + } + + struct stat st; + if (stat(linksDir.c_str(), &st) == -1) + throw SysError(format("statting `%1%'") % linksDir); + long long overhead = st.st_blocks * 512ULL; + + printMsg(lvlInfo, format("note: currently hard linking saves %.2f MiB") + % ((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0))); +} + + +void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) +{ + GCState state(results); + state.options = options; + state.trashDir = settings.nixStore + "/trash"; + state.gcKeepOutputs = settings.gcKeepOutputs; + state.gcKeepDerivations = settings.gcKeepDerivations; + + /* Using `--ignore-liveness' with `--delete' can have unintended + consequences if `gc-keep-outputs' or `gc-keep-derivations' are + true (the garbage collector will recurse into deleting the + outputs or derivers, respectively). So disable them. */ + if (options.action == GCOptions::gcDeleteSpecific && options.ignoreLiveness) { + state.gcKeepOutputs = false; + state.gcKeepDerivations = false; + } + + state.shouldDelete = options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific; + + /* Acquire the global GC root. This prevents + a) New roots from being added. + b) Processes from creating new temporary root files. */ + AutoCloseFD fdGCLock = openGCLock(ltWrite); + + /* Find the roots. Since we've grabbed the GC lock, the set of + permanent roots cannot increase now. */ + printMsg(lvlError, format("finding garbage collector roots...")); + Roots rootMap = options.ignoreLiveness ? Roots() : findRoots(); + + foreach (Roots::iterator, i, rootMap) state.roots.insert(i->second); + + /* Add additional roots returned by the program specified by the + NIX_ROOT_FINDER environment variable. This is typically used + to add running programs to the set of roots (to prevent them + from being garbage collected). */ + if (!options.ignoreLiveness) + addAdditionalRoots(*this, state.roots); + + /* Read the temporary roots. This acquires read locks on all + per-process temporary root files. So after this point no paths + can be added to the set of temporary roots. */ + FDs fds; + readTempRoots(state.tempRoots, fds); + state.roots.insert(state.tempRoots.begin(), state.tempRoots.end()); + + /* After this point the set of roots or temporary roots cannot + increase, since we hold locks on everything. So everything + that is not reachable from `roots'. */ + + if (state.shouldDelete) { + if (pathExists(state.trashDir)) deleteGarbage(state, state.trashDir); + createDirs(state.trashDir); + } + + /* Now either delete all garbage paths, or just the specified + paths (for gcDeleteSpecific). */ + + if (options.action == GCOptions::gcDeleteSpecific) { + + foreach (PathSet::iterator, i, options.pathsToDelete) { + assertStorePath(*i); + tryToDelete(state, *i); + if (state.dead.find(*i) == state.dead.end()) + throw Error(format("cannot delete path `%1%' since it is still alive") % *i); + } + + } else if (options.maxFreed > 0) { + + if (state.shouldDelete) + printMsg(lvlError, format("deleting garbage...")); + else + printMsg(lvlError, format("determining live/dead paths...")); + + try { + + AutoCloseDir dir = opendir(settings.nixStore.c_str()); + if (!dir) throw SysError(format("opening directory `%1%'") % settings.nixStore); + + /* Read the store and immediately delete all paths that + aren't valid. When using --max-freed etc., deleting + invalid paths is preferred over deleting unreachable + paths, since unreachable paths could become reachable + again. We don't use readDirectory() here so that GCing + can start faster. */ + Paths entries; + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir)) { + checkInterrupt(); + string name = dirent->d_name; + if (name == "." || name == "..") continue; + Path path = settings.nixStore + "/" + name; + if (isValidPath(path)) + entries.push_back(path); + else + tryToDelete(state, path); + } + + dir.close(); + + /* Now delete the unreachable valid paths. Randomise the + order in which we delete entries to make the collector + less biased towards deleting paths that come + alphabetically first (e.g. /nix/store/000...). This + matters when using --max-freed etc. */ + vector entries_(entries.begin(), entries.end()); + random_shuffle(entries_.begin(), entries_.end()); + + foreach (vector::iterator, i, entries_) + tryToDelete(state, *i); + + } catch (GCLimitReached & e) { + } + } + + if (state.options.action == GCOptions::gcReturnLive) { + state.results.paths = state.alive; + return; + } + + if (state.options.action == GCOptions::gcReturnDead) { + state.results.paths = state.dead; + return; + } + + /* Allow other processes to add to the store from here on. */ + fdGCLock.close(); + fds.clear(); + + /* Delete the trash directory. */ + printMsg(lvlInfo, format("deleting `%1%'") % state.trashDir); + deleteGarbage(state, state.trashDir); + + /* Clean up the links directory. */ + if (options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific) { + printMsg(lvlError, format("deleting unused links...")); + removeUnusedLinks(state); + } + + /* While we're at it, vacuum the database. */ + if (options.action == GCOptions::gcDeleteDead) vacuumDB(); +} + + +} diff --git a/nix/libstore/globals.cc b/nix/libstore/globals.cc new file mode 100644 index 0000000000..86fa56739c --- /dev/null +++ b/nix/libstore/globals.cc @@ -0,0 +1,240 @@ +#include "config.h" + +#include "globals.hh" +#include "util.hh" + +#include +#include + + +namespace nix { + + +/* The default location of the daemon socket, relative to nixStateDir. + The socket is in a directory to allow you to control access to the + Nix daemon by setting the mode/ownership of the directory + appropriately. (This wouldn't work on the socket itself since it + must be deleted and recreated on startup.) */ +#define DEFAULT_SOCKET_PATH "/daemon-socket/socket" + + +Settings settings; + + +Settings::Settings() +{ + keepFailed = false; + keepGoing = false; + tryFallback = false; + buildVerbosity = lvlError; + maxBuildJobs = 1; + buildCores = 1; + readOnlyMode = false; + thisSystem = SYSTEM; + maxSilentTime = 0; + buildTimeout = 0; + useBuildHook = true; + printBuildTrace = false; + reservedSize = 1024 * 1024; + fsyncMetadata = true; + useSQLiteWAL = true; + syncBeforeRegistering = false; + useSubstitutes = true; + useChroot = false; + useSshSubstituter = false; + impersonateLinux26 = false; + keepLog = true; + compressLog = true; + maxLogSize = 0; + cacheFailure = false; + pollInterval = 5; + checkRootReachability = false; + gcKeepOutputs = false; + gcKeepDerivations = true; + autoOptimiseStore = false; + envKeepDerivations = false; + lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1"; + showTrace = false; +} + + +void Settings::processEnvironment() +{ + nixStore = canonPath(getEnv("NIX_STORE_DIR", getEnv("NIX_STORE", NIX_STORE_DIR))); + nixDataDir = canonPath(getEnv("NIX_DATA_DIR", NIX_DATA_DIR)); + nixLogDir = canonPath(getEnv("NIX_LOG_DIR", NIX_LOG_DIR)); + nixStateDir = canonPath(getEnv("NIX_STATE_DIR", NIX_STATE_DIR)); + nixDBPath = getEnv("NIX_DB_DIR", nixStateDir + "/db"); + nixConfDir = canonPath(getEnv("NIX_CONF_DIR", NIX_CONF_DIR)); + nixLibexecDir = canonPath(getEnv("NIX_LIBEXEC_DIR", NIX_LIBEXEC_DIR)); + nixBinDir = canonPath(getEnv("NIX_BIN_DIR", NIX_BIN_DIR)); + nixDaemonSocketFile = canonPath(nixStateDir + DEFAULT_SOCKET_PATH); +} + + +void Settings::loadConfFile() +{ + Path settingsFile = (format("%1%/%2%") % nixConfDir % "nix.conf").str(); + if (!pathExists(settingsFile)) return; + string contents = readFile(settingsFile); + + unsigned int pos = 0; + + while (pos < contents.size()) { + string line; + while (pos < contents.size() && contents[pos] != '\n') + line += contents[pos++]; + pos++; + + string::size_type hash = line.find('#'); + if (hash != string::npos) + line = string(line, 0, hash); + + vector tokens = tokenizeString >(line); + if (tokens.empty()) continue; + + if (tokens.size() < 2 || tokens[1] != "=") + throw Error(format("illegal configuration line `%1%' in `%2%'") % line % settingsFile); + + string name = tokens[0]; + + vector::iterator i = tokens.begin(); + advance(i, 2); + settings[name] = concatStringsSep(" ", Strings(i, tokens.end())); // FIXME: slow + }; +} + + +void Settings::set(const string & name, const string & value) +{ + settings[name] = value; + overrides[name] = value; +} + + +void Settings::update() +{ + get(tryFallback, "build-fallback"); + get(maxBuildJobs, "build-max-jobs"); + get(buildCores, "build-cores"); + get(thisSystem, "system"); + get(maxSilentTime, "build-max-silent-time"); + get(buildTimeout, "build-timeout"); + get(reservedSize, "gc-reserved-space"); + get(fsyncMetadata, "fsync-metadata"); + get(useSQLiteWAL, "use-sqlite-wal"); + get(syncBeforeRegistering, "sync-before-registering"); + get(useSubstitutes, "build-use-substitutes"); + get(buildUsersGroup, "build-users-group"); + get(useChroot, "build-use-chroot"); + get(dirsInChroot, "build-chroot-dirs"); + get(impersonateLinux26, "build-impersonate-linux-26"); + get(keepLog, "build-keep-log"); + get(compressLog, "build-compress-log"); + get(maxLogSize, "build-max-log-size"); + get(cacheFailure, "build-cache-failure"); + get(pollInterval, "build-poll-interval"); + get(checkRootReachability, "gc-check-reachability"); + get(gcKeepOutputs, "gc-keep-outputs"); + get(gcKeepDerivations, "gc-keep-derivations"); + get(autoOptimiseStore, "auto-optimise-store"); + get(envKeepDerivations, "env-keep-derivations"); + get(sshSubstituterHosts, "ssh-substituter-hosts"); + get(useSshSubstituter, "use-ssh-substituter"); + + string subs = getEnv("NIX_SUBSTITUTERS", "default"); + if (subs == "default") { + substituters.clear(); +#if 0 + if (getEnv("NIX_OTHER_STORES") != "") + substituters.push_back(nixLibexecDir + "/nix/substituters/copy-from-other-stores.pl"); +#endif + substituters.push_back(nixLibexecDir + "/nix/substituters/download-using-manifests.pl"); + substituters.push_back(nixLibexecDir + "/nix/substituters/download-from-binary-cache.pl"); + if (useSshSubstituter) + substituters.push_back(nixLibexecDir + "/nix/substituters/download-via-ssh"); + } else + substituters = tokenizeString(subs, ":"); +} + + +void Settings::get(string & res, const string & name) +{ + SettingsMap::iterator i = settings.find(name); + if (i == settings.end()) return; + res = i->second; +} + + +void Settings::get(bool & res, const string & name) +{ + SettingsMap::iterator i = settings.find(name); + if (i == settings.end()) return; + if (i->second == "true") res = true; + else if (i->second == "false") res = false; + else throw Error(format("configuration option `%1%' should be either `true' or `false', not `%2%'") + % name % i->second); +} + + +void Settings::get(StringSet & res, const string & name) +{ + SettingsMap::iterator i = settings.find(name); + if (i == settings.end()) return; + res.clear(); + Strings ss = tokenizeString(i->second); + res.insert(ss.begin(), ss.end()); +} + +void Settings::get(Strings & res, const string & name) +{ + SettingsMap::iterator i = settings.find(name); + if (i == settings.end()) return; + res = tokenizeString(i->second); +} + + +template void Settings::get(N & res, const string & name) +{ + SettingsMap::iterator i = settings.find(name); + if (i == settings.end()) return; + if (!string2Int(i->second, res)) + throw Error(format("configuration setting `%1%' should have an integer value") % name); +} + + +string Settings::pack() +{ + string s; + foreach (SettingsMap::iterator, i, settings) { + if (i->first.find('\n') != string::npos || + i->first.find('=') != string::npos || + i->second.find('\n') != string::npos) + throw Error("illegal option name/value"); + s += i->first; s += '='; s += i->second; s += '\n'; + } + return s; +} + + +void Settings::unpack(const string & pack) { + Strings lines = tokenizeString(pack, "\n"); + foreach (Strings::iterator, i, lines) { + string::size_type eq = i->find('='); + if (eq == string::npos) + throw Error("illegal option name/value"); + set(i->substr(0, eq), i->substr(eq + 1)); + } +} + + +Settings::SettingsMap Settings::getOverrides() +{ + return overrides; +} + + +const string nixVersion = PACKAGE_VERSION; + + +} diff --git a/nix/libstore/globals.hh b/nix/libstore/globals.hh new file mode 100644 index 0000000000..711c365294 --- /dev/null +++ b/nix/libstore/globals.hh @@ -0,0 +1,218 @@ +#pragma once + +#include "types.hh" + +#include +#include + + +namespace nix { + + +struct Settings { + + typedef std::map SettingsMap; + + Settings(); + + void processEnvironment(); + + void loadConfFile(); + + void set(const string & name, const string & value); + + void update(); + + string pack(); + + void unpack(const string & pack); + + SettingsMap getOverrides(); + + /* The directory where we store sources and derived files. */ + Path nixStore; + + Path nixDataDir; /* !!! fix */ + + /* The directory where we log various operations. */ + Path nixLogDir; + + /* The directory where state is stored. */ + Path nixStateDir; + + /* The directory where we keep the SQLite database. */ + Path nixDBPath; + + /* The directory where configuration files are stored. */ + Path nixConfDir; + + /* The directory where internal helper programs are stored. */ + Path nixLibexecDir; + + /* The directory where the main programs are stored. */ + Path nixBinDir; + + /* File name of the socket the daemon listens to. */ + Path nixDaemonSocketFile; + + /* Whether to keep temporary directories of failed builds. */ + bool keepFailed; + + /* Whether to keep building subgoals when a sibling (another + subgoal of the same goal) fails. */ + bool keepGoing; + + /* Whether, if we cannot realise the known closure corresponding + to a derivation, we should try to normalise the derivation + instead. */ + bool tryFallback; + + /* Verbosity level for build output. */ + Verbosity buildVerbosity; + + /* Maximum number of parallel build jobs. 0 means unlimited. */ + unsigned int maxBuildJobs; + + /* Number of CPU cores to utilize in parallel within a build, + i.e. by passing this number to Make via '-j'. 0 means that the + number of actual CPU cores on the local host ought to be + auto-detected. */ + unsigned int buildCores; + + /* Read-only mode. Don't copy stuff to the store, don't change + the database. */ + bool readOnlyMode; + + /* The canonical system name, as returned by config.guess. */ + string thisSystem; + + /* The maximum time in seconds that a builer can go without + producing any output on stdout/stderr before it is killed. 0 + means infinity. */ + time_t maxSilentTime; + + /* The maximum duration in seconds that a builder can run. 0 + means infinity. */ + time_t buildTimeout; + + /* The substituters. There are programs that can somehow realise + a store path without building, e.g., by downloading it or + copying it from a CD. */ + Paths substituters; + + /* Whether to use build hooks (for distributed builds). Sometimes + users want to disable this from the command-line. */ + bool useBuildHook; + + /* Whether buildDerivations() should print out lines on stderr in + a fixed format to allow its progress to be monitored. Each + line starts with a "@". The following are defined: + + @ build-started + @ build-failed + @ build-succeeded + @ substituter-started + @ substituter-failed + @ substituter-succeeded + + Best combined with --no-build-output, otherwise stderr might + conceivably contain lines in this format printed by the + builders. */ + bool printBuildTrace; + + /* Amount of reserved space for the garbage collector + (/nix/var/nix/db/reserved). */ + off_t reservedSize; + + /* Whether SQLite should use fsync. */ + bool fsyncMetadata; + + /* Whether SQLite should use WAL mode. */ + bool useSQLiteWAL; + + /* Whether to call sync() before registering a path as valid. */ + bool syncBeforeRegistering; + + /* Whether to use substitutes. */ + bool useSubstitutes; + + /* The Unix group that contains the build users. */ + string buildUsersGroup; + + /* Whether to build in chroot. */ + bool useChroot; + + /* The directories from the host filesystem to be included in the + chroot. */ + StringSet dirsInChroot; + + /* Set of ssh connection strings for the ssh substituter */ + Strings sshSubstituterHosts; + + /* Whether to use the ssh substituter at all */ + bool useSshSubstituter; + + /* Whether to impersonate a Linux 2.6 machine on newer kernels. */ + bool impersonateLinux26; + + /* Whether to store build logs. */ + bool keepLog; + + /* Whether to compress logs. */ + bool compressLog; + + /* Maximum number of bytes a builder can write to stdout/stderr + before being killed (0 means no limit). */ + unsigned long maxLogSize; + + /* Whether to cache build failures. */ + bool cacheFailure; + + /* How often (in seconds) to poll for locks. */ + unsigned int pollInterval; + + /* Whether to check if new GC roots can in fact be found by the + garbage collector. */ + bool checkRootReachability; + + /* Whether the garbage collector should keep outputs of live + derivations. */ + bool gcKeepOutputs; + + /* Whether the garbage collector should keep derivers of live + paths. */ + bool gcKeepDerivations; + + /* Whether to automatically replace files with identical contents + with hard links. */ + bool autoOptimiseStore; + + /* Whether to add derivations as a dependency of user environments + (to prevent them from being GCed). */ + bool envKeepDerivations; + + /* Whether to lock the Nix client and worker to the same CPU. */ + bool lockCPU; + + /* Whether to show a stack trace if Nix evaluation fails. */ + bool showTrace; + +private: + SettingsMap settings, overrides; + + void get(string & res, const string & name); + void get(bool & res, const string & name); + void get(StringSet & res, const string & name); + void get(Strings & res, const string & name); + template void get(N & res, const string & name); +}; + + +// FIXME: don't use a global variable. +extern Settings settings; + + +extern const string nixVersion; + + +} diff --git a/nix/libstore/local-store.cc b/nix/libstore/local-store.cc new file mode 100644 index 0000000000..1293a6e8f2 --- /dev/null +++ b/nix/libstore/local-store.cc @@ -0,0 +1,2010 @@ +#include "config.h" +#include "local-store.hh" +#include "globals.hh" +#include "archive.hh" +#include "pathlocks.hh" +#include "worker-protocol.hh" +#include "derivations.hh" +#include "affinity.hh" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_UNSHARE && HAVE_STATVFS && HAVE_SYS_MOUNT_H +#include +#include +#include +#endif + +#if HAVE_LINUX_FS_H +#include +#include +#include +#endif + +#include + + +namespace nix { + + +MakeError(SQLiteError, Error); +MakeError(SQLiteBusy, SQLiteError); + + +static void throwSQLiteError(sqlite3 * db, const format & f) + __attribute__ ((noreturn)); + +static void throwSQLiteError(sqlite3 * db, const format & f) +{ + int err = sqlite3_errcode(db); + if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { + if (err == SQLITE_PROTOCOL) + printMsg(lvlError, "warning: SQLite database is busy (SQLITE_PROTOCOL)"); + else { + static bool warned = false; + if (!warned) { + printMsg(lvlError, "warning: SQLite database is busy"); + warned = true; + } + } + /* Sleep for a while since retrying the transaction right away + is likely to fail again. */ +#if HAVE_NANOSLEEP + struct timespec t; + t.tv_sec = 0; + t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */ + nanosleep(&t, 0); +#else + sleep(1); +#endif + throw SQLiteBusy(format("%1%: %2%") % f.str() % sqlite3_errmsg(db)); + } + else + throw SQLiteError(format("%1%: %2%") % f.str() % sqlite3_errmsg(db)); +} + + +/* Convenience macros for retrying a SQLite transaction. */ +#define retry_sqlite while (1) { try { +#define end_retry_sqlite break; } catch (SQLiteBusy & e) { } } + + +SQLite::~SQLite() +{ + try { + if (db && sqlite3_close(db) != SQLITE_OK) + throwSQLiteError(db, "closing database"); + } catch (...) { + ignoreException(); + } +} + + +void SQLiteStmt::create(sqlite3 * db, const string & s) +{ + checkInterrupt(); + assert(!stmt); + if (sqlite3_prepare_v2(db, s.c_str(), -1, &stmt, 0) != SQLITE_OK) + throwSQLiteError(db, "creating statement"); + this->db = db; +} + + +void SQLiteStmt::reset() +{ + assert(stmt); + /* Note: sqlite3_reset() returns the error code for the most + recent call to sqlite3_step(). So ignore it. */ + sqlite3_reset(stmt); + curArg = 1; +} + + +SQLiteStmt::~SQLiteStmt() +{ + try { + if (stmt && sqlite3_finalize(stmt) != SQLITE_OK) + throwSQLiteError(db, "finalizing statement"); + } catch (...) { + ignoreException(); + } +} + + +void SQLiteStmt::bind(const string & value) +{ + if (sqlite3_bind_text(stmt, curArg++, value.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) + throwSQLiteError(db, "binding argument"); +} + + +void SQLiteStmt::bind(int value) +{ + if (sqlite3_bind_int(stmt, curArg++, value) != SQLITE_OK) + throwSQLiteError(db, "binding argument"); +} + + +void SQLiteStmt::bind64(long long value) +{ + if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK) + throwSQLiteError(db, "binding argument"); +} + + +void SQLiteStmt::bind() +{ + if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK) + throwSQLiteError(db, "binding argument"); +} + + +/* Helper class to ensure that prepared statements are reset when + leaving the scope that uses them. Unfinished prepared statements + prevent transactions from being aborted, and can cause locks to be + kept when they should be released. */ +struct SQLiteStmtUse +{ + SQLiteStmt & stmt; + SQLiteStmtUse(SQLiteStmt & stmt) : stmt(stmt) + { + stmt.reset(); + } + ~SQLiteStmtUse() + { + try { + stmt.reset(); + } catch (...) { + ignoreException(); + } + } +}; + + +struct SQLiteTxn +{ + bool active; + sqlite3 * db; + + SQLiteTxn(sqlite3 * db) : active(false) { + this->db = db; + if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK) + throwSQLiteError(db, "starting transaction"); + active = true; + } + + void commit() + { + if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK) + throwSQLiteError(db, "committing transaction"); + active = false; + } + + ~SQLiteTxn() + { + try { + if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK) + throwSQLiteError(db, "aborting transaction"); + } catch (...) { + ignoreException(); + } + } +}; + + +void checkStoreNotSymlink() +{ + if (getEnv("NIX_IGNORE_SYMLINK_STORE") == "1") return; + Path path = settings.nixStore; + struct stat st; + while (path != "/") { + if (lstat(path.c_str(), &st)) + throw SysError(format("getting status of `%1%'") % path); + if (S_ISLNK(st.st_mode)) + throw Error(format( + "the path `%1%' is a symlink; " + "this is not allowed for the Nix store and its parent directories") + % path); + path = dirOf(path); + } +} + + +LocalStore::LocalStore(bool reserveSpace) + : didSetSubstituterEnv(false) +{ + schemaPath = settings.nixDBPath + "/schema"; + + if (settings.readOnlyMode) { + openDB(false); + return; + } + + /* Create missing state directories if they don't already exist. */ + createDirs(settings.nixStore); + makeStoreWritable(); + createDirs(linksDir = settings.nixStore + "/.links"); + Path profilesDir = settings.nixStateDir + "/profiles"; + createDirs(settings.nixStateDir + "/profiles"); + createDirs(settings.nixStateDir + "/temproots"); + createDirs(settings.nixDBPath); + Path gcRootsDir = settings.nixStateDir + "/gcroots"; + if (!pathExists(gcRootsDir)) { + createDirs(gcRootsDir); + createSymlink(profilesDir, gcRootsDir + "/profiles"); + } + + checkStoreNotSymlink(); + + /* We can't open a SQLite database if the disk is full. Since + this prevents the garbage collector from running when it's most + needed, we reserve some dummy space that we can free just + before doing a garbage collection. */ + try { + Path reservedPath = settings.nixDBPath + "/reserved"; + if (reserveSpace) { + struct stat st; + if (stat(reservedPath.c_str(), &st) == -1 || + st.st_size != settings.reservedSize) + writeFile(reservedPath, string(settings.reservedSize, 'X')); + } + else + deletePath(reservedPath); + } catch (SysError & e) { /* don't care about errors */ + } + + /* Acquire the big fat lock in shared mode to make sure that no + schema upgrade is in progress. */ + try { + Path globalLockPath = settings.nixDBPath + "/big-lock"; + globalLock = openLockFile(globalLockPath.c_str(), true); + } catch (SysError & e) { + if (e.errNo != EACCES) throw; + settings.readOnlyMode = true; + openDB(false); + return; + } + + if (!lockFile(globalLock, ltRead, false)) { + printMsg(lvlError, "waiting for the big Nix store lock..."); + lockFile(globalLock, ltRead, true); + } + + /* Check the current database schema and if necessary do an + upgrade. */ + int curSchema = getSchema(); + if (curSchema > nixSchemaVersion) + throw Error(format("current Nix store schema is version %1%, but I only support %2%") + % curSchema % nixSchemaVersion); + + else if (curSchema == 0) { /* new store */ + curSchema = nixSchemaVersion; + openDB(true); + writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str()); + } + + else if (curSchema < nixSchemaVersion) { + if (curSchema < 5) + throw Error( + "Your Nix store has a database in Berkeley DB format,\n" + "which is no longer supported. To convert to the new format,\n" + "please upgrade Nix to version 0.12 first."); + + if (!lockFile(globalLock, ltWrite, false)) { + printMsg(lvlError, "waiting for exclusive access to the Nix store..."); + lockFile(globalLock, ltWrite, true); + } + + /* Get the schema version again, because another process may + have performed the upgrade already. */ + curSchema = getSchema(); + + if (curSchema < 6) upgradeStore6(); + else if (curSchema < 7) { upgradeStore7(); openDB(true); } + + writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str()); + + lockFile(globalLock, ltRead, true); + } + + else openDB(false); +} + + +LocalStore::~LocalStore() +{ + try { + foreach (RunningSubstituters::iterator, i, runningSubstituters) { + if (i->second.disabled) continue; + i->second.to.close(); + i->second.from.close(); + i->second.error.close(); + i->second.pid.wait(true); + } + } catch (...) { + ignoreException(); + } +} + + +int LocalStore::getSchema() +{ + int curSchema = 0; + if (pathExists(schemaPath)) { + string s = readFile(schemaPath); + if (!string2Int(s, curSchema)) + throw Error(format("`%1%' is corrupt") % schemaPath); + } + return curSchema; +} + + +void LocalStore::openDB(bool create) +{ + if (access(settings.nixDBPath.c_str(), R_OK | W_OK)) + throw SysError(format("Nix database directory `%1%' is not writable") % settings.nixDBPath); + + /* Open the Nix database. */ + string dbPath = settings.nixDBPath + "/db.sqlite"; + if (sqlite3_open_v2(dbPath.c_str(), &db.db, + SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), 0) != SQLITE_OK) + throw Error(format("cannot open Nix database `%1%'") % dbPath); + + if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK) + throwSQLiteError(db, "setting timeout"); + + if (sqlite3_exec(db, "pragma foreign_keys = 1;", 0, 0, 0) != SQLITE_OK) + throwSQLiteError(db, "enabling foreign keys"); + + /* !!! check whether sqlite has been built with foreign key + support */ + + /* Whether SQLite should fsync(). "Normal" synchronous mode + should be safe enough. If the user asks for it, don't sync at + all. This can cause database corruption if the system + crashes. */ + string syncMode = settings.fsyncMetadata ? "normal" : "off"; + if (sqlite3_exec(db, ("pragma synchronous = " + syncMode + ";").c_str(), 0, 0, 0) != SQLITE_OK) + throwSQLiteError(db, "setting synchronous mode"); + + /* Set the SQLite journal mode. WAL mode is fastest, so it's the + default. */ + string mode = settings.useSQLiteWAL ? "wal" : "truncate"; + string prevMode; + { + SQLiteStmt stmt; + stmt.create(db, "pragma main.journal_mode;"); + if (sqlite3_step(stmt) != SQLITE_ROW) + throwSQLiteError(db, "querying journal mode"); + prevMode = string((const char *) sqlite3_column_text(stmt, 0)); + } + if (prevMode != mode && + sqlite3_exec(db, ("pragma main.journal_mode = " + mode + ";").c_str(), 0, 0, 0) != SQLITE_OK) + throwSQLiteError(db, "setting journal mode"); + + /* Increase the auto-checkpoint interval to 40000 pages. This + seems enough to ensure that instantiating the NixOS system + derivation is done in a single fsync(). */ + if (mode == "wal" && sqlite3_exec(db, "pragma wal_autocheckpoint = 40000;", 0, 0, 0) != SQLITE_OK) + throwSQLiteError(db, "setting autocheckpoint interval"); + + /* Initialise the database schema, if necessary. */ + if (create) { + const char * schema = +#include "schema.sql.hh" + ; + if (sqlite3_exec(db, (const char *) schema, 0, 0, 0) != SQLITE_OK) + throwSQLiteError(db, "initialising database schema"); + } + + /* Prepare SQL statements. */ + stmtRegisterValidPath.create(db, + "insert into ValidPaths (path, hash, registrationTime, deriver, narSize) values (?, ?, ?, ?, ?);"); + stmtUpdatePathInfo.create(db, + "update ValidPaths set narSize = ?, hash = ? where path = ?;"); + stmtAddReference.create(db, + "insert or replace into Refs (referrer, reference) values (?, ?);"); + stmtQueryPathInfo.create(db, + "select id, hash, registrationTime, deriver, narSize from ValidPaths where path = ?;"); + stmtQueryReferences.create(db, + "select path from Refs join ValidPaths on reference = id where referrer = ?;"); + stmtQueryReferrers.create(db, + "select path from Refs join ValidPaths on referrer = id where reference = (select id from ValidPaths where path = ?);"); + stmtInvalidatePath.create(db, + "delete from ValidPaths where path = ?;"); + stmtRegisterFailedPath.create(db, + "insert or ignore into FailedPaths (path, time) values (?, ?);"); + stmtHasPathFailed.create(db, + "select time from FailedPaths where path = ?;"); + stmtQueryFailedPaths.create(db, + "select path from FailedPaths;"); + // If the path is a derivation, then clear its outputs. + stmtClearFailedPath.create(db, + "delete from FailedPaths where ?1 = '*' or path = ?1 " + "or path in (select d.path from DerivationOutputs d join ValidPaths v on d.drv = v.id where v.path = ?1);"); + stmtAddDerivationOutput.create(db, + "insert or replace into DerivationOutputs (drv, id, path) values (?, ?, ?);"); + stmtQueryValidDerivers.create(db, + "select v.id, v.path from DerivationOutputs d join ValidPaths v on d.drv = v.id where d.path = ?;"); + stmtQueryDerivationOutputs.create(db, + "select id, path from DerivationOutputs where drv = ?;"); + // Use "path >= ?" with limit 1 rather than "path like '?%'" to + // ensure efficient lookup. + stmtQueryPathFromHashPart.create(db, + "select path from ValidPaths where path >= ? limit 1;"); +} + + +/* To improve purity, users may want to make the Nix store a read-only + bind mount. So make the Nix store writable for this process. */ +void LocalStore::makeStoreWritable() +{ +#if HAVE_UNSHARE && HAVE_STATVFS && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_REMOUNT) + if (getuid() != 0) return; + /* Check if /nix/store is on a read-only mount. */ + struct statvfs stat; + if (statvfs(settings.nixStore.c_str(), &stat) != 0) + throw SysError("getting info about the Nix store mount point"); + + if (stat.f_flag & ST_RDONLY) { + if (unshare(CLONE_NEWNS) == -1) + throw SysError("setting up a private mount namespace"); + + if (mount(0, settings.nixStore.c_str(), 0, MS_REMOUNT | MS_BIND, 0) == -1) + throw SysError(format("remounting %1% writable") % settings.nixStore); + } +#endif +} + + +const time_t mtimeStore = 1; /* 1 second into the epoch */ + + +static void canonicaliseTimestampAndPermissions(const Path & path, const struct stat & st) +{ + if (!S_ISLNK(st.st_mode)) { + + /* Mask out all type related bits. */ + mode_t mode = st.st_mode & ~S_IFMT; + + if (mode != 0444 && mode != 0555) { + mode = (st.st_mode & S_IFMT) + | 0444 + | (st.st_mode & S_IXUSR ? 0111 : 0); + if (chmod(path.c_str(), mode) == -1) + throw SysError(format("changing mode of `%1%' to %2$o") % path % mode); + } + + } + + if (st.st_mtime != mtimeStore) { + struct timeval times[2]; + times[0].tv_sec = st.st_atime; + times[0].tv_usec = 0; + times[1].tv_sec = mtimeStore; + times[1].tv_usec = 0; +#if HAVE_LUTIMES + if (lutimes(path.c_str(), times) == -1) + if (errno != ENOSYS || + (!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1)) +#else + if (!S_ISLNK(st.st_mode) && utimes(path.c_str(), times) == -1) +#endif + throw SysError(format("changing modification time of `%1%'") % path); + } +} + + +void canonicaliseTimestampAndPermissions(const Path & path) +{ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path `%1%'") % path); + canonicaliseTimestampAndPermissions(path, st); +} + + +static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSeen & inodesSeen) +{ + checkInterrupt(); + + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path `%1%'") % path); + + /* Really make sure that the path is of a supported type. This + has already been checked in dumpPath(). */ + assert(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)); + + /* Fail if the file is not owned by the build user. This prevents + us from messing up the ownership/permissions of files + hard-linked into the output (e.g. "ln /etc/shadow $out/foo"). + However, ignore files that we chown'ed ourselves previously to + ensure that we don't fail on hard links within the same build + (i.e. "touch $out/foo; ln $out/foo $out/bar"). */ + if (fromUid != (uid_t) -1 && st.st_uid != fromUid) { + assert(!S_ISDIR(st.st_mode)); + if (inodesSeen.find(Inode(st.st_dev, st.st_ino)) == inodesSeen.end()) + throw BuildError(format("invalid ownership on file `%1%'") % path); + mode_t mode = st.st_mode & ~S_IFMT; + assert(S_ISLNK(st.st_mode) || (st.st_uid == geteuid() && (mode == 0444 || mode == 0555) && st.st_mtime == mtimeStore)); + return; + } + + inodesSeen.insert(Inode(st.st_dev, st.st_ino)); + + canonicaliseTimestampAndPermissions(path, st); + + /* Change ownership to the current uid. If it's a symlink, use + lchown if available, otherwise don't bother. Wrong ownership + of a symlink doesn't matter, since the owning user can't change + the symlink and can't delete it because the directory is not + writable. The only exception is top-level paths in the Nix + store (since that directory is group-writable for the Nix build + users group); we check for this case below. */ + if (st.st_uid != geteuid()) { +#if HAVE_LCHOWN + if (lchown(path.c_str(), geteuid(), (gid_t) -1) == -1) +#else + if (!S_ISLNK(st.st_mode) && + chown(path.c_str(), geteuid(), (gid_t) -1) == -1) +#endif + throw SysError(format("changing owner of `%1%' to %2%") + % path % geteuid()); + } + + if (S_ISDIR(st.st_mode)) { + Strings names = readDirectory(path); + foreach (Strings::iterator, i, names) + canonicalisePathMetaData_(path + "/" + *i, fromUid, inodesSeen); + } +} + + +void canonicalisePathMetaData(const Path & path, uid_t fromUid, InodesSeen & inodesSeen) +{ + canonicalisePathMetaData_(path, fromUid, inodesSeen); + + /* On platforms that don't have lchown(), the top-level path can't + be a symlink, since we can't change its ownership. */ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path `%1%'") % path); + + if (st.st_uid != geteuid()) { + assert(S_ISLNK(st.st_mode)); + throw Error(format("wrong ownership of top-level store path `%1%'") % path); + } +} + + +void canonicalisePathMetaData(const Path & path, uid_t fromUid) +{ + InodesSeen inodesSeen; + canonicalisePathMetaData(path, fromUid, inodesSeen); +} + + +void LocalStore::checkDerivationOutputs(const Path & drvPath, const Derivation & drv) +{ + string drvName = storePathToName(drvPath); + assert(isDerivation(drvName)); + drvName = string(drvName, 0, drvName.size() - drvExtension.size()); + + if (isFixedOutputDrv(drv)) { + DerivationOutputs::const_iterator out = drv.outputs.find("out"); + if (out == drv.outputs.end()) + throw Error(format("derivation `%1%' does not have an output named `out'") % drvPath); + + bool recursive; HashType ht; Hash h; + out->second.parseHashInfo(recursive, ht, h); + Path outPath = makeFixedOutputPath(recursive, ht, h, drvName); + + StringPairs::const_iterator j = drv.env.find("out"); + if (out->second.path != outPath || j == drv.env.end() || j->second != outPath) + throw Error(format("derivation `%1%' has incorrect output `%2%', should be `%3%'") + % drvPath % out->second.path % outPath); + } + + else { + Derivation drvCopy(drv); + foreach (DerivationOutputs::iterator, i, drvCopy.outputs) { + i->second.path = ""; + drvCopy.env[i->first] = ""; + } + + Hash h = hashDerivationModulo(*this, drvCopy); + + foreach (DerivationOutputs::const_iterator, i, drv.outputs) { + Path outPath = makeOutputPath(i->first, h, drvName); + StringPairs::const_iterator j = drv.env.find(i->first); + if (i->second.path != outPath || j == drv.env.end() || j->second != outPath) + throw Error(format("derivation `%1%' has incorrect output `%2%', should be `%3%'") + % drvPath % i->second.path % outPath); + } + } +} + + +unsigned long long LocalStore::addValidPath(const ValidPathInfo & info, bool checkOutputs) +{ + SQLiteStmtUse use(stmtRegisterValidPath); + stmtRegisterValidPath.bind(info.path); + stmtRegisterValidPath.bind("sha256:" + printHash(info.hash)); + stmtRegisterValidPath.bind(info.registrationTime == 0 ? time(0) : info.registrationTime); + if (info.deriver != "") + stmtRegisterValidPath.bind(info.deriver); + else + stmtRegisterValidPath.bind(); // null + if (info.narSize != 0) + stmtRegisterValidPath.bind64(info.narSize); + else + stmtRegisterValidPath.bind(); // null + if (sqlite3_step(stmtRegisterValidPath) != SQLITE_DONE) + throwSQLiteError(db, format("registering valid path `%1%' in database") % info.path); + unsigned long long id = sqlite3_last_insert_rowid(db); + + /* If this is a derivation, then store the derivation outputs in + the database. This is useful for the garbage collector: it can + efficiently query whether a path is an output of some + derivation. */ + if (isDerivation(info.path)) { + Derivation drv = parseDerivation(readFile(info.path)); + + /* Verify that the output paths in the derivation are correct + (i.e., follow the scheme for computing output paths from + derivations). Note that if this throws an error, then the + DB transaction is rolled back, so the path validity + registration above is undone. */ + if (checkOutputs) checkDerivationOutputs(info.path, drv); + + foreach (DerivationOutputs::iterator, i, drv.outputs) { + SQLiteStmtUse use(stmtAddDerivationOutput); + stmtAddDerivationOutput.bind(id); + stmtAddDerivationOutput.bind(i->first); + stmtAddDerivationOutput.bind(i->second.path); + if (sqlite3_step(stmtAddDerivationOutput) != SQLITE_DONE) + throwSQLiteError(db, format("adding derivation output for `%1%' in database") % info.path); + } + } + + return id; +} + + +void LocalStore::addReference(unsigned long long referrer, unsigned long long reference) +{ + SQLiteStmtUse use(stmtAddReference); + stmtAddReference.bind(referrer); + stmtAddReference.bind(reference); + if (sqlite3_step(stmtAddReference) != SQLITE_DONE) + throwSQLiteError(db, "adding reference to database"); +} + + +void LocalStore::registerFailedPath(const Path & path) +{ + retry_sqlite { + SQLiteStmtUse use(stmtRegisterFailedPath); + stmtRegisterFailedPath.bind(path); + stmtRegisterFailedPath.bind(time(0)); + if (sqlite3_step(stmtRegisterFailedPath) != SQLITE_DONE) + throwSQLiteError(db, format("registering failed path `%1%'") % path); + } end_retry_sqlite; +} + + +bool LocalStore::hasPathFailed(const Path & path) +{ + retry_sqlite { + SQLiteStmtUse use(stmtHasPathFailed); + stmtHasPathFailed.bind(path); + int res = sqlite3_step(stmtHasPathFailed); + if (res != SQLITE_DONE && res != SQLITE_ROW) + throwSQLiteError(db, "querying whether path failed"); + return res == SQLITE_ROW; + } end_retry_sqlite; +} + + +PathSet LocalStore::queryFailedPaths() +{ + retry_sqlite { + SQLiteStmtUse use(stmtQueryFailedPaths); + + PathSet res; + int r; + while ((r = sqlite3_step(stmtQueryFailedPaths)) == SQLITE_ROW) { + const char * s = (const char *) sqlite3_column_text(stmtQueryFailedPaths, 0); + assert(s); + res.insert(s); + } + + if (r != SQLITE_DONE) + throwSQLiteError(db, "error querying failed paths"); + + return res; + } end_retry_sqlite; +} + + +void LocalStore::clearFailedPaths(const PathSet & paths) +{ + retry_sqlite { + SQLiteTxn txn(db); + + foreach (PathSet::const_iterator, i, paths) { + SQLiteStmtUse use(stmtClearFailedPath); + stmtClearFailedPath.bind(*i); + if (sqlite3_step(stmtClearFailedPath) != SQLITE_DONE) + throwSQLiteError(db, format("clearing failed path `%1%' in database") % *i); + } + + txn.commit(); + } end_retry_sqlite; +} + + +Hash parseHashField(const Path & path, const string & s) +{ + string::size_type colon = s.find(':'); + if (colon == string::npos) + throw Error(format("corrupt hash `%1%' in valid-path entry for `%2%'") + % s % path); + HashType ht = parseHashType(string(s, 0, colon)); + if (ht == htUnknown) + throw Error(format("unknown hash type `%1%' in valid-path entry for `%2%'") + % string(s, 0, colon) % path); + return parseHash(ht, string(s, colon + 1)); +} + + +ValidPathInfo LocalStore::queryPathInfo(const Path & path) +{ + ValidPathInfo info; + info.path = path; + + assertStorePath(path); + + retry_sqlite { + + /* Get the path info. */ + SQLiteStmtUse use1(stmtQueryPathInfo); + + stmtQueryPathInfo.bind(path); + + int r = sqlite3_step(stmtQueryPathInfo); + if (r == SQLITE_DONE) throw Error(format("path `%1%' is not valid") % path); + if (r != SQLITE_ROW) throwSQLiteError(db, "querying path in database"); + + info.id = sqlite3_column_int(stmtQueryPathInfo, 0); + + const char * s = (const char *) sqlite3_column_text(stmtQueryPathInfo, 1); + assert(s); + info.hash = parseHashField(path, s); + + info.registrationTime = sqlite3_column_int(stmtQueryPathInfo, 2); + + s = (const char *) sqlite3_column_text(stmtQueryPathInfo, 3); + if (s) info.deriver = s; + + /* Note that narSize = NULL yields 0. */ + info.narSize = sqlite3_column_int64(stmtQueryPathInfo, 4); + + /* Get the references. */ + SQLiteStmtUse use2(stmtQueryReferences); + + stmtQueryReferences.bind(info.id); + + while ((r = sqlite3_step(stmtQueryReferences)) == SQLITE_ROW) { + s = (const char *) sqlite3_column_text(stmtQueryReferences, 0); + assert(s); + info.references.insert(s); + } + + if (r != SQLITE_DONE) + throwSQLiteError(db, format("error getting references of `%1%'") % path); + + return info; + } end_retry_sqlite; +} + + +/* Update path info in the database. Currently only updates the + narSize field. */ +void LocalStore::updatePathInfo(const ValidPathInfo & info) +{ + SQLiteStmtUse use(stmtUpdatePathInfo); + if (info.narSize != 0) + stmtUpdatePathInfo.bind64(info.narSize); + else + stmtUpdatePathInfo.bind(); // null + stmtUpdatePathInfo.bind("sha256:" + printHash(info.hash)); + stmtUpdatePathInfo.bind(info.path); + if (sqlite3_step(stmtUpdatePathInfo) != SQLITE_DONE) + throwSQLiteError(db, format("updating info of path `%1%' in database") % info.path); +} + + +unsigned long long LocalStore::queryValidPathId(const Path & path) +{ + SQLiteStmtUse use(stmtQueryPathInfo); + stmtQueryPathInfo.bind(path); + int res = sqlite3_step(stmtQueryPathInfo); + if (res == SQLITE_ROW) return sqlite3_column_int(stmtQueryPathInfo, 0); + if (res == SQLITE_DONE) throw Error(format("path `%1%' is not valid") % path); + throwSQLiteError(db, "querying path in database"); +} + + +bool LocalStore::isValidPath_(const Path & path) +{ + SQLiteStmtUse use(stmtQueryPathInfo); + stmtQueryPathInfo.bind(path); + int res = sqlite3_step(stmtQueryPathInfo); + if (res != SQLITE_DONE && res != SQLITE_ROW) + throwSQLiteError(db, "querying path in database"); + return res == SQLITE_ROW; +} + + +bool LocalStore::isValidPath(const Path & path) +{ + retry_sqlite { + return isValidPath_(path); + } end_retry_sqlite; +} + + +PathSet LocalStore::queryValidPaths(const PathSet & paths) +{ + retry_sqlite { + PathSet res; + foreach (PathSet::const_iterator, i, paths) + if (isValidPath_(*i)) res.insert(*i); + return res; + } end_retry_sqlite; +} + + +PathSet LocalStore::queryAllValidPaths() +{ + retry_sqlite { + SQLiteStmt stmt; + stmt.create(db, "select path from ValidPaths"); + + PathSet res; + int r; + while ((r = sqlite3_step(stmt)) == SQLITE_ROW) { + const char * s = (const char *) sqlite3_column_text(stmt, 0); + assert(s); + res.insert(s); + } + + if (r != SQLITE_DONE) + throwSQLiteError(db, "error getting valid paths"); + + return res; + } end_retry_sqlite; +} + + +void LocalStore::queryReferences(const Path & path, + PathSet & references) +{ + ValidPathInfo info = queryPathInfo(path); + references.insert(info.references.begin(), info.references.end()); +} + + +void LocalStore::queryReferrers_(const Path & path, PathSet & referrers) +{ + SQLiteStmtUse use(stmtQueryReferrers); + + stmtQueryReferrers.bind(path); + + int r; + while ((r = sqlite3_step(stmtQueryReferrers)) == SQLITE_ROW) { + const char * s = (const char *) sqlite3_column_text(stmtQueryReferrers, 0); + assert(s); + referrers.insert(s); + } + + if (r != SQLITE_DONE) + throwSQLiteError(db, format("error getting references of `%1%'") % path); +} + + +void LocalStore::queryReferrers(const Path & path, PathSet & referrers) +{ + assertStorePath(path); + retry_sqlite { + queryReferrers_(path, referrers); + } end_retry_sqlite; +} + + +Path LocalStore::queryDeriver(const Path & path) +{ + return queryPathInfo(path).deriver; +} + + +PathSet LocalStore::queryValidDerivers(const Path & path) +{ + assertStorePath(path); + + retry_sqlite { + SQLiteStmtUse use(stmtQueryValidDerivers); + stmtQueryValidDerivers.bind(path); + + PathSet derivers; + int r; + while ((r = sqlite3_step(stmtQueryValidDerivers)) == SQLITE_ROW) { + const char * s = (const char *) sqlite3_column_text(stmtQueryValidDerivers, 1); + assert(s); + derivers.insert(s); + } + + if (r != SQLITE_DONE) + throwSQLiteError(db, format("error getting valid derivers of `%1%'") % path); + + return derivers; + } end_retry_sqlite; +} + + +PathSet LocalStore::queryDerivationOutputs(const Path & path) +{ + retry_sqlite { + SQLiteStmtUse use(stmtQueryDerivationOutputs); + stmtQueryDerivationOutputs.bind(queryValidPathId(path)); + + PathSet outputs; + int r; + while ((r = sqlite3_step(stmtQueryDerivationOutputs)) == SQLITE_ROW) { + const char * s = (const char *) sqlite3_column_text(stmtQueryDerivationOutputs, 1); + assert(s); + outputs.insert(s); + } + + if (r != SQLITE_DONE) + throwSQLiteError(db, format("error getting outputs of `%1%'") % path); + + return outputs; + } end_retry_sqlite; +} + + +StringSet LocalStore::queryDerivationOutputNames(const Path & path) +{ + retry_sqlite { + SQLiteStmtUse use(stmtQueryDerivationOutputs); + stmtQueryDerivationOutputs.bind(queryValidPathId(path)); + + StringSet outputNames; + int r; + while ((r = sqlite3_step(stmtQueryDerivationOutputs)) == SQLITE_ROW) { + const char * s = (const char *) sqlite3_column_text(stmtQueryDerivationOutputs, 0); + assert(s); + outputNames.insert(s); + } + + if (r != SQLITE_DONE) + throwSQLiteError(db, format("error getting output names of `%1%'") % path); + + return outputNames; + } end_retry_sqlite; +} + + +Path LocalStore::queryPathFromHashPart(const string & hashPart) +{ + if (hashPart.size() != 32) throw Error("invalid hash part"); + + Path prefix = settings.nixStore + "/" + hashPart; + + retry_sqlite { + SQLiteStmtUse use(stmtQueryPathFromHashPart); + stmtQueryPathFromHashPart.bind(prefix); + + int res = sqlite3_step(stmtQueryPathFromHashPart); + if (res == SQLITE_DONE) return ""; + if (res != SQLITE_ROW) throwSQLiteError(db, "finding path in database"); + + const char * s = (const char *) sqlite3_column_text(stmtQueryPathFromHashPart, 0); + return s && prefix.compare(0, prefix.size(), s, prefix.size()) == 0 ? s : ""; + } end_retry_sqlite; +} + + +void LocalStore::setSubstituterEnv() +{ + if (didSetSubstituterEnv) return; + + /* Pass configuration options (including those overridden with + --option) to substituters. */ + setenv("_NIX_OPTIONS", settings.pack().c_str(), 1); + + didSetSubstituterEnv = true; +} + + +void LocalStore::startSubstituter(const Path & substituter, RunningSubstituter & run) +{ + if (run.disabled || run.pid != -1) return; + + debug(format("starting substituter program `%1%'") % substituter); + + Pipe toPipe, fromPipe, errorPipe; + + toPipe.create(); + fromPipe.create(); + errorPipe.create(); + + setSubstituterEnv(); + + run.pid = maybeVfork(); + + switch (run.pid) { + + case -1: + throw SysError("unable to fork"); + + case 0: /* child */ + try { + restoreAffinity(); + if (dup2(toPipe.readSide, STDIN_FILENO) == -1) + throw SysError("dupping stdin"); + if (dup2(fromPipe.writeSide, STDOUT_FILENO) == -1) + throw SysError("dupping stdout"); + if (dup2(errorPipe.writeSide, STDERR_FILENO) == -1) + throw SysError("dupping stderr"); + execl(substituter.c_str(), substituter.c_str(), "--query", NULL); + throw SysError(format("executing `%1%'") % substituter); + } catch (std::exception & e) { + std::cerr << "error: " << e.what() << std::endl; + } + _exit(1); + } + + /* Parent. */ + + run.program = baseNameOf(substituter); + run.to = toPipe.writeSide.borrow(); + run.from = run.fromBuf.fd = fromPipe.readSide.borrow(); + run.error = errorPipe.readSide.borrow(); + + toPipe.readSide.close(); + fromPipe.writeSide.close(); + errorPipe.writeSide.close(); + + /* The substituter may exit right away if it's disabled in any way + (e.g. copy-from-other-stores.pl will exit if no other stores + are configured). */ + try { + getLineFromSubstituter(run); + } catch (EndOfFile & e) { + run.to.close(); + run.from.close(); + run.error.close(); + run.disabled = true; + if (run.pid.wait(true) != 0) throw; + } +} + + +/* Read a line from the substituter's stdout, while also processing + its stderr. */ +string LocalStore::getLineFromSubstituter(RunningSubstituter & run) +{ + string res, err; + + /* We might have stdout data left over from the last time. */ + if (run.fromBuf.hasData()) goto haveData; + + while (1) { + checkInterrupt(); + + fd_set fds; + FD_ZERO(&fds); + FD_SET(run.from, &fds); + FD_SET(run.error, &fds); + + /* Wait for data to appear on the substituter's stdout or + stderr. */ + if (select(run.from > run.error ? run.from + 1 : run.error + 1, &fds, 0, 0, 0) == -1) { + if (errno == EINTR) continue; + throw SysError("waiting for input from the substituter"); + } + + /* Completely drain stderr before dealing with stdout. */ + if (FD_ISSET(run.error, &fds)) { + char buf[4096]; + ssize_t n = read(run.error, (unsigned char *) buf, sizeof(buf)); + if (n == -1) { + if (errno == EINTR) continue; + throw SysError("reading from substituter's stderr"); + } + if (n == 0) throw EndOfFile(format("substituter `%1%' died unexpectedly") % run.program); + err.append(buf, n); + string::size_type p; + while ((p = err.find('\n')) != string::npos) { + printMsg(lvlError, run.program + ": " + string(err, 0, p)); + err = string(err, p + 1); + } + } + + /* Read from stdout until we get a newline or the buffer is empty. */ + else if (run.fromBuf.hasData() || FD_ISSET(run.from, &fds)) { + haveData: + do { + unsigned char c; + run.fromBuf(&c, 1); + if (c == '\n') { + if (!err.empty()) printMsg(lvlError, run.program + ": " + err); + return res; + } + res += c; + } while (run.fromBuf.hasData()); + } + } +} + + +template T LocalStore::getIntLineFromSubstituter(RunningSubstituter & run) +{ + string s = getLineFromSubstituter(run); + T res; + if (!string2Int(s, res)) throw Error("integer expected from stream"); + return res; +} + + +PathSet LocalStore::querySubstitutablePaths(const PathSet & paths) +{ + PathSet res; + foreach (Paths::iterator, i, settings.substituters) { + if (res.size() == paths.size()) break; + RunningSubstituter & run(runningSubstituters[*i]); + startSubstituter(*i, run); + if (run.disabled) continue; + string s = "have "; + foreach (PathSet::const_iterator, j, paths) + if (res.find(*j) == res.end()) { s += *j; s += " "; } + writeLine(run.to, s); + while (true) { + /* FIXME: we only read stderr when an error occurs, so + substituters should only write (short) messages to + stderr when they fail. I.e. they shouldn't write debug + output. */ + Path path = getLineFromSubstituter(run); + if (path == "") break; + res.insert(path); + } + } + return res; +} + + +void LocalStore::querySubstitutablePathInfos(const Path & substituter, + PathSet & paths, SubstitutablePathInfos & infos) +{ + RunningSubstituter & run(runningSubstituters[substituter]); + startSubstituter(substituter, run); + if (run.disabled) return; + + string s = "info "; + foreach (PathSet::const_iterator, i, paths) + if (infos.find(*i) == infos.end()) { s += *i; s += " "; } + writeLine(run.to, s); + + while (true) { + Path path = getLineFromSubstituter(run); + if (path == "") break; + if (paths.find(path) == paths.end()) + throw Error(format("got unexpected path `%1%' from substituter") % path); + paths.erase(path); + SubstitutablePathInfo & info(infos[path]); + info.deriver = getLineFromSubstituter(run); + if (info.deriver != "") assertStorePath(info.deriver); + int nrRefs = getIntLineFromSubstituter(run); + while (nrRefs--) { + Path p = getLineFromSubstituter(run); + assertStorePath(p); + info.references.insert(p); + } + info.downloadSize = getIntLineFromSubstituter(run); + info.narSize = getIntLineFromSubstituter(run); + } +} + + +void LocalStore::querySubstitutablePathInfos(const PathSet & paths, + SubstitutablePathInfos & infos) +{ + PathSet todo = paths; + foreach (Paths::iterator, i, settings.substituters) { + if (todo.empty()) break; + querySubstitutablePathInfos(*i, todo, infos); + } +} + + +Hash LocalStore::queryPathHash(const Path & path) +{ + return queryPathInfo(path).hash; +} + + +void LocalStore::registerValidPath(const ValidPathInfo & info) +{ + ValidPathInfos infos; + infos.push_back(info); + registerValidPaths(infos); +} + + +void LocalStore::registerValidPaths(const ValidPathInfos & infos) +{ + /* SQLite will fsync by default, but the new valid paths may not be fsync-ed. + * So some may want to fsync them before registering the validity, at the + * expense of some speed of the path registering operation. */ + if (settings.syncBeforeRegistering) sync(); + + retry_sqlite { + SQLiteTxn txn(db); + PathSet paths; + + foreach (ValidPathInfos::const_iterator, i, infos) { + assert(i->hash.type == htSHA256); + if (isValidPath_(i->path)) + updatePathInfo(*i); + else + addValidPath(*i, false); + paths.insert(i->path); + } + + foreach (ValidPathInfos::const_iterator, i, infos) { + unsigned long long referrer = queryValidPathId(i->path); + foreach (PathSet::iterator, j, i->references) + addReference(referrer, queryValidPathId(*j)); + } + + /* Check that the derivation outputs are correct. We can't do + this in addValidPath() above, because the references might + not be valid yet. */ + foreach (ValidPathInfos::const_iterator, i, infos) + if (isDerivation(i->path)) { + // FIXME: inefficient; we already loaded the + // derivation in addValidPath(). + Derivation drv = parseDerivation(readFile(i->path)); + checkDerivationOutputs(i->path, drv); + } + + /* Do a topological sort of the paths. This will throw an + error if a cycle is detected and roll back the + transaction. Cycles can only occur when a derivation + has multiple outputs. */ + topoSortPaths(*this, paths); + + txn.commit(); + } end_retry_sqlite; +} + + +/* Invalidate a path. The caller is responsible for checking that + there are no referrers. */ +void LocalStore::invalidatePath(const Path & path) +{ + debug(format("invalidating path `%1%'") % path); + + drvHashes.erase(path); + + SQLiteStmtUse use(stmtInvalidatePath); + + stmtInvalidatePath.bind(path); + + if (sqlite3_step(stmtInvalidatePath) != SQLITE_DONE) + throwSQLiteError(db, format("invalidating path `%1%' in database") % path); + + /* Note that the foreign key constraints on the Refs table take + care of deleting the references entries for `path'. */ +} + + +Path LocalStore::addToStoreFromDump(const string & dump, const string & name, + bool recursive, HashType hashAlgo, bool repair) +{ + Hash h = hashString(hashAlgo, dump); + + Path dstPath = makeFixedOutputPath(recursive, hashAlgo, h, name); + + addTempRoot(dstPath); + + if (repair || !isValidPath(dstPath)) { + + /* The first check above is an optimisation to prevent + unnecessary lock acquisition. */ + + PathLocks outputLock(singleton(dstPath)); + + if (repair || !isValidPath(dstPath)) { + + if (pathExists(dstPath)) deletePath(dstPath); + + if (recursive) { + StringSource source(dump); + restorePath(dstPath, source); + } else + writeFile(dstPath, dump); + + canonicalisePathMetaData(dstPath, -1); + + /* Register the SHA-256 hash of the NAR serialisation of + the path in the database. We may just have computed it + above (if called with recursive == true and hashAlgo == + sha256); otherwise, compute it here. */ + HashResult hash; + if (recursive) { + hash.first = hashAlgo == htSHA256 ? h : hashString(htSHA256, dump); + hash.second = dump.size(); + } else + hash = hashPath(htSHA256, dstPath); + + optimisePath(dstPath); // FIXME: combine with hashPath() + + ValidPathInfo info; + info.path = dstPath; + info.hash = hash.first; + info.narSize = hash.second; + registerValidPath(info); + } + + outputLock.setDeletion(true); + } + + return dstPath; +} + + +Path LocalStore::addToStore(const Path & _srcPath, + bool recursive, HashType hashAlgo, PathFilter & filter, bool repair) +{ + Path srcPath(absPath(_srcPath)); + debug(format("adding `%1%' to the store") % srcPath); + + /* Read the whole path into memory. This is not a very scalable + method for very large paths, but `copyPath' is mainly used for + small files. */ + StringSink sink; + if (recursive) + dumpPath(srcPath, sink, filter); + else + sink.s = readFile(srcPath); + + return addToStoreFromDump(sink.s, baseNameOf(srcPath), recursive, hashAlgo, repair); +} + + +Path LocalStore::addTextToStore(const string & name, const string & s, + const PathSet & references, bool repair) +{ + Path dstPath = computeStorePathForText(name, s, references); + + addTempRoot(dstPath); + + if (repair || !isValidPath(dstPath)) { + + PathLocks outputLock(singleton(dstPath)); + + if (repair || !isValidPath(dstPath)) { + + if (pathExists(dstPath)) deletePath(dstPath); + + writeFile(dstPath, s); + + canonicalisePathMetaData(dstPath, -1); + + HashResult hash = hashPath(htSHA256, dstPath); + + optimisePath(dstPath); + + ValidPathInfo info; + info.path = dstPath; + info.hash = hash.first; + info.narSize = hash.second; + info.references = references; + registerValidPath(info); + } + + outputLock.setDeletion(true); + } + + return dstPath; +} + + +struct HashAndWriteSink : Sink +{ + Sink & writeSink; + HashSink hashSink; + HashAndWriteSink(Sink & writeSink) : writeSink(writeSink), hashSink(htSHA256) + { + } + virtual void operator () (const unsigned char * data, size_t len) + { + writeSink(data, len); + hashSink(data, len); + } + Hash currentHash() + { + return hashSink.currentHash().first; + } +}; + + +#define EXPORT_MAGIC 0x4558494e + + +static void checkSecrecy(const Path & path) +{ + struct stat st; + if (stat(path.c_str(), &st)) + throw SysError(format("getting status of `%1%'") % path); + if ((st.st_mode & (S_IRWXG | S_IRWXO)) != 0) + throw Error(format("file `%1%' should be secret (inaccessible to everybody else)!") % path); +} + + +void LocalStore::exportPath(const Path & path, bool sign, + Sink & sink) +{ + assertStorePath(path); + + addTempRoot(path); + if (!isValidPath(path)) + throw Error(format("path `%1%' is not valid") % path); + + HashAndWriteSink hashAndWriteSink(sink); + + dumpPath(path, hashAndWriteSink); + + /* Refuse to export paths that have changed. This prevents + filesystem corruption from spreading to other machines. + Don't complain if the stored hash is zero (unknown). */ + Hash hash = hashAndWriteSink.currentHash(); + Hash storedHash = queryPathHash(path); + if (hash != storedHash && storedHash != Hash(storedHash.type)) + throw Error(format("hash of path `%1%' has changed from `%2%' to `%3%'!") % path + % printHash(storedHash) % printHash(hash)); + + writeInt(EXPORT_MAGIC, hashAndWriteSink); + + writeString(path, hashAndWriteSink); + + PathSet references; + queryReferences(path, references); + writeStrings(references, hashAndWriteSink); + + Path deriver = queryDeriver(path); + writeString(deriver, hashAndWriteSink); + + if (sign) { + Hash hash = hashAndWriteSink.currentHash(); + + writeInt(1, hashAndWriteSink); + + Path tmpDir = createTempDir(); + AutoDelete delTmp(tmpDir); + Path hashFile = tmpDir + "/hash"; + writeFile(hashFile, printHash(hash)); + + Path secretKey = settings.nixConfDir + "/signing-key.sec"; + checkSecrecy(secretKey); + + Strings args; + args.push_back("rsautl"); + args.push_back("-sign"); + args.push_back("-inkey"); + args.push_back(secretKey); + args.push_back("-in"); + args.push_back(hashFile); + string signature = runProgram(OPENSSL_PATH, true, args); + + writeString(signature, hashAndWriteSink); + + } else + writeInt(0, hashAndWriteSink); +} + + +struct HashAndReadSource : Source +{ + Source & readSource; + HashSink hashSink; + bool hashing; + HashAndReadSource(Source & readSource) : readSource(readSource), hashSink(htSHA256) + { + hashing = true; + } + size_t read(unsigned char * data, size_t len) + { + size_t n = readSource.read(data, len); + if (hashing) hashSink(data, n); + return n; + } +}; + + +/* Create a temporary directory in the store that won't be + garbage-collected. */ +Path LocalStore::createTempDirInStore() +{ + Path tmpDir; + do { + /* There is a slight possibility that `tmpDir' gets deleted by + the GC between createTempDir() and addTempRoot(), so repeat + until `tmpDir' exists. */ + tmpDir = createTempDir(settings.nixStore); + addTempRoot(tmpDir); + } while (!pathExists(tmpDir)); + return tmpDir; +} + + +Path LocalStore::importPath(bool requireSignature, Source & source) +{ + HashAndReadSource hashAndReadSource(source); + + /* We don't yet know what store path this archive contains (the + store path follows the archive data proper), and besides, we + don't know yet whether the signature is valid. */ + Path tmpDir = createTempDirInStore(); + AutoDelete delTmp(tmpDir); + Path unpacked = tmpDir + "/unpacked"; + + restorePath(unpacked, hashAndReadSource); + + unsigned int magic = readInt(hashAndReadSource); + if (magic != EXPORT_MAGIC) + throw Error("Nix archive cannot be imported; wrong format"); + + Path dstPath = readStorePath(hashAndReadSource); + + printMsg(lvlInfo, format("importing path `%1%'") % dstPath); + + PathSet references = readStorePaths(hashAndReadSource); + + Path deriver = readString(hashAndReadSource); + if (deriver != "") assertStorePath(deriver); + + Hash hash = hashAndReadSource.hashSink.finish().first; + hashAndReadSource.hashing = false; + + bool haveSignature = readInt(hashAndReadSource) == 1; + + if (requireSignature && !haveSignature) + throw Error(format("imported archive of `%1%' lacks a signature") % dstPath); + + if (haveSignature) { + string signature = readString(hashAndReadSource); + + if (requireSignature) { + Path sigFile = tmpDir + "/sig"; + writeFile(sigFile, signature); + + Strings args; + args.push_back("rsautl"); + args.push_back("-verify"); + args.push_back("-inkey"); + args.push_back(settings.nixConfDir + "/signing-key.pub"); + args.push_back("-pubin"); + args.push_back("-in"); + args.push_back(sigFile); + string hash2 = runProgram(OPENSSL_PATH, true, args); + + /* Note: runProgram() throws an exception if the signature + is invalid. */ + + if (printHash(hash) != hash2) + throw Error( + "signed hash doesn't match actual contents of imported " + "archive; archive could be corrupt, or someone is trying " + "to import a Trojan horse"); + } + } + + /* Do the actual import. */ + + /* !!! way too much code duplication with addTextToStore() etc. */ + addTempRoot(dstPath); + + if (!isValidPath(dstPath)) { + + PathLocks outputLock; + + /* Lock the output path. But don't lock if we're being called + from a build hook (whose parent process already acquired a + lock on this path). */ + Strings locksHeld = tokenizeString(getEnv("NIX_HELD_LOCKS")); + if (find(locksHeld.begin(), locksHeld.end(), dstPath) == locksHeld.end()) + outputLock.lockPaths(singleton(dstPath)); + + if (!isValidPath(dstPath)) { + + if (pathExists(dstPath)) deletePath(dstPath); + + if (rename(unpacked.c_str(), dstPath.c_str()) == -1) + throw SysError(format("cannot move `%1%' to `%2%'") + % unpacked % dstPath); + + canonicalisePathMetaData(dstPath, -1); + + /* !!! if we were clever, we could prevent the hashPath() + here. */ + HashResult hash = hashPath(htSHA256, dstPath); + + optimisePath(dstPath); // FIXME: combine with hashPath() + + ValidPathInfo info; + info.path = dstPath; + info.hash = hash.first; + info.narSize = hash.second; + info.references = references; + info.deriver = deriver != "" && isValidPath(deriver) ? deriver : ""; + registerValidPath(info); + } + + outputLock.setDeletion(true); + } + + return dstPath; +} + + +Paths LocalStore::importPaths(bool requireSignature, Source & source) +{ + Paths res; + while (true) { + unsigned long long n = readLongLong(source); + if (n == 0) break; + if (n != 1) throw Error("input doesn't look like something created by `nix-store --export'"); + res.push_back(importPath(requireSignature, source)); + } + return res; +} + + +void LocalStore::invalidatePathChecked(const Path & path) +{ + assertStorePath(path); + + retry_sqlite { + SQLiteTxn txn(db); + + if (isValidPath_(path)) { + PathSet referrers; queryReferrers_(path, referrers); + referrers.erase(path); /* ignore self-references */ + if (!referrers.empty()) + throw PathInUse(format("cannot delete path `%1%' because it is in use by %2%") + % path % showPaths(referrers)); + invalidatePath(path); + } + + txn.commit(); + } end_retry_sqlite; +} + + +bool LocalStore::verifyStore(bool checkContents, bool repair) +{ + printMsg(lvlError, format("reading the Nix store...")); + + bool errors = false; + + /* Acquire the global GC lock to prevent a garbage collection. */ + AutoCloseFD fdGCLock = openGCLock(ltWrite); + + Paths entries = readDirectory(settings.nixStore); + PathSet store(entries.begin(), entries.end()); + + /* Check whether all valid paths actually exist. */ + printMsg(lvlInfo, "checking path existence..."); + + PathSet validPaths2 = queryAllValidPaths(), validPaths, done; + + foreach (PathSet::iterator, i, validPaths2) + verifyPath(*i, store, done, validPaths, repair, errors); + + /* Release the GC lock so that checking content hashes (which can + take ages) doesn't block the GC or builds. */ + fdGCLock.close(); + + /* Optionally, check the content hashes (slow). */ + if (checkContents) { + printMsg(lvlInfo, "checking hashes..."); + + Hash nullHash(htSHA256); + + foreach (PathSet::iterator, i, validPaths) { + try { + ValidPathInfo info = queryPathInfo(*i); + + /* Check the content hash (optionally - slow). */ + printMsg(lvlTalkative, format("checking contents of `%1%'") % *i); + HashResult current = hashPath(info.hash.type, *i); + + if (info.hash != nullHash && info.hash != current.first) { + printMsg(lvlError, format("path `%1%' was modified! " + "expected hash `%2%', got `%3%'") + % *i % printHash(info.hash) % printHash(current.first)); + if (repair) repairPath(*i); else errors = true; + } else { + + bool update = false; + + /* Fill in missing hashes. */ + if (info.hash == nullHash) { + printMsg(lvlError, format("fixing missing hash on `%1%'") % *i); + info.hash = current.first; + update = true; + } + + /* Fill in missing narSize fields (from old stores). */ + if (info.narSize == 0) { + printMsg(lvlError, format("updating size field on `%1%' to %2%") % *i % current.second); + info.narSize = current.second; + update = true; + } + + if (update) updatePathInfo(info); + + } + + } catch (Error & e) { + /* It's possible that the path got GC'ed, so ignore + errors on invalid paths. */ + if (isValidPath(*i)) + printMsg(lvlError, format("error: %1%") % e.msg()); + else + printMsg(lvlError, format("warning: %1%") % e.msg()); + errors = true; + } + } + } + + return errors; +} + + +void LocalStore::verifyPath(const Path & path, const PathSet & store, + PathSet & done, PathSet & validPaths, bool repair, bool & errors) +{ + checkInterrupt(); + + if (done.find(path) != done.end()) return; + done.insert(path); + + if (!isStorePath(path)) { + printMsg(lvlError, format("path `%1%' is not in the Nix store") % path); + invalidatePath(path); + return; + } + + if (store.find(baseNameOf(path)) == store.end()) { + /* Check any referrers first. If we can invalidate them + first, then we can invalidate this path as well. */ + bool canInvalidate = true; + PathSet referrers; queryReferrers(path, referrers); + foreach (PathSet::iterator, i, referrers) + if (*i != path) { + verifyPath(*i, store, done, validPaths, repair, errors); + if (validPaths.find(*i) != validPaths.end()) + canInvalidate = false; + } + + if (canInvalidate) { + printMsg(lvlError, format("path `%1%' disappeared, removing from database...") % path); + invalidatePath(path); + } else { + printMsg(lvlError, format("path `%1%' disappeared, but it still has valid referrers!") % path); + if (repair) + try { + repairPath(path); + } catch (Error & e) { + printMsg(lvlError, format("warning: %1%") % e.msg()); + errors = true; + } + else errors = true; + } + + return; + } + + validPaths.insert(path); +} + + +bool LocalStore::pathContentsGood(const Path & path) +{ + std::map::iterator i = pathContentsGoodCache.find(path); + if (i != pathContentsGoodCache.end()) return i->second; + printMsg(lvlInfo, format("checking path `%1%'...") % path); + ValidPathInfo info = queryPathInfo(path); + bool res; + if (!pathExists(path)) + res = false; + else { + HashResult current = hashPath(info.hash.type, path); + Hash nullHash(htSHA256); + res = info.hash == nullHash || info.hash == current.first; + } + pathContentsGoodCache[path] = res; + if (!res) printMsg(lvlError, format("path `%1%' is corrupted or missing!") % path); + return res; +} + + +void LocalStore::markContentsGood(const Path & path) +{ + pathContentsGoodCache[path] = true; +} + + +/* Functions for upgrading from the pre-SQLite database. */ + +PathSet LocalStore::queryValidPathsOld() +{ + PathSet paths; + Strings entries = readDirectory(settings.nixDBPath + "/info"); + foreach (Strings::iterator, i, entries) + if (i->at(0) != '.') paths.insert(settings.nixStore + "/" + *i); + return paths; +} + + +ValidPathInfo LocalStore::queryPathInfoOld(const Path & path) +{ + ValidPathInfo res; + res.path = path; + + /* Read the info file. */ + string baseName = baseNameOf(path); + Path infoFile = (format("%1%/info/%2%") % settings.nixDBPath % baseName).str(); + if (!pathExists(infoFile)) + throw Error(format("path `%1%' is not valid") % path); + string info = readFile(infoFile); + + /* Parse it. */ + Strings lines = tokenizeString(info, "\n"); + + foreach (Strings::iterator, i, lines) { + string::size_type p = i->find(':'); + if (p == string::npos) + throw Error(format("corrupt line in `%1%': %2%") % infoFile % *i); + string name(*i, 0, p); + string value(*i, p + 2); + if (name == "References") { + Strings refs = tokenizeString(value, " "); + res.references = PathSet(refs.begin(), refs.end()); + } else if (name == "Deriver") { + res.deriver = value; + } else if (name == "Hash") { + res.hash = parseHashField(path, value); + } else if (name == "Registered-At") { + int n = 0; + string2Int(value, n); + res.registrationTime = n; + } + } + + return res; +} + + +/* Upgrade from schema 5 (Nix 0.12) to schema 6 (Nix >= 0.15). */ +void LocalStore::upgradeStore6() +{ + printMsg(lvlError, "upgrading Nix store to new schema (this may take a while)..."); + + openDB(true); + + PathSet validPaths = queryValidPathsOld(); + + SQLiteTxn txn(db); + + foreach (PathSet::iterator, i, validPaths) { + addValidPath(queryPathInfoOld(*i), false); + std::cerr << "."; + } + + std::cerr << "|"; + + foreach (PathSet::iterator, i, validPaths) { + ValidPathInfo info = queryPathInfoOld(*i); + unsigned long long referrer = queryValidPathId(*i); + foreach (PathSet::iterator, j, info.references) + addReference(referrer, queryValidPathId(*j)); + std::cerr << "."; + } + + std::cerr << "\n"; + + txn.commit(); +} + + +#if defined(FS_IOC_SETFLAGS) && defined(FS_IOC_GETFLAGS) && defined(FS_IMMUTABLE_FL) + +static void makeMutable(const Path & path) +{ + checkInterrupt(); + + struct stat st = lstat(path); + + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) return; + + if (S_ISDIR(st.st_mode)) { + Strings names = readDirectory(path); + foreach (Strings::iterator, i, names) + makeMutable(path + "/" + *i); + } + + /* The O_NOFOLLOW is important to prevent us from changing the + mutable bit on the target of a symlink (which would be a + security hole). */ + AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_NOFOLLOW); + if (fd == -1) { + if (errno == ELOOP) return; // it's a symlink + throw SysError(format("opening file `%1%'") % path); + } + + unsigned int flags = 0, old; + + /* Silently ignore errors getting/setting the immutable flag so + that we work correctly on filesystems that don't support it. */ + if (ioctl(fd, FS_IOC_GETFLAGS, &flags)) return; + old = flags; + flags &= ~FS_IMMUTABLE_FL; + if (old == flags) return; + if (ioctl(fd, FS_IOC_SETFLAGS, &flags)) return; +} + +/* Upgrade from schema 6 (Nix 0.15) to schema 7 (Nix >= 1.3). */ +void LocalStore::upgradeStore7() +{ + if (getuid() != 0) return; + printMsg(lvlError, "removing immutable bits from the Nix store (this may take a while)..."); + makeMutable(settings.nixStore); +} + +#else + +void LocalStore::upgradeStore7() +{ +} + +#endif + + +void LocalStore::vacuumDB() +{ + if (sqlite3_exec(db, "vacuum;", 0, 0, 0) != SQLITE_OK) + throwSQLiteError(db, "vacuuming SQLite database"); +} + + +} diff --git a/nix/libstore/local-store.hh b/nix/libstore/local-store.hh new file mode 100644 index 0000000000..09639e74cf --- /dev/null +++ b/nix/libstore/local-store.hh @@ -0,0 +1,333 @@ +#pragma once + +#include + +#include "store-api.hh" +#include "util.hh" +#include "pathlocks.hh" + + +class sqlite3; +class sqlite3_stmt; + + +namespace nix { + + +/* Nix store and database schema version. Version 1 (or 0) was Nix <= + 0.7. Version 2 was Nix 0.8 and 0.9. Version 3 is Nix 0.10. + Version 4 is Nix 0.11. Version 5 is Nix 0.12-0.16. Version 6 is + Nix 1.0. Version 7 is Nix 1.3. */ +const int nixSchemaVersion = 7; + + +extern string drvsLogDir; + + +struct Derivation; + + +struct OptimiseStats +{ + unsigned long totalFiles; + unsigned long sameContents; + unsigned long filesLinked; + unsigned long long bytesFreed; + unsigned long long blocksFreed; + OptimiseStats() + { + totalFiles = sameContents = filesLinked = 0; + bytesFreed = blocksFreed = 0; + } +}; + + +struct RunningSubstituter +{ + Path program; + Pid pid; + AutoCloseFD to, from, error; + FdSource fromBuf; + bool disabled; + RunningSubstituter() : disabled(false) { }; +}; + + +/* Wrapper object to close the SQLite database automatically. */ +struct SQLite +{ + sqlite3 * db; + SQLite() { db = 0; } + ~SQLite(); + operator sqlite3 * () { return db; } +}; + + +/* Wrapper object to create and destroy SQLite prepared statements. */ +struct SQLiteStmt +{ + sqlite3 * db; + sqlite3_stmt * stmt; + unsigned int curArg; + SQLiteStmt() { stmt = 0; } + void create(sqlite3 * db, const string & s); + void reset(); + ~SQLiteStmt(); + operator sqlite3_stmt * () { return stmt; } + void bind(const string & value); + void bind(int value); + void bind64(long long value); + void bind(); +}; + + +class LocalStore : public StoreAPI +{ +private: + typedef std::map RunningSubstituters; + RunningSubstituters runningSubstituters; + + Path linksDir; + +public: + + /* Initialise the local store, upgrading the schema if + necessary. */ + LocalStore(bool reserveSpace = true); + + ~LocalStore(); + + /* Implementations of abstract store API methods. */ + + bool isValidPath(const Path & path); + + PathSet queryValidPaths(const PathSet & paths); + + PathSet queryAllValidPaths(); + + ValidPathInfo queryPathInfo(const Path & path); + + Hash queryPathHash(const Path & path); + + void queryReferences(const Path & path, PathSet & references); + + void queryReferrers(const Path & path, PathSet & referrers); + + Path queryDeriver(const Path & path); + + PathSet queryValidDerivers(const Path & path); + + PathSet queryDerivationOutputs(const Path & path); + + StringSet queryDerivationOutputNames(const Path & path); + + Path queryPathFromHashPart(const string & hashPart); + + PathSet querySubstitutablePaths(const PathSet & paths); + + void querySubstitutablePathInfos(const Path & substituter, + PathSet & paths, SubstitutablePathInfos & infos); + + void querySubstitutablePathInfos(const PathSet & paths, + SubstitutablePathInfos & infos); + + Path addToStore(const Path & srcPath, + bool recursive = true, HashType hashAlgo = htSHA256, + PathFilter & filter = defaultPathFilter, bool repair = false); + + /* Like addToStore(), but the contents of the path are contained + in `dump', which is either a NAR serialisation (if recursive == + true) or simply the contents of a regular file (if recursive == + false). */ + Path addToStoreFromDump(const string & dump, const string & name, + bool recursive = true, HashType hashAlgo = htSHA256, bool repair = false); + + Path addTextToStore(const string & name, const string & s, + const PathSet & references, bool repair = false); + + void exportPath(const Path & path, bool sign, + Sink & sink); + + Paths importPaths(bool requireSignature, Source & source); + + void buildPaths(const PathSet & paths, BuildMode buildMode); + + void ensurePath(const Path & path); + + void addTempRoot(const Path & path); + + void addIndirectRoot(const Path & path); + + void syncWithGC(); + + Roots findRoots(); + + void collectGarbage(const GCOptions & options, GCResults & results); + + /* Optimise the disk space usage of the Nix store by hard-linking + files with the same contents. */ + void optimiseStore(OptimiseStats & stats); + + /* Optimise a single store path. */ + void optimisePath(const Path & path); + + /* Check the integrity of the Nix store. Returns true if errors + remain. */ + bool verifyStore(bool checkContents, bool repair); + + /* Register the validity of a path, i.e., that `path' exists, that + the paths referenced by it exists, and in the case of an output + path of a derivation, that it has been produced by a successful + execution of the derivation (or something equivalent). Also + register the hash of the file system contents of the path. The + hash must be a SHA-256 hash. */ + void registerValidPath(const ValidPathInfo & info); + + void registerValidPaths(const ValidPathInfos & infos); + + /* Register that the build of a derivation with output `path' has + failed. */ + void registerFailedPath(const Path & path); + + /* Query whether `path' previously failed to build. */ + bool hasPathFailed(const Path & path); + + PathSet queryFailedPaths(); + + void clearFailedPaths(const PathSet & paths); + + void vacuumDB(); + + /* Repair the contents of the given path by redownloading it using + a substituter (if available). */ + void repairPath(const Path & path); + + /* Check whether the given valid path exists and has the right + contents. */ + bool pathContentsGood(const Path & path); + + void markContentsGood(const Path & path); + + void setSubstituterEnv(); + +private: + + Path schemaPath; + + /* Lock file used for upgrading. */ + AutoCloseFD globalLock; + + /* The SQLite database object. */ + SQLite db; + + /* Some precompiled SQLite statements. */ + SQLiteStmt stmtRegisterValidPath; + SQLiteStmt stmtUpdatePathInfo; + SQLiteStmt stmtAddReference; + SQLiteStmt stmtQueryPathInfo; + SQLiteStmt stmtQueryReferences; + SQLiteStmt stmtQueryReferrers; + SQLiteStmt stmtInvalidatePath; + SQLiteStmt stmtRegisterFailedPath; + SQLiteStmt stmtHasPathFailed; + SQLiteStmt stmtQueryFailedPaths; + SQLiteStmt stmtClearFailedPath; + SQLiteStmt stmtAddDerivationOutput; + SQLiteStmt stmtQueryValidDerivers; + SQLiteStmt stmtQueryDerivationOutputs; + SQLiteStmt stmtQueryPathFromHashPart; + + /* Cache for pathContentsGood(). */ + std::map pathContentsGoodCache; + + bool didSetSubstituterEnv; + + int getSchema(); + + void openDB(bool create); + + void makeStoreWritable(); + + unsigned long long queryValidPathId(const Path & path); + + unsigned long long addValidPath(const ValidPathInfo & info, bool checkOutputs = true); + + void addReference(unsigned long long referrer, unsigned long long reference); + + void appendReferrer(const Path & from, const Path & to, bool lock); + + void rewriteReferrers(const Path & path, bool purge, PathSet referrers); + + void invalidatePath(const Path & path); + + /* Delete a path from the Nix store. */ + void invalidatePathChecked(const Path & path); + + void verifyPath(const Path & path, const PathSet & store, + PathSet & done, PathSet & validPaths, bool repair, bool & errors); + + void updatePathInfo(const ValidPathInfo & info); + + void upgradeStore6(); + void upgradeStore7(); + PathSet queryValidPathsOld(); + ValidPathInfo queryPathInfoOld(const Path & path); + + struct GCState; + + void deleteGarbage(GCState & state, const Path & path); + + void tryToDelete(GCState & state, const Path & path); + + bool canReachRoot(GCState & state, PathSet & visited, const Path & path); + + void deletePathRecursive(GCState & state, const Path & path); + + bool isActiveTempFile(const GCState & state, + const Path & path, const string & suffix); + + int openGCLock(LockType lockType); + + void removeUnusedLinks(const GCState & state); + + void startSubstituter(const Path & substituter, + RunningSubstituter & runningSubstituter); + + string getLineFromSubstituter(RunningSubstituter & run); + + template T getIntLineFromSubstituter(RunningSubstituter & run); + + Path createTempDirInStore(); + + Path importPath(bool requireSignature, Source & source); + + void checkDerivationOutputs(const Path & drvPath, const Derivation & drv); + + void optimisePath_(OptimiseStats & stats, const Path & path); + + // Internal versions that are not wrapped in retry_sqlite. + bool isValidPath_(const Path & path); + void queryReferrers_(const Path & path, PathSet & referrers); +}; + + +typedef std::pair Inode; +typedef set InodesSeen; + + +/* "Fix", or canonicalise, the meta-data of the files in a store path + after it has been built. In particular: + - the last modification date on each file is set to 1 (i.e., + 00:00:01 1/1/1970 UTC) + - the permissions are set of 444 or 555 (i.e., read-only with or + without execute permission; setuid bits etc. are cleared) + - the owner and group are set to the Nix user and group, if we're + running as root. */ +void canonicalisePathMetaData(const Path & path, uid_t fromUid, InodesSeen & inodesSeen); +void canonicalisePathMetaData(const Path & path, uid_t fromUid); + +void canonicaliseTimestampAndPermissions(const Path & path); + +MakeError(PathInUse, Error); + +} diff --git a/nix/libstore/misc.cc b/nix/libstore/misc.cc new file mode 100644 index 0000000000..1bf3f93782 --- /dev/null +++ b/nix/libstore/misc.cc @@ -0,0 +1,220 @@ +#include "misc.hh" +#include "store-api.hh" +#include "local-store.hh" +#include "globals.hh" + + +namespace nix { + + +Derivation derivationFromPath(StoreAPI & store, const Path & drvPath) +{ + assertStorePath(drvPath); + store.ensurePath(drvPath); + return parseDerivation(readFile(drvPath)); +} + + +void computeFSClosure(StoreAPI & store, const Path & path, + PathSet & paths, bool flipDirection, bool includeOutputs, bool includeDerivers) +{ + if (paths.find(path) != paths.end()) return; + paths.insert(path); + + PathSet edges; + + if (flipDirection) { + store.queryReferrers(path, edges); + + if (includeOutputs) { + PathSet derivers = store.queryValidDerivers(path); + foreach (PathSet::iterator, i, derivers) + edges.insert(*i); + } + + if (includeDerivers && isDerivation(path)) { + PathSet outputs = store.queryDerivationOutputs(path); + foreach (PathSet::iterator, i, outputs) + if (store.isValidPath(*i) && store.queryDeriver(*i) == path) + edges.insert(*i); + } + + } else { + store.queryReferences(path, edges); + + if (includeOutputs && isDerivation(path)) { + PathSet outputs = store.queryDerivationOutputs(path); + foreach (PathSet::iterator, i, outputs) + if (store.isValidPath(*i)) edges.insert(*i); + } + + if (includeDerivers) { + Path deriver = store.queryDeriver(path); + if (store.isValidPath(deriver)) edges.insert(deriver); + } + } + + foreach (PathSet::iterator, i, edges) + computeFSClosure(store, *i, paths, flipDirection, includeOutputs, includeDerivers); +} + + +Path findOutput(const Derivation & drv, string id) +{ + foreach (DerivationOutputs::const_iterator, i, drv.outputs) + if (i->first == id) return i->second.path; + throw Error(format("derivation has no output `%1%'") % id); +} + + +void queryMissing(StoreAPI & store, const PathSet & targets, + PathSet & willBuild, PathSet & willSubstitute, PathSet & unknown, + unsigned long long & downloadSize, unsigned long long & narSize) +{ + downloadSize = narSize = 0; + + PathSet todo(targets.begin(), targets.end()), done; + + /* Getting substitute info has high latency when using the binary + cache substituter. Thus it's essential to do substitute + queries in parallel as much as possible. To accomplish this + we do the following: + + - For all paths still to be processed (‘todo’), we add all + paths for which we need info to the set ‘query’. For an + unbuilt derivation this is the output paths; otherwise, it's + the path itself. + + - We get info about all paths in ‘query’ in parallel. + + - We process the results and add new items to ‘todo’ if + necessary. E.g. if a path is substitutable, then we need to + get info on its references. + + - Repeat until ‘todo’ is empty. + */ + + while (!todo.empty()) { + + PathSet query, todoDrv, todoNonDrv; + + foreach (PathSet::iterator, i, todo) { + if (done.find(*i) != done.end()) continue; + done.insert(*i); + + DrvPathWithOutputs i2 = parseDrvPathWithOutputs(*i); + + if (isDerivation(i2.first)) { + if (!store.isValidPath(i2.first)) { + // FIXME: we could try to substitute p. + unknown.insert(*i); + continue; + } + Derivation drv = derivationFromPath(store, i2.first); + + PathSet invalid; + foreach (DerivationOutputs::iterator, j, drv.outputs) + if (wantOutput(j->first, i2.second) + && !store.isValidPath(j->second.path)) + invalid.insert(j->second.path); + if (invalid.empty()) continue; + + todoDrv.insert(*i); + if (settings.useSubstitutes && !willBuildLocally(drv)) + query.insert(invalid.begin(), invalid.end()); + } + + else { + if (store.isValidPath(*i)) continue; + query.insert(*i); + todoNonDrv.insert(*i); + } + } + + todo.clear(); + + SubstitutablePathInfos infos; + store.querySubstitutablePathInfos(query, infos); + + foreach (PathSet::iterator, i, todoDrv) { + DrvPathWithOutputs i2 = parseDrvPathWithOutputs(*i); + + // FIXME: cache this + Derivation drv = derivationFromPath(store, i2.first); + + PathSet outputs; + bool mustBuild = false; + if (settings.useSubstitutes && !willBuildLocally(drv)) { + foreach (DerivationOutputs::iterator, j, drv.outputs) { + if (!wantOutput(j->first, i2.second)) continue; + if (!store.isValidPath(j->second.path)) { + if (infos.find(j->second.path) == infos.end()) + mustBuild = true; + else + outputs.insert(j->second.path); + } + } + } else + mustBuild = true; + + if (mustBuild) { + willBuild.insert(i2.first); + todo.insert(drv.inputSrcs.begin(), drv.inputSrcs.end()); + foreach (DerivationInputs::iterator, j, drv.inputDrvs) + todo.insert(makeDrvPathWithOutputs(j->first, j->second)); + } else + todoNonDrv.insert(outputs.begin(), outputs.end()); + } + + foreach (PathSet::iterator, i, todoNonDrv) { + done.insert(*i); + SubstitutablePathInfos::iterator info = infos.find(*i); + if (info != infos.end()) { + willSubstitute.insert(*i); + downloadSize += info->second.downloadSize; + narSize += info->second.narSize; + todo.insert(info->second.references.begin(), info->second.references.end()); + } else + unknown.insert(*i); + } + } +} + + +static void dfsVisit(StoreAPI & store, const PathSet & paths, + const Path & path, PathSet & visited, Paths & sorted, + PathSet & parents) +{ + if (parents.find(path) != parents.end()) + throw BuildError(format("cycle detected in the references of `%1%'") % path); + + if (visited.find(path) != visited.end()) return; + visited.insert(path); + parents.insert(path); + + PathSet references; + if (store.isValidPath(path)) + store.queryReferences(path, references); + + foreach (PathSet::iterator, i, references) + /* Don't traverse into paths that don't exist. That can + happen due to substitutes for non-existent paths. */ + if (*i != path && paths.find(*i) != paths.end()) + dfsVisit(store, paths, *i, visited, sorted, parents); + + sorted.push_front(path); + parents.erase(path); +} + + +Paths topoSortPaths(StoreAPI & store, const PathSet & paths) +{ + Paths sorted; + PathSet visited, parents; + foreach (PathSet::const_iterator, i, paths) + dfsVisit(store, paths, *i, visited, sorted, parents); + return sorted; +} + + +} diff --git a/nix/libstore/misc.hh b/nix/libstore/misc.hh new file mode 100644 index 0000000000..144cb7f457 --- /dev/null +++ b/nix/libstore/misc.hh @@ -0,0 +1,38 @@ +#pragma once + +#include "derivations.hh" + + +namespace nix { + + +/* Read a derivation, after ensuring its existence through + ensurePath(). */ +Derivation derivationFromPath(StoreAPI & store, const Path & drvPath); + +/* Place in `paths' the set of all store paths in the file system + closure of `storePath'; that is, all paths than can be directly or + indirectly reached from it. `paths' is not cleared. If + `flipDirection' is true, the set of paths that can reach + `storePath' is returned; that is, the closures under the + `referrers' relation instead of the `references' relation is + returned. */ +void computeFSClosure(StoreAPI & store, const Path & path, + PathSet & paths, bool flipDirection = false, + bool includeOutputs = false, bool includeDerivers = false); + +/* Return the path corresponding to the output identifier `id' in the + given derivation. */ +Path findOutput(const Derivation & drv, string id); + +/* Given a set of paths that are to be built, return the set of + derivations that will be built, and the set of output paths that + will be substituted. */ +void queryMissing(StoreAPI & store, const PathSet & targets, + PathSet & willBuild, PathSet & willSubstitute, PathSet & unknown, + unsigned long long & downloadSize, unsigned long long & narSize); + +bool willBuildLocally(const Derivation & drv); + + +} diff --git a/nix/libstore/optimise-store.cc b/nix/libstore/optimise-store.cc new file mode 100644 index 0000000000..d833f3aa05 --- /dev/null +++ b/nix/libstore/optimise-store.cc @@ -0,0 +1,180 @@ +#include "config.h" + +#include "util.hh" +#include "local-store.hh" +#include "globals.hh" + +#include +#include +#include +#include +#include + + +namespace nix { + + +static void makeWritable(const Path & path) +{ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path `%1%'") % path); + if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) + throw SysError(format("changing writability of `%1%'") % path); +} + + +struct MakeReadOnly +{ + Path path; + MakeReadOnly(const Path & path) : path(path) { } + ~MakeReadOnly() + { + try { + /* This will make the path read-only. */ + if (path != "") canonicaliseTimestampAndPermissions(path); + } catch (...) { + ignoreException(); + } + } +}; + + +void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path) +{ + checkInterrupt(); + + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path `%1%'") % path); + + if (S_ISDIR(st.st_mode)) { + Strings names = readDirectory(path); + foreach (Strings::iterator, i, names) + optimisePath_(stats, path + "/" + *i); + return; + } + + /* We can hard link regular files and maybe symlinks. */ + if (!S_ISREG(st.st_mode) +#if CAN_LINK_SYMLINK + && !S_ISLNK(st.st_mode) +#endif + ) return; + + /* Sometimes SNAFUs can cause files in the Nix store to be + modified, in particular when running programs as root under + NixOS (example: $fontconfig/var/cache being modified). Skip + those files. FIXME: check the modification time. */ + if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) { + printMsg(lvlError, format("skipping suspicious writable file `%1%'") % path); + return; + } + + /* Hash the file. Note that hashPath() returns the hash over the + NAR serialisation, which includes the execute bit on the file. + Thus, executable and non-executable files with the same + contents *won't* be linked (which is good because otherwise the + permissions would be screwed up). + + Also note that if `path' is a symlink, then we're hashing the + contents of the symlink (i.e. the result of readlink()), not + the contents of the target (which may not even exist). */ + Hash hash = hashPath(htSHA256, path).first; + stats.totalFiles++; + printMsg(lvlDebug, format("`%1%' has hash `%2%'") % path % printHash(hash)); + + /* Check if this is a known hash. */ + Path linkPath = linksDir + "/" + printHash32(hash); + + if (!pathExists(linkPath)) { + /* Nope, create a hard link in the links directory. */ + if (link(path.c_str(), linkPath.c_str()) == 0) return; + if (errno != EEXIST) + throw SysError(format("cannot link `%1%' to `%2%'") % linkPath % path); + /* Fall through if another process created ‘linkPath’ before + we did. */ + } + + /* Yes! We've seen a file with the same contents. Replace the + current file with a hard link to that file. */ + struct stat stLink; + if (lstat(linkPath.c_str(), &stLink)) + throw SysError(format("getting attributes of path `%1%'") % linkPath); + + stats.sameContents++; + if (st.st_ino == stLink.st_ino) { + printMsg(lvlDebug, format("`%1%' is already linked to `%2%'") % path % linkPath); + return; + } + + printMsg(lvlTalkative, format("linking `%1%' to `%2%'") % path % linkPath); + + /* Make the containing directory writable, but only if it's not + the store itself (we don't want or need to mess with its + permissions). */ + bool mustToggle = !isStorePath(path); + if (mustToggle) makeWritable(dirOf(path)); + + /* When we're done, make the directory read-only again and reset + its timestamp back to 0. */ + MakeReadOnly makeReadOnly(mustToggle ? dirOf(path) : ""); + + Path tempLink = (format("%1%/.tmp-link-%2%-%3%") + % settings.nixStore % getpid() % rand()).str(); + + if (link(linkPath.c_str(), tempLink.c_str()) == -1) { + if (errno == EMLINK) { + /* Too many links to the same file (>= 32000 on most file + systems). This is likely to happen with empty files. + Just shrug and ignore. */ + if (st.st_size) + printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath); + return; + } + throw SysError(format("cannot link `%1%' to `%2%'") % tempLink % linkPath); + } + + /* Atomically replace the old file with the new hard link. */ + if (rename(tempLink.c_str(), path.c_str()) == -1) { + if (unlink(tempLink.c_str()) == -1) + printMsg(lvlError, format("unable to unlink `%1%'") % tempLink); + if (errno == EMLINK) { + /* Some filesystems generate too many links on the rename, + rather than on the original link. (Probably it + temporarily increases the st_nlink field before + decreasing it again.) */ + if (st.st_size) + printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath); + return; + } + throw SysError(format("cannot rename `%1%' to `%2%'") % tempLink % path); + } + + stats.filesLinked++; + stats.bytesFreed += st.st_size; + stats.blocksFreed += st.st_blocks; +} + + +void LocalStore::optimiseStore(OptimiseStats & stats) +{ + PathSet paths = queryAllValidPaths(); + + foreach (PathSet::iterator, i, paths) { + addTempRoot(*i); + if (!isValidPath(*i)) continue; /* path was GC'ed, probably */ + startNest(nest, lvlChatty, format("hashing files in `%1%'") % *i); + optimisePath_(stats, *i); + } +} + + +void LocalStore::optimisePath(const Path & path) +{ + OptimiseStats stats; + if (settings.autoOptimiseStore) optimisePath_(stats, path); +} + + +} diff --git a/nix/libstore/pathlocks.cc b/nix/libstore/pathlocks.cc new file mode 100644 index 0000000000..b858ed238d --- /dev/null +++ b/nix/libstore/pathlocks.cc @@ -0,0 +1,199 @@ +#include "pathlocks.hh" +#include "util.hh" + +#include +#include + +#include +#include +#include + + +namespace nix { + + +int openLockFile(const Path & path, bool create) +{ + AutoCloseFD fd; + + fd = open(path.c_str(), O_RDWR | (create ? O_CREAT : 0), 0600); + if (fd == -1 && (create || errno != ENOENT)) + throw SysError(format("opening lock file `%1%'") % path); + + closeOnExec(fd); + + return fd.borrow(); +} + + +void deleteLockFile(const Path & path, int fd) +{ + /* Get rid of the lock file. Have to be careful not to introduce + races. Write a (meaningless) token to the file to indicate to + other processes waiting on this lock that the lock is stale + (deleted). */ + unlink(path.c_str()); + writeFull(fd, (const unsigned char *) "d", 1); + /* Note that the result of unlink() is ignored; removing the lock + file is an optimisation, not a necessity. */ +} + + +bool lockFile(int fd, LockType lockType, bool wait) +{ + struct flock lock; + if (lockType == ltRead) lock.l_type = F_RDLCK; + else if (lockType == ltWrite) lock.l_type = F_WRLCK; + else if (lockType == ltNone) lock.l_type = F_UNLCK; + else abort(); + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; /* entire file */ + + if (wait) { + while (fcntl(fd, F_SETLKW, &lock) != 0) { + checkInterrupt(); + if (errno != EINTR) + throw SysError(format("acquiring/releasing lock")); + } + } else { + while (fcntl(fd, F_SETLK, &lock) != 0) { + checkInterrupt(); + if (errno == EACCES || errno == EAGAIN) return false; + if (errno != EINTR) + throw SysError(format("acquiring/releasing lock")); + } + } + + return true; +} + + +/* This enables us to check whether are not already holding a lock on + a file ourselves. POSIX locks (fcntl) suck in this respect: if we + close a descriptor, the previous lock will be closed as well. And + there is no way to query whether we already have a lock (F_GETLK + only works on locks held by other processes). */ +static StringSet lockedPaths; /* !!! not thread-safe */ + + +PathLocks::PathLocks() + : deletePaths(false) +{ +} + + +PathLocks::PathLocks(const PathSet & paths, const string & waitMsg) + : deletePaths(false) +{ + lockPaths(paths, waitMsg); +} + + +bool PathLocks::lockPaths(const PathSet & _paths, + const string & waitMsg, bool wait) +{ + assert(fds.empty()); + + /* Note that `fds' is built incrementally so that the destructor + will only release those locks that we have already acquired. */ + + /* Sort the paths. This assures that locks are always acquired in + the same order, thus preventing deadlocks. */ + Paths paths(_paths.begin(), _paths.end()); + paths.sort(); + + /* Acquire the lock for each path. */ + foreach (Paths::iterator, i, paths) { + checkInterrupt(); + Path path = *i; + Path lockPath = path + ".lock"; + + debug(format("locking path `%1%'") % path); + + if (lockedPaths.find(lockPath) != lockedPaths.end()) + throw Error("deadlock: trying to re-acquire self-held lock"); + + AutoCloseFD fd; + + while (1) { + + /* Open/create the lock file. */ + fd = openLockFile(lockPath, true); + + /* Acquire an exclusive lock. */ + if (!lockFile(fd, ltWrite, false)) { + if (wait) { + if (waitMsg != "") printMsg(lvlError, waitMsg); + lockFile(fd, ltWrite, true); + } else { + /* Failed to lock this path; release all other + locks. */ + unlock(); + return false; + } + } + + debug(format("lock acquired on `%1%'") % lockPath); + + /* Check that the lock file hasn't become stale (i.e., + hasn't been unlinked). */ + struct stat st; + if (fstat(fd, &st) == -1) + throw SysError(format("statting lock file `%1%'") % lockPath); + if (st.st_size != 0) + /* This lock file has been unlinked, so we're holding + a lock on a deleted file. This means that other + processes may create and acquire a lock on + `lockPath', and proceed. So we must retry. */ + debug(format("open lock file `%1%' has become stale") % lockPath); + else + break; + } + + /* Use borrow so that the descriptor isn't closed. */ + fds.push_back(FDPair(fd.borrow(), lockPath)); + lockedPaths.insert(lockPath); + } + + return true; +} + + +PathLocks::~PathLocks() +{ + unlock(); +} + + +void PathLocks::unlock() +{ + foreach (list::iterator, i, fds) { + if (deletePaths) deleteLockFile(i->second, i->first); + + lockedPaths.erase(i->second); + if (close(i->first) == -1) + printMsg(lvlError, + format("error (ignored): cannot close lock file on `%1%'") % i->second); + + debug(format("lock released on `%1%'") % i->second); + } + + fds.clear(); +} + + +void PathLocks::setDeletion(bool deletePaths) +{ + this->deletePaths = deletePaths; +} + + +bool pathIsLockedByMe(const Path & path) +{ + Path lockPath = path + ".lock"; + return lockedPaths.find(lockPath) != lockedPaths.end(); +} + + +} diff --git a/nix/libstore/pathlocks.hh b/nix/libstore/pathlocks.hh new file mode 100644 index 0000000000..8a6b1450da --- /dev/null +++ b/nix/libstore/pathlocks.hh @@ -0,0 +1,45 @@ +#pragma once + +#include "types.hh" + + +namespace nix { + + +/* Open (possibly create) a lock file and return the file descriptor. + -1 is returned if create is false and the lock could not be opened + because it doesn't exist. Any other error throws an exception. */ +int openLockFile(const Path & path, bool create); + +/* Delete an open lock file. */ +void deleteLockFile(const Path & path, int fd); + +enum LockType { ltRead, ltWrite, ltNone }; + +bool lockFile(int fd, LockType lockType, bool wait); + + +class PathLocks +{ +private: + typedef std::pair FDPair; + list fds; + bool deletePaths; + +public: + PathLocks(); + PathLocks(const PathSet & paths, + const string & waitMsg = ""); + bool lockPaths(const PathSet & _paths, + const string & waitMsg = "", + bool wait = true); + ~PathLocks(); + void unlock(); + void setDeletion(bool deletePaths); +}; + + +bool pathIsLockedByMe(const Path & path); + + +} diff --git a/nix/libstore/references.cc b/nix/libstore/references.cc new file mode 100644 index 0000000000..282b848938 --- /dev/null +++ b/nix/libstore/references.cc @@ -0,0 +1,122 @@ +#include "references.hh" +#include "hash.hh" +#include "util.hh" +#include "archive.hh" + +#include +#include + + +namespace nix { + + +static unsigned int refLength = 32; /* characters */ + + +static void search(const unsigned char * s, unsigned int len, + StringSet & hashes, StringSet & seen) +{ + static bool initialised = false; + static bool isBase32[256]; + if (!initialised) { + for (unsigned int i = 0; i < 256; ++i) isBase32[i] = false; + for (unsigned int i = 0; i < base32Chars.size(); ++i) + isBase32[(unsigned char) base32Chars[i]] = true; + initialised = true; + } + + for (unsigned int i = 0; i + refLength <= len; ) { + int j; + bool match = true; + for (j = refLength - 1; j >= 0; --j) + if (!isBase32[(unsigned char) s[i + j]]) { + i += j + 1; + match = false; + break; + } + if (!match) continue; + string ref((const char *) s + i, refLength); + if (hashes.find(ref) != hashes.end()) { + debug(format("found reference to `%1%' at offset `%2%'") + % ref % i); + seen.insert(ref); + hashes.erase(ref); + } + ++i; + } +} + + +struct RefScanSink : Sink +{ + HashSink hashSink; + StringSet hashes; + StringSet seen; + + string tail; + + RefScanSink() : hashSink(htSHA256) { } + + void operator () (const unsigned char * data, size_t len); +}; + + +void RefScanSink::operator () (const unsigned char * data, size_t len) +{ + hashSink(data, len); + + /* It's possible that a reference spans the previous and current + fragment, so search in the concatenation of the tail of the + previous fragment and the start of the current fragment. */ + string s = tail + string((const char *) data, len > refLength ? refLength : len); + search((const unsigned char *) s.data(), s.size(), hashes, seen); + + search(data, len, hashes, seen); + + unsigned int tailLen = len <= refLength ? len : refLength; + tail = + string(tail, tail.size() < refLength - tailLen ? 0 : tail.size() - (refLength - tailLen)) + + string((const char *) data + len - tailLen, tailLen); +} + + +PathSet scanForReferences(const string & path, + const PathSet & refs, HashResult & hash) +{ + RefScanSink sink; + std::map backMap; + + /* For efficiency (and a higher hit rate), just search for the + hash part of the file name. (This assumes that all references + have the form `HASH-bla'). */ + foreach (PathSet::const_iterator, i, refs) { + string baseName = baseNameOf(*i); + string::size_type pos = baseName.find('-'); + if (pos == string::npos) + throw Error(format("bad reference `%1%'") % *i); + string s = string(baseName, 0, pos); + assert(s.size() == refLength); + assert(backMap.find(s) == backMap.end()); + // parseHash(htSHA256, s); + sink.hashes.insert(s); + backMap[s] = *i; + } + + /* Look for the hashes in the NAR dump of the path. */ + dumpPath(path, sink); + + /* Map the hashes found back to their store paths. */ + PathSet found; + foreach (StringSet::iterator, i, sink.seen) { + std::map::iterator j; + if ((j = backMap.find(*i)) == backMap.end()) abort(); + found.insert(j->second); + } + + hash = sink.hashSink.finish(); + + return found; +} + + +} diff --git a/nix/libstore/references.hh b/nix/libstore/references.hh new file mode 100644 index 0000000000..013809d122 --- /dev/null +++ b/nix/libstore/references.hh @@ -0,0 +1,11 @@ +#pragma once + +#include "types.hh" +#include "hash.hh" + +namespace nix { + +PathSet scanForReferences(const Path & path, const PathSet & refs, + HashResult & hash); + +} diff --git a/nix/libstore/remote-store.cc b/nix/libstore/remote-store.cc new file mode 100644 index 0000000000..4619206932 --- /dev/null +++ b/nix/libstore/remote-store.cc @@ -0,0 +1,602 @@ +#include "serialise.hh" +#include "util.hh" +#include "remote-store.hh" +#include "worker-protocol.hh" +#include "archive.hh" +#include "affinity.hh" +#include "globals.hh" + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace nix { + + +Path readStorePath(Source & from) +{ + Path path = readString(from); + assertStorePath(path); + return path; +} + + +template T readStorePaths(Source & from) +{ + T paths = readStrings(from); + foreach (typename T::iterator, i, paths) assertStorePath(*i); + return paths; +} + +template PathSet readStorePaths(Source & from); + + +RemoteStore::RemoteStore() +{ + initialised = false; +} + + +void RemoteStore::openConnection(bool reserveSpace) +{ + if (initialised) return; + initialised = true; + + string remoteMode = getEnv("NIX_REMOTE"); + + if (remoteMode == "daemon") + /* Connect to a daemon that does the privileged work for + us. */ + connectToDaemon(); + else + throw Error(format("invalid setting for NIX_REMOTE, `%1%'") % remoteMode); + + from.fd = fdSocket; + to.fd = fdSocket; + + /* Send the magic greeting, check for the reply. */ + try { + writeInt(WORKER_MAGIC_1, to); + to.flush(); + unsigned int magic = readInt(from); + if (magic != WORKER_MAGIC_2) throw Error("protocol mismatch"); + + daemonVersion = readInt(from); + if (GET_PROTOCOL_MAJOR(daemonVersion) != GET_PROTOCOL_MAJOR(PROTOCOL_VERSION)) + throw Error("Nix daemon protocol version not supported"); + writeInt(PROTOCOL_VERSION, to); + + if (GET_PROTOCOL_MINOR(daemonVersion) >= 14) { + int cpu = settings.lockCPU ? lockToCurrentCPU() : -1; + if (cpu != -1) { + writeInt(1, to); + writeInt(cpu, to); + } else + writeInt(0, to); + } + + if (GET_PROTOCOL_MINOR(daemonVersion) >= 11) + writeInt(reserveSpace, to); + + processStderr(); + } + catch (Error & e) { + throw Error(format("cannot start worker (%1%)") + % e.msg()); + } + + setOptions(); +} + + +void RemoteStore::connectToDaemon() +{ + fdSocket = socket(PF_UNIX, SOCK_STREAM, 0); + if (fdSocket == -1) + throw SysError("cannot create Unix domain socket"); + closeOnExec(fdSocket); + + string socketPath = settings.nixDaemonSocketFile; + + /* 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. !!! this is probably a bad idea in multi-threaded + applications... */ + AutoCloseFD fdPrevDir = open(".", O_RDONLY); + if (fdPrevDir == -1) throw SysError("couldn't open current directory"); + chdir(dirOf(socketPath).c_str()); + Path socketPathRel = "./" + baseNameOf(socketPath); + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + if (socketPathRel.size() >= sizeof(addr.sun_path)) + throw Error(format("socket path `%1%' is too long") % socketPathRel); + using namespace std; + strcpy(addr.sun_path, socketPathRel.c_str()); + + if (connect(fdSocket, (struct sockaddr *) &addr, sizeof(addr)) == -1) + throw SysError(format("cannot connect to daemon at `%1%'") % socketPath); + + if (fchdir(fdPrevDir) == -1) + throw SysError("couldn't change back to previous directory"); +} + + +RemoteStore::~RemoteStore() +{ + try { + to.flush(); + fdSocket.close(); + if (child != -1) + child.wait(true); + } catch (...) { + ignoreException(); + } +} + + +void RemoteStore::setOptions() +{ + writeInt(wopSetOptions, to); + + writeInt(settings.keepFailed, to); + writeInt(settings.keepGoing, to); + writeInt(settings.tryFallback, to); + writeInt(verbosity, to); + writeInt(settings.maxBuildJobs, to); + writeInt(settings.maxSilentTime, to); + if (GET_PROTOCOL_MINOR(daemonVersion) >= 2) + writeInt(settings.useBuildHook, to); + if (GET_PROTOCOL_MINOR(daemonVersion) >= 4) { + writeInt(settings.buildVerbosity, to); + writeInt(logType, to); + writeInt(settings.printBuildTrace, to); + } + if (GET_PROTOCOL_MINOR(daemonVersion) >= 6) + writeInt(settings.buildCores, to); + if (GET_PROTOCOL_MINOR(daemonVersion) >= 10) + writeInt(settings.useSubstitutes, to); + + if (GET_PROTOCOL_MINOR(daemonVersion) >= 12) { + Settings::SettingsMap overrides = settings.getOverrides(); + writeInt(overrides.size(), to); + foreach (Settings::SettingsMap::iterator, i, overrides) { + writeString(i->first, to); + writeString(i->second, to); + } + } + + processStderr(); +} + + +bool RemoteStore::isValidPath(const Path & path) +{ + openConnection(); + writeInt(wopIsValidPath, to); + writeString(path, to); + processStderr(); + unsigned int reply = readInt(from); + return reply != 0; +} + + +PathSet RemoteStore::queryValidPaths(const PathSet & paths) +{ + openConnection(); + if (GET_PROTOCOL_MINOR(daemonVersion) < 12) { + PathSet res; + foreach (PathSet::const_iterator, i, paths) + if (isValidPath(*i)) res.insert(*i); + return res; + } else { + writeInt(wopQueryValidPaths, to); + writeStrings(paths, to); + processStderr(); + return readStorePaths(from); + } +} + + +PathSet RemoteStore::queryAllValidPaths() +{ + openConnection(); + writeInt(wopQueryAllValidPaths, to); + processStderr(); + return readStorePaths(from); +} + + +PathSet RemoteStore::querySubstitutablePaths(const PathSet & paths) +{ + openConnection(); + if (GET_PROTOCOL_MINOR(daemonVersion) < 12) { + PathSet res; + foreach (PathSet::const_iterator, i, paths) { + writeInt(wopHasSubstitutes, to); + writeString(*i, to); + processStderr(); + if (readInt(from)) res.insert(*i); + } + return res; + } else { + writeInt(wopQuerySubstitutablePaths, to); + writeStrings(paths, to); + processStderr(); + return readStorePaths(from); + } +} + + +void RemoteStore::querySubstitutablePathInfos(const PathSet & paths, + SubstitutablePathInfos & infos) +{ + if (paths.empty()) return; + + openConnection(); + + if (GET_PROTOCOL_MINOR(daemonVersion) < 3) return; + + if (GET_PROTOCOL_MINOR(daemonVersion) < 12) { + + foreach (PathSet::const_iterator, i, paths) { + SubstitutablePathInfo info; + writeInt(wopQuerySubstitutablePathInfo, to); + writeString(*i, to); + processStderr(); + unsigned int reply = readInt(from); + if (reply == 0) continue; + info.deriver = readString(from); + if (info.deriver != "") assertStorePath(info.deriver); + info.references = readStorePaths(from); + info.downloadSize = readLongLong(from); + info.narSize = GET_PROTOCOL_MINOR(daemonVersion) >= 7 ? readLongLong(from) : 0; + infos[*i] = info; + } + + } else { + + writeInt(wopQuerySubstitutablePathInfos, to); + writeStrings(paths, to); + processStderr(); + unsigned int count = readInt(from); + for (unsigned int n = 0; n < count; n++) { + Path path = readStorePath(from); + SubstitutablePathInfo & info(infos[path]); + info.deriver = readString(from); + if (info.deriver != "") assertStorePath(info.deriver); + info.references = readStorePaths(from); + info.downloadSize = readLongLong(from); + info.narSize = readLongLong(from); + } + + } +} + + +ValidPathInfo RemoteStore::queryPathInfo(const Path & path) +{ + openConnection(); + writeInt(wopQueryPathInfo, to); + writeString(path, to); + processStderr(); + ValidPathInfo info; + info.path = path; + info.deriver = readString(from); + if (info.deriver != "") assertStorePath(info.deriver); + info.hash = parseHash(htSHA256, readString(from)); + info.references = readStorePaths(from); + info.registrationTime = readInt(from); + info.narSize = readLongLong(from); + return info; +} + + +Hash RemoteStore::queryPathHash(const Path & path) +{ + openConnection(); + writeInt(wopQueryPathHash, to); + writeString(path, to); + processStderr(); + string hash = readString(from); + return parseHash(htSHA256, hash); +} + + +void RemoteStore::queryReferences(const Path & path, + PathSet & references) +{ + openConnection(); + writeInt(wopQueryReferences, to); + writeString(path, to); + processStderr(); + PathSet references2 = readStorePaths(from); + references.insert(references2.begin(), references2.end()); +} + + +void RemoteStore::queryReferrers(const Path & path, + PathSet & referrers) +{ + openConnection(); + writeInt(wopQueryReferrers, to); + writeString(path, to); + processStderr(); + PathSet referrers2 = readStorePaths(from); + referrers.insert(referrers2.begin(), referrers2.end()); +} + + +Path RemoteStore::queryDeriver(const Path & path) +{ + openConnection(); + writeInt(wopQueryDeriver, to); + writeString(path, to); + processStderr(); + Path drvPath = readString(from); + if (drvPath != "") assertStorePath(drvPath); + return drvPath; +} + + +PathSet RemoteStore::queryValidDerivers(const Path & path) +{ + openConnection(); + writeInt(wopQueryValidDerivers, to); + writeString(path, to); + processStderr(); + return readStorePaths(from); +} + + +PathSet RemoteStore::queryDerivationOutputs(const Path & path) +{ + openConnection(); + writeInt(wopQueryDerivationOutputs, to); + writeString(path, to); + processStderr(); + return readStorePaths(from); +} + + +PathSet RemoteStore::queryDerivationOutputNames(const Path & path) +{ + openConnection(); + writeInt(wopQueryDerivationOutputNames, to); + writeString(path, to); + processStderr(); + return readStrings(from); +} + + +Path RemoteStore::queryPathFromHashPart(const string & hashPart) +{ + openConnection(); + writeInt(wopQueryPathFromHashPart, to); + writeString(hashPart, to); + processStderr(); + Path path = readString(from); + if (!path.empty()) assertStorePath(path); + return path; +} + + +Path RemoteStore::addToStore(const Path & _srcPath, + bool recursive, HashType hashAlgo, PathFilter & filter, bool repair) +{ + if (repair) throw Error("repairing is not supported when building through the Nix daemon"); + + openConnection(); + + Path srcPath(absPath(_srcPath)); + + writeInt(wopAddToStore, to); + writeString(baseNameOf(srcPath), to); + /* backwards compatibility hack */ + writeInt((hashAlgo == htSHA256 && recursive) ? 0 : 1, to); + writeInt(recursive ? 1 : 0, to); + writeString(printHashType(hashAlgo), to); + dumpPath(srcPath, to, filter); + processStderr(); + return readStorePath(from); +} + + +Path RemoteStore::addTextToStore(const string & name, const string & s, + const PathSet & references, bool repair) +{ + if (repair) throw Error("repairing is not supported when building through the Nix daemon"); + + openConnection(); + writeInt(wopAddTextToStore, to); + writeString(name, to); + writeString(s, to); + writeStrings(references, to); + + processStderr(); + return readStorePath(from); +} + + +void RemoteStore::exportPath(const Path & path, bool sign, + Sink & sink) +{ + openConnection(); + writeInt(wopExportPath, to); + writeString(path, to); + writeInt(sign ? 1 : 0, to); + processStderr(&sink); /* sink receives the actual data */ + readInt(from); +} + + +Paths RemoteStore::importPaths(bool requireSignature, Source & source) +{ + openConnection(); + writeInt(wopImportPaths, to); + /* We ignore requireSignature, since the worker forces it to true + anyway. */ + processStderr(0, &source); + return readStorePaths(from); +} + + +void RemoteStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode) +{ + if (buildMode != bmNormal) throw Error("repairing or checking is not supported when building through the Nix daemon"); + openConnection(); + writeInt(wopBuildPaths, to); + if (GET_PROTOCOL_MINOR(daemonVersion) >= 13) + writeStrings(drvPaths, to); + else { + /* For backwards compatibility with old daemons, strip output + identifiers. */ + PathSet drvPaths2; + foreach (PathSet::const_iterator, i, drvPaths) + drvPaths2.insert(string(*i, 0, i->find('!'))); + writeStrings(drvPaths2, to); + } + processStderr(); + readInt(from); +} + + +void RemoteStore::ensurePath(const Path & path) +{ + openConnection(); + writeInt(wopEnsurePath, to); + writeString(path, to); + processStderr(); + readInt(from); +} + + +void RemoteStore::addTempRoot(const Path & path) +{ + openConnection(); + writeInt(wopAddTempRoot, to); + writeString(path, to); + processStderr(); + readInt(from); +} + + +void RemoteStore::addIndirectRoot(const Path & path) +{ + openConnection(); + writeInt(wopAddIndirectRoot, to); + writeString(path, to); + processStderr(); + readInt(from); +} + + +void RemoteStore::syncWithGC() +{ + openConnection(); + writeInt(wopSyncWithGC, to); + processStderr(); + readInt(from); +} + + +Roots RemoteStore::findRoots() +{ + openConnection(); + writeInt(wopFindRoots, to); + processStderr(); + unsigned int count = readInt(from); + Roots result; + while (count--) { + Path link = readString(from); + Path target = readStorePath(from); + result[link] = target; + } + return result; +} + + +void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results) +{ + openConnection(false); + + writeInt(wopCollectGarbage, to); + writeInt(options.action, to); + writeStrings(options.pathsToDelete, to); + writeInt(options.ignoreLiveness, to); + writeLongLong(options.maxFreed, to); + writeInt(0, to); + if (GET_PROTOCOL_MINOR(daemonVersion) >= 5) { + /* removed options */ + writeInt(0, to); + writeInt(0, to); + } + + processStderr(); + + results.paths = readStrings(from); + results.bytesFreed = readLongLong(from); + readLongLong(from); // obsolete +} + + +PathSet RemoteStore::queryFailedPaths() +{ + openConnection(); + writeInt(wopQueryFailedPaths, to); + processStderr(); + return readStorePaths(from); +} + + +void RemoteStore::clearFailedPaths(const PathSet & paths) +{ + openConnection(); + writeInt(wopClearFailedPaths, to); + writeStrings(paths, to); + processStderr(); + readInt(from); +} + + +void RemoteStore::processStderr(Sink * sink, Source * source) +{ + to.flush(); + unsigned int msg; + while ((msg = readInt(from)) == STDERR_NEXT + || msg == STDERR_READ || msg == STDERR_WRITE) { + if (msg == STDERR_WRITE) { + string s = readString(from); + if (!sink) throw Error("no sink"); + (*sink)((const unsigned char *) s.data(), s.size()); + } + else if (msg == STDERR_READ) { + if (!source) throw Error("no source"); + size_t len = readInt(from); + unsigned char * buf = new unsigned char[len]; + AutoDeleteArray d(buf); + writeString(buf, source->read(buf, len), to); + to.flush(); + } + else { + string s = readString(from); + writeToStderr(s); + } + } + if (msg == STDERR_ERROR) { + string error = readString(from); + unsigned int status = GET_PROTOCOL_MINOR(daemonVersion) >= 8 ? readInt(from) : 1; + throw Error(format("%1%") % error, status); + } + else if (msg != STDERR_LAST) + throw Error("protocol error processing standard error"); +} + + +} diff --git a/nix/libstore/remote-store.hh b/nix/libstore/remote-store.hh new file mode 100644 index 0000000000..04b60fce4b --- /dev/null +++ b/nix/libstore/remote-store.hh @@ -0,0 +1,104 @@ +#pragma once + +#include + +#include "store-api.hh" + + +namespace nix { + + +class Pipe; +class Pid; +struct FdSink; +struct FdSource; + + +class RemoteStore : public StoreAPI +{ +public: + + RemoteStore(); + + ~RemoteStore(); + + /* Implementations of abstract store API methods. */ + + bool isValidPath(const Path & path); + + PathSet queryValidPaths(const PathSet & paths); + + PathSet queryAllValidPaths(); + + ValidPathInfo queryPathInfo(const Path & path); + + Hash queryPathHash(const Path & path); + + void queryReferences(const Path & path, PathSet & references); + + void queryReferrers(const Path & path, PathSet & referrers); + + Path queryDeriver(const Path & path); + + PathSet queryValidDerivers(const Path & path); + + PathSet queryDerivationOutputs(const Path & path); + + StringSet queryDerivationOutputNames(const Path & path); + + Path queryPathFromHashPart(const string & hashPart); + + PathSet querySubstitutablePaths(const PathSet & paths); + + void querySubstitutablePathInfos(const PathSet & paths, + SubstitutablePathInfos & infos); + + Path addToStore(const Path & srcPath, + bool recursive = true, HashType hashAlgo = htSHA256, + PathFilter & filter = defaultPathFilter, bool repair = false); + + Path addTextToStore(const string & name, const string & s, + const PathSet & references, bool repair = false); + + void exportPath(const Path & path, bool sign, + Sink & sink); + + Paths importPaths(bool requireSignature, Source & source); + + void buildPaths(const PathSet & paths, BuildMode buildMode); + + void ensurePath(const Path & path); + + void addTempRoot(const Path & path); + + void addIndirectRoot(const Path & path); + + void syncWithGC(); + + Roots findRoots(); + + void collectGarbage(const GCOptions & options, GCResults & results); + + PathSet queryFailedPaths(); + + void clearFailedPaths(const PathSet & paths); + +private: + AutoCloseFD fdSocket; + FdSink to; + FdSource from; + Pid child; + unsigned int daemonVersion; + bool initialised; + + void openConnection(bool reserveSpace = true); + + void processStderr(Sink * sink = 0, Source * source = 0); + + void connectToDaemon(); + + void setOptions(); +}; + + +} diff --git a/nix/libstore/schema.sql b/nix/libstore/schema.sql new file mode 100644 index 0000000000..c1b4a689af --- /dev/null +++ b/nix/libstore/schema.sql @@ -0,0 +1,44 @@ +create table if not exists ValidPaths ( + id integer primary key autoincrement not null, + path text unique not null, + hash text not null, + registrationTime integer not null, + deriver text, + narSize integer +); + +create table if not exists Refs ( + referrer integer not null, + reference integer not null, + primary key (referrer, reference), + foreign key (referrer) references ValidPaths(id) on delete cascade, + foreign key (reference) references ValidPaths(id) on delete restrict +); + +create index if not exists IndexReferrer on Refs(referrer); +create index if not exists IndexReference on Refs(reference); + +-- Paths can refer to themselves, causing a tuple (N, N) in the Refs +-- table. This causes a deletion of the corresponding row in +-- ValidPaths to cause a foreign key constraint violation (due to `on +-- delete restrict' on the `reference' column). Therefore, explicitly +-- get rid of self-references. +create trigger if not exists DeleteSelfRefs before delete on ValidPaths + begin + delete from Refs where referrer = old.id and reference = old.id; + end; + +create table if not exists DerivationOutputs ( + drv integer not null, + id text not null, -- symbolic output id, usually "out" + path text not null, + primary key (drv, id), + foreign key (drv) references ValidPaths(id) on delete cascade +); + +create index if not exists IndexDerivationOutputs on DerivationOutputs(path); + +create table if not exists FailedPaths ( + path text primary key not null, + time integer not null +); diff --git a/nix/libstore/store-api.cc b/nix/libstore/store-api.cc new file mode 100644 index 0000000000..0238e5b0b6 --- /dev/null +++ b/nix/libstore/store-api.cc @@ -0,0 +1,331 @@ +#include "store-api.hh" +#include "globals.hh" +#include "util.hh" + +#include + + +namespace nix { + + +GCOptions::GCOptions() +{ + action = gcDeleteDead; + ignoreLiveness = false; + maxFreed = ULLONG_MAX; +} + + +bool isInStore(const Path & path) +{ + return isInDir(path, settings.nixStore); +} + + +bool isStorePath(const Path & path) +{ + return isInStore(path) + && path.find('/', settings.nixStore.size() + 1) == Path::npos; +} + + +void assertStorePath(const Path & path) +{ + if (!isStorePath(path)) + throw Error(format("path `%1%' is not in the Nix store") % path); +} + + +Path toStorePath(const Path & path) +{ + if (!isInStore(path)) + throw Error(format("path `%1%' is not in the Nix store") % path); + Path::size_type slash = path.find('/', settings.nixStore.size() + 1); + if (slash == Path::npos) + return path; + else + return Path(path, 0, slash); +} + + +Path followLinksToStore(const Path & _path) +{ + Path path = absPath(_path); + while (!isInStore(path)) { + if (!isLink(path)) break; + string target = readLink(path); + path = absPath(target, dirOf(path)); + } + if (!isInStore(path)) + throw Error(format("path `%1%' is not in the Nix store") % path); + return path; +} + + +Path followLinksToStorePath(const Path & path) +{ + return toStorePath(followLinksToStore(path)); +} + + +string storePathToName(const Path & path) +{ + assertStorePath(path); + return string(path, settings.nixStore.size() + 34); +} + + +void checkStoreName(const string & name) +{ + string validChars = "+-._?="; + /* Disallow names starting with a dot for possible security + reasons (e.g., "." and ".."). */ + if (string(name, 0, 1) == ".") + throw Error(format("illegal name: `%1%'") % name); + foreach (string::const_iterator, i, name) + if (!((*i >= 'A' && *i <= 'Z') || + (*i >= 'a' && *i <= 'z') || + (*i >= '0' && *i <= '9') || + validChars.find(*i) != string::npos)) + { + throw Error(format("invalid character `%1%' in name `%2%'") + % *i % name); + } +} + + +/* Store paths have the following form: + + /- + + where + + = the location of the Nix store, usually /nix/store + + = a human readable name for the path, typically obtained + from the name attribute of the derivation, or the name of the + source file from which the store path is created. For derivation + outputs other than the default "out" output, the string "-" + is suffixed to . + + = base-32 representation of the first 160 bits of a SHA-256 + hash of ; the hash part of the store name + + = the string ":sha256:

::"; + note that it includes the location of the store as well as the + name to make sure that changes to either of those are reflected + in the hash (e.g. you won't get /nix/store/-name1 and + /nix/store/-name2 with equal hash parts). + + = one of: + "text:::..." + for plain text files written to the store using + addTextToStore(); ... are the references of the + path. + "source" + for paths copied to the store using addToStore() when recursive + = true and hashAlgo = "sha256" + "output:" + for either the outputs created by derivations, OR paths copied + to the store using addToStore() with recursive != true or + hashAlgo != "sha256" (in that case "source" is used; it's + silly, but it's done that way for compatibility). is the + name of the output (usually, "out"). + +

= base-16 representation of a SHA-256 hash of: + if = "text:...": + the string written to the resulting store path + if = "source": + the serialisation of the path from which this store path is + copied, as returned by hashPath() + if = "output:out": + for non-fixed derivation outputs: + the derivation (see hashDerivationModulo() in + primops.cc) + for paths copied by addToStore() or produced by fixed-output + derivations: + the string "fixed:out:::", where + = "r:" for recursive (path) hashes, or "" or flat + (file) hashes + = "md5", "sha1" or "sha256" + = base-16 representation of the path or flat hash of + the contents of the path (or expected contents of the + path for fixed-output derivations) + + It would have been nicer to handle fixed-output derivations under + "source", e.g. have something like "source:", but we're + stuck with this for now... + + The main reason for this way of computing names is to prevent name + collisions (for security). For instance, it shouldn't be feasible + to come up with a derivation whose output path collides with the + path for a copied source. The former would have a starting with + "output:out:", while the latter would have a <2> starting with + "source:". +*/ + + +Path makeStorePath(const string & type, + const Hash & hash, const string & name) +{ + /* e.g., "source:sha256:1abc...:/nix/store:foo.tar.gz" */ + string s = type + ":sha256:" + printHash(hash) + ":" + + settings.nixStore + ":" + name; + + checkStoreName(name); + + return settings.nixStore + "/" + + printHash32(compressHash(hashString(htSHA256, s), 20)) + + "-" + name; +} + + +Path makeOutputPath(const string & id, + const Hash & hash, const string & name) +{ + return makeStorePath("output:" + id, hash, + name + (id == "out" ? "" : "-" + id)); +} + + +Path makeFixedOutputPath(bool recursive, + HashType hashAlgo, Hash hash, string name) +{ + return hashAlgo == htSHA256 && recursive + ? makeStorePath("source", hash, name) + : makeStorePath("output:out", hashString(htSHA256, + "fixed:out:" + (recursive ? (string) "r:" : "") + + printHashType(hashAlgo) + ":" + printHash(hash) + ":"), + name); +} + + +std::pair computeStorePathForPath(const Path & srcPath, + bool recursive, HashType hashAlgo, PathFilter & filter) +{ + HashType ht(hashAlgo); + Hash h = recursive ? hashPath(ht, srcPath, filter).first : hashFile(ht, srcPath); + string name = baseNameOf(srcPath); + Path dstPath = makeFixedOutputPath(recursive, hashAlgo, h, name); + return std::pair(dstPath, h); +} + + +Path computeStorePathForText(const string & name, const string & s, + const PathSet & references) +{ + Hash hash = hashString(htSHA256, s); + /* Stuff the references (if any) into the type. This is a bit + hacky, but we can't put them in `s' since that would be + ambiguous. */ + string type = "text"; + foreach (PathSet::const_iterator, i, references) { + type += ":"; + type += *i; + } + return makeStorePath(type, hash, name); +} + + +/* Return a string accepted by decodeValidPathInfo() that + registers the specified paths as valid. Note: it's the + responsibility of the caller to provide a closure. */ +string StoreAPI::makeValidityRegistration(const PathSet & paths, + bool showDerivers, bool showHash) +{ + string s = ""; + + foreach (PathSet::iterator, i, paths) { + s += *i + "\n"; + + ValidPathInfo info = queryPathInfo(*i); + + if (showHash) { + s += printHash(info.hash) + "\n"; + s += (format("%1%\n") % info.narSize).str(); + } + + Path deriver = showDerivers ? info.deriver : ""; + s += deriver + "\n"; + + s += (format("%1%\n") % info.references.size()).str(); + + foreach (PathSet::iterator, j, info.references) + s += *j + "\n"; + } + + return s; +} + + +ValidPathInfo decodeValidPathInfo(std::istream & str, bool hashGiven) +{ + ValidPathInfo info; + getline(str, info.path); + if (str.eof()) { info.path = ""; return info; } + if (hashGiven) { + string s; + getline(str, s); + info.hash = parseHash(htSHA256, s); + getline(str, s); + if (!string2Int(s, info.narSize)) throw Error("number expected"); + } + getline(str, info.deriver); + string s; int n; + getline(str, s); + if (!string2Int(s, n)) throw Error("number expected"); + while (n--) { + getline(str, s); + info.references.insert(s); + } + if (!str || str.eof()) throw Error("missing input"); + return info; +} + + +string showPaths(const PathSet & paths) +{ + string s; + foreach (PathSet::const_iterator, i, paths) { + if (s.size() != 0) s += ", "; + s += "`" + *i + "'"; + } + return s; +} + + +void exportPaths(StoreAPI & store, const Paths & paths, + bool sign, Sink & sink) +{ + foreach (Paths::const_iterator, i, paths) { + writeInt(1, sink); + store.exportPath(*i, sign, sink); + } + writeInt(0, sink); +} + + +} + + +#include "local-store.hh" +#include "serialise.hh" +#include "remote-store.hh" + + +namespace nix { + + +std::shared_ptr store; + + +std::shared_ptr openStore(bool reserveSpace) +{ + if (getEnv("NIX_REMOTE") == "") + return std::shared_ptr(new LocalStore(reserveSpace)); + else + return std::shared_ptr(new RemoteStore()); +} + + +} diff --git a/nix/libstore/store-api.hh b/nix/libstore/store-api.hh new file mode 100644 index 0000000000..b635fee2cf --- /dev/null +++ b/nix/libstore/store-api.hh @@ -0,0 +1,366 @@ +#pragma once + +#include "hash.hh" +#include "serialise.hh" + +#include +#include +#include + + +namespace nix { + + +typedef std::map Roots; + + +struct GCOptions +{ + /* Garbage collector operation: + + - `gcReturnLive': return the set of paths reachable from + (i.e. in the closure of) the roots. + + - `gcReturnDead': return the set of paths not reachable from + the roots. + + - `gcDeleteDead': actually delete the latter set. + + - `gcDeleteSpecific': delete the paths listed in + `pathsToDelete', insofar as they are not reachable. + */ + typedef enum { + gcReturnLive, + gcReturnDead, + gcDeleteDead, + gcDeleteSpecific, + } GCAction; + + GCAction action; + + /* If `ignoreLiveness' is set, then reachability from the roots is + ignored (dangerous!). However, the paths must still be + unreferenced *within* the store (i.e., there can be no other + store paths that depend on them). */ + bool ignoreLiveness; + + /* For `gcDeleteSpecific', the paths to delete. */ + PathSet pathsToDelete; + + /* Stop after at least `maxFreed' bytes have been freed. */ + unsigned long long maxFreed; + + GCOptions(); +}; + + +struct GCResults +{ + /* Depending on the action, the GC roots, or the paths that would + be or have been deleted. */ + PathSet paths; + + /* For `gcReturnDead', `gcDeleteDead' and `gcDeleteSpecific', the + number of bytes that would be or was freed. */ + unsigned long long bytesFreed; + + GCResults() + { + bytesFreed = 0; + } +}; + + +struct SubstitutablePathInfo +{ + Path deriver; + PathSet references; + unsigned long long downloadSize; /* 0 = unknown or inapplicable */ + unsigned long long narSize; /* 0 = unknown */ +}; + +typedef std::map SubstitutablePathInfos; + + +struct ValidPathInfo +{ + Path path; + Path deriver; + Hash hash; + PathSet references; + time_t registrationTime; + unsigned long long narSize; // 0 = unknown + unsigned long long id; // internal use only + ValidPathInfo() : registrationTime(0), narSize(0) { } +}; + +typedef list ValidPathInfos; + + +enum BuildMode { bmNormal, bmRepair, bmCheck }; + + +class StoreAPI +{ +public: + + virtual ~StoreAPI() { } + + /* Check whether a path is valid. */ + virtual bool isValidPath(const Path & path) = 0; + + /* Query which of the given paths is valid. */ + virtual PathSet queryValidPaths(const PathSet & paths) = 0; + + /* Query the set of all valid paths. */ + virtual PathSet queryAllValidPaths() = 0; + + /* Query information about a valid path. */ + virtual ValidPathInfo queryPathInfo(const Path & path) = 0; + + /* Query the hash of a valid path. */ + virtual Hash queryPathHash(const Path & path) = 0; + + /* Query the set of outgoing FS references for a store path. The + result is not cleared. */ + virtual void queryReferences(const Path & path, + PathSet & references) = 0; + + /* Queries the set of incoming FS references for a store path. + The result is not cleared. */ + virtual void queryReferrers(const Path & path, + PathSet & referrers) = 0; + + /* Query the deriver of a store path. Return the empty string if + no deriver has been set. */ + virtual Path queryDeriver(const Path & path) = 0; + + /* Return all currently valid derivations that have `path' as an + output. (Note that the result of `queryDeriver()' is the + derivation that was actually used to produce `path', which may + not exist anymore.) */ + virtual PathSet queryValidDerivers(const Path & path) = 0; + + /* Query the outputs of the derivation denoted by `path'. */ + virtual PathSet queryDerivationOutputs(const Path & path) = 0; + + /* Query the output names of the derivation denoted by `path'. */ + virtual StringSet queryDerivationOutputNames(const Path & path) = 0; + + /* Query the full store path given the hash part of a valid store + path, or "" if the path doesn't exist. */ + virtual Path queryPathFromHashPart(const string & hashPart) = 0; + + /* Query which of the given paths have substitutes. */ + virtual PathSet querySubstitutablePaths(const PathSet & paths) = 0; + + /* Query substitute info (i.e. references, derivers and download + sizes) of a set of paths. If a path does not have substitute + info, it's omitted from the resulting ‘infos’ map. */ + virtual void querySubstitutablePathInfos(const PathSet & paths, + SubstitutablePathInfos & infos) = 0; + + /* Copy the contents of a path to the store and register the + validity the resulting path. The resulting path is returned. + The function object `filter' can be used to exclude files (see + libutil/archive.hh). */ + virtual Path addToStore(const Path & srcPath, + bool recursive = true, HashType hashAlgo = htSHA256, + PathFilter & filter = defaultPathFilter, bool repair = false) = 0; + + /* Like addToStore, but the contents written to the output path is + a regular file containing the given string. */ + virtual Path addTextToStore(const string & name, const string & s, + const PathSet & references, bool repair = false) = 0; + + /* Export a store path, that is, create a NAR dump of the store + path and append its references and its deriver. Optionally, a + cryptographic signature (created by OpenSSL) of the preceding + data is attached. */ + virtual void exportPath(const Path & path, bool sign, + Sink & sink) = 0; + + /* Import a sequence of NAR dumps created by exportPaths() into + the Nix store. */ + virtual Paths importPaths(bool requireSignature, Source & source) = 0; + + /* For each path, if it's a derivation, build it. Building a + derivation means ensuring that the output paths are valid. If + they are already valid, this is a no-op. Otherwise, validity + can be reached in two ways. First, if the output paths is + substitutable, then build the path that way. Second, the + output paths can be created by running the builder, after + recursively building any sub-derivations. For inputs that are + not derivations, substitute them. */ + virtual void buildPaths(const PathSet & paths, BuildMode buildMode = bmNormal) = 0; + + /* Ensure that a path is valid. If it is not currently valid, it + may be made valid by running a substitute (if defined for the + path). */ + virtual void ensurePath(const Path & path) = 0; + + /* Add a store path as a temporary root of the garbage collector. + The root disappears as soon as we exit. */ + virtual void addTempRoot(const Path & path) = 0; + + /* Add an indirect root, which is merely a symlink to `path' from + /nix/var/nix/gcroots/auto/. `path' is supposed + to be a symlink to a store path. The garbage collector will + automatically remove the indirect root when it finds that + `path' has disappeared. */ + virtual void addIndirectRoot(const Path & path) = 0; + + /* Acquire the global GC lock, then immediately release it. This + function must be called after registering a new permanent root, + but before exiting. Otherwise, it is possible that a running + garbage collector doesn't see the new root and deletes the + stuff we've just built. By acquiring the lock briefly, we + ensure that either: + + - The collector is already running, and so we block until the + collector is finished. The collector will know about our + *temporary* locks, which should include whatever it is we + want to register as a permanent lock. + + - The collector isn't running, or it's just started but hasn't + acquired the GC lock yet. In that case we get and release + the lock right away, then exit. The collector scans the + permanent root and sees our's. + + In either case the permanent root is seen by the collector. */ + virtual void syncWithGC() = 0; + + /* Find the roots of the garbage collector. Each root is a pair + (link, storepath) where `link' is the path of the symlink + outside of the Nix store that point to `storePath'. */ + virtual Roots findRoots() = 0; + + /* Perform a garbage collection. */ + virtual void collectGarbage(const GCOptions & options, GCResults & results) = 0; + + /* Return the set of paths that have failed to build.*/ + virtual PathSet queryFailedPaths() = 0; + + /* Clear the "failed" status of the given paths. The special + value `*' causes all failed paths to be cleared. */ + virtual void clearFailedPaths(const PathSet & paths) = 0; + + /* Return a string representing information about the path that + can be loaded into the database using `nix-store --load-db' or + `nix-store --register-validity'. */ + string makeValidityRegistration(const PathSet & paths, + bool showDerivers, bool showHash); +}; + + +/* !!! These should be part of the store API, I guess. */ + +/* Throw an exception if `path' is not directly in the Nix store. */ +void assertStorePath(const Path & path); + +bool isInStore(const Path & path); +bool isStorePath(const Path & path); + +/* Extract the name part of the given store path. */ +string storePathToName(const Path & path); + +void checkStoreName(const string & name); + + +/* Chop off the parts after the top-level store name, e.g., + /nix/store/abcd-foo/bar => /nix/store/abcd-foo. */ +Path toStorePath(const Path & path); + + +/* Follow symlinks until we end up with a path in the Nix store. */ +Path followLinksToStore(const Path & path); + + +/* Same as followLinksToStore(), but apply toStorePath() to the + result. */ +Path followLinksToStorePath(const Path & path); + + +/* Constructs a unique store path name. */ +Path makeStorePath(const string & type, + const Hash & hash, const string & name); + +Path makeOutputPath(const string & id, + const Hash & hash, const string & name); + +Path makeFixedOutputPath(bool recursive, + HashType hashAlgo, Hash hash, string name); + + +/* This is the preparatory part of addToStore() and addToStoreFixed(); + it computes the store path to which srcPath is to be copied. + Returns the store path and the cryptographic hash of the + contents of srcPath. */ +std::pair computeStorePathForPath(const Path & srcPath, + bool recursive = true, HashType hashAlgo = htSHA256, + PathFilter & filter = defaultPathFilter); + +/* Preparatory part of addTextToStore(). + + !!! Computation of the path should take the references given to + addTextToStore() into account, otherwise we have a (relatively + minor) security hole: a caller can register a source file with + bogus references. If there are too many references, the path may + not be garbage collected when it has to be (not really a problem, + the caller could create a root anyway), or it may be garbage + collected when it shouldn't be (more serious). + + Hashing the references would solve this (bogus references would + simply yield a different store path, so other users wouldn't be + affected), but it has some backwards compatibility issues (the + hashing scheme changes), so I'm not doing that for now. */ +Path computeStorePathForText(const string & name, const string & s, + const PathSet & references); + + +/* Remove the temporary roots file for this process. Any temporary + root becomes garbage after this point unless it has been registered + as a (permanent) root. */ +void removeTempRoots(); + + +/* Register a permanent GC root. */ +Path addPermRoot(StoreAPI & store, const Path & storePath, + const Path & gcRoot, bool indirect, bool allowOutsideRootsDir = false); + + +/* Sort a set of paths topologically under the references relation. + If p refers to q, then p preceeds q in this list. */ +Paths topoSortPaths(StoreAPI & store, const PathSet & paths); + + +/* For now, there is a single global store API object, but we'll + purify that in the future. */ +extern std::shared_ptr store; + + +/* Factory method: open the Nix database, either through the local or + remote implementation. */ +std::shared_ptr openStore(bool reserveSpace = true); + + +/* Display a set of paths in human-readable form (i.e., between quotes + and separated by commas). */ +string showPaths(const PathSet & paths); + + +ValidPathInfo decodeValidPathInfo(std::istream & str, + bool hashGiven = false); + + +/* Export multiple paths in the format expected by ‘nix-store + --import’. */ +void exportPaths(StoreAPI & store, const Paths & paths, + bool sign, Sink & sink); + + +MakeError(SubstError, Error) +MakeError(BuildError, Error) /* denotes a permanent build failure */ + + +} diff --git a/nix/libstore/worker-protocol.hh b/nix/libstore/worker-protocol.hh new file mode 100644 index 0000000000..9317f89c37 --- /dev/null +++ b/nix/libstore/worker-protocol.hh @@ -0,0 +1,60 @@ +#pragma once + +namespace nix { + + +#define WORKER_MAGIC_1 0x6e697863 +#define WORKER_MAGIC_2 0x6478696f + +#define PROTOCOL_VERSION 0x10e +#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00) +#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff) + + +typedef enum { + wopQuit = 0, + wopIsValidPath = 1, + wopHasSubstitutes = 3, + wopQueryPathHash = 4, + wopQueryReferences = 5, + wopQueryReferrers = 6, + wopAddToStore = 7, + wopAddTextToStore = 8, + wopBuildPaths = 9, + wopEnsurePath = 10, + wopAddTempRoot = 11, + wopAddIndirectRoot = 12, + wopSyncWithGC = 13, + wopFindRoots = 14, + wopExportPath = 16, + wopQueryDeriver = 18, + wopSetOptions = 19, + wopCollectGarbage = 20, + wopQuerySubstitutablePathInfo = 21, + wopQueryDerivationOutputs = 22, + wopQueryAllValidPaths = 23, + wopQueryFailedPaths = 24, + wopClearFailedPaths = 25, + wopQueryPathInfo = 26, + wopImportPaths = 27, + wopQueryDerivationOutputNames = 28, + wopQueryPathFromHashPart = 29, + wopQuerySubstitutablePathInfos = 30, + wopQueryValidPaths = 31, + wopQuerySubstitutablePaths = 32, + wopQueryValidDerivers = 33, +} WorkerOp; + + +#define STDERR_NEXT 0x6f6c6d67 +#define STDERR_READ 0x64617461 // data needed from source +#define STDERR_WRITE 0x64617416 // data for sink +#define STDERR_LAST 0x616c7473 +#define STDERR_ERROR 0x63787470 + + +Path readStorePath(Source & from); +template T readStorePaths(Source & from); + + +} diff --git a/nix/libutil/affinity.cc b/nix/libutil/affinity.cc new file mode 100644 index 0000000000..3e21f43a2e --- /dev/null +++ b/nix/libutil/affinity.cc @@ -0,0 +1,55 @@ +#include "types.hh" +#include "util.hh" +#include "affinity.hh" + +#if HAVE_SCHED_H +#include +#endif + +namespace nix { + + +#if HAVE_SCHED_SETAFFINITY +static bool didSaveAffinity = false; +static cpu_set_t savedAffinity; +#endif + + +void setAffinityTo(int cpu) +{ +#if HAVE_SCHED_SETAFFINITY + if (sched_getaffinity(0, sizeof(cpu_set_t), &savedAffinity) == -1) return; + didSaveAffinity = true; + printMsg(lvlDebug, format("locking this thread to CPU %1%") % cpu); + cpu_set_t newAffinity; + CPU_ZERO(&newAffinity); + CPU_SET(cpu, &newAffinity); + if (sched_setaffinity(0, sizeof(cpu_set_t), &newAffinity) == -1) + printMsg(lvlError, format("failed to lock thread to CPU %1%") % cpu); +#endif +} + + +int lockToCurrentCPU() +{ +#if HAVE_SCHED_SETAFFINITY + int cpu = sched_getcpu(); + if (cpu != -1) setAffinityTo(cpu); + return cpu; +#else + return -1; +#endif +} + + +void restoreAffinity() +{ +#if HAVE_SCHED_SETAFFINITY + if (!didSaveAffinity) return; + if (sched_setaffinity(0, sizeof(cpu_set_t), &savedAffinity) == -1) + printMsg(lvlError, "failed to restore affinity %1%"); +#endif +} + + +} diff --git a/nix/libutil/affinity.hh b/nix/libutil/affinity.hh new file mode 100644 index 0000000000..c1bd28e136 --- /dev/null +++ b/nix/libutil/affinity.hh @@ -0,0 +1,9 @@ +#pragma once + +namespace nix { + +void setAffinityTo(int cpu); +int lockToCurrentCPU(); +void restoreAffinity(); + +} diff --git a/nix/libutil/archive.cc b/nix/libutil/archive.cc new file mode 100644 index 0000000000..ab4cd47351 --- /dev/null +++ b/nix/libutil/archive.cc @@ -0,0 +1,335 @@ +#include "config.h" + +#include +#include +#include + +#define _XOPEN_SOURCE 600 +#include +#include +#include +#include +#include + +#include "archive.hh" +#include "util.hh" + + +namespace nix { + + +static string archiveVersion1 = "nix-archive-1"; + + +PathFilter defaultPathFilter; + + +static void dump(const string & path, Sink & sink, PathFilter & filter); + + +static void dumpEntries(const Path & path, Sink & sink, PathFilter & filter) +{ + Strings names = readDirectory(path); + vector names2(names.begin(), names.end()); + sort(names2.begin(), names2.end()); + + for (vector::iterator i = names2.begin(); + i != names2.end(); ++i) + { + Path entry = path + "/" + *i; + if (filter(entry)) { + writeString("entry", sink); + writeString("(", sink); + writeString("name", sink); + writeString(*i, sink); + writeString("node", sink); + dump(entry, sink, filter); + writeString(")", sink); + } + } +} + + +static void dumpContents(const Path & path, size_t size, + Sink & sink) +{ + writeString("contents", sink); + writeLongLong(size, sink); + + AutoCloseFD fd = open(path.c_str(), O_RDONLY); + if (fd == -1) throw SysError(format("opening file `%1%'") % path); + + unsigned char buf[65536]; + size_t left = size; + + while (left > 0) { + size_t n = left > sizeof(buf) ? sizeof(buf) : left; + readFull(fd, buf, n); + left -= n; + sink(buf, n); + } + + writePadding(size, sink); +} + + +static void dump(const Path & path, Sink & sink, PathFilter & filter) +{ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path `%1%'") % path); + + writeString("(", sink); + + if (S_ISREG(st.st_mode)) { + writeString("type", sink); + writeString("regular", sink); + if (st.st_mode & S_IXUSR) { + writeString("executable", sink); + writeString("", sink); + } + dumpContents(path, (size_t) st.st_size, sink); + } + + else if (S_ISDIR(st.st_mode)) { + writeString("type", sink); + writeString("directory", sink); + dumpEntries(path, sink, filter); + } + + else if (S_ISLNK(st.st_mode)) { + writeString("type", sink); + writeString("symlink", sink); + writeString("target", sink); + writeString(readLink(path), sink); + } + + else throw Error(format("file `%1%' has an unknown type") % path); + + writeString(")", sink); +} + + +void dumpPath(const Path & path, Sink & sink, PathFilter & filter) +{ + writeString(archiveVersion1, sink); + dump(path, sink, filter); +} + + +static SerialisationError badArchive(string s) +{ + return SerialisationError("bad archive: " + s); +} + + +static void skipGeneric(Source & source) +{ + if (readString(source) == "(") { + while (readString(source) != ")") + skipGeneric(source); + } +} + + +static void parse(ParseSink & sink, Source & source, const Path & path); + + + +static void parseEntry(ParseSink & sink, Source & source, const Path & path) +{ + string s, name; + + s = readString(source); + if (s != "(") throw badArchive("expected open tag"); + + while (1) { + checkInterrupt(); + + s = readString(source); + + if (s == ")") { + break; + } else if (s == "name") { + name = readString(source); + } else if (s == "node") { + if (s == "") throw badArchive("entry name missing"); + parse(sink, source, path + "/" + name); + } else { + throw badArchive("unknown field " + s); + skipGeneric(source); + } + } +} + + +static void parseContents(ParseSink & sink, Source & source, const Path & path) +{ + unsigned long long size = readLongLong(source); + + sink.preallocateContents(size); + + unsigned long long left = size; + unsigned char buf[65536]; + + while (left) { + checkInterrupt(); + unsigned int n = sizeof(buf); + if ((unsigned long long) n > left) n = left; + source(buf, n); + sink.receiveContents(buf, n); + left -= n; + } + + readPadding(size, source); +} + + +static void parse(ParseSink & sink, Source & source, const Path & path) +{ + string s; + + s = readString(source); + if (s != "(") throw badArchive("expected open tag"); + + enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown; + + while (1) { + checkInterrupt(); + + s = readString(source); + + if (s == ")") { + break; + } + + else if (s == "type") { + if (type != tpUnknown) + throw badArchive("multiple type fields"); + string t = readString(source); + + if (t == "regular") { + type = tpRegular; + sink.createRegularFile(path); + } + + else if (t == "directory") { + sink.createDirectory(path); + type = tpDirectory; + } + + else if (t == "symlink") { + type = tpSymlink; + } + + else throw badArchive("unknown file type " + t); + + } + + else if (s == "contents" && type == tpRegular) { + parseContents(sink, source, path); + } + + else if (s == "executable" && type == tpRegular) { + readString(source); + sink.isExecutable(); + } + + else if (s == "entry" && type == tpDirectory) { + parseEntry(sink, source, path); + } + + else if (s == "target" && type == tpSymlink) { + string target = readString(source); + sink.createSymlink(path, target); + } + + else { + throw badArchive("unknown field " + s); + skipGeneric(source); + } + } +} + + +void parseDump(ParseSink & sink, Source & source) +{ + string version; + try { + version = readString(source); + } catch (SerialisationError & e) { + /* This generally means the integer at the start couldn't be + decoded. Ignore and throw the exception below. */ + } + if (version != archiveVersion1) + throw badArchive("input doesn't look like a Nix archive"); + parse(sink, source, ""); +} + + +struct RestoreSink : ParseSink +{ + Path dstPath; + AutoCloseFD fd; + + void createDirectory(const Path & path) + { + Path p = dstPath + path; + if (mkdir(p.c_str(), 0777) == -1) + throw SysError(format("creating directory `%1%'") % p); + }; + + void createRegularFile(const Path & path) + { + Path p = dstPath + path; + fd.close(); + fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd == -1) throw SysError(format("creating file `%1%'") % p); + } + + void isExecutable() + { + struct stat st; + if (fstat(fd, &st) == -1) + throw SysError("fstat"); + if (fchmod(fd, st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1) + throw SysError("fchmod"); + } + + void preallocateContents(unsigned long long len) + { +#if HAVE_POSIX_FALLOCATE + if (len) { + errno = posix_fallocate(fd, 0, len); + /* Note that EINVAL may indicate that the underlying + filesystem doesn't support preallocation (e.g. on + OpenSolaris). Since preallocation is just an + optimisation, ignore it. */ + if (errno && errno != EINVAL) + throw SysError(format("preallocating file of %1% bytes") % len); + } +#endif + } + + void receiveContents(unsigned char * data, unsigned int len) + { + writeFull(fd, data, len); + } + + void createSymlink(const Path & path, const string & target) + { + Path p = dstPath + path; + nix::createSymlink(target, p); + } +}; + + +void restorePath(const Path & path, Source & source) +{ + RestoreSink sink; + sink.dstPath = path; + parseDump(sink, source); +} + + +} diff --git a/nix/libutil/archive.hh b/nix/libutil/archive.hh new file mode 100644 index 0000000000..ccac92074d --- /dev/null +++ b/nix/libutil/archive.hh @@ -0,0 +1,75 @@ +#pragma once + +#include "types.hh" +#include "serialise.hh" + + +namespace nix { + + +/* dumpPath creates a Nix archive of the specified path. The format + is as follows: + + IF path points to a REGULAR FILE: + dump(path) = attrs( + [ ("type", "regular") + , ("contents", contents(path)) + ]) + + IF path points to a DIRECTORY: + dump(path) = attrs( + [ ("type", "directory") + , ("entries", concat(map(f, sort(entries(path))))) + ]) + where f(fn) = attrs( + [ ("name", fn) + , ("file", dump(path + "/" + fn)) + ]) + + where: + + attrs(as) = concat(map(attr, as)) + encN(0) + attrs((a, b)) = encS(a) + encS(b) + + encS(s) = encN(len(s)) + s + (padding until next 64-bit boundary) + + encN(n) = 64-bit little-endian encoding of n. + + contents(path) = the contents of a regular file. + + sort(strings) = lexicographic sort by 8-bit value (strcmp). + + entries(path) = the entries of a directory, without `.' and + `..'. + + `+' denotes string concatenation. */ + +struct PathFilter +{ + virtual ~PathFilter() { } + virtual bool operator () (const Path & path) { return true; } +}; + +extern PathFilter defaultPathFilter; + +void dumpPath(const Path & path, Sink & sink, + PathFilter & filter = defaultPathFilter); + +struct ParseSink +{ + virtual void createDirectory(const Path & path) { }; + + virtual void createRegularFile(const Path & path) { }; + virtual void isExecutable() { }; + virtual void preallocateContents(unsigned long long size) { }; + virtual void receiveContents(unsigned char * data, unsigned int len) { }; + + virtual void createSymlink(const Path & path, const string & target) { }; +}; + +void parseDump(ParseSink & sink, Source & source); + +void restorePath(const Path & path, Source & source); + + +} diff --git a/nix/libutil/hash.cc b/nix/libutil/hash.cc new file mode 100644 index 0000000000..050446610f --- /dev/null +++ b/nix/libutil/hash.cc @@ -0,0 +1,382 @@ +#include "config.h" + +#include +#include + +#ifdef HAVE_OPENSSL +#include +#include +#else +extern "C" { +#include "md5.h" +#include "sha1.h" +#include "sha256.h" +} +#endif + +#include "hash.hh" +#include "archive.hh" +#include "util.hh" + +#include +#include +#include + + +namespace nix { + + +Hash::Hash() +{ + type = htUnknown; + hashSize = 0; + memset(hash, 0, maxHashSize); +} + + +Hash::Hash(HashType type) +{ + this->type = type; + if (type == htMD5) hashSize = md5HashSize; + else if (type == htSHA1) hashSize = sha1HashSize; + else if (type == htSHA256) hashSize = sha256HashSize; + else throw Error("unknown hash type"); + assert(hashSize <= maxHashSize); + memset(hash, 0, maxHashSize); +} + + +bool Hash::operator == (const Hash & h2) const +{ + if (hashSize != h2.hashSize) return false; + for (unsigned int i = 0; i < hashSize; i++) + if (hash[i] != h2.hash[i]) return false; + return true; +} + + +bool Hash::operator != (const Hash & h2) const +{ + return !(*this == h2); +} + + +bool Hash::operator < (const Hash & h) const +{ + for (unsigned int i = 0; i < hashSize; i++) { + if (hash[i] < h.hash[i]) return true; + if (hash[i] > h.hash[i]) return false; + } + return false; +} + + +const string base16Chars = "0123456789abcdef"; + + +string printHash(const Hash & hash) +{ + char buf[hash.hashSize * 2]; + for (unsigned int i = 0; i < hash.hashSize; i++) { + buf[i * 2] = base16Chars[hash.hash[i] >> 4]; + buf[i * 2 + 1] = base16Chars[hash.hash[i] & 0x0f]; + } + return string(buf, hash.hashSize * 2); +} + + +Hash parseHash(HashType ht, const string & s) +{ + Hash hash(ht); + if (s.length() != hash.hashSize * 2) + throw Error(format("invalid hash `%1%'") % s); + for (unsigned int i = 0; i < hash.hashSize; i++) { + string s2(s, i * 2, 2); + if (!isxdigit(s2[0]) || !isxdigit(s2[1])) + throw Error(format("invalid hash `%1%'") % s); + std::istringstream str(s2); + int n; + str >> std::hex >> n; + hash.hash[i] = n; + } + return hash; +} + + +static unsigned char divMod(unsigned char * bytes, unsigned char y) +{ + unsigned int borrow = 0; + + int pos = Hash::maxHashSize - 1; + while (pos >= 0 && !bytes[pos]) --pos; + + for ( ; pos >= 0; --pos) { + unsigned int s = bytes[pos] + (borrow << 8); + unsigned int d = s / y; + borrow = s % y; + bytes[pos] = d; + } + + return borrow; +} + + +unsigned int hashLength32(const Hash & hash) +{ + return (hash.hashSize * 8 - 1) / 5 + 1; +} + + +// omitted: E O U T +const string base32Chars = "0123456789abcdfghijklmnpqrsvwxyz"; + + +string printHash32(const Hash & hash) +{ + Hash hash2(hash); + unsigned int len = hashLength32(hash); + + const char * chars = base32Chars.data(); + + string s(len, '0'); + + int pos = len - 1; + while (pos >= 0) { + unsigned char digit = divMod(hash2.hash, 32); + s[pos--] = chars[digit]; + } + + for (unsigned int i = 0; i < hash2.maxHashSize; ++i) + assert(hash2.hash[i] == 0); + + return s; +} + + +string printHash16or32(const Hash & hash) +{ + return hash.type == htMD5 ? printHash(hash) : printHash32(hash); +} + + +static bool mul(unsigned char * bytes, unsigned char y, int maxSize) +{ + unsigned char carry = 0; + + for (int pos = 0; pos < maxSize; ++pos) { + unsigned int m = bytes[pos] * y + carry; + bytes[pos] = m & 0xff; + carry = m >> 8; + } + + return carry; +} + + +static bool add(unsigned char * bytes, unsigned char y, int maxSize) +{ + unsigned char carry = y; + + for (int pos = 0; pos < maxSize; ++pos) { + unsigned int m = bytes[pos] + carry; + bytes[pos] = m & 0xff; + carry = m >> 8; + if (carry == 0) break; + } + + return carry; +} + + +Hash parseHash32(HashType ht, const string & s) +{ + Hash hash(ht); + + const char * chars = base32Chars.data(); + + for (unsigned int i = 0; i < s.length(); ++i) { + char c = s[i]; + unsigned char digit; + for (digit = 0; digit < base32Chars.size(); ++digit) /* !!! slow */ + if (chars[digit] == c) break; + if (digit >= 32) + throw Error(format("invalid base-32 hash `%1%'") % s); + if (mul(hash.hash, 32, hash.hashSize) || + add(hash.hash, digit, hash.hashSize)) + throw Error(format("base-32 hash `%1%' is too large") % s); + } + + return hash; +} + + +Hash parseHash16or32(HashType ht, const string & s) +{ + Hash hash(ht); + if (s.size() == hash.hashSize * 2) + /* hexadecimal representation */ + hash = parseHash(ht, s); + else if (s.size() == hashLength32(hash)) + /* base-32 representation */ + hash = parseHash32(ht, s); + else + throw Error(format("hash `%1%' has wrong length for hash type `%2%'") + % s % printHashType(ht)); + return hash; +} + + +bool isHash(const string & s) +{ + if (s.length() != 32) return false; + for (int i = 0; i < 32; i++) { + char c = s[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f'))) + return false; + } + return true; +} + + +struct Ctx +{ + MD5_CTX md5; + SHA_CTX sha1; + SHA256_CTX sha256; +}; + + +static void start(HashType ht, Ctx & ctx) +{ + if (ht == htMD5) MD5_Init(&ctx.md5); + else if (ht == htSHA1) SHA1_Init(&ctx.sha1); + else if (ht == htSHA256) SHA256_Init(&ctx.sha256); +} + + +static void update(HashType ht, Ctx & ctx, + const unsigned char * bytes, unsigned int len) +{ + if (ht == htMD5) MD5_Update(&ctx.md5, bytes, len); + else if (ht == htSHA1) SHA1_Update(&ctx.sha1, bytes, len); + else if (ht == htSHA256) SHA256_Update(&ctx.sha256, bytes, len); +} + + +static void finish(HashType ht, Ctx & ctx, unsigned char * hash) +{ + if (ht == htMD5) MD5_Final(hash, &ctx.md5); + else if (ht == htSHA1) SHA1_Final(hash, &ctx.sha1); + else if (ht == htSHA256) SHA256_Final(hash, &ctx.sha256); +} + + +Hash hashString(HashType ht, const string & s) +{ + Ctx ctx; + Hash hash(ht); + start(ht, ctx); + update(ht, ctx, (const unsigned char *) s.data(), s.length()); + finish(ht, ctx, hash.hash); + return hash; +} + + +Hash hashFile(HashType ht, const Path & path) +{ + Ctx ctx; + Hash hash(ht); + start(ht, ctx); + + AutoCloseFD fd = open(path.c_str(), O_RDONLY); + if (fd == -1) throw SysError(format("opening file `%1%'") % path); + + unsigned char buf[8192]; + ssize_t n; + while ((n = read(fd, buf, sizeof(buf)))) { + checkInterrupt(); + if (n == -1) throw SysError(format("reading file `%1%'") % path); + update(ht, ctx, buf, n); + } + + finish(ht, ctx, hash.hash); + return hash; +} + + +HashSink::HashSink(HashType ht) : ht(ht) +{ + ctx = new Ctx; + bytes = 0; + start(ht, *ctx); +} + +HashSink::~HashSink() +{ + bufPos = 0; + delete ctx; +} + +void HashSink::write(const unsigned char * data, size_t len) +{ + bytes += len; + update(ht, *ctx, data, len); +} + +HashResult HashSink::finish() +{ + flush(); + Hash hash(ht); + nix::finish(ht, *ctx, hash.hash); + return HashResult(hash, bytes); +} + +HashResult HashSink::currentHash() +{ + flush(); + Ctx ctx2 = *ctx; + Hash hash(ht); + nix::finish(ht, ctx2, hash.hash); + return HashResult(hash, bytes); +} + + +HashResult hashPath( + HashType ht, const Path & path, PathFilter & filter) +{ + HashSink sink(ht); + dumpPath(path, sink, filter); + return sink.finish(); +} + + +Hash compressHash(const Hash & hash, unsigned int newSize) +{ + Hash h; + h.hashSize = newSize; + for (unsigned int i = 0; i < hash.hashSize; ++i) + h.hash[i % newSize] ^= hash.hash[i]; + return h; +} + + +HashType parseHashType(const string & s) +{ + if (s == "md5") return htMD5; + else if (s == "sha1") return htSHA1; + else if (s == "sha256") return htSHA256; + else return htUnknown; +} + + +string printHashType(HashType ht) +{ + if (ht == htMD5) return "md5"; + else if (ht == htSHA1) return "sha1"; + else if (ht == htSHA256) return "sha256"; + else throw Error("cannot print unknown hash type"); +} + + +} diff --git a/nix/libutil/hash.hh b/nix/libutil/hash.hh new file mode 100644 index 0000000000..8f099c4f07 --- /dev/null +++ b/nix/libutil/hash.hh @@ -0,0 +1,113 @@ +#pragma once + +#include "types.hh" +#include "serialise.hh" + + +namespace nix { + + +typedef enum { htUnknown, htMD5, htSHA1, htSHA256 } HashType; + + +const int md5HashSize = 16; +const int sha1HashSize = 20; +const int sha256HashSize = 32; + +extern const string base32Chars; + + +struct Hash +{ + static const unsigned int maxHashSize = 32; + unsigned int hashSize; + unsigned char hash[maxHashSize]; + + HashType type; + + /* Create an unusable hash object. */ + Hash(); + + /* Create a zero-filled hash object. */ + Hash(HashType type); + + /* Check whether two hash are equal. */ + bool operator == (const Hash & h2) const; + + /* Check whether two hash are not equal. */ + bool operator != (const Hash & h2) const; + + /* For sorting. */ + bool operator < (const Hash & h) const; +}; + + +/* Convert a hash to a hexadecimal representation. */ +string printHash(const Hash & hash); + +/* Parse a hexadecimal representation of a hash code. */ +Hash parseHash(HashType ht, const string & s); + +/* Returns the length of a base-32 hash representation. */ +unsigned int hashLength32(const Hash & hash); + +/* Convert a hash to a base-32 representation. */ +string printHash32(const Hash & hash); + +/* Print a hash in base-16 if it's MD5, or base-32 otherwise. */ +string printHash16or32(const Hash & hash); + +/* Parse a base-32 representation of a hash code. */ +Hash parseHash32(HashType ht, const string & s); + +/* Parse a base-16 or base-32 representation of a hash code. */ +Hash parseHash16or32(HashType ht, const string & s); + +/* Verify that the given string is a valid hash code. */ +bool isHash(const string & s); + +/* Compute the hash of the given string. */ +Hash hashString(HashType ht, const string & s); + +/* Compute the hash of the given file. */ +Hash hashFile(HashType ht, const Path & path); + +/* Compute the hash of the given path. The hash is defined as + (essentially) hashString(ht, dumpPath(path)). */ +struct PathFilter; +extern PathFilter defaultPathFilter; +typedef std::pair HashResult; +HashResult hashPath(HashType ht, const Path & path, + PathFilter & filter = defaultPathFilter); + +/* Compress a hash to the specified number of bytes by cyclically + XORing bytes together. */ +Hash compressHash(const Hash & hash, unsigned int newSize); + +/* Parse a string representing a hash type. */ +HashType parseHashType(const string & s); + +/* And the reverse. */ +string printHashType(HashType ht); + + +struct Ctx; + +class HashSink : public BufferedSink +{ +private: + HashType ht; + Ctx * ctx; + unsigned long long bytes; + +public: + HashSink(HashType ht); + HashSink(const HashSink & h); + ~HashSink(); + void write(const unsigned char * data, size_t len); + HashResult finish(); + HashResult currentHash(); +}; + + +} diff --git a/nix/libutil/serialise.cc b/nix/libutil/serialise.cc new file mode 100644 index 0000000000..6b71f52c15 --- /dev/null +++ b/nix/libutil/serialise.cc @@ -0,0 +1,259 @@ +#include "serialise.hh" +#include "util.hh" + +#include +#include + + +namespace nix { + + +BufferedSink::~BufferedSink() +{ + /* We can't call flush() here, because C++ for some insane reason + doesn't allow you to call virtual methods from a destructor. */ + assert(!bufPos); + delete[] buffer; +} + + +void BufferedSink::operator () (const unsigned char * data, size_t len) +{ + if (!buffer) buffer = new unsigned char[bufSize]; + + while (len) { + /* Optimisation: bypass the buffer if the data exceeds the + buffer size. */ + if (bufPos + len >= bufSize) { + flush(); + write(data, len); + break; + } + /* Otherwise, copy the bytes to the buffer. Flush the buffer + when it's full. */ + size_t n = bufPos + len > bufSize ? bufSize - bufPos : len; + memcpy(buffer + bufPos, data, n); + data += n; bufPos += n; len -= n; + if (bufPos == bufSize) flush(); + } +} + + +void BufferedSink::flush() +{ + if (bufPos == 0) return; + size_t n = bufPos; + bufPos = 0; // don't trigger the assert() in ~BufferedSink() + write(buffer, n); +} + + +FdSink::~FdSink() +{ + try { flush(); } catch (...) { ignoreException(); } +} + + +void FdSink::write(const unsigned char * data, size_t len) +{ + writeFull(fd, data, len); +} + + +void Source::operator () (unsigned char * data, size_t len) +{ + while (len) { + size_t n = read(data, len); + data += n; len -= n; + } +} + + +BufferedSource::~BufferedSource() +{ + delete[] buffer; +} + + +size_t BufferedSource::read(unsigned char * data, size_t len) +{ + if (!buffer) buffer = new unsigned char[bufSize]; + + if (!bufPosIn) bufPosIn = readUnbuffered(buffer, bufSize); + + /* Copy out the data in the buffer. */ + size_t n = len > bufPosIn - bufPosOut ? bufPosIn - bufPosOut : len; + memcpy(data, buffer + bufPosOut, n); + bufPosOut += n; + if (bufPosIn == bufPosOut) bufPosIn = bufPosOut = 0; + return n; +} + + +bool BufferedSource::hasData() +{ + return bufPosOut < bufPosIn; +} + + +size_t FdSource::readUnbuffered(unsigned char * data, size_t len) +{ + ssize_t n; + do { + checkInterrupt(); + n = ::read(fd, (char *) data, bufSize); + } while (n == -1 && errno == EINTR); + if (n == -1) throw SysError("reading from file"); + if (n == 0) throw EndOfFile("unexpected end-of-file"); + return n; +} + + +size_t StringSource::read(unsigned char * data, size_t len) +{ + if (pos == s.size()) throw EndOfFile("end of string reached"); + size_t n = s.copy((char *) data, len, pos); + pos += n; + return n; +} + + +void writePadding(size_t len, Sink & sink) +{ + if (len % 8) { + unsigned char zero[8]; + memset(zero, 0, sizeof(zero)); + sink(zero, 8 - (len % 8)); + } +} + + +void writeInt(unsigned int n, Sink & sink) +{ + unsigned char buf[8]; + memset(buf, 0, sizeof(buf)); + buf[0] = n & 0xff; + buf[1] = (n >> 8) & 0xff; + buf[2] = (n >> 16) & 0xff; + buf[3] = (n >> 24) & 0xff; + sink(buf, sizeof(buf)); +} + + +void writeLongLong(unsigned long long n, Sink & sink) +{ + unsigned char buf[8]; + buf[0] = n & 0xff; + buf[1] = (n >> 8) & 0xff; + buf[2] = (n >> 16) & 0xff; + buf[3] = (n >> 24) & 0xff; + buf[4] = (n >> 32) & 0xff; + buf[5] = (n >> 40) & 0xff; + buf[6] = (n >> 48) & 0xff; + buf[7] = (n >> 56) & 0xff; + sink(buf, sizeof(buf)); +} + + +void writeString(const unsigned char * buf, size_t len, Sink & sink) +{ + writeInt(len, sink); + sink(buf, len); + writePadding(len, sink); +} + + +void writeString(const string & s, Sink & sink) +{ + writeString((const unsigned char *) s.data(), s.size(), sink); +} + + +template void writeStrings(const T & ss, Sink & sink) +{ + writeInt(ss.size(), sink); + foreach (typename T::const_iterator, i, ss) + writeString(*i, sink); +} + +template void writeStrings(const Paths & ss, Sink & sink); +template void writeStrings(const PathSet & ss, Sink & sink); + + +void readPadding(size_t len, Source & source) +{ + if (len % 8) { + unsigned char zero[8]; + size_t n = 8 - (len % 8); + source(zero, n); + for (unsigned int i = 0; i < n; i++) + if (zero[i]) throw SerialisationError("non-zero padding"); + } +} + + +unsigned int readInt(Source & source) +{ + unsigned char buf[8]; + source(buf, sizeof(buf)); + if (buf[4] || buf[5] || buf[6] || buf[7]) + throw SerialisationError("implementation cannot deal with > 32-bit integers"); + return + buf[0] | + (buf[1] << 8) | + (buf[2] << 16) | + (buf[3] << 24); +} + + +unsigned long long readLongLong(Source & source) +{ + unsigned char buf[8]; + source(buf, sizeof(buf)); + return + ((unsigned long long) buf[0]) | + ((unsigned long long) buf[1] << 8) | + ((unsigned long long) buf[2] << 16) | + ((unsigned long long) buf[3] << 24) | + ((unsigned long long) buf[4] << 32) | + ((unsigned long long) buf[5] << 40) | + ((unsigned long long) buf[6] << 48) | + ((unsigned long long) buf[7] << 56); +} + + +size_t readString(unsigned char * buf, size_t max, Source & source) +{ + size_t len = readInt(source); + if (len > max) throw Error("string is too long"); + source(buf, len); + readPadding(len, source); + return len; +} + + +string readString(Source & source) +{ + size_t len = readInt(source); + unsigned char * buf = new unsigned char[len]; + AutoDeleteArray d(buf); + source(buf, len); + readPadding(len, source); + return string((char *) buf, len); +} + + +template T readStrings(Source & source) +{ + unsigned int count = readInt(source); + T ss; + while (count--) + ss.insert(ss.end(), readString(source)); + return ss; +} + +template Paths readStrings(Source & source); +template PathSet readStrings(Source & source); + + +} diff --git a/nix/libutil/serialise.hh b/nix/libutil/serialise.hh new file mode 100644 index 0000000000..e5a9df1d05 --- /dev/null +++ b/nix/libutil/serialise.hh @@ -0,0 +1,133 @@ +#pragma once + +#include "types.hh" + + +namespace nix { + + +/* Abstract destination of binary data. */ +struct Sink +{ + virtual ~Sink() { } + virtual void operator () (const unsigned char * data, size_t len) = 0; +}; + + +/* A buffered abstract sink. */ +struct BufferedSink : Sink +{ + size_t bufSize, bufPos; + unsigned char * buffer; + + BufferedSink(size_t bufSize = 32 * 1024) + : bufSize(bufSize), bufPos(0), buffer(0) { } + ~BufferedSink(); + + void operator () (const unsigned char * data, size_t len); + + void flush(); + + virtual void write(const unsigned char * data, size_t len) = 0; +}; + + +/* Abstract source of binary data. */ +struct Source +{ + virtual ~Source() { } + + /* Store exactly ‘len’ bytes in the buffer pointed to by ‘data’. + It blocks until all the requested data is available, or throws + an error if it is not going to be available. */ + void operator () (unsigned char * data, size_t len); + + /* Store up to ‘len’ in the buffer pointed to by ‘data’, and + return the number of bytes stored. If blocks until at least + one byte is available. */ + virtual size_t read(unsigned char * data, size_t len) = 0; +}; + + +/* A buffered abstract source. */ +struct BufferedSource : Source +{ + size_t bufSize, bufPosIn, bufPosOut; + unsigned char * buffer; + + BufferedSource(size_t bufSize = 32 * 1024) + : bufSize(bufSize), bufPosIn(0), bufPosOut(0), buffer(0) { } + ~BufferedSource(); + + size_t read(unsigned char * data, size_t len); + + /* Underlying read call, to be overridden. */ + virtual size_t readUnbuffered(unsigned char * data, size_t len) = 0; + + bool hasData(); +}; + + +/* A sink that writes data to a file descriptor. */ +struct FdSink : BufferedSink +{ + int fd; + + FdSink() : fd(-1) { } + FdSink(int fd) : fd(fd) { } + ~FdSink(); + + void write(const unsigned char * data, size_t len); +}; + + +/* A source that reads data from a file descriptor. */ +struct FdSource : BufferedSource +{ + int fd; + FdSource() : fd(-1) { } + FdSource(int fd) : fd(fd) { } + size_t readUnbuffered(unsigned char * data, size_t len); +}; + + +/* A sink that writes data to a string. */ +struct StringSink : Sink +{ + string s; + void operator () (const unsigned char * data, size_t len) + { + s.append((const char *) data, len); + } +}; + + +/* A source that reads data from a string. */ +struct StringSource : Source +{ + const string & s; + size_t pos; + StringSource(const string & _s) : s(_s), pos(0) { } + size_t read(unsigned char * data, size_t len); +}; + + +void writePadding(size_t len, Sink & sink); +void writeInt(unsigned int n, Sink & sink); +void writeLongLong(unsigned long long n, Sink & sink); +void writeString(const unsigned char * buf, size_t len, Sink & sink); +void writeString(const string & s, Sink & sink); +template void writeStrings(const T & ss, Sink & sink); + +void readPadding(size_t len, Source & source); +unsigned int readInt(Source & source); +unsigned long long readLongLong(Source & source); +size_t readString(unsigned char * buf, size_t max, Source & source); +string readString(Source & source); +template T readStrings(Source & source); + + +MakeError(SerialisationError, Error) + + +} diff --git a/nix/libutil/types.hh b/nix/libutil/types.hh new file mode 100644 index 0000000000..4b5ce9a78c --- /dev/null +++ b/nix/libutil/types.hh @@ -0,0 +1,86 @@ +#pragma once + +#include "config.h" + +#include +#include +#include + +#include + + +namespace nix { + + +/* Inherit some names from other namespaces for convenience. */ +using std::string; +using std::list; +using std::set; +using std::vector; +using boost::format; + + +struct FormatOrString +{ + string s; + FormatOrString(const string & s) : s(s) { }; + FormatOrString(const format & f) : s(f.str()) { }; + FormatOrString(const char * s) : s(s) { }; +}; + + +/* BaseError should generally not be caught, as it has Interrupted as + a subclass. Catch Error instead. */ +class BaseError : public std::exception +{ +protected: + string prefix_; // used for location traces etc. + string err; +public: + unsigned int status; // exit status + BaseError(const FormatOrString & fs, unsigned int status = 1); + ~BaseError() throw () { }; + const char * what() const throw () { return err.c_str(); } + const string & msg() const throw () { return err; } + const string & prefix() const throw () { return prefix_; } + BaseError & addPrefix(const FormatOrString & fs); +}; + +#define MakeError(newClass, superClass) \ + class newClass : public superClass \ + { \ + public: \ + newClass(const FormatOrString & fs, unsigned int status = 1) : superClass(fs, status) { }; \ + }; + +MakeError(Error, BaseError) + +class SysError : public Error +{ +public: + int errNo; + SysError(const FormatOrString & fs); +}; + + +typedef list Strings; +typedef set StringSet; + + +/* Paths are just strings. */ +typedef string Path; +typedef list Paths; +typedef set PathSet; + + +typedef enum { + lvlError = 0, + lvlInfo, + lvlTalkative, + lvlChatty, + lvlDebug, + lvlVomit +} Verbosity; + + +} diff --git a/nix/libutil/util.cc b/nix/libutil/util.cc new file mode 100644 index 0000000000..15c462ce4e --- /dev/null +++ b/nix/libutil/util.cc @@ -0,0 +1,1105 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#endif + +#include "util.hh" + + +extern char * * environ; + + +namespace nix { + + +BaseError::BaseError(const FormatOrString & fs, unsigned int status) + : status(status) +{ + err = fs.s; +} + + +BaseError & BaseError::addPrefix(const FormatOrString & fs) +{ + prefix_ = fs.s + prefix_; + return *this; +} + + +SysError::SysError(const FormatOrString & fs) + : Error(format("%1%: %2%") % fs.s % strerror(errno)) + , errNo(errno) +{ +} + + +string getEnv(const string & key, const string & def) +{ + char * value = getenv(key.c_str()); + return value ? string(value) : def; +} + + +Path absPath(Path path, Path dir) +{ + if (path[0] != '/') { + if (dir == "") { +#ifdef __GNU__ + /* GNU (aka. GNU/Hurd) doesn't have any limitation on path + lengths and doesn't define `PATH_MAX'. */ + char *buf = getcwd(NULL, 0); + if (buf == NULL) +#else + char buf[PATH_MAX]; + if (!getcwd(buf, sizeof(buf))) +#endif + throw SysError("cannot get cwd"); + dir = buf; +#ifdef __GNU__ + free(buf); +#endif + } + path = dir + "/" + path; + } + return canonPath(path); +} + + +Path canonPath(const Path & path, bool resolveSymlinks) +{ + string s; + + if (path[0] != '/') + throw Error(format("not an absolute path: `%1%'") % path); + + string::const_iterator i = path.begin(), end = path.end(); + string temp; + + /* Count the number of times we follow a symlink and stop at some + arbitrary (but high) limit to prevent infinite loops. */ + unsigned int followCount = 0, maxFollow = 1024; + + while (1) { + + /* Skip slashes. */ + while (i != end && *i == '/') i++; + if (i == end) break; + + /* Ignore `.'. */ + if (*i == '.' && (i + 1 == end || i[1] == '/')) + i++; + + /* If `..', delete the last component. */ + else if (*i == '.' && i + 1 < end && i[1] == '.' && + (i + 2 == end || i[2] == '/')) + { + if (!s.empty()) s.erase(s.rfind('/')); + i += 2; + } + + /* Normal component; copy it. */ + else { + s += '/'; + while (i != end && *i != '/') s += *i++; + + /* If s points to a symlink, resolve it and restart (since + the symlink target might contain new symlinks). */ + if (resolveSymlinks && isLink(s)) { + if (++followCount >= maxFollow) + throw Error(format("infinite symlink recursion in path `%1%'") % path); + temp = absPath(readLink(s), dirOf(s)) + + string(i, end); + i = temp.begin(); /* restart */ + end = temp.end(); + s = ""; + /* !!! potential for infinite loop */ + } + } + } + + return s.empty() ? "/" : s; +} + + +Path dirOf(const Path & path) +{ + Path::size_type pos = path.rfind('/'); + if (pos == string::npos) + throw Error(format("invalid file name `%1%'") % path); + return pos == 0 ? "/" : Path(path, 0, pos); +} + + +string baseNameOf(const Path & path) +{ + Path::size_type pos = path.rfind('/'); + if (pos == string::npos) + throw Error(format("invalid file name `%1%'") % path); + return string(path, pos + 1); +} + + +bool isInDir(const Path & path, const Path & dir) +{ + return path[0] == '/' + && string(path, 0, dir.size()) == dir + && path.size() >= dir.size() + 2 + && path[dir.size()] == '/'; +} + + +struct stat lstat(const Path & path) +{ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting status of `%1%'") % path); + return st; +} + + +bool pathExists(const Path & path) +{ + int res; + struct stat st; + res = lstat(path.c_str(), &st); + if (!res) return true; + if (errno != ENOENT && errno != ENOTDIR) + throw SysError(format("getting status of %1%") % path); + return false; +} + + +Path readLink(const Path & path) +{ + checkInterrupt(); + struct stat st = lstat(path); + if (!S_ISLNK(st.st_mode)) + throw Error(format("`%1%' is not a symlink") % path); + char buf[st.st_size]; + if (readlink(path.c_str(), buf, st.st_size) != st.st_size) + throw SysError(format("reading symbolic link `%1%'") % path); + return string(buf, st.st_size); +} + + +bool isLink(const Path & path) +{ + struct stat st = lstat(path); + return S_ISLNK(st.st_mode); +} + + +Strings readDirectory(const Path & path) +{ + Strings names; + + AutoCloseDir dir = opendir(path.c_str()); + if (!dir) throw SysError(format("opening directory `%1%'") % path); + + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir)) { /* sic */ + checkInterrupt(); + string name = dirent->d_name; + if (name == "." || name == "..") continue; + names.push_back(name); + } + if (errno) throw SysError(format("reading directory `%1%'") % path); + + return names; +} + + +string readFile(int fd) +{ + struct stat st; + if (fstat(fd, &st) == -1) + throw SysError("statting file"); + + unsigned char * buf = new unsigned char[st.st_size]; + AutoDeleteArray d(buf); + readFull(fd, buf, st.st_size); + + return string((char *) buf, st.st_size); +} + + +string readFile(const Path & path, bool drain) +{ + AutoCloseFD fd = open(path.c_str(), O_RDONLY); + if (fd == -1) + throw SysError(format("opening file `%1%'") % path); + return drain ? drainFD(fd) : readFile(fd); +} + + +void writeFile(const Path & path, const string & s) +{ + AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666); + if (fd == -1) + throw SysError(format("opening file `%1%'") % path); + writeFull(fd, (unsigned char *) s.data(), s.size()); +} + + +string readLine(int fd) +{ + string s; + while (1) { + checkInterrupt(); + char ch; + ssize_t rd = read(fd, &ch, 1); + if (rd == -1) { + if (errno != EINTR) + throw SysError("reading a line"); + } else if (rd == 0) + throw EndOfFile("unexpected EOF reading a line"); + else { + if (ch == '\n') return s; + s += ch; + } + } +} + + +void writeLine(int fd, string s) +{ + s += '\n'; + writeFull(fd, (const unsigned char *) s.data(), s.size()); +} + + +static void _deletePath(const Path & path, unsigned long long & bytesFreed) +{ + checkInterrupt(); + + printMsg(lvlVomit, format("%1%") % path); + + struct stat st = lstat(path); + + if (!S_ISDIR(st.st_mode) && st.st_nlink == 1) + bytesFreed += st.st_blocks * 512; + + if (S_ISDIR(st.st_mode)) { + Strings names = readDirectory(path); + + /* Make the directory writable. */ + if (!(st.st_mode & S_IWUSR)) { + if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) + throw SysError(format("making `%1%' writable") % path); + } + + for (Strings::iterator i = names.begin(); i != names.end(); ++i) + _deletePath(path + "/" + *i, bytesFreed); + } + + if (remove(path.c_str()) == -1) + throw SysError(format("cannot unlink `%1%'") % path); +} + + +void deletePath(const Path & path) +{ + unsigned long long dummy; + deletePath(path, dummy); +} + + +void deletePath(const Path & path, unsigned long long & bytesFreed) +{ + startNest(nest, lvlDebug, + format("recursively deleting path `%1%'") % path); + bytesFreed = 0; + _deletePath(path, bytesFreed); +} + + +static Path tempName(Path tmpRoot, const Path & prefix, bool includePid, + int & counter) +{ + tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR", "/tmp") : tmpRoot, true); + if (includePid) + return (format("%1%/%2%-%3%-%4%") % tmpRoot % prefix % getpid() % counter++).str(); + else + return (format("%1%/%2%-%3%") % tmpRoot % prefix % counter++).str(); +} + + +Path createTempDir(const Path & tmpRoot, const Path & prefix, + bool includePid, bool useGlobalCounter, mode_t mode) +{ + static int globalCounter = 0; + int localCounter = 0; + int & counter(useGlobalCounter ? globalCounter : localCounter); + + while (1) { + checkInterrupt(); + Path tmpDir = tempName(tmpRoot, prefix, includePid, counter); + if (mkdir(tmpDir.c_str(), mode) == 0) { + /* Explicitly set the group of the directory. This is to + work around around problems caused by BSD's group + ownership semantics (directories inherit the group of + the parent). For instance, the group of /tmp on + FreeBSD is "wheel", so all directories created in /tmp + will be owned by "wheel"; but if the user is not in + "wheel", then "tar" will fail to unpack archives that + have the setgid bit set on directories. */ + if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0) + throw SysError(format("setting group of directory `%1%'") % tmpDir); + return tmpDir; + } + if (errno != EEXIST) + throw SysError(format("creating directory `%1%'") % tmpDir); + } +} + + +Paths createDirs(const Path & path) +{ + Paths created; + if (path == "/") return created; + + struct stat st; + if (lstat(path.c_str(), &st) == -1) { + created = createDirs(dirOf(path)); + if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST) + throw SysError(format("creating directory `%1%'") % path); + st = lstat(path); + created.push_back(path); + } + + if (!S_ISDIR(st.st_mode)) throw Error(format("`%1%' is not a directory") % path); + + return created; +} + + +void createSymlink(const Path & target, const Path & link) +{ + if (symlink(target.c_str(), link.c_str())) + throw SysError(format("creating symlink from `%1%' to `%2%'") % link % target); +} + + +LogType logType = ltPretty; +Verbosity verbosity = lvlInfo; + +static int nestingLevel = 0; + + +Nest::Nest() +{ + nest = false; +} + + +Nest::~Nest() +{ + close(); +} + + +static string escVerbosity(Verbosity level) +{ + return int2String((int) level); +} + + +void Nest::open(Verbosity level, const FormatOrString & fs) +{ + if (level <= verbosity) { + if (logType == ltEscapes) + std::cerr << "\033[" << escVerbosity(level) << "p" + << fs.s << "\n"; + else + printMsg_(level, fs); + nest = true; + nestingLevel++; + } +} + + +void Nest::close() +{ + if (nest) { + nestingLevel--; + if (logType == ltEscapes) + std::cerr << "\033[q"; + nest = false; + } +} + + +void printMsg_(Verbosity level, const FormatOrString & fs) +{ + checkInterrupt(); + if (level > verbosity) return; + string prefix; + if (logType == ltPretty) + for (int i = 0; i < nestingLevel; i++) + prefix += "| "; + else if (logType == ltEscapes && level != lvlInfo) + prefix = "\033[" + escVerbosity(level) + "s"; + string s = (format("%1%%2%\n") % prefix % fs.s).str(); + writeToStderr(s); +} + + +void warnOnce(bool & haveWarned, const FormatOrString & fs) +{ + if (!haveWarned) { + printMsg(lvlError, format("warning: %1%") % fs.s); + haveWarned = true; + } +} + + +void writeToStderr(const string & s) +{ + try { + _writeToStderr((const unsigned char *) s.data(), s.size()); + } catch (SysError & e) { + /* Ignore failing writes to stderr if we're in an exception + handler, otherwise throw an exception. We need to ignore + write errors in exception handlers to ensure that cleanup + code runs to completion if the other side of stderr has + been closed unexpectedly. */ + if (!std::uncaught_exception()) throw; + } +} + + +static void defaultWriteToStderr(const unsigned char * buf, size_t count) +{ + writeFull(STDERR_FILENO, buf, count); +} + + +void (*_writeToStderr) (const unsigned char * buf, size_t count) = defaultWriteToStderr; + + +void readFull(int fd, unsigned char * buf, size_t count) +{ + while (count) { + checkInterrupt(); + ssize_t res = read(fd, (char *) buf, count); + if (res == -1) { + if (errno == EINTR) continue; + throw SysError("reading from file"); + } + if (res == 0) throw EndOfFile("unexpected end-of-file"); + count -= res; + buf += res; + } +} + + +void writeFull(int fd, const unsigned char * buf, size_t count) +{ + while (count) { + checkInterrupt(); + ssize_t res = write(fd, (char *) buf, count); + if (res == -1) { + if (errno == EINTR) continue; + throw SysError("writing to file"); + } + count -= res; + buf += res; + } +} + + +string drainFD(int fd) +{ + string result; + unsigned char buffer[4096]; + while (1) { + checkInterrupt(); + ssize_t rd = read(fd, buffer, sizeof buffer); + if (rd == -1) { + if (errno != EINTR) + throw SysError("reading from file"); + } + else if (rd == 0) break; + else result.append((char *) buffer, rd); + } + return result; +} + + + +////////////////////////////////////////////////////////////////////// + + +AutoDelete::AutoDelete(const string & p, bool recursive) : path(p) +{ + del = true; + this->recursive = recursive; +} + +AutoDelete::~AutoDelete() +{ + try { + if (del) { + if (recursive) + deletePath(path); + else { + if (remove(path.c_str()) == -1) + throw SysError(format("cannot unlink `%1%'") % path); + } + } + } catch (...) { + ignoreException(); + } +} + +void AutoDelete::cancel() +{ + del = false; +} + + + +////////////////////////////////////////////////////////////////////// + + +AutoCloseFD::AutoCloseFD() +{ + fd = -1; +} + + +AutoCloseFD::AutoCloseFD(int fd) +{ + this->fd = fd; +} + + +AutoCloseFD::AutoCloseFD(const AutoCloseFD & fd) +{ + /* Copying an AutoCloseFD isn't allowed (who should get to close + it?). But as an edge case, allow copying of closed + AutoCloseFDs. This is necessary due to tiresome reasons + involving copy constructor use on default object values in STL + containers (like when you do `map[value]' where value isn't in + the map yet). */ + this->fd = fd.fd; + if (this->fd != -1) abort(); +} + + +AutoCloseFD::~AutoCloseFD() +{ + try { + close(); + } catch (...) { + ignoreException(); + } +} + + +void AutoCloseFD::operator =(int fd) +{ + if (this->fd != fd) close(); + this->fd = fd; +} + + +AutoCloseFD::operator int() const +{ + return fd; +} + + +void AutoCloseFD::close() +{ + if (fd != -1) { + if (::close(fd) == -1) + /* This should never happen. */ + throw SysError(format("closing file descriptor %1%") % fd); + fd = -1; + } +} + + +bool AutoCloseFD::isOpen() +{ + return fd != -1; +} + + +/* Pass responsibility for closing this fd to the caller. */ +int AutoCloseFD::borrow() +{ + int oldFD = fd; + fd = -1; + return oldFD; +} + + +void Pipe::create() +{ + int fds[2]; + if (pipe(fds) != 0) throw SysError("creating pipe"); + readSide = fds[0]; + writeSide = fds[1]; + closeOnExec(readSide); + closeOnExec(writeSide); +} + + + +////////////////////////////////////////////////////////////////////// + + +AutoCloseDir::AutoCloseDir() +{ + dir = 0; +} + + +AutoCloseDir::AutoCloseDir(DIR * dir) +{ + this->dir = dir; +} + + +AutoCloseDir::~AutoCloseDir() +{ + close(); +} + + +void AutoCloseDir::operator =(DIR * dir) +{ + this->dir = dir; +} + + +AutoCloseDir::operator DIR *() +{ + return dir; +} + + +void AutoCloseDir::close() +{ + if (dir) { + closedir(dir); + dir = 0; + } +} + + +////////////////////////////////////////////////////////////////////// + + +Pid::Pid() +{ + pid = -1; + separatePG = false; + killSignal = SIGKILL; +} + + +Pid::~Pid() +{ + kill(); +} + + +void Pid::operator =(pid_t pid) +{ + if (this->pid != pid) kill(); + this->pid = pid; + killSignal = SIGKILL; // reset signal to default +} + + +Pid::operator pid_t() +{ + return pid; +} + + +void Pid::kill() +{ + if (pid == -1 || pid == 0) return; + + printMsg(lvlError, format("killing process %1%") % pid); + + /* Send the requested signal to the child. If it has its own + process group, send the signal to every process in the child + process group (which hopefully includes *all* its children). */ + if (::kill(separatePG ? -pid : pid, killSignal) != 0) + printMsg(lvlError, (SysError(format("killing process %1%") % pid).msg())); + + /* Wait until the child dies, disregarding the exit status. */ + int status; + while (waitpid(pid, &status, 0) == -1) { + checkInterrupt(); + if (errno != EINTR) { + printMsg(lvlError, + (SysError(format("waiting for process %1%") % pid).msg())); + break; + } + } + + pid = -1; +} + + +int Pid::wait(bool block) +{ + assert(pid != -1); + while (1) { + int status; + int res = waitpid(pid, &status, block ? 0 : WNOHANG); + if (res == pid) { + pid = -1; + return status; + } + if (res == 0 && !block) return -1; + if (errno != EINTR) + throw SysError("cannot get child exit status"); + checkInterrupt(); + } +} + + +void Pid::setSeparatePG(bool separatePG) +{ + this->separatePG = separatePG; +} + + +void Pid::setKillSignal(int signal) +{ + this->killSignal = signal; +} + + +void killUser(uid_t uid) +{ + debug(format("killing all processes running under uid `%1%'") % uid); + + assert(uid != 0); /* just to be safe... */ + + /* The system call kill(-1, sig) sends the signal `sig' to all + users to which the current process can send signals. So we + fork a process, switch to uid, and send a mass kill. */ + + Pid pid; + pid = fork(); + switch (pid) { + + case -1: + throw SysError("unable to fork"); + + case 0: + try { /* child */ + + if (setuid(uid) == -1) + throw SysError("setting uid"); + + while (true) { +#ifdef __APPLE__ + /* OSX's kill syscall takes a third parameter that, among other + things, determines if kill(-1, signo) affects the calling + process. In the OSX libc, it's set to true, which means + "follow POSIX", which we don't want here + */ + if (syscall(SYS_kill, -1, SIGKILL, false) == 0) break; +#else + if (kill(-1, SIGKILL) == 0) break; +#endif + if (errno == ESRCH) break; /* no more processes */ + if (errno != EINTR) + throw SysError(format("cannot kill processes for uid `%1%'") % uid); + } + + } catch (std::exception & e) { + writeToStderr((format("killing processes belonging to uid `%1%': %2%\n") % uid % e.what()).str()); + _exit(1); + } + _exit(0); + } + + /* parent */ + int status = pid.wait(true); + if (status != 0) + throw Error(format("cannot kill processes for uid `%1%': %2%") % uid % statusToString(status)); + + /* !!! We should really do some check to make sure that there are + no processes left running under `uid', but there is no portable + way to do so (I think). The most reliable way may be `ps -eo + uid | grep -q $uid'. */ +} + + +////////////////////////////////////////////////////////////////////// + + +string runProgram(Path program, bool searchPath, const Strings & args) +{ + checkInterrupt(); + + std::vector cargs; /* careful with c_str()! */ + cargs.push_back(program.c_str()); + for (Strings::const_iterator i = args.begin(); i != args.end(); ++i) + cargs.push_back(i->c_str()); + cargs.push_back(0); + + /* Create a pipe. */ + Pipe pipe; + pipe.create(); + + /* Fork. */ + Pid pid; + pid = maybeVfork(); + + switch (pid) { + + case -1: + throw SysError("unable to fork"); + + case 0: /* child */ + try { + if (dup2(pipe.writeSide, STDOUT_FILENO) == -1) + throw SysError("dupping stdout"); + + if (searchPath) + execvp(program.c_str(), (char * *) &cargs[0]); + else + execv(program.c_str(), (char * *) &cargs[0]); + throw SysError(format("executing `%1%'") % program); + + } catch (std::exception & e) { + writeToStderr("error: " + string(e.what()) + "\n"); + } + _exit(1); + } + + /* Parent. */ + + pipe.writeSide.close(); + + string result = drainFD(pipe.readSide); + + /* Wait for the child to finish. */ + int status = pid.wait(true); + if (!statusOk(status)) + throw Error(format("program `%1%' %2%") + % program % statusToString(status)); + + return result; +} + + +void closeMostFDs(const set & exceptions) +{ + int maxFD = 0; + maxFD = sysconf(_SC_OPEN_MAX); + for (int fd = 0; fd < maxFD; ++fd) + if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO + && exceptions.find(fd) == exceptions.end()) + close(fd); /* ignore result */ +} + + +void closeOnExec(int fd) +{ + int prev; + if ((prev = fcntl(fd, F_GETFD, 0)) == -1 || + fcntl(fd, F_SETFD, prev | FD_CLOEXEC) == -1) + throw SysError("setting close-on-exec flag"); +} + + +#if HAVE_VFORK +pid_t (*maybeVfork)() = vfork; +#else +pid_t (*maybeVfork)() = fork; +#endif + + +////////////////////////////////////////////////////////////////////// + + +volatile sig_atomic_t _isInterrupted = 0; + +void _interrupted() +{ + /* Block user interrupts while an exception is being handled. + Throwing an exception while another exception is being handled + kills the program! */ + if (!std::uncaught_exception()) { + _isInterrupted = 0; + throw Interrupted("interrupted by the user"); + } +} + + + +////////////////////////////////////////////////////////////////////// + + +template C tokenizeString(const string & s, const string & separators) +{ + C result; + string::size_type pos = s.find_first_not_of(separators, 0); + while (pos != string::npos) { + string::size_type end = s.find_first_of(separators, pos + 1); + if (end == string::npos) end = s.size(); + string token(s, pos, end - pos); + result.insert(result.end(), token); + pos = s.find_first_not_of(separators, end); + } + return result; +} + +template Strings tokenizeString(const string & s, const string & separators); +template StringSet tokenizeString(const string & s, const string & separators); +template vector tokenizeString(const string & s, const string & separators); + + +string concatStringsSep(const string & sep, const Strings & ss) +{ + string s; + foreach (Strings::const_iterator, i, ss) { + if (s.size() != 0) s += sep; + s += *i; + } + return s; +} + + +string concatStringsSep(const string & sep, const StringSet & ss) +{ + string s; + foreach (StringSet::const_iterator, i, ss) { + if (s.size() != 0) s += sep; + s += *i; + } + return s; +} + + +string chomp(const string & s) +{ + size_t i = s.find_last_not_of(" \n\r\t"); + return i == string::npos ? "" : string(s, 0, i + 1); +} + + +string statusToString(int status) +{ + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (WIFEXITED(status)) + return (format("failed with exit code %1%") % WEXITSTATUS(status)).str(); + else if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); +#if HAVE_STRSIGNAL + const char * description = strsignal(sig); + return (format("failed due to signal %1% (%2%)") % sig % description).str(); +#else + return (format("failed due to signal %1%") % sig).str(); +#endif + } + else + return "died abnormally"; + } else return "succeeded"; +} + + +bool statusOk(int status) +{ + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +} + + +bool hasSuffix(const string & s, const string & suffix) +{ + return s.size() >= suffix.size() && string(s, s.size() - suffix.size()) == suffix; +} + + +void expect(std::istream & str, const string & s) +{ + char s2[s.size()]; + str.read(s2, s.size()); + if (string(s2, s.size()) != s) + throw Error(format("expected string `%1%'") % s); +} + + +string parseString(std::istream & str) +{ + string res; + expect(str, "\""); + int c; + while ((c = str.get()) != '"') + if (c == '\\') { + c = str.get(); + if (c == 'n') res += '\n'; + else if (c == 'r') res += '\r'; + else if (c == 't') res += '\t'; + else res += c; + } + else res += c; + return res; +} + + +bool endOfList(std::istream & str) +{ + if (str.peek() == ',') { + str.get(); + return false; + } + if (str.peek() == ']') { + str.get(); + return true; + } + return false; +} + + +string decodeOctalEscaped(const string & s) +{ + string r; + for (string::const_iterator i = s.begin(); i != s.end(); ) { + if (*i != '\\') { r += *i++; continue; } + unsigned char c = 0; + ++i; + while (i != s.end() && *i >= '0' && *i < '8') + c = c * 8 + (*i++ - '0'); + r += c; + } + return r; +} + + +void ignoreException() +{ + try { + throw; + } catch (std::exception & e) { + printMsg(lvlError, format("error (ignored): %1%") % e.what()); + } +} + + +} diff --git a/nix/libutil/util.hh b/nix/libutil/util.hh new file mode 100644 index 0000000000..8bedfea9a0 --- /dev/null +++ b/nix/libutil/util.hh @@ -0,0 +1,349 @@ +#pragma once + +#include "types.hh" + +#include +#include +#include +#include +#include + +#include + + +namespace nix { + + +#define foreach(it_type, it, collection) \ + for (it_type it = (collection).begin(); it != (collection).end(); ++it) + +#define foreach_reverse(it_type, it, collection) \ + for (it_type it = (collection).rbegin(); it != (collection).rend(); ++it) + + +/* Return an environment variable. */ +string getEnv(const string & key, const string & def = ""); + +/* Return an absolutized path, resolving paths relative to the + specified directory, or the current directory otherwise. The path + is also canonicalised. */ +Path absPath(Path path, Path dir = ""); + +/* Canonicalise a path by removing all `.' or `..' components and + double or trailing slashes. Optionally resolves all symlink + components such that each component of the resulting path is *not* + a symbolic link. */ +Path canonPath(const Path & path, bool resolveSymlinks = false); + +/* Return the directory part of the given canonical path, i.e., + everything before the final `/'. If the path is the root or an + immediate child thereof (e.g., `/foo'), this means an empty string + is returned. */ +Path dirOf(const Path & path); + +/* Return the base name of the given canonical path, i.e., everything + following the final `/'. */ +string baseNameOf(const Path & path); + +/* Check whether a given path is a descendant of the given + directory. */ +bool isInDir(const Path & path, const Path & dir); + +/* Get status of `path'. */ +struct stat lstat(const Path & path); + +/* Return true iff the given path exists. */ +bool pathExists(const Path & path); + +/* Read the contents (target) of a symbolic link. The result is not + in any way canonicalised. */ +Path readLink(const Path & path); + +bool isLink(const Path & path); + +/* Read the contents of a directory. The entries `.' and `..' are + removed. */ +Strings readDirectory(const Path & path); + +/* Read the contents of a file into a string. */ +string readFile(int fd); +string readFile(const Path & path, bool drain = false); + +/* Write a string to a file. */ +void writeFile(const Path & path, const string & s); + +/* Read a line from a file descriptor. */ +string readLine(int fd); + +/* Write a line to a file descriptor. */ +void writeLine(int fd, string s); + +/* Delete a path; i.e., in the case of a directory, it is deleted + recursively. Don't use this at home, kids. The second variant + returns the number of bytes and blocks freed. */ +void deletePath(const Path & path); + +void deletePath(const Path & path, unsigned long long & bytesFreed); + +/* Create a temporary directory. */ +Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix", + bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755); + +/* Create a directory and all its parents, if necessary. Returns the + list of created directories, in order of creation. */ +Paths createDirs(const Path & path); + +/* Create a symlink. */ +void createSymlink(const Path & target, const Path & link); + + +template +T singleton(const A & a) +{ + T t; + t.insert(a); + return t; +} + + +/* Messages. */ + + +typedef enum { + ltPretty, /* nice, nested output */ + ltEscapes, /* nesting indicated using escape codes (for log2xml) */ + ltFlat /* no nesting */ +} LogType; + +extern LogType logType; +extern Verbosity verbosity; /* suppress msgs > this */ + +class Nest +{ +private: + bool nest; +public: + Nest(); + ~Nest(); + void open(Verbosity level, const FormatOrString & fs); + void close(); +}; + +void printMsg_(Verbosity level, const FormatOrString & fs); + +#define startNest(varName, level, f) \ + Nest varName; \ + if (level <= verbosity) { \ + varName.open(level, (f)); \ + } + +#define printMsg(level, f) \ + do { \ + if (level <= verbosity) { \ + printMsg_(level, (f)); \ + } \ + } while (0) + +#define debug(f) printMsg(lvlDebug, f) + +void warnOnce(bool & haveWarned, const FormatOrString & fs); + +void writeToStderr(const string & s); + +extern void (*_writeToStderr) (const unsigned char * buf, size_t count); + + +/* Wrappers arount read()/write() that read/write exactly the + requested number of bytes. */ +void readFull(int fd, unsigned char * buf, size_t count); +void writeFull(int fd, const unsigned char * buf, size_t count); + +MakeError(EndOfFile, Error) + + +/* Read a file descriptor until EOF occurs. */ +string drainFD(int fd); + + + +/* Automatic cleanup of resources. */ + + +template +struct AutoDeleteArray +{ + T * p; + AutoDeleteArray(T * p) : p(p) { } + ~AutoDeleteArray() + { + delete [] p; + } +}; + + +class AutoDelete +{ + Path path; + bool del; + bool recursive; +public: + AutoDelete(const Path & p, bool recursive = true); + ~AutoDelete(); + void cancel(); +}; + + +class AutoCloseFD +{ + int fd; +public: + AutoCloseFD(); + AutoCloseFD(int fd); + AutoCloseFD(const AutoCloseFD & fd); + ~AutoCloseFD(); + void operator =(int fd); + operator int() const; + void close(); + bool isOpen(); + int borrow(); +}; + + +class Pipe +{ +public: + AutoCloseFD readSide, writeSide; + void create(); +}; + + +class AutoCloseDir +{ + DIR * dir; +public: + AutoCloseDir(); + AutoCloseDir(DIR * dir); + ~AutoCloseDir(); + void operator =(DIR * dir); + operator DIR *(); + void close(); +}; + + +class Pid +{ + pid_t pid; + bool separatePG; + int killSignal; +public: + Pid(); + ~Pid(); + void operator =(pid_t pid); + operator pid_t(); + void kill(); + int wait(bool block); + void setSeparatePG(bool separatePG); + void setKillSignal(int signal); +}; + + +/* Kill all processes running under the specified uid by sending them + a SIGKILL. */ +void killUser(uid_t uid); + + +/* Run a program and return its stdout in a string (i.e., like the + shell backtick operator). */ +string runProgram(Path program, bool searchPath = false, + const Strings & args = Strings()); + +/* Close all file descriptors except stdin, stdout, stderr, and those + listed in the given set. Good practice in child processes. */ +void closeMostFDs(const set & exceptions); + +/* Set the close-on-exec flag for the given file descriptor. */ +void closeOnExec(int fd); + +/* Call vfork() if available, otherwise fork(). */ +extern pid_t (*maybeVfork)(); + + +/* User interruption. */ + +extern volatile sig_atomic_t _isInterrupted; + +void _interrupted(); + +void inline checkInterrupt() +{ + if (_isInterrupted) _interrupted(); +} + +MakeError(Interrupted, BaseError) + + +/* String tokenizer. */ +template C tokenizeString(const string & s, const string & separators = " \t\n\r"); + + +/* Concatenate the given strings with a separator between the + elements. */ +string concatStringsSep(const string & sep, const Strings & ss); +string concatStringsSep(const string & sep, const StringSet & ss); + + +/* Remove trailing whitespace from a string. */ +string chomp(const string & s); + + +/* Convert the exit status of a child as returned by wait() into an + error string. */ +string statusToString(int status); + +bool statusOk(int status); + + +/* Parse a string into an integer. */ +template bool string2Int(const string & s, N & n) +{ + std::istringstream str(s); + str >> n; + return str && str.get() == EOF; +} + +template string int2String(N n) +{ + std::ostringstream str; + str << n; + return str.str(); +} + + +/* Return true iff `s' ends in `suffix'. */ +bool hasSuffix(const string & s, const string & suffix); + + +/* Read string `s' from stream `str'. */ +void expect(std::istream & str, const string & s); + + +/* Read a C-style string from stream `str'. */ +string parseString(std::istream & str); + + +/* Utility function used to parse legacy ATerms. */ +bool endOfList(std::istream & str); + + +/* Escape a string that contains octal-encoded escape codes such as + used in /etc/fstab and /proc/mounts (e.g. "foo\040bar" decodes to + "foo bar"). */ +string decodeOctalEscaped(const string & s); + + +/* Exception handling in destructors: print an error message, then + ignore the exception. */ +void ignoreException(); + + +} diff --git a/nix/libutil/xml-writer.cc b/nix/libutil/xml-writer.cc new file mode 100644 index 0000000000..01794001b2 --- /dev/null +++ b/nix/libutil/xml-writer.cc @@ -0,0 +1,94 @@ +#include + +#include "xml-writer.hh" + + +namespace nix { + + +XMLWriter::XMLWriter(bool indent, std::ostream & output) + : output(output), indent(indent) +{ + output << "" << std::endl; + closed = false; +} + + +XMLWriter::~XMLWriter() +{ + close(); +} + + +void XMLWriter::close() +{ + if (closed) return; + while (!pendingElems.empty()) closeElement(); + closed = true; +} + + +void XMLWriter::indent_(unsigned int depth) +{ + if (!indent) return; + output << string(depth * 2, ' '); +} + + +void XMLWriter::openElement(const string & name, + const XMLAttrs & attrs) +{ + assert(!closed); + indent_(pendingElems.size()); + output << "<" << name; + writeAttrs(attrs); + output << ">"; + if (indent) output << std::endl; + pendingElems.push_back(name); +} + + +void XMLWriter::closeElement() +{ + assert(!pendingElems.empty()); + indent_(pendingElems.size() - 1); + output << ""; + if (indent) output << std::endl; + pendingElems.pop_back(); + if (pendingElems.empty()) closed = true; +} + + +void XMLWriter::writeEmptyElement(const string & name, + const XMLAttrs & attrs) +{ + assert(!closed); + indent_(pendingElems.size()); + output << "<" << name; + writeAttrs(attrs); + output << " />"; + if (indent) output << std::endl; +} + + +void XMLWriter::writeAttrs(const XMLAttrs & attrs) +{ + for (XMLAttrs::const_iterator i = attrs.begin(); i != attrs.end(); ++i) { + output << " " << i->first << "=\""; + for (unsigned int j = 0; j < i->second.size(); ++j) { + char c = i->second[j]; + if (c == '"') output << """; + else if (c == '<') output << "<"; + else if (c == '>') output << ">"; + else if (c == '&') output << "&"; + /* Escape newlines to prevent attribute normalisation (see + XML spec, section 3.3.3. */ + else if (c == '\n') output << " "; + else output << c; + } + output << "\""; + } +} + + +} diff --git a/nix/libutil/xml-writer.hh b/nix/libutil/xml-writer.hh new file mode 100644 index 0000000000..3cefe3712c --- /dev/null +++ b/nix/libutil/xml-writer.hh @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include + + +namespace nix { + +using std::string; +using std::map; +using std::list; + + +typedef map XMLAttrs; + + +class XMLWriter +{ +private: + + std::ostream & output; + + bool indent; + bool closed; + + list pendingElems; + +public: + + XMLWriter(bool indent, std::ostream & output); + ~XMLWriter(); + + void close(); + + void openElement(const string & name, + const XMLAttrs & attrs = XMLAttrs()); + void closeElement(); + + void writeEmptyElement(const string & name, + const XMLAttrs & attrs = XMLAttrs()); + +private: + void writeAttrs(const XMLAttrs & attrs); + + void indent_(unsigned int depth); +}; + + +class XMLOpenElement +{ +private: + XMLWriter & writer; +public: + XMLOpenElement(XMLWriter & writer, const string & name, + const XMLAttrs & attrs = XMLAttrs()) + : writer(writer) + { + writer.openElement(name, attrs); + } + ~XMLOpenElement() + { + writer.closeElement(); + } +}; + + +} diff --git a/nix/nix-daemon/nix-daemon.cc b/nix/nix-daemon/nix-daemon.cc new file mode 100644 index 0000000000..8814fe3155 --- /dev/null +++ b/nix/nix-daemon/nix-daemon.cc @@ -0,0 +1,939 @@ +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + 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; +pid_t myPid; + + + +/* 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) +{ + /* Don't send the message to the client if we're a child of the + process handling the connection. Otherwise we could screw up + the protocol. It's up to the parent to redirect stderr and + send it to the client somehow (e.g., as in build.cc). */ + if (canSendStderr && myPid == getpid()) { + 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 : Sink +{ + Sink & to; + TunnelSink(Sink & to) : to(to) { } + virtual void operator () (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) { + +#if 0 + case wopQuit: { + /* Close the database. */ + store.reset((StoreAPI *) 0); + writeInt(1, to); + break; + } +#endif + + case wopIsValidPath: { + /* 'readStorePath' could raise an error leading to the connection + being closed. To be able to recover from an invalid path error, + 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(from); + startWork(); + PathSet res = store->queryValidPaths(paths); + stopWork(); + writeStrings(res, to); + break; + } + + case wopHasSubstitutes: { + Path path = readStorePath(from); + startWork(); + PathSet res = store->querySubstitutablePaths(singleton(path)); + stopWork(); + writeInt(res.find(path) != res.end(), to); + break; + } + + case wopQuerySubstitutablePaths: { + PathSet paths = readStorePaths(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(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(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); + store->exportPath(path, sign, sink); + stopWork(); + writeInt(1, to); + break; + } + + case wopImportPaths: { + startWork(); + TunnelSource source(from); + Paths paths = store->importPaths(true, source); + stopWork(); + writeStrings(paths, to); + break; + } + + case wopBuildPaths: { + PathSet drvs = readStorePaths(from); + startWork(); + store->buildPaths(drvs); + 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: { + GCOptions options; + options.action = (GCOptions::GCAction) readInt(from); + options.pathsToDelete = readStorePaths(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; + settings.keepGoing = readInt(from) != 0; + settings.set("build-fallback", readInt(from) ? "true" : "false"); + verbosity = (Verbosity) readInt(from); + settings.set("build-max-jobs", int2String(readInt(from))); + settings.set("build-max-silent-time", int2String(readInt(from))); + if (GET_PROTOCOL_MINOR(clientVersion) >= 2) + settings.useBuildHook = readInt(from) != 0; + 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) + settings.set("build-cores", int2String(readInt(from))); + if (GET_PROTOCOL_MINOR(clientVersion) >= 10) + settings.set("build-use-substitutes", readInt(from) ? "true" : "false"); + 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 == "use-ssh-substituter") + settings.set(name, 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(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(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(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; + } + + default: + throw Error(format("invalid operation %1%") % op); + } +} + + +static void processConnection(bool trusted) +{ + canSendStderr = false; + myPid = getpid(); + _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(new LocalStore(reserveSpace)); + + stopWork(); + to.flush(); + + } catch (Error & e) { + stopWork(false, e.msg()); + 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; + if (!errorAllowed) printMsg(lvlError, format("error processing client input: %1%") % e.msg()); + stopWork(false, e.msg(), GET_PROTOCOL_MINOR(clientVersion) >= 8 ? e.status : 0); + if (!errorAllowed) break; + } catch (std::bad_alloc & e) { + if (canSendStderr) + stopWork(false, "Nix daemon out of memory", GET_PROTOCOL_MINOR(clientVersion) >= 8 ? 1 : 0); + throw; + } + + to.flush(); + + assert(!canSendStderr); + }; + + printMsg(lvlError, 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"); +} + + +#define SD_LISTEN_FDS_START 3 + + +static void daemonLoop() +{ + /* Get rid of children automatically; don't let them become + zombies. */ + setSigChldAction(true); + + AutoCloseFD fdSocket; + + /* Handle socket-based activation by systemd. */ + if (getEnv("LISTEN_FDS") != "") { + if (getEnv("LISTEN_PID") != int2String(getpid()) || getEnv("LISTEN_FDS") != "1") + throw Error("unexpected systemd environment variables"); + fdSocket = SD_LISTEN_FDS_START; + } + + /* Otherwise, create and bind to a Unix domain socket. */ + else { + + /* Create and bind to a Unix domain socket. */ + fdSocket = socket(PF_UNIX, SOCK_STREAM, 0); + if (fdSocket == -1) + throw SysError("cannot create Unix domain socket"); + + string socketPath = settings.nixDaemonSocketFile; + + createDirs(dirOf(socketPath)); + + /* Urgh, sockaddr_un allows path names of only 108 characters. + So chdir to the socket directory so that we can pass a + relative path name. */ + chdir(dirOf(socketPath).c_str()); + Path socketPathRel = "./" + baseNameOf(socketPath); + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + if (socketPathRel.size() >= sizeof(addr.sun_path)) + throw Error(format("socket path `%1%' is too long") % socketPathRel); + strcpy(addr.sun_path, socketPathRel.c_str()); + + unlink(socketPath.c_str()); + + /* Make sure that the socket is created with 0666 permission + (everybody can connect --- provided they have access to the + directory containing the socket). */ + mode_t oldMode = umask(0111); + int res = bind(fdSocket, (struct sockaddr *) &addr, sizeof(addr)); + umask(oldMode); + if (res == -1) + throw SysError(format("cannot bind to socket `%1%'") % socketPath); + + chdir("/"); /* back to the root */ + + if (listen(fdSocket, 5) == -1) + throw SysError(format("cannot listen on socket `%1%'") % socketPath); + } + + closeOnExec(fdSocket); + + /* Loop accepting connections. */ + while (1) { + + try { + /* Important: the server process *cannot* open the SQLite + database, because it doesn't like forks very much. */ + assert(!store); + + /* Accept a connection. */ + struct sockaddr_un remoteAddr; + socklen_t remoteAddrLen = sizeof(remoteAddr); + + AutoCloseFD remote = accept(fdSocket, + (struct sockaddr *) &remoteAddr, &remoteAddrLen); + checkInterrupt(); + if (remote == -1) { + if (errno == EINTR) + continue; + else + throw SysError("accepting connection"); + } + + closeOnExec(remote); + + /* Get the identity of the caller, if possible. */ + uid_t clientUid = -1; + pid_t clientPid = -1; + bool trusted = false; + +#if defined(SO_PEERCRED) + ucred cred; + socklen_t credLen = sizeof(cred); + if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) != -1) { + clientPid = cred.pid; + clientUid = cred.uid; + if (clientUid == 0) trusted = true; + } +#endif + + printMsg(lvlInfo, format("accepted connection from pid %1%, uid %2%") % clientPid % clientUid); + + /* Fork a child to handle the connection. */ + pid_t child; + child = fork(); + + switch (child) { + + case -1: + throw SysError("unable to fork"); + + case 0: + try { /* child */ + + /* 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 = int2String(clientPid); + strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1])); + } + + /* Handle the connection. */ + from.fd = remote; + to.fd = remote; + processConnection(trusted); + + } catch (std::exception & e) { + writeToStderr("unexpected Nix daemon error: " + string(e.what()) + "\n"); + } + exit(0); + } + + } catch (Interrupted & e) { + throw; + } catch (Error & e) { + printMsg(lvlError, format("error processing connection: %1%") % e.msg()); + } + } +} + + +void run(Strings args) +{ + for (Strings::iterator i = args.begin(); i != args.end(); ) { + string arg = *i++; + if (arg == "--daemon") /* ignored for backwards compatibility */; + } + + chdir("/"); + daemonLoop(); +} + + +void printHelp() +{ + showManPage("nix-daemon"); +} + + +string programId = "nix-daemon"; diff --git a/nix/sync-with-upstream b/nix/sync-with-upstream deleted file mode 100755 index e9cb070dcc..0000000000 --- a/nix/sync-with-upstream +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/sh -# GNU Guix --- Functional package management for GNU -# Copyright © 2012, 2013, 2014 Ludovic Courtès -# -# 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 . - -# -# Update the local copy of Nix source code needed to build the daemon. -# Assume GNU Coreutils and Git are available. -# - -top_srcdir="${top_srcdir:-..}" - -log() -{ - echo "sync-with-upstream: $@" >&2 -} - -# checked_in_p FILE -checked_in_p() -{ - ( cd "$top_srcdir" ; - git ls-tree HEAD -- "nix/$1" | grep "$1" > /dev/null ) -} - -if [ ! -d "$top_srcdir/build-aux" ] -then - log "\`$top_srcdir' is not the valid top-level source directory" - exit 1 -fi - -set -e -for upstream_file in `cd "$top_srcdir/nix-upstream/src" ; - find . -name \*.c -or -name \*.h -or -name \*.cc -or -name \*.hh \ - -or -name \*.cpp -or -name \*.hpp -or -name \*.sql` -do - if grep "$upstream_file" "$top_srcdir/daemon.am" > /dev/null - then - if checked_in_p "$upstream_file" - then - log "skipping \`$upstream_file', which has a checked-in copy" - else - ( cd "$top_srcdir/nix-upstream/src" && \ - cp -v --parents "$upstream_file" ../../nix ) - fi - else - log "skipping \`$upstream_file', which is not used" - fi -done - -# This file should be generated by our build system so remove it. -rm -fv "$top_srcdir/nix/libstore/schema.sql.hh" - -cp -v "$top_srcdir/nix-upstream/COPYING" "$top_srcdir/nix" - -# Generate an 'AUTHORS' file since upstream Nix no longer has one. -cat > "$top_srcdir/nix/AUTHORS" <> "$top_srcdir/nix/AUTHORS" - -# Substitutions. -sed -i "$top_srcdir/nix/libstore/gc.cc" \ - -e 's|/nix/find-runtime-roots\.pl|/guix/list-runtime-roots|g' - -# Our 'guix_hash_context' structure has a copy constructor, specifically to -# handle the use case in 'HashSink::currentHash()' where the copy of the -# context is expected to truly copy the underlying hash context. The copy -# constructor cannot be used in 'Ctx' if that's a union, so turn it into a -# structure (we can afford to two wasted words.) -sed -i "$top_srcdir/nix/libutil/hash.cc" "$top_srcdir/nix/libutil/hash.hh" \ - -e 's|union Ctx|struct Ctx|g' diff --git a/po/guix/LINGUAS b/po/guix/LINGUAS index 1987e5e30c..8052ce9d3e 100644 --- a/po/guix/LINGUAS +++ b/po/guix/LINGUAS @@ -5,6 +5,7 @@ de en@boldquot en@quot eo +fr hu pt_BR sr diff --git a/po/guix/fr.po b/po/guix/fr.po new file mode 100644 index 0000000000..d1a49077e2 --- /dev/null +++ b/po/guix/fr.po @@ -0,0 +1,1368 @@ +# French translation of guix. +# Copyright (C) 2013, 2014 Free Software Foundation, Inc. +# This file is distributed under the same license as the guix package. +# Rémy Chevalier , 2013, 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: guix 0.8\n" +"Report-Msgid-Bugs-To: ludo@gnu.org\n" +"POT-Creation-Date: 2014-11-09 22:32+0100\n" +"PO-Revision-Date: 2014-12-09 09:42+0100\n" +"Last-Translator: Rémy Chevalier \n" +"Language-Team: French \n" +"Language: fr\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" + +#: gnu/packages.scm:120 +#, scheme-format +msgid "cannot access `~a': ~a~%" +msgstr "impossible d'accéder à \"~a\": ~a~%" + +#: gnu/packages.scm:350 +#, scheme-format +msgid "looking for the latest release of GNU ~a..." +msgstr "recherche de la dernière version de GNU ~a..." + +#: gnu/packages.scm:354 +#, scheme-format +msgid "~a: note: using ~a but ~a is available upstream~%" +msgstr "~a: note: utilisation de ~a mais ~a est disponible en amont" + +#: gnu/packages.scm:376 guix/scripts/package.scm:305 +#, scheme-format +msgid "ambiguous package specification `~a'~%" +msgstr "spécification du paquet \"~a\" ambiguë~%" + +#: gnu/packages.scm:377 guix/scripts/package.scm:307 +#, scheme-format +msgid "choosing ~a from ~a~%" +msgstr "choix de ~a parmi ~a~%" + +#: gnu/packages.scm:383 +#, scheme-format +msgid "~A: package not found for version ~a~%" +msgstr "~A: paquet introuvable pour la version ~a~%" + +#: gnu/packages.scm:385 +#, scheme-format +msgid "~A: unknown package~%" +msgstr "~A: paquet inconnu~%" + +#: guix/scripts/build.scm:65 +#, scheme-format +msgid "failed to create GC root `~a': ~a~%" +msgstr "impossible de créer la racine du GC \"~a\": ~a~%" + +#: guix/scripts/build.scm:102 +msgid "" +"\n" +" -L, --load-path=DIR prepend DIR to the package module search path" +msgstr "" +"\n" +" -L, --load-path=REP préfixer le chemin de recherche par REP " + +#: guix/scripts/build.scm:104 +msgid "" +"\n" +" -K, --keep-failed keep build tree of failed builds" +msgstr "" +"\n" +" -K, --keep-failed garder l'arbre de compilation pour les compilations ayant échoué" + +#: guix/scripts/build.scm:106 +msgid "" +"\n" +" -n, --dry-run do not build the derivations" +msgstr "" +"\n" +" -n, --dry-run ne pas compiler les dérivations" + +#: guix/scripts/build.scm:108 +msgid "" +"\n" +" --fallback fall back to building when the substituter fails" +msgstr "" +"\n" +" --fallback revenir à la compilation quand le substitut échoue" + +#: guix/scripts/build.scm:110 +msgid "" +"\n" +" --no-substitutes build instead of resorting to pre-built substitutes" +msgstr "" +"\n" +" --no-substitutes compiler plutot que recourir à des substituts pré-compilés" + +#: guix/scripts/build.scm:112 +msgid "" +"\n" +" --no-build-hook do not attempt to offload builds via the build hook" +msgstr "" +"\n" +" --no-build-hook ne pas essayer de décharger les compilations via le hook de compilation" + +#: guix/scripts/build.scm:114 +msgid "" +"\n" +" --max-silent-time=SECONDS\n" +" mark the build as failed after SECONDS of silence" +msgstr "" +"\n" +" --max-silent-time=N\n" +" marquer la compilation comme ayant échouée après N secondes de silence" + +#: guix/scripts/build.scm:117 +msgid "" +"\n" +" --timeout=SECONDS mark the build as failed after SECONDS of activity" +msgstr "" +"\n" +" --timeout=N marquer la compilation comme ayant échouée après N secondes d'activité" + +#: guix/scripts/build.scm:119 +msgid "" +"\n" +" --verbosity=LEVEL use the given verbosity LEVEL" +msgstr "" +"\n" +" --verbosity=NIVEAU utiliser le NIVEAU de verbosité donné" + +#: guix/scripts/build.scm:121 +msgid "" +"\n" +" -c, --cores=N allow the use of up to N CPU cores for the build" +msgstr "" +"\n" +" -c, --cores=N utiliser jusqu'à N coeurs CPU pour la compilation" + +#: guix/scripts/build.scm:195 +#, scheme-format +msgid "~a: not a number~%" +msgstr "~a: pas un nombre~%" + +#: guix/scripts/build.scm:213 +msgid "" +"Usage: guix build [OPTION]... PACKAGE-OR-DERIVATION...\n" +"Build the given PACKAGE-OR-DERIVATION and return their output paths.\n" +msgstr "" +"Usage: guix build [OPTION]... PAQUET-OU-DERIVATION...\n" +"Compiler le PAQUET-OU-DERIVATION donné et retourner leur chemin de sortie.\n" + +#: guix/scripts/build.scm:215 +msgid "" +"\n" +" -e, --expression=EXPR build the package or derivation EXPR evaluates to" +msgstr "" +"\n" +" -e, --expression=EXPR compiler le paquet ou la dérivation évalué par EXPR" + +#: guix/scripts/build.scm:217 +msgid "" +"\n" +" -S, --source build the packages' source derivations" +msgstr "" +"\n" +" -S, --source compiler les dérivations de source du paquet" + +#: guix/scripts/build.scm:219 +msgid "" +"\n" +" -s, --system=SYSTEM attempt to build for SYSTEM--e.g., \"i686-linux\"" +msgstr "" +"\n" +" -s, --system=SYSTEME essayer de compiler pour le SYSTEME donné, par exemple \"i686-linux\"" + +#: guix/scripts/build.scm:221 +msgid "" +"\n" +" --target=TRIPLET cross-build for TRIPLET--e.g., \"armel-linux-gnu\"" +msgstr "" +"\n" +" --target=TRIPLET effectuer une compilation croisée pour TRIPLET, par exemple \"armel-linux-gnu\"" + +#: guix/scripts/build.scm:223 +msgid "" +"\n" +" --with-source=SOURCE\n" +" use SOURCE when building the corresponding package" +msgstr "" +"\n" +" --with-source=SOURCE\n" +" utiliser la SOURCE donnée pour compiler le paquet correspondant" + +#: guix/scripts/build.scm:226 +msgid "" +"\n" +" --no-grafts do not graft packages" +msgstr "" +"\n" +" --no-grafts ne pas greffer kes paquets" + +#: guix/scripts/build.scm:228 +msgid "" +"\n" +" -d, --derivations return the derivation paths of the given packages" +msgstr "" +"\n" +" -d, --derivations retourner les chemins de dérivation pour les paquets donnés" + +#: guix/scripts/build.scm:230 +msgid "" +"\n" +" -r, --root=FILE make FILE a symlink to the result, and register it\n" +" as a garbage collector root" +msgstr "" +"\n" +" -r, --root=FICHIER faire de FICHIER un lien symbolique pointant sur le résultat\n" +" et l'enregistrer en tant que racine du garbage collector" + +#: guix/scripts/build.scm:233 +msgid "" +"\n" +" --log-file return the log file names for the given derivations" +msgstr "" +"\n" +" --log-file retourner les fichiers de journalisation pour les dérivations données" + +#: guix/scripts/build.scm:238 guix/scripts/download.scm:53 +#: guix/scripts/package.scm:451 guix/scripts/gc.scm:58 +#: guix/scripts/hash.scm:55 guix/scripts/pull.scm:81 +#: guix/scripts/substitute-binary.scm:560 guix/scripts/system.scm:371 +#: guix/scripts/lint.scm:262 +msgid "" +"\n" +" -h, --help display this help and exit" +msgstr "" +"\n" +" -h, --help afficher cette aide et quitter" + +#: guix/scripts/build.scm:240 guix/scripts/download.scm:55 +#: guix/scripts/package.scm:453 guix/scripts/gc.scm:60 +#: guix/scripts/hash.scm:57 guix/scripts/pull.scm:83 +#: guix/scripts/substitute-binary.scm:562 guix/scripts/system.scm:373 +#: guix/scripts/lint.scm:266 +msgid "" +"\n" +" -V, --version display version information and exit" +msgstr "" +"\n" +" -V, --version afficher les informations sur la version et quitter" + +#: guix/scripts/build.scm:366 +#, scheme-format +msgid "sources do not match any package:~{ ~a~}~%" +msgstr "les sources ne correspondent à aucun paquet:~{ ~a~}~%" + +#: guix/scripts/build.scm:395 guix/scripts/download.scm:96 +#: guix/scripts/package.scm:673 guix/scripts/gc.scm:122 +#: guix/scripts/pull.scm:213 guix/scripts/system.scm:426 +#: guix/scripts/lint.scm:313 +#, scheme-format +msgid "~A: unrecognized option~%" +msgstr "~A: option non reconnue~%" + +#: guix/scripts/build.scm:423 +#, scheme-format +msgid "no build log for '~a'~%" +msgstr "aucun journal de compilation pour \"~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 "" +"Usage: guix download [OPTION] URL\n" +"Télécharger le fichier à partir de l'URL spécifiée, l'ajouter au dépôt et\n" +"afficher son chemin et l'empreinte de son contenu.\n" +"\n" +"Formats supportés: 'nix-base32' (défaut), 'base32', et 'base16'\n" +"('hex' et 'hexadecimal' peuvent aussi être utilisés).\n" + +#: guix/scripts/download.scm:50 guix/scripts/hash.scm:50 +msgid "" +"\n" +" -f, --format=FMT write the hash in the given format" +msgstr "" +"\n" +" -f, --format=FORMAT écrire l'empreinte dans le FORMAT donné" + +#: guix/scripts/download.scm:73 guix/scripts/hash.scm:75 +#, scheme-format +msgid "unsupported hash format: ~a~%" +msgstr "format d'empreinte non supporté: ~a~%" + +#: guix/scripts/download.scm:106 +#, scheme-format +msgid "~a: failed to parse URI~%" +msgstr "~a: impossible d'évaluer l'URI~%" + +#: guix/scripts/download.scm:117 +#, scheme-format +msgid "~a: download failed~%" +msgstr "~a: le téléchargement a échoué~%" + +#: guix/scripts/package.scm:97 +#, scheme-format +msgid "failed to build the empty profile~%" +msgstr "échec de la compilation du profil vide~%" + +#: guix/scripts/package.scm:113 +#, scheme-format +msgid "switching from generation ~a to ~a~%" +msgstr "passage de la génération ~a à ~a~%" + +#: guix/scripts/package.scm:132 +#, scheme-format +msgid "nothing to do: already at the empty profile~%" +msgstr "aucune action à faire: profil courant vide" + +#: guix/scripts/package.scm:144 +#, scheme-format +msgid "deleting ~a~%" +msgstr "suppression de ~a~%" + +#: guix/scripts/package.scm:295 +#, scheme-format +msgid "package `~a' lacks output `~a'~%" +msgstr "le paquet \"~a\" requiert la sortie \"~a\"~%" + +#: guix/scripts/package.scm:312 +#, scheme-format +msgid "~a: package not found~%" +msgstr "~a: paquet introuvable~%" + +#: guix/scripts/package.scm:390 +#, scheme-format +msgid "The following environment variable definitions may be needed:~%" +msgstr "Il pourrait être nécessaire de définir les variables d'environnement suivantes:~%" + +#: guix/scripts/package.scm:406 +msgid "" +"Usage: guix package [OPTION]... PACKAGES...\n" +"Install, remove, or upgrade PACKAGES in a single transaction.\n" +msgstr "" +"Usage: guix package [OPTION]... PAQUETS...\n" +"Installer, supprimer ou mettre à jour les PAQUETS spécifiés en une seule transaction.\n" + +#: guix/scripts/package.scm:408 +msgid "" +"\n" +" -i, --install=PACKAGE install PACKAGE" +msgstr "" +"\n" +" -i, --install=PAQUET installer PAQUET" + +#: guix/scripts/package.scm:410 +msgid "" +"\n" +" -e, --install-from-expression=EXP\n" +" install the package EXP evaluates to" +msgstr "" +"\n" +" -e, --install-from-expression=EXP\n" +" installer le paquet évalué par EXP" + +#: guix/scripts/package.scm:413 +msgid "" +"\n" +" -r, --remove=PACKAGE remove PACKAGE" +msgstr "" +"\n" +" -r, --remove=PAQUET supprimer PAQUET" + +#: guix/scripts/package.scm:415 +msgid "" +"\n" +" -u, --upgrade[=REGEXP] upgrade all the installed packages matching REGEXP" +msgstr "" +"\n" +" -u, --upgrade[=REGEXP] mettre à jour tous les paquets installés correspondant à REGEXP" + +#: guix/scripts/package.scm:417 +msgid "" +"\n" +" --roll-back roll back to the previous generation" +msgstr "" +"\n" +" --roll-back revenir à la génération antérieure" + +#: guix/scripts/package.scm:419 +msgid "" +"\n" +" --search-paths display needed environment variable definitions" +msgstr "" +"\n" +" --search-paths afficher les définitions de variable d'environnement requises" + +#: guix/scripts/package.scm:421 +msgid "" +"\n" +" -l, --list-generations[=PATTERN]\n" +" list generations matching PATTERN" +msgstr "" +"\n" +" -l, --list-generations[=PATTERN]\n" +" lister les générations correspondant à PATTERN" + +#: guix/scripts/package.scm:424 +msgid "" +"\n" +" -d, --delete-generations[=PATTERN]\n" +" delete generations matching PATTERN" +msgstr "" +"\n" +" -d, --delete-generations[=PATTERN]\n" +" supprimer les générations correspondant à PATTERN" + +#: guix/scripts/package.scm:427 +msgid "" +"\n" +" -S, --switch-generation=PATTERN\n" +" switch to a generation matching PATTERN" +msgstr "" +"\n" +" -d, --delete-generations[=PATTERN]\n" +" basculer vers une génération correspondant à PATTERN" + +#: guix/scripts/package.scm:430 +msgid "" +"\n" +" -p, --profile=PROFILE use PROFILE instead of the user's default profile" +msgstr "" +"\n" +" -p, --profile=PROFIL utiliser PROFIL au lieu du profil par défaut de l'utilisateur" + +#: guix/scripts/package.scm:433 +msgid "" +"\n" +" --bootstrap use the bootstrap Guile to build the profile" +msgstr "" +"\n" +" --bootstrap utiliser le programme d'amorçage Guile pour compiler le profil" + +#: guix/scripts/package.scm:435 guix/scripts/pull.scm:74 +msgid "" +"\n" +" --verbose produce verbose output" +msgstr "" +"\n" +" --verbose utiliser le mode verbeux" + +#: guix/scripts/package.scm:438 +msgid "" +"\n" +" -s, --search=REGEXP search in synopsis and description using REGEXP" +msgstr "" +"\n" +" -s, --search=REGEXP chercher dans le synopsis et la description en utilisant REGEXP" + +#: guix/scripts/package.scm:440 +msgid "" +"\n" +" -I, --list-installed[=REGEXP]\n" +" list installed packages matching REGEXP" +msgstr "" +"\n" +" -I, --list-installed[=REGEXP]\n" +" lister les paquets installés correspondant à REGEXP" + +#: guix/scripts/package.scm:443 +msgid "" +"\n" +" -A, --list-available[=REGEXP]\n" +" list available packages matching REGEXP" +msgstr "" +"\n" +" -A, --list-available[=REGEXP]\n" +" lister les paquets disponibles correspondant à REGEXP" + +#: guix/scripts/package.scm:446 +msgid "" +"\n" +" --show=PACKAGE show details about PACKAGE" +msgstr "" +"\n" +" --show=PAQUET montrer des détails sur le PAQUET" + +#: guix/scripts/package.scm:677 +#, scheme-format +msgid "~A: extraneous argument~%" +msgstr "~A: argument superflu~%" + +#: guix/scripts/package.scm:687 +#, scheme-format +msgid "Try \"info '(guix) Invoking guix package'\" for more information.~%" +msgstr "Essayez \"info '(guix) Invoking guix package'\" pour plus d'information.~%" + +#: guix/scripts/package.scm:709 +#, scheme-format +msgid "error: while creating directory `~a': ~a~%" +msgstr "erreur: pendant la création du répertoire \"~a\": ~a~%" + +#: guix/scripts/package.scm:713 +#, scheme-format +msgid "Please create the `~a' directory, with you as the owner.~%" +msgstr "Veuillez créer un répertoire \"~a\" dont vous êtes le propriétaire.~%" + +#: guix/scripts/package.scm:720 +#, scheme-format +msgid "error: directory `~a' is not owned by you~%" +msgstr "erreur: vous de possédez pas le répertoire \"~a\"" + +#: guix/scripts/package.scm:723 +#, scheme-format +msgid "Please change the owner of `~a' to user ~s.~%" +msgstr "Veuillez définir ~s comme propriétaire de \"~a\".~%" + +#: guix/scripts/package.scm:756 +#, scheme-format +msgid "cannot switch to generation '~a'~%" +msgstr "impossible de passer à la génération \"~a\"~%" + +#: guix/scripts/package.scm:788 guix/scripts/package.scm:889 +#, scheme-format +msgid "invalid syntax: ~a~%" +msgstr "syntaxe non valide: ~a~%" + +#: guix/scripts/package.scm:825 +#, scheme-format +msgid "nothing to be done~%" +msgstr "aucune action à faire~%" + +#: guix/scripts/package.scm:840 +#, scheme-format +msgid "~a package in profile~%" +msgid_plural "~a packages in profile~%" +msgstr[0] "~a paquet dans le profile~%" +msgstr[1] "~a paquets dans le profile~%" + +#: guix/scripts/package.scm:855 +#, scheme-format +msgid "Generation ~a\t~a" +msgstr "Génération ~a\t~a" + +#: guix/scripts/package.scm:862 +#, scheme-format +msgid "~a\t(current)~%" +msgstr "~a\t(actuel)~%" + +#: guix/scripts/gc.scm:39 +msgid "" +"Usage: guix gc [OPTION]... PATHS...\n" +"Invoke the garbage collector.\n" +msgstr "" +"Usage: guix gc [OPTION]... CHEMINS...\n" +"Appeller le garbage collector.\n" + +#: guix/scripts/gc.scm:41 +msgid "" +"\n" +" -C, --collect-garbage[=MIN]\n" +" collect at least MIN bytes of garbage" +msgstr "" +"\n" +" -C, --collect-garbage[=MIN]\n" +" collecter au moins MIN octets dans le garbage-collector" + +#: guix/scripts/gc.scm:44 +msgid "" +"\n" +" -d, --delete attempt to delete PATHS" +msgstr "" +"\n" +" -d, --delete supprimer les CHEMINS" + +#: guix/scripts/gc.scm:46 +msgid "" +"\n" +" --list-dead list dead paths" +msgstr "" +"\n" +" --list-dead lister les chemins non valides" + +#: guix/scripts/gc.scm:48 +msgid "" +"\n" +" --list-live list live paths" +msgstr "" +"\n" +" --list-live lister les chemins valides" + +#: guix/scripts/gc.scm:51 +msgid "" +"\n" +" --references list the references of PATHS" +msgstr "" +"\n" +" --references lister les références de CHEMINS" + +#: guix/scripts/gc.scm:53 +msgid "" +"\n" +" -R, --requisites list the requisites of PATHS" +msgstr "" +"\n" +" -R, --requisites lister les prérequis de CHEMINS" + +#: guix/scripts/gc.scm:55 +msgid "" +"\n" +" --referrers list the referrers of PATHS" +msgstr "" +"\n" +" --referrers lister les référents de CHEMINS" + +#: guix/scripts/gc.scm:84 +#, scheme-format +msgid "invalid amount of storage: ~a~%" +msgstr "quantité de stockage non valide: ~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 "" +"Usage: guix hash [OPTION] FICHIER\n" +"Retourner l'empreinte cryptographique du FICHIER.\n" +"\n" +"Formats supportés: 'nix-base32' (défaut), 'base32', et 'base16' ('hex'\n" +"et 'hexadecimal' peuvent également être utilisés).\n" + +#: guix/scripts/hash.scm:52 +msgid "" +"\n" +" -r, --recursive compute the hash on FILE recursively" +msgstr "" +"\n" +" -r, --recursive calculer l'empreinte de FICHIER de manière récursive" + +#: guix/scripts/hash.scm:103 +#, scheme-format +msgid "unrecognized option: ~a~%" +msgstr "option non reconnue: ~a~%" + +#: guix/scripts/hash.scm:134 guix/ui.scm:252 +#, scheme-format +msgid "~a~%" +msgstr "~a~%" + +#: guix/scripts/hash.scm:137 +#, scheme-format +msgid "wrong number of arguments~%" +msgstr "nombre d'arguments incorrect~%" + +#: guix/scripts/pull.scm:72 +msgid "" +"Usage: guix pull [OPTION]...\n" +"Download and deploy the latest version of Guix.\n" +msgstr "" +"Usage: guix pull [OPTION]...\n" +"Télécharger et déployer la dernière version de Guix.\n" + +#: guix/scripts/pull.scm:76 +msgid "" +"\n" +" --url=URL download the Guix tarball from URL" +msgstr "" +"\n" +" --url=URL télécharger le tarball de Guix depuis URL" + +#: guix/scripts/pull.scm:78 +msgid "" +"\n" +" --bootstrap use the bootstrap Guile to build the new Guix" +msgstr "" +"\n" +" --bootstrap utiliser le programme d'amorçage Guile pour compiler le nouveau Guix" + +#: guix/scripts/pull.scm:132 +msgid "tarball did not produce a single source directory" +msgstr "la tarball n'a produit aucun répertoire source" + +#: guix/scripts/pull.scm:150 +#, scheme-format +msgid "unpacking '~a'...~%" +msgstr "dépaquetage \"~a\"...~%" + +#: guix/scripts/pull.scm:159 +msgid "failed to unpack source code" +msgstr "échec du dépaquetage du code source" + +#: guix/scripts/pull.scm:200 +#, scheme-format +msgid "updated ~a successfully deployed under `~a'~%" +msgstr "~a a été mis à jour et déployé avec succès sous \"~a\"~%" + +#: guix/scripts/pull.scm:203 +#, scheme-format +msgid "failed to update Guix, check the build log~%" +msgstr "échec de la mise à jour de Guix; consultez le journal de compilation~%" + +#: guix/scripts/pull.scm:205 +msgid "Guix already up to date\n" +msgstr "Guix est déja à jour\n" + +#: guix/scripts/pull.scm:215 +#, scheme-format +msgid "~A: unexpected argument~%" +msgstr "~A: argument inattendu~%" + +#: guix/scripts/pull.scm:224 +msgid "failed to download up-to-date source, exiting\n" +msgstr "impossible de télécharger une source à jour; fin\n" + +#: guix/scripts/substitute-binary.scm:80 +#, scheme-format +msgid "authentication and authorization of substitutes disabled!~%" +msgstr "authentification et autorisation des substituts désactivées !~%" + +#: guix/scripts/substitute-binary.scm:163 +#, scheme-format +msgid "download from '~a' failed: ~a, ~s~%" +msgstr "le téléchargement depuis '~a' a échoué: ~a, ~s~%" + +#: guix/scripts/substitute-binary.scm:178 +#, scheme-format +msgid "while fetching ~a: server is unresponsive~%" +msgstr "pendant la recherche de ~a: le serveur ne répond pas~%" + +#: guix/scripts/substitute-binary.scm:180 +#, scheme-format +msgid "try `--no-substitutes' if the problem persists~%" +msgstr "essayez l'option \"--no-substitutes\" si le problème persiste~%" + +#: guix/scripts/substitute-binary.scm:244 +#, scheme-format +msgid "signature version must be a number: ~a~%" +msgstr "la version de la signature doit être un nombre: ~a~%" + +#: guix/scripts/substitute-binary.scm:248 +#, scheme-format +msgid "unsupported signature version: ~a~%" +msgstr "version de signature non supportée: ~a~%" + +#: guix/scripts/substitute-binary.scm:256 +#, scheme-format +msgid "signature is not a valid s-expression: ~s~%" +msgstr "la signature n'est pas une s-expression valide: ~s~%" + +#: guix/scripts/substitute-binary.scm:260 +#, scheme-format +msgid "invalid format of the signature field: ~a~%" +msgstr "signature non valide pour \"~a\"~%" + +#: guix/scripts/substitute-binary.scm:295 +#, scheme-format +msgid "invalid signature for '~a'~%" +msgstr "signature non valide pour \"~a\"~%" + +#: guix/scripts/substitute-binary.scm:297 +#, scheme-format +msgid "hash mismatch for '~a'~%" +msgstr "empreinte non valide pour \"~a\"~%" + +#: guix/scripts/substitute-binary.scm:299 +#, scheme-format +msgid "'~a' is signed with an unauthorized key~%" +msgstr "\"~a\" est signé avec une clé non autorisée~%" + +#: guix/scripts/substitute-binary.scm:301 +#, scheme-format +msgid "signature on '~a' is corrupt~%" +msgstr "la signature de \"~a\" est corrompue~%" + +#: guix/scripts/substitute-binary.scm:338 +#, scheme-format +msgid "substitute at '~a' lacks a signature~%" +msgstr "le substitut à \"~a\" requiert une signature~%" + +#: guix/scripts/substitute-binary.scm:526 +#, scheme-format +msgid "Downloading, please wait...~%" +msgstr "Téléchargement en cours..." + +#: guix/scripts/substitute-binary.scm:528 +#, scheme-format +msgid "(Please consider upgrading Guile to get proper progress report.)~%" +msgstr "(Veuillez mettre Guile à jour pour obtenir le rapport de progression approprié.)~%" + +#: guix/scripts/substitute-binary.scm:541 +#, scheme-format +msgid "host name lookup error: ~a~%" +msgstr "erreur lors de la consultation du nom d'hôte: ~a~%" + +#: guix/scripts/substitute-binary.scm:550 +msgid "" +"Usage: guix substitute-binary [OPTION]...\n" +"Internal tool to substitute a pre-built binary to a local build.\n" +msgstr "" +"Usage: guix substitute-binary [OPTION]...\n" +"Outil interne pour substituer un binaire pré-compilé à une compilation locale.\n" + +#: guix/scripts/substitute-binary.scm:552 +msgid "" +"\n" +" --query report on the availability of substitutes for the\n" +" store file names passed on the standard input" +msgstr "" +"\n" +" --query afficher les substituts disponibles pour les\n" +" noms de fichier de dépôt passés sur l'entrée\n" +" standard" + +#: guix/scripts/substitute-binary.scm:555 +msgid "" +"\n" +" --substitute STORE-FILE DESTINATION\n" +" download STORE-FILE and store it as a Nar in file\n" +" DESTINATION" +msgstr "" +"\n" +" --substitute FICHIER-DEPOT DESTINATION\n" +" télécharger FICHIER-DEPOT et l'enregistrer comme un Nar\n" +" dans le fichier DESTINATION" + +#: guix/scripts/substitute-binary.scm:600 +msgid "ACL for archive imports seems to be uninitialized, substitutes may be unavailable\n" +msgstr "l'ACL pour l'import d'archives ne semble pas initialisée ; les substituts pourraient être indisponibles\n" + +#: guix/scripts/substitute-binary.scm:634 +#, scheme-format +msgid "these substitute URLs will not be used:~{ ~a~}~%" +msgstr "ces URL de substitution ne seront pas utilisées:~{ ~a~}~%" + +#: guix/scripts/substitute-binary.scm:660 +#, scheme-format +msgid "failed to look up host '~a' (~a), substituter disabled~%" +msgstr "impossible de trouver l'hôte \"~a\" (~a), substitution désactivée~%" + +#: guix/scripts/substitute-binary.scm:767 +#, scheme-format +msgid "~a: unrecognized options~%" +msgstr "~a: options non reconnues~%" + +#: guix/scripts/authenticate.scm:58 +#, scheme-format +msgid "cannot find public key for secret key '~a'~%" +msgstr "impossible de trouver la clé publique correspondant à la clé secrète \"~a\"~%" + +#: guix/scripts/authenticate.scm:78 +#, scheme-format +msgid "error: invalid signature: ~a~%" +msgstr "error: signature non valide: ~a~%" + +#: guix/scripts/authenticate.scm:80 +#, scheme-format +msgid "error: unauthorized public key: ~a~%" +msgstr "error: clé publique non autorisée: ~a~%" + +#: guix/scripts/authenticate.scm:82 +#, scheme-format +msgid "error: corrupt signature data: ~a~%" +msgstr "erreur: signature corrompue: ~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 "" +"Usage: guix authenticate OPTION...\n" +"Signer ou vérifier la signature du fichier donné. Cet outil est destiné\n" +"à être utilisé en interne par \"guix-daemon\".\n" + +#: guix/scripts/authenticate.scm:132 +msgid "wrong arguments" +msgstr "arguments non valides" + +#: guix/scripts/system.scm:74 +#, scheme-format +msgid "failed to open operating system file '~a': ~a~%" +msgstr "impossible d'ouvrir le fichier du système d'exploitation \"~a\": ~a~%" + +#: guix/scripts/system.scm:78 guix/ui.scm:258 +#, 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 "impossible de charger le fichier du système d'exploitation \"~a\": ~s~%" + +#: guix/scripts/system.scm:116 +#, scheme-format +msgid "failed to register '~a' under '~a'~%" +msgstr "impossible d'enregistrer \"~a\" sous \"~a\"~%" + +#: guix/scripts/system.scm:144 +#, scheme-format +msgid "initializing the current root file system~%" +msgstr "initialisation du système de fichier racine courant~%" + +#: guix/scripts/system.scm:162 guix/scripts/system.scm:325 +#, scheme-format +msgid "failed to install GRUB on device '~a'~%" +msgstr "échec de l'installation de GRUB sur le périphérique \"~a\"~%" + +#: guix/scripts/system.scm:197 +#, scheme-format +msgid "activating system...~%" +msgstr "activation du système...~%" + +#: guix/scripts/system.scm:239 +#, scheme-format +msgid "unrecognized boot parameters for '~a'~%" +msgstr "paramètres de démarrage non reconus pour \"~a\"~%" + +#: guix/scripts/system.scm:330 +#, scheme-format +msgid "initializing operating system under '~a'...~%" +msgstr "initialisation du système d'exploitation sous \"~a\"...~%" + +#: guix/scripts/system.scm:346 +msgid "" +"Usage: guix system [OPTION] ACTION FILE\n" +"Build the operating system declared in FILE according to ACTION.\n" +msgstr "" +"Usage: guix system [OPTION] ACTION FICHIER\n" +"Compiler le système d'exploitation déclaré dans FICHER en suivant ACTION.\n" + +#: guix/scripts/system.scm:349 +msgid "The valid values for ACTION are:\n" +msgstr "Les valeurs possibles pour ACTION sont: \n" + +#: guix/scripts/system.scm:350 +msgid " - 'reconfigure', switch to a new operating system configuration\n" +msgstr " - 'reconfigure', changer la configuration du système d'exploitation\n" + +#: guix/scripts/system.scm:352 +msgid " - 'build', build the operating system without installing anything\n" +msgstr " - 'build', compiler le système d'exploitation sans rien installer\n" + +#: guix/scripts/system.scm:354 +msgid " - 'vm', build a virtual machine image that shares the host's store\n" +msgstr " - 'vm', compiler une machine virtuelle partageant le dépôt de l'hôte\n" + +#: guix/scripts/system.scm:356 +msgid " - 'vm-image', build a freestanding virtual machine image\n" +msgstr " - 'vm-image', compiler une image autonome de machine virtuelle\n" + +#: guix/scripts/system.scm:358 +msgid " - 'disk-image', build a disk image, suitable for a USB stick\n" +msgstr " - 'disk-image', compiler une image disque adaptée pour une clé USB\n" + +#: guix/scripts/system.scm:360 +msgid " - 'init', initialize a root file system to run GNU.\n" +msgstr " - 'init', initialiser un système de fichier racine pour lancer GNU.\n" + +#: guix/scripts/system.scm:364 +msgid "" +"\n" +" --image-size=SIZE for 'vm-image', produce an image of SIZE" +msgstr "" +"\n" +" --image-size=TAILLE pour 'vm-image', produire une image de TAILLE" + +#: guix/scripts/system.scm:366 +msgid "" +"\n" +" --no-grub for 'init', do not install GRUB" +msgstr "" +"\n" +" --no-grub pour 'init', ne pas installer GRUB" + +#: guix/scripts/system.scm:368 +msgid "" +"\n" +" --full-boot for 'vm', make a full boot sequence" +msgstr "" +"\n" +" --full-boot pour 'vm', accomplire une séquence complète de démarrage" + +#: guix/scripts/system.scm:434 +#, scheme-format +msgid "~a: unknown action~%" +msgstr "~a: action inconnue~%" + +#: guix/scripts/system.scm:451 +#, scheme-format +msgid "wrong number of arguments for action '~a'~%" +msgstr "nombre d'arguments incorrect pour l'action \"~a\"~%" + +#: guix/scripts/system.scm:471 +#, scheme-format +msgid "no configuration file specified~%" +msgstr "aucun fichier de configuration spécifié~%" + +#: guix/scripts/lint.scm:51 +#, scheme-format +msgid "~a: ~a: ~a~%" +msgstr "~a: ~a: ~a~%" + +#: guix/scripts/lint.scm:72 +#, scheme-format +msgid "Available checkers:~%" +msgstr "Vérificateurs disponibles:~%" + +#: guix/scripts/lint.scm:226 +msgid "Validate package descriptions" +msgstr "Validers des descriptions de paquets" + +#: guix/scripts/lint.scm:230 +msgid "Identify inputs that should be native inputs" +msgstr "Identifier les entrées qui devraient être natives" + +#: guix/scripts/lint.scm:234 +msgid "Validate filenames of patches" +msgstr "Valider les noms de patches" + +#: guix/scripts/lint.scm:238 +msgid "Validate package synopsis" +msgstr "Valider les synopsis de paquets" + +#: guix/scripts/lint.scm:257 +msgid "" +"Usage: guix lint [OPTION]... [PACKAGE]...\n" +"Run a set of checkers on the specified package; if none is specified, run the checkers on all packages.\n" +msgstr "" +"Usage: guix lint [OPTION]... [PAQUET]...\n" +"Lancer un ensemble de vérificateurs sur le paquet spécifié; si aucun n'est spécifié, lancer les vérificateurs sur tous les paquets.\n" + +#: guix/scripts/lint.scm:259 +msgid "" +"\n" +" -c, --checkers=CHECKER1,CHECKER2...\n" +" only run the specificed checkers" +msgstr "" +"\n" +" -c, --checkers=CHECKER1,CHECKER2...\n" +" lancer uniquement les vérificateurs spécifiés" + +#: guix/scripts/lint.scm:264 +msgid "" +"\n" +" -l, --list-checkers display the list of available lint checkers" +msgstr "" +"\n" +" -l, --list-checkers affiche la liste des vérificateurs disponibles" + +#: guix/scripts/lint.scm:283 +#, scheme-format +msgid "~a: invalid checker" +msgstr "~a: vérificateur non valide" + +#: guix/gnu-maintenance.scm:373 +#, scheme-format +msgid "signature verification failed for `~a'~%" +msgstr "la vérification de la signature a échoué pour \"~a\"~%" + +#: guix/gnu-maintenance.scm:375 +#, scheme-format +msgid "(could be because the public key is not in your keyring)~%" +msgstr "(il est possible que la clé publique ne soit pas dans dans votre trousseau)~%" + +#: guix/gnu-maintenance.scm:450 +#, scheme-format +msgid "~a: could not locate source file" +msgstr "~a: le fichier source est introuvable" + +#: guix/gnu-maintenance.scm:455 +#, scheme-format +msgid "~a: ~a: no `version' field in source; skipping~%" +msgstr "~a: ~a: aucun champ \"version\" dans la source; ignoré~%" + +#: guix/ui.scm:135 +#, scheme-format +msgid "failed to install locale: ~a~%" +msgstr "impossible d'installer la locale: ~a~%" + +#: guix/ui.scm:154 +msgid "" +"Copyright (C) 2014 the Guix authors\n" +"License GPLv3+: GNU GPL version 3 or later \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 "" +"Copyright (C) 2014 les auteurs de Guix\n" +"Licence GPLv3+: GNU GPL version 3 ou ultérieure \n" +"Ceci est un logiciel libre: vous êtes libre de le modifier et de le redistribuer.\n" +"Il n'y a AUCUNE GARANTIE, dans la limite de ce qui est autorisé par la loi.\n" + +#: guix/ui.scm:162 +#, scheme-format +msgid "" +"\n" +"Report bugs to: ~a." +msgstr "" +"\n" +"Signalez toute anomalie à : ~a." + +#: guix/ui.scm:164 +#, scheme-format +msgid "" +"\n" +"~a home page: <~a>" +msgstr "" +"\n" +"~a page d'accueil: <~a>" + +#: guix/ui.scm:166 +msgid "" +"\n" +"General help using GNU software: " +msgstr "" +"\n" +"Aide générale sur l'utilisation des logiciels GNU: " + +#: guix/ui.scm:173 +#, scheme-format +msgid "~a: invalid number~%" +msgstr "~a: nombre non valide~%" + +#: guix/ui.scm:190 +#, scheme-format +msgid "invalid number: ~a~%" +msgstr "nombre non valide: ~a~%" + +#: guix/ui.scm:213 +#, scheme-format +msgid "unknown unit: ~a~%" +msgstr "unité inconnue: ~a~%" + +#: guix/ui.scm:224 +#, scheme-format +msgid "~a:~a:~a: package `~a' has an invalid input: ~s~%" +msgstr "~a:~a:~a: le paquet \"~a\" a une entrée non valide: ~s~%" + +#: guix/ui.scm:231 +#, scheme-format +msgid "~a: ~a: build system `~a' does not support cross builds~%" +msgstr "~a: ~a: le système de compilation \"~a\" ne supporte pas la compilation croisée~%" + +#: guix/ui.scm:236 +#, scheme-format +msgid "profile '~a' does not exist~%" +msgstr "le profile \"~a\" n'existe pas~%" + +#: guix/ui.scm:239 +#, scheme-format +msgid "generation ~a of profile '~a' does not exist~%" +msgstr "la génération ~a du profile \"~a\" n'existe pas~%" + +#: guix/ui.scm:243 +#, scheme-format +msgid "failed to connect to `~a': ~a~%" +msgstr "impossible de se connecter à \"~a\": ~a~%" + +#: guix/ui.scm:248 +#, scheme-format +msgid "build failed: ~a~%" +msgstr "la compilation a échoué: ~a~%" + +#: guix/ui.scm:277 +#, scheme-format +msgid "failed to read expression ~s: ~s~%" +msgstr "impossible de lire l'expression ~s: ~s~%" + +#: guix/ui.scm:283 +#, scheme-format +msgid "failed to evaluate expression `~a': ~s~%" +msgstr "impossible d'évaluer l'expression `~a': ~s~%" + +#: guix/ui.scm:292 +#, scheme-format +msgid "expression ~s does not evaluate to a package~%" +msgstr "l'expression ~s ne correspond à aucun paquet~%" + +#: guix/ui.scm:339 +#, scheme-format +msgid "~:[The following derivation would be built:~%~{ ~a~%~}~;~]" +msgid_plural "~:[The following derivations would be built:~%~{ ~a~%~}~;~]" +msgstr[0] "~:[La dérivation suivante serait compilée:~%~{ ~a~%~}~;~]" +msgstr[1] "~:[Les dérivations suivantes seraient compilées:~%~{ ~a~%~}~;~]" + +#: guix/ui.scm:344 +#, scheme-format +msgid "~:[The following file would be downloaded:~%~{ ~a~%~}~;~]" +msgid_plural "~:[The following files would be downloaded:~%~{ ~a~%~}~;~]" +msgstr[0] "~:[Le fichier suivant serait téléchargé:~%~{ ~a~%~}~;~]" +msgstr[1] "~:[Les fichiers suivants seraient téléchargés:~%~{ ~a~%~}~;~]" + +#: guix/ui.scm:350 +#, scheme-format +msgid "~:[The following derivation will be built:~%~{ ~a~%~}~;~]" +msgid_plural "~:[The following derivations will be built:~%~{ ~a~%~}~;~]" +msgstr[0] "~:[La dérivation suivante sera compilée:~%~{ ~a~%~}~;~]" +msgstr[1] "~:[Les dérivations suivantes seront compilées:~%~{ ~a~%~}~;~]" + +#: guix/ui.scm:355 +#, scheme-format +msgid "~:[The following file will be downloaded:~%~{ ~a~%~}~;~]" +msgid_plural "~:[The following files will be downloaded:~%~{ ~a~%~}~;~]" +msgstr[0] "~:[Le fichier suivant sera téléchargé:~%~{ ~a~%~}~;~]" +msgstr[1] "~:[Les fichiers suivants seront téléchargés:~%~{ ~a~%~}~;~]" + +#: guix/ui.scm:407 +#, scheme-format +msgid "The following package would be removed:~%~{~a~%~}~%" +msgid_plural "The following packages would be removed:~%~{~a~%~}~%" +msgstr[0] "Le paquet suivant serait supprimé:~%~{~a~%~}~%" +msgstr[1] "Les paquets suivants seraient supprimés:~%~{~a~%~}~%" + +#: guix/ui.scm:412 +#, scheme-format +msgid "The following package will be removed:~%~{~a~%~}~%" +msgid_plural "The following packages will be removed:~%~{~a~%~}~%" +msgstr[0] "Le paquet suivant sera supprimé:~%~{~a~%~}~%" +msgstr[1] "Les paquets suivants seront supprimés:~%~{~a~%~}~%" + +#: guix/ui.scm:425 +#, scheme-format +msgid "The following package would be upgraded:~%~{~a~%~}~%" +msgid_plural "The following packages would be upgraded:~%~{~a~%~}~%" +msgstr[0] "Le paquet suivant serait mis à jour:~%~{~a~%~}~%" +msgstr[1] "Les paquets suivants seraient mis à jour:~%~{~a~%~}~%" + +#: guix/ui.scm:430 +#, scheme-format +msgid "The following package will be upgraded:~%~{~a~%~}~%" +msgid_plural "The following packages will be upgraded:~%~{~a~%~}~%" +msgstr[0] "Le paquet suivant sera mis à jour:~%~{~a~%~}~%" +msgstr[1] "Les paquets suivants seront mis à jour:~%~{~a~%~}~%" + +#: guix/ui.scm:441 +#, scheme-format +msgid "The following package would be installed:~%~{~a~%~}~%" +msgid_plural "The following packages would be installed:~%~{~a~%~}~%" +msgstr[0] "Le paquet suivant serait installé:~%~{~a~%~}~%" +msgstr[1] "Les paquets suivants seraient installés:~%~{~a~%~}~%" + +#: guix/ui.scm:446 +#, scheme-format +msgid "The following package will be installed:~%~{~a~%~}~%" +msgid_plural "The following packages will be installed:~%~{~a~%~}~%" +msgstr[0] "Le paquet suivant sera installé:~%~{~a~%~}~%" +msgstr[1] "Les paquets suivants seront installés:~%~{~a~%~}~%" + +#: guix/ui.scm:463 +msgid "" +msgstr "" + +#: guix/ui.scm:489 +#, scheme-format +msgid "failed to create configuration directory `~a': ~a~%" +msgstr "impossible de créer le répertoire de configuration \"~a\": ~a~%" + +#: guix/ui.scm:589 guix/ui.scm:603 +msgid "unknown" +msgstr "inconnu" + +#: guix/ui.scm:712 +#, scheme-format +msgid "invalid argument: ~a~%" +msgstr "argument non valide: ~a~%" + +#: guix/ui.scm:717 +#, scheme-format +msgid "Try `guix --help' for more information.~%" +msgstr "Essayez \"guix --help\" pour plus d'informations.~%" + +#: guix/ui.scm:747 +msgid "" +"Usage: guix COMMAND ARGS...\n" +"Run COMMAND with ARGS.\n" +msgstr "" +"Usage: guix COMMANDE ARGS...\n" +"Lance la COMMANDE avec les arguments ARGS.\n" + +#: guix/ui.scm:750 +msgid "COMMAND must be one of the sub-commands listed below:\n" +msgstr "COMMANDE doit être une des sous-commandes listées ci-dessous:\n" + +#: guix/ui.scm:770 +#, scheme-format +msgid "guix: ~a: command not found~%" +msgstr "guix: ~a: commande introuvable~%" + +#: guix/ui.scm:788 +#, scheme-format +msgid "guix: missing command name~%" +msgstr "guix: nom de commande manquant~%" + +#: guix/ui.scm:796 +#, scheme-format +msgid "guix: unrecognized option '~a'~%" +msgstr "guix: option \"~a\" non reconnue ~%" + +#: guix/http-client.scm:217 +#, scheme-format +msgid "using Guile ~a, which does not support ~s encoding~%" +msgstr "utilisation de Guile ~a, qui ne supporte pas l'encodage ~s~%" + +#: guix/http-client.scm:220 +#, scheme-format +msgid "download failed; use a newer Guile~%" +msgstr "le téléchargement a échoué; veuillez utiliser une version plus récente de Guile~%" + +#: guix/http-client.scm:232 +#, scheme-format +msgid "following redirection to `~a'...~%" +msgstr "redirection vers \"~a\"...~%" + +#: guix/http-client.scm:241 +msgid "download failed" +msgstr "le téléchargement a échoué" + +#: guix/nar.scm:155 +msgid "signature is not a valid s-expression" +msgstr "la signature n'est pas une s-expression valide" + +#: guix/nar.scm:164 +msgid "invalid signature" +msgstr "signature non valide" + +#: guix/nar.scm:168 +msgid "invalid hash" +msgstr "empreinte non valide" + +#: guix/nar.scm:176 +msgid "unauthorized public key" +msgstr "clé publique non autorisée" + +#: guix/nar.scm:181 +msgid "corrupt signature data" +msgstr "signature corrompue" + +#: guix/nar.scm:201 +msgid "corrupt file set archive" +msgstr "archive corrompue" + +#: guix/nar.scm:211 +#, scheme-format +msgid "importing file or directory '~a'...~%" +msgstr "import du fichier ou répertoire \"~a\"...~%" + +#: guix/nar.scm:220 +#, scheme-format +msgid "found valid signature for '~a'~%" +msgstr "signature valide trouvée pour \"~a\"~%" + +#: guix/nar.scm:227 +msgid "imported file lacks a signature" +msgstr "les fichiers importés requièrent une signature" + +#: guix/nar.scm:266 +msgid "invalid inter-file archive mark" +msgstr "marque d'archive inter-fichier non valide" diff --git a/po/packages/LINGUAS b/po/packages/LINGUAS index 6ba2fe22cc..19a3bbf515 100644 --- a/po/packages/LINGUAS +++ b/po/packages/LINGUAS @@ -4,6 +4,7 @@ de en@boldquot en@quot eo +fr hu pt_BR sr diff --git a/po/packages/fr.po b/po/packages/fr.po new file mode 100644 index 0000000000..5c086c0771 --- /dev/null +++ b/po/packages/fr.po @@ -0,0 +1,1567 @@ +# French translation of guix-packages. +# Copyright (C) 2014 Free Software Foundation, Inc. +# This file is distributed under the same license as the guix package. +# Rémy Chevalier , 2014. +msgid "" +msgstr "" +"Project-Id-Version: guix-packages 0.8\n" +"Report-Msgid-Bugs-To: ludo@gnu.org\n" +"POT-Creation-Date: 2014-11-10 15:37+0100\n" +"PO-Revision-Date: 2014-12-20 22:00+0100\n" +"Last-Translator: Rémy Chevalier \n" +"Language-Team: French \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: gnu/packages/aspell.scm:42 +msgid "Spell checker" +msgstr "Correcteur orthographique" + +#: gnu/packages/aspell.scm:44 +msgid "" +"Aspell is a spell-checker which can be used either as a library or as\n" +"a standalone program. Notable features of Aspell include its full support of\n" +"documents written in the UTF-8 encoding and its ability to use multiple\n" +"dictionaries, including personal ones." +msgstr "" +"Aspell est un correcteur orthographique pouvant être utilisé comme une\n" +"bibliothèque ou comme un programme. Aspell inclut notamment un support complet\n" +"des documents encodés UTF-8 et la possibilité d'utiliser plusieurs dictionnaires\n" +"y compris personnels." + +#: gnu/packages/aspell.scm:84 +msgid "This package provides a dictionary for the GNU Aspell spell checker." +msgstr "Ce paquet fournit un dictionnaire pour le correcteur orthographique Aspell" + +#: gnu/packages/backup.scm:87 +msgid "Encrypted backup using rsync algorithm" +msgstr "Système de sauvegarde chiffrée utilisant l'algorithme rsync" + +#: gnu/packages/backup.scm:89 +msgid "" +"Duplicity backs up directories by producing encrypted tar-format volumes\n" +"and uploading them to a remote or local file server. Because duplicity uses\n" +"librsync, the incremental archives are space efficient and only record the\n" +"parts of files that have changed since the last backup. Because duplicity\n" +"uses GnuPG to encrypt and/or sign these archives, they will be safe from\n" +"spying and/or modification by the server." +msgstr "" +"Duplicity sauvegarde des dossiers en créant des volumes chiffrés au format\n" +"tar et en envoyant ceux-ci vers un serveur de fichier local ou distant. Parce que\n" +"Duplicity utilise librsync, les archives incrémentales sont peu encombrantes\n" +"et ne contiennent que les parties des fichiers qui ont été modifiées depuis la\n" +"dernière sauvegarde. L'utilisation de GnuPG pour chiffrer ou signer ces archives\n" +"permet de prévenir tout rique d'espionnage ou d'altération par le serveur." + +#: gnu/packages/backup.scm:123 +msgid "Simple incremental backup tool" +msgstr "Outil simple de sauvegarde incrémentale" + +#: gnu/packages/backup.scm:125 +msgid "" +"Hdup2 is a backup utilty, its aim is to make backup really simple. The\n" +"backup scheduling is done by means of a cron job. It supports an\n" +"include/exclude mechanism, remote backups, encrypted backups and split\n" +"backups (called chunks) to allow easy burning to CD/DVD." +msgstr "" +"Hdup2 est un outil de sauvegarde convivial. La planification des sauvegardes\n" +"s'effectue au moyen de tâches cron. Il supporte un mécanisme\n" +"d'inclusion et d'exclusion, les sauvegardes distantes, chiffrées ou\n" +"morcelées (appellées chunks) permettant un gravure sur CD/DVD aisée." + +#: gnu/packages/backup.scm:178 +msgid "Multi-format archive and compression library" +msgstr "Bibliothèque de compression et d'archivage multi-format" + +#: gnu/packages/backup.scm:180 +msgid "" +"Libarchive provides a flexible interface for reading and writing\n" +"archives in various formats such as tar and cpio. Libarchive also supports\n" +"reading and writing archives compressed using various compression filters such\n" +"as gzip and bzip2. The library is inherently stream-oriented; readers\n" +"serially iterate through the archive, writers serially add things to the\n" +"archive. In particular, note that there is currently no built-in support for\n" +"random access nor for in-place modification." +msgstr "" +"Libarchive fournit une interface flexible pour lire et écrire\n" +"des archives dans des formats variés tels que tar ou cpio. Libarchive supporte\n" +"aussi la lecture et l'écriture d'archives compressées au moyen de filtres divers\n" +"comme gzip ou bzip2. La bibliothèque est intrinsèquement orientée flux; des\n" +"lecteurs itèrent sur l'archive alors que des rédacteurs ajoutent des données à\n" +"l'archive en continu. Notez qu'il n'existe pour l'instant aucun support intégré\n" +"pour les accès aléatoires ou les modifications ponctuelles." + +#: gnu/packages/backup.scm:243 +msgid "Provide a list of files to backup" +msgstr "Fournit une liste des fichiers à sauvegarder" + +#: gnu/packages/backup.scm:245 +msgid "" +"Rdup is a utility inspired by rsync and the plan9 way of doing backups.\n" +"Rdup itself does not backup anything, it only print a list of absolute\n" +"filenames to standard output. Auxiliary scripts are needed that act on this\n" +"list and implement the backup strategy." +msgstr "" +"Rdup est un utilitaire inspiré par rsync et par la façon dont plan9 réalise\n" +"les sauvegardes. Rdup ne sauvegarde rien en tant que tel mais affiche une\n" +"liste de fichiers sur la sortie standard. Des scripts auxiliaires utilisant\n" +"cette liste et implantant la stratégie de sauvegarde sont nécessaires." + +#: gnu/packages/backup.scm:275 +msgid "Tar-compatible archiver" +msgstr "Archiveur compatible avec le format tar" + +#: gnu/packages/backup.scm:277 +msgid "" +"Btar is a tar-compatible archiver which allows arbitrary compression and\n" +"ciphering, redundancy, differential backup, indexed extraction, multicore\n" +"compression, input and output serialisation, and tolerance to partial archive\n" +"errors." +msgstr "" +"Btar est un archiveur compatible avec le format tar permettant la compression\n" +"et le chiffrement arbitraires, la redondance, les sauvegardes différentielles,\n" +"l'extraction indexée, la compression multi-coeurs, la sérialisation des entrées-\n" +"sorties et la tolérance aux erreurs d'archivage partielles." + +#: gnu/packages/backup.scm:305 +msgid "Local/remote mirroring+incremental backup" +msgstr "Sauvegarde mirroir/incrémentales, locales ou distantes" + +#: gnu/packages/backup.scm:307 +msgid "" +"Rdiff-backup backs up one directory to another, possibly over a network.\n" +"The target directory ends up a copy of the source directory, but extra reverse\n" +"diffs are stored in a special subdirectory of that target directory, so you\n" +"can still recover files lost some time ago. The idea is to combine the best\n" +"features of a mirror and an incremental backup. Rdiff-backup also preserves\n" +"subdirectories, hard links, dev files, permissions, uid/gid ownership,\n" +"modification times, extended attributes, acls, and resource forks. Also,\n" +"rdiff-backup can operate in a bandwidth efficient manner over a pipe, like\n" +"rsync. Thus you can use rdiff-backup and ssh to securely back a hard drive up\n" +"to a remote location, and only the differences will be transmitted. Finally,\n" +"rdiff-backup is easy to use and settings have sensical defaults." +msgstr "" +"Rdiff-backup sauvegarde un répertoire vers un autre, éventuellement sur un\n" +"réseau. Le répertoire cible devient une copie du répertoire source, mais le\n" +"contenu en trop est stocké dans un sous-répertoire du répertoire cible de façon\n" +"à ce que vous puissiez récupérer les fichiers perdus. L'idée est de combiner les\n" +"meilleures fonctionnalités d'un mirroir et d'une sauvegarde incrémentale.\n" +"Rdiff-backup préserve les sous-répertoires, liens matériels, fichiers dev,\n" +"permissions, uid/gid des propriétaires, dates de modification, attributs étendus,\n" +"acls et forks de ressources. Rdiff-backup peut aussi opérer d'une manière efficace\n" +"sur un pipe tout comme rsync. Vous pouvez ainsi utiliser rdiff-bacup et ssh pour\n" +"sauvegarder de manière sûre un disque dur vers une machine distante, et seule la\n" +"différence sera transmise. Enfin, rdiff-backup est simple d'utilisation et la\n" +"configuration par défaut sera généralement suffisante." + +#: gnu/packages/base.scm:56 +msgid "Hello, GNU world: An example GNU package" +msgstr "Hello, GNU world: Un exemple de paquet GNU" + +#: gnu/packages/base.scm:58 +msgid "" +"GNU Hello prints the message \"Hello, world!\" and then exits. It\n" +"serves as an example of standard GNU coding practices. As such, it supports\n" +"command-line arguments, multiple languages, and so on." +msgstr "" +"GNU Hello affiche le message \"Hello, world!\" puis se termine. Il sert\n" +"d'exemple pour les pratiques de programmation GNU standard. Il supporte\n" +"les arguments en ligne de commande, plusieurs langages, etc." + +#: gnu/packages/base.scm:76 +msgid "Print lines matching a pattern" +msgstr "Affiche les lignes correspondant à un pattern donné" + +#: gnu/packages/base.scm:78 +msgid "" +"grep is a tool for finding text inside files. Text is found by\n" +"matching a pattern provided by the user in one or many files. The pattern\n" +"may be provided as a basic or extended regular expression, or as fixed\n" +"strings. By default, the matching text is simply printed to the screen,\n" +"however the output can be greatly customized to include, for example, line\n" +"numbers. GNU grep offers many extensions over the standard utility,\n" +"including, for example, recursive directory searching." +msgstr "" +"grep est un outil dédié à la recherche de texte dans des fichiers. Un texte\n" +"peut être trouvé dans un ou plusieurs fichiers à partir d'un pattern fourni par l'utilisateur.\n" +"Le pattern peut être fourni comme un expression régulière basique ou étendue, ou comme\n" +"des chaines fixes. Par défaut, le texte trouvé est simplement affiché à l'écran\n" +"mais il est possible de personnaliser la sortie pour inclure, par exemple, les\n" +"les numéros de ligne. GNU grep offre de nombreuses extensions, incluant par exemple\n" +"la recherche récursive sur des répertoires." + +#: gnu/packages/base.scm:100 +msgid "Stream editor" +msgstr "Éditeur de flux" + +#: gnu/packages/base.scm:115 +msgid "" +"Sed is a non-interactive, text stream editor. It receives a text\n" +"input from a file or from standard input and it then applies a series of text\n" +"editing commands to the stream and prints its output to standard output. It\n" +"is often used for substituting text patterns in a stream. The GNU\n" +"implementation offers several extensions over the standard utility." +msgstr "" +"Sed est un éditeur de texte par flux non interactif. Il reçoit un texte\n" +"depuis un fichier ou l'entrée standard et lui applique une série de moficiation\n" +"puis affiche le résultat sur la sortie. Il est souvent utilisé pour substituer\n" +"des morceaux de texte dans un flux. L'implantation GNU ajoute plusieurs extensions\n" +"à l'utilitaire standard." + +#: gnu/packages/base.scm:135 +msgid "Managing tar archives" +msgstr "Gestion d'archives tar" + +#: gnu/packages/base.scm:137 +msgid "" +"Tar provides the ability to create tar archives, as well as the\n" +"ability to extract, update or list files in an existing archive. It is\n" +"useful for combining many files into one larger file, while maintaining\n" +"directory structure and file information such as permissions and\n" +"creation/modification dates. GNU tar offers many extensions over the\n" +"standard utility." +msgstr "" +"Tar permet de créer des archives tar ainsi que d'extraire, de mettre à jour\n" +"et de lister les fichiers présents dans ces archives. Il est utile pour combiner\n" +"de nombreux fichiers au sein d'un fichier plus grand tout en préservant\n" +"la structure des répertoires et certaines informations comme\n" +"les permissions et les dates de création/modification. GNU tar ajoute de\n" +"nombreuses extensions à l'utilaire standard." + +#: gnu/packages/base.scm:161 +msgid "Apply differences to originals, with optional backups" +msgstr "Applique les différences aux originaux, avec sauvegardes optionnelles" + +#: gnu/packages/base.scm:163 +msgid "" +"Patch is a program that applies changes to files based on differences\n" +"laid out as by the program \"diff\". The changes may be applied to one or more\n" +"files depending on the contents of the diff file. It accepts several\n" +"different diff formats. It may also be used to revert previously applied\n" +"differences." +msgstr "" +"Patch est une programme qui applique des modifications aux fichiers basées\n" +"sur les différences données par le programme \"diff\". Les changements peuvent\n" +"être appliqués sur un ou plusieurs fichiers selon le contenu du fichier diff. Il\n" +"accepte différents formats diff. Il peut aussi être utilisé pour annuler des\n" +"différences appliquées antérieurement." + +#: gnu/packages/base.scm:183 +msgid "Comparing and merging files" +msgstr "Comparaison et fusion des fichiers" + +#: gnu/packages/base.scm:185 +msgid "" +"GNU Diffutils is a package containing tools for finding the\n" +"differences between files. The \"diff\" command is used to show how two files\n" +"differ, while \"cmp\" shows the offsets and line numbers where they differ. \n" +"\"diff3\" allows you to compare three files. Finally, \"sdiff\" offers an\n" +"interactive means to merge two files." +msgstr "" +"GNU Diffutils est un paquet contenant des outils pour trouver\n" +"des différences entre fichiers. La commande \"diff\" est utilisée pour\n" +"déterminer les différences entre fichiers alors que \"cmp\" montre les\n" +"numéros de lignes et décalages où ils diffèrent. \"diff3\" permet de comparer\n" +"trois fichiers. Enfin, \"sdiff\" offre un moyen interactif de fusionner deux fichiers." + +#: gnu/packages/base.scm:212 +msgid "Operating on files matching given criteria" +msgstr "Opération sur les fichiers correspondant au critère donné" + +#: gnu/packages/base.scm:214 +msgid "" +"Findutils supplies the basic file directory searching utilities of the\n" +"GNU system. It consists of two primary searching utilities: \"find\"\n" +"recursively searches for files in a directory according to given criteria and\n" +"\"locate\" lists files in a database that match a query. Two auxiliary tools\n" +"are included: \"updatedb\" updates the file name database and \"xargs\" may be\n" +"used to apply commands with arbitrarily long arguments." +msgstr "" +"Findutils fournit les outils basiques de recherche de fichier du système GNU.\n" +"Il consiste en deux utilitaires de recherche : \"find\" cherche récursivement les fichiers\n" +"correspondant à un critère donné dans un dossier et \"locate\" liste les fichiers\n" +"présents dans une base de données à partir d'une requête. Deux outils auxiliaires\n" +"sont inclus : \"updatedb\" met à jour la base de données et \"xargs\" peut être utilisé \n" +"pour appliquer des commandes avec des arguments longs et arbitraires" + +#: gnu/packages/base.scm:264 +msgid "Core GNU utilities (file, text, shell)" +msgstr "Utilitaires GNU (fichier, texte, shell)" + +#: gnu/packages/base.scm:266 +msgid "" +"GNU Coreutils includes all of the basic command-line tools that are\n" +"expected in a POSIX system. These provide the basic file, shell and text\n" +"manipulation functions of the GNU system. Most of these tools offer extended\n" +"functionality beyond that which is outlined in the POSIX standard." +msgstr "" +"GNU Coreutils inclut tous les outils basiques en ligne de commande\n" +"attendus dans un système POSIX. Ceux-ci fournissent les fonctions basiques de manipulation\n" +"de fichiers, du shell et de textes d'un système GNU. Ils offrent pour la plupart\n" +"des fonctionnalités étendues au-delà de ce qui est défini dans le standard POSIX." + +#: gnu/packages/base.scm:300 +msgid "Remake files automatically" +msgstr "Recompiler les fichiers automatiquement" + +#: gnu/packages/base.scm:302 +msgid "" +"Make is a program that is used to control the production of\n" +"executables or other files from their source files. The process is\n" +"controlled from a Makefile, in which the developer specifies how each file is\n" +"generated from its source. It has powerful dependency resolution and the\n" +"ability to determine when files have to be regenerated after their sources\n" +"change. GNU make offers many powerful extensions over the standard utility." +msgstr "" +"Make est un programme pouvant être utilisé pour contrôler la création\n" +"d'exécutables ou d'autres fichiers depuis leurs fichiers sources. Le processus\n" +"est contrôlé depuis un fichier Makefile dans lequel le développeur spécifie\n" +"comment chaque fichier est généré depuis ses sources. Cet outil a un puissant système\n" +"de résolution des dépendances et peut déterminer quand les fichiers doivent être\n" +"regénérés. GNU make possède beaucoup d'extensions en plus de\n" +"l'utilitaire standard." + +#: gnu/packages/base.scm:347 +msgid "Binary utilities: bfd gas gprof ld" +msgstr "Utilitaires binaires: bfd gas gprof ld" + +#: gnu/packages/base.scm:349 +msgid "" +"GNU Binutils is a collection of tools for working with binary files.\n" +"Perhaps the most notable are \"ld\", a linker, and \"as\", an assembler.\n" +"Other tools include programs to display binary profiling information, list\n" +"the strings in a binary file, and utilities for working with archives. The\n" +"\"bfd\" library for working with executable and object formats is also\n" +"included." +msgstr "" +"GNU Binutils est une collection d'outils pour travailler avec les fichiers binaires.\n" +"Les plus notables sont peut-être \"ld\", un éditeur de liens, et \"as\", un assembleur.\n" +"Les autres outils incluent des programmes pour afficher des informations de profilage sur \n" +"les binaires, lister les chaines de caractères et des utilitaires pour travailler avec\n" +"des archives. La bibliothèque \"bfd\", permettant de travailler avec des exécutables et des\n" +"des formats objet, est aussi incluse." + +#: gnu/packages/base.scm:491 +msgid "The GNU C Library" +msgstr "La bibliothèque GNU C" + +#: gnu/packages/base.scm:493 +msgid "" +"Any Unix-like operating system needs a C library: the library which\n" +"defines the \"system calls\" and other basic facilities such as open, malloc,\n" +"printf, exit...\n" +"\n" +"The GNU C library is used as the C library in the GNU system and most systems\n" +"with the Linux kernel." +msgstr "" +"Tout système d'exploitation basé sur Unix requiert une bibliothèque C:\n" +"la bibliothèque qui définit les \"appels système\" et autres fonctions basiques\n" +"telles que open, malloc, printf, exit, ... La bibliothèque GNU C est utilisée comme\n" +"bibliothèque C dans les systèmes GNU et la plupart des systèmes basés sur le noyau Linux." + +#: gnu/packages/base.scm:562 +msgid "Database of current and historical time zones" +msgstr "Base de données des fuseaux horaires courant et historiques" + +#: gnu/packages/base.scm:563 +msgid "" +"The Time Zone Database (often called tz or zoneinfo)\n" +"contains code and data that represent the history of local time for many\n" +"representative locations around the globe. It is updated periodically to\n" +"reflect changes made by political bodies to time zone boundaries, UTC offsets,\n" +"and daylight-saving rules." +msgstr "" +"La base de données des fuseaux horaires (souvent appelée \"tz\" pour \"zoneinfo\")\n" +"contient du code et des données représentant l'historique de l'heure locale pour\n" +"de nombreux endroits représentatifs dans le monde. Elle est mise à jour périodiquement\n" +"pour refléter les changements effectués par les entités politiques aux limites de ces zones,\n" +"les décalages UTC et les changements d'heures." + +#: gnu/packages/databases.scm:83 +msgid "Berkeley database" +msgstr "Base de données Berkeley" + +#: gnu/packages/databases.scm:85 +msgid "" +"Berkeley DB is an embeddable database allowing developers the choice of\n" +"SQL, Key/Value, XML/XQuery or Java Object storage for their data model." +msgstr "" +"Berkeley DB est une base de données embarquée proposant aux développeurs\n" +"le choix de SQL, clé/valeur, XML/XQuery ou Java Object Storage pour leur\n" +"modèle de données." + +#: gnu/packages/databases.scm:143 +msgid "Fast, easy to use, and popular database" +msgstr "Base de données rapide, facile d'utilisation et populaire" + +#: gnu/packages/databases.scm:145 +msgid "" +"MySQL is a fast, reliable, and easy to use relational database\n" +"management system that supports the standardized Structured Query\n" +"Language." +msgstr "" +"MySQL est un système de gestion de base de données relationnelle rapide,\n" +" fiable et facile d'emploi, supportant le SQL standardisé." + +#: gnu/packages/databases.scm:166 +msgid "Powerful object-relational database system" +msgstr "Système de base de données relationnelle puissant" + +#: gnu/packages/databases.scm:168 +msgid "" +"PostgreSQL is a powerful object-relational database system. It is fully\n" +"ACID compliant, has full support for foreign keys, joins, views, triggers, and\n" +"stored procedures (in multiple languages). It includes most SQL:2008 data\n" +"types, including INTEGER, NUMERIC, BOOLEAN, CHAR, VARCHAR, DATE, INTERVAL, and\n" +"TIMESTAMP. It also supports storage of binary large objects, including\n" +"pictures, sounds, or video." +msgstr "" +"PostgreSQL est un sytème de base de données relationnelle puissant. Totalement\n" +"conforme à ACID, il possède un support complet des clés étrangères, jointures, vues, \n" +"triggers, et procédures stockées (dans plusieurs langages). Il inclut la plupart des\n" +"types de données SQL:2008, y compris INTEGER, NUMERIC, BOOLEAN, CHAR, VARCHAR, DATE, \n" +"INTERVAL et TIMESTAMP. Il supporte aussi le stockage binaire de grands objets, dont\n" +"les images, le son et la vidéo." + +#: gnu/packages/databases.scm:203 +msgid "Manipulate plain text files as databases" +msgstr "Manipule les fichiers texte comme des bases de données" + +#: gnu/packages/databases.scm:205 +msgid "" +"GNU Recutils is a set of tools and libraries for creating and\n" +"manipulating text-based, human-editable databases. Despite being text-based,\n" +"databases created with Recutils carry all of the expected features such as\n" +"unique fields, primary keys, time stamps and more. Many different field\n" +"types are supported, as is encryption." +msgstr "" +"GNU Recutils est un ensemble d'outils et de bibliothèques permettant de\n" +"créer et de manipuler des bases de données textuelles. Bien que textuelles,\n" +"les bases de données créées avec Recutils fournissent toutes les fonctionnalités\n" +"attendues d'une base de données, telles que les champs uniques, les clés primaires,\n" +"les timestamps et plus encore. De nombreux types de champs sont supportés, tout comme\n" +"le chiffrement." + +#: gnu/packages/databases.scm:243 +msgid "The SQLite database management system" +msgstr "Le système de gestion de bases de données SQLite" + +#: gnu/packages/databases.scm:245 +msgid "" +"SQLite is a software library that implements a self-contained, serverless,\n" +"zero-configuration, transactional SQL database engine. SQLite is the most\n" +"widely deployed SQL database engine in the world. The source code for SQLite\n" +"is in the public domain." +msgstr "SQLite est une bibliothèque logicielle implantant" + +#: gnu/packages/databases.scm:280 +msgid "Trivial database" +msgstr "Base de données triviale" + +#: gnu/packages/databases.scm:282 +msgid "" +"TDB is a Trivial Database. In concept, it is very much like GDBM,\n" +"and BSD's DB except that it allows multiple simultaneous writers and uses\n" +"locking internally to keep writers from trampling on each other. TDB is also\n" +"extremely small." +msgstr "" +"TDB est une base de données triviale. Elle se rapproche conceptuellement\n" +"de GDBM et de la base de données de BSD, si ce n'est qu'elle autorise plusieurs\n" +"accès simultanés en écriture et utilise un verrouillage interne pour éviter les\n" +"empiétements. Notez que TDB est aussi très léger." + +#: gnu/packages/databases.scm:301 +msgid "Database independent interface for Perl" +msgstr "Interface de base de données pour Perl" + +#: gnu/packages/databases.scm:302 +msgid "This package provides an database interface for Perl." +msgstr "Ce paquet fournit une interface de base de données pour Perl." + +#: gnu/packages/databases.scm:321 +msgid "SQlite interface for Perl" +msgstr "Interface SQLite pour Perl" + +#: gnu/packages/databases.scm:322 +msgid "" +"DBD::SQLite is a Perl DBI driver for SQLite, that includes\n" +"the entire thing in the distribution. So in order to get a fast transaction\n" +"capable RDBMS working for your Perl project you simply have to install this\n" +"module, and nothing else." +msgstr "" +"DBD::SQLite est driver DBI pour SQLite écrit en Perl. Il suffit d'installer ce\n" +"module dans votre projet Perl pour obtenir une" + +#: gnu/packages/databases.scm:342 +msgid "Data source abstraction library" +msgstr "Bibliothèque d'abstraction de source de données" + +#: gnu/packages/databases.scm:343 +msgid "" +"Unixodbc is a library providing an API with which to access\n" +"data sources. Data sources include SQL Servers and any software with an ODBC\n" +"Driver." +msgstr "" +"Unixodbc est une bibliothèque fournissant une API permettant\n" +"d'accéder à des sources de données. Ces sources incluent des serveurs SQL\n" +"où tout logiciel avec un driver ODBC." + +#: gnu/packages/gcc.scm:254 +msgid "GNU Compiler Collection" +msgstr "GNU Compiler Collection" + +#: gnu/packages/gcc.scm:256 +msgid "" +"GCC is the GNU Compiler Collection. It provides compiler front-ends\n" +"for several languages, including C, C++, Objective-C, Fortran, Java, Ada, and\n" +"Go. It also includes runtime support libraries for these languages." +msgstr "" +"GCC est la collection de compilateurs GNU. Il fournit des compilateurs\n" +"pour plusieurs langages, dont C, C++, Objective-C, Fortran, Java, Ada,\n" +"et Go. Il inclut également le support de bibliothèques pour ces langages." + +#: gnu/packages/gcc.scm:340 +msgid "Manipulating sets and relations of integer points bounded by linear constraints" +msgstr "" +"Manipulation des ensembles et relations d'entiers liés par des\n" +"contraintes linéaires." + +#: gnu/packages/gcc.scm:343 +msgid "" +"isl is a library for manipulating sets and relations of integer points\n" +"bounded by linear constraints. Supported operations on sets include\n" +"intersection, union, set difference, emptiness check, convex hull, (integer)\n" +"affine hull, integer projection, computing the lexicographic minimum using\n" +"parametric integer programming, coalescing and parametric vertex\n" +"enumeration. It also includes an ILP solver based on generalized basis\n" +"reduction, transitive closures on maps (which may encode infinite graphs),\n" +"dependence analysis and bounds on piecewise step-polynomials." +msgstr "" + +#: gnu/packages/gcc.scm:375 +msgid "Library to generate code for scanning Z-polyhedra" +msgstr "" + +#: gnu/packages/gcc.scm:377 +msgid "" +"CLooG is a free software library to generate code for scanning\n" +"Z-polyhedra. That is, it finds a code (e.g., in C, FORTRAN...) that\n" +"reaches each integral point of one or more parameterized polyhedra.\n" +"CLooG has been originally written to solve the code generation problem\n" +"for optimizing compilers based on the polytope model. Nevertheless it\n" +"is used now in various area e.g., to build control automata for\n" +"high-level synthesis or to find the best polynomial approximation of a\n" +"function. CLooG may help in any situation where scanning polyhedra\n" +"matters. While the user has full control on generated code quality,\n" +"CLooG is designed to avoid control overhead and to produce a very\n" +"effective code." +msgstr "CLooG est bibliothèque logiciel libre permettant de générer du code" + +#: gnu/packages/gettext.scm:74 +msgid "Tools and documentation for translation" +msgstr "Outils et documentation pour la traduction" + +#: gnu/packages/gettext.scm:76 +#, fuzzy +#| msgid "" +#| "GNU Gettext is a package providing a framework for translating the\n" +#| "textual output of programs into multiple languages. It provides translators\n" +#| "with the means to create message catalogs, as well as an Emacs mode to work\n" +#| "with them, and a runtime library to load translated messages from the\n" +#| "catalogs. Nearly all GNU packages use Gettext." +msgid "" +"GNU Gettext is a package providing a framework for translating the\n" +"textual output of programs into multiple languages. It provides translators\n" +"with the means to create message catalogs, as well as an Emacs mode to work\n" +"with them, and a runtime library to load translated messages from the\n" +"catalogs. Nearly all GNU packages use Gettext." +msgstr "" +"GNU Gettext est un paquet fournissant un framework pour la traduction de\n" +"sorties textuelles de programmes vers de nombreux langages. Il fournit\n" +"des traducteur dans le but de créer des catalogues de messages, ainsi qu'un\n" +"mode Emacs pour travailler avec ceux-ci, et une bibliothèque pour charger lesmessages depuis les catalogues. Presque tous les paquets GNU utilisent Gettext." + +#: gnu/packages/guile.scm:100 gnu/packages/guile.scm:163 +msgid "Scheme implementation intended especially for extensions" +msgstr "Implantation de Scheme spécialement destinée aux extensions" + +#: gnu/packages/guile.scm:102 gnu/packages/guile.scm:165 +msgid "" +"Guile is the GNU Ubiquitous Intelligent Language for Extensions, the\n" +"official extension language of the GNU system. It is an implementation of\n" +"the Scheme language which can be easily embedded in other applications to\n" +"provide a convenient means of extending the functionality of the application\n" +"without requiring the source code to be rewritten." +msgstr "" +"Guile (GNU Ubiquitous Intelligent Langage for Extensions) est le langage\n" +"d'extension officiel du système GNU. Il s'agit d'une implantation du langage\n" +"Scheme qui peut être facilement incluse dans d'autres applications pour faciliter\n" +"l'ajout de fonctionnalités sans avoir à réécrire le code source." + +#: gnu/packages/guile.scm:208 +msgid "Framework for building readers for GNU Guile" +msgstr "Framework pour la construction de lecteurs GNU Guile" + +#: gnu/packages/guile.scm:210 +msgid "" +"Guile-Reader is a simple framework for building readers for GNU Guile.\n" +"\n" +"The idea is to make it easy to build procedures that extend Guile’s read\n" +"procedure. Readers supporting various syntax variants can easily be written,\n" +"possibly by re-using existing “token readers” of a standard Scheme\n" +"readers. For example, it is used to implement Skribilo’s R5RS-derived\n" +"document syntax.\n" +"\n" +"Guile-Reader’s approach is similar to Common Lisp’s “read table”, but\n" +"hopefully more powerful and flexible (for instance, one may instantiate as\n" +"many readers as needed)." +msgstr "" +"Guile-Reader est un framework permettant de construire des lecteurs pour\n" +"GNU Guile. L'idée est de rendre facile la construction de procédures étendant\n" +"la procédure de lecture de Guile. Il est possible d'écrire facilement des lecteurs\n" +"supportant de nombreuses syntaxes. Par exemple, Guile-Reader est utilisé pour\n" +"implanter la syntaxe de documents basés sur R5RS de Skribilo.\n" +"\n" +"L'approche de Guile-Reader est similaire à la table de lecture de Lisp, mais\n" +"plus puissante et plus flexible (il est par exemple possible d'instancier autant\n" +"de lecteurs que nécessaires)." + +#: gnu/packages/guile.scm:263 +msgid "Guile bindings to ncurses" +msgstr "Bindings Guile pour ncurses" + +#: gnu/packages/guile.scm:265 +msgid "" +"guile-ncurses provides Guile language bindings for the ncurses\n" +"library." +msgstr "guile-ncurses fournit un binding Guile pour la biliothèque ncurses." + +#: gnu/packages/guile.scm:285 +msgid "Run jobs at scheduled times" +msgstr "Plannification de tâches" + +#: gnu/packages/guile.scm:287 +msgid "" +"GNU Mcron is a complete replacement for Vixie cron. It is used to run\n" +"tasks on a schedule, such as every hour or every Monday. Mcron is written in\n" +"Guile, so its configuration can be written in Scheme; the original cron\n" +"format is also supported." +msgstr "" +"GNU Mcron est un remplaçant à Vixie cron. Il est utilisé pour la planification\n" +"de tâches à intervalles réguliers (toutes les heures, tous les lundi, etc...).\n" +"Mcron est écrit en Guile et peut donc être configuré en Scheme ; le format cron\n" +"original est aussi supporté." + +#: gnu/packages/guile.scm:315 +msgid "Collection of useful Guile Scheme modules" +msgstr "Collection de modules Scheme utiles pour Guile" + +#: gnu/packages/guile.scm:317 +msgid "" +"Guile-Lib is intended as an accumulation place for pure-scheme Guile\n" +"modules, allowing for people to cooperate integrating their generic Guile\n" +"modules into a coherent library. Think \"a down-scaled, limited-scope CPAN\n" +"for Guile\"." +msgstr "" +"Guile-Lib est conçue comme une collection de modules Scheme pour Guile permettant\n" +"aux utilisateurs d'intégrer leurs modules Guile au sein d'une bilbiothéque commune\n" +"et cohérente. Voyez Guile-Lib comme une version plus simple de CPAN limitée à Guile." + +#: gnu/packages/guile.scm:348 +msgid "JSON module for Guile" +msgstr "Module JSON pour Guile" + +#: gnu/packages/guile.scm:350 +msgid "" +"Guile-json supports parsing and building JSON documents according to the\n" +"http:://json.org specification. These are the main features:\n" +"- Strictly complies to http://json.org specification.\n" +"- Build JSON documents programmatically via macros.\n" +"- Unicode support for strings.\n" +"- Allows JSON pretty printing." +msgstr "" +"Guile-json supporte l'analyse et la construction de documents JSON respectant\n" +"la spécification http:://json.org . Les principales fonctionnalitées proposées\n" +"sont les suivantes :\n" +"- stricte conformité à la spécification http://json.org ;\n" +"- création de documents JSON par programmation via macros ;\n" +"- support d'unicode pour les chaines de caractère ;\n" +"- formatage élégant." + +#: gnu/packages/guile.scm:381 +msgid "Create charts and graphs in Guile" +msgstr "Création de diagrammes et de graphiques dans Guile" + +#: gnu/packages/guile.scm:383 +msgid "" +"Guile-Charting is a Guile Scheme library to create bar charts and graphs\n" +"using the Cairo drawing library." +msgstr "" +"Guile-Charting est une bibliothèque pour Guile permettant de créer des diagrammes\n" +"en barre et des graphiques utilisant la bibliothèque de dessin Cairo." + +#: gnu/packages/inkscape.scm:78 +msgid "Vector graphics editor" +msgstr "Éditeur graphique vectoriel" + +#: gnu/packages/inkscape.scm:79 +msgid "" +"Inkscape is a vector graphics editor. What sets Inkscape\n" +"apart is its use of Scalable Vector Graphics (SVG), an XML-based W3C standard,\n" +"as the native format." +msgstr "" +"Inkscape est un éditeur graphique vectoriel. Ce qui différencie Inkscape\n" +"est son utilisation du format SVG, un standard W3C basé sur XML, comme format natif." + +#: gnu/packages/linux.scm:131 +msgid "GNU Linux-Libre kernel headers" +msgstr "Fichiers d'en-tête pour le noyau GNU Linux-Libre" + +#: gnu/packages/linux.scm:132 +msgid "Headers of the Linux-Libre kernel." +msgstr "Fichiers d'en-tête pour le noyau Linux-Libre" + +#: gnu/packages/linux.scm:163 +msgid "Tools for loading and managing Linux kernel modules" +msgstr "Outils de chargement et de gestion de modules noyau pour Linux" + +#: gnu/packages/linux.scm:165 +msgid "" +"Tools for loading and managing Linux kernel modules, such as `modprobe',\n" +"`insmod', `lsmod', and more." +msgstr "" +"Outils pour le chargement et la gestion des modules noyau Linux, tels que\n" +"\"modprob\", \"insmod\", \"lsmod\" et plus." + +#: gnu/packages/linux.scm:296 +msgid "100% free redistribution of a cleaned Linux kernel" +msgstr "Redistribution 100% libre d'un noyau Linux propre" + +#: gnu/packages/linux.scm:298 +msgid "" +"GNU Linux-Libre is a free (as in freedom) variant of the Linux kernel.\n" +"It has been modified to remove all non-free binary blobs." +msgstr "" +"GNU Linux-Libre est une variante libre du noyau Linux.\n" +"Il a été modifié pour en retirer toutes les composantes non-libres." + +#: gnu/packages/linux.scm:341 +msgid "Pluggable authentication modules for Linux" +msgstr "Modules d'authentification pour Linux" + +#: gnu/packages/linux.scm:343 +msgid "" +"A *Free* project to implement OSF's RFC 86.0.\n" +"Pluggable authentication modules are small shared object files that can\n" +"be used through the PAM API to perform tasks, like authenticating a user\n" +"at login. Local and dynamic reconfiguration are its key features" +msgstr "" +"Un projet libre implantant OSF (RFC 86.0).\n" +"Les modules d'authentification sont de petits fichiers objets partagés pouvant\n" +"être utilisés à travers l'API PAM pour effectuer des tâches, comme l'authentification\n" +"d'un utilisateur au moment de la connexion. Ses principales fonctionnalités sont\n" +"la reconfiguration locale et dynamique." + +#: gnu/packages/linux.scm:370 +msgid "Small utilities that use the proc filesystem" +msgstr "Petits utilitaires utilisant le système de fichier proc" + +#: gnu/packages/linux.scm:372 +msgid "" +"This PSmisc package is a set of some small useful utilities that\n" +"use the proc filesystem. We're not about changing the world, but\n" +"providing the system administrator with some help in common tasks." +msgstr "" +"Le paquet PSmisc est un ensemble de petits utilitaires utilisant le\n" +"système de fichier proc. Notre objectif n'est pas de changer le monde\n" +"mais simplement de fournir de l'aide à l'administrateur système dans ses\n" +"tâches les plus courantes." + +#: gnu/packages/linux.scm:416 +msgid "Collection of utilities for the Linux kernel" +msgstr "Collection d'utilitaires pour le noyau Linux" + +#: gnu/packages/linux.scm:418 +msgid "Util-linux is a random collection of utilities for the Linux kernel." +msgstr "Util-linux est une collection d'utilitaires pour le noyau Linux." + +#: gnu/packages/linux.scm:472 +msgid "Utilities that give information about processes" +msgstr "Utilitaires fournissant des informations sur les processus" + +#: gnu/packages/linux.scm:474 +msgid "" +"Procps is the package that has a bunch of small useful utilities\n" +"that give information about processes using the Linux /proc file system.\n" +"The package includes the programs ps, top, vmstat, w, kill, free,\n" +"slabtop, and skill." +msgstr "" +"Procps est un paquet fournissant de nombreux outils simples donnant des\n" +"des informations sur les processus utilisant le système de fichier /proc.\n" +"Le paquet inclut les programmes ps, top, vmstat, w, kill, free,\n" +"slabtop et skill." + +#: gnu/packages/linux.scm:499 +msgid "Tools for working with USB devices, such as lsusb" +msgstr "Outils de manipulation des périphériques USB, tels que lsusb." + +#: gnu/packages/linux.scm:501 +msgid "Tools for working with USB devices, such as lsusb." +msgstr "Outils de manipulation des périphériques USB, tels que lsusb." + +#: gnu/packages/linux.scm:542 +msgid "Creating and checking ext2/ext3/ext4 file systems" +msgstr "Création et vérification de systèmes de fichiers ext1/ext3/ext4" + +#: gnu/packages/linux.scm:544 +msgid "This package provides tools for manipulating ext2/ext3/ext4 file systems." +msgstr "Ce paquet fournit des outils pour manipuler les systèmes de fichiers ext2/ext3/ext4" + +#: gnu/packages/linux.scm:575 +msgid "Statically-linked fsck.* commands from e2fsprogs" +msgstr "Commandes de e2fsprogs (fsck.*) liées statiquement" + +#: gnu/packages/linux.scm:577 +msgid "" +"This package provides statically-linked command of fsck.ext[234] taken\n" +"from the e2fsprogs package. It is meant to be used in initrds." +msgstr "" +"Ce paquet fournit la commande liée statiquement de fsck.ext[234] issue\n" +"du paquet e2fsprogs. Il est censé être utilisé dans initrds." + +#: gnu/packages/linux.scm:596 +msgid "System call tracer for Linux" +msgstr "Traceur d'appel système pour Linux" + +#: gnu/packages/linux.scm:598 +msgid "" +"strace is a system call tracer, i.e. a debugging tool which prints out a\n" +"trace of all the system calls made by a another process/program." +msgstr "" +"strace est un traceur d'appels système, c-à-d un outil de débogage affichant\n" +"les appels système effectués par un autre processus/programme." + +#: gnu/packages/linux.scm:617 +msgid "The Advanced Linux Sound Architecture libraries" +msgstr "Bibliothèques ALSA (Advanced Linux Sound Architecture)" + +#: gnu/packages/linux.scm:619 gnu/packages/linux.scm:661 +msgid "" +"The Advanced Linux Sound Architecture (ALSA) provides audio and\n" +"MIDI functionality to the Linux-based operating system." +msgstr "" +"ALSA fournit des fonctionnalités audio et MIDI pour les sytèmes\n" +"basés sur Linux." + +#: gnu/packages/linux.scm:659 +msgid "Utilities for the Advanced Linux Sound Architecture (ALSA)" +msgstr "Utilitaires pour ALSA (Advanced Linux Sound Architecture)" + +#: gnu/packages/linux.scm:683 +msgid "Program to configure the Linux IP packet filtering rules" +msgstr "Programme de configuration de règles de filtrage des paquets IP pour Linux" + +#: gnu/packages/linux.scm:685 +msgid "" +"iptables is the userspace command line program used to configure the\n" +"Linux 2.4.x and later IPv4 packet filtering ruleset. It is targeted towards\n" +"system administrators. Since Network Address Translation is also configured\n" +"from the packet filter ruleset, iptables is used for this, too. The iptables\n" +"package also includes ip6tables. ip6tables is used for configuring the IPv6\n" +"packet filter." +msgstr "" +"iptables est un programme utilisateur en ligne de commande utilisé pour\n" +"configurer le filtrage des paquets IPv4 sur Linux depuis la version 2.4.x.\n" +"Il s'adresse particulièrement aux administrateurs. iptables gère aussi le NAT\n" +"Network Address Translation). Le paquet inclut aussi ip6tables, utilisé pour\n" +"configurer le filtrage IPv6." + +#: gnu/packages/linux.scm:733 +msgid "Utilities for controlling TCP/IP networking and traffic in Linux" +msgstr "Utilitaires de contrôle du traffic TCP/IP pour Linux" + +#: gnu/packages/linux.scm:735 +msgid "" +"Iproute2 is a collection of utilities for controlling TCP/IP\n" +"networking and traffic with the Linux kernel.\n" +"\n" +"Most network configuration manuals still refer to ifconfig and route as the\n" +"primary network configuration tools, but ifconfig is known to behave\n" +"inadequately in modern network environments. They should be deprecated, but\n" +"most distros still include them. Most network configuration systems make use\n" +"of ifconfig and thus provide a limited feature set. The /etc/net project aims\n" +"to support most modern network technologies, as it doesn't use ifconfig and\n" +"allows a system administrator to make use of all iproute2 features, including\n" +"traffic control.\n" +"\n" +"iproute2 is usually shipped in a package called iproute or iproute2 and\n" +"consists of several tools, of which the most important are ip and tc. ip\n" +"controls IPv4 and IPv6 configuration and tc stands for traffic control. Both\n" +"tools print detailed usage messages and are accompanied by a set of\n" +"manpages." +msgstr "" +"Iproute2 est une collection d'utilitaires pour le contrôle des réseaux TCP/IP\n" +"sous Linux. De nombreux manuels sur la configuration du réseau sous Linux se\n" +"réfèrent toujours à ifconfig et route comme des outils indispensables\n" +"bien que ifconfig soit connu pour son inadéquation aux réseaux modernes. Ils devraient\n" +"être dépréciés mais de nombreuses distributions les incluent encore. Le projet\n" +"/etc/net a pour but de supporter des technologies réseau plus modernes. Iproute2\n" +"est habituellement fourni dans un paquet appelé iproute ou iproute2 et consiste\n" +"en plusieurs outils dont les plus importants sont ip et tc. ip contrôle la\n" +"configuration IPv4 et IPv6 tandis que tc se charge du contrôle du trafic. Ces\n" +"deux outils sont accompagnés par leur manuel." + +#: gnu/packages/linux.scm:827 +msgid "Tools for controlling the network subsystem in Linux" +msgstr "Outils pour contrôler le sous-système réseau dans Linux" + +#: gnu/packages/linux.scm:829 +msgid "" +"This package includes the important tools for controlling the network\n" +"subsystem of the Linux kernel. This includes arp, hostname, ifconfig,\n" +"netstat, rarp and route. Additionally, this package contains utilities\n" +"relating to particular network hardware types (plipconfig, slattach) and\n" +"advanced aspects of IP configuration (iptunnel, ipmaddr)." +msgstr "" +"Ce paquet inclut des outils importants pour contrôler le sous-système réseau\n" +"du noyau Linux. Cela inclut arp, hostname, ifconfig, netstat, rarp et route.\n" +"Aditionnellement, ce paquet contient des utilitaires relatifs à des architectures\n" +"particulières (pliconfig, slattach) et à des aspects avancés de la configuration\n" +"réseau (iptunnel, ipmaddr)." + +#: gnu/packages/linux.scm:862 +msgid "Library for working with POSIX capabilities" +msgstr "Bibliothèque pour travailler avec les possibilités de POSIX" + +#: gnu/packages/linux.scm:864 +msgid "" +"Libcap2 provides a programming interface to POSIX capabilities on\n" +"Linux-based operating systems." +msgstr "Libcap2 fournit une interface de programmation POSIX aux systèmes basés sur Linux." + +#: gnu/packages/linux.scm:896 +msgid "Manipulate Ethernet bridges" +msgstr "Manipulation des ponts Ethernet" + +#: gnu/packages/linux.scm:898 +msgid "" +"Utilities for Linux's Ethernet bridging facilities. A bridge is a way\n" +"to connect two Ethernet segments together in a protocol independent way.\n" +"Packets are forwarded based on Ethernet address, rather than IP address (like\n" +"a router). Since forwarding is done at Layer 2, all protocols can go\n" +"transparently through a bridge." +msgstr "" +"Utilitaires pour la gestion des ponts ethernet sous Linux. Un pont est un\n" +"moyen de connecter deux segments Ethernet indépendant du protocole utilisé.\n" +"Les paquets sont transférés en se basant sur leur adresse Ethernet plutôt que\n" +"sur leur adresse IP (contrairement aux routeurs). Le forwarding se faisant au\n" +"niveau 2, tous les protocoles peuvent transiter de manière transparente sur un\n" +"un pont." + +#: gnu/packages/linux.scm:920 +msgid "NetLink protocol library suite" +msgstr "Bibliothèqye pour le protocole NetLink" + +#: gnu/packages/linux.scm:922 +msgid "" +"The libnl suite is a collection of libraries providing APIs to netlink\n" +"protocol based Linux kernel interfaces. Netlink is an IPC mechanism primarly\n" +"between the kernel and user space processes. It was designed to be a more\n" +"flexible successor to ioctl to provide mainly networking related kernel\n" +"configuration and monitoring interfaces." +msgstr "" +"La suite libnl est une collection de bibliothèques fournissant des API pour\n" +"le protocole netlink basé sur des interfaces du noyau Linux. Netlink est un\n" +"mécanisme IPC intervenant entre le noyay et les processus utilisateur. Il\n" +"est conçu pour être un successeur plus flexible à ioctl permettant de configurer\n" +"le réseau au niveau noyau et surveiller les interfaces." + +#: gnu/packages/linux.scm:955 +msgid "Analyze power consumption on Intel-based laptops" +msgstr "Analyse de la consommation des portables basés sur Intel" + +#: gnu/packages/linux.scm:957 +msgid "" +"PowerTOP is a Linux tool to diagnose issues with power consumption and\n" +"power management. In addition to being a diagnostic tool, PowerTOP also has\n" +"an interactive mode where the user can experiment various power management\n" +"settings for cases where the operating system has not enabled these\n" +"settings." +msgstr "" +"PowerTOP est un outil Linux permettant de diagnostiquer des problèmes de\n" +"consommation électrique ou de gestion de l'énergie. En plus d'être un outil\n" +"de diagnostique, PowerTOP propose aussi un mode interactif dans lequel\n" +"l'utilisateur peut expérimenter de multiples configurations de gestion de\n" +"l'énergie pour des cas où le système d'exploitation n'a pas " + +#: gnu/packages/linux.scm:979 +msgid "Audio mixer for X and the console" +msgstr "Table de mixage audio basée sur X et la console" + +#: gnu/packages/linux.scm:981 +msgid "" +"Aumix adjusts an audio mixer from X, the console, a terminal,\n" +"the command line or a script." +msgstr "" +"Aumix ajuste un mixer audio depuis X, la console, un terminal,\n" +"la ligne de commande ou un script." + +#: gnu/packages/linux.scm:1005 +msgid "Displays the IO activity of running processes" +msgstr "Affiche l'activité des entrées-sorties des processus en cours d'exécution." + +#: gnu/packages/linux.scm:1007 +msgid "" +"Iotop is a Python program with a top like user interface to show the\n" +"processes currently causing I/O." +msgstr "" +"Iotop est un programme Python doté d'une interface utilisateur affichant les\n" +"entrées-sorties en cours des processus." + +#: gnu/packages/linux.scm:1058 +msgid "Support file systems implemented in user space" +msgstr "Support des systèmes de fichiers implantés dans l'espace utilisateur." + +#: gnu/packages/linux.scm:1060 +msgid "" +"As a consequence of its monolithic design, file system code for Linux\n" +"normally goes into the kernel itself---which is not only a robustness issue,\n" +"but also an impediment to system extensibility. FUSE, for \"file systems in\n" +"user space\", is a kernel module and user-space library that tries to address\n" +"part of this problem by allowing users to run file system implementations as\n" +"user-space processes." +msgstr "" +"Parce qu'il est basé sur un modèle monolithique, la gestion du système de\n" +"de fichiers sous Linux se trouve normalement au sein du noyau, ce qui\n" +"consititue un problème de robustesse et également une entrave\n" +"à l'extensibilité du système. FUSE (File systems in user space) est\n" +"un module noyau et une bibliothèque utilisateur destinée à résoudre\n" +"ce problème en permettant aux utilisateurs de lancer les implantations de\n" +"systèmes de fichiers comme des processus utilisateur." + +#: gnu/packages/linux.scm:1085 +msgid "User-space union file system" +msgstr "" + +#: gnu/packages/linux.scm:1087 +msgid "" +"UnionFS-FUSE is a flexible union file system implementation in user\n" +"space, using the FUSE library. Mounting a union file system allows you to\n" +"\"aggregate\" the contents of several directories into a single mount point.\n" +"UnionFS-FUSE additionally supports copy-on-write." +msgstr "" + +#: gnu/packages/linux.scm:1112 +msgid "User-space union file system (statically linked)" +msgstr "" + +#: gnu/packages/linux.scm:1154 +msgid "Mount remote file systems over SSH" +msgstr "Montage de systèmes de fichier distants avec SSH" + +#: gnu/packages/linux.scm:1156 +msgid "" +"This is a file system client based on the SSH File Transfer Protocol.\n" +"Since most SSH servers already support this protocol it is very easy to set\n" +"up: on the server side there's nothing to do; on the client side mounting the\n" +"file system is as easy as logging into the server with an SSH client." +msgstr "" +"Système de fichier client basé sur SFTP (SSH File Transfer Protocole).\n" +"Beaucoup de serveurs SSH supportant le protocole, ce client est très facile\n" +"à configurer : il n'y a rien à faire du côté serveur ; du côté client, il est\n" +"aussi facile de monter le système de fichiers que de se connecter avec un client\n" +"SSH." + +#: gnu/packages/linux.scm:1204 +msgid "Tools for non-uniform memory access (NUMA) machines" +msgstr "Outils pour les machines basées sur NUMA (non-uniform memory access)" + +#: gnu/packages/linux.scm:1206 +msgid "" +"NUMA stands for Non-Uniform Memory Access, in other words a system whose\n" +"memory is not all in one place. The numactl program allows you to run your\n" +"application program on specific CPU's and memory nodes. It does this by\n" +"supplying a NUMA memory policy to the operating system before running your\n" +"program.\n" +"\n" +"The package contains other commands, such as numademo, numastat and memhog.\n" +"The numademo command provides a quick overview of NUMA performance on your\n" +"system." +msgstr "" +"NUMA (Non-Uniform Memory Access) est un système dans lequel la mémoire est\n" +"répartie en différents endroits. Le programme numactl permet de lancer une\n" +"une application sur des noeuds CPU et mémoire spécifiques au moyen de politiques\n" +"fournies au système d'exploitation avant le lancement du programme." + +#: gnu/packages/linux.scm:1269 +msgid "Linux keyboard utilities and keyboard maps" +msgstr "" + +#: gnu/packages/linux.scm:1271 +msgid "" +"This package contains keytable files and keyboard utilities compatible\n" +"for systems using the Linux kernel. This includes commands such as\n" +"'loadkeys', 'setfont', 'kbdinfo', and 'chvt'." +msgstr "" + +#: gnu/packages/linux.scm:1290 +msgid "Monitor file accesses" +msgstr "Surveillance des accès fichier" + +#: gnu/packages/linux.scm:1292 +msgid "" +"The inotify-tools packages provides a C library and command-line tools\n" +"to use Linux' inotify mechanism, which allows file accesses to be monitored." +msgstr "" +"Le paquet inotify-tools fournit une bibliothèque C et des outils en ligne de commande\n" +"permettant d'utiliser le mécanisme inotify de Linux qui autorise la surveillance\n" +"des accès fichier." + +#: gnu/packages/linux.scm:1330 +msgid "Kernel module tools" +msgstr "Outils de module noyau" + +#: gnu/packages/linux.scm:1331 +msgid "" +"Kmod is a set of tools to handle common tasks with Linux\n" +"kernel modules like insert, remove, list, check properties, resolve\n" +"dependencies and aliases.\n" +"\n" +"These tools are designed on top of libkmod, a library that is shipped with\n" +"kmod. The aim is to be compatible with tools, configurations and indices\n" +"from the module-init-tools project." +msgstr "" +"Kmod est un ensemble d'outils gérant les opérations courantes sur les\n" +"les modules noyau comme insérer, supprimer, lister, vérifier les propriétés,\n" +"résoudre les dépendances et alias.\n" +"\n" +"Ces outils sont construits au-dessus libkmod, une bibliothèque fournie avec\n" +"kmod. L'objectif est d'être compatible avec les outils, les configurations et\n" +"les indices du projet module-init-tools." + +#: gnu/packages/linux.scm:1380 +msgid "Userspace device management" +msgstr "Gestion de périphériques utilisateurs" + +#: gnu/packages/linux.scm:1381 +msgid "" +"Udev is a daemon which dynamically creates and removes\n" +"device nodes from /dev/, handles hotplug events and loads drivers at boot\n" +"time." +msgstr "" +"Udev est un daemon capable de créer et supprimer dynamiquement\n" +"des noeuds de périphériques dans /dev/, gérer leur branchement à chaud\n" +"et charger leur pilotes au démarrage." + +#: gnu/packages/linux.scm:1470 +msgid "Logical volume management for Linux" +msgstr "Gestion de volumes logiques pour Linux" + +#: gnu/packages/linux.scm:1472 +msgid "" +"LVM2 is the logical volume management tool set for Linux-based systems.\n" +"This package includes the user-space libraries and tools, including the device\n" +"mapper. Kernel components are part of Linux-libre." +msgstr "" + +#: gnu/packages/linux.scm:1499 +msgid "Tools for manipulating Linux Wireless Extensions" +msgstr "Outils de manipulation d'extensions sans fil pour Linux" + +#: gnu/packages/linux.scm:1500 +msgid "" +"Wireless Tools are used to manipulate the Linux Wireless\n" +"Extensions. The Wireless Extension is an interface allowing you to set\n" +"Wireless LAN specific parameters and get the specific stats." +msgstr "" + +#: gnu/packages/linux.scm:1572 +msgid "Utilities to read temperature/voltage/fan sensors" +msgstr "Utilitaires pour la lecture de capteurs de tempéture/voltage/ventilateur" + +#: gnu/packages/linux.scm:1574 +msgid "" +"Lm-sensors is a hardware health monitoring package for Linux. It allows\n" +"you to access information from temperature, voltage, and fan speed sensors.\n" +"It works with most newer systems." +msgstr "" +"Lm-sensors est un utilitaire de monitoring hardware pour Linux. Il permet\n" +"d'accéder à des informations sur les composants telles que la température,\n" +"le voltage ou la vitesse des ventilatuers. Il fonctionne avec la plupart des\n" +"systèmes les plus récents." + +#: gnu/packages/linux.scm:1609 +msgid "Hardware health information viewer" +msgstr "Utiltaire de monitoring hardware" + +#: gnu/packages/linux.scm:1611 +msgid "" +"Xsensors reads data from the libsensors library regarding hardware\n" +"health such as temperature, voltage and fan speed and displays the information\n" +"in a digital read-out." +msgstr "Xsensors lit les données depuis la bibliothèque libsensors " + +#: gnu/packages/linux.scm:1654 +msgid "Linux profiling with performance counters" +msgstr "" + +#: gnu/packages/linux.scm:1656 +msgid "" +"perf is a tool suite for profiling using hardware performance counters,\n" +"with support in the Linux kernel. perf can instrument CPU performance\n" +"counters, tracepoints, kprobes, and uprobes (dynamic tracing). It is capable\n" +"of lightweight profiling. This package contains the user-land tools and in\n" +"particular the 'perf' command." +msgstr "" + +#: gnu/packages/lout.scm:109 +msgid "Document layout system" +msgstr "" + +#: gnu/packages/lout.scm:111 +msgid "" +"The Lout document formatting system reads a high-level description of\n" +"a document similar in style to LaTeX and produces a PostScript or plain text\n" +"output file.\n" +"\n" +"Lout offers an unprecedented range of advanced features, including optimal\n" +"paragraph and page breaking, automatic hyphenation, PostScript EPS file\n" +"inclusion and generation, equation formatting, tables, diagrams, rotation and\n" +"scaling, sorted indexes, bibliographic databases, running headers and\n" +"odd-even pages, automatic cross referencing, multilingual documents including\n" +"hyphenation (most European languages are supported), formatting of computer\n" +"programs, and much more, all ready to use. Furthermore, Lout is easily\n" +"extended with definitions which are very much easier to write than troff of\n" +"TeX macros because Lout is a high-level, purely functional language, the\n" +"outcome of an eight-year research project that went back to the\n" +"beginning." +msgstr "" + +#: gnu/packages/mpd.scm:62 +msgid "Music Player Daemon client library" +msgstr "" + +#: gnu/packages/mpd.scm:63 +msgid "" +"A stable, documented, asynchronous API library for\n" +"interfacing MPD in the C, C++ & Objective C languages." +msgstr "" + +#: gnu/packages/mpd.scm:121 +msgid "Music Player Daemon" +msgstr "" + +#: gnu/packages/mpd.scm:122 +msgid "" +"Music Player Daemon (MPD) is a flexible, powerful,\n" +"server-side application for playing music. Through plugins and libraries it\n" +"can play a variety of sound files while being controlled by its network\n" +"protocol." +msgstr "" + +#: gnu/packages/mpd.scm:147 +msgid "Curses Music Player Daemon client" +msgstr "" + +#: gnu/packages/mpd.scm:148 +msgid "" +"ncmpc is a fully featured MPD client, which runs in a\n" +"terminal using ncurses." +msgstr "" + +#: gnu/packages/mpd.scm:169 +msgid "Featureful ncurses based MPD client inspired by ncmpc" +msgstr "" + +#: gnu/packages/mpd.scm:170 +msgid "" +"Ncmpcpp is an mpd client with a UI very similar to ncmpc,\n" +"but it provides new useful features such as support for regular expressions\n" +"for library searches, extended song format, items filtering, the ability to\n" +"sort playlists, and a local filesystem browser." +msgstr "" + +#: gnu/packages/pdf.scm:79 +msgid "PDF rendering library" +msgstr "Bibliothèque de rendu PDF" + +#: gnu/packages/pdf.scm:81 +msgid "Poppler is a PDF rendering library based on the xpdf-3.0 code base." +msgstr "" + +#: gnu/packages/pdf.scm:124 +msgid "Viewer for PDF files based on the Motif toolkit" +msgstr "Visionneuse pour fichiers PDF basée sur la boîte à outil Motif" + +#: gnu/packages/pdf.scm:126 +msgid "Xpdf is a viewer for Portable Document Format (PDF) files" +msgstr "Xpdf est une visionneuse pour fichiers PDF (Portable Document Format)" + +#: gnu/packages/pdf.scm:154 +msgid "Tools to work with the PDF file format" +msgstr "Outils de manipulation de fichiers PDF" + +#: gnu/packages/pdf.scm:156 +msgid "" +"PoDoFo is a C++ library and set of command-line tools to work with the\n" +"PDF file format. It can parse PDF files and load them into memory, and makes\n" +"it easy to modify them and write the changes to disk. It is primarily useful\n" +"for applications that wish to do lower level manipulation of PDF, such as\n" +"extracting content or merging files." +msgstr "" + +#: gnu/packages/pdf.scm:217 +msgid "Lightweight PDF viewer and toolkit" +msgstr "Boite à outils légère pour la visualisation de documents PDF" + +#: gnu/packages/pdf.scm:219 +msgid "" +"MuPDF is a C library that implements a PDF and XPS parsing and\n" +"rendering engine. It is used primarily to render pages into bitmaps,\n" +"but also provides support for other operations such as searching and\n" +"listing the table of contents and hyperlinks.\n" +"\n" +"The library ships with a rudimentary X11 viewer, and a set of command\n" +"line tools for batch rendering (pdfdraw), examining the file structure\n" +"(pdfshow), and rewriting files (pdfclean)." +msgstr "" +"MuPDF est une bibliothèque C permettant d'analyser et de faire le rendu de\n" +"fichiers PDF et XPS. Elle est surtout utilisée pour effectuer le rendu de pages dans\n" +"des bitmaps mais fournit aussi le support pour d'autres opérations comme la recherche\n" +"et l'affichage de tables des matières et d'hyperliens.\n" +"\n" +"La bibliothèque est fournie avec une visionneuse rudimentaire X11 et un ensemble\n" +"d'outils en ligne de commande pour le rendu par lots (pdfdraw), l'analyse de structure\n" +"(pdfshow), et la réécriture de fichiers (pdfclean)." + +#: gnu/packages/ratpoison.scm:60 +msgid "Simple mouse-free tiling window manager" +msgstr "" + +#: gnu/packages/ratpoison.scm:62 +msgid "" +"Ratpoison is a simple window manager with no fat library\n" +"dependencies, no fancy graphics, no window decorations, and no\n" +"rodent dependence. It is largely modelled after GNU Screen which\n" +"has done wonders in the virtual terminal market.\n" +"\n" +"The screen can be split into non-overlapping frames. All windows\n" +"are kept maximized inside their frames to take full advantage of\n" +"your precious screen real estate.\n" +"\n" +"All interaction with the window manager is done through keystrokes.\n" +"Ratpoison has a prefix map to minimize the key clobbering that\n" +"cripples Emacs and other quality pieces of software." +msgstr "" + +#: gnu/packages/scanner.scm:52 +msgid "Raster image scanner library and drivers" +msgstr "" + +#: gnu/packages/scanner.scm:53 +msgid "" +"SANE stands for \"Scanner Access Now Easy\" and is an API\n" +"proving access to any raster image scanner hardware (flatbed scanner,\n" +"hand-held scanner, video- and still-cameras, frame-grabbers, etc.). The\n" +"package contains the library and drivers." +msgstr "" + +#: gnu/packages/scheme.scm:126 +msgid "A Scheme implementation with integrated editor and debugger" +msgstr "Une implantation de Scheme avec éditeur et débogueur intégrés" + +#: gnu/packages/scheme.scm:128 +msgid "" +"GNU/MIT Scheme is an implementation of the Scheme programming\n" +"language. It provides an interpreter, a compiler and a debugger. It also\n" +"features an integrated Emacs-like editor and a large runtime library." +msgstr "" +"GNU/MIT Scheme est une implantation du langage de programmation Scheme\n" +"Il fournit un interpréteur, un compilateur et un débogueur. Il intègre\n" +"également un éditeur sur le modèle d'Emacs et une librairie d'exécution complète." + +#: gnu/packages/scheme.scm:208 +msgid "Efficient Scheme compiler" +msgstr "Compilateur Scheme efficace" + +#: gnu/packages/scheme.scm:210 +msgid "" +"Bigloo is a Scheme implementation devoted to one goal: enabling\n" +"Scheme based programming style where C(++) is usually\n" +"required. Bigloo attempts to make Scheme practical by offering\n" +"features usually presented by traditional programming languages\n" +"but not offered by Scheme and functional programming. Bigloo\n" +"compiles Scheme modules. It delivers small and fast stand alone\n" +"binary executables. Bigloo enables full connections between\n" +"Scheme and C programs and between Scheme and Java programs." +msgstr "" +"Bigloo est une implantation de Scheme tournée vers un seul but: \n" +"permettre un style de programmation basé sur Schema là où C(++)\n" +"est généralement requis. Bigloo " + +#: gnu/packages/scheme.scm:281 +msgid "Multi-tier programming language for the Web 2.0" +msgstr "" + +#: gnu/packages/scheme.scm:283 +msgid "" +"HOP is a multi-tier programming language for the Web 2.0 and the\n" +"so-called diffuse Web. It is designed for programming interactive web\n" +"applications in many fields such as multimedia (web galleries, music players,\n" +"...), ubiquitous and house automation (SmartPhones, personal appliance),\n" +"mashups, office (web agendas, mail clients, ...), etc." +msgstr "" + +#: gnu/packages/scheme.scm:323 +msgid "R5RS Scheme implementation that compiles native code via C" +msgstr "" + +#: gnu/packages/scheme.scm:325 +msgid "" +"CHICKEN is a compiler for the Scheme programming language. CHICKEN\n" +"produces portable and efficient C, supports almost all of the R5RS Scheme\n" +"language standard, and includes many enhancements and extensions." +msgstr "" +"CHICKEN est un compilateur pour le langage de programmation Scheme. CHICKEN\n" +"produit du C portable et efficace, supporte l'essentiel du standard R5RS et\n" +"incult de nombreuses améliorations et extensions." + +#: gnu/packages/scheme.scm:344 +msgid "Scheme implementation using a bytecode interpreter" +msgstr "Implantation de Scheme utilisanat un interpréteur pour bytecode" + +#: gnu/packages/scheme.scm:346 +msgid "" +"Scheme 48 is an implementation of Scheme based on a byte-code\n" +"interpreter and is designed to be used as a testbed for experiments in\n" +"implementation techniques and as an expository tool." +msgstr "" +"Scheme 48 est une implantation de Scheme basée sur un interpréteur bytecode\n" +"destinée à être utilisée comme un banc d'essai pour des expérimentations\n" +"portant sur des techniques d'implantation et comme un outil d'exposition." + +#: gnu/packages/scheme.scm:419 +msgid "Implementation of Scheme and related languages" +msgstr "Implantation de Scheme et d'autres langages associés" + +#: gnu/packages/scheme.scm:421 +msgid "" +"Racket is an implementation of the Scheme programming language (R5RS and\n" +"R6RS) and related languages, such as Typed Racket. It features a compiler and\n" +"a virtual machine with just-in-time native compilation, as well as a large set\n" +"of libraries." +msgstr "" +"Racket est une implantation du langage de programmation Schema (R5RS et R6RS)\n" +"et de langages associés comme Typed Racket. Racket fournit un compilateur et\n" +"une machine virtuelle avec compilation à la volée ainsi qu'un ensemble de\n" +"bibliothèques." + +#: gnu/packages/wordnet.scm:79 +msgid "Lexical database for the English language" +msgstr "Base de données lexicale pour la langue anglaise" + +#: gnu/packages/wordnet.scm:81 +msgid "" +"WordNet® is a large lexical database of English. Nouns, verbs,\n" +"adjectives and adverbs are grouped into sets of cognitive synonyms\n" +"(synsets), each expressing a distinct concept. Synsets are interlinked by\n" +"means of conceptual-semantic and lexical relations. The resulting network of\n" +"meaningfully related words and concepts can be navigated with the browser.\n" +"WordNet is also freely and publicly available for download. WordNet's\n" +"structure makes it a useful tool for computational linguistics and natural\n" +"language processing." +msgstr "" +"WordNet® est une vaste base de données pour la langue anglaise. Noms, verbes,\n" +"adjectifs et adverbes sont regoupés en" + +#: gnu/packages/zip.scm:56 +msgid "Compression and file packing utility" +msgstr "Utilitaire de compression et de paquetage de fichiers" + +#: gnu/packages/zip.scm:58 +msgid "" +"Zip is a compression and file packaging/archive utility. Zip is useful\n" +"for packaging a set of files for distribution, for archiving files, and for\n" +"saving disk space by temporarily compressing unused files or directories.\n" +"Zip puts one or more compressed files into a single ZIP archive, along with\n" +"information about the files (name, path, date, time of last modification,\n" +"protection, and check information to verify file integrity). An entire\n" +"directory structure can be packed into a ZIP archive with a single command.\n" +"\n" +"Zip has one compression method (deflation) and can also store files without\n" +"compression. Zip automatically chooses the better of the two for each file.\n" +"Compression ratios of 2:1 to 3:1 are common for text files." +msgstr "" +"Zip est un utilitaire de compression et de paquetage/archivage. Zip est utile\n" +"pour regrouper des fichiers à distribuer, pour archiver ou pour gagner\n" +"temporairement de l'espace sur le disque en compressant des fichiers ou\n" +"répertoires inutilisés. Zip compresse un ou plusieurs fichiers en une seule\n" +"archive ZIP tout en concervant certaines informations (nom, chemin, date de\n" +"dernière modification, protection et contrôle d'intégrité). Un répertoire\n" +"et tout son contenu peuvent être convertis en une archive ZIP à l'aide d'une\n" +"seule commande. \n" +"\n" +"Zip possède une méthode de compression (deflation) et peut aussi stocker des\n" +"fichiers sans les compresser. Zip choisit la meilleure des deux possibilités\n" +"pour chaque fichier.Des ratios de compression de 2 à 3 sont généralement\n" +"atteignables pour les fichiers texte." + +#: gnu/packages/zip.scm:98 +msgid "Decompression and file extraction utility" +msgstr "Utilitaire de décompression et d'extraction de fichiers" + +#: gnu/packages/zip.scm:100 +msgid "" +"UnZip is an extraction utility for archives compressed in .zip format,\n" +"also called \"zipfiles\".\n" +"\n" +"UnZip lists, tests, or extracts files from a .zip archive. The default\n" +"behaviour (with no options) is to extract into the current directory, and\n" +"subdirectories below it, all files from the specified zipfile. UnZip\n" +"recreates the stored directory structure by default." +msgstr "" +"UnZip est un utilitaire d'extraction pour les archives compressées\n" +"au format .zip.\n" +"Unzip liste, teste ou extrait des fichiers depuis une archive .zip. Par défaut\n" +"(aucune option fournie), Unzip extrait les fichiers de l'archive\n" +"vers le répertoire courant et ses sous-répertoires. Unzip recrée par défaut la\n" +"structure des répertoires contenus dans l'archive." + +#: gnu/packages/zip.scm:134 +msgid "Library for accessing zip files" +msgstr "Bibliothèque de manipulation des fichiers zip" + +#: gnu/packages/zip.scm:136 +msgid "ZZipLib is a library based on zlib for accessing zip files." +msgstr "ZZipLib est une bibliothèque basée sur zlip pour la manipulation de fichiers zip." + +#: gnu/packages/zip.scm:154 +msgid "Provides an interface to ZIP archive files" +msgstr "Fournit une interface pour les archives ZIP" + +#: gnu/packages/zip.scm:155 +msgid "" +"The Archive::Zip module allows a Perl program to create,\n" +"manipulate, read, and write Zip archive files." +msgstr "" +"Le module Archive::Zip permet à un programme Perl de créer,\n" +"manipuler, lire et écrire des archives Zip." + +#: gnu/packages/zsh.scm:63 +msgid "Powerful shell for interactive use and scripting" +msgstr "Shell puissant pour un usage interactif et l'écriture de scripts" + +#: gnu/packages/zsh.scm:64 +msgid "" +"The Z shell (zsh) is a Unix shell that can be used\n" +"as an interactive login shell and as a powerful command interpreter\n" +"for shell scripting. Zsh can be thought of as an extended Bourne shell\n" +"with a large number of improvements, including some features of bash,\n" +"ksh, and tcsh." +msgstr "" +"Le Z shell (zsh) est un shell Unix pouvant être utilisé comme un shell\n" +"interactif de connexion et un puissant interpréteur de commande pour l'écriture de scripts\n" +"shell. Zsh peut être vu comme un Bourn shell étendu doté de nombreuses améliorations\n" +"et incluant certaines fonctionnalités de bash, ksh et tcsh." diff --git a/tests/profiles.scm b/tests/profiles.scm index 61c801c351..d816248994 100644 --- a/tests/profiles.scm +++ b/tests/profiles.scm @@ -35,6 +35,11 @@ (define %store (open-connection-for-tests)) +(define-syntax-rule (test-assertm name exp) + (test-assert name + (run-with-store %store exp + #:guile-for-build (%guile-for-build)))) + ;; Example manifest entries. (define guile-1.8.8 @@ -156,19 +161,18 @@ (equal? (list glibc) install) (equal? (list (cons guile-1.8.8 guile-2.0.9)) upgrade))))) -(test-assert "profile-derivation" - (run-with-store %store - (mlet* %store-monad - ((entry -> (package->manifest-entry %bootstrap-guile)) - (guile (package->derivation %bootstrap-guile)) - (drv (profile-derivation (manifest (list entry)) - #:info-dir? #f)) - (profile -> (derivation->output-path drv)) - (bindir -> (string-append profile "/bin")) - (_ (built-derivations (list drv)))) - (return (and (file-exists? (string-append bindir "/guile")) - (string=? (dirname (readlink bindir)) - (derivation->output-path guile))))))) +(test-assertm "profile-derivation" + (mlet* %store-monad + ((entry -> (package->manifest-entry %bootstrap-guile)) + (guile (package->derivation %bootstrap-guile)) + (drv (profile-derivation (manifest (list entry)) + #:info-dir? #f)) + (profile -> (derivation->output-path drv)) + (bindir -> (string-append profile "/bin")) + (_ (built-derivations (list drv)))) + (return (and (file-exists? (string-append bindir "/guile")) + (string=? (dirname (readlink bindir)) + (derivation->output-path guile)))))) (test-end "profiles") diff --git a/tests/syscalls.scm b/tests/syscalls.scm index d65ec82740..21d6637ff6 100644 --- a/tests/syscalls.scm +++ b/tests/syscalls.scm @@ -74,7 +74,7 @@ (lset<= string=? names (all-network-interfaces))))) (test-assert "network-interface-flags" - (let* ((sock (socket SOCK_STREAM AF_INET 0)) + (let* ((sock (socket AF_INET SOCK_STREAM 0)) (flags (network-interface-flags sock "lo"))) (close-port sock) (and (not (zero? (logand flags IFF_LOOPBACK))) @@ -90,6 +90,38 @@ (lambda args (system-error-errno args))))) +(test-skip (if (zero? (getuid)) 1 0)) +(test-equal "set-network-interface-flags" + EPERM + (let ((sock (socket AF_INET SOCK_STREAM 0))) + (catch 'system-error + (lambda () + (set-network-interface-flags sock "lo" IFF_UP)) + (lambda args + (close-port sock) + (system-error-errno args))))) + +(test-equal "network-interface-address lo" + (make-socket-address AF_INET (inet-pton AF_INET "127.0.0.1") 0) + (let* ((sock (socket AF_INET SOCK_STREAM 0)) + (addr (network-interface-address sock "lo"))) + (close-port sock) + addr)) + +(test-equal "set-network-interface-address" + EPERM + (let ((sock (socket AF_INET SOCK_STREAM 0))) + (catch 'system-error + (lambda () + (set-network-interface-address sock "nonexistent" + (make-socket-address + AF_INET + (inet-pton AF_INET "127.12.14.15") + 0))) + (lambda args + (close-port sock) + (system-error-errno args))))) + (test-end)