Add additional plugin code.

Change-Id: I99d4fae11900055782b7832e99a8267b20144393
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3f3ea2b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,50 @@
+# 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..85c82bc
--- /dev/null
+++ b/channel_buffer.c
@@ -0,0 +1,458 @@
+// 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"
+
+#define sage_log(x)  _sagelog x
+void _sagelog( int type, int level, const char* cstr, ... );
+
+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., maxium 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..d586915
--- /dev/null
+++ b/channel_buffer.h
@@ -0,0 +1,50 @@
+// 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 muticast 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..3d384c8
--- /dev/null
+++ b/hdhomerun_dev.c
@@ -0,0 +1,124 @@
+
+#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;
+}
+
+  //parse_http_page( "192.168.1.168", "/", hdhomerun_parser, NULL );
+  //parse_http_page( "192.168.1.168", "/lineup.xml", program_parser, NULL );
+
diff --git a/hdhomerun_dev.h b/hdhomerun_dev.h
new file mode 100644
index 0000000..452e25a
--- /dev/null
+++ b/hdhomerun_dev.h
@@ -0,0 +1,9 @@
+
+#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..9ee9fca
--- /dev/null
+++ b/hdhomerun_http.c
@@ -0,0 +1,148 @@
+#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;
+  }
+
+  //recieve 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()
+{
+  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..0708ff8
--- /dev/null
+++ b/hdhomerun_http.h
@@ -0,0 +1,8 @@
+
+#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..b32abd5
--- /dev/null
+++ b/hdhomerun_plugin.c
@@ -0,0 +1,303 @@
+// 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 "../channel_srv/channel_device.h"
+//#include "../channel_srv/channel_buffer.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;
+  char *type_name[2] ={ "NR", "CC" };
+  sage_log(( _LOG_TRACE, 3,  "hdhr::hdhomerun device init" ));
+  struct device_list *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++ ) {
+      int type = 0;
+      devices->hdhrs[i].tuner_id = index++;
+      if ( devices->hdhrs[i].cc_tuner )
+        type = 1;
+      else
+        type = 0;
+      snprintf( devices->hdhrs[i].tuner_name, sizeof(devices->hdhrs[i].tuner_name), "HDHR-%s-%s", type_name[type], 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->type = attr; //1:atsc 2:qam
+  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;
+  }
+  //cablecard tuner:HDHomerun prime unit
+  if ( hdhr->cc_tuner ) {
+    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 unit
+    char tune_str[32], map_str[8], prog_str[8];
+    int ch = -1, prog = -1;
+    char *ps, *pe;
+    map_str[0] = 0x0;
+    //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 ) {
+        int atsc = ( hdhr->type == 1 ); //TODO dvb 
+        snprintf( map_str, sizeof( map_str), "map:us-%s", atsc? "atsc":"cable" );
+      }
+    }
+    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;
+    }
+  }
+  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 {
+    //TODO
+  }  
+  
+  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 { //TODO
+  
+  }
+  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..9928358
--- /dev/null
+++ b/hdhomerun_plugin.h
@@ -0,0 +1,16 @@
+
+#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..cae211c
--- /dev/null
+++ b/hdhomerun_tuner.c
@@ -0,0 +1,648 @@
+// 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;
+  struct hdhomerun_channel_list_t *channel_list=NULL;
+  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;
+    }
+    channel_list = hdhomerun_channel_list_create( channel_map );
+    if ( channel_list == NULL ) {
+      printf( "hdhomerun failed to create channel map\n");
+      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-16",stream_target );
+  ret = hdhomerun_device_set_tuner_target( hd, target );
+  if ( ret < 0 ) {
+    printf( "hdhomerun failed set target %s  ret:%d\n", target, ret );
+  }
+  if ( channel_list )
+    hdhomerun_channel_list_destroy( channel_list );
+ 
+  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 dumplicated 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., maxium 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 ( 1 ) {
+    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..2ffe6db
--- /dev/null
+++ b/hdhomerun_tuner.h
@@ -0,0 +1,46 @@
+
+
+#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 );
+//get unit name of a tuner, one unit contains 1-6 tuners
+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 tuner_input_sharing( struct hdhr_tuner_t *ht1, struct hdhr_tuner_t *ht2 );
+int tune_channel( void* device, char *channel, char *program, 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/sagelog.c b/sagelog.c
new file mode 100644
index 0000000..101d412
--- /dev/null
+++ b/sagelog.c
@@ -0,0 +1,105 @@
+// 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( )
+{
+  sagelog_enabled=1;
+}
+void _disable_native_log( )
+{
+  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(  )
+{
+  return log_filename;
+}
+
+int _get_flog_state( )
+{
+  return sagelog_enabled;
+}
+
+int _flog_check()
+{
+  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..f482629
--- /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(  );
+int   _get_flog_state( );
+int   _flog_check();
+void  _set_native_log( int level );
+void _enable_native_log( );
+void  _disable_native_log();
+#endif /* SAGELOG_H_ */
diff --git a/utility.c b/utility.c
new file mode 100644
index 0000000..24f77e2
--- /dev/null
+++ b/utility.c
@@ -0,0 +1,241 @@
+// 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"
+#define sage_log(x)  _sagelog x
+void _sagelog( int type, int level, const char* cstr, ... );
+
+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..5ce0b67
--- /dev/null
+++ b/utility.h
@@ -0,0 +1,15 @@
+
+
+#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