libei/src/libei.c
Peter Hutterer 1c9ce9b680 Add serial numbers to the protocol
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2023-03-03 11:27:01 +10:00

907 lines
21 KiB
C

/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include "util-io.h"
#include "util-macros.h"
#include "util-object.h"
#include "util-sources.h"
#include "util-strings.h"
#include "util-time.h"
#include "util-version.h"
#include "libei.h"
#include "libei-private.h"
#include "brei-shared.h"
#include "ei-proto.h"
_Static_assert(sizeof(enum ei_device_capability) == sizeof(int), "Invalid enum size");
_Static_assert(sizeof(enum ei_keymap_type) == sizeof(int), "Invalid enum size");
_Static_assert(sizeof(enum ei_event_type) == sizeof(int), "Invalid enum size");
_Static_assert(sizeof(enum ei_log_priority) == sizeof(int), "Invalid enum size");
static void
ei_destroy(struct ei *ei)
{
ei_disconnect(ei);
struct ei_event *e;
while ((e = ei_get_event(ei)) != NULL)
ei_event_unref(e);
if (ei->backend_interface.destroy)
ei->backend_interface.destroy(ei, ei->backend);
ei->backend = NULL;
ei_connection_setup_unref(ei->connection_setup);
ei_connection_unref(ei->connection);
brei_context_unref(ei->brei);
sink_unref(ei->sink);
free(ei->name);
}
static
OBJECT_IMPLEMENT_CREATE(ei);
_public_
OBJECT_IMPLEMENT_REF(ei);
_public_
OBJECT_IMPLEMENT_UNREF_CLEANUP(ei);
_public_
OBJECT_IMPLEMENT_SETTER(ei, user_data, void *);
_public_
OBJECT_IMPLEMENT_GETTER(ei, user_data, void *);
OBJECT_IMPLEMENT_GETTER(ei, connection, struct ei_connection *);
OBJECT_IMPLEMENT_GETTER(ei, serial, uint32_t);
DEFINE_UNREF_CLEANUP_FUNC(brei_result);
DEFINE_UNREF_CLEANUP_FUNC(ei_device);
DEFINE_UNREF_CLEANUP_FUNC(ei_region);
DEFINE_UNREF_CLEANUP_FUNC(ei_pingpong);
struct ei *
ei_get_context(struct ei *ei)
{
return ei; /* for the protocol bindings */
}
static struct ei *
ei_create_context(bool is_sender, void *user_data)
{
_unref_(ei) *ei = ei_create(NULL);
ei->state = EI_STATE_NEW;
list_init(&ei->event_queue);
list_init(&ei->seats);
list_init(&ei->proto_objects);
ei->interface_versions = (struct ei_interface_versions){
.ei_connection = VERSION_V(1),
.ei_connection_setup = VERSION_V(1),
.ei_callback = VERSION_V(1),
.ei_pingpong = VERSION_V(1),
.ei_seat = VERSION_V(1),
.ei_device = VERSION_V(1),
.ei_pointer = VERSION_V(1),
.ei_keyboard = VERSION_V(1),
.ei_touchscreen = VERSION_V(1),
};
/* This must be v1 until the server tells us otherwise */
ei->connection_setup = ei_connection_setup_new(ei, VERSION_V(1));
ei->next_object_id = 1;
ei->brei = brei_context_new(ei);
brei_context_set_log_context(ei->brei, ei);
brei_context_set_log_func(ei->brei, (brei_logfunc_t)ei_log_msg_va);
ei_log_set_handler(ei, NULL);
ei_log_set_priority(ei, EI_LOG_PRIORITY_INFO);
ei->sink = sink_new();
if (!ei->sink)
return NULL;
ei->user_data = user_data;
ei->backend = NULL;
ei->is_sender = is_sender;
return steal(&ei);
}
uint32_t
ei_get_new_id(struct ei *ei)
{
static const int server_range = 0xff000000;
return ei->next_object_id++ & ~server_range;
}
void
ei_update_serial(struct ei *ei, uint32_t serial)
{
ei->serial = serial;
}
void
ei_register_object(struct ei *ei, struct brei_object *object)
{
log_debug(ei, "registering %s v%u object %#x", object->interface->name, object->version, object->id);
list_append(&ei->proto_objects, &object->link);
}
void
ei_unregister_object(struct ei *ei, struct brei_object *object)
{
log_debug(ei, "deregistering %s v%u object %#x", object->interface->name, object->version, object->id);
list_remove(&object->link);
}
_public_ bool
ei_is_sender(struct ei *ei)
{
return ei->is_sender;
}
_public_ struct ei *
ei_new(void *user_data)
{
return ei_new_sender(user_data);
}
_public_ struct ei *
ei_new_sender(void *user_data)
{
return ei_create_context(true, user_data);
}
_public_ struct ei *
ei_new_receiver(void *user_data)
{
return ei_create_context(false, user_data);
}
_public_ int
ei_get_fd(struct ei *ei)
{
return sink_get_fd(ei->sink);
}
_public_ void
ei_dispatch(struct ei *ei)
{
sink_dispatch(ei->sink);
}
static void
update_event_timestamp(struct ei_event *event, uint64_t time)
{
switch (event->type) {
case EI_EVENT_POINTER_MOTION:
case EI_EVENT_POINTER_MOTION_ABSOLUTE:
case EI_EVENT_POINTER_BUTTON:
case EI_EVENT_POINTER_SCROLL:
case EI_EVENT_POINTER_SCROLL_STOP:
case EI_EVENT_POINTER_SCROLL_CANCEL:
case EI_EVENT_POINTER_SCROLL_DISCRETE:
case EI_EVENT_KEYBOARD_KEY:
case EI_EVENT_TOUCH_DOWN:
case EI_EVENT_TOUCH_UP:
case EI_EVENT_TOUCH_MOTION:
if (event->timestamp != 0) {
log_bug(ei_event_get_context(event),
"Unexpected timestamp for event of type: %s",
ei_event_type_to_string(event->type));
return;
}
event->timestamp = time;
break;
default:
log_bug(ei_event_get_context(event),
"Unexpected event %s in pending queue event",
ei_event_type_to_string(event->type));
return;
}
}
static void
queue_event(struct ei *ei, struct ei_event *event)
{
struct ei_device *device = ei_event_get_device(event);
struct list *queue = &ei->event_queue;
const char *prefix = "";
switch (event->type) {
case EI_EVENT_POINTER_MOTION:
case EI_EVENT_POINTER_MOTION_ABSOLUTE:
case EI_EVENT_POINTER_BUTTON:
case EI_EVENT_POINTER_SCROLL:
case EI_EVENT_POINTER_SCROLL_STOP:
case EI_EVENT_POINTER_SCROLL_CANCEL:
case EI_EVENT_POINTER_SCROLL_DISCRETE:
case EI_EVENT_KEYBOARD_KEY:
case EI_EVENT_TOUCH_DOWN:
case EI_EVENT_TOUCH_UP:
case EI_EVENT_TOUCH_MOTION:
prefix = "pending ";
queue = &device->pending_event_queue;
break;
case EI_EVENT_FRAME:
/* silently discard empty frames */
if (list_empty(&device->pending_event_queue))
return;
struct ei_event *pending;
list_for_each_safe(pending, &device->pending_event_queue, link) {
update_event_timestamp(pending, event->timestamp);
list_remove(&pending->link);
list_append(&ei->event_queue, &pending->link);
}
break;
default:
if (device && !list_empty(&device->pending_event_queue))
ei_queue_frame_event(device, ei_now(ei));
break;
}
log_debug(ei, "queuing %sevent type %s (%u)",
prefix,
ei_event_type_to_string(event->type), event->type);
list_append(queue, &event->link);
}
static void
insert_event(struct ei *ei, struct ei_event *event)
{
log_debug(ei, "inserting event type %s (%u)",
ei_event_type_to_string(event->type), event->type);
list_insert(&ei->event_queue, &event->link);
}
void
ei_queue_connect_event(struct ei *ei)
{
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_CONNECT;
queue_event(ei, e);
}
void
ei_queue_disconnect_event(struct ei *ei)
{
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_DISCONNECT;
queue_event(ei, e);
}
void
ei_queue_seat_added_event(struct ei_seat *seat)
{
struct ei *ei= ei_seat_get_context(seat);
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_SEAT_ADDED;
e->seat = ei_seat_ref(seat);
queue_event(ei, e);
}
void
ei_queue_seat_removed_event(struct ei_seat *seat)
{
struct ei *ei= ei_seat_get_context(seat);
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_SEAT_REMOVED;
e->seat = ei_seat_ref(seat);
queue_event(ei, e);
}
static void
queue_device_added_event(struct ei_device *device)
{
struct ei *ei= ei_device_get_context(device);
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_DEVICE_ADDED;
queue_event(ei, e);
}
static void
queue_device_removed_event(struct ei_device *device)
{
struct ei *ei= ei_device_get_context(device);
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_DEVICE_REMOVED;
queue_event(ei, e);
}
static void
insert_device_removed_event(struct ei_device *device)
{
struct ei *ei= ei_device_get_context(device);
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_DEVICE_REMOVED;
insert_event(ei, e);
}
void
ei_queue_device_paused_event(struct ei_device *device)
{
struct ei *ei= ei_device_get_context(device);
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_DEVICE_PAUSED;
queue_event(ei, e);
}
void
ei_queue_device_resumed_event(struct ei_device *device)
{
struct ei *ei= ei_device_get_context(device);
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_DEVICE_RESUMED;
queue_event(ei, e);
}
void
ei_queue_keyboard_modifiers_event(struct ei_device *device,
const struct ei_xkb_modifiers *mods)
{
struct ei *ei= ei_device_get_context(device);
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_KEYBOARD_MODIFIERS;
e->modifiers = *mods;
queue_event(ei, e);
}
void
ei_queue_frame_event(struct ei_device *device, uint64_t time)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_FRAME;
e->timestamp = time;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_device_start_emulating_event(struct ei_device *device, uint32_t sequence)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_DEVICE_START_EMULATING;
e->start_emulating.sequence = sequence;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_device_stop_emulating_event(struct ei_device *device)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_DEVICE_STOP_EMULATING;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_pointer_rel_event(struct ei_device *device,
double dx, double dy)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_POINTER_MOTION;
e->pointer.dx = dx;
e->pointer.dy = dy;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_pointer_abs_event(struct ei_device *device,
double x, double y)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_POINTER_MOTION_ABSOLUTE;
e->pointer.absx = x;
e->pointer.absy = y;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_pointer_button_event(struct ei_device *device, uint32_t button,
bool is_press)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_POINTER_BUTTON;
e->pointer.button = button;
e->pointer.button_is_press = is_press;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_pointer_scroll_event(struct ei_device *device,
double x, double y)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_POINTER_SCROLL;
e->pointer.sx = x;
e->pointer.sy = y;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_pointer_scroll_discrete_event(struct ei_device *device,
int32_t x, int32_t y)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_POINTER_SCROLL_DISCRETE;
e->pointer.sdx = x;
e->pointer.sdy = y;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_pointer_scroll_stop_event(struct ei_device *device, bool x, bool y)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_POINTER_SCROLL_STOP;
e->pointer.stop_x = x;
e->pointer.stop_y = y;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_pointer_scroll_cancel_event(struct ei_device *device, bool x, bool y)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_POINTER_SCROLL_CANCEL;
e->pointer.stop_x = x;
e->pointer.stop_y = y;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_keyboard_key_event(struct ei_device *device, uint32_t key,
bool is_press)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_KEYBOARD_KEY;
e->keyboard.key = key;
e->keyboard.key_is_press = is_press;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_touch_down_event(struct ei_device *device, uint32_t touchid,
double x, double y)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_TOUCH_DOWN;
e->touch.touchid = touchid,
e->touch.x = x;
e->touch.y = y;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_touch_motion_event(struct ei_device *device, uint32_t touchid,
double x, double y)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_TOUCH_MOTION;
e->touch.touchid = touchid,
e->touch.x = x;
e->touch.y = y;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_touch_up_event(struct ei_device *device, uint32_t touchid)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_TOUCH_UP;
e->touch.touchid = touchid,
queue_event(ei_device_get_context(device), e);
}
void
ei_disconnect(struct ei *ei)
{
if (ei->state == EI_STATE_DISCONNECTED ||
ei->state == EI_STATE_DISCONNECTING)
return;
enum ei_state state = ei->state;
/* We need the disconnecting state to be re-entrant
ei_device_remove() may call ei_disconnect() on a socket error */
ei->state = EI_STATE_DISCONNECTING;
struct ei_seat *seat;
list_for_each_safe(seat, &ei->seats, link) {
ei_seat_remove(seat);
}
if (state != EI_STATE_NEW) {
ei_connection_request_disconnect(ei->connection);
}
ei_queue_disconnect_event(ei);
ei->state = EI_STATE_DISCONNECTED;
if (ei->source)
source_remove(ei->source);
ei->source = source_unref(ei->source);
}
static struct brei_result *
handle_msg_seat(struct ei_connection *connection, uint32_t seat_id, uint32_t version)
{
struct ei *ei = ei_connection_get_context(connection);
struct ei_seat *seat = ei_seat_new(ei, seat_id, version);
/* We might get the seat event before our callback finished, so make sure
* we know we're connected
*/
ei_connected(ei);
/* seats list owns the ref */
list_append(&ei->seats, &seat->link);
return NULL;
}
void
ei_queue_device_removed_event(struct ei_device *device)
{
queue_device_removed_event(device);
}
void
ei_queue_device_added_event(struct ei_device *device)
{
queue_device_added_event(device);
}
void
ei_insert_device_removed_event(struct ei_device *device)
{
insert_device_removed_event(device);
}
_public_ struct ei_event*
ei_get_event(struct ei *ei)
{
if (list_empty(&ei->event_queue))
return NULL;
struct ei_event *e = list_first_entry(&ei->event_queue, e, link);
list_remove(&e->link);
return e;
}
_public_ struct ei_event*
ei_peek_event(struct ei *ei)
{
if (list_empty(&ei->event_queue))
return NULL;
struct ei_event *e = list_first_entry(&ei->event_queue, e, link);
return ei_event_ref(e);
}
void
ei_connected(struct ei *ei)
{
if (ei->state == EI_STATE_CONNECTING) {
ei->state = EI_STATE_CONNECTED;
ei_queue_connect_event(ei);
}
}
static struct brei_result *
handle_msg_disconnected(struct ei_connection *connection, uint32_t last_serial,
uint32_t reason, const char *explanation)
{
struct ei *ei = ei_connection_get_context(connection);
if (reason == EI_CONNECTION_DISCONNECT_REASON_DISCONNECTED) {
log_info(ei, "Disconnected by EIS");
ei_disconnect(ei);
return NULL;
} else {
log_info(ei, "Disconnected after error: %s", explanation);
return brei_result_new(reason, "%s", explanation);
}
}
static struct brei_result *
handle_msg_invalid_object(struct ei_connection *connection, uint32_t last_serial, uint32_t object)
{
struct ei *ei = ei_connection_get_context(connection);
log_bug(ei, "Invalid object %#x after %u, I don't yet know how to handle that", last_serial, object);
return NULL;
}
static struct brei_result *
handle_msg_ping(struct ei_connection *connection, uint32_t id, uint32_t version)
{
struct ei *ei = ei_connection_get_context(connection);
_unref_(ei_pingpong) *pingpong = ei_pingpong_new_for_id(ei, id, version);
ei_pingpong_request_done(pingpong, 0);
return NULL;
}
static const struct ei_connection_interface interface = {
.disconnected = handle_msg_disconnected,
.seat = handle_msg_seat,
.invalid_object = handle_msg_invalid_object,
.ping = handle_msg_ping,
};
const struct ei_connection_interface *
ei_get_interface(struct ei *ei)
{
return &interface;
}
static int
lookup_object(uint32_t object_id, struct brei_object **object, void *userdata)
{
struct ei *ei = userdata;
struct brei_object *obj;
list_for_each(obj, &ei->proto_objects, link) {
if (obj->id == object_id) {
*object = obj;
return 0;
}
}
log_debug(ei, "Failed to find object %#x", object_id);
return -ENOENT;
}
static void
connection_dispatch(struct source *source, void *userdata)
{
struct ei *ei = userdata;
enum ei_state old_state = ei->state;
_unref_(brei_result) *result = brei_dispatch(ei->brei, source_get_fd(source),
lookup_object, ei);
if (result) {
brei_drain_fd(source_get_fd(source));
ei_disconnect(ei);
}
static const char *states[] = {
"NEW",
"BACKEND",
"CONNECTING",
"CONNECTED",
"DISCONNECTED",
"DISCONNECTING",
};
if (result)
log_warn(ei, "Connection error: %s", brei_result_get_explanation(result));
if (old_state != ei->state)
log_debug(ei, "Connection dispatch: %s -> %s",
states[old_state],
states[ei->state]);
}
int
ei_send_message(struct ei *ei, const struct brei_object *object,
uint32_t opcode, const char *signature, size_t nargs, ...)
{
log_debug(ei, "sending: object %#x (%s@v%u:%s(%u)) signature '%s'",
object->id,
object->interface->name,
object->interface->version,
object->interface->requests[opcode].name,
opcode,
signature);
va_list args;
va_start(args, nargs);
_unref_(brei_result) *result = brei_marshal_message(ei->brei,
object->id,
opcode, signature,
nargs, args);
va_end(args);
if (brei_result_get_reason(result) != 0) {
log_warn(ei, "failed to marshal message: %s",
brei_result_get_explanation(result));
return -EBADMSG;
}
_cleanup_iobuf_ struct iobuf *buf = brei_result_get_data(result);
assert(buf);
int fd = source_get_fd(ei->source);
int rc = iobuf_send(buf, fd);
return rc < 0 ? rc : 0;
}
int
ei_set_socket(struct ei *ei, int fd)
{
struct source *source = source_new(fd, connection_dispatch, ei);
int rc = sink_add_source(ei->sink, source);
if (rc == 0) {
ei->source = source_ref(source);
ei->state = EI_STATE_BACKEND;
/* The server SHOULD have already sent the connection setup
* version, let's process that. If not ready, it'll happen
* in the next dispatch.
*
* FIXME: this will block if O_NONBLOCK is missing
*/
ei_dispatch(ei);
}
source_unref(source);
return rc < 0 ? rc : 0;
}
_public_ void
ei_configure_name(struct ei *ei, const char *name)
{
if (ei->state != EI_STATE_NEW) {
log_bug_client(ei,"Client is already connected");
return;
}
if (strlen(name) > 1024) {
log_bug_client(ei, "Client name too long");
return;
}
free(ei->name);
ei->name = xstrdup(name);
}
_public_ uint64_t
ei_now(struct ei *ei)
{
uint64_t ts = 0;
int rc = now(&ts);
if (rc < 0) {
/* We should probably disconnect here but the chances of this
* happening are so slim it's not worth worrying about. Plus,
* if this fails we're likely to be inside eis_device_frame()
* so we should flush a frame event before disconnecting and... */
log_error(ei, "clock_gettime failed: %s", strerror(-rc));
}
return ts;
}
#ifdef _enable_tests_
#include "util-munit.h"
MUNIT_TEST(test_init_unref)
{
struct ei *ei = ei_new(NULL);
munit_assert_int(ei->state, ==, EI_STATE_NEW);
munit_assert(list_empty(&ei->event_queue));
munit_assert(list_empty(&ei->seats));
munit_assert_not_null(ei->sink);
struct ei *refd = ei_ref(ei);
munit_assert_ptr_equal(ei, refd);
munit_assert_int(ei->object.refcount, ==, 2);
struct ei *unrefd = ei_unref(ei);
munit_assert_null(unrefd);
unrefd = ei_unref(ei);
munit_assert_null(unrefd);
return MUNIT_OK;
}
MUNIT_TEST(test_configure_name)
{
struct ei *ei = ei_new(NULL);
ei_configure_name(ei, "foo");
munit_assert_string_equal(ei->name, "foo");
ei_configure_name(ei, "bar");
munit_assert_string_equal(ei->name, "bar");
/* ignore names that are too long */
char buf[1200] = {0};
memset(buf, 'a', sizeof(buf) - 1);
ei_configure_name(ei, buf);
munit_assert_string_equal(ei->name, "bar");
/* ignore names in all other states */
for (enum ei_state state = EI_STATE_NEW + 1;
state <= EI_STATE_DISCONNECTED;
state++) {
ei->state = state;
ei_configure_name(ei, "expect ignored");
munit_assert_string_equal(ei->name, "bar");
}
ei_unref(ei);
return MUNIT_OK;
}
#endif