Imported Upstream version 3.3.1
[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         hdisk_conf = lookup_holdingdisk(il->data);
405
406         hdisk = holdingdisk_get_diskdir(hdisk_conf);
407
408         if (per_disk_fn) 
409             proceed = per_disk_fn(datap, 
410                         NULL, 
411                         hdisk, 
412                         hdisk, 
413                         0);
414         if (proceed && stop_at != STOP_AT_DISK)
415             holding_walk_disk(hdisk,
416                     datap,
417                     stop_at,
418                     per_dir_fn,
419                     per_file_fn,
420                     per_chunk_fn);
421     }
422 }
423
424 /*
425  * holding_get_* functions
426  */
427 typedef struct {
428     GSList *result;
429     int fullpaths;
430 } holding_get_datap_t;
431
432 /* Functor for holding_get_*; adds 'element' or 'fqpath' to
433  * the result.
434  */
435 static int
436 holding_get_walk_fn(
437     gpointer datap,
438     G_GNUC_UNUSED char *base,
439     char *element,
440     char *fqpath,
441     int is_cruft)
442 {
443     holding_get_datap_t *data = (holding_get_datap_t *)datap;
444
445     /* ignore cruft */
446     if (is_cruft) return 0;
447
448     if (data->fullpaths)
449         data->result = g_slist_insert_sorted(data->result,
450                 stralloc(fqpath), 
451                 g_compare_strings);
452     else
453         data->result = g_slist_insert_sorted(data->result, 
454                 stralloc(element), 
455                 g_compare_strings);
456
457     /* don't proceed any deeper */
458     return 0;
459 }
460
461 GSList *
462 holding_get_disks(void)
463 {
464     holding_get_datap_t data;
465     data.result = NULL;
466     data.fullpaths = 1; /* ignored anyway */
467
468     holding_walk((gpointer)&data,
469         STOP_AT_DISK,
470         holding_get_walk_fn, NULL, NULL, NULL);
471
472     return data.result;
473 }
474
475 GSList *
476 holding_get_files(
477     char *hdir,
478     int fullpaths)
479 {
480     holding_get_datap_t data;
481     data.result = NULL;
482     data.fullpaths = fullpaths;
483
484     if (hdir) {
485         holding_walk_dir(hdir, (gpointer)&data,
486             STOP_AT_FILE,
487             holding_get_walk_fn, NULL);
488     } else {
489         holding_walk((gpointer)&data,
490             STOP_AT_FILE,
491             NULL, NULL, holding_get_walk_fn, NULL);
492     }
493
494     return data.result;
495 }
496
497 GSList *
498 holding_get_file_chunks(char *hfile)
499 {
500     holding_get_datap_t data;
501     data.result = NULL;
502     data.fullpaths = 1;
503
504     holding_walk_file(hfile, (gpointer)&data,
505         holding_get_walk_fn);
506
507     return data.result;
508 }
509
510 GSList *
511 holding_get_files_for_flush(
512     GSList *dateargs)
513 {
514     GSList *file_list, *file_elt;
515     GSList *date;
516     int date_matches;
517     dumpfile_t file;
518     GSList *result_list = NULL;
519
520     /* loop over *all* files, checking each one's datestamp against the expressions
521      * in dateargs */
522     file_list = holding_get_files(NULL, 1);
523     for (file_elt = file_list; file_elt != NULL; file_elt = file_elt->next) {
524         /* get info on that file */
525         if (!holding_file_get_dumpfile((char *)file_elt->data, &file))
526             continue;
527
528         if (file.type != F_DUMPFILE) {
529             dumpfile_free_data(&file);
530             continue;
531         }
532
533         if (dateargs) {
534             date_matches = 0;
535             /* loop over date args, until we find a match */
536             for (date = dateargs; date !=NULL; date = date->next) {
537                 if (strcmp((char *)date->data, file.datestamp) == 0) {
538                     date_matches = 1;
539                     break;
540                 }
541             }
542         } else {
543             /* if no date list was provided, then all dates match */
544             date_matches = 1;
545         }
546         if (!date_matches) {
547             dumpfile_free_data(&file);
548             continue;
549         }
550
551         /* passed all tests -- we'll flush this file */
552         result_list = g_slist_insert_sorted(result_list, 
553             stralloc(file_elt->data), 
554             g_compare_strings);
555         dumpfile_free_data(&file);
556     }
557
558     if (file_list) slist_free_full(file_list, g_free);
559
560     return result_list;
561 }
562
563 GSList *
564 holding_get_all_datestamps(void)
565 {
566     GSList *all_files, *file;
567     GSList *datestamps = NULL;
568
569     /* enumerate all files */
570     all_files = holding_get_files(NULL, 1);
571     for (file = all_files; file != NULL; file = file->next) {
572         dumpfile_t dfile;
573         if (!holding_file_get_dumpfile((char *)file->data, &dfile))
574             continue;
575         if (!g_slist_find_custom(datestamps, dfile.datestamp,
576                                  g_compare_strings)) {
577             datestamps = g_slist_insert_sorted(datestamps, 
578                                                stralloc(dfile.datestamp), 
579                                                g_compare_strings);
580         }
581         dumpfile_free_data(&dfile);
582     }
583
584     slist_free_full(all_files, g_free);
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             size = -1;
609             break;
610         }
611         size += (finfo.st_size+(off_t)1023)/(off_t)1024;
612         if (strip_headers)
613             size -= (off_t)(DISK_BLOCK_BYTES / 1024);
614
615         /* get the header to look for cont_filename */
616         if (!holding_file_get_dumpfile(filename, &file)) {
617             dbprintf(_("holding_file_size: open of %s failed.\n"), filename);
618             size = -1;
619             break;
620         }
621
622         /* on to the next chunk */
623         filename = newstralloc(filename, file.cont_filename);
624         dumpfile_free_data(&file);
625     }
626     amfree(filename);
627     return size;
628 }
629
630
631 int
632 holding_file_unlink(
633     char *hfile)
634 {
635     GSList *chunklist;
636     GSList *chunk;
637
638     chunklist = holding_get_file_chunks(hfile);
639     if (!chunklist)
640         return 0;
641
642     for (chunk = chunklist; chunk != NULL; chunk = chunk->next) {
643         if (unlink((char *)chunk->data)<0) {
644             dbprintf(_("holding_file_unlink: could not unlink %s: %s\n"),
645                     (char *)chunk->data, strerror(errno));
646             return 0;
647         }
648     }
649     return 1;
650 }
651
652 int
653 holding_file_get_dumpfile(
654     char *      fname,
655     dumpfile_t *file)
656 {
657     char buffer[DISK_BLOCK_BYTES];
658     int fd;
659
660     memset(buffer, 0, sizeof(buffer));
661
662     fh_init(file);
663     file->type = F_UNKNOWN;
664     if((fd = robust_open(fname, O_RDONLY, 0)) == -1)
665         return 0;
666
667     if(full_read(fd, buffer, SIZEOF(buffer)) != sizeof(buffer)) {
668         aclose(fd);
669         return 0;
670     }
671     aclose(fd);
672
673     parse_file_header(buffer, file, SIZEOF(buffer));
674     return 1;
675 }
676
677 /*
678  * Cleanup
679  */
680
681 typedef struct {
682     corrupt_dle_fn corrupt_dle;
683     FILE *verbose_output;
684 } holding_cleanup_datap_t;
685
686 static int
687 holding_cleanup_disk(
688     gpointer datap,
689     G_GNUC_UNUSED char *base,
690     G_GNUC_UNUSED char *element,
691     char *fqpath,
692     int is_cruft)
693 {
694     holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
695
696     if (data->verbose_output) {
697         if (is_cruft)
698             g_fprintf(data->verbose_output, 
699                 _("Invalid holding disk '%s'\n"), fqpath);
700         else
701             g_fprintf(data->verbose_output, 
702                 _("Cleaning up holding disk '%s'\n"), fqpath);
703     }
704
705     return 1;
706 }
707
708 static int
709 holding_cleanup_dir(
710     gpointer datap,
711     G_GNUC_UNUSED char *base,
712     char *element,
713     char *fqpath,
714     int is_cruft)
715 {
716     holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
717
718     if (is_cruft) {
719         if (data->verbose_output)
720             g_fprintf(data->verbose_output, 
721                 _("Invalid holding directory '%s'\n"), fqpath);
722         return 0;
723     }
724
725     /* try removing it */
726     if (rmdir(fqpath) == 0) {
727         /* success, so don't try to walk into it */
728         if (data->verbose_output)
729             g_fprintf(data->verbose_output,
730                 _(" ..removed empty directory '%s'\n"), element);
731         return 0;
732     }
733
734     if (data->verbose_output)
735         g_fprintf(data->verbose_output, 
736             _(" ..cleaning up holding directory '%s'\n"), element);
737
738     return 1;
739 }
740
741 static int
742 holding_cleanup_file(
743     gpointer datap,
744     G_GNUC_UNUSED char *base,
745     char *element,
746     char *fqpath,
747     int is_cruft)
748 {
749     holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
750     int stat;
751     int l;
752     dumpfile_t file;
753     disk_t *dp;
754
755     if (is_cruft) {
756         if (data->verbose_output)
757             g_fprintf(data->verbose_output, 
758                 _("Invalid holding file '%s'\n"), element);
759         return 0;
760     }
761
762
763     stat = holding_file_get_dumpfile(fqpath, &file);
764
765     if (!stat) {
766         if (data->verbose_output)
767             g_fprintf(data->verbose_output, 
768                 _("Could not read read header from '%s'\n"), element);
769         dumpfile_free_data(&file);
770         return 0;
771     }
772
773     if (file.type != F_DUMPFILE && file.type != F_CONT_DUMPFILE) {
774         if (data->verbose_output)
775             g_fprintf(data->verbose_output, 
776                 _("File '%s' is not a dump file\n"), element);
777         dumpfile_free_data(&file);
778         return 0;
779     }
780
781     if(file.dumplevel < 0 || file.dumplevel > 399) {
782         if (data->verbose_output)
783             g_fprintf(data->verbose_output, 
784                 _("File '%s' has invalid level %d\n"), element, file.dumplevel);
785         dumpfile_free_data(&file);
786         return 0;
787     }
788
789     dp = lookup_disk(file.name, file.disk);
790
791     if (dp == NULL) {
792         if (data->verbose_output)
793             g_fprintf(data->verbose_output, 
794                 _("File '%s' is for '%s:%s', which is not in the disklist\n"), 
795                     element, file.name, file.disk);
796         dumpfile_free_data(&file);
797         return 0;
798     }
799
800     if ((l = strlen(element)) >= 7 && strncmp(&fqpath[l-4],".tmp",4) == 0) {
801         char *destname;
802
803         /* generate a name without '.tmp' */
804         destname = stralloc(fqpath);
805         destname[strlen(destname) - 4] = '\0';
806
807         /* OK, it passes muster -- rename it to salvage some data,
808          * and mark the DLE as corrupted */
809         if (data->verbose_output)
810             g_fprintf(data->verbose_output, 
811                 _("Processing partial holding file '%s'\n"), element);
812
813         if(rename_tmp_holding(destname, 0)) {
814             if (data->corrupt_dle)
815                 data->corrupt_dle(dp->host->hostname, dp->name);
816         } else {
817             dbprintf(_("rename_tmp_holding(%s) failed\n"), destname);
818             if (data->verbose_output)
819                 g_fprintf(data->verbose_output, 
820                     _("Rename of '%s' to '%s' failed.\n"), element, destname);
821         }
822
823         amfree(destname);
824     }
825
826     dumpfile_free_data(&file);
827     return 1;
828 }
829
830 void
831 holding_cleanup(
832     corrupt_dle_fn corrupt_dle,
833     FILE *verbose_output)
834 {
835     holding_cleanup_datap_t data;
836     data.corrupt_dle = corrupt_dle;
837     data.verbose_output = verbose_output;
838
839     holding_walk((gpointer)&data,
840         STOP_AT_FILE,
841         holding_cleanup_disk,
842         holding_cleanup_dir,
843         holding_cleanup_file,
844         NULL);
845 }
846
847 /*
848  * Application support
849  */
850
851 void
852 holding_set_origsize(
853     char  *holding_file,
854     off_t  orig_size)
855 {
856     int         fd;
857     size_t      buflen;
858     char        buffer[DISK_BLOCK_BYTES];
859     char       *read_buffer;
860     dumpfile_t  file;
861
862     if((fd = robust_open(holding_file, O_RDWR, 0)) == -1) {
863         dbprintf(_("holding_set_origsize: open of %s failed: %s\n"),
864                  holding_file, strerror(errno));
865         return;
866     }
867
868     buflen = full_read(fd, buffer, SIZEOF(buffer));
869     if (buflen <= 0) {
870         dbprintf(_("holding_set_origsize: %s: empty file?\n"), holding_file);
871         return;
872     }
873     parse_file_header(buffer, &file, (size_t)buflen);
874     lseek(fd, (off_t)0, SEEK_SET);
875     file.orig_size = orig_size;
876     read_buffer = build_header(&file, NULL, DISK_BLOCK_BYTES);
877     full_write(fd, read_buffer, DISK_BLOCK_BYTES);
878     dumpfile_free_data(&file);
879     amfree(read_buffer);
880     close(fd);
881 }
882
883 int
884 rename_tmp_holding(
885     char *      holding_file,
886     int         complete)
887 {
888     int fd;
889     size_t buflen;
890     char buffer[DISK_BLOCK_BYTES];
891     dumpfile_t file;
892     char *filename;
893     char *filename_tmp = NULL;
894
895     memset(buffer, 0, sizeof(buffer));
896     filename = stralloc(holding_file);
897     while(filename != NULL && filename[0] != '\0') {
898         filename_tmp = newvstralloc(filename_tmp, filename, ".tmp", NULL);
899         if((fd = robust_open(filename_tmp,O_RDONLY, 0)) == -1) {
900             dbprintf(_("rename_tmp_holding: open of %s failed: %s\n"),filename_tmp,strerror(errno));
901             amfree(filename);
902             amfree(filename_tmp);
903             return 0;
904         }
905         buflen = full_read(fd, buffer, SIZEOF(buffer));
906         close(fd);
907
908         if(rename(filename_tmp, filename) != 0) {
909             dbprintf(_("rename_tmp_holding: could not rename \"%s\" to \"%s\": %s"),
910                     filename_tmp, filename, strerror(errno));
911         }
912
913         if (buflen <= 0) {
914             dbprintf(_("rename_tmp_holding: %s: empty file?\n"), filename);
915             amfree(filename);
916             amfree(filename_tmp);
917             return 0;
918         }
919         parse_file_header(buffer, &file, (size_t)buflen);
920         if(complete == 0 ) {
921             char * header;
922             if((fd = robust_open(filename, O_RDWR, 0)) == -1) {
923                 dbprintf(_("rename_tmp_holdingX: open of %s failed: %s\n"),
924                         filename, strerror(errno));
925                 dumpfile_free_data(&file);
926                 amfree(filename);
927                 amfree(filename_tmp);
928                 return 0;
929
930             }
931             file.is_partial = 1;
932             if (debug_holding > 1)
933                 dump_dumpfile_t(&file);
934             header = build_header(&file, NULL, DISK_BLOCK_BYTES);
935             if (!header) /* this shouldn't happen */
936                 error(_("header does not fit in %zd bytes"), (size_t)DISK_BLOCK_BYTES);
937             if (full_write(fd, header, DISK_BLOCK_BYTES) != DISK_BLOCK_BYTES) {
938                 dbprintf(_("rename_tmp_holding: writing new header failed: %s"),
939                         strerror(errno));
940                 dumpfile_free_data(&file);
941                 amfree(filename);
942                 amfree(filename_tmp);
943                 free(header);
944                 close(fd);
945                 return 0;
946             }
947             free(header);
948             close(fd);
949         }
950         filename = newstralloc(filename, file.cont_filename);
951         dumpfile_free_data(&file);
952     }
953     amfree(filename);
954     amfree(filename_tmp);
955     return 1;
956 }
957
958
959 int
960 mkholdingdir(
961     char *      diskdir)
962 {
963     struct stat stat_hdp;
964     int success = 1;
965
966     if (mkpdir(diskdir, 0770, (uid_t)-1, (gid_t)-1) != 0 && errno != EEXIST) {
967         log_add(L_WARNING, _("WARNING: could not create parents of %s: %s"),
968                 diskdir, strerror(errno));
969         success = 0;
970     }
971     else if (mkdir(diskdir, 0770) != 0 && errno != EEXIST) {
972         log_add(L_WARNING, _("WARNING: could not create %s: %s"),
973                 diskdir, strerror(errno));
974         success = 0;
975     }
976     else if (stat(diskdir, &stat_hdp) == -1) {
977         log_add(L_WARNING, _("WARNING: could not stat %s: %s"),
978                 diskdir, strerror(errno));
979         success = 0;
980     }
981     else {
982         if (!S_ISDIR((stat_hdp.st_mode))) {
983             log_add(L_WARNING, _("WARNING: %s is not a directory"),
984                     diskdir);
985             success = 0;
986         }
987         else if (access(diskdir,W_OK) != 0) {
988             log_add(L_WARNING, _("WARNING: directory %s is not writable"),
989                     diskdir);
990             success = 0;
991         }
992     }
993     return success;
994 }