v0.2.0 ab733e5

scm_linux.go RawHistoryFolder

1//go:build linux
2// +build linux
3
4package wlturbo
5
6import (
7	"errors"
8	"fmt"
9	"golang.org/x/sys/unix"
10	"sync"
11	"syscall"
12)
13
14// maxRecvFDs bounds how many ancillary file descriptors a single read accepts.
15// A socket read that carries more than this is reported as a protocol failure
16// instead of silently dropping descriptors.
17const maxRecvFDs = 64
18
19// controlBufferPool provides control-message buffers sized for maxRecvFDs.
20var controlBufferPool = sync.Pool{
21	New: func() interface{} {
22		return make([]byte, unix.CmsgSpace(maxRecvFDs*4))
23	},
24}
25
26// readChunk reads one chunk of bytes from the display connection and queues any
27// ancillary file descriptors that arrive with it. A single read never defines a
28// message boundary; callers frame the returned bytes themselves.
29//
30// Descriptors are owned by the connection and consumed in arrival order by
31// Event.Fd, which is what the Wayland wire protocol requires: an fd belongs to
32// the argument position in the byte stream, not to whichever message happens to
33// end first.
34func (d *Display) readChunk() (int, error) {
35	if d.unix == nil {
36		n, err := d.conn.Read(d.recvBuf)
37		if n < 0 {
38			n = 0
39		}
40		return n, err
41	}
42
43	oob := controlBufferPool.Get().([]byte)
44	defer controlBufferPool.Put(oob)
45
46	n, oobn, flags, _, err := d.unix.ReadMsgUnix(d.recvBuf, oob)
47	if n < 0 {
48		n = 0
49	}
50
51	if oobn > 0 {
52		fds, perr := parseFDs(oob[:oobn])
53		d.queueFDs(fds)
54		if perr != nil && err == nil {
55			err = perr
56		}
57	}
58
59	if flags&unix.MSG_CTRUNC != 0 && err == nil {
60		// Descriptors were dropped by the kernel, so the connection can no
61		// longer be demarshalled correctly: treat it as a protocol failure so
62		// the caller closes the descriptors that did arrive.
63		err = &ProtocolError{Kind: "control_truncated", Err: ErrMalformedFrame}
64	}
65	return n, err
66}
67
68// parseFDs extracts the file descriptors carried by socket control messages.
69// Descriptors parsed before an error are returned so the caller can close them.
70func parseFDs(oob []byte) ([]int, error) {
71	scms, err := syscall.ParseSocketControlMessage(oob)
72	if err != nil {
73		return nil, fmt.Errorf("parse control message: %w", err)
74	}
75
76	var fds []int
77	for i := range scms {
78		scm := &scms[i]
79		if scm.Header.Type != syscall.SCM_RIGHTS {
80			continue
81		}
82		parsed, err := syscall.ParseUnixRights(scm)
83		if err != nil {
84			return fds, fmt.Errorf("parse unix rights: %w", err)
85		}
86		fds = append(fds, parsed...)
87	}
88	return fds, nil
89}
90
91// queueFDs appends descriptors to this connection's pending list.
92func (d *Display) queueFDs(fds []int) {
93	if len(fds) == 0 {
94		return
95	}
96	d.fdMu.Lock()
97	d.pendingFDs = append(d.pendingFDs, fds...)
98	d.fdMu.Unlock()
99}
100
101// nextFD removes and returns the oldest pending descriptor.
102func (d *Display) nextFD() (int, bool) {
103	d.fdMu.Lock()
104	defer d.fdMu.Unlock()
105	if len(d.pendingFDs) == 0 {
106		return -1, false
107	}
108	fd := d.pendingFDs[0]
109	d.pendingFDs = d.pendingFDs[1:]
110	return fd, true
111}
112
113// closePendingFDs closes every descriptor this connection still holds.
114func (d *Display) closePendingFDs() {
115	d.fdMu.Lock()
116	fds := d.pendingFDs
117	d.pendingFDs = nil
118	d.fdMu.Unlock()
119
120	for _, fd := range fds {
121		if fd >= 0 {
122			_ = unix.Close(fd)
123		}
124	}
125}
126
127// sendmsgWithFDs sends a message, attaching file descriptors when present.
128func (d *Display) sendmsgWithFDs(buf []byte, fds []int) error {
129	d.sendMu.Lock()
130	defer d.sendMu.Unlock()
131
132	if len(fds) == 0 {
133		_, err := d.conn.Write(buf)
134		return err
135	}
136	if d.unix == nil {
137		return errors.New("wlturbo: cannot send file descriptors over a non-Unix connection")
138	}
139
140	_, _, err := d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil)
141	return err
142}
143
144// Memory mapping helpers for shared memory buffers
145
146// CreateAnonymousFile creates an anonymous file for shared memory
147func CreateAnonymousFile(size int64) (fd int, err error) {
148	// Try memfd_create first (Linux 3.17+)
149	fd, err = unix.MemfdCreate("wlclient-shm", unix.MFD_CLOEXEC|unix.MFD_ALLOW_SEALING)
150	if err == nil {
151		// Set file size
152		err = unix.Ftruncate(fd, size)
153		if err != nil {
154			_ = unix.Close(fd)
155			return -1, err
156		}
157
158		// Add seals to prevent resizing
159		_, err = unix.FcntlInt(uintptr(fd), unix.F_ADD_SEALS,
160			unix.F_SEAL_SHRINK|unix.F_SEAL_GROW|unix.F_SEAL_SEAL)
161		if err != nil {
162			_ = unix.Close(fd)
163			return -1, err
164		}
165
166		return fd, nil
167	}
168
169	// Fallback to O_TMPFILE if available
170	fd, err = unix.Open("/dev/shm", unix.O_TMPFILE|unix.O_RDWR|unix.O_CLOEXEC, 0600)
171	if err == nil {
172		err = unix.Ftruncate(fd, size)
173		if err != nil {
174			_ = unix.Close(fd)
175			return -1, err
176		}
177		return fd, nil
178	}
179
180	// Final fallback: create temp file and unlink
181	name := fmt.Sprintf("/dev/shm/wlclient-%d", unix.Getpid())
182	fd, err = unix.Open(name, unix.O_RDWR|unix.O_CREAT|unix.O_EXCL|unix.O_CLOEXEC, 0600)
183	if err != nil {
184		return -1, err
185	}
186
187	// Unlink immediately
188	_ = unix.Unlink(name)
189
190	// Set size
191	err = unix.Ftruncate(fd, size)
192	if err != nil {
193		unix.Close(fd)
194		return -1, err
195	}
196
197	return fd, nil
198}
199
200// MapMemory maps a file descriptor into memory
201func MapMemory(fd int, size int) ([]byte, error) {
202	return unix.Mmap(fd, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
203}
204
205// UnmapMemory unmaps memory
206func UnmapMemory(data []byte) error {
207	return unix.Munmap(data)
208}

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 8

↑↓ moveenter openesc close