log_tree_mon.c

/*
 * Compile like...
 
  gcc -W -Wall -O2 -o log_tree_mon log_tree_mon.c \
  `pkg-config --cflags --libs gtk+-2.0 vstr`

  * Use like...

  ./log_tree_mon T:ret T:frm_group T:req /local/file ssh-uh:my_ssh_alias ssh-file:/path/to/log/file
  ./log_tree_mon T:main_ref T:ref /local/file ssh-uh:my_ssh_alias ssh-file:/path/to/log/file
  ./log_tree_mon T:main_ref T:ref /local/file ssh-uh:my_ssh_alias ssh-file:/path/to/log/file
 */
/*
 *  Copyright (C) 2003  James Antill
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  email: james@and.org
 */


#define _GNU_SOURCE 1 /* for strptime */

#define VSTR_COMPILE_INCLUDE 1

#include <vstr.h>

#include <stdio.h>
#include <unistd.h>

#include <sys/time.h>
#include <time.h>

#define LTM_DEBUG 0

#define LTM_CONF_ADD_ENTERIES_PER_LOOP 250

#define FALSE 0
#define TRUE  1

#define LTM_INDENT_ON_TYPE_DEF LTM_INDENT_ON_TYPE_REQ
#define LTM_INDENT_ON_TYPE_USR 1
#define LTM_INDENT_ON_TYPE_MAIN_USR 2
#define LTM_INDENT_ON_TYPE_FRM 3
#define LTM_INDENT_ON_TYPE_REF 4
#define LTM_INDENT_ON_TYPE_MAIN_REF 5
#define LTM_INDENT_ON_TYPE_REQ 6
#define LTM_INDENT_ON_TYPE_MAIN_REQ 7
#define LTM_INDENT_ON_TYPE_RET 8
#define LTM_INDENT_ON_TYPE_FRM_GROUP 9
#define LTM_INDENT_ON_TYPE_ALL 10
#define LTM_INDENT_ON_TYPE_LAST 11
#define LTM_INDENT_ON_TYPE_END 0
#define LTM_INDENT_ON_NUM 12
#define LTM_INDENT_ON_SZ 6

static unsigned int ltm_conf_indent_on_type[LTM_INDENT_ON_SZ] =
{
 LTM_INDENT_ON_TYPE_DEF,
 LTM_INDENT_ON_TYPE_END,
 LTM_INDENT_ON_TYPE_END,
 LTM_INDENT_ON_TYPE_END,
 LTM_INDENT_ON_TYPE_END,
 LTM_INDENT_ON_TYPE_END,
};

static const char *ltm_conf_indent_name_map[LTM_INDENT_ON_NUM] = {
 "",
 "User Agent",
 "User Agent, ignoring versions",
 "From IP address",
 "Referrer URL",
 "Referrer URL, group on certain hosts",
 "Request",
 "Request, group certain requests",
 "Return Code",
 "Group clients",
 "All",
};

typedef struct Ltm_vstr_pt
{
 Vstr_base *vs;
 size_t pos;
 size_t len;
 unsigned int h_val;
} Ltm_vstr_pt;

static struct
{
 const char *name;
 Ltm_vstr_pt pt;
 unsigned int sect_num;
} ltm_readables[] = 
{
#define LTM_READABLE_VAL_ALL 0
 { "All", {NULL,0,0,0},0},
#define LTM_READABLE_VAL_OTHER 1
 { "Other", {NULL,0,0,0},0},
#define LTM_READABLE_VAL_AND_ORG 2
 { "And.org", {NULL,0,0,0},0},
#define LTM_READABLE_VAL_LAST 3
 { "-- LAST --", {NULL,0,0,0},0},
 
 { "groups.google", {NULL,0,0,0},0},
 { "google", {NULL,0,0,0},0},
 { "search.yahoo.com", {NULL,0,0,0},0},
 { "altavista.com", {NULL,0,0,0},0},
 { "msn", {NULL,0,0,0},0},
 { "mysearch.com", {NULL,0,0,0},0},
 { "search.aol.com", {NULL,0,0,0},0},
 { "search.netscape.com", {NULL,0,0,0},0},
 { "hotbot.com", {NULL,0,0,0},0},
 { "search.com", {NULL,0,0,0},0},
 { "ask", {NULL,0,0,0},0},
 { "alltheweb.com", {NULL,0,0,0},0},
 { "and.org/vstr", {NULL,0,0,0},0},
 { "and.org/pictures", {NULL,0,0,0},0},
 { "and.org", {NULL,0,0,0},0},
 { "www.and.org", {NULL,0,0,0},0},
 { "http://www.and.org", {NULL,0,0,0},0},
 { "http://and.org", {NULL,0,0,0},0},
 { "crazylands.org", {NULL,0,0,0},0},
 { "freshmeat", {NULL,0,0,0},0},
 { "gnu.org/directory", {NULL,0,0,0},0},
 { "gnu.org/search", {NULL,0,0,0},0},
 { "sourceforge.net", {NULL,0,0,0},0},
 { "sf.net", {NULL,0,0,0},0},
 { "cuj.com", {NULL,0,0,0},0},
 { "slashdot.org", {NULL,0,0,0},0},
 { "news", {NULL,0,0,0},0},
 { "nntp", {NULL,0,0,0},0},
 /* search bots... */
 { "Scooter", {NULL,0,0,0},0},
 { "Googlebot", {NULL,0,0,0},0},
 { "ia_archiver", {NULL,0,0,0},0},
 { "FAST-WebCrawler", {NULL,0,0,0},0},
 { "webcollage", {NULL,0,0,0},0},
 { "WebFilter", {NULL,0,0,0},0},
 { "fmII", {NULL,0,0,0},0},
 { "Dillo", {NULL,0,0,0},0},
 { "sitecheck.internetseer.com", {NULL,0,0,0},0},
 { "NPBot", {NULL,0,0,0},0},
 { "Slurp", {NULL,0,0,0},0},
 { "Wget", {NULL,0,0,0},0},
 { "curl", {NULL,0,0,0},0},
 { "gURLChecker", {NULL,0,0,0},0},
 { "almaden.ibm.com/cs/crawler", {NULL,0,0,0},0},
 /* requests */
 { "/pictures/People/img_thumb", {NULL,0,0,0},0},
 { "/pictures/Animals/img_thumb", {NULL,0,0,0},0},
 { "/pictures/House/img_thumb", {NULL,0,0,0},0},
 { "/pictures/Snow/img_thumb", {NULL,0,0,0},0},
 { "/pictures/Quilts/img_thumb", {NULL,0,0,0},0},
 { "/pictures/Ridge%20Hill%20Road/img_thumb", {NULL,0,0,0},0},
 { "/pictures/Garlic%20Festival%202003/img_thumb", {NULL,0,0,0},0},
 { "/icons", {NULL,0,0,0},0},
 { NULL, {NULL,0,0,0},0},
};

#include <gtk/gtk.h>

struct Ltm_file_data
{
 Vstr_base *io_r;
 size_t pos;
 size_t len;
 size_t unparsed_len;
 Vstr_sects *sects;
 unsigned int skip_num;
 unsigned int parsed_num;
 unsigned int unknown_lines;

 unsigned int idle_id;
 
 Vstr_base *t1;

 /* gtk stuff... */
 GHashTable   *h_iter;
 GtkTreeView  *view;
 GtkTreeStore *store;
 GtkWidget    *label;
};
#define F_DATA_DECL(x, y) x y = f_data-> y

struct Ltm_hash_data
{
 GtkTreeIter  parent_iter;
 int          sz;
 int          req_num;
 GHashTable  *h_next_iter;
};

static GHashTable *ltm_readable_strings = NULL;

/* If we have a readable string for this point, then give that section number */
#define LTM_SET_PARENT_SECTION_READABLE(x) do { \
 unsigned int *ltm__local_num = g_hash_table_lookup(ltm_readable_strings, pt); \
  \
  if (!ltm__local_num) { \
    fprintf(stderr, "Not found readable string: %s\n", \
            vstr_export_cstr_ptr(pt->vs, pt->pos, pt->len)); \
    break; } \
  \
  ltm_parent_sect_typ = (x); \
  ltm_parent_sect_num = *ltm__local_num; \
} while (FALSE)

