diff --git a/cmd/vfkit/root.go b/cmd/vfkit/root.go index eea2f9f2..18716f62 100644 --- a/cmd/vfkit/root.go +++ b/cmd/vfkit/root.go @@ -5,6 +5,7 @@ import ( "os" "github.com/crc-org/vfkit/pkg/cmdline" + "github.com/onsi/gocleanup" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -57,10 +58,11 @@ func getLogLevel() (logrus.Level, error) { func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) - os.Exit(1) + gocleanup.Exit(1) } } func main() { Execute() + gocleanup.Exit(0) } diff --git a/doc/usage.md b/doc/usage.md index 38887f96..f8d06c81 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -225,11 +225,12 @@ This is useful in combination with usermode networking stacks such as [gvisor-ta #### Description The `--device virtio-serial` option adds a serial device to the virtual machine. This is useful to redirect text output from the virtual machine to a log file. -The `logFilePath` and `stdio` arguments are mutually exclusive. +The `logFilePath`, `stdio`, `pty` arguments are mutually exclusive. #### Arguments - `logFilePath`: path where the serial port output should be written. - `stdio`: uses stdin/stdout for the serial console input/output. +- `pty`: allocates a pseudo-terminal for the serial console input/output. #### Example @@ -244,6 +245,18 @@ launched from will be used as an interactive serial console for that device: --device virtio-serial,stdio ``` +This adds a virtio-serial device to the VM, and creates a pseudo-terminal for +the console for that device: +``` +--device virtio-serial,pty +``` +Once the VM is running, you can connect to its console with: +``` +screen /dev/ttys002 +``` +`/dev/ttys002` will vary between `vfkit` runs. +The `/dev/ttys???` path to the pty is printed during vfkit startup. +It's also available through the `/vm/inspect` endpoint of [REST API](#restful-service) in the `ptyName` field of the `virtio-serial` device. ### Random Number Generator diff --git a/go.mod b/go.mod index 45f9afc5..09f4d7c6 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,8 @@ require ( github.com/containers/common v0.58.0 github.com/crc-org/crc/v2 v2.33.0 github.com/gin-gonic/gin v1.9.1 + github.com/onsi/gocleanup v0.0.0-20140331211545-c1a5478700b5 + github.com/pkg/term v1.1.0 github.com/prashantgupta24/mac-sleep-notifier v1.0.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 @@ -49,6 +51,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/go.sum b/go.sum index 5f69122b..599179ca 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,9 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -50,8 +53,19 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -59,6 +73,7 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -85,10 +100,24 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gocleanup v0.0.0-20140331211545-c1a5478700b5 h1:uuhPqmc+m7Nj7btxZEjdEUv+uFoBHNf2Tk/E7gGM+kY= +github.com/onsi/gocleanup v0.0.0-20140331211545-c1a5478700b5/go.mod h1:tHaogb+iP6wJXwCqVUlmxYuJb4XDyEKxxs3E4DvMBK0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= +github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -116,6 +145,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -134,20 +164,43 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -159,15 +212,35 @@ golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/config/json_test.go b/pkg/config/json_test.go index 72763f1f..88b3480f 100644 --- a/pkg/config/json_test.go +++ b/pkg/config/json_test.go @@ -232,7 +232,7 @@ var jsonStabilityTests = map[string]jsonStabilityTest{ }, "VirtioSerial": { obj: &VirtioSerial{}, - expectedJSON: `{"kind":"virtioserial","logFile":"LogFile","usesStdio":true}`, + expectedJSON: `{"kind":"virtioserial","logFile":"LogFile","ptyName":"PtyName","usesPty":true,"usesStdio":true}`, }, "VirtioVsock": { obj: &VirtioVsock{}, diff --git a/pkg/config/virtio.go b/pkg/config/virtio.go index 5523cf9a..10ddc92e 100644 --- a/pkg/config/virtio.go +++ b/pkg/config/virtio.go @@ -104,6 +104,10 @@ type VirtioNet struct { type VirtioSerial struct { LogFile string `json:"logFile,omitempty"` UsesStdio bool `json:"usesStdio,omitempty"` + UsesPty bool `json:"usesPty,omitempty"` + // PtyName must not be set when creating the VM, from a user perspective, it's read-only, + // vfkit will set it during VM startup. + PtyName string `json:"ptyName,omitempty"` } // TODO: Add VirtioBalloon @@ -195,12 +199,24 @@ func VirtioSerialNewStdio() (VirtioDevice, error) { }, nil } +func VirtioSerialNewPty() (VirtioDevice, error) { + return &VirtioSerial{ + UsesPty: true, + }, nil +} + func (dev *VirtioSerial) validate() error { if dev.LogFile != "" && dev.UsesStdio { return fmt.Errorf("'logFilePath' and 'stdio' cannot be set at the same time") } - if dev.LogFile == "" && !dev.UsesStdio { - return fmt.Errorf("one of 'logFilePath' or 'stdio' must be set") + if dev.LogFile != "" && dev.UsesPty { + return fmt.Errorf("'logFilePath' and 'pty' cannot be set at the same time") + } + if dev.UsesStdio && dev.UsesPty { + return fmt.Errorf("'stdio' and 'pty' cannot be set at the same time") + } + if dev.LogFile == "" && !dev.UsesStdio && !dev.UsesPty { + return fmt.Errorf("one of 'logFilePath', 'stdio' or 'pty' must be set") } return nil @@ -210,11 +226,16 @@ func (dev *VirtioSerial) ToCmdLine() ([]string, error) { if err := dev.validate(); err != nil { return nil, err } - if dev.UsesStdio { + switch { + case dev.UsesStdio: return []string{"--device", "virtio-serial,stdio"}, nil + case dev.UsesPty: + return []string{"--device", "virtio-serial,pty"}, nil + case dev.LogFile != "": + fallthrough + default: + return []string{"--device", fmt.Sprintf("virtio-serial,logFilePath=%s", dev.LogFile)}, nil } - - return []string{"--device", fmt.Sprintf("virtio-serial,logFilePath=%s", dev.LogFile)}, nil } func (dev *VirtioSerial) FromOptions(options []option) error { @@ -227,6 +248,8 @@ func (dev *VirtioSerial) FromOptions(options []option) error { return fmt.Errorf("unexpected value for virtio-serial 'stdio' option: %s", option.value) } dev.UsesStdio = true + case "pty": + dev.UsesPty = true default: return fmt.Errorf("unknown option for virtio-serial devices: %s", option.key) } diff --git a/pkg/config/virtio_test.go b/pkg/config/virtio_test.go index 371c1875..149ffd97 100644 --- a/pkg/config/virtio_test.go +++ b/pkg/config/virtio_test.go @@ -120,6 +120,13 @@ var virtioDevTests = map[string]virtioDevTest{ }, expectedCmdLine: []string{"--device", "virtio-serial,stdio"}, }, + "NewVirtioSerialPty": { + newDev: VirtioSerialNewPty, + expectedDev: &VirtioSerial{ + UsesPty: true, + }, + expectedCmdLine: []string{"--device", "virtio-serial,pty"}, + }, "NewVirtioNet": { newDev: func() (VirtioDevice, error) { return VirtioNetNew("") }, expectedDev: &VirtioNet{ diff --git a/pkg/rest/rest.go b/pkg/rest/rest.go index 3db945c0..17ebef82 100644 --- a/pkg/rest/rest.go +++ b/pkg/rest/rest.go @@ -4,11 +4,13 @@ import ( "errors" "fmt" "net/url" + "os" "strings" "syscall" "github.com/crc-org/vfkit/pkg/cmdline" "github.com/gin-gonic/gin" + "github.com/onsi/gocleanup" "github.com/sirupsen/logrus" ) @@ -73,6 +75,7 @@ func (v *VFKitService) Start() { case TCP: err = v.router.Run(v.Host) case Unix: + gocleanup.Register(func() { os.Remove(v.Path) }) err = v.router.RunUnix(v.Path) } logrus.Fatal(err) diff --git a/pkg/vf/virtio.go b/pkg/vf/virtio.go index 38cec7a9..5f80219d 100644 --- a/pkg/vf/virtio.go +++ b/pkg/vf/virtio.go @@ -4,13 +4,14 @@ import ( "fmt" "os" "path/filepath" - "syscall" "github.com/crc-org/vfkit/pkg/config" + "github.com/onsi/gocleanup" + "golang.org/x/sys/unix" "github.com/Code-Hex/vz/v3" + "github.com/pkg/term/termios" log "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" ) type RosettaShare config.RosettaShare @@ -201,37 +202,54 @@ func (dev *VirtioRng) AddToVirtualMachineConfig(vmConfig *VirtualMachineConfigur // https://developer.apple.com/documentation/virtualization/running_linux_in_a_virtual_machine?language=objc#:~:text=Configure%20the%20Serial%20Port%20Device%20for%20Standard%20In%20and%20Out func setRawMode(f *os.File) error { - // Get settings for terminal - attr, _ := unix.IoctlGetTermios(int(f.Fd()), unix.TIOCGETA) + var attr unix.Termios + err := termios.Tcgetattr(f.Fd(), &attr) + if err != nil { + return err + } // Put stdin into raw mode, disabling local echo, input canonicalization, // and CR-NL mapping. - attr.Iflag &^= syscall.ICRNL - attr.Lflag &^= syscall.ICANON | syscall.ECHO - - // Set minimum characters when reading = 1 char - attr.Cc[syscall.VMIN] = 1 + attr.Iflag &^= unix.ICRNL + attr.Lflag &^= unix.ICANON | unix.ECHO - // set timeout when reading as non-canonical mode - attr.Cc[syscall.VTIME] = 0 - - // reflects the changed settings - return unix.IoctlSetTermios(int(f.Fd()), unix.TIOCSETA, attr) + return termios.Tcsetattr(f.Fd(), termios.TCSANOW, &attr) } func (dev *VirtioSerial) toVz() (*vz.VirtioConsoleDeviceSerialPortConfiguration, error) { var serialPortAttachment vz.SerialPortAttachment - var err error - if dev.UsesStdio { + var retErr error + switch { + case dev.UsesStdio: if err := setRawMode(os.Stdin); err != nil { return nil, err } - serialPortAttachment, err = vz.NewFileHandleSerialPortAttachment(os.Stdin, os.Stdout) - } else { - serialPortAttachment, err = vz.NewFileSerialPortAttachment(dev.LogFile, false) + serialPortAttachment, retErr = vz.NewFileHandleSerialPortAttachment(os.Stdin, os.Stdout) + case dev.UsesPty: + master, slave, err := termios.Pty() + if err != nil { + return nil, err + } + // as far as I can tell, we have no use for the slave fd in the + // vfkit process, the user will open minicom/screen/... /dev/ttys00? + // when needed + defer slave.Close() + + // the master fd must stay open for vfkit's lifetime + gocleanup.Register(func() { _ = master.Close() }) + + dev.PtyName = slave.Name() + + if err := setRawMode(master); err != nil { + return nil, err + } + serialPortAttachment, retErr = vz.NewFileHandleSerialPortAttachment(master, master) + + default: + serialPortAttachment, retErr = vz.NewFileSerialPortAttachment(dev.LogFile, false) } - if err != nil { - return nil, err + if retErr != nil { + return nil, retErr } return vz.NewVirtioConsoleDeviceSerialPortConfiguration(serialPortAttachment) @@ -244,11 +262,17 @@ func (dev *VirtioSerial) AddToVirtualMachineConfig(vmConfig *VirtualMachineConfi if dev.UsesStdio { log.Infof("Adding stdio console") } + if dev.PtyName != "" { + return fmt.Errorf("VirtioSerial.PtyName must be empty (current value: %s)", dev.PtyName) + } consoleConfig, err := dev.toVz() if err != nil { return err } + if dev.UsesPty { + log.Infof("Using PTY (pty path: %s)", dev.PtyName) + } vmConfig.serialPortsConfiguration = append(vmConfig.serialPortsConfiguration, consoleConfig) return nil diff --git a/pkg/vf/virtionet.go b/pkg/vf/virtionet.go index 72fd73c2..d0f8d9a9 100644 --- a/pkg/vf/virtionet.go +++ b/pkg/vf/virtionet.go @@ -4,13 +4,13 @@ import ( "fmt" "net" "os" - "os/signal" "path/filepath" "syscall" "github.com/crc-org/vfkit/pkg/config" "github.com/Code-Hex/vz/v3" + "github.com/onsi/gocleanup" log "github.com/sirupsen/logrus" ) @@ -89,7 +89,7 @@ func (dev *VirtioNet) connectUnixPath() error { dev.Socket = fd dev.localAddr = &localAddr dev.UnixSocketPath = "" - registerExitHandler(func() { _ = dev.Shutdown() }) + gocleanup.Register(func() { _ = dev.Shutdown() }) return nil } @@ -160,15 +160,3 @@ func (dev *VirtioNet) Shutdown() error { return nil } - -func registerExitHandler(handler func()) { - sigChan := make(chan os.Signal, 2) - signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) - go func() { - for sig := range sigChan { - log.Printf("captured %v, calling exit handlers and exiting..", sig) - handler() - os.Exit(1) - } - }() -} diff --git a/test/vm_test.go b/test/vm_test.go index 336ecea0..d63a3632 100644 --- a/test/vm_test.go +++ b/test/vm_test.go @@ -15,6 +15,7 @@ import ( "github.com/crc-org/vfkit/pkg/config" log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -272,7 +273,7 @@ var pciidVersionedTests = map[int]map[string]pciidTest{ 14: pciidMacOS14Tests, } -func checkRestDevices(t *testing.T, vm *testVM) { +func restInspect(t *testing.T, vm *testVM) *config.VirtualMachine { tr := &http.Transport{ Dial: func(_, _ string) (conn net.Conn, err error) { return net.Dial("unix", vm.restSocketPath) @@ -287,7 +288,7 @@ func checkRestDevices(t *testing.T, vm *testVM) { var unmarshalledVM config.VirtualMachine err = json.Unmarshal(body, &unmarshalledVM) require.NoError(t, err) - require.Equal(t, vm.config, &unmarshalledVM) + return &unmarshalledVM } func testPCIId(t *testing.T, test pciidTest, provider OsProvider) { @@ -303,7 +304,9 @@ func testPCIId(t *testing.T, test pciidTest, provider OsProvider) { vm.Start(t) vm.WaitForSSH(t) checkPCIDevice(t, vm, test.vendorID, test.deviceID) - checkRestDevices(t, vm) + + unmarshalledVM := restInspect(t, vm) + require.Equal(t, vm.config, unmarshalledVM) vm.Stop(t) } @@ -334,6 +337,39 @@ func TestPCIIds(t *testing.T) { } } +func TestVirtioSerialPTY(t *testing.T) { + puipuiProvider := NewPuipuiProvider() + log.Info("fetching os image") + tempDir := t.TempDir() + err := puipuiProvider.Fetch(tempDir) + require.NoError(t, err) + + vm := NewTestVM(t, puipuiProvider) + defer vm.Close(t) + require.NotNil(t, vm) + + vm.AddSSH(t, "tcp") + dev, err := config.VirtioSerialNewPty() + require.NoError(t, err) + vm.AddDevice(t, dev) + + vm.Start(t) + vm.WaitForSSH(t) + runtimeVM := restInspect(t, vm) + var foundVirtioSerial bool + for _, dev := range runtimeVM.Devices { + runtimeDev, ok := dev.(*config.VirtioSerial) + if ok { + assert.NotEmpty(t, runtimeDev.PtyName) + foundVirtioSerial = true + break + } + } + require.True(t, foundVirtioSerial) + + vm.Stop(t) +} + func checkPCIDevice(t *testing.T, vm *testVM, vendorID, deviceID int) { re := regexp.MustCompile(fmt.Sprintf("(?m)[[:blank:]]%04x:%04x\n", vendorID, deviceID)) lspci, err := vm.SSHCombinedOutput(t, "lspci")