aboutsummaryrefslogtreecommitdiff
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2016-2019, 2022-2023 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2017, 2020 Mathieu Othacehe <m.othacehe@gmail.com>
;;; Copyright © 2023 Maxim Cournoyer <maxim.cournoyer@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 services herd)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-9)
  #:use-module (srfi srfi-9 gnu)
  #:use-module (srfi srfi-11)
  #:use-module (srfi srfi-34)
  #:use-module (srfi srfi-35)
  #:use-module (ice-9 match)
  #:export (%shepherd-socket-file
            shepherd-message-port

            shepherd-error?
            service-not-found-error?
            service-not-found-error-service
            action-not-found-error?
            action-not-found-error-service
            action-not-found-error-action
            action-exception-error?
            action-exception-error-service
            action-exception-error-action
            action-exception-error-key
            action-exception-error-arguments
            unknown-shepherd-error?
            unknown-shepherd-error-sexp

            live-service
            live-service?
            live-service-provision
            live-service-requirement
            live-service-running
            live-service-transient?
            live-service-canonical-name

            with-shepherd-action
            current-service
            current-services
            unload-services
            unload-service
            load-services
            load-services/safe
            start-service
            stop-service
            restart-service
            wait-for-service))

;;; Commentary:
;;;
;;; This module provides an interface to the GNU Shepherd, similar to the
;;; 'herd' command.  Essentially it implements a subset of the (shepherd comm)
;;; module, but focusing only on the parts relevant to 'guix system
;;; reconfigure'.
;;;
;;; Code:

(define %shepherd-socket-file
  (make-parameter "/var/run/shepherd/socket"))

