utils.scm 7.0 KB

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