/*
 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
 * Copyright (c) 1991-1998 University of Maryland at College Park
 * Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
 * Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of U.M. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  U.M. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: James da Silva, Systems Design and Analysis Group
 *			   Computer Science Department
 *			   University of Maryland at College Park
 */
/*
 * $Id: find.c,v 1.33 2006/07/06 13:13:15 martinea Exp $
 *
 * controlling process for the Amanda backup system
 */
#include "amanda.h"
#include "match.h"
#include "conffile.h"
#include "tapefile.h"
#include "logfile.h"
#include "holding.h"
#include "find.h"
#include <regex.h>
#include "cmdline.h"

int find_match(char *host, char *disk);
static char *find_nicedate(char *datestamp);
static int len_find_nicedate(char *datestamp);
static int find_compare(const void *, const void *);
static int parse_taper_datestamp_log(char *logline, char **datestamp,
				     char **level, char **storage, char **pool);
static gboolean logfile_has_tape(char * label, char * datestamp,
                                 char * logfile);

static char *find_sort_order = NULL;
static GStringChunk *string_chunk = NULL;

find_result_t *
find_dump(
    disklist_t *diskqp,
    int added_todo)
{
    char *conf_logdir, *logfile = NULL;
    int tape, maxtape, logs;
    unsigned seq;
    tape_t *tp;
    find_result_t *output_find = NULL;
    GHashTable *tape_seen = g_hash_table_new(g_str_hash, g_str_equal);

    if (string_chunk == NULL) {
	string_chunk = g_string_chunk_new(32768);
    }
    conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
    maxtape = lookup_nb_tape();

    for(tape = 1; tape <= maxtape; tape++) {

	tp = lookup_tapepos(tape);
	if(tp == NULL) continue;

	/* Do not search the log file if we already searched that datestamp */
	if (g_hash_table_lookup(tape_seen, tp->datestamp)) {
	    continue;
	}
	g_hash_table_insert(tape_seen, tp->datestamp, GINT_TO_POINTER(1));

	/* search log files */

	logs = 0;

	/* new-style log.<date>.<seq> */

	for(seq = 0; 1; seq++) {
	    char seq_str[NUM_STR_SIZE];

	    g_snprintf(seq_str, sizeof(seq_str), "%u", seq);
	    g_free(logfile);
	    logfile = g_strconcat(conf_logdir, "/log.", tp->datestamp, ".",
	        seq_str, NULL);
	    if(access(logfile, R_OK) != 0) break;
	    if (search_logfile(&output_find, NULL, tp->datestamp,
                               logfile, diskqp, added_todo)) {
                logs ++;
            }
	}

	/* search old-style amflush log, if any */

	g_free(logfile);
	logfile = g_strconcat(conf_logdir, "/log.", tp->datestamp, ".amflush",
	    NULL);
	if(access(logfile,R_OK) == 0) {
	    if (search_logfile(&output_find, NULL, tp->datestamp,
                               logfile, diskqp, added_todo)) {
                logs ++;
            }
        }

	/* search old-style main log, if any */

	g_free(logfile);
	logfile = g_strconcat(conf_logdir, "/log.", tp->datestamp, NULL);
	if(access(logfile,R_OK) == 0) {
	    if (search_logfile(&output_find, NULL, tp->datestamp,
                               logfile, diskqp, added_todo)) {
                logs ++;
            }
	}
    }
    g_hash_table_destroy(tape_seen);
    amfree(logfile);
    amfree(conf_logdir);

    search_holding_disk(&output_find, diskqp, added_todo);

    return(output_find);
}

char **
find_log(void)
{
    char *conf_logdir, *logfile = NULL;
    char *pathlogfile = NULL;
    int tape, maxtape, logs;
    unsigned seq;
    tape_t *tp;
    char **output_find_log = NULL;
    char **current_log;

    conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
    maxtape = lookup_nb_tape();

    output_find_log = g_malloc((maxtape*5+10) * sizeof(char *));
    current_log = output_find_log;

    for(tape = 1; tape <= maxtape; tape++) {

	tp = lookup_tapepos(tape);
	if(tp == NULL) continue;

	/* search log files */

	logs = 0;

	/* new-style log.<date>.<seq> */

	for(seq = 0; 1; seq++) {
	    char seq_str[NUM_STR_SIZE];

	    g_snprintf(seq_str, sizeof(seq_str), "%u", seq);
	    g_free(logfile);
	    logfile = g_strconcat("log.", tp->datestamp, ".", seq_str, NULL);

	    g_free(pathlogfile);
	    pathlogfile = g_strconcat(conf_logdir, "/", logfile, NULL);
	    if (access(pathlogfile, R_OK) != 0) break;
	    if (logfile_has_tape(tp->label, tp->datestamp, pathlogfile)) {
		if (current_log == output_find_log || !g_str_equal(*(current_log - 1),
                                                                   logfile)) {
		    *current_log = g_strdup(logfile);
		    current_log++;
		}
		logs++;
		break;
	    }
	}

	/* search old-style amflush log, if any */

	g_free(logfile);
	logfile = g_strconcat("log.", tp->datestamp, ".amflush", NULL);

	g_free(pathlogfile);
	pathlogfile = g_strconcat(conf_logdir, "/", logfile, NULL);
	if (access(pathlogfile, R_OK) == 0) {
	    if (logfile_has_tape(tp->label, tp->datestamp, pathlogfile)) {
		if (current_log == output_find_log || !g_str_equal(*(current_log - 1),
                                                                   logfile)) {
		    *current_log = g_strdup(logfile);
		    current_log++;
		}
		logs++;
	    }
	}

	/* search old-style main log, if any */

	g_free(logfile);
	logfile = g_strconcat("log.", tp->datestamp, NULL);
	g_free(pathlogfile);
	pathlogfile = g_strconcat(conf_logdir, "/", logfile, NULL);
	if (access(pathlogfile, R_OK) == 0) {
	    if (logfile_has_tape(tp->label, tp->datestamp, pathlogfile)) {
		if (current_log == output_find_log || !g_str_equal(*(current_log - 1),
                                                                   logfile)) {
		    *current_log = g_strdup(logfile);
		    current_log++;
		}
		logs++;
	    }
	}

	if(logs == 0 && !g_str_equal(tp->datestamp, "0"))
	    g_fprintf(stderr, _("Warning: no log files found for tape %s written %s\n"),
		   tp->label, find_nicedate(tp->datestamp));
    }
    amfree(logfile);
    amfree(pathlogfile);
    amfree(conf_logdir);
    *current_log = NULL;
    return(output_find_log);
}

GHashTable *
hash_find_log(void)
{
    int tape, maxtape;
    tape_t *tp;
    char *prefix;
    GHashTable *hash_output_find_log = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);

    maxtape = lookup_nb_tape();

    for (tape = 1; tape <= maxtape; tape++) {

	tp = lookup_tapepos(tape);
	if (tp == NULL) continue;

	prefix = g_strconcat("log.", tp->datestamp, NULL);
	g_hash_table_insert(hash_output_find_log, prefix, prefix);
    }
    return hash_output_find_log;
}

