code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2016 Mark H Weaver <mhw@netris.org>
;;; Copyright © 2016 Jan Nieuwenhuizen <janneke@gnu.org>
;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu system linux-initrd)
  #:use-module (guix monads)
  #:use-module (guix store)
  #:use-module (guix gexp)
  #:use-module (guix utils)
  #:use-module ((guix store)
                #:select (%store-prefix))
  #:use-module ((guix derivations)
                #:select (derivation->output-path))
  #:use-module (guix modules)
  #:use-module (gnu packages compression)
  #:use-module (gnu packages disk)
  #:use-module (gnu packages linux)
  #:use-module (gnu packages guile)
  #:use-module ((gnu packages make-bootstrap)
                #:select (%guile-static-stripped))
  #:use-module (gnu system file-systems)
  #:use-module (gnu system mapped-devices)
  #:use-module (ice-9 match)
  #:use-module (ice-9 regex)
  #:use-module (ice-9 vlist)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:export (expression->initrd
            %base-initrd-modules
            raw-initrd
            file-system-packages
            base-initrd))

;;; Commentary:
;;;
;;; Tools to build initial RAM disks (initrd's) for Linux-Libre, and in
;;; particular initrd's that run Guile.
;;;
;;; Code:

(define* (expression->initrd exp
                             #:key
                             (guile %guile-static-stripped)
                             (gzip gzip)
                             (name "guile-initrd")
                             (system (%current-system)))
  "Return a derivation that builds a Linux initrd (a gzipped cpio archive)
containing GUILE and that evaluates EXP, a G-expression, upon booting.  All
the derivations referenced by EXP are automatically copied to the initrd."

  ;; General Linux overview in `Documentation/early-userspace/README' and
  ;; `Documentation/filesystems/ramfs-rootfs-initramfs.txt'.

  (define init
    (program-file "init" exp #:guile guile))

  (define builder
    (with-imported-modules (source-module-closure
                            '((gnu build linux-initrd)))
      #~(begin
          (use-modules (gnu build linux-initrd))

          (mkdir #$output)

          ;; The guile used in the initrd must be present in the store, so
          ;; that module loading works once the root is switched.
          ;;
          ;; To ensure that is the case, add an explicit reference to the
          ;; guile package used in the initrd to the output.
          ;;
          ;; This fixes guix-patches bug #28399, "Fix mysql activation, and
          ;; add a basic test".
          (call-with-output-file (string-append #$ output "/references")
            (lambda (port)
              (simple-format port "~A\n" #$guile)))

          (build-initrd (string-append #$output "/initrd")
                        #:guile #$guile
                        #:init #$init
                        ;; Copy everything INIT refers to into the initrd.
                        #:references-graphs '("closure")
                        #:gzip (string-append #$gzip "/bin/gzip")))))

  (gexp->derivation name builder
                    #:references-graphs `(("closure" ,init))))

(define (flat-linux-module-directory linux modules)
  "Return a flat directory containing the Linux kernel modules listed in
MODULES and taken from LINUX."
  (define build-exp
    (with-imported-modules (source-module-closure
                            '((gnu build linux-modules)))
      #~(begin
          (use-modules (gnu build linux-modules)
                       (srfi srfi-1)
                       (srfi srfi-26))

          (define module-dir
            (string-append #$linux "/lib/modules"))

          (define modules
            (let* ((lookup  (cut find-module-file module-dir <>))
                   (modules (map lookup '#$modules)))
              (append modules
                      (recursive-module-dependencies modules
                                                     #:lookup-module lookup))))

          (mkdir #$output)
          (for-each (lambda (module)
                      (format #t "copying '~a'...~%" module)
                      (copy-file module
                                 (string-append #$output "/"
                                                (basename module))))
                    (delete-duplicates modules)))))

  (computed-file "linux-modules" build-exp))

(define* (raw-initrd file-systems
                      #:key
                      (linux linux-libre)
                      (linux-modules '())
                      (mapped-devices '())
                      (helper-packages '())
                      qemu-networking?
                      volatile-root?
                      (on-error 'debug))
  "Return a monadic derivation that builds a raw initrd, with kernel
modules taken from LINUX.  FILE-SYSTEMS is a list of file-systems to be
mounted by the initrd, possibly in addition to the root file system specified
on the kernel command line via '--root'. LINUX-MODULES is a list of kernel
modules to be loaded at boot time. MAPPED-DEVICES is a list of device
mappings to realize before FILE-SYSTEMS are mounted.
HELPER-PACKAGES is a list of packages to be copied in the initrd. It may include
e2fsck/static or other packages needed by the initrd to check root partition.

When QEMU-NETWORKING? is true, set up networking with the standard QEMU
parameters.

When VOLATILE-ROOT? is true, the root file system is writable but any changes
to it are lost.

ON-ERROR is passed to 'call-with-error-handling'; it determines what happens
upon error."
  (define device-mapping-commands
    ;; List of gexps to open the mapped devices.
    (map (lambda (md)
           (let* ((source (mapped-device-source md))
                  (target (mapped-device-target md))
                  (type   (mapped-device-type md))
                  (open   (mapped-device-kind-open type)))
             (open source target)))
         mapped-devices))

  (define kodir
    (flat-linux-module-directory linux linux-modules))

  (expression->initrd
   (with-imported-modules (source-module-closure
                           '((gnu build linux-boot)
                             (guix build utils)
                             (guix build bournish)
                             (gnu system file-systems)
                             (gnu build file-systems)))
     #~(begin
         (use-modules (gnu build linux-boot)
                      (gnu system file-systems)
                      (guix build utils)
                      (guix build bournish)   ;add the 'bournish' meta-command
                      (srfi srfi-26)

                      ;; FIXME: The following modules are for
                      ;; LUKS-DEVICE-MAPPING.  We should instead propagate
                      ;; this info via gexps.
                      ((gnu build file-systems)
                       #:select (find-partition-by-luks-uuid))
                      (rnrs bytevectors))

         (with-output-to-port (%make-void-port "w")
           (lambda ()
             (set-path-environment-variable "PATH" '("bin" "sbin")
                                            '#$helper-packages)))

         (boot-system #:mounts
                      (map spec->file-system
                           '#$(map file-system->spec file-systems))
                      #:pre-mount (lambda ()
                                    (and #$@device-mapping-commands))
                      #:linux-modules '#$linux-modules
                      #:linux-module-directory '#$kodir
                      #:qemu-guest-networking? #$qemu-networking?
                      #:volatile-root? '#$volatile-root?
                      #:on-error '#$on-error)))
   #:name "raw-initrd"))

(define* (file-system-packages file-systems #:key (volatile-root? #f))
  "Return the list of statically-linked, stripped packages to check
FILE-SYSTEMS."
  `(,@(if (find (lambda (fs)
                  (string-prefix? "ext" (file-system-type fs)))
                file-systems)
          (list e2fsck/static)
          '())
    ,@(if (find (lambda (fs)
                  (string-suffix? "fat" (file-system-type fs)))
                file-systems)
          (list fatfsck/static)
          '())
    ,@(if (find (file-system-type-predicate "btrfs") file-systems)
          (list btrfs-progs/static)
          '())))

(define-syntax vhash                              ;TODO: factorize
  (syntax-rules (=>)
    "Build a vhash with the given key/value mappings."
    ((_)
     vlist-null)
    ((_ (key others ... => value) rest ...)
     (vhash-cons key value
                 (vhash (others ... => value) rest ...)))
    ((_ (=> value) rest ...)
     (vhash rest ...))))

(define-syntax lookup-procedure
  (syntax-rules (else)
    "Return a procedure that lookups keys in the given dictionary."
    ((_ mapping ... (else default))
     (let ((table (vhash mapping ...)))
       (lambda (key)
         (match (vhash-assoc key table)
           (#f            default)
           ((key . value) value)))))))

(define file-system-type-modules
  ;; Given a file system type, return the list of modules it needs.
  (lookup-procedure ("cifs" => '("md4" "ecb" "cifs"))
                    ("9p" => '("9p" "9pnet_virtio"))
                    ("btrfs" => '("btrfs"))
                    ("iso9660" => '("isofs"))
                    (else '())))

(define (file-system-modules file-systems)
  "Return the list of Linux modules needed to mount FILE-SYSTEMS."
  (append-map (compose file-system-type-modules file-system-type)
              file-systems))

(define* (default-initrd-modules #:optional (system (%current-system)))
  "Return the list of modules included in the initrd by default."
  (define virtio-modules
    ;; Modules for Linux para-virtualized devices, for use in QEMU guests.
    '("virtio_pci" "virtio_balloon" "virtio_blk" "virtio_net"
      "virtio_console" "virtio-rng"))

  `("ahci"                                  ;for SATA controllers
    "usb-storage" "uas"                     ;for the installation image etc.
    "usbhid" "hid-generic" "hid-apple"      ;keyboards during early boot
    "dm-crypt" "xts" "serpent_generic" "wp512" ;for encrypted root partitions
    "nls_iso8859-1"                            ;for `mkfs.fat`, et.al
    ,@(if (string-match "^(x86_64|i[3-6]86)-" system)
          '("pata_acpi" "pata_atiixp"    ;for ATA controllers
            "isci")                      ;for SAS controllers like Intel C602
          '())

    ,@virtio-modules))

(define-syntax %base-initrd-modules
  ;; This more closely matches our naming convention.
  (identifier-syntax (default-initrd-modules)))

(define* (base-initrd file-systems
                      #:key
                      (linux linux-libre)
                      (linux-modules '())
                      (mapped-devices '())
                      qemu-networking?
                      volatile-root?
                      (extra-modules '())         ;deprecated
                      (on-error 'debug))
  "Return a monadic derivation that builds a generic initrd, with kernel
modules taken from LINUX.  FILE-SYSTEMS is a list of file-systems to be
mounted by the initrd, possibly in addition to the root file system specified
on the kernel command line via '--root'.  MAPPED-DEVICES is a list of device
mappings to realize before FILE-SYSTEMS are mounted.

QEMU-NETWORKING? and VOLATILE-ROOT? behaves as in raw-initrd.

The initrd is automatically populated with all the kernel modules necessary
for FILE-SYSTEMS and for the given options.  Additional kernel
modules can be listed in LINUX-MODULES.  They will be added to the initrd, and
loaded at boot time in the order in which they appear."
  (define linux-modules*
    ;; Modules added to the initrd and loaded from the initrd.
    `(,@linux-modules
      ,@(file-system-modules file-systems)
      ,@(if volatile-root?
            '("overlay")
            '())
      ,@extra-modules))

  (define helper-packages
    (file-system-packages file-systems #:volatile-root? volatile-root?))

  (raw-initrd file-systems
              #:linux linux
              #:linux-modules linux-modules*
              #:mapped-devices mapped-devices
              #:helper-packages helper-packages
              #:qemu-networking? qemu-networking?
              #:volatile-root? volatile-root?
              #:on-error on-error))

;;; linux-initrd.scm ends here


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2018 Mark H Weaver <mhw@netris.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu system locale)
  #:use-module (guix gexp)
  #:use-module (guix store)
  #:use-module (guix records)
  #:use-module (guix packages)
  #:use-module (guix utils)
  #:use-module (gnu packages base)
  #:use-module (gnu packages compression)
  #:use-module (srfi srfi-26)
  #:use-module (ice-9 match)
  #:export (locale-definition
            locale-definition?
            locale-definition-name
            locale-definition-source
            locale-definition-charset

            locale-name->definition
            locale-directory

            %default-locale-libcs
            %default-locale-definitions))

;;; Commentary:
;;;
;;; Locale definitions, and compilation thereof.
;;;
;;; Code:

(define-record-type* <locale-definition> locale-definition
  make-locale-definition
  locale-definition?
  (name    locale-definition-name)                ;string--e.g., "fr_FR.utf8"
  (source  locale-definition-source)              ;string--e.g., "fr_FR"
  (charset locale-definition-charset              ;string--e.g., "UTF-8"
           (default "UTF-8")))

(define %not-dot
  (char-set-complement (char-set #\.)))

(define (denormalize-codeset codeset)
  "Attempt to guess the \"real\" name of CODESET, a normalized codeset as
defined in (info \"(libc) Using gettextized software\")."
  (cond ((string=? codeset "utf8")
         "UTF-8")
        ((string-prefix? "iso8859" codeset)
         (string-append "ISO-8859-" (string-drop codeset 7)))
        ((string=? codeset "eucjp")
         "EUC-JP")
        (else                                ;cross fingers, hope for the best
         codeset)))

(define (locale-name->definition name)
  "Return a <locale-definition> corresponding to NAME, guessing the charset,
or #f on failure."
  (match (string-tokenize name %not-dot)
    ((source charset)
     ;; XXX: NAME is supposed to use the "normalized codeset", such as "utf8",
     ;; whereas the actual name used is different.  Add a special case to make
     ;; the right guess for UTF-8.
     (locale-definition (name name)
                        (source source)
                        (charset (denormalize-codeset charset))))
    (_
     #f)))

(define* (localedef-command locale
                            #:key (libc (canonical-package glibc)))
  "Return a gexp that runs 'localedef' from LIBC to build LOCALE."
  #~(begin
      (format #t "building locale '~a'...~%"
              #$(locale-definition-name locale))
      (zero? (system* (string-append #+libc "/bin/localedef")
                      "--no-archive" "--prefix" #$output
                      "-i" #$(locale-definition-source locale)
                      "-f" #$(locale-definition-charset locale)
                      (string-append #$output "/" #$(version-major+minor
                                                     (package-version libc))
                                     "/" #$(locale-definition-name locale))))))

(define* (single-locale-directory locales
                                  #:key (libc (canonical-package glibc)))
  "Return a directory containing all of LOCALES for LIBC compiled.

Because locale data formats are incompatible when switching from one libc to
another, locale data is put in a sub-directory named after the 'version' field
of LIBC."
  (define version
    (version-major+minor (package-version libc)))

  (define build
    #~(begin
        (mkdir #$output)

        (mkdir (string-append #$output "/" #$version))

        ;; 'localedef' executes 'gzip' to access compressed locale sources.
        (setenv "PATH" (string-append #$gzip "/bin"))

        (exit
         (and #$@(map (cut localedef-command <> #:libc libc)
                      locales)))))

  (computed-file (string-append "locale-" version) build))

(define* (locale-directory locales
                           #:key (libcs %default-locale-libcs))
  "Return a locale directory containing all of LOCALES for each libc package
listed in LIBCS.

It is useful to list more than one libc when willing to support
already-installed packages built against a different libc since the locale
data format changes between libc versions."
  (match libcs
    ((libc)
     (single-locale-directory locales #:libc libc))
    ((libcs ..1)
     (let ((dirs (map (lambda (libc)
                        (single-locale-directory locales #:libc libc))
                      libcs)))
       (computed-file "locale-multiple-versions"
                      (with-imported-modules '((guix build union))
                        #~(begin
                            (use-modules (guix build union))
                            (union-build #$output (list #$@dirs))))
                      #:options '(#:local-build? #t
                                  #:substitutable? #f))))))

(define %default-locale-libcs
  ;; The libcs for which we build locales by default.
  (list (canonical-package glibc)))

(define %default-locale-definitions
  ;; Arbitrary set of locales that are built by default.  They are here mostly
  ;; to facilitate first-time use to some people, while others may have to add
  ;; a specific <locale-definition>.
  (letrec-syntax ((utf8-locale (syntax-rules ()
                                 ((_ name*)
                                  (locale-definition
                                   ;; Note: We choose "utf8", which is the
                                   ;; "normalized codeset".
                                   (name (string-append name* ".utf8"))
                                   (source name*)
                                   (charset "UTF-8")))))
                  (utf8-locales (syntax-rules ()
                                  ((_ name ...)
                                   (list (utf8-locale name) ...)))))
    ;; Add "en_US.UTF-8" for compatibility with Guix 0.8.
    (cons (locale-definition
           (name "en_US.UTF-8")
           (source "en_US")
           (charset "UTF-8"))
          (utf8-locales "ca_ES"
                        "cs_CZ"
                        "da_DK"
                        "de_DE"
                        "el_GR"
                        "en_AU"
                        "en_CA"
                        "en_GB"
                        "en_US"
                        "es_AR"
                        "es_CL"
                        "es_ES"
                        "es_MX"
                        "fi_FI"
                        "fr_BE"
                        "fr_CA"
                        "fr_CH"
                        "fr_FR"
                        "ga_IE"
                        "it_IT"
                        "ja_JP"
                        "ko_KR"
                        "nb_NO"
                        "nl_NL"
                        "pl_PL"
                        "pt_PT"
                        "ro_RO"
                        "ru_RU"
                        "sv_SE"
                        "tr_TR"
                        "uk_UA"
                        "vi_VN"
                        "zh_CN"))))

;;; locale.scm ends here

license: mit


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2016 Andreas Enge <andreas@enge.fr>
;;; Copyright © 2017, 2018 Mark H Weaver <mhw@netris.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu system mapped-devices)
  #:use-module (guix gexp)
  #:use-module (guix records)
  #:use-module ((guix modules) #:hide (file-name->module-name))
  #:use-module (guix i18n)
  #:use-module ((guix utils)
                #:select (source-properties->location
                          &fix-hint
                          &error-location))
  #:use-module (gnu services)
  #:use-module (gnu services shepherd)
  #:use-module (gnu system uuid)
  #:autoload   (gnu build file-systems) (find-partition-by-luks-uuid)
  #:autoload   (gnu build linux-modules)
                 (device-module-aliases matching-modules known-module-aliases
                  normalize-module-name file-name->module-name)
  #:autoload   (gnu packages cryptsetup) (cryptsetup-static)
  #:autoload   (gnu packages linux) (mdadm-static)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:use-module (srfi srfi-34)
  #:use-module (srfi srfi-35)
  #:use-module (ice-9 match)
  #:export (mapped-device
            mapped-device?
            mapped-device-source
            mapped-device-target
            mapped-device-type
            mapped-device-location

            mapped-device-kind
            mapped-device-kind?
            mapped-device-kind-open
            mapped-device-kind-close
            mapped-device-kind-check

            device-mapping-service-type
            device-mapping-service

            check-device-initrd-modules           ;XXX: needs a better place

            luks-device-mapping
            raid-device-mapping))

;;; Commentary:
;;;
;;; This module supports "device mapping", a concept implemented by Linux's
;;; device-mapper.
;;;
;;; Code:

(define-record-type* <mapped-device> mapped-device
  make-mapped-device
  mapped-device?
  (source    mapped-device-source)                ;string | list of strings
  (target    mapped-device-target)                ;string
  (type      mapped-device-type)                  ;<mapped-device-kind>
  (location  mapped-device-location
             (default (current-source-location)) (innate)))

(define-record-type* <mapped-device-type> mapped-device-kind
  make-mapped-device-kind
  mapped-device-kind?
  (open      mapped-device-kind-open)             ;source target -> gexp
  (close     mapped-device-kind-close             ;source target -> gexp
             (default (const #~(const #f))))
  (check     mapped-device-kind-check             ;source -> Boolean
             (default (const #t))))

;;;
;;; Device mapping as a Shepherd service.
;;;

(define device-mapping-service-type
  (shepherd-service-type
   'device-mapping
   (match-lambda
     (($ <mapped-device> source target
                         ($ <mapped-device-type> open close))
      (shepherd-service
       (provision (list (symbol-append 'device-mapping- (string->symbol target))))
       (requirement '(udev))
       (documentation "Map a device node using Linux's device mapper.")
       (start #~(lambda () #$(open source target)))
       (stop #~(lambda _ (not #$(close source target))))
       (respawn? #f))))))

(define (device-mapping-service mapped-device)
  "Return a service that sets up @var{mapped-device}."
  (service device-mapping-service-type mapped-device))

;;;
;;; Static checks.
;;;

(define (check-device-initrd-modules device linux-modules location)
  "Raise an error if DEVICE needs modules beyond LINUX-MODULES to operate.
DEVICE must be a \"/dev\" file name."
  (define aliases
    ;; Attempt to load 'modules.alias' from the current kernel, assuming we're
    ;; on GuixSD, and assuming that corresponds to the kernel we'll be
    ;; installing.  Skip the whole thing if that file cannot be read.
    (catch 'system-error
      (lambda ()
        (known-module-aliases))
      (const #f)))

  (when aliases
    (let* ((modules  (delete-duplicates
                      (append-map (cut matching-modules <> aliases)
                                  (device-module-aliases device))))

           ;; Module names (not file names) are supposed to use underscores
           ;; instead of hyphens.  MODULES is a list of module names, whereas
           ;; LINUX-MODULES is file names without '.ko', so normalize them.
           (provided (map file-name->module-name linux-modules))
           (missing  (remove (cut member <> provided) modules)))
      (unless (null? missing)
        ;; Note: What we suggest here is a list of module names (e.g.,
        ;; "usb_storage"), not file names (e.g., "usb-storage.ko").  This is
        ;; OK because we have machinery that accepts both the hyphen and the
        ;; underscore version.
        (raise (condition
                (&message
                 (message (format #f (G_ "you may need these modules \
in the initrd for ~a:~{ ~a~}")
                                  device missing)))
                (&fix-hint
                 (hint (format #f (G_ "Try adding them to the
@code{initrd-modules} field of your @code{operating-system} declaration, along
these lines:

@example
 (operating-system
   ;; @dots{}
   (initrd-modules (append (list~{ ~s~})
                           %base-initrd-modules)))
@end example

If you think this diagnostic is inaccurate, use the @option{--skip-checks}
option of @command{guix system}.\n")
                               missing)))
                (&error-location
                 (location (source-properties->location location)))))))))

;;;
;;; Common device mappings.
;;;

(define (open-luks-device source target)
  "Return a gexp that maps SOURCE to TARGET as a LUKS device, using
'cryptsetup'."
  (with-imported-modules (source-module-closure
                          '((gnu build file-systems)))
    #~(let ((source #$(if (uuid? source)
                          (uuid-bytevector source)
                          source)))
        ;; XXX: 'use-modules' should be at the top level.
        (use-modules (rnrs bytevectors)           ;bytevector?
                     ((gnu build file-systems)
                      #:select (find-partition-by-luks-uuid)))

        ;; Use 'cryptsetup-static', not 'cryptsetup', to avoid pulling the
        ;; whole world inside the initrd (for when we're in an initrd).
        (zero? (system* #$(file-append cryptsetup-static "/sbin/cryptsetup")
                        "open" "--type" "luks"

                        ;; Note: We cannot use the "UUID=source" syntax here
                        ;; because 'cryptsetup' implements it by searching the
                        ;; udev-populated /dev/disk/by-id directory but udev may
                        ;; be unavailable at the time we run this.
                        (if (bytevector? source)
                            (or (let loop ((tries-left 10))
                                  (and (positive? tries-left)
                                       (or (find-partition-by-luks-uuid source)
                                           ;; If the underlying partition is
                                           ;; not found, try again after
                                           ;; waiting a second, up to ten
                                           ;; times.  FIXME: This should be
                                           ;; dealt with in a more robust way.
                                           (begin (sleep 1)
                                                  (loop (- tries-left 1))))))
                                (error "LUKS partition not found" source))
                            source)

                        #$target)))))

(define (close-luks-device source target)
  "Return a gexp that closes TARGET, a LUKS device."
  #~(zero? (system* #$(file-append cryptsetup-static "/sbin/cryptsetup")
                    "close" #$target)))

(define* (check-luks-device md #:key
                            needed-for-boot?
                            (initrd-modules '())
                            #:allow-other-keys
                            #:rest rest)
  "Ensure the source of MD is valid."
  (let ((source   (mapped-device-source md))
        (location (mapped-device-location md)))
    (or (not (zero? (getuid)))
        (if (uuid? source)
            (match (find-partition-by-luks-uuid (uuid-bytevector source))
              (#f
               (raise (condition
                       (&message
                        (message (format #f (G_ "no LUKS partition with UUID '~a'")
                                         (uuid->string source))))
                       (&error-location
                        (location (source-properties->location
                                   (mapped-device-location md)))))))
              ((? string? device)
               (check-device-initrd-modules device initrd-modules location)))
            (check-device-initrd-modules source initrd-modules location)))))

(define luks-device-mapping
  ;; The type of LUKS mapped devices.
  (mapped-device-kind
   (open open-luks-device)
   (close close-luks-device)
   (check check-luks-device)))

(define (open-raid-device sources target)
  "Return a gexp that assembles SOURCES (a list of devices) to the RAID device
TARGET (e.g., \"/dev/md0\"), using 'mdadm'."
  #~(let ((sources '#$sources)

          ;; XXX: We're not at the top level here.  We could use a
          ;; non-top-level 'use-modules' form but that doesn't work when the
          ;; code is eval'd, like the Shepherd does.
          (every   (@ (srfi srfi-1) every))
          (format  (@ (ice-9 format) format)))
      (let loop ((attempts 0))
        (unless (every file-exists? sources)
          (when (> attempts 20)
            (error "RAID devices did not show up; bailing out"
                   sources))

          (format #t "waiting for RAID source devices~{ ~a~}...~%"
                  sources)
          (sleep 1)
          (loop (+ 1 attempts))))

      ;; Use 'mdadm-static' rather than 'mdadm' to avoid pulling its whole
      ;; closure (80 MiB) in the initrd when a RAID device is needed for boot.
      (zero? (apply system* #$(file-append mdadm-static "/sbin/mdadm")
                    "--assemble" #$target sources))))

(define (close-raid-device sources target)
  "Return a gexp that stops the RAID device TARGET."
  #~(zero? (system* #$(file-append mdadm-static "/sbin/mdadm")
                    "--stop" #$target)))

(define raid-device-mapping
  ;; The type of RAID mapped devices.
  (mapped-device-kind
   (open open-raid-device)
   (close close-raid-device)))

;;; mapped-devices.scm ends here


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2015, 2018 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu system nss)
  #:use-module (rnrs enums)
  #:use-module (guix records)
  #:use-module (srfi srfi-9)
  #:use-module (ice-9 match)
  #:export (name-service-switch?
            name-service-switch
            name-service?
            name-service

            lookup-specification

            %default-nss
            %mdns-host-lookup-nss

            %files
            %compat
            %dns

            name-service-switch->string))

;;; Commentary:
;;;
;;; Bindings for libc's name service switch (NSS) configuration.
;;;
;;; Code:

(define-record-type* <name-service> name-service
  make-name-service
  name-service?
  (name     name-service-name)
  (reaction name-service-reaction
            (default (lookup-specification))))

;; Lookup specification (info "(libc) Actions in the NSS Configuration").

(define-enumeration lookup-action
  (return continue)
  make-lookup-action)

(define-enumeration lookup-status
  (success
   not-found
   unavailable
   try-again)
  make-lookup-status)

(define-record-type <lookup-status-negation>
  (lookup-status-negation status)
  lookup-status-negation?
  (status lookup-status-negation-status))

(define-record-type <lookup-reaction>
  (make-lookup-reaction status action)
  lookup-reaction?
  (status  lookup-reaction-status)
  (action  lookup-reaction-action))

(define-syntax lookup-reaction
  (syntax-rules (not =>)
    ((_ ((not status) => action))
     (make-lookup-reaction (lookup-status-negation (lookup-status status))
                           (lookup-action action)))
    ((_ (status => action))
     (make-lookup-reaction (lookup-status status)
                           (lookup-action action)))))

(define-syntax-rule (lookup-specification reaction ...)
  "Return an NSS lookup specification."
  (list (lookup-reaction reaction) ...))

;;;
;;; Common name services and default NSS configuration.
;;;

(define %compat
  ;; Note: Starting from version 2.26, libc no longer provides libnss_compat
  ;; so this specification has become useless.
  (name-service
    (name "compat")
    (reaction (lookup-specification (not-found => return)))))

(define %files
  (name-service (name "files")))

(define %dns
  ;; DNS is supposed to be authoritative, so unless it's unavailable, return
  ;; what it finds.
  (name-service
    (name "dns")
    (reaction (lookup-specification ((not unavailable) => return)))))

;; The NSS.  We list all the databases here because that allows us to
;; statically ensure that the user's configuration refers to existing
;; databases.  See libc/nss/databases.def for the list of databases.  Default
;; values obtained by looking for "DEFAULT_CONFIG" in libc/nss/*.c.
;;
;; Although libc places 'dns' before 'files' in the default configurations of
;; the 'hosts' and 'networks' databases, we choose to put 'files' before 'dns'
;; by default, so that users can override host/address mappings in /etc/hosts
;; and bypass DNS to improve their privacy and escape NSA's MORECOWBELL.
(define-record-type* <name-service-switch> name-service-switch
  make-name-service-switch
  name-service-switch?
  (aliases    name-service-switch-aliases
              (default '()))
  (ethers     name-service-switch-ethers
              (default '()))
  (group      name-service-switch-group
              (default (list %files)))
  (gshadow    name-service-switch-gshadow
              (default '()))
  (hosts      name-service-switch-hosts
              (default (list %files %dns)))
  (initgroups name-service-switch-initgroups
              (default '()))
  (netgroup   name-service-switch-netgroup
              (default '()))
  (networks   name-service-switch-networks
              (default (list %files %dns)))
  (password   name-service-switch-password
              (default (list %files)))
  (public-key name-service-switch-public-key
              (default '()))
  (rpc        name-service-switch-rpc
              (default '()))
  (services   name-service-switch-services
              (default '()))
  (shadow     name-service-switch-shadow
              (default (list %files))))

(define %default-nss
  ;; Default NSS configuration.
  (name-service-switch))

(define %mdns-host-lookup-nss
  (name-service-switch
    (hosts (list %files                           ;first, check /etc/hosts

                 ;; If the above did not succeed, try with 'mdns_minimal'.
                 (name-service
                   (name "mdns_minimal")

                   ;; 'mdns_minimal' is authoritative for '.local'.  When it
                   ;; returns "not found", no need to try the next methods.
                   (reaction (lookup-specification
                              (not-found => return))))

                 ;; Then fall back to DNS.
                 (name-service
                   (name "dns"))

                 ;; Finally, try with the "full" 'mdns'.
                 (name-service
                   (name "mdns"))))))

;;;
;;; Serialization.
;;;

(define (lookup-status->string status)
  (match status
    ('success     "SUCCESS")
    ('not-found   "NOTFOUND")
    ('unavailable "UNAVAIL")
    ('try-again   "TRYAGAIN")
    (($ <lookup-status-negation> status)
     (string-append "!" (lookup-status->string status)))))

(define lookup-reaction->string
  (match-lambda
   (($ <lookup-reaction> status action)
    (string-append (lookup-status->string status) "="
                   (symbol->string action)))))

(define name-service->string
  (match-lambda
   (($ <name-service> name ())
    name)
   (($ <name-service> name reactions)
    (string-append name " ["
                   (string-join (map lookup-reaction->string reactions))
                   "]"))))

(define (name-service-switch->string nss)
  "Return the 'nsswitch.conf' contents for NSS as a string.  See \"NSS
Configuration File\" in the libc manual."
  (let-syntax ((->string
                (syntax-rules ()
                  ((_ name field)
                   (match (field nss)
                     (()                          ;keep the default config
                      "")
                     ((services (... ...))
                      (string-append name ":\t"
                                     (string-join
                                      (map name-service->string services))
                                     "\n")))))))
    (string-append (->string "aliases"    name-service-switch-aliases)
                   (->string "ethers"     name-service-switch-ethers)
                   (->string "group"      name-service-switch-group)
                   (->string "gshadow"    name-service-switch-gshadow)
                   (->string "hosts"      name-service-switch-hosts)
                   (->string "initgroups" name-service-switch-initgroups)
                   (->string "netgroup"   name-service-switch-netgroup)
                   (->string "networks"   name-service-switch-networks)
                   (->string "passwd"     name-service-switch-password)
                   (->string "publickey"  name-service-switch-public-key)
                   (->string "rpc"        name-service-switch-rpc)
                   (->string "services"   name-service-switch-services)
                   (->string "shadow"     name-service-switch-shadow))))

;;; Local Variables:
;;; eval: (put 'name-service 'scheme-indent-function 0)
;;; eval: (put 'name-service-switch 'scheme-indent-function 0)
;;; End:

;;; nss.scm ends here


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu system pam)
  #:use-module (guix records)
  #:use-module (guix derivations)
  #:use-module (guix gexp)
  #:use-module (gnu services)
  #:use-module (ice-9 match)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-9)
  #:use-module (srfi srfi-11)
  #:use-module (srfi srfi-26)
  #:use-module ((guix utils) #:select (%current-system))
  #:export (pam-service
            pam-service-name
            pam-service-account
            pam-service-auth
            pam-service-password
            pam-service-session

            pam-entry
            pam-entry-control
            pam-entry-module
            pam-entry-arguments

            pam-limits-entry
            pam-limits-entry-domain
            pam-limits-entry-type
            pam-limits-entry-item
            pam-limits-entry-value
            pam-limits-entry->string

            pam-services->directory
            unix-pam-service
            base-pam-services

            session-environment-service
            session-environment-service-type

            pam-root-service-type
            pam-root-service))

;;; Commentary:
;;;
;;; Configuration of the pluggable authentication modules (PAM).
;;;
;;; Code:

;; PAM services (see
;; <http://www.linux-pam.org/Linux-PAM-html/sag-configuration-file.html>.)
(define-record-type* <pam-service> pam-service
  make-pam-service
  pam-service?
  (name       pam-service-name)                   ; string

  ;; The four "management groups".
  (account    pam-service-account                 ; list of <pam-entry>
              (default '()))
  (auth       pam-service-auth
              (default '()))
  (password   pam-service-password
              (default '()))
  (session    pam-service-session
              (default '())))

(define-record-type* <pam-entry> pam-entry
  make-pam-entry
  pam-entry?
  (control    pam-entry-control)         ; string
  (module     pam-entry-module)          ; file name
  (arguments  pam-entry-arguments        ; list of string-valued g-expressions
              (default '())))

;; PAM limits entries are used by the pam_limits PAM module to set or override
;; limits on system resources for user sessions.  The format is specified
;; here: http://linux-pam.org/Linux-PAM-html/sag-pam_limits.html
(define-record-type <pam-limits-entry>
  (make-pam-limits-entry domain type item value)
  pam-limits-entry?
  (domain     pam-limits-entry-domain)   ; string
  (type       pam-limits-entry-type)     ; symbol
  (item       pam-limits-entry-item)     ; symbol
  (value      pam-limits-entry-value))   ; symbol or number

(define (pam-limits-entry domain type item value)
  "Construct a pam-limits-entry ensuring that the provided values are valid."
  (define (valid? value)
    (case item
      ((priority) (number? value))
      ((nice)     (and (number? value)
                       (>= value -20)
                       (<= value 19)))
      (else       (or (and (number? value)
                           (>= value -1))
                      (member value '(unlimited infinity))))))
  (define items
    (list 'core      'data       'fsize
          'memlock   'nofile     'rss
          'stack     'cpu        'nproc
          'as        'maxlogins  'maxsyslogins
          'priority  'locks      'sigpending
          'msgqueue  'nice       'rtprio))
  (when (not (member type '(hard soft both)))
    (error "invalid limit type" type))
  (when (not (member item items))
    (error "invalid limit item" item))
  (when (not (valid? value))
    (error "invalid limit value" value))
  (make-pam-limits-entry domain type item value))

(define (pam-limits-entry->string entry)
  "Convert a pam-limits-entry record to a string."
  (match entry
    (($ <pam-limits-entry> domain type item value)
     (string-join (list domain
                        (if (eq? type 'both)
                            "-"
                            (symbol->string type))
                        (symbol->string item)
                        (cond
                         ((symbol? value)
                          (symbol->string value))
                         (else
                          (number->string value))))
                  "	"))))

(define (pam-service->configuration service)
  "Return the derivation building the configuration file for SERVICE, to be
dumped in /etc/pam.d/NAME, where NAME is the name of SERVICE."
  (define (entry->gexp type entry)
    (match entry
      (($ <pam-entry> control module (arguments ...))
       #~(format #t "~a ~a ~a ~a~%"
                 #$type #$control #$module
                 (string-join (list #$@arguments))))))

  (match service
    (($ <pam-service> name account auth password session)
     (define builder
       #~(begin
           (with-output-to-file #$output
             (lambda ()
               #$@(append (map (cut entry->gexp "account" <>) account)
                          (map (cut entry->gexp "auth" <>) auth)
                          (map (cut entry->gexp "password" <>) password)
                          (map (cut entry->gexp "session" <>) session))
               #t))))

     (computed-file name builder))))

(define (pam-services->directory services)
  "Return the derivation to build the configuration directory to be used as
/etc/pam.d for SERVICES."
  (let ((names (map pam-service-name services))
        (files (map pam-service->configuration services)))
    (define builder
      #~(begin
          (use-modules (ice-9 match)
                       (srfi srfi-1))

          (mkdir #$output)
          (for-each (match-lambda
                      ((name file)
                       (symlink file (string-append #$output "/" name))))

                    ;; Since <pam-service> objects cannot be compared with
                    ;; 'equal?' since they contain gexps, which contain
                    ;; closures, use 'delete-duplicates' on the build-side
                    ;; instead.  See <http://bugs.gnu.org/20037>.
                    (delete-duplicates '#$(zip names files)))))

    (computed-file "pam.d" builder)))

(define %pam-other-services
  ;; The "other" PAM configuration, which denies everything (see
  ;; <http://www.linux-pam.org/Linux-PAM-html/sag-configuration-example.html>.)
  (let ((deny (pam-entry
               (control "required")
               (module "pam_deny.so"))))
    (pam-service
     (name "other")
     (account (list deny))
     (auth (list deny))
     (password (list deny))
     (session (list deny)))))

(define unix-pam-service
  (let ((unix (pam-entry
               (control "required")
               (module "pam_unix.so")))
        (env  (pam-entry ; to honor /etc/environment.
               (control "required")
               (module "pam_env.so"))))
    (lambda* (name #:key allow-empty-passwords? (allow-root? #f) motd)
      "Return a standard Unix-style PAM service for NAME.  When
ALLOW-EMPTY-PASSWORDS? is true, allow empty passwords.  When ALLOW-ROOT? is
true, allow root to run the command without authentication.  When MOTD is
true, it should be a file-like object used as the message-of-the-day."
      ;; See <http://www.linux-pam.org/Linux-PAM-html/sag-configuration-example.html>.
      (let ((name* name))
        (pam-service
         (name name*)
         (account (list unix))
         (auth (append (if allow-root?
                           (list (pam-entry
                                  (control "sufficient")
                                  (module "pam_rootok.so")))
                           '())
                       (list (if allow-empty-passwords?
                                 (pam-entry
                                  (control "required")
                                  (module "pam_unix.so")
                                  (arguments '("nullok")))
                                 unix))))
         (password (list (pam-entry
                          (control "required")
                          (module "pam_unix.so")
                          ;; Store SHA-512 encrypted passwords in /etc/shadow.
                          (arguments '("sha512" "shadow")))))
         (session (if motd
                      (list env unix
                            (pam-entry
                             (control "optional")
                             (module "pam_motd.so")
                             (arguments
                              (list #~(string-append "motd=" #$motd)))))
                      (list env unix))))))))

(define (rootok-pam-service command)
  "Return a PAM service for COMMAND such that 'root' does not need to
authenticate to run COMMAND."
  (let ((unix (pam-entry
               (control "required")
               (module "pam_unix.so"))))
    (pam-service
     (name command)
     (account (list unix))
     (auth (list (pam-entry
                  (control "sufficient")
                  (module "pam_rootok.so"))))
     (password (list unix))
     (session (list unix)))))

(define* (base-pam-services #:key allow-empty-passwords?)
  "Return the list of basic PAM services everyone would want."
  ;; TODO: Add other Shadow programs?
  (append (list %pam-other-services)

          ;; These programs are setuid-root.
          (map (cut unix-pam-service <>
                    #:allow-empty-passwords? allow-empty-passwords?)
               '("passwd" "sudo"))
          ;; This is setuid-root, as well.  Allow root to run "su" without
          ;; authenticating.
          (list (unix-pam-service "su"
                                  #:allow-empty-passwords? allow-empty-passwords?
                                  #:allow-root? #t))

          ;; These programs are not setuid-root, and we want root to be able
          ;; to run them without having to authenticate (notably because
          ;; 'useradd' and 'groupadd' are run during system activation.)
          (map rootok-pam-service
               '("useradd" "userdel" "usermod"
                 "groupadd" "groupdel" "groupmod"))))

;;;
;;; System-wide environment variables.
;;;

(define (environment-variables->environment-file vars)
  "Return a file for pam_env(8) that contains environment variables VARS."
  (apply mixed-text-file "environment"
         (append-map (match-lambda
                       ((key . value)
                        (list key "=" value "\n")))
                     vars)))

(define session-environment-service-type
  (service-type
   (name 'session-environment)
   (extensions
    (list (service-extension
           etc-service-type
           (lambda (vars)
             (list `("environment"
                     ,(environment-variables->environment-file vars)))))))
   (compose concatenate)
   (extend append)
   (description
    "Populate @file{/etc/environment}, which is honored by @code{pam_env},
with the specified environment variables.  The value of this service is a list
of name/value pairs for environments variables, such as:

@example
'((\"TZ\" . \"Canada/Pacific\"))
@end example\n")))

(define (session-environment-service vars)
  "Return a service that builds the @file{/etc/environment}, which can be read
by PAM-aware applications to set environment variables for sessions.

VARS should be an association list in which both the keys and the values are
strings or string-valued gexps."
  (service session-environment-service-type vars))

;;;
;;; PAM root service.
;;;

;; Overall PAM configuration: a list of services, plus a procedure that takes
;; one <pam-service> and returns a <pam-service>.  The procedure is used to
;; implement cross-cutting concerns such as the use of the 'elogind.so'
;; session module that keeps track of logged-in users.
(define-record-type* <pam-configuration>
  pam-configuration make-pam-configuration? pam-configuration?
  (services  pam-configuration-services)          ;list of <pam-service>
  (transform pam-configuration-transform))        ;procedure

(define (/etc-entry config)
  "Return the /etc/pam.d entry corresponding to CONFIG."
  (match config
    (($ <pam-configuration> services transform)
     (let ((services (map transform services)))
       `(("pam.d" ,(pam-services->directory services)))))))

(define (extend-configuration initial extensions)
  "Extend INITIAL with NEW."
  (let-values (((services procs)
                (partition pam-service? extensions)))
    (pam-configuration
     (services (append (pam-configuration-services initial)
                       services))
     (transform (apply compose
                       (pam-configuration-transform initial)
                       procs)))))

(define pam-root-service-type
  (service-type (name 'pam)
                (extensions (list (service-extension etc-service-type
                                                     /etc-entry)))

                ;; Arguments include <pam-service> as well as procedures.
                (compose concatenate)
                (extend extend-configuration)))

(define* (pam-root-service base #:key (transform identity))
  "The \"root\" PAM service, which collects <pam-service> instance and turns
them into a /etc/pam.d directory, including the <pam-service> listed in BASE.
TRANSFORM is a procedure that takes a <pam-service> and returns a
<pam-service>.  It can be used to implement cross-cutting concerns that affect
all the PAM services."
  (service pam-root-service-type
           (pam-configuration (services base)
                              (transform transform))))


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2016 Alex Griffin <a@ajgrf.com>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu system shadow)
  #:use-module (guix records)
  #:use-module (guix gexp)
  #:use-module (guix store)
  #:use-module (guix modules)
  #:use-module (guix sets)
  #:use-module (guix ui)
  #:use-module (gnu services)
  #:use-module (gnu services shepherd)
  #:use-module ((gnu system file-systems)
                #:select (%tty-gid))
  #:use-module ((gnu packages admin)
                #:select (shadow))
  #:use-module (gnu packages bash)
  #:use-module (gnu packages guile-wm)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:use-module (srfi srfi-34)
  #:use-module (srfi srfi-35)
  #:export (user-account
            user-account?
            user-account-name
            user-account-password
            user-account-uid
            user-account-group
            user-account-supplementary-groups
            user-account-comment
            user-account-home-directory
            user-account-create-home-directory?
            user-account-shell
            user-account-system?

            user-group
            user-group?
            user-group-name
            user-group-password
            user-group-id
            user-group-system?

            default-skeletons
            skeleton-directory
            %base-groups
            %base-user-accounts

            account-service-type
            account-service))

;;; Commentary:
;;;
;;; Utilities for configuring the Shadow tool suite ('login', 'passwd', etc.)
;;;
;;; Code:

(define-record-type* <user-account>
  user-account make-user-account
  user-account?
  (name           user-account-name)
  (password       user-account-password (default #f))
  (uid            user-account-uid (default #f))
  (group          user-account-group)             ; number | string
  (supplementary-groups user-account-supplementary-groups
                        (default '()))            ; list of strings
  (comment        user-account-comment (default ""))
  (home-directory user-account-home-directory)
  (create-home-directory? user-account-create-home-directory? ;Boolean
                          (default #t))
  (shell          user-account-shell              ; gexp
                  (default (file-append bash "/bin/bash")))
  (system?        user-account-system?            ; Boolean
                  (default #f)))

(define-record-type* <user-group>
  user-group make-user-group
  user-group?
  (name           user-group-name)
  (password       user-group-password (default #f))
  (id             user-group-id (default #f))
  (system?        user-group-system?              ; Boolean
                  (default #f)))

(define %base-groups
  ;; Default set of groups.
  (let-syntax ((system-group (syntax-rules ()
                               ((_ args ...)
                                (user-group (system? #t) args ...)))))
    (list (system-group (name "root") (id 0))
          (system-group (name "wheel"))           ; root-like users
          (system-group (name "users"))           ; normal users
          (system-group (name "nogroup"))         ; for daemons etc.

          ;; The following groups are conventionally used by things like udev to
          ;; control access to hardware devices.
          (system-group (name "tty") (id %tty-gid))
          (system-group (name "dialout"))
          (system-group (name "kmem"))
          (system-group (name "input"))           ; input devices, from udev
          (system-group (name "video"))
          (system-group (name "audio"))
          (system-group (name "netdev"))          ; used in avahi-dbus.conf
          (system-group (name "lp"))
          (system-group (name "disk"))
          (system-group (name "floppy"))
          (system-group (name "cdrom"))
          (system-group (name "tape"))
          (system-group (name "kvm")))))          ; for /dev/kvm

(define %base-user-accounts
  ;; List of standard user accounts.  Note that "root" is a special case, so
  ;; it's not listed here.
  (list (user-account
         (name "nobody")
         (uid 65534)
         (group "nogroup")
         (shell (file-append shadow "/sbin/nologin"))
         (home-directory "/nonexistent")
         (create-home-directory? #f)
         (system? #t))))

(define (default-skeletons)
  "Return the default skeleton files for /etc/skel.  These files are copied by
'useradd' in the home directory of newly created user accounts."
  (define copy-guile-wm
    (with-imported-modules '((guix build utils))
      #~(begin
          (use-modules (guix build utils))
          (copy-file (car (find-files #+guile-wm "wm-init-sample.scm"))
                     #$output))))

  (let ((profile (plain-file "bash_profile" "\
# Honor per-interactive-shell startup file
if [ -f ~/.bashrc ]; then . ~/.bashrc; fi\n"))
        (bashrc  (plain-file "bashrc" "\
# Bash initialization for interactive non-login shells and
# for remote shells (info \"(bash) Bash Startup Files\").

# Export 'SHELL' to child processes.  Programs such as 'screen'
# honor it and otherwise use /bin/sh.
export SHELL

if [[ $- != *i* ]]
then
    # We are being invoked from a non-interactive shell.  If this
    # is an SSH session (as in \"ssh host command\"), source
    # /etc/profile so we get PATH and other essential variables.
    [[ -n \"$SSH_CLIENT\" ]] && source /etc/profile

    # Don't do anything else.
    return
fi

# Source the system-wide file.
source /etc/bashrc

# Adjust the prompt depending on whether we're in 'guix environment'.
if [ -n \"$GUIX_ENVIRONMENT\" ]
then
    PS1='\\u@\\h \\w [env]\\$ '
else
    PS1='\\u@\\h \\w\\$ '
fi
alias ls='ls -p --color=auto'
alias ll='ls -l'
alias grep='grep --color=auto'\n"))
        (zlogin    (plain-file "zlogin" "\
# Honor system-wide environment variables
source /etc/profile\n"))
        (guile-wm  (computed-file "guile-wm" copy-guile-wm))
        (xdefaults (plain-file "Xdefaults" "\
XTerm*utf8: always
XTerm*metaSendsEscape: true\n"))
        (gdbinit   (plain-file "gdbinit" "\
# Tell GDB where to look for separate debugging files.
set debug-file-directory ~/.guix-profile/lib/debug

# Authorize extensions found in the store, such as the
# pretty-printers of libstdc++.
set auto-load safe-path /gnu/store/*/lib\n")))
    `((".bash_profile" ,profile)
      (".bashrc" ,bashrc)
      (".zlogin" ,zlogin)
      (".nanorc" ,(plain-file "nanorc" "\
# Include all the syntax highlighting modules.
include /run/current-system/profile/share/nano/*.nanorc\n"))
      (".Xdefaults" ,xdefaults)
      (".guile" ,(plain-file "dot-guile"
                             "(cond ((false-if-exception (resolve-interface '(ice-9 readline)))
       =>
       (lambda (module)
         ;; Enable completion and input history at the REPL.
         ((module-ref module 'activate-readline))))
      (else
       (display \"Consider installing the 'guile-readline' package for
convenient interactive line editing and input history.\\n\\n\")))

      (unless (getenv \"INSIDE_EMACS\")
        (cond ((false-if-exception (resolve-interface '(ice-9 colorized)))
               =>
               (lambda (module)
                 ;; Enable completion and input history at the REPL.
                 ((module-ref module 'activate-colorized))))
              (else
               (display \"Consider installing the 'guile-colorized' package
for a colorful Guile experience.\\n\\n\"))))\n"))
      (".guile-wm" ,guile-wm)
      (".gdbinit" ,gdbinit))))

(define (skeleton-directory skeletons)
  "Return a directory containing SKELETONS, a list of name/derivation tuples."
  (computed-file "skel"
                 (with-imported-modules '((guix build utils))
                   #~(begin
                       (use-modules (ice-9 match)
                                    (guix build utils))

                       (mkdir #$output)
                       (chdir #$output)

                       ;; Note: copy the skeletons instead of symlinking
                       ;; them like 'file-union' does, because 'useradd'
                       ;; would just copy the symlinks as is.
                       (for-each (match-lambda
                                   ((target source)
                                    (copy-recursively source target)))
                                 '#$skeletons)
                       #t))))

(define (assert-valid-users/groups users groups)
  "Raise an error if USERS refer to groups not listed in GROUPS."
  (let ((groups (list->set (map user-group-name groups))))
    (define (validate-supplementary-group user group)
      (unless (set-contains? groups group)
        (raise (condition
                (&message
                 (message
                  (format #f (G_ "supplementary group '~a' \
of user '~a' is undeclared")
                          group
                          (user-account-name user))))))))

    (for-each (lambda (user)
                (unless (set-contains? groups (user-account-group user))
                  (raise (condition
                          (&message
                           (message
                            (format #f (G_ "primary group '~a' \
of user '~a' is undeclared")
                                    (user-account-group user)
                                    (user-account-name user)))))))

                (for-each (cut validate-supplementary-group user <>)
                          (user-account-supplementary-groups user)))
              users)))

;;;
;;; Service.
;;;

(define (user-group->gexp group)
  "Turn GROUP, a <user-group> object, into a list-valued gexp suitable for
'active-groups'."
  #~(list #$(user-group-name group)
          #$(user-group-password group)
          #$(user-group-id group)
          #$(user-group-system? group)))

(define (user-account->gexp account)
  "Turn ACCOUNT, a <user-account> object, into a list-valued gexp suitable for
'activate-users'."
  #~`(#$(user-account-name account)
      #$(user-account-uid account)
      #$(user-account-group account)
      #$(user-account-supplementary-groups account)
      #$(user-account-comment account)
      #$(user-account-home-directory account)
      #$(user-account-create-home-directory? account)
      ,#$(user-account-shell account)             ; this one is a gexp
      #$(user-account-password account)
      #$(user-account-system? account)))

(define (account-activation accounts+groups)
  "Return a gexp that activates ACCOUNTS+GROUPS, a list of <user-account> and
<user-group> objects.  Raise an error if a user account refers to a undefined
group."
  (define accounts
    (filter user-account? accounts+groups))

  (define user-specs
    (map user-account->gexp accounts))

  (define groups
    (filter user-group? accounts+groups))

  (define group-specs
    (map user-group->gexp groups))

  (assert-valid-users/groups accounts groups)

  ;; Add users and user groups.
  #~(begin
      (setenv "PATH"
              (string-append #$(@ (gnu packages admin) shadow) "/sbin"))
      (activate-users+groups (list #$@user-specs)
                             (list #$@group-specs))))

(define (account-shepherd-service accounts+groups)
  "Return a Shepherd service that creates the home directories for the user
accounts among ACCOUNTS+GROUPS."
  (define accounts
    (filter user-account? accounts+groups))

  ;; Create home directories only once 'file-systems' is up.  This makes sure
  ;; they are created in the right place if /home lives on a separate
  ;; partition.
  ;;
  ;; XXX: We arrange for this service to stop right after it's done its job so
  ;; that 'guix system reconfigure' knows that it can reload it fearlessly
  ;; (and thus create new home directories).  The cost of this hack is that
  ;; there's a small window during which first-time logins could happen before
  ;; the home directory has been created.
  (list (shepherd-service
         (requirement '(file-systems))
         (provision '(user-homes))
         (modules '((gnu build activation)))
         (start (with-imported-modules (source-module-closure
                                        '((gnu build activation)))
                  #~(lambda ()
                      (activate-user-home
                       (list #$@(map user-account->gexp accounts)))
                      #f)))                       ;stop
         (stop #~(const #f))
         (respawn? #f)
         (documentation "Create user home directories."))))

(define (shells-file shells)
  "Return a file-like object that builds a shell list for use as /etc/shells
based on SHELLS.  /etc/shells is used by xterm, polkit, and other programs."
  (computed-file "shells"
                 #~(begin
                     (use-modules (srfi srfi-1))

                     (define shells
                       (delete-duplicates (list #$@shells)))

                     (call-with-output-file #$output
                       (lambda (port)
                         (display "\
/bin/sh
/run/current-system/profile/bin/sh
/run/current-system/profile/bin/bash\n" port)
                         (for-each (lambda (shell)
                                     (display shell port)
                                     (newline port))
                                   shells))))))
(define (etc-files arguments)
  "Filter out among ARGUMENTS things corresponding to skeletons, and return
the /etc/skel directory for those."
  (let ((skels (filter pair? arguments))
        (users (filter user-account? arguments)))
    `(("skel" ,(skeleton-directory skels))
      ("shells" ,(shells-file (map user-account-shell users))))))

(define account-service-type
  (service-type (name 'account)

                ;; Concatenate <user-account>, <user-group>, and skeleton
                ;; lists.
                (compose concatenate)
                (extend append)

                (extensions
                 (list (service-extension activation-service-type
                                          account-activation)
                       (service-extension shepherd-root-service-type
                                          account-shepherd-service)
                       (service-extension etc-service-type
                                          etc-files)))))

(define (account-service accounts+groups skeletons)
  "Return a <service> that takes care of user accounts and user groups, with
ACCOUNTS+GROUPS as its initial list of accounts and groups."
  (service account-service-type
           (append skeletons accounts+groups)))

;;; shadow.scm ends here


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2017 Danny Milosavljevic <dannym@scratchpost.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu system uuid)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-9)
  #:use-module (rnrs bytevectors)
  #:use-module (ice-9 match)
  #:use-module (ice-9 vlist)
  #:use-module (ice-9 regex)
  #:use-module (ice-9 format)
  #:export (uuid
            uuid?
            uuid-type
            uuid-bytevector
            uuid=?

            bytevector->uuid

            uuid->string
            dce-uuid->string
            string->uuid
            string->dce-uuid
            string->iso9660-uuid
            string->ext2-uuid
            string->ext3-uuid
            string->ext4-uuid
            string->btrfs-uuid
            string->fat-uuid
            iso9660-uuid->string

            ;; XXX: For lack of a better place.
            sub-bytevector
            latin1->string))

;;;
;;; Tools that lack a better place.
;;;

(define (sub-bytevector bv start size)
  "Return a copy of the SIZE bytes of BV starting from offset START."
  (let ((result (make-bytevector size)))
    (bytevector-copy! bv start result 0 size)
    result))

(define (latin1->string bv terminator)
  "Return a string of BV, a latin1 bytevector, or #f.  TERMINATOR is a predicate
that takes a number and returns #t when a termination character is found."
    (let ((bytes (take-while (negate terminator) (bytevector->u8-list bv))))
      (if (null? bytes)
          #f
          (list->string (map integer->char bytes)))))

;;;
;;; DCE UUIDs.
;;;

(define-syntax %network-byte-order
  (identifier-syntax (endianness big)))

(define (dce-uuid->string uuid)
  "Convert UUID, a 16-byte bytevector, to its string representation, something
like \"6b700d61-5550-48a1-874c-a3d86998990e\"."
  ;; See <https://tools.ietf.org/html/rfc4122>.
  (let ((time-low  (bytevector-uint-ref uuid 0 %network-byte-order 4))
        (time-mid  (bytevector-uint-ref uuid 4 %network-byte-order 2))
        (time-hi   (bytevector-uint-ref uuid 6 %network-byte-order 2))
        (clock-seq (bytevector-uint-ref uuid 8 %network-byte-order 2))
        (node      (bytevector-uint-ref uuid 10 %network-byte-order 6)))
    (format #f "~8,'0x-~4,'0x-~4,'0x-~4,'0x-~12,'0x"
            time-low time-mid time-hi clock-seq node)))

(define %uuid-rx
  ;; The regexp of a UUID.
  (make-regexp "^([[:xdigit:]]{8})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{12})$"))

(define (string->dce-uuid str)
  "Parse STR as a DCE UUID (see <https://tools.ietf.org/html/rfc4122>) and
return its contents as a 16-byte bytevector.  Return #f if STR is not a valid
UUID representation."
  (and=> (regexp-exec %uuid-rx str)
         (lambda (match)
           (letrec-syntax ((hex->number
                            (syntax-rules ()
                              ((_ index)
                               (string->number (match:substring match index)
                                               16))))
                           (put!
                            (syntax-rules ()
                              ((_ bv index (number len) rest ...)
                               (begin
                                 (bytevector-uint-set! bv index number
                                                       (endianness big) len)
                                 (put! bv (+ index len) rest ...)))
                              ((_ bv index)
                               bv))))
             (let ((time-low  (hex->number 1))
                   (time-mid  (hex->number 2))
                   (time-hi   (hex->number 3))
                   (clock-seq (hex->number 4))
                   (node      (hex->number 5))
                   (uuid      (make-bytevector 16)))
               (put! uuid 0
                     (time-low 4) (time-mid 2) (time-hi 2)
                     (clock-seq 2) (node 6)))))))

;;;
;;; ISO-9660.
;;;

;; <http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf>.

(define %iso9660-uuid-rx
  ;;                   Y                m                d                H                M                S                ss
  (make-regexp "^([[:digit:]]{4})-([[:digit:]]{2})-([[:digit:]]{2})-([[:digit:]]{2})-([[:digit:]]{2})-([[:digit:]]{2})-([[:digit:]]{2})$"))
(define (string->iso9660-uuid str)
  "Parse STR as a ISO9660 UUID (which is really a timestamp - see /dev/disk/by-uuid).
Return its contents as a 16-byte bytevector.  Return #f if STR is not a valid
ISO9660 UUID representation."
  (and=> (regexp-exec %iso9660-uuid-rx str)
         (lambda (match)
           (letrec-syntax ((match-numerals
                            (syntax-rules ()
                              ((_ index (name rest ...) body)
                               (let ((name (match:substring match index)))
                                 (match-numerals (+ 1 index) (rest ...) body)))
                              ((_ index () body)
                               body))))
            (match-numerals 1 (year month day hour minute second hundredths)
              (string->utf8 (string-append year month day
                                           hour minute second hundredths)))))))
(define (iso9660-uuid->string uuid)
  "Given an UUID bytevector, return its timestamp string."
  (define (digits->string bytes)
    (latin1->string bytes (lambda (c) #f)))
  (let* ((year (sub-bytevector uuid 0 4))
         (month (sub-bytevector uuid 4 2))
         (day (sub-bytevector uuid 6 2))
         (hour (sub-bytevector uuid 8 2))
         (minute (sub-bytevector uuid 10 2))
         (second (sub-bytevector uuid 12 2))
         (hundredths (sub-bytevector uuid 14 2))
         (parts (list year month day hour minute second hundredths)))
    (string-append (string-join (map digits->string parts) "-"))))

;;;
;;; FAT32/FAT16.
;;;

(define-syntax %fat-endianness
  ;; Endianness of FAT32/FAT16 file systems.
  (identifier-syntax (endianness little)))

(define (fat-uuid->string uuid)
  "Convert FAT32/FAT16 UUID, a 4-byte bytevector, to its string representation."
  (let ((high  (bytevector-uint-ref uuid 0 %fat-endianness 2))
        (low (bytevector-uint-ref uuid 2 %fat-endianness 2)))
    (format #f "~:@(~x-~x~)" low high)))

(define %fat-uuid-rx
  (make-regexp "^([[:xdigit:]]{4})-([[:xdigit:]]{4})$"))

(define (string->fat-uuid str)
  "Parse STR, which is in FAT32/FAT16 format, and return a bytevector or #f."
  (match (regexp-exec %fat-uuid-rx str)
    (#f
     #f)
    (rx-match
     (uint-list->bytevector (list (string->number
                                   (match:substring rx-match 2) 16)
                                  (string->number
                                   (match:substring rx-match 1) 16))
                            %fat-endianness
                            2))))

;;;
;;; Generic interface.
;;;

(define string->ext2-uuid string->dce-uuid)
(define string->ext3-uuid string->dce-uuid)
(define string->ext4-uuid string->dce-uuid)
(define string->btrfs-uuid string->dce-uuid)

(define-syntax vhashq
  (syntax-rules (=>)
    ((_)
     vlist-null)
    ((_ (key others ... => value) rest ...)
     (vhash-consq key value
                  (vhashq (others ... => value) rest ...)))
    ((_ (=> value) rest ...)
     (vhashq rest ...))))

(define %uuid-parsers
  (vhashq
   ('dce 'ext2 'ext3 'ext4 'btrfs 'luks => string->dce-uuid)
   ('fat32 'fat16 'fat => string->fat-uuid)
   ('iso9660 => string->iso9660-uuid)))

(define %uuid-printers
  (vhashq
   ('dce 'ext2 'ext3 'ext4 'btrfs 'luks => dce-uuid->string)
   ('iso9660 => iso9660-uuid->string)
   ('fat32 'fat16 'fat => fat-uuid->string)))

(define* (string->uuid str #:optional (type 'dce))
  "Parse STR as a UUID of the given TYPE.  On success, return the
corresponding bytevector; otherwise return #f."
  (match (vhash-assq type %uuid-parsers)
    (#f #f)
    ((_ . (? procedure? parse)) (parse str))))

;; High-level UUID representation that carries its type with it.
;;
;; This is necessary to serialize bytevectors with the right printer in some
;; circumstances.  For instance, GRUB "search --fs-uuid" command compares the
;; string representation of UUIDs, not the raw bytes; thus, when emitting a
;; GRUB 'search' command, we need to produce the right string representation
;; (see <https://debbugs.gnu.org/cgi/bugreport.cgi?msg=52;att=0;bug=27735>).
(define-record-type <uuid>
  (make-uuid type bv)
  uuid?
  (type  uuid-type)                               ;'dce | 'iso9660 | ...
  (bv    uuid-bytevector))

(define* (bytevector->uuid bv #:optional (type 'dce))
  "Return a UUID object make of BV and TYPE."
  (make-uuid type bv))

(define-syntax uuid
  (lambda (s)
    "Return the UUID object corresponding to the given UUID representation or
#f if the string could not be parsed."
    (syntax-case s (quote)
      ((_ str (quote type))
       (and (string? (syntax->datum #'str))
            (identifier? #'type))
       ;; A literal string: do the conversion at expansion time.
       (let ((bv (string->uuid (syntax->datum #'str)
                               (syntax->datum #'type))))
         (unless bv
           (syntax-violation 'uuid "invalid UUID" s))
         #`(make-uuid 'type #,(datum->syntax s bv))))
      ((_ str)
       (string? (syntax->datum #'str))
       #'(uuid str 'dce))
      ((_ str)
       #'(let ((bv (string->uuid str 'dce)))
           (and bv (make-uuid 'dce bv))))
      ((_ str type)
       #'(let ((bv (string->uuid str type)))
           (and bv (make-uuid type bv)))))))

(define uuid->string
  ;; Convert the given bytevector or UUID object, to the corresponding UUID
  ;; string representation.
  (match-lambda*
    (((? bytevector? bv))
     (uuid->string bv 'dce))
    (((? bytevector? bv) type)
     (match (vhash-assq type %uuid-printers)
       (#f #f)
       ((_ . (? procedure? unparse)) (unparse bv))))
    (((? uuid? uuid))
     (uuid->string (uuid-bytevector uuid) (uuid-type uuid)))))

(define uuid=?
  ;; Return true if A is equal to B, comparing only the actual bits.
  (match-lambda*
    (((? bytevector? a) (? bytevector? b))
     (bytevector=? a b))
    (((? uuid? a) (? bytevector? b))
     (bytevector=? (uuid-bytevector a) b))
    (((? uuid? a) (? uuid? b))
     (bytevector=? (uuid-bytevector a) (uuid-bytevector b)))
    ((a b)
     (uuid=? b a))))