Merge hdhomerun_drops into master

Update libhdhomerun and firmware to release 20130328

Conflicts:
	libhdhomerun/hdhomerun_channels.c

Change-Id: Idac9378db9d8e32c2afad988460f90b07e9bc5d7
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1679151
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,49 @@
+# Copyright 2011 Google Inc. All Rights Reserved.
+# Author: qianzhang@google.com (ken Zhang)
+#MAKEFILE for cross platform
+
+DEBUG=-g
+ifdef DEBUG
+  DEBUG_CFLAGS= -g $(DEBUG)
+else
+  DEBUG_CFLAGS= -O3
+endif
+
+ifdef TARGET
+	CROSS_PREFIX:=$(TARGET)
+else
+	CROSS_PREFIX:=
+endif
+
+CC:=$(CROSS_PREFIX)gcc
+LD:=$(CROSS_PREFIX)ld
+AR:=$(CROSS_PREFIX)ar
+RANLIB:=$(CROSS_PREFIX)ranlib
+STRIP:=$(CROSS_PREFIX)strip
+
+CFLAGS= -Wall -fPIC -O2 -Wpointer-arith
+
+FILES=sagelog.c hdhomerun_tuner.c hdhomerun_http.c hdhomerun_dev.c utility.c hdhomerun_plugin.c
+INC=  sagelog.h hdhomerun_tuner.h hdhomerun_http.h hdhomerun_dev.h utility.h
+EXTRA_INCS= ../tvstreamparser
+
+#all:hdhomerun
+all:hdhr_plugin.so
+dep_make:hdrun_dep
+
+hdhr_plugin.so:dep_make $(FILES) $(INC)
+	$(CC) $(CFLAGS) $(DEBUG_CFLAGS) $(FILES) -shared -o hdhr_plugin.so -I$(EXTRA_INCS) -L. -lhdhomerun  -lpthread
+
+hdhomerun:dep_make $(FILES) $(INC)
+	$(CC) $(CFLAGS) $(DEBUG_CFLAGS) $(FILES) -DTEST_APP -o hdhomerun -I$(EXTRA_INCS) -L. -lhdhomerun  -lpthread 
+
+hdrun_dep:
+	make -C libhdhomerun 
+	cp libhdhomerun/libhdhomerun.so .
+	
+clean:
+	rm -f $(ALL) hdrun libhdhomerun.so  hdhr_plugin.so get_http *~
+	make clean -C libhdhomerun 
+
+get_http:hdhomerun_http.c hdhomerun_http.h
+	$(CC) $(CFLAGS) $(DEBUG_CFLAGS) -DHTTP_GET_APP hdhomerun_http.c -o get_http
diff --git a/channel_buffer.c b/channel_buffer.c
new file mode 100644
index 0000000..0a8ded0
--- /dev/null
+++ b/channel_buffer.c
@@ -0,0 +1,466 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (Qiann Zhang)
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/errno.h>
+#include <features.h>
+#include <sys/time.h>
+#include <math.h>
+#include <time.h>
+#include <sys/timeb.h>
+#include <sys/un.h>
+#include <poll.h>
+#include <pthread.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include "channel_buffer.h"
+
+static const char *delimiter = " \t,;\r\n:=|'\"#";
+
+static int is_delimiter(char ch)
+{
+  int i;
+  if (ch == 0)
+    return 1;
+  for (i = 0; delimiter[i]; i++)
+    if (ch == delimiter[i])
+      return 1;
+  return 0;
+}
+
+// format name:zzz (or name=zzzz)
+int get_string(char *p, const char *name, char *string, int max_len,
+               char **next)
+{
+  char *s, *e;
+  e = p;
+
+  while (e != NULL && *e) {
+    s = strstr(e, name);
+    if (s == NULL)
+      return 0;
+    // is a token name
+    if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+        is_delimiter(*(s + strlen(name)))) {
+      s += (int)strlen(name);
+      while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+      if (*s == '=' || *s == ':') {
+        s++;
+        while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+        if (*s) {
+          int i = 0;
+          while (i++ < max_len && *s && *s != ' ' && *s != '\t' && *s != '\n' &&
+                 *s != '\r')
+            *string++ = *s++;
+
+          *string = 0x0;  // terminator
+          if (next != NULL) {
+            while (*s && *s != ' ' && *s != '\t' && *s != '\n' && *s != '\r')
+              s++;
+            *next = s;
+          }
+          return 1;
+        }
+      }
+    }
+    e = ++s;
+  }
+  return 0;
+}
+
+// format name:"zzz" (or name="zzzz")
+int get_string_with_quote(char *p, const char *name, char *string, int max_len,
+                          char **next)
+{
+  char *s, *e;
+  e = p;
+
+  while (e != NULL && *e) {
+    s = strstr(e, name);
+    if (s == NULL)
+      return 0;
+    // is a token name
+    if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+        is_delimiter(*(s + strlen(name)))) {
+      s += (int)strlen(name);
+      while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+      if (*s == '=' || *s == ':') {
+        s++;
+        while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+        if (*s == '\"') {
+          int i = 0;
+          s++;
+          while (i++ < max_len && *s && *s != '\"') *string++ = *s++;
+
+          *string = 0x0;  // terminator
+
+          if (next != NULL) {
+            while (*s && *s != '\"') s++;
+            *next = s + 1;
+          }
+          return 1;
+        } else
+          return 0;
+      }
+    }
+    e = ++s;
+  }
+  return 0;
+}
+
+int get_string_token(char *p, int num, char *string, int max_len, char **next)
+{
+  int count = 0;
+  char *s;
+  s = p;
+  if (s == NULL)
+    return 0;
+  while (*s) {
+    while (*s && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'))
+      s++;  // skip white space
+    if (*s == 0)
+      break;
+
+    if (count++ != num) {
+      while (*s && !(*s == ' ' || *s == '\t' || *s == ';' || *s == ',' ||
+                     *s == '\r' || *s == '\n'))
+        s++;  // skip a token
+    } else {
+      if (*s) {
+        int i = 0;
+        while (i++ < max_len && *s && !(*s == ' ' || *s == '\t' || *s == ';' ||
+                                        *s == ',' || *s == '\r' || *s == '\n'))
+          *string++ = *s++;
+
+        *string = 0x0;  // terminator
+        if (next != NULL) {
+          while (*s && !(*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'))
+            s++;
+          *next = s;
+        }
+        return 1;
+      }
+    }
+    if (*s)
+      s++;
+  }
+  return 0;
+}
+
+int get_int_val(char *p, const char *name, int *val, char **next)
+{
+  char *s, *e;
+  e = p;
+  while (e != NULL && *e) {
+    s = strstr(e, name);
+    if (s == NULL)
+      return 0;
+    // is a token name
+    if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+        is_delimiter(*(s + strlen(name)))) {
+      s += strlen(name);
+      while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+      if (*s == '=' || *s == ':') {
+        s++;
+        while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+        if (*s) {
+          *val = atoi(s);
+          if (*val < 0 && *s == '-')
+            s++;
+          while (*s >= '0' && *s <= '9') s++;
+          if (next != NULL)
+            *next = s;
+          return 1;
+        }
+      }
+    }
+    e = ++s;
+  }
+  return 0;
+}
+
+// format: name val; ext
+int get_cfg_val(char *string, char *name, int max_name_lenn, int *Val,
+                char *ext, int max_ext_len)
+{
+  const char *delima = " \t,;\r\n";
+  char *token, *saved_ptr;
+  if (name == NULL || ext == NULL || max_name_lenn == 0 || max_ext_len == 0)
+    return 0;
+
+  name[0] = 0;
+  *Val = 0;
+  ext[0] = 0;
+
+  token = (char *)strtok_r(string, delima, &saved_ptr);
+  if (token != NULL)
+    strncpy(name, token, max_name_lenn);
+  token = (char *)strtok_r(saved_ptr, delima, &saved_ptr);
+  if (token != NULL) {
+    *Val = atoi(token);
+    strncpy(ext, token, max_ext_len);
+  }
+  token = (char *)strtok_r(saved_ptr, delima, &saved_ptr);
+  if (token != NULL)
+    strncpy(ext, token, max_ext_len);
+  return 1;
+}
+
+int make_channel_entry_buffer(struct channel_entry_t *ce, char *buf, int size)
+{
+  int pos = 0;
+  pos +=
+      snprintf(buf + pos, size - pos, "ch:%03d state:%d ", ce->ch, ce->state);
+  if (ce->ctrl_bits)
+    pos += snprintf(buf + pos, size - pos, "ctrl:%d ", ce->ctrl_bits);
+  if (ce->ch_name[0])
+    pos += snprintf(buf + pos, size - pos, "name:%s ", ce->ch_name);
+  if (ce->src_ip[0])
+    pos += snprintf(buf + pos, size - pos, "ip:%s port:%d ", ce->src_ip,
+                    ce->src_port);
+  if (ce->epg_id[0])
+    pos += snprintf(buf + pos, size - pos, "epg:%s ", ce->epg_id);
+  if (ce->dev_name[0])
+    pos += snprintf(buf + pos, size - pos, "dev:%s ", ce->dev_name);
+  if (ce->dev_tuning[0])
+    pos += snprintf(buf + pos, size - pos, "tune:%s ", ce->dev_tuning);
+  if (ce->av_inf[0])
+    pos += snprintf(buf + pos, size - pos, "av:\"%s\" ", ce->av_inf);
+  if (ce->ch_desc[0])
+    pos += snprintf(buf + pos, size - pos, "desc:%s ", ce->ch_desc);
+  pos += snprintf(buf + pos, size - pos, "\n");
+  return pos;
+}
+
+int parse_channel_entry_t_buffer(struct channel_entry_t *ce, char *buf)
+{
+  int item = 0;
+  char *p = buf;
+  if (!get_int_val(p, "ch", &ce->ch, NULL))
+    ce->ch = -1;
+  else if (!get_int_val(p, "state", &ce->state, NULL))
+    ce->ch = 1;
+  else
+    item++;
+  if (!get_int_val(p, "ctrl", &ce->ctrl_bits, NULL))
+    ce->ctrl_bits = 0;
+  else
+    item++;
+  if (!get_string(p, "name", ce->ch_name, sizeof(ce->ch_name) - 1, NULL))
+    ce->ch_name[0] = 0x0;
+  else
+    item++;
+  if (!get_string(p, "desc", ce->ch_desc, sizeof(ce->ch_desc) - 1, NULL))
+    ce->ch_desc[0] = 0x0;
+  else
+    item++;
+  if (!get_string(p, "ip", ce->src_ip, sizeof(ce->src_ip) - 1, NULL))
+    ce->src_ip[0] = 0x0;
+  else
+    item++;
+  if (!get_int_val(p, "port", &ce->src_port, NULL))
+    ce->src_port = -1;
+  else
+    item++;
+  if (!get_string(p, "epg", ce->epg_id, sizeof(ce->epg_id) - 1, NULL))
+    ce->epg_id[0] = 0x0;
+  else
+    item++;
+  if (!get_string(p, "dev", ce->dev_name, sizeof(ce->dev_name) - 1, NULL))
+    ce->dev_name[0] = 0x0;
+  else
+    item++;
+  if (!get_string(p, "tuning", ce->dev_tuning, sizeof(ce->dev_tuning) - 1,
+                  NULL))
+    ce->dev_tuning[0] = 0x0;
+  else
+    item++;
+  if (!get_string_with_quote(p, "av", ce->av_inf, sizeof(ce->av_inf) - 1, NULL))
+    ce->av_inf[0] = 0x0;
+  else
+    item++;
+  if (!get_string_with_quote(p, "desc", ce->ch_desc, sizeof(ce->ch_desc) - 1,
+                             NULL))
+    ce->ch_desc[0] = 0x0;
+  else
+    item++;
+
+  return item;
+}
+
+struct channel_list_t *create_channel_list(int num)
+{
+  if (num <= 0)
+    return NULL;
+  struct channel_list_t *cl = malloc(sizeof(struct channel_list_t));
+  if (cl == NULL)
+    return NULL;
+  cl->num = num;
+  cl->channel_num = 0;
+  cl->ce = malloc(sizeof(struct channel_entry_t) * num);
+  if (cl->ce == NULL) {
+    free(cl);
+    return NULL;
+  }
+  memset(cl->ce, 0x0, sizeof(struct channel_entry_t) * num);
+  return cl;
+}
+
+void release_channel_list(struct channel_list_t *cl)
+{
+  if (cl) {
+    if (cl->ce)
+      free(cl->ce);
+    free(cl);
+  }
+}
+
+struct channel_list_t *join_channel_list(struct channel_list_t *cl1,
+                                         struct channel_list_t *cl2)
+{
+  int i, n, channel_num;
+  if (cl1 == NULL)
+    return cl2;
+  if (cl2 == NULL)
+    return cl1;
+
+  channel_num = cl1->channel_num + cl2->channel_num;
+  if (channel_num < cl1->num) {
+    n = 0;
+    for (i = cl1->channel_num; i < channel_num; i++) {
+      cl1->ce[i] = cl2->ce[n++];
+      cl1->ce[i].ch = i;
+    }
+    cl2->channel_num = channel_num;
+    release_channel_list(cl2);
+    return cl1;
+  } else if (channel_num < cl2->num) {
+    n = 0;
+    for (i = cl2->channel_num; i < channel_num; i++) {
+      cl2->ce[i] = cl1->ce[n++];
+      cl2->ce[i].ch = i;
+    }
+    cl2->channel_num = channel_num;
+    release_channel_list(cl1);
+    return cl2;
+  } else {
+    struct channel_list_t *cl = create_channel_list(channel_num);
+    for (i = 0; i < cl1->channel_num; i++) {
+      cl->ce[i] = cl1->ce[i];
+      cl->ce[i].ch = i;
+    }
+    for (n = 0; n < cl2->channel_num; n++, i++) {
+      cl->ce[i] = cl2->ce[n];
+      cl->ce[i].ch = i;
+    }
+    release_channel_list(cl1);
+    release_channel_list(cl2);
+    cl->channel_num = channel_num;
+    return cl;
+  }
+  return NULL;
+}
+
+struct channel_list_t *load_channel_entry(char *file_name)
+{
+  int item;
+  int count = 0;
+  char buf[2048] = {0};
+  struct channel_list_t *cl;
+  FILE *fp;
+  fp = fopen(file_name, "r");
+  if (fp == NULL) {
+    // printf( "Can't open file %s to load channel table, errno:%d\n",
+    // file_name, errno );
+    return NULL;
+  }
+  // count entry number
+  while (!feof(fp)) {
+    char *p = fgets(buf, sizeof(buf) - 1, fp);
+    if (p == NULL)
+      break;
+    if (buf[0] != '#' && strlen(buf) > 4)
+      count++;
+  }
+  fseek(fp, 0, SEEK_SET);
+  cl = create_channel_list(count);
+  while (!feof(fp) && cl->channel_num < cl->num) {
+    char *p = fgets(buf, sizeof(buf) - 1, fp);
+    if (p == NULL)
+      break;
+
+    // skip comments
+    if (buf[0] == '#')
+      continue;
+
+    item = parse_channel_entry_t_buffer(&cl->ce[cl->channel_num], buf);
+    if (item > 0) {
+      cl->channel_num++;
+    }
+  }
+  fclose(fp);
+  return cl;
+}
+
+int save_channel_entry(char *file_name, struct channel_list_t *cl)
+{
+  int i;
+  char buf[2048];
+  FILE *fp;
+  if (cl == NULL || cl->channel_num == 0)
+    return 0;
+
+  fp = fopen(file_name, "w");
+  if (fp == NULL) {
+    // printf( "Can't open file %s to save channel table, errno:%d\n",
+    // file_name, errno );
+    return -1;
+  }
+  fputs("#Google sagetv channel table ver 1.0., maximum 2048 bytes per line\n",
+        fp);
+  fprintf(fp, "total_channel:%d\n", cl->channel_num);
+  for (i = 0; i < cl->channel_num; i++) {
+    make_channel_entry_buffer(&cl->ce[i], buf, sizeof(buf));
+    fputs(buf, fp);
+  }
+  fclose(fp);
+  return 1;
+}
+
+char *join_alloc_string(char *str1, char *str2)
+{
+  int len, len1, len2;
+  char *str;
+  if (str1 == NULL)
+    return str2;
+  if (str2 == NULL)
+    return str1;
+  len1 = strlen(str1);
+  len2 = strlen(str2);
+  len = len1 + len2;
+  str = (char *)malloc(len + 1);
+  strncpy(str, str1, len1);
+  strncpy(str + len1, str2, len2);
+  str[len] = 0x0;
+  free(str1);
+  free(str2);
+  return str;
+}
diff --git a/channel_buffer.h b/channel_buffer.h
new file mode 100644
index 0000000..194ebd1
--- /dev/null
+++ b/channel_buffer.h
@@ -0,0 +1,53 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (ken Zhang)
+
+#ifndef _CHANNEL_BUFFER_H_
+#define _CHANNEL_BUFFER_H_
+
+#ifndef MAX
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+#ifndef MIN
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+struct channel_entry_t {
+  int ch;
+  int state;
+  int ctrl_bits;  // 0x01 fix multicast IP/port
+  char ch_name[20];
+  char ch_desc[32];
+  char src_ip[16];
+  int src_port;
+  char epg_id[8];
+  char dev_name[32];
+  char dev_tuning[16];
+  char av_inf[1024];
+  int frq;
+};
+
+struct channel_list_t {
+  int num;
+  int channel_num;
+  struct channel_entry_t *ce;
+};
+
+char *join_alloc_string(char *str1, char *str2);
+int get_string(char *p, const char *name, char *string, int max_len,
+               char **next);
+int get_string_with_quote(char *p, const char *name, char *string, int max_len,
+                          char **next);
+int get_string_token(char *p, int num, char *string, int max_len, char **next);
+int get_int_val(char *p, const char *name, int *val, char **next);
+
+struct channel_list_t *create_channel_list(int num);
+void release_channel_list(struct channel_list_t *cl);
+struct channel_list_t *join_channel_list(struct channel_list_t *cl1,
+                                         struct channel_list_t *cl2);
+
+int make_channel_entry_buffer(struct channel_entry_t *ce, char *buf, int size);
+int parse_channel_entry_buffer(struct channel_entry_t *ce, char *buf);
+struct channel_list_t *load_channel_entry(char *file_name);
+int save_channel_entry(char *file_name, struct channel_list_t *cl);
+
+#endif
diff --git a/hdhomerun_dev.c b/hdhomerun_dev.c
new file mode 100644
index 0000000..62e12fa
--- /dev/null
+++ b/hdhomerun_dev.c
@@ -0,0 +1,123 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include "utility.h"
+#include "hdhomerun_tuner.h"
+#include "hdhomerun_http.h"
+#include "hdhomerun_dev.h"
+#include "sagelog.h"
+
+#define _MIN_(x, y) ((x) > (y) ? (y) : (x))
+
+static int read_elemnt(char *xml, char *tag, char *buf, int size)
+{
+  char open_tag[256], close_tag[256];
+  char *start, *end;
+  snprintf(open_tag, sizeof(open_tag), "<%s>", tag);
+  snprintf(close_tag, sizeof(open_tag), "</%s>", tag);
+
+  start = strstr(xml, open_tag);
+  if (start != NULL) {
+    start += strlen(open_tag);
+    end = strstr(start, close_tag);
+    if (end != NULL) {
+      int len = _MIN_(end - start, size);
+      memcpy(buf, start, len);
+      buf[len] = 0;
+      end += strlen(close_tag);
+      return end - xml;
+    }
+  }
+  return 0;
+}
+
+static void html_txt_converison(char *txt)
+{
+  char *p;
+  do {
+    // remove &amp;
+    p = strstr(txt, "&amp;");
+    if (p)
+      strcpy(p + 1, p + 1 + 4);
+  } while (p);
+}
+
+// remove tag and trim space
+static void cleanup_txt(char *txt)
+{
+  int n = strlen(txt);
+  while (n > 0) {
+    if (txt[n] == ' ' || txt[n] == '<' || txt[n] == '\r' || txt[n] == '\n')
+      txt[n] = 0x0;
+    n--;
+  }
+}
+
+static int program_parser(void *context, char *buf, int bytes)
+{
+  struct vchan_tbl_t *vt = (struct vchan_tbl_t *)context;
+  int pos, used_bytes;
+  char guide_num[16], guide_name[64];
+
+  pos = 0;
+  while (pos < bytes) {
+    used_bytes =
+        read_elemnt(buf + pos, "GuideNumber", guide_num, sizeof(guide_num));
+    if (used_bytes == 0)
+      break;
+    pos += used_bytes;
+    // printf( "%s ", guide_num );
+    used_bytes =
+        read_elemnt(buf + pos, "GuideName", guide_name, sizeof(guide_name));
+    if (used_bytes == 0)
+      break;
+    html_txt_converison(guide_name);
+    if (vt->channel_num >= vt->channel_size)
+      grow_vhcan_tbl(vt);
+    if (vt->channel_num < vt->channel_size) {
+      int i = vt->channel_num;
+      vt->vchan[i].guide_id = atoi(guide_num);
+      snprintf(vt->vchan[i].guide_name, sizeof(vt->vchan[i].guide_name),
+               "\"%s\"", guide_name);
+      vt->channel_num++;
+    }
+    pos += used_bytes;
+    // printf( "%s;\n", guide_name );
+  }
+  return pos;
+}
+
+static int hdhomerun_parser(void *context, char *buf, int bytes)
+{
+  char *p;
+  struct hdhr_tuner_t *ht = (struct hdhr_tuner_t *)context;
+  p = strstr(buf, "Model:");
+  if (p != NULL) {
+    if (strlen(p + 6) < 10)
+      return p - buf;
+    if (get_string_by_name(p, "Model", ht->model, sizeof(ht->model), NULL)) {
+      cleanup_txt(ht->model);
+    }
+  }
+
+  if (strstr(buf, "CableCARD Menu") != NULL) {
+    ht->cc_tuner = 1;
+  }
+  return bytes;
+}
+
+int get_dev_model(struct hdhr_tuner_t *ht)
+{
+  int ret = parse_http_page(ht->ip, "/", hdhomerun_parser, ht);
+  return ret;
+}
+
+int get_vchannel_list(struct vchan_tbl_t *vt)
+{
+  int ret = parse_http_page(vt->ht.ip, "/lineup.xml", program_parser, vt);
+  return ret;
+}
diff --git a/hdhomerun_dev.h b/hdhomerun_dev.h
new file mode 100644
index 0000000..2848ce0
--- /dev/null
+++ b/hdhomerun_dev.h
@@ -0,0 +1,7 @@
+#ifndef _HDHOMERUN_DEV_H_
+#define _HDHOMERUN_DEV_H_
+
+int get_dev_model(struct hdhr_tuner_t *ht);
+int get_vchannel_list(struct vchan_tbl_t *vt);
+
+#endif
diff --git a/hdhomerun_http.c b/hdhomerun_http.c
new file mode 100644
index 0000000..a22c57e
--- /dev/null
+++ b/hdhomerun_http.c
@@ -0,0 +1,147 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <string.h>
+#include "hdhomerun_http.h"
+
+#define HOST "google.com"
+#define PAGE "/"
+#define PORT 80
+#define USERAGENT "SAGETV 1.0"
+
+#define _MIN_(x, y) ((x) > (y) ? (y) : (x))
+
+static char *get_ip(char *host, char *buf, int size)
+{
+  struct hostent *hent;
+  char *ip = buf;
+  memset(ip, 0, size);
+  if ((hent = gethostbyname(host)) == NULL) {
+    herror("Can't get IP");
+    exit(1);
+  }
+  if (inet_ntop(AF_INET, (void *)hent->h_addr_list[0], ip, size) == NULL) {
+    perror("Can't resolve host");
+    exit(1);
+  }
+  return ip;
+}
+
+static char *build_get_query(char *host, char *page)
+{
+  char *query;
+  char *getpage = page;
+  char *tpl = "GET /%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\n\r\n";
+  if (getpage[0] == '/') {
+    getpage = getpage + 1;
+  }
+  // -5 is to consider the %s %s %s in tpl and the ending \0
+  query = (char *)malloc(strlen(host) + strlen(getpage) + strlen(USERAGENT) +
+                         strlen(tpl) - 5);
+  sprintf(query, tpl, getpage, host, USERAGENT);
+  return query;
+}
+
+int parse_http_page(char *host, char *page, parser_f parser, void *context)
+{
+  struct sockaddr_in *remote;
+  int ret;
+  char buf[BUFSIZ + 1];
+  int sock;
+  char *get;
+  char ip[32];
+  if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+    perror("Can't create TCP socket");
+    return -1;
+  }
+
+  get_ip(host, ip, sizeof(ip));
+  remote = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in *));
+  remote->sin_family = AF_INET;
+  ret = inet_pton(AF_INET, ip, (void *)(&(remote->sin_addr.s_addr)));
+  if (ret < 0) {
+    perror("Can't set remote->sin_addr.s_addr");
+    return -1;
+  } else if (ret == 0) {
+    fprintf(stderr, "%s is not a valid IP address\n", ip);
+    return -1;
+  }
+  remote->sin_port = htons(PORT);
+
+  if (connect(sock, (struct sockaddr *)remote, sizeof(struct sockaddr)) < 0) {
+    perror("Could not connect");
+    return -1;
+  }
+  get = build_get_query(host, page);
+
+  // send the query to the server
+  int sent = 0;
+  while (sent < strlen(get)) {
+    ret = send(sock, get + sent, strlen(get) - sent, 0);
+    if (ret == -1) {
+      perror("Can't send query");
+      break;
+    }
+    sent += ret;
+  }
+
+  // receive page
+  int remain = 0;
+  while ((ret = recv(sock, buf + remain, BUFSIZ - remain, 0)) > 0) {
+    int used_bytes = 0;
+    if (parser != NULL && ret >= 0) {
+      *(buf + remain + ret) = 0x0;
+      used_bytes = parser(context, buf, ret + remain);
+      remain = ret + remain - used_bytes;
+      if (remain > 0)
+        memcpy(buf, buf + used_bytes, remain);
+    }
+    if (used_bytes < 0)
+      break;
+  }
+  free(get);
+  free(remote);
+  close(sock);
+  return 1;
+}
+
+#ifdef HTTP_GET_APP
+static void usage(void)
+{
+  fprintf(stderr,
+          "USAGE: htmlget host [page]\n"
+          "\thost: the website hostname. ex: coding.debuntu.org\n"
+          "\tpage: the page to retrieve. ex: index.html, default: /\n");
+}
+
+static int noop_parser(void *context, char *buf, int bytes)
+{
+  printf("%s", buf);
+  return bytes;
+}
+
+int main(int argc, char **argv)
+{
+  char *host;
+  char *page;
+
+  if (argc == 1) {
+    usage();
+    exit(2);
+  }
+  host = argv[1];
+  if (argc > 2) {
+    page = argv[2];
+  } else {
+    page = PAGE;
+  }
+
+  parse_http_page(host, page, noop_parser, NULL);
+  return 0;
+}
+
+#endif
diff --git a/hdhomerun_http.h b/hdhomerun_http.h
new file mode 100644
index 0000000..0c61c7b
--- /dev/null
+++ b/hdhomerun_http.h
@@ -0,0 +1,7 @@
+#ifndef _HDHOMERUN_HTTP_H_
+#define _HDHOMERUN_HTTP_H_
+
+typedef int (*parser_f)(void *, char *, int);
+int parse_http_page(char *host, char *page, parser_f parser, void *context);
+
+#endif
diff --git a/hdhomerun_plugin.c b/hdhomerun_plugin.c
new file mode 100644
index 0000000..adc2c25
--- /dev/null
+++ b/hdhomerun_plugin.c
@@ -0,0 +1,337 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (Qian Zhang)
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <assert.h>
+#include "utility.h"
+#include "hdhomerun_plugin.h"
+#include "libhdhomerun/hdhomerun.h"
+#include "hdhomerun_tuner.h"
+#include "hdhomerun_dev.h"
+#include "hdhomerun_tuner.h"
+#include "channel_buffer.h"
+#include "sagelog.h"
+
+#define MAX_TUNER_NUM 16
+
+struct device_list {
+  int tuner_num;
+  int list_num;
+  struct hdhr_tuner_t *hdhrs;
+};
+
+static int discover_hdhrs(struct hdhr_tuner_t *ht, int max_ht_num)
+{
+  int i, ret = 0;
+  ret = discover_device(ht, max_ht_num);
+  if (ret > 0) {
+    for (i = 0; i < ret; i++) {
+      // if a unit shares a source input in a device, copy data.
+      if (i > 0 && tuner_input_sharing(&ht[i - 1], &ht[i])) {
+        ht[i].cc_tuner = ht[i - 1].cc_tuner;
+        strncpy(ht[i].model, ht[i - 1].model, sizeof(ht[i].model));
+      } else
+        get_dev_model(&ht[i]);
+    }
+    return i;
+  }
+  return 0;
+}
+
+void *dev_init(int *num)
+{
+  int i, index = 0, tuner_num;
+  struct device_list *devices;
+
+  sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun device init"));
+
+  devices = (struct device_list *)malloc(sizeof(struct device_list));
+  if (devices != NULL) {
+    devices->hdhrs = (struct hdhr_tuner_t *)malloc(sizeof(struct hdhr_tuner_t) *
+                                                   MAX_TUNER_NUM);
+    memset(devices->hdhrs, 0,
+           sizeof(sizeof(struct hdhr_tuner_t) * MAX_TUNER_NUM));
+    if (devices->hdhrs == NULL) {
+      free(devices);
+      return 0;
+    }
+    devices->list_num = MAX_TUNER_NUM;
+    devices->tuner_num = 0;
+    tuner_num = discover_hdhrs(devices->hdhrs, MAX_TUNER_NUM);
+    for (i = 0; i < tuner_num; i++) {
+      devices->hdhrs[i].tuner_id = index++;
+      snprintf(devices->hdhrs[i].tuner_name,
+               sizeof(devices->hdhrs[i].tuner_name), "%s-%s",
+               devices->hdhrs[i].model, devices->hdhrs[i].name);
+      devices->tuner_num++;
+      sage_log((_LOG_TRACE, 3, "hdhr:%d %s %s %s", i,
+                devices->hdhrs[i].tuner_name, devices->hdhrs[i].model,
+                devices->hdhrs[i].cc_tuner ? "CableCard" : ""));
+    }
+    devices->tuner_num = tuner_num;
+    *num = tuner_num;
+    return devices;
+  }
+  return NULL;
+}
+
+void dev_release(void *handle)
+{
+  struct device_list *devices = (struct device_list *)handle;
+  if (devices != NULL) {
+    if (devices->hdhrs != NULL)
+      free(devices->hdhrs);
+    free(devices);
+  }
+  sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun device release."));
+}
+
+int get_dev_name(void *handle, int index, char *name, int max_name_len)
+{
+  struct device_list *devices = (struct device_list *)handle;
+  if (devices != NULL && index < devices->tuner_num) {
+    struct hdhr_tuner_t *hdhr = &devices->hdhrs[index];
+    strncpy(name, hdhr->tuner_name, max_name_len);
+    return 1;
+  }
+  return 0;
+}
+
+void *dev_open(void *handle, int index, int attr)
+{
+  struct device_list *devices = (struct device_list *)handle;
+  struct hdhr_tuner_t *hdhr;
+  if (index >= devices->tuner_num)
+    return NULL;
+  hdhr = &devices->hdhrs[index];
+  hdhr->hd = open_hdhr(hdhr->name);
+  return hdhr;
+}
+
+void dev_close(void *dev)
+{
+  struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+  if (hdhr == NULL) {
+    sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+    return;
+  }
+  stop_channel(hdhr->hd);
+  close_hdhr(hdhr->hd);
+}
+
+int dev_tune(void *dev, char *channel, char *stream_tar)
+{
+  int ret;
+  struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+  if (hdhr == NULL) {
+    sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+    return 0;
+  }
+  sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun tune %s %s.", hdhr->name,
+            (char *)channel));
+  if (strstr(stream_tar, "udp://") == NULL) {
+    sage_log((_LOG_TRACE, 3, "hdhr:ERROR: stream target \"%s\" is wrong.",
+              stream_tar));
+    return 0;
+  }
+
+  if (hdhr->cc_tuner) {
+    // HDHomeRun PRIME
+    if (strstr(channel, "vchan:") == NULL) {
+      char vchan[32];
+      snprintf(vchan, sizeof(vchan), "vchan:%d", atoi(channel));
+      ret = tune_channel(hdhr->hd, vchan, "", stream_tar);
+    } else
+      ret = tune_channel(hdhr->hd, channel, "", stream_tar);
+  } else {
+    // HDHomeRun US
+    char tune_str[32], map_str[32], prog_str[8];
+    int ch = -1, prog = -1, vch_major = -1, vch_minor = -1;
+    char *ps, *pe;
+    map_str[0] = 0x0;
+
+    // channel format: xx-yy-zz
+    // xx=physical channel, yy=virtual major channel, zz=virtual minor channel
+    if (sscanf(channel, "%d-%d-%d", &ch, &vch_major, &vch_minor) == 3) {
+      ret = tune_vchannel(hdhr->hd, ch, vch_major, vch_minor, stream_tar);
+      goto out;
+    }
+
+    // channel format: xx-yy
+    // xx=physical channel, yy=virtual major channel
+    if (sscanf(channel, "%d-%d", &ch, &vch_major) == 2) {
+      vch_minor = 0;
+      ret = tune_vchannel(hdhr->hd, ch, vch_major, vch_minor, stream_tar);
+      goto out;
+    }
+
+    // if channel in format "map:xxx-yy|auto:zzz prog:ppp"
+    if ((ps = strstr(channel, "map:")) != NULL &&
+        (pe = strchr(ps + 4, '|')) != NULL) {
+      strncpy(map_str, ps + 4, pe - (ps + 4));
+      map_str[pe - (ps + 4)] = 0x0;
+    }
+    get_int_val_by_name(channel, "auto", &ch, NULL);
+    get_int_val_by_name(channel, "prog", &prog, NULL);
+    if (prog == -1) {
+      sage_log(
+          (_LOG_TRACE, 3, "hdhr:ERROR: invalid channel string %s", channel));
+      return 0;
+    }
+    // if channel in format "nnn prog:mmm", based on dev to guess map
+    if (map_str[0] == 0x0 && prog > 0 && ch == -1) {
+      ch = atoi(channel);
+      if (ch > 0) {
+        snprintf(map_str, sizeof(map_str), "map:us-bcast");
+      }
+    }
+    if (map_str[0] && ch > 0 && prog > 0) {
+      snprintf(tune_str, sizeof(tune_str), "%s|auto:%d", map_str, ch);
+      snprintf(prog_str, sizeof(prog), "%d", prog);
+      ret = tune_channel(hdhr->hd, tune_str, prog_str, stream_tar);
+    } else {
+      sage_log(
+          (_LOG_ERROR, 3, "hdhr:ERROR: invalid channel string %s", channel));
+      return 0;
+    }
+  }
+
+out:
+  sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun tune %s %s.", (char *)channel,
+            ret > 0 ? "successful" : "failed"));
+  return ret > 0;
+}
+
+int dev_stop(void *dev)
+{
+  struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+  if (hdhr == NULL) {
+    sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+    return 0;
+  }
+  sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun stop %s.", (char *)hdhr->name));
+  int ret = stop_channel(hdhr->hd);
+  return ret > 0;
+}
+
+int dev_scan(void *dev, char *tuner_name, char *channel,
+             struct channel_list_t **cl_p)
+{
+  struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+  if (hdhr == NULL) {
+    sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+    return 0;
+  }
+  sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun scan %s.", (char *)hdhr->name));
+  if (hdhr->cc_tuner) {
+    int vchannel;
+    char *p;
+    if ((p = strstr(channel, "vchan:")) == NULL)
+      vchannel = atoi(channel);
+    else
+      vchannel = atoi(p + 6);
+    if (vchannel == 0)
+      return 0;
+
+    struct channel_list_t *cl = _create_channel_list(1);
+    int ret = scan_vchannel(hdhr->hd, vchannel, cl->ce);
+    char unit_name[16];
+    snprintf(cl->ce[0].dev_name, sizeof(cl->ce[0].dev_name) - 1, "HDHR.%s",
+             get_unit_name(hdhr, unit_name, sizeof(unit_name)));
+    cl->channel_num = ret;
+    if (ret == 0) {
+      _release_channel_list(cl);
+      cl = NULL;
+    }
+    if (cl_p != NULL)
+      *cl_p = cl;
+    return ret;
+  } else {
+    sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun ATSC dev_scan not implemented %s.",
+              (char *)hdhr->name));
+  }
+
+  return 0;
+}
+
+static struct channel_list_t *build_hdhr_channel_table(
+    struct hdhr_tuner_t *hdhr)
+{
+  if (hdhr->cc_tuner) {
+    struct vchan_tbl_t *vt = create_vchan_tbl(hdhr);
+    struct channel_list_t *cl = NULL;
+    if (get_vchannel_list(vt) > 0) {
+      int num = vt->channel_num;
+      cl = _create_channel_list(num);
+      int i;
+      for (i = 0; i < num; i++) {
+        char unit_name[16];
+        cl->ce[i].state = 1;
+        snprintf(cl->ce[i].dev_tuning, sizeof(cl->ce[i].dev_tuning) - 1,
+                 "vchan:%d", vt->vchan[i].guide_id);
+        snprintf(cl->ce[i].ch_name, sizeof(cl->ce[i].ch_name) - 1, "%d",
+                 vt->vchan[i].guide_id);
+        strncpy(cl->ce[i].ch_desc, vt->vchan[i].guide_name,
+                sizeof(cl->ce[i].ch_desc) - 1);
+        cl->ce[i].src_ip[0] = 0x0;
+        cl->ce[i].src_port = 0;  // dynamic assigning
+        snprintf(cl->ce[i].dev_name, sizeof(cl->ce[i].dev_name) - 1, "HDHR.%s",
+                 get_unit_name(hdhr, unit_name, sizeof(unit_name)));
+        cl->ce[i].ch = vt->vchan[i].guide_id;
+        cl->ce[i].frq = 0;
+        cl->ce[i].av_inf[0] = 0;
+      }
+      cl->channel_num = i;
+    }
+    release_vchan_tbl(vt);
+    return cl;
+  } else {
+    sage_log(
+        (_LOG_TRACE, 3,
+         "hdhr:hdhomerun ATSC build_hdhr_channel_table not implemented %s.",
+         (char *)hdhr->name));
+  }
+  return NULL;
+}
+
+int dev_build_channel_table(void *dev, char *option,
+                            struct channel_list_t **cl_p)
+{
+  struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+  if (hdhr == NULL) {
+    sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+    return 0;
+  }
+  sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun dev_build_channel_table %s.",
+            (char *)hdhr->name));
+  struct channel_list_t *cl = build_hdhr_channel_table(hdhr);
+  if (cl_p != NULL) {
+    *cl_p = cl;
+    if (cl == NULL)
+      return 0;
+    return cl->channel_num;
+  }
+  return 0;
+}
+
+int dev_release_channel_table(void *dev)
+{
+  struct hdhr_tuner_t *hdhr = (struct hdhr_tuner_t *)dev;
+  if (hdhr == NULL) {
+    sage_log((_LOG_TRACE, 3, "hdhr:ERROR: invalid device handle."));
+    return 0;
+  }
+  sage_log((_LOG_TRACE, 3, "hdhr:hdhomerun dev_release_channel_table %s.",
+            (char *)hdhr->name));
+  if (hdhr->channel_table != NULL) {
+    free(hdhr->channel_table);
+    hdhr->channel_table = NULL;
+    return 1;
+  } else {  // TODO
+  }
+  return 0;
+}
diff --git a/hdhomerun_plugin.h b/hdhomerun_plugin.h
new file mode 100644
index 0000000..8d8fc84
--- /dev/null
+++ b/hdhomerun_plugin.h
@@ -0,0 +1,17 @@
+#ifndef _HDHOMERUN_PLUGIN_H_
+#define _HDHOMERUN_PLUGIN_H_
+
+void *dev_init(int *num);        // return tuner handle
+void dev_release(void *handle);  // release tuner
+int get_dev_name(void *handle, int index, char *name, int max_name_len);
+void *dev_open(void *handle, int index, int attr);
+void dev_close(void *dev);
+int dev_tune(void *dev, char *channel, char *stream_tar);
+int dev_stop(void *dev);
+int dev_scan(void *dev, char *tuner_name, char *channel,
+             struct channel_list_t **cl_p);
+int dev_build_channel_table(void *dev, char *option,
+                            struct channel_list_t **cl_p);
+int dev_release_channel_table(void *dev);
+
+#endif
diff --git a/hdhomerun_tuner.c b/hdhomerun_tuner.c
new file mode 100644
index 0000000..bff2315
--- /dev/null
+++ b/hdhomerun_tuner.c
@@ -0,0 +1,802 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (Qian Zhang)
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include "utility.h"
+#include "channel_buffer.h"
+#include "libhdhomerun/hdhomerun.h"
+#include "hdhomerun_tuner.h"
+#include "hdhomerun_dev.h"
+#include "sagelog.h"
+
+#define PARSE_TIMEOUT 2
+#define CHANNE_SCAN_SRC "227.0.0.1"
+#define CHANNEL_SCAN_PORT 6069
+
+static volatile sig_atomic_t sigabort_flag = FALSE;
+static volatile sig_atomic_t siginfo_flag = FALSE;
+
+static const char *map_tbl[] = {"us-bcast", "us-cable", "us-hrc",   "us-irc",
+                                "au-bcast", "tw-bcast", "eu-bcast", 0x0};
+
+static void sigabort_handler(int arg)
+{
+  sigabort_flag = TRUE;
+}
+
+static void siginfo_handler(int arg)
+{
+  siginfo_flag = TRUE;
+}
+
+static void register_signal_handlers(sig_t sigpipe_handler,
+                                     sig_t sigint_handler,
+                                     sig_t siginfo_handler)
+{
+#if defined(SIGPIPE)
+  signal(SIGPIPE, sigpipe_handler);
+#endif
+#if defined(SIGINT)
+  signal(SIGINT, sigint_handler);
+#endif
+#if defined(SIGINFO)
+  signal(SIGINFO, siginfo_handler);
+#endif
+}
+
+// channel format: map:us-cable,auto:68
+void *open_hdhr(char *name)
+{
+  struct hdhomerun_device_t *hd;
+  hd = hdhomerun_device_create_from_str(name, NULL);
+  return hd;
+}
+
+void close_hdhr(void *device)
+{
+  struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+  if (hd) {
+    hdhomerun_device_stream_stop(hd);
+    hdhomerun_device_destroy(hd);
+  }
+}
+
+int tune_channel(void *device, char *channel, char *program,
+                 char *stream_target)
+{
+  char channel_str[32] = "auto:";
+  char vchannel_str[16];
+  char channel_map[32];
+  struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+  int ret;
+
+  if (hd == NULL) {
+    printf("hdhomerun invalid handle\n");
+    return -1;
+  }
+
+  if (!get_string_by_name(channel, "vchan", vchannel_str, sizeof(vchannel_str),
+                          NULL)) {
+    vchannel_str[0] = 0x0;
+
+    if (!get_string_by_name(channel, "auto", channel_str + 5,
+                            sizeof(channel_str) - 5, NULL)) {
+      printf("hdhomerun %p invalid channel %s\n", device, channel);
+      return -1;
+    }
+    if (!get_string_by_name(channel, "map", channel_map, sizeof(channel_map),
+                            NULL)) {
+      printf("hdhomerun %p invalid channel map %s\n", device, channel);
+      strncpy(channel_map, "us-cable", sizeof(channel_map));
+    }
+    if (program[0] == 0x0) {
+      printf("hdhomerun %p invalid channel program %s\n", device, program);
+      return -1;
+    }
+    ret = hdhomerun_device_set_tuner_channelmap(hd, channel_map);
+    if (ret < 0)
+      printf("hdhomerun failed to set tuner channel map %s\n", channel_map);
+
+    ret = hdhomerun_device_set_tuner_channel(hd, channel_str);
+    if (ret < 0)
+      printf("hdhomerun tuning failed:%s ret:%d\n", channel_str, ret);
+
+    ret = hdhomerun_device_set_tuner_program(hd, program);
+    if (ret <= 0)
+      printf("hdhomerun failed set program:%s ret:%d\n", program, ret);
+  } else {
+    ret = hdhomerun_device_set_tuner_vchannel(hd, vchannel_str);
+    if (ret < 0)
+      printf("hdhomerun tuning failed: vchannel:%s ret:%d\n", vchannel_str,
+             ret);
+  }
+
+  char target[64];
+  snprintf(target, sizeof(target), "%s ttl=2", stream_target);
+  ret = hdhomerun_device_set_tuner_target(hd, target);
+  if (ret < 0) {
+    printf("hdhomerun failed set target %s  ret:%d\n", target, ret);
+  }
+
+  return ret;
+}
+
+int vchan_to_prog_num(char *streaminfo, unsigned int vchan_major,
+                      unsigned int vchan_minor, unsigned int *prog_num)
+{
+  char *line;
+  char *next_line;
+  unsigned int program_number;
+  unsigned int virtual_major, virtual_minor;
+
+  next_line = streaminfo;
+
+  while (1) {
+    line = next_line;
+    next_line = strchr(line, '\n');
+    if (!next_line) {
+      break;
+    }
+    *next_line++ = 0;
+
+    if (sscanf(line, "%u: %u.%u", &program_number, &virtual_major,
+               &virtual_minor) != 3) {
+      if (sscanf(line, "%u: %u", &program_number, &virtual_major) != 2) {
+        continue;
+      }
+      virtual_minor = 0;
+    }
+
+    if (vchan_major == virtual_major && vchan_minor == virtual_minor) {
+      *prog_num = program_number;
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+int tune_vchannel(void *device, unsigned int channel,
+                  unsigned int vchannel_major, unsigned int vchannel_minor,
+                  char *stream_target)
+{
+  char channel_map[16];
+  char channel_str[16];
+  char program_str[16];
+  char target_str[64];
+  struct hdhomerun_device_t *hd;
+  struct hdhomerun_tuner_status_t ts;
+  char *streaminfo;
+  unsigned int program;
+  int i, ret;
+
+  hd = (struct hdhomerun_device_t *)device;
+
+  // Set tuner channel map to US broadcast
+  snprintf(channel_map, sizeof(channel_map), "us-bcast");
+  ret = hdhomerun_device_set_tuner_channelmap(hd, channel_map);
+  if (ret < 0) {
+    printf("hdhr:ERROR: set tuner channel map %s\n", channel_map);
+    goto out;
+  }
+
+  // Tune to physical channel
+  snprintf(channel_str, sizeof(channel_str), "auto:%u", channel);
+  ret = hdhomerun_device_set_tuner_channel(hd, channel_str);
+  if (ret <= 0) {
+    printf("hdhr:ERROR: set tuner channel %s\n", channel_str);
+    goto out;
+  }
+
+  // Wait for SER to hit 100% (max 3 secs)
+  usleep(500000);
+  for (i = 0; i < 10; i++) {
+    ret = hdhomerun_device_get_tuner_status(hd, NULL, &ts);
+    if (ret > 0 && ts.symbol_error_quality >= 100) {
+      break;
+    }
+    usleep(250000);
+  }
+
+  // Get MPEG2-TS stream info
+  ret = hdhomerun_device_get_tuner_streaminfo(hd, &streaminfo);
+  if (ret <= 0) {
+    printf("hdhr:ERROR: get tuner streaminfo\n");
+    goto out;
+  }
+  printf("hdhr:hdhomerun streaminfo:\n%s", streaminfo);
+
+  // Translate virtual channel (major.minor) to program number
+  ret = vchan_to_prog_num(streaminfo, vchannel_major, vchannel_minor, &program);
+  if (ret <= 0) {
+    printf("hdhr:ERROR: vchannel %d.%d not found in streaminfo\n",
+           vchannel_major, vchannel_minor);
+    goto out;
+  }
+  printf("hdhr:hdhomerun vchannel %u.%u -> program %u\n", vchannel_major,
+         vchannel_minor, program);
+
+  // Set tuner program
+  snprintf(program_str, sizeof(program_str), "%u", program);
+  ret = hdhomerun_device_set_tuner_program(hd, program_str);
+  if (ret <= 0) {
+    printf("hdhr:ERROR: set tuner program %s\n", program_str);
+    goto out;
+  }
+
+  // Set tuner stream target
+  snprintf(target_str, sizeof(target_str), "%s ttl=2", stream_target);
+  ret = hdhomerun_device_set_tuner_target(hd, target_str);
+  if (ret <= 0) {
+    printf("hdhr:ERROR: set tuner target %s\n", target_str);
+  }
+
+out:
+  return ret;
+}
+
+// channel format: map:us-cable,auto:68
+int stop_channel(void *device)
+{
+  int ret;
+  struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+  if (hd == NULL) {
+    printf("hdhomerun invalid handle\n");
+    return -1;
+  }
+
+  ret = hdhomerun_device_set_tuner_channel(hd, "none");
+  if (ret < 0) {
+    printf("hdhomerun failed set channel none to stop streaming  ret:%d\n",
+           ret);
+  }
+
+  ret = hdhomerun_device_set_tuner_vchannel(hd, "none");
+  if (ret < 0) {
+    printf("hdhomerun failed set vchannel none to stop streaming  ret:%d\n",
+           ret);
+  }
+
+  ret = hdhomerun_device_set_tuner_target(hd, "none");
+  if (ret < 0) {
+    printf("hdhomerun failed set target none to stop streaming  ret:%d\n", ret);
+  }
+
+  return ret;
+}
+
+char *get_unit_name(struct hdhr_tuner_t *ht, char *name, int size)
+{
+  char *p = strchr(ht->name, '-');
+  if (p != NULL) {
+    int len = p - ht->name;
+    strncpy(name, ht->name, len);
+    name[len] = 0x0;
+    return name;
+  }
+  name[0] = 0x0;
+  return name;
+}
+
+static char *ip4_address(int ip4, char *buf, int size)
+{
+  snprintf(buf, size, "%u.%u.%u.%u", (unsigned int)(ip4 >> 24) & 0xFF,
+           (unsigned int)(ip4 >> 16) & 0xFF, (unsigned int)(ip4 >> 8) & 0xFF,
+           (unsigned int)(ip4 >> 0) & 0xFF);
+  return buf;
+}
+
+int discover_device(struct hdhr_tuner_t *ht, int max_ht_num)
+{
+  int i, k, num = 0;
+  struct hdhomerun_discover_device_t *device_info =
+      malloc(max_ht_num * sizeof(struct hdhomerun_discover_device_t));
+  int ret = hdhomerun_discover_find_devices_custom(
+      0, HDHOMERUN_DEVICE_TYPE_TUNER, -1, device_info, 16);
+  if (ret <= 0) {
+    free(device_info);
+    return 0;
+  }
+  // printf( "found %d hdhomerun device\n", ret );
+  for (i = 0; i < ret; i++) {
+    // printf( "ip:%x type:%x id:%x num:%d\n", device_info[i].ip_addr,
+    // device_info[i].device_type, device_info[i].device_id,
+    //    device_info[i].tuner_count );
+    for (k = 0; k < device_info[i].tuner_count && num < max_ht_num; k++) {
+      snprintf(ht[num].name, sizeof(ht[num].name), "%X-%d",
+               device_info[i].device_id, k);
+      ip4_address(device_info[i].ip_addr, ht[num].ip, sizeof(ht[num].ip));
+      ht[num].type = device_info[i].device_type;
+      ht[num].cc_tuner = 0;
+      ht[num].model[0] = 0;
+      num++;
+    }
+  }
+  free(device_info);
+  return num;
+}
+
+static char *get_tune_string(char *result, char *tune_str, int tune_str_len)
+{
+  int i = 0;
+  int ch;
+  while (map_tbl[i][0]) {
+    if (get_int_val_by_name(result, map_tbl[i], &ch, NULL) > 0) {
+      snprintf(tune_str, tune_str_len, "map:%s|auto:%d", map_tbl[i], ch);
+      return tune_str;
+    }
+    i++;
+  }
+  return "";
+}
+
+static int get_tune_channel(char *result)
+{
+  int i = 0;
+  int ch;
+  while (map_tbl[i][0]) {
+    if (get_int_val_by_name(result, map_tbl[i], &ch, NULL) > 0) {
+      return ch;
+    }
+    i++;
+  }
+  return 0;
+}
+
+int scan_all(void *device, struct channel_entry_t *ce, int ce_num)
+{
+  int found_channel = 0;
+  struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+  if (hd == NULL) {
+    printf("hdhomerun invalid handle\n");
+    return -1;
+  }
+
+  char *ret_error;
+  if (hdhomerun_device_tuner_lockkey_request(hd, &ret_error) <= 0) {
+    fprintf(stderr, "failed to lock tuner\n");
+    if (ret_error) {
+      fprintf(stderr, "%s\n", ret_error);
+    }
+    return -1;
+  }
+
+  hdhomerun_device_set_tuner_target(hd, "none");
+
+  char *channelmap;
+  if (hdhomerun_device_get_tuner_channelmap(hd, &channelmap) <= 0) {
+    fprintf(stderr, "failed to query channelmap from device\n");
+    return -1;
+  }
+
+  const char *channelmap_scan_group =
+      hdhomerun_channelmap_get_channelmap_scan_group(channelmap);
+  if (!channelmap_scan_group) {
+    fprintf(stderr, "unknown channelmap '%s'\n", channelmap);
+    return -1;
+  }
+
+  if (hdhomerun_device_channelscan_init(hd, channelmap_scan_group) <= 0) {
+    fprintf(stderr, "failed to initialize channel scan\n");
+    return -1;
+  }
+
+  register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler);
+
+  int ret = 0;
+  while (!sigabort_flag && found_channel < ce_num) {
+    struct hdhomerun_channelscan_result_t result;
+    char tune_str[32];
+    char channel_name[32];
+    int channel_num;
+    ret = hdhomerun_device_channelscan_advance(hd, &result);
+    if (ret <= 0) {
+      break;
+    }
+
+    ret = hdhomerun_device_channelscan_detect(hd, &result);
+    if (ret < 0) {
+      break;
+    }
+    if (ret == 0) {
+      continue;
+    }
+    if (0)
+      printf("LOCK: %s %s (ss=%u snq=%u seq=%u)\n",
+             get_tune_string(result.channel_str, tune_str, sizeof(tune_str)),
+             result.status.lock_str, result.status.signal_strength,
+             result.status.signal_to_noise_quality,
+             result.status.symbol_error_quality);
+
+    channel_num = get_tune_channel(result.channel_str);
+    int i;
+    for (i = 0; i < result.program_count; i++) {
+      struct hdhomerun_channelscan_program_t *program = &result.programs[i];
+      if (strstr(program->program_str, "encrypted") == NULL) {
+        int program_id = atoi(program->program_str);
+
+        if (!get_string_by_token(program->program_str, 2, channel_name,
+                                 sizeof(channel_name), NULL)) {
+          if (!get_string_by_token(program->program_str, 1, channel_name,
+                                   sizeof(channel_name), NULL))
+            snprintf(channel_name, sizeof(channel_name), "%d.%d", channel_num,
+                     i);
+          else {
+            if (!strcmp(channel_name, "0"))
+              snprintf(channel_name, sizeof(channel_name), "%d.%d", channel_num,
+                       i);
+          }
+        }
+        snprintf(
+            ce->dev_tuning, sizeof(ce->dev_tuning), "%s prog:%d",
+            get_tune_string(result.channel_str, tune_str, sizeof(tune_str)),
+            program_id);
+        strncpy(ce->ch_name, channel_name, sizeof(ce->ch_name));
+        strncpy(ce->dev_name, device, sizeof(ce->dev_name));
+        ce->ch = found_channel;
+        ce->frq = result.frequency;
+        ce->av_inf[0] = 0x0;
+        ce++;
+        if (++found_channel >= ce_num)
+          break;
+
+        printf("o");
+      }
+    }
+
+    printf(".");
+    fflush(stdout);
+  }
+
+  hdhomerun_device_tuner_lockkey_release(hd);
+
+  if (ret < 0) {
+    fprintf(stderr,
+            "communication error sending request to hdhomerun device\n");
+  }
+  return found_channel;
+}
+
+struct vchan_tbl_t *create_vchan_tbl(struct hdhr_tuner_t *ht)
+{
+  struct vchan_tbl_t *vt = malloc(sizeof(struct vchan_tbl_t));
+  vt->ht = *ht;
+  vt->channel_size = 500;
+  vt->channel_num = 0;
+  vt->vchan = malloc(sizeof(struct vchan_t) * vt->channel_size);
+  memset(vt->vchan, 0x0, sizeof(struct vchan_t) * vt->channel_size);
+  return vt;
+}
+
+void release_vchan_tbl(struct vchan_tbl_t *vt)
+{
+  if (vt->vchan)
+    free(vt->vchan);
+  free(vt);
+}
+
+int grow_vhcan_tbl(struct vchan_tbl_t *vt)
+{
+  int new_channel_size = vt->channel_size + 100;
+  struct vchan_t *new_vchan_list =
+      malloc(sizeof(struct vchan_t) * new_channel_size);
+  if (new_vchan_list == NULL)
+    return 0;
+  memcpy(new_vchan_list, vt->vchan, vt->channel_size * sizeof(struct vchan_t));
+  free(vt->vchan);
+  vt->vchan = new_vchan_list;
+  vt->channel_size = new_channel_size;
+  return 1;
+}
+
+int tuner_input_sharing(struct hdhr_tuner_t *ht1, struct hdhr_tuner_t *ht2)
+{
+  if (ht1->cc_tuner == 1 && !strcmp(ht1->ip, ht2->ip))
+    return 1;
+  return 0;
+}
+
+#ifdef TEST_APP
+static int scan_channel(void *device, int index, struct channel_entry_t *ce,
+                        int ce_channel_num, int ce_num)
+{
+  int found_channel = 0;
+  struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+  if (hd == NULL) {
+    printf("hdhomerun invalid handle\n");
+    return -1;
+  }
+
+  char *ret_error;
+  if (hdhomerun_device_tuner_lockkey_request(hd, &ret_error) <= 0) {
+    fprintf(stderr, "failed to lock tuner\n");
+    if (ret_error) {
+      fprintf(stderr, "%s\n", ret_error);
+    }
+    return -1;
+  }
+
+  hdhomerun_device_set_tuner_target(hd, "none");
+
+  char *channelmap;
+  if (hdhomerun_device_get_tuner_channelmap(hd, &channelmap) <= 0) {
+    fprintf(stderr, "failed to query channelmap from device\n");
+    return -1;
+  }
+
+  const char *channelmap_scan_group =
+      hdhomerun_channelmap_get_channelmap_scan_group(channelmap);
+  if (!channelmap_scan_group) {
+    fprintf(stderr, "unknown channelmap '%s'\n", channelmap);
+    return -1;
+  }
+
+  if (hdhomerun_device_channelscan_init(hd, channelmap_scan_group) <= 0) {
+    fprintf(stderr, "failed to initialize channel scan\n");
+    return -1;
+  }
+
+  register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler);
+
+  int ret = 0, i;
+  do {
+    struct hdhomerun_channelscan_result_t result;
+    char tune_str[32];
+    char channel_name[32];
+    int channel_num;
+    ret = hdhomerun_device_channelscan_at(hd, index, &result);
+    // ret = hdhomerun_device_channelscan_advance( hd, &result );
+    if (ret <= 0) {
+      break;
+    }
+    // skip duplicated channel
+    for (i = 0; i < ce_channel_num; i++) {
+      if (ce->frq == result.frequency)
+        break;
+    }
+    if (ce->frq == result.frequency)
+      break;
+    ret = hdhomerun_device_channelscan_detect(hd, &result);
+    if (ret < 0) {
+      break;
+    }
+    if (ret == 0) {
+      continue;
+    }
+
+    if (0)
+      printf("LOCK: %s %s (ss=%u snq=%u seq=%u)\n",
+             get_tune_string(result.channel_str, tune_str, sizeof(tune_str)),
+             result.status.lock_str, result.status.signal_strength,
+             result.status.signal_to_noise_quality,
+             result.status.symbol_error_quality);
+
+    channel_num = get_tune_channel(result.channel_str);
+    for (i = 0; i < result.program_count; i++) {
+      struct hdhomerun_channelscan_program_t *program = &result.programs[i];
+      if (strstr(program->program_str, "encrypted") == NULL) {
+        int program_id = atoi(program->program_str);
+
+        if (!get_string_by_token(program->program_str, 2, channel_name,
+                                 sizeof(channel_name), NULL)) {
+          if (!get_string_by_token(program->program_str, 1, channel_name,
+                                   sizeof(channel_name), NULL))
+            snprintf(channel_name, sizeof(channel_name), "%d.%d", channel_num,
+                     i);
+          else {
+            if (!strcmp(channel_name, "0"))
+              snprintf(channel_name, sizeof(channel_name), "%d.%d", channel_num,
+                       i);
+          }
+        }
+        struct channel_entry_t *ce_p = &ce[ce_channel_num];
+        snprintf(
+            ce->dev_tuning, sizeof(ce->dev_tuning), "%s prog:%d",
+            get_tune_string(result.channel_str, tune_str, sizeof(tune_str)),
+            program_id);
+        strncpy(ce_p->ch_name, channel_name, sizeof(ce_p->ch_name));
+        strncpy(ce_p->dev_name, device, sizeof(ce_p->dev_name));
+        ce_p->ch = found_channel + ce_channel_num;
+        ce_p->frq = result.frequency;
+        ce_p->av_inf[0] = 0x0;
+        char stream_tar[32];
+        snprintf(stream_tar, sizeof(stream_tar), "udp://%s:%u", CHANNE_SCAN_SRC,
+                 CHANNEL_SCAN_PORT);
+        ret = tune_channel(hd, ce_p->dev_tuning, "", stream_tar);
+        if (ret > 0)
+          ret = parse_avinf(CHANNE_SCAN_SRC, CHANNEL_SCAN_PORT, ce_p->av_inf,
+                            sizeof(ce_p->av_inf), PARSE_TIMEOUT);
+        if (ret > 0)
+          ret = strstr(ce_p->av_inf, "ENCRYPTED") == NULL &&
+                strstr(ce_p->av_inf, "NO-DATA") == NULL;
+        if (ret)
+          if (++ce_channel_num > ce_num)
+            break;
+      }
+    }
+  } while (0);
+
+  hdhomerun_device_tuner_lockkey_release(hd);
+
+  if (ret < 0) {
+    fprintf(stderr,
+            "communication error sending request to hdhomerun device\n");
+  }
+  return found_channel;
+}
+
+int scan_vchannel(void *device, int vchannel, struct channel_entry_t *ce)
+{
+  int ret;
+  struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)device;
+  if (hd == NULL) {
+    sage_log((_LOG_ERROR, 3, "hdhomerun invalid handle."));
+    return -1;
+  }
+
+  ce->state = 1;
+  ce->ch = vchannel;
+  ce->frq = 0;
+  ce->av_inf[0] = 0x0;
+  char stream_tar[32];
+  snprintf(ce->ch_name, sizeof(ce->ch_name), "%d", vchannel);
+  snprintf(ce->dev_tuning, sizeof(ce->dev_tuning), "vchan:%d", vchannel);
+  snprintf(stream_tar, sizeof(stream_tar), "udp://%s:%u", CHANNE_SCAN_SRC,
+           CHANNEL_SCAN_PORT);
+  ret = tune_channel(device, ce->dev_tuning, "", stream_tar);
+  if (ret > 0)
+    ret = parse_avinf(CHANNE_SCAN_SRC, CHANNEL_SCAN_PORT, ce->av_inf,
+                      sizeof(ce->av_inf), PARSE_TIMEOUT);
+  if (ret > 0)
+    ret = strstr(ce->av_inf, "ENCRYPTED") == NULL &&
+          strstr(ce->av_inf, "NO-DATA") == NULL;
+  if (ret <= 0) {
+    ce->state = 0;
+    return 0;
+  }
+
+  return 1;
+}
+
+static int _make_channel_entry_buffer(struct channel_entry_t *ce, char *buf,
+                                      int size)
+{
+  int pos = 0;
+  pos += snprintf(buf + pos, size - pos, "ch:%03d ", ce->ch);
+  if (ce->ch_name[0])
+    pos += snprintf(buf + pos, size - pos, "name:%s ", ce->ch_name);
+  if (ce->src_ip[0] && ce->src_port)
+    pos += snprintf(buf + pos, size - pos, "ip:%s port:%d ", ce->src_ip,
+                    ce->src_port);
+  if (ce->epg_id[0])
+    pos += snprintf(buf + pos, size - pos, "epg:%s ", ce->epg_id);
+  if (ce->dev_name[0])
+    pos += snprintf(buf + pos, size - pos, "dev:%s ", ce->dev_name);
+  if (ce->dev_tuning[0])
+    pos += snprintf(buf + pos, size - pos, "tune:%s ", ce->dev_tuning);
+  if (ce->av_inf[0])
+    pos += snprintf(buf + pos, size - pos, "av:\"%s\" ", ce->av_inf);
+  pos += snprintf(buf + pos, size - pos, "\n");
+  return pos;
+}
+
+static int _save_channel_entry(char *file_name, struct channel_entry_t *ce,
+                               int num)
+{
+  int i;
+  char buf[2048];
+  FILE *fp;
+  fp = fopen(file_name, "w");
+  if (fp == NULL) {
+    // printf( "Can't open file %s to save channel table, errno:%d\n",
+    // file_name, errno );
+    return -1;
+  }
+  fputs("#Google sagetv channel table ver 1.0., maximum 2048 bytes per line\n",
+        fp);
+  fprintf(fp, "total_channel:%d\n", num);
+  for (i = 0; i < num; i++) {
+    _make_channel_entry_buffer(ce, buf, sizeof(buf));
+    fputs(buf, fp);
+    ce++;
+  }
+  fclose(fp);
+  return 1;
+}
+
+int main(int argc, char *argv[])
+{
+  int i, ret = 0;
+  int tuner_count = 0;
+  struct hdhr_tuner_t ht[16];
+
+  ret = discover_device(ht, 16);
+  printf("found tuners:%d\n", ret);
+  if (ret > 0) {
+    for (i = 0; i < ret; i++) {
+      // if a unit shares a source input in a device, copy data.
+      if (i > 0 && tuner_input_sharing(&ht[i - 1], &ht[i])) {
+        ht[i].cc_tuner = ht[i - 1].cc_tuner;
+        strncpy(ht[i].model, ht[i - 1].model, sizeof(ht[i].model));
+      } else
+        get_dev_model(&ht[i]);
+
+      printf("%d tuner:%s ip:%s type:%d cc:%d model:%s\n", i, ht[i].name,
+             ht[i].ip, ht[i].type, ht[i].cc_tuner, ht[i].model);
+    }
+    tuner_count = ret;
+  }
+
+  // test channel scan
+  if (0) {
+    time_t t0 = time(NULL);
+    struct channel_entry_t ce[400];
+    int channel_num = 0;
+    // channel_num = scan_all( "101007FD-0", ce, 400 );
+    for (i = 60; i <= 70; i++) {
+      ret = scan_channel("101007FD-1", i, ce, channel_num, 400);
+      if (ret < 0 || sigabort_flag)
+        break;
+      printf("find channe:%d\n", ret);
+      channel_num += ret;
+    }
+    printf("time:%ld\n", time(NULL) - t0);
+    _save_channel_entry("channel-scan.txt", ce, channel_num);
+  }
+
+  // test vchannel scan
+  if (0) {
+    struct hdhr_tuner_t *cc_ht = NULL;
+    for (i = 0; i < tuner_count; i++) {
+      if (ht[i].cc_tuner) {
+        cc_ht = &ht[i];
+        break;
+      }
+    }
+
+    if (cc_ht != NULL) {
+      time_t t0 = time(NULL);
+      struct vchan_tbl_t *vt = create_vchan_tbl(cc_ht);
+      ret = get_vchannel_list(vt);
+      if (ret > 0) {
+        int n;
+        int channel_num = 0;
+        struct channel_list_t *cl = create_channel_list(ret);
+        for (n = 1; n < vt->channel_num && channel_num < 500; n++) {
+          printf("TRACE %d %s\n", n, vt->vchan[n].guide_name);
+          ret = scan_vchannel(vt, n, cl);
+          if (ret > 0) {
+            struct channel_entry_t *ce_p = &cl->ce[channel_num];
+            snprintf(ce_p->dev_name, sizeof(ce_p->dev_name), "HDHR.%s", device,
+                     sizeof(ce_p->dev_name));
+            channel_num++;
+          }
+          printf("TRACE channel %d, %d\n", channel_num, n);
+        }
+        _save_channel_entry("channel-scan.txt", cl);
+      }
+      release_vchan_tbl(vt);
+      printf("time:%ld\n", time(NULL) - t0);
+    }
+  }
+
+  // test tune
+  if (1) {
+    // printf( "tuning %s \n", device[i] );
+    char *dev_name = "1311A273-0";  //"101007FD-1";
+    // ret = tune_channel( dev_name, "map:us-cable|auto:68", "343",
+    // "udp://226.0.0.1:4096" );
+    ret = tune_channel(dev_name, "vchan:600", "", "udp://226.0.0.1:4096");
+    printf("hit enter to stop\n");
+    getchar();
+    ret = stop_channel(dev_name);
+  }
+
+  return ret;
+}
+
+#endif
diff --git a/hdhomerun_tuner.h b/hdhomerun_tuner.h
new file mode 100644
index 0000000..54791ed
--- /dev/null
+++ b/hdhomerun_tuner.h
@@ -0,0 +1,47 @@
+#ifndef _HDHOMERUN_TUNER_H_
+#define _HDHOMERUN_TUNER_H_
+
+struct hdhr_tuner_t {
+  void *hd;
+  int tuner_id;         // unique id in a channel service
+  char tuner_name[20];  // external name used by plugin in a channel service
+  char name[16];        // internal used by hdhomerun device
+  char ip[16];          // device unit ip
+  char model[16];       // device model
+  int type;             // device type 1:atsc 2:qam 3:dvb
+  int cc_tuner;         // cable card tuner
+  void *channel_table;  // channel scan private data
+};
+
+struct vchan_t {
+  int guide_id;
+  char guide_name[32];
+};
+
+struct vchan_tbl_t {
+  struct hdhr_tuner_t ht;
+  int channel_num;
+  int channel_size;
+  struct vchan_t *vchan;
+};
+
+struct channel_entry_t;
+
+int discover_device(struct hdhr_tuner_t *ht, int max_ht_num);
+void *open_hdhr(char *name);
+void close_hdhr(void *device);
+char *get_unit_name(struct hdhr_tuner_t *ht, char *name, int size);
+int scan_all(void *device, struct channel_entry_t *ce, int ce_num);
+int tune_channel(void *device, char *channel, char *program,
+                 char *stream_target);
+int tune_vchannel(void *device, unsigned int channel,
+                  unsigned int vchannel_major, unsigned int vchannel_minor,
+                  char *stream_target);
+int stop_channel(void *device);
+int scan_vchannel(void *device, int vchannel, struct channel_entry_t *ce);
+int tuner_input_sharing(struct hdhr_tuner_t *ht1, struct hdhr_tuner_t *ht2);
+struct vchan_tbl_t *create_vchan_tbl(struct hdhr_tuner_t *ht);
+void release_vchan_tbl(struct vchan_tbl_t *vt);
+int grow_vhcan_tbl(struct vchan_tbl_t *vt);
+
+#endif
diff --git a/libhdhomerun/hdhomerun_channelscan.c b/libhdhomerun/hdhomerun_channelscan.c
index a96f925..5203280 100644
--- a/libhdhomerun/hdhomerun_channelscan.c
+++ b/libhdhomerun/hdhomerun_channelscan.c
@@ -261,6 +261,29 @@
 	return 1;
 }
 