#define LTM_IS_PARENT_SECTION_READABLE(x) \
 (((ltm_parent_sect_typ == (x)) || \
   (ltm_parent_sect_typ == LTM_INDENT_ON_TYPE_ALL)) && ltm_parent_sect_num)

static int ltm_skip_chr(Vstr_base *io_r, size_t *pos, size_t *len, char chr)
{
  if (vstr_export_chr(io_r, *pos) != chr)
    return (FALSE);

  *pos += 1;
  *len -= 1;

  return (TRUE);
}

static int ltm_add_sect_term_chr(Vstr_sects *sects,
                                Vstr_base *io_r, size_t *pos, size_t *len,
                                char chr)
{
  size_t tmp = vstr_srch_chr_fwd(io_r, *pos, *len, chr);

  if (!tmp)
    return (FALSE);
  
  vstr_sects_add(sects, *pos, tmp - *pos);

  *len -= vstr_sc_posdiff(*pos, tmp);
  *pos = tmp + 1;

  return (TRUE);
}

static int ltm_add_sect_between_chrs(Vstr_sects *sects,
                                    Vstr_base *io_r, size_t *pos, size_t *len,
                                    char beg_chr, char end_chr, char skip_chr)
{
  char buf[2];
  size_t tmp = 0;

  if (!ltm_skip_chr(io_r, pos, len, beg_chr))
    return (FALSE);

  buf[0] = end_chr;
  buf[1] = skip_chr;
  
  tmp = vstr_srch_buf_fwd(io_r, *pos, *len, buf, 2);
    
  if (!tmp)
    return (FALSE);
  
  vstr_sects_add(sects, *pos, tmp - *pos);
  
  *len -= vstr_sc_posdiff(*pos, tmp) + 1;
  *pos = tmp + 2;

  return (TRUE);
}

guint ltm_hash_gen(gconstpointer passed_pt)
{
  Ltm_vstr_pt *pt = (void *)passed_pt;
  Vstr_iter iter[1];
  guint h = 0;

  //  fprintf(stderr, "%p [%p.%zu.%zu] %u\n", pt, pt->vs, pt->pos, pt->len,
  //          pt->h_val);

  if (!pt->len) return (0);
  
  if (pt->h_val)
    return (pt->h_val);
  
  if (!vstr_iter_fwd_beg(pt->vs, pt->pos, pt->len, iter))
    g_assert_not_reached();

  do
  {
    while (iter->len--) /* borrowed from glib, and altered to be case safe */
    {
      guint val = *iter->ptr;

      if ((val >= 'A') && (val <= 'Z')) /* NOTE: ASSUMES ASCII */
        val += ('a' - 'A');
      
      h = (h << 5) - h + val;
      
      iter->ptr++;
    }

  } while (vstr_iter_fwd_nxt(iter));

  pt->h_val = h;
  
  return (h);
}

gboolean ltm_hash_eq(gconstpointer passed_pt1, gconstpointer passed_pt2)
{
  const Ltm_vstr_pt *pt1 = passed_pt1;
  const Ltm_vstr_pt *pt2 = passed_pt2;
  return (vstr_cmp_case_eq(pt1->vs, pt1->pos, pt1->len,
                           pt2->vs, pt2->pos, pt2->len));
}

#define LTM_GTK_COL_T_USR 0 /* vstr */
#define LTM_GTK_COL_T_DAT 1 /* time_t * */
#define LTM_GTK_COL_T_FRM 2 /* vstr */
#define LTM_GTK_COL_T_SIZ 3 /* uint */
#define LTM_GTK_COL_T_REQ 4 /* vstr */
#define LTM_GTK_COL_T_REF 5 /* vstr */
#define LTM_GTK_COL_T_NUM 6 /* uint */
#define LTM_GTK_COL_T_CNT 7 /* uint */
#define LTM_GTK_COL_T_RET 8 /* uint */
#define LTM_GTK_COL_SZ 9

static void ltm_cell_render_int(GtkCellRenderer *cell,
                                GtkTreeModel *model,
                                GtkTreeIter *iter,
                                guint t_off, Vstr_base *t1)
{
  const char *ptr = NULL;
  int info = 0;
  size_t tmp = t1->len;
  
  gtk_tree_model_get(model, iter, t_off, &info, -1);

  vstr_add_fmt(t1, tmp, "%'u", (unsigned int)info);
  
  ptr = vstr_export_cstr_ptr(t1, tmp + 1, vstr_sc_posdiff(tmp + 1, t1->len));
  g_object_set(GTK_CELL_RENDERER (cell), "text", ptr, NULL);

  if (t1->len > (1024 * 4)) vstr_del(t1, 1, t1->len / 2);
}

static void ltm_cell_render_sect(GtkCellRenderer *cell,
                                 GtkTreeModel *model,
                                 GtkTreeIter *iter,
                                 guint t_off, struct Ltm_file_data *f_data)
{
  F_DATA_DECL(Vstr_base *, io_r);
  F_DATA_DECL(Vstr_sects *, sects);
  const char *ptr = NULL;
  int info = 0;
  
  gtk_tree_model_get(model, iter, t_off, &info, -1);

  ptr = vstr_export_cstr_ptr(io_r,
                             VSTR_SECTS_NUM(sects, info)->pos,
                             VSTR_SECTS_NUM(sects, info)->len);

  g_object_set(GTK_CELL_RENDERER (cell), "text", ptr, NULL);
}

static void ltm_cell_render_siz(GtkCellRenderer *cell,
                                GtkTreeModel *model,
                                GtkTreeIter *iter,
                                guint t_off, Vstr_base *t1)
{
  const char *ptr = NULL;
  int info = 0;
  size_t tmp = t1->len;
  
  gtk_tree_model_get(model, iter, t_off, &info, -1);

  vstr_add_fmt(t1, tmp, "${BKMG.u:%u}", (unsigned int)info);
  ptr = vstr_export_cstr_ptr(t1, tmp + 1, vstr_sc_posdiff(tmp + 1, t1->len));

  g_object_set(GTK_CELL_RENDERER (cell), "text", ptr, NULL);
  if (t1->len > (1024 * 4)) vstr_del(t1, 1, t1->len / 2);
}

static void ltm_cell_render_time(GtkCellRenderer *cell,
                                GtkTreeModel *model,
                                GtkTreeIter *iter,
                                guint t_off, Vstr_base *t1)
{
  const char *ptr = NULL;
  time_t *info = NULL;
  size_t tmp = t1->len;
  time_t now = time(NULL);
  struct tm real_now_tm;
  struct tm *now_tm = localtime(&now);
  struct tm *info_tm = NULL;
  
  gtk_tree_model_get(model, iter, t_off, &info, -1);

  real_now_tm = *now_tm;
  now_tm = &real_now_tm;
  info_tm = localtime(info);
  
  if ((now_tm->tm_year == info_tm->tm_year) &&
      (now_tm->tm_mon  == info_tm->tm_mon) &&
      (now_tm->tm_mday == info_tm->tm_mday))
    vstr_add_fmt(t1, t1->len, "Today %02u:%02u:%02u",
                 info_tm->tm_hour, info_tm->tm_min, info_tm->tm_sec);
  else if (difftime(now, *info) < 0)
  {
    if (difftime(*info, now) < (60 * 60 * 18))
      vstr_add_fmt(t1, t1->len, "Tomorrow %02u:%02u:%02u",
                   info_tm->tm_hour, info_tm->tm_min, info_tm->tm_sec);
    else
      vstr_add_fmt(t1, t1->len, "%04u-%02u-%02u %02u:%02u:%02u",
                   info_tm->tm_year + 1900,
                   info_tm->tm_mon  +    1, info_tm->tm_mday,
                   info_tm->tm_hour, info_tm->tm_min, info_tm->tm_sec);
  }
  else if (difftime(now, *info) < (60 * 60 * 18))
    vstr_add_fmt(t1, t1->len, "Yesterday %02u:%02u:%02u",
               info_tm->tm_hour, info_tm->tm_min, info_tm->tm_sec);
  else if (difftime(now, *info) < (60 * 60 * 24 * 4))
  {
    char day_buf[1024];

    strftime(day_buf, sizeof(day_buf), "%A", info_tm);
    vstr_add_fmt(t1, t1->len, "%s %02u:%02u:%02u",
                 day_buf,
                 info_tm->tm_hour, info_tm->tm_min, info_tm->tm_sec);
  }
  else
    vstr_add_fmt(t1, t1->len, "%04u-%02u-%02u %02u:%02u:%02u",
                 info_tm->tm_year + 1900, info_tm->tm_mon + 1, info_tm->tm_mday,
                 info_tm->tm_hour, info_tm->tm_min, info_tm->tm_sec);
  
  ptr = vstr_export_cstr_ptr(t1, tmp + 1, vstr_sc_posdiff(tmp + 1, t1->len));

  g_object_set(GTK_CELL_RENDERER (cell), "text", ptr, NULL);
  if (t1->len > (1024 * 4)) vstr_del(t1, 1, t1->len / 2);
}

