v0.5.1 25d29f5

connect.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package guest
4
5import (
6	"context"
7	"fmt"
8	"io"
9	"net"
10	"path/filepath"
11	"strconv"
12	"strings"
13	"time"
14
15	"golang.org/x/sys/unix"
16
17	"bnema.dev/ermite-runner/internal/sandbox"
18)
19
20// Target is where the executor connects back to: "unix:/path" (container) or
21// "vsock:PORT" (vm; the host is CID 2).
22type Target struct {
23	Network string // "unix" or "vsock"
24	Addr    string // socket path, or the vsock port
25}
26
27// ParseTarget parses "unix:/path" or "vsock:port".
28func ParseTarget(s string) (Target, error) {
29	kind, rest, ok := strings.Cut(s, ":")
30	if !ok || rest == "" {
31		return Target{}, fmt.Errorf("connect target %q: want unix:PATH or vsock:PORT", s)
32	}
33	switch kind {
34	case "unix":
35		return Target{Network: "unix", Addr: rest}, nil
36	case "vsock":
37		if p, err := strconv.ParseUint(rest, 10, 32); err != nil || p == 0 {
38			return Target{}, fmt.Errorf("connect target %q: bad vsock port", s)
39		}
40		return Target{Network: "vsock", Addr: rest}, nil
41	}
42	return Target{}, fmt.Errorf("connect target %q: unknown kind %q", s, kind)
43}
44
45func (t Target) String() string { return t.Network + ":" + t.Addr }
46
47// CacheTarget is where the cache streams of the sandbox connect: the host
48// channel of the executor (t) has a sibling for the cache, on the same
49// sandbox-private path. A VM uses its own vsock port (sandbox.CacheVsockPort);
50// a container uses sandbox.CacheSocket next to the executor socket.
51func (t Target) CacheTarget() Target {
52	if t.Network == "vsock" {
53		return Target{Network: "vsock", Addr: strconv.Itoa(sandbox.CacheVsockPort)}
54	}
55	return Target{Network: "unix", Addr: filepath.Join(filepath.Dir(t.Addr), sandbox.CacheSocket)}
56}
57
58// DialStream opens one new connection to the target, without retrying: the
59// cache relay opens one per accepted TCP connection and a failure closes that
60// connection (the client retries).
61func (t Target) DialStream(ctx context.Context) (io.ReadWriteCloser, error) {
62	if t.Network == "unix" {
63		var d net.Dialer
64		return d.DialContext(ctx, "unix", t.Addr)
65	}
66	c, err := dialVsock(t.Addr)
67	if err != nil {
68		return nil, err
69	}
70	return c, nil
71}
72
73// Dial connects to the host, retrying until ctx ends or timeout elapses: the
74// host listens before the sandbox starts, but a vsock device may take a moment.
75func (t Target) Dial(ctx context.Context, timeout time.Duration) (io.ReadWriteCloser, error) {
76	ctx, cancel := context.WithTimeout(ctx, timeout)
77	defer cancel()
78	var last error
79	for {
80		var c io.ReadWriteCloser
81		var err error
82		if t.Network == "unix" {
83			var d net.Dialer
84			c, err = d.DialContext(ctx, "unix", t.Addr)
85		} else {
86			var vc *fdConn
87			if vc, err = dialVsock(t.Addr); err == nil {
88				c = vc
89			}
90		}
91		if err == nil {
92			return c, nil
93		}
94		last = err
95		select {
96		case <-ctx.Done():
97			return nil, fmt.Errorf("connect to %s: %w (last error: %v)", t, ctx.Err(), last)
98		case <-time.After(100 * time.Millisecond):
99		}
100	}
101}
102
103// vsockHostCID is the well-known CID of the host.
104const vsockHostCID = 2
105
106func dialVsock(port string) (*fdConn, error) {
107	p, _ := strconv.ParseUint(port, 10, 32)
108	fd, err := unix.Socket(unix.AF_VSOCK, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
109	if err != nil {
110		return nil, err
111	}
112	// A blocking connect is fine: a Firecracker vsock either connects to the
113	// host listener at once or fails.
114	if err := unix.Connect(fd, &unix.SockaddrVM{CID: vsockHostCID, Port: uint32(p)}); err != nil {
115		_ = unix.Close(fd)
116		return nil, err
117	}
118	return newFDConn(fd)
119}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close