+int channelscan_at(struct hdhomerun_channelscan_t *scan, int number, struct hdhomerun_channelscan_result_t *result)
+{
+	memset(result, 0, sizeof(struct hdhomerun_channelscan_result_t));
+  struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_last( scan->channel_list );
+  while ( entry ) {
+    if ( hdhomerun_channel_entry_channel_number( entry ) == number )
+      break;
+    entry = hdhomerun_channel_list_prev(scan->channel_list, entry);
+  }
+	scan->next_channel = 0;
+  
+
+	/* Combine channels with same frequency. */
+	result->frequency = hdhomerun_channel_entry_frequency(entry);
+	strncpy(result->channel_str, hdhomerun_channel_entry_name(entry), sizeof(result->channel_str) - 1);
+	result->channel_str[sizeof(result->channel_str) - 1] = 0;
+
+	char *ptr = strchr(result->channel_str, 0);
+	sprintf(ptr, ", %s", hdhomerun_channel_entry_name(entry));
+
+	return 1;
+}
+
 int channelscan_detect(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result)
 {
 	scan->scanned_channels++;
diff --git a/libhdhomerun/hdhomerun_channelscan.h b/libhdhomerun/hdhomerun_channelscan.h
index 8a1fac2..69db553 100644
--- a/libhdhomerun/hdhomerun_channelscan.h
+++ b/libhdhomerun/hdhomerun_channelscan.h
@@ -47,6 +47,7 @@
 extern LIBTYPE int channelscan_advance(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result);
 extern LIBTYPE int channelscan_detect(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result);
 extern LIBTYPE uint8_t channelscan_get_progress(struct hdhomerun_channelscan_t *scan);
+extern LIBTYPE int channelscan_at(struct hdhomerun_channelscan_t *scan, int number, struct hdhomerun_channelscan_result_t *result); //ZQ
 
 #ifdef __cplusplus
 }