(define* (open-connection #:optional (file (%shepherd-socket-file)))
  "Open a connection to the daemon, using the Unix-domain socket at FILE, and
return the socket."
  ;; The protocol is sexp-based and UTF-8-encoded.
  (with-fluids ((%default-port-encoding "UTF-8"))
    (let ((sock    (socket PF_UNIX SOCK_STREAM 0))
          (address (make-socket-address PF_UNIX file)))
      (catch 'system-error
        (lambda ()
          (connect sock address)
          (setvbuf sock 'block 1024)
          sock)
        (lambda args
          (close-port sock)
          (apply throw args))))))

(define-syntax-rule (with-shepherd connection body ...)
  "Evaluate BODY... with CONNECTION bound to an open socket to PID 1."
  (let ((connection (open-connection)))
    (dynamic-wind
      (const #t)
      (lambda ()
        body ...)
      (lambda ()
        (close-port connection)))))

(define-condition-type &shepherd-error &error
  shepherd-error?)

(define-condition-type &service-not-found-error &shepherd-error
  service-not-found-error?
  (service service-not-found-error-service))

(define-condition-type &action-not-found-error &shepherd-error
  action-not-found-error?
  (service action-not-found-error-service)
  (action  action-not-found-error-action))

(define-condition-type &action-exception-error &shepherd-error
  action-exception-error?
  (service action-exception-error-service)
  (action  action-exception-error-action)
  (key     action-exception-error-key)
  (args    action-exception-error-arguments))

(define-condition-type &unknown-shepherd-error &shepherd-error
  unknown-shepherd-error?
  (sexp   unknown-shepherd-error-sexp))

(define (raise-shepherd-error error)
  "Raise an error condition corresponding to ERROR, an sexp received by a
shepherd client in reply to COMMAND, a command object.  Return #t if ERROR
does not denote an error."
  (match error
    (('error ('version 0 x ...) 'service-not-found service)
     (raise (condition (&service-not-found-error
                        (service service)))))
    (('error ('version 0 x ...) 'action-not-found action service)
     (raise (condition (&action-not-found-error
                        (service service)
                        (action action)))))
    (('error ('version 0 x ...) 'action-exception action service
             key (args ...))
     (raise (condition (&action-exception-error
                        (service service)
                        (action action)
                        (key key) (args args)))))
    (('error . _)
     (raise (condition (&unknown-shepherd-error (sexp error)))))
    (#f                                           ;not an error
     #t)))

(define shepherd-message-port
  ;; Port where messages coming from shepherd are printed.
  (make-parameter (current-error-port)))

(define (display-message message)
  (format (shepherd-message-port) "shepherd: ~a~%" message))

(define* (invoke-action service action arguments cont)
  "Invoke ACTION on SERVICE with ARGUMENTS.  On success, call CONT with the
list of results (one result per instance with the name SERVICE).  Otherwise
return #f."
  (with-shepherd sock
    (write `(shepherd-command (version 0)
                              (action ,action)
                              (service ,service)
                              (arguments ,arguments)
                              (directory ,(getcwd)))
           sock)
    (force-output sock)

    (match (read sock)
      (('reply ('version 0 _ ...) ('result result) ('error #f)
               ('messages messages))
       (for-each display-message messages)
       (cont result))
      (('reply ('version 0 x ...) ('result y) ('error error)
               ('messages messages))
       (for-each display-message messages)
       (raise-shepherd-error error)
       #f)
      (x
       ;; invalid reply
       #f))))

(define-syntax-rule (with-shepherd-action service (action args ...)
                      result body ...)
  "Invoke ACTION on SERVICE with the given ARGS, and evaluate BODY with RESULT
bound to the action's result."
  (invoke-action service action (list args ...)
                 (lambda (result) body ...)))

(define-syntax alist-let*
  (syntax-rules ()
    "Bind the given KEYs in EXP to the corresponding items in ALIST.  ALIST
is assumed to be a list of two-element tuples rather than a traditional list
of pairs."
    ((_ alist (key ...) exp ...)
     (let ((key (and=> (assoc-ref alist 'key) car)) ...)
       exp ...))))

;; Information about live Shepherd services.
(define-record-type <live-service>
  (live-service provision requirement transient? running)
  live-service?
  (provision    live-service-provision)           ;list of symbols
  (requirement  live-service-requirement)         ;list of symbols
  (transient?   live-service-transient?)          ;Boolean
  (running      live-service-running))            ;#f | object

(define (live-service-canonical-name service)
  "Return the 'canonical name' of SERVICE."
  (first (live-service-provision service)))

(define (current-service name)
  "Return the currently defined Shepherd service NAME, as a <live-service>
object.  Return #f if the service could not be obtained.  As a special case,
@code{(current-service 'root)} returns all the current services."
  (define (process-services services)
    (resolve-transients
     (map (lambda (service)
            (alist-let* service (provides requires running transient?)
              ;; The Shepherd 0.9.0 would not provide 'transient?' in
              ;; its status sexp.  Thus, when it's missing, query it
              ;; via an "eval" request.
              (live-service provides requires
                            (if (sloppy-assq 'transient? service)
                                transient?
                                (and running *unspecified*))
                            running)))
          services)))

  (with-shepherd-action name ('status) results
    ;; We get a list of results, one for each service with the name NAME.
    ;; In practice there's only one such service though.
    (match results
      ((services _ ...)
       (match services
         ((('service ('version 0 _ ...) _ ...) ...)
          ;; Summary of all services (when NAME is 'root or 'shepherd).
          (process-services services))
         (('service ('version 0 _ ...) _ ...) ;single service
          (first (process-services (list services))))
         (x
          #f))))))                ;singleton

(define (current-services)
  "Return the list of currently defined Shepherd services, represented as
<live-service> objects.  Return #f if the list of services could not be
obtained."
  (current-service 'root))

(define (resolve-transients services)
  "Resolve the subset of SERVICES whose 'transient?' field is undefined.  This
is necessary to deal with Shepherd 0.9.0, which did not communicate whether a
service is transient."
  ;; All the fuss here is to make sure we make a single "eval root" request
  ;; for all of SERVICES.
  (let* ((unresolved (filter (compose unspecified? live-service-transient?)
                             services))
         (values     (or (and (pair? unresolved)
                              (eval-there
                               `(and (defined? 'transient?) ;shepherd >= 0.9.0
                                     (map (compose transient? lookup-running)
                                          ',(map (compose first
                                                          live-service-provision)
                                                 unresolved)))))
                         (make-list (length unresolved) #f)))
         (resolved   (map (lambda (unresolved transient?)
                            (cons unresolved
                                  (set-field unresolved
                                             (live-service-transient?)
                                             transient?)))
                          unresolved values)))
    (map (lambda (service)
           (or (assq-ref resolved service) service))
         services)))

(define (unload-service service)
  "Unload SERVICE, a symbol name; return #t on success."
  (with-shepherd-action 'root ('unload (symbol->string service)) result
    (first result)))

(define (%load-file file)
  "Load FILE in the Shepherd."
  (with-shepherd-action 'root ('load file) result
    (first result)))

(define (eval-there exp)
  "Eval EXP in the Shepherd."
  (with-shepherd-action 'root ('eval (object->string exp)) result
    (first result)))

(define (load-services files)
  "Load and register the services from FILES, where FILES contain code that
returns a shepherd <service> object."
  (eval-there `(register-services
                ,@(map (lambda (file)
                         `(primitive-load ,file))
                       files))))

