1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2016-2019, 2022 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2017, 2020 Mathieu Othacehe <m.othacehe@gmail.com>
5 ;;; This file is part of GNU Guix.
7 ;;; GNU Guix is free software; you can redistribute it and/or modify it
8 ;;; under the terms of the GNU General Public License as published by
9 ;;; the Free Software Foundation; either version 3 of the License, or (at
10 ;;; your option) any later version.
12 ;;; GNU Guix is distributed in the hope that it will be useful, but
13 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;;; GNU General Public License for more details.
17 ;;; You should have received a copy of the GNU General Public License
18 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
20 (define-module (gnu services herd)
21 #:use-module (srfi srfi-1)
22 #:use-module (srfi srfi-9)
23 #:use-module (srfi srfi-9 gnu)
24 #:use-module (srfi srfi-11)
25 #:use-module (srfi srfi-34)
26 #:use-module (srfi srfi-35)
27 #:use-module (ice-9 match)
28 #:export (%shepherd-socket-file
32 service-not-found-error?
33 service-not-found-error-service
34 action-not-found-error?
35 action-not-found-error-service
36 action-not-found-error-action
37 action-exception-error?
38 action-exception-error-service
39 action-exception-error-action
40 action-exception-error-key
41 action-exception-error-arguments
42 unknown-shepherd-error?
43 unknown-shepherd-error-sexp
47 live-service-provision
48 live-service-requirement
50 live-service-transient?
51 live-service-canonical-name
66 ;;; This module provides an interface to the GNU Shepherd, similar to the
67 ;;; 'herd' command. Essentially it implements a subset of the (shepherd comm)
68 ;;; module, but focusing only on the parts relevant to 'guix system
73 (define %shepherd-socket-file
74 (make-parameter "/var/run/shepherd/socket"))
76 (define* (open-connection #:optional (file (%shepherd-socket-file)))
77 "Open a connection to the daemon, using the Unix-domain socket at FILE, and
79 ;; The protocol is sexp-based and UTF-8-encoded.
80 (with-fluids ((%default-port-encoding "UTF-8"))
81 (let ((sock (socket PF_UNIX SOCK_STREAM 0))
82 (address (make-socket-address PF_UNIX file)))
85 (connect sock address)
86 (setvbuf sock 'block 1024)
90 (apply throw args))))))
92 (define-syntax-rule (with-shepherd connection body ...)
93 "Evaluate BODY... with CONNECTION bound to an open socket to PID 1."
94 (let ((connection (open-connection)))
100 (close-port connection)))))
102 (define-condition-type &shepherd-error &error
105 (define-condition-type &service-not-found-error &shepherd-error
106 service-not-found-error?
107 (service service-not-found-error-service))
109 (define-condition-type &action-not-found-error &shepherd-error
110 action-not-found-error?
111 (service action-not-found-error-service)
112 (action action-not-found-error-action))
114 (define-condition-type &action-exception-error &shepherd-error
115 action-exception-error?
116 (service action-exception-error-service)
117 (action action-exception-error-action)
118 (key action-exception-error-key)
119 (args action-exception-error-arguments))
121 (define-condition-type &unknown-shepherd-error &shepherd-error
122 unknown-shepherd-error?
123 (sexp unknown-shepherd-error-sexp))
125 (define (raise-shepherd-error error)
126 "Raise an error condition corresponding to ERROR, an sexp received by a
127 shepherd client in reply to COMMAND, a command object. Return #t if ERROR
128 does not denote an error."
130 (('error ('version 0 x ...) 'service-not-found service)
131 (raise (condition (&service-not-found-error
132 (service service)))))
133 (('error ('version 0 x ...) 'action-not-found action service)
134 (raise (condition (&action-not-found-error
137 (('error ('version 0 x ...) 'action-exception action service
139 (raise (condition (&action-exception-error
142 (key key) (args args)))))
144 (raise (condition (&unknown-shepherd-error (sexp error)))))
148 (define shepherd-message-port
149 ;; Port where messages coming from shepherd are printed.
150 (make-parameter (current-error-port)))
152 (define (display-message message)
153 (format (shepherd-message-port) "shepherd: ~a~%" message))
155 (define* (invoke-action service action arguments cont)
156 "Invoke ACTION on SERVICE with ARGUMENTS. On success, call CONT with the
157 list of results (one result per instance with the name SERVICE). Otherwise
160 (write `(shepherd-command (version 0)
163 (arguments ,arguments)
164 (directory ,(getcwd)))
169 (('reply ('version 0 _ ...) ('result result) ('error #f)
170 ('messages messages))
171 (for-each display-message messages)
173 (('reply ('version 0 x ...) ('result y) ('error error)
174 ('messages messages))
175 (for-each display-message messages)
176 (raise-shepherd-error error)
182 (define-syntax-rule (with-shepherd-action service (action args ...)
184 "Invoke ACTION on SERVICE with the given ARGS, and evaluate BODY with RESULT
185 bound to the action's result."
186 (invoke-action service action (list args ...)
187 (lambda (result) body ...)))
189 (define-syntax alist-let*
191 "Bind the given KEYs in EXP to the corresponding items in ALIST. ALIST
192 is assumed to be a list of two-element tuples rather than a traditional list
194 ((_ alist (key ...) exp ...)
195 (let ((key (and=> (assoc-ref alist 'key) car)) ...)
198 ;; Information about live Shepherd services.
199 (define-record-type <live-service>
200 (live-service provision requirement transient? running)
202 (provision live-service-provision) ;list of symbols
203 (requirement live-service-requirement) ;list of symbols
204 (transient? live-service-transient?) ;Boolean
205 (running live-service-running)) ;#f | object
207 (define (live-service-canonical-name service)
208 "Return the 'canonical name' of SERVICE."
209 (first (live-service-provision service)))
211 (define (current-services)
212 "Return the list of currently defined Shepherd services, represented as
213 <live-service> objects. Return #f if the list of services could not be
215 (with-shepherd-action 'root ('status) results
216 ;; We get a list of results, one for each service with the name 'root'.
217 ;; In practice there's only one such service though.
221 ((('service ('version 0 _ ...) _ ...) ...)
223 (map (lambda (service)
224 (alist-let* service (provides requires running transient?)
225 ;; The Shepherd 0.9.0 would not provide 'transient?' in its
226 ;; status sexp. Thus, when it's missing, query it via an
228 (live-service provides requires
229 (if (sloppy-assq 'transient? service)
231 (and running *unspecified*))
237 (define (resolve-transients services)
238 "Resolve the subset of SERVICES whose 'transient?' field is undefined. This
239 is necessary to deal with Shepherd 0.9.0, which did not communicate whether a
240 service is transient."
241 ;; All the fuss here is to make sure we make a single "eval root" request
242 ;; for all of SERVICES.
243 (let* ((unresolved (filter (compose unspecified? live-service-transient?)
245 (values (or (eval-there
246 `(and (defined? 'transient?) ;shepherd >= 0.9.0
247 (map (compose transient? lookup-running)
248 ',(map (compose first
249 live-service-provision)
251 (make-list (length unresolved) #f)))
252 (resolved (map (lambda (unresolved transient?)
254 (set-field unresolved
255 (live-service-transient?)
258 (map (lambda (service)
259 (or (assq-ref resolved service) service))
262 (define (unload-service service)
263 "Unload SERVICE, a symbol name; return #t on success."
264 (with-shepherd-action 'root ('unload (symbol->string service)) result
267 (define (%load-file file)
268 "Load FILE in the Shepherd."
269 (with-shepherd-action 'root ('load file) result
272 (define (eval-there exp)
273 "Eval EXP in the Shepherd."
274 (with-shepherd-action 'root ('eval (object->string exp)) result
277 (define (load-services files)
278 "Load and register the services from FILES, where FILES contain code that
279 returns a shepherd <service> object."
280 (eval-there `(register-services
281 ,@(map (lambda (file)
282 `(primitive-load ,file))
285 (define (load-services/safe files)
286 "This is like 'load-services', but make sure only the subset of FILES that
287 can be safely reloaded is actually reloaded.
289 This is done to accommodate the Shepherd < 0.15.0 where services lacked the
290 'replacement' slot, and where 'register-services' would throw an exception
291 when passed a service with an already-registered name."
292 (eval-there `(let* ((services (map primitive-load ',files))
293 (slots (map slot-definition-name
294 (class-slots <service>)))
295 (can-replace? (memq 'replacement slots)))
296 (define (registered? service)
297 (not (null? (lookup-services (canonical-name service)))))
299 (apply register-services
302 (remove registered? services))))))
304 (define* (start-service name #:optional (arguments '()))
305 (invoke-action name 'start arguments
309 (define (stop-service name)
310 (with-shepherd-action name ('stop) result
313 (define (restart-service name)
314 (with-shepherd-action name ('restart) result
317 (define* (wait-for-service name #:key (timeout 20))
318 "Wait for the service providing NAME, a symbol, to be up and running, and
319 return its \"running value\". Give up after TIMEOUT seconds and raise a
320 '&shepherd-error' exception. Raise a '&service-not-found-error' exception
321 when NAME is not found."
322 (define (relevant-service? service)
323 (memq name (live-service-provision service)))
326 (car (gettimeofday)))
328 ;; Note: As of Shepherd 0.9.1, we cannot just call the 'start' method and
329 ;; wait for it: it would spawn an additional elogind process. Thus, poll.
330 (let loop ((attempts 0))
335 (car (gettimeofday)))
337 (when (>= (- now start) timeout)
338 (raise (condition (&shepherd-error)))) ;XXX: better exception?
340 (match (find relevant-service? services)
342 (raise (condition (&service-not-found-error
345 (or (live-service-running service)
348 (loop (+ attempts 1))))))))
351 ;; eval: (put 'alist-let* 'scheme-indent-function 2)
352 ;; eval: (put 'with-shepherd 'scheme-indent-function 1)
353 ;; eval: (put 'with-shepherd-action 'scheme-indent-function 3)
356 ;;; herd.scm ends here