diff --git a/libhdhomerun/hdhomerun_device.c b/libhdhomerun/hdhomerun_device.c
index fb6e5e0..6bd71d2 100644
--- a/libhdhomerun/hdhomerun_device.c
+++ b/libhdhomerun/hdhomerun_device.c
@@ -1286,6 +1286,22 @@
 	return ret;
 }
 
+int hdhomerun_device_channelscan_at(struct hdhomerun_device_t *hd, int channel_num, struct hdhomerun_channelscan_result_t *result)
+{
+	if (!hd->scan) {
+		hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_channelscan_at: scan not initialized\n");
+		return 0;
+	}
+
+	int ret = channelscan_at(hd->scan, channel_num, result);
+	if (ret <= 0) { /* Free scan if normal finish or fatal error */
+		channelscan_destroy(hd->scan);
+		hd->scan = NULL;
+	}
+
+	return ret;
+}
+
 int hdhomerun_device_channelscan_detect(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result)
 {
 	if (!hd->scan) {
diff --git a/libhdhomerun/hdhomerun_device.h b/libhdhomerun/hdhomerun_device.h
index 51c2b86..0648caa 100644
--- a/libhdhomerun/hdhomerun_device.h
+++ b/libhdhomerun/hdhomerun_device.h
@@ -239,6 +239,7 @@
 extern LIBTYPE int hdhomerun_device_channelscan_advance(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result);
 extern LIBTYPE int hdhomerun_device_channelscan_detect(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result);
 extern LIBTYPE uint8_t hdhomerun_device_channelscan_get_progress(struct hdhomerun_device_t *hd);
+extern LIBTYPE int hdhomerun_device_channelscan_at(struct hdhomerun_device_t *hd, int channel_num, struct hdhomerun_channelscan_result_t *result);
 
 /*
  * Upload new firmware to the device.
diff --git a/libhdhomerun/hdhomerun_sock_posix.c b/libhdhomerun/hdhomerun_sock_posix.c
index 74dcd7c..e59f903 100644
--- a/libhdhomerun/hdhomerun_sock_posix.c
+++ b/libhdhomerun/hdhomerun_sock_posix.c
@@ -50,19 +50,13 @@
 
 #include <net/if.h>
 #include <sys/ioctl.h>
-
 #ifndef SIOCGIFCONF
 #include <sys/sockio.h>
 #endif
-
 #ifndef _SIZEOF_ADDR_IFREQ
 #define _SIZEOF_ADDR_IFREQ(x) sizeof(x)
 #endif
 
-#ifndef MSG_NOSIGNAL
-#define MSG_NOSIGNAL 0
-#endif
-
 int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count)
 {
 	int sock = socket(AF_INET, SOCK_DGRAM, 0);
@@ -389,20 +383,30 @@
 	sock_addr.sin_addr.s_addr = htonl(remote_addr);
 	sock_addr.sin_port = htons(remote_port);
 
-	if (connect(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) {
-		if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
-			return FALSE;
-		}
+	if (connect(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == 0) {
+		return TRUE;
 	}
 
 	uint64_t stop_time = getcurrenttime() + timeout;
 
+	/*
+	 * getpeername() is used to detect if connect succeeded. Bug - cygwin
+	 * will return getpeername success even if the connect process hasn't
+	 * completed. This first call to select is used to work around the
+	 * problem (at least for low numbered sockets where select is used).
+	 */
+	if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
+		return FALSE;
+	}
+
 	while (1) {
-		if (send(sock, NULL, 0, MSG_NOSIGNAL) == 0) {
+		struct sockaddr_in sock_addr;
+		socklen_t sockaddr_size = sizeof(sock_addr);
+		if (getpeername(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) == 0) {
 			return TRUE;
 		}
 
-		if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS) && (errno != ENOTCONN)) {
+		if (errno != ENOTCONN) {
 			return FALSE;
 		}
 
@@ -418,24 +422,26 @@
 	const uint8_t *ptr = (const uint8_t *)data;
 
 	while (1) {
-		int ret = send(sock, ptr, length, MSG_NOSIGNAL);
-		if (ret <= 0) {
-			if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
-				return FALSE;
-			}
-			if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
-				return FALSE;
-			}
-			continue;
+		int ret = send(sock, ptr, length, 0);
+		if (ret >= (int)length) {
+			return TRUE;
 		}
 
-		if (ret < (int)length) {
+		if (ret > 0) {
 			ptr += ret;
 			length -= ret;
-			continue;
 		}
 
-		return TRUE;
+		if (errno == EINPROGRESS) {
+			errno = EWOULDBLOCK;
+		}
+		if (errno != EWOULDBLOCK) {
+			return FALSE;
+		}
+
+		if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
+			return FALSE;
+		}
 	}
 }
 