void
search_holding_disk(
    find_result_t **output_find,
    disklist_t * dynamic_disklist,
    int added_todo)
{
    GSList *holding_file_list;
    GSList *e;
    char   *holding_file;
    disk_t *dp;
    char   *orig_name;

    holding_file_list = holding_get_files(NULL, 1, 0);

    if (string_chunk == NULL) {
	string_chunk = g_string_chunk_new(32768);
    }

    for(e = holding_file_list; e != NULL; e = e->next) {
	dumpfile_t file;

	holding_file = (char *)e->data;

	if (!holding_file_get_dumpfile(holding_file, &file))
	    continue;

	if (file.dumplevel < 0 || file.dumplevel >= DUMP_LEVELS) {
	    dumpfile_free_data(&file);
	    continue;
	}

	dp = NULL;
	orig_name = g_strdup(file.name);
	for (;;) {
	    char *s;
	    if ((dp = lookup_disk(orig_name, file.disk)))
		break;
	    if ((s = strrchr(orig_name,'.')) == NULL)
		break;
	    *s = '\0';
	}
	g_free(orig_name);

	if ( dp == NULL ) {
	    if (dynamic_disklist == NULL) {
		dumpfile_free_data(&file);
		continue;
	    }
	    dp = add_disk(dynamic_disklist, file.name, file.disk);
	    dp->todo = added_todo;
	}

	if(find_match(file.name,file.disk)) {
	    find_result_t *new_output_find = g_new0(find_result_t, 1);
	    new_output_find->next=*output_find;
	    new_output_find->timestamp = g_string_chunk_insert_const(string_chunk, file.datestamp);
	    new_output_find->write_timestamp = g_string_chunk_insert_const(string_chunk, "00000000000000");
	    new_output_find->hostname = g_string_chunk_insert_const(string_chunk, file.name);
	    new_output_find->diskname = g_string_chunk_insert_const(string_chunk, file.disk);
	    new_output_find->storage = g_string_chunk_insert_const(string_chunk, "HOLDING");
	    new_output_find->pool = g_string_chunk_insert_const(string_chunk, "HOLDING");
	    new_output_find->level=file.dumplevel;
	    new_output_find->label=g_string_chunk_insert_const(string_chunk, holding_file);
	    new_output_find->partnum = -1;
	    new_output_find->totalparts = -1;
	    new_output_find->filenum=0;
	    if (file.is_partial) {
		new_output_find->status="PARTIAL";
		new_output_find->dump_status="PARTIAL";
	    } else {
		new_output_find->status="OK";
		new_output_find->dump_status="OK";
	    }
	    new_output_find->message="";
	    new_output_find->kb = holding_file_size(holding_file, 1);
	    new_output_find->bytes = 0;

	    new_output_find->orig_kb = file.orig_size;

	    *output_find=new_output_find;
	}
	dumpfile_free_data(&file);
    }

    slist_free_full(holding_file_list, g_free);
}

static int
find_compare(
    const void *i1,
    const void *j1)
{
    int compare=0;
    find_result_t *i, *j;

    size_t nb_compare=strlen(find_sort_order);
    size_t k;

    for(k=0;k<nb_compare;k++) {
        char sort_key = find_sort_order[k];
        if (isupper((int)sort_key)) {
            /* swap */
            sort_key = tolower(sort_key);
            j = *(find_result_t **)i1;
            i = *(find_result_t **)j1;
        } else {
            i = *(find_result_t **)i1;
            j = *(find_result_t **)j1;
        }

	switch (sort_key) {
	case 'h' : compare=strcmp(i->hostname,j->hostname);
		   break;
	case 'k' : compare=strcmp(i->diskname,j->diskname);
		   break;
	case 'd' : compare=strcmp(i->timestamp,j->timestamp);
		   break;
	case 'l' : compare=j->level - i->level;
		   break;
	case 'f' : compare=(i->filenum == j->filenum) ? 0 :
		           ((i->filenum < j->filenum) ? -1 : 1);
		   break;
	case 'b' : compare=compare_possibly_null_strings(i->label,
                                                         j->label);
                   break;
	case 'w': compare=strcmp(i->write_timestamp, j->write_timestamp);
		   break;
	case 'p' :
		   compare=i->partnum - j->partnum;
		   break;
	case 's' : compare=i->storage_id - j->storage_id;
		   break;
	}
	if(compare != 0)
	    return compare;
    }
    return 0;
}

void
sort_find_result(
    char *sort_order,
    find_result_t **output_find)
{
    sort_find_result_with_storage(sort_order, NULL, output_find);
}

void
sort_find_result_with_storage(
    char *sort_order,
    char **storage_list,
    find_result_t **output_find)
{
    find_result_t *output_find_result;
    find_result_t **array_find_result = NULL;
    size_t nb_result=0;
    size_t no_result;
    int    i;
    identlist_t il;

    find_sort_order = sort_order;
    /* qsort core dump if nothing to sort */
    if(*output_find==NULL)
	return;

    /* How many result */
    for(output_find_result=*output_find;
	output_find_result;
	output_find_result=output_find_result->next) {
	nb_result++;
	if (!storage_list) {
	    for (i = 1, il = getconf_identlist(CNF_STORAGE); il != NULL;
		i++, il = il->next) {
		char *storage_n = il->data;
		if (g_str_equal(output_find_result->storage,
				storage_n)) {
		    output_find_result->storage_id = i;
		}
	    }
	} else {
	    char **storage_l;
	    for (i=1, storage_l = storage_list; *storage_l != NULL;
		 storage_l++, i++) {
		if (g_str_equal(output_find_result->storage, *storage_l)) {
		    output_find_result->storage_id = i;
		}
	    }
	}
    }

    /* put the list in an array */
    array_find_result=g_malloc(nb_result * sizeof(find_result_t *));
    for(output_find_result=*output_find,no_result=0;
	output_find_result;
	output_find_result=output_find_result->next,no_result++) {
	array_find_result[no_result]=output_find_result;
    }

    /* sort the array */
    qsort(array_find_result,nb_result,sizeof(find_result_t *),
	  find_compare);

    /* put the sorted result in the list */
    for(no_result=0;
	no_result<nb_result-1; no_result++) {
	array_find_result[no_result]->next = array_find_result[no_result+1];
    }
    array_find_result[nb_result-1]->next=NULL;
    *output_find=array_find_result[0];
    amfree(array_find_result);
}

