feat(server): add Client.Credentials for peer PID, UID and GID
- commit
- ef8c520d36c9e462928b1740044251691041825a
- author
- brice · · 2026-09-30 09:02 UTC
- committer
- brice · · 2026-09-30 14:46 UTC
- parent
- d4696de
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +64 -9
M server/display.go +29 -7
| @@ -9,6 +9,7 @@ import ( | |||
| 9 | 9 | "sync/atomic" | |
| 10 | 10 | "unsafe" | |
| 11 | 11 | ||
| 12 | + | "github.com/bnema/purego" | |
| 12 | 13 | "golang.org/x/sys/unix" | |
| 13 | 14 | ) | |
| 14 | 15 | ||
| @@ -234,16 +235,37 @@ func (d *Display) Stopped() <-chan struct{} { return d.stopped } | |||
| 234 | 235 | // LiveResources reports resources not yet destroyed. Display goroutine only. | |
| 235 | 236 | func LiveResources() int { return len(live.resources) } | |
| 236 | 237 | ||
| 237 | - | // PID is the process ID of the client, read from the socket credentials | |
| 238 | - | // (SO_PEERCRED) when it connected. Zero for an invalid client. | |
| 239 | - | // Display goroutine only. | |
| 240 | - | func (c Client) PID() int { | |
| 238 | + | // Credentials are the peer credentials of a client, read from the socket | |
| 239 | + | // (SO_PEERCRED) when it connected. | |
| 240 | + | type Credentials struct { | |
| 241 | + | PID int | |
| 242 | + | UID uint32 | |
| 243 | + | GID uint32 | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | var credScratch [3]int32 | |
| 247 | + | ||
| 248 | + | var errInvalidClient = errors.New("purego-libwayland: invalid client") | |
| 249 | + | ||
| 250 | + | // Credentials returns the peer credentials of the client. It returns an error | |
| 251 | + | // for an invalid (zero) Client. It does not allocate. Display goroutine only. | |
| 252 | + | func (c Client) Credentials() (Credentials, error) { | |
| 241 | 253 | if c.c == 0 { | |
| 254 | + | return Credentials{}, errInvalidClient | |
| 255 | + | } | |
| 256 | + | // Package-level scratch (display goroutine only) avoids heap-escaping locals. | |
| 257 | + | purego.Syscall6(symClientGetCredentials, c.c, uintptr(unsafe.Pointer(&credScratch[0])), uintptr(unsafe.Pointer(&credScratch[1])), uintptr(unsafe.Pointer(&credScratch[2])), 0, 0) | |
| 258 | + | return Credentials{PID: int(credScratch[0]), UID: uint32(credScratch[1]), GID: uint32(credScratch[2])}, nil | |
| 259 | + | } | |
| 260 | + | ||
| 261 | + | // PID is the process ID of the client, from Credentials. Zero for an invalid | |
| 262 | + | // client. Display goroutine only. | |
| 263 | + | func (c Client) PID() int { | |
| 264 | + | cr, err := c.Credentials() | |
| 265 | + | if err != nil { | |
| 242 | 266 | return 0 | |
| 243 | 267 | } | |
| 244 | - | var pid, uid, gid int32 | |
| 245 | - | wlClientGetCredentials(c.c, unsafe.Pointer(&pid), unsafe.Pointer(&uid), unsafe.Pointer(&gid)) | |
| 246 | - | return int(pid) | |
| 268 | + | return cr.PID | |
| 247 | 269 | } | |
| 248 | 270 | ||
| 249 | 271 | func (c Client) CreateResource(iface *Interface, version int32, id uint32, h Handler) (*Resource, error) { | |
| @@ -9,6 +9,7 @@ import ( | |||||
| 9 | "sync/atomic" | 9 | "sync/atomic" | ||
| 10 | "unsafe" | 10 | "unsafe" | ||
| 11 | 11 | ||||
| 12 | + | "github.com/bnema/purego" | |||
| 12 | "golang.org/x/sys/unix" | 13 | "golang.org/x/sys/unix" | ||
| 13 | ) | 14 | ) | ||
| 14 | 15 | ||||
| @@ -234,16 +235,37 @@ func (d *Display) Stopped() <-chan struct{} { return d.stopped } | |||||
| 234 | // LiveResources reports resources not yet destroyed. Display goroutine only. | 235 | // LiveResources reports resources not yet destroyed. Display goroutine only. | ||
| 235 | func LiveResources() int { return len(live.resources) } | 236 | func LiveResources() int { return len(live.resources) } | ||
| 236 | 237 | ||||
| 237 | - | // PID is the process ID of the client, read from the socket credentials | 238 | + | // Credentials are the peer credentials of a client, read from the socket |
| 238 | - | // (SO_PEERCRED) when it connected. Zero for an invalid client. | 239 | + | // (SO_PEERCRED) when it connected. |
| 239 | - | // Display goroutine only. | 240 | + | type Credentials struct { |
| 240 | - | func (c Client) PID() int { | 241 | + | PID int |
| 242 | + | UID uint32 | |||
| 243 | + | GID uint32 | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | var credScratch [3]int32 | |||
| 247 | + | ||||
| 248 | + | var errInvalidClient = errors.New("purego-libwayland: invalid client") | |||
| 249 | + | ||||
| 250 | + | // Credentials returns the peer credentials of the client. It returns an error | |||
| 251 | + | // for an invalid (zero) Client. It does not allocate. Display goroutine only. | |||
| 252 | + | func (c Client) Credentials() (Credentials, error) { | |||
| 241 | if c.c == 0 { | 253 | if c.c == 0 { | ||
| 254 | + | return Credentials{}, errInvalidClient | |||
| 255 | + | } | |||
| 256 | + | // Package-level scratch (display goroutine only) avoids heap-escaping locals. | |||
| 257 | + | purego.Syscall6(symClientGetCredentials, c.c, uintptr(unsafe.Pointer(&credScratch[0])), uintptr(unsafe.Pointer(&credScratch[1])), uintptr(unsafe.Pointer(&credScratch[2])), 0, 0) | |||
| 258 | + | return Credentials{PID: int(credScratch[0]), UID: uint32(credScratch[1]), GID: uint32(credScratch[2])}, nil | |||
| 259 | + | } | |||
| 260 | + | ||||
| 261 | + | // PID is the process ID of the client, from Credentials. Zero for an invalid | |||
| 262 | + | // client. Display goroutine only. | |||
| 263 | + | func (c Client) PID() int { | |||
| 264 | + | cr, err := c.Credentials() | |||
| 265 | + | if err != nil { | |||
| 242 | return 0 | 266 | return 0 | ||
| 243 | } | 267 | } | ||
| 244 | - | var pid, uid, gid int32 | 268 | + | return cr.PID |
| 245 | - | wlClientGetCredentials(c.c, unsafe.Pointer(&pid), unsafe.Pointer(&uid), unsafe.Pointer(&gid)) | |||
| 246 | - | return int(pid) | |||
| 247 | } | 269 | } | ||
| 248 | 270 | ||||
| 249 | func (c Client) CreateResource(iface *Interface, version int32, id uint32, h Handler) (*Resource, error) { | 271 | func (c Client) CreateResource(iface *Interface, version int32, id uint32, h Handler) (*Resource, error) { | ||
M server/hotpath_test.go +33 -0
| @@ -2,6 +2,7 @@ package server | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | + | "os" | |
| 5 | 6 | "testing" | |
| 6 | 7 | "unsafe" | |
| 7 | 8 | ||
| @@ -134,3 +135,35 @@ func BenchmarkPostEvent(b *testing.B) { | |||
| 134 | 135 | b.Fatal("display stopped") | |
| 135 | 136 | } | |
| 136 | 137 | } | |
| 138 | + | ||
| 139 | + | func TestClientCredentials(t *testing.T) { | |
| 140 | + | iface := &Interface{Name: "cred_test", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}, Events: []Message{{Name: "event", Signature: "uuu"}}} | |
| 141 | + | d, r := hotDisplay(t, iface, hotHandler) | |
| 142 | + | var ( | |
| 143 | + | got, zero Credentials | |
| 144 | + | err, zerr error | |
| 145 | + | allocs float64 | |
| 146 | + | pid int | |
| 147 | + | ) | |
| 148 | + | c := r.Client() | |
| 149 | + | if !d.Do(func() { | |
| 150 | + | got, err = c.Credentials() | |
| 151 | + | zero, zerr = Client{}.Credentials() | |
| 152 | + | pid = c.PID() | |
| 153 | + | allocs = testing.AllocsPerRun(100, func() { _, _ = c.Credentials() }) | |
| 154 | + | }) { | |
| 155 | + | t.Fatal("display stopped") | |
| 156 | + | } | |
| 157 | + | if err != nil { | |
| 158 | + | t.Fatal(err) | |
| 159 | + | } | |
| 160 | + | if got.PID != os.Getpid() || got.UID != uint32(os.Getuid()) || got.GID != uint32(os.Getgid()) || pid != got.PID { | |
| 161 | + | t.Fatalf("credentials %+v pid %d, want pid=%d uid=%d gid=%d", got, pid, os.Getpid(), os.Getuid(), os.Getgid()) | |
| 162 | + | } | |
| 163 | + | if zerr == nil || zero != (Credentials{}) || (Client{}).PID() != 0 { | |
| 164 | + | t.Fatalf("invalid client: %+v, %v", zero, zerr) | |
| 165 | + | } | |
| 166 | + | if allocs != 0 { | |
| 167 | + | t.Fatalf("Credentials allocs = %g, want 0", allocs) | |
| 168 | + | } | |
| 169 | + | } | |
| @@ -2,6 +2,7 @@ package server | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | + | "os" | |||
| 5 | "testing" | 6 | "testing" | ||
| 6 | "unsafe" | 7 | "unsafe" | ||
| 7 | 8 | ||||
| @@ -134,3 +135,35 @@ func BenchmarkPostEvent(b *testing.B) { | |||||
| 134 | b.Fatal("display stopped") | 135 | b.Fatal("display stopped") | ||
| 135 | } | 136 | } | ||
| 136 | } | 137 | } | ||
| 138 | + | ||||
| 139 | + | func TestClientCredentials(t *testing.T) { | |||
| 140 | + | iface := &Interface{Name: "cred_test", Version: 1, Requests: []Message{{Name: "request", Signature: "uuu"}}, Events: []Message{{Name: "event", Signature: "uuu"}}} | |||
| 141 | + | d, r := hotDisplay(t, iface, hotHandler) | |||
| 142 | + | var ( | |||
| 143 | + | got, zero Credentials | |||
| 144 | + | err, zerr error | |||
| 145 | + | allocs float64 | |||
| 146 | + | pid int | |||
| 147 | + | ) | |||
| 148 | + | c := r.Client() | |||
| 149 | + | if !d.Do(func() { | |||
| 150 | + | got, err = c.Credentials() | |||
| 151 | + | zero, zerr = Client{}.Credentials() | |||
| 152 | + | pid = c.PID() | |||
| 153 | + | allocs = testing.AllocsPerRun(100, func() { _, _ = c.Credentials() }) | |||
| 154 | + | }) { | |||
| 155 | + | t.Fatal("display stopped") | |||
| 156 | + | } | |||
| 157 | + | if err != nil { | |||
| 158 | + | t.Fatal(err) | |||
| 159 | + | } | |||
| 160 | + | if got.PID != os.Getpid() || got.UID != uint32(os.Getuid()) || got.GID != uint32(os.Getgid()) || pid != got.PID { | |||
| 161 | + | t.Fatalf("credentials %+v pid %d, want pid=%d uid=%d gid=%d", got, pid, os.Getpid(), os.Getuid(), os.Getgid()) | |||
| 162 | + | } | |||
| 163 | + | if zerr == nil || zero != (Credentials{}) || (Client{}).PID() != 0 { | |||
| 164 | + | t.Fatalf("invalid client: %+v, %v", zero, zerr) | |||
| 165 | + | } | |||
| 166 | + | if allocs != 0 { | |||
| 167 | + | t.Fatalf("Credentials allocs = %g, want 0", allocs) | |||
| 168 | + | } | |||
| 169 | + | } | |||
M server/lib.go +2 -2
| @@ -22,7 +22,6 @@ var ( | |||
| 22 | 22 | wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr | |
| 23 | 23 | wlGlobalRemove func(global uintptr) | |
| 24 | 24 | wlResourcePostError func(resource uintptr, code uint32, msg uintptr) | |
| 25 | - | wlClientGetCredentials func(client uintptr, pid, uid, gid unsafe.Pointer) | |
| 26 | 25 | ) | |
| 27 | 26 | ||
| 28 | 27 | var ( | |
| @@ -31,6 +30,7 @@ var ( | |||
| 31 | 30 | ||
| 32 | 31 | symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr | |
| 33 | 32 | symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr | |
| 33 | + | symClientGetCredentials uintptr | |
| 34 | 34 | symErrnoLocation uintptr | |
| 35 | 35 | // wl_fixes support (libwayland 1.26+); zero on older libraries. | |
| 36 | 36 | symFixesAckGlobalRemove uintptr | |
| @@ -67,7 +67,6 @@ func load() error { | |||
| 67 | 67 | reg(&wlGlobalCreate, "wl_global_create") | |
| 68 | 68 | reg(&wlGlobalRemove, "wl_global_remove") | |
| 69 | 69 | reg(&wlResourcePostError, "wl_resource_post_error") | |
| 70 | - | reg(&wlClientGetCredentials, "wl_client_get_credentials") | |
| 71 | 70 | for _, entry := range []struct { | |
| 72 | 71 | name string | |
| 73 | 72 | sym *uintptr | |
| @@ -76,6 +75,7 @@ func load() error { | |||
| 76 | 75 | {"wl_event_loop_dispatch", &symEventLoopDispatch}, | |
| 77 | 76 | {"wl_resource_create", &symResourceCreate}, | |
| 78 | 77 | {"wl_resource_get_id", &symResourceGetID}, | |
| 78 | + | {"wl_client_get_credentials", &symClientGetCredentials}, | |
| 79 | 79 | {"wl_resource_set_dispatcher", &symResourceSetDispatcher}, | |
| 80 | 80 | {"wl_resource_post_event_array", &symResourcePostEventArr}, | |
| 81 | 81 | {"wl_resource_destroy", &symResourceDestroy}, | |
| @@ -22,7 +22,6 @@ var ( | |||||
| 22 | wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr | 22 | wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr | ||
| 23 | wlGlobalRemove func(global uintptr) | 23 | wlGlobalRemove func(global uintptr) | ||
| 24 | wlResourcePostError func(resource uintptr, code uint32, msg uintptr) | 24 | wlResourcePostError func(resource uintptr, code uint32, msg uintptr) | ||
| 25 | - | wlClientGetCredentials func(client uintptr, pid, uid, gid unsafe.Pointer) | |||
| 26 | ) | 25 | ) | ||
| 27 | 26 | ||||
| 28 | var ( | 27 | var ( | ||
| @@ -31,6 +30,7 @@ var ( | |||||
| 31 | 30 | ||||
| 32 | symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr | 31 | symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr | ||
| 33 | symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr | 32 | symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr | ||
| 33 | + | symClientGetCredentials uintptr | |||
| 34 | symErrnoLocation uintptr | 34 | symErrnoLocation uintptr | ||
| 35 | // wl_fixes support (libwayland 1.26+); zero on older libraries. | 35 | // wl_fixes support (libwayland 1.26+); zero on older libraries. | ||
| 36 | symFixesAckGlobalRemove uintptr | 36 | symFixesAckGlobalRemove uintptr | ||
| @@ -67,7 +67,6 @@ func load() error { | |||||
| 67 | reg(&wlGlobalCreate, "wl_global_create") | 67 | reg(&wlGlobalCreate, "wl_global_create") | ||
| 68 | reg(&wlGlobalRemove, "wl_global_remove") | 68 | reg(&wlGlobalRemove, "wl_global_remove") | ||
| 69 | reg(&wlResourcePostError, "wl_resource_post_error") | 69 | reg(&wlResourcePostError, "wl_resource_post_error") | ||
| 70 | - | reg(&wlClientGetCredentials, "wl_client_get_credentials") | |||
| 71 | for _, entry := range []struct { | 70 | for _, entry := range []struct { | ||
| 72 | name string | 71 | name string | ||
| 73 | sym *uintptr | 72 | sym *uintptr | ||
| @@ -76,6 +75,7 @@ func load() error { | |||||
| 76 | {"wl_event_loop_dispatch", &symEventLoopDispatch}, | 75 | {"wl_event_loop_dispatch", &symEventLoopDispatch}, | ||
| 77 | {"wl_resource_create", &symResourceCreate}, | 76 | {"wl_resource_create", &symResourceCreate}, | ||
| 78 | {"wl_resource_get_id", &symResourceGetID}, | 77 | {"wl_resource_get_id", &symResourceGetID}, | ||
| 78 | + | {"wl_client_get_credentials", &symClientGetCredentials}, | |||
| 79 | {"wl_resource_set_dispatcher", &symResourceSetDispatcher}, | 79 | {"wl_resource_set_dispatcher", &symResourceSetDispatcher}, | ||
| 80 | {"wl_resource_post_event_array", &symResourcePostEventArr}, | 80 | {"wl_resource_post_event_array", &symResourcePostEventArr}, | ||
| 81 | {"wl_resource_destroy", &symResourceDestroy}, | 81 | {"wl_resource_destroy", &symResourceDestroy}, | ||