You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3220 lines
84 KiB

#include "config.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <Eina.h>
#include "evas_cs2.h"
#include "evas_cs2_private.h"
#include "evas_cs2_utils.h"
#ifdef EVAS_CSERVE2
#define TIMEOUT 1000
#define USE_SHARED_INDEX 1
#define SHARED_INDEX_ADD_TO_HASH 1
#define HKEY_LOAD_OPTS_STR_LEN 215
#define SPECIAL_RID_INDEX_LIST ((unsigned int) 0xFFFFFF42)
typedef Eina_Bool (*Op_Callback)(void *data, const void *msg, int size);
static const Evas_Image_Load_Opts empty_lo = {
{ 0, 0, 0, 0 },
{
0, 0, 0, 0,
0, 0,
0,
0
},
0.0,
0, 0,
0,
0,
EINA_FALSE
};
struct _File_Entry {
unsigned int file_id;
unsigned int server_file_id;
EINA_REFCOUNT;
Eina_Stringshare *hkey;
};
struct _Client_Request {
Msg_Base *msg;
int msg_size;
Op_Callback cb;
void *data;
};
typedef struct _File_Entry File_Entry;
typedef struct _Client_Request Client_Request;
static int cserve2_init = 0;
static int socketfd = -1;
static int sr_size = 0;
static int sr_got = 0;
static char *sr_buf = NULL;
static unsigned int _rid_count = 0;
static unsigned int _file_id = 0;
static unsigned int _data_id = 0;
static Eina_List *_requests = NULL;
static Eina_Hash *_file_entries = NULL;
// Shared index table
static Index_Table _index;
static const char *_shared_string_get(int id);
static const char *_shared_string_safe_get(int id); // Do not allow remap during search
static Eina_Bool _string_index_refresh(void);
static int _server_index_list_set(Msg_Base *data, int size);
static const File_Data *_shared_file_data_get_by_id(unsigned int id);
static const Shm_Object *_shared_index_item_get_by_id(Shared_Index *si, int elemsize, unsigned int id, Eina_Bool safe);
static const File_Data *_shared_image_entry_file_data_find(Image_Entry *ie);
static const Image_Data *_shared_image_entry_image_data_find(Image_Entry *ie);
static const Font_Data *_shared_font_entry_data_find(Font_Entry *fe);
static Eina_Bool _shared_index_remap_check(Shared_Index *si, int elemsize);
static Eina_Bool _server_dispatch_until(unsigned int rid);
unsigned int _image_load_server_send(Image_Entry *ie);
static unsigned int _image_open_server_send(Image_Entry *ie);
static unsigned int _glyph_request_server_send(Font_Entry *fe, Font_Hint_Flags hints, Eina_Bool used);
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX sizeof(((struct sockaddr_un *)NULL)->sun_path)
#endif
static inline Eina_Bool
_memory_zero_cmp(void *data, size_t len)
{
const int *idata = data;
const char *cdata;
int remain;
if (!data || !len) return EINA_TRUE;
for (remain = len / sizeof(idata); remain > 0; --remain)
if (*idata++ != 0) return EINA_FALSE;
cdata = (const char*) idata;
for (remain = ((const char*) data + len) - cdata; remain > 0; --remain)
if (*cdata++ != 0) return EINA_FALSE;
return EINA_TRUE;
}
static void
_file_entry_free(void *data)
{
File_Entry *fentry = data;
if (!fentry) return;
eina_stringshare_del(fentry->hkey);
free(fentry);
}
static void
_socket_path_set(char *path)
{
char *env;
char buf[UNIX_PATH_MAX];
#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
if (getuid() == geteuid())
#endif
{
env = getenv("EVAS_CSERVE2_SOCKET");
if (env && env[0])
{
eina_strlcpy(path, env, UNIX_PATH_MAX);
return;
}
}
snprintf(buf, sizeof(buf), "/tmp/.evas-cserve2-%x.socket", (int)getuid());
/* FIXME: check we can actually create this socket */
strcpy(path, buf);
#if 0
#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
if (getuid() == geteuid())
#endif
{
env = getenv("XDG_RUNTIME_DIR");
if (!env || !env[0])
{
env = getenv("HOME");
if (!env || !env[0])
{
env = getenv("TMPDIR");
if (!env || !env[0])
env = "/tmp";
}
}
snprintf(buf, sizeof(buf), "%s/evas-cserve2-%x.socket", env, getuid());
/* FIXME: check we can actually create this socket */
strcpy(path, buf);
}
#endif
}
static Eina_Bool
_server_connect(void)
{
int s, len;
struct sockaddr_un remote;
#ifdef HAVE_FCNTL
int flags;
#endif
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
{
ERR("cserve2 socket creation failed with error [%d] %s", errno, strerror(errno));
return EINA_FALSE;
}
#ifdef HAVE_FCNTL
flags = fcntl(s, F_GETFD);
flags |= FD_CLOEXEC;
if (fcntl(s, F_SETFD, flags) < 0) ERR("can't set CLOEXEC on fd");
#endif
remote.sun_family = AF_UNIX;
_socket_path_set(remote.sun_path);
len = strlen(remote.sun_path) + sizeof(remote.sun_family);
for (;;)
{
errno = 0;
if (connect(s, (struct sockaddr *)&remote, len) != -1) break;
if (errno == EACCES)
{
ERR("not authorized to connect to cserve2!");
close(s);
return EINA_FALSE;
}
ERR("cserve2 connect failed: [%d] %s. Retrying...", errno, strerror(errno));
errno = 0;
usleep(10000);
if (errno == EINTR)
{
WRN("received interruption while trying to connect to cserve2!");
close(s);
return EINA_FALSE;
}
/* FIXME: Here we should identify the error, maybe signal the daemon manager
* that we need cserve2 to [re]start or just quit and return false.
* There probably should be a timeout of some sort also...
* -- jpeg
*/
}
#ifdef HAVE_FCNTL
if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) ERR("can't set non-blocking fd");
#endif
socketfd = s;
sr_size = 0;
DBG("connected to cserve2 server.");
return EINA_TRUE;
}
static void
_server_disconnect(void)
{
if (socketfd != -1)
close(socketfd);
socketfd = -1;
sr_size = 0;
}
static void
_request_answer_add(Msg_Base *msg, int size, Op_Callback cb, void *data)
{
Client_Request *cr = calloc(1, sizeof(*cr));
cr->msg = msg;
cr->msg_size = size;
cr->cb = cb;
cr->data = data;
_requests = eina_list_append(_requests, cr);
}
static Eina_Bool
_server_safe_send(int fd, const void *data, int size)
{
int sent = 0;
ssize_t ret;
const char *msg = data;
while (sent < size)
{
ret = send(fd, msg + sent, size - sent, MSG_NOSIGNAL);
if (ret < 0)
{
if ((errno == EAGAIN) || (errno == EINTR))
continue;
ERR("send() failed with error %d %m", errno);
return EINA_FALSE;
}
sent += ret;
}
return EINA_TRUE;
}
static Eina_Bool
_request_resend(unsigned int rid)
{
Eina_List *l;
Client_Request *cr;
Eina_Bool found = EINA_FALSE;
DBG("Re-sending %d requests...", eina_list_count(_requests));
EINA_LIST_FOREACH(_requests, l, cr)
{
if (rid)
{
if (cr->msg->rid != rid)
continue;
found = EINA_TRUE;
}
DBG("Sending request %d again: type %d", cr->msg->rid, cr->msg->type);
if (!_server_safe_send(socketfd, &cr->msg_size, sizeof(cr->msg_size)))
return EINA_FALSE;
if (!_server_safe_send(socketfd, cr->msg, cr->msg_size))
return EINA_FALSE;
if (found) break;
}
if (rid)
return found;
return EINA_TRUE;
}
static void
_shared_index_close(Shared_Index *si)
{
if (!si) return;
if (si->f)
{
if (si->data)
eina_file_map_free(si->f, si->data);
eina_file_close(si->f);
}
if (si->entries_by_hkey)
eina_hash_free(si->entries_by_hkey);
memset(si, 0, sizeof(Shared_Index));
}
static void
_shared_index_close_all()
{
DBG("Closing all index files");
if (_index.strings_entries.f)
{
if (_index.strings_entries.data)
eina_file_map_free(_index.strings_entries.f, _index.strings_entries.data);
eina_file_close(_index.strings_entries.f);
_index.strings_entries.data = NULL;
_index.strings_entries.f = NULL;
}
_shared_index_close(&_index.strings_index);
_shared_index_close(&_index.files);
_shared_index_close(&_index.images);
_shared_index_close(&_index.fonts);
_index.generation_id = 0;
}
static Eina_Bool
_server_reconnect()
{
_shared_index_close_all();
errno = 0;
_server_disconnect();
if (!_server_connect())
goto on_error;
if (!_server_dispatch_until(SPECIAL_RID_INDEX_LIST))
goto on_error;
/* NOTE: (TODO?)
* Either we reopen all images & fonts now
* Or we wait until new data is required again to request cserve2 to load
* it for us. Not sure which approch is the best now.
* So, for the moment, we'll just wait until the client needs new data.
*/
if (!_request_resend(0))
goto on_error;
INF("Successfully reconnected to cserve2");
return EINA_TRUE;
on_error:
ERR("Unable to reconnect to server: %d %m", errno);
return EINA_FALSE;
}
static Eina_Bool
_request_answer_required(int type, Eina_Bool *valid)
{
switch (type)
{
case CSERVE2_OPEN:
case CSERVE2_LOAD:
case CSERVE2_PRELOAD:
case CSERVE2_FONT_LOAD:
case CSERVE2_FONT_GLYPHS_LOAD:
if (valid) *valid = EINA_TRUE;
return EINA_TRUE;
case CSERVE2_CLOSE:
case CSERVE2_UNLOAD:
case CSERVE2_FONT_UNLOAD:
case CSERVE2_FONT_GLYPHS_USED:
if (valid) *valid = EINA_TRUE;
return EINA_FALSE;
default:
ERR("Invalid message type %d", type);
if (valid) *valid = EINA_FALSE;
return EINA_FALSE;
}
}
static Eina_Bool
_server_send(void *buf, int size, Op_Callback cb, void *data)
{
Msg_Base *msg = buf;
int type = msg->type;
Eina_Bool valid = EINA_TRUE;
if (!_server_safe_send(socketfd, &size, sizeof(size)))
{
ERR("Couldn't send message size to server.");
goto on_error;
}
if (!_server_safe_send(socketfd, buf, size))
{
ERR("Couldn't send message body to server.");
goto on_error;
}
if (_request_answer_required(type, &valid))
_request_answer_add(msg, size, cb, data);
else
free(msg);
return valid;
on_error:
if (!_request_answer_required(type, NULL))
{
free(buf);
return EINA_FALSE;
}
ERR("Socket error: %d %m", errno);
switch (errno)
{
case EPIPE:
case EBADF:
WRN("Trying to reconnect to server...");
if (!_server_reconnect())
{
free(buf);
return EINA_FALSE;
}
return _server_send(buf, size, cb, data);
default:
ERR("Can not recover from this error!");
free(buf);
return EINA_FALSE;
}
}
static void *
_server_read(int *size)
{
int n;
void *ret;
if (socketfd < 0)
return NULL;
if (sr_size)
goto get_data;
n = recv(socketfd, &sr_size, sizeof(sr_size), 0);
if (n < 0)
return NULL;
if (n == 0)
{
DBG("Socket connection closed by server.");
_server_disconnect();
return NULL;
}
sr_buf = malloc(sr_size);
get_data:
n = recv(socketfd, sr_buf + sr_got, sr_size - sr_got, 0);
if (n < 0)
return NULL;
sr_got += n;
if (sr_got < sr_size)
return NULL;
*size = sr_size;
sr_size = 0;
sr_got = 0;
ret = sr_buf;
sr_buf = NULL;
return ret;
}
int
evas_cserve2_init(void)
{
if (cserve2_init++)
return cserve2_init;
memset(&_index, 0, sizeof(_index));
DBG("Connecting to cserve2.");
if (!_server_connect())
{
cserve2_init = 0;
return 0;
}
_file_entries = eina_hash_string_superfast_new(EINA_FREE_CB(_file_entry_free));
return cserve2_init;
}
int
evas_cserve2_shutdown(void)
{
const char zeros[sizeof(Msg_Index_List)] = {0};
Msg_Index_List *empty = (Msg_Index_List *) zeros;
if (cserve2_init <= 0)
{
CRI("cserve2 is already shutdown");
return -1;
}
if ((--cserve2_init) > 0)
return cserve2_init;
DBG("Disconnecting from cserve2.");
empty->base.type = CSERVE2_INDEX_LIST;
_server_index_list_set((Msg_Base *) empty, sizeof(Msg_Index_List));
_server_disconnect();
eina_hash_free(_file_entries);
_file_entries = NULL;
return cserve2_init;
}
int
evas_cserve2_use_get(void)
{
return cserve2_init;
}
static unsigned int
_next_rid(void)
{
if (!_rid_count)
_rid_count++;
return _rid_count++;
}
static unsigned int
_server_dispatch(Eina_Bool *failed)
{
int size;
unsigned int rid;
Eina_List *l, *l_next;
Client_Request *cr;
Msg_Base *msg;
Eina_Bool found = EINA_FALSE;
msg = _server_read(&size);
if (!msg)
{
*failed = EINA_TRUE;
return 0;
}
*failed = EINA_FALSE;
// Special messages (no request)
switch (msg->type)
{
case CSERVE2_INDEX_LIST:
_server_index_list_set(msg, size);
free(msg);
return SPECIAL_RID_INDEX_LIST;
default:
break;
}
// Normal client to server requests
EINA_LIST_FOREACH_SAFE(_requests, l, l_next, cr)
{
Eina_Bool remove = EINA_TRUE;
if (cr->msg->rid != msg->rid) // dispatch this answer
continue;
found = EINA_TRUE;
if (cr->cb)
remove = cr->cb(cr->data, msg, size);
if (remove)
{
_requests = eina_list_remove_list(_requests, l);
free(cr->msg);
free(cr);
}
}
rid = msg->rid;
if (!found)
{
if (msg->type == CSERVE2_ERROR)
{
Msg_Error *error = (Msg_Error *) msg;
ERR("Cserve2 sent error %d for rid %d", error->error, rid);
}
else WRN("Got unexpected response %d for request %d", msg->type, rid);
}
free(msg);
return rid;
}
static Eina_Bool
_server_dispatch_until(unsigned int rid)
{
Eina_Bool failed;
unsigned int rrid;
sigset_t sigmask;
// We want to block some signals from interrupting pselect().
// If the kernel implements TIF_RESTORE_SIGMASK, the
// signal handlers should be called right after pselect
// SIGCHLD: apps can have children that just terminated
sigprocmask(0, NULL, &sigmask);
sigaddset(&sigmask, SIGCHLD);
while (1)
{
rrid = _server_dispatch(&failed);
if (rrid == rid) break;
#if TIMEOUT
else if (failed)
{
fd_set rfds;
struct timespec ts;
int sel;
if (socketfd == -1)
{
DBG("Reconnecting to server...");
if (!_server_connect())
{
ERR("Could not reconnect to cserve2!");
return EINA_FALSE;
}
if (socketfd == -1) return EINA_FALSE;
}
//DBG("Waiting for request %d...", rid);
FD_ZERO(&rfds);
FD_SET(socketfd, &rfds);
ts.tv_sec = TIMEOUT / 1000;
ts.tv_nsec = (TIMEOUT % 1000) * 1000000;
sel = pselect(socketfd + 1, &rfds, NULL, NULL, &ts, &sigmask);
if (sel == -1)
{
ERR("select() failed: [%d] %s", errno, strerror(errno));
/* FIXME: Depending on the error, we should probably try to reconnect to the server.
* Or even ask to [re]start the daemon.
* Or maybe just return EINA_FALSE after some timeout?
* -- jpeg
*/
if (errno == EINTR)
{
/* FIXME: Actually we might want to cancel our request
* ONLY when we received a SIGINT, but at this point
* there is no way we can know which signal we got.
* So we assume SIGINT and abandon this request.
*/
DBG("giving up on request %d after interrupt", rid);
return EINA_FALSE;
}
}