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