@@ -452,23 +458,25 @@
 		sock_addr.sin_port = htons(remote_port);
 
 		int ret = sendto(sock, ptr, length, 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr));
-		if (ret <= 0) {
-			if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
-				return FALSE;
-			}
-			if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
-				return FALSE;
-			}
-			continue;
+		if (ret >= (int)length) {
+			return TRUE;
 		}
 
-		if (ret < (int)length) {
+		if (ret > 0) {
 			ptr += ret;
 			length -= ret;
-			continue;
 		}
 
-		return TRUE;
+		if (errno == EINPROGRESS) {
+			errno = EWOULDBLOCK;
+		}
+		if (errno != EWOULDBLOCK) {
+			return FALSE;
+		}
+
+		if (!hdhomerun_sock_wait_for_write_event(sock, stop_time)) {
+			return FALSE;
+		}
 	}
 }
 
@@ -478,22 +486,21 @@
 
 	while (1) {
 		int ret = recv(sock, data, *length, 0);
-		if (ret < 0) {
-			if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
-				return FALSE;
-			}
-			if (!hdhomerun_sock_wait_for_read_event(sock, stop_time)) {
-				return FALSE;
-			}
-			continue;
+		if (ret > 0) {
+			*length = ret;
+			return TRUE;
 		}
 
-		if (ret == 0) {
+		if (errno == EINPROGRESS) {
+			errno = EWOULDBLOCK;
+		}
+		if (errno != EWOULDBLOCK) {
 			return FALSE;
 		}
 
-		*length = ret;
-		return TRUE;
+		if (!hdhomerun_sock_wait_for_read_event(sock, stop_time)) {
+			return FALSE;
+		}
 	}
 }
 
