Skip to content

Commit a6745e4

Browse files
committed
fix pty
1 parent a6bb841 commit a6745e4

6 files changed

Lines changed: 88 additions & 46 deletions

File tree

hypervisor/cloudhypervisor/api.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ type chVMConfig struct {
1010
Balloon *chBalloon `json:"balloon,omitempty"`
1111

1212
// Required — value (always present).
13-
CPUs chCPUs `json:"cpus"`
14-
Memory chMemory `json:"memory"`
15-
Disks []chDisk `json:"disks,omitempty"`
16-
RNG chRNG `json:"rng"`
17-
Watchdog bool `json:"watchdog"`
18-
Serial chSerial `json:"serial"`
19-
Console chConsole `json:"console"`
13+
CPUs chCPUs `json:"cpus"`
14+
Memory chMemory `json:"memory"`
15+
Disks []chDisk `json:"disks,omitempty"`
16+
RNG chRNG `json:"rng"`
17+
Watchdog bool `json:"watchdog"`
18+
Serial chRuntimeFile `json:"serial"`
19+
Console chRuntimeFile `json:"console"`
2020
}
2121

2222
type chPayload struct {
@@ -58,14 +58,15 @@ type chRNG struct {
5858
Src string `json:"src"`
5959
}
6060

61-
type chSerial struct {
61+
type chRuntimeFile struct {
6262
Mode string `json:"mode"`
6363
File string `json:"file,omitempty"`
6464
Socket string `json:"socket,omitempty"`
6565
}
6666

67-
type chConsole struct {
68-
Mode string `json:"mode"`
69-
File string `json:"file,omitempty"`
70-
Socket string `json:"socket,omitempty"`
67+
type chVMInfoResponse struct {
68+
Config struct {
69+
Serial chRuntimeFile `json:"serial"`
70+
Console chRuntimeFile `json:"console"`
71+
} `json:"config"`
7172
}

hypervisor/cloudhypervisor/conf.go

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ func buildVMConfig(rec *hypervisor.VMRecord, consoleSockPath string) *chVMConfig
2828
}
2929

3030
if isDirectBoot(rec.BootConfig) {
31-
cfg.Serial = chSerial{Mode: "Off"}
32-
cfg.Console = chConsole{Mode: "Socket", Socket: consoleSockPath}
31+
cfg.Serial = chRuntimeFile{Mode: "Off"}
32+
cfg.Console = chRuntimeFile{Mode: "Pty"}
3333
} else {
34-
cfg.Serial = chSerial{Mode: "Socket", Socket: consoleSockPath}
35-
cfg.Console = chConsole{Mode: "Off"}
34+
cfg.Serial = chRuntimeFile{Mode: "Socket", Socket: consoleSockPath}
35+
cfg.Console = chRuntimeFile{Mode: "Off"}
3636
}
3737

3838
// Balloon: 25% of memory, only when memory >= 256 MiB.
@@ -137,8 +137,8 @@ func buildCLIArgs(cfg *chVMConfig, socketPath string) []string {
137137
args = append(args, "--balloon", balloonToCLIArg(b))
138138
}
139139

140-
args = append(args, "--serial", serialToCLIArg(cfg.Serial))
141-
args = append(args, "--console", consoleToCLIArg(cfg.Console))
140+
args = append(args, "--serial", runtimeFiletoCLIArg(cfg.Serial))
141+
args = append(args, "--console", runtimeFiletoCLIArg(cfg.Console))
142142

143143
return args
144144
}
@@ -183,26 +183,15 @@ func balloonToCLIArg(b *chBalloon) string {
183183
return strings.Join(parts, ",")
184184
}
185185

186-
func serialToCLIArg(s chSerial) string {
187-
switch strings.ToLower(s.Mode) {
188-
case "file":
189-
return "file=" + s.File
190-
case "socket":
191-
return "socket=" + s.Socket
192-
case "tty":
193-
return "tty"
194-
default:
195-
return strings.ToLower(s.Mode) // "off", "null", "pty"
196-
}
197-
}
198-
199-
func consoleToCLIArg(c chConsole) string {
186+
func runtimeFiletoCLIArg(c chRuntimeFile) string {
200187
switch strings.ToLower(c.Mode) {
201188
case "file":
202189
return "file=" + c.File
203190
case "socket":
204191
return "socket=" + c.Socket
192+
case "tty":
193+
return "tty"
205194
default:
206-
return strings.ToLower(c.Mode) // "off", "pty", "tty"
195+
return strings.ToLower(c.Mode) // "off", "null", "pty"
207196
}
208197
}

hypervisor/cloudhypervisor/console.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import (
55
"fmt"
66
"io"
77
"net"
8+
"os"
89
)
910

10-
// Console connects to the VM's console socket and returns a bidirectional stream.
11+
// Console connects to the VM's console output and returns a bidirectional stream.
1112
//
12-
// For UEFI-boot VMs (cloudimg): CH binds the serial port (ttyS0) to console.sock.
13-
// For direct-boot VMs (OCI): CH binds the virtio-console (hvc0) to console.sock.
13+
// For UEFI-boot VMs (cloudimg): connects to the serial socket (console.sock).
14+
// For direct-boot VMs (OCI): opens the virtio-console PTY allocated by CH.
1415
//
16+
// The endpoint is stored in VMInfo.ConsolePath at start time.
1517
// The caller is responsible for closing the returned ReadCloser.
1618
func (ch *CloudHypervisor) Console(ctx context.Context, ref string) (io.ReadCloser, error) {
1719
info, err := ch.Inspect(ctx, ref)
@@ -21,12 +23,29 @@ func (ch *CloudHypervisor) Console(ctx context.Context, ref string) (io.ReadClos
2123

2224
var conn io.ReadCloser
2325
if err := ch.withRunningVM(info.ID, func(_ int) error {
24-
sockPath := ch.conf.CHVMConsoleSock(info.ID)
25-
c, dialErr := (&net.Dialer{}).DialContext(ctx, "unix", sockPath)
26-
if dialErr != nil {
27-
return fmt.Errorf("connect to console socket %s: %w", sockPath, dialErr)
26+
path := info.ConsolePath
27+
if path == "" {
28+
return fmt.Errorf("no console path for VM %s", info.ID)
29+
}
30+
31+
fi, statErr := os.Stat(path)
32+
if statErr != nil {
33+
return fmt.Errorf("stat console path %s: %w", path, statErr)
34+
}
35+
36+
if fi.Mode()&os.ModeSocket != 0 {
37+
c, dialErr := (&net.Dialer{}).DialContext(ctx, "unix", path)
38+
if dialErr != nil {
39+
return fmt.Errorf("connect to console socket %s: %w", path, dialErr)
40+
}
41+
conn = c
42+
} else {
43+
f, openErr := os.OpenFile(path, os.O_RDWR, 0) //nolint:gosec
44+
if openErr != nil {
45+
return fmt.Errorf("open console PTY %s: %w", path, openErr)
46+
}
47+
conn = f
2848
}
29-
conn = c
3049
return nil
3150
}); err != nil {
3251
return nil, fmt.Errorf("console %s: %w", info.ID, err)

hypervisor/cloudhypervisor/helper.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cloudhypervisor
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
67
"fmt"
78
"path/filepath"
@@ -27,6 +28,20 @@ func powerButton(ctx context.Context, socketPath string) error {
2728
})
2829
}
2930

31+
// queryConsolePTY retrieves the virtio-console PTY path from a running CH instance
32+
// via GET /api/v1/vm.info. Returns empty string if the console is not in Pty mode.
33+
func queryConsolePTY(ctx context.Context, apiSocketPath string) (string, error) {
34+
body, err := hypervisor.DoGET(ctx, apiSocketPath, "/api/v1/vm.info")
35+
if err != nil {
36+
return "", fmt.Errorf("query vm.info: %w", err)
37+
}
38+
var info chVMInfoResponse
39+
if err := json.Unmarshal(body, &info); err != nil {
40+
return "", fmt.Errorf("decode vm.info: %w", err)
41+
}
42+
return info.Config.Console.File, nil
43+
}
44+
3045
// blobHexFromPath extracts the digest hex from a blob file path.
3146
// e.g., "/var/lib/cocoon/oci/blobs/abc123.erofs" → "abc123"
3247
func blobHexFromPath(path string) string {

hypervisor/cloudhypervisor/start.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,26 @@ func (ch *CloudHypervisor) startOne(ctx context.Context, id string) error {
6767
return fmt.Errorf("launch VM: %w", err)
6868
}
6969

70-
if err := ch.updateState(ctx, id, types.VMStateRunning); err != nil {
71-
// Kill orphan process — the DB doesn't know the VM is running,
72-
// so clean up and let the next start retry from a clean slate.
70+
var consolePath string
71+
if isDirectBoot(rec.BootConfig) {
72+
consolePath, _ = queryConsolePTY(ctx, socketPath)
73+
} else {
74+
consolePath = ch.conf.CHVMConsoleSock(id)
75+
}
76+
77+
// Persist running state + console path.
78+
now := time.Now()
79+
if err := ch.store.Update(ctx, func(idx *hypervisor.VMIndex) error {
80+
r := idx.VMs[id]
81+
if r == nil {
82+
return fmt.Errorf("VM %s disappeared from index", id)
83+
}
84+
r.State = types.VMStateRunning
85+
r.StartedAt = &now
86+
r.UpdatedAt = now
87+
r.ConsolePath = consolePath
88+
return nil
89+
}); err != nil {
7390
_ = utils.TerminateProcess(ctx, pid, filepath.Base(ch.conf.CHBinary), socketPath, terminateGracePeriod)
7491
ch.cleanupRuntimeFiles(id)
7592
return fmt.Errorf("update state: %w", err)

types/vm.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ type VMInfo struct {
3030
Config VMConfig `json:"config"`
3131

3232
// Runtime — populated only while State == VMStateRunning.
33-
PID int `json:"pid,omitempty"`
34-
SocketPath string `json:"socket_path,omitempty"` // CH API Unix socket
33+
PID int `json:"pid,omitempty"`
34+
SocketPath string `json:"socket_path,omitempty"` // CH API Unix socket
35+
ConsolePath string `json:"console_path,omitempty"` // console output endpoint (socket or PTY)
3536

3637
// Timestamps.
3738
CreatedAt time.Time `json:"created_at"`

0 commit comments

Comments
 (0)