fix(core): dispatch bootstrap registry and retain FD owner across handlers
- commit
- c47d0a5a9bd5a9ab455587c7461f2c9de35cc81e
- author
- brice · · 2026-09-28 07:23 UTC
- parent
- 7576621
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +104 -3
M client.go +19 -0
| @@ -454,6 +454,25 @@ func (d *Display) dispatchFrame(f receivedFrame) error { | |||
| 454 | 454 | } | |
| 455 | 455 | return d.handleDisplayEvent(f.opcode, f.body) | |
| 456 | 456 | } | |
| 457 | + | if f.object == d.registry.id { | |
| 458 | + | var sig string | |
| 459 | + | switch f.opcode { | |
| 460 | + | case 0: | |
| 461 | + | sig = "uint,string,uint," | |
| 462 | + | case 1: | |
| 463 | + | sig = "uint," | |
| 464 | + | default: | |
| 465 | + | return &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode} | |
| 466 | + | } | |
| 467 | + | if err := validateEventBody(sig, f.body); err != nil { | |
| 468 | + | return &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err} | |
| 469 | + | } | |
| 470 | + | if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 { | |
| 471 | + | return &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |
| 472 | + | } | |
| 473 | + | d.notifyListeners(f.object, f.opcode, f.body) | |
| 474 | + | return nil | |
| 475 | + | } | |
| 457 | 476 | obj, ok := d.objects.Load(f.object) | |
| 458 | 477 | if !ok { | |
| 459 | 478 | return &ProtocolError{Kind: "unknown_object", Object: f.object, Opcode: f.opcode, Err: ErrUnknownObject} | |
| @@ -454,6 +454,25 @@ func (d *Display) dispatchFrame(f receivedFrame) error { | |||||
| 454 | } | 454 | } | ||
| 455 | return d.handleDisplayEvent(f.opcode, f.body) | 455 | return d.handleDisplayEvent(f.opcode, f.body) | ||
| 456 | } | 456 | } | ||
| 457 | + | if f.object == d.registry.id { | |||
| 458 | + | var sig string | |||
| 459 | + | switch f.opcode { | |||
| 460 | + | case 0: | |||
| 461 | + | sig = "uint,string,uint," | |||
| 462 | + | case 1: | |||
| 463 | + | sig = "uint," | |||
| 464 | + | default: | |||
| 465 | + | return &ProtocolError{Kind: "unknown_opcode", Object: f.object, Opcode: f.opcode, Err: ErrUnknownOpcode} | |||
| 466 | + | } | |||
| 467 | + | if err := validateEventBody(sig, f.body); err != nil { | |||
| 468 | + | return &ProtocolError{Kind: "malformed_payload", Object: f.object, Opcode: f.opcode, Err: err} | |||
| 469 | + | } | |||
| 470 | + | if len(d.pendingFDs) != 0 && len(d.rbuf) == 0 { | |||
| 471 | + | return &ProtocolError{Kind: "extra_fd", Object: f.object, Opcode: f.opcode, Err: ErrMalformedFrame} | |||
| 472 | + | } | |||
| 473 | + | d.notifyListeners(f.object, f.opcode, f.body) | |||
| 474 | + | return nil | |||
| 475 | + | } | |||
| 457 | obj, ok := d.objects.Load(f.object) | 476 | obj, ok := d.objects.Load(f.object) | ||
| 458 | if !ok { | 477 | if !ok { | ||
| 459 | return &ProtocolError{Kind: "unknown_object", Object: f.object, Opcode: f.opcode, Err: ErrUnknownObject} | 478 | return &ProtocolError{Kind: "unknown_object", Object: f.object, Opcode: f.opcode, Err: ErrUnknownObject} | ||
M internal/scanner/template.go +2 -1
| @@ -119,7 +119,8 @@ func (o *{{$iface.GoName}}) Dispatch(event *wl.Event) { | |||
| 119 | 119 | {{- range .DecodeLines}} | |
| 120 | 120 | {{.}} | |
| 121 | 121 | {{- end}} | |
| 122 | - | {{if .HasFD}}defer func(){ {{range .FDNames}}_ = {{.}}.Close(); {{end}} }(){{end}} | |
| 122 | + | {{if .HasFD}}{{range .FDNames}}owned{{.}} := {{.}}; defer owned{{.}}.Close() | |
| 123 | + | {{end}}{{end}} | |
| 123 | 124 | for i, handler := range o.{{.HandlerAccessor}}() { | |
| 124 | 125 | {{- if .HasFD}} | |
| 125 | 126 | if i > 0 { {{range .FDNames}}{{.}} = nil; {{end}} } | |
| @@ -119,7 +119,8 @@ func (o *{{$iface.GoName}}) Dispatch(event *wl.Event) { | |||||
| 119 | {{- range .DecodeLines}} | 119 | {{- range .DecodeLines}} | ||
| 120 | {{.}} | 120 | {{.}} | ||
| 121 | {{- end}} | 121 | {{- end}} | ||
| 122 | - | {{if .HasFD}}defer func(){ {{range .FDNames}}_ = {{.}}.Close(); {{end}} }(){{end}} | 122 | + | {{if .HasFD}}{{range .FDNames}}owned{{.}} := {{.}}; defer owned{{.}}.Close() |
| 123 | + | {{end}}{{end}} | |||
| 123 | for i, handler := range o.{{.HandlerAccessor}}() { | 124 | for i, handler := range o.{{.HandlerAccessor}}() { | ||
| 124 | {{- if .HasFD}} | 125 | {{- if .HasFD}} | ||
| 125 | if i > 0 { {{range .FDNames}}{{.}} = nil; {{end}} } | 126 | if i > 0 { {{range .FDNames}}{{.}} = nil; {{end}} } | ||
M protocol/core/core_test.go +77 -0
| @@ -322,3 +322,80 @@ func TestServerChildEventsAfterOffer(t *testing.T) { | |||
| 322 | 322 | t.Fatalf("typed child event: %q", got) | |
| 323 | 323 | } | |
| 324 | 324 | } | |
| 325 | + | ||
| 326 | + | func TestRegistryBootstrapAnnouncements(t *testing.T) { | |
| 327 | + | c, p := pair(t) | |
| 328 | + | d, e := wlturbo.ConnectFromConn(c) | |
| 329 | + | if e != nil { | |
| 330 | + | t.Fatal(e) | |
| 331 | + | } | |
| 332 | + | defer d.Close() | |
| 333 | + | receiveRequest(t, p) | |
| 334 | + | var got wlturbo.Global | |
| 335 | + | d.Registry().AddHandler("wl_compositor", func(r *wlturbo.Registry, n, v uint32) { got, _ = r.FindGlobalByName(n) }) | |
| 336 | + | name := "wl_compositor" | |
| 337 | + | body := make([]byte, 4+4+((len(name)+1+3)&^3)+4) | |
| 338 | + | binary.LittleEndian.PutUint32(body, 17) | |
| 339 | + | binary.LittleEndian.PutUint32(body[4:], uint32(len(name)+1)) | |
| 340 | + | copy(body[8:], name) | |
| 341 | + | binary.LittleEndian.PutUint32(body[len(body)-4:], 7) | |
| 342 | + | msg := append(frame(d.Registry().ID(), 0), body...) | |
| 343 | + | binary.LittleEndian.PutUint32(msg[4:], uint32(len(msg))<<16) | |
| 344 | + | send(t, p, msg) | |
| 345 | + | if e := d.Dispatch(); e != nil { | |
| 346 | + | t.Fatal(e) | |
| 347 | + | } | |
| 348 | + | if got.Name != 17 || got.Interface != name || got.Version != 7 { | |
| 349 | + | t.Fatalf("global: %+v", got) | |
| 350 | + | } | |
| 351 | + | send(t, p, frame(d.Registry().ID(), 1, 17)) | |
| 352 | + | if e := d.Dispatch(); e != nil { | |
| 353 | + | t.Fatal(e) | |
| 354 | + | } | |
| 355 | + | if _, ok := d.Registry().FindGlobalByName(17); ok { | |
| 356 | + | t.Fatal("global not removed") | |
| 357 | + | } | |
| 358 | + | send(t, p, frame(d.Registry().ID(), 2)) | |
| 359 | + | if e := d.Dispatch(); !errors.Is(e, wlturbo.ErrUnknownOpcode) { | |
| 360 | + | t.Fatalf("unknown registry opcode: %v", e) | |
| 361 | + | } | |
| 362 | + | } | |
| 363 | + | func TestUnclaimedFirstHandlerFDClosed(t *testing.T) { | |
| 364 | + | c, p := pair(t) | |
| 365 | + | d, e := wlturbo.ConnectFromConn(c) | |
| 366 | + | if e != nil { | |
| 367 | + | t.Fatal(e) | |
| 368 | + | } | |
| 369 | + | defer d.Close() | |
| 370 | + | receiveRequest(t, p) | |
| 371 | + | kb := core.NewKeyboard(d.Context()) | |
| 372 | + | kb.SetID(d.AllocateID()) | |
| 373 | + | d.Context().Register(kb) | |
| 374 | + | kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) { | |
| 375 | + | if fd == nil { | |
| 376 | + | t.Fatal("missing first owner") | |
| 377 | + | } | |
| 378 | + | }) | |
| 379 | + | kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) { | |
| 380 | + | if fd != nil { | |
| 381 | + | t.Fatal("duplicate owner") | |
| 382 | + | } | |
| 383 | + | }) | |
| 384 | + | r, w, e := os.Pipe() | |
| 385 | + | if e != nil { | |
| 386 | + | t.Fatal(e) | |
| 387 | + | } | |
| 388 | + | defer r.Close() | |
| 389 | + | send(t, p, frame(kb.ID(), 0, 1, 8), int(w.Fd())) | |
| 390 | + | w.Close() | |
| 391 | + | if e := d.Dispatch(); e != nil { | |
| 392 | + | t.Fatal(e) | |
| 393 | + | } | |
| 394 | + | if e := r.SetReadDeadline(testDeadline()); e != nil { | |
| 395 | + | t.Fatal(e) | |
| 396 | + | } | |
| 397 | + | var b [1]byte | |
| 398 | + | if n, e := r.Read(b[:]); n != 0 || e != io.EOF { | |
| 399 | + | t.Fatalf("unclaimed FD not closed: %d %v", n, e) | |
| 400 | + | } | |
| 401 | + | } | |
| @@ -322,3 +322,80 @@ func TestServerChildEventsAfterOffer(t *testing.T) { | |||||
| 322 | t.Fatalf("typed child event: %q", got) | 322 | t.Fatalf("typed child event: %q", got) | ||
| 323 | } | 323 | } | ||
| 324 | } | 324 | } | ||
| 325 | + | ||||
| 326 | + | func TestRegistryBootstrapAnnouncements(t *testing.T) { | |||
| 327 | + | c, p := pair(t) | |||
| 328 | + | d, e := wlturbo.ConnectFromConn(c) | |||
| 329 | + | if e != nil { | |||
| 330 | + | t.Fatal(e) | |||
| 331 | + | } | |||
| 332 | + | defer d.Close() | |||
| 333 | + | receiveRequest(t, p) | |||
| 334 | + | var got wlturbo.Global | |||
| 335 | + | d.Registry().AddHandler("wl_compositor", func(r *wlturbo.Registry, n, v uint32) { got, _ = r.FindGlobalByName(n) }) | |||
| 336 | + | name := "wl_compositor" | |||
| 337 | + | body := make([]byte, 4+4+((len(name)+1+3)&^3)+4) | |||
| 338 | + | binary.LittleEndian.PutUint32(body, 17) | |||
| 339 | + | binary.LittleEndian.PutUint32(body[4:], uint32(len(name)+1)) | |||
| 340 | + | copy(body[8:], name) | |||
| 341 | + | binary.LittleEndian.PutUint32(body[len(body)-4:], 7) | |||
| 342 | + | msg := append(frame(d.Registry().ID(), 0), body...) | |||
| 343 | + | binary.LittleEndian.PutUint32(msg[4:], uint32(len(msg))<<16) | |||
| 344 | + | send(t, p, msg) | |||
| 345 | + | if e := d.Dispatch(); e != nil { | |||
| 346 | + | t.Fatal(e) | |||
| 347 | + | } | |||
| 348 | + | if got.Name != 17 || got.Interface != name || got.Version != 7 { | |||
| 349 | + | t.Fatalf("global: %+v", got) | |||
| 350 | + | } | |||
| 351 | + | send(t, p, frame(d.Registry().ID(), 1, 17)) | |||
| 352 | + | if e := d.Dispatch(); e != nil { | |||
| 353 | + | t.Fatal(e) | |||
| 354 | + | } | |||
| 355 | + | if _, ok := d.Registry().FindGlobalByName(17); ok { | |||
| 356 | + | t.Fatal("global not removed") | |||
| 357 | + | } | |||
| 358 | + | send(t, p, frame(d.Registry().ID(), 2)) | |||
| 359 | + | if e := d.Dispatch(); !errors.Is(e, wlturbo.ErrUnknownOpcode) { | |||
| 360 | + | t.Fatalf("unknown registry opcode: %v", e) | |||
| 361 | + | } | |||
| 362 | + | } | |||
| 363 | + | func TestUnclaimedFirstHandlerFDClosed(t *testing.T) { | |||
| 364 | + | c, p := pair(t) | |||
| 365 | + | d, e := wlturbo.ConnectFromConn(c) | |||
| 366 | + | if e != nil { | |||
| 367 | + | t.Fatal(e) | |||
| 368 | + | } | |||
| 369 | + | defer d.Close() | |||
| 370 | + | receiveRequest(t, p) | |||
| 371 | + | kb := core.NewKeyboard(d.Context()) | |||
| 372 | + | kb.SetID(d.AllocateID()) | |||
| 373 | + | d.Context().Register(kb) | |||
| 374 | + | kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) { | |||
| 375 | + | if fd == nil { | |||
| 376 | + | t.Fatal("missing first owner") | |||
| 377 | + | } | |||
| 378 | + | }) | |||
| 379 | + | kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) { | |||
| 380 | + | if fd != nil { | |||
| 381 | + | t.Fatal("duplicate owner") | |||
| 382 | + | } | |||
| 383 | + | }) | |||
| 384 | + | r, w, e := os.Pipe() | |||
| 385 | + | if e != nil { | |||
| 386 | + | t.Fatal(e) | |||
| 387 | + | } | |||
| 388 | + | defer r.Close() | |||
| 389 | + | send(t, p, frame(kb.ID(), 0, 1, 8), int(w.Fd())) | |||
| 390 | + | w.Close() | |||
| 391 | + | if e := d.Dispatch(); e != nil { | |||
| 392 | + | t.Fatal(e) | |||
| 393 | + | } | |||
| 394 | + | if e := r.SetReadDeadline(testDeadline()); e != nil { | |||
| 395 | + | t.Fatal(e) | |||
| 396 | + | } | |||
| 397 | + | var b [1]byte | |||
| 398 | + | if n, e := r.Read(b[:]); n != 0 || e != io.EOF { | |||
| 399 | + | t.Fatalf("unclaimed FD not closed: %d %v", n, e) | |||
| 400 | + | } | |||
| 401 | + | } | |||
M protocol/core/wayland_generated.go +6 -2
| @@ -1432,7 +1432,9 @@ func (o *DataSource) Dispatch(event *wl.Event) { | |||
| 1432 | 1432 | case 1: // send | |
| 1433 | 1433 | mimeType := event.String() | |
| 1434 | 1434 | fd := event.FD() | |
| 1435 | - | defer func() { _ = fd.Close() }() | |
| 1435 | + | ownedfd := fd | |
| 1436 | + | defer ownedfd.Close() | |
| 1437 | + | ||
| 1436 | 1438 | for i, handler := range o.handlersForSend() { | |
| 1437 | 1439 | if i > 0 { | |
| 1438 | 1440 | fd = nil | |
| @@ -3174,7 +3176,9 @@ func (o *Keyboard) Dispatch(event *wl.Event) { | |||
| 3174 | 3176 | format := event.Uint32() | |
| 3175 | 3177 | fd := event.FD() | |
| 3176 | 3178 | size := event.Uint32() | |
| 3177 | - | defer func() { _ = fd.Close() }() | |
| 3179 | + | ownedfd := fd | |
| 3180 | + | defer ownedfd.Close() | |
| 3181 | + | ||
| 3178 | 3182 | for i, handler := range o.handlersForKeymap() { | |
| 3179 | 3183 | if i > 0 { | |
| 3180 | 3184 | fd = nil | |
| @@ -1432,7 +1432,9 @@ func (o *DataSource) Dispatch(event *wl.Event) { | |||||
| 1432 | case 1: // send | 1432 | case 1: // send | ||
| 1433 | mimeType := event.String() | 1433 | mimeType := event.String() | ||
| 1434 | fd := event.FD() | 1434 | fd := event.FD() | ||
| 1435 | - | defer func() { _ = fd.Close() }() | 1435 | + | ownedfd := fd |
| 1436 | + | defer ownedfd.Close() | |||
| 1437 | + | ||||
| 1436 | for i, handler := range o.handlersForSend() { | 1438 | for i, handler := range o.handlersForSend() { | ||
| 1437 | if i > 0 { | 1439 | if i > 0 { | ||
| 1438 | fd = nil | 1440 | fd = nil | ||
| @@ -3174,7 +3176,9 @@ func (o *Keyboard) Dispatch(event *wl.Event) { | |||||
| 3174 | format := event.Uint32() | 3176 | format := event.Uint32() | ||
| 3175 | fd := event.FD() | 3177 | fd := event.FD() | ||
| 3176 | size := event.Uint32() | 3178 | size := event.Uint32() | ||
| 3177 | - | defer func() { _ = fd.Close() }() | 3179 | + | ownedfd := fd |
| 3180 | + | defer ownedfd.Close() | |||
| 3181 | + | ||||
| 3178 | for i, handler := range o.handlersForKeymap() { | 3182 | for i, handler := range o.handlersForKeymap() { | ||
| 3179 | if i > 0 { | 3183 | if i > 0 { | ||
| 3180 | fd = nil | 3184 | fd = nil | ||