(define load-services/safe
  ;; Deprecated.  It used to behave differently before service replacements
  ;; were a thing.
  load-services)

(define* (start-service name #:optional (arguments '()))
  (invoke-action name 'start arguments
                 (lambda (result)
                   result)))

(define (stop-service name)
  (with-shepherd-action name ('stop) result
    result))

(define (restart-service name)
  (with-shepherd-action name ('restart) result
    result))

(define* (wait-for-service name #:key (timeout 20))
  "Wait for the service providing NAME, a symbol, to be up and running, and
return its \"running value\".  Give up after TIMEOUT seconds and raise a
'&shepherd-error' exception.  Raise a '&service-not-found-error' exception
when NAME is not found."
  (define (relevant-service? service)
    (memq name (live-service-provision service)))

  (define start
    (car (gettimeofday)))

  ;; Note: As of Shepherd 0.9.1, we cannot just call the 'start' method and
  ;; wait for it: it would spawn an additional elogind process.  Thus, poll.
  (let loop ((attempts 0))
    (define services
      (current-services))

    (define now
      (car (gettimeofday)))

    (when (>= (- now start) timeout)
      (raise (condition (&shepherd-error))))      ;XXX: better exception?

    (match (find relevant-service? services)
      (#f
       (raise (condition (&service-not-found-error
                          (service name)))))
      (service
       (or (live-service-running service)
           (begin
             (sleep 1)
             (loop (+ attempts 1))))))))

;; Local Variables:
;; eval: (put 'alist-let* 'scheme-indent-function 2)
;; eval: (put 'with-shepherd 'scheme-indent-function 1)
;; eval: (put 'with-shepherd-action 'scheme-indent-function 3)
;; End:

;;; herd.scm ends here
lass='msg-avail'>...* gnu/build/image.scm (initialize-efi-partition): Rename bootloader-package argument to grub-efi. * gnu/system/image.scm (system-disk-image): Adapt accordingly to pass grub-efi package. Mathieu Othacehe 2020-05-29image: Add bootloader installation support....* gnu/build/image.scm (initialize-root-partition): Add bootloader-package and bootloader-installer arguments. Run the bootloader-installer if defined. * gnu/system/image.scm (system-disk-image): Adapt the partition initializer call accordingly. Mathieu Othacehe 2020-05-26image: Add partition file-system options support....* gnu/image.scm (<partition>)[file-system-options]: New field, (partition-file-system-options): new exported procedure. * gnu/system/image.scm (partition->gexp): Adapt accordingly. * gnu/build/image.scm (sexp->partition): Also adapt accordingly, (make-ext-image): and pass file-system options to mke2fs. Mathieu Othacehe 2020-05-26build: image: Add support for EXT2 and EXT3 file-systems....* gnu/build/image.scm (make-ext4-image): Rename to ... (make-ext-image): ... it, and pass the file-system type to mke2fs, (make-partition-image): Adapt to call "make-ext-image" if the partition file-system is prefixed by "ext". Mathieu Othacehe 2020-05-05image: Add a new API....Raw disk-images and ISO9660 images are created in a Qemu virtual machine. This is quite fragile, very slow, and almost unusable without KVM. For all these reasons, add support for host image generation. This implies the use new image generation mechanisms. - Raw disk images: images of partitions are created using tools such as mke2fs and mkdosfs depending on the partition file-system type. The partition images are then assembled into a final image using genimage. - ISO9660 images: the ISO root directory is populated within the store. GNU xorriso is then called on that directory, in the exact same way as this is done in (gnu build vm) module. Those mechanisms are built upon the new (gnu image) module. * gnu/image.scm: New file. * gnu/system/image.scm: New file. * gnu/build/image: New file. * gnu/local.mk: Add them. * gnu/system/vm.scm (system-disk-image): Rename to system-disk-image-in-vm. * gnu/ci.scm (qemu-jobs): Adapt to new API. * gnu/tests/install.scm (run-install): Ditto. * guix/scripts/system.scm (system-derivation-for-action): Ditto. Mathieu Othacehe