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}