2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1998 University of Maryland at College Park
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.
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.
23 * Authors: the Amanda Development Team. Its members are listed in a
24 * file named AUTHORS, in the root directory of this distribution.
27 * $Id: holding.c,v 1.56 2006/06/09 23:07:26 martinea Exp $
29 * Functions to access holding disk
35 #include "fileheader.h"
41 /* Is fname a directory?
43 * @param fname: filename (fully qualified)
46 static int is_dir(char *fname);
48 /* Is fname an empty file?
50 * @param fname: filename (fully qualified)
53 static int is_emptyfile(char *fname);
55 /* sanity check that datestamp is of the form YYYYMMDD or
58 * @param fname: a filename (without directory)
61 static int is_datestr(char *fname);
72 if(stat(fname, &statbuf) == -1) return 0;
74 return (statbuf.st_mode & S_IFDIR) == S_IFDIR;
83 if(stat(fname, &statbuf) == -1) return 0;
85 return ((statbuf.st_mode & S_IFDIR) != S_IFDIR) &&
86 (statbuf.st_size == (off_t)0);
94 int ch, num, date, year, month, hour, minute, second;
97 /* must be 8 digits */
98 for(cp = fname; (ch = *cp) != '\0'; cp++) {
103 if(ch != '\0' || (cp-fname != 8 && cp-fname != 14)) {
107 /* sanity check year, month, and day */
109 strncpy(ymd, fname, 8);
113 month = (num / 100) % 100;
115 if(year<1990 || year>2100 || month<1 || month>12 || date<1 || date>31)
121 /* sanity check hour, minute, and second */
122 strncpy(hms, fname+8, 6);
126 minute = (num / 100) % 100;
128 if(hour> 23 || minute>59 || second>59)
131 /* yes, we passed all the checks */
137 * Recursion functions
139 * These implement a general-purpose walk down the holding-* hierarchy.
142 /* Perform a custom action for this holding element (disk, dir, file, chunk).
144 * If the element is not cruft, the next step into the tree will only take place
145 * if this function returns a nonzero value.
147 * The walk is depth-first, with the callback for an element invoked
148 * before entering that element. Callbacks may depend on this behavior.
150 * @param datap: generic user-data pointer
151 * @param base: the parent of the element being examined, or NULL for
153 * @param element: the name of the element being examined
154 * @param fqpath: fully qualified path to 'element'
155 * @param is_cruft: nonzero if this element doesn't belong here
156 * @returns: nonzero if the walk should descend into this element.
158 typedef int (*holding_walk_fn)(
172 /* Recurse over all holding chunks in a holding file.
174 * Call per_chunk_fn for each chunk of the given file
176 * datap is passed, unchanged, to all holding_walk_fns.
178 * @param hfile: holding file to examine (fully qualified path)
179 * @param datap: generic user-data pointer
180 * @param per_chunk_fn: function to call for each holding chunk
182 static void holding_walk_file(
185 holding_walk_fn per_chunk_fn)
188 char *filename = NULL;
190 /* Loop through all cont_filenames (subsequent chunks) */
191 filename = stralloc(hfile);
192 while (filename != NULL && filename[0] != '\0') {
195 /* get the header to look for cont_filename */
196 if (!holding_file_get_dumpfile(filename, &file)) {
208 /* and go on to the next chunk if this wasn't cruft */
210 filename = stralloc(file.cont_filename);
211 dumpfile_free_data(&file);
217 /* Recurse over all holding files in a holding directory.
219 * Call per_file_fn for each file, and so on, stopping at the level given by
222 * datap is passed, unchanged, to all holding_walk_fns.
224 * @param hdir: holding directory to examine (fully qualified path)
225 * @param datap: generic user-data pointer
226 * @param stop_at: do not proceed beyond this level of the hierarchy
227 * @param per_file_fn: function to call for each holding file
228 * @param per_chunk_fn: function to call for each holding chunk
230 static void holding_walk_dir(
234 holding_walk_fn per_file_fn,
235 holding_walk_fn per_chunk_fn)
238 struct dirent *workdir;
244 if ((dir = opendir(hdir)) == NULL) {
246 dbprintf(_("Warning: could not open holding dir %s: %s\n"),
247 hdir, strerror(errno));
251 while ((workdir = readdir(dir)) != NULL) {
254 if (is_dot_or_dotdot(workdir->d_name))
255 continue; /* expected cruft */
257 hfile = newvstralloc(hfile,
258 hdir, "/", workdir->d_name,
261 /* filter out various undesirables */
262 if (is_emptyfile(hfile))
269 if (!(dumpf_ok=holding_file_get_dumpfile(hfile, &dumpf)) ||
270 dumpf.type != F_DUMPFILE) {
271 if (dumpf_ok && dumpf.type == F_CONT_DUMPFILE)
272 continue; /* silently skip expected file */
277 if (dumpf.dumplevel < 0 || dumpf.dumplevel > 9) {
282 proceed = per_file_fn(datap,
287 if (!is_cruft && proceed && stop_at != STOP_AT_FILE)
288 holding_walk_file(hfile,
291 dumpfile_free_data(&dumpf);
298 /* Recurse over all holding directories in a holding disk.
300 * Call per_dir_fn for each dir, and so on, stopping at the level given by
303 * datap is passed, unchanged, to all holding_walk_fns.
305 * @param hdisk: holding disk to examine (fully qualified path)
306 * @param datap: generic user-data pointer
307 * @param stop_at: do not proceed beyond this level of the hierarchy
308 * @param per_dir_fn: function to call for each holding dir
309 * @param per_file_fn: function to call for each holding file
310 * @param per_chunk_fn: function to call for each holding chunk
317 holding_walk_fn per_dir_fn,
318 holding_walk_fn per_file_fn,
319 holding_walk_fn per_chunk_fn)
322 struct dirent *workdir;
326 if ((dir = opendir(hdisk)) == NULL) {
328 dbprintf(_("Warning: could not open holding disk %s: %s\n"),
329 hdisk, strerror(errno));
333 while ((workdir = readdir(dir)) != NULL) {
336 if (is_dot_or_dotdot(workdir->d_name))
337 continue; /* expected cruft */
339 hdir = newvstralloc(hdir,
340 hdisk, "/", workdir->d_name,
346 } else if (!is_datestr(workdir->d_name)) {
347 /* EXT2/3 leave these in the root of each volume */
348 if (strcmp(workdir->d_name, "lost+found") == 0)
349 continue; /* expected cruft */
351 is_cruft = 1; /* unexpected */
355 proceed = per_dir_fn(datap,
360 if (!is_cruft && proceed && stop_at != STOP_AT_DIR)
361 holding_walk_dir(hdir,
372 /* Recurse over all holding disks.
374 * Call per_disk_fn for each disk, per_dir_fn for each dir, and so on, stopping
375 * at the level given by stop_at.
377 * datap is passed, unchanged, to all holding_walk_fns.
379 * @param datap: generic user-data pointer
380 * @param stop_at: do not proceed beyond this level of the hierarchy
381 * @param per_disk_fn: function to call for each holding disk
382 * @param per_dir_fn: function to call for each holding dir
383 * @param per_file_fn: function to call for each holding file
384 * @param per_chunk_fn: function to call for each holding chunk
390 holding_walk_fn per_disk_fn,
391 holding_walk_fn per_dir_fn,
392 holding_walk_fn per_file_fn,
393 holding_walk_fn per_chunk_fn)
396 holdingdisk_t *hdisk_conf;
400 for (il = getconf_identlist(CNF_HOLDINGDISK);
404 hdisk_conf = lookup_holdingdisk(il->data);
406 hdisk = holdingdisk_get_diskdir(hdisk_conf);
411 proceed = per_disk_fn(datap,
416 if (proceed && stop_at != STOP_AT_DISK)
417 holding_walk_disk(hdisk,
427 * holding_get_* functions
432 } holding_get_datap_t;
434 /* Functor for holding_get_*; adds 'element' or 'fqpath' to
440 G_GNUC_UNUSED char *base,
445 holding_get_datap_t *data = (holding_get_datap_t *)datap;
448 if (is_cruft) return 0;
451 data->result = g_slist_insert_sorted(data->result,
455 data->result = g_slist_insert_sorted(data->result,
459 /* don't proceed any deeper */
464 holding_get_disks(void)
466 holding_get_datap_t data;
468 data.fullpaths = 1; /* ignored anyway */
470 holding_walk((gpointer)&data,
472 holding_get_walk_fn, NULL, NULL, NULL);
482 holding_get_datap_t data;
484 data.fullpaths = fullpaths;
487 holding_walk_dir(hdir, (gpointer)&data,
489 holding_get_walk_fn, NULL);
491 holding_walk((gpointer)&data,
493 NULL, NULL, holding_get_walk_fn, NULL);
500 holding_get_file_chunks(char *hfile)
502 holding_get_datap_t data;
506 holding_walk_file(hfile, (gpointer)&data,
507 holding_get_walk_fn);
513 holding_get_files_for_flush(
516 GSList *file_list, *file_elt;
521 GSList *result_list = NULL;
523 /* loop over *all* files, checking each one's datestamp against the expressions
525 file_list = holding_get_files(NULL, 1);
526 for (file_elt = file_list; file_elt != NULL; file_elt = file_elt->next) {
527 /* get info on that file */
528 if (!holding_file_get_dumpfile((char *)file_elt->data, &file))
531 if (file.type != F_DUMPFILE) {
532 dumpfile_free_data(&file);
538 /* loop over date args, until we find a match */
539 for (date = dateargs; date !=NULL; date = date->next) {
540 if (strcmp((char *)date->data, file.datestamp) == 0) {
546 /* if no date list was provided, then all dates match */
550 dumpfile_free_data(&file);
554 /* check that the hostname and disk are in the disklist */
555 dp = lookup_disk(file.name, file.disk);
557 dbprintf(_("%s: disk %s:%s not in database, skipping it."),
558 (char *)file_elt->data, file.name, file.disk);
559 dumpfile_free_data(&file);
563 /* passed all tests -- we'll flush this file */
564 result_list = g_slist_insert_sorted(result_list,
565 stralloc(file_elt->data),
567 dumpfile_free_data(&file);
570 if (file_list) g_slist_free_full(file_list);
576 holding_get_all_datestamps(void)
578 GSList *all_files, *file;
579 GSList *datestamps = NULL;
581 /* enumerate all files */
582 all_files = holding_get_files(NULL, 1);
583 for (file = all_files; file != NULL; file = file->next) {
585 if (!holding_file_get_dumpfile((char *)file->data, &dfile))
587 if (!g_slist_find_custom(datestamps, dfile.datestamp,
588 g_compare_strings)) {
589 datestamps = g_slist_insert_sorted(datestamps,
590 stralloc(dfile.datestamp),
593 dumpfile_free_data(&dfile);
596 g_slist_free_full(all_files);
608 off_t size = (off_t)0;
611 /* (note: we don't use holding_get_file_chunks here because that would
612 * entail opening each file twice) */
614 /* Loop through all cont_filenames (subsequent chunks) */
615 filename = stralloc(hfile);
616 while (filename != NULL && filename[0] != '\0') {
617 /* stat the file for its size */
618 if (stat(filename, &finfo) == -1) {
619 dbprintf(_("stat %s: %s\n"), filename, strerror(errno));
623 size += (finfo.st_size+(off_t)1023)/(off_t)1024;
625 size -= (off_t)(DISK_BLOCK_BYTES / 1024);
627 /* get the header to look for cont_filename */
628 if (!holding_file_get_dumpfile(filename, &file)) {
629 dbprintf(_("holding_file_size: open of %s failed.\n"), filename);
634 /* on to the next chunk */
635 filename = newstralloc(filename, file.cont_filename);
636 dumpfile_free_data(&file);
650 chunklist = holding_get_file_chunks(hfile);
654 for (chunk = chunklist; chunk != NULL; chunk = chunk->next) {
655 if (unlink((char *)chunk->data)<0) {
656 dbprintf(_("holding_file_unlink: could not unlink %s: %s\n"),
657 (char *)chunk->data, strerror(errno));
665 holding_file_get_dumpfile(
669 char buffer[DISK_BLOCK_BYTES];
672 memset(buffer, 0, sizeof(buffer));
675 file->type = F_UNKNOWN;
676 if((fd = robust_open(fname, O_RDONLY, 0)) == -1)
679 if(full_read(fd, buffer, SIZEOF(buffer)) != sizeof(buffer)) {
685 parse_file_header(buffer, file, SIZEOF(buffer));
694 corrupt_dle_fn corrupt_dle;
695 FILE *verbose_output;
696 } holding_cleanup_datap_t;
699 holding_cleanup_disk(
701 G_GNUC_UNUSED char *base,
702 G_GNUC_UNUSED char *element,
706 holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
708 if (data->verbose_output) {
710 g_fprintf(data->verbose_output,
711 _("Invalid holding disk '%s'\n"), fqpath);
713 g_fprintf(data->verbose_output,
714 _("Cleaning up holding disk '%s'\n"), fqpath);
723 G_GNUC_UNUSED char *base,
728 holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
731 if (data->verbose_output)
732 g_fprintf(data->verbose_output,
733 _("Invalid holding directory '%s'\n"), fqpath);
737 /* try removing it */
738 if (rmdir(fqpath) == 0) {
739 /* success, so don't try to walk into it */
740 if (data->verbose_output)
741 g_fprintf(data->verbose_output,
742 _(" ..removed empty directory '%s'\n"), element);
746 if (data->verbose_output)
747 g_fprintf(data->verbose_output,
748 _(" ..cleaning up holding directory '%s'\n"), element);
754 holding_cleanup_file(
756 G_GNUC_UNUSED char *base,
761 holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
768 if (data->verbose_output)
769 g_fprintf(data->verbose_output,
770 _("Invalid holding file '%s'\n"), element);
775 stat = holding_file_get_dumpfile(fqpath, &file);
778 if (data->verbose_output)
779 g_fprintf(data->verbose_output,
780 _("Could not read read header from '%s'\n"), element);
781 dumpfile_free_data(&file);
785 if (file.type != F_DUMPFILE && file.type != F_CONT_DUMPFILE) {
786 if (data->verbose_output)
787 g_fprintf(data->verbose_output,
788 _("File '%s' is not a dump file\n"), element);
789 dumpfile_free_data(&file);
793 if(file.dumplevel < 0 || file.dumplevel > 9) {
794 if (data->verbose_output)
795 g_fprintf(data->verbose_output,
796 _("File '%s' has invalid level %d\n"), element, file.dumplevel);
797 dumpfile_free_data(&file);
801 dp = lookup_disk(file.name, file.disk);
804 if (data->verbose_output)
805 g_fprintf(data->verbose_output,
806 _("File '%s' is for '%s:%s', which is not in the disklist\n"),
807 element, file.name, file.disk);
808 dumpfile_free_data(&file);
812 if ((l = strlen(element)) >= 7 && strncmp(&fqpath[l-4],".tmp",4) == 0) {
815 /* generate a name without '.tmp' */
816 destname = stralloc(fqpath);
817 destname[strlen(destname) - 4] = '\0';
819 /* OK, it passes muster -- rename it to salvage some data,
820 * and mark the DLE as corrupted */
821 if (data->verbose_output)
822 g_fprintf(data->verbose_output,
823 _("Processing partial holding file '%s'\n"), element);
825 if(rename_tmp_holding(destname, 0)) {
826 if (data->corrupt_dle)
827 data->corrupt_dle(dp->host->hostname, dp->name);
829 dbprintf(_("rename_tmp_holding(%s) failed\n"), destname);
830 if (data->verbose_output)
831 g_fprintf(data->verbose_output,
832 _("Rename of '%s' to '%s' failed.\n"), element, destname);
838 dumpfile_free_data(&file);
844 corrupt_dle_fn corrupt_dle,
845 FILE *verbose_output)
847 holding_cleanup_datap_t data;
848 data.corrupt_dle = corrupt_dle;
849 data.verbose_output = verbose_output;
851 holding_walk((gpointer)&data,
853 holding_cleanup_disk,
855 holding_cleanup_file,
860 * Application support
864 holding_set_origsize(
870 char buffer[DISK_BLOCK_BYTES];
874 if((fd = robust_open(holding_file, O_RDWR, 0)) == -1) {
875 dbprintf(_("holding_set_origsize: open of %s failed: %s\n"),
876 holding_file, strerror(errno));
880 buflen = full_read(fd, buffer, SIZEOF(buffer));
882 dbprintf(_("holding_set_origsize: %s: empty file?\n"), holding_file);
885 parse_file_header(buffer, &file, (size_t)buflen);
886 lseek(fd, (off_t)0, SEEK_SET);
887 file.orig_size = orig_size;
888 read_buffer = build_header(&file, NULL, DISK_BLOCK_BYTES);
889 full_write(fd, read_buffer, DISK_BLOCK_BYTES);
890 dumpfile_free_data(&file);
902 char buffer[DISK_BLOCK_BYTES];
905 char *filename_tmp = NULL;
907 memset(buffer, 0, sizeof(buffer));
908 filename = stralloc(holding_file);
909 while(filename != NULL && filename[0] != '\0') {
910 filename_tmp = newvstralloc(filename_tmp, filename, ".tmp", NULL);
911 if((fd = robust_open(filename_tmp,O_RDONLY, 0)) == -1) {
912 dbprintf(_("rename_tmp_holding: open of %s failed: %s\n"),filename_tmp,strerror(errno));
914 amfree(filename_tmp);
917 buflen = full_read(fd, buffer, SIZEOF(buffer));
920 if(rename(filename_tmp, filename) != 0) {
921 dbprintf(_("rename_tmp_holding: could not rename \"%s\" to \"%s\": %s"),
922 filename_tmp, filename, strerror(errno));
926 dbprintf(_("rename_tmp_holding: %s: empty file?\n"), filename);
928 amfree(filename_tmp);
931 parse_file_header(buffer, &file, (size_t)buflen);
934 if((fd = robust_open(filename, O_RDWR, 0)) == -1) {
935 dbprintf(_("rename_tmp_holdingX: open of %s failed: %s\n"),
936 filename, strerror(errno));
937 dumpfile_free_data(&file);
939 amfree(filename_tmp);
944 if (debug_holding > 1)
945 dump_dumpfile_t(&file);
946 header = build_header(&file, NULL, DISK_BLOCK_BYTES);
947 if (!header) /* this shouldn't happen */
948 error(_("header does not fit in %zd bytes"), (size_t)DISK_BLOCK_BYTES);
949 if (full_write(fd, header, DISK_BLOCK_BYTES) != DISK_BLOCK_BYTES) {
950 dbprintf(_("rename_tmp_holding: writing new header failed: %s"),
952 dumpfile_free_data(&file);
954 amfree(filename_tmp);
960 filename = newstralloc(filename, file.cont_filename);
961 dumpfile_free_data(&file);
964 amfree(filename_tmp);
973 struct stat stat_hdp;
976 if (mkpdir(diskdir, 0770, (uid_t)-1, (gid_t)-1) != 0 && errno != EEXIST) {
977 log_add(L_WARNING, _("WARNING: could not create parents of %s: %s"),
978 diskdir, strerror(errno));
981 else if (mkdir(diskdir, 0770) != 0 && errno != EEXIST) {
982 log_add(L_WARNING, _("WARNING: could not create %s: %s"),
983 diskdir, strerror(errno));
986 else if (stat(diskdir, &stat_hdp) == -1) {
987 log_add(L_WARNING, _("WARNING: could not stat %s: %s"),
988 diskdir, strerror(errno));
992 if (!S_ISDIR((stat_hdp.st_mode))) {
993 log_add(L_WARNING, _("WARNING: %s is not a directory"),
997 else if (access(diskdir,W_OK) != 0) {
998 log_add(L_WARNING, _("WARNING: directory %s is not writable"),