static void  __attribute__((unused)) ltm_cell_num(GtkTreeViewColumn *tree_column __attribute__((unused)),
                        GtkCellRenderer *cell,
                        GtkTreeModel *model,
                        GtkTreeIter *iter,
                        gpointer data)
{
  ltm_cell_render_int(cell, model, iter, LTM_GTK_COL_T_NUM, data);
}

static void ltm_cell_cnt(GtkTreeViewColumn *tree_column __attribute__((unused)),
                         GtkCellRenderer *cell,
                         GtkTreeModel *model,
                         GtkTreeIter *iter,
                         gpointer data)
{
  ltm_cell_render_int(cell, model, iter, LTM_GTK_COL_T_CNT, data);
}

static void ltm_cell_ret(GtkTreeViewColumn *tree_column __attribute__((unused)),
                         GtkCellRenderer *cell,
                         GtkTreeModel *model,
                         GtkTreeIter *iter,
                         gpointer data)
{
  ltm_cell_render_int(cell, model, iter, LTM_GTK_COL_T_RET, data);
}

static void ltm_cell_usr(GtkTreeViewColumn *tree_column __attribute__((unused)),
                         GtkCellRenderer *cell,
                         GtkTreeModel *model,
                         GtkTreeIter *iter,
                         gpointer data)
{
  ltm_cell_render_sect(cell, model, iter, LTM_GTK_COL_T_USR, data);
}

static void ltm_cell_dat(GtkTreeViewColumn *tree_column __attribute__((unused)),
                         GtkCellRenderer *cell,
                         GtkTreeModel *model,
                         GtkTreeIter *iter,
                         gpointer data)
{
  ltm_cell_render_time(cell, model, iter, LTM_GTK_COL_T_DAT, data);
}

static void ltm_cell_siz(GtkTreeViewColumn *tree_column __attribute__((unused)),
                         GtkCellRenderer *cell,
                         GtkTreeModel *model,
                         GtkTreeIter *iter,
                         gpointer data)
{
  ltm_cell_render_siz(cell, model, iter, LTM_GTK_COL_T_SIZ, data);
}

static void ltm_cell_frm(GtkTreeViewColumn *tree_column __attribute__((unused)),
                         GtkCellRenderer *cell,
                         GtkTreeModel *model,
                         GtkTreeIter *iter,
                         gpointer data)
{
  ltm_cell_render_sect(cell, model, iter, LTM_GTK_COL_T_FRM, data);
}

static void ltm_cell_req(GtkTreeViewColumn *tree_column __attribute__((unused)),
                         GtkCellRenderer *cell,
                         GtkTreeModel *model,
                         GtkTreeIter *iter,
                         gpointer data)
{
  ltm_cell_render_sect(cell, model, iter, LTM_GTK_COL_T_REQ, data);
}

static void ltm_cell_ref(GtkTreeViewColumn *tree_column __attribute__((unused)),
                         GtkCellRenderer *cell,
                         GtkTreeModel *model,
                         GtkTreeIter *iter,
                         gpointer data)
{
  ltm_cell_render_sect(cell, model, iter, LTM_GTK_COL_T_REF, data);
}

static gint ltm_cmp_dat(GtkTreeModel *model,
                       GtkTreeIter  *iter_a,
                       GtkTreeIter  *iter_b,
                       gpointer      user_data __attribute__((unused)))
{
  time_t *a = NULL;
  time_t *b = NULL;
  
  gtk_tree_model_get(model, iter_a, LTM_GTK_COL_T_DAT, &a, -1);
  gtk_tree_model_get(model, iter_b, LTM_GTK_COL_T_DAT, &b, -1);

  if (!a && !b)
    return (0);
  
  if (!b)
    return (1);
  
  if (!a)
    return (-1);

  return (difftime(*a, *b));
}

static void ltm_trans_ptr_str(const GValue *src_value __attribute__((unused)),
                             GValue       *dest_value)
{
  g_value_set_static_string(dest_value, "");
}

static void ltm_hash_data_free(void *ptr)
{
  struct Ltm_hash_data *h_vals = ptr;

  if (h_vals->h_next_iter) /* stupid, stupid, stupid... */
    g_hash_table_destroy(h_vals->h_next_iter);
  g_free(h_vals);
}

