barbieri pushed a commit to branch master. http://git.enlightenment.org/core/efl.git/commit/?id=651ff136163bf6fb4986f9dfaff09ca3f212178e
commit 651ff136163bf6fb4986f9dfaff09ca3f212178e Author: Gustavo Sverzut Barbieri <[email protected]> Date: Wed Oct 26 18:57:37 2016 -0200 addded efl_net_{socket,dialer,server}_unix This introduces AF_UNIX server and dialer, these are not available on Windows as in that platform we'll create a custom class for native 'local' communication. In the future we can add a wrapper class Efl.Net.Local that will use the class for each platform, but won't expose its details. For instance, if we ever expose 'credentials' (which I didn't because they are not portable), then it doesn't make sense to try to match that on Windows. The 'Efl.Net.Local' would just stick to the basics: Reader, Writer and Closer APIs. --- src/Makefile_Ecore_Con.am | 13 +- src/examples/ecore/.gitignore | 1 + src/examples/ecore/Makefile.am | 6 + src/examples/ecore/efl_net_dialer_unix_example.c | 212 +++++++++++++++++++ src/examples/ecore/efl_net_server_example.c | 12 ++ src/lib/ecore_con/Ecore_Con_Eo.h | 7 + src/lib/ecore_con/ecore_con.c | 68 +++++- src/lib/ecore_con/ecore_con_private.h | 3 +- src/lib/ecore_con/efl_net_dialer_unix.c | 236 +++++++++++++++++++++ src/lib/ecore_con/efl_net_dialer_unix.eo | 22 ++ src/lib/ecore_con/efl_net_server_unix.c | 259 +++++++++++++++++++++++ src/lib/ecore_con/efl_net_server_unix.eo | 28 +++ src/lib/ecore_con/efl_net_socket_unix.c | 77 +++++++ src/lib/ecore_con/efl_net_socket_unix.eo | 13 ++ 14 files changed, 950 insertions(+), 7 deletions(-) diff --git a/src/Makefile_Ecore_Con.am b/src/Makefile_Ecore_Con.am index 58a73ae..49cf65b 100644 --- a/src/Makefile_Ecore_Con.am +++ b/src/Makefile_Ecore_Con.am @@ -25,6 +25,14 @@ ecore_con_eolian_files = \ lib/ecore_con/ecore_con_eet_client_obj.eo \ lib/ecore_con/efl_network_url.eo +if HAVE_WINDOWS +else +ecore_con_eolian_files += \ + lib/ecore_con/efl_net_socket_unix.eo \ + lib/ecore_con/efl_net_dialer_unix.eo \ + lib/ecore_con/efl_net_server_unix.eo +endif + ecore_con_eolian_type_files = \ lib/ecore_con/efl_net_http_types.eot @@ -94,7 +102,10 @@ EXTRA_DIST2 += lib/ecore_con/ecore_con_legacy.c if HAVE_WINDOWS lib_ecore_con_libecore_con_la_SOURCES += lib/ecore_con/ecore_con_local_win32.c else -lib_ecore_con_libecore_con_la_SOURCES += lib/ecore_con/ecore_con_local.c +lib_ecore_con_libecore_con_la_SOURCES += lib/ecore_con/ecore_con_local.c \ +lib/ecore_con/efl_net_socket_unix.c \ +lib/ecore_con/efl_net_dialer_unix.c \ +lib/ecore_con/efl_net_server_unix.c endif lib_ecore_con_libecore_con_la_CPPFLAGS = -I$(top_builddir)/src/lib/efl @ECORE_CON_CFLAGS@ diff --git a/src/examples/ecore/.gitignore b/src/examples/ecore/.gitignore index 3aaec19..fd2d749 100644 --- a/src/examples/ecore/.gitignore +++ b/src/examples/ecore/.gitignore @@ -55,3 +55,4 @@ /efl_net_dialer_websocket_example /efl_net_dialer_websocket_autobahntestee /efl_net_dialer_udp_example +/efl_net_dialer_unix_example diff --git a/src/examples/ecore/Makefile.am b/src/examples/ecore/Makefile.am index da6b086..a0eb588 100644 --- a/src/examples/ecore/Makefile.am +++ b/src/examples/ecore/Makefile.am @@ -314,6 +314,12 @@ efl_net_dialer_websocket_autobahntestee_LDADD = $(ECORE_CON_COMMON_LDADD) efl_net_dialer_udp_example_SOURCES = efl_net_dialer_udp_example.c efl_net_dialer_udp_example_LDADD = $(ECORE_CON_COMMON_LDADD) +if ! HAVE_WINDOWS +EXTRA_PROGRAMS += efl_net_dialer_unix_example +efl_net_dialer_unix_example_SOURCES = efl_net_dialer_unix_example.c +efl_net_dialer_unix_example_LDADD = $(ECORE_CON_COMMON_LDADD) +endif + SRCS = \ ecore_animator_example.c \ ecore_buffer_example.c \ diff --git a/src/examples/ecore/efl_net_dialer_unix_example.c b/src/examples/ecore/efl_net_dialer_unix_example.c new file mode 100644 index 0000000..57110ef --- /dev/null +++ b/src/examples/ecore/efl_net_dialer_unix_example.c @@ -0,0 +1,212 @@ +#define EFL_BETA_API_SUPPORT 1 +#define EFL_EO_API_SUPPORT 1 +#include <Ecore.h> +#include <Ecore_Con.h> +#include <Ecore_Getopt.h> +#include <fcntl.h> +#include <ctype.h> + +static int retval = EXIT_SUCCESS; +static Eina_Bool do_read = EINA_FALSE; + +static void +_connected(void *data EINA_UNUSED, const Efl_Event *event) +{ + fprintf(stderr, + "INFO: connected to '%s' (%s)\n" + "INFO: - local address=%s\n" + "INFO: - read-after-write=%u\n", + efl_net_dialer_address_dial_get(event->object), + efl_net_socket_address_remote_get(event->object), + efl_net_socket_address_local_get(event->object), + do_read); +} + +static void +_eos(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED) +{ + fprintf(stderr, "INFO: end of stream. \n"); + ecore_main_loop_quit(); +} + +static void +_can_read(void *data EINA_UNUSED, const Efl_Event *event) +{ + char buf[4]; + Eina_Rw_Slice rw_slice = EINA_SLICE_ARRAY(buf); + Eina_Error err; + Eina_Bool can_read = efl_io_reader_can_read_get(event->object); + + /* NOTE: this message may appear with can read=0 BEFORE + * "read '...'" because efl_io_readr_read() will change the status + * of can_read to FALSE prior to return so we can print it! + */ + fprintf(stderr, "INFO: can read=%d\n", can_read); + if (!can_read) return; + if (!do_read) return; + + err = efl_io_reader_read(event->object, &rw_slice); + if (err) + { + fprintf(stderr, "ERROR: could not read: %s\n", eina_error_msg_get(err)); + retval = EXIT_FAILURE; + ecore_main_loop_quit(); + return; + } + + fprintf(stderr, "INFO: read '" EINA_SLICE_STR_FMT "'\n", EINA_SLICE_STR_PRINT(rw_slice)); +} + +static void +_can_write(void *data EINA_UNUSED, const Efl_Event *event) +{ + static Eina_Slice slice = EINA_SLICE_STR_LITERAL("Hello World!"); + Eina_Slice to_write; + Eina_Error err; + Eina_Bool can_write = efl_io_writer_can_write_get(event->object); + + /* NOTE: this message may appear with can write=0 BEFORE + * "wrote '...'" because efl_io_writer_write() will change the status + * of can_write to FALSE prior to return so we can print it! + */ + fprintf(stderr, "INFO: can write=%d (wanted bytes=%zd)\n", can_write, slice.len); + if (!can_write) return; + if (slice.len == 0) return; + + to_write = slice; + err = efl_io_writer_write(event->object, &to_write, &slice); + if (err) + { + fprintf(stderr, "ERROR: could not write: %s\n", eina_error_msg_get(err)); + retval = EXIT_FAILURE; + ecore_main_loop_quit(); + return; + } + + fprintf(stderr, "INFO: wrote '" EINA_SLICE_STR_FMT "', still pending=%zd bytes\n", EINA_SLICE_STR_PRINT(to_write), slice.len); + + if ((!do_read) && (slice.len == 0)) + { + retval = EXIT_SUCCESS; + ecore_main_loop_quit(); + return; + } +} + +static void +_resolved(void *data EINA_UNUSED, const Efl_Event *event) +{ + fprintf(stderr, "INFO: resolved %s => %s\n", + efl_net_dialer_address_dial_get(event->object), + efl_net_socket_address_remote_get(event->object)); +} + +static void +_error(void *data EINA_UNUSED, const Efl_Event *event) +{ + const Eina_Error *perr = event->info; + fprintf(stderr, "INFO: error: %d '%s'\n", *perr, eina_error_msg_get(*perr)); + retval = EXIT_FAILURE; +} + +EFL_CALLBACKS_ARRAY_DEFINE(dialer_cbs, + { EFL_NET_DIALER_EVENT_CONNECTED, _connected }, + { EFL_NET_DIALER_EVENT_RESOLVED, _resolved }, + { EFL_NET_DIALER_EVENT_ERROR, _error }, + { EFL_IO_READER_EVENT_EOS, _eos }, + { EFL_IO_READER_EVENT_CAN_READ_CHANGED, _can_read }, + { EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, _can_write } + ); + +static const Ecore_Getopt options = { + "efl_net_dialer_unix_example", /* program name */ + NULL, /* usage line */ + "1", /* version */ + "(C) 2016 Enlightenment Project", /* copyright */ + "BSD 2-Clause", /* license */ + /* long description, may be multiline and contain \n */ + "Example of Efl_Net_Dialer_Unix usage, sending a message and receiving a reply\n", + EINA_FALSE, + { + ECORE_GETOPT_STORE_TRUE('r', "read-after-write", "Do a read after writes are done."), + ECORE_GETOPT_VERSION('V', "version"), + ECORE_GETOPT_COPYRIGHT('C', "copyright"), + ECORE_GETOPT_LICENSE('L', "license"), + ECORE_GETOPT_HELP('h', "help"), + ECORE_GETOPT_STORE_METAVAR_STR(0, NULL, + "The address (URL) to dial", "address"), + ECORE_GETOPT_SENTINEL + } +}; + +int +main(int argc, char **argv) +{ + char *address = NULL; + Eina_Bool quit_option = EINA_FALSE; + Ecore_Getopt_Value values[] = { + ECORE_GETOPT_VALUE_BOOL(do_read), + + /* standard block to provide version, copyright, license and help */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -C/--copyright quits */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */ + + /* positional argument */ + ECORE_GETOPT_VALUE_STR(address), + + ECORE_GETOPT_VALUE_NONE /* sentinel */ + }; + int args; + Eo *dialer, *loop; + Eina_Error err; + + ecore_init(); + ecore_con_init(); + + args = ecore_getopt_parse(&options, values, argc, argv); + if (args < 0) + { + fputs("ERROR: Could not parse command line options.\n", stderr); + retval = EXIT_FAILURE; + goto end; + } + + if (quit_option) goto end; + + loop = ecore_main_loop_get(); + + args = ecore_getopt_parse_positional(&options, values, argc, argv, args); + if (args < 0) + { + fputs("ERROR: Could not parse positional arguments.\n", stderr); + retval = EXIT_FAILURE; + goto end; + } + + dialer = efl_add(EFL_NET_DIALER_UNIX_CLASS, loop, + efl_name_set(efl_added, "dialer"), + efl_event_callback_array_add(efl_added, dialer_cbs(), NULL)); + + err = efl_net_dialer_dial(dialer, address); + if (err != 0) + { + fprintf(stderr, "ERROR: could not dial '%s': %s", + address, eina_error_msg_get(err)); + goto no_mainloop; + } + + ecore_main_loop_begin(); + + fprintf(stderr, "INFO: main loop finished.\n"); + + no_mainloop: + efl_del(dialer); + + end: + ecore_con_shutdown(); + ecore_shutdown(); + + return retval; +} diff --git a/src/examples/ecore/efl_net_server_example.c b/src/examples/ecore/efl_net_server_example.c index 3bec44c..98f1615 100644 --- a/src/examples/ecore/efl_net_server_example.c +++ b/src/examples/ecore/efl_net_server_example.c @@ -474,6 +474,9 @@ EFL_CALLBACKS_ARRAY_DEFINE(server_cbs, static const char * protocols[] = { "tcp", "udp", +#ifndef _WIN32 + "unix", +#endif NULL }; @@ -600,6 +603,9 @@ main(int argc, char **argv) if (strcmp(protocol, "tcp") == 0) cls = EFL_NET_SERVER_TCP_CLASS; else if (strcmp(protocol, "udp") == 0) cls = EFL_NET_SERVER_UDP_CLASS; +#ifndef _WIN32 + else if (strcmp(protocol, "unix") == 0) cls = EFL_NET_SERVER_UNIX_CLASS; +#endif else { fprintf(stderr, "ERROR: unsupported protocol: %s\n", protocol); @@ -636,6 +642,12 @@ main(int argc, char **argv) EINA_LIST_FOREACH(udp_mcast_groups, lst, str) efl_net_server_udp_multicast_join(server, str); } +#ifndef _WIN32 + else if (cls == EFL_NET_SERVER_UNIX_CLASS) + { + efl_net_server_unix_unlink_before_bind_set(server, EINA_TRUE); /* makes testing easier */ + } +#endif /* an explicit call to efl_net_server_serve() after the object is * constructed allows for more complex setup, such as interacting diff --git a/src/lib/ecore_con/Ecore_Con_Eo.h b/src/lib/ecore_con/Ecore_Con_Eo.h index f8198f6..b3e3bf4 100644 --- a/src/lib/ecore_con/Ecore_Con_Eo.h +++ b/src/lib/ecore_con/Ecore_Con_Eo.h @@ -15,6 +15,13 @@ #include "efl_net_dialer_tcp.eo.h" #include "efl_net_server_tcp.eo.h" +#ifdef _WIN32 +#else +#include "efl_net_socket_unix.eo.h" +#include "efl_net_dialer_unix.eo.h" +#include "efl_net_server_unix.eo.h" +#endif + #include "efl_net_socket_udp.eo.h" #include "efl_net_dialer_udp.eo.h" #include "efl_net_server_udp.eo.h" diff --git a/src/lib/ecore_con/ecore_con.c b/src/lib/ecore_con/ecore_con.c index c6b27c8..9bc3388 100644 --- a/src/lib/ecore_con/ecore_con.c +++ b/src/lib/ecore_con/ecore_con.c @@ -3027,7 +3027,60 @@ _ecore_con_lookup_done(void *data, #include "efl_network_connector.eo.c" Eina_Bool -efl_net_ip_port_fmt(char *buf, int buflen, const struct sockaddr *addr) +efl_net_unix_fmt(char *buf, size_t buflen, SOCKET fd, const struct sockaddr_un *addr, socklen_t addrlen) +{ + const char *src = addr->sun_path; + socklen_t pathlen = addrlen - offsetof(struct sockaddr_un, sun_path); + + if (addr->sun_family != AF_UNIX) + { + ERR("unsupported address family: %d", addr->sun_family); + return EINA_FALSE; + } + + if (addrlen == offsetof(struct sockaddr_un, sun_path)) + { + int r = snprintf(buf, buflen, "unnamed:%d", fd); + if (r < 0) + { + ERR("snprintf(): %s", strerror(errno)); + return EINA_FALSE; + } + else if ((size_t)r > buflen) + { + ERR("buflen=%zu is too small, required=%d", buflen, r); + return EINA_FALSE; + } + return EINA_TRUE; + } + + if (src[0] != '\0') + { + if (buflen < pathlen) + { + ERR("buflen=%zu is too small, required=%u", buflen, pathlen); + return EINA_FALSE; + } + } + else + { + if (buflen < pathlen + sizeof("abstract:") - 2) + { + ERR("buflen=%zu is too small, required=%zu", buflen, pathlen + sizeof("abstract:") - 2); + return EINA_FALSE; + } + memcpy(buf, "abstract:", sizeof("abstract:") - 1); + buf += sizeof("abstract:") - 1; + src++; + } + + memcpy(buf, src, pathlen); + buf[pathlen] = '\0'; + return EINA_TRUE; +} + +Eina_Bool +efl_net_ip_port_fmt(char *buf, size_t buflen, const struct sockaddr *addr) { char p[INET6_ADDRSTRLEN]; const void *mem; @@ -3069,9 +3122,9 @@ efl_net_ip_port_fmt(char *buf, int buflen, const struct sockaddr *addr) ERR("could not snprintf(): %s", strerror(errno)); return EINA_FALSE; } - else if (r > buflen) + else if ((size_t)r > buflen) { - ERR("buffer is too small: %d, required %d", buflen, r); + ERR("buffer is too small: %zu, required %d", buflen, r); return EINA_FALSE; } @@ -3294,7 +3347,7 @@ static void _efl_net_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED) { Efl_Net_Connect_Async_Data *d = data; - char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = ""; + char buf[INET6_ADDRSTRLEN + sizeof("[]:65536") + sizeof(struct sockaddr_un)] = ""; int r; /* allows ecore_thread_cancel() to cancel at some points, see @@ -3319,7 +3372,12 @@ _efl_net_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED) } if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG)) - efl_net_ip_port_fmt(buf, sizeof(buf), d->addr); + { + if (d->addr->sa_family == AF_UNIX) + efl_net_unix_fmt(buf, sizeof(buf), d->sockfd, (const struct sockaddr_un *)d->addr, d->addrlen); + else + efl_net_ip_port_fmt(buf, sizeof(buf), d->addr); + } DBG("connecting fd=%d to %s", d->sockfd, buf); diff --git a/src/lib/ecore_con/ecore_con_private.h b/src/lib/ecore_con/ecore_con_private.h index 894356f..ef78f96 100644 --- a/src/lib/ecore_con/ecore_con_private.h +++ b/src/lib/ecore_con/ecore_con_private.h @@ -402,7 +402,8 @@ void _efl_net_server_udp_client_feed(Eo *client, Eina_Rw_Slice slice); void _efl_net_socket_udp_init(Eo *o, const struct sockaddr *addr, socklen_t addrlen, const char *str); -Eina_Bool efl_net_ip_port_fmt(char *buf, int buflen, const struct sockaddr *addr); +Eina_Bool efl_net_unix_fmt(char *buf, size_t buflen, SOCKET fd, const struct sockaddr_un *addr, socklen_t addrlen); +Eina_Bool efl_net_ip_port_fmt(char *buf, size_t buflen, const struct sockaddr *addr); /** * @brief splits an address in the format "host:port" in two diff --git a/src/lib/ecore_con/efl_net_dialer_unix.c b/src/lib/ecore_con/efl_net_dialer_unix.c new file mode 100644 index 0000000..d5ead1b --- /dev/null +++ b/src/lib/ecore_con/efl_net_dialer_unix.c @@ -0,0 +1,236 @@ +#define EFL_NET_DIALER_UNIX_PROTECTED 1 +#define EFL_NET_DIALER_PROTECTED 1 +#define EFL_NET_SOCKET_FD_PROTECTED 1 +#define EFL_NET_SOCKET_PROTECTED 1 +#define EFL_IO_READER_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "Ecore.h" +#include "Ecore_Con.h" +#include "ecore_con_private.h" + +#ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + +#define MY_CLASS EFL_NET_DIALER_UNIX_CLASS + +typedef struct _Efl_Net_Dialer_Unix_Data +{ + struct { + Ecore_Thread *thread; + Efl_Future *timeout; + } connect; + Efl_Future *connect_job; + Eina_Stringshare *address_dial; + Eina_Bool connected; + double timeout_dial; +} Efl_Net_Dialer_Unix_Data; + +EOLIAN static Eo* +_efl_net_dialer_unix_efl_object_constructor(Eo *o, Efl_Net_Dialer_Unix_Data *pd EINA_UNUSED) +{ + o = efl_constructor(efl_super(o, MY_CLASS)); + if (!o) return NULL; + + efl_net_dialer_timeout_dial_set(o, 30.0); + return o; +} + +EOLIAN static void +_efl_net_dialer_unix_efl_object_destructor(Eo *o, Efl_Net_Dialer_Unix_Data *pd) +{ + if (efl_io_closer_close_on_destructor_get(o) && + (!efl_io_closer_closed_get(o))) + efl_io_closer_close(o); + + if (pd->connect.thread) + { + ecore_thread_cancel(pd->connect.thread); + pd->connect.thread = NULL; + } + + efl_destructor(efl_super(o, MY_CLASS)); + + eina_stringshare_replace(&pd->address_dial, NULL); +} + +static void +_efl_net_dialer_unix_connect_timeout(void *data, const Efl_Event *ev EINA_UNUSED) +{ + Eo *o = data; + Efl_Net_Dialer_Unix_Data *pd = efl_data_scope_get(o, MY_CLASS); + Eina_Error err = ETIMEDOUT; + + if (pd->connect.thread) + { + ecore_thread_cancel(pd->connect.thread); + pd->connect.thread = NULL; + } + + efl_ref(o); + efl_io_reader_eos_set(o, EINA_TRUE); + efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err); + efl_unref(o); +} + +static void +_efl_net_dialer_unix_connected(void *data, const struct sockaddr *addr, socklen_t addrlen EINA_UNUSED, int sockfd, Eina_Error err) +{ + Eo *o = data; + Efl_Net_Dialer_Unix_Data *pd = efl_data_scope_get(o, MY_CLASS); + + pd->connect.thread = NULL; + + efl_ref(o); /* we're emitting callbacks then continuing the workflow */ + + if (err) goto error; + + efl_net_socket_fd_family_set(o, addr->sa_family); + efl_loop_fd_set(o, sockfd); + if (efl_net_socket_address_remote_get(o)) + { + efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, NULL); + efl_net_dialer_connected_set(o, EINA_TRUE); + } + else + { + err = EFL_NET_DIALER_ERROR_COULDNT_CONNECT; + efl_loop_fd_set(o, INVALID_SOCKET); + closesocket(sockfd); + goto error; + } + + error: + if (err) + { + efl_io_reader_eos_set(o, EINA_TRUE); + efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err); + } + + efl_unref(o); +} + +EOLIAN static Eina_Error +_efl_net_dialer_unix_efl_net_dialer_dial(Eo *o, Efl_Net_Dialer_Unix_Data *pd EINA_UNUSED, const char *address) +{ + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + socklen_t addrlen; + + EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(address[0] == '\0', EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_net_dialer_connected_get(o), EISCONN); + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EBADF); + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_loop_fd_get(o) >= 0, EALREADY); + + if (pd->connect.thread) + { + ecore_thread_cancel(pd->connect.thread); + pd->connect.thread = NULL; + } + + if (strncmp(address, "abstract:", strlen("abstract:")) == 0) + { + const char *path = address + strlen("abstract:"); + if (strlen(path) + 2 > sizeof(addr.sun_path)) + { + ERR("abstract path is too long: %s", path); + return EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST; + } + addr.sun_path[0] = '\0'; + memcpy(addr.sun_path + 1, path, strlen(path) + 1); + addrlen = strlen(path) + 2 + offsetof(struct sockaddr_un, sun_path); + } + else + { + const char *path = address; + if (strlen(path) + 1 > sizeof(addr.sun_path)) + { + ERR("path is too long: %s", path); + return EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST; + } + memcpy(addr.sun_path, path, strlen(path) + 1); + addrlen = strlen(path) + 1 + offsetof(struct sockaddr_un, sun_path); + } + + pd->connect.thread = efl_net_connect_async_new((const struct sockaddr *)&addr, addrlen, SOCK_STREAM, 0, + efl_io_closer_close_on_exec_get(o), + _efl_net_dialer_unix_connected, o); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->connect.thread, EINVAL); + + efl_net_dialer_address_dial_set(o, address); + + if (pd->connect.timeout) + efl_future_cancel(pd->connect.timeout); + if (pd->timeout_dial > 0.0) + { + efl_future_use(&pd->connect.timeout, efl_loop_timeout(efl_loop_get(o), pd->timeout_dial, o)); + efl_future_then(pd->connect.timeout, _efl_net_dialer_unix_connect_timeout, NULL, NULL, o); + efl_future_link(o, pd->connect.timeout); + } + + return 0; +} + +EOLIAN static void +_efl_net_dialer_unix_efl_net_dialer_address_dial_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Unix_Data *pd, const char *address) +{ + eina_stringshare_replace(&pd->address_dial, address); +} + +EOLIAN static const char * +_efl_net_dialer_unix_efl_net_dialer_address_dial_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Unix_Data *pd) +{ + return pd->address_dial; +} + +EOLIAN static void +_efl_net_dialer_unix_efl_net_dialer_timeout_dial_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Unix_Data *pd, double seconds) +{ + pd->timeout_dial = seconds; + if (pd->connect.timeout) + efl_future_cancel(pd->connect.timeout); + + if ((pd->timeout_dial > 0.0) && (pd->connect.thread)) + { + efl_future_use(&pd->connect.timeout, efl_loop_timeout(efl_loop_get(o), pd->timeout_dial, o)); + efl_future_then(pd->connect.timeout, _efl_net_dialer_unix_connect_timeout, NULL, NULL, o); + } +} + +EOLIAN static double +_efl_net_dialer_unix_efl_net_dialer_timeout_dial_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Unix_Data *pd) +{ + return pd->timeout_dial; +} + +EOLIAN static void +_efl_net_dialer_unix_efl_net_dialer_connected_set(Eo *o, Efl_Net_Dialer_Unix_Data *pd, Eina_Bool connected) +{ + if (pd->connect.timeout) + efl_future_cancel(pd->connect.timeout); + if (pd->connected == connected) return; + pd->connected = connected; + if (connected) efl_event_callback_call(o, EFL_NET_DIALER_EVENT_CONNECTED, NULL); +} + +EOLIAN static Eina_Bool +_efl_net_dialer_unix_efl_net_dialer_connected_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Unix_Data *pd) +{ + return pd->connected; +} + +EOLIAN static Eina_Error +_efl_net_dialer_unix_efl_io_closer_close(Eo *o, Efl_Net_Dialer_Unix_Data *pd EINA_UNUSED) +{ + efl_net_dialer_connected_set(o, EINA_FALSE); + return efl_io_closer_close(efl_super(o, MY_CLASS)); +} + +#include "efl_net_dialer_unix.eo.c" diff --git a/src/lib/ecore_con/efl_net_dialer_unix.eo b/src/lib/ecore_con/efl_net_dialer_unix.eo new file mode 100644 index 0000000..1169dc6 --- /dev/null +++ b/src/lib/ecore_con/efl_net_dialer_unix.eo @@ -0,0 +1,22 @@ +class Efl.Net.Dialer.Unix (Efl.Net.Socket.Unix, Efl.Net.Dialer) { + [[Connects to a local AF_UNIX server. + + The dial address is a file system path (portable) or + "abstract:ID" (Linux-only extension). + + \@note Proxies are meaningless for AF_UNIX family, thus are not + implemented. + + @since 1.19 + ]] + + implements { + Efl.Object.constructor; + Efl.Object.destructor; + Efl.Net.Dialer.dial; + Efl.Net.Dialer.address_dial; + Efl.Net.Dialer.connected; + Efl.Net.Dialer.timeout_dial; + Efl.Io.Closer.close; + } +} diff --git a/src/lib/ecore_con/efl_net_server_unix.c b/src/lib/ecore_con/efl_net_server_unix.c new file mode 100644 index 0000000..845df44 --- /dev/null +++ b/src/lib/ecore_con/efl_net_server_unix.c @@ -0,0 +1,259 @@ +#define EFL_NET_SERVER_UNIX_PROTECTED 1 +#define EFL_NET_SERVER_FD_PROTECTED 1 +#define EFL_NET_SERVER_PROTECTED 1 +#define EFL_LOOP_FD_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "Ecore.h" +#include "Ecore_Con.h" +#include "ecore_con_private.h" + +#ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + +/* no include EVIL as it's not supposed to be compiled on Windows */ + +#define MY_CLASS EFL_NET_SERVER_UNIX_CLASS + +typedef struct _Efl_Net_Server_Unix_Data +{ + Efl_Future *bind_job; + Eina_Bool unlink_before_bind; +} Efl_Net_Server_Unix_Data; + +EOLIAN static void +_efl_net_server_unix_efl_object_destructor(Eo *o, Efl_Net_Server_Unix_Data *pd EINA_UNUSED) +{ + SOCKET fd = efl_loop_fd_get(o); + + if (fd != INVALID_SOCKET) + { + const char *address = efl_net_server_address_get(o); + if ((address) && (strncmp(address, "abstract:", strlen("abstract:")) != 0)) + unlink(address); + } + + efl_destructor(efl_super(o, MY_CLASS)); +} + +static void +_efl_net_server_unix_bind_job(void *data, const Efl_Event *event EINA_UNUSED) +{ + Eo *o = data; + Efl_Net_Server_Unix_Data *pd = efl_data_scope_get(o, MY_CLASS); + const char *address = efl_net_server_address_get(o); + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + socklen_t addrlen; + SOCKET fd; + Eina_Error err = 0; + int r; + + pd->bind_job = NULL; + + efl_ref(o); /* we're emitting callbacks then continuing the workflow */ + + efl_net_server_fd_family_set(o, AF_UNIX); + + do + { + fd = efl_net_socket4(AF_UNIX, SOCK_STREAM, 0, + efl_net_server_fd_close_on_exec_get(o)); + if (fd == INVALID_SOCKET) + { + err = efl_net_socket_error_get(); + ERR("socket(AF_UNIX, SOCK_STREAM, 0): %s", eina_error_msg_get(err)); + goto error; + } + + if (strncmp(address, "abstract:", strlen("abstract:")) == 0) + { + const char *path = address + strlen("abstract:"); + if (strlen(path) + 2 > sizeof(addr.sun_path)) + { + ERR("abstract path is too long: %s", path); + err = EFL_NET_SERVER_ERROR_COULDNT_RESOLVE_HOST; + } + addr.sun_path[0] = '\0'; + memcpy(addr.sun_path + 1, path, strlen(path) + 1); + addrlen = strlen(path) + 2 + offsetof(struct sockaddr_un, sun_path); + } + else + { + const char *path = address; + if (strlen(path) + 1 > sizeof(addr.sun_path)) + { + ERR("path is too long: %s", path); + err = EFL_NET_SERVER_ERROR_COULDNT_RESOLVE_HOST; + } + memcpy(addr.sun_path, path, strlen(path) + 1); + addrlen = strlen(path) + 1 + offsetof(struct sockaddr_un, sun_path); + } + + if ((pd->unlink_before_bind) && (addr.sun_path[0] != '\0')) + { + DBG("unlinking AF_UNIX path '%s'", addr.sun_path); + unlink(addr.sun_path); + } + + r = bind(fd, (struct sockaddr *)&addr, addrlen); + if (r != 0) + { + err = efl_net_socket_error_get(); + if ((err == EADDRINUSE) && (pd->unlink_before_bind) && (addr.sun_path[0] != '\0')) + { + closesocket(fd); + err = 0; + continue; + } + DBG("bind(%d, %s): %s", fd, address, eina_error_msg_get(err)); + goto error; + } + break; + } + while (pd->unlink_before_bind); + + efl_loop_fd_set(o, fd); + + r = listen(fd, 0); + if (r != 0) + { + err = efl_net_socket_error_get(); + DBG("listen(%d): %s", fd, eina_error_msg_get(err)); + goto error; + } + + addrlen = sizeof(addr); + if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0) + { + err = efl_net_socket_error_get(); + ERR("getsockname(%d): %s", fd, eina_error_msg_get(err)); + goto error; + } + else + { + char str[sizeof(addr) + sizeof("abstract:")]; + if (!efl_net_unix_fmt(str, sizeof(str), fd, &addr, addrlen)) + ERR("could not format unix address"); + else + { + efl_net_server_address_set(o, str); + address = efl_net_server_address_get(o); + } + } + + DBG("fd=%d serving at %s", fd, address); + efl_net_server_serving_set(o, EINA_TRUE); + + error: + if (err) + { + efl_event_callback_call(o, EFL_NET_SERVER_EVENT_ERROR, &err); + if (fd) closesocket(fd); + efl_loop_fd_set(o, INVALID_SOCKET); + } + + efl_unref(o); +} + +EOLIAN static Eina_Error +_efl_net_server_unix_efl_net_server_serve(Eo *o, Efl_Net_Server_Unix_Data *pd, const char *address) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(address[0] == '\0', EINVAL); + + efl_net_server_address_set(o, address); + + if (pd->bind_job) + efl_future_cancel(pd->bind_job); + + efl_future_use(&pd->bind_job, efl_loop_job(efl_loop_get(o), o)); + efl_future_then(pd->bind_job, _efl_net_server_unix_bind_job, NULL, NULL, o); + efl_future_link(o, pd->bind_job); + + return 0; +} + +static Efl_Callback_Array_Item *_efl_net_server_unix_client_cbs(void); + +static void +_efl_net_server_unix_client_event_closed(void *data, const Efl_Event *event) +{ + Eo *server = data; + Eo *client = event->object; + + efl_event_callback_array_del(client, _efl_net_server_unix_client_cbs(), server); + if (efl_parent_get(client) == server) + efl_parent_set(client, NULL); + + efl_net_server_clients_count_set(server, efl_net_server_clients_count_get(server) - 1); +} + +EFL_CALLBACKS_ARRAY_DEFINE(_efl_net_server_unix_client_cbs, + { EFL_IO_CLOSER_EVENT_CLOSED, _efl_net_server_unix_client_event_closed }); + +static void +_efl_net_server_unix_efl_net_server_fd_client_add(Eo *o, Efl_Net_Server_Unix_Data *pd EINA_UNUSED, int client_fd) +{ + Eo *client = efl_add(EFL_NET_SOCKET_UNIX_CLASS, o, + efl_event_callback_array_add(efl_added, _efl_net_server_unix_client_cbs(), o), + efl_io_closer_close_on_exec_set(efl_added, efl_net_server_fd_close_on_exec_get(o)), + efl_io_closer_close_on_destructor_set(efl_added, EINA_TRUE), + efl_loop_fd_set(efl_added, client_fd)); + if (!client) + { + ERR("could not create client object fd=%d", client_fd); + closesocket(client_fd); + return; + } + + efl_net_server_clients_count_set(o, efl_net_server_clients_count_get(o) + 1); + efl_event_callback_call(o, EFL_NET_SERVER_EVENT_CLIENT_ADD, client); + + if (efl_ref_get(client) == 1) /* users must take a reference themselves */ + { + DBG("client %s was not handled, closing it...", + efl_net_socket_address_remote_get(client)); + efl_del(client); + } +} + +static void +_efl_net_server_unix_efl_net_server_fd_client_reject(Eo *o, Efl_Net_Server_Unix_Data *pd EINA_UNUSED, int client_fd) +{ + struct sockaddr_un addr; + socklen_t addrlen; + char str[sizeof(addr) + sizeof("abstract:")] = ""; + + addrlen = sizeof(addr); + if (getpeername(client_fd, (struct sockaddr *)&addr, &addrlen) != 0) + ERR("getpeername(%d): %s", client_fd, eina_error_msg_get(efl_net_socket_error_get())); + else + { + if (!efl_net_unix_fmt(str, sizeof(str), client_fd, &addr, addrlen)) + ERR("could not format rejected client unix address fd=%d", client_fd); + } + + closesocket(client_fd); + efl_event_callback_call(o, EFL_NET_SERVER_EVENT_CLIENT_REJECTED, str); +} + +static void +_efl_net_server_unix_unlink_before_bind_set(Eo *o EINA_UNUSED, Efl_Net_Server_Unix_Data *pd, Eina_Bool unlink_before_bind) +{ + pd->unlink_before_bind = unlink_before_bind; +} + +static Eina_Bool +_efl_net_server_unix_unlink_before_bind_get(Eo *o EINA_UNUSED, Efl_Net_Server_Unix_Data *pd) +{ + return pd->unlink_before_bind; +} + +#include "efl_net_server_unix.eo.c" diff --git a/src/lib/ecore_con/efl_net_server_unix.eo b/src/lib/ecore_con/efl_net_server_unix.eo new file mode 100644 index 0000000..c068a1d --- /dev/null +++ b/src/lib/ecore_con/efl_net_server_unix.eo @@ -0,0 +1,28 @@ +class Efl.Net.Server.Unix (Efl.Net.Server.Fd) { + [[An AF_UNIX server. + + @since 1.19 + ]] + + methods { + @property unlink_before_bind { + [[AF_UNIX paths may be stale due crashes, remove files and try again. + + If this property is $true, then it will unlink() before + bind() is done, repeating this process if EADDRINUSE. + + By default it's false and you will get EADDRINUSE. + ]] + values { + unlink_before_bind: bool; [[if $true, server will unlink() the path before bind() is called.]] + } + } + } + + implements { + Efl.Object.destructor; + Efl.Net.Server.serve; + Efl.Net.Server.Fd.client_add; + Efl.Net.Server.Fd.client_reject; + } +} diff --git a/src/lib/ecore_con/efl_net_socket_unix.c b/src/lib/ecore_con/efl_net_socket_unix.c new file mode 100644 index 0000000..2647a4e --- /dev/null +++ b/src/lib/ecore_con/efl_net_socket_unix.c @@ -0,0 +1,77 @@ +#define EFL_NET_SOCKET_UNIX_PROTECTED 1 +#define EFL_NET_SOCKET_FD_PROTECTED 1 +#define EFL_LOOP_FD_PROTECTED 1 +#define EFL_IO_READER_FD_PROTECTED 1 +#define EFL_IO_WRITER_FD_PROTECTED 1 +#define EFL_IO_CLOSER_FD_PROTECTED 1 +#define EFL_IO_READER_PROTECTED 1 +#define EFL_IO_WRITER_PROTECTED 1 +#define EFL_IO_CLOSER_PROTECTED 1 +#define EFL_NET_SOCKET_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "Ecore.h" +#include "Ecore_Con.h" +#include "ecore_con_private.h" + +#ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + +/* no include EVIL as it's not supposed to be compiled on Windows */ + +#define MY_CLASS EFL_NET_SOCKET_UNIX_CLASS + +typedef struct _Efl_Net_Socket_Unix_Data +{ +} Efl_Net_Socket_Unix_Data; + +EOLIAN static void +_efl_net_socket_unix_efl_loop_fd_fd_set(Eo *o, Efl_Net_Socket_Unix_Data *pd EINA_UNUSED, int fd) +{ + efl_loop_fd_set(efl_super(o, MY_CLASS), fd); + + if (fd != INVALID_SOCKET) + { + struct sockaddr_un addr; + socklen_t addrlen; + int family; + + family = efl_net_socket_fd_family_get(o); + if (family == AF_UNSPEC) return; + EINA_SAFETY_ON_TRUE_RETURN(family != AF_UNIX); + + addrlen = sizeof(addr); + if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0) + ERR("getsockname(%d): %s", fd, eina_error_msg_get(efl_net_socket_error_get())); + else + { + char str[sizeof(addr) + sizeof("abstract:")]; + + if (!efl_net_unix_fmt(str, sizeof(str), fd, &addr, addrlen)) + ERR("could not format local unix address"); + else + efl_net_socket_address_local_set(o, str); + } + + addrlen = sizeof(addr); + if (getpeername(fd, (struct sockaddr *)&addr, &addrlen) != 0) + ERR("getpeername(%d): %s", fd, eina_error_msg_get(efl_net_socket_error_get())); + else + { + char str[sizeof(addr) + sizeof("abstract:")]; + if (!efl_net_unix_fmt(str, sizeof(str), fd, &addr, addrlen)) + ERR("could not format remote unix address"); + else + efl_net_socket_address_remote_set(o, str); + } + } +} + +#include "efl_net_socket_unix.eo.c" diff --git a/src/lib/ecore_con/efl_net_socket_unix.eo b/src/lib/ecore_con/efl_net_socket_unix.eo new file mode 100644 index 0000000..48ea4cd --- /dev/null +++ b/src/lib/ecore_con/efl_net_socket_unix.eo @@ -0,0 +1,13 @@ +class Efl.Net.Socket.Unix (Efl.Net.Socket.Fd) { + [[A base UNIX socket. + + This is the common class and takes an existing FD, usually + created by an dialer or server. + + @since 1.19 + ]] + + implements { + Efl.Loop.Fd.fd.set; + } +} --