void
print_find_result(
    find_result_t *output_find)
{
    find_result_t *output_find_result;
    int max_len_datestamp = 4;
    int max_len_hostname  = 4;
    int max_len_diskname  = 4;
    int max_len_level     = 2;
    int max_len_storage   = 7;
    int max_len_pool      = 4;
    int max_len_label     =12;
    int max_len_filenum   = 4;
    int max_len_part      = 4;
    int max_len_status    = 6;
    size_t len;

    for(output_find_result=output_find;
	output_find_result;
	output_find_result=output_find_result->next) {
	char *s;

	len=len_find_nicedate(output_find_result->timestamp);
	if((int)len > max_len_datestamp)
	    max_len_datestamp=(int)len;

	len=strlen(output_find_result->hostname);
	if((int)len > max_len_hostname)
	    max_len_hostname = (int)len;

	len = len_quote_string(output_find_result->diskname);
	if((int)len > max_len_diskname)
	    max_len_diskname = (int)len;

        if (output_find_result->label != NULL) {
	    len = len_quote_string(output_find_result->label);
            if((int)len > max_len_label)
                max_len_label = (int)len;
        }

        if (output_find_result->storage != NULL) {
	    len = len_quote_string(output_find_result->storage);
            if((int)len > max_len_storage)
                max_len_storage = (int)len;
        }

        if (output_find_result->pool != NULL) {
	    len = len_quote_string(output_find_result->pool);
            if((int)len > max_len_pool)
                max_len_pool = (int)len;
        }

	len=strlen(output_find_result->status) + 1 + strlen(output_find_result->dump_status);
	if((int)len > max_len_status)
	    max_len_status = (int)len;

	s = g_strdup_printf("%d/%d", output_find_result->partnum,
				     output_find_result->totalparts);
	len=strlen(s);
	if((int)len > max_len_part)
	    max_len_part = (int)len;
	amfree(s);
    }

    /*
     * Since status is the rightmost field, we zap the maximum length
     * because it is not needed.  The code is left in place in case
     * another column is added later.
     */
    max_len_status = 1;

    if(output_find==NULL) {
	g_printf(_("\nNo dump to list\n"));
    }
    else {
	g_printf(_("\ndate%*s host%*s disk%*s lv%*s storage%*s pool%*s tape or file%*s file%*s part%*s status\n"),
	       max_len_datestamp-4,"",
	       max_len_hostname-4 ,"",
	       max_len_diskname-4 ,"",
	       max_len_level-2    ,"",
	       max_len_storage-7  ,"",
	       max_len_pool-4     ,"",
	       max_len_label-12   ,"",
	       max_len_filenum-4  ,"",
	       max_len_part-4  ,"");
        for(output_find_result=output_find;
	        output_find_result;
	        output_find_result=output_find_result->next) {
	    char *qdiskname;
            char * formatted_label;
	    char *s;
	    char *status;

	    qdiskname = quote_string(output_find_result->diskname);
            if (output_find_result->label == NULL)
                formatted_label = g_strdup("");
	    else
		formatted_label = quote_string(output_find_result->label);

	    if (!g_str_equal(output_find_result->status, "OK") ||
		!g_str_equal(output_find_result->dump_status, "OK")) {
		status = g_strjoin(NULL, output_find_result->status, " ",
				   output_find_result->dump_status, NULL);
	    } else {
		status = g_strdup(output_find_result->status);
	    }

	    /*@ignore@*/
	    /* sec and kb are omitted here, for compatibility with the existing
	     * output from 'amadmin' */
	    s = g_strdup_printf("%d/%d", output_find_result->partnum,
					 output_find_result->totalparts);
	    g_printf("%-*s %-*s %-*s %*d %-*s %-*s %-*s %*lld %*s %s %s\n",
                     max_len_datestamp,
                     find_nicedate(output_find_result->timestamp),
                     max_len_hostname,  output_find_result->hostname,
                     max_len_diskname,  qdiskname,
                     max_len_level,     output_find_result->level,
                     max_len_storage,   output_find_result->storage,
                     max_len_pool,      output_find_result->pool,
                     max_len_label,     formatted_label,
                     max_len_filenum,   (long long)output_find_result->filenum,
                     max_len_part,      s,
                                        status,
					output_find_result->message
		    );
	    amfree(status);
	    amfree(s);
	    /*@end@*/
	    amfree(qdiskname);
	    amfree(formatted_label);
	}
    }
}

void
free_find_result(
    find_result_t **output_find)
{
    find_result_t *output_find_result, *prev;

    prev=NULL;
    for(output_find_result=*output_find;
	    output_find_result;
	    output_find_result=output_find_result->next) {
	amfree(prev);
	prev = output_find_result;
    }
    amfree(prev);
    *output_find = NULL;
}

int
find_match(
    char *host,
    char *disk)
{
    disk_t *dp = lookup_disk(host,disk);
    return (dp && dp->todo);
}

static char *
find_nicedate(
    char *datestamp)
{
    static char nice[20];
    int year, month, day;
    int hours, minutes, seconds;
    char date[9], atime[7];
    int  numdate, numtime;

    strncpy(date, datestamp, 8);
    date[8] = '\0';
    numdate = atoi(date);
    year  = numdate / 10000;
    month = (numdate / 100) % 100;
    day   = numdate % 100;

    if(strlen(datestamp) <= 8) {
	g_snprintf(nice, sizeof(nice), "%4d-%02d-%02d",
		year, month, day);
    }
    else {
	strncpy(atime, &(datestamp[8]), 6);
	atime[6] = '\0';
	numtime = atoi(atime);
	hours = numtime / 10000;
	minutes = (numtime / 100) % 100;
	seconds = numtime % 100;

	g_snprintf(nice, sizeof(nice), "%4d-%02d-%02d %02d:%02d:%02d",
		year, month, day, hours, minutes, seconds);
    }

    return nice;
}

static int
len_find_nicedate(
    char *datestamp)
{
    if(strlen(datestamp) <= 8) {
	return 10;
    } else {
	return 19;
    }
}

