v0.1.0 4a0dffe

scm_linux.go RawHistoryFolder

1//go:build linux
2// +build linux
3
4package wlturbo
5
6import (
7	"fmt"
8	"net"
9	"golang.org/x/sys/unix"
10	"sync"
11	"sync/atomic"
12	"syscall"
13)
14
15// fdQueue is a lock-free multi-producer single-consumer queue for file descriptors
16type fdQueue struct {
17	items [256]atomic.Pointer[fdItem]
18	head  atomic.Uint64
19	tail  atomic.Uint64
20}
21
22type fdItem struct {
23	fd   int
24	next atomic.Pointer[fdItem]
25}
26
27var (
28	// Global FD queue for zero-allocation FD passing
29	globalFDQueue = &fdQueue{}
30	
31	// Pre-allocated FD items pool
32	fdItemPool = sync.Pool{
33		New: func() interface{} {
34			return &fdItem{}
35		},
36	}
37	
38	// Pre-allocated buffers for control messages
39	controlBufferPool = sync.Pool{
40		New: func() interface{} {
41			return make([]byte, unix.CmsgSpace(4*4)) // Space for 4 FDs
42		},
43	}
44)
45
46// enqueueFD adds a file descriptor to the lock-free queue
47func (q *fdQueue) enqueueFD(fd int) {
48	item := fdItemPool.Get().(*fdItem)
49	item.fd = fd
50	item.next.Store(nil)
51	
52	for {
53		tail := q.tail.Load()
54		next := tail & 255
55		
56		if q.items[next].CompareAndSwap(nil, item) {
57			q.tail.Add(1)
58			return
59		}
60		// Retry if CAS failed
61	}
62}
63
64// dequeueFD removes and returns a file descriptor from the queue
65func (q *fdQueue) dequeueFD() (int, bool) {
66	for {
67		head := q.head.Load()
68		tail := q.tail.Load()
69		
70		if head >= tail {
71			return -1, false
72		}
73		
74		next := head & 255
75		item := q.items[next].Load()
76		
77		if item == nil {
78			continue
79		}
80		
81		if q.head.CompareAndSwap(head, head+1) {
82			fd := item.fd
83			q.items[next].Store(nil)
84			fdItemPool.Put(item)
85			return fd, true
86		}
87	}
88}
89
90// recvmsgWithFDs receives a message potentially containing file descriptors
91func (d *Display) recvmsgWithFDs(buf []byte) (n int, fds []int, err error) {
92	// Get control buffer from pool
93	oob := controlBufferPool.Get().([]byte)
94	defer controlBufferPool.Put(oob)
95	
96	// Receive message with control data
97	n, oobn, _, _, err := d.conn.(*net.UnixConn).ReadMsgUnix(buf, oob)
98	if err != nil {
99		return 0, nil, err
100	}
101	
102	// Parse control messages if any
103	if oobn > 0 {
104		scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
105		if err != nil {
106			return n, nil, fmt.Errorf("parse control message: %w", err)
107		}
108		
109		for _, scm := range scms {
110			if scm.Header.Type == syscall.SCM_RIGHTS {
111				// Parse unix rights (file descriptors)
112				parsedFDs, err := syscall.ParseUnixRights(&scm)
113				if err != nil {
114					return n, nil, fmt.Errorf("parse unix rights: %w", err)
115				}
116				
117				// Add FDs to our lock-free queue
118				for _, fd := range parsedFDs {
119					globalFDQueue.enqueueFD(fd)
120				}
121				
122				fds = append(fds, parsedFDs...)
123			}
124		}
125	}
126	
127	return n, fds, nil
128}
129
130// sendmsgWithFDs sends a message potentially containing file descriptors
131func (d *Display) sendmsgWithFDs(buf []byte, fds []int) error {
132	d.sendMu.Lock()
133	defer d.sendMu.Unlock()
134	
135	if len(fds) == 0 {
136		// Fast path: no FDs to send
137		_, err := d.conn.Write(buf)
138		return err
139	}
140	
141	// Create control message with file descriptors
142	oob := syscall.UnixRights(fds...)
143	
144	// Send message with control data
145	_, _, err := d.conn.(*net.UnixConn).WriteMsgUnix(buf, oob, nil)
146	return err
147}
148
149// GetNextFD retrieves the next file descriptor from the queue
150func GetNextFD() (int, bool) {
151	return globalFDQueue.dequeueFD()
152}
153
154// Memory mapping helpers for shared memory buffers
155
156// CreateAnonymousFile creates an anonymous file for shared memory
157func CreateAnonymousFile(size int64) (fd int, err error) {
158	// Try memfd_create first (Linux 3.17+)
159	fd, err = unix.MemfdCreate("wlclient-shm", unix.MFD_CLOEXEC|unix.MFD_ALLOW_SEALING)
160	if err == nil {
161		// Set file size
162		err = unix.Ftruncate(fd, size)
163		if err != nil {
164			_ = unix.Close(fd)
165			return -1, err
166		}
167		
168		// Add seals to prevent resizing
169		_, err = unix.FcntlInt(uintptr(fd), unix.F_ADD_SEALS, 
170			unix.F_SEAL_SHRINK|unix.F_SEAL_GROW|unix.F_SEAL_SEAL)
171		if err != nil {
172			_ = unix.Close(fd)
173			return -1, err
174		}
175		
176		return fd, nil
177	}
178	
179	// Fallback to O_TMPFILE if available
180	fd, err = unix.Open("/dev/shm", unix.O_TMPFILE|unix.O_RDWR|unix.O_CLOEXEC, 0600)
181	if err == nil {
182		err = unix.Ftruncate(fd, size)
183		if err != nil {
184			_ = unix.Close(fd)
185			return -1, err
186		}
187		return fd, nil
188	}
189	
190	// Final fallback: create temp file and unlink
191	name := fmt.Sprintf("/dev/shm/wlclient-%d", unix.Getpid())
192	fd, err = unix.Open(name, unix.O_RDWR|unix.O_CREAT|unix.O_EXCL|unix.O_CLOEXEC, 0600)
193	if err != nil {
194		return -1, err
195	}
196	
197	// Unlink immediately
198	_ = unix.Unlink(name)
199	
200	// Set size
201	err = unix.Ftruncate(fd, size)
202	if err != nil {
203		unix.Close(fd)
204		return -1, err
205	}
206	
207	return fd, nil
208}
209
210// MapMemory maps a file descriptor into memory
211func MapMemory(fd int, size int) ([]byte, error) {
212	return unix.Mmap(fd, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
213}
214
215// UnmapMemory unmaps memory
216func UnmapMemory(data []byte) error {
217	return unix.Munmap(data)
218}

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