Imported Upstream version 3.2.0
[debian/amanda] / server-src / find.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Author: James da Silva, Systems Design and Analysis Group
24  *                         Computer Science Department
25  *                         University of Maryland at College Park
26  */
27 /*
28  * $Id: find.c,v 1.33 2006/07/06 13:13:15 martinea Exp $
29  *
30  * controlling process for the Amanda backup system
31  */
32 #include "amanda.h"
33 #include "match.h"
34 #include "conffile.h"
35 #include "tapefile.h"
36 #include "logfile.h"
37 #include "holding.h"
38 #include "find.h"
39 #include <regex.h>
40 #include "cmdline.h"
41
42 int find_match(char *host, char *disk);
43 char *find_nicedate(char *datestamp);
44 static int find_compare(const void *, const void *);
45 static int parse_taper_datestamp_log(char *logline, char **datestamp, char **level);
46 static gboolean logfile_has_tape(char * label, char * datestamp,
47                                  char * logfile);
48
49 static char *find_sort_order = NULL;
50
51 find_result_t * find_dump(disklist_t* diskqp) {
52     char *conf_logdir, *logfile = NULL;
53     int tape, tape1, maxtape, logs;
54     unsigned seq;
55     tape_t *tp, *tp1;
56     find_result_t *output_find = NULL;
57     gboolean *tape_seen = NULL;
58
59     conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
60     maxtape = lookup_nb_tape();
61     tape_seen = g_new0(gboolean, maxtape+1);
62
63     for(tape = 1; tape <= maxtape; tape++) {
64
65         if (tape_seen[tape] == 1)
66             continue;
67         tp = lookup_tapepos(tape);
68         if(tp == NULL) continue;
69
70         /* find all tape with the same datestamp */
71         for (tape1 = tape; tape1 <= maxtape; tape1++) {
72             tp1 = lookup_tapepos(tape1);
73             if (tp1 == NULL) continue;
74             if (strcmp(tp->datestamp, tp1->datestamp) != 0)
75                 continue;
76
77             tape_seen[tape1] = 1;
78         }
79
80         /* search log files */
81
82         logs = 0;
83
84         /* new-style log.<date>.<seq> */
85
86         for(seq = 0; 1; seq++) {
87             char seq_str[NUM_STR_SIZE];
88
89             g_snprintf(seq_str, SIZEOF(seq_str), "%u", seq);
90             logfile = newvstralloc(logfile,
91                         conf_logdir, "/log.", tp->datestamp, ".", seq_str, NULL);
92             if(access(logfile, R_OK) != 0) break;
93             if (search_logfile(&output_find, NULL, tp->datestamp,
94                                logfile, diskqp)) {
95                 logs ++;
96             }
97         }
98
99         /* search old-style amflush log, if any */
100
101         logfile = newvstralloc(logfile, conf_logdir, "/log.",
102                                tp->datestamp, ".amflush", NULL);
103         if(access(logfile,R_OK) == 0) {
104             if (search_logfile(&output_find, NULL, tp->datestamp,
105                                logfile, diskqp)) {
106                 logs ++;
107             }
108         }
109         
110         /* search old-style main log, if any */
111
112         logfile = newvstralloc(logfile, conf_logdir, "/log.", tp->datestamp,
113                                NULL);
114         if(access(logfile,R_OK) == 0) {
115             if (search_logfile(&output_find, NULL, tp->datestamp,
116                                logfile, diskqp)) {
117                 logs ++;
118             }
119         }
120     }
121     g_free(tape_seen);
122     amfree(logfile);
123     amfree(conf_logdir);
124
125     search_holding_disk(&output_find, diskqp);
126
127     return(output_find);
128 }
129
130 char **
131 find_log(void)
132 {
133     char *conf_logdir, *logfile = NULL;
134     char *pathlogfile = NULL;
135     int tape, maxtape, logs;
136     unsigned seq;
137     tape_t *tp;
138     char **output_find_log = NULL;
139     char **current_log;
140
141     conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
142     maxtape = lookup_nb_tape();
143
144     output_find_log = alloc((maxtape*5+10) * SIZEOF(char *));
145     current_log = output_find_log;
146
147     for(tape = 1; tape <= maxtape; tape++) {
148
149         tp = lookup_tapepos(tape);
150         if(tp == NULL) continue;
151
152         /* search log files */
153
154         logs = 0;
155
156         /* new-style log.<date>.<seq> */
157
158         for(seq = 0; 1; seq++) {
159             char seq_str[NUM_STR_SIZE];
160
161             g_snprintf(seq_str, SIZEOF(seq_str), "%u", seq);
162             logfile = newvstralloc(logfile, "log.", tp->datestamp, ".", seq_str, NULL);
163             pathlogfile = newvstralloc(pathlogfile, conf_logdir, "/", logfile, NULL);
164             if (access(pathlogfile, R_OK) != 0) break;
165             if (logfile_has_tape(tp->label, tp->datestamp, pathlogfile)) {
166                 if (current_log == output_find_log || strcmp(*(current_log-1), logfile)) {
167                     *current_log = stralloc(logfile);
168                     current_log++;
169                 }
170                 logs++;
171                 break;
172             }
173         }
174
175         /* search old-style amflush log, if any */
176
177         logfile = newvstralloc(logfile, "log.", tp->datestamp, ".amflush", NULL);
178         pathlogfile = newvstralloc(pathlogfile, conf_logdir, "/", logfile, NULL);
179         if (access(pathlogfile, R_OK) == 0) {
180             if (logfile_has_tape(tp->label, tp->datestamp, pathlogfile)) {
181                 if (current_log == output_find_log || strcmp(*(current_log-1), logfile)) {
182                     *current_log = stralloc(logfile);
183                     current_log++;
184                 }
185                 logs++;
186             }
187         }
188
189         /* search old-style main log, if any */
190
191         logfile = newvstralloc(logfile, "log.", tp->datestamp, NULL);
192         pathlogfile = newvstralloc(pathlogfile, conf_logdir, "/", logfile, NULL);
193         if (access(pathlogfile, R_OK) == 0) {
194             if (logfile_has_tape(tp->label, tp->datestamp, pathlogfile)) {
195                 if (current_log == output_find_log || strcmp(*(current_log-1), logfile)) {
196                     *current_log = stralloc(logfile);
197                     current_log++;
198                 }
199                 logs++;
200             }
201         }
202
203         if(logs == 0 && strcmp(tp->datestamp,"0") != 0)
204             g_fprintf(stderr, _("Warning: no log files found for tape %s written %s\n"),
205                    tp->label, find_nicedate(tp->datestamp));
206     }
207     amfree(logfile);
208     amfree(pathlogfile);
209     amfree(conf_logdir);
210     *current_log = NULL;
211     return(output_find_log);
212 }
213
214 void
215 search_holding_disk(
216     find_result_t **output_find,
217     disklist_t * dynamic_disklist)
218 {
219     GSList *holding_file_list;
220     GSList *e;
221     char   *holding_file;
222     disk_t *dp;
223     char   *orig_name;
224
225     holding_file_list = holding_get_files(NULL, 1);
226
227     for(e = holding_file_list; e != NULL; e = e->next) {
228         dumpfile_t file;
229
230         holding_file = (char *)e->data;
231
232         if (!holding_file_get_dumpfile(holding_file, &file))
233             continue;
234
235         if (file.dumplevel < 0 || file.dumplevel >= DUMP_LEVELS) {
236             dumpfile_free_data(&file);
237             continue;
238         }
239
240         dp = NULL;
241         orig_name = g_strdup(file.name);
242         for(;;) {
243             char *s;
244             if((dp = lookup_disk(file.name, file.disk)))
245                 break;
246             if((s = strrchr(file.name,'.')) == NULL)
247                 break;
248             *s = '\0';
249         }
250         strcpy(file.name, orig_name); /* restore munged string */
251         g_free(orig_name);
252
253         if ( dp == NULL ) {
254             if (dynamic_disklist == NULL) {
255                 dumpfile_free_data(&file);
256                 continue;
257             }
258             dp = add_disk(dynamic_disklist, file.name, file.disk);
259             enqueue_disk(dynamic_disklist, dp);
260         }
261
262         if(find_match(file.name,file.disk)) {
263             find_result_t *new_output_find = g_new0(find_result_t, 1);
264             new_output_find->next=*output_find;
265             new_output_find->timestamp = stralloc(file.datestamp);
266             new_output_find->write_timestamp = stralloc("00000000000000");
267             new_output_find->hostname = stralloc(file.name);
268             new_output_find->diskname = stralloc(file.disk);
269             new_output_find->level=file.dumplevel;
270             new_output_find->label=stralloc(holding_file);
271             new_output_find->partnum = -1;
272             new_output_find->totalparts = -1;
273             new_output_find->filenum=0;
274             if (file.is_partial) {
275                 new_output_find->status=stralloc("PARTIAL");
276                 new_output_find->dump_status=stralloc("PARTIAL");
277             } else {
278                 new_output_find->status=stralloc("OK");
279                 new_output_find->dump_status=stralloc("OK");
280             }
281             new_output_find->message=stralloc("");
282             new_output_find->kb = holding_file_size(holding_file, 1);
283             new_output_find->orig_kb = file.orig_size;
284
285             *output_find=new_output_find;
286         }
287         dumpfile_free_data(&file);
288     }
289
290     g_slist_free_full(holding_file_list);
291 }
292
293 static int
294 find_compare(
295     const void *i1,
296     const void *j1)
297 {
298     int compare=0;
299     find_result_t *i, *j;
300
301     size_t nb_compare=strlen(find_sort_order);
302     size_t k;
303
304     for(k=0;k<nb_compare;k++) {
305         char sort_key = find_sort_order[k];
306         if (isupper((int)sort_key)) {
307             /* swap */
308             sort_key = tolower(sort_key);
309             j = *(find_result_t **)i1;
310             i = *(find_result_t **)j1;
311         } else {
312             i = *(find_result_t **)i1;
313             j = *(find_result_t **)j1;
314         }            
315         
316         switch (sort_key) {
317         case 'h' : compare=strcmp(i->hostname,j->hostname);
318                    break;
319         case 'k' : compare=strcmp(i->diskname,j->diskname);
320                    break;
321         case 'd' : compare=strcmp(i->timestamp,j->timestamp);
322                    break;
323         case 'l' : compare=j->level - i->level;
324                    break;
325         case 'f' : compare=(i->filenum == j->filenum) ? 0 :
326                            ((i->filenum < j->filenum) ? -1 : 1);
327                    break;
328         case 'b' : compare=compare_possibly_null_strings(i->label,
329                                                          j->label);
330                    break;
331         case 'w': compare=strcmp(i->write_timestamp, j->write_timestamp);
332                    break;
333         case 'p' :
334                    compare=i->partnum - j->partnum;
335                    break;
336         }
337         if(compare != 0)
338             return compare;
339     }
340     return 0;
341 }
342
343 void
344 sort_find_result(
345     char *sort_order,
346     find_result_t **output_find)
347 {
348     find_result_t *output_find_result;
349     find_result_t **array_find_result = NULL;
350     size_t nb_result=0;
351     size_t no_result;
352
353     find_sort_order = sort_order;
354     /* qsort core dump if nothing to sort */
355     if(*output_find==NULL)
356         return;
357
358     /* How many result */
359     for(output_find_result=*output_find;
360         output_find_result;
361         output_find_result=output_find_result->next) {
362         nb_result++;
363     }
364
365     /* put the list in an array */
366     array_find_result=alloc(nb_result * SIZEOF(find_result_t *));
367     for(output_find_result=*output_find,no_result=0;
368         output_find_result;
369         output_find_result=output_find_result->next,no_result++) {
370         array_find_result[no_result]=output_find_result;
371     }
372
373     /* sort the array */
374     qsort(array_find_result,nb_result,SIZEOF(find_result_t *),
375           find_compare);
376
377     /* put the sorted result in the list */
378     for(no_result=0;
379         no_result<nb_result-1; no_result++) {
380         array_find_result[no_result]->next = array_find_result[no_result+1];
381     }
382     array_find_result[nb_result-1]->next=NULL;
383     *output_find=array_find_result[0];
384     amfree(array_find_result);
385 }
386
387 void
388 print_find_result(
389     find_result_t *output_find)
390 {
391     find_result_t *output_find_result;
392     int max_len_datestamp = 4;
393     int max_len_hostname  = 4;
394     int max_len_diskname  = 4;
395     int max_len_level     = 2;
396     int max_len_label     =12;
397     int max_len_filenum   = 4;
398     int max_len_part      = 4;
399     int max_len_status    = 6;
400     size_t len;
401
402     for(output_find_result=output_find;
403         output_find_result;
404         output_find_result=output_find_result->next) {
405         char *qdiskname;
406         char *s;
407
408         len=strlen(find_nicedate(output_find_result->timestamp));
409         if((int)len > max_len_datestamp)
410             max_len_datestamp=(int)len;
411
412         len=strlen(output_find_result->hostname);
413         if((int)len > max_len_hostname)
414             max_len_hostname = (int)len;
415
416         qdiskname=quote_string(output_find_result->diskname);
417         len=strlen(qdiskname);
418         amfree(qdiskname);
419         if((int)len > max_len_diskname)
420             max_len_diskname = (int)len;
421
422         if (output_find_result->label != NULL) {
423             char *qlabel = quote_string(output_find_result->label);
424             len=strlen(qlabel);
425             amfree(qlabel);
426             if((int)len > max_len_label)
427                 max_len_label = (int)len;
428         }
429
430         len=strlen(output_find_result->status) + 1 + strlen(output_find_result->dump_status);
431         if((int)len > max_len_status)
432             max_len_status = (int)len;
433
434         s = g_strdup_printf("%d/%d", output_find_result->partnum,
435                                      output_find_result->totalparts);
436         len=strlen(s);
437         if((int)len > max_len_part)
438             max_len_part = (int)len;
439         amfree(s);
440     }
441
442     /*
443      * Since status is the rightmost field, we zap the maximum length
444      * because it is not needed.  The code is left in place in case
445      * another column is added later.
446      */
447     max_len_status = 1;
448
449     if(output_find==NULL) {
450         g_printf(_("\nNo dump to list\n"));
451     }
452     else {
453         g_printf(_("\ndate%*s host%*s disk%*s lv%*s tape or file%*s file%*s part%*s status\n"),
454                max_len_datestamp-4,"",
455                max_len_hostname-4 ,"",
456                max_len_diskname-4 ,"",
457                max_len_level-2    ,"",
458                max_len_label-12   ,"",
459                max_len_filenum-4  ,"",
460                max_len_part-4  ,"");
461         for(output_find_result=output_find;
462                 output_find_result;
463                 output_find_result=output_find_result->next) {
464             char *qdiskname;
465             char * formatted_label;
466             char *s;
467             char *status;
468
469             qdiskname = quote_string(output_find_result->diskname);
470             if (output_find_result->label == NULL)
471                 formatted_label = stralloc("");
472             else
473                 formatted_label = quote_string(output_find_result->label);
474
475             if (strcmp(output_find_result->status, "OK") != 0 ||
476                 strcmp(output_find_result->dump_status, "OK") != 0) {
477                 status = vstralloc(output_find_result->status, " ",
478                                    output_find_result->dump_status, NULL);
479             } else {
480                 status = stralloc(output_find_result->status);
481             }
482
483             /*@ignore@*/
484             /* sec and kb are omitted here, for compatibility with the existing
485              * output from 'amadmin' */
486             s = g_strdup_printf("%d/%d", output_find_result->partnum,
487                                          output_find_result->totalparts);
488             g_printf("%-*s %-*s %-*s %*d %-*s %*lld %*s %s %s\n",
489                      max_len_datestamp, 
490                      find_nicedate(output_find_result->timestamp),
491                      max_len_hostname,  output_find_result->hostname,
492                      max_len_diskname,  qdiskname,
493                      max_len_level,     output_find_result->level,
494                      max_len_label,     formatted_label,
495                      max_len_filenum,   (long long)output_find_result->filenum,
496                      max_len_part,      s,
497                                         status,
498                                         output_find_result->message
499                     );
500             amfree(status);
501             amfree(s);
502             /*@end@*/
503             amfree(qdiskname);
504             amfree(formatted_label);
505         }
506     }
507 }
508
509 void
510 free_find_result(
511     find_result_t **output_find)
512 {
513     find_result_t *output_find_result, *prev;
514
515     prev=NULL;
516     for(output_find_result=*output_find;
517             output_find_result;
518             output_find_result=output_find_result->next) {
519         amfree(prev);
520         amfree(output_find_result->timestamp);
521         amfree(output_find_result->write_timestamp);
522         amfree(output_find_result->hostname);
523         amfree(output_find_result->diskname);
524         amfree(output_find_result->label);
525         amfree(output_find_result->status);
526         amfree(output_find_result->dump_status);
527         amfree(output_find_result->message);
528         prev = output_find_result;
529     }
530     amfree(prev);
531     *output_find = NULL;
532 }
533
534 int
535 find_match(
536     char *host,
537     char *disk)
538 {
539     disk_t *dp = lookup_disk(host,disk);
540     return (dp && dp->todo);
541 }
542
543 char *
544 find_nicedate(
545     char *datestamp)
546 {
547     static char nice[20];
548     int year, month, day;
549     int hours, minutes, seconds;
550     char date[9], atime[7];
551     int  numdate, numtime;
552
553     strncpy(date, datestamp, 8);
554     date[8] = '\0';
555     numdate = atoi(date);
556     year  = numdate / 10000;
557     month = (numdate / 100) % 100;
558     day   = numdate % 100;
559
560     if(strlen(datestamp) <= 8) {
561         g_snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d",
562                 year, month, day);
563     }
564     else {
565         strncpy(atime, &(datestamp[8]), 6);
566         atime[6] = '\0';
567         numtime = atoi(atime);
568         hours = numtime / 10000;
569         minutes = (numtime / 100) % 100;
570         seconds = numtime % 100;
571
572         g_snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d %02d:%02d:%02d",
573                 year, month, day, hours, minutes, seconds);
574     }
575
576     return nice;
577 }
578
579 static int
580 parse_taper_datestamp_log(
581     char *logline,
582     char **datestamp,
583     char **label)
584 {
585     char *s;
586     int ch;
587
588     s = logline;
589     ch = *s++;
590
591     skip_whitespace(s, ch);
592     if(ch == '\0') {
593         return 0;
594     }
595     if(strncmp_const_skip(s - 1, "datestamp", s, ch) != 0) {
596         return 0;
597     }
598
599     skip_whitespace(s, ch);
600     if(ch == '\0') {
601         return 0;
602     }
603     *datestamp = s - 1;
604     skip_non_whitespace(s, ch);
605     s[-1] = '\0';
606
607     skip_whitespace(s, ch);
608     if(ch == '\0') {
609         return 0;
610     }
611     if(strncmp_const_skip(s - 1, "label", s, ch) != 0) {
612         return 0;
613     }
614
615     skip_whitespace(s, ch);
616     if(ch == '\0') {
617         return 0;
618     }
619     *label = s - 1;
620     skip_non_whitespace(s, ch);
621     s[-1] = '\0';
622
623     return 1;
624 }
625
626 /* Returns TRUE if the given logfile mentions the given tape. */
627 static gboolean logfile_has_tape(char * label, char * datestamp,
628                                  char * logfile) {
629     FILE * logf;
630     char * ck_datestamp, *ck_label;
631     if((logf = fopen(logfile, "r")) == NULL) {
632         error(_("could not open logfile %s: %s"), logfile, strerror(errno));
633         /*NOTREACHED*/
634     }
635
636     while(get_logline(logf)) {
637         if(curlog == L_START && curprog == P_TAPER) {
638             if(parse_taper_datestamp_log(curstr,
639                                          &ck_datestamp, &ck_label) == 0) {
640                 g_printf(_("strange log line \"start taper %s\" curstr='%s'\n"),
641                          logfile, curstr);
642             } else if(strcmp(ck_datestamp, datestamp) == 0
643                       && strcmp(ck_label, label) == 0) {
644                 afclose(logf);
645                 return TRUE;
646             }
647         }
648     }
649
650     afclose(logf);
651     return FALSE;
652 }
653
654 static gboolean
655 volume_matches(
656     const char *label1,
657     const char *label2,
658     const char *datestamp)
659 {
660     tape_t *tp;
661
662     if (!label2)
663         return TRUE;
664
665     if (label1)
666         return (strcmp(label1, label2) == 0);
667
668     /* check in tapelist */
669     if (!(tp = lookup_tapelabel(label2)))
670         return FALSE;
671
672     if (strcmp(tp->datestamp, datestamp) != 0)
673         return FALSE;
674
675     return TRUE;
676 }
677
678 /* WARNING: Function accesses globals find_diskqp, curlog, curlog, curstr,
679  * dynamic_disklist */
680 gboolean
681 search_logfile(
682     find_result_t **output_find,
683     const char *label,
684     const char *passed_datestamp,
685     const char *logfile,
686     disklist_t * dynamic_disklist)
687 {
688     FILE *logf;
689     char *host, *host_undo;
690     char *disk, *qdisk, *disk_undo;
691     char *date, *date_undo;
692     int  partnum;
693     int  totalparts;
694     int  maxparts = -1;
695     char *number;
696     int fileno;
697     char *current_label = stralloc("");
698     char *rest;
699     char *ck_label=NULL;
700     int level = 0;
701     off_t filenum;
702     char *ck_datestamp, *datestamp;
703     char *s;
704     int ch;
705     disk_t *dp;
706     GHashTable* valid_label;
707     GHashTable* part_by_dle;
708     find_result_t *part_find;
709     find_result_t *a_part_find;
710     gboolean right_label = FALSE;
711     gboolean found_something = FALSE;
712     regex_t regex;
713     int reg_result;
714     regmatch_t pmatch[4];
715     double sec;
716     off_t kb;
717     off_t orig_kb;
718     int   taper_part = 0;
719
720     g_return_val_if_fail(output_find != NULL, 0);
721     g_return_val_if_fail(logfile != NULL, 0);
722
723     valid_label = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
724     part_by_dle = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
725     datestamp = g_strdup(passed_datestamp);
726
727     if((logf = fopen(logfile, "r")) == NULL) {
728         error(_("could not open logfile %s: %s"), logfile, strerror(errno));
729         /*NOTREACHED*/
730     }
731
732     filenum = (off_t)0;
733     while(get_logline(logf)) {
734         if (curlog == L_START && curprog == P_TAPER) {
735             if(parse_taper_datestamp_log(curstr, &ck_datestamp,
736                                          &ck_label) == 0) {
737                 g_printf(_("strange log line in %s \"start taper %s\"\n"),
738                          logfile, curstr);
739                 continue;
740             }
741             if (datestamp != NULL) {
742                 if (strcmp(datestamp, ck_datestamp) != 0) {
743                     g_printf(_("Log file %s stamped %s, expecting %s!\n"),
744                              logfile, ck_datestamp, datestamp);
745                     break;
746                 }
747             }
748
749             right_label = volume_matches(label, ck_label, ck_datestamp);
750             if (right_label && ck_label) {
751                 g_hash_table_insert(valid_label, g_strdup(ck_label),
752                                     GINT_TO_POINTER(1));
753             }
754             if (label && datestamp && right_label) {
755                 found_something = TRUE;
756             }
757             amfree(current_label);
758             current_label = g_strdup(ck_label);
759             if (datestamp == NULL) {
760                 datestamp = g_strdup(ck_datestamp);
761             }
762             filenum = (off_t)0;
763         }
764         if (right_label &&
765             (curlog == L_SUCCESS ||
766              curlog == L_CHUNK || curlog == L_PART || curlog == L_PARTPARTIAL) &&
767             curprog == P_TAPER) {
768             filenum++;
769         } else if (right_label && curlog == L_PARTIAL && curprog == P_TAPER &&
770                    taper_part == 0) {
771             filenum++;
772         }
773         partnum = -1;
774         totalparts = -1;
775         if (curlog == L_SUCCESS || curlog == L_CHUNKSUCCESS ||
776             curlog == L_DONE    || curlog == L_FAIL ||
777             curlog == L_CHUNK   || curlog == L_PART || curlog == L_PARTIAL ||
778             curlog == L_PARTPARTIAL ) {
779             s = curstr;
780             ch = *s++;
781
782             skip_whitespace(s, ch);
783             if(ch == '\0') {
784                 g_printf(_("strange log line in %s \"%s\"\n"),
785                     logfile, curstr);
786                 continue;
787             }
788
789             if (curlog == L_PART || curlog == L_PARTPARTIAL) {
790                 char * part_label = s - 1;
791                 taper_part++;
792                 skip_non_whitespace(s, ch);
793                 s[-1] = '\0';
794
795                 if (!g_hash_table_lookup(valid_label, part_label))
796                     continue;
797                 amfree(current_label);
798                 current_label = stralloc(part_label);
799
800                 skip_whitespace(s, ch);
801                 if(ch == '\0') {
802                     g_printf("strange log line in %s \"%s\"\n",
803                            logfile, curstr);
804                     continue;
805                 }
806
807                 number = s - 1;
808                 skip_non_whitespace(s, ch);
809                 s[-1] = '\0';
810                 fileno = atoi(number);
811                 filenum = fileno;
812                 if (filenum == 0)
813                     continue;
814
815                 skip_whitespace(s, ch);
816                 if(ch == '\0') {
817                     g_printf("strange log line in %s \"%s\"\n",
818                            logfile, curstr);
819                     continue;
820                 }
821             } else {
822                 taper_part = 0;
823             }
824
825             host = s - 1;
826             skip_non_whitespace(s, ch);
827             host_undo = s - 1;
828             *host_undo = '\0';
829
830             skip_whitespace(s, ch);
831             if(ch == '\0') {
832                 g_printf(_("strange log line in %s \"%s\"\n"),
833                     logfile, curstr);
834                 continue;
835             }
836             qdisk = s - 1;
837             skip_quoted_string(s, ch);
838             disk_undo = s - 1;
839             *disk_undo = '\0';
840             disk = unquote_string(qdisk);
841
842             skip_whitespace(s, ch);
843             if(ch == '\0') {
844                 g_printf(_("strange log line in %s \"%s\"\n"),
845                          logfile, curstr);
846                 continue;
847             }
848             date = s - 1;
849             skip_non_whitespace(s, ch);
850             date_undo = s - 1;
851             *date_undo = '\0';
852
853             if(strlen(date) < 3) { /* old log didn't have datestamp */
854                 level = atoi(date);
855                 date = stralloc(datestamp);
856                 partnum = 1;
857                 totalparts =1;
858             } else {
859                 if (curprog == P_TAPER &&
860                         (curlog == L_CHUNK || curlog == L_PART ||
861                          curlog == L_PARTPARTIAL || curlog == L_PARTIAL ||
862                          curlog == L_DONE)) {
863                     skip_whitespace(s, ch);
864                     number = s - 1;
865                     skip_non_whitespace(s, ch);
866                     s[-1] = '\0';
867                     sscanf(number, "%d/%d", &partnum, &totalparts);
868                     if (partnum > maxparts)
869                         maxparts = partnum;
870                     if (totalparts > maxparts)
871                         maxparts = totalparts;
872                 }
873                 skip_whitespace(s, ch);
874                 if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
875                     g_printf(_("strange log line in %s \"%s\"\n"),
876                     logfile, curstr);
877                     continue;
878                 }
879                 skip_integer(s, ch);
880             }
881
882             skip_whitespace(s, ch);
883             if(ch == '\0') {
884                 g_printf(_("strange log line in %s \"%s\"\n"),
885                     logfile, curstr);
886                 continue;
887             }
888             rest = s - 1;
889             if((s = strchr(s, '\n')) != NULL) {
890                 *s = '\0';
891             }
892
893             /* extract sec, kb, kps, orig-kb from 'rest', if present.  This isn't the stone age
894              * anymore, so we'll just do it the easy way (a regex) */
895             bzero(&regex, sizeof(regex));
896             reg_result = regcomp(&regex,
897                     "\\[sec ([0-9.]+) kb ([0-9]+) kps [0-9.]+ orig-kb ([0-9]+)\\]", REG_EXTENDED);
898             if (reg_result != 0) {
899                 error("Error compiling regular expression for parsing log lines");
900                 /* NOTREACHED */
901             }
902
903             /* an error here just means the line wasn't found -- not fatal. */
904             reg_result = regexec(&regex, rest, sizeof(pmatch)/sizeof(*pmatch), pmatch, 0);
905             if (reg_result == 0) {
906                 char *str;
907
908                 str = find_regex_substring(rest, pmatch[1]);
909                 sec = atof(str);
910                 amfree(str);
911
912                 str = find_regex_substring(rest, pmatch[2]);
913                 kb = OFF_T_ATOI(str);
914                 amfree(str);
915
916                 str = find_regex_substring(rest, pmatch[3]);
917                 orig_kb = OFF_T_ATOI(str);
918                 amfree(str);
919             } else {
920                 regfree(&regex);
921                 bzero(&regex, sizeof(regex));
922                 /* the .* at the end of this captures the old {wr: .. } statistics */
923                 reg_result = regcomp(&regex,
924                     "\\[sec ([0-9.]+) kb ([0-9]+) kps [0-9.]+.*\\]", REG_EXTENDED);
925                 if (reg_result != 0) {
926                     error("Error compiling regular expression for parsing log lines");
927                     /* NOTREACHED */
928                 }
929
930                 /* an error here just means the line wasn't found -- not fatal. */
931                 reg_result = regexec(&regex, rest, sizeof(pmatch)/sizeof(*pmatch), pmatch, 0);
932                 if (reg_result == 0) {
933                     char *str;
934
935                     str = find_regex_substring(rest, pmatch[1]);
936                     sec = atof(str);
937                     amfree(str);
938
939                     str = find_regex_substring(rest, pmatch[2]);
940                     kb = OFF_T_ATOI(str);
941                     amfree(str);
942                     orig_kb = 0;
943                 } else {
944                     sec = 0;
945                     kb = 0;
946                     orig_kb = 0;
947                 }
948             }
949             if (strncmp(rest, "error ", 6) == 0) rest += 6;
950             if (strncmp(rest, "config ", 7) == 0) rest += 7;
951             regfree(&regex);
952
953             dp = lookup_disk(host,disk);
954             if ( dp == NULL ) {
955                 if (dynamic_disklist == NULL) {
956                     continue;
957                 }
958                 dp = add_disk(dynamic_disklist, host, disk);
959                 enqueue_disk(dynamic_disklist, dp);
960             }
961             if (find_match(host, disk)) {
962                 if(curprog == P_TAPER) {
963                     char *key = g_strdup_printf(
964                                         "HOST:%s DISK:%s: DATE:%s LEVEL:%d",
965                                         host, disk, date, level);
966                     find_result_t *new_output_find = g_new0(find_result_t, 1);
967                     part_find = g_hash_table_lookup(part_by_dle, key);
968                     new_output_find->timestamp = stralloc(date);
969                     new_output_find->write_timestamp = stralloc(datestamp);
970                     new_output_find->hostname=stralloc(host);
971                     new_output_find->diskname=stralloc(disk);
972                     new_output_find->level=level;
973                     new_output_find->partnum = partnum;
974                     new_output_find->totalparts = totalparts;
975                     new_output_find->label=stralloc(current_label);
976                     new_output_find->status=NULL;
977                     new_output_find->dump_status=NULL;
978                     new_output_find->message=stralloc("");
979                     new_output_find->filenum=filenum;
980                     new_output_find->sec=sec;
981                     new_output_find->kb=kb;
982                     new_output_find->orig_kb=orig_kb;
983                     new_output_find->next=NULL;
984                     if (curlog == L_SUCCESS) {
985                         new_output_find->status = stralloc("OK");
986                         new_output_find->dump_status = stralloc("OK");
987                         new_output_find->next = *output_find;
988                         new_output_find->partnum = 1; /* L_SUCCESS is pre-splitting */
989                         *output_find = new_output_find;
990                         found_something = TRUE;
991                     } else if (curlog == L_CHUNKSUCCESS || curlog == L_DONE ||
992                                curlog == L_PARTIAL      || curlog == L_FAIL) {
993                         /* result line */
994                         if (curlog == L_PARTIAL || curlog == L_FAIL) {
995                             /* set dump_status of each part */
996                             for (a_part_find = part_find;
997                                  a_part_find;
998                                  a_part_find = a_part_find->next) {
999                                 amfree(a_part_find->dump_status);
1000                                 if (curlog == L_PARTIAL)
1001                                     a_part_find->dump_status = stralloc("PARTIAL");
1002                                 else {
1003                                     a_part_find->dump_status = stralloc("FAIL");
1004                                     amfree(a_part_find->message);
1005                                     a_part_find->message = stralloc(rest);
1006                                 }
1007                             }
1008                         } else {
1009                             if (maxparts > -1) { /* format with part */
1010                                 /* must check if all part are there */
1011                                 int num_part = maxparts;
1012                                 for (a_part_find = part_find;
1013                                      a_part_find;
1014                                      a_part_find = a_part_find->next) {
1015                                     if (a_part_find->partnum == num_part &&
1016                                         strcmp(a_part_find->status, "OK") == 0)
1017                                         num_part--;
1018                                 }
1019                                 /* set dump_status of each part */
1020                                 for (a_part_find = part_find;
1021                                      a_part_find;
1022                                      a_part_find = a_part_find->next) {
1023                                     amfree(a_part_find->dump_status);
1024                                     if (num_part == 0) {
1025                                         a_part_find->dump_status =
1026                                                 stralloc("OK");
1027                                     } else {
1028                                         a_part_find->dump_status =
1029                                                 stralloc("FAIL");
1030                                         amfree(a_part_find->message);
1031                                         a_part_find->message =
1032                                                 stralloc("Missing part");
1033                                     }
1034                                 }
1035                             }
1036                         }
1037                         if (curlog == L_DONE) {
1038                             for (a_part_find = part_find;
1039                                  a_part_find;
1040                                  a_part_find = a_part_find->next) {
1041                                 if (a_part_find->totalparts == -1) {
1042                                     a_part_find->totalparts = maxparts;
1043                                 }
1044                                 if (a_part_find->orig_kb == 0) {
1045                                     a_part_find->orig_kb = orig_kb;
1046                                 }
1047                             }
1048                         }
1049                         if (part_find) { /* find last element */
1050                             for (a_part_find = part_find;
1051                                  a_part_find->next != NULL;
1052                                  a_part_find=a_part_find->next) {
1053                             }
1054                             /* merge part_find to *output_find */
1055                             a_part_find->next = *output_find;
1056                             *output_find = part_find;
1057                             part_find = NULL;
1058                             maxparts = -1;
1059                             found_something = TRUE;
1060                             g_hash_table_remove(part_by_dle, key);
1061                         }
1062                         free_find_result(&new_output_find);
1063                     } else { /* part line */
1064                         if (curlog == L_PART || curlog == L_CHUNK) {
1065                             new_output_find->status=stralloc("OK");
1066                             new_output_find->dump_status=stralloc("OK");
1067                         } else { /* PARTPARTIAL */
1068                             new_output_find->status=stralloc("PARTIAL");
1069                             new_output_find->dump_status=stralloc("PARTIAL");
1070                         }
1071                         /* Add to part_find list */
1072                         if (part_find) {
1073                             new_output_find->next = part_find;
1074                             part_find = new_output_find;
1075                         } else {
1076                             new_output_find->next = NULL;
1077                             part_find = new_output_find;
1078                         }
1079                         g_hash_table_insert(part_by_dle, g_strdup(key),
1080                                             part_find);
1081                         found_something = TRUE;
1082                     }
1083                     amfree(key);
1084                 }
1085                 else if(curlog == L_FAIL) {
1086                     /* print other failures too -- this is a hack to ensure that failures which
1087                      * did not make it to tape are also listed in the output of 'amadmin x find';
1088                      * users that do not want this information (e.g., Amanda::DB::Catalog) should
1089                      * filter dumps with a NULL label. */
1090                     find_result_t *new_output_find = g_new0(find_result_t, 1);
1091                     new_output_find->next = *output_find;
1092                     new_output_find->timestamp = stralloc(date);
1093                     new_output_find->write_timestamp = g_strdup("00000000000000"); /* dump was not written.. */
1094                     new_output_find->hostname=stralloc(host);
1095                     new_output_find->diskname=stralloc(disk);
1096                     new_output_find->level=level;
1097                     new_output_find->label=NULL;
1098                     new_output_find->partnum=partnum;
1099                     new_output_find->totalparts=totalparts;
1100                     new_output_find->filenum=0;
1101                     new_output_find->sec=sec;
1102                     new_output_find->kb=kb;
1103                     new_output_find->kb=orig_kb;
1104                     new_output_find->status=vstralloc(
1105                          "FAILED (",
1106                          program_str[(int)curprog],
1107                          ") ",
1108                          rest,
1109                          NULL);
1110                     new_output_find->dump_status=stralloc("");
1111                     new_output_find->message=stralloc("");
1112                     *output_find=new_output_find;
1113                     found_something = TRUE;
1114                     maxparts = -1;
1115                 }
1116             }
1117             amfree(disk);
1118         }
1119     }
1120
1121     g_hash_table_destroy(valid_label);
1122     afclose(logf);
1123     amfree(datestamp);
1124     amfree(current_label);
1125
1126     return found_something;
1127 }
1128
1129
1130 /*
1131  * Return the set of dumps that match *all* of the given patterns (we consider
1132  * an empty pattern to match .*, though).  If 'ok' is true, will only match
1133  * dumps with SUCCESS status.
1134  *
1135  * Returns a newly allocated list of results, where all strings are also newly
1136  * allocated.  Apparently some part of Amanda leaks under this condition.
1137  */
1138 find_result_t *
1139 dumps_match(
1140     find_result_t *output_find,
1141     char *hostname,
1142     char *diskname,
1143     char *datestamp,
1144     char *level,
1145     int ok)
1146 {
1147     find_result_t *cur_result;
1148     find_result_t *matches = NULL;
1149
1150     for(cur_result=output_find;
1151         cur_result;
1152         cur_result=cur_result->next) {
1153         char level_str[NUM_STR_SIZE];
1154         g_snprintf(level_str, SIZEOF(level_str), "%d", cur_result->level);
1155         if((!hostname || *hostname == '\0' || match_host(hostname, cur_result->hostname)) &&
1156            (!diskname || *diskname == '\0' || match_disk(diskname, cur_result->diskname)) &&
1157            (!datestamp || *datestamp== '\0' || match_datestamp(datestamp, cur_result->timestamp)) &&
1158            (!level || *level== '\0' || match_level(level, level_str)) &&
1159            (!ok || !strcmp(cur_result->status, "OK")) &&
1160            (!ok || !strcmp(cur_result->dump_status, "OK"))){
1161
1162             find_result_t *curmatch = g_new0(find_result_t, 1);
1163             memcpy(curmatch, cur_result, SIZEOF(find_result_t));
1164
1165             curmatch->timestamp = stralloc(cur_result->timestamp);
1166             curmatch->write_timestamp = stralloc(cur_result->write_timestamp);
1167             curmatch->hostname = stralloc(cur_result->hostname);
1168             curmatch->diskname = stralloc(cur_result->diskname);
1169             curmatch->level = cur_result->level;
1170             curmatch->label = cur_result->label? stralloc(cur_result->label) : NULL;
1171             curmatch->filenum = cur_result->filenum;
1172             curmatch->sec = cur_result->sec;
1173             curmatch->kb = cur_result->kb;
1174             curmatch->orig_kb = cur_result->orig_kb;
1175             curmatch->status = stralloc(cur_result->status);
1176             curmatch->dump_status = stralloc(cur_result->dump_status);
1177             curmatch->message = stralloc(cur_result->message);
1178             curmatch->partnum = cur_result->partnum;
1179             curmatch->totalparts = cur_result->totalparts;
1180             curmatch->next = matches;
1181             matches = curmatch;
1182         }
1183     }
1184
1185     return(matches);
1186 }
1187
1188 /*
1189  * Return the set of dumps that match one or more of the given dumpspecs,
1190  * If 'ok' is true, only dumps with a SUCCESS status will be matched.
1191  * 
1192  * Returns a newly allocated list of results, where all strings are also newly
1193  * allocated.  Apparently some part of Amanda leaks under this condition.
1194  */
1195 find_result_t *
1196 dumps_match_dumpspecs(
1197     find_result_t *output_find,
1198     GSList        *dumpspecs,
1199     int ok)
1200 {
1201     find_result_t *cur_result;
1202     find_result_t *matches = NULL;
1203     GSList        *dumpspec;
1204     dumpspec_t    *ds;
1205
1206     for(cur_result=output_find;
1207         cur_result;
1208         cur_result=cur_result->next) {
1209         char level_str[NUM_STR_SIZE];
1210         char *zeropad_ts = NULL;
1211         char *zeropad_w_ts = NULL;
1212         g_snprintf(level_str, SIZEOF(level_str), "%d", cur_result->level);
1213
1214         /* get the timestamp padded to full width */
1215         if (strlen(cur_result->timestamp) < 14) {
1216             zeropad_ts = g_new0(char, 15);
1217             memset(zeropad_ts, '0', 14);
1218             memcpy(zeropad_ts, cur_result->timestamp, strlen(cur_result->timestamp));
1219         }
1220         if (strlen(cur_result->write_timestamp) < 14) {
1221             zeropad_w_ts = g_new0(char, 15);
1222             memset(zeropad_w_ts, '0', 14);
1223             memcpy(zeropad_w_ts, cur_result->write_timestamp, strlen(cur_result->write_timestamp));
1224         }
1225
1226         for (dumpspec = dumpspecs; dumpspec; dumpspec = dumpspec->next) {
1227             ds = (dumpspec_t *)dumpspec->data;
1228             if((!ds->host || *ds->host == '\0' || match_host(ds->host, cur_result->hostname)) &&
1229                (!ds->disk || *ds->disk == '\0' || match_disk(ds->disk, cur_result->diskname)) &&
1230                (!ds->datestamp || *ds->datestamp== '\0'
1231                         || match_datestamp(ds->datestamp, cur_result->timestamp)
1232                         || (zeropad_ts && match_datestamp(ds->datestamp, zeropad_ts))) &&
1233                (!ds->write_timestamp || *ds->write_timestamp== '\0'
1234                         || match_datestamp(ds->write_timestamp, cur_result->write_timestamp)
1235                         || (zeropad_w_ts && match_datestamp(ds->write_timestamp, zeropad_w_ts))) &&
1236                (!ds->level || *ds->level== '\0' || match_level(ds->level, level_str)) &&
1237                (!ok || !strcmp(cur_result->status, "OK")) &&
1238                (!ok || !strcmp(cur_result->dump_status, "OK"))) {
1239
1240                 find_result_t *curmatch = alloc(SIZEOF(find_result_t));
1241                 memcpy(curmatch, cur_result, SIZEOF(find_result_t));
1242
1243                 curmatch->timestamp = stralloc(cur_result->timestamp);
1244                 curmatch->write_timestamp = stralloc(cur_result->write_timestamp);
1245                 curmatch->hostname = stralloc(cur_result->hostname);
1246                 curmatch->diskname = stralloc(cur_result->diskname);
1247                 curmatch->level = cur_result->level;
1248                 curmatch->label = cur_result->label? stralloc(cur_result->label) : NULL;
1249                 curmatch->filenum = cur_result->filenum;
1250                 curmatch->status = stralloc(cur_result->status);
1251                 curmatch->dump_status = stralloc(cur_result->dump_status);
1252                 curmatch->message = stralloc(cur_result->message);
1253                 curmatch->partnum = cur_result->partnum;
1254                 curmatch->totalparts = cur_result->totalparts;
1255
1256                 curmatch->next = matches;
1257                 matches = curmatch;
1258                 break;
1259             }
1260         }
1261
1262         amfree(zeropad_ts);
1263     }
1264
1265     return(matches);
1266 }
1267
1268 find_result_t *
1269 dump_exist(
1270     find_result_t *output_find,
1271     char *hostname,
1272     char *diskname,
1273     char *datestamp,
1274     int level)
1275 {
1276     find_result_t *output_find_result;
1277
1278     for(output_find_result=output_find;
1279         output_find_result;
1280         output_find_result=output_find_result->next) {
1281         if( !strcmp(output_find_result->hostname, hostname) &&
1282             !strcmp(output_find_result->diskname, diskname) &&
1283             !strcmp(output_find_result->timestamp, datestamp) &&
1284             output_find_result->level == level) {
1285
1286             return output_find_result;
1287         }
1288     }
1289     return(NULL);
1290 }