static int
parse_taper_datestamp_log(
    char *logline,
    char **datestamp,
    char **label,
    char **storage,
    char **pool)
{
    char *s;
    int ch;
    char *qnext_token, *uqnext_token;

    s = logline;
    ch = *s++;

    skip_whitespace(s, ch);
    if(ch == '\0') {
	return 0;
    }
    if(strncmp_const_skip(s - 1, "datestamp", s, ch) != 0) {
	return 0;
    }

    skip_whitespace(s, ch);
    if(ch == '\0') {
	return 0;
    }
    *datestamp = s - 1;
    skip_non_whitespace(s, ch);
    s[-1] = '\0';

    skip_whitespace(s, ch);
    if(ch == '\0') {
	return 0;
    }
    qnext_token = s - 1;
    skip_quoted_string(s, ch);
    s[-1] = '\0';

    *storage = g_strdup(get_config_name());
    uqnext_token = unquote_string(qnext_token);
    if (strncmp(uqnext_token, "ST:", 3) == 0) {
	skip_whitespace(s, ch);
	if(ch == '\0') {
	    return 0;
	}
	g_free(*storage);
	*storage = g_strdup(uqnext_token+3);
	g_free(uqnext_token);
	skip_whitespace(s, ch);
	if(ch == '\0') {
	    return 0;
	}
	qnext_token = s - 1;
	skip_quoted_string(s, ch);
	s[-1] = '\0';

	uqnext_token = unquote_string(qnext_token);
    }

    *pool = g_strdup(*storage);
    if (strncmp(uqnext_token, "POOL:", 5) == 0) {
	skip_whitespace(s, ch);
	if(ch == '\0') {
	    return 0;
	}
	g_free(*pool);
	*pool = g_strdup(uqnext_token+5);
	g_free(uqnext_token);
	skip_whitespace(s, ch);
	if(ch == '\0') {
	    return 0;
	}
	qnext_token = s - 1;
	skip_quoted_string(s, ch);
	s[-1] = '\0';

	uqnext_token = unquote_string(qnext_token);
    }

    if (strncmp(uqnext_token, "label", 5) != 0) {
	g_free(uqnext_token);
	return 0;
    }
    g_free(uqnext_token);

    skip_whitespace(s, ch);
    if(ch == '\0') {
	return 0;
    }
    *label = s - 1;
    skip_quoted_string(s, ch);
    s[-1] = '\0';

    *label = unquote_string(*label);

    return 1;
}

/* Returns TRUE if the given logfile mentions the given tape. */
static gboolean logfile_has_tape(char * label, char * datestamp,
                                 char * logfile) {
    FILE * logf;
    char * ck_datestamp, *ck_label = NULL, *ck_storage = NULL, *ck_pool = NULL;
    if((logf = fopen(logfile, "r")) == NULL) {
	error(_("could not open logfile %s: %s"), logfile, strerror(errno));
	/*NOTREACHED*/
    }

    while(get_logline(logf)) {
	if(curlog == L_START && curprog == P_TAPER) {
	    if(parse_taper_datestamp_log(curstr,
					 &ck_datestamp, &ck_label, &ck_storage, &ck_pool) == 0) {
		g_printf(_("strange log line \"start taper %s\" curstr='%s'\n"),
                         logfile, curstr);
	    } else if(g_str_equal(ck_datestamp, datestamp)
		      && g_str_equal(ck_label, label)) {
		amfree(ck_label);
		amfree(ck_storage);
		amfree(ck_pool);
                afclose(logf);
                return TRUE;
	    }
	    amfree(ck_label);
	    amfree(ck_storage);
	    amfree(ck_pool);
	}
    }

    afclose(logf);
    return FALSE;
}

static gboolean
volume_matches(
    const char *label1,
    const char *label2,
    const char *datestamp)
{
    tape_t *tp;

    if (!label2)
	return TRUE;

    if (label1)
	return (g_str_equal(label1, label2));

    /* check in tapelist */
    if (!(tp = lookup_tapelabel(label2)))
	return FALSE;

    if (!g_str_equal(tp->datestamp, datestamp))
	return FALSE;

    return TRUE;
}

/* WARNING: Function accesses globals find_diskqp, curlog, curlog, curstr,
 * dynamic_disklist */
