;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2018, 2020 Mathieu Othacehe ;;; Copyright © 2019, 2020, 2022 Ludovic Courtès ;;; Copyright © 2024 Janneke Nieuwenhuizen ;;; ;;; This file is part of GNU Guix. ;;; ;;; GNU Guix is free software; you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 3 of the License, or (at ;;; your option) any later version. ;;; ;;; GNU Guix is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Guix. If not, see . (define-module (gnu installer utils) #:use-module (gnu services herd) #:use-module (guix utils) #:use-module ((guix build syscalls) #:select (openpty login-tty)) #:use-module (guix build utils) #:use-module (guix i18n) #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) #:use-module (srfi srfi-9 gnu) #:use-module (srfi srfi-19) #:use-module (srfi srfi-34) #:use-module (srfi srfi-35) #:use-module (ice-9 control) #:use-module (ice-9 match) #:use-module (ice-9 popen) #:use-module (ice-9 rdelim) #:use-module (ice-9 regex) #:use-module (ice-9 format) #:use-module (ice-9 textual-ports) #:export ( secret? make-secret secret-content read-lines read-all nearest-exact-integer read-percentage run-external-command-with-handler run-external-command-with-handler/tty run-external-command-with-line-hooks dry-run-command run-command %run-command-in-installer syslog-port %syslog-line-hook installer-log-port %installer-log-line-hook %default-installer-line-hooks installer-log-line call-with-time let/time with-server-socket current-server-socket current-clients send-to-clients with-silent-shepherd)) (define-record-type (make-secret content) secret? (content secret-content)) (set-record-type-printer! (lambda (secret port) (format port ""))) (define* (read-lines #:optional (port (current-input-port))) "Read lines from PORT and return them as a list." (let loop ((line (read-line port)) (lines '())) (if (eof-object? line) (reverse lines) (loop (read-line port) (cons line lines))))) (define (read-all file) "Return the content of the given FILE as a string." (call-with-input-file file get-string-all)) (define (nearest-exact-integer x) "Given a real number X, return the nearest exact integer, with ties going to the nearest exact even integer." (inexact->exact (round x))) (define (read-percentage percentage) "Read PERCENTAGE string and return the corresponding percentage as a number. If no percentage is found, return #f" (let ((result (string-match "^([0-9]+)%$" percentage))) (and result (string->number (match:substring result 1))))) (define* (run-external-command-with-handler handler command) "Run command specified by the list COMMAND in a child with output handler HANDLER. HANDLER is a procedure taking an input port, to which the command will write its standard output and error. Returns the integer status value of the child process as returned by waitpid." (match-let (((input . output) (pipe))) ;; Hack to work around Guile bug 52835 (define dup-output (duplicate-port output "w")) ;; Void pipe, but holds the pid for close-pipe. (define dummy-pipe (with-input-from-file "/dev/null" (lambda () (with-output-to-port output (lambda () (with-error-to-port dup-output (lambda () (apply open-pipe* (cons "" command))))))))) (close-port output) (close-port dup-output) (handler input) (close-port input) (close-pipe dummy-pipe))) (define (run-external-command-with-handler/tty handler command) "Run command specified by the list COMMAND in a child operating in a pseudoterminal with output handler HANDLER. HANDLER is a procedure taking an input port, to which the command will write its standard output and error. Returns the integer status value of the child process as returned by waitpid." (define-values (controller inferior) (openpty)) (match (primitive-fork) (0 (catch #t (lambda () (close-fdes controller) (login-tty inferior) (apply execlp (car command) command)) (lambda _ (primitive-exit 127)))) (pid (close-fdes inferior) (let* ((port (fdopen controller "r0")) (result (false-if-exception (handler port)))) (close-port port) (cdr (waitpid pid)))))) (define* (run-external-command-with-line-hooks line-hooks command #:key (tty? #false)) "Run command specified by the list COMMAND in a child, processing each output line with the procedures in LINE-HOOKS. If TTY is set to #true, the COMMAND will be run in a pseudoterminal. Returns the integer status value of the child process as returned by waitpid." (define (handler input) (and ;; Lines for progress bars etc. end in \r; treat is as a line ending so ;; those lines are printed right away. (and=> (read-delimited "\r\n" input 'concat) (lambda (line) (if (eof-object? line) #f (begin (for-each (lambda (f) (f line)) (append line-hooks %default-installer-line-hooks)) #t)))) (handler input))) (if tty? (run-external-command-with-handler/tty handler command) (run-external-co