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