返回 DeepSeek-Reasonix
endpoint_windows.go
根目录 / desktop / internal / instanceidentity / endpoint_windows.go
1 //go:build windows
2
3 package instanceidentity
4
5 import (
6 "errors"
7 "fmt"
8 "io"
9 "log/slog"
10 "sync"
11
12 "golang.org/x/sys/windows"
13 )
14
15 func endpointName(id string) (*uint16, error) {
16 if !Valid(id) {
17 return nil, fmt.Errorf("invalid desktop instance identity")
18 }
19 return windows.UTF16PtrFromString(`\\.\pipe\reasonix-desktop-` + id)
20 }
21
22 // ListenEndpoint exposes a kernel-owned service PID to the updater. No PID file
23 // can outlive a generation or be confused with a recycled process. It carries
24 // no business RPC and rejects remote clients.
25 func ListenEndpoint(id string) (func(), error) {
26 name, err := endpointName(id)
27 if err != nil {
28 return nil, err
29 }
30 pipe, err := windows.CreateNamedPipe(name, windows.PIPE_ACCESS_DUPLEX|windows.FILE_FLAG_OVERLAPPED|windows.FILE_FLAG_FIRST_PIPE_INSTANCE, windows.PIPE_TYPE_BYTE|windows.PIPE_READMODE_BYTE|windows.PIPE_WAIT|windows.PIPE_REJECT_REMOTE_CLIENTS, 1, 1, 1, 0, nil)
31 if err != nil {
32 return nil, err
33 }
34 event, err := windows.CreateEvent(nil, 1, 0, nil)
35 if err != nil {
36 windows.CloseHandle(pipe)
37 return nil, err
38 }
39 done := make(chan struct{})
40 var mu sync.Mutex
41 stopped := false
42 go func() {
43 defer close(done)
44 wait := func(err error, ov *windows.Overlapped) bool {
45 if !errors.Is(err, windows.ERROR_IO_PENDING) {
46 return err == nil || errors.Is(err, windows.ERROR_PIPE_CONNECTED)
47 }
48 _, err = windows.WaitForSingleObject(event, windows.INFINITE)
49 if err != nil {
50 return false
51 }
52 var transferred uint32
53 return windows.GetOverlappedResult(pipe, ov, &transferred, false) == nil
54 }
55 submit := func(call func(*windows.Overlapped) error) bool {
56 mu.Lock()
57 if stopped {
58 mu.Unlock()
59 return false
60 }
61 if err := windows.ResetEvent(event); err != nil {
62 mu.Unlock()
63 slog.Warn("desktop identity endpoint: reset completion event", "err", err)
64 return false
65 }
66 ov := windows.Overlapped{HEvent: event}
67 err := call(&ov)
68 mu.Unlock()
69 return wait(err, &ov)
70 }
71 for {
72 if !submit(func(ov *windows.Overlapped) error { return windows.ConnectNamedPipe(pipe, ov) }) {
73 return
74 }
75 var buf [1]byte
76 var read uint32
77 // The client acknowledges only after querying its server PID, so the
78 // connection remains bound during identity inspection.
79 submit(func(ov *windows.Overlapped) error { return windows.ReadFile(pipe, buf[:], &read, ov) })
80 if err := windows.DisconnectNamedPipe(pipe); err != nil && !errors.Is(err, windows.ERROR_PIPE_NOT_CONNECTED) {
81 slog.Warn("desktop identity endpoint: disconnect client", "err", err)
82 return
83 }
84 }
85 }()
86 var once sync.Once
87 return func() {
88 once.Do(func() {
89 mu.Lock()
90 stopped = true
91 // No pending I/O is normal between submissions. On any other
92 // failure, retain the handles and OVERLAPPED until the worker
93 // finishes; cancellation failure cannot prove I/O completion.
94 if err := windows.CancelIoEx(pipe, nil); err != nil && !errors.Is(err, windows.ERROR_NOT_FOUND) {
95 slog.Warn("desktop identity endpoint: cancel pending I/O", "err", err)
96 }
97 mu.Unlock()
98 <-done
99 windows.CloseHandle(pipe)
100 windows.CloseHandle(event)
101 })
102 }, nil
103 }
104
105 // EndpointImage queries the server process through an actual local pipe handle.
106 // An absent endpoint means no Electron service owns this data home.
107 func EndpointImage(id string) (string, error) {
108 name, err := endpointName(id)
109 if err != nil {
110 return "", err
111 }
112 pipe, err := windows.CreateFile(name, windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, 0, 0)
113 if errors.Is(err, windows.ERROR_FILE_NOT_FOUND) {
114 return "", nil
115 }
116 if err != nil {
117 return "", err
118 }
119 defer windows.CloseHandle(pipe)
120 var pid uint32
121 if err := windows.GetNamedPipeServerProcessId(pipe, &pid); err != nil {
122 return "", err
123 }
124 process, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION|windows.SYNCHRONIZE, false, pid)
125 if err != nil {
126 return "", err
127 }
128 defer windows.CloseHandle(process)
129 size := uint32(32768)
130 buffer := make([]uint16, size)
131 if err := windows.QueryFullProcessImageName(process, 0, &buffer[0], &size); err != nil {
132 return "", err
133 }
134 state, err := windows.WaitForSingleObject(process, 0)
135 if err != nil || state != uint32(windows.WAIT_TIMEOUT) {
136 return "", fmt.Errorf("desktop endpoint process exited")
137 }
138 var written uint32
139 if err := windows.WriteFile(pipe, []byte{1}, &written, nil); err != nil {
140 return "", fmt.Errorf("desktop endpoint closed during inspection: %w", err)
141 }
142 if written != 1 {
143 return "", fmt.Errorf("desktop endpoint acknowledgement: %w", io.ErrShortWrite)
144 }
145 return windows.UTF16ToString(buffer[:size]), nil
146 }
147
147 lines GO