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