Imported Upstream version 3.3.2
[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=NULL;
715     char *datestamp;
716     char *s;
717     int ch;
718     disk_t *dp;
719     GHashTable* valid_label;
720     GHashTable* part_by_dle;
721     find_result_t *part_find;
722     find_result_t *a_part_find;
723     gboolean right_label = FALSE;
724     gboolean found_something = FALSE;
725     double sec;
726     off_t kb;
727     off_t bytes;
728     off_t orig_kb;
729     int   taper_part = 0;
730
731     g_return_val_if_fail(output_find != NULL, 0);
732     g_return_val_if_fail(logfile != NULL, 0);
733
734     if (string_chunk == NULL) {
735         string_chunk = g_string_chunk_new(32768);
736     }
737     valid_label = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
738     part_by_dle = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
739     datestamp = g_strdup(passed_datestamp);
740
741     if((logf = fopen(logfile, "r")) == NULL) {
742         error(_("could not open logfile %s: %s"), logfile, strerror(errno));
743         /*NOTREACHED*/
744     }
745
746     filenum = (off_t)0;
747     while(get_logline(logf)) {
748         if (curlog == L_START && curprog == P_TAPER) {
749             amfree(ck_label);
750             ck_datestamp = NULL;
751             if(parse_taper_datestamp_log(curstr, &ck_datestamp,
752                                          &ck_label) == 0) {
753                 g_printf(_("strange log line in %s \"start taper %s\"\n"),
754                          logfile, curstr);
755                 continue;
756             }
757             if (datestamp != NULL) {
758                 if (strcmp(datestamp, ck_datestamp) != 0) {
759                     g_printf(_("Log file %s stamped %s, expecting %s!\n"),
760                              logfile, ck_datestamp, datestamp);
761                     amfree(ck_label);
762                     break;
763                 }
764             }
765
766             right_label = volume_matches(label, ck_label, ck_datestamp);
767             if (right_label && ck_label) {
768                 g_hash_table_insert(valid_label, g_strdup(ck_label),
769                                     GINT_TO_POINTER(1));
770             }
771             if (label && datestamp && right_label) {
772                 found_something = TRUE;
773             }
774             amfree(current_label);
775             current_label = ck_label;
776             ck_label = NULL;
777             if (datestamp == NULL) {
778                 datestamp = g_strdup(ck_datestamp);
779             }
780             filenum = (off_t)0;
781         }
782         if (!datestamp)
783             continue;
784         if (right_label &&
785             (curlog == L_SUCCESS ||
786              curlog == L_CHUNK || curlog == L_PART || curlog == L_PARTPARTIAL) &&
787             curprog == P_TAPER) {
788             filenum++;
789         } else if (right_label && curlog == L_PARTIAL && curprog == P_TAPER &&
790                    taper_part == 0) {
791             filenum++;
792         }
793         partnum = -1;
794         totalparts = -1;
795         if (curlog == L_SUCCESS || curlog == L_CHUNKSUCCESS ||
796             curlog == L_DONE    || curlog == L_FAIL ||
797             curlog == L_CHUNK   || curlog == L_PART || curlog == L_PARTIAL ||
798             curlog == L_PARTPARTIAL ) {
799             s = curstr;
800             ch = *s++;
801
802             skip_whitespace(s, ch);
803             if(ch == '\0') {
804                 g_printf(_("strange log line in %s \"%s\"\n"),
805                     logfile, curstr);
806                 continue;
807             }
808
809             if (curlog == L_PART || curlog == L_PARTPARTIAL) {
810                 char *part_label;
811                 char *qpart_label = s - 1;
812                 taper_part++;
813                 skip_quoted_string(s, ch);
814                 s[-1] = '\0';
815
816                 part_label = unquote_string(qpart_label);
817                 if (!g_hash_table_lookup(valid_label, part_label)) {
818                     amfree(part_label);
819                     continue;
820                 }
821                 amfree(current_label);
822                 current_label = part_label;
823
824                 skip_whitespace(s, ch);
825                 if(ch == '\0') {
826                     g_printf("strange log line in %s \"%s\"\n",
827                            logfile, curstr);
828                     continue;
829                 }
830
831                 number = s - 1;
832                 skip_non_whitespace(s, ch);
833                 s[-1] = '\0';
834                 fileno = atoi(number);
835                 filenum = fileno;
836                 if (filenum == 0)
837                     continue;
838
839                 skip_whitespace(s, ch);
840                 if(ch == '\0') {
841                     g_printf("strange log line in %s \"%s\"\n",
842                            logfile, curstr);
843                     continue;
844                 }
845             } else {
846                 taper_part = 0;
847             }
848
849             host = s - 1;
850             skip_non_whitespace(s, ch);
851             host_undo = s - 1;
852             *host_undo = '\0';
853
854             skip_whitespace(s, ch);
855             if(ch == '\0') {
856                 g_printf(_("strange log line in %s \"%s\"\n"),
857                     logfile, curstr);
858                 continue;
859             }
860             qdisk = s - 1;
861             skip_quoted_string(s, ch);
862             disk_undo = s - 1;
863             *disk_undo = '\0';
864             disk = unquote_string(qdisk);
865
866             skip_whitespace(s, ch);
867             if(ch == '\0') {
868                 g_printf(_("strange log line in %s \"%s\"\n"),
869                          logfile, curstr);
870                 continue;
871             }
872             date = s - 1;
873             skip_non_whitespace(s, ch);
874             date_undo = s - 1;
875             *date_undo = '\0';
876
877             if(strlen(date) < 3) { /* old log didn't have datestamp */
878                 level = atoi(date);
879                 date = stralloc(datestamp);
880                 partnum = 1;
881                 totalparts = 1;
882             } else {
883                 if (curprog == P_TAPER &&
884                         (curlog == L_CHUNK || curlog == L_PART ||
885                          curlog == L_PARTPARTIAL || curlog == L_PARTIAL ||
886                          curlog == L_DONE)) {
887                     char *s1, ch1;
888                     skip_whitespace(s, ch);
889                     number = s - 1;
890                     skip_non_whitespace(s, ch);
891                     s1 = &s[-1];
892                     ch1 = *s1;
893                     skip_whitespace(s, ch);
894                     if (*(s-1) != '[') {
895                         *s1 = ch1;
896                         sscanf(number, "%d/%d", &partnum, &totalparts);
897                         if (partnum > maxparts)
898                             maxparts = partnum;
899                         if (totalparts > maxparts)
900                             maxparts = totalparts;
901                     } else { /* nparts is not in all PARTIAL lines */
902                         partnum = 1;
903                         totalparts = 1;
904                         s = number + 1;
905                     }
906                 } else {
907                     skip_whitespace(s, ch);
908                 }
909                 if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
910                     g_printf(_("Fstrange log line in %s \"%s\"\n"),
911                     logfile, s-1);
912                     continue;
913                 }
914                 skip_integer(s, ch);
915             }
916
917             skip_whitespace(s, ch);
918             if(ch == '\0') {
919                 g_printf(_("strange log line in %s \"%s\"\n"),
920                     logfile, curstr);
921                 continue;
922             }
923             rest = s - 1;
924             skip_non_whitespace(s, ch);
925             rest_undo = s - 1;
926             *rest_undo = '\0';
927             if (strcmp(rest, "[sec") == 0) {
928                 skip_whitespace(s, ch);
929                 if(ch == '\0') {
930                     g_printf(_("strange log line in %s \"%s\"\n"),
931                              logfile, curstr);
932                     continue;
933                 }
934                 sec = atof(s - 1);
935                 skip_non_whitespace(s, ch);
936                 skip_whitespace(s, ch);
937                 rest = s - 1;
938                 skip_non_whitespace(s, ch);
939                 rest_undo = s - 1;
940                 *rest_undo = '\0';
941                 if (strcmp(rest, "kb") != 0 &&
942                     strcmp(rest, "bytes") != 0) {
943                     g_printf(_("Bstrange log line in %s \"%s\"\n"),
944                              logfile, curstr);
945                     continue;
946                 }
947
948                 skip_whitespace(s, ch);
949                 if (ch == '\0') {
950                      g_printf(_("strange log line in %s \"%s\"\n"),
951                               logfile, curstr);
952                      continue;
953                 }
954                 if (strcmp(rest, "kb") == 0) {
955                     kb = atof(s - 1);
956                     bytes = 0;
957                 } else {
958                     bytes = atof(s - 1);
959                     kb = bytes / 1024;
960                 }
961                 skip_non_whitespace(s, ch);
962                 skip_whitespace(s, ch);
963                 rest = s - 1;
964                 skip_non_whitespace(s, ch);
965                 rest_undo = s - 1;
966                 *rest_undo = '\0';
967                 if (strcmp(rest, "kps") != 0) {
968                     g_printf(_("Cstrange log line in %s \"%s\"\n"),
969                              logfile, curstr);
970                     continue;
971                 }
972
973                 skip_whitespace(s, ch);
974                 if (ch == '\0') {
975                     g_printf(_("strange log line in %s \"%s\"\n"),
976                              logfile, curstr);
977                     continue;
978                 }
979                 /* kps = atof(s - 1); */
980                 skip_non_whitespace(s, ch);
981                 skip_whitespace(s, ch);
982                 rest = s - 1;
983                 skip_non_whitespace(s, ch);
984                 rest_undo = s - 1;
985                 *rest_undo = '\0';
986                 if (strcmp(rest, "orig-kb") != 0) {
987                     orig_kb = 0;
988                 } else {
989
990                     skip_whitespace(s, ch);
991                     if(ch == '\0') {
992                         g_printf(_("strange log line in %s \"%s\"\n"),
993                                  logfile, curstr);
994                         continue;
995                     }
996                     orig_kb = atof(s - 1);
997                 }
998             } else {
999                 sec = 0;
1000                 kb = 0;
1001                 bytes = 0;
1002                 orig_kb = 0;
1003                 *rest_undo = ' ';
1004             }
1005
1006             if (strncmp(rest, "error", 5) == 0) rest += 6;
1007             if (strncmp(rest, "config", 6) == 0) rest += 7;
1008
1009             dp = lookup_disk(host,disk);
1010             if ( dp == NULL ) {
1011                 if (dynamic_disklist == NULL) {
1012                     continue;
1013                 }
1014                 dp = add_disk(dynamic_disklist, host, disk);
1015                 enqueue_disk(dynamic_disklist, dp);
1016             }
1017             if (find_match(host, disk)) {
1018                 if(curprog == P_TAPER) {
1019                     char *key = g_strdup_printf(
1020                                         "HOST:%s DISK:%s: DATE:%s LEVEL:%d",
1021                                         host, disk, date, level);
1022                     find_result_t *new_output_find = g_new0(find_result_t, 1);
1023                     part_find = g_hash_table_lookup(part_by_dle, key);
1024                     maxparts = partnum;
1025                     if (maxparts < totalparts)
1026                         maxparts = totalparts;
1027                     for (a_part_find = part_find;
1028                          a_part_find;
1029                          a_part_find = a_part_find->next) {
1030                         if (maxparts < a_part_find->partnum)
1031                             maxparts = a_part_find->partnum;
1032                         if (maxparts < a_part_find->totalparts)
1033                             maxparts = a_part_find->totalparts;
1034                     }
1035                     new_output_find->timestamp = g_string_chunk_insert_const(string_chunk, date);
1036                     new_output_find->write_timestamp = g_string_chunk_insert_const(string_chunk, datestamp);
1037                     new_output_find->hostname=g_string_chunk_insert_const(string_chunk, host);
1038                     new_output_find->diskname=g_string_chunk_insert_const(string_chunk, disk);
1039                     new_output_find->level=level;
1040                     new_output_find->partnum = partnum;
1041                     new_output_find->totalparts = totalparts;
1042                     new_output_find->label=g_string_chunk_insert_const(string_chunk, current_label);
1043                     new_output_find->status=NULL;
1044                     new_output_find->dump_status=NULL;
1045                     new_output_find->message="";
1046                     new_output_find->filenum=filenum;
1047                     new_output_find->sec=sec;
1048                     new_output_find->kb=kb;
1049                     new_output_find->bytes=bytes;
1050                     new_output_find->orig_kb=orig_kb;
1051                     new_output_find->next=NULL;
1052                     if (curlog == L_SUCCESS) {
1053                         new_output_find->status = "OK";
1054                         new_output_find->dump_status = "OK";
1055                         new_output_find->next = *output_find;
1056                         new_output_find->partnum = 1; /* L_SUCCESS is pre-splitting */
1057                         *output_find = new_output_find;
1058                         found_something = TRUE;
1059                     } else if (curlog == L_CHUNKSUCCESS || curlog == L_DONE ||
1060                                curlog == L_PARTIAL      || curlog == L_FAIL) {
1061                         /* result line */
1062                         if (curlog == L_PARTIAL || curlog == L_FAIL) {
1063                             /* set dump_status of each part */
1064                             for (a_part_find = part_find;
1065                                  a_part_find;
1066                                  a_part_find = a_part_find->next) {
1067                                 if (curlog == L_PARTIAL)
1068                                     a_part_find->dump_status = "PARTIAL";
1069                                 else {
1070                                     a_part_find->dump_status = "FAIL";
1071                                     a_part_find->message = g_string_chunk_insert_const(string_chunk, rest);
1072                                 }
1073                             }
1074                         } else {
1075                             if (maxparts > -1) { /* format with part */
1076                                 /* must check if all part are there */
1077                                 int num_part = maxparts;
1078                                 for (a_part_find = part_find;
1079                                      a_part_find;
1080                                      a_part_find = a_part_find->next) {
1081                                     if (a_part_find->partnum == num_part &&
1082                                         strcmp(a_part_find->status, "OK") == 0)
1083                                         num_part--;
1084                                 }
1085                                 /* set dump_status of each part */
1086                                 for (a_part_find = part_find;
1087                                      a_part_find;
1088                                      a_part_find = a_part_find->next) {
1089                                     if (num_part == 0) {
1090                                         a_part_find->dump_status = "OK";
1091                                     } else {
1092                                         a_part_find->dump_status = "FAIL";
1093                                         a_part_find->message =
1094                                                 g_string_chunk_insert_const(string_chunk, "Missing part");
1095                                     }
1096                                 }
1097                             }
1098                         }
1099                         if (curlog == L_DONE) {
1100                             for (a_part_find = part_find;
1101                                  a_part_find;
1102                                  a_part_find = a_part_find->next) {
1103                                 if (a_part_find->totalparts == -1) {
1104                                     a_part_find->totalparts = maxparts;
1105                                 }
1106                                 if (a_part_find->orig_kb == 0) {
1107                                     a_part_find->orig_kb = orig_kb;
1108                                 }
1109                             }
1110                         }
1111                         if (part_find) { /* find last element */
1112                             for (a_part_find = part_find;
1113                                  a_part_find->next != NULL;
1114                                  a_part_find=a_part_find->next) {
1115                             }
1116                             /* merge part_find to *output_find */
1117                             a_part_find->next = *output_find;
1118                             *output_find = part_find;
1119                             part_find = NULL;
1120                             maxparts = -1;
1121                             found_something = TRUE;
1122                             g_hash_table_remove(part_by_dle, key);
1123                         }
1124                         free_find_result(&new_output_find);
1125                     } else { /* part line */
1126                         if (curlog == L_PART || curlog == L_CHUNK) {
1127                             new_output_find->status = "OK";
1128                             new_output_find->dump_status = "OK";
1129                         } else { /* PARTPARTIAL */
1130                             new_output_find->status = "PARTIAL";
1131                             new_output_find->dump_status = "PARTIAL";
1132                         }
1133                         /* Add to part_find list */
1134                         if (part_find) {
1135                             new_output_find->next = part_find;
1136                             part_find = new_output_find;
1137                         } else {
1138                             new_output_find->next = NULL;
1139                             part_find = new_output_find;
1140                         }
1141                         g_hash_table_insert(part_by_dle, g_strdup(key),
1142                                             part_find);
1143                         found_something = TRUE;
1144                     }
1145                     amfree(key);
1146                 }
1147                 else if(curlog == L_FAIL) {
1148                     char *status_failed;
1149                     /* print other failures too -- this is a hack to ensure that failures which
1150                      * did not make it to tape are also listed in the output of 'amadmin x find';
1151                      * users that do not want this information (e.g., Amanda::DB::Catalog) should
1152                      * filter dumps with a NULL label. */
1153                     find_result_t *new_output_find = g_new0(find_result_t, 1);
1154                     new_output_find->next = *output_find;
1155                     new_output_find->timestamp = g_string_chunk_insert_const(string_chunk, date);
1156                     new_output_find->write_timestamp = g_strdup("00000000000000"); /* dump was not written.. */
1157                     new_output_find->hostname=g_string_chunk_insert_const(string_chunk, host);
1158                     new_output_find->diskname=g_string_chunk_insert_const(string_chunk, disk);
1159                     new_output_find->level=level;
1160                     new_output_find->label=NULL;
1161                     new_output_find->partnum=partnum;
1162                     new_output_find->totalparts=totalparts;
1163                     new_output_find->filenum=0;
1164                     new_output_find->sec=sec;
1165                     new_output_find->kb=kb;
1166                     new_output_find->bytes=bytes;
1167                     new_output_find->orig_kb=orig_kb;
1168                     status_failed = vstralloc(
1169                          "FAILED (",
1170                          program_str[(int)curprog],
1171                          ") ",
1172                          rest,
1173                          NULL);
1174                     new_output_find->status = g_string_chunk_insert_const(string_chunk, status_failed);
1175                     amfree(status_failed);
1176                     new_output_find->dump_status="";
1177                     new_output_find->message="";
1178                     *output_find=new_output_find;
1179                     found_something = TRUE;
1180                     maxparts = -1;
1181                 }
1182             }
1183             amfree(disk);
1184         }
1185     }
1186
1187     g_hash_table_destroy(valid_label);
1188     afclose(logf);
1189     amfree(datestamp);
1190     amfree(current_label);
1191
1192     return found_something;
1193 }
1194
1195
1196 /*
1197  * Return the set of dumps that match *all* of the given patterns (we consider
1198  * an empty pattern to match .*, though).  If 'ok' is true, will only match
1199  * dumps with SUCCESS status.
1200  *
1201  * Returns a newly allocated list of results, where all strings are also newly
1202  * allocated.  Apparently some part of Amanda leaks under this condition.
1203  */
1204 find_result_t *
1205 dumps_match(
1206     find_result_t *output_find,
1207     char *hostname,
1208     char *diskname,
1209     char *datestamp,
1210     char *level,
1211     int ok)
1212 {
1213     find_result_t *cur_result;
1214     find_result_t *matches = NULL;
1215
1216     for(cur_result=output_find;
1217         cur_result;
1218         cur_result=cur_result->next) {
1219         char level_str[NUM_STR_SIZE];
1220         g_snprintf(level_str, SIZEOF(level_str), "%d", cur_result->level);
1221         if((!hostname || *hostname == '\0' || match_host(hostname, cur_result->hostname)) &&
1222            (!diskname || *diskname == '\0' || match_disk(diskname, cur_result->diskname)) &&
1223            (!datestamp || *datestamp== '\0' || match_datestamp(datestamp, cur_result->timestamp)) &&
1224            (!level || *level== '\0' || match_level(level, level_str)) &&
1225            (!ok || !strcmp(cur_result->status, "OK")) &&
1226            (!ok || !strcmp(cur_result->dump_status, "OK"))){
1227
1228             find_result_t *curmatch = g_new0(find_result_t, 1);
1229             memcpy(curmatch, cur_result, SIZEOF(find_result_t));
1230
1231             curmatch->timestamp = cur_result->timestamp;
1232             curmatch->write_timestamp = cur_result->write_timestamp;
1233             curmatch->hostname = cur_result->hostname;
1234             curmatch->diskname = cur_result->diskname;
1235             curmatch->level = cur_result->level;
1236             curmatch->label = cur_result->label? cur_result->label : NULL;
1237             curmatch->filenum = cur_result->filenum;
1238             curmatch->sec = cur_result->sec;
1239             curmatch->kb = cur_result->kb;
1240             curmatch->bytes = cur_result->bytes;
1241             curmatch->orig_kb = cur_result->orig_kb;
1242             curmatch->status = cur_result->status;
1243             curmatch->dump_status = cur_result->dump_status;
1244             curmatch->message = cur_result->message;
1245             curmatch->partnum = cur_result->partnum;
1246             curmatch->totalparts = cur_result->totalparts;
1247             curmatch->next = matches;
1248             matches = curmatch;
1249         }
1250     }
1251
1252     return(matches);
1253 }
1254
1255 /*
1256  * Return the set of dumps that match one or more of the given dumpspecs,
1257  * If 'ok' is true, only dumps with a SUCCESS status will be matched.
1258  * 
1259  * Returns a newly allocated list of results, where all strings are also newly
1260  * allocated.  Apparently some part of Amanda leaks under this condition.
1261  */
1262 find_result_t *
1263 dumps_match_dumpspecs(
1264     find_result_t *output_find,
1265     GSList        *dumpspecs,
1266     int ok)
1267 {
1268     find_result_t *cur_result;
1269     find_result_t *matches = NULL;
1270     GSList        *dumpspec;
1271     dumpspec_t    *ds;
1272
1273     for(cur_result=output_find;
1274         cur_result;
1275         cur_result=cur_result->next) {
1276         char level_str[NUM_STR_SIZE];
1277         char *zeropad_ts = NULL;
1278         char *zeropad_w_ts = NULL;
1279         g_snprintf(level_str, SIZEOF(level_str), "%d", cur_result->level);
1280
1281         /* get the timestamp padded to full width */
1282         if (strlen(cur_result->timestamp) < 14) {
1283             zeropad_ts = g_new0(char, 15);
1284             memset(zeropad_ts, '0', 14);
1285             memcpy(zeropad_ts, cur_result->timestamp, strlen(cur_result->timestamp));
1286         }
1287         if (strlen(cur_result->write_timestamp) < 14) {
1288             zeropad_w_ts = g_new0(char, 15);
1289             memset(zeropad_w_ts, '0', 14);
1290             memcpy(zeropad_w_ts, cur_result->write_timestamp, strlen(cur_result->write_timestamp));
1291         }
1292
1293         for (dumpspec = dumpspecs; dumpspec; dumpspec = dumpspec->next) {
1294             ds = (dumpspec_t *)dumpspec->data;
1295             if((!ds->host || *ds->host == '\0' || match_host(ds->host, cur_result->hostname)) &&
1296                (!ds->disk || *ds->disk == '\0' || match_disk(ds->disk, cur_result->diskname)) &&
1297                (!ds->datestamp || *ds->datestamp== '\0'
1298                         || match_datestamp(ds->datestamp, cur_result->timestamp)
1299                         || (zeropad_ts && match_datestamp(ds->datestamp, zeropad_ts))) &&
1300                (!ds->write_timestamp || *ds->write_timestamp== '\0'
1301                         || match_datestamp(ds->write_timestamp, cur_result->write_timestamp)
1302                         || (zeropad_w_ts && match_datestamp(ds->write_timestamp, zeropad_w_ts))) &&
1303                (!ds->level || *ds->level== '\0' || match_level(ds->level, level_str)) &&
1304                (!ok || !strcmp(cur_result->status, "OK")) &&
1305                (!ok || !strcmp(cur_result->dump_status, "OK"))) {
1306
1307                 find_result_t *curmatch = alloc(SIZEOF(find_result_t));
1308                 memcpy(curmatch, cur_result, SIZEOF(find_result_t));
1309
1310                 curmatch->timestamp = cur_result->timestamp;
1311                 curmatch->write_timestamp = cur_result->write_timestamp;
1312                 curmatch->hostname = cur_result->hostname;
1313                 curmatch->diskname = cur_result->diskname;
1314                 curmatch->level = cur_result->level;
1315                 curmatch->label = cur_result->label? cur_result->label : NULL;
1316                 curmatch->filenum = cur_result->filenum;
1317                 curmatch->status = cur_result->status;
1318                 curmatch->dump_status =  cur_result->dump_status;
1319                 curmatch->message = cur_result->message;
1320                 curmatch->partnum = cur_result->partnum;
1321                 curmatch->totalparts = cur_result->totalparts;
1322
1323                 curmatch->next = matches;
1324                 matches = curmatch;
1325                 break;
1326             }
1327         }
1328
1329         amfree(zeropad_ts);
1330     }
1331
1332     return(matches);
1333 }
1334
1335 find_result_t *
1336 dump_exist(
1337     find_result_t *output_find,
1338     char *hostname,
1339     char *diskname,
1340     char *datestamp,
1341     int level)
1342 {
1343     find_result_t *output_find_result;
1344
1345     for(output_find_result=output_find;
1346         output_find_result;
1347         output_find_result=output_find_result->next) {
1348         if( !strcmp(output_find_result->hostname, hostname) &&
1349             !strcmp(output_find_result->diskname, diskname) &&
1350             !strcmp(output_find_result->timestamp, datestamp) &&
1351             output_find_result->level == level) {
1352
1353             return output_find_result;
1354         }
1355     }
1356     return(NULL);
1357 }