]> oss.titaniummirror.com Git - tinyos-2.x.git/blobdiff - support/sdk/c/blip/driver/vty/vty.c
Merge TinyOS 2.1.1 into master.
[tinyos-2.x.git] / support / sdk / c / blip / driver / vty / vty.c
diff --git a/support/sdk/c/blip/driver/vty/vty.c b/support/sdk/c/blip/driver/vty/vty.c
new file mode 100644 (file)
index 0000000..10fed9b
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * "Copyright (c) 2008 The Regents of the University  of California.
+ * All rights reserved."
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice, the following
+ * two paragraphs and the author appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
+ *
+ */
+/*
+ * @author Stephen Dawson-Haggerty <stevedh@eecs.berkeley.edu>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "vty.h"
+#include "../logging.h"
+
+#define max(X,Y)  (((X) > (Y)) ?  (X) : (Y))
+
+typedef enum {
+  FALSE = 0,
+  TRUE  = 1,
+} bool;
+
+enum {
+  VTY_REMOVE_PENDING = 0x1,
+};
+
+struct vty_client {
+  int                 flags;
+  int                 readfd;
+  int                 writefd;
+  struct sockaddr_in6 ep;
+  struct vty_client  *next;
+  
+  int                 buf_off;
+  unsigned char       buf[2][1024];
+  int                 argc;
+  char                *argv[N_ARGS];
+};
+
+static int sock = -1;
+static struct vty_client *conns = NULL;
+static struct vty_cmd_table *cmd_tab;
+static char prompt_str[40];
+
+int vty_init(struct vty_cmd_table * cmds, short port) {
+  struct sockaddr_in6 si_me = {
+    .sin6_family = AF_INET6,
+    .sin6_port = htons(port),
+    .sin6_addr = IN6ADDR_ANY_INIT,
+  };
+  struct vty_client *tty_client;
+  int len, yes = 1;
+
+  conns = NULL;
+  cmd_tab = cmds;
+
+  strncpy(prompt_str, "blip:", 5);
+  gethostname(prompt_str+5, sizeof(prompt_str)- 5);
+  len=strlen(prompt_str+5);
+  prompt_str[len+5]='>';
+  prompt_str[len+6]=' ';
+  prompt_str[len+7]='\0';
+
+  if (isatty(fileno(stdin))) {
+    setbuf(stdin, NULL);
+    tty_client = (struct vty_client *)malloc(sizeof(struct vty_client));
+    memset(tty_client, 0, sizeof(struct vty_client));
+    tty_client->flags = 0;
+    tty_client->readfd = fileno(stdin);
+    tty_client->writefd = fileno(stdout);
+    tty_client->next = NULL;
+    conns = tty_client;
+  }
+
+  sock = socket(PF_INET6, SOCK_STREAM, 0);
+  if (sock < 0) {
+    log_fatal_perror("vty: socket");
+    return -1;
+  }
+
+  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
+    log_fatal_perror("vty: setsockopt");
+    goto abort;
+  }
+
+  if (bind(sock, (struct sockaddr *)&si_me, sizeof(si_me)) < 0) {
+    log_fatal_perror("vty: bind");
+    goto abort;
+  }
+
+  if (listen(sock, 2) < 0) {
+    log_fatal_perror("vty: listen");
+    goto abort;
+  }
+
+  return 0;
+ abort:
+  close(sock);
+  sock = -1;
+  return -1;
+}
+
+int vty_add_fds(fd_set *fds) {
+  int maxfd;
+  struct vty_client *cur;
+  if (sock >= 0) FD_SET(sock, fds);
+  maxfd = sock;
+
+  for (cur = conns; cur != NULL; cur = cur->next) {
+    if (cur->flags & VTY_REMOVE_PENDING) continue;
+    FD_SET(cur->readfd, fds);
+    maxfd = max(maxfd, cur->readfd);
+  }
+  return maxfd;
+}
+
+static void vty_print_string(struct vty_client *c, const char *fmt, ...) {
+  char buf[1024];
+  int len;
+  va_list ap;
+  va_start(ap, fmt);
+  len = vsnprintf(buf, 1024, fmt, ap);
+  len = write(c->writefd, buf, len);
+}
+
+static void prompt(struct vty_client *c) {
+  vty_print_string(c, prompt_str);
+}
+
+static void vty_new_connection() {
+  char addr_buf[512];
+  struct vty_client * c = malloc(sizeof(struct vty_client));
+  socklen_t len;
+  if (c == NULL) return;
+
+  len = sizeof(struct sockaddr_in6);
+  c->readfd = c->writefd = accept(sock, (struct sockaddr *)&c->ep, &len);
+  if (c->readfd < 0) {
+    error("Accept failed!\n");
+    log_fatal_perror(0);
+    free(c);
+    return;
+  }
+  c->buf_off = 0;
+  memset(c->buf, 0, sizeof(c->buf));
+
+  inet_ntop(AF_INET6, c->ep.sin6_addr.s6_addr, addr_buf, 512);
+  info("VTY: new connection accepted from %s\n", addr_buf);
+  vty_print_string(c, "Welcome to the blip console!\r\n");
+  vty_print_string(c, " type 'help' to print the command options\r\n");
+  prompt(c);
+  c->flags = 0;
+  c->next = conns;
+  conns = c;
+}
+
+void vty_close_connection(struct vty_client *c) {
+  close(c->readfd);
+  if (c->readfd != c->writefd) close(c->writefd);
+  c->flags |= VTY_REMOVE_PENDING;
+}
+
+
+void vty_dispatch_command(struct vty_client *c) {
+  int i;
+
+  if (c->argc > 0) {
+    for (i = 0; i < cmd_tab->n; i++) {
+      if (strncmp(c->argv[0], cmd_tab->table[i].name, 
+                  strlen(cmd_tab->table[i].name) + 1) == 0) {
+        cmd_tab->table[i].fn(c->writefd, c->argc, c->argv);
+        break;
+      }
+      
+      if (strncmp(c->argv[0], "quit", 4) == 0) {
+        vty_close_connection(c);
+        return;
+      }
+    }
+    if (i == cmd_tab->n) {
+      vty_print_string(c, "vty: %s: command not found\r\n", c->argv[0]);
+    }
+  }
+  prompt(c);
+}
+
+void vty_handle_data(struct vty_client *c) {
+  int len, i;
+  char addr_buf[512];
+  bool prompt_pending = FALSE;
+  len = read(c->readfd, c->buf[0] + c->buf_off, sizeof(c->buf) - c->buf_off);
+  if (len <= 0) {
+    inet_ntop(AF_INET6, c->ep.sin6_addr.s6_addr, addr_buf, 512);
+    warn("Invalid read on connection from %s: closing\n", addr_buf);
+    vty_close_connection(c);
+  }
+  c->buf_off += len;
+  // try to scan the whole line we're building up and remove/process
+  // any telnet escapes in there
+  
+  for (i = 0; i < c->buf_off; i++) {
+    int escape_len;
+
+    if (c->buf[0][i] == 255) {
+      escape_len = 2;
+      // process and remove a command;
+      // the command code is in buf[i+1]
+      switch (c->buf[0][i+1]) {
+      case TELNET_INTERRUPT:
+        // ignore the command buffer we've accumulated
+        memmove(&c->buf[0][0], &c->buf[0][i+2], c->buf_off - i - 2);
+        c->buf_off -= i + 2;
+        i = -1;
+        prompt_pending = TRUE;
+        continue;
+      }
+      if (c->buf[0][i+1] >= 250) {
+        unsigned char response[3];
+        // we don't do __anything__
+        response[0] = 255;
+        response[1] = TELNET_WONT;
+        response[2] = c->buf[0][i+2];
+        len = write(c->writefd, response, 3);
+        escape_len++;
+      }
+
+      // this isn't like the most efficient parser ever, but since we
+      // don't ask for anything and reply to everyone with DONT it seems okay.
+      memmove(&c->buf[0][i], &c->buf[0][i+escape_len], 
+              c->buf_off - (i + escape_len));
+      c->buf_off -= escape_len;
+      // restart processing at the same place
+      i--;
+    } else if (c->buf[0][i] == 4) {
+      // EOT : make C-d work
+      vty_close_connection(c);
+    }
+    // technically, clients are supposed to send \r\n as a newline.
+    // however, the client in busybox (an important one) doesn't
+    // escape the terminal input and so only sends \n.
+    if (/* i > 0 && c->buf[i-1] == '\r' && */ c->buf[0][i] == '\n') {
+
+      if (!(i <= 1 && (c->buf[0][i] == '\n' || c->buf[0][i] == '\r'))) {
+        c->buf[0][i] = '\0';
+        memcpy(c->buf[1], c->buf[0], i + 1);
+        init_argv((char *)c->buf[1], i + 1, c->argv, &c->argc);
+      }
+
+      prompt_pending = FALSE;
+      vty_dispatch_command(c);
+
+      // start a new command at the head of the buffer
+      memmove(&c->buf[0][0], &c->buf[0][i+1], c->buf_off - i - 1);
+      c->buf_off -= i + 1;
+      i = -1;
+    }
+  }
+
+  
+  if (prompt_pending) {
+    vty_print_string(c, "\r\n");
+    prompt(c);
+    prompt_pending = FALSE;
+  }
+}
+
+int vty_process(fd_set *fds) {
+  struct vty_client *prev, *cur, *next;
+  if (sock >= 0 && FD_ISSET(sock, fds)) {
+    vty_new_connection();
+  }
+  for (cur = conns; cur != NULL; cur = cur->next) {
+    if (FD_ISSET(cur->readfd, fds)) {
+      vty_handle_data(cur);
+    }
+  }
+
+  prev = NULL;
+  cur = conns;
+  while (cur != NULL) {
+    next = cur->next;
+    if (cur->flags & VTY_REMOVE_PENDING) {
+      char addr_buf[512];
+      inet_ntop(AF_INET6, cur->ep.sin6_addr.s6_addr, addr_buf, 512);
+      info("VTY: removing connection with endpoint %s\n", addr_buf);
+      free(cur);
+      if (cur == conns) conns = next;
+      if (prev != NULL) prev->next = next;
+    } else {
+      prev = cur;
+    }
+    cur = next;
+  }
+
+  return 0;
+}
+
+void vty_shutdown() {
+  struct vty_client *cur = conns, *next;
+  if (sock >= 0) close(sock);
+
+  while (cur != NULL) {
+    next = cur->next;
+    if (!(cur->flags & VTY_REMOVE_PENDING)) {
+      close(cur->readfd);
+      if (cur->readfd != cur->writefd) close(cur->writefd);
+    }
+    free(cur);
+    cur = next;
+  }
+}