Add shell remote connection
authorDaniel Zaoui <daniel.zaoui@yahoo.com>
Thu, 4 May 2017 07:17:05 +0000 (10:17 +0300)
committerDaniel Zaoui <daniel.zaoui@yahoo.com>
Mon, 5 Jun 2017 05:55:36 +0000 (08:55 +0300)
This feature is essential to debug remote applications.

src/Makefile_Efl.am
src/bin/efl/.gitignore
src/bin/efl/efl_debug_shell_bridge.c [new file with mode: 0644]
src/bin/efl/efl_debugd.c
src/lib/eina/eina_debug.c
src/lib/eina/eina_debug.h

index 6fda23e..3f8ef92 100644 (file)
@@ -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@
index b38a2ee..b051748 100644 (file)
@@ -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 (file)
index 0000000..66deac6
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <Eo.h>
+#include <Eina.h>
+#include <Ecore.h>
+
+#include <unistd.h>
+
+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();
+}
+
index 9da2648..0430441 100644 (file)
@@ -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();
index e1237c0..b2548e3 100644 (file)
@@ -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
+        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
-   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);
+        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)
 {
index 36d5760..bda8a64 100644 (file)
@@ -156,6 +156,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
  *
  * @param session the session to terminate
@@ -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
 /**
  * @}