c11b55c5a65947ea1984fba7b4ab7274c3f7aa54
[debian/amanda] / client-src / sendbackup-gnutar.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1999 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: sendbackup-gnutar.c,v 1.98 2006/07/25 18:35:21 martinea Exp $
28  *
29  * send backup data using GNU tar
30  */
31
32 #include "amanda.h"
33 #include "sendbackup.h"
34 #include "amandates.h"
35 #include "clock.h"
36 #include "util.h"
37 #include "getfsent.h"                   /* for amname_to_dirname lookup */
38 #include "version.h"
39 #include "conffile.h"
40
41 #ifdef SAMBA_CLIENT
42 #include "findpass.h"
43 #endif
44
45 static amregex_t re_table[] = {
46   /* tar prints the size in bytes */
47   AM_SIZE_RE("^ *Total bytes written: [0-9][0-9]*", 1, 1),
48   AM_NORMAL_RE("^Elapsed time:"),
49   AM_NORMAL_RE("^Throughput"),
50
51   /* GNU tar 1.13.17 will print this warning when (not) backing up a
52      Unix named socket.  */
53   AM_NORMAL_RE(": socket ignored$"),
54
55   /* GNUTAR produces a few error messages when files are modified or
56      removed while it is running.  They may cause data to be lost, but
57      then they may not.  We shouldn't consider them NORMAL until
58      further investigation.  */
59 #ifdef IGNORE_TAR_ERRORS
60   AM_NORMAL_RE(": File .* shrunk by [0-9][0-9]* bytes, padding with zeros"),
61   AM_NORMAL_RE(": Cannot add file .*: No such file or directory$"),
62   AM_NORMAL_RE(": Error exit delayed from previous errors"),
63 #endif
64   
65   /* samba may produce these output messages */
66   AM_NORMAL_RE("^[Aa]dded interface"),
67   AM_NORMAL_RE("^session request to "),
68   AM_NORMAL_RE("^tar: dumped [0-9][0-9]* (tar )?files"),
69
70 #if SAMBA_VERSION < 2
71   AM_NORMAL_RE("^doing parameter"),
72   AM_NORMAL_RE("^pm_process\\(\\)"),
73   AM_NORMAL_RE("^adding IPC"),
74   AM_NORMAL_RE("^Opening"),
75   AM_NORMAL_RE("^Connect"),
76   AM_NORMAL_RE("^Domain="),
77   AM_NORMAL_RE("^max"),
78   AM_NORMAL_RE("^security="),
79   AM_NORMAL_RE("^capabilities"),
80   AM_NORMAL_RE("^Sec mode "),
81   AM_NORMAL_RE("^Got "),
82   AM_NORMAL_RE("^Chose protocol "),
83   AM_NORMAL_RE("^Server "),
84   AM_NORMAL_RE("^Timezone "),
85   AM_NORMAL_RE("^received"),
86   AM_NORMAL_RE("^FINDFIRST"),
87   AM_NORMAL_RE("^FINDNEXT"),
88   AM_NORMAL_RE("^dos_clean_name"),
89   AM_NORMAL_RE("^file"),
90   AM_NORMAL_RE("^getting file"),
91   AM_NORMAL_RE("^Rejected chained"),
92   AM_NORMAL_RE("^nread="),
93   AM_NORMAL_RE("^\\([0-9][0-9]* kb/s\\)"),
94   AM_NORMAL_RE("^\\([0-9][0-9]*\\.[0-9][0-9]* kb/s\\)"),
95   AM_NORMAL_RE("^[ \t]*[0-9][0-9]* \\([ \t]*[0-9][0-9]*\\.[0-9][0-9]* kb/s\\)"),
96   AM_NORMAL_RE("^[ \t]*directory "),
97   AM_NORMAL_RE("^load_client_codepage"),
98 #endif
99
100 #ifdef IGNORE_SMBCLIENT_ERRORS
101   /* This will cause amanda to ignore real errors, but that may be
102    * unavoidable when you're backing up system disks.  It seems to be
103    * a safe thing to do if you know what you're doing.  */
104   AM_NORMAL_RE("^ERRDOS - ERRbadshare opening remote file"),
105   AM_NORMAL_RE("^ERRDOS - ERRbadfile opening remote file"),
106   AM_NORMAL_RE("^ERRDOS - ERRnoaccess opening remote file"),
107   AM_NORMAL_RE("^ERRSRV - ERRaccess setting attributes on file"),
108   AM_NORMAL_RE("^ERRDOS - ERRnoaccess setting attributes on file"),
109 #endif
110
111 #if SAMBA_VERSION >= 2
112   /* Backup attempt of nonexisting directory */
113   AM_ERROR_RE("ERRDOS - ERRbadpath (Directory invalid.)"),
114   AM_NORMAL_RE("^Domain="),
115 #endif
116
117   /* catch-all: DMP_STRANGE is returned for all other lines */
118   AM_STRANGE_RE(NULL)
119 };
120
121 extern char *efile;
122
123 int cur_level;
124 char *cur_disk;
125 time_t cur_dumptime;
126
127 static char *gnutar_list_dir = NULL;
128 static char *incrname = NULL;
129 static char *amandates_file;
130 /*
131  *  doing similar to $ gtar | compression | encryption 
132  */
133 static void
134 start_backup(
135     char *      host,
136     char *      disk,
137     char *      amdevice,
138     int         level,
139     char *      dumpdate,
140     int         dataf,
141     int         mesgf,
142     int         indexf)
143 {
144     char tmppath[PATH_MAX];
145     int dumpin, dumpout, compout;
146     char *cmd = NULL;
147     char *indexcmd = NULL;
148     char *dirname = NULL;
149     int l;
150     char dumptimestr[80];
151     struct tm *gmtm;
152     amandates_t *amdates;
153     time_t prev_dumptime;
154     char *error_pn = NULL;
155     char *compopt  = NULL;
156     char *encryptopt = skip_argument;
157     char *tquoted;
158     char *fquoted;
159     char *qdisk;
160     int infd, outfd;
161     ssize_t nb;
162     char buf[32768];
163
164     (void)dumpdate;     /* Quiet unused parameter warning */
165
166     error_pn = stralloc2(get_pname(), "-smbclient");
167
168     qdisk = quote_string(disk);
169     dbprintf(_("start: %s:%s lev %d\n"), host, qdisk, level);
170
171     g_fprintf(stderr, _("%s: start [%s:%s level %d]\n"),
172             get_pname(), host, qdisk, level);
173
174      /*  apply client-side encryption here */
175      if ( options->encrypt == ENCRYPT_CUST ) {
176          encpid = pipespawn(options->clnt_encrypt, STDIN_PIPE,
177                         &compout, &dataf, &mesgf, 
178                         options->clnt_encrypt, encryptopt, NULL);
179          dbprintf(_("gnutar: pid %ld: %s\n"), (long)encpid, options->clnt_encrypt);
180     } else {
181        compout = dataf;
182        encpid = -1;
183     } 
184      /*  now do the client-side compression */
185     if(options->compress == COMP_FAST || options->compress == COMP_BEST) {
186           compopt = skip_argument;
187 #if defined(COMPRESS_BEST_OPT) && defined(COMPRESS_FAST_OPT)
188         if(options->compress == COMP_BEST) {
189             compopt = COMPRESS_BEST_OPT;
190         } else {
191             compopt = COMPRESS_FAST_OPT;
192         }
193 #endif
194         comppid = pipespawn(COMPRESS_PATH, STDIN_PIPE,
195                             &dumpout, &compout, &mesgf,
196                             COMPRESS_PATH, compopt, NULL);
197         dbprintf(_("gnutar: pid %ld: %s"), (long)comppid, COMPRESS_PATH);
198         if(compopt != skip_argument) {
199             dbprintf(_("pid %ld: %s %s\n"),
200                         (long)comppid, COMPRESS_PATH, compopt);
201         } else {
202             dbprintf(_("pid %ld: %s\n"), (long)comppid, COMPRESS_PATH);
203         }
204      } else if (options->compress == COMP_CUST) {
205         compopt = skip_argument;
206         comppid = pipespawn(options->clntcompprog, STDIN_PIPE,
207                             &dumpout, &compout, &mesgf,
208                             options->clntcompprog, compopt, NULL);
209         if(compopt != skip_argument) {
210             dbprintf(_("pid %ld: %s %s\n"),
211                      (long)comppid, options->clntcompprog, compopt);
212         } else {
213             dbprintf(_("pid %ld: %s\n"), (long)comppid, options->clntcompprog);
214         }
215     } else {
216         dumpout = compout;
217         comppid = -1;
218     }
219
220     gnutar_list_dir = getconf_str(CNF_GNUTAR_LIST_DIR);
221     if (strlen(gnutar_list_dir) == 0)
222         gnutar_list_dir = NULL;
223
224 #ifdef SAMBA_CLIENT                                                     /* { */
225     if (amdevice[0] == '/' && amdevice[1]=='/')
226         amfree(incrname);
227     else
228 #endif                                                                  /* } */
229     if (gnutar_list_dir) {
230         char *basename = NULL;
231         char number[NUM_STR_SIZE];
232         char *inputname = NULL;
233         int baselevel;
234         char *sdisk = sanitise_filename(disk);
235
236         basename = vstralloc(gnutar_list_dir,
237                              "/",
238                              host,
239                              sdisk,
240                              NULL);
241         amfree(sdisk);
242
243         g_snprintf(number, SIZEOF(number), "%d", level);
244         incrname = vstralloc(basename, "_", number, ".new", NULL);
245         unlink(incrname);
246
247         /*
248          * Open the listed incremental file from the previous level.  Search
249          * backward until one is found.  If none are found (which will also
250          * be true for a level 0), arrange to read from /dev/null.
251          */
252         baselevel = level;
253         infd = -1;
254         while (infd == -1) {
255             if (--baselevel >= 0) {
256                 g_snprintf(number, SIZEOF(number), "%d", baselevel);
257                 inputname = newvstralloc(inputname,
258                                          basename, "_", number, NULL);
259             } else {
260                 inputname = newstralloc(inputname, "/dev/null");
261             }
262             if ((infd = open(inputname, O_RDONLY)) == -1) {
263                 int save_errno = errno;
264                 char *qname = quote_string(inputname);
265
266                 dbprintf(_("gnutar: error opening '%s': %s\n"),
267                           qname,
268                           strerror(save_errno));
269                 if (baselevel < 0) {
270                     error(_("error [opening '%s': %s]"), qname, strerror(save_errno));
271                     /*NOTREACHED*/
272                 }
273                 amfree(qname);
274             }
275         }
276
277         /*
278          * Copy the previous listed incremental file to the new one.
279          */
280         if ((outfd = open(incrname, O_WRONLY|O_CREAT, 0600)) == -1) {
281             error(_("error [opening '%s': %s]"), incrname, strerror(errno));
282             /*NOTREACHED*/
283         }
284
285         while ((nb = read(infd, &buf, SIZEOF(buf))) > 0) {
286             if (fullwrite(outfd, &buf, (size_t)nb) < nb) {
287                 error(_("error [writing to '%s': %s]"), incrname,
288                        strerror(errno));
289                 /*NOTREACHED*/
290             }
291         }
292
293         if (nb < 0) {
294             error(_("error [reading from '%s': %s]"), inputname, strerror(errno));
295             /*NOTREACHED*/
296         }
297
298         if (close(infd) != 0) {
299             error(_("error [closing '%s': %s]"), inputname, strerror(errno));
300             /*NOTREACHED*/
301         }
302         if (close(outfd) != 0) {
303             error(_("error [closing '%s': %s]"), incrname, strerror(errno));
304             /*NOTREACHED*/
305         }
306
307         tquoted = quote_string(incrname);
308         if(baselevel >= 0) {
309             fquoted = quote_string(inputname);
310             dbprintf(_("doing level %d dump as listed-incremental from '%s' to '%s'\n"),
311                      level, fquoted, tquoted);
312             amfree(fquoted);
313         } else {
314             dbprintf(_("doing level %d dump as listed-incremental to '%s'\n"),
315                      level, tquoted);
316         }
317         amfree(tquoted);
318         amfree(inputname);
319         amfree(basename);
320     }
321
322     /* find previous dump time */
323
324     amandates_file = getconf_str(CNF_AMANDATES);
325     if(!start_amandates(amandates_file, 0)) {
326         error(_("error [opening %s: %s]"), amandates_file, strerror(errno));
327         /*NOTREACHED*/
328     }
329
330     amdates = amandates_lookup(disk);
331
332     prev_dumptime = EPOCH;
333     for(l = 0; l < level; l++) {
334         if(amdates->dates[l] > prev_dumptime)
335             prev_dumptime = amdates->dates[l];
336     }
337
338     finish_amandates();
339     free_amandates();
340
341     gmtm = gmtime(&prev_dumptime);
342     g_snprintf(dumptimestr, SIZEOF(dumptimestr),
343                 "%04d-%02d-%02d %2d:%02d:%02d GMT",
344                 gmtm->tm_year + 1900, gmtm->tm_mon+1, gmtm->tm_mday,
345                 gmtm->tm_hour, gmtm->tm_min, gmtm->tm_sec);
346
347     dbprintf(_("gnutar: doing level %d dump from date: %s\n"),
348               level, dumptimestr);
349
350     dirname = amname_to_dirname(amdevice);
351
352     cur_dumptime = time(0);
353     cur_level = level;
354     cur_disk = stralloc(disk);
355 #ifdef GNUTAR
356 #  define PROGRAM_GNUTAR GNUTAR
357 #else
358 #  define PROGRAM_GNUTAR "tar"
359 #endif
360     indexcmd = vstralloc(
361                          PROGRAM_GNUTAR,
362                          " -tf", " -",
363                          " 2>/dev/null",
364                          " | sed", " -e",
365                          " \'s/^\\.//\'",
366                          NULL);
367
368 #ifdef SAMBA_CLIENT                                                     /* { */
369     /* Use sambatar if the disk to back up is a PC disk */
370     if (amdevice[0] == '/' && amdevice[1]=='/') {
371         char *sharename = NULL, *user_and_password = NULL, *domain = NULL;
372         char *share = NULL, *subdir = NULL;
373         char *pwtext = NULL;
374         char *taropt;
375         int passwdf = -1;
376         size_t lpass;
377         size_t pwtext_len;
378         char *pw_fd_env;
379
380         parsesharename(amdevice, &share, &subdir);
381         if (!share) {
382             amfree(share);
383             amfree(subdir);
384             set_pname(error_pn);
385             amfree(error_pn);
386             error(_("cannot parse disk entry %s for share/subdir"), qdisk);
387             /*NOTREACHED*/
388         }
389         if ((subdir) && (SAMBA_VERSION < 2)) {
390             amfree(share);
391             amfree(subdir);
392             set_pname(error_pn);
393             amfree(error_pn);
394             error(_("subdirectory specified for share %s but samba not v2 or better"), qdisk);
395             /*NOTREACHED*/
396         }
397         if ((user_and_password = findpass(share, &domain)) == NULL) {
398             if(domain) {
399                 memset(domain, '\0', strlen(domain));
400                 amfree(domain);
401             }
402             set_pname(error_pn);
403             amfree(error_pn);
404             error(_("error [invalid samba host or password not found?]"));
405             /*NOTREACHED*/
406         }
407         lpass = strlen(user_and_password);
408         if ((pwtext = strchr(user_and_password, '%')) == NULL) {
409             memset(user_and_password, '\0', lpass);
410             amfree(user_and_password);
411             if(domain) {
412                 memset(domain, '\0', strlen(domain));
413                 amfree(domain);
414             }
415             set_pname(error_pn);
416             amfree(error_pn);
417             error(_("password field not \'user%%pass\' for %s"), qdisk);
418             /*NOTREACHED*/
419         }
420         *pwtext++ = '\0';
421         pwtext_len = strlen(pwtext);
422         if ((sharename = makesharename(share, 0)) == 0) {
423             memset(user_and_password, '\0', lpass);
424             amfree(user_and_password);
425             if(domain) {
426                 memset(domain, '\0', strlen(domain));
427                 amfree(domain);
428             }
429             set_pname(error_pn);
430             amfree(error_pn);
431             error(_("error [can't make share name of %s]"), share);
432             /*NOTREACHED*/
433         }
434
435         taropt = stralloc("-T");
436         if(options->exclude_file && options->exclude_file->nb_element == 1) {
437             strappend(taropt, "X");
438         }
439 #if SAMBA_VERSION >= 2
440         strappend(taropt, "q");
441 #endif
442         strappend(taropt, "c");
443         if (level != 0) {
444             strappend(taropt, "g");
445         } else if (!options->no_record) {
446             strappend(taropt, "a");
447         }
448
449         if (subdir) {
450             dbprintf(_("gnutar: backup of %s/%s\n"), sharename, subdir);
451         } else {
452             dbprintf(_("gnutar: backup of %s\n"), sharename);
453         }
454
455         program->backup_name = program->restore_name = SAMBA_CLIENT;
456         cmd = stralloc(program->backup_name);
457         info_tapeheader();
458
459         start_index(options->createindex, dumpout, mesgf, indexf, indexcmd);
460
461         if (pwtext_len > 0) {
462             pw_fd_env = "PASSWD_FD";
463         } else {
464             pw_fd_env = "dummy_PASSWD_FD";
465         }
466         dumppid = pipespawn(cmd, STDIN_PIPE|PASSWD_PIPE,
467                             &dumpin, &dumpout, &mesgf,
468                             pw_fd_env, &passwdf,
469                             "smbclient",
470                             sharename,
471                             *user_and_password ? "-U" : skip_argument,
472                             *user_and_password ? user_and_password : skip_argument,
473                             "-E",
474                             domain ? "-W" : skip_argument,
475                             domain ? domain : skip_argument,
476 #if SAMBA_VERSION >= 2
477                             subdir ? "-D" : skip_argument,
478                             subdir ? subdir : skip_argument,
479 #endif
480                             "-d0",
481                             taropt,
482                             "-",
483                             options->exclude_file && options->exclude_file->nb_element == 1 ? options->exclude_file->first->name : skip_argument,
484                             NULL);
485         if(domain) {
486             memset(domain, '\0', strlen(domain));
487             amfree(domain);
488         }
489         if(pwtext_len > 0 && fullwrite(passwdf, pwtext, pwtext_len) < 0) {
490             int save_errno = errno;
491
492             aclose(passwdf);
493             memset(user_and_password, '\0', lpass);
494             amfree(user_and_password);
495             set_pname(error_pn);
496             amfree(error_pn);
497             error(_("error [password write failed: %s]"), strerror(save_errno));
498             /*NOTREACHED*/
499         }
500         memset(user_and_password, '\0', lpass);
501         amfree(user_and_password);
502         aclose(passwdf);
503         amfree(sharename);
504         amfree(share);
505         amfree(subdir);
506         amfree(taropt);
507         tarpid = dumppid;
508     } else
509 #endif                  /*end of samba */
510     {
511
512         int nb_exclude = 0;
513         int nb_include = 0;
514         char **my_argv;
515         int i = 0;
516         char *file_exclude = NULL;
517         char *file_include = NULL;
518
519         if(options->exclude_file) nb_exclude+=options->exclude_file->nb_element;
520         if(options->exclude_list) nb_exclude+=options->exclude_list->nb_element;
521         if(options->include_file) nb_include+=options->include_file->nb_element;
522         if(options->include_list) nb_include+=options->include_list->nb_element;
523
524         if(nb_exclude > 0) file_exclude = build_exclude(disk, amdevice, options, 0);
525         if(nb_include > 0) file_include = build_include(disk, amdevice, options, 0);
526
527         my_argv = alloc(SIZEOF(char *) * (22 + (nb_exclude*2)+(nb_include*2)));
528
529         cmd = vstralloc(amlibexecdir, "/", "runtar", versionsuffix(), NULL);
530         info_tapeheader();
531
532         start_index(options->createindex, dumpout, mesgf, indexf, indexcmd);
533
534         my_argv[i++] = "runtar";
535         if (g_options->config)
536             my_argv[i++] = g_options->config;
537         else
538             my_argv[i++] = "NOCONFIG";
539 #ifdef GNUTAR
540         my_argv[i++] = GNUTAR;
541 #else
542         my_argv[i++] = "tar";
543 #endif
544         my_argv[i++] = "--create";
545         my_argv[i++] = "--file";
546         my_argv[i++] = "-";
547         my_argv[i++] = "--directory";
548         canonicalize_pathname(dirname, tmppath);
549         my_argv[i++] = tmppath;
550         my_argv[i++] = "--one-file-system";
551         if (gnutar_list_dir && incrname) {
552             my_argv[i++] = "--listed-incremental";
553             my_argv[i++] = incrname;
554         } else {
555             my_argv[i++] = "--incremental";
556             my_argv[i++] = "--newer";
557             my_argv[i++] = dumptimestr;
558         }
559 #ifdef ENABLE_GNUTAR_ATIME_PRESERVE
560         /* --atime-preserve causes gnutar to call
561          * utime() after reading files in order to
562          * adjust their atime.  However, utime()
563          * updates the file's ctime, so incremental
564          * dumps will think the file has changed. */
565         my_argv[i++] = "--atime-preserve";
566 #endif
567         my_argv[i++] = "--sparse";
568         my_argv[i++] = "--ignore-failed-read";
569         my_argv[i++] = "--totals";
570
571         if(file_exclude) {
572             my_argv[i++] = "--exclude-from";
573             my_argv[i++] = file_exclude;
574         }
575
576         if(file_include) {
577             my_argv[i++] = "--files-from";
578             my_argv[i++] = file_include;
579         }
580         else {
581             my_argv[i++] = ".";
582         }
583         my_argv[i++] = NULL;
584         dumppid = pipespawnv(cmd, STDIN_PIPE,
585                              &dumpin, &dumpout, &mesgf, my_argv);
586         tarpid = dumppid;
587         amfree(file_exclude);
588         amfree(file_include);
589         amfree(my_argv);
590     }
591     dbprintf(_("gnutar: %s: pid %ld\n"), cmd, (long)dumppid);
592
593     amfree(qdisk);
594     amfree(dirname);
595     amfree(cmd);
596     amfree(indexcmd);
597     amfree(error_pn);
598
599     /* close the write ends of the pipes */
600
601     aclose(dumpin);
602     aclose(dumpout);
603     aclose(compout);
604     aclose(dataf);
605     aclose(mesgf);
606     if (options->createindex)
607         aclose(indexf);
608 }
609
610 static void
611 end_backup(
612     int         goterror)
613 {
614     if(!options->no_record && !goterror) {
615         if (incrname != NULL && strlen(incrname) > 4) {
616             char *nodotnew;
617         
618             nodotnew = stralloc(incrname);
619             nodotnew[strlen(nodotnew)-4] = '\0';
620             if (rename(incrname, nodotnew)) {
621                 g_fprintf(stderr, _("%s: warning [renaming %s to %s: %s]\n"), 
622                         get_pname(), incrname, nodotnew, strerror(errno));
623             }
624             amfree(nodotnew);
625             amfree(incrname);
626         }
627
628         if(!start_amandates(amandates_file, 1)) {
629             g_fprintf(stderr, _("%s: warning [opening %s: %s]"), get_pname(),
630                     amandates_file, strerror(errno));
631         }
632         else {
633             amandates_updateone(cur_disk, cur_level, cur_dumptime);
634             finish_amandates();
635             free_amandates();
636         }
637     }
638 }
639
640 backup_program_t gnutar_program = {
641   "GNUTAR",
642 #ifdef GNUTAR
643   GNUTAR, GNUTAR,
644 #else
645   "gtar", "gtar",
646 #endif
647   re_table, start_backup, end_backup
648 };