gboolean
search_logfile(
    find_result_t **output_find,
    const char *label,
    const char *passed_datestamp,
    const char *logfile,
    disklist_t * dynamic_disklist,
    int added_todo)
{
    FILE *logf;
    char *host = NULL;
    char *disk = NULL, *qdisk, *disk_undo;
    char *date = NULL, *date_undo;
    int  partnum;
    int  totalparts;
    int  maxparts = -1;
    char *number;
    int fileno;
    char *current_label;
    char *current_storage;
    char *current_pool;
    char *rest, *rest_undo;
    char *ck_label=NULL;
    char *ck_storage=NULL;
    char *ck_pool=NULL;
    int level = 0;
    off_t filenum;
    char *ck_datestamp=NULL;
    char *datestamp;
    char *s;
    int ch;
    disk_t *dp;
    GHashTable* valid_label;
    GHashTable* part_by_dle;
    find_result_t *part_find;
    find_result_t *a_part_find;
    gboolean right_label = FALSE;
    gboolean found_something = FALSE;
    double sec;
    off_t kb;
    off_t bytes;
    off_t orig_kb;
    int   taper_part = 0;
    crc_t crc1, crc2, crc3;
    crc_t native_crc;
    crc_t client_crc;
    crc_t server_crc;

    g_return_val_if_fail(output_find != NULL, 0);
    g_return_val_if_fail(logfile != NULL, 0);

    current_label = g_strdup("");
    current_storage = g_strdup("");
    current_pool = g_strdup("");
    if (string_chunk == NULL) {
	string_chunk = g_string_chunk_new(32768);
    }
    valid_label = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
    part_by_dle = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
    datestamp = g_strdup(passed_datestamp);

    if((logf = fopen(logfile, "r")) == NULL) {
	if (errno != ENOENT) {
	    g_debug("could not open logfile %s: %s", logfile, strerror(errno));
	}
	error(_("could not open logfile %s: %s"), logfile, strerror(errno));
	/*NOTREACHED*/
    }

    filenum = (off_t)0;
    while(get_logline(logf)) {
	if (curlog == L_START && curprog == P_TAPER) {
	    amfree(ck_label);
	    amfree(ck_storage);
	    amfree(ck_pool);
	    ck_datestamp = NULL;
	    if(parse_taper_datestamp_log(curstr, &ck_datestamp,
                                         &ck_label, &ck_storage, &ck_pool) == 0) {
		g_printf(_("strange log line in %s \"start taper %s\"\n"),
                         logfile, curstr);
                continue;
	    }
            if (datestamp != NULL) {
                if (!g_str_equal(datestamp, ck_datestamp)) {
                    g_printf(_("Log file %s stamped %s, expecting %s!\n"),
                             logfile, ck_datestamp, datestamp);
		    amfree(ck_label);
                    break;
                }
            }

            right_label = volume_matches(label, ck_label, ck_datestamp);
	    if (right_label && ck_label) {
		g_hash_table_insert(valid_label, g_strdup(ck_label),
				    GINT_TO_POINTER(1));
	    }
	    if (label && datestamp && right_label) {
		found_something = TRUE;
	    }
            amfree(current_label);
            current_label = ck_label;
	    ck_label = NULL;
            amfree(current_storage);
            current_storage = ck_storage;
	    ck_storage = NULL;
            amfree(current_pool);
            current_pool = ck_pool;
	    ck_pool = NULL;
            if (datestamp == NULL) {
                datestamp = g_strdup(ck_datestamp);
            }
	    filenum = (off_t)0;
	}
	if (!datestamp)
	    continue;
	if (right_label &&
	    (curlog == L_SUCCESS ||
	     curlog == L_CHUNK || curlog == L_PART || curlog == L_PARTPARTIAL) &&
	    curprog == P_TAPER) {
	    filenum++;
	} else if (right_label && curlog == L_PARTIAL && curprog == P_TAPER &&
		   taper_part == 0) {
	    filenum++;
	}
	partnum = -1;
	totalparts = -1;
	if (curlog == L_SUCCESS || curlog == L_CHUNKSUCCESS ||
	    curlog == L_DONE    || curlog == L_FAIL ||
	    curlog == L_CHUNK   || curlog == L_PART || curlog == L_PARTIAL ||
	    curlog == L_PARTPARTIAL ) {
	    s = curstr;
	    ch = *s++;

	    skip_whitespace(s, ch);
	    if(ch == '\0') {
		g_printf(_("strange log line in %s \"%s\"\n"),
		    logfile, curstr);
		continue;
	    }

	    if (curlog == L_PART || curlog == L_PARTPARTIAL ||
		curlog == L_DONE || curlog == L_FAIL || curlog == L_PARTIAL) {
		char *part_storage;
		char *qpart_storage;
		char *part_pool;
		char *part_label;
		char *qnext_string;
		char *next_string;
		taper_part++;

		qpart_storage = s - 1;
		skip_quoted_string(s, ch);
		s[-1] = '\0';
		part_storage = unquote_string(qpart_storage);
		if (strcmp(part_storage, "VAULT") == 0) {
		    g_free(part_storage);
		    skip_whitespace(s, ch);
		    qpart_storage = s - 1;
		    skip_quoted_string(s, ch);
		    s[-1] = '\0';
		    part_storage = unquote_string(qpart_storage);
		}
		if (strncmp(part_storage, "ST:", 3) == 0) {
		    char *ps = part_storage;
		    part_storage = g_strdup(ps+3);
		    g_free(ps);
		    skip_whitespace(s, ch);
		    qnext_string = s - 1;
		    skip_quoted_string(s, ch);
		    s[-1] = '\0';
		    next_string = unquote_string(qnext_string);
		} else {
		    next_string = part_storage;
		    part_storage = g_strdup(get_config_name());
		}

		if (strncmp(next_string, "POOL:", 5) == 0) {
		    char *pp = next_string;
		    part_pool = g_strdup(next_string+5);
		    g_free(pp);
		    skip_whitespace(s, ch);
		    qnext_string = s - 1;
		    skip_quoted_string(s, ch);
		    s[-1] = '\0';
		    next_string = unquote_string(qnext_string);
		} else {
		    part_pool = g_strdup(part_storage);
		}

		if (curlog == L_PART || curlog == L_PARTPARTIAL) {
		    part_label = next_string;
		    if (!g_hash_table_lookup(valid_label, part_label)) {
			amfree(part_label);
			amfree(part_storage);
			amfree(part_pool);
			continue;
		    }
		    amfree(current_label);
		    current_label = part_label;
		    amfree(current_storage);
		    current_storage = part_storage;
		    amfree(current_pool);
		    current_pool = part_pool;

		    skip_whitespace(s, ch);
		    if (ch == '\0') {
			g_printf("strange log line in %s \"%s\"\n",
			   logfile, curstr);
			continue;
		    }

		    number = s - 1;
		    skip_non_whitespace(s, ch);
		    s[-1] = '\0';
		    fileno = atoi(number);
		    filenum = fileno;
		    if (filenum == 0)
			continue;

		    skip_whitespace(s, ch);
		    if(ch == '\0') {
			g_printf("strange log line in %s \"%s\"\n",
			   logfile, curstr);
			continue;
		    }
		    amfree(host);
		    host = s - 1;
		    skip_non_whitespace(s, ch);
		    s[-1] = '\0';
		    host = g_strdup(host);
		} else {
		    amfree(part_storage);
		    amfree(part_pool);
		    amfree(host);
		    host = next_string;
		}
	    } else {
		taper_part = 0;
		    amfree(host);
		host = s - 1;
		skip_non_whitespace(s, ch);
		s[-1] = '\0';
		host = g_strdup(host);
	    }

	    skip_whitespace(s, ch);
	    if(ch == '\0') {
		g_printf(_("strange log line in %s \"%s\"\n"),
		    logfile, curstr);
		continue;
	    }
	    qdisk = s - 1;
	    skip_quoted_string(s, ch);
	    disk_undo = s - 1;
	    *disk_undo = '\0';
	    amfree(disk);
	    disk = unquote_string(qdisk);

	    skip_whitespace(s, ch);
	    if(ch == '\0') {
		g_printf(_("strange log line in %s \"%s\"\n"),
                         logfile, curstr);
		amfree(disk);
		continue;
	    }
	    amfree(date);
	    date = s - 1;
	    skip_non_whitespace(s, ch);
	    date_undo = s - 1;
	    *date_undo = '\0';
	    date = g_strdup(date);

	    if(strlen(date) < 3) { /* old log didn't have datestamp */
		level = atoi(date);
		date = g_strdup(datestamp);
		partnum = 1;
		totalparts = 1;
	    } else {
		if (curprog == P_TAPER &&
			(curlog == L_CHUNK || curlog == L_PART ||
			 curlog == L_PARTPARTIAL || curlog == L_PARTIAL ||
			 curlog == L_DONE)) {
		    char *s1, ch1;
		    skip_whitespace(s, ch);
		    number = s - 1;
		    skip_non_whitespace(s, ch);
		    s1 = &s[-1];
		    ch1 = *s1;
		    skip_whitespace(s, ch);
		    if (*(s-1) != '[') {
			*s1 = ch1;
			sscanf(number, "%d/%d", &partnum, &totalparts);
			if (partnum > maxparts)
			    maxparts = partnum;
			if (totalparts > maxparts)
			    maxparts = totalparts;
		    } else { /* nparts is not in all PARTIAL lines */
			partnum = 1;
			totalparts = 1;
			s = number + 1;
		    }
		} else {
		    skip_whitespace(s, ch);
		}
		if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
		    g_printf(_("Fstrange log line in %s \"%s\"\n"),
		    logfile, s-1);
		    amfree(disk);
		    continue;
		}
		skip_integer(s, ch);
	    }

	    skip_whitespace(s, ch);
	    if(ch == '\0') {
		g_printf(_("strange log line in %s \"%s\"\n"),
		    logfile, curstr);
		amfree(disk);
		continue;
	    }
	    rest = s - 1;
	    skip_non_whitespace(s, ch);
	    rest_undo = s - 1;
	    *rest_undo = '\0';
	    crc1.crc = 0;
	    crc1.size = 0;
	    crc2.crc = 0;
	    crc2.size = 0;
	    crc3.crc = 0;
	    crc3.size = 0;
	    if (curlog == L_DONE) {
		if (!g_str_equal(rest, "[sec")) {
		    // CRC
		    parse_crc(rest, &crc1);
		    skip_whitespace(s, ch);
		    rest = s - 1;
		    skip_non_whitespace(s, ch);
		    rest_undo = s - 1;
		    *rest_undo = '\0';
		}
		if (!g_str_equal(rest, "[sec")) {
		    // CRC
		    parse_crc(rest, &crc2);
		    skip_whitespace(s, ch);
		    rest = s - 1;
		    skip_non_whitespace(s, ch);
		    rest_undo = s - 1;
		    *rest_undo = '\0';
		}
		if (!g_str_equal(rest, "[sec")) {
		    // CRC
		    parse_crc(rest, &crc3);
		    skip_whitespace(s, ch);
		    rest = s - 1;
		    skip_non_whitespace(s, ch);
		    rest_undo = s - 1;
		    *rest_undo = '\0';
		}
	    }
	    native_crc.crc = 0;
	    native_crc.size = 0;
	    client_crc.crc = 0;
	    client_crc.size = 0;
	    server_crc.crc = 0;
	    server_crc.size = 0;
	    if (curprog == P_DUMPER) {
		native_crc = crc1;
		client_crc = crc2;
	    } else if (curprog == P_CHUNKER) {
		server_crc = crc1;
	    } else if (curprog == P_TAPER) {
		native_crc = crc1;
		client_crc = crc2;
		server_crc = crc3;
	    }
	    if (g_str_equal(rest, "[sec")) {
		skip_whitespace(s, ch);
		if(ch == '\0') {
		    g_printf(_("strange log line in %s \"%s\"\n"),
			     logfile, curstr);
		    amfree(disk);
		    continue;
		}
		sec = atof(s - 1);
		skip_non_whitespace(s, ch);
		skip_whitespace(s, ch);
		rest = s - 1;
		skip_non_whitespace(s, ch);
		rest_undo = s - 1;
		*rest_undo = '\0';
		if (!g_str_equal(rest, "kb") &&
		    !g_str_equal(rest, "bytes")) {
		    g_printf(_("Bstrange log line in %s \"%s\"\n"),
			     logfile, curstr);
		    amfree(disk);
		    continue;
		}

		skip_whitespace(s, ch);
		if (ch == '\0') {
		     g_printf(_("strange log line in %s \"%s\"\n"),
			      logfile, curstr);
		     amfree(disk);
		     continue;
		}
		if (g_str_equal(rest, "kb")) {
		    kb = atof(s - 1);
		    bytes = 0;
		} else {
		    bytes = atof(s - 1);
		    kb = bytes / 1024;
		}
		skip_non_whitespace(s, ch);
		skip_whitespace(s, ch);
		rest = s - 1;
		skip_non_whitespace(s, ch);
		rest_undo = s - 1;
		*rest_undo = '\0';
		if (!g_str_equal(rest, "kps")) {
		    g_printf(_("Cstrange log line in %s \"%s\"\n"),
			     logfile, curstr);
		    amfree(disk);
		    continue;
		}

		skip_whitespace(s, ch);
		if (ch == '\0') {
		    g_printf(_("strange log line in %s \"%s\"\n"),
			     logfile, curstr);
		    amfree(disk);
		    continue;
		}
		/* kps = atof(s - 1); */
		skip_non_whitespace(s, ch);
		skip_whitespace(s, ch);
		rest = s - 1;
		skip_non_whitespace(s, ch);
		rest_undo = s - 1;
		*rest_undo = '\0';
		if (!g_str_equal(rest, "orig-kb")) {
		    orig_kb = 0;
		} else {

		    skip_whitespace(s, ch);
		    if(ch == '\0') {
			g_printf(_("strange log line in %s \"%s\"\n"),
				 logfile, curstr);
			amfree(disk);
			continue;
		    }
		    orig_kb = atof(s - 1);
		}
	    } else {
		sec = 0;
		kb = 0;
		bytes = 0;
		orig_kb = 0;
		*rest_undo = ' ';
	    }

	    if (g_str_has_prefix(rest, "error")) rest += 6;
	    if (g_str_has_prefix(rest, "config")) rest += 7;

	    dp = lookup_disk(host,disk);
	    if ( dp == NULL ) {
		if (dynamic_disklist == NULL) {
		    amfree(disk);
		    continue;
		}
		dp = add_disk(dynamic_disklist, host, disk);
		dp->todo = added_todo;
	    }
            if (find_match(host, disk)) {
		if(curprog == P_TAPER) {
		    char *key = g_strdup_printf(
					"HOST:%s DISK:%s: DATE:%s LEVEL:%d",
					host, disk, date, level);
		    find_result_t *new_output_find = g_new0(find_result_t, 1);
		    part_find = g_hash_table_lookup(part_by_dle, key);
		    maxparts = partnum;
		    if (maxparts < totalparts)
			maxparts = totalparts;
		    for (a_part_find = part_find;
			 a_part_find;
			 a_part_find = a_part_find->next) {
			if (maxparts < a_part_find->partnum)
			    maxparts = a_part_find->partnum;
			if (maxparts < a_part_find->totalparts)
			    maxparts = a_part_find->totalparts;
		    }
		    new_output_find->timestamp = g_string_chunk_insert_const(string_chunk, date);
		    new_output_find->write_timestamp = g_string_chunk_insert_const(string_chunk, datestamp);
		    new_output_find->hostname=g_string_chunk_insert_const(string_chunk, host);
		    new_output_find->diskname=g_string_chunk_insert_const(string_chunk, disk);
		    new_output_find->level=level;
		    new_output_find->partnum = partnum;
		    new_output_find->totalparts = totalparts;
		    new_output_find->label=g_string_chunk_insert_const(string_chunk, current_label);
		    if (current_storage) {
			new_output_find->storage=g_string_chunk_insert_const(string_chunk, current_storage);
		    }
		    if (current_pool) {
			new_output_find->pool=g_string_chunk_insert_const(string_chunk, current_pool);
		    }
		    new_output_find->status=NULL;
		    new_output_find->dump_status=NULL;
		    new_output_find->message="";
		    new_output_find->filenum=filenum;
		    new_output_find->sec=sec;
		    new_output_find->kb=kb;
		    new_output_find->bytes=bytes;
		    new_output_find->orig_kb=orig_kb;
		    new_output_find->next=NULL;
		    new_output_find->native_crc = native_crc;
		    new_output_find->client_crc = client_crc;
		    new_output_find->server_crc = server_crc;
		    if (curlog == L_SUCCESS) {
			new_output_find->status = "OK";
			new_output_find->dump_status = "OK";
			new_output_find->next = *output_find;
			new_output_find->partnum = 1; /* L_SUCCESS is pre-splitting */
			*output_find = new_output_find;
                        found_something = TRUE;
		    } else if (curlog == L_CHUNKSUCCESS || curlog == L_DONE ||
			       curlog == L_PARTIAL      || curlog == L_FAIL) {
			/* result line */
			if (curlog == L_PARTIAL || curlog == L_FAIL) {
			    /* set dump_status of each part */
			    for (a_part_find = part_find;
				 a_part_find;
				 a_part_find = a_part_find->next) {
				if (curlog == L_PARTIAL)
				    a_part_find->dump_status = "PARTIAL";
				else {
				    a_part_find->dump_status = "FAIL";
				    a_part_find->message = g_string_chunk_insert_const(string_chunk, rest);
				}
			    }
			} else {
			    if (maxparts > -1) { /* format with part */
				/* must check if all part are there */
				int num_part = maxparts;
				for (a_part_find = part_find;
				     a_part_find;
				     a_part_find = a_part_find->next) {
				    if (a_part_find->partnum == num_part &&
					g_str_equal(a_part_find->status, "OK"))
					num_part--;
			        }
				/* set dump_status of each part */
				for (a_part_find = part_find;
				     a_part_find;
				     a_part_find = a_part_find->next) {
				    if (num_part == 0) {
					a_part_find->dump_status = "OK";
				    } else {
					a_part_find->dump_status = "FAIL";
					a_part_find->message =
						g_string_chunk_insert_const(string_chunk, "Missing part");
				    }
				}
			    }
			}
			if (curlog == L_DONE) {
			    for (a_part_find = part_find;
				 a_part_find;
			         a_part_find = a_part_find->next) {
				if (a_part_find->totalparts == -1) {
				    a_part_find->totalparts = maxparts;
				}
				if (a_part_find->orig_kb == 0) {
				    a_part_find->orig_kb = orig_kb;
				}
				a_part_find->native_crc = native_crc;
				a_part_find->client_crc = client_crc;
				a_part_find->server_crc = server_crc;
			    }
			}
			if (part_find) { /* find last element */
			    for (a_part_find = part_find;
				 a_part_find->next != NULL;
				 a_part_find=a_part_find->next) {
			    }
			    /* merge part_find to *output_find */
			    a_part_find->next = *output_find;
			    *output_find = part_find;
			    part_find = NULL;
			    maxparts = -1;
                            found_something = TRUE;
			    g_hash_table_remove(part_by_dle, key);
			}
			free_find_result(&new_output_find);
		    } else { /* part line */
			if (curlog == L_PART || curlog == L_CHUNK) {
			    new_output_find->status = "OK";
			    new_output_find->dump_status = "OK";
			} else { /* PARTPARTIAL */
			    new_output_find->status = "PARTIAL";
			    new_output_find->dump_status = "PARTIAL";
			}
			/* Add to part_find list */
			if (part_find) {
			    new_output_find->next = part_find;
			    part_find = new_output_find;
			} else {
			    new_output_find->next = NULL;
			    part_find = new_output_find;
			}
			g_hash_table_insert(part_by_dle, g_strdup(key),
					    part_find);
			found_something = TRUE;
		    }
		    amfree(key);
		}
		else if(curlog == L_FAIL) {
		    char *status_failed;
		    /* print other failures too -- this is a hack to ensure that failures which
		     * did not make it to tape are also listed in the output of 'amadmin x find';
		     * users that do not want this information (e.g., Amanda::DB::Catalog) should
		     * filter dumps with a NULL label. */
		    find_result_t *new_output_find = g_new0(find_result_t, 1);
		    new_output_find->next = *output_find;
		    new_output_find->timestamp = g_string_chunk_insert_const(string_chunk, date);
		    new_output_find->write_timestamp = g_string_chunk_insert_const(string_chunk, "00000000000000"); /* dump was not written.. */
		    new_output_find->hostname=g_string_chunk_insert_const(string_chunk, host);
		    new_output_find->diskname=g_string_chunk_insert_const(string_chunk, disk);
		    if (current_storage != NULL) {
			new_output_find->storage=g_string_chunk_insert_const(string_chunk, current_storage);
		    }
		    if (current_pool != NULL) {
			new_output_find->pool=g_string_chunk_insert_const(string_chunk, current_pool);
		    }
		    new_output_find->level=level;
		    new_output_find->label=NULL;
		    new_output_find->partnum=partnum;
		    new_output_find->totalparts=totalparts;
		    new_output_find->filenum=0;
		    new_output_find->sec=sec;
		    new_output_find->kb=kb;
		    new_output_find->bytes=bytes;
		    new_output_find->orig_kb=orig_kb;
		    status_failed = g_strjoin(NULL,
			 "FAILED (",
			 program_str[(int)curprog],
			 ") ",
			 rest,
			 NULL);
		    new_output_find->status = g_string_chunk_insert_const(string_chunk, status_failed);
		    amfree(status_failed);
		    new_output_find->dump_status="";
		    new_output_find->message="";
		    *output_find=new_output_find;
                    found_something = TRUE;
		    maxparts = -1;
		}
	    }
	}
    }
    amfree(host);
    amfree(date);
    amfree(disk);

    g_hash_table_destroy(valid_label);
    g_hash_table_destroy(part_by_dle);
    afclose(logf);
    amfree(datestamp);
    amfree(current_label);
    amfree(current_storage);
    amfree(current_pool);
    amfree(disk);

    return found_something;
}


