Imported Upstream version 3.2.0
[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 "diskfile.h"
36 #include "fileheader.h"
37 #include "logfile.h"
38
39 /*
40  * utilities */
41
42 /* Is fname a directory?
43  *
44  * @param fname: filename (fully qualified)
45  * @returns: boolean
46  */
47 static int is_dir(char *fname);
48
49 /* Is fname an empty file?
50  *
51  * @param fname: filename (fully qualified)
52  * @returns: boolean
53  */
54 static int is_emptyfile(char *fname);
55
56 /* sanity check that datestamp is of the form YYYYMMDD or 
57  * YYYYMMDDhhmmss
58  *
59  * @param fname: a filename (without directory)
60  * @returns: boolean
61  */
62 static int is_datestr(char *fname);
63
64 /*
65  * Static functions */
66
67 static int
68 is_dir(
69     char *fname)
70 {
71     struct stat statbuf;
72
73     if(stat(fname, &statbuf) == -1) return 0;
74
75     return (statbuf.st_mode & S_IFDIR) == S_IFDIR;
76 }
77
78 static int
79 is_emptyfile(
80     char *fname)
81 {
82     struct stat statbuf;
83
84     if(stat(fname, &statbuf) == -1) return 0;
85
86     return ((statbuf.st_mode & S_IFDIR) != S_IFDIR) &&
87                 (statbuf.st_size == (off_t)0);
88 }
89
90 static int
91 is_datestr(
92     char *fname)
93 {
94     char *cp;
95     int ch, num, date, year, month, hour, minute, second;
96     char ymd[9], hms[7];
97
98     /* must be 8 digits */
99     for(cp = fname; (ch = *cp) != '\0'; cp++) {
100         if(!isdigit(ch)) {
101             break;
102         }
103     }
104     if(ch != '\0' || (cp-fname != 8 && cp-fname != 14)) {
105         return 0;
106     }
107
108     /* sanity check year, month, and day */
109
110     strncpy(ymd, fname, 8);
111     ymd[8] = '\0';
112     num = atoi(ymd);
113     year = num / 10000;
114     month = (num / 100) % 100;
115     date = num % 100;
116     if(year<1990 || year>2100 || month<1 || month>12 || date<1 || date>31)
117         return 0;
118
119     if(cp-fname == 8)
120         return 1;
121
122     /* sanity check hour, minute, and second */
123     strncpy(hms, fname+8, 6);
124     hms[6] = '\0';
125     num = atoi(hms);
126     hour = num / 10000;
127     minute = (num / 100) % 100;
128     second = num % 100;
129     if(hour> 23 || minute>59 || second>59)
130         return 0;
131
132     /* yes, we passed all the checks */
133
134     return 1;
135 }
136
137 /*
138  * Recursion functions
139  *
140  * These implement a general-purpose walk down the holding-* hierarchy.
141  */
142
143 /* Perform a custom action for this holding element (disk, dir, file, chunk).
144  *
145  * If the element is not cruft, the next step into the tree will only take place 
146  * if this function returns a nonzero value.
147  *
148  * The walk is depth-first, with the callback for an element invoked
149  * before entering that element.  Callbacks may depend on this behavior.
150  *
151  * @param datap: generic user-data pointer
152  * @param base: the parent of the element being examined, or NULL for 
153  * holding disks
154  * @param element: the name of the element being examined
155  * @param fqpath: fully qualified path to 'element'
156  * @param is_cruft: nonzero if this element doesn't belong here
157  * @returns: nonzero if the walk should descend into this element.
158  */
159 typedef int (*holding_walk_fn)(
160     gpointer datap,
161     char *base,
162     char *element,
163     char *fqpath,
164     int is_cruft);
165
166 typedef enum {
167     STOP_AT_DISK,
168     STOP_AT_DIR,
169     STOP_AT_FILE,
170     STOP_AT_CHUNK
171 } stop_at_t;
172
173 /* Recurse over all holding chunks in a holding file.
174  *
175  * Call per_chunk_fn for each chunk of the given file
176  *
177  * datap is passed, unchanged, to all holding_walk_fns.
178  *
179  * @param hfile: holding file to examine (fully qualified path)
180  * @param datap: generic user-data pointer
181  * @param per_chunk_fn: function to call for each holding chunk
182  */
183 static void holding_walk_file(
184     char *hfile,
185     gpointer datap,
186     holding_walk_fn per_chunk_fn)
187 {
188     dumpfile_t file;
189     char *filename = NULL;
190
191     /* Loop through all cont_filenames (subsequent chunks) */
192     filename = stralloc(hfile);
193     while (filename != NULL && filename[0] != '\0') {
194         int is_cruft = 0;
195
196         /* get the header to look for cont_filename */
197         if (!holding_file_get_dumpfile(filename, &file)) {
198             is_cruft = 1;
199         }
200
201         if (per_chunk_fn) 
202             per_chunk_fn(datap, 
203                         hfile, 
204                         filename, 
205                         filename, 
206                         is_cruft);
207         amfree(filename);
208
209         /* and go on to the next chunk if this wasn't cruft */
210         if (!is_cruft)
211             filename = stralloc(file.cont_filename);
212         dumpfile_free_data(&file);
213     }
214
215     amfree(filename);
216 }
217
218 /* Recurse over all holding files in a holding directory.
219  *
220  * Call per_file_fn for each file, and so on, stopping at the level given by 
221  * stop_at.
222  *
223  * datap is passed, unchanged, to all holding_walk_fns.
224  *
225  * @param hdir: holding directory to examine (fully qualified path)
226  * @param datap: generic user-data pointer
227  * @param stop_at: do not proceed beyond this level of the hierarchy
228  * @param per_file_fn: function to call for each holding file
229  * @param per_chunk_fn: function to call for each holding chunk
230  */
231 static void holding_walk_dir(
232     char *hdir,
233     gpointer datap,
234     stop_at_t stop_at,
235     holding_walk_fn per_file_fn,
236     holding_walk_fn per_chunk_fn)
237 {
238     DIR *dir;
239     struct dirent *workdir;
240     char *hfile = NULL;
241     dumpfile_t dumpf;
242     int dumpf_ok;
243     int proceed = 1;
244
245     if ((dir = opendir(hdir)) == NULL) {
246         if (errno != ENOENT)
247            dbprintf(_("Warning: could not open holding dir %s: %s\n"),
248                   hdir, strerror(errno));
249         return;
250     }
251
252     while ((workdir = readdir(dir)) != NULL) {
253         int is_cruft = 0;
254
255         if (is_dot_or_dotdot(workdir->d_name))
256             continue; /* expected cruft */
257
258         hfile = newvstralloc(hfile,
259                      hdir, "/", workdir->d_name,
260                      NULL);
261
262         /* filter out various undesirables */
263         if (is_emptyfile(hfile))
264             is_cruft = 1;
265
266         if (is_dir(hfile)) {
267             is_cruft= 1;
268         }
269
270         if (!(dumpf_ok=holding_file_get_dumpfile(hfile, &dumpf)) ||
271             dumpf.type != F_DUMPFILE) {
272             if (dumpf_ok && dumpf.type == F_CONT_DUMPFILE)
273                 continue; /* silently skip expected file */
274
275             is_cruft = 1;
276         }
277
278         if (dumpf.dumplevel < 0 || dumpf.dumplevel > 9) {
279             is_cruft = 1;
280         }
281
282         if (per_file_fn) 
283             proceed = per_file_fn(datap, 
284                         hdir, 
285                         workdir->d_name, 
286                         hfile, 
287                         is_cruft);
288         if (!is_cruft && proceed && stop_at != STOP_AT_FILE)
289             holding_walk_file(hfile,
290                     datap,
291                     per_chunk_fn);
292         dumpfile_free_data(&dumpf);
293     }
294
295     closedir(dir);
296     amfree(hfile);
297 }
298
299 /* Recurse over all holding directories in a holding disk.
300  *
301  * Call per_dir_fn for each dir, and so on, stopping at the level given by 
302  * stop_at.
303  *
304  * datap is passed, unchanged, to all holding_walk_fns.
305  *
306  * @param hdisk: holding disk to examine (fully qualified path)
307  * @param datap: generic user-data pointer
308  * @param stop_at: do not proceed beyond this level of the hierarchy
309  * @param per_dir_fn: function to call for each holding dir
310  * @param per_file_fn: function to call for each holding file
311  * @param per_chunk_fn: function to call for each holding chunk
312  */
313 static void 
314 holding_walk_disk(
315     char *hdisk,
316     gpointer datap,
317     stop_at_t stop_at,
318     holding_walk_fn per_dir_fn,
319     holding_walk_fn per_file_fn,
320     holding_walk_fn per_chunk_fn)
321 {
322     DIR *dir;
323     struct dirent *workdir;
324     char *hdir = NULL;
325     int proceed = 1;
326
327     if ((dir = opendir(hdisk)) == NULL) {
328         if (errno != ENOENT)
329            dbprintf(_("Warning: could not open holding disk %s: %s\n"),
330                   hdisk, strerror(errno));
331         return;
332     }
333
334     while ((workdir = readdir(dir)) != NULL) {
335         int is_cruft = 0;
336
337         if (is_dot_or_dotdot(workdir->d_name))
338             continue; /* expected cruft */
339
340         hdir = newvstralloc(hdir,
341                      hdisk, "/", workdir->d_name,
342                      NULL);
343
344         /* detect cruft */
345         if (!is_dir(hdir)) {
346             is_cruft = 1;
347         } else if (!is_datestr(workdir->d_name)) {
348             /* EXT2/3 leave these in the root of each volume */
349             if (strcmp(workdir->d_name, "lost+found") == 0)
350                 continue; /* expected cruft */
351             else
352                 is_cruft = 1; /* unexpected */
353         }
354
355         if (per_dir_fn) 
356             proceed = per_dir_fn(datap, 
357                         hdisk, 
358                         workdir->d_name, 
359                         hdir, 
360                         is_cruft);
361         if (!is_cruft && proceed && stop_at != STOP_AT_DIR)
362             holding_walk_dir(hdir,
363                     datap,
364                     stop_at,
365                     per_file_fn,
366                     per_chunk_fn);
367     }
368
369     closedir(dir);
370     amfree(hdir);
371 }
372
373 /* Recurse over all holding disks.
374  *
375  * Call per_disk_fn for each disk, per_dir_fn for each dir, and so on, stopping
376  * at the level given by stop_at.
377  *
378  * datap is passed, unchanged, to all holding_walk_fns.
379  *
380  * @param datap: generic user-data pointer
381  * @param stop_at: do not proceed beyond this level of the hierarchy
382  * @param per_disk_fn: function to call for each holding disk
383  * @param per_dir_fn: function to call for each holding dir
384  * @param per_file_fn: function to call for each holding file
385  * @param per_chunk_fn: function to call for each holding chunk
386  */
387 static void 
388 holding_walk(
389     gpointer datap,
390     stop_at_t stop_at,
391     holding_walk_fn per_disk_fn,
392     holding_walk_fn per_dir_fn,
393     holding_walk_fn per_file_fn,
394     holding_walk_fn per_chunk_fn)
395 {
396     identlist_t    il;
397     holdingdisk_t *hdisk_conf;
398     char *hdisk;
399     int proceed = 1;
400
401     for (il = getconf_identlist(CNF_HOLDINGDISK);
402                 il != NULL;
403                 il = il->next) {
404         int is_cruft = 0;
405         hdisk_conf = lookup_holdingdisk(il->data);
406
407         hdisk = holdingdisk_get_diskdir(hdisk_conf);
408         if (!is_dir(hdisk))
409             is_cruft = 1;
410
411         if (per_disk_fn) 
412             proceed = per_disk_fn(datap, 
413                         NULL, 
414                         hdisk, 
415                         hdisk, 
416                         0);
417         if (proceed && stop_at != STOP_AT_DISK)
418             holding_walk_disk(hdisk,
419                     datap,
420                     stop_at,
421                     per_dir_fn,
422                     per_file_fn,
423                     per_chunk_fn);
424     }
425 }
426
427 /*
428  * holding_get_* functions
429  */
430 typedef struct {
431     GSList *result;
432     int fullpaths;
433 } holding_get_datap_t;
434
435 /* Functor for holding_get_*; adds 'element' or 'fqpath' to
436  * the result.
437  */
438 static int
439 holding_get_walk_fn(
440     gpointer datap,
441     G_GNUC_UNUSED char *base,
442     char *element,
443     char *fqpath,
444     int is_cruft)
445 {
446     holding_get_datap_t *data = (holding_get_datap_t *)datap;
447
448     /* ignore cruft */
449     if (is_cruft) return 0;
450
451     if (data->fullpaths)
452         data->result = g_slist_insert_sorted(data->result,
453                 stralloc(fqpath), 
454                 g_compare_strings);
455     else
456         data->result = g_slist_insert_sorted(data->result, 
457                 stralloc(element), 
458                 g_compare_strings);
459
460     /* don't proceed any deeper */
461     return 0;
462 }
463
464 GSList *
465 holding_get_disks(void)
466 {
467     holding_get_datap_t data;
468     data.result = NULL;
469     data.fullpaths = 1; /* ignored anyway */
470
471     holding_walk((gpointer)&data,
472         STOP_AT_DISK,
473         holding_get_walk_fn, NULL, NULL, NULL);
474
475     return data.result;
476 }
477
478 GSList *
479 holding_get_files(
480     char *hdir,
481     int fullpaths)
482 {
483     holding_get_datap_t data;
484     data.result = NULL;
485     data.fullpaths = fullpaths;
486
487     if (hdir) {
488         holding_walk_dir(hdir, (gpointer)&data,
489             STOP_AT_FILE,
490             holding_get_walk_fn, NULL);
491     } else {
492         holding_walk((gpointer)&data,
493             STOP_AT_FILE,
494             NULL, NULL, holding_get_walk_fn, NULL);
495     }
496
497     return data.result;
498 }
499
500 GSList *
501 holding_get_file_chunks(char *hfile)
502 {
503     holding_get_datap_t data;
504     data.result = NULL;
505     data.fullpaths = 1;
506
507     holding_walk_file(hfile, (gpointer)&data,
508         holding_get_walk_fn);
509
510     return data.result;
511 }
512
513 GSList *
514 holding_get_files_for_flush(
515     GSList *dateargs)
516 {
517     GSList *file_list, *file_elt;
518     GSList *date;
519     int date_matches;
520     dumpfile_t file;
521     GSList *result_list = NULL;
522
523     /* loop over *all* files, checking each one's datestamp against the expressions
524      * in dateargs */
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))
529             continue;
530
531         if (file.type != F_DUMPFILE) {
532             dumpfile_free_data(&file);
533             continue;
534         }
535
536         if (dateargs) {
537             date_matches = 0;
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) {
541                     date_matches = 1;
542                     break;
543                 }
544             }
545         } else {
546             /* if no date list was provided, then all dates match */
547             date_matches = 1;
548         }
549         if (!date_matches) {
550             dumpfile_free_data(&file);
551             continue;
552         }
553
554         /* passed all tests -- we'll flush this file */
555         result_list = g_slist_insert_sorted(result_list, 
556             stralloc(file_elt->data), 
557             g_compare_strings);
558         dumpfile_free_data(&file);
559     }
560
561     if (file_list) g_slist_free_full(file_list);
562
563     return result_list;
564 }
565
566 GSList *
567 holding_get_all_datestamps(void)
568 {
569     GSList *all_files, *file;
570     GSList *datestamps = NULL;
571
572     /* enumerate all files */
573     all_files = holding_get_files(NULL, 1);
574     for (file = all_files; file != NULL; file = file->next) {
575         dumpfile_t dfile;
576         if (!holding_file_get_dumpfile((char *)file->data, &dfile))
577             continue;
578         if (!g_slist_find_custom(datestamps, dfile.datestamp,
579                                  g_compare_strings)) {
580             datestamps = g_slist_insert_sorted(datestamps, 
581                                                stralloc(dfile.datestamp), 
582                                                g_compare_strings);
583         }
584         dumpfile_free_data(&dfile);
585     }
586
587     g_slist_free_full(all_files);
588
589     return datestamps;
590 }
591
592 off_t
593 holding_file_size(
594     char *hfile,
595     int strip_headers)
596 {
597     dumpfile_t file;
598     char *filename;
599     off_t size = (off_t)0;
600     struct stat finfo;
601
602     /* (note: we don't use holding_get_file_chunks here because that would
603      * entail opening each file twice) */
604
605     /* Loop through all cont_filenames (subsequent chunks) */
606     filename = stralloc(hfile);
607     while (filename != NULL && filename[0] != '\0') {
608         /* stat the file for its size */
609         if (stat(filename, &finfo) == -1) {
610             dbprintf(_("stat %s: %s\n"), filename, strerror(errno));
611             size = -1;
612             break;
613         }
614         size += (finfo.st_size+(off_t)1023)/(off_t)1024;
615         if (strip_headers)
616             size -= (off_t)(DISK_BLOCK_BYTES / 1024);
617
618         /* get the header to look for cont_filename */
619         if (!holding_file_get_dumpfile(filename, &file)) {
620             dbprintf(_("holding_file_size: open of %s failed.\n"), filename);
621             size = -1;
622             break;
623         }
624
625         /* on to the next chunk */
626         filename = newstralloc(filename, file.cont_filename);
627         dumpfile_free_data(&file);
628     }
629     amfree(filename);
630     return size;
631 }
632
633
634 int
635 holding_file_unlink(
636     char *hfile)
637 {
638     GSList *chunklist;
639     GSList *chunk;
640
641     chunklist = holding_get_file_chunks(hfile);
642     if (!chunklist)
643         return 0;
644
645     for (chunk = chunklist; chunk != NULL; chunk = chunk->next) {
646         if (unlink((char *)chunk->data)<0) {
647             dbprintf(_("holding_file_unlink: could not unlink %s: %s\n"),
648                     (char *)chunk->data, strerror(errno));
649             return 0;
650         }
651     }
652     return 1;
653 }
654
655 int
656 holding_file_get_dumpfile(
657     char *      fname,
658     dumpfile_t *file)
659 {
660     char buffer[DISK_BLOCK_BYTES];
661     int fd;
662
663     memset(buffer, 0, sizeof(buffer));
664
665     fh_init(file);
666     file->type = F_UNKNOWN;
667     if((fd = robust_open(fname, O_RDONLY, 0)) == -1)
668         return 0;
669
670     if(full_read(fd, buffer, SIZEOF(buffer)) != sizeof(buffer)) {
671         aclose(fd);
672         return 0;
673     }
674     aclose(fd);
675
676     parse_file_header(buffer, file, SIZEOF(buffer));
677     return 1;
678 }
679
680 /*
681  * Cleanup
682  */
683
684 typedef struct {
685     corrupt_dle_fn corrupt_dle;
686     FILE *verbose_output;
687 } holding_cleanup_datap_t;
688
689 static int
690 holding_cleanup_disk(
691     gpointer datap,
692     G_GNUC_UNUSED char *base,
693     G_GNUC_UNUSED char *element,
694     char *fqpath,
695     int is_cruft)
696 {
697     holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
698
699     if (data->verbose_output) {
700         if (is_cruft)
701             g_fprintf(data->verbose_output, 
702                 _("Invalid holding disk '%s'\n"), fqpath);
703         else
704             g_fprintf(data->verbose_output, 
705                 _("Cleaning up holding disk '%s'\n"), fqpath);
706     }
707
708     return 1;
709 }
710
711 static int
712 holding_cleanup_dir(
713     gpointer datap,
714     G_GNUC_UNUSED char *base,
715     char *element,
716     char *fqpath,
717     int is_cruft)
718 {
719     holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
720
721     if (is_cruft) {
722         if (data->verbose_output)
723             g_fprintf(data->verbose_output, 
724                 _("Invalid holding directory '%s'\n"), fqpath);
725         return 0;
726     }
727
728     /* try removing it */
729     if (rmdir(fqpath) == 0) {
730         /* success, so don't try to walk into it */
731         if (data->verbose_output)
732             g_fprintf(data->verbose_output,
733                 _(" ..removed empty directory '%s'\n"), element);
734         return 0;
735     }
736
737     if (data->verbose_output)
738         g_fprintf(data->verbose_output, 
739             _(" ..cleaning up holding directory '%s'\n"), element);
740
741     return 1;
742 }
743
744 static int
745 holding_cleanup_file(
746     gpointer datap,
747     G_GNUC_UNUSED char *base,
748     char *element,
749     char *fqpath,
750     int is_cruft)
751 {
752     holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
753     int stat;
754     int l;
755     dumpfile_t file;
756     disk_t *dp;
757
758     if (is_cruft) {
759         if (data->verbose_output)
760             g_fprintf(data->verbose_output, 
761                 _("Invalid holding file '%s'\n"), element);
762         return 0;
763     }
764
765
766     stat = holding_file_get_dumpfile(fqpath, &file);
767
768     if (!stat) {
769         if (data->verbose_output)
770             g_fprintf(data->verbose_output, 
771                 _("Could not read read header from '%s'\n"), element);
772         dumpfile_free_data(&file);
773         return 0;
774     }
775
776     if (file.type != F_DUMPFILE && file.type != F_CONT_DUMPFILE) {
777         if (data->verbose_output)
778             g_fprintf(data->verbose_output, 
779                 _("File '%s' is not a dump file\n"), element);
780         dumpfile_free_data(&file);
781         return 0;
782     }
783
784     if(file.dumplevel < 0 || file.dumplevel > 9) {
785         if (data->verbose_output)
786             g_fprintf(data->verbose_output, 
787                 _("File '%s' has invalid level %d\n"), element, file.dumplevel);
788         dumpfile_free_data(&file);
789         return 0;
790     }
791
792     dp = lookup_disk(file.name, file.disk);
793
794     if (dp == NULL) {
795         if (data->verbose_output)
796             g_fprintf(data->verbose_output, 
797                 _("File '%s' is for '%s:%s', which is not in the disklist\n"), 
798                     element, file.name, file.disk);
799         dumpfile_free_data(&file);
800         return 0;
801     }
802
803     if ((l = strlen(element)) >= 7 && strncmp(&fqpath[l-4],".tmp",4) == 0) {
804         char *destname;
805
806         /* generate a name without '.tmp' */
807         destname = stralloc(fqpath);
808         destname[strlen(destname) - 4] = '\0';
809
810         /* OK, it passes muster -- rename it to salvage some data,
811          * and mark the DLE as corrupted */
812         if (data->verbose_output)
813             g_fprintf(data->verbose_output, 
814                 _("Processing partial holding file '%s'\n"), element);
815
816         if(rename_tmp_holding(destname, 0)) {
817             if (data->corrupt_dle)
818                 data->corrupt_dle(dp->host->hostname, dp->name);
819         } else {
820             dbprintf(_("rename_tmp_holding(%s) failed\n"), destname);
821             if (data->verbose_output)
822                 g_fprintf(data->verbose_output, 
823                     _("Rename of '%s' to '%s' failed.\n"), element, destname);
824         }
825
826         amfree(destname);
827     }
828
829     dumpfile_free_data(&file);
830     return 1;
831 }
832
833 void
834 holding_cleanup(
835     corrupt_dle_fn corrupt_dle,
836     FILE *verbose_output)
837 {
838     holding_cleanup_datap_t data;
839     data.corrupt_dle = corrupt_dle;
840     data.verbose_output = verbose_output;
841
842     holding_walk((gpointer)&data,
843         STOP_AT_FILE,
844         holding_cleanup_disk,
845         holding_cleanup_dir,
846         holding_cleanup_file,
847         NULL);
848 }
849
850 /*
851  * Application support
852  */
853
854 void
855 holding_set_origsize(
856     char  *holding_file,
857     off_t  orig_size)
858 {
859     int         fd;
860     size_t      buflen;
861     char        buffer[DISK_BLOCK_BYTES];
862     char       *read_buffer;
863     dumpfile_t  file;
864
865     if((fd = robust_open(holding_file, O_RDWR, 0)) == -1) {
866         dbprintf(_("holding_set_origsize: open of %s failed: %s\n"),
867                  holding_file, strerror(errno));
868         return;
869     }
870
871     buflen = full_read(fd, buffer, SIZEOF(buffer));
872     if (buflen <= 0) {
873         dbprintf(_("holding_set_origsize: %s: empty file?\n"), holding_file);
874         return;
875     }
876     parse_file_header(buffer, &file, (size_t)buflen);
877     lseek(fd, (off_t)0, SEEK_SET);
878     file.orig_size = orig_size;
879     read_buffer = build_header(&file, NULL, DISK_BLOCK_BYTES);
880     full_write(fd, read_buffer, DISK_BLOCK_BYTES);
881     dumpfile_free_data(&file);
882     amfree(read_buffer);
883     close(fd);
884 }
885
886 int
887 rename_tmp_holding(
888     char *      holding_file,
889     int         complete)
890 {
891     int fd;
892     size_t buflen;
893     char buffer[DISK_BLOCK_BYTES];
894     dumpfile_t file;
895     char *filename;
896     char *filename_tmp = NULL;
897
898     memset(buffer, 0, sizeof(buffer));
899     filename = stralloc(holding_file);
900     while(filename != NULL && filename[0] != '\0') {
901         filename_tmp = newvstralloc(filename_tmp, filename, ".tmp", NULL);
902         if((fd = robust_open(filename_tmp,O_RDONLY, 0)) == -1) {
903             dbprintf(_("rename_tmp_holding: open of %s failed: %s\n"),filename_tmp,strerror(errno));
904             amfree(filename);
905             amfree(filename_tmp);
906             return 0;
907         }
908         buflen = full_read(fd, buffer, SIZEOF(buffer));
909         close(fd);
910
911         if(rename(filename_tmp, filename) != 0) {
912             dbprintf(_("rename_tmp_holding: could not rename \"%s\" to \"%s\": %s"),
913                     filename_tmp, filename, strerror(errno));
914         }
915
916         if (buflen <= 0) {
917             dbprintf(_("rename_tmp_holding: %s: empty file?\n"), filename);
918             amfree(filename);
919             amfree(filename_tmp);
920             return 0;
921         }
922         parse_file_header(buffer, &file, (size_t)buflen);
923         if(complete == 0 ) {
924             char * header;
925             if((fd = robust_open(filename, O_RDWR, 0)) == -1) {
926                 dbprintf(_("rename_tmp_holdingX: open of %s failed: %s\n"),
927                         filename, strerror(errno));
928                 dumpfile_free_data(&file);
929                 amfree(filename);
930                 amfree(filename_tmp);
931                 return 0;
932
933             }
934             file.is_partial = 1;
935             if (debug_holding > 1)
936                 dump_dumpfile_t(&file);
937             header = build_header(&file, NULL, DISK_BLOCK_BYTES);
938             if (!header) /* this shouldn't happen */
939                 error(_("header does not fit in %zd bytes"), (size_t)DISK_BLOCK_BYTES);
940             if (full_write(fd, header, DISK_BLOCK_BYTES) != DISK_BLOCK_BYTES) {
941                 dbprintf(_("rename_tmp_holding: writing new header failed: %s"),
942                         strerror(errno));
943                 dumpfile_free_data(&file);
944                 amfree(filename);
945                 amfree(filename_tmp);
946                 free(header);
947                 close(fd);
948                 return 0;
949             }
950             free(header);
951             close(fd);
952         }
953         filename = newstralloc(filename, file.cont_filename);
954         dumpfile_free_data(&file);
955     }
956     amfree(filename);
957     amfree(filename_tmp);
958     return 1;
959 }
960
961
962 int
963 mkholdingdir(
964     char *      diskdir)
965 {
966     struct stat stat_hdp;
967     int success = 1;
968
969     if (mkpdir(diskdir, 0770, (uid_t)-1, (gid_t)-1) != 0 && errno != EEXIST) {
970         log_add(L_WARNING, _("WARNING: could not create parents of %s: %s"),
971                 diskdir, strerror(errno));
972         success = 0;
973     }
974     else if (mkdir(diskdir, 0770) != 0 && errno != EEXIST) {
975         log_add(L_WARNING, _("WARNING: could not create %s: %s"),
976                 diskdir, strerror(errno));
977         success = 0;
978     }
979     else if (stat(diskdir, &stat_hdp) == -1) {
980         log_add(L_WARNING, _("WARNING: could not stat %s: %s"),
981                 diskdir, strerror(errno));
982         success = 0;
983     }
984     else {
985         if (!S_ISDIR((stat_hdp.st_mode))) {
986             log_add(L_WARNING, _("WARNING: %s is not a directory"),
987                     diskdir);
988             success = 0;
989         }
990         else if (access(diskdir,W_OK) != 0) {
991             log_add(L_WARNING, _("WARNING: directory %s is not writable"),
992                     diskdir);
993             success = 0;
994         }
995     }
996     return success;
997 }