carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package registry
2
3import (
4 "context"
5 "database/sql"
6 "encoding/gob"
7 "errors"
8 "fmt"
9 "io"
10 "os"
11 "sync"
12 "time"
13)
14
15var errRemoteMethod = errors.New("registry: unsupported remote operation")
16
17var remoteErrors = []error{os.ErrNotExist, os.ErrPermission, sql.ErrNoRows, ErrClaimOwnership, ErrTimerChanged, ErrTimerKind, ErrPathsChanged, ErrFleetBoxActive, context.Canceled, context.DeadlineExceeded, ErrGoodBuildChanged}
18
19func responseError(out remoteResponse) error {
20 if !out.Failed {
21 return nil
22 }
23 if out.ErrorKind > 0 && out.ErrorKind <= len(remoteErrors) {
24 sentinel := remoteErrors[out.ErrorKind-1]
25 if out.Error == sentinel.Error() {
26 return sentinel
27 }
28 return fmt.Errorf("%s: %w", out.Error, sentinel)
29 }
30 return errors.New(out.Error)
31}
32
33func setResponseError(out *remoteResponse, err error) {
34 if err == nil {
35 return
36 }
37 out.Failed = true
38 out.Error = err.Error()
39 for i, sentinel := range remoteErrors {
40 if errors.Is(err, sentinel) {
41 out.ErrorKind = i + 1
42 break
43 }
44 }
45}
46
47type remoteClient struct {
48 mu sync.Mutex
49 enc *gob.Encoder
50 dec *gob.Decoder
51 input io.WriteCloser
52 output io.ReadCloser
53 wait func() error
54 closed bool
55 connect func() (io.WriteCloser, io.ReadCloser, func() error, error)
56}
57
58// OpenRemote connects to an already privilege-dropped registry worker.
59func OpenRemote(path string, input io.WriteCloser, output io.ReadCloser, wait func() error) (*Registry, error) {
60 c := &remoteClient{}
61 if err := c.attach(input, output, wait); err != nil {
62 return nil, err
63 }
64 return &Registry{path: path, remote: c}, nil
65}
66
67// OpenRemoteReconnect reads service-controlled answers, not an authority boundary.
68// Failed or cancelled mutations are never retried; the next call reconnects.
69// Canceled does not mean not applied: cancellation can race a committed mutation.
70func OpenRemoteReconnect(path string, connect func() (io.WriteCloser, io.ReadCloser, func() error, error)) (*Registry, error) {
71 c := &remoteClient{connect: connect}
72 if err := c.reconnect(context.Background()); err != nil {
73 return nil, err
74 }
75 return &Registry{path: path, remote: c}, nil
76}
77
78func (c *remoteClient) reconnect(ctx context.Context) (err error) {
79 input, output, wait, err := c.connect()
80 if err != nil {
81 return err
82 }
83 finished := make(chan struct{})
84 stop := context.AfterFunc(ctx, func() { input.Close(); output.Close(); close(finished) })
85 defer func() {
86 if !stop() {
87 <-finished
88 }
89 if ctx.Err() != nil {
90 c.disconnect()
91 err = ctx.Err()
92 }
93 }()
94 return c.attach(input, output, wait)
95}
96
97func (c *remoteClient) attach(input io.WriteCloser, output io.ReadCloser, wait func() error) error {
98 c.input, c.output, c.wait = input, output, wait
99 c.enc, c.dec = gob.NewEncoder(input), gob.NewDecoder(output)
100 var ready remoteResponse
101 err := c.dec.Decode(&ready)
102 if err == nil {
103 err = responseError(ready)
104 }
105 if err != nil {
106 c.disconnect()
107 }
108 return err
109}
110
111func (c *remoteClient) disconnect() error {
112 if c.input == nil {
113 return nil
114 }
115 c.input.Close()
116 c.output.Close()
117 c.input, c.output = nil, nil
118 if c.wait != nil {
119 return c.wait()
120 }
121 return nil
122}
123
124func (c *remoteClient) stop() error {
125 if c.closed {
126 return nil
127 }
128 c.closed = true
129 return c.disconnect()
130}
131
132func (c *remoteClient) close() error {
133 c.mu.Lock()
134 defer c.mu.Unlock()
135 return c.stop()
136}
137
138func (c *remoteClient) call(ctx context.Context, q remoteRequest, callback func(remoteResponse) error) (out remoteResponse, err error) {
139 c.mu.Lock()
140 defer c.mu.Unlock()
141 if c.closed {
142 return out, os.ErrClosed
143 }
144 if ctx != nil {
145 if err = ctx.Err(); err != nil {
146 return
147 }
148 if deadline, ok := ctx.Deadline(); ok {
149 q.Deadline = deadline.UnixNano()
150 }
151 }
152 if c.input == nil {
153 if c.connect == nil {
154 return out, os.ErrClosed
155 }
156 reconnectContext := ctx
157 if reconnectContext == nil {
158 reconnectContext = context.Background()
159 }
160 if err = c.reconnect(reconnectContext); err != nil {
161 return
162 }
163 }
164 // Closing the pipe cancels the worker context, including an in-flight SQL call.
165 if ctx != nil {
166 input, output := c.input, c.output
167 stopped := make(chan struct{})
168 stop := context.AfterFunc(ctx, func() { input.Close(); output.Close(); close(stopped) })
169 defer func() {
170 if !stop() {
171 <-stopped
172 }
173 if ctx.Err() != nil {
174 err = ctx.Err()
175 c.disconnect()
176 }
177 }()
178 }
179 // A callback panic or a broken pipe must release any child transaction.
180 complete := false
181 defer func() {
182 if !complete {
183 c.disconnect()
184 }
185 }()
186 if err = c.enc.Encode(q); err != nil {
187 return
188 }
189 if err = c.dec.Decode(&out); err != nil {
190 return
191 }
192 var callbackErr error
193 if out.Callback != "" {
194 if callback == nil {
195 return out, errRemoteMethod
196 }
197 callbackErr = callback(out)
198 if ctx != nil && ctx.Err() != nil {
199 return out, ctx.Err()
200 }
201 ack := remoteRequest{Method: "callback"}
202 if callbackErr != nil {
203 ack.CallbackFailed = true
204 ack.CallbackError = callbackErr.Error()
205 }
206 if err = c.enc.Encode(ack); err != nil {
207 return
208 }
209 out = remoteResponse{}
210 if err = c.dec.Decode(&out); err != nil {
211 return
212 }
213 if out.Callback != "" {
214 return out, errRemoteMethod
215 }
216 }
217 complete = true
218 if callbackErr != nil {
219 return out, callbackErr
220 }
221 return out, responseError(out)
222}
223
224// ServeRemote owns SQLite in the worker. Callers must drop credentials first.
225func ServeRemote(path string, input io.Reader, output io.Writer) error {
226 return serveRemote(path, input, output, false)
227}
228
229func ServeRemoteReadOnly(path string, input io.Reader, output io.Writer) error {
230 return serveRemote(path, input, output, true)
231}
232
233func serveRemote(path string, input io.Reader, output io.Writer, readOnly bool) error {
234 if os.Geteuid() == 0 {
235 return errors.New("registry: remote worker refuses root")
236 }
237 enc, dec := gob.NewEncoder(output), gob.NewDecoder(input)
238 var r *Registry
239 var err error
240 if readOnly {
241 r, err = OpenReadOnly(path)
242 } else {
243 r, err = Open(path)
244 }
245 ready := remoteResponse{}
246 setResponseError(&ready, err)
247 if writeErr := enc.Encode(ready); writeErr != nil {
248 if r != nil {
249 r.Close()
250 }
251 return writeErr
252 }
253 if err != nil {
254 return err
255 }
256 defer r.Close()
257 workerCtx, stopWorker := context.WithCancel(context.Background())
258 defer stopWorker()
259 requests := make(chan remoteRequest)
260 readErrors := make(chan error, 1)
261 go func() {
262 defer close(requests)
263 defer stopWorker()
264 for {
265 var q remoteRequest
266 if err := dec.Decode(&q); err != nil {
267 readErrors <- err
268 return
269 }
270 select {
271 case requests <- q:
272 case <-workerCtx.Done():
273 return
274 }
275 }
276 }()
277 for q := range requests {
278 if readOnly && q.Method != "Get" && q.Method != "TimersFor" && q.Method != "ArtifactHosts" {
279 out := remoteResponse{}
280 setResponseError(&out, errRemoteMethod)
281 if err := enc.Encode(out); err != nil {
282 return err
283 }
284 continue
285 }
286 ctx := workerCtx
287 cancel := func() {}
288 if q.Deadline != 0 {
289 ctx, cancel = context.WithDeadline(ctx, time.Unix(0, q.Deadline))
290 }
291 out, callErr := r.dispatchRemote(ctx, q, func(event remoteResponse) error {
292 if err := enc.Encode(event); err != nil {
293 return err
294 }
295 ack, ok := <-requests
296 if !ok {
297 return workerCtx.Err()
298 }
299 if ack.Method != "callback" {
300 return errRemoteMethod
301 }
302 if ack.CallbackFailed {
303 return errors.New(ack.CallbackError)
304 }
305 return nil
306 })
307 cancel()
308 setResponseError(&out, callErr)
309 if err = enc.Encode(out); err != nil {
310 return err
311 }
312 }
313 select {
314 case err := <-readErrors:
315 if errors.Is(err, io.EOF) {
316 return nil
317 }
318 return err
319 default:
320 return workerCtx.Err()
321 }
322
323}
324