utils.scm 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018, 2020 Mathieu Othacehe <m.othacehe@gmail.com>
  3. ;;; Copyright © 2019, 2020 Ludovic Courtès <ludo@gnu.org>
  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 installer utils)
  20. #:use-module (gnu services herd)
  21. #:use-module (guix utils)
  22. #:use-module (guix build utils)
  23. #:use-module (guix i18n)
  24. #:use-module (srfi srfi-1)
  25. #:use-module (srfi srfi-19)
  26. #:use-module (srfi srfi-34)
  27. #:use-module (ice-9 match)
  28. #:use-module (ice-9 rdelim)
  29. #:use-module (ice-9 regex)
  30. #:use-module (ice-9 format)
  31. #:use-module (ice-9 textual-ports)
  32. #:export (read-lines
  33. read-all
  34. nearest-exact-integer
  35. read-percentage
  36. run-command
  37. syslog-port
  38. syslog
  39. call-with-time
  40. let/time
  41. with-server-socket
  42. current-server-socket
  43. current-clients
  44. send-to-clients
  45. with-silent-shepherd))
  46. (define* (read-lines #:optional (port (current-input-port)))
  47. "Read lines from PORT and return them as a list."
  48. (let loop ((line (read-line port))
  49. (lines '()))
  50. (if (eof-object? line)
  51. (reverse lines)
  52. (loop (read-line port)
  53. (cons line lines)))))
  54. (define (read-all file)
  55. "Return the content of the given FILE as a string."
  56. (call-with-input-file file
  57. get-string-all))
  58. (define (nearest-exact-integer x)
  59. "Given a real number X, return the nearest exact integer, with ties going to
  60. the nearest exact even integer."
  61. (inexact->exact (round x)))
  62. (define (read-percentage percentage)
  63. "Read PERCENTAGE string and return the corresponding percentage as a
  64. number. If no percentage is found, return #f"
  65. (let ((result (string-match "^([0-9]+)%$" percentage)))
  66. (and result
  67. (string->number (match:substring result 1)))))
  68. (define* (run-command command #:key locale)
  69. "Run COMMAND, a list of strings, in the given LOCALE. Return true if
  70. COMMAND exited successfully, #f otherwise."
  71. (define env (environ))
  72. (define (pause)
  73. (format #t (G_ "Press Enter to continue.~%"))
  74. (send-to-clients '(pause))
  75. (environ env) ;restore environment variables
  76. (match (select (cons (current-input-port) (current-clients))
  77. '() '())
  78. (((port _ ...) _ _)
  79. (read-line port))))
  80. (setenv "PATH" "/run/current-system/profile/bin")
  81. (when locale
  82. (let ((supported? (false-if-exception
  83. (setlocale LC_ALL locale))))
  84. ;; If LOCALE is not supported, then set LANGUAGE, which might at
  85. ;; least give us translated messages.
  86. (if supported?
  87. (setenv "LC_ALL" locale)
  88. (setenv "LANGUAGE"
  89. (string-take locale
  90. (or (string-index locale #\_)
  91. (string-length locale)))))))
  92. (guard (c ((invoke-error? c)
  93. (newline)
  94. (format (current-error-port)
  95. (G_ "Command failed with exit code ~a.~%")
  96. (invoke-error-exit-status c))
  97. (syslog "command ~s failed with exit code ~a"
  98. command (invoke-error-exit-status c))
  99. (pause)
  100. #f))
  101. (syslog "running command ~s~%" command)
  102. (apply invoke command)
  103. (syslog "command ~s succeeded~%" command)
  104. (newline)
  105. (pause)
  106. #t))
  107. ;;;
  108. ;;; Logging.
  109. ;;;
  110. (define (call-with-time thunk kont)
  111. "Call THUNK and pass KONT the elapsed time followed by THUNK's return
  112. values."
  113. (let* ((start (current-time time-monotonic))
  114. (result (call-with-values thunk list))
  115. (end (current-time time-monotonic)))
  116. (apply kont (time-difference end start) result)))
  117. (define-syntax-rule (let/time ((time result exp)) body ...)
  118. (call-with-time (lambda () exp) (lambda (time result) body ...)))
  119. (define (open-syslog-port)
  120. "Return an open port (a socket) to /dev/log or #f if that wasn't possible."
  121. (let ((sock (socket AF_UNIX SOCK_DGRAM 0)))
  122. (catch 'system-error
  123. (lambda ()
  124. (connect sock AF_UNIX "/dev/log")
  125. (setvbuf sock 'line)
  126. sock)
  127. (lambda args
  128. (close-port sock)
  129. #f))))
  130. (define syslog-port
  131. (let ((port #f))
  132. (lambda ()
  133. "Return an output port to syslog."
  134. (unless port
  135. (set! port (open-syslog-port)))
  136. (or port (%make-void-port "w")))))
  137. (define-syntax syslog
  138. (lambda (s)
  139. "Like 'format', but write to syslog."
  140. (syntax-case s ()
  141. ((_ fmt args ...)
  142. (string? (syntax->datum #'fmt))
  143. (with-syntax ((fmt (string-append "installer[~d]: "
  144. (syntax->datum #'fmt))))
  145. #'(format (syslog-port) fmt (getpid) args ...))))))
  146. ;;;
  147. ;;; Client protocol.
  148. ;;;
  149. (define %client-socket-file
  150. ;; Unix-domain socket where the installer accepts connections.
  151. "/var/guix/installer-socket")
  152. (define current-server-socket
  153. ;; Socket on which the installer is currently accepting connections, or #f.
  154. (make-parameter #f))
  155. (define current-clients
  156. ;; List of currently connected clients.
  157. (make-parameter '()))
  158. (define* (open-server-socket
  159. #:optional (socket-file %client-socket-file))
  160. "Open SOCKET-FILE as a Unix-domain socket to accept incoming connections and
  161. return it."
  162. (mkdir-p (dirname socket-file))
  163. (when (file-exists? socket-file)
  164. (delete-file socket-file))
  165. (let ((sock (socket AF_UNIX SOCK_STREAM 0)))
  166. (bind sock AF_UNIX socket-file)
  167. (listen sock 0)
  168. sock))
  169. (define (call-with-server-socket thunk)
  170. (if (current-server-socket)
  171. (thunk)
  172. (let ((socket (open-server-socket)))
  173. (dynamic-wind
  174. (const #t)
  175. (lambda ()
  176. (parameterize ((current-server-socket socket))
  177. (thunk)))
  178. (lambda ()
  179. (close-port socket))))))
  180. (define-syntax-rule (with-server-socket exp ...)
  181. "Evaluate EXP with 'current-server-socket' parameterized to a currently
  182. accepting socket."
  183. (call-with-server-socket (lambda () exp ...)))
  184. (define* (send-to-clients exp)
  185. "Send EXP to all the current clients."
  186. (define remainder
  187. (fold (lambda (client remainder)
  188. (catch 'system-error
  189. (lambda ()
  190. (write exp client)
  191. (newline client)
  192. (force-output client)
  193. (cons client remainder))
  194. (lambda args
  195. ;; We might get EPIPE if the client disconnects; when that
  196. ;; happens, remove CLIENT from the set of available clients.
  197. (let ((errno (system-error-errno args)))
  198. (if (memv errno (list EPIPE ECONNRESET ECONNABORTED))
  199. (begin
  200. (syslog "removing client ~s due to ~s while replying~%"
  201. (fileno client) (strerror errno))
  202. (false-if-exception (close-port client))
  203. remainder)
  204. (cons client remainder))))))
  205. '()
  206. (current-clients)))
  207. (current-clients (reverse remainder))
  208. exp)
  209. (define-syntax-rule (with-silent-shepherd exp ...)
  210. "Evaluate EXP while discarding shepherd messages."
  211. (parameterize ((shepherd-message-port
  212. (%make-void-port "w")))
  213. exp ...))