detached 602baa7

setup.go RawHistoryFolder

1package xturbo
2
3import (
4	"encoding/binary"
5	"fmt"
6	"io"
7	"os"
8	"time"
9
10	"golang.org/x/sys/unix"
11)
12
13// Fixed layout of the xproto Setup reply (offsets from its status byte).
14const (
15	setupHeaderSize = 8  // status, pad/reason_len, protocol major, minor, length
16	setupFixedSize  = 40 // fixed part of a successful Setup
17	screenFixedSize = 40 // fixed part of a SCREEN
18	formatSize      = 8  // FORMAT
19	depthFixedSize  = 8  // fixed part of a DEPTH
20	visualSize      = 24 // VISUALTYPE
21	protocolMajor   = 11
22	protocolMinor   = 0
23)
24
25// setupInfo is what root keeps from a successful setup reply. Everything else
26// is parsed by callers from Conn.SetupBytes.
27type setupInfo struct {
28	xidBase, xidMask uint32
29	root             uint32
30}
31
32func malformed(what string) error { return fmt.Errorf("%w: %s", ErrMalformed, what) }
33
34// encodeSetupRequest builds the little-endian connection setup request.
35func encodeSetupRequest(authName string, authData []byte) []byte {
36	n, d := pad4(len(authName)), pad4(len(authData))
37	p := make([]byte, 12+n+d)
38	p[0] = 'l'
39	binary.LittleEndian.PutUint16(p[2:], protocolMajor)
40	binary.LittleEndian.PutUint16(p[4:], protocolMinor)
41	binary.LittleEndian.PutUint16(p[6:], uint16(len(authName)))
42	binary.LittleEndian.PutUint16(p[8:], uint16(len(authData)))
43	copy(p[12:], authName)
44	copy(p[12+n:], authData)
45	return p
46}
47
48func pad4(n int) int { return (n + 3) &^ 3 }
49
50// handshake runs the setup exchange on the non-blocking fd, waiting with poll
51// until deadline, and returns the full setup reply. A refusal is a
52// *SetupError; a broken reply wraps ErrMalformed; a silent server an error
53// wrapping os.ErrDeadlineExceeded.
54func handshake(fd int, authName string, authData []byte, deadline time.Time) ([]byte, setupInfo, error) {
55	if len(authName) > 0xffff || len(authData) > 0xffff {
56		return nil, setupInfo{}, ErrBadLength
57	}
58	if err := writeFull(fd, encodeSetupRequest(authName, authData), deadline); err != nil {
59		return nil, setupInfo{}, err
60	}
61	var hdr [setupHeaderSize]byte
62	if err := readFull(fd, hdr[:], deadline); err != nil {
63		return nil, setupInfo{}, err
64	}
65	reply := make([]byte, setupHeaderSize+4*int(binary.LittleEndian.Uint16(hdr[6:])))
66	copy(reply, hdr[:])
67	if err := readFull(fd, reply[setupHeaderSize:], deadline); err != nil {
68		return nil, setupInfo{}, err
69	}
70	info, err := parseSetup(reply)
71	if err != nil {
72		return nil, setupInfo{}, err
73	}
74	return reply, info, nil
75}
76
77// parseSetup validates a complete setup reply (header length already
78// honoured) and extracts what root needs.
79func parseSetup(p []byte) (setupInfo, error) {
80	if len(p) < setupHeaderSize {
81		return setupInfo{}, malformed("short setup reply")
82	}
83	switch p[0] {
84	case setupStatusFailed:
85		// SetupFailed: reason_len at byte 1, reason after the header.
86		n := int(p[1])
87		if setupHeaderSize+n > len(p) {
88			return setupInfo{}, malformed("setup failure reason overflows reply")
89		}
90		return setupInfo{}, &SetupError{Status: setupStatusFailed, Reason: string(p[setupHeaderSize : setupHeaderSize+n])}
91	case setupStatusAuthenticate:
92		// SetupAuthenticate: reason fills length*4 bytes, NUL padded.
93		reason := p[setupHeaderSize:]
94		for len(reason) > 0 && reason[len(reason)-1] == 0 {
95			reason = reason[:len(reason)-1]
96		}
97		return setupInfo{}, &SetupError{Status: setupStatusAuthenticate, Reason: string(reason)}
98	case setupStatusSuccess:
99	default:
100		return setupInfo{}, malformed(fmt.Sprintf("setup status %d", p[0]))
101	}
102	if len(p) < setupFixedSize {
103		return setupInfo{}, malformed("short setup reply")
104	}
105	if binary.LittleEndian.Uint16(p[2:]) != protocolMajor {
106		return setupInfo{}, malformed(fmt.Sprintf("protocol major %d", binary.LittleEndian.Uint16(p[2:])))
107	}
108	info := setupInfo{
109		xidBase: binary.LittleEndian.Uint32(p[12:]),
110		xidMask: binary.LittleEndian.Uint32(p[16:]),
111	}
112	if info.xidMask == 0 || info.xidBase&info.xidMask != 0 {
113		return setupInfo{}, malformed("resource id base/mask")
114	}
115	if p[28] == 0 {
116		return setupInfo{}, malformed("setup has no screens")
117	}
118	off := setupFixedSize + pad4(int(binary.LittleEndian.Uint16(p[24:]))) + formatSize*int(p[29])
119	if off > len(p) {
120		return setupInfo{}, malformed("vendor or pixmap formats overflow setup reply")
121	}
122	roots := int(p[28])
123	for i := range roots {
124		if len(p)-off < screenFixedSize {
125			return setupInfo{}, malformed(fmt.Sprintf("screen %d overflows setup reply", i))
126		}
127		if i == 0 {
128			info.root = binary.LittleEndian.Uint32(p[off:])
129		}
130		depths := int(p[off+39])
131		off += screenFixedSize
132		for range depths {
133			if len(p)-off < depthFixedSize {
134				return setupInfo{}, malformed(fmt.Sprintf("screen %d depth overflows setup reply", i))
135			}
136			visuals := visualSize * int(binary.LittleEndian.Uint16(p[off+2:]))
137			off += depthFixedSize
138			if len(p)-off < visuals {
139				return setupInfo{}, malformed(fmt.Sprintf("screen %d visuals overflow setup reply", i))
140			}
141			off += visuals
142		}
143	}
144	return info, nil
145}
146
147// waitFd polls fd for events until deadline.
148func waitFd(fd int, events int16, deadline time.Time) error {
149	left := time.Until(deadline)
150	if left <= 0 {
151		return deadlineError("connection setup")
152	}
153	return pollFd(fd, events, left)
154}
155
156func writeFull(fd int, p []byte, deadline time.Time) error {
157	for len(p) > 0 {
158		n, err := unix.Write(fd, p)
159		switch {
160		case err == unix.EINTR:
161			continue
162		case err == unix.EAGAIN:
163			if err := waitFd(fd, unix.POLLOUT, deadline); err != nil {
164				return err
165			}
166			continue
167		case err == unix.EPIPE || err == unix.ECONNRESET:
168			return ErrClosed
169		case err != nil:
170			return os.NewSyscallError("write", err)
171		case n <= 0:
172			return io.ErrShortWrite
173		}
174		p = p[n:]
175	}
176	return nil
177}
178
179func readFull(fd int, p []byte, deadline time.Time) error {
180	for len(p) > 0 {
181		n, err := unix.Read(fd, p)
182		switch {
183		case err == unix.EINTR:
184			continue
185		case err == unix.EAGAIN:
186			if err := waitFd(fd, unix.POLLIN, deadline); err != nil {
187				return err
188			}
189			continue
190		case err == unix.ECONNRESET:
191			return ErrClosed
192		case err != nil:
193			return os.NewSyscallError("read", err)
194		case n == 0:
195			return fmt.Errorf("%w: truncated setup reply: %w", ErrMalformed, io.ErrUnexpectedEOF)
196		}
197		p = p[n:]
198	}
199	return nil
200}

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 1

Tags 1

↑↓ moveenter openesc close