@@ -31,6 +31,7 @@ import (
3131 "os"
3232 "os/exec"
3333 "os/user"
34+ "path"
3435 "path/filepath"
3536 "strconv"
3637 "strings"
@@ -40,11 +41,11 @@ import (
4041)
4142
4243const (
43- // ConsoleStdio configures console to use Stdio
44+ // ConsoleStdio configures console to use Stdio (deprecated)
4445 ConsoleStdio = iota
45- // ConsoleFile configures console to a tty and output to a file
46+ // ConsoleFile configures console to a tty and output to a file (deprecated)
4647 ConsoleFile
47- // ConsoleLog configures console to a tty and sends its contents to the logs
48+ // ConsoleLog configures console to a tty and sends its contents to the logs (deprecated)
4849 ConsoleLog
4950
5051 legacyVPNKitSock = "Library/Containers/com.docker.docker/Data/s50"
@@ -120,9 +121,13 @@ type HyperKit struct {
120121 // Memory is the amount of megabytes of memory for the VM.
121122 Memory int `json:"memory"`
122123
123- // Console defines where the console of the VM should be connected to.
124+ // Console defines where the console of the VM should be connected to. (deprecated)
124125 Console int `json:"console"`
125126
127+ // Serials defines what happens to the I/O on the serial ports. If this is not nil
128+ // it overrides the Console setting.
129+ Serials []Serial `json:"serials"`
130+
126131 // Below here are internal members, but they are exported so
127132 // that they are written to the state json file, if configured.
128133
@@ -136,6 +141,28 @@ type HyperKit struct {
136141 process * os.Process
137142}
138143
144+ // Serial port.
145+ type Serial struct {
146+ // InteractiveConsole allows a user to connect to a live VM serial console.
147+ InteractiveConsole InteractiveConsole
148+ // LogToRingBuffer will write console output to a fixed size ring buffer file.
149+ LogToRingBuffer bool
150+ // LogToASL will write console output to the Apple System Log.
151+ LogToASL bool
152+ }
153+
154+ // InteractiveConsole is an optional interactive VM console.
155+ type InteractiveConsole int
156+
157+ const (
158+ // NoInteractiveConsole disables the interactive console.
159+ NoInteractiveConsole = InteractiveConsole (iota )
160+ // StdioInteractiveConsole creates a console on stdio.
161+ StdioInteractiveConsole
162+ // TTYInteractiveConsole creates a console on a TTY.
163+ TTYInteractiveConsole
164+ )
165+
139166// New creates a template config structure.
140167// - If hyperkit can't be found an error is returned.
141168// - If vpnkitsock is empty no networking is configured. If it is set
@@ -196,11 +223,7 @@ func (h *HyperKit) Start(cmdline string) (chan error, error) {
196223 return errCh , nil
197224}
198225
199- // check validates `h`. It also creates the disks if needed.
200- func (h * HyperKit ) check () error {
201- log .Debugf ("hyperkit: check %#v" , h )
202- var err error
203- // Sanity checks on configuration
226+ func (h * HyperKit ) checkLegacyConsole () error {
204227 switch h .Console {
205228 case ConsoleFile , ConsoleLog :
206229 if h .StateDir == "" {
@@ -211,6 +234,48 @@ func (h *HyperKit) check() error {
211234 return fmt .Errorf ("If ConsoleStdio is set but stdio is not a terminal, StateDir must be specified" )
212235 }
213236 }
237+ return nil
238+ }
239+
240+ func (h * HyperKit ) checkSerials () error {
241+ stdioConsole := - 1
242+ for i , serial := range h .Serials {
243+ if serial .LogToRingBuffer && h .StateDir == "" {
244+ return fmt .Errorf ("If VM is to log to a ring buffer, StateDir must be specified" )
245+ }
246+ if serial .InteractiveConsole == StdioInteractiveConsole {
247+ if isTerminal (os .Stdout ) {
248+ return fmt .Errorf ("If StdioInteractiveConsole is set, stdio must be a TTY" )
249+ }
250+ if stdioConsole != - 1 {
251+ return fmt .Errorf ("Only one serial port can be nominated as the stdio interactive console" )
252+ }
253+ stdioConsole = i
254+ }
255+ if serial .InteractiveConsole == TTYInteractiveConsole && h .StateDir == "" {
256+ return fmt .Errorf ("If TTYInteractiveConsole is set, StateDir must be specified " )
257+ }
258+ if serial .LogToRingBuffer && h .StateDir == "" {
259+ return fmt .Errorf ("If LogToRingBuffer is set, StateDir must be specified" )
260+ }
261+ }
262+ return nil
263+ }
264+
265+ // check validates `h`. It also creates the disks if needed.
266+ func (h * HyperKit ) check () error {
267+ log .Debugf ("hyperkit: check %#v" , h )
268+ var err error
269+ // Sanity checks on configuration
270+ if h .Serials == nil {
271+ if err := h .checkLegacyConsole (); err != nil {
272+ return err
273+ }
274+ } else {
275+ if err := h .checkSerials (); err != nil {
276+ return err
277+ }
278+ }
214279 for _ , image := range h .ISOImages {
215280 if _ , err = os .Stat (image ); os .IsNotExist (err ) {
216281 return fmt .Errorf ("ISO %s does not exist" , image )
@@ -365,6 +430,44 @@ func intArrayToString(i []int, sep string) string {
365430 return strings .Join (s , sep )
366431}
367432
433+ func (h * HyperKit ) legacyConsoleArgs () []string {
434+ cfg := "com1"
435+ if h .Console == ConsoleStdio && isTerminal (os .Stdout ) {
436+ cfg += fmt .Sprintf (",stdio" )
437+ } else {
438+ cfg += fmt .Sprintf (",autopty=%s/tty" , h .StateDir )
439+ }
440+ if h .Console == ConsoleLog {
441+ cfg += fmt .Sprintf (",asl" )
442+ } else {
443+ cfg += fmt .Sprintf (",log=%s/console-ring" , h .StateDir )
444+ }
445+ return []string {"-l" , cfg }
446+ }
447+
448+ func (h * HyperKit ) serialArgs () []string {
449+ results := []string {}
450+ for i , serial := range h .Serials {
451+ cfg := fmt .Sprintf ("com%d" , i + 1 )
452+ switch serial .InteractiveConsole {
453+ case NoInteractiveConsole :
454+ cfg += ",null"
455+ case StdioInteractiveConsole :
456+ cfg += fmt .Sprintf (",stdio" )
457+ case TTYInteractiveConsole :
458+ cfg += fmt .Sprintf (",autopty=%s/tty%d" , h .StateDir , i + 1 )
459+ }
460+ if serial .LogToASL {
461+ cfg += fmt .Sprintf (",asl" )
462+ }
463+ if serial .LogToRingBuffer {
464+ cfg += fmt .Sprintf (",log=%s/console-ring" , h .StateDir )
465+ }
466+ results = append (results , "-l" , cfg )
467+ }
468+ return results
469+ }
470+
368471func (h * HyperKit ) buildArgs (cmdline string ) {
369472 a := []string {"-A" , "-u" }
370473 if h .StateDir != "" {
@@ -429,19 +532,10 @@ func (h *HyperKit) buildArgs(cmdline string) {
429532 }
430533
431534 // -l: LPC device configuration.
432- {
433- cfg := "com1"
434- if h .Console == ConsoleStdio && isTerminal (os .Stdout ) {
435- cfg += fmt .Sprintf (",stdio" )
436- } else {
437- cfg += fmt .Sprintf (",autopty=%s/tty" , h .StateDir )
438- }
439- if h .Console == ConsoleLog {
440- cfg += fmt .Sprintf (",asl" )
441- } else {
442- cfg += fmt .Sprintf (",log=%s/console-ring" , h .StateDir )
443- }
444- a = append (a , "-l" , cfg )
535+ if h .Serials == nil {
536+ a = append (a , h .legacyConsoleArgs ()... )
537+ } else {
538+ a = append (a , h .serialArgs ()... )
445539 }
446540
447541 if h .Bootrom == "" {
@@ -459,8 +553,8 @@ func (h *HyperKit) buildArgs(cmdline string) {
459553}
460554
461555// openTTY opens the tty files for reading, and returns it.
462- func (h * HyperKit ) openTTY () * os.File {
463- path := fmt . Sprintf ( "%s/tty" , h .StateDir )
556+ func (h * HyperKit ) openTTY (filename string ) * os.File {
557+ path := path . Join ( h .StateDir , filename )
464558 for {
465559 if res , err := os .OpenFile (path , os .O_RDONLY , 0 ); err != nil {
466560 log .Infof ("hyperkit: openTTY: %v, retrying" , err )
@@ -474,6 +568,21 @@ func (h *HyperKit) openTTY() *os.File {
474568 }
475569}
476570
571+ func (h * HyperKit ) findStdioTTY () string {
572+ if h .Serials != nil {
573+ for i , serial := range h .Serials {
574+ if serial .InteractiveConsole == StdioInteractiveConsole {
575+ return fmt .Sprintf ("tty%d" , i )
576+ }
577+ }
578+ return ""
579+ }
580+ if h .Console == ConsoleStdio {
581+ return "tty"
582+ }
583+ return ""
584+ }
585+
477586// execute forges the command to run hyperkit, runs and returns it.
478587// It also plumbs stdin/stdout/stderr.
479588func (h * HyperKit ) execute () (* exec.Cmd , error ) {
@@ -496,19 +605,21 @@ func (h *HyperKit) execute() (*exec.Cmd, error) {
496605 //
497606 // If a logger is specified, use it for stdout/stderr
498607 // logging. Otherwise use the default /dev/null.
499- if h .Console == ConsoleStdio {
608+ filename := h .findStdioTTY ()
609+ if filename != "" {
500610 if isTerminal (os .Stdout ) {
501611 cmd .Stdin = os .Stdin
502612 cmd .Stdout = os .Stdout
503613 cmd .Stderr = os .Stderr
504614 } else {
505615 go func () {
506- tty := h .openTTY ()
616+ tty := h .openTTY (filename )
507617 defer tty .Close ()
508618 io .Copy (os .Stdout , tty )
509619 }()
510620 }
511- } else if log != nil {
621+ }
622+ if log != nil {
512623 log .Debugf ("hyperkit: Redirecting stdout/stderr to logger" )
513624 stdout , err := cmd .StdoutPipe ()
514625 if err != nil {
0 commit comments