c0e1ff8af62455466ff58f9c09a4f0b7a2c5e813
[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         dumpfile_free_data(&file);
212     }
213
214     amfree(filename);
215 }
216
217 /* Recurse over all holding files in a holding directory.
218  *
219  * Call per_file_fn for each file, and so on, stopping at the level given by 
220  * stop_at.
221  *
222  * datap is passed, unchanged, to all holding_walk_fns.
223  *
224  * @param hdir: holding directory to examine (fully qualified path)
225  * @param datap: generic user-data pointer
226  * @param stop_at: do not proceed beyond this level of the hierarchy
227  * @param per_file_fn: function to call for each holding file
228  * @param per_chunk_fn: function to call for each holding chunk
229  */
230 static void holding_walk_dir(
231     char *hdir,
232     gpointer datap,
233     stop_at_t stop_at,
234     holding_walk_fn per_file_fn,
235     holding_walk_fn per_chunk_fn)
236 {
237     DIR *dir;
238     struct dirent *workdir;
239     char *hfile = NULL;
240     dumpfile_t dumpf;
241     int dumpf_ok;
242     int proceed = 1;
243
244     if ((dir = opendir(hdir)) == NULL) {
245         if (errno != ENOENT)
246            dbprintf(_("Warning: could not open holding dir %s: %s\n"),
247                   hdir, strerror(errno));
248         return;
249     }
250
251     while ((workdir = readdir(dir)) != NULL) {
252         int is_cruft = 0;
253
254         if (is_dot_or_dotdot(workdir->d_name))
255             continue; /* expected cruft */
256
257         hfile = newvstralloc(hfile,
258                      hdir, "/", workdir->d_name,
259                      NULL);
260
261         /* filter out various undesirables */
262         if (is_emptyfile(hfile))
263             is_cruft = 1;
264
265         if (is_dir(hfile)) {
266             is_cruft= 1;
267         }
268
269         if (!(dumpf_ok=holding_file_get_dumpfile(hfile, &dumpf)) ||
270             dumpf.type != F_DUMPFILE) {
271             if (dumpf_ok && dumpf.type == F_CONT_DUMPFILE)
272                 continue; /* silently skip expected file */
273
274             is_cruft = 1;
275         }
276
277         if (dumpf.dumplevel < 0 || dumpf.dumplevel > 9) {
278             is_cruft = 1;
279         }
280
281         if (per_file_fn) 
282             proceed = per_file_fn(datap, 
283                         hdir, 
284                         workdir->d_name, 
285                         hfile, 
286                         is_cruft);
287         if (!is_cruft && proceed && stop_at != STOP_AT_FILE)
288             holding_walk_file(hfile,
289                     datap,
290                     per_chunk_fn);
291         dumpfile_free_data(&dumpf);
292     }
293
294     closedir(dir);
295     amfree(hfile);
296 }
297
298 /* Recurse over all holding directories in a holding disk.
299  *
300  * Call per_dir_fn for each dir, and so on, stopping at the level given by 
301  * stop_at.
302  *
303  * datap is passed, unchanged, to all holding_walk_fns.
304  *
305  * @param hdisk: holding disk to examine (fully qualified path)
306  * @param datap: generic user-data pointer
307  * @param stop_at: do not proceed beyond this level of the hierarchy
308  * @param per_dir_fn: function to call for each holding dir
309  * @param per_file_fn: function to call for each holding file
310  * @param per_chunk_fn: function to call for each holding chunk
311  */
312 static void 
313 holding_walk_disk(
314     char *hdisk,
315     gpointer datap,
316     stop_at_t stop_at,
317     holding_walk_fn per_dir_fn,
318     holding_walk_fn per_file_fn,
319     holding_walk_fn per_chunk_fn)
320 {
321     DIR *dir;
322     struct dirent *workdir;
323     char *hdir = NULL;
324     int proceed = 1;
325
326     if ((dir = opendir(hdisk)) == NULL) {
327         if (errno != ENOENT)
328            dbprintf(_("Warning: could not open holding disk %s: %s\n"),
329                   hdisk, strerror(errno));
330         return;
331     }
332
333     while ((workdir = readdir(dir)) != NULL) {
334         int is_cruft = 0;
335
336         if (is_dot_or_dotdot(workdir->d_name))
337             continue; /* expected cruft */
338
339         hdir = newvstralloc(hdir,
340                      hdisk, "/", workdir->d_name,
341                      NULL);
342
343         /* detect cruft */
344         if (!is_dir(hdir)) {
345             is_cruft = 1;
346         } else if (!is_datestr(workdir->d_name)) {
347             /* EXT2/3 leave these in the root of each volume */
348             if (strcmp(workdir->d_name, "lost+found") == 0)
349                 continue; /* expected cruft */
350             else
351                 is_cruft = 1; /* unexpected */
352         }
353
354         if (per_dir_fn) 
355             proceed = per_dir_fn(datap, 
356                         hdisk, 
357                         workdir->d_name, 
358                         hdir, 
359                         is_cruft);
360         if (!is_cruft && proceed && stop_at != STOP_AT_DIR)
361             holding_walk_dir(hdir,
362                     datap,
363                     stop_at,
364                     per_file_fn,
365                     per_chunk_fn);
366     }
367
368     closedir(dir);
369     amfree(hdir);
370 }
371
372 /* Recurse over all holding disks.
373  *
374  * Call per_disk_fn for each disk, per_dir_fn for each dir, and so on, stopping
375  * at the level given by stop_at.
376  *
377  * datap is passed, unchanged, to all holding_walk_fns.
378  *
379  * @param datap: generic user-data pointer
380  * @param stop_at: do not proceed beyond this level of the hierarchy
381  * @param per_disk_fn: function to call for each holding disk
382  * @param per_dir_fn: function to call for each holding dir
383  * @param per_file_fn: function to call for each holding file
384  * @param per_chunk_fn: function to call for each holding chunk
385  */
386 static void 
387 holding_walk(
388     gpointer datap,
389     stop_at_t stop_at,
390     holding_walk_fn per_disk_fn,
391     holding_walk_fn per_dir_fn,
392     holding_walk_fn per_file_fn,
393     holding_walk_fn per_chunk_fn)
394 {
395     holdingdisk_t *hdisk_conf;
396     char *hdisk;
397     int proceed = 1;
398
399     for (hdisk_conf = getconf_holdingdisks(); 
400                 hdisk_conf != NULL;
401                 hdisk_conf = holdingdisk_next(hdisk_conf)) {
402         int is_cruft = 0;
403
404         hdisk = holdingdisk_get_diskdir(hdisk_conf);
405         if (!is_dir(hdisk))
406             is_cruft = 1;
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     disk_t *dp;
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         /* check that the hostname and disk are in the disklist */
553         dp = lookup_disk(file.name, file.disk);
554         if (dp == NULL) {
555             dbprintf(_("%s: disk %s:%s not in database, skipping it."),
556                         (char *)file_elt->data, file.name, file.disk);
557             dumpfile_free_data(&file);
558             continue;
559         }
560
561         /* passed all tests -- we'll flush this file */
562         result_list = g_slist_insert_sorted(result_list, 
563             stralloc(file_elt->data), 
564             g_compare_strings);
565         dumpfile_free_data(&file);
566     }
567
568     if (file_list) g_slist_free_full(file_list);
569
570     return result_list;
571 }
572
573 GSList *
574 holding_get_all_datestamps(void)
575 {
576     GSList *all_files, *file;
577     GSList *datestamps = NULL;
578
579     /* enumerate all files */
580     all_files = holding_get_files(NULL, 1);
581     for (file = all_files; file != NULL; file = file->next) {
582         dumpfile_t dfile;
583         if (!holding_file_get_dumpfile((char *)file->data, &dfile))
584             continue;
585         if (!g_slist_find_custom(datestamps, dfile.datestamp,
586                                  g_compare_strings)) {
587             datestamps = g_slist_insert_sorted(datestamps, 
588                                                stralloc(dfile.datestamp), 
589                                                g_compare_strings);
590         }
591         dumpfile_free_data(&dfile);
592     }
593
594     g_slist_free_full(all_files);
595
596     return datestamps;
597 }
598
599 off_t
600 holding_file_size(
601     char *hfile,
602     int strip_headers)
603 {
604     dumpfile_t file;
605     char *filename;
606     off_t size = (off_t)0;
607     struct stat finfo;
608
609     /* (note: we don't use holding_get_file_chunks here because that would
610      * entail opening each file twice) */
611
612     /* Loop through all cont_filenames (subsequent chunks) */
613     filename = stralloc(hfile);
614     while (filename != NULL && filename[0] != '\0') {
615         /* stat the file for its size */
616         if (stat(filename, &finfo) == -1) {
617             dbprintf(_("stat %s: %s\n"), filename, strerror(errno));
618             size = -1;
619             break;
620         }
621         size += (finfo.st_size+(off_t)1023)/(off_t)1024;
622         if (strip_headers)
623             size -= (off_t)(DISK_BLOCK_BYTES / 1024);
624
625         /* get the header to look for cont_filename */
626         if (!holding_file_get_dumpfile(filename, &file)) {
627             dbprintf(_("holding_file_size: open of %s failed.\n"), filename);
628             size = -1;
629             break;
630         }
631
632         /* on to the next chunk */
633         filename = newstralloc(filename, file.cont_filename);
634         dumpfile_free_data(&file);
635     }
636     amfree(filename);
637     return size;
638 }
639
640
641 int
642 holding_file_unlink(
643     char *hfile)
644 {
645     GSList *chunklist;
646     GSList *chunk;
647
648     chunklist = holding_get_file_chunks(hfile);
649     if (!chunklist)
650         return 0;
651
652     for (chunk = chunklist; chunk != NULL; chunk = chunk->next) {
653         if (unlink((char *)chunk->data)<0) {
654             dbprintf(_("holding_file_unlink: could not unlink %s: %s\n"),
655                     (char *)chunk->data, strerror(errno));
656             return 0;
657         }
658     }
659     return 1;
660 }
661
662 int
663 holding_file_get_dumpfile(
664     char *      fname,
665     dumpfile_t *file)
666 {
667     char buffer[DISK_BLOCK_BYTES];
668     int fd;
669
670     memset(buffer, 0, sizeof(buffer));
671
672     fh_init(file);
673     file->type = F_UNKNOWN;
674     if((fd = robust_open(fname, O_RDONLY, 0)) == -1)
675         return 0;
676
677     if(full_read(fd, buffer, SIZEOF(buffer)) != sizeof(buffer)) {
678         aclose(fd);
679         return 0;
680     }
681     aclose(fd);
682
683     parse_file_header(buffer, file, SIZEOF(buffer));
684     return 1;
685 }
686
687 /*
688  * Cleanup
689  */
690
691 typedef struct {
692     corrupt_dle_fn corrupt_dle;
693     FILE *verbose_output;
694 } holding_cleanup_datap_t;
695
696 static int
697 holding_cleanup_disk(
698     gpointer datap,
699     G_GNUC_UNUSED char *base,
700     G_GNUC_UNUSED char *element,
701     char *fqpath,
702     int is_cruft)
703 {
704     holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
705
706     if (data->verbose_output) {
707         if (is_cruft)
708             g_fprintf(data->verbose_output, 
709                 _("Invalid holding disk '%s'\n"), fqpath);
710         else
711             g_fprintf(data->verbose_output, 
712                 _("Cleaning up holding disk '%s'\n"), fqpath);
713     }
714
715     return 1;
716 }
717
718 static int
719 holding_cleanup_dir(
720     gpointer datap,
721     G_GNUC_UNUSED char *base,
722     char *element,
723     char *fqpath,
724     int is_cruft)
725 {
726     holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
727
728     if (is_cruft) {
729         if (data->verbose_output)
730             g_fprintf(data->verbose_output, 
731                 _("Invalid holding directory '%s'\n"), fqpath);
732         return 0;
733     }
734
735     /* try removing it */
736     if (rmdir(fqpath) == 0) {
737         /* success, so don't try to walk into it */
738         if (data->verbose_output)
739             g_fprintf(data->verbose_output,
740                 _(" ..removed empty directory '%s'\n"), element);
741         return 0;
742     }
743
744     if (data->verbose_output)
745         g_fprintf(data->verbose_output, 
746             _(" ..cleaning up holding directory '%s'\n"), element);
747
748     return 1;
749 }
750
751 static int
752 holding_cleanup_file(
753     gpointer datap,
754     G_GNUC_UNUSED char *base,
755     char *element,
756     char *fqpath,
757     int is_cruft)
758 {
759     holding_cleanup_datap_t *data = (holding_cleanup_datap_t *)datap;
760     int stat;
761     int l;
762     dumpfile_t file;
763     disk_t *dp;
764
765     if (is_cruft) {
766         if (data->verbose_output)
767             g_fprintf(data->verbose_output, 
768                 _("Invalid holding file '%s'\n"), element);
769         return 0;
770     }
771
772
773     stat = holding_file_get_dumpfile(fqpath, &file);
774
775     if (!stat) {
776         if (data->verbose_output)
777             g_fprintf(data->verbose_output, 
778                 _("Could not read read header from '%s'\n"), element);
779         dumpfile_free_data(&file);
780         return 0;
781     }
782
783     if (file.type != F_DUMPFILE && file.type != F_CONT_DUMPFILE) {
784         if (data->verbose_output)
785             g_fprintf(data->verbose_output, 
786                 _("File '%s' is not a dump file\n"), element);
787         dumpfile_free_data(&file);
788         return 0;
789     }
790
791     if(file.dumplevel < 0 || file.dumplevel > 9) {
792         if (data->verbose_output)
793             g_fprintf(data->verbose_output, 
794                 _("File '%s' has invalid level %d\n"), element, file.dumplevel);
795         dumpfile_free_data(&file);
796         return 0;
797     }
798
799     dp = lookup_disk(file.name, file.disk);
800
801     if (dp == NULL) {
802         if (data->verbose_output)
803             g_fprintf(data->verbose_output, 
804                 _("File '%s' is for '%s:%s', which is not in the disklist\n"), 
805                     element, file.name, file.disk);
806         dumpfile_free_data(&file);
807         return 0;
808     }
809
810     if ((l = strlen(element)) >= 7 && strncmp(&fqpath[l-4],".tmp",4) == 0) {
811         char *destname;
812
813         /* generate a name without '.tmp' */
814         destname = stralloc(fqpath);
815         destname[strlen(destname) - 4] = '\0';
816
817         /* OK, it passes muster -- rename it to salvage some data,
818          * and mark the DLE as corrupted */
819         if (data->verbose_output)
820             g_fprintf(data->verbose_output, 
821                 _("Processing partial holding file '%s'\n"), element);
822
823         if(rename_tmp_holding(destname, 0)) {
824             if (data->corrupt_dle)
825                 data->corrupt_dle(dp->host->hostname, dp->name);
826         } else {
827             dbprintf(_("rename_tmp_holding(%s) failed\n"), destname);
828             if (data->verbose_output)
829                 g_fprintf(data->verbose_output, 
830                     _("Rename of '%s' to '%s' failed.\n"), element, destname);
831         }
832
833         amfree(destname);
834     }
835
836     dumpfile_free_data(&file);
837     return 1;
838 }
839
840 void
841 holding_cleanup(
842     corrupt_dle_fn corrupt_dle,
843     FILE *verbose_output)
844 {
845     holding_cleanup_datap_t data;
846     data.corrupt_dle = corrupt_dle;
847     data.verbose_output = verbose_output;
848
849     holding_walk((gpointer)&data,
850         STOP_AT_FILE,
851         holding_cleanup_disk,
852         holding_cleanup_dir,
853         holding_cleanup_file,
854         NULL);
855 }
856
857 /*
858  * Application support
859  */
860
861 int
862 rename_tmp_holding(
863     char *      holding_file,
864     int         complete)
865 {
866     int fd;
867     size_t buflen;
868     char buffer[DISK_BLOCK_BYTES];
869     dumpfile_t file;
870     char *filename;
871     char *filename_tmp = NULL;
872
873     memset(buffer, 0, sizeof(buffer));
874     filename = stralloc(holding_file);
875     while(filename != NULL && filename[0] != '\0') {
876         filename_tmp = newvstralloc(filename_tmp, filename, ".tmp", NULL);
877         if((fd = robust_open(filename_tmp,O_RDONLY, 0)) == -1) {
878             dbprintf(_("rename_tmp_holding: open of %s failed: %s\n"),filename_tmp,strerror(errno));
879             amfree(filename);
880             amfree(filename_tmp);
881             return 0;
882         }
883         buflen = full_read(fd, buffer, SIZEOF(buffer));
884         close(fd);
885
886         if(rename(filename_tmp, filename) != 0) {
887             dbprintf(_("rename_tmp_holding: could not rename \"%s\" to \"%s\": %s"),
888                     filename_tmp, filename, strerror(errno));
889         }
890
891         if (buflen <= 0) {
892             dbprintf(_("rename_tmp_holding: %s: empty file?\n"), filename);
893             amfree(filename);
894             amfree(filename_tmp);
895             return 0;
896         }
897         parse_file_header(buffer, &file, (size_t)buflen);
898         if(complete == 0 ) {
899             char * header;
900             if((fd = robust_open(filename, O_RDWR, 0)) == -1) {
901                 dbprintf(_("rename_tmp_holdingX: open of %s failed: %s\n"),
902                         filename, strerror(errno));
903                 dumpfile_free_data(&file);
904                 amfree(filename);
905                 amfree(filename_tmp);
906                 return 0;
907
908             }
909             file.is_partial = 1;
910             header = build_header(&file, DISK_BLOCK_BYTES);
911             if (full_write(fd, header, DISK_BLOCK_BYTES) != DISK_BLOCK_BYTES) {
912                 dbprintf(_("rename_tmp_holding: writing new header failed: %s"),
913                         strerror(errno));
914                 dumpfile_free_data(&file);
915                 amfree(filename);
916                 amfree(filename_tmp);
917                 close(fd);
918                 return 0;
919             }
920             close(fd);
921         }
922         filename = newstralloc(filename, file.cont_filename);
923         dumpfile_free_data(&file);
924     }
925     amfree(filename);
926     amfree(filename_tmp);
927     return 1;
928 }
929
930
931 int
932 mkholdingdir(
933     char *      diskdir)
934 {
935     struct stat stat_hdp;
936     int success = 1;
937
938     if (mkpdir(diskdir, 0770, (uid_t)-1, (gid_t)-1) != 0 && errno != EEXIST) {
939         log_add(L_WARNING, _("WARNING: could not create parents of %s: %s"),
940                 diskdir, strerror(errno));
941         success = 0;
942     }
943     else if (mkdir(diskdir, 0770) != 0 && errno != EEXIST) {
944         log_add(L_WARNING, _("WARNING: could not create %s: %s"),
945                 diskdir, strerror(errno));
946         success = 0;
947     }
948     else if (stat(diskdir, &stat_hdp) == -1) {
949         log_add(L_WARNING, _("WARNING: could not stat %s: %s"),
950                 diskdir, strerror(errno));
951         success = 0;
952     }
953     else {
954         if (!S_ISDIR((stat_hdp.st_mode))) {
955             log_add(L_WARNING, _("WARNING: %s is not a directory"),
956                     diskdir);
957             success = 0;
958         }
959         else if (access(diskdir,W_OK) != 0) {
960             log_add(L_WARNING, _("WARNING: directory %s is not writable"),
961                     diskdir);
962             success = 0;
963         }
964     }
965     return success;
966 }