@@ -507,23 +514,22 @@
 		socklen_t sockaddr_size = sizeof(sock_addr);
 
 		int ret = recvfrom(sock, data, *length, 0, (struct sockaddr *)&sock_addr, &sockaddr_size);
-		if (ret < 0) {
-			if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
-				return FALSE;
-			}
-			if (!hdhomerun_sock_wait_for_read_event(sock, stop_time)) {
-				return FALSE;
-			}
-			continue;
+		if (ret > 0) {
+			*remote_addr = ntohl(sock_addr.sin_addr.s_addr);
+			*remote_port = ntohs(sock_addr.sin_port);
+			*length = ret;
+			return TRUE;
 		}
 
-		if (ret == 0) {
+		if (errno == EINPROGRESS) {
+			errno = EWOULDBLOCK;
+		}
+		if (errno != EWOULDBLOCK) {
 			return FALSE;
 		}
 
-		*remote_addr = ntohl(sock_addr.sin_addr.s_addr);
-		*remote_port = ntohs(sock_addr.sin_port);
-		*length = ret;
-		return TRUE;
+		if (!hdhomerun_sock_wait_for_read_event(sock, stop_time)) {
+			return FALSE;
+		}
 	}
 }
diff --git a/libhdhomerun/hdhomerun_sock_windows.c b/libhdhomerun/hdhomerun_sock_windows.c
index 4eb0c45..7bb14f5 100644
--- a/libhdhomerun/hdhomerun_sock_windows.c
+++ b/libhdhomerun/hdhomerun_sock_windows.c
@@ -268,6 +268,16 @@
 
 bool_t hdhomerun_sock_connect(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout)
 {
+	WSAEVENT wsa_event = WSACreateEvent();
+	if (wsa_event == WSA_INVALID_EVENT) {
+		return FALSE;
+	}
+
+	if (WSAEventSelect(sock, wsa_event, FD_CONNECT) == SOCKET_ERROR) {
+		WSACloseEvent(wsa_event);
+		return FALSE;
+	}
+
 	/* Connect (non-blocking). */
 	struct sockaddr_in sock_addr;
 	memset(&sock_addr, 0, sizeof(sock_addr));
@@ -277,41 +287,22 @@
 
 	if (connect(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) {
 		if (WSAGetLastError() != WSAEWOULDBLOCK) {
+			WSACloseEvent(wsa_event);
 			return FALSE;
 		}
 	}
 
 	/* Wait for connect to complete (both success and failure will signal). */
-	WSAEVENT wsa_event = WSACreateEvent();
-	if (wsa_event == WSA_INVALID_EVENT) {
-		return FALSE;
-	}
-
-	if (WSAEventSelect(sock, wsa_event, FD_WRITE | FD_CLOSE) == SOCKET_ERROR) {
-		WSACloseEvent(wsa_event);
-		return FALSE;
-	}
-
 	DWORD ret = WaitForSingleObjectEx(wsa_event, (DWORD)timeout, FALSE);
 	WSACloseEvent(wsa_event);
+
 	if (ret != WAIT_OBJECT_0) {
 		return FALSE;
 	}
 
 	/* Detect success/failure. */
-	wsa_event = WSACreateEvent();
-	if (wsa_event == WSA_INVALID_EVENT) {
-		return FALSE;
-	}
-
-	if (WSAEventSelect(sock, wsa_event, FD_CLOSE) == SOCKET_ERROR) {
-		WSACloseEvent(wsa_event);
-		return FALSE;
-	}
-
-	ret = WaitForSingleObjectEx(wsa_event, 0, FALSE);
-	WSACloseEvent(wsa_event);
-	if (ret == WAIT_OBJECT_0) {
+	int sockaddr_size = sizeof(sock_addr);
+	if (getpeername(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) {
 		return FALSE;
 	}
 
@@ -352,23 +343,22 @@
 
 	while (1) {
 		int ret = send(sock, (char *)ptr, (int)length, 0);
-		if (ret <= 0) {
-			if (WSAGetLastError() != WSAEWOULDBLOCK) {
-				return FALSE;
-			}
-			if (!hdhomerun_sock_wait_for_event(sock, FD_WRITE | FD_CLOSE, stop_time)) {
-				return FALSE;
-			}
-			continue;
+		if (ret >= (int)length) {
+			return TRUE;
 		}
 
-		if (ret < (int)length) {
+		if (ret > 0) {
 			ptr += ret;
 			length -= ret;
-			continue;
 		}
 
-		return TRUE;
+		if (WSAGetLastError() != WSAEWOULDBLOCK) {
+			return FALSE;
+		}
+
+		if (!hdhomerun_sock_wait_for_event(sock, FD_WRITE | FD_CLOSE, stop_time)) {
+			return FALSE;
+		}
 	}
 }
 
@@ -385,23 +375,22 @@
 		sock_addr.sin_port = htons(remote_port);
 
 		int ret = sendto(sock, (char *)ptr, (int)length, 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr));
-		if (ret <= 0) {
-			if (WSAGetLastError() != WSAEWOULDBLOCK) {
-				return FALSE;
-			}
-			if (!hdhomerun_sock_wait_for_event(sock, FD_WRITE | FD_CLOSE, stop_time)) {
-				return FALSE;
-			}
-			continue;
+		if (ret >= (int)length) {
+			return TRUE;
 		}
 
-		if (ret < (int)length) {
+		if (ret > 0) {
 			ptr += ret;
 			length -= ret;
-			continue;
 		}
 
-		return TRUE;
+		if (WSAGetLastError() != WSAEWOULDBLOCK) {
+			return FALSE;
+		}
+
+		if (!hdhomerun_sock_wait_for_event(sock, FD_WRITE | FD_CLOSE, stop_time)) {
+			return FALSE;
+		}
 	}
 }
 
@@ -411,22 +400,18 @@
 
 	while (1) {
 		int ret = recv(sock, (char *)data, (int)(*length), 0);
-		if (ret < 0) {
-			if (WSAGetLastError() != WSAEWOULDBLOCK) {
-				return FALSE;
-			}
-			if (!hdhomerun_sock_wait_for_event(sock, FD_READ | FD_CLOSE, stop_time)) {
-				return FALSE;
-			}
-			continue;
+		if (ret > 0) {
+			*length = ret;
+			return TRUE;
 		}
 
-		if (ret == 0) {
+		if (WSAGetLastError() != WSAEWOULDBLOCK) {
 			return FALSE;
 		}
 
-		*length = ret;
-		return TRUE;
+		if (!hdhomerun_sock_wait_for_event(sock, FD_READ | FD_CLOSE, stop_time)) {
+			return FALSE;
+		}
 	}
 }
 
@@ -440,23 +425,19 @@
 		int sockaddr_size = sizeof(sock_addr);
 
 		int ret = recvfrom(sock, (char *)data, (int)(*length), 0, (struct sockaddr *)&sock_addr, &sockaddr_size);
-		if (ret < 0) {
-			if (WSAGetLastError() != WSAEWOULDBLOCK) {
-				return FALSE;
-			}
-			if (!hdhomerun_sock_wait_for_event(sock, FD_READ | FD_CLOSE, stop_time)) {
-				return FALSE;
-			}
-			continue;
+		if (ret > 0) {
+			*remote_addr = ntohl(sock_addr.sin_addr.s_addr);
+			*remote_port = ntohs(sock_addr.sin_port);
+			*length = ret;
+			return TRUE;
 		}
 
-		if (ret == 0) {
+		if (WSAGetLastError() != WSAEWOULDBLOCK) {
 			return FALSE;
 		}
 
-		*remote_addr = ntohl(sock_addr.sin_addr.s_addr);
-		*remote_port = ntohs(sock_addr.sin_port);
-		*length = ret;
-		return TRUE;
+		if (!hdhomerun_sock_wait_for_event(sock, FD_READ | FD_CLOSE, stop_time)) {
+			return FALSE;
+		}
 	}
 }
diff --git a/sagelog.c b/sagelog.c
new file mode 100644
index 0000000..d0e7038
--- /dev/null
+++ b/sagelog.c
@@ -0,0 +1,104 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (ken Zhang)
+
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include "sagelog.h"
+
+int sagelog_enabled = 1;
+int console_enabled = 1;
+int trace_level = 3;
+char log_filename[128] = "/tmp/SageEncoder.log";
+
+void _enable_native_log(void)
+{
+  sagelog_enabled = 1;
+}
+
+void _disable_native_log(void)
+{
+  sagelog_enabled = 0;
+}
+
+void _set_native_log(int level)
+{
+  if (level == 0) {
+    sagelog_enabled = 0;
+    printf("sagelog is disabled.");
+  } else {
+    sagelog_enabled = 1;
+    trace_level = level;
+    printf("sagelog is enabled, \"%s\"\n", log_filename);
+  }
+}
+
+void _sagelog(int type, int level, const char *cstr, ...)
+{
+  time_t ct;
+  struct tm ltm;
+  va_list args;
+  char szInfo[1024 * 3];
+  va_list va;
+  FILE *fp;
+
+  if (!sagelog_enabled)
+    return;
+  if (type == _LOG_TRACE && level > trace_level)
+    return;
+
+  fp = fopen(log_filename, "a");
+  if (fp == NULL)
+    return;
+  time(&ct);
+  localtime_r(&ct, &ltm);
+
+  va_start(va, cstr);
+
+  snprintf(szInfo, sizeof(szInfo) - 3, TEXT("%02d/%02d/%d %02d:%02d:%02d "),
+           ltm.tm_mon + 1, ltm.tm_mday, ltm.tm_year + 1900, ltm.tm_hour,
+           ltm.tm_min, ltm.tm_sec);
+
+  vsnprintf(szInfo + lstrlen(szInfo), (int)sizeof(szInfo) - lstrlen(szInfo) - 3,
+            cstr, va);
+  lstrcat(szInfo, TEXT("\r\n"));
+  fwrite((char *)szInfo, 1, lstrlen(szInfo), fp);
+
+  if (console_enabled)
+    printf("%s", szInfo);
+
+  va_end(args);
+  fclose(fp);
+}
+
+void _flog_setup(char *filename, int enable)
+{
+  if (filename != NULL && filename[0])
+    strncpy(log_filename, filename, sizeof(log_filename));
+
+  sagelog_enabled = enable;
+}
+
+char *_get_flog_path(void)
+{
+  return log_filename;
+}
+
+int _get_flog_state(void)
+{
+  return sagelog_enabled;
+}
+
+int _flog_check(void)
+{
+  FILE *fp = fopen("NATIVE_LOG.ENABLE", "r");
+  if (fp != NULL) {
+    sagelog_enabled = 1;
+    fclose(fp);
+    return 1;
+  }
+  return 0;
+}
diff --git a/sagelog.h b/sagelog.h
new file mode 100644
index 0000000..164ba9d
--- /dev/null
+++ b/sagelog.h
@@ -0,0 +1,52 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (ken Zhang)
+
+#ifndef _SAGELOG_H_
+#define _SAGELOG_H_
+
+#include <assert.h>
+
+enum {
+  _LOG_TIMING = 0x01,   // Timing and performance measurements
+  _LOG_TRACE = 0x02,    // General step point call tracing
+  _LOG_MEMORY = 0x04,   // Memory and object allocation/destruction
+  _LOG_LOCKING = 0x08,  // Locking/unlocking of critical sections
+  _LOG_ERROR = 0x10,    // Debug error notification
+  _LOG_CUSTOM1 = 0x20,
+  _LOG_CUSTOM2 = 0x40,
+};
+
+#define _MIN(x, y) ((x) > (y) ? (y) : (x))
+#define _MAX(x, y) ((x) > (y) ? (x) : (y))
+
+#ifndef TEXT
+#define TEXT(x) x
+#endif
+
+#define snprintf snprintf
+#define vsnprintf vsnprintf
+
+#ifndef lstrlen
+#define lstrlen strlen
+#endif
+
+#ifndef lstrcat
+#define lstrcat strcat
+#endif
+
+#ifndef ASSERT
+#define ASSERT assert
+#endif
+
+#define sage_log(x) _sagelog x
+
+void _sagelog(int type, int level, const char *cstr, ...);
+void _flog_setup(char *filename, int enable);
+char *_get_flog_path(void);
+int _get_flog_state(void);
+int _flog_check(void);
+void _set_native_log(int level);
+void _enable_native_log(void);
+void _disable_native_log(void);
+
+#endif
diff --git a/utility.c b/utility.c
new file mode 100644
index 0000000..5770319
--- /dev/null
+++ b/utility.c
@@ -0,0 +1,237 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+// Author: qianzhang@google.com (ken Zhang)
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "channel_buffer.h"
+#include "utility.h"
+
+static const char *delimiter = " \t,;\r\n:=|'\"#";
+
+static int is_delimiter(char ch)
+{
+  int i;
+  if (ch == 0)
+    return 1;
+  for (i = 0; delimiter[i]; i++)
+    if (ch == delimiter[i])
+      return 1;
+  return 0;
+}
+
+// format name:zzz (or name=zzzz)
+int get_string_by_name(char *p, const char *name, char *string, int max_len,
+                       char **next)
+{
+  char *s, *e;
+  e = p;
+
+  while (e != NULL && *e) {
+    s = strstr(e, name);
+    if (s == NULL)
+      return 0;
+    // is a token name
+    if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+        is_delimiter(*(s + strlen(name)))) {
+      s += (int)strlen(name);
+      while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+      if (*s == '=' || *s == ':') {
+        s++;
+        while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+        if (*s) {
+          int i = 0;
+          while (i++ < max_len && *s && *s != ' ' && *s != '\t' && *s != '\n' &&
+                 *s != '\r')
+            *string++ = *s++;
+
+          *string = 0x0;  // terminator
+          if (next != NULL) {
+            while (*s && *s != ' ' && *s != '\t' && *s != '\n' && *s != '\r')
+              s++;
+            *next = s;
+          }
+          return 1;
+        }
+      }
+    }
+    e = ++s;
+  }
+  return 0;
+}
+
+// format name:zzz (or name=zzzz)
+int get_string_by_token(char *p, int num, char *string, int max_len,
+                        char **next)
+{
+  int count = 0;
+  char *s;
+  s = p;
+  if (s == NULL)
+    return 0;
+  while (*s) {
+    while (*s && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'))
+      s++;  // skip white space
+    if (*s == 0)
+      break;
+
+    if (count++ != num) {
+      while (*s && !(*s == ' ' || *s == '\t' || *s == ';' || *s == ',' ||
+                     *s == '\r' || *s == '\n'))
+        s++;  // skip a token
+    } else {
+      if (*s) {
+        int i = 0;
+        while (i++ < max_len && *s && !(*s == ' ' || *s == '\t' || *s == ';' ||
+                                        *s == ',' || *s == '\r' || *s == '\n'))
+          *string++ = *s++;
+
+        *string = 0x0;  // terminator
+        if (next != NULL) {
+          while (*s && !(*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'))
+            s++;
+          *next = s;
+        }
+        return 1;
+      }
+    }
+    if (*s)
+      s++;
+  }
+  return 0;
+}
+
+// format name:"zzz" (or name="zzzz")
+int get_quote_string_by_name(char *p, const char *name, char *string,
+                             int max_len, char **next)
+{
+  char *s, *e;
+  e = p;
+
+  while (e != NULL && *e) {
+    s = strstr(e, name);
+    if (s == NULL)
+      return 0;
+    // is a token name
+    if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+        is_delimiter(*(s + strlen(name)))) {
+      s += (int)strlen(name);
+      while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+      if (*s == '=' || *s == ':') {
+        s++;
+        while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+        if (*s == '\"') {
+          int i = 0;
+          s++;
+          while (i++ < max_len && *s && *s != '\"') *string++ = *s++;
+
+          *string = 0x0;  // terminator
+
+          if (next != NULL) {
+            while (*s && *s != '\"') s++;
+            *next = s + 1;
+          }
+          return 1;
+        } else
+          return 0;
+      }
+    }
+    e = ++s;
+  }
+  return 0;
+}
+
+int get_int_val_by_name(char *p, const char *name, int *val, char **next)
+{
+  char *s, *e;
+  e = p;
+  while (e != NULL && *e) {
+    s = strstr(e, name);
+    if (s == NULL)
+      return 0;
+    // is a token name
+    if (((s > p && is_delimiter(*(s - 1))) || s == p) &&
+        is_delimiter(*(s + strlen(name)))) {
+      s += strlen(name);
+      while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+      if (*s == '=' || *s == ':') {
+        s++;
+        while (*s && (*s == ' ' || *s == '\t')) s++;  // skip white space
+        if (*s) {
+          *val = atoi(s);
+          if (*val < 0 && *s == '-')
+            s++;
+          while (*s >= '0' && *s <= '9') s++;
+          if (next != NULL)
+            *next = s;
+          return 1;
+        }
+      }
+    }
+    e = ++s;
+  }
+  return 0;
+}
+
+struct channel_list_t *_create_channel_list(int num)
+{
+  if (num <= 0)
+    return NULL;
+  struct channel_list_t *cl = malloc(sizeof(struct channel_list_t));
+  if (cl == NULL)
+    return NULL;
+  cl->num = num;
+  cl->channel_num = 0;
+  cl->ce = malloc(sizeof(struct channel_entry_t) * num);
+  if (cl->ce == NULL) {
+    free(cl);
+    return NULL;
+  }
+  memset(cl->ce, 0x0, sizeof(struct channel_entry_t) * num);
+  return cl;
+}
+
+void _release_channel_list(struct channel_list_t *cl)
+{
+  if (cl) {
+    if (cl->ce)
+      free(cl->ce);
+    free(cl);
+  }
+}
+
+struct channel_list_t *_joint_channel_list(struct channel_list_t *cl1,
+                                           struct channel_list_t *cl2)
+{
+  int i, n, channel_num;
+  if (cl1 == NULL)
+    return cl2;
+  if (cl2 == NULL)
+    return cl1;
+
+  channel_num = cl1->channel_num + cl2->channel_num;
+  if (channel_num < cl1->num) {
+    n = 0;
+    for (i = cl1->channel_num; i < channel_num; i++) cl1->ce[i] = cl2->ce[n++];
+    cl2->channel_num = channel_num;
+    _release_channel_list(cl2);
+    return cl1;
+  } else if (channel_num < cl2->num) {
+    n = 0;
+    for (i = cl2->channel_num; i < channel_num; i++) cl2->ce[i] = cl1->ce[n++];
+    cl2->channel_num = channel_num;
+    _release_channel_list(cl1);
+    return cl2;
+  } else {
+    struct channel_list_t *cl = _create_channel_list(channel_num);
+    for (i = 0; i < cl1->channel_num; i++) cl->ce[i] = cl1->ce[i];
+    for (n = 0; n < cl2->channel_num; n++) cl->ce[i++] = cl2->ce[n];
+    _release_channel_list(cl1);
+    _release_channel_list(cl2);
+    cl->channel_num = channel_num;
+    return cl;
+  }
+  return NULL;
+}
diff --git a/utility.h b/utility.h
new file mode 100644
index 0000000..b62b9ce
--- /dev/null
+++ b/utility.h
@@ -0,0 +1,17 @@
+#ifndef _UTILITY_H_
+#define _UTILITY_H_
+
+int get_string_by_name(char *p, const char *name, char *string, int max_len,
+                       char **next);
+int get_string_by_token(char *p, int num, char *string, int max_len,
+                        char **next);
+int get_quote_string_by_name(char *p, const char *name, char *string,
+                             int max_len, char **next);
+int get_int_val_by_name(char *p, const char *name, int *val, char **next);
+
+struct channel_list_t *_create_channel_list(int num);
+void _release_channel_list(struct channel_list_t *cl);
+struct channel_list_t *_joint_channel_list(struct channel_list_t *cl1,
+                                           struct channel_list_t *cl2);
+
+#endif