static void ltm_gtk_list(Vstr_base *io_r, struct Ltm_file_data *f_data)
{
  GtkTreeStore *store;
  GtkWidget *treeview;
  int dum_argc = 1;
  char *dum_argv1[] = { "abcd", NULL };
  char **dum_argv = &dum_argv1[0];
  Vstr_base *t1 = NULL;

  f_data->t1 = t1 = vstr_make_base(io_r->conf);

  ltm_readable_strings = g_hash_table_new_full(ltm_hash_gen, ltm_hash_eq,
                                               (GDestroyNotify)vstr_ref_cb_free_nothing,
                                               (GDestroyNotify)vstr_ref_cb_free_nothing);
  
  {
    unsigned int scan = 0;

    while (ltm_readables[scan].name)
    {
      Ltm_vstr_pt  *pt       = &ltm_readables[scan].pt;
      unsigned int *sect_num = &ltm_readables[scan].sect_num;
      size_t len = strlen(ltm_readables[scan].name);
      size_t pos = io_r->len + 1;
      
      vstr_add_ptr(io_r, pos - 1, ltm_readables[scan].name, len);
      vstr_sects_add(f_data->sects, pos, len);
      
      *sect_num = f_data->sects->num;
      
      pt->vs  = io_r;
      pt->pos = pos;
      pt->len = len;

      g_hash_table_insert(ltm_readable_strings, pt, sect_num);

      ++scan;
    }

    f_data->skip_num   += scan;
    f_data->parsed_num += scan;

    f_data->pos = io_r->len + 1;
    f_data->len = 0;
  }
  
  gtk_init(&dum_argc, &dum_argv);

  /* g_warns without this... *sigh* */
  g_value_register_transform_func(G_TYPE_POINTER, G_TYPE_STRING,
                                  ltm_trans_ptr_str);
  
  f_data->h_iter = g_hash_table_new_full(ltm_hash_gen, ltm_hash_eq,
                                         g_free, ltm_hash_data_free);
  
  store = gtk_tree_store_new(LTM_GTK_COL_SZ,
                             G_TYPE_UINT, /* vstr */
                             G_TYPE_POINTER,
                             G_TYPE_UINT, /* vstr */
                             G_TYPE_UINT,
                             G_TYPE_UINT, /* vstr */
                             G_TYPE_UINT, /* vstr */
                             G_TYPE_UINT,
                             G_TYPE_UINT,
                             G_TYPE_UINT);
  gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store), LTM_GTK_COL_T_DAT,
                                  ltm_cmp_dat, NULL, NULL);
  f_data->store = store;
  
  treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
  f_data->view = GTK_TREE_VIEW(treeview);
  gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
  gtk_tree_view_set_reorderable(GTK_TREE_VIEW(treeview), TRUE);
  gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview),
                                  LTM_GTK_COL_T_REQ);
  g_object_unref(G_OBJECT(store));
      
  { /* create columns in view */
    GtkCellRenderer *renderer; 
    GtkTreeViewColumn *column = NULL;
    
    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("Count",
                                                      renderer,
                                                      "text", LTM_GTK_COL_T_CNT,
                                                      NULL);
    gtk_tree_view_column_set_cell_data_func(column, renderer,
                                            ltm_cell_cnt, t1, NULL);
    gtk_tree_view_column_set_sort_column_id(column, LTM_GTK_COL_T_CNT);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);

    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("Return Code",
                                                      renderer,
                                                      "text", LTM_GTK_COL_T_RET,
                                                      NULL);
    gtk_tree_view_column_set_cell_data_func(column, renderer,
                                            ltm_cell_ret, t1, NULL);
    gtk_tree_view_column_set_sort_column_id(column, LTM_GTK_COL_T_RET);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);

    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("User-agent",
                                                      renderer,
                                                      "text", LTM_GTK_COL_T_USR,
                                                      NULL);
    gtk_tree_view_column_set_cell_data_func(column, renderer,
                                            ltm_cell_usr, f_data, NULL);
    gtk_tree_view_column_set_resizable(column, TRUE);
    gtk_tree_view_column_set_sort_column_id(column, LTM_GTK_COL_T_USR);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);

    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("Date",
                                                      renderer,
                                                      "text",
                                                      LTM_GTK_COL_T_DAT,
                                                      NULL);
    gtk_tree_view_column_set_cell_data_func(column, renderer,
                                            ltm_cell_dat, t1, NULL);
    gtk_tree_view_column_set_sort_column_id(column, LTM_GTK_COL_T_DAT);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
    
    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("From IP",
                                                      renderer,
                                                      "text",
                                                      LTM_GTK_COL_T_FRM,
                                                      NULL);
    gtk_tree_view_column_set_cell_data_func(column, renderer,
                                            ltm_cell_frm, f_data, NULL);
    gtk_tree_view_column_set_sort_column_id(column, LTM_GTK_COL_T_FRM);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
    
    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("Size",
                                                      renderer,
                                                      "text",
                                                      LTM_GTK_COL_T_SIZ,
                                                      NULL);
    gtk_tree_view_column_set_cell_data_func(column, renderer,
                                            ltm_cell_siz, t1, NULL);
    gtk_tree_view_column_set_sort_column_id(column, LTM_GTK_COL_T_SIZ);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);

    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("Request",
                                                      renderer,
                                                      "text",
                                                      LTM_GTK_COL_T_REQ,
                                                      NULL);
    gtk_tree_view_column_set_cell_data_func(column, renderer,
                                            ltm_cell_req, f_data, NULL);
    gtk_tree_view_column_set_resizable(column, TRUE);
    gtk_tree_view_column_set_sort_column_id(column, LTM_GTK_COL_T_REQ);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);

    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("Referrer",
                                                      renderer,
                                                      "text",
                                                      LTM_GTK_COL_T_REF,
                                                      NULL);
    gtk_tree_view_column_set_cell_data_func(column, renderer,
                                            ltm_cell_ref, f_data, NULL);
    gtk_tree_view_column_set_sort_column_id(column, LTM_GTK_COL_T_REF);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
  }
  
  {
    GtkWidget *window;
    GtkWidget *vbox;
    GtkWidget *sw;
    
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    {
      size_t tmp = t1->len;
      const char *ptr = NULL;
      int scan = 0;
      
      vstr_add_fmt(t1, t1->len, "Log tree monitor: %s",
                   ltm_conf_indent_name_map[ltm_conf_indent_on_type[0]]);

      ++scan;
      while (ltm_conf_indent_on_type[scan] && (scan < LTM_INDENT_ON_SZ))
      {
        vstr_add_fmt(t1, t1->len, ", %s",
                     ltm_conf_indent_name_map[ltm_conf_indent_on_type[scan]]);

        ++scan;
      }
      
      ptr = vstr_export_cstr_ptr(t1,
                                 tmp + 1, vstr_sc_posdiff(tmp + 1, t1->len));
      gtk_window_set_title(GTK_WINDOW(window), ptr);
    }
    
    g_signal_connect(G_OBJECT(window), "destroy",
                     G_CALLBACK(gtk_main_quit), NULL);

    vbox = gtk_vbox_new(FALSE, 8);
    gtk_container_add(GTK_CONTAINER(window), vbox);

    f_data->label = gtk_label_new("");
    gtk_box_pack_start(GTK_BOX(vbox), f_data->label, FALSE, FALSE, 0);
    
    sw = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
                                        GTK_SHADOW_ETCHED_IN);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
                                   GTK_POLICY_AUTOMATIC,
                                   GTK_POLICY_AUTOMATIC);
    gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);

    gtk_container_add(GTK_CONTAINER(sw), treeview);
    
    gtk_window_set_default_size(GTK_WINDOW(window), 280, 250);
    
    gtk_widget_show_all(window);
  }

  vstr_del(t1, 1, t1->len);
  
  gtk_main();

  g_hash_table_destroy(f_data->h_iter);
  
  vstr_del(io_r, 1, io_r->len);
  vstr_free_base(t1);  
}

#define BEG_MATCH(x) \
    (vstr_cmp_bod_cstr_eq(io_r, VSTR_SECTS_NUM(sects, sects->num)->pos, \
                          VSTR_SECTS_NUM(sects, sects->num)->len, (x)) && \
     (VSTR_SECTS_NUM(sects, sects->num)->len >= strlen(x)))

#define BEG_SKIP(x) do { \
      VSTR_SECTS_NUM(sects, sects->num)->pos += strlen(x); \
      VSTR_SECTS_NUM(sects, sects->num)->len -= strlen(x); \
   } while (FALSE)

#define END_MATCH(x) \
    (vstr_cmp_eod_cstr_eq(io_r, VSTR_SECTS_NUM(sects, sects->num)->pos, \
                          VSTR_SECTS_NUM(sects, sects->num)->len, (x)) && \
     (VSTR_SECTS_NUM(sects, sects->num)->len >= strlen(x)))

#define END_SKIP(x) do { \
      VSTR_SECTS_NUM(sects, sects->num)->len -= strlen(x); \
   } while (FALSE)

#define TST_SET_REF(x, y, z) \
 else if ((tmp = vstr_srch_case_cstr_buf_fwd(io_r, pt->pos, pt->len, (x)))) \
 do { \
   pt->pos = tmp + (y); \
   if ((z) > 0)       pt->len = (z); \
   else if ((z) < 0)  pt->len = (strlen(x) - (y)) + (z); \
   else               pt->len = strlen(x) - (y); \
   LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_REF); \
 } while (FALSE)

#define TST_BEG_SET_REF(x, y, z) \
 else if ((tmp = vstr_srch_case_cstr_buf_fwd(io_r, pt->pos, pt->len, (x))) && \
          (tmp == pt->pos)) \
 do { \
   pt->pos = tmp + (y); \
   if ((z) > 0)       pt->len = (z); \
   else if ((z) < 0)  pt->len = (strlen(x) - (y)) + (z); \
   else               pt->len = strlen(x) - (y); \
   LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_REF); \
  } while (FALSE)

#define TST_SET_REQ(x, y, z) \
 else if ((tmp = vstr_srch_case_cstr_buf_fwd(io_r, pt->pos, pt->len, (x)))) \
 do { \
   pt->pos = tmp + (y); \
   if ((z) > 0)       pt->len = (z); \
   else if ((z) < 0)  pt->len = (strlen(x) - (y)) + (z); \
   else               pt->len = strlen(x) - (y); \
   LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_REQ); \
 } while (FALSE)

#define LTM_DISPLAY_ITER_DATA() \
 (!h_iter && (ltm_conf_indent_on_type[scan] != LTM_INDENT_ON_TYPE_LAST))


