Imported Upstream version 3.2.0
[debian/amanda] / server-src / find.c
index 2f8c950f062b68bdbd84069911f6ad0cb3087463..dadbd5738ba0f7b6d844f25d5816ad13177f7ce6 100644 (file)
  *                        University of Maryland at College Park
  */
 /*
- * $Id: find.c,v 1.23 2006/01/15 21:01:00 martinea Exp $
+ * $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"
 
-void find P((int argc, char **argv));
-int find_match P((char *host, char *disk));
-int search_logfile P((find_result_t **output_find, char *label, int datestamp, int datestamp_aux, char *logfile));
-void search_holding_disk P((find_result_t **output_find));
-void strip_failed_chunks P((find_result_t *output_find));
-char *find_nicedate P((int datestamp));
-static int find_compare P((const void *, const void *));
-static int parse_taper_datestamp_log P((char *, int *, char **));
+int find_match(char *host, char *disk);
+char *find_nicedate(char *datestamp);
+static int find_compare(const void *, const void *);
+static int parse_taper_datestamp_log(char *logline, char **datestamp, char **level);
+static gboolean logfile_has_tape(char * label, char * datestamp,
+                                 char * logfile);
 
 static char *find_sort_order = NULL;
-int dynamic_disklist = 0;
-disklist_t* find_diskqp = NULL;
 
-find_result_t *find_dump(dyna_disklist, diskqp)
-int dyna_disklist;
-disklist_t* diskqp;
-{
+find_result_t * find_dump(disklist_t* diskqp) {
     char *conf_logdir, *logfile = NULL;
-    int tape, maxtape, seq, logs;
-    tape_t *tp;
+    int tape, tape1, maxtape, logs;
+    unsigned seq;
+    tape_t *tp, *tp1;
     find_result_t *output_find = NULL;
+    gboolean *tape_seen = NULL;
 
-    dynamic_disklist = dyna_disklist;
-    find_diskqp = diskqp;
-    conf_logdir = getconf_str(CNF_LOGDIR);
-    if (*conf_logdir == '/') {
-       conf_logdir = stralloc(conf_logdir);
-    } else {
-       conf_logdir = stralloc2(config_dir, conf_logdir);
-    }
+    conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
     maxtape = lookup_nb_tape();
+    tape_seen = g_new0(gboolean, maxtape+1);
 
     for(tape = 1; tape <= maxtape; tape++) {
-       char ds_str[NUM_STR_SIZE];
 
+       if (tape_seen[tape] == 1)
+           continue;
        tp = lookup_tapepos(tape);
        if(tp == NULL) continue;
-       snprintf(ds_str, sizeof(ds_str), "%d", tp->datestamp);
+
+       /* find all tape with the same datestamp */
+       for (tape1 = tape; tape1 <= maxtape; tape1++) {
+           tp1 = lookup_tapepos(tape1);
+           if (tp1 == NULL) continue;
+           if (strcmp(tp->datestamp, tp1->datestamp) != 0)
+               continue;
+
+           tape_seen[tape1] = 1;
+       }
 
        /* search log files */
 
@@ -84,66 +86,68 @@ disklist_t* diskqp;
        for(seq = 0; 1; seq++) {
            char seq_str[NUM_STR_SIZE];
 
-           snprintf(seq_str, sizeof(seq_str), "%d", seq);
+           g_snprintf(seq_str, SIZEOF(seq_str), "%u", seq);
            logfile = newvstralloc(logfile,
-                       conf_logdir, "/log.", ds_str, ".", seq_str, NULL);
+                       conf_logdir, "/log.", tp->datestamp, ".", seq_str, NULL);
            if(access(logfile, R_OK) != 0) break;
-           logs += search_logfile(&output_find, tp->label, tp->datestamp, seq, logfile);
+           if (search_logfile(&output_find, NULL, tp->datestamp,
+                               logfile, diskqp)) {
+                logs ++;
+            }
        }
 
        /* search old-style amflush log, if any */
 
-       logfile = newvstralloc(logfile,
-                              conf_logdir, "/log.", ds_str, ".amflush", NULL);
+       logfile = newvstralloc(logfile, conf_logdir, "/log.",
+                               tp->datestamp, ".amflush", NULL);
        if(access(logfile,R_OK) == 0) {
-           logs += search_logfile(&output_find, tp->label, tp->datestamp, 1000, logfile);
-       }
-
+           if (search_logfile(&output_find, NULL, tp->datestamp,
+                               logfile, diskqp)) {
+                logs ++;
+            }
+        }
+        
        /* search old-style main log, if any */
 
-       logfile = newvstralloc(logfile, conf_logdir, "/log.", ds_str, NULL);
+       logfile = newvstralloc(logfile, conf_logdir, "/log.", tp->datestamp,
+                               NULL);
        if(access(logfile,R_OK) == 0) {
-           logs += search_logfile(&output_find, tp->label, tp->datestamp, -1, logfile);
+           if (search_logfile(&output_find, NULL, tp->datestamp,
+                               logfile, diskqp)) {
+                logs ++;
+            }
        }
-       if(logs == 0 && tp->datestamp != 0)
-           printf("Warning: no log files found for tape %s written %s\n",
-                  tp->label, find_nicedate(tp->datestamp));
     }
+    g_free(tape_seen);
     amfree(logfile);
     amfree(conf_logdir);
 
-    search_holding_disk(&output_find);
+    search_holding_disk(&output_find, diskqp);
 
-    strip_failed_chunks(output_find);
-    
     return(output_find);
 }
 
