8bdbb058f0e6978d27881fd5c96fe75ceb980734
[debian/amanda] / application-src / amgtar.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: amgtar.c 8888 2007-10-02 13:40:42Z martineau $
28  *
29  * send estimated backup sizes using dump
30  */
31
32 /* PROPERTY:
33  *
34  * GNUTAR-PATH     (default GNUTAR)
35  * GNUTAR-LISTDIR  (default CNF_GNUTAR_LIST_DIR)
36  * DIRECTORY       (no default, if set, the backup will be from that directory
37  *                  instead of from the --device)
38  * ONE-FILE-SYSTEM (default YES)
39  * SPARSE          (default YES)
40  * ATIME-PRESERVE  (default YES)
41  * CHECK-DEVICE    (default YES)
42  * INCLUDE-FILE
43  * INCLUDE-LIST
44  * INCLUDE-OPTIONAL
45  * EXCLUDE-FILE
46  * EXCLUDE-LIST
47  * EXCLUDE-OPTIONAL
48  * NORMAL
49  * IGNORE
50  * STRANGE
51  * EXIT-HANDLING   (1=GOOD 2=BAD)
52  * TAR-BLOCKSIZE   (default does not add --blocking-factor option,
53  *                  using tar's default)
54  */
55
56 #include "amanda.h"
57 #include "pipespawn.h"
58 #include "amfeatures.h"
59 #include "clock.h"
60 #include "util.h"
61 #include "getfsent.h"
62 #include "version.h"
63 #include "client_util.h"
64 #include "conffile.h"
65 #include "amandad.h"
66 #include "getopt.h"
67 #include "sendbackup.h"
68
69 int debug_application = 1;
70 #define application_debug(i, ...) do {  \
71         if ((i) <= debug_application) { \
72             dbprintf(__VA_ARGS__);      \
73         }                               \
74 } while (0)
75
76 static amregex_t init_re_table[] = {
77   /* tar prints the size in bytes */
78   AM_SIZE_RE("^ *Total bytes written: [0-9][0-9]*", 1, 1),
79   AM_NORMAL_RE("^could not open conf file"),
80   AM_NORMAL_RE("^Elapsed time:"),
81   AM_NORMAL_RE("^Throughput"),
82   AM_IGNORE_RE(": Directory is new$"),
83   AM_IGNORE_RE(": Directory has been renamed"),
84
85   /* GNU tar 1.13.17 will print this warning when (not) backing up a
86      Unix named socket.  */
87   AM_NORMAL_RE(": socket ignored$"),
88
89   /* GNUTAR produces a few error messages when files are modified or
90      removed while it is running.  They may cause data to be lost, but
91      then they may not.  We shouldn't consider them NORMAL until
92      further investigation.  */
93   AM_NORMAL_RE(": File .* shrunk by [0-9][0-9]* bytes, padding with zeros"),
94   AM_NORMAL_RE(": Cannot add file .*: No such file or directory$"),
95   AM_NORMAL_RE(": Error exit delayed from previous errors"),
96   
97   /* catch-all: DMP_STRANGE is returned for all other lines */
98   AM_STRANGE_RE(NULL)
99 };
100 static amregex_t *re_table;
101
102 /* local functions */
103 int main(int argc, char **argv);
104
105 typedef struct application_argument_s {
106     char      *config;
107     char      *host;
108     int        message;
109     int        collection;
110     int        calcsize;
111     char      *tar_blocksize;
112     GSList    *level;
113     dle_t      dle;
114     int        argc;
115     char     **argv;
116 } application_argument_t;
117
118 enum { CMD_ESTIMATE, CMD_BACKUP };
119
120 static void amgtar_support(application_argument_t *argument);
121 static void amgtar_selfcheck(application_argument_t *argument);
122 static void amgtar_estimate(application_argument_t *argument);
123 static void amgtar_backup(application_argument_t *argument);
124 static void amgtar_restore(application_argument_t *argument);
125 static void amgtar_validate(application_argument_t *argument);
126 static void amgtar_build_exinclude(dle_t *dle, int verbose,
127                                    int *nb_exclude, char **file_exclude,
128                                    int *nb_include, char **file_include);
129 static char *amgtar_get_incrname(application_argument_t *argument, int level);
130 static char **amgtar_build_argv(application_argument_t *argument,
131                                 char *incrname, int command);
132 static amregex_t *build_re_table(amregex_t *orig_re_table,
133                                  GSList *normal_message,
134                                  GSList *ignore_message,
135                                  GSList *strange_message);
136 static void add_type_table(dmpline_t typ,
137                            amregex_t **re_table, amregex_t *orig_re_table,
138                            GSList *normal_message, GSList *ignore_message,
139                            GSList *strange_message);
140 static void add_list_table(dmpline_t typ, amregex_t **re_table,
141                           GSList *message);
142 static char *gnutar_path;
143 static char *gnutar_listdir;
144 static char *gnutar_directory;
145 static int gnutar_onefilesystem;
146 static int gnutar_atimepreserve;
147 static int gnutar_checkdevice;
148 static int gnutar_sparse;
149 static GSList *normal_message = NULL;
150 static GSList *ignore_message = NULL;
151 static GSList *strange_message = NULL;
152 static char   *exit_handling;
153 static int    exit_value[256];
154
155 static struct option long_options[] = {
156     {"config"          , 1, NULL,  1},
157     {"host"            , 1, NULL,  2},
158     {"disk"            , 1, NULL,  3},
159     {"device"          , 1, NULL,  4},
160     {"level"           , 1, NULL,  5},
161     {"index"           , 1, NULL,  6},
162     {"message"         , 1, NULL,  7},
163     {"collection"      , 0, NULL,  8},
164     {"record"          , 0, NULL,  9},
165     {"gnutar-path"     , 1, NULL, 10},
166     {"gnutar-listdir"  , 1, NULL, 11},
167     {"one-file-system" , 1, NULL, 12},
168     {"sparse"          , 1, NULL, 13},
169     {"atime-preserve"  , 1, NULL, 14},
170     {"check-device"    , 1, NULL, 15},
171     {"include-file"    , 1, NULL, 16},
172     {"include-list"    , 1, NULL, 17},
173     {"include-optional", 1, NULL, 18},
174     {"exclude-file"    , 1, NULL, 19},
175     {"exclude-list"    , 1, NULL, 20},
176     {"exclude-optional", 1, NULL, 21},
177     {"directory"       , 1, NULL, 22},
178     {"normal"          , 1, NULL, 23},
179     {"ignore"          , 1, NULL, 24},
180     {"strange"         , 1, NULL, 25},
181     {"exit-handling"   , 1, NULL, 26},
182     {"calcsize"        , 0, NULL, 27},
183     {"tar-blocksize"   , 1, NULL, 28},
184     {NULL, 0, NULL, 0}
185 };
186
187
188 void
189 add_type_table(
190     dmpline_t   typ,
191     amregex_t **re_table,
192     amregex_t  *orig_re_table,
193     GSList     *normal_message,
194     GSList     *ignore_message,
195     GSList     *strange_message)
196 {
197     amregex_t *rp;
198
199     for(rp = orig_re_table; rp->regex != NULL; rp++) {
200         if (rp->typ == typ) {
201             int     found = 0;
202             GSList *mes;
203
204             for (mes = normal_message; mes != NULL; mes = mes->next) {
205                 if (strcmp(rp->regex, (char *)mes->data) == 0)
206                     found = 1;
207             }
208             for (mes = ignore_message; mes != NULL; mes = mes->next) {
209                 if (strcmp(rp->regex, (char *)mes->data) == 0)
210                     found = 1;
211             }
212             for (mes = strange_message; mes != NULL; mes = mes->next) {
213                 if (strcmp(rp->regex, (char *)mes->data) == 0)
214                     found = 1;
215             }
216             if (found == 0) {
217                 (*re_table)->regex   = rp->regex;
218                 (*re_table)->srcline = rp->srcline;
219                 (*re_table)->scale   = rp->scale;
220                 (*re_table)->field   = rp->field;
221                 (*re_table)->typ     = rp->typ;
222                 (*re_table)++;
223             }
224         }
225     }
226 }
227
228 void
229 add_list_table(
230     dmpline_t   typ,
231     amregex_t **re_table,
232     GSList     *message)
233 {
234     GSList *mes;
235
236     for (mes = message; mes != NULL; mes = mes->next) {
237         (*re_table)->regex = (char *)mes->data;
238         (*re_table)->srcline = 0;
239         (*re_table)->scale   = 0;
240         (*re_table)->field   = 0;
241         (*re_table)->typ     = typ;
242         (*re_table)++;
243     }
244 }
245
246 amregex_t *
247 build_re_table(
248     amregex_t *orig_re_table,
249     GSList    *normal_message,
250     GSList    *ignore_message,
251     GSList    *strange_message)
252 {
253     int        nb = 0;
254     amregex_t *rp;
255     amregex_t *re_table, *new_re_table;
256
257     for(rp = orig_re_table; rp->regex != NULL; rp++) {
258         nb++;
259     }
260     nb += g_slist_length(normal_message);
261     nb += g_slist_length(ignore_message);
262     nb += g_slist_length(strange_message);
263     nb ++;
264
265     re_table =  new_re_table = malloc(nb * sizeof(amregex_t));
266     
267     /* add SIZE from orig_re_table */
268     add_type_table(DMP_SIZE, &re_table, orig_re_table,
269                    normal_message, ignore_message, strange_message);
270
271     /* add ignore_message */
272     add_list_table(DMP_IGNORE, &re_table, ignore_message);
273
274     /* add IGNORE from orig_re_table */
275     add_type_table(DMP_IGNORE, &re_table, orig_re_table,
276                    normal_message, ignore_message, strange_message);
277
278     /* add normal_message */
279     add_list_table(DMP_NORMAL, &re_table, normal_message);
280
281     /* add NORMAL from orig_re_table */
282     add_type_table(DMP_NORMAL, &re_table, orig_re_table,
283                    normal_message, ignore_message, strange_message);
284
285     /* add strange_message */
286     add_list_table(DMP_STRANGE, &re_table, strange_message);
287
288     /* add STRANGE from orig_re_table */
289     add_type_table(DMP_STRANGE, &re_table, orig_re_table,
290                    normal_message, ignore_message, strange_message);
291
292     /* Add DMP_STRANGE with NULL regex,       */
293     /* it is not copied by previous statement */
294     re_table->regex = NULL;
295     re_table->srcline = 0;
296     re_table->scale = 0;
297     re_table->field = 0;
298     re_table->typ = DMP_STRANGE;
299
300     return new_re_table;
301 }
302
303 int
304 main(
305     int         argc,
306     char **     argv)
307 {
308     int c;
309     char *command;
310     application_argument_t argument;
311     int i;
312
313 #ifdef GNUTAR
314     gnutar_path = GNUTAR;
315 #else
316     gnutar_path = NULL;
317 #endif
318     gnutar_directory = NULL;
319     gnutar_onefilesystem = 1;
320     gnutar_atimepreserve = 1;
321     gnutar_checkdevice = 1;
322     gnutar_sparse = 1;
323     exit_handling = NULL;
324
325     /* initialize */
326
327     /*
328      * Configure program for internationalization:
329      *   1) Only set the message locale for now.
330      *   2) Set textdomain for all amanda related programs to "amanda"
331      *      We don't want to be forced to support dozens of message catalogs.
332      */  
333     setlocale(LC_MESSAGES, "C");
334     textdomain("amanda"); 
335
336     /* drop root privileges */
337     if (!set_root_privs(0)) {
338         error(_("amgtar must be run setuid root"));
339     }
340
341     safe_fd(3, 2);
342
343     set_pname("amgtar");
344
345     /* Don't die when child closes pipe */
346     signal(SIGPIPE, SIG_IGN);
347
348 #if defined(USE_DBMALLOC)
349     malloc_size_1 = malloc_inuse(&malloc_hist_1);
350 #endif
351
352     erroutput_type = (ERR_INTERACTIVE|ERR_SYSLOG);
353     dbopen(DBG_SUBDIR_CLIENT);
354     startclock();
355     dbprintf(_("version %s\n"), version());
356
357     config_init(CONFIG_INIT_CLIENT, NULL);
358
359     //check_running_as(RUNNING_AS_DUMPUSER_PREFERRED);
360     //root for amrecover
361     //RUNNING_AS_CLIENT_LOGIN from selfcheck, sendsize, sendbackup
362
363     /* parse argument */
364     command = argv[1];
365
366     argument.config     = NULL;
367     argument.host       = NULL;
368     argument.message    = 0;
369     argument.collection = 0;
370     argument.calcsize   = 0;
371     argument.tar_blocksize = NULL;
372     argument.level      = NULL;
373     init_dle(&argument.dle);
374
375     while (1) {
376         int option_index = 0;
377         c = getopt_long (argc, argv, "", long_options, &option_index);
378         if (c == -1) {
379             break;
380         }
381         switch (c) {
382         case 1: argument.config = stralloc(optarg);
383                 break;
384         case 2: argument.host = stralloc(optarg);
385                 break;
386         case 3: argument.dle.disk = stralloc(optarg);
387                 break;
388         case 4: argument.dle.device = stralloc(optarg);
389                 break;
390         case 5: argument.level = g_slist_append(argument.level,
391                                                 GINT_TO_POINTER(atoi(optarg)));
392                 break;
393         case 6: argument.dle.create_index = 1;
394                 break;
395         case 7: argument.message = 1;
396                 break;
397         case 8: argument.collection = 1;
398                 break;
399         case 9: argument.dle.record = 1;
400                 break;
401         case 10: gnutar_path = stralloc(optarg);
402                  break;
403         case 11: gnutar_listdir = stralloc(optarg);
404                  break;
405         case 12: if (optarg && strcasecmp(optarg, "YES") != 0)
406                      gnutar_onefilesystem = 0;
407                  break;
408         case 13: if (optarg && strcasecmp(optarg, "YES") != 0)
409                      gnutar_sparse = 0;
410                  break;
411         case 14: if (optarg && strcasecmp(optarg, "YES") != 0)
412                      gnutar_atimepreserve = 0;
413                  break;
414         case 15: if (optarg && strcasecmp(optarg, "YES") != 0)
415                      gnutar_checkdevice = 0;
416                  break;
417         case 16: if (optarg)
418                      argument.dle.include_file =
419                          append_sl(argument.dle.include_file, optarg);
420                  break;
421         case 17: if (optarg)
422                      argument.dle.include_list =
423                          append_sl(argument.dle.include_list, optarg);
424                  break;
425         case 18: argument.dle.include_optional = 1;
426                  break;
427         case 19: if (optarg)
428                      argument.dle.exclude_file =
429                          append_sl(argument.dle.exclude_file, optarg);
430                  break;
431         case 20: if (optarg)
432                      argument.dle.exclude_list =
433                          append_sl(argument.dle.exclude_list, optarg);
434                  break;
435         case 21: argument.dle.exclude_optional = 1;
436                  break;
437         case 22: gnutar_directory = stralloc(optarg);
438                  break;
439         case 23: if (optarg)
440                      normal_message = 
441                          g_slist_append(normal_message, optarg);
442                  break;
443         case 24: if (optarg)
444                      ignore_message = 
445                          g_slist_append(ignore_message, optarg);
446                  break;
447         case 25: if (optarg)
448                      strange_message = 
449                          g_slist_append(strange_message, optarg);
450                  break;
451         case 26: if (optarg)
452                      exit_handling = stralloc(optarg);
453                  break;
454         case 27: argument.calcsize = 1;
455                  break;
456         case 28: argument.tar_blocksize = stralloc(optarg);
457         case ':':
458         case '?':
459                 break;
460         }
461     }
462
463     argument.argc = argc - optind;
464     argument.argv = argv + optind;
465
466     if (argument.config) {
467         config_init(CONFIG_INIT_CLIENT | CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_OVERLAY,
468                     argument.config);
469         dbrename(get_config_name(), DBG_SUBDIR_CLIENT);
470     }
471
472     if (config_errors(NULL) >= CFGERR_ERRORS) {
473         g_critical(_("errors processing config file"));
474     }
475
476     re_table = build_re_table(init_re_table, normal_message, ignore_message,
477                               strange_message);
478
479     for(i=0;i<256;i++)
480         exit_value[i] = 1; /* BAD  */
481     exit_value[0] = 0;     /* GOOD */
482     exit_value[1] = 0;     /* GOOD */
483     if (exit_handling) {
484         char *s = exit_handling;
485         while (s) {
486             char *r = strchr(s, '=');
487             if (r) {
488                 int j = atoi(s);
489                 if (j >= 0 && j < 256) {
490                     r++;
491                     if (strncasecmp(r, "GOOD", 4) == 0) {
492                         exit_value[j] = 0;
493                     }
494                 }
495             }
496             s = strchr(s+1, ' ');
497         }
498     }
499
500     gnutar_listdir = getconf_str(CNF_GNUTAR_LIST_DIR);
501     if (strlen(gnutar_listdir) == 0)
502         gnutar_listdir = NULL;
503
504     if (gnutar_path) {
505         dbprintf("GNUTAR-PATH %s\n", gnutar_path);
506     } else {
507         dbprintf("GNUTAR-PATH is not set\n");
508     }
509     if (gnutar_listdir) {
510             dbprintf("GNUTAR-LISTDIR %s\n", gnutar_listdir);
511     } else {
512         dbprintf("GNUTAR-LISTDIR is not set\n");
513     }
514     if (gnutar_directory) {
515         dbprintf("DIRECTORY %s\n", gnutar_directory);
516     }
517     dbprintf("ONE-FILE-SYSTEM %s\n", gnutar_onefilesystem? "yes":"no");
518     dbprintf("SPARSE %s\n", gnutar_sparse? "yes":"no");
519     dbprintf("ATIME-PRESERVE %s\n", gnutar_atimepreserve? "yes":"no");
520     dbprintf("CHECK-DEVICE %s\n", gnutar_checkdevice? "yes":"no");
521     {
522         amregex_t *rp;
523         for (rp = re_table; rp->regex != NULL; rp++) {
524             switch (rp->typ) {
525                 case DMP_NORMAL : dbprintf("NORMAL %s\n", rp->regex); break;
526                 case DMP_IGNORE : dbprintf("IGNORE %s\n", rp->regex); break;
527                 case DMP_STRANGE: dbprintf("STRANGE %s\n", rp->regex); break;
528                 case DMP_SIZE   : dbprintf("SIZE %s\n", rp->regex); break;
529                 case DMP_ERROR  : dbprintf("ERROR %s\n", rp->regex); break;
530             }
531         }
532     }
533
534     if (strcmp(command, "support") == 0) {
535         amgtar_support(&argument);
536     } else if (strcmp(command, "selfcheck") == 0) {
537         amgtar_selfcheck(&argument);
538     } else if (strcmp(command, "estimate") == 0) {
539         amgtar_estimate(&argument);
540     } else if (strcmp(command, "backup") == 0) {
541         amgtar_backup(&argument);
542     } else if (strcmp(command, "restore") == 0) {
543         amgtar_restore(&argument);
544     } else if (strcmp(command, "validate") == 0) {
545         amgtar_validate(&argument);
546     } else {
547         dbprintf("Unknown command `%s'.\n", command);
548         fprintf(stderr, "Unknown command `%s'.\n", command);
549         exit (1);
550     }
551     return 0;
552 }
553
554 static void
555 amgtar_support(
556     application_argument_t *argument)
557 {
558     (void)argument;
559     fprintf(stdout, "CONFIG YES\n");
560     fprintf(stdout, "HOST YES\n");
561     fprintf(stdout, "DISK YES\n");
562     fprintf(stdout, "MAX-LEVEL 9\n");
563     fprintf(stdout, "INDEX-LINE YES\n");
564     fprintf(stdout, "INDEX-XML NO\n");
565     fprintf(stdout, "MESSAGE-LINE YES\n");
566     fprintf(stdout, "MESSAGE-XML NO\n");
567     fprintf(stdout, "RECORD YES\n");
568     fprintf(stdout, "INCLUDE-FILE YES\n");
569     fprintf(stdout, "INCLUDE-LIST YES\n");
570     fprintf(stdout, "INCLUDE-OPTIONAL YES\n");
571     fprintf(stdout, "EXCLUDE-FILE YES\n");
572     fprintf(stdout, "EXCLUDE-LIST YES\n");
573     fprintf(stdout, "EXCLUDE-OPTIONAL YES\n");
574     fprintf(stdout, "COLLECTION NO\n");
575     fprintf(stdout, "MULTI-ESTIMATE YES\n");
576     fprintf(stdout, "CALCSIZE YES\n");
577 }
578
579 static void
580 amgtar_selfcheck(
581     application_argument_t *argument)
582 {
583     amgtar_build_exinclude(&argument->dle, 1, NULL, NULL, NULL, NULL);
584
585     if (gnutar_path) {
586         check_file(gnutar_path, X_OK);
587     } else {
588         printf(_("ERROR [GNUTAR program not available]\n"));
589     }
590
591     set_root_privs(1);
592     if (gnutar_listdir && strlen(gnutar_listdir) == 0)
593         gnutar_listdir = NULL;
594     if (gnutar_listdir) {
595         check_dir(gnutar_listdir, R_OK|W_OK);
596     } else {
597         printf(_("ERROR [No GNUTAR-LISTDIR]\n"));
598     }
599
600     fprintf(stdout, "OK %s\n", argument->dle.disk);
601     if (gnutar_directory) {
602         check_dir(gnutar_directory, R_OK);
603     } else {
604         check_dir(argument->dle.device, R_OK);
605     }
606     set_root_privs(0);
607 }
608
609 static void
610 amgtar_estimate(
611     application_argument_t *argument)
612 {
613     char  *incrname = NULL;
614     char **my_argv = NULL;
615     char  *cmd = NULL;
616     int    nullfd = -1;
617     int    pipefd = -1;
618     FILE  *dumpout = NULL;
619     off_t  size = -1;
620     char   line[32768];
621     char  *errmsg = NULL;
622     char  *qerrmsg = NULL;
623     char  *qdisk;
624     amwait_t wait_status;
625     int tarpid;
626     amregex_t *rp;
627     times_t start_time;
628     int     level;
629     GSList *levels;
630
631     qdisk = quote_string(argument->dle.disk);
632
633     if (argument->calcsize) {
634         char *dirname;
635         char *file_exclude;
636         char *file_include;
637         int   nb_exclude;
638         int   nb_include;
639
640         if (gnutar_directory) {
641             dirname = gnutar_directory;
642         } else {
643             dirname = amname_to_dirname(argument->dle.device);
644         }
645         amgtar_build_exinclude(&argument->dle, 1,
646                                &nb_exclude, &file_exclude,
647                                &nb_include, &file_include);
648
649         run_calcsize(argument->config, "GNUTAR", argument->dle.disk, dirname,
650                      argument->level, file_exclude, file_include);
651         return;
652     }
653
654     if (!gnutar_path) {
655         errmsg = vstrallocf(_("GNUTAR-PATH not defined"));
656         goto common_error;
657     }
658
659     if (!gnutar_listdir) {
660         errmsg = vstrallocf(_("GNUTAR-LISTDIR not defined"));
661         goto common_error;
662     }
663
664     for (levels = argument->level; levels != NULL; levels = levels->next) {
665         level = GPOINTER_TO_INT(levels->data);
666         incrname = amgtar_get_incrname(argument, level);
667         cmd = stralloc(gnutar_path);
668         my_argv = amgtar_build_argv(argument, incrname, CMD_ESTIMATE);
669
670         start_time = curclock();
671
672         if ((nullfd = open("/dev/null", O_RDWR)) == -1) {
673             errmsg = vstrallocf(_("Cannot access /dev/null : %s"),
674                                 strerror(errno));
675             goto common_exit;
676         }
677
678         tarpid = pipespawnv(cmd, STDERR_PIPE, 1,
679                             &nullfd, &nullfd, &pipefd, my_argv);
680
681         dumpout = fdopen(pipefd,"r");
682         if (!dumpout) {
683             error(_("Can't fdopen: %s"), strerror(errno));
684             /*NOTREACHED*/
685         }
686
687         size = (off_t)-1;
688         while (size < 0 && (fgets(line, sizeof(line), dumpout) != NULL)) {
689             if (line[strlen(line)-1] == '\n') /* remove trailling \n */
690                 line[strlen(line)-1] = '\0';
691             if (line[0] == '\0')
692                 continue;
693             dbprintf("%s\n", line);
694             /* check for size match */
695             /*@ignore@*/
696             for(rp = re_table; rp->regex != NULL; rp++) {
697                 if(match(rp->regex, line)) {
698                     if (rp->typ == DMP_SIZE) {
699                         size = ((the_num(line, rp->field)*rp->scale+1023.0)/1024.0);
700                         if(size < 0.0)
701                             size = 1.0;             /* found on NeXT -- sigh */
702                     }
703                     break;
704                 }
705             }
706             /*@end@*/
707         }
708
709         while (fgets(line, sizeof(line), dumpout) != NULL) {
710             dbprintf("%s", line);
711         }
712
713         dbprintf(".....\n");
714         dbprintf(_("estimate time for %s level %d: %s\n"),
715                  qdisk,
716                  level,
717                  walltime_str(timessub(curclock(), start_time)));
718         if(size == (off_t)-1) {
719             errmsg = vstrallocf(_("no size line match in %s output"),
720                                 my_argv[0]);
721             dbprintf(_("%s for %s\n"), errmsg, qdisk);
722             dbprintf(".....\n");
723         } else if(size == (off_t)0 && argument->level == 0) {
724             dbprintf(_("possible %s problem -- is \"%s\" really empty?\n"),
725                       my_argv[0], argument->dle.disk);
726             dbprintf(".....\n");
727         }
728         dbprintf(_("estimate size for %s level %d: %lld KB\n"),
729                  qdisk,
730                  level,
731                  (long long)size);
732
733         kill(-tarpid, SIGTERM);
734
735         dbprintf(_("waiting for %s \"%s\" child\n"), my_argv[0], qdisk);
736         waitpid(tarpid, &wait_status, 0);
737         if (WIFSIGNALED(wait_status)) {
738             errmsg = vstrallocf(_("%s terminated with signal %d: see %s"),
739                                 cmd, WTERMSIG(wait_status), dbfn());
740         } else if (WIFEXITED(wait_status)) {
741             if (exit_value[WEXITSTATUS(wait_status)] == 1) {
742                 errmsg = vstrallocf(_("%s exited with status %d: see %s"),
743                                     cmd, WEXITSTATUS(wait_status), dbfn());
744             } else {
745                 /* Normal exit */
746             }
747         } else {
748             errmsg = vstrallocf(_("%s got bad exit: see %s"),
749                                 cmd, dbfn());
750         }
751         dbprintf(_("after %s %s wait\n"), my_argv[0], qdisk);
752
753 common_exit:
754         if (errmsg) {
755             dbprintf("%s", errmsg);
756             fprintf(stdout, "ERROR %s\n", errmsg);
757         }
758
759         if (incrname) {
760             unlink(incrname);
761         }
762         amfree(my_argv);
763         amfree(cmd);
764
765         aclose(nullfd);
766         afclose(dumpout);
767
768         fprintf(stdout, "%d %lld 1\n", level, (long long)size);
769     }
770     amfree(qdisk);
771     return;
772
773 common_error:
774     qerrmsg = quote_string(errmsg);
775     amfree(qdisk);
776     dbprintf("%s", errmsg);
777     fprintf(stdout, "ERROR %s\n", qerrmsg);
778     amfree(errmsg);
779     amfree(qerrmsg);
780     return;
781 }
782
783 static void
784 amgtar_backup(
785     application_argument_t *argument)
786 {
787     int dumpin;
788     char *cmd = NULL;
789     char *qdisk;
790     char *incrname;
791     char line[32768];
792     amregex_t *rp;
793     off_t dump_size = -1;
794     char *type;
795     char startchr;
796
797     int dataf = 1;
798     int mesgf = 3;
799     int indexf = 4;
800     int outf;
801     FILE *mesgstream;
802     FILE *indexstream = NULL;
803     FILE *outstream;
804     char *errmsg = NULL;
805     amwait_t wait_status;
806
807     char **my_argv;
808     int tarpid;
809
810     if (!gnutar_path) {
811         error(_("GNUTAR-PATH not defined"));
812     }
813     if (!gnutar_listdir) {
814         error(_("GNUTAR-LISTDIR not defined"));
815     }
816
817     qdisk = quote_string(argument->dle.disk);
818
819     incrname = amgtar_get_incrname(argument,
820                                    GPOINTER_TO_INT(argument->level->data));
821     cmd = stralloc(gnutar_path);
822     my_argv = amgtar_build_argv(argument, incrname, CMD_BACKUP);
823
824     tarpid = pipespawnv(cmd, STDIN_PIPE|STDERR_PIPE, 1,
825                         &dumpin, &dataf, &outf, my_argv);
826     /* close the write ends of the pipes */
827
828     aclose(dumpin);
829     aclose(dataf);
830     if (argument->dle.create_index) {
831         indexstream = fdopen(indexf, "w");
832         if (!indexstream) {
833             error(_("error indexstream(%d): %s\n"), indexf, strerror(errno));
834         }
835     }
836     mesgstream = fdopen(mesgf, "w");
837     if (!mesgstream) {
838         error(_("error mesgstream(%d): %s\n"), mesgf, strerror(errno));
839     }
840     outstream = fdopen(outf, "r");
841     if (!outstream) {
842         error(_("error outstream(%d): %s\n"), outf, strerror(errno));
843     }
844
845     while (fgets(line, sizeof(line), outstream) != NULL) {
846         if (line[strlen(line)-1] == '\n') /* remove trailling \n */
847             line[strlen(line)-1] = '\0';
848         if (*line == '.' && *(line+1) == '/') { /* filename */
849             if (argument->dle.create_index) {
850                 fprintf(indexstream, "%s\n", &line[1]); /* remove . */
851             }
852         } else { /* message */
853             for(rp = re_table; rp->regex != NULL; rp++) {
854                 if(match(rp->regex, line)) {
855                     break;
856                 }
857             }
858             if(rp->typ == DMP_SIZE) {
859                 dump_size = (long)((the_num(line, rp->field)* rp->scale+1023.0)/1024.0);
860             }
861             switch(rp->typ) {
862             case DMP_NORMAL:
863                 type = "normal";
864                 startchr = '|';
865                 break;
866             case DMP_IGNORE:
867                 continue;
868             case DMP_STRANGE:
869                 type = "strange";
870                 startchr = '?';
871                 break;
872             case DMP_SIZE:
873                 type = "size";
874                 startchr = '|';
875                 break;
876             case DMP_ERROR:
877                 type = "error";
878                 startchr = '?';
879                 break;
880             default:
881                 type = "unknown";
882                 startchr = '!';
883                 break;
884             }
885             dbprintf("%3d: %7s(%c): %s\n", rp->srcline, type, startchr, line);
886             fprintf(mesgstream,"%c %s\n", startchr, line);
887         }
888     }
889
890     waitpid(tarpid, &wait_status, 0);
891     if (WIFSIGNALED(wait_status)) {
892         errmsg = vstrallocf(_("%s terminated with signal %d: see %s"),
893                             cmd, WTERMSIG(wait_status), dbfn());
894     } else if (WIFEXITED(wait_status)) {
895         if (exit_value[WEXITSTATUS(wait_status)] == 1) {
896             errmsg = vstrallocf(_("%s exited with status %d: see %s"),
897                                 cmd, WEXITSTATUS(wait_status), dbfn());
898         } else {
899             /* Normal exit */
900         }
901     } else {
902         errmsg = vstrallocf(_("%s got bad exit: see %s"),
903                             cmd, dbfn());
904     }
905     dbprintf(_("after %s %s wait\n"), my_argv[0], qdisk);
906     dbprintf(_("amgtar: %s: pid %ld\n"), cmd, (long)tarpid);
907     if (errmsg) {
908         dbprintf("%s", errmsg);
909         g_fprintf(mesgstream, "sendbackup: error [%s]\n", errmsg);
910     }
911
912     if (!errmsg && incrname && strlen(incrname) > 4) {
913         char *nodotnew;
914         nodotnew = stralloc(incrname);
915         nodotnew[strlen(nodotnew)-4] = '\0';
916         if (rename(incrname, nodotnew)) {
917             dbprintf(_("%s: warning [renaming %s to %s: %s]\n"),
918                      get_pname(), incrname, nodotnew, strerror(errno));
919             g_fprintf(mesgstream, _("? warning [renaming %s to %s: %s]\n"),
920                       incrname, nodotnew, strerror(errno));
921         }
922         amfree(nodotnew);
923     }
924
925     dbprintf("sendbackup: size %lld\n", (long long)dump_size);
926     fprintf(mesgstream, "sendbackup: size %lld\n", (long long)dump_size);
927     dbprintf("sendbackup: end\n");
928     fprintf(mesgstream, "sendbackup: end\n");
929
930     if (argument->dle.create_index)
931         fclose(indexstream);
932
933     fclose(mesgstream);
934
935     amfree(incrname);
936     amfree(qdisk);
937     amfree(cmd);
938 }
939
940 static void
941 amgtar_restore(
942     application_argument_t *argument)
943 {
944     char  *cmd;
945     char **my_argv;
946     char **env;
947     int    i, j;
948     char  *e;
949
950     if (!gnutar_path) {
951         error(_("GNUTAR-PATH not defined"));
952     }
953
954     cmd = stralloc(gnutar_path);
955     my_argv = alloc(SIZEOF(char *) * (6 + argument->argc));
956     i = 0;
957     my_argv[i++] = stralloc(gnutar_path);
958     my_argv[i++] = stralloc("--numeric-owner");
959     my_argv[i++] = stralloc("-xpGvf");
960     my_argv[i++] = stralloc("-");
961
962     for (j=1; j< argument->argc; j++) {
963         my_argv[i++] = stralloc(argument->argv[j]);
964     }
965     my_argv[i++] = NULL;
966
967     env = safe_env();
968     become_root();
969     execve(cmd, my_argv, env);
970     e = strerror(errno);
971     error(_("error [exec %s: %s]"), cmd, e);
972 }
973
974 static void
975 amgtar_validate(
976     application_argument_t *argument G_GNUC_UNUSED)
977 {
978     char  *cmd;
979     char **my_argv;
980     char **env;
981     int    i;
982     char  *e;
983
984     if (!gnutar_path) {
985         error(_("GNUTAR-PATH not defined"));
986     }
987
988     cmd = stralloc(gnutar_path);
989     my_argv = alloc(SIZEOF(char *) * 4);
990     i = 0;
991     my_argv[i++] = stralloc(gnutar_path);
992     my_argv[i++] = stralloc("-tf");
993     my_argv[i++] = stralloc("-");
994     my_argv[i++] = NULL;
995
996     env = safe_env();
997     execve(cmd, my_argv, env);
998     e = strerror(errno);
999     error(_("error [exec %s: %s]"), cmd, e);
1000 }
1001
1002 static void
1003 amgtar_build_exinclude(
1004     dle_t  *dle,
1005     int     verbose,
1006     int    *nb_exclude,
1007     char  **file_exclude,
1008     int    *nb_include,
1009     char  **file_include)
1010 {
1011     int n_exclude = 0;
1012     int n_include = 0;
1013     char *exclude = NULL;
1014     char *include = NULL;
1015
1016     if (dle->exclude_file) n_exclude += dle->exclude_file->nb_element;
1017     if (dle->exclude_list) n_exclude += dle->exclude_list->nb_element;
1018     if (dle->include_file) n_include += dle->include_file->nb_element;
1019     if (dle->include_list) n_include += dle->include_list->nb_element;
1020
1021     if (n_exclude > 0) exclude = build_exclude(dle, verbose);
1022     if (n_include > 0) include = build_include(dle, verbose);
1023
1024     if (nb_exclude)
1025         *nb_exclude = n_exclude;
1026     if (file_exclude)
1027         *file_exclude = exclude;
1028     else
1029         amfree(exclude);
1030
1031     if (nb_include)
1032         *nb_include = n_include;
1033     if (file_include)
1034         *file_include = include;
1035     else
1036         amfree(include);
1037 }
1038
1039 static char *
1040 amgtar_get_incrname(
1041     application_argument_t *argument,
1042     int                     level)
1043 {
1044     char *basename = NULL;
1045     char *incrname = NULL;
1046     int   infd, outfd;
1047     ssize_t   nb;
1048     char *inputname = NULL;
1049     char *errmsg = NULL;
1050     char *buf;
1051
1052     if (gnutar_listdir) {
1053         char number[NUM_STR_SIZE];
1054         int baselevel;
1055         char *sdisk = sanitise_filename(argument->dle.disk);
1056
1057         basename = vstralloc(gnutar_listdir,
1058                              "/",
1059                              argument->host,
1060                              sdisk,
1061                              NULL);
1062         amfree(sdisk);
1063
1064         snprintf(number, SIZEOF(number), "%d", level);
1065         incrname = vstralloc(basename, "_", number, ".new", NULL);
1066         unlink(incrname);
1067
1068         /*
1069          * Open the listed incremental file from the previous level.  Search
1070          * backward until one is found.  If none are found (which will also
1071          * be true for a level 0), arrange to read from /dev/null.
1072          */
1073         baselevel = level;
1074         infd = -1;
1075         while (infd == -1) {
1076             if (--baselevel >= 0) {
1077                 snprintf(number, SIZEOF(number), "%d", baselevel);
1078                 inputname = newvstralloc(inputname,
1079                                          basename, "_", number, NULL);
1080             } else {
1081                 inputname = newstralloc(inputname, "/dev/null");
1082             }
1083             if ((infd = open(inputname, O_RDONLY)) == -1) {
1084
1085                 errmsg = vstrallocf(_("amgtar: error opening %s: %s"),
1086                                      inputname, strerror(errno));
1087                 dbprintf("%s\n", errmsg);
1088                 if (baselevel < 0) {
1089                     return NULL;
1090                 }
1091                 amfree(errmsg);
1092             }
1093         }
1094
1095         /*
1096          * Copy the previous listed incremental file to the new one.
1097          */
1098         if ((outfd = open(incrname, O_WRONLY|O_CREAT, 0600)) == -1) {
1099             errmsg = vstrallocf(_("opening %s: %s"),
1100                                  incrname, strerror(errno));
1101             dbprintf("%s\n", errmsg);
1102             return NULL;
1103         }
1104
1105         while ((nb = read(infd, &buf, SIZEOF(buf))) > 0) {
1106             if (full_write(outfd, &buf, (size_t)nb) < (size_t)nb) {
1107                 errmsg = vstrallocf(_("writing to %s: %s"),
1108                                      incrname, strerror(errno));
1109                 dbprintf("%s\n", errmsg);
1110                 return NULL;
1111             }
1112         }
1113
1114         if (nb < 0) {
1115             errmsg = vstrallocf(_("reading from %s: %s"),
1116                                  inputname, strerror(errno));
1117             dbprintf("%s\n", errmsg);
1118             return NULL;
1119         }
1120
1121         if (close(infd) != 0) {
1122             errmsg = vstrallocf(_("closing %s: %s"),
1123                                  inputname, strerror(errno));
1124             dbprintf("%s\n", errmsg);
1125             return NULL;
1126         }
1127         if (close(outfd) != 0) {
1128             errmsg = vstrallocf(_("closing %s: %s"),
1129                                  incrname, strerror(errno));
1130             dbprintf("%s\n", errmsg);
1131             return NULL;
1132         }
1133
1134         amfree(inputname);
1135         amfree(basename);
1136     }
1137     return incrname;
1138 }
1139
1140 char **amgtar_build_argv(
1141     application_argument_t *argument,
1142     char *incrname,
1143     int   command)
1144 {
1145     int    i;
1146     int    nb_exclude;
1147     int    nb_include;
1148     char  *file_exclude;
1149     char  *file_include;
1150     char  *dirname;
1151     char   tmppath[PATH_MAX];
1152     char **my_argv;
1153
1154     amgtar_build_exinclude(&argument->dle, 1,
1155                            &nb_exclude, &file_exclude,
1156                            &nb_include, &file_include);
1157
1158     if (gnutar_directory) {
1159         dirname = gnutar_directory;
1160     } else {
1161         dirname = amname_to_dirname(argument->dle.device);
1162     }
1163
1164     my_argv = alloc(SIZEOF(char *) * 23);
1165     i = 0;
1166
1167     my_argv[i++] = gnutar_path;
1168
1169     my_argv[i++] = "--create";
1170     if (command == CMD_BACKUP && argument->dle.create_index)
1171         my_argv[i++] = "--verbose";
1172     my_argv[i++] = "--file";
1173     if (command == CMD_ESTIMATE) {
1174         my_argv[i++] = "/dev/null";
1175     } else {
1176         my_argv[i++] = "-";
1177     }
1178     my_argv[i++] = "--directory";
1179     canonicalize_pathname(dirname, tmppath);
1180     my_argv[i++] = stralloc(tmppath);
1181     if (gnutar_onefilesystem)
1182         my_argv[i++] = "--one-file-system";
1183     if (gnutar_atimepreserve)
1184         my_argv[i++] = "--atime-preserve=system";
1185     if (!gnutar_checkdevice)
1186         my_argv[i++] = "--no-check-device";
1187     my_argv[i++] = "--listed-incremental";
1188     my_argv[i++] = incrname;
1189     if (gnutar_sparse)
1190         my_argv[i++] = "--sparse";
1191     if (argument->tar_blocksize) {
1192         my_argv[i++] = "--blocking-factor";
1193         my_argv[i++] = argument->tar_blocksize;
1194     }
1195     my_argv[i++] = "--ignore-failed-read";
1196     my_argv[i++] = "--totals";
1197
1198     if(file_exclude) {
1199         my_argv[i++] = "--exclude-from";
1200         my_argv[i++] = file_exclude;
1201     }
1202
1203     if(file_include) {
1204         my_argv[i++] = "--files-from";
1205         my_argv[i++] = file_include;
1206     }
1207     else {
1208         my_argv[i++] = ".";
1209     }
1210     my_argv[i++] = NULL;
1211
1212     return(my_argv);
1213 }
1214