Imported Upstream version 2.5.2p1
[debian/amanda] / server-src / holding.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  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /*
27  * $Id: holding.c,v 1.56 2006/06/09 23:07:26 martinea Exp $
28  *
29  * Functions to access holding disk
30  */
31
32 #include "amanda.h"
33 #include "util.h"
34 #include "holding.h"
35 #include "fileheader.h"
36 #include "logfile.h"
37
38 /*
39  * utilities */
40
41 /* Is fname a directory?
42  *
43  * @param fname: filename (fully qualified)
44  * @returns: boolean
45  */
46 static int is_dir(char *fname);
47
48 /* sanity check that datestamp is of the form YYYYMMDD or 
49  * YYYYMMDDhhmmss
50  *
51  * @param fname: a filename (without directory)
52  * @returns: boolean
53  */
54 static int is_datestr(char *fname);
55
56 /*
57  * Static variables */
58 static int verbose = 0;
59
60 /*
61  * Static functions */
62
63 static int
64 is_dir(
65     char *fname)
66 {
67     struct stat statbuf;
68
69     if(stat(fname, &statbuf) == -1) return 0;
70
71     return (statbuf.st_mode & S_IFDIR) == S_IFDIR;
72 }
73
74 static int
75 is_emptyfile(
76     char *fname)
77 {
78     struct stat statbuf;
79
80     if(stat(fname, &statbuf) == -1) return 0;
81
82     return ((statbuf.st_mode & S_IFDIR) != S_IFDIR) &&
83                 (statbuf.st_size == (off_t)0);
84 }
85
86 static int
87 is_datestr(
88     char *fname)
89 {
90     char *cp;
91     int ch, num, date, year, month, hour, minute, second;
92     char ymd[9], hms[7];
93
94     /* must be 8 digits */
95     for(cp = fname; (ch = *cp) != '\0'; cp++) {
96         if(!isdigit(ch)) {
97             break;
98         }
99     }
100     if(ch != '\0' || (cp-fname != 8 && cp-fname != 14)) {
101         return 0;
102     }
103
104     /* sanity check year, month, and day */
105
106     strncpy(ymd, fname, 8);
107     ymd[8] = '\0';
108     num = atoi(ymd);
109     year = num / 10000;
110     month = (num / 100) % 100;
111     date = num % 100;
112     if(year<1990 || year>2100 || month<1 || month>12 || date<1 || date>31)
113         return 0;
114
115     if(cp-fname == 8)
116         return 1;
117
118     /* sanity check hour, minute, and second */
119     strncpy(hms, fname+8, 6);
120     hms[6] = '\0';
121     num = atoi(hms);
122     hour = num / 10000;
123     minute = (num / 100) % 100;
124     second = num % 100;
125     if(hour> 23 || minute>59 || second>59)
126         return 0;
127
128     /* yes, we passed all the checks */
129
130     return 1;
131 }
132
133 /*
134  * Verbosity
135  */
136 int
137 holding_set_verbosity(int v)
138 {
139     int old = verbose;
140     verbose = v;
141     return old;
142 }
143
144 /*
145  * Holding directories
146  */
147
148 static void
149 holding_get_directories_per_disk(
150     char *hdisk,
151     sl_t *date_list,
152     int fullpaths,
153     sl_t *rv)
154 {
155     DIR *dir;
156     struct dirent *workdir;
157     char *hdir = NULL;
158     sle_t *dl;
159     int date_found;
160
161     if ((dir = opendir(hdisk)) == NULL) {
162         if (verbose && errno != ENOENT)
163            printf(_("Warning: could not open holding disk %s: %s\n"),
164                   hdisk, strerror(errno));
165         return;
166     }
167
168     if (verbose)
169         printf(_("Scanning %s...\n"), hdisk);
170
171     while ((workdir = readdir(dir)) != NULL) {
172         if (is_dot_or_dotdot(workdir->d_name))
173             continue;
174
175         if(verbose) 
176             printf("  %s: ", workdir->d_name);
177             
178         hdir = newvstralloc(hdir,
179                      hdisk, "/", workdir->d_name,
180                      NULL);
181
182         /* filter out various undesirables */
183         if (!is_dir(hdir)) {
184             if (verbose)
185                 puts(_("skipping cruft file, perhaps you should delete it."));
186         } else if (!is_datestr(workdir->d_name)) {
187             /* EXT2/3 leave these in the root of each volume */
188             if (strcmp(workdir->d_name, "lost+found")==0)
189                 puts(_("skipping system directory"));
190             if (verbose)
191                 puts(_("skipping cruft directory, perhaps you should delete it."));
192         } else {
193             /* found a holding directory -- keep it */
194             if (date_list) {
195                 date_found = 0;
196                 for (dl= date_list->first; dl != NULL; dl = dl->next) {
197                     if (strcmp(dl->name, workdir->d_name) == 0) {
198                         date_found = 1;
199                         break;
200                     }
201                 }
202             } else {
203                 date_found = 1;
204             }
205             if (date_found == 1) {
206                 if (fullpaths)
207                     rv = insert_sort_sl(rv, hdir);
208                 else
209                     rv = insert_sort_sl(rv, workdir->d_name);
210                 if (verbose) {
211                     puts(_("found Amanda directory."));
212                 }
213             }
214         }
215     }
216
217     if (hdir) amfree(hdir);
218 }
219
220
221 sl_t *
222 holding_get_directories(
223     char *hdisk,
224     sl_t *date_list,
225     int fullpaths)
226 {
227     holdingdisk_t *hdisk_conf;
228     sl_t *rv;
229
230     rv = new_sl();
231     if (!rv) {
232         return NULL;
233     }
234
235     /* call _per_disk for the hdisk we were given, or for all
236      * hdisks if we were given NULL */
237     if (hdisk) {
238         holding_get_directories_per_disk(hdisk, date_list, fullpaths, rv);
239     } else {
240         for (hdisk_conf = getconf_holdingdisks(); 
241                     hdisk_conf != NULL;
242                     hdisk_conf = hdisk_conf->next) {
243             hdisk = holdingdisk_get_diskdir(hdisk_conf);
244             holding_get_directories_per_disk(hdisk, date_list, fullpaths, rv);
245         }
246     }
247
248     return rv;
249 }
250
251 /*
252  * Holding files
253  */
254 static void
255 holding_get_files_per_dir(
256     char *hdir,
257     int fullpaths,
258     sl_t *rv)
259 {
260     DIR *dir;
261     struct dirent *workdir;
262     char *hfile = NULL;
263     dumpfile_t dumpf;
264     int dumpf_ok;
265
266     if ((dir = opendir(hdir)) == NULL) {
267         if (verbose && errno != ENOENT)
268            printf(_("Warning: could not open holding dir %s: %s\n"),
269                   hdir, strerror(errno));
270         return;
271     }
272
273     if (verbose)
274         printf(_("Scanning %s...\n"), hdir);
275
276     while ((workdir = readdir(dir)) != NULL) {
277         if (is_dot_or_dotdot(workdir->d_name))
278             continue;
279
280         hfile = newvstralloc(hfile,
281                      hdir, "/", workdir->d_name,
282                      NULL);
283
284         /* filter out various undesirables */
285         if (is_emptyfile(hfile))
286             continue;
287
288         if (is_dir(hfile)) {
289             if (verbose)
290                 printf(_("%s: ignoring directory\n"), hfile);
291             continue;
292         }
293
294         if (!(dumpf_ok=holding_file_get_dumpfile(hfile, &dumpf)) ||
295             dumpf.type != F_DUMPFILE) {
296             if (dumpf_ok && dumpf.type == F_CONT_DUMPFILE)
297                 continue; /* silently skip expected file */
298             if (verbose)
299                 printf(_("%s: not a dumpfile\n"), hfile);
300             continue;
301         }
302
303         if (dumpf.dumplevel < 0 || dumpf.dumplevel > 9) {
304             if (verbose)
305                 printf(_("%s: ignoring file with bogus dump level %d.\n"),
306                        hfile, dumpf.dumplevel);
307             continue;
308         }
309
310         /* found a holding file -- keep it */
311         if (fullpaths)
312             rv = insert_sort_sl(rv, hfile);
313         else
314             rv = insert_sort_sl(rv, workdir->d_name);
315     }
316
317     if (hfile) amfree(hfile);
318 }
319
320 sl_t *
321 holding_get_files(
322     char *hdir,
323     sl_t *date_list,
324     int fullpaths)
325 {
326     sl_t *hdirs;
327     sle_t *e;
328     sl_t *rv;
329
330     rv = new_sl();
331     if (!rv) {
332         return NULL;
333     }
334
335     /* call _per_dir for the hdir we were given, or for all
336      * hdir if we were given NULL */
337     if (hdir) {
338         holding_get_files_per_dir(hdir, fullpaths, rv);
339     } else {
340         hdirs = holding_get_directories(NULL, date_list, 1);
341         for (e = hdirs->first; e != NULL; e = e->next) {
342             holding_get_files_per_dir(e->name, fullpaths, rv);
343         }
344     }
345
346     return rv;
347 }
348
349 sl_t *
350 holding_get_files_for_flush(
351     sl_t *dateargs,
352     int interactive)
353 {
354     sl_t *date_list;
355     sl_t *file_list;
356     sl_t *result_list;
357     sle_t *datearg;
358     sle_t *date, *next_date;
359     sle_t *file_elt;
360     disk_t *dp;
361     char *host;
362     char *disk;
363     char *datestamp;
364     filetype_t filetype;
365
366     /* make date_list the intersection of available holding directories and
367      * the dateargs parameter.  */
368     if (dateargs) {
369         int ok;
370
371         date_list = pick_all_datestamp(verbose);
372         for (date = date_list->first; date != NULL;) {
373             next_date = date->next;
374             ok = 0;
375             for(datearg=dateargs->first; datearg != NULL && ok==0;
376                 datearg = datearg->next) {
377                 ok = match_datestamp(datearg->name, date->name);
378             }
379             if(ok == 0) { /* remove dir */
380                 remove_sl(date_list, date);
381             }
382             date = next_date;
383         }
384     }
385     else {
386         /* no date args were provided, so use everything */
387         if (interactive)
388             date_list = pick_datestamp(verbose);
389         else
390             date_list = pick_all_datestamp(verbose);
391     }
392
393     result_list = new_sl();
394     if (!result_list) {
395         return NULL;
396     }
397
398     /* loop over *all* files, checking each one */
399     file_list = holding_get_files(NULL, date_list, 1);
400     for (file_elt = file_list->first; file_elt != NULL; file_elt = file_elt->next) {
401         /* get info on that file */
402         filetype = holding_file_read_header(file_elt->name, &host, &disk, NULL, &datestamp);
403         if (filetype != F_DUMPFILE)
404             continue;
405
406         /* check that the hostname and disk are in the disklist */
407         dp = lookup_disk(host, disk);
408         if (dp == NULL) {
409             if (verbose)
410                 printf(_("%s: disk %s:%s not in database, skipping it."),
411                         file_elt->name, host, disk);
412             continue;
413         }
414
415         /* passed all tests -- we'll flush this file */
416         result_list = insert_sort_sl(result_list, file_elt->name);
417     }
418
419     if (date_list) free_sl(date_list);
420     if (file_list) free_sl(file_list);
421
422     return result_list;
423 }
424
425 sl_t *
426 holding_get_file_chunks(char *hfile)
427 {
428     dumpfile_t file;
429     char *filename;
430     sl_t *rv = new_sl();
431
432     if (!rv) {
433         return NULL;
434     }
435
436     /* Loop through all cont_filenames (subsequent chunks) */
437     filename = stralloc(hfile);
438     while (filename != NULL && filename[0] != '\0') {
439         /* get the header to look for cont_filename */
440         if (!holding_file_get_dumpfile(filename, &file)) {
441             if (verbose)
442                 printf(_("holding_get_file_chunks: open of %s failed.\n"), filename);
443             amfree(filename);
444             return rv;
445         }
446
447         /* add the file to the results */
448         insert_sort_sl(rv, filename);
449
450         /* and go on to the next chunk */
451         filename = newstralloc(filename, file.cont_filename);
452     }
453     amfree(filename);
454     return rv;
455 }
456
457 off_t
458 holding_file_size(
459     char *hfile,
460     int strip_headers)
461 {
462     dumpfile_t file;
463     char *filename;
464     off_t size = (off_t)0;
465     struct stat finfo;
466
467     /* (note: we don't use holding_get_file_chunks here because that would
468      * entail opening each file twice) */
469
470     /* Loop through all cont_filenames (subsequent chunks) */
471     filename = stralloc(hfile);
472     while (filename != NULL && filename[0] != '\0') {
473         /* stat the file for its size */
474         if (stat(filename, &finfo) == -1) {
475             if (verbose)
476                 printf(_("stat %s: %s\n"), filename, strerror(errno));
477             return (off_t)-1;
478         }
479         size += (finfo.st_size+(off_t)1023)/(off_t)1024;
480         if (strip_headers)
481             size -= (off_t)(DISK_BLOCK_BYTES / 1024);
482
483         /* get the header to look for cont_filename */
484         if (!holding_file_get_dumpfile(filename, &file)) {
485             if (verbose)
486                 printf(_("holding_file_size: open of %s failed.\n"), filename);
487             amfree(filename);
488             return (off_t)-1;
489         }
490
491         /* on to the next chunk */
492         filename = newstralloc(filename, file.cont_filename);
493     }
494     amfree(filename);
495     return size;
496 }
497
498
499 int
500 holding_file_unlink(
501     char *hfile)
502 {
503     sl_t *chunklist;
504     sle_t *chunk;
505
506     chunklist = holding_get_file_chunks(hfile);
507     if (!chunklist)
508         return 0;
509
510     for (chunk = chunklist->first; chunk != NULL; chunk = chunk->next) {
511         if (unlink(chunk->name)<0) {
512             if (verbose)
513                 printf(_("holding_file_unlink: could not unlink %s: %s\n"),
514                     chunk->name, strerror(errno));
515             return 0;
516         }
517     }
518
519     return 1;
520 }
521
522 filetype_t
523 holding_file_read_header( 
524     char *      fname,
525     char **     hostname,
526     char **     diskname,
527     int *       level,
528     char ** datestamp)
529 {
530     dumpfile_t file;
531
532     if (hostname) *hostname = NULL;
533     if (diskname) *diskname = NULL;
534     if (datestamp) *datestamp = NULL;
535
536     if (!holding_file_get_dumpfile(fname, &file)) {
537         return F_UNKNOWN;
538     }
539
540     if(file.type != F_DUMPFILE && file.type != F_CONT_DUMPFILE) {
541         return file.type;
542     }
543
544     if (hostname) *hostname = stralloc(file.name);
545     if (diskname) *diskname = stralloc(file.disk);
546     if (level) *level = file.dumplevel;
547     if (datestamp) *datestamp = stralloc(file.datestamp);
548
549     return file.type;
550 }
551
552
553 int
554 holding_file_get_dumpfile(
555     char *      fname,
556     dumpfile_t *file)
557 {
558     char buffer[DISK_BLOCK_BYTES];
559     int fd;
560
561     memset(buffer, 0, sizeof(buffer));
562
563     fh_init(file);
564     file->type = F_UNKNOWN;
565     if((fd = open(fname, O_RDONLY)) == -1)
566         return 0;
567
568     if(fullread(fd, buffer, SIZEOF(buffer)) != (ssize_t)sizeof(buffer)) {
569         aclose(fd);
570         return 0;
571     }
572     aclose(fd);
573
574     parse_file_header(buffer, file, SIZEOF(buffer));
575     return 1;
576 }
577
578 /*
579  * Interactive functions 
580  */
581
582 sl_t *
583 pick_all_datestamp(
584     int v)
585 {
586     int old_verbose = holding_set_verbosity(v);
587     sl_t *rv;
588
589     /* get all holding directories, without full paths -- this
590      * will be datestamps only */
591     rv = holding_get_directories(NULL, NULL, 0);
592
593     holding_set_verbosity(old_verbose);
594     return rv;
595 }
596
597 sl_t *
598 pick_datestamp(
599     int         verbose)
600 {
601     sl_t *holding_list;
602     sl_t *r_holding_list = NULL;
603     sle_t *dir;
604     char **directories = NULL;
605     int i;
606     char *answer = NULL;
607     char *a = NULL;
608     int ch = 0;
609     char max_char = '\0', chupper = '\0';
610
611     holding_list = pick_all_datestamp(verbose);
612
613     if(holding_list->nb_element == 0) {
614         return holding_list;
615     }
616     else if(holding_list->nb_element == 1 || !verbose) {
617         return holding_list;
618     }
619     else {
620         directories = alloc((holding_list->nb_element) * SIZEOF(char *));
621         for(dir = holding_list->first, i=0; dir != NULL; dir = dir->next,i++) {
622             directories[i] = dir->name;
623         }
624
625         while(1) {
626             puts(_("\nMultiple Amanda directories, please pick one by letter:"));
627             for(dir = holding_list->first, max_char = 'A';
628                 dir != NULL && max_char <= 'Z';
629                 dir = dir->next, max_char++) {
630                 printf("  %c. %s\n", max_char, dir->name);
631             }
632             max_char--;
633             printf(_("Select directories to flush [A..%c]: [ALL] "), max_char);
634             fflush(stdout); fflush(stderr);
635             amfree(answer);
636             if ((answer = agets(stdin)) == NULL) {
637                 clearerr(stdin);
638                 continue;
639             }
640
641             if (*answer == '\0' || strncasecmp(answer, "ALL", 3) == 0) {
642                 break;
643             }
644
645             a = answer;
646             while ((ch = *a++) != '\0') {
647                 if (!isspace(ch))
648                     break;
649             }
650
651             do {
652                 if (isspace(ch) || ch == ',') {
653                     continue;
654                 }
655                 chupper = (char)toupper(ch);
656                 if (chupper < 'A' || chupper > max_char) {
657                     free_sl(r_holding_list);
658                     r_holding_list = NULL;
659                     break;
660                 }
661                 r_holding_list = append_sl(r_holding_list,
662                                            directories[chupper - 'A']);
663             } while ((ch = *a++) != '\0');
664             if (r_holding_list && ch == '\0') {
665                 free_sl(holding_list);
666                 holding_list = r_holding_list;
667                 break;
668             }
669         }
670     }
671     amfree(directories);
672     amfree(answer);
673     return holding_list;
674 }
675
676 /*
677  * Application support
678  */
679
680 int
681 rename_tmp_holding(
682     char *      holding_file,
683     int         complete)
684 {
685     int fd;
686     ssize_t buflen;
687     char buffer[DISK_BLOCK_BYTES];
688     dumpfile_t file;
689     char *filename;
690     char *filename_tmp = NULL;
691
692     memset(buffer, 0, sizeof(buffer));
693     filename = stralloc(holding_file);
694     while(filename != NULL && filename[0] != '\0') {
695         filename_tmp = newvstralloc(filename_tmp, filename, ".tmp", NULL);
696         if((fd = open(filename_tmp,O_RDONLY)) == -1) {
697             fprintf(stderr,_("rename_tmp_holding: open of %s failed: %s\n"),filename_tmp,strerror(errno));
698             amfree(filename);
699             amfree(filename_tmp);
700             return 0;
701         }
702         buflen = fullread(fd, buffer, SIZEOF(buffer));
703         close(fd);
704
705         if(rename(filename_tmp, filename) != 0) {
706             fprintf(stderr,
707                     _("rename_tmp_holding: could not rename \"%s\" to \"%s\": %s"),
708                     filename_tmp, filename, strerror(errno));
709         }
710
711         if (buflen <= 0) {
712             fprintf(stderr,_("rename_tmp_holding: %s: empty file?\n"), filename);
713             amfree(filename);
714             amfree(filename_tmp);
715             return 0;
716         }
717         parse_file_header(buffer, &file, (size_t)buflen);
718         if(complete == 0 ) {
719             if((fd = open(filename, O_RDWR)) == -1) {
720                 fprintf(stderr, _("rename_tmp_holdingX: open of %s failed: %s\n"),
721                         filename, strerror(errno));
722                 amfree(filename);
723                 amfree(filename_tmp);
724                 return 0;
725
726             }
727             file.is_partial = 1;
728             build_header(buffer, &file, SIZEOF(buffer));
729             fullwrite(fd, buffer, SIZEOF(buffer));
730             close(fd);
731         }
732         filename = newstralloc(filename, file.cont_filename);
733     }
734     amfree(filename);
735     amfree(filename_tmp);
736     return 1;
737 }
738
739 void
740 cleanup_holdingdisk(
741     char *      diskdir,
742     int         verbose)
743 {
744     DIR *topdir;
745     struct dirent *workdir;
746
747     if((topdir = opendir(diskdir)) == NULL) {
748         if(verbose && errno != ENOENT)
749             printf(_("Warning: could not open holding dir %s: %s\n"),
750                    diskdir, strerror(errno));
751         return;
752    }
753
754     /* find all directories of the right format  */
755
756     if(verbose)
757         printf(_("Scanning %s...\n"), diskdir);
758     if ((chdir(diskdir)) == -1) {
759         log_add(L_INFO, _("%s: could not chdir: %s"),
760                     diskdir, strerror(errno));
761     }
762     while((workdir = readdir(topdir)) != NULL) {
763         if(strcmp(workdir->d_name, ".") == 0
764            || strcmp(workdir->d_name, "..") == 0
765            || strcmp(workdir->d_name, "lost+found") == 0)
766             continue;
767
768         if(verbose)
769             printf("  %s: ", workdir->d_name);
770         if(!is_dir(workdir->d_name)) {
771             if(verbose)
772                 puts(_("skipping cruft file, perhaps you should delete it."));
773         }
774         else if(!is_datestr(workdir->d_name)) {
775             if(verbose && (strcmp(workdir->d_name, "lost+found")!=0) )
776                 puts(_("skipping cruft directory, perhaps you should delete it."));
777         }
778         else if(rmdir(workdir->d_name) == 0) {
779             if(verbose)
780                 puts(_("deleted empty Amanda directory."));
781         }
782      }
783      closedir(topdir);
784 }
785
786
787 int
788 mkholdingdir(
789     char *      diskdir)
790 {
791     struct stat stat_hdp;
792     int success = 1;
793
794     if (mkpdir(diskdir, 0770, (uid_t)-1, (gid_t)-1) != 0 && errno != EEXIST) {
795         log_add(L_WARNING, _("WARNING: could not create parents of %s: %s"),
796                 diskdir, strerror(errno));
797         success = 0;
798     }
799     else if (mkdir(diskdir, 0770) != 0 && errno != EEXIST) {
800         log_add(L_WARNING, _("WARNING: could not create %s: %s"),
801                 diskdir, strerror(errno));
802         success = 0;
803     }
804     else if (stat(diskdir, &stat_hdp) == -1) {
805         log_add(L_WARNING, _("WARNING: could not stat %s: %s"),
806                 diskdir, strerror(errno));
807         success = 0;
808     }
809     else {
810         if (!S_ISDIR((stat_hdp.st_mode))) {
811             log_add(L_WARNING, _("WARNING: %s is not a directory"),
812                     diskdir);
813             success = 0;
814         }
815         else if (access(diskdir,W_OK) != 0) {
816             log_add(L_WARNING, _("WARNING: directory %s is not writable"),
817                     diskdir);
818             success = 0;
819         }
820     }
821     return success;
822 }