/*
 * Return the set of dumps that match *all* of the given patterns (we consider
 * an empty pattern to match .*, though).  If 'ok' is true, will only match
 * dumps with SUCCESS status.
 *
 * Returns a newly allocated list of results, where all strings are also newly
 * allocated.  Apparently some part of Amanda leaks under this condition.
 */
find_result_t *
dumps_match(
    find_result_t *output_find,
    char *hostname,
    char *diskname,
    char *datestamp,
    char *level,
    int ok)
{
    find_result_t *cur_result;
    find_result_t *matches = NULL;

    for(cur_result=output_find;
	cur_result;
	cur_result=cur_result->next) {
	char level_str[NUM_STR_SIZE];
	g_snprintf(level_str, sizeof(level_str), "%d", cur_result->level);
	if((!hostname || *hostname == '\0' || match_host(hostname, cur_result->hostname)) &&
	   (!diskname || *diskname == '\0' || match_disk(diskname, cur_result->diskname)) &&
	   (!datestamp || *datestamp== '\0' || match_datestamp(datestamp, cur_result->timestamp)) &&
	   (!level || *level== '\0' || match_level(level, level_str)) &&
	   (!ok || g_str_equal(cur_result->status, "OK")) &&
	   (!ok || g_str_equal(cur_result->dump_status, "OK"))){

	    find_result_t *curmatch = g_new0(find_result_t, 1);
	    memcpy(curmatch, cur_result, sizeof(find_result_t));

	    curmatch->timestamp = cur_result->timestamp;
	    curmatch->write_timestamp = cur_result->write_timestamp;
	    curmatch->hostname = cur_result->hostname;
	    curmatch->diskname = cur_result->diskname;
	    curmatch->level = cur_result->level;
	    curmatch->label = cur_result->label? cur_result->label : NULL;
	    curmatch->filenum = cur_result->filenum;
	    curmatch->sec = cur_result->sec;
	    curmatch->kb = cur_result->kb;
	    curmatch->bytes = cur_result->bytes;
	    curmatch->orig_kb = cur_result->orig_kb;
	    curmatch->status = cur_result->status;
	    curmatch->dump_status = cur_result->dump_status;
	    curmatch->message = cur_result->message;
	    curmatch->partnum = cur_result->partnum;
	    curmatch->totalparts = cur_result->totalparts;
	    curmatch->next = matches;
	    matches = curmatch;
	}
    }

    return(matches);
}