static gboolean ltm_idle_tree_insert_cb(gpointer userdata)
{
  struct Ltm_file_data *f_data = userdata;
  F_DATA_DECL(Vstr_base *, io_r);
  F_DATA_DECL(size_t, pos);
  F_DATA_DECL(size_t, len);
  F_DATA_DECL(Vstr_sects *, sects);
  F_DATA_DECL(unsigned int, parsed_num);
  F_DATA_DECL(Vstr_base *, t1);
  F_DATA_DECL(GtkTreeStore *, store);
  unsigned int count = 0;
  Ltm_vstr_pt *pt = NULL;
  unsigned int added_entries = 0;
  
  if (len != f_data->unparsed_len)
  {
    f_data->len = f_data->unparsed_len;
    return (TRUE);
  }

  /* SECT: parse combined log info. into sections */
  while (len)
  {
    size_t loop_pos = pos;
    size_t loop_len = len;
    size_t del_num = 0;
    
    if (!ltm_add_sect_term_chr(sects, io_r, &pos, &len, ' '))
      goto next_0;
    if (!ltm_add_sect_term_chr(sects, io_r, &pos, &len, ' '))
      goto next_1;
    if (!ltm_add_sect_term_chr(sects, io_r, &pos, &len, ' '))
      goto next_2;

    if (!ltm_add_sect_between_chrs(sects, io_r, &pos, &len, '[', ']', ' '))
      goto next_3;

    if (!ltm_add_sect_between_chrs(sects, io_r, &pos, &len, '"', '"', ' '))
      goto next_4;

    if (BEG_MATCH("GET "))
      BEG_SKIP("GET ");
    else if (BEG_MATCH("HEAD "))
      BEG_SKIP("HEAD ");
    
    if (END_MATCH(" HTTP/1.0"))
      END_SKIP(" HTTP/1.0");
    else if (END_MATCH(" HTTP/1.1"))
      END_SKIP(" HTTP/1.1");
    
    if (!ltm_add_sect_term_chr(sects, io_r, &pos, &len, ' '))
      goto next_5;
    if (!ltm_add_sect_term_chr(sects, io_r, &pos, &len, ' '))
      goto next_6;
    
    if (!ltm_add_sect_between_chrs(sects, io_r, &pos, &len, '"', '"', ' '))
      goto next_7;
    
    if (!ltm_add_sect_between_chrs(sects, io_r, &pos, &len, '"', '"', '\n'))
      goto next_8;

    continue;
   next_8: ++del_num;
   next_7: ++del_num;
   next_6: ++del_num;
   next_5: ++del_num;
   next_4: ++del_num;
   next_3: ++del_num;
   next_2: ++del_num;
   next_1: ++del_num;
   next_0:
    {
      size_t tmp = vstr_srch_chr_fwd(io_r, pos, len, '\n');

      while (del_num--)
        vstr_sects_del(sects, sects->num);
      
      if (!tmp)
      {
        pos = loop_pos;
        len = loop_len;
        break;
      }
      
      ++f_data->unknown_lines;
      
      len -= vstr_sc_posdiff(pos, tmp);
      pos = tmp + 1;
    }
  }

  /* SECT: update gtk */
  g_object_freeze_notify(G_OBJECT(store));
  pt = g_new(Ltm_vstr_pt, 1);
  count = parsed_num;
  while ((count + 8) <= sects->num)
  { /* create content */
    const char *ptr = NULL;
    size_t tmp = 0;
    GtkTreeIter *parent_iter = NULL;
    size_t comment_pos = 0;
    int *sz = NULL;
    int *req_num = NULL;
    int tot_req_num = (count + 8) / 9;
    GtkTreeIter iter;
    F_DATA_DECL(GHashTable *, h_iter);
    struct Ltm_hash_data *h_vals = NULL;
    unsigned int scan = 0;

    pt->vs = io_r;
    
    while (ltm_conf_indent_on_type[scan] && (scan < LTM_INDENT_ON_SZ))
    {
      unsigned int ltm_parent_sect_num = 0;
      unsigned int ltm_parent_sect_typ = 0;
    
      switch (ltm_conf_indent_on_type[scan])
      {
        case LTM_INDENT_ON_TYPE_USR:
          pt->pos = VSTR_SECTS_NUM(sects, count + 8)->pos;
          pt->len = VSTR_SECTS_NUM(sects, count + 8)->len;
          break;
        case LTM_INDENT_ON_TYPE_MAIN_USR:
          pt->pos = VSTR_SECTS_NUM(sects, count + 8)->pos;
          pt->len = VSTR_SECTS_NUM(sects, count + 8)->len;
          if ((comment_pos = vstr_srch_cstr_buf_fwd(io_r, pt->pos, pt->len,
                                                    " (")))
            pt->len = vstr_sc_posdiff(pt->pos, comment_pos) - 1;
          break;
        case LTM_INDENT_ON_TYPE_FRM:
          pt->pos = VSTR_SECTS_NUM(sects, count)->pos;
          pt->len = VSTR_SECTS_NUM(sects, count)->len;
          break;
        case LTM_INDENT_ON_TYPE_REF:
          pt->pos = VSTR_SECTS_NUM(sects, count + 7)->pos;
          pt->len = VSTR_SECTS_NUM(sects, count + 7)->len;
          break;
        case LTM_INDENT_ON_TYPE_MAIN_REF:
        {
          pt->pos = VSTR_SECTS_NUM(sects, count + 7)->pos;
          pt->len = VSTR_SECTS_NUM(sects, count + 7)->len;
          
          pt->h_val = 0;
          if (0) { }
          TST_SET_REF("groups.google.at/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.be/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.ca/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.ch/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.ar/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.au/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.br/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.gr/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.hk/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.mx/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.my/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.ru/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.sg/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.tw/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.com.vn/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.co.uk/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.co.fi/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.co.fr/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.co.il/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.co.in/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.co.kr/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.co.jp/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.de/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.fi/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.fr/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.it/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.lt/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.nl/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.pl/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.pt/", 0, strlen("groups.google"));
          TST_SET_REF("groups.google.ti/", 0, strlen("groups.google"));
          TST_SET_REF("google.at/", 0, strlen("google"));
          TST_SET_REF("google.be/", 0, strlen("google"));
          TST_SET_REF("google.ca/", 0, strlen("google"));
          TST_SET_REF("google.ch/", 0, strlen("google"));
          TST_SET_REF("google.com/", 0, strlen("google"));
          TST_SET_REF("google.com.ar/", 0, strlen("google"));
          TST_SET_REF("google.com.au/", 0, strlen("google"));
          TST_SET_REF("google.com.br/", 0, strlen("google"));
          TST_SET_REF("google.com.gr/", 0, strlen("google"));
          TST_SET_REF("google.com.hk/", 0, strlen("google"));
          TST_SET_REF("google.com.mx/", 0, strlen("google"));
          TST_SET_REF("google.com.my/", 0, strlen("google"));
          TST_SET_REF("google.com.ru/", 0, strlen("google"));
          TST_SET_REF("google.com.sg/", 0, strlen("google"));
          TST_SET_REF("google.com.tw/", 0, strlen("google"));
          TST_SET_REF("google.com.vn/", 0, strlen("google"));
          TST_SET_REF("google.co.uk/", 0, strlen("google"));
          TST_SET_REF("google.co.fi/", 0, strlen("google"));
          TST_SET_REF("google.co.fr/", 0, strlen("google"));
          TST_SET_REF("google.co.il/", 0, strlen("google"));
          TST_SET_REF("google.co.in/", 0, strlen("google"));
          TST_SET_REF("google.co.kr/", 0, strlen("google"));
          TST_SET_REF("google.co.jp/", 0, strlen("google"));
          TST_SET_REF("google.de/", 0, strlen("google"));
          TST_SET_REF("google.fi/", 0, strlen("google"));
          TST_SET_REF("google.fr/", 0, strlen("google"));
          TST_SET_REF("google.it/", 0, strlen("google"));
          TST_SET_REF("google.lt/", 0, strlen("google"));
          TST_SET_REF("google.nl/", 0, strlen("google"));
          TST_SET_REF("google.pl/", 0, strlen("google"));
          TST_SET_REF("google.pt/", 0, strlen("google"));
          TST_SET_REF("google.ti/", 0, strlen("google"));
          TST_SET_REF("search.yahoo.com/", 0, -1);
          TST_SET_REF("altavista.com/", 0, -1);
          TST_SET_REF("msn.com/", 0, strlen("msn"));
          TST_SET_REF("msn.nl/", 0, strlen("msn"));
          TST_SET_REF("mysearch.com/", 0, -1);
          TST_SET_REF("search.aol.com/", 0, -1);
          TST_SET_REF("search.netscape.com/", 0, -1);
          TST_SET_REF("hotbot.com/", 0, -1);
          TST_SET_REF("search.com/", 0, -1);
          TST_SET_REF("ask.com/", 0, strlen("ask"));
          TST_SET_REF("ask.co.uk/", 0, strlen("ask"));
          TST_SET_REF("alltheweb.com/", 0, -1);
        
          TST_SET_REF("and.org/vstr", 0, 0);
          TST_SET_REF("and.org/pictures", 0, 0);
          TST_BEG_SET_REF("and.org", 0, 0);
          TST_BEG_SET_REF("www.and.org", 0, 0);
          TST_SET_REF("http://www.and.org", 0, 0);
          TST_SET_REF("http://and.org", 0, 0);
          TST_SET_REF("crazylands.org/", 0, -1);
          TST_SET_REF("http://www.freshmeat.net", strlen("http://www."), strlen("freshmeat"));
          TST_SET_REF("http://freshmeat.net", strlen("http://"), strlen("freshmeat"));
          TST_SET_REF("gnu.org/directory", 0, 0);
          TST_SET_REF("gnu.org/search", 0, 0);
          TST_SET_REF("sourceforge.net/", 0, -1);
          TST_SET_REF("sf.net/", 0, -1);
          TST_SET_REF("cuj.com/", 0, -1);
          TST_SET_REF("slashdot.org/", 0, -1);
          TST_BEG_SET_REF("news://", 0, strlen("news"));
          TST_BEG_SET_REF("nntp://", 0, strlen("nntp"));
        }
        break;

        case LTM_INDENT_ON_TYPE_FRM_GROUP:
          pt->pos = VSTR_SECTS_NUM(sects, count + 8)->pos;
          pt->len = VSTR_SECTS_NUM(sects, count + 8)->len;
        
          if ((comment_pos = vstr_srch_chr_fwd(io_r, pt->pos, pt->len, '/')))
            pt->len = vstr_sc_posdiff(pt->pos, comment_pos) - 1;
          if ((comment_pos = vstr_srch_chr_fwd(io_r, pt->pos, pt->len, ' ')))
            pt->len = vstr_sc_posdiff(pt->pos, comment_pos) - 1;

          pt->h_val = 0;
          if (0) { }
          else if (vstr_cmp_cstr_eq(io_r, pt->pos, pt->len, "Scooter"))
          { LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR); break; }
          else if (vstr_cmp_cstr_eq(io_r, pt->pos, pt->len, "Googlebot"))
          { LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR); break; }
          else if (vstr_cmp_cstr_eq(io_r, pt->pos, pt->len, "ia_archiver"))
          { LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR); break; }
          else if (vstr_cmp_cstr_eq(io_r, pt->pos, pt->len, "FAST-WebCrawler"))
          { LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR); break; }
          else if (vstr_cmp_cstr_eq(io_r, pt->pos, pt->len, "webcollage"))
          { LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR); break; }
          else if (vstr_cmp_cstr_eq(io_r, pt->pos, pt->len, "WebFilter"))
          { LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR); break; }
          else if (vstr_cmp_cstr_eq(io_r, pt->pos, pt->len, "fmII"))
          { LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR); break; }
          else if (vstr_cmp_cstr_eq(io_r, pt->pos, pt->len, "Dillo"))
          { LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR); break; }
          else if (vstr_cmp_cstr_eq(io_r, pt->pos, pt->len, "sitecheck.internetseer.com"))
          { LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR); break; }
          else if (vstr_cmp_cstr_eq(io_r, pt->pos, pt->len, "NPBot"))
          { LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR); break; }
          else
          {
            pt->pos = VSTR_SECTS_NUM(sects, count + 8)->pos;
            pt->len = VSTR_SECTS_NUM(sects, count + 8)->len;
            
            if (0) { }
            else if ((tmp = vstr_srch_cstr_buf_fwd(io_r, pt->pos, pt->len,
                                                   "Slurp/")))
            {
              pt->pos   = tmp;
              pt->len   = strlen("Slurp");
              pt->h_val = 0;
              LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR);
              break;
            }
            else if ((tmp = vstr_srch_case_cstr_buf_fwd(io_r, pt->pos, pt->len,
                                                        "Wget/")))
            {
              pt->pos   = tmp;
              pt->len   = strlen("Wget");
              pt->h_val = 0;
              LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR);
              break;
            }
            else if ((tmp = vstr_srch_case_cstr_buf_fwd(io_r, pt->pos, pt->len,
                                                        "curl/")))
            {
              pt->pos   = tmp;
              pt->len   = strlen("curl");
              pt->h_val = 0;
              LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR);
              break;
            }
            else if ((tmp = vstr_srch_case_cstr_buf_fwd(io_r, pt->pos, pt->len,
                                                        "gURLChecker/")))
            {
              pt->pos   = tmp;
              pt->len   = strlen("gURLChecker");
              pt->h_val = 0;
              LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR);
              break;
            }
            else if ((tmp = vstr_srch_cstr_buf_fwd(io_r, pt->pos, pt->len,
                                                   "http://www.almaden.ibm.com/cs/crawler")))
            {
              pt->pos   = tmp + strlen("http://www.");
              pt->len   = strlen("almaden.ibm.com/cs/crawler");
              pt->h_val = 0;
              LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR);
              break;
            }
            else
            {
              pt->pos = VSTR_SECTS_NUM(sects, count)->pos;
              pt->len = VSTR_SECTS_NUM(sects, count)->len;
              if (0) { }
              else if ((tmp = vstr_srch_cstr_buf_fwd(io_r, pt->pos, pt->len,
                                                     "63.113.167.33")))
                
              {
                pt->pos   = ltm_readables[LTM_READABLE_VAL_AND_ORG].pt.pos;
                pt->len   = ltm_readables[LTM_READABLE_VAL_AND_ORG].pt.len;
                pt->h_val = 0;
                LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_FRM);
                break;
              }
            }
          }
        
          pt->pos   = ltm_readables[LTM_READABLE_VAL_OTHER].pt.pos;
          pt->len   = ltm_readables[LTM_READABLE_VAL_OTHER].pt.len;
          pt->h_val = 0;
          LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR);
          break;
          
        case LTM_INDENT_ON_TYPE_REQ:
          pt->pos = VSTR_SECTS_NUM(sects, count + 4)->pos;
          pt->len = VSTR_SECTS_NUM(sects, count + 4)->len;
          break;
          
        case LTM_INDENT_ON_TYPE_MAIN_REQ:
          pt->pos   = VSTR_SECTS_NUM(sects, count + 4)->pos;
          pt->len   = VSTR_SECTS_NUM(sects, count + 4)->len;
          pt->h_val = 0;
            
          if (0) { }
          TST_SET_REQ("/pictures/People/img_thumb/", 0, -1);
          TST_SET_REQ("/pictures/Animals/img_thumb/", 0, -1);
          TST_SET_REQ("/pictures/House/img_thumb/", 0, -1);
          TST_SET_REQ("/pictures/Snow/img_thumb/", 0, -1);
          TST_SET_REQ("/pictures/Quilts/img_thumb/", 0, -1);
          TST_SET_REQ("/pictures/Ridge%20Hill%20Road/img_thumb/", 0, -1);
          TST_SET_REQ("/pictures/Garlic%20Festival%202003/img_thumb/", 0, -1);
          TST_SET_REQ("/icons/", 0, -1);
          break;
        
        case LTM_INDENT_ON_TYPE_RET:
          pt->pos = VSTR_SECTS_NUM(sects, count + 5)->pos;
          pt->len = VSTR_SECTS_NUM(sects, count + 5)->len;
          break;
        
        case LTM_INDENT_ON_TYPE_ALL:
          pt->pos   = ltm_readables[LTM_READABLE_VAL_ALL].pt.pos;
          pt->len   = ltm_readables[LTM_READABLE_VAL_ALL].pt.len;
          pt->h_val = 0;
          LTM_SET_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_ALL);
          break;
        
        case LTM_INDENT_ON_TYPE_LAST:
          pt->pos = ltm_readables[LTM_READABLE_VAL_LAST].pt.pos;
          pt->len = ltm_readables[LTM_READABLE_VAL_LAST].pt.len;
          break;
        
        default:
          g_assert_not_reached();
      }
    
      pt->h_val = 0;
      if ((h_vals = g_hash_table_lookup(h_iter, pt)))
      {
        parent_iter = &h_vals->parent_iter;
        sz          = &h_vals->sz;
        req_num     = &h_vals->req_num;
        h_iter      =  h_vals->h_next_iter;
        
        ++*req_num;
      }
      else
      {
        int nxt_scan = scan + 1;

        if (ltm_conf_indent_on_type[scan] == LTM_INDENT_ON_TYPE_LAST)
          iter = *parent_iter;
        else
          gtk_tree_store_append (store, &iter, parent_iter);          

        h_vals              = g_new(struct Ltm_hash_data, 1);
        h_vals->parent_iter = iter;
        h_vals->sz          = 0;
        h_vals->req_num     = 1;

        g_hash_table_insert(h_iter, pt, h_vals);
        
        h_iter = NULL;
        if ((nxt_scan < LTM_INDENT_ON_SZ) && ltm_conf_indent_on_type[nxt_scan])
          h_iter = g_hash_table_new_full(ltm_hash_gen, ltm_hash_eq, g_free,
                                         ltm_hash_data_free);

        h_vals->h_next_iter = h_iter;
        
        parent_iter = &h_vals->parent_iter;
        sz          = &h_vals->sz;
        req_num     = &h_vals->req_num;

        {
          Ltm_vstr_pt *tmp_pt = g_new(Ltm_vstr_pt, 1);
        
          memcpy(tmp_pt, pt, sizeof(Ltm_vstr_pt));
          pt = tmp_pt;
        }
      }
      
      if (!h_iter && (ltm_conf_indent_on_type[scan] != LTM_INDENT_ON_TYPE_LAST))
      {
        gtk_tree_store_append (store, &iter, parent_iter);
        gtk_tree_store_set(store, &iter, LTM_GTK_COL_T_NUM, tot_req_num, -1);
        gtk_tree_store_set(store, &iter, LTM_GTK_COL_T_CNT, *req_num, -1);
        gtk_tree_store_set(store, &iter, LTM_GTK_COL_T_FRM, count, -1);
      }
      
      gtk_tree_store_set(store, parent_iter,
                         LTM_GTK_COL_T_NUM, tot_req_num, -1);
      gtk_tree_store_set(store, parent_iter,
                         LTM_GTK_COL_T_CNT, *req_num, -1);
      if (!LTM_IS_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_FRM))
      gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_FRM, count, -1);
      else
      gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_FRM,
                         ltm_parent_sect_num, -1);

      { /* FIXME: timezone ignored */
        time_t now = time(NULL);
        struct tm *tm = localtime(&now);
        time_t *val = g_new(time_t, 1);
      
        ptr = vstr_export_cstr_ptr(io_r,
                                   VSTR_SECTS_NUM(sects, count + 3)->pos,
                                   VSTR_SECTS_NUM(sects, count + 3)->len);
        strptime(ptr, "%d/%b/%Y:%H:%M:%S", tm);
        *val = mktime(tm);
      
        if (LTM_DISPLAY_ITER_DATA())
        gtk_tree_store_set(store, &iter,       LTM_GTK_COL_T_DAT, val, -1);
        gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_DAT, val, -1);
      }
      
      if (LTM_DISPLAY_ITER_DATA())
      gtk_tree_store_set(store, &iter,       LTM_GTK_COL_T_REQ, count + 4, -1);
      if (!LTM_IS_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_REQ))
      gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_REQ, count + 4, -1);
      else
      gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_REQ,
                         ltm_parent_sect_num, -1);
    
      {
        unsigned int ret_code = 0;

        ret_code = vstr_parse_uint(io_r,
                                   VSTR_SECTS_NUM(sects, count + 5)->pos,
                                   VSTR_SECTS_NUM(sects, count + 5)->len,
                                   VSTR_FLAG_PARSE_NUM_DEF | 10,
                                   NULL, NULL);
        if (LTM_DISPLAY_ITER_DATA())
        gtk_tree_store_set(store, &iter,       LTM_GTK_COL_T_RET, ret_code, -1);
        gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_RET, ret_code, -1);
      }
    
      ptr = vstr_export_cstr_ptr(io_r,
                                 VSTR_SECTS_NUM(sects, count + 6)->pos,
                                 VSTR_SECTS_NUM(sects, count + 6)->len);
      *sz += atoi(ptr);
      if (LTM_DISPLAY_ITER_DATA())
      gtk_tree_store_set(store, &iter,       LTM_GTK_COL_T_SIZ, atoi(ptr), -1);
      gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_SIZ, *sz, -1);
    
      if (LTM_DISPLAY_ITER_DATA())
      gtk_tree_store_set(store, &iter,       LTM_GTK_COL_T_REF, count + 7, -1);
      if (!LTM_IS_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_REF))
      gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_REF, count + 7, -1);
      else
      gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_REF,
                         ltm_parent_sect_num, -1);
      
      if (LTM_DISPLAY_ITER_DATA())
      gtk_tree_store_set(store, &iter,       LTM_GTK_COL_T_USR, count + 8, -1);
      if (!LTM_IS_PARENT_SECTION_READABLE(LTM_INDENT_ON_TYPE_USR))
      gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_USR, count + 8, -1);
      else
      gtk_tree_store_set(store, parent_iter, LTM_GTK_COL_T_USR,
                         ltm_parent_sect_num, -1);
      
      ++scan;
    }
    g_assert(!h_iter);
    
    count += 9;

    if (++added_entries >= LTM_CONF_ADD_ENTERIES_PER_LOOP)
      break;
  }
  g_object_thaw_notify(G_OBJECT(store));
  g_free(pt);

  /* update the label... */
  {
    const char *ptr = NULL;
    size_t tmp = t1->len;
    unsigned int cnum = (count      - f_data->skip_num) / 9;
    unsigned int tnum = (sects->num - f_data->skip_num) / 9;
    
    vstr_add_fmt(t1, t1->len, "<b>%'u</b> entries", cnum);
    if (f_data->unknown_lines)
      vstr_add_fmt(t1, t1->len,
                   ", <b>%'u unknown</b> entries", f_data->unknown_lines);
    if (cnum < tnum)
      vstr_add_fmt(t1, t1->len,
                   ", <b>%'u total</b> entries", sects->num / 9);
    if (len)
      vstr_add_fmt(t1, t1->len,
                   ", <b>${BKMG.u:%u}</b> of data left", len);
      
    ++tmp;
    ptr = vstr_export_cstr_ptr(t1, tmp, vstr_sc_posdiff(tmp, t1->len));
    gtk_label_set_markup(GTK_LABEL(f_data->label), ptr);
    if (t1->len > (1024 * 4)) vstr_del(t1, 1, t1->len / 2);
  }
  
  /* SECT: update file data for next time around ... */
  f_data->pos          = pos;
  f_data->unparsed_len = len;
  f_data->len          = len;
  f_data->parsed_num   = count;  

  if (!added_entries)    
    f_data->idle_id = 0;
  
  return (!!added_entries);
}

