From 61972d61f650be2fb86eb2ead452b74c02b3e00f Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sun, 16 Nov 2025 01:23:16 +0900 Subject: internal/wayland: reimplement connect/bind code The old implementation is relocated to system/wayland/deprecated.go. Signed-off-by: Ophestra --- internal/wayland/conn.go | 100 +++++++++++++++++++++---------- internal/wayland/wayland-client-helper.c | 4 +- internal/wayland/wayland-client-helper.h | 9 ++- internal/wayland/wayland.go | 21 ++++++- internal/wayland/wayland_test.go | 20 +++++++ 5 files changed, 118 insertions(+), 36 deletions(-) (limited to 'internal/wayland') diff --git a/internal/wayland/conn.go b/internal/wayland/conn.go index addbc0d3..c861c05f 100644 --- a/internal/wayland/conn.go +++ b/internal/wayland/conn.go @@ -1,47 +1,85 @@ package wayland import ( + "errors" "os" "syscall" + + "hakurei.app/container/check" ) -func bindRawConn(done chan struct{}, rc syscall.RawConn, p, appID, instanceID string) ([2]int, error) { - var closeFds [2]int - if err := syscall.Pipe2(closeFds[0:], syscall.O_CLOEXEC); err != nil { - return closeFds, err - } +// SecurityContext holds resources associated with a Wayland security_context. +type SecurityContext struct { + // Pipe with its write end passed to security-context-v1. + closeFds [2]int +} - setupDone := make(chan error, 1) // does not block with c.done +// Close releases any resources held by [SecurityContext], and prevents further +// connections to its associated socket. +func (sc *SecurityContext) Close() error { + if sc == nil { + return os.ErrInvalid + } + return errors.Join( + syscall.Close(sc.closeFds[1]), + syscall.Close(sc.closeFds[0]), + ) +} - go func() { - if err := rc.Control(func(fd uintptr) { - // allow the Bind method to return after setup - setupDone <- bind(fd, p, appID, instanceID, uintptr(closeFds[1])) - close(setupDone) +// New creates a new security context on the Wayland display at displayPath +// and associates it with a new socket bound to bindPath. +// +// New does not attach a finalizer to the resulting [SecurityContext] struct. +// The caller is responsible for calling [SecurityContext.Close]. +// +// A non-nil error unwraps to concrete type [Error]. +func New(displayPath, bindPath *check.Absolute, appID, instanceID string) (*SecurityContext, error) { + // ensure bindPath is available + if f, err := os.Create(bindPath.String()); err != nil { + return nil, &Error{Cause: RHostCreate, Errno: err} + } else if err = f.Close(); err != nil { + return nil, &Error{Cause: RHostCreate, Errno: err} + } else if err = os.Remove(bindPath.String()); err != nil { + return nil, &Error{Cause: RHostCreate, Errno: err} + } - // keep socket alive until done is requested - <-done - }); err != nil { - setupDone <- err + if fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0); err != nil { + return nil, &Error{RHostSocket, err} + } else if err = syscall.Connect(fd, &syscall.SockaddrUnix{Name: displayPath.String()}); err != nil { + _ = syscall.Close(fd) + return nil, &Error{RHostConnect, err} + } else { + closeFds, bindErr := bindSecurityContext(fd, bindPath, appID, instanceID) + if bindErr != nil { + // do not leak the pipe and socket + err = errors.Join(bindErr, // already wrapped + syscall.Close(closeFds[1]), + syscall.Close(closeFds[0]), + syscall.Close(fd), + ) } - - // notify Close that rc.Control has returned - close(done) - }() - - // return write end of the pipe - return closeFds, <-setupDone + return &SecurityContext{closeFds}, err + } } -func bind(fd uintptr, p, appID, instanceID string, syncFd uintptr) error { - // ensure p is available - if f, err := os.Create(p); err != nil { - return err - } else if err = f.Close(); err != nil { - return err - } else if err = os.Remove(p); err != nil { - return err +// bindSecurityContext binds a socket associated to a security context created on serverFd, +// returning the pipe file descriptors used for security-context-v1 close_fd. +// +// A non-nil error unwraps to concrete type [Error]. +func bindSecurityContext(serverFd int, bindPath *check.Absolute, appID, instanceID string) ([2]int, error) { + // write end passed to security-context-v1 close_fd + var closeFds [2]int + if err := syscall.Pipe2(closeFds[0:], syscall.O_CLOEXEC); err != nil { + return closeFds, err } - return bindWaylandFd(p, fd, appID, instanceID, syncFd) + // returned error is already wrapped + if err := bindWaylandFd(bindPath.String(), uintptr(serverFd), appID, instanceID, uintptr(closeFds[1])); err != nil { + return closeFds, errors.Join(err, + syscall.Close(closeFds[1]), + syscall.Close(closeFds[0]), + ) + } else { + return closeFds, nil + } } diff --git a/internal/wayland/wayland-client-helper.c b/internal/wayland/wayland-client-helper.c index ccfbaf6c..bf7b3722 100644 --- a/internal/wayland/wayland-client-helper.c +++ b/internal/wayland/wayland-client-helper.c @@ -33,7 +33,7 @@ static const struct wl_registry_listener registry_listener = { hakurei_wayland_res hakurei_bind_wayland_fd( char *socket_path, - int fd, + int server_fd, const char *app_id, const char *instance_id, int close_fd) { @@ -47,7 +47,7 @@ hakurei_wayland_res hakurei_bind_wayland_fd( struct sockaddr_un sockaddr = {0}; struct wp_security_context_v1 *security_context; - display = wl_display_connect_to_fd(fd); + display = wl_display_connect_to_fd(server_fd); if (display == NULL) { res = HAKUREI_WAYLAND_CONNECT; goto out; diff --git a/internal/wayland/wayland-client-helper.h b/internal/wayland/wayland-client-helper.h index 9dd7c901..fc75a87b 100644 --- a/internal/wayland/wayland-client-helper.h +++ b/internal/wayland/wayland-client-helper.h @@ -14,11 +14,18 @@ typedef enum { HAKUREI_WAYLAND_BIND, /* listen failed, errno */ HAKUREI_WAYLAND_LISTEN, + + /* ensure pathname failed, implemented in conn.go */ + HAKUREI_WAYLAND_HOST_CREAT, + /* socket for host server failed, implemented in conn.go */ + HAKUREI_WAYLAND_HOST_SOCKET, + /* connect for host server failed, implemented in conn.go */ + HAKUREI_WAYLAND_HOST_CONNECT, } hakurei_wayland_res; hakurei_wayland_res hakurei_bind_wayland_fd( char *socket_path, - int fd, + int server_fd, const char *app_id, const char *instance_id, int close_fd); diff --git a/internal/wayland/wayland.go b/internal/wayland/wayland.go index a7cddf5a..ee8dfd1e 100644 --- a/internal/wayland/wayland.go +++ b/internal/wayland/wayland.go @@ -31,10 +31,10 @@ const ( ) type ( - // Res is the outcome of a call to hakurei_bind_wayland_fd. + // Res is the outcome of a call to [New]. Res = C.hakurei_wayland_res - // An Error represents a failure during hakurei_bind_wayland_fd. + // An Error represents a failure during [New]. Error struct { // Where the failure occurred. Cause Res @@ -68,6 +68,13 @@ const ( RBind Res = C.HAKUREI_WAYLAND_BIND // RListen is returned if listen failed. The global errno is set. RListen Res = C.HAKUREI_WAYLAND_LISTEN + + // RHostCreate is returned if ensuring pathname availability failed. Returned by [New]. + RHostCreate Res = C.HAKUREI_WAYLAND_HOST_CREAT + // RHostSocket is returned if socket failed for host server. Returned by [New]. + RHostSocket Res = C.HAKUREI_WAYLAND_HOST_SOCKET + // RHostConnect is returned if connect failed for host server. Returned by [New]. + RHostConnect Res = C.HAKUREI_WAYLAND_HOST_CONNECT ) func (e *Error) Unwrap() error { return e.Errno } @@ -95,6 +102,16 @@ func (e *Error) Error() string { } return e.Errno.Error() + case RHostCreate: + if e.Errno == nil { + return "cannot ensure wayland pathname socket" + } + return e.Errno.Error() + case RHostSocket: + return e.withPrefix("socket for host wayland server") + case RHostConnect: + return e.withPrefix("connect to host wayland server") + default: return e.withPrefix("impossible outcome") /* not reached */ } diff --git a/internal/wayland/wayland_test.go b/internal/wayland/wayland_test.go index 059ce108..a74d7498 100644 --- a/internal/wayland/wayland_test.go +++ b/internal/wayland/wayland_test.go @@ -1,6 +1,7 @@ package wayland_test import ( + "os" "syscall" "testing" @@ -58,6 +59,25 @@ func TestError(t *testing.T) { Cause: wayland.RSocket, }, "socket operation failed"}, + {"host create", wayland.Error{ + Cause: wayland.RHostCreate, + }, "cannot ensure wayland pathname socket"}, + + {"host create path", wayland.Error{ + Cause: wayland.RHostCreate, + Errno: &os.PathError{Op: "create", Path: "/proc/nonexistent", Err: syscall.EEXIST}, + }, "create /proc/nonexistent: file exists"}, + + {"host socket", wayland.Error{ + Cause: wayland.RHostSocket, + Errno: stub.UniqueError(5), + }, "socket for host wayland server: unique error 5 injected by the test suite"}, + + {"host connect", wayland.Error{ + Cause: wayland.RHostConnect, + Errno: stub.UniqueError(6), + }, "connect to host wayland server: unique error 6 injected by the test suite"}, + {"invalid", wayland.Error{ Cause: 0xbad, }, "impossible outcome"}, -- cgit v1.3.1