tizen 2.3.1 release
[framework/system/sdbd.git] / src / transport_local.c
index 609d26f..67a0887 100644 (file)
@@ -1,14 +1,14 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
+ * Licensed under the Apache License, Version 2.0 (the License);
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
+ * distributed under the License is distributed on an AS IS BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <arpa/inet.h>
 
 #include "sysdeps.h"
 #include <sys/types.h>
 
+#ifndef HAVE_WIN32_IPC
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <unistd.h>
+#endif
+
 #define  TRACE_TAG  TRACE_TRANSPORT
 #include "sdb.h"
+#include "strutils.h"
 
-#ifdef __ppc__
+#ifdef HAVE_BIG_ENDIAN
 #define H4(x)  (((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24)
 static inline void fix_endians(apacket *p)
 {
@@ -52,6 +60,11 @@ SDB_MUTEX_DEFINE( local_transports_lock );
 static atransport*  local_transports[ SDB_LOCAL_TRANSPORT_MAX ];
 #endif /* SDB_HOST */
 
+SDB_MUTEX_DEFINE( register_noti_lock );
+#ifndef _WIN32
+static pthread_cond_t noti_cond = PTHREAD_COND_INITIALIZER;
+#endif
+
 static int remote_read(apacket *p, atransport *t)
 {
     if(readx(t->sfd, &p->msg, sizeof(amessage))){
@@ -61,7 +74,7 @@ static int remote_read(apacket *p, atransport *t)
 
     fix_endians(p);
 
-#if 0 && defined __ppc__
+#if 0 && defined HAVE_BIG_ENDIAN
     D("read remote packet: %04x arg0=%0x arg1=%0x data_length=%0x data_check=%0x magic=%0x\n",
       p->msg.command, p->msg.arg0, p->msg.arg1, p->msg.data_length, p->msg.data_check, p->msg.magic);
 #endif
@@ -89,7 +102,7 @@ static int remote_write(apacket *p, atransport *t)
 
     fix_endians(p);
 
-#if 0 && defined __ppc__
+#if 0 && defined HAVE_BIG_ENDIAN
     D("write remote packet: %04x arg0=%0x arg1=%0x data_length=%0x data_check=%0x magic=%0x\n",
       p->msg.command, p->msg.arg0, p->msg.arg1, p->msg.data_length, p->msg.data_check, p->msg.magic);
 #endif
@@ -102,11 +115,11 @@ static int remote_write(apacket *p, atransport *t)
 }
 
 
-int local_connect(int port) {
-    return local_connect_arbitrary_ports(port-1, port);
+int local_connect(int port, const char *device_name) {
+    return local_connect_arbitrary_ports(port-1, port, device_name);
 }
 
-int local_connect_arbitrary_ports(int console_port, int sdb_port)
+int local_connect_arbitrary_ports(int console_port, int sdb_port, const char *device_name)
 {
     char buf[64];
     int  fd = -1;
@@ -123,34 +136,114 @@ int local_connect_arbitrary_ports(int console_port, int sdb_port)
 
     if (fd >= 0) {
         D("client: connected on remote on fd %d\n", fd);
-        close_on_exec(fd);
-        disable_tcp_nagle(fd);
+        if (close_on_exec(fd) < 0) {
+            D("failed to close fd exec\n");
+        }
+        if (disable_tcp_nagle(fd) < 0) {
+            D("failed to disable_tcp_nagle\n");
+        }
         snprintf(buf, sizeof buf, "%s%d", LOCAL_CLIENT_PREFIX, console_port);
-        register_socket_transport(fd, buf, sdb_port, 1);
+        register_socket_transport(fd, buf, sdb_port, 1, device_name);
         return 0;
     }
     return -1;
 }
 
+#if SDB_HOST /* tizen specific */
+int get_devicename_from_shdmem(int port, char *device_name)
+{
+    char *vms = NULL;
+#ifndef HAVE_WIN32_IPC
+    int shm_id;
+    void *shared_memory = (void *)0;
+
+    shm_id = shmget( (key_t)port-1, 0, 0);
+    if (shm_id == -1)
+        return -1;
+
+    shared_memory = shmat(shm_id, (void *)0, SHM_RDONLY);
+
+    if (shared_memory == (void *)-1)
+    {
+        D("faild to get shdmem key (%d) : %s\n", port, strerror(errno));
+        return -1;
+    }
+
+    vms = strstr((char*)shared_memory, VMS_PATH);
+    if (vms != NULL)
+        s_strncpy(device_name, vms+strlen(VMS_PATH), DEVICENAME_MAX);
+    else
+        s_strncpy(device_name, DEFAULT_DEVICENAME, DEVICENAME_MAX);
+
+#else /* _WIN32*/
+    HANDLE hMapFile;
+    char s_port[5];
+    char* pBuf;
+
+    sprintf(s_port, "%d", port-1);
+    hMapFile = OpenFileMapping(FILE_MAP_READ, TRUE, s_port);
+
+    if(hMapFile == NULL) {
+        D("faild to get shdmem key (%ld) : %s\n", port, GetLastError() );
+        return -1;
+    }
+    pBuf = (char*)MapViewOfFile(hMapFile,
+                            FILE_MAP_READ,
+                            0,
+                            0,
+                            50);
+    if (pBuf == NULL) {
+        D("Could not map view of file (%ld)\n", GetLastError());
+        CloseHandle(hMapFile);
+        return -1;
+    }
+
+    vms = strstr((char*)pBuf, VMS_PATH);
+    if (vms != NULL)
+        s_strncpy(device_name, vms+strlen(VMS_PATH), DEVICENAME_MAX);
+    else
+        s_strncpy(device_name, DEFAULT_DEVICENAME, DEVICENAME_MAX);
+    CloseHandle(hMapFile);
+#endif
+    D("init device name %s on port %d\n", device_name, port);
+
+    return 0;
+}
+
+int read_line(const int fd, char* ptr, size_t maxlen)
+{
+    unsigned int n = 0;
+    char c[2];
+    int rc;
+
+    while(n != maxlen) {
+        if((rc = sdb_read(fd, c, 1)) != 1)
+            return -1; // eof or read err
+
+        if(*c == '\n') {
+            ptr[n] = 0;
+            return n;
+        }
+        ptr[n++] = *c;
+    }
+    return -1; // no space
+}
+#endif
 
 static void *client_socket_thread(void *x)
 {
 #if SDB_HOST
-//     for (;;){
-       int  port  = DEFAULT_SDB_LOCAL_TRANSPORT_PORT;
-       int  count = SDB_LOCAL_TRANSPORT_MAX;
-
-       D("transport: client_socket_thread() starting\n");
-
-       /* try to connect to any number of running emulator instances     */
-       /* this is only done when SDB starts up. later, each new emulator */
-       /* will send a message to SDB to indicate that is is starting up  */
-       for ( ; count > 0; count--, port += 10 ) {
-               (void) local_connect(port);
-       }
-               
-//             sdb_sleep_ms(1000);
-//     }
+    int  port  = DEFAULT_SDB_LOCAL_TRANSPORT_PORT;
+    int  count = SDB_LOCAL_TRANSPORT_MAX;
+
+    D("transport: client_socket_thread() starting\n");
+
+    /* try to connect to any number of running emulator instances     */
+    /* this is only done when SDB starts up. later, each new emulator */
+    /* will send a message to SDB to indicate that is is starting up  */
+    for ( ; count > 0; count--, port += 10 ) { /* tizen specific */
+        (void) local_connect(port, NULL);
+    }
 #endif
     return 0;
 }
@@ -166,29 +259,346 @@ static void *server_socket_thread(void * arg)
     serverfd = -1;
     for(;;) {
         if(serverfd == -1) {
+            // socket_inaddr_any_server returns -1 if there is any error
             serverfd = socket_inaddr_any_server(port, SOCK_STREAM);
             if(serverfd < 0) {
                 D("server: cannot bind socket yet\n");
                 sdb_sleep_ms(1000);
                 continue;
             }
-            close_on_exec(serverfd);
+            if (close_on_exec(serverfd) < 0) {
+                D("failed to close serverfd exec\n");
+            }
         }
 
         alen = sizeof(addr);
         D("server: trying to get new connection from %d\n", port);
+
+        if (is_emulator()) {
+            // im ready to accept new client!
+            pthread_cond_broadcast(&noti_cond);
+        }
+
         fd = sdb_socket_accept(serverfd, &addr, &alen);
         if(fd >= 0) {
             D("server: new connection on fd %d\n", fd);
-            close_on_exec(fd);
-            disable_tcp_nagle(fd);
-            register_socket_transport(fd, "host", port, 1);
+            if (close_on_exec(fd) < 0) {
+                D("failed to close fd exec\n");
+            }
+            if (disable_tcp_nagle(fd) < 0) {
+                D("failed to disable_tcp_nagle\n");
+            }
+            register_socket_transport(fd, "host", port, 1, NULL);
         }
     }
     D("transport: server_socket_thread() exiting\n");
     return 0;
 }
 
+/* This is relevant only for SDB daemon running inside the emulator. */
+#if !SDB_HOST
+/*
+ * Redefine open and write for qemu_pipe.h that contains inlined references
+ * to those routines. We will redifine them back after qemu_pipe.h inclusion.
+ */
+#undef open
+#undef write
+#define open    sdb_open
+#define write   sdb_write
+#include "qemu_pipe.h"
+#undef open
+#undef write
+#define open    ___xxx_open
+#define write   ___xxx_write
+
+/* A worker thread that monitors host connections, and registers a transport for
+ * every new host connection. This thread replaces server_socket_thread on
+ * condition that sdbd daemon runs inside the emulator, and emulator uses QEMUD
+ * pipe to communicate with sdbd daemon inside the guest. This is done in order
+ * to provide more robust communication channel between SDB host and guest. The
+ * main issue with server_socket_thread approach is that it runs on top of TCP,
+ * and thus is sensitive to network disruptions. For instance, the
+ * ConnectionManager may decide to reset all network connections, in which case
+ * the connection between SDB host and guest will be lost. To make SDB traffic
+ * independent from the network, we use here 'sdb' QEMUD service to transfer data
+ * between the host, and the guest. See external/qemu/android/sdb-*.* that
+ * implements the emulator's side of the protocol. Another advantage of using
+ * QEMUD approach is that SDB will be up much sooner, since it doesn't depend
+ * anymore on network being set up.
+ * The guest side of the protocol contains the following phases:
+ * - Connect with sdb QEMUD service. In this phase a handle to 'sdb' QEMUD service
+ *   is opened, and it becomes clear whether or not emulator supports that
+ *   protocol.
+ * - Wait for the SDB host to create connection with the guest. This is done by
+ *   sending an 'accept' request to the sdb QEMUD service, and waiting on
+ *   response.
+ * - When new SDB host connection is accepted, the connection with sdb QEMUD
+ *   service is registered as the transport, and a 'start' request is sent to the
+ *   sdb QEMUD service, indicating that the guest is ready to receive messages.
+ *   Note that the guest will ignore messages sent down from the emulator before
+ *   the transport registration is completed. That's why we need to send the
+ *   'start' request after the transport is registered.
+ */
+#if 0
+static void *qemu_socket_thread(void * arg)
+{
+/* 'accept' request to the sdb QEMUD service. */
+static const char _accept_req[] = "accept";
+/* 'start' request to the sdb QEMUD service. */
+static const char _start_req[]  = "start";
+/* 'ok' reply from the sdb QEMUD service. */
+static const char _ok_resp[]    = "ok";
+
+    const int port = (int)arg;
+    int res, fd;
+    char tmp[256];
+    char con_name[32];
+
+    D("transport: qemu_socket_thread() starting\n");
+
+    /* sdb QEMUD service connection request. */
+    snprintf(con_name, sizeof(con_name), "qemud:sdb:%d", port);
+
+    /* Connect to the sdb QEMUD service. */
+    fd = qemu_pipe_open(con_name);
+    if (fd < 0) {
+        /* This could be an older version of the emulator, that doesn't
+         * implement sdb QEMUD service. Fall back to the old TCP way. */
+        sdb_thread_t thr;
+        D("sdb service is not available. Falling back to TCP socket.\n");
+        sdb_thread_create(&thr, server_socket_thread, arg);
+        return 0;
+    }
+
+    for(;;) {
+        /*
+         * Wait till the host creates a new connection.
+         */
+
+        /* Send the 'accept' request. */
+        res = sdb_write(fd, _accept_req, strlen(_accept_req));
+        if (res == strlen(_accept_req)) {
+            /* Wait for the response. In the response we expect 'ok' on success,
+             * or 'ko' on failure. */
+            res = sdb_read(fd, tmp, sizeof(tmp));
+            if (res != 2 || memcmp(tmp, _ok_resp, 2)) {
+                D("Accepting SDB host connection has failed.\n");
+                sdb_close(fd);
+            } else {
+                /* Host is connected. Register the transport, and start the
+                 * exchange. */
+                register_socket_transport(fd, "host", port, 1, NULL);
+                sdb_write(fd, _start_req, strlen(_start_req));
+            }
+
+            /* Prepare for accepting of the next SDB host connection. */
+            fd = qemu_pipe_open(con_name);
+            if (fd < 0) {
+                D("sdb service become unavailable.\n");
+                return 0;
+            }
+        } else {
+            D("Unable to send the '%s' request to SDB service.\n", _accept_req);
+            return 0;
+        }
+    }
+    D("transport: qemu_socket_thread() exiting\n");
+    return 0;
+}
+#endif  // !SDB_HOST
+#endif
+
+int connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen,
+        int nsec) {
+    int flags, n, error;
+    socklen_t len;
+    fd_set rset, wset;
+    struct timeval tval;
+
+    flags = fcntl(sockfd, F_GETFL, 0);
+    if(fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
+        D("failed to set file O_NONBLOCK status flag for socket %d: %s\n",
+                     sockfd, strerror(errno));
+    }
+
+    error = 0;
+    if ((n = connect(sockfd, (struct sockaddr *) saptr, salen)) < 0)
+        if (errno != EINPROGRESS)
+            return (-1);
+
+    /* Do whatever we want while the connect is taking place. */
+
+    if (n == 0)
+        goto done;
+    /* connect completed immediately */
+
+    FD_ZERO(&rset);
+    FD_SET(sockfd, &rset);
+    wset = rset;
+    tval.tv_sec = nsec;
+    tval.tv_usec = 0;
+    if ((n = select(sockfd + 1, &rset, &wset, NULL, nsec ? &tval : NULL))
+            == 0) {
+        sdb_close(sockfd); /* timeout */
+        errno = ETIMEDOUT;
+        return (-1);
+    }
+    if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
+        len = sizeof(error);
+        if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
+            return (-1); /* Solaris pending error */
+    } else
+        D("select error: sockfd not set\n");
+
+    done:
+    if(fcntl(sockfd, F_SETFL, flags) == -1) { /* restore file status flags */
+        D("failed to restore file status flag for socket %d: (errno:%d)\n",
+                 sockfd, errno);
+    }
+
+    if (error) {
+        sdb_close(sockfd); /* just in case */
+        errno = error;
+        return (-1);
+    }
+    return (0);
+}
+
+static int send_msg_to_localhost_from_guest(int local_port, char *request, int sock_type) {
+    int                  ret, s;
+    struct sockaddr_in   server;
+    int connect_timeout = 1;
+    memset( &server, 0, sizeof(server) );
+    server.sin_family      = AF_INET;
+    server.sin_port        = htons(local_port);
+    server.sin_addr.s_addr = inet_addr(QEMU_FORWARD_IP);
+
+    D("try to send notification to host(%s:%d) using %s:[%s]\n", QEMU_FORWARD_IP, local_port, (sock_type == 0) ? "tcp" : "udp", request);
+
+    if (sock_type == 0) {
+        s = socket(AF_INET, SOCK_STREAM, 0);
+    } else {
+        s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    }
+    if (s < 0) {
+        D("could not create socket\n");
+        return -1;
+    }
+    ret = connect_nonb(s, (struct sockaddr*) &server, sizeof(server), connect_timeout);
+    if (ret < 0) {
+        D("could not connect to server\n");
+        sdb_close(s);
+        return -1;
+    }
+    // writex handles EINTR and returns 0 if success
+    if (writex(s, request, strlen(request)) != 0) {
+        D("could not send notification request to host\n");
+        sdb_close(s);
+        return -1;
+    }
+    sdb_close(s);
+    D("sent notification request to host\n");
+
+    return 0;
+}
+
+static void notify_sdbd_startup() {
+    char                 buffer[512];
+    char                 request[512];
+
+    // send the request to sdbserver
+    char vm_name[256]={0,};
+    int base_port = get_emulator_forward_port();
+    int r = get_emulator_name(vm_name, sizeof vm_name);
+    int time = 0;
+    int try_limit_time = 60; // It would take 20 or more trials in slow VMs
+    if (base_port < 0 || r < 0) {
+        return;
+    }
+
+    // tell qemu sdbd is just started with udp
+    char sensord_buf[16];
+    while (time < try_limit_time) {
+        snprintf(sensord_buf, sizeof sensord_buf, "2\n");
+        if (send_msg_to_localhost_from_guest(base_port + 3, sensord_buf, 1) < 0) {
+            D("could not send sensord noti request, try again %dth\n", time+1);
+        } else {
+            // tell sdb server emulator's vms name
+            snprintf(request, sizeof request, "host:emulator:%d:%s",base_port + 1, vm_name);
+            snprintf(buffer, sizeof buffer, "%04x%s", strlen(request), request );
+
+            if (send_msg_to_localhost_from_guest(DEFAULT_SDB_PORT, buffer, 0) <0) {
+                D("could not send sdbd noti request. it might sdb server has not been started yet.\n");
+            } else {
+                break;
+            }
+
+        }
+        time ++;
+        sleep(1);
+    }
+}
+
+// send the "emulator" request to sdbserver
+static void notify_sdbd_startup_thread() {
+    char                 buffer[512];
+    char                 request[512];
+
+    char vm_name[256]={0,};
+    int base_port = get_emulator_forward_port();
+    int r = get_emulator_name(vm_name, sizeof vm_name);
+    int time = 0;
+    int try_limit_time = -1; // try_limit_time < 0 if unlimited
+    if (base_port < 0 || r < 0) {
+        return;
+    }
+
+    // XXX: Known issue - log collision
+    while (1) {
+        /* XXX: Known issue - timing issue
+        A request failure can happen at the starting up of a SDB server
+        (with very little probability)
+        If a SDB server establish a new connection and send a request
+        between D1 and D2, the request will fail.
+        Becuase when a SDB server gets a duplicated "emulator:" command,
+        it closes the existing transport connection.
+        */
+
+        // If there is any connected (via TCP/IP) SDB server, sleep 10 secs (D1)
+        if (get_connected_count(kTransportLocal) > 0) {
+            if (time >= 0) {
+                time = 0;
+                D("notify_sdbd_startup() success after %d trial(s)", time);
+            }
+            sleep(10);
+            continue;
+        }
+
+        // tell qemu sdbd is just started with udp
+        if (send_msg_to_localhost_from_guest(base_port + 3, "2\n", 1) < 0) {
+            D("could not send sensord noti request, try again %dth\n", time+1);
+            goto sleep_and_continue;
+        }
+
+        // tell sdb server emulator's vms name (D2)
+        snprintf(request, sizeof request, "host:emulator:%d:%s",base_port + 1, vm_name);
+        snprintf(buffer, sizeof buffer, "%04x%s", strlen(request), request );
+
+        if (get_connected_count(kTransportLocal) > 0) continue; // See comment above
+
+        if (send_msg_to_localhost_from_guest(DEFAULT_SDB_PORT, buffer, 0) <0) {
+            D("could not send sdbd noti request. it might sdb server has not been started yet.\n");
+            goto sleep_and_continue;
+        }
+        
+        //LOGI("sdbd noti request sent.\n");
+
+sleep_and_continue:
+        time++;
+        sleep(1);
+    }
+}
+
 void local_init(int port)
 {
     sdb_thread_t thr;
@@ -197,7 +607,24 @@ void local_init(int port)
     if(HOST) {
         func = client_socket_thread;
     } else {
+#if SDB_HOST
         func = server_socket_thread;
+#else
+        /* For the sdbd daemon in the system image we need to distinguish
+         * between the device, and the emulator. */
+#if 0 /* tizen specific */
+        char is_qemu[PROPERTY_VALUE_MAX];
+        property_get("ro.kernel.qemu", is_qemu, "");
+        if (!strcmp(is_qemu, "1")) {
+            /* Running inside the emulator: use QEMUD pipe as the transport. */
+            func = qemu_socket_thread;
+        } else
+#endif
+        {
+            /* Running inside the device: use TCP socket as the transport. */
+            func = server_socket_thread;
+        }
+#endif // !SDB_HOST
     }
 
     D("transport: local %s init\n", HOST ? "client" : "server");
@@ -206,6 +633,22 @@ void local_init(int port)
         fatal_errno("cannot create local socket %s thread",
                     HOST ? "client" : "server");
     }
+
+    /*
+     * wait until server socket thread made!
+     * get noti from server_socket_thread
+     */
+    if (is_emulator()) {
+        sdb_mutex_lock(&register_noti_lock);
+        pthread_cond_wait(&noti_cond, &register_noti_lock);
+
+        // thread start
+        if(sdb_thread_create(&thr, notify_sdbd_startup_thread, NULL)) {
+            fatal("cannot create notify_sdbd_startup_thread");
+            notify_sdbd_startup(); // defensive code
+        }
+        sdb_mutex_unlock(&register_noti_lock);
+    }
 }
 
 static void remote_kick(atransport *t)