herd.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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>
  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. (define-module (gnu services herd)
  20. #:use-module (srfi srfi-1)
  21. #:use-module (srfi srfi-9)
  22. #:use-module (srfi srfi-9 gnu)
  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. shepherd-error?
  30. service-not-found-error?
  31. service-not-found-error-service
  32. action-not-found-error?
  33. action-not-found-error-service
  34. action-not-found-error-action
  35. action-exception-error?
  36. action-exception-error-service
  37. action-exception-error-action
  38. action-exception-error-key
  39. action-exception-error-arguments
  40. unknown-shepherd-error?
  41. unknown-shepherd-error-sexp
  42. live-service
  43. live-service?
  44. live-service-provision
  45. live-service-requirement
  46. live-service-running
  47. live-service-transient?
  48. live-service-canonical-name
  49. with-shepherd-action
  50. current-services
  51. unload-services
  52. unload-service
  53. load-services
  54. load-services/safe
  55. start-service
  56. stop-service
  57. restart-service
  58. wait-for-service))
  59. ;;; Commentary:
  60. ;;;
  61. ;;; This module provides an interface to the GNU Shepherd, similar to the
  62. ;;; 'herd' command. Essentially it implements a subset of the (shepherd comm)
  63. ;;; module, but focusing only on the parts relevant to 'guix system
  64. ;;; reconfigure'.
  65. ;;;
  66. ;;; Code:
  67. (define %shepherd-socket-file
  68. (make-parameter "/var/run/shepherd/socket"))
  69. (define* (open-connection #:optional (file (%shepherd-socket-file)))
  70. "Open a connection to the daemon, using the Unix-domain socket at FILE, and
  71. return the socket."
  72. ;; The protocol is sexp-based and UTF-8-encoded.
  73. (with-fluids ((%default-port-encoding "UTF-8"))
  74. (let ((sock (socket PF_UNIX SOCK_STREAM 0))
  75. (address (make-socket-address PF_UNIX file)))
  76. (catch 'system-error
  77. (lambda ()
  78. (connect sock address)
  79. (setvbuf sock 'block 1024)
  80. sock)
  81. (lambda args
  82. (close-port sock)
  83. (apply throw args))))))
  84. (define-syntax-rule (with-shepherd connection body ...)
  85. "Evaluate BODY... with CONNECTION bound to an open socket to PID 1."
  86. (let ((connection (open-connection)))
  87. (dynamic-wind
  88. (const #t)
  89. (lambda ()
  90. body ...)
  91. (lambda ()
  92. (close-port connection)))))
  93. (define-condition-type &shepherd-error &error
  94. shepherd-error?)
  95. (define-condition-type &service-not-found-error &shepherd-error
  96. service-not-found-error?
  97. (service service-not-found-error-service))
  98. (define-condition-type &action-not-found-error &shepherd-error
  99. action-not-found-error?
  100. (service action-not-found-error-service)
  101. (action action-not-found-error-action))
  102. (define-condition-type &action-exception-error &shepherd-error
  103. action-exception-error?
  104. (service action-exception-error-service)
  105. (action action-exception-error-action)
  106. (key action-exception-error-key)
  107. (args action-exception-error-arguments))
  108. (define-condition-type &unknown-shepherd-error &shepherd-error
  109. unknown-shepherd-error?
  110. (sexp unknown-shepherd-error-sexp))
  111. (define (raise-shepherd-error error)
  112. "Raise an error condition corresponding to ERROR, an sexp received by a
  113. shepherd client in reply to COMMAND, a command object. Return #t if ERROR
  114. does not denote an error."
  115. (match error
  116. (('error ('version 0 x ...) 'service-not-found service)
  117. (raise (condition (&service-not-found-error
  118. (service service)))))
  119. (('error ('version 0 x ...) 'action-not-found action service)
  120. (raise (condition (&action-not-found-error
  121. (service service)
  122. (action action)))))
  123. (('error ('version 0 x ...) 'action-exception action service
  124. key (args ...))
  125. (raise (condition (&action-exception-error
  126. (service service)
  127. (action action)
  128. (key key) (args args)))))
  129. (('error . _)
  130. (raise (condition (&unknown-shepherd-error (sexp error)))))
  131. (#f ;not an error
  132. #t)))
  133. (define shepherd-message-port
  134. ;; Port where messages coming from shepherd are printed.
  135. (make-parameter (current-error-port)))
  136. (define (display-message message)
  137. (format (shepherd-message-port) "shepherd: ~a~%" message))
  138. (define* (invoke-action service action arguments cont)
  139. "Invoke ACTION on SERVICE with ARGUMENTS. On success, call CONT with the
  140. list of results (one result per instance with the name SERVICE). Otherwise
  141. return #f."
  142. (with-shepherd sock
  143. (write `(shepherd-command (version 0)
  144. (action ,action)
  145. (service ,service)
  146. (arguments ,arguments)
  147. (directory ,(getcwd)))
  148. sock)
  149. (force-output sock)
  150. (match (read sock)
  151. (('reply ('version 0 _ ...) ('result result) ('error #f)
  152. ('messages messages))
  153. (for-each display-message messages)
  154. (cont result))
  155. (('reply ('version 0 x ...) ('result y) ('error error)
  156. ('messages messages))
  157. (for-each display-message messages)
  158. (raise-shepherd-error error)
  159. #f)
  160. (x
  161. ;; invalid reply
  162. #f))))
  163. (define-syntax-rule (with-shepherd-action service (action args ...)
  164. result body ...)
  165. "Invoke ACTION on SERVICE with the given ARGS, and evaluate BODY with RESULT
  166. bound to the action's result."
  167. (invoke-action service action (list args ...)
  168. (lambda (result) body ...)))
  169. (define-syntax alist-let*
  170. (syntax-rules ()
  171. "Bind the given KEYs in EXP to the corresponding items in ALIST. ALIST
  172. is assumed to be a list of two-element tuples rather than a traditional list
  173. of pairs."
  174. ((_ alist (key ...) exp ...)
  175. (let ((key (and=> (assoc-ref alist 'key) car)) ...)
  176. exp ...))))
  177. ;; Information about live Shepherd services.
  178. (define-record-type <live-service>
  179. (live-service provision requirement transient? running)
  180. live-service?
  181. (provision live-service-provision) ;list of symbols
  182. (requirement live-service-requirement) ;list of symbols
  183. (transient? live-service-transient?) ;Boolean
  184. (running live-service-running)) ;#f | object
  185. (define (live-service-canonical-name service)
  186. "Return the 'canonical name' of SERVICE."
  187. (first (live-service-provision service)))
  188. (define (current-services)
  189. "Return the list of currently defined Shepherd services, represented as
  190. <live-service> objects. Return #f if the list of services could not be
  191. obtained."
  192. (with-shepherd-action 'root ('status) results
  193. ;; We get a list of results, one for each service with the name 'root'.
  194. ;; In practice there's only one such service though.
  195. (match results
  196. ((services _ ...)
  197. (match services
  198. ((('service ('version 0 _ ...) _ ...) ...)
  199. (resolve-transients
  200. (map (lambda (service)
  201. (alist-let* service (provides requires running transient?)
  202. ;; The Shepherd 0.9.0 would not provide 'transient?' in its
  203. ;; status sexp. Thus, when it's missing, query it via an
  204. ;; "eval" request.
  205. (live-service provides requires
  206. (if (sloppy-assq 'transient? service)
  207. transient?
  208. (and running *unspecified*))
  209. running)))
  210. services)))
  211. (x
  212. #f))))))
  213. (define (resolve-transients services)
  214. "Resolve the subset of SERVICES whose 'transient?' field is undefined. This
  215. is necessary to deal with Shepherd 0.9.0, which did not communicate whether a
  216. service is transient."
  217. ;; All the fuss here is to make sure we make a single "eval root" request
  218. ;; for all of SERVICES.
  219. (let* ((unresolved (filter (compose unspecified? live-service-transient?)
  220. services))
  221. (values (or (eval-there
  222. `(and (defined? 'transient?) ;shepherd >= 0.9.0
  223. (map (compose transient? lookup-running)
  224. ',(map (compose first
  225. live-service-provision)
  226. unresolved))))
  227. (make-list (length unresolved) #f)))
  228. (resolved (map (lambda (unresolved transient?)
  229. (cons unresolved
  230. (set-field unresolved
  231. (live-service-transient?)
  232. transient?)))
  233. unresolved values)))
  234. (map (lambda (service)
  235. (or (assq-ref resolved service) service))
  236. services)))
  237. (define (unload-service service)
  238. "Unload SERVICE, a symbol name; return #t on success."
  239. (with-shepherd-action 'root ('unload (symbol->string service)) result
  240. (first result)))
  241. (define (%load-file file)
  242. "Load FILE in the Shepherd."
  243. (with-shepherd-action 'root ('load file) result
  244. (first result)))
  245. (define (eval-there exp)
  246. "Eval EXP in the Shepherd."
  247. (with-shepherd-action 'root ('eval (object->string exp)) result
  248. (first result)))
  249. (define (load-services files)
  250. "Load and register the services from FILES, where FILES contain code that
  251. returns a shepherd <service> object."
  252. (eval-there `(register-services
  253. ,@(map (lambda (file)
  254. `(primitive-load ,file))
  255. files))))
  256. (define (load-services/safe files)
  257. "This is like 'load-services', but make sure only the subset of FILES that
  258. can be safely reloaded is actually reloaded.
  259. This is done to accommodate the Shepherd < 0.15.0 where services lacked the
  260. 'replacement' slot, and where 'register-services' would throw an exception
  261. when passed a service with an already-registered name."
  262. (eval-there `(let* ((services (map primitive-load ',files))
  263. (slots (map slot-definition-name
  264. (class-slots <service>)))
  265. (can-replace? (memq 'replacement slots)))
  266. (define (registered? service)
  267. (not (null? (lookup-services (canonical-name service)))))
  268. (apply register-services
  269. (if can-replace?
  270. services
  271. (remove registered? services))))))
  272. (define* (start-service name #:optional (arguments '()))
  273. (invoke-action name 'start arguments
  274. (lambda (result)
  275. result)))
  276. (define (stop-service name)
  277. (with-shepherd-action name ('stop) result
  278. result))
  279. (define (restart-service name)
  280. (with-shepherd-action name ('restart) result
  281. result))
  282. (define* (wait-for-service name #:key (timeout 20))
  283. "Wait for the service providing NAME, a symbol, to be up and running, and
  284. return its \"running value\". Give up after TIMEOUT seconds and raise a
  285. '&shepherd-error' exception. Raise a '&service-not-found-error' exception
  286. when NAME is not found."
  287. (define (relevant-service? service)
  288. (memq name (live-service-provision service)))
  289. (define start
  290. (car (gettimeofday)))
  291. ;; Note: As of Shepherd 0.9.1, we cannot just call the 'start' method and
  292. ;; wait for it: it would spawn an additional elogind process. Thus, poll.
  293. (let loop ((attempts 0))
  294. (define services
  295. (current-services))
  296. (define now
  297. (car (gettimeofday)))
  298. (when (>= (- now start) timeout)
  299. (raise (condition (&shepherd-error)))) ;XXX: better exception?
  300. (match (find relevant-service? services)
  301. (#f
  302. (raise (condition (&service-not-found-error
  303. (service name)))))
  304. (service
  305. (or (live-service-running service)
  306. (begin
  307. (sleep 1)
  308. (loop (+ attempts 1))))))))
  309. ;; Local Variables:
  310. ;; eval: (put 'alist-let* 'scheme-indent-function 2)
  311. ;; eval: (put 'with-shepherd 'scheme-indent-function 1)
  312. ;; eval: (put 'with-shepherd-action 'scheme-indent-function 3)
  313. ;; End:
  314. ;;; herd.scm ends here