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