/*
 * Return the set of dumps that match one or more of the given dumpspecs,
 * If 'ok' is true, only dumps with a SUCCESS status will be matched.
 * 
 * Returns a newly allocated list of results, where all strings are also newly
 * allocated.  Apparently some part of Amanda leaks under this condition.
 */
find_result_t *
dumps_match_dumpspecs(
    find_result_t *output_find,
    GSList        *dumpspecs,
    int ok)
{
    find_result_t *cur_result;
    find_result_t *matches = NULL;
    GSList        *dumpspec;
    dumpspec_t    *ds;

    for(cur_result=output_find;
	cur_result;
	cur_result=cur_result->next) {
	char level_str[NUM_STR_SIZE];
	char *zeropad_ts = NULL;
	char *zeropad_w_ts = NULL;
	g_snprintf(level_str, sizeof(level_str), "%d", cur_result->level);

	/* get the timestamp padded to full width */
	if (strlen(cur_result->timestamp) < 14) {
	    zeropad_ts = g_new0(char, 15);
	    memset(zeropad_ts, '0', 14);
	    memcpy(zeropad_ts, cur_result->timestamp, strlen(cur_result->timestamp));
	}
	if (strlen(cur_result->write_timestamp) < 14) {
	    zeropad_w_ts = g_new0(char, 15);
	    memset(zeropad_w_ts, '0', 14);
	    memcpy(zeropad_w_ts, cur_result->write_timestamp, strlen(cur_result->write_timestamp));
	}

	for (dumpspec = dumpspecs; dumpspec; dumpspec = dumpspec->next) {
	    ds = (dumpspec_t *)dumpspec->data;
	    if((!ds->host || *ds->host == '\0' || match_host(ds->host, cur_result->hostname)) &&
	       (!ds->disk || *ds->disk == '\0' || match_disk(ds->disk, cur_result->diskname)) &&
	       (!ds->datestamp || *ds->datestamp== '\0'
			|| match_datestamp(ds->datestamp, cur_result->timestamp)
			|| (zeropad_ts && match_datestamp(ds->datestamp, zeropad_ts))) &&
	       (!ds->write_timestamp || *ds->write_timestamp== '\0'
			|| match_datestamp(ds->write_timestamp, cur_result->write_timestamp)
			|| (zeropad_w_ts && match_datestamp(ds->write_timestamp, zeropad_w_ts))) &&
	       (!ds->level || *ds->level== '\0' || match_level(ds->level, level_str)) &&
	       (!ok || g_str_equal(cur_result->status, "OK")) &&
	       (!ok || g_str_equal(cur_result->dump_status, "OK"))) {

		find_result_t *curmatch = g_malloc(sizeof(find_result_t));
		memcpy(curmatch, cur_result, sizeof(find_result_t));

		curmatch->timestamp = cur_result->timestamp;
		curmatch->write_timestamp = cur_result->write_timestamp;
		curmatch->hostname = cur_result->hostname;
		curmatch->diskname = cur_result->diskname;
		curmatch->storage = cur_result->storage;
		curmatch->level = cur_result->level;
		curmatch->label = cur_result->label? cur_result->label : NULL;
		curmatch->filenum = cur_result->filenum;
		curmatch->status = cur_result->status;
		curmatch->dump_status =  cur_result->dump_status;
		curmatch->message = cur_result->message;
		curmatch->partnum = cur_result->partnum;
		curmatch->totalparts = cur_result->totalparts;

		curmatch->next = matches;
		matches = curmatch;
		break;
	    }
	}

	amfree(zeropad_ts);
    }

    return(matches);
}