static gboolean ltm_gio_cb(GIOChannel *source, GIOCondition condition,
                           gpointer userdata)
{
  struct Ltm_file_data *f_data = userdata;
  F_DATA_DECL(Vstr_base *, io_r);
  int fd = g_io_channel_unix_get_fd(source);
  size_t prev_len = io_r->len;

  if (condition & (G_IO_ERR|G_IO_HUP|G_IO_NVAL))
  {
    if (vstr_sc_read_iov_fd(f_data->io_r, io_r->len, fd, 1024, 1024, NULL))
      goto got_data;

    return (FALSE);
  }
  
  if (!vstr_sc_read_iov_fd(f_data->io_r, io_r->len, fd, 1024, 1024, NULL))
    return (FALSE);
  
 got_data:
  f_data->unparsed_len += io_r->len - prev_len;
  
  if (!f_data->idle_id)
    f_data->idle_id = g_idle_add(ltm_idle_tree_insert_cb, f_data);

  return (TRUE);
}

int main(int argc, char *argv[])
{
  Vstr_base *io_r = NULL;
  Vstr_base *io_w = NULL;
  int arg_count = 1;
  int scan = 0;
  struct Ltm_file_data *f_data = g_new0(struct Ltm_file_data, 1);
  Vstr_sects *sects = NULL;
  const char *ssh_usr = NULL;

  f_data->pos = 1;
  f_data->parsed_num = 1;
  
  if (!vstr_init())
    abort();

  vstr_cntl_conf(NULL, VSTR_CNTL_CONF_SET_NUM_BUF_SZ, 4096);

  io_r = vstr_make_base(NULL);
  io_w = vstr_make_base(NULL);
  sects = vstr_sects_make(1024);

  if (!io_r || !io_w || !sects)
    abort();
  
  vstr_cntl_conf(io_w->conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$');
  vstr_cntl_conf(io_w->conf, VSTR_CNTL_CONF_SET_LOC_CSTR_THOU_SEP, "_");
  vstr_cntl_conf(io_w->conf, VSTR_CNTL_CONF_SET_LOC_CSTR_THOU_GRP, "\3");
    
  vstr_sc_fmt_add_all(io_w->conf);

  if (argc < 2)
  {
    vstr_add_fmt(io_w, io_w->len, " Usage: %s <type> <options>\n%s\n",
                 argv[0],
                 "  <type>:\n"
                 "    T:all       = Group everyone\n"
                 "    T:frm       = Group on the From IP address\n"
                 "    T:frm_group = Group on clients\n"
                 "    T:last      = Throw away all but last\n"
                 "    T:main_ref  = Group on sub sections of the Referrer URL\n"
                 "    T:main_req  = Group on sub sections of the Request\n"
                 "    T:main_usr  = Group on the User header, ignoring versions\n"
                 "    T:ref       = Group on the Referrer URL\n"
                 "    T:req       = Group on the Request\n"
                 "    T:ret       = Group on the Return code\n"
                 "    T:usr       = Group on the User header\n"
                 "their own groups\n");
    
    while (io_w->len)
      if (!vstr_sc_write_fd(io_w, 1, io_w->len, 2, NULL))
        abort();
    
    exit (EXIT_FAILURE);
  }
  
  while ((arg_count < argc) && (scan < LTM_INDENT_ON_SZ))
  {
    if (0) { }
    else if (!strcmp(argv[arg_count], "T:all"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_ALL;
    else if (!strcmp(argv[arg_count], "T:frm"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_FRM;
    else if (!strcmp(argv[arg_count], "T:frm_group"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_FRM_GROUP;
    else if (!strcmp(argv[arg_count], "T:last"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_LAST;
    else if (!strcmp(argv[arg_count], "T:main_ref"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_MAIN_REF;
    else if (!strcmp(argv[arg_count], "T:main_req"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_MAIN_REQ;
    else if (!strcmp(argv[arg_count], "T:main_usr"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_MAIN_USR;
    else if (!strcmp(argv[arg_count], "T:ref"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_REF;
    else if (!strcmp(argv[arg_count], "T:req"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_REQ;
    else if (!strcmp(argv[arg_count], "T:ret"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_RET;
    else if (!strcmp(argv[arg_count], "T:usr"))
      ltm_conf_indent_on_type[scan] = LTM_INDENT_ON_TYPE_USR;
    else
      break;
    
    ++arg_count;
    ++scan;
  }
  
  while (arg_count < argc)
  {
    char *spawn_argv[16];
    pid_t spawn_pid = 0;
    int spawn_io_1 = -1;
    GIOChannel *spawn_gio = NULL;
    GError *spawn_err = NULL;
    unsigned int spawn_argc = 0;
    size_t len = strlen(argv[arg_count]);

    if (0) { }
    else if ((len >= strlen("tail:")) &&
             !memcmp(argv[arg_count], "tail:", strlen("tail:")))
    {
      argv[arg_count] += strlen("tail:");
      spawn_argv[spawn_argc++] = (char *)"tail";
      spawn_argv[spawn_argc++] = (char *)"Apache logfile monitor";
      spawn_argv[spawn_argc++] = (char *)"-n";
      spawn_argv[spawn_argc++] = (char *)"0";
      spawn_argv[spawn_argc++] = (char *)"-f";
    }
    else if ((len >= strlen("ssh-uh:")) &&
             !memcmp(argv[arg_count], "ssh-uh:", strlen("ssh-uh:")))
    {
      ssh_usr = argv[arg_count++];
      ssh_usr += strlen("ssh-uh:");
      continue;
    }
    else if (ssh_usr &&
             (len >= strlen("ssh-file:")) &&
             !memcmp(argv[arg_count], "ssh-file:", strlen("ssh-file:")))
    {
      argv[arg_count] += strlen("ssh-file:");
      spawn_argv[spawn_argc++] = (char *)"ssh";
      spawn_argv[spawn_argc++] = (char *)"Apache remote logfile monitor";
      spawn_argv[spawn_argc++] = (char *)ssh_usr;
      spawn_argv[spawn_argc++] = (char *)"--";
      spawn_argv[spawn_argc++] = (char *)"tail";
      spawn_argv[spawn_argc++] = (char *)"-n";
      spawn_argv[spawn_argc++] = (char *)"0";
      spawn_argv[spawn_argc++] = (char *)"-f";
    }
    else
    {
      if ((len >= strlen("file:")) &&
          !memcmp(argv[arg_count], "file:", strlen("file:")))
        argv[arg_count] += strlen("file:");
      
      spawn_argv[spawn_argc++] = (char *)"cat";
      spawn_argv[spawn_argc++] = (char *)"Apache logfile dumper";
    }
    
    spawn_argv[spawn_argc++] = (char *)"--";
    spawn_argv[spawn_argc++] = argv[arg_count];
    spawn_argv[spawn_argc++] = NULL;
    g_assert(spawn_argc <= (sizeof(spawn_argv) / sizeof(spawn_argv[0])));
    
    if (!g_spawn_async_with_pipes(".", spawn_argv, NULL,
                                  G_SPAWN_LEAVE_DESCRIPTORS_OPEN |
                                  G_SPAWN_SEARCH_PATH |
                                  G_SPAWN_STDERR_TO_DEV_NULL |
                                  G_SPAWN_CHILD_INHERITS_STDIN |
                                  G_SPAWN_FILE_AND_ARGV_ZERO,
                                  NULL, NULL,
                                  &spawn_pid,
                                  NULL, &spawn_io_1, NULL,
                                  &spawn_err))
      g_error("g_spawn: %s", spawn_err->message);
    
    spawn_gio = g_io_channel_unix_new(spawn_io_1);
    g_io_add_watch(spawn_gio, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
                   ltm_gio_cb, f_data);
    
    ++arg_count;
  }

  f_data->sects = sects;
  f_data->io_r = io_r;
  ltm_gtk_list(io_r, f_data);

  vstr_sects_free(sects);
  vstr_free_base(io_r);
  vstr_free_base(io_w);

  vstr_exit();

  g_free(f_data);
  
  return (EXIT_SUCCESS);
}