herd.scm 13 KB

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