find_result_t *
dump_exist(
    find_result_t *output_find,
    char *hostname,
    char *diskname,
    char *datestamp,
    int level)
{
    find_result_t *output_find_result;

    for(output_find_result=output_find;
	output_find_result;
	output_find_result=output_find_result->next) {
	if( g_str_equal(output_find_result->hostname, hostname) &&
	    g_str_equal(output_find_result->diskname, diskname) &&
	    g_str_equal(output_find_result->timestamp, datestamp) &&
	    output_find_result->level == level) {

	    return output_find_result;
	}
    }
    return(NULL);
}

GHashTable *
make_dump_hash(
    find_result_t *output_find)
{
    find_result_t *output_find_result;
    GHashTable *dump_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_hash_table_destroy);
    GHashTable *host_hash;
    GHashTable *disk_hash;
    GHashTable *time_hash;

    for (output_find_result=output_find;
         output_find_result;
         output_find_result=output_find_result->next) {

	host_hash = g_hash_table_lookup(dump_hash, output_find_result->hostname);
	if (!host_hash) {
	    host_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_hash_table_destroy);
	    g_hash_table_insert(dump_hash, output_find_result->hostname, host_hash);
	}

	disk_hash = g_hash_table_lookup(host_hash, output_find_result->diskname);
	if (!disk_hash) {
	    disk_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_hash_table_destroy);
	    g_hash_table_insert(host_hash, output_find_result->diskname, disk_hash);
	}

	time_hash = g_hash_table_lookup(disk_hash, output_find_result->timestamp);
	if (!time_hash) {
	    time_hash = g_hash_table_new(g_int_hash, g_int_equal);
	    g_hash_table_insert(disk_hash, output_find_result->timestamp, time_hash);
	}

	g_hash_table_insert(time_hash, &output_find_result->level, output_find_result);
    }

    return dump_hash;
}


find_result_t *
dump_hash_exist(
    GHashTable *dump_hash,
    char *hostname,
    char *diskname,
    char *timestamp,
    int level)
{
    GHashTable *host_hash;
    GHashTable *disk_hash;
    GHashTable *time_hash;
    find_result_t *output_find_result;

    host_hash = g_hash_table_lookup(dump_hash, hostname);
    if (!host_hash) return NULL;

    disk_hash = g_hash_table_lookup(host_hash, diskname);
    if (!disk_hash) return NULL;

    time_hash = g_hash_table_lookup(disk_hash, timestamp);
    if (!time_hash) return NULL;

    output_find_result = g_hash_table_lookup(time_hash, &level);

    return output_find_result;
}

void free_dump_hash(GHashTable *dump_hash G_GNUC_UNUSED)
{
    //It fail on some machine
    //g_hash_table_destroy(dump_hash);
}
