gnu: Add rust-linked-hash-map-0.4.
[jackhill/guix/guix.git] / gnu / services / herd.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
4 ;;;
5 ;;; This file is part of GNU Guix.
6 ;;;
7 ;;; GNU Guix is free software; you can redistribute it and/or modify it
8 ;;; under the terms of the GNU General Public License as published by
9 ;;; the Free Software Foundation; either version 3 of the License, or (at
10 ;;; your option) any later version.
11 ;;;
12 ;;; GNU Guix is distributed in the hope that it will be useful, but
13 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;;; GNU General Public License for more details.
16 ;;;
17 ;;; You should have received a copy of the GNU General Public License
18 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19
20 (define-module (gnu services herd)
21 #:use-module (srfi srfi-1)
22 #:use-module (srfi srfi-9)
23 #:use-module (srfi srfi-11)
24 #:use-module (srfi srfi-34)
25 #:use-module (srfi srfi-35)
26 #:use-module (ice-9 match)
27 #:export (%shepherd-socket-file
28 shepherd-message-port
29
30 shepherd-error?
31 service-not-found-error?
32 service-not-found-error-service
33 action-not-found-error?
34 action-not-found-error-service
35 action-not-found-error-action
36 action-exception-error?
37 action-exception-error-service
38 action-exception-error-action
39 action-exception-error-key
40 action-exception-error-arguments
41 unknown-shepherd-error?
42 unknown-shepherd-error-sexp
43
44 live-service
45 live-service?
46 live-service-provision
47 live-service-requirement
48 live-service-running
49 live-service-canonical-name
50
51 with-shepherd-action
52 current-services
53 unload-services
54 unload-service
55 load-services
56 load-services/safe
57 start-service
58 stop-service))
59
60 ;;; Commentary:
61 ;;;
62 ;;; This module provides an interface to the GNU Shepherd, similar to the
63 ;;; 'herd' command. Essentially it implements a subset of the (shepherd comm)
64 ;;; module, but focusing only on the parts relevant to 'guix system
65 ;;; reconfigure'.
66 ;;;
67 ;;; Code:
68
69 (define %shepherd-socket-file
70 (make-parameter "/var/run/shepherd/socket"))
71
72 (define* (open-connection #:optional (file (%shepherd-socket-file)))
73 "Open a connection to the daemon, using the Unix-domain socket at FILE, and
74 return the socket."
75 ;; The protocol is sexp-based and UTF-8-encoded.
76 (with-fluids ((%default-port-encoding "UTF-8"))
77 (let ((sock (socket PF_UNIX SOCK_STREAM 0))
78 (address (make-socket-address PF_UNIX file)))
79 (catch 'system-error
80 (lambda ()
81 (connect sock address)
82 (setvbuf sock 'block 1024)
83 sock)
84 (lambda args
85 (close-port sock)
86 (apply throw args))))))
87
88 (define-syntax-rule (with-shepherd connection body ...)
89 "Evaluate BODY... with CONNECTION bound to an open socket to PID 1."
90 (let ((connection (open-connection)))
91 (dynamic-wind
92 (const #t)
93 (lambda ()
94 body ...)
95 (lambda ()
96 (close-port connection)))))
97
98 (define-condition-type &shepherd-error &error
99 shepherd-error?)
100
101 (define-condition-type &service-not-found-error &shepherd-error
102 service-not-found-error?
103 (service service-not-found-error-service))
104
105 (define-condition-type &action-not-found-error &shepherd-error
106 action-not-found-error?
107 (service action-not-found-error-service)
108 (action action-not-found-error-action))
109
110 (define-condition-type &action-exception-error &shepherd-error
111 action-exception-error?
112 (service action-exception-error-service)
113 (action action-exception-error-action)
114 (key action-exception-error-key)
115 (args action-exception-error-arguments))
116
117 (define-condition-type &unknown-shepherd-error &shepherd-error
118 unknown-shepherd-error?
119 (sexp unknown-shepherd-error-sexp))
120
121 (define (raise-shepherd-error error)
122 "Raise an error condition corresponding to ERROR, an sexp received by a
123 shepherd client in reply to COMMAND, a command object. Return #t if ERROR
124 does not denote an error."
125 (match error
126 (('error ('version 0 x ...) 'service-not-found service)
127 (raise (condition (&service-not-found-error
128 (service service)))))
129 (('error ('version 0 x ...) 'action-not-found action service)
130 (raise (condition (&action-not-found-error
131 (service service)
132 (action action)))))
133 (('error ('version 0 x ...) 'action-exception action service
134 key (args ...))
135 (raise (condition (&action-exception-error
136 (service service)
137 (action action)
138 (key key) (args args)))))
139 (('error . _)
140 (raise (condition (&unknown-shepherd-error (sexp error)))))
141 (#f ;not an error
142 #t)))
143
144 (define shepherd-message-port
145 ;; Port where messages coming from shepherd are printed.
146 (make-parameter (current-error-port)))
147
148 (define (display-message message)
149 (format (shepherd-message-port) "shepherd: ~a~%" message))
150
151 (define* (invoke-action service action arguments cont)
152 "Invoke ACTION on SERVICE with ARGUMENTS. On success, call CONT with the
153 list of results (one result per instance with the name SERVICE). Otherwise
154 return #f."
155 (with-shepherd sock
156 (write `(shepherd-command (version 0)
157 (action ,action)
158 (service ,service)
159 (arguments ,arguments)
160 (directory ,(getcwd)))
161 sock)
162 (force-output sock)
163
164 (match (read sock)
165 (('reply ('version 0 _ ...) ('result result) ('error #f)
166 ('messages messages))
167 (for-each display-message messages)
168 (cont result))
169 (('reply ('version 0 x ...) ('result y) ('error error)
170 ('messages messages))
171 (for-each display-message messages)
172 (raise-shepherd-error error)
173 #f)
174 (x
175 ;; invalid reply
176 #f))))
177
178 (define-syntax-rule (with-shepherd-action service (action args ...)
179 result body ...)
180 "Invoke ACTION on SERVICE with the given ARGS, and evaluate BODY with RESULT
181 bound to the action's result."
182 (invoke-action service action (list args ...)
183 (lambda (result) body ...)))
184
185 (define-syntax alist-let*
186 (syntax-rules ()
187 "Bind the given KEYs in EXP to the corresponding items in ALIST. ALIST
188 is assumed to be a list of two-element tuples rather than a traditional list
189 of pairs."
190 ((_ alist (key ...) exp ...)
191 (let ((key (and=> (assoc-ref alist 'key) car)) ...)
192 exp ...))))
193
194 ;; Information about live Shepherd services.
195 (define-record-type <live-service>
196 (live-service provision requirement running)
197 live-service?
198 (provision live-service-provision) ;list of symbols
199 (requirement live-service-requirement) ;list of symbols
200 (running live-service-running)) ;#f | object
201
202 (define (live-service-canonical-name service)
203 "Return the 'canonical name' of SERVICE."
204 (first (live-service-provision service)))
205
206 (define (current-services)
207 "Return the list of currently defined Shepherd services, represented as
208 <live-service> objects. Return #f if the list of services could not be
209 obtained."
210 (with-shepherd-action 'root ('status) results
211 ;; We get a list of results, one for each service with the name 'root'.
212 ;; In practice there's only one such service though.
213 (match results
214 ((services _ ...)
215 (match services
216 ((('service ('version 0 _ ...) _ ...) ...)
217 (map (lambda (service)
218 (alist-let* service (provides requires running)
219 (live-service provides requires running)))
220 services))
221 (x
222 #f))))))
223
224 (define (unload-service service)
225 "Unload SERVICE, a symbol name; return #t on success."
226 (with-shepherd-action 'root ('unload (symbol->string service)) result
227 (first result)))
228
229 (define (%load-file file)
230 "Load FILE in the Shepherd."
231 (with-shepherd-action 'root ('load file) result
232 (first result)))
233
234 (define (eval-there exp)
235 "Eval EXP in the Shepherd."
236 (with-shepherd-action 'root ('eval (object->string exp)) result
237 (first result)))
238
239 (define (load-services files)
240 "Load and register the services from FILES, where FILES contain code that
241 returns a shepherd <service> object."
242 (eval-there `(register-services
243 ,@(map (lambda (file)
244 `(primitive-load ,file))
245 files))))
246
247 (define (load-services/safe files)
248 "This is like 'load-services', but make sure only the subset of FILES that
249 can be safely reloaded is actually reloaded.
250
251 This is done to accommodate the Shepherd < 0.15.0 where services lacked the
252 'replacement' slot, and where 'register-services' would throw an exception
253 when passed a service with an already-registered name."
254 (eval-there `(let* ((services (map primitive-load ',files))
255 (slots (map slot-definition-name
256 (class-slots <service>)))
257 (can-replace? (memq 'replacement slots)))
258 (define (registered? service)
259 (not (null? (lookup-services (canonical-name service)))))
260
261 (apply register-services
262 (if can-replace?
263 services
264 (remove registered? services))))))
265
266 (define* (start-service name #:optional (arguments '()))
267 (invoke-action name 'start arguments
268 (lambda (result)
269 result)))
270
271 (define (stop-service name)
272 (with-shepherd-action name ('stop) result
273 result))
274
275 ;; Local Variables:
276 ;; eval: (put 'alist-let* 'scheme-indent-function 2)
277 ;; eval: (put 'with-shepherd 'scheme-indent-function 1)
278 ;; eval: (put 'with-shepherd-action 'scheme-indent-function 3)
279 ;; End:
280
281 ;;; herd.scm ends here