1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
4 ;;; This file is part of GNU Guix.
6 ;;; GNU Guix is free software; you can redistribute it and/or modify it
7 ;;; under the terms of the GNU General Public License as published by
8 ;;; the Free Software Foundation; either version 3 of the License, or (at
9 ;;; your option) any later version.
11 ;;; GNU Guix is distributed in the hope that it will be useful, but
12 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;;; GNU General Public License for more details.
16 ;;; You should have received a copy of the GNU General Public License
17 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19 (define-module (gnu build marionette)
20 #:use-module (srfi srfi-9)
21 #:use-module (srfi srfi-26)
22 #:use-module (rnrs io ports)
23 #:use-module (ice-9 match)
33 ;;; Instrumentation tools for QEMU virtual machines (VMs). A "marionette" is
34 ;;; essentially a VM (a QEMU instance) with its monitor connected to a
35 ;;; Unix-domain socket, and with a REPL inside the guest listening on a
36 ;;; virtual console, which is itself connected to the host via a Unix-domain
37 ;;; socket--these are the marionette's strings, connecting it to the almighty
42 (define-record-type <marionette>
43 (marionette command pid monitor repl)
45 (command marionette-command) ;list of strings
46 (pid marionette-pid) ;integer
47 (monitor marionette-monitor) ;port
48 (repl marionette-repl)) ;port
50 (define* (wait-for-monitor-prompt port #:key (quiet? #t))
51 "Read from PORT until we have seen all of QEMU's monitor prompt. When
52 QUIET? is false, the monitor's output is written to the current output port."
54 (string->list "(qemu) "))
56 (let loop ((prompt full-prompt)
61 ;; It's useful to set QUIET? so we don't display the echo of our own
64 (for-each (lambda (line)
65 (format #t "qemu monitor: ~a~%" line))
66 (string-tokenize (list->string (reverse prefix))
67 (char-set-complement (char-set #\newline))))))
69 (let ((read (read-char port)))
70 (cond ((eqv? read chr)
71 (loop rest (cons read matches) prefix))
73 (error "EOF while waiting for QEMU monitor prompt"
74 (list->string (reverse prefix))))
78 (cons read (append matches prefix))))))))))
80 (define* (make-marionette command
81 #:key (socket-directory "/tmp") (timeout 20))
82 "Return a QEMU marionette--i.e., a virtual machine with open connections to the
83 QEMU monitor and to the guest's backdoor REPL."
84 (define (file->sockaddr file)
85 (make-socket-address AF_UNIX
86 (string-append socket-directory "/" file)))
90 "-monitor" (string-append "unix:" socket-directory "/monitor")
91 "-chardev" (string-append "socket,id=repl,path=" socket-directory
93 "-device" "virtio-serial"
94 "-device" "virtconsole,chardev=repl"))
96 (let ((monitor (socket AF_UNIX SOCK_STREAM 0))
97 (repl (socket AF_UNIX SOCK_STREAM 0)))
98 (bind monitor (file->sockaddr "monitor"))
100 (bind repl (file->sockaddr "repl"))
103 (match (primitive-fork)
111 (apply execl program program
112 (append args extra-options)))))
114 (print-exception (current-error-port)
115 (stack-ref (make-stack #t) 1)
117 (primitive-exit 1))))
119 (format #t "QEMU runs as PID ~a~%" pid)
122 (display "time is up!\n") ;FIXME: break
126 (match (accept monitor)
128 (display "connected to QEMU's monitor\n")
130 (wait-for-monitor-prompt monitor-conn)
131 (display "read QEMU monitor prompt\n")
134 (display "connected to guest REPL\n")
136 (match (read repl-conn)
139 (sigaction SIGALRM SIG_DFL)
140 (display "marionette is ready\n")
141 (marionette (append command extra-options) pid
142 monitor-conn repl-conn)))))))))))
144 (define (marionette-eval exp marionette)
145 "Evaluate EXP in MARIONETTE's backdoor REPL. Return the result."
147 (($ <marionette> command pid monitor repl)
152 (define (marionette-control command marionette)
153 "Run COMMAND in the QEMU monitor of MARIONETTE. COMMAND is a string such as
154 \"sendkey ctrl-alt-f1\" or \"screendump foo.ppm\" (info \"(qemu-doc)
157 (($ <marionette> _ _ monitor)
158 (display command monitor)
160 (wait-for-monitor-prompt monitor))))
162 (define %qwerty-us-keystrokes
163 ;; Maps "special" characters to their keystrokes.
164 '((#\newline . "ret")
167 (#\+ . "shift-equal")
170 (#\? . "shift-slash")
171 (#\[ . "bracket_left")
172 (#\] . "bracket_right")
184 (define* (string->keystroke-commands str
187 %qwerty-us-keystrokes))
188 "Return a list of QEMU monitor commands to send the keystrokes corresponding
189 to STR. KEYSTROKES is an alist specifying a mapping from characters to
191 (string-fold-right (lambda (chr result)
192 (cons (string-append "sendkey "
193 (or (assoc-ref keystrokes chr)
199 (define* (marionette-type str marionette
200 #:key (keystrokes %qwerty-us-keystrokes))
201 "Type STR on MARIONETTE's keyboard, using the KEYSTROKES alist to map characters
202 to actual keystrokes."
203 (for-each (cut marionette-control <> marionette)
204 (string->keystroke-commands str keystrokes)))
206 ;;; marionette.scm ends here