-char **find_log()
+char **
+find_log(void)
 {
     char *conf_logdir, *logfile = NULL;
-    int tape, maxtape, seq, logs;
+    char *pathlogfile = NULL;
+    int tape, maxtape, logs;
+    unsigned seq;
     tape_t *tp;
     char **output_find_log = NULL;
     char **current_log;
 
-    conf_logdir = getconf_str(CNF_LOGDIR);
-    if (*conf_logdir == '/') {
-       conf_logdir = stralloc(conf_logdir);
-    } else {
-       conf_logdir = stralloc2(config_dir, conf_logdir);
-    }
+    conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
     maxtape = lookup_nb_tape();
 
-    output_find_log = alloc((maxtape*5+10) * sizeof(char *));
+    output_find_log = alloc((maxtape*5+10) * SIZEOF(char *));
     current_log = output_find_log;
 
     for(tape = 1; tape <= maxtape; tape++) {
-       char ds_str[NUM_STR_SIZE];
 
        tp = lookup_tapepos(tape);
        if(tp == NULL) continue;
-       snprintf(ds_str, sizeof(ds_str), "%d", tp->datestamp);
 
        /* search log files */
 
@@ -154,13 +158,15 @@ char **find_log()
        for(seq = 0; 1; seq++) {
            char seq_str[NUM_STR_SIZE];
 
-           snprintf(seq_str, sizeof(seq_str), "%d", seq);
-           logfile = newvstralloc(logfile,
-                       conf_logdir, "/log.", ds_str, ".", seq_str, NULL);
-           if(access(logfile, R_OK) != 0) break;
-           if( search_logfile(NULL, tp->label, tp->datestamp, seq, logfile)) {
-               *current_log = vstralloc("log.", ds_str, ".", seq_str, NULL);
-               current_log++;
+           g_snprintf(seq_str, SIZEOF(seq_str), "%u", seq);
+           logfile = newvstralloc(logfile, "log.", tp->datestamp, ".", seq_str, NULL);
+           pathlogfile = newvstralloc(pathlogfile, 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 || strcmp(*(current_log-1), logfile)) {
+                   *current_log = stralloc(logfile);
+                   current_log++;
+               }
                logs++;
                break;
            }
@@ -168,251 +174,164 @@ char **find_log()
 
        /* search old-style amflush log, if any */
 
-       logfile = newvstralloc(logfile,
-                              conf_logdir, "/log.", ds_str, ".amflush", NULL);
-       if(access(logfile,R_OK) == 0) {
-           if( search_logfile(NULL, tp->label, tp->datestamp, 1000, logfile)) {
-               *current_log = vstralloc("log.", ds_str, ".amflush", NULL);
-               current_log++;
+       logfile = newvstralloc(logfile, "log.", tp->datestamp, ".amflush", NULL);
+       pathlogfile = newvstralloc(pathlogfile, 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 || strcmp(*(current_log-1), logfile)) {
+                   *current_log = stralloc(logfile);
+                   current_log++;
+               }
                logs++;
            }
        }
 
        /* search old-style main log, if any */
 
-       logfile = newvstralloc(logfile, conf_logdir, "/log.", ds_str, NULL);
-       if(access(logfile,R_OK) == 0) {
-           if(search_logfile(NULL, tp->label, tp->datestamp, -1, logfile)) {
-               *current_log = vstralloc("log.", ds_str, NULL);
-               current_log++;
+       logfile = newvstralloc(logfile, "log.", tp->datestamp, NULL);
+       pathlogfile = newvstralloc(pathlogfile, 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 || strcmp(*(current_log-1), logfile)) {
+                   *current_log = stralloc(logfile);
+                   current_log++;
+               }
                logs++;
            }
        }
-       if(logs == 0 && tp->datestamp != 0)
-           printf("Warning: no log files found for tape %s written %s\n",
+
+       if(logs == 0 && strcmp(tp->datestamp,"0") != 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);
 }
 
-/*
- * Remove CHUNK entries from dumps that ultimately failed from our report.
- */
-void strip_failed_chunks(output_find)
-find_result_t *output_find;
+void
+search_holding_disk(
+    find_result_t **output_find,
+    disklist_t * dynamic_disklist)
 {
-    find_result_t *cur, *prev = NULL, *failed = NULL, *failures = NULL;
-
-    /* Generate a list of failures */
-    for(cur=output_find; cur; cur=cur->next) {
-       if(!cur->hostname || !cur->diskname) continue;
-
-       if(strcmp(cur->status, "OK")){
-           failed = alloc(sizeof(find_result_t));
-           memcpy(failed, cur, sizeof(find_result_t));
-           failed->next = failures;
-           failed->diskname = stralloc(cur->diskname);
-           failed->hostname = stralloc(cur->hostname);
-           failures = failed;
-       }
-    }
-
-    /* Now if a CHUNK matches the parameters of a failed dump, remove it */
-    for(failed=failures; failed; failed=failed->next) {
-       prev = NULL;
-       for(cur=output_find; cur; cur=cur->next) {
-           if(!cur->hostname || !cur->diskname ||
-                 !strcmp(cur->partnum, "--") || strcmp(cur->status, "OK")){
-               prev = cur;
-               continue;
-           }
+    GSList *holding_file_list;
+    GSList *e;
+    char   *holding_file;
+    disk_t *dp;
+    char   *orig_name;
 
-           if(!strcmp(cur->hostname, failed->hostname) &&
-                !strcmp(cur->diskname, failed->diskname) &&
-                cur->datestamp == failed->datestamp &&
-                cur->datestamp_aux == failed->datestamp_aux &&
-                cur->level == failed->level){
-               find_result_t *next = cur->next;
-               amfree(cur->diskname);
-               amfree(cur->hostname);
-               next = cur->next;
-               amfree(cur);
-               if(prev){
-                   prev->next = next;
-                   cur = prev;
-               }
-               else output_find = next;
-           }
-            else prev = cur;
-       }
-    }
+    holding_file_list = holding_get_files(NULL, 1);
 
-    for(failed=failures; failed;) {
-       find_result_t *fai = failed->next;
-       amfree(failed->diskname);
-       amfree(failed->hostname);
-       fai = failed->next;
-       amfree(failed);
-       failed=fai;
-    }
-}
+    for(e = holding_file_list; e != NULL; e = e->next) {
+       dumpfile_t file;
 
-void search_holding_disk(output_find)
-find_result_t **output_find;
-{
-    holdingdisk_t *hdisk;
-    sl_t  *holding_list;
-    sle_t *dir;
-    char *sdirname = NULL;
-    char *destname = NULL;
-    char *hostname = NULL;
-    char *diskname = NULL;
-    DIR *workdir;
-    struct dirent *entry;
-    int level;
-    disk_t *dp;
+       holding_file = (char *)e->data;
 
-    holding_list = pick_all_datestamp(1);
+       if (!holding_file_get_dumpfile(holding_file, &file))
+           continue;
 
-    for(hdisk = getconf_holdingdisks(); hdisk != NULL; hdisk = hdisk->next) {
-       for(dir = holding_list->first; dir != NULL; dir = dir->next) {
-           sdirname = newvstralloc(sdirname,
-                                   hdisk->diskdir, "/", dir->name,
-                                   NULL);
-           if((workdir = opendir(sdirname)) == NULL) {
-               continue;
-           }
+       if (file.dumplevel < 0 || file.dumplevel >= DUMP_LEVELS) {
+           dumpfile_free_data(&file);
+           continue;
+       }
 
-           while((entry = readdir(workdir)) != NULL) {
-               if(is_dot_or_dotdot(entry->d_name)) {
-                   continue;
-               }
-               destname = newvstralloc(destname,
-                                       sdirname, "/", entry->d_name,
-                                       NULL);
-               if(is_emptyfile(destname)) {
-                   continue;
-               }
-               amfree(hostname);
-               amfree(diskname);
-               if(get_amanda_names(destname, &hostname, &diskname, &level) != F_DUMPFILE) {
-                   continue;
-               }
-               if(level < 0 || level > 9)
-                   continue;
+       dp = NULL;
+       orig_name = g_strdup(file.name);
+       for(;;) {
+           char *s;
+           if((dp = lookup_disk(file.name, file.disk)))
+               break;
+           if((s = strrchr(file.name,'.')) == NULL)
+               break;
+           *s = '\0';
+       }
+       strcpy(file.name, orig_name); /* restore munged string */
+       g_free(orig_name);
 
-               dp = NULL;
-               for(;;) {
-                   char *s;
-                   if((dp = lookup_disk(hostname, diskname)))
-                       break;
-                   if((s = strrchr(hostname,'.')) == NULL)
-                       break;
-                   *s = '\0';
-               }
-               if ( dp == NULL ) {
-                   continue;
-               }
+       if ( dp == NULL ) {
+           if (dynamic_disklist == NULL) {
+               dumpfile_free_data(&file);
+               continue;
+           }
+           dp = add_disk(dynamic_disklist, file.name, file.disk);
+           enqueue_disk(dynamic_disklist, dp);
+       }
 
-               if(find_match(hostname,diskname)) {
-                   find_result_t *new_output_find =
-                       alloc(sizeof(find_result_t));
-                   new_output_find->next=*output_find;
-                   if(strlen(dir->name) == 8) {
-                       new_output_find->datestamp=atoi(dir->name);
-                       new_output_find->timestamp=stralloc2(dir->name, "000000");
-                   }
-                   else if(strlen(dir->name) == 14) {
-                       char *name = stralloc(dir->name);
-                       name[8] = '\0';
-                       new_output_find->datestamp=atoi(name);
-                       new_output_find->timestamp=stralloc(dir->name);
-                       amfree(name);
-                   }
-                   else {
-                       error("Bad date\n");
-                   }
-                   new_output_find->datestamp_aux=1001;
-                   new_output_find->hostname=hostname;
-                   hostname = NULL;
-                   new_output_find->diskname=diskname;
-                   diskname = NULL;
-                   new_output_find->level=level;
-                   new_output_find->label=stralloc(destname);
-                   new_output_find->partnum=stralloc("--");
-                   new_output_find->filenum=0;
-                   new_output_find->status=stralloc("OK");
-                   *output_find=new_output_find;
-               }
+       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 = stralloc(file.datestamp);
+           new_output_find->write_timestamp = stralloc("00000000000000");
+           new_output_find->hostname = stralloc(file.name);
+           new_output_find->diskname = stralloc(file.disk);
+           new_output_find->level=file.dumplevel;
+           new_output_find->label=stralloc(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=stralloc("PARTIAL");
+               new_output_find->dump_status=stralloc("PARTIAL");
+           } else {
+               new_output_find->status=stralloc("OK");
+               new_output_find->dump_status=stralloc("OK");
            }
-           closedir(workdir);
-       }       
+           new_output_find->message=stralloc("");
+           new_output_find->kb = holding_file_size(holding_file, 1);
+           new_output_find->orig_kb = file.orig_size;
+
+           *output_find=new_output_find;
+       }
+       dumpfile_free_data(&file);
     }
-    free_sl(holding_list);
-    holding_list = NULL;
-    amfree(destname);
-    amfree(sdirname);
-    amfree(hostname);
-    amfree(diskname);
+
+    g_slist_free_full(holding_file_list);
 }
 
-static int find_compare(i1, j1)
-const void *i1;
-const void *j1;
+static int
+find_compare(
+    const void *i1,
+    const void *j1)
 {
     int compare=0;
-    find_result_t **i = (find_result_t **)i1;
-    find_result_t **j = (find_result_t **)j1;
+    find_result_t *i, *j;
 
-    int nb_compare=strlen(find_sort_order);
-    int k;
+    size_t nb_compare=strlen(find_sort_order);
+    size_t k;
 
     for(k=0;k<nb_compare;k++) {
-       switch (find_sort_order[k]) {
-       case 'h' : compare=strcmp((*i)->hostname,(*j)->hostname);
-                  break;
-       case 'H' : compare=strcmp((*j)->hostname,(*i)->hostname);
-                  break;
-       case 'k' : compare=strcmp((*i)->diskname,(*j)->diskname);
-                  break;
-       case 'K' : compare=strcmp((*j)->diskname,(*i)->diskname);
-                  break;
-       case 'd' : compare=(*i)->datestamp - (*j)->datestamp;
-                  if (compare == 0)
-                       compare = (*i)->datestamp_aux - (*j)->datestamp_aux;
-                  break;
-       case 'D' : compare=(*j)->datestamp - (*i)->datestamp;
-                  if (compare == 0)
-                       compare = (*j)->datestamp_aux - (*i)->datestamp_aux;
-                  break;
-       case 'l' : compare=(*j)->level - (*i)->level;
+        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 'f' : compare=(*i)->filenum - (*j)->filenum;
+       case 'k' : compare=strcmp(i->diskname,j->diskname);
                   break;
-       case 'F' : compare=(*j)->filenum - (*i)->filenum;
+       case 'd' : compare=strcmp(i->timestamp,j->timestamp);
                   break;
-       case 'L' : compare=(*i)->level - (*j)->level;
+       case 'l' : compare=j->level - i->level;
                   break;
-       case 'b' : compare=strcmp((*i)->label,(*j)->label);
+       case 'f' : compare=(i->filenum == j->filenum) ? 0 :
+                          ((i->filenum < j->filenum) ? -1 : 1);
                   break;
-       case 'B' : compare=strcmp((*j)->label,(*i)->label);
+       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' :
-                  if(strcmp((*i)->partnum, "--") != 0 &&
-                     strcmp((*j)->partnum, "--") != 0){
-                     compare = atoi((*i)->partnum) - atoi((*j)->partnum);
-                  }
-                  else compare=strcmp((*i)->partnum,(*j)->partnum);
-                  break;
-       case 'P' :
-                  if(strcmp((*i)->partnum, "--") != 0 &&
-                     strcmp((*j)->partnum, "--") != 0){
-                     compare = atoi((*j)->partnum) - atoi((*i)->partnum);
-                  }
-                  else compare=strcmp((*j)->partnum,(*i)->partnum);
+                  compare=i->partnum - j->partnum;
                   break;
        }
        if(compare != 0)
@@ -421,14 +340,15 @@ const void *j1;
     return 0;
 }
 
-void sort_find_result(sort_order, output_find)
-char *sort_order;
-find_result_t **output_find;
+void
+sort_find_result(
+    char *sort_order,
+    find_result_t **output_find)
 {
     find_result_t *output_find_result;
     find_result_t **array_find_result = NULL;
-    int nb_result=0;
-    int no_result;
+    size_t nb_result=0;
+    size_t no_result;
 
     find_sort_order = sort_order;
     /* qsort core dump if nothing to sort */
@@ -443,7 +363,7 @@ find_result_t **output_find;
     }
 
     /* put the list in an array */
-    array_find_result=alloc(nb_result * sizeof(find_result_t *));
+    array_find_result=alloc(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++) {
@@ -451,7 +371,7 @@ find_result_t **output_find;
     }
 
     /* sort the array */
-    qsort(array_find_result,nb_result,sizeof(find_result_t *),
+    qsort(array_find_result,nb_result,SIZEOF(find_result_t *),
          find_compare);
 
     /* put the sorted result in the list */
@@ -464,8 +384,9 @@ find_result_t **output_find;
     amfree(array_find_result);
 }
 
-void print_find_result(output_find)
-find_result_t *output_find;
+void
+print_find_result(
+    find_result_t *output_find)
 {
     find_result_t *output_find_result;
     int max_len_datestamp = 4;
@@ -476,29 +397,46 @@ find_result_t *output_find;
     int max_len_filenum   = 4;
     int max_len_part      = 4;
     int max_len_status    = 6;
-    int len;
+    size_t len;
 
     for(output_find_result=output_find;
        output_find_result;
        output_find_result=output_find_result->next) {
+       char *qdiskname;
+       char *s;
 
-       len=strlen(find_nicedate(output_find_result->datestamp));
-       if(len>max_len_datestamp) max_len_datestamp=len;
+       len=strlen(find_nicedate(output_find_result->timestamp));
+       if((int)len > max_len_datestamp)
+           max_len_datestamp=(int)len;
 
        len=strlen(output_find_result->hostname);
-       if(len>max_len_hostname) max_len_hostname=len;
-
-       len=strlen(output_find_result->diskname);
-       if(len>max_len_diskname) max_len_diskname=len;
-
-       len=strlen(output_find_result->label);
-       if(len>max_len_label) max_len_label=len;
-
-       len=strlen(output_find_result->status);
-       if(len>max_len_status) max_len_status=len;
-
-       len=strlen(output_find_result->partnum);
-       if(len>max_len_part) max_len_part=len;
+       if((int)len > max_len_hostname)
+           max_len_hostname = (int)len;
+
+       qdiskname=quote_string(output_find_result->diskname);
+       len=strlen(qdiskname);
+       amfree(qdiskname);
+       if((int)len > max_len_diskname)
+           max_len_diskname = (int)len;
+
+        if (output_find_result->label != NULL) {
+            char *qlabel = quote_string(output_find_result->label);
+            len=strlen(qlabel);
+            amfree(qlabel);
+            if((int)len > max_len_label)
+                max_len_label = (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);
     }
 
     /*
@@ -509,10 +447,10 @@ find_result_t *output_find;
     max_len_status = 1;
 
     if(output_find==NULL) {
-       printf("\nNo dump to list\n");
+       g_printf(_("\nNo dump to list\n"));
     }
     else {
-       printf("\ndate%*s host%*s disk%*s lv%*s tape or file%*s file%*s part%*s status\n",
+       g_printf(_("\ndate%*s host%*s disk%*s lv%*s tape or file%*s file%*s part%*s status\n"),
               max_len_datestamp-4,"",
               max_len_hostname-4 ,"",
               max_len_diskname-4 ,"",
@@ -523,24 +461,54 @@ find_result_t *output_find;
         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 = stralloc("");
+           else
+               formatted_label = quote_string(output_find_result->label);
+
+           if (strcmp(output_find_result->status, "OK") != 0 ||
+               strcmp(output_find_result->dump_status, "OK") != 0) {
+               status = vstralloc(output_find_result->status, " ",
+                                  output_find_result->dump_status, NULL);
+           } else {
+               status = stralloc(output_find_result->status);
+           }
 
-           printf("%-*s %-*s %-*s %*d %-*s %*d %*s %-*s\n",
-                   max_len_datestamp, 
-                       find_nicedate(output_find_result->datestamp),
-                   max_len_hostname,  output_find_result->hostname,
-                   max_len_diskname,  output_find_result->diskname,
-                   max_len_level,     output_find_result->level,
-                   max_len_label,     output_find_result->label,
-                   max_len_filenum,   output_find_result->filenum,
-                   max_len_part,      output_find_result->partnum,
-                   max_len_status,    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 %*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_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(output_find)
-find_result_t **output_find;
+void
+free_find_result(
+    find_result_t **output_find)
 {
     find_result_t *output_find_result, *prev;
 
@@ -548,45 +516,71 @@ find_result_t **output_find;
     for(output_find_result=*output_find;
            output_find_result;
            output_find_result=output_find_result->next) {
-       if(prev != NULL) amfree(prev);
+       amfree(prev);
+       amfree(output_find_result->timestamp);
+       amfree(output_find_result->write_timestamp);
        amfree(output_find_result->hostname);
        amfree(output_find_result->diskname);
        amfree(output_find_result->label);
-       amfree(output_find_result->partnum);
        amfree(output_find_result->status);
-       amfree(output_find_result->timestamp);
+       amfree(output_find_result->dump_status);
+       amfree(output_find_result->message);
        prev = output_find_result;
     }
-    if(prev != NULL) amfree(prev);
-    output_find = NULL;
+    amfree(prev);
+    *output_find = NULL;
 }
 
-int find_match(host, disk)
-char *host, *disk;
+int
+find_match(
+    char *host,
+    char *disk)
 {
     disk_t *dp = lookup_disk(host,disk);
     return (dp && dp->todo);
 }
 
-char *find_nicedate(datestamp)
-int datestamp;
+char *
+find_nicedate(
+    char *datestamp)
 {
     static char nice[20];
     int year, month, day;
-
-    year  = datestamp / 10000;
-    month = (datestamp / 100) % 100;
-    day   = datestamp % 100;
-
-    snprintf(nice, sizeof(nice), "%4d-%02d-%02d", 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 parse_taper_datestamp_log(logline, datestamp, label)
-char *logline;
-int *datestamp;
-char **label;
+static int
+parse_taper_datestamp_log(
+    char *logline,
+    char **datestamp,
+    char **label)
 {
     char *s;
     int ch;
@@ -598,31 +592,25 @@ char **label;
     if(ch == '\0') {
        return 0;
     }
-#define sc "datestamp"
-    if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
+    if(strncmp_const_skip(s - 1, "datestamp", s, ch) != 0) {
        return 0;
     }
-    s += sizeof(sc)-1;
-    ch = s[-1];
-#undef sc
 
     skip_whitespace(s, ch);
-    if(ch == '\0' || sscanf(s - 1, "%d", datestamp) != 1) {
+    if(ch == '\0') {
        return 0;
     }
-    skip_integer(s, ch);
+    *datestamp = s - 1;
+    skip_non_whitespace(s, ch);
+    s[-1] = '\0';
 
     skip_whitespace(s, ch);
     if(ch == '\0') {
        return 0;
     }
-#define sc "label"
-    if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
+    if(strncmp_const_skip(s - 1, "label", s, ch) != 0) {
        return 0;
     }
-    s += sizeof(sc)-1;
-    ch = s[-1];
-#undef sc
 
     skip_whitespace(s, ch);
     if(ch == '\0') {
@@ -635,110 +623,205 @@ char **label;
     return 1;
 }
 
-/*
- * Check whether we've already seen a CHUNK log entry for the given dump.
- * This is so we can interpret the final SUCCESS entry for a split dump as 
- * 'list its parts' instead.  Return 1 if we have, 0 if not.
- */
-int seen_chunk_of(output_find, date, host, disk, level)
-find_result_t *output_find;
-int date, level;
-char *host, *disk;
+/* 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;
+    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) == 0) {
+               g_printf(_("strange log line \"start taper %s\" curstr='%s'\n"),
+                         logfile, curstr);
+           } else if(strcmp(ck_datestamp, datestamp) == 0
+                     && strcmp(ck_label, label) == 0) {
+                afclose(logf);
+                return TRUE;
+           }
+       }
+    }
+
+    afclose(logf);
+    return FALSE;
+}
+
+static gboolean
+volume_matches(
+    const char *label1,
+    const char *label2,
+    const char *datestamp)
 {
-    find_result_t *cur;
+    tape_t *tp;
 
-    if(!host || !disk) return(0);
+    if (!label2)
+       return TRUE;
 
-    for(cur=output_find; cur; cur=cur->next) {
-       if(atoi(cur->partnum) < 1 || !cur->hostname || !cur->diskname) continue;
+    if (label1)
+       return (strcmp(label1, label2) == 0);
 
-       if(cur->datestamp == date && strcmp(cur->hostname, host) == 0 &&
-               strcmp(cur->diskname, disk) == 0 && cur->level == level){
-           return(1);
-       }
-    }
-    return(0);
+    /* check in tapelist */
+    if (!(tp = lookup_tapelabel(label2)))
+       return FALSE;
+
+    if (strcmp(tp->datestamp, datestamp) != 0)
+       return FALSE;
+
+    return TRUE;
 }
 
-/* if output_find is NULL                                      */
-/*     return 1 if this is the logfile for this label          */
-/*     return 0 if this is not the logfile for this label      */
-/* else                                                                */
-/*     add to output_find all the dump for this label          */
-/*     return the number of dump added.                        */
-int search_logfile(output_find, label, datestamp, datestamp_aux, logfile)
-find_result_t **output_find;
-char *label, *logfile;
-int datestamp, datestamp_aux;
+/* 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)
 {
     FILE *logf;
     char *host, *host_undo;
-    char *disk, *disk_undo;
-    char *partnum=NULL, *partnum_undo;
-    int   datestampI;
+    char *disk, *qdisk, *disk_undo;
+    char *date, *date_undo;
+    int  partnum;
+    int  totalparts;
+    int  maxparts = -1;
+    char *number;
+    int fileno;
+    char *current_label = stralloc("");
     char *rest;
-    char *ck_label;
-    int level, filenum, ck_datestamp, tapematch;
-    int passlabel, ck_datestamp2;
+    char *ck_label=NULL;
+    int level = 0;
+    off_t filenum;
+    char *ck_datestamp, *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;
+    regex_t regex;
+    int reg_result;
+    regmatch_t pmatch[4];
+    double sec;
+    off_t kb;
+    off_t orig_kb;
+    int   taper_part = 0;
+
+    g_return_val_if_fail(output_find != NULL, 0);
+    g_return_val_if_fail(logfile != NULL, 0);
+
+    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) {
+       error(_("could not open logfile %s: %s"), logfile, strerror(errno));
+       /*NOTREACHED*/
+    }
 
-    if((logf = fopen(logfile, "r")) == NULL)
-       error("could not open logfile %s: %s", logfile, strerror(errno));
-
-    /* check that this log file corresponds to the right tape */
-    tapematch = 0;
-    while(!tapematch && get_logline(logf)) {
-       if(curlog == L_START && curprog == P_TAPER) {
-           if(parse_taper_datestamp_log(curstr,
-                                        &ck_datestamp, &ck_label) == 0) {
-               printf("strange log line \"start taper %s\"\n", curstr);
-           } else if(ck_datestamp == datestamp
-                     && strcmp(ck_label, label) == 0) {
-               tapematch = 1;
+    filenum = (off_t)0;
+    while(get_logline(logf)) {
+       if (curlog == L_START && curprog == P_TAPER) {
+           if(parse_taper_datestamp_log(curstr, &ck_datestamp,
+                                         &ck_label) == 0) {
+               g_printf(_("strange log line in %s \"start taper %s\"\n"),
+                         logfile, curstr);
+                continue;
+           }
+            if (datestamp != NULL) {
+                if (strcmp(datestamp, ck_datestamp) != 0) {
+                    g_printf(_("Log file %s stamped %s, expecting %s!\n"),
+                             logfile, ck_datestamp, datestamp);
+                    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 = g_strdup(ck_label);
+            if (datestamp == NULL) {
+                datestamp = g_strdup(ck_datestamp);
+            }
+           filenum = (off_t)0;
        }
-    }
-
-    if(output_find == NULL) {
-       afclose(logf);
-        if(tapematch == 0)
-           return 0;
-       else
-           return 1;
-    }
-
-    if(tapematch == 0) {
-       afclose(logf);
-       return 0;
-    }
-
-    filenum = 0;
-    passlabel = 1;
-    while(get_logline(logf) && passlabel) {
-       if((curlog == L_SUCCESS || curlog == L_CHUNK) &&
-                               curprog == P_TAPER && passlabel){
+       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++;
        }
-       if(curlog == L_START && curprog == P_TAPER) {
-           if(parse_taper_datestamp_log(curstr,
-                                        &ck_datestamp2, &ck_label) == 0) {
-               printf("strange log line \"start taper %s\"\n", curstr);
-           } else if (strcmp(ck_label, label)) {
-               passlabel = !passlabel;
-           }
-       }
-       partnum = "--";
-       if(curlog == L_SUCCESS || curlog == L_FAIL || curlog == L_CHUNK) {
+       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') {
-               printf("strange log line \"%s\"\n", curstr);
+               g_printf(_("strange log line in %s \"%s\"\n"),
+                   logfile, curstr);
                continue;
            }
+
+           if (curlog == L_PART || curlog == L_PARTPARTIAL) {
+               char * part_label = s - 1;
+               taper_part++;
+               skip_non_whitespace(s, ch);
+               s[-1] = '\0';
+
+               if (!g_hash_table_lookup(valid_label, part_label))
+                   continue;
+               amfree(current_label);
+               current_label = stralloc(part_label);
+
+               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;
+               }
+           } else {
+               taper_part = 0;
+           }
+
            host = s - 1;
            skip_non_whitespace(s, ch);
            host_undo = s - 1;
@@ -746,36 +829,51 @@ int datestamp, datestamp_aux;
 
            skip_whitespace(s, ch);
            if(ch == '\0') {
-               printf("strange log line \"%s\"\n", curstr);
+               g_printf(_("strange log line in %s \"%s\"\n"),
+                   logfile, curstr);
                continue;
            }
-           disk = s - 1;
-           skip_non_whitespace(s, ch);
+           qdisk = s - 1;
+           skip_quoted_string(s, ch);
            disk_undo = s - 1;
            *disk_undo = '\0';
+           disk = unquote_string(qdisk);
 
            skip_whitespace(s, ch);
-           if(ch == '\0' || sscanf(s - 1, "%d", &datestampI) != 1) {
-               printf("strange log line \"%s\"\n", curstr);
+           if(ch == '\0') {
+               g_printf(_("strange log line in %s \"%s\"\n"),
+                         logfile, curstr);
                continue;
            }
-           skip_integer(s, ch);
-
-           if(datestampI < 100)  { /* old log didn't have datestamp */
-               level = datestampI;
-               datestampI = datestamp;
-           }
-           else {
-               if(curlog == L_CHUNK){
+           date = s - 1;
+           skip_non_whitespace(s, ch);
+           date_undo = s - 1;
+           *date_undo = '\0';
+
+           if(strlen(date) < 3) { /* old log didn't have datestamp */
+               level = atoi(date);
+               date = stralloc(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)) {
                    skip_whitespace(s, ch);
-                   partnum = s - 1;
+                   number = s - 1;
                    skip_non_whitespace(s, ch);
-                   partnum_undo = s - 1;
-                   *partnum_undo = '\0';
+                   s[-1] = '\0';
+                   sscanf(number, "%d/%d", &partnum, &totalparts);
+                   if (partnum > maxparts)
+                       maxparts = partnum;
+                   if (totalparts > maxparts)
+                       maxparts = totalparts;
                }
                skip_whitespace(s, ch);
                if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
-                   printf("strange log line \"%s\"\n", curstr);
+                   g_printf(_("strange log line in %s \"%s\"\n"),
+                   logfile, curstr);
                    continue;
                }
                skip_integer(s, ch);
@@ -783,7 +881,8 @@ int datestamp, datestamp_aux;
 
            skip_whitespace(s, ch);
            if(ch == '\0') {
-               printf("strange log line \"%s\"\n", curstr);
+               g_printf(_("strange log line in %s \"%s\"\n"),
+                   logfile, curstr);
                continue;
            }
            rest = s - 1;
@@ -791,63 +890,240 @@ int datestamp, datestamp_aux;
                *s = '\0';
            }
 
+           /* extract sec, kb, kps, orig-kb from 'rest', if present.  This isn't the stone age
+            * anymore, so we'll just do it the easy way (a regex) */
+           bzero(&regex, sizeof(regex));
+           reg_result = regcomp(&regex,
+                   "\\[sec ([0-9.]+) kb ([0-9]+) kps [0-9.]+ orig-kb ([0-9]+)\\]", REG_EXTENDED);
+           if (reg_result != 0) {
+               error("Error compiling regular expression for parsing log lines");
+               /* NOTREACHED */
+           }
+
+           /* an error here just means the line wasn't found -- not fatal. */
+           reg_result = regexec(&regex, rest, sizeof(pmatch)/sizeof(*pmatch), pmatch, 0);
+           if (reg_result == 0) {
+               char *str;
+
+               str = find_regex_substring(rest, pmatch[1]);
+               sec = atof(str);
+               amfree(str);
+
+               str = find_regex_substring(rest, pmatch[2]);
+               kb = OFF_T_ATOI(str);
+               amfree(str);
+
+               str = find_regex_substring(rest, pmatch[3]);
+               orig_kb = OFF_T_ATOI(str);
+               amfree(str);
+           } else {
+               regfree(&regex);
+               bzero(&regex, sizeof(regex));
+               /* the .* at the end of this captures the old {wr: .. } statistics */
+               reg_result = regcomp(&regex,
+                   "\\[sec ([0-9.]+) kb ([0-9]+) kps [0-9.]+.*\\]", REG_EXTENDED);
+               if (reg_result != 0) {
+                   error("Error compiling regular expression for parsing log lines");
+                   /* NOTREACHED */
+               }
+
+               /* an error here just means the line wasn't found -- not fatal. */
+               reg_result = regexec(&regex, rest, sizeof(pmatch)/sizeof(*pmatch), pmatch, 0);
+               if (reg_result == 0) {
+                   char *str;
+
+                   str = find_regex_substring(rest, pmatch[1]);
+                   sec = atof(str);
+                   amfree(str);
+
+                   str = find_regex_substring(rest, pmatch[2]);
+                   kb = OFF_T_ATOI(str);
+                   amfree(str);
+                   orig_kb = 0;
+               } else {
+                   sec = 0;
+                   kb = 0;
+                   orig_kb = 0;
+               }
+           }
+           if (strncmp(rest, "error ", 6) == 0) rest += 6;
+           if (strncmp(rest, "config ", 7) == 0) rest += 7;
+           regfree(&regex);
+
            dp = lookup_disk(host,disk);
            if ( dp == NULL ) {
-               if (dynamic_disklist == 0) {
+               if (dynamic_disklist == NULL) {
                    continue;
                }
-               dp = add_disk(find_diskqp, host, disk);
-               enqueue_disk(find_diskqp, dp);
+               dp = add_disk(dynamic_disklist, host, disk);
+               enqueue_disk(dynamic_disklist, dp);
            }
-            if(find_match(host, disk) && (curlog != L_SUCCESS ||
-               !seen_chunk_of(*output_find,datestampI,host,disk,level))){
+            if (find_match(host, disk)) {
                if(curprog == P_TAPER) {
-                   find_result_t *new_output_find =
-                       (find_result_t *)alloc(sizeof(find_result_t));
-                   new_output_find->next=*output_find;
-                   new_output_find->datestamp=datestampI;
-                   new_output_find->timestamp = alloc(15);
-                   snprintf(new_output_find->timestamp, 15, "%d000000", datestampI);
-                   new_output_find->datestamp_aux=datestamp_aux;
+                   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);
+                   new_output_find->timestamp = stralloc(date);
+                   new_output_find->write_timestamp = stralloc(datestamp);
                    new_output_find->hostname=stralloc(host);
                    new_output_find->diskname=stralloc(disk);
                    new_output_find->level=level;
-                   new_output_find->partnum = stralloc(partnum);
-                   new_output_find->label=stralloc(label);
+                   new_output_find->partnum = partnum;
+                   new_output_find->totalparts = totalparts;
+                    new_output_find->label=stralloc(current_label);
+                   new_output_find->status=NULL;
+                   new_output_find->dump_status=NULL;
+                   new_output_find->message=stralloc("");
                    new_output_find->filenum=filenum;
-                   if(curlog == L_SUCCESS || curlog == L_CHUNK) 
-                       new_output_find->status=stralloc("OK");
-                   else
-                       new_output_find->status=stralloc(rest);
-                   *output_find=new_output_find;
+                   new_output_find->sec=sec;
+                   new_output_find->kb=kb;
+                   new_output_find->orig_kb=orig_kb;
+                   new_output_find->next=NULL;
+                   if (curlog == L_SUCCESS) {
+                       new_output_find->status = stralloc("OK");
+                       new_output_find->dump_status = stralloc("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) {
+                               amfree(a_part_find->dump_status);
+                               if (curlog == L_PARTIAL)
+                                   a_part_find->dump_status = stralloc("PARTIAL");
+                               else {
+                                   a_part_find->dump_status = stralloc("FAIL");
+                                   amfree(a_part_find->message);
+                                   a_part_find->message = stralloc(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 &&
+                                       strcmp(a_part_find->status, "OK") == 0)
+                                       num_part--;
+                               }
+                               /* set dump_status of each part */
+                               for (a_part_find = part_find;
+                                    a_part_find;
+                                    a_part_find = a_part_find->next) {
+                                   amfree(a_part_find->dump_status);
+                                   if (num_part == 0) {
+                                       a_part_find->dump_status =
+                                               stralloc("OK");
+                                   } else {
+                                       a_part_find->dump_status =
+                                               stralloc("FAIL");
+                                       amfree(a_part_find->message);
+                                       a_part_find->message =
+                                               stralloc("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;
+                               }
+                           }
+                       }
+                       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=stralloc("OK");
+                           new_output_find->dump_status=stralloc("OK");
+                       } else { /* PARTPARTIAL */
+                           new_output_find->status=stralloc("PARTIAL");
+                           new_output_find->dump_status=stralloc("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) {     /* print other failures too */
-                   find_result_t *new_output_find =
-                       (find_result_t *)alloc(sizeof(find_result_t));
-                   new_output_find->next=*output_find;
-                   new_output_find->datestamp=datestamp;
-                   new_output_find->datestamp_aux=datestamp_aux;
-                   new_output_find->timestamp = alloc(15);
-                   snprintf(new_output_find->timestamp, 15, "%d000000", datestamp);
+               else if(curlog == L_FAIL) {
+                   /* 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 = stralloc(date);
+                   new_output_find->write_timestamp = g_strdup("00000000000000"); /* dump was not written.. */
                    new_output_find->hostname=stralloc(host);
                    new_output_find->diskname=stralloc(disk);
                    new_output_find->level=level;
-                   new_output_find->label=stralloc(label);
-                   new_output_find->partnum=stralloc(partnum);
+                   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->kb=orig_kb;
                    new_output_find->status=vstralloc(
                         "FAILED (",
                         program_str[(int)curprog],
                         ") ",
                         rest,
                         NULL);
+                   new_output_find->dump_status=stralloc("");
+                   new_output_find->message=stralloc("");
                    *output_find=new_output_find;
+                    found_something = TRUE;
+                   maxparts = -1;
                }
            }
+           amfree(disk);
        }
     }
+
+    g_hash_table_destroy(valid_label);
     afclose(logf);
-    return 1;
+    amfree(datestamp);
+    amfree(current_label);
+
+    return found_something;
 }
 
 
@@ -855,14 +1131,18 @@ int datestamp, datestamp_aux;
  * 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(output_find,hostname,diskname,datestamp,level,ok)
-find_result_t *output_find;
-char *hostname;
-char *diskname;
-char *datestamp;
-char *level;
-int ok;
+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;
@@ -870,27 +1150,33 @@ int ok;
     for(cur_result=output_find;
        cur_result;
        cur_result=cur_result->next) {
-       char date_str[NUM_STR_SIZE];
        char level_str[NUM_STR_SIZE];
-       snprintf(date_str, sizeof(date_str), "%d", cur_result->datestamp);
-       snprintf(level_str, sizeof(level_str), "%d", cur_result->level);
-       if((*hostname == '\0' || match_host(hostname, cur_result->hostname)) &&
-          (*diskname == '\0' || match_disk(diskname, cur_result->diskname)) &&
-          (*datestamp== '\0' || match_datestamp(datestamp, date_str)) &&
-          (*level== '\0' || match_level(level, level_str)) &&
-          (!ok || !strcmp(cur_result->status, "OK"))){
-
-           find_result_t *curmatch = alloc(sizeof(find_result_t));
-           memcpy(curmatch, cur_result, sizeof(find_result_t));
-
-/*
+       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 || !strcmp(cur_result->status, "OK")) &&
+          (!ok || !strcmp(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 = stralloc(cur_result->timestamp);
+           curmatch->write_timestamp = stralloc(cur_result->write_timestamp);
            curmatch->hostname = stralloc(cur_result->hostname);
            curmatch->diskname = stralloc(cur_result->diskname);
-           curmatch->datestamp = stralloc(cur_result->datestamp);
-           curmatch->partnum = stralloc(cur_result->partnum);
+           curmatch->level = cur_result->level;
+           curmatch->label = cur_result->label? stralloc(cur_result->label) : NULL;
+           curmatch->filenum = cur_result->filenum;
+           curmatch->sec = cur_result->sec;
+           curmatch->kb = cur_result->kb;
+           curmatch->orig_kb = cur_result->orig_kb;
            curmatch->status = stralloc(cur_result->status);
-           curmatch->level = stralloc(cur_result->level);
-*/         
+           curmatch->dump_status = stralloc(cur_result->dump_status);
+           curmatch->message = stralloc(cur_result->message);
+           curmatch->partnum = cur_result->partnum;
+           curmatch->totalparts = cur_result->totalparts;
            curmatch->next = matches;
            matches = curmatch;
        }
@@ -899,12 +1185,93 @@ int ok;
     return(matches);
 }
 
-find_result_t *dump_exist(output_find, hostname, diskname, datestamp, level)
-find_result_t *output_find;
-char *hostname;
-char *diskname;
-int datestamp;
-int level;
+/*
+ * 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 || !strcmp(cur_result->status, "OK")) &&
+              (!ok || !strcmp(cur_result->dump_status, "OK"))) {
+
+               find_result_t *curmatch = alloc(SIZEOF(find_result_t));
+               memcpy(curmatch, cur_result, SIZEOF(find_result_t));
+
+               curmatch->timestamp = stralloc(cur_result->timestamp);
+               curmatch->write_timestamp = stralloc(cur_result->write_timestamp);
+               curmatch->hostname = stralloc(cur_result->hostname);
+               curmatch->diskname = stralloc(cur_result->diskname);
+               curmatch->level = cur_result->level;
+               curmatch->label = cur_result->label? stralloc(cur_result->label) : NULL;
+               curmatch->filenum = cur_result->filenum;
+               curmatch->status = stralloc(cur_result->status);
+               curmatch->dump_status = stralloc(cur_result->dump_status);
+               curmatch->message = stralloc(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;
 
@@ -913,7 +1280,7 @@ int level;
        output_find_result=output_find_result->next) {
        if( !strcmp(output_find_result->hostname, hostname) &&
            !strcmp(output_find_result->diskname, diskname) &&
-           output_find_result->datestamp == datestamp &&
+           !strcmp(output_find_result->timestamp, datestamp) &&
            output_find_result->level == level) {
 
            return output_find_result;