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