aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--internal/system/output.go4
-rw-r--r--internal/wayland/wayland-client-helper.c63
-rw-r--r--internal/wayland/wayland-client-helper.h20
-rw-r--r--internal/wayland/wayland.go91
-rw-r--r--internal/wayland/wayland_test.go79
5 files changed, 225 insertions, 32 deletions
diff --git a/internal/system/output.go b/internal/system/output.go
index 71acace4..a1666dc3 100644
--- a/internal/system/output.go
+++ b/internal/system/output.go
@@ -40,6 +40,10 @@ func (e *OpError) Error() string {
}
func (e *OpError) Message() string {
+ if m, ok := message.GetMessage(e.Err); ok {
+ return m
+ }
+
switch {
case e.Msg != "":
return e.Error()
diff --git a/internal/wayland/wayland-client-helper.c b/internal/wayland/wayland-client-helper.c
index b1fcca92..a6a824b5 100644
--- a/internal/wayland/wayland-client-helper.c
+++ b/internal/wayland/wayland-client-helper.c
@@ -31,67 +31,84 @@ static const struct wl_registry_listener registry_listener = {
.global_remove = registry_handle_global_remove,
};
-int32_t hakurei_bind_wayland_fd(
+hakurei_wayland_res hakurei_bind_wayland_fd(
char *socket_path,
int fd,
const char *app_id,
const char *instance_id,
int sync_fd) {
- int32_t res = 0; /* refer to resErr for corresponding Go error */
+ hakurei_wayland_res res = HAKUREI_WAYLAND_SUCCESS; /* see wayland.go for handling */
+
+ struct wl_display *display = NULL;
+ struct wl_registry *registry;
+ struct wp_security_context_manager_v1 *security_context_manager = NULL;
+ int event_cnt;
+ int listen_fd = -1;
+ struct sockaddr_un sockaddr = {0};
+ struct wp_security_context_v1 *security_context;
- struct wl_display *display;
display = wl_display_connect_to_fd(fd);
- if (!display) {
- res = 1;
+ if (display == NULL) {
+ res = HAKUREI_WAYLAND_CONNECT;
goto out;
};
- struct wl_registry *registry;
registry = wl_display_get_registry(display);
-
- struct wp_security_context_manager_v1 *security_context_manager = NULL;
- wl_registry_add_listener(registry, &registry_listener, &security_context_manager);
- int ret;
- ret = wl_display_roundtrip(display);
+ if (wl_registry_add_listener(registry, &registry_listener, &security_context_manager) < 0) {
+ res = HAKUREI_WAYLAND_LISTENER;
+ goto out;
+ }
+ event_cnt = wl_display_roundtrip(display);
wl_registry_destroy(registry);
- if (ret < 0)
+ if (event_cnt < 0) {
+ res = HAKUREI_WAYLAND_ROUNDTRIP;
goto out;
+ }
- if (!security_context_manager) {
- res = 2;
+ if (security_context_manager == NULL) {
+ res = HAKUREI_WAYLAND_NOT_AVAIL;
goto out;
}
- int listen_fd = -1;
listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (listen_fd < 0)
+ if (listen_fd < 0) {
+ res = HAKUREI_WAYLAND_SOCKET;
goto out;
+ }
- struct sockaddr_un sockaddr = {0};
sockaddr.sun_family = AF_UNIX;
snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", socket_path);
- if (bind(listen_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0)
+ if (bind(listen_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0) {
+ res = HAKUREI_WAYLAND_BIND;
goto out;
+ }
- if (listen(listen_fd, 0) != 0)
+ if (listen(listen_fd, 0) != 0) {
+ res = HAKUREI_WAYLAND_LISTEN;
goto out;
+ }
- struct wp_security_context_v1 *security_context;
security_context = wp_security_context_manager_v1_create_listener(security_context_manager, listen_fd, sync_fd);
+ if (security_context == NULL) { /* not reached */
+ res = HAKUREI_WAYLAND_NOT_AVAIL;
+ goto out;
+ }
wp_security_context_v1_set_sandbox_engine(security_context, "app.hakurei");
wp_security_context_v1_set_app_id(security_context, app_id);
wp_security_context_v1_set_instance_id(security_context, instance_id);
wp_security_context_v1_commit(security_context);
wp_security_context_v1_destroy(security_context);
- if (wl_display_roundtrip(display) < 0)
+ if (wl_display_roundtrip(display) < 0) {
+ res = HAKUREI_WAYLAND_ROUNDTRIP;
goto out;
+ }
out:
if (listen_fd >= 0)
close(listen_fd);
- if (security_context_manager)
+ if (security_context_manager != NULL)
wp_security_context_manager_v1_destroy(security_context_manager);
- if (display)
+ if (display != NULL)
wl_display_disconnect(display);
free((void *)socket_path);
diff --git a/internal/wayland/wayland-client-helper.h b/internal/wayland/wayland-client-helper.h
index 6ed93a4a..dc456c31 100644
--- a/internal/wayland/wayland-client-helper.h
+++ b/internal/wayland/wayland-client-helper.h
@@ -1,6 +1,22 @@
-#include <stdint.h>
+typedef enum {
+ HAKUREI_WAYLAND_SUCCESS,
+ /* wl_display_connect_to_fd failed, errno */
+ HAKUREI_WAYLAND_CONNECT,
+ /* wl_registry_add_listener failed, errno */
+ HAKUREI_WAYLAND_LISTENER,
+ /* wl_display_roundtrip failed, errno */
+ HAKUREI_WAYLAND_ROUNDTRIP,
+ /* compositor does not implement wp_security_context_v1 */
+ HAKUREI_WAYLAND_NOT_AVAIL,
+ /* socket failed, errno */
+ HAKUREI_WAYLAND_SOCKET,
+ /* bind failed, errno */
+ HAKUREI_WAYLAND_BIND,
+ /* listen failed, errno */
+ HAKUREI_WAYLAND_LISTEN,
+} hakurei_wayland_res;
-int32_t hakurei_bind_wayland_fd(
+hakurei_wayland_res hakurei_bind_wayland_fd(
char *socket_path,
int fd,
const char *app_id,
diff --git a/internal/wayland/wayland.go b/internal/wayland/wayland.go
index 7a8694cd..a7cddf5a 100644
--- a/internal/wayland/wayland.go
+++ b/internal/wayland/wayland.go
@@ -12,7 +12,6 @@ package wayland
*/
import "C"
import (
- "errors"
"strings"
"syscall"
)
@@ -31,18 +30,96 @@ const (
FallbackName = "wayland-0"
)
-var resErr = [...]error{
- 0: nil,
- 1: errors.New("wl_display_connect_to_fd() failed"),
- 2: errors.New("wp_security_context_v1 not available"),
+type (
+ // Res is the outcome of a call to hakurei_bind_wayland_fd.
+ Res = C.hakurei_wayland_res
+
+ // An Error represents a failure during hakurei_bind_wayland_fd.
+ Error struct {
+ // Where the failure occurred.
+ Cause Res
+ // Global errno value set during the fault.
+ Errno error
+ }
+)
+
+// withPrefix returns prefix suffixed with errno description if available.
+func (e *Error) withPrefix(prefix string) string {
+ if e.Errno == nil {
+ return prefix
+ }
+ return prefix + ": " + e.Errno.Error()
}
+const (
+ // RSuccess is returned on a successful call.
+ RSuccess Res = C.HAKUREI_WAYLAND_SUCCESS
+ // RConnect is returned if wl_display_connect_to_fd failed. The global errno is set.
+ RConnect Res = C.HAKUREI_WAYLAND_CONNECT
+ // RListener is returned if wl_registry_add_listener failed. The global errno is set.
+ RListener Res = C.HAKUREI_WAYLAND_LISTENER
+ // RRoundtrip is returned if wl_display_roundtrip failed. The global errno is set.
+ RRoundtrip Res = C.HAKUREI_WAYLAND_ROUNDTRIP
+ // RNotAvail is returned if compositor does not implement wp_security_context_v1.
+ RNotAvail Res = C.HAKUREI_WAYLAND_NOT_AVAIL
+ // RSocket is returned if socket failed. The global errno is set.
+ RSocket Res = C.HAKUREI_WAYLAND_SOCKET
+ // RBind is returned if bind failed. The global errno is set.
+ RBind Res = C.HAKUREI_WAYLAND_BIND
+ // RListen is returned if listen failed. The global errno is set.
+ RListen Res = C.HAKUREI_WAYLAND_LISTEN
+)
+
+func (e *Error) Unwrap() error { return e.Errno }
+func (e *Error) Message() string { return e.Error() }
+func (e *Error) Error() string {
+ switch e.Cause {
+ case RSuccess:
+ if e.Errno == nil {
+ return "success"
+ }
+ return e.Errno.Error()
+
+ case RConnect:
+ return e.withPrefix("wl_display_connect_to_fd failed")
+ case RListener:
+ return e.withPrefix("wl_registry_add_listener failed")
+ case RRoundtrip:
+ return e.withPrefix("wl_display_roundtrip failed")
+ case RNotAvail:
+ return "compositor does not implement security_context_v1"
+
+ case RSocket, RBind, RListen:
+ if e.Errno == nil {
+ return "socket operation failed"
+ }
+ return e.Errno.Error()
+
+ default:
+ return e.withPrefix("impossible outcome") /* not reached */
+ }
+}
+
+// bindWaylandFd calls hakurei_bind_wayland_fd. A non-nil error has concrete type [Error].
func bindWaylandFd(socketPath string, fd uintptr, appID, instanceID string, syncFd uintptr) error {
if hasNull(appID) || hasNull(instanceID) {
return syscall.EINVAL
}
- res := C.hakurei_bind_wayland_fd(C.CString(socketPath), C.int(fd), C.CString(appID), C.CString(instanceID), C.int(syncFd))
- return resErr[int32(res)]
+
+ var e Error
+ e.Cause, e.Errno = C.hakurei_bind_wayland_fd(
+ C.CString(socketPath),
+ C.int(fd),
+ C.CString(appID),
+ C.CString(instanceID),
+ C.int(syncFd),
+ )
+
+ if e.Cause == RSuccess {
+ return nil
+ }
+ return &e
}
+// hasNull returns whether s contains the NUL character.
func hasNull(s string) bool { return strings.IndexByte(s, 0) > -1 }
diff --git a/internal/wayland/wayland_test.go b/internal/wayland/wayland_test.go
new file mode 100644
index 00000000..059ce108
--- /dev/null
+++ b/internal/wayland/wayland_test.go
@@ -0,0 +1,79 @@
+package wayland_test
+
+import (
+ "syscall"
+ "testing"
+
+ "hakurei.app/container/stub"
+ "hakurei.app/internal/wayland"
+)
+
+func TestError(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ err wayland.Error
+ want string
+ }{
+ {"success", wayland.Error{
+ Cause: wayland.RSuccess,
+ }, "success"},
+
+ {"success errno", wayland.Error{
+ Cause: wayland.RSuccess,
+ Errno: stub.UniqueError(0),
+ }, "unique error 0 injected by the test suite"},
+
+ {"wl_display_connect_to_fd", wayland.Error{
+ Cause: wayland.RConnect,
+ Errno: stub.UniqueError(1),
+ }, "wl_display_connect_to_fd failed: unique error 1 injected by the test suite"},
+
+ {"wl_registry_add_listener", wayland.Error{
+ Cause: wayland.RListener,
+ Errno: stub.UniqueError(2),
+ }, "wl_registry_add_listener failed: unique error 2 injected by the test suite"},
+
+ {"wl_display_roundtrip", wayland.Error{
+ Cause: wayland.RRoundtrip,
+ Errno: stub.UniqueError(3),
+ }, "wl_display_roundtrip failed: unique error 3 injected by the test suite"},
+
+ {"not available", wayland.Error{
+ Cause: wayland.RNotAvail,
+ }, "compositor does not implement security_context_v1"},
+
+ {"not available errno", wayland.Error{
+ Cause: wayland.RNotAvail,
+ Errno: syscall.EAGAIN,
+ }, "compositor does not implement security_context_v1"},
+
+ {"socket", wayland.Error{
+ Cause: wayland.RSocket,
+ Errno: stub.UniqueError(4),
+ }, "unique error 4 injected by the test suite"},
+
+ {"socket invalid", wayland.Error{
+ Cause: wayland.RSocket,
+ }, "socket operation failed"},
+
+ {"invalid", wayland.Error{
+ Cause: 0xbad,
+ }, "impossible outcome"},
+
+ {"invalid errno", wayland.Error{
+ Cause: 0xbad,
+ Errno: stub.UniqueError(5),
+ }, "impossible outcome: unique error 5 injected by the test suite"},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tc.err.Message(); got != tc.want {
+ t.Errorf("Message: %q, want %q", got, tc.want)
+ }
+ })
+ }
+}