diff --git a/src/Makefile_Efl.am b/src/Makefile_Efl.am index 6fda23e101..3f8ef92660 100644 --- a/src/Makefile_Efl.am +++ b/src/Makefile_Efl.am @@ -164,6 +164,7 @@ endif bin_PROGRAMS += \ bin/efl/efl_debugd \ +bin/efl/efl_debug_shell_bridge \ bin/efl/efl_debug bin_efl_efl_debugd_SOURCES = bin/efl/efl_debugd.c @@ -171,6 +172,11 @@ bin_efl_efl_debugd_CPPFLAGS = -I$(top_builddir)/src/bin/efl @EINA_CFLAGS@ @ECORE bin_efl_efl_debugd_LDADD = @EFL_LIBS@ @USE_EINA_INTERNAL_LIBS@ @USE_ECORE_INTERNAL_LIBS@ @USE_ECORE_CON_INTERNAL_LIBS@ bin_efl_efl_debugd_DEPENDENCIES = @USE_EINA_INTERNAL_LIBS@ @USE_ECORE_INTERNAL_LIBS@ @USE_ECORE_CON_INTERNAL_LIBS@ +bin_efl_efl_debug_shell_bridge_SOURCES = bin/efl/efl_debug_shell_bridge.c +bin_efl_efl_debug_shell_bridge_CPPFLAGS = -I$(top_builddir)/src/bin/efl @EINA_CFLAGS@ @ECORE_CFLAGS@ @ECORE_CON_CFLAGS@ +bin_efl_efl_debug_shell_bridge_LDADD = @USE_EINA_INTERNAL_LIBS@ @USE_ECORE_INTERNAL_LIBS@ @USE_ECORE_CON_INTERNAL_LIBS@ +bin_efl_efl_debug_shell_bridge_DEPENDENCIES = @USE_EINA_INTERNAL_LIBS@ @USE_ECORE_INTERNAL_LIBS@ @USE_ECORE_CON_INTERNAL_LIBS@ + bin_efl_efl_debug_SOURCES = bin/efl/efl_debug.c bin_efl_efl_debug_CPPFLAGS = -I$(top_builddir)/src/bin/efl @EINA_CFLAGS@ @ECORE_CFLAGS@ @ECORE_CON_CFLAGS@ bin_efl_efl_debug_LDADD = @EFL_LIBS@ @USE_EINA_INTERNAL_LIBS@ @USE_ECORE_INTERNAL_LIBS@ @USE_ECORE_CON_INTERNAL_LIBS@ diff --git a/src/bin/efl/.gitignore b/src/bin/efl/.gitignore index b38a2ee5ea..b051748b5f 100644 --- a/src/bin/efl/.gitignore +++ b/src/bin/efl/.gitignore @@ -1,2 +1,3 @@ efl_debugd +efl_debug_shell_bridge efl_debug diff --git a/src/bin/efl/efl_debug_shell_bridge.c b/src/bin/efl/efl_debug_shell_bridge.c new file mode 100644 index 0000000000..66deac6f34 --- /dev/null +++ b/src/bin/efl/efl_debug_shell_bridge.c @@ -0,0 +1,78 @@ +/* EINA - EFL data type library + * Copyright (C) 2015 Carsten Haitzler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; + * if not, see . + */ + +#include +#include +#include + +#include + +static Eina_Debug_Session *_shell_session = NULL, *_local_session = NULL; + +static int _nb_msgs = 0; + +static Eina_Bool +_check_nb_msgs(void *data EINA_UNUSED) +{ + static int last_nb_msgs = 0; + if (last_nb_msgs == _nb_msgs) + { + ecore_main_loop_quit(); + return EINA_FALSE; + } + last_nb_msgs = _nb_msgs; + return EINA_TRUE; +} + +static Eina_Debug_Error +_forward(Eina_Debug_Session *session, void *buffer) +{ + Eina_Debug_Packet_Header *hdr = buffer; + char *payload = ((char *)buffer) + sizeof(*hdr); + int size = hdr->size - sizeof(*hdr); + eina_debug_session_send_to_thread(session == _local_session ? _shell_session : _local_session, + hdr->cid, hdr->thread_id, hdr->opcode, payload, size); + if (session == _shell_session) _nb_msgs = (_nb_msgs + 1) % 1000000; + free(buffer); + return EINA_DEBUG_OK; +} + +int +main(int argc, char **argv) +{ + (void)argc; + (void)argv; + + eina_debug_disable(); + eina_init(); + ecore_init(); + + _local_session = eina_debug_local_connect(EINA_TRUE); + eina_debug_session_dispatch_override(_local_session, _forward); + + _shell_session = eina_debug_fds_attach(STDIN_FILENO, STDOUT_FILENO); + eina_debug_session_dispatch_override(_shell_session, _forward); + eina_debug_session_shell_codec_enable(_shell_session); + + ecore_timer_add(30.0, _check_nb_msgs, NULL); + ecore_main_loop_begin(); + + ecore_shutdown(); + eina_shutdown(); +} + diff --git a/src/bin/efl/efl_debugd.c b/src/bin/efl/efl_debugd.c index 9da2648723..0430441cc5 100644 --- a/src/bin/efl/efl_debugd.c +++ b/src/bin/efl/efl_debugd.c @@ -574,7 +574,7 @@ main(int argc EINA_UNUSED, char **argv EINA_UNUSED) _slave_added_opcode = _opcode_register("daemon/observer/slave_added", EINA_DEBUG_OPCODE_INVALID, NULL); _slave_deleted_opcode = _opcode_register("daemon/observer/slave_deleted", EINA_DEBUG_OPCODE_INVALID, NULL); _cid_from_pid_opcode = _opcode_register("daemon/info/cid_from_pid", EINA_DEBUG_OPCODE_INVALID, _cid_get_cb); - _test_loop_opcode = _opcode_register("daemon/test/loop", EINA_DEBUG_OPCODE_INVALID, _data_test_cb); + _test_loop_opcode = _opcode_register("Test/data_loop", EINA_DEBUG_OPCODE_INVALID, _data_test_cb); _server_launch(); _monitor(); diff --git a/src/lib/eina/eina_debug.c b/src/lib/eina/eina_debug.c index e1237c06cb..b2548e35ab 100644 --- a/src/lib/eina/eina_debug.c +++ b/src/lib/eina/eina_debug.c @@ -45,6 +45,7 @@ #include "eina_util.h" #include "eina_evlog.h" #include "eina_hash.h" +#include "eina_stringshare.h" #include "eina_debug_private.h" #ifdef __CYGWIN__ @@ -83,12 +84,17 @@ static int _module_init_opcode = EINA_DEBUG_OPCODE_INVALID; static int _module_shutdown_opcode = EINA_DEBUG_OPCODE_INVALID; static Eina_Hash *_modules_hash = NULL; +static int _bridge_keep_alive_opcode = EINA_DEBUG_OPCODE_INVALID; + static unsigned int _poll_time = 0; static Eina_Debug_Timer_Cb _poll_timer_cb = NULL; static void *_poll_timer_data = NULL; static Eina_Semaphore _thread_cmd_ready_sem; +typedef void *(*Eina_Debug_Encode_Cb)(const void *buffer, int size, int *ret_size); +typedef void *(*Eina_Debug_Decode_Cb)(const void *buffer, int size, int *ret_size); + typedef struct { int magic; /* Used to certify the validity of the struct */ @@ -101,12 +107,22 @@ struct _Eina_Debug_Session Eina_List **cbs; /* Table of callbacks lists indexed by opcode id */ Eina_List *opcode_reply_infos; Eina_Debug_Dispatch_Cb dispatch_cb; /* Session dispatcher */ + Eina_Debug_Encode_Cb encode_cb; /* Packet encoder */ + Eina_Debug_Decode_Cb decode_cb; /* Packet decoder */ + double encoding_ratio; /* Encoding ratio */ + /* List of shell commands to send before the communication + * with the daemon. Only used when a shell remote connection is requested. + */ + Eina_List *cmds; int cbs_length; /* cbs table size */ int fd_in; /* File descriptor to read */ int fd_out; /* File descriptor to write */ + /* Indicator to wait for input before continuing sending commands. + * Only used in shell remote connections */ + Eina_Bool wait_for_input : 1; }; -static void _opcodes_register_all(); +static void _opcodes_register_all(Eina_Debug_Session *session); static void _thread_start(Eina_Debug_Session *session); EAPI int @@ -121,16 +137,39 @@ eina_debug_session_send_to_thread(Eina_Debug_Session *session, int dest_id, int hdr.opcode = op; hdr.cid = dest_id; hdr.thread_id = thread_id; + if (!session->encode_cb) + { + e_debug("socket: %d / opcode %X / bytes to send: %d", + session->fd_out, op, hdr.size); #ifndef _WIN32 - e_debug("socket: %d / opcode %X / packet size %ld / bytes to send: %d", - session->fd_out, op, hdr->size + sizeof(int), total_size); - eina_spinlock_take(&_eina_debug_lock); - /* Sending header */ - write(session->fd_out, &hdr, sizeof(hdr)); - /* Sending payload */ - if (size) write(session->fd_out, data, size); - eina_spinlock_release(&_eina_debug_lock); + eina_spinlock_take(&_eina_debug_lock); + /* Sending header */ + write(session->fd_out, &hdr, sizeof(hdr)); + /* Sending payload */ + if (size) write(session->fd_out, data, size); + eina_spinlock_release(&_eina_debug_lock); #endif + } + else + { + unsigned char *total_buf = NULL; + void *new_buf; + int total_size = size + sizeof(hdr), new_size = 0; + total_buf = alloca(total_size); + memcpy(total_buf, &hdr, sizeof(hdr)); + if (size > 0) memcpy(total_buf + sizeof(hdr), data, size); + + new_buf = session->encode_cb(total_buf, total_size, &new_size); + e_debug("socket: %d / opcode %X / packet size %d / bytes to send: %d", + session->fd_out, op, total_size, new_size); +#ifndef _WIN32 + eina_spinlock_take(&_eina_debug_lock); + write(session->fd_out, new_buf, new_size); + eina_spinlock_release(&_eina_debug_lock); +#endif + free(new_buf); + } + return hdr.size; } @@ -159,33 +198,133 @@ _daemon_greet(Eina_Debug_Session *session) eina_debug_session_send(session, 0, EINA_DEBUG_OPCODE_HELLO, buf, size); } +static void +_cmd_consume(Eina_Debug_Session *session) +{ + const char *line = NULL; + do { + line = eina_list_data_get(session->cmds); + session->cmds = eina_list_remove_list(session->cmds, session->cmds); + if (line) + { + if (!strncmp(line, "WAIT", 4)) + { + e_debug("Wait for input"); + session->wait_for_input = EINA_TRUE; + return; + } + else if (!strncmp(line, "SLEEP_1", 7)) + { + e_debug("Sleep 1s"); + sleep(1); + } + else + { + e_debug("Apply cmd line: %s", line); + write(session->fd_out, line, strlen(line)); + write(session->fd_out, "\n", 1); + } + } + } + while (line); + /* When all the cmd has been applied, we can begin to send debug packets */ + _daemon_greet(session); + _opcodes_register_all(session); +} + +static Eina_List * +_parse_cmds(const char *cmds) +{ + Eina_List *lines = NULL; + while (cmds && *cmds) + { + char *tmp = strchr(cmds, '\n'); + Eina_Stringshare *line; + if (tmp) + { + line = eina_stringshare_add_length(cmds, tmp - cmds); + cmds = tmp + 1; + } + else + { + line = eina_stringshare_add(cmds); + cmds = NULL; + } + lines = eina_list_append(lines, line); + } + return lines; +} + #ifndef _WIN32 static int _packet_receive(unsigned char **buffer) { - unsigned char *packet_buf = NULL; - int rret = -1; - int size = 0; + unsigned char *packet_buf = NULL, *size_buf; + int rret = -1, ratio, size_sz; if (!_session) goto end; - if (read(_session->fd_in, &size, 4) == 4) + if (_session->wait_for_input) { + /* Wait for input */ + char c; + int flags = fcntl(_session->fd_in, F_GETFL, 0); + e_debug_begin("Characters received: "); + fcntl(_session->fd_in, F_SETFL, flags | O_NONBLOCK); + while (read(_session->fd_in, &c, 1) == 1) e_debug_continue("%c", c); + fcntl(_session->fd_in, F_SETFL, flags); + e_debug_end(); + _session->wait_for_input = EINA_FALSE; + _cmd_consume(_session); + return 0; + } + + ratio = _session->decode_cb && _session->encoding_ratio ? _session->encoding_ratio : 1.0; + size_sz = sizeof(int) * ratio; + size_buf = alloca(size_sz); + if ((rret = read(_session->fd_in, size_buf, size_sz)) == size_sz) + { + int size; + if (_session->decode_cb) + { + /* Decode the size if needed */ + void *size_decoded_buf = _session->decode_cb(size_buf, size_sz, NULL); + size = (*(int *)size_decoded_buf) * _session->encoding_ratio; + free(size_decoded_buf); + } + else + { + size = *(int *)size_buf; + } + e_debug("Begin to receive a packet of %d bytes", size); // allocate a buffer for the next bytes to receive packet_buf = malloc(size); if (packet_buf) { - int cur_packet_size = 4; - memcpy(packet_buf, &size, sizeof(int)); + int cur_packet_size = size_sz; + memcpy(packet_buf, size_buf, size_sz); /* Receive all the remaining packet bytes */ while (cur_packet_size < size) { rret = read(_session->fd_in, packet_buf + cur_packet_size, size - cur_packet_size); - if (rret <= 0) goto end; + if (rret <= 0) + { + e_debug("Error on read: %d", rret); + perror("Read"); + goto end; + } cur_packet_size += rret; } + if (_session->decode_cb) + { + /* Decode the packet if needed */ + void *decoded_buf = _session->decode_cb(packet_buf, size, &cur_packet_size); + free(packet_buf); + packet_buf = decoded_buf; + } *buffer = packet_buf; rret = cur_packet_size; + e_debug("Received a packet of %d bytes", cur_packet_size); } else { @@ -332,6 +471,11 @@ static const Eina_Debug_Opcode _EINA_DEBUG_MONITOR_OPS[] = { {NULL, NULL, NULL} }; +static const Eina_Debug_Opcode _EINA_DEBUG_BRIDGE_OPS[] = { + {"Bridge/Keep-Alive", &_bridge_keep_alive_opcode, NULL}, + {NULL, NULL, NULL} +}; + static void _static_opcode_register(Eina_Debug_Session *session, int op_id, Eina_Debug_Cb cb) @@ -534,6 +678,95 @@ err: return NULL; } +EAPI Eina_Debug_Session * +eina_debug_fds_attach(int fd_in, int fd_out) +{ + Eina_Debug_Session *session = calloc(1, sizeof(*session)); + session->dispatch_cb = eina_debug_dispatch; + session->fd_out = fd_out; + session->fd_in = fd_in; + // start the monitor thread + _thread_start(session); + _opcodes_register_all(session); + _last_local_session = session; + return session; +} + +static Eina_Bool +_bridge_keep_alive_send(void *data) +{ + Eina_Debug_Session *s = data; + eina_debug_session_send(s, 0, _bridge_keep_alive_opcode, NULL, 0); + return EINA_TRUE; +} + +EAPI Eina_Debug_Session * +eina_debug_shell_remote_connect(const char *cmds_str) +{ +#ifndef _WIN32 + Eina_List *cmds = _parse_cmds(cmds_str); + char *cmd = eina_list_data_get(cmds); + int pipeToShell[2], pipeFromShell[2]; + int pid = -1; + + cmds = eina_list_remove_list(cmds, cmds); + + pipe(pipeToShell); + pipe(pipeFromShell); + pid = fork(); + if (pid == -1) return EINA_FALSE; + if (!pid) + { + int i = 0; + const char *args[16] = { 0 }; + /* Child */ + close(STDIN_FILENO); + dup2(pipeToShell[0], STDIN_FILENO); + close(STDOUT_FILENO); + dup2(pipeFromShell[1], STDOUT_FILENO); + args[i++] = cmd; + do + { + cmd = strchr(cmd, ' '); + if (cmd) + { + *cmd = '\0'; + args[i++] = ++cmd; + } + } + while (cmd); + args[i++] = 0; + execvpe(args[0], (char **)args, environ); + perror("execvpe"); + exit(-1); + } + else + { + Eina_Debug_Session *session = calloc(1, sizeof(*session)); + /* Parent */ + session->dispatch_cb = eina_debug_dispatch; + session->fd_in = pipeFromShell[0]; + session->fd_out = pipeToShell[1]; + + int flags = fcntl(session->fd_in, F_GETFL, 0); + flags &= ~O_NONBLOCK; + if (fcntl(session->fd_in, F_SETFL, flags) == -1) perror(0); + + eina_debug_session_shell_codec_enable(session); + session->cmds = cmds; + _cmd_consume(session); + eina_debug_opcodes_register(session, _EINA_DEBUG_BRIDGE_OPS, NULL); + eina_debug_timer_add(10000, _bridge_keep_alive_send, session); + // start the monitor thread + _thread_start(session); + return session; + } +#else + (void) cmd; + return NULL; +#endif +} + EAPI Eina_Bool eina_debug_timer_add(unsigned int timeout_ms, Eina_Debug_Timer_Cb cb, void *data) { @@ -605,7 +838,7 @@ _monitor(void *_data) // if not negative - we have a real message if (size > 0) { - if(!_session->dispatch_cb(_session, buffer)) + if (EINA_DEBUG_OK != _session->dispatch_cb(_session, buffer)) { // something we don't understand e_debug("EINA DEBUG ERROR: Unknown command"); @@ -694,10 +927,67 @@ eina_debug_opcodes_register(Eina_Debug_Session *session, const Eina_Debug_Opcode session->opcode_reply_infos, info); //send only if _session's fd connected, if not - it will be sent when connected - if(session && session->fd_in != -1) + if(session && session->fd_in != -1 && !session->cmds) _opcodes_registration_send(session, info); } +/* + * Encoder for shell sessions + * Each byte is encoded in two bytes. + */ +static void * +_shell_encode_cb(const void *data, int src_size, int *dest_size) +{ + const char *src = data; + int new_size = src_size * 2; + char *dest = malloc(new_size); + int i; + for (i = 0; i < src_size; i++) + { + dest[(i << 1) + 0] = ((src[i] & 0xF0) >> 4) + 0x40; + dest[(i << 1) + 1] = ((src[i] & 0x0F) >> 0) + 0x40; + } + if (dest_size) *dest_size = new_size; + return dest; +} + +/* + * Decoder for shell sessions + * Each two bytes are merged into one byte. + */ +static void * +_shell_decode_cb(const void *data, int src_size, int *dest_size) +{ + const char *src = data; + int i = 0, j; + char *dest = malloc(src_size / 2); + if (!dest) goto error; + for (i = 0, j = 0; j < src_size; j++) + { + if ((src[j] & 0xF0) == 0x40 && (src[j + 1] & 0xF0) == 0x40) + { + dest[i++] = ((src[j] - 0x40) << 4) | ((src[j + 1] - 0x40)); + j++; + } + } + goto end; +error: + free(dest); + dest = NULL; +end: + if (dest_size) *dest_size = i; + return dest; +} + +EAPI void +eina_debug_session_shell_codec_enable(Eina_Debug_Session *session) +{ + if (!session) return; + session->encode_cb = _shell_encode_cb; + session->decode_cb = _shell_decode_cb; + session->encoding_ratio = 2.0; +} + static Eina_Debug_Error _self_dispatch(Eina_Debug_Session *session, void *buffer) { diff --git a/src/lib/eina/eina_debug.h b/src/lib/eina/eina_debug.h index 36d5760777..bda8a64bef 100644 --- a/src/lib/eina/eina_debug.h +++ b/src/lib/eina/eina_debug.h @@ -155,6 +155,44 @@ EAPI void eina_debug_disable(void); */ EAPI Eina_Debug_Session *eina_debug_local_connect(Eina_Bool is_master); +/** + * @brief Connect to remote shell daemon + * + * This function executes the shell. The given commands will be parsed and consumed one by one. + * The last command should be the execution of efl_debug_shell_bridge. + * + * @param cmds the commands to execute + * + * @return EINA_TRUE on success, EINA_FALSE otherwise. + */ +EAPI Eina_Debug_Session *eina_debug_shell_remote_connect(const char *cmds); + +/** + * @brief Create a session and attach the given file descriptors + * + * This function is essentially used for the shell bridge, as it needs to connect + * to the stdin/stdout file descriptors. + * + * @param fd_in the file descriptor to read from + * @param fd_out the file descriptor to write to + * + * @return EINA_TRUE on success, EINA_FALSE otherwise. + */ +EAPI Eina_Debug_Session *eina_debug_fds_attach(int fd_in, int fd_out); + +/** + * @brief Enable the shell codec on the given session + * + * This leads to encode and decode each packet that are going to/coming from + * on this session. + * It is needed for the communication between the debug tool and the + * shell bridge, as some characters are interpreted by the terminal (sh/ssh...). + * + * @param session the session + */ +EAPI void +eina_debug_session_shell_codec_enable(Eina_Debug_Session *session); + /** * @brief Terminate the session * @@ -248,6 +286,7 @@ EAPI int eina_debug_session_send_to_thread(Eina_Debug_Session *session, int dest EAPI Eina_Bool eina_debug_timer_add(unsigned int timeout_ms, Eina_Debug_Timer_Cb cb, void *data); EAPI int eina_debug_thread_id_get(void); + #endif /** * @}