herd.scm 12 KB

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