herd.scm 10 KB

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