666796e2bcc4eff097783acc607fba8579128ffb
[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.2 2005/06/23 08:46:07 weichinger 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   AM_NORMAL_RE("^Domain="),
122 #endif
123
124   /* catch-all: DMP_STRANGE is returned for all other lines */
125   AM_STRANGE_RE(NULL)
126 };
127
128 int cur_level;
129 char *cur_disk;
130 time_t cur_dumptime;
131
132 #ifdef GNUTAR_LISTED_INCREMENTAL_DIR
133 static char *incrname = NULL;
134 #endif
135
136 static void start_backup(host, disk, amdevice, level, dumpdate, dataf, mesgf, indexf)
137     char *host;
138     char *disk, *amdevice;
139     int level, dataf, mesgf, indexf;
140     char *dumpdate;
141 {
142     int dumpin, dumpout;
143     char *cmd = NULL;
144     char *indexcmd = NULL;
145     char *dirname = NULL;
146     int l;
147     char dumptimestr[80];
148     struct tm *gmtm;
149     amandates_t *amdates;
150     time_t prev_dumptime;
151     char *error_pn = NULL;
152
153     error_pn = stralloc2(get_pname(), "-smbclient");
154
155     fprintf(stderr, "%s: start [%s:%s level %d]\n",
156             get_pname(), host, disk, level);
157
158     NAUGHTY_BITS;
159
160     if(options->compress == COMPR_FAST || options->compress == COMPR_BEST) {
161         char *compopt = skip_argument;
162
163 #if defined(COMPRESS_BEST_OPT) && defined(COMPRESS_FAST_OPT)
164         if(options->compress == COMPR_BEST) {
165             compopt = COMPRESS_BEST_OPT;
166         } else {
167             compopt = COMPRESS_FAST_OPT;
168         }
169 #endif
170         comppid = pipespawn(COMPRESS_PATH, STDIN_PIPE,
171                             &dumpout, &dataf, &mesgf,
172                             COMPRESS_PATH, compopt, NULL);
173         dbprintf(("%s: pid %ld: %s",
174                   debug_prefix_time("-gnutar"), (long)comppid, COMPRESS_PATH));
175         if(compopt != skip_argument) {
176             dbprintf((" %s", compopt));
177         }
178         dbprintf(("\n"));
179     } else {
180         dumpout = dataf;
181         comppid = -1;
182     }
183
184 #ifdef GNUTAR_LISTED_INCREMENTAL_DIR                                    /* { */
185 #ifdef SAMBA_CLIENT                                                     /* { */
186     if (amdevice[0] == '/' && amdevice[1]=='/')
187         amfree(incrname);
188     else
189 #endif                                                                  /* } */
190     {
191         char *basename = NULL;
192         char number[NUM_STR_SIZE];
193         char *s;
194         int ch;
195         char *inputname = NULL;
196         FILE *in = NULL;
197         FILE *out;
198         int baselevel;
199         char *line = NULL;
200
201         basename = vstralloc(GNUTAR_LISTED_INCREMENTAL_DIR,
202                              "/",
203                              host,
204                              disk,
205                              NULL);
206         /*
207          * The loop starts at the first character of the host name,
208          * not the '/'.
209          */
210         s = basename + sizeof(GNUTAR_LISTED_INCREMENTAL_DIR);
211         while((ch = *s++) != '\0') {
212             if(ch == '/' || isspace(ch)) s[-1] = '_';
213         }
214
215         ap_snprintf(number, sizeof(number), "%d", level);
216         incrname = vstralloc(basename, "_", number, ".new", NULL);
217         unlink(incrname);
218
219         /*
220          * Open the listed incremental file for the previous level.  Search
221          * backward until one is found.  If none are found (which will also
222          * be true for a level 0), arrange to read from /dev/null.
223          */
224         baselevel = level;
225         while (in == NULL) {
226             if (--baselevel >= 0) {
227                 ap_snprintf(number, sizeof(number), "%d", baselevel);
228                 inputname = newvstralloc(inputname,
229                                          basename, "_", number, NULL);
230             } else {
231                 inputname = newstralloc(inputname, "/dev/null");
232             }
233             if ((in = fopen(inputname, "r")) == NULL) {
234                 int save_errno = errno;
235
236                 dbprintf(("%s: error opening %s: %s\n",
237                           debug_prefix_time("-gnutar"),
238                           inputname,
239                           strerror(save_errno)));
240                 if (baselevel < 0) {
241                     error("error [opening %s: %s]", inputname, strerror(save_errno));
242                 }
243             }
244         }
245
246         /*
247          * Copy the previous listed incremental file to the new one.
248          */
249         if ((out = fopen(incrname, "w")) == NULL) {
250             error("error [opening %s: %s]", incrname, strerror(errno));
251         }
252
253         for (; (line = agets(in)) != NULL; free(line)) {
254             if(fputs(line, out) == EOF || putc('\n', out) == EOF) {
255                 error("error [writing to %s: %s]", incrname, strerror(errno));
256             }
257         }
258         amfree(line);
259
260         if (ferror(in)) {
261             error("error [reading from %s: %s]", inputname, strerror(errno));
262         }
263         if (fclose(in) == EOF) {
264             error("error [closing %s: %s]", inputname, strerror(errno));
265         }
266         in = NULL;
267         if (fclose(out) == EOF) {
268             error("error [closing %s: %s]", incrname, strerror(errno));
269         }
270         out = NULL;
271
272         dbprintf(("%s: doing level %d dump as listed-incremental",
273                   debug_prefix_time("-gnutar"), level));
274         if(baselevel >= 0) {
275             dbprintf((" from %s", inputname));
276         }
277         dbprintf((" to %s\n", incrname));
278         amfree(inputname);
279         amfree(basename);
280     }
281 #endif                                                                  /* } */
282
283     /* find previous dump time */
284
285     if(!start_amandates(0))
286         error("error [opening %s: %s]", AMANDATES_FILE, strerror(errno));
287
288     amdates = amandates_lookup(disk);
289
290     prev_dumptime = EPOCH;
291     for(l = 0; l < level; l++) {
292         if(amdates->dates[l] > prev_dumptime)
293             prev_dumptime = amdates->dates[l];
294     }
295
296     finish_amandates();
297     free_amandates();
298
299     gmtm = gmtime(&prev_dumptime);
300     ap_snprintf(dumptimestr, sizeof(dumptimestr),
301                 "%04d-%02d-%02d %2d:%02d:%02d GMT",
302                 gmtm->tm_year + 1900, gmtm->tm_mon+1, gmtm->tm_mday,
303                 gmtm->tm_hour, gmtm->tm_min, gmtm->tm_sec);
304
305     dbprintf(("%s: doing level %d dump from date: %s\n",
306               debug_prefix_time("-gnutar"), level, dumptimestr));
307
308     dirname = amname_to_dirname(amdevice);
309
310     cur_dumptime = time(0);
311     cur_level = level;
312     cur_disk = stralloc(disk);
313     indexcmd = vstralloc(
314 #ifdef GNUTAR
315                          GNUTAR,
316 #else
317                          "tar",
318 #endif
319                          " -tf", " -",
320                          " 2>/dev/null",
321                          " | sed", " -e",
322                          " \'s/^\\.//\'",
323                          NULL);
324
325 #ifdef SAMBA_CLIENT                                                     /* { */
326     /* Use sambatar if the disk to back up is a PC disk */
327     if (amdevice[0] == '/' && amdevice[1]=='/') {
328         char *sharename = NULL, *user_and_password = NULL, *domain = NULL;
329         char *share = NULL, *subdir = NULL;
330         char *pwtext;
331         char *taropt;
332         int passwdf;
333         int lpass;
334         int pwtext_len;
335         char *pw_fd_env;
336
337         parsesharename(amdevice, &share, &subdir);
338         if (!share) {
339              amfree(share);
340              amfree(subdir);
341              set_pname(error_pn);
342              amfree(error_pn);
343              error("cannot parse disk entry '%s' for share/subdir", disk);
344         }
345         if ((subdir) && (SAMBA_VERSION < 2)) {
346              amfree(share);
347              amfree(subdir);
348              set_pname(error_pn);
349              amfree(error_pn);
350              error("subdirectory specified for share '%s' but samba not v2 or better", disk);
351         }
352         if ((user_and_password = findpass(share, &domain)) == NULL) {
353             if(domain) {
354                 memset(domain, '\0', strlen(domain));
355                 amfree(domain);
356             }
357             set_pname(error_pn);
358             amfree(error_pn);
359             error("error [invalid samba host or password not found?]");
360         }
361         lpass = strlen(user_and_password);
362         if ((pwtext = strchr(user_and_password, '%')) == NULL) {
363             memset(user_and_password, '\0', lpass);
364             amfree(user_and_password);
365             if(domain) {
366                 memset(domain, '\0', strlen(domain));
367                 amfree(domain);
368             }
369             set_pname(error_pn);
370             amfree(error_pn);
371             error("password field not \'user%%pass\' for %s", disk);
372         }
373         *pwtext++ = '\0';
374         pwtext_len = strlen(pwtext);
375         if ((sharename = makesharename(share, 0)) == 0) {
376             memset(user_and_password, '\0', lpass);
377             amfree(user_and_password);
378             if(domain) {
379                 memset(domain, '\0', strlen(domain));
380                 amfree(domain);
381             }
382             set_pname(error_pn);
383             amfree(error_pn);
384             error("error [can't make share name of %s]", share);
385         }
386
387         taropt = stralloc("-T");
388         if(options->exclude_file && options->exclude_file->nb_element == 1) {
389             strappend(taropt, "X");
390         }
391 #if SAMBA_VERSION >= 2
392         strappend(taropt, "q");
393 #endif
394         strappend(taropt, "c");
395         if (level != 0) {
396             strappend(taropt, "g");
397         } else if (!options->no_record) {
398             strappend(taropt, "a");
399         }
400
401         dbprintf(("%s: backup of %s", debug_prefix_time("-gnutar"), sharename));
402         if (subdir) {
403             dbprintf(("/%s",subdir));
404         }
405         dbprintf(("\n"));
406
407         program->backup_name = program->restore_name = SAMBA_CLIENT;
408         cmd = stralloc(program->backup_name);
409         write_tapeheader();
410
411         start_index(options->createindex, dumpout, mesgf, indexf, indexcmd);
412
413         if (pwtext_len > 0) {
414             pw_fd_env = "PASSWD_FD";
415         } else {
416             pw_fd_env = "dummy_PASSWD_FD";
417         }
418         dumppid = pipespawn(cmd, STDIN_PIPE|PASSWD_PIPE,
419                             &dumpin, &dumpout, &mesgf,
420                             pw_fd_env, &passwdf,
421                             "smbclient",
422                             sharename,
423                             *user_and_password ? "-U" : skip_argument,
424                             *user_and_password ? user_and_password : skip_argument,
425                             "-E",
426                             domain ? "-W" : skip_argument,
427                             domain ? domain : skip_argument,
428 #if SAMBA_VERSION >= 2
429                             subdir ? "-D" : skip_argument,
430                             subdir ? subdir : skip_argument,
431 #endif
432                             "-d0",
433                             taropt,
434                             "-",
435                             options->exclude_file && options->exclude_file->nb_element == 1 ? options->exclude_file->first->name : skip_argument,
436                             NULL);
437         if(domain) {
438             memset(domain, '\0', strlen(domain));
439             amfree(domain);
440         }
441         if(pwtext_len > 0 && fullwrite(passwdf, pwtext, pwtext_len) < 0) {
442             int save_errno = errno;
443
444             aclose(passwdf);
445             memset(user_and_password, '\0', lpass);
446             amfree(user_and_password);
447             set_pname(error_pn);
448             amfree(error_pn);
449             error("error [password write failed: %s]", strerror(save_errno));
450         }
451         memset(user_and_password, '\0', lpass);
452         amfree(user_and_password);
453         aclose(passwdf);
454         amfree(sharename);
455         amfree(share);
456         amfree(subdir);
457         amfree(taropt);
458         tarpid = dumppid;
459     } else
460 #endif                                                                  /* } */
461     {
462         int nb_exclude = 0;
463         int nb_include = 0;
464         char **my_argv;
465         int i = 0;
466         char *file_exclude = NULL;
467         char *file_include = NULL;
468
469         if(options->exclude_file) nb_exclude+=options->exclude_file->nb_element;
470         if(options->exclude_list) nb_exclude+=options->exclude_list->nb_element;
471         if(options->include_file) nb_include+=options->include_file->nb_element;
472         if(options->include_list) nb_include+=options->include_list->nb_element;
473
474         if(nb_exclude > 0) file_exclude = build_exclude(disk, amdevice, options, 0);
475         if(nb_include > 0) file_include = build_include(disk, amdevice, options, 0);
476
477         my_argv = alloc(sizeof(char *) * (17 + (nb_exclude*2)+(nb_include*2)));
478
479         cmd = vstralloc(libexecdir, "/", "runtar", versionsuffix(), NULL);
480         write_tapeheader();
481
482         start_index(options->createindex, dumpout, mesgf, indexf, indexcmd);
483
484         my_argv[i++] = "gtar";
485         my_argv[i++] = "--create";
486         my_argv[i++] = "--file";
487         my_argv[i++] = "-";
488         my_argv[i++] = "--directory";
489         my_argv[i++] = dirname;
490         my_argv[i++] = "--one-file-system";
491 #ifdef GNUTAR_LISTED_INCREMENTAL_DIR
492         my_argv[i++] = "--listed-incremental";
493         my_argv[i++] = incrname;
494 #else
495         my_argv[i++] = "--incremental";
496         my_argv[i++] = "--newer";
497         my_argv[i++] = dumptimestr;
498 #endif 
499 #ifdef ENABLE_GNUTAR_ATIME_PRESERVE
500         /* --atime-preserve causes gnutar to call
501          * utime() after reading files in order to
502          * adjust their atime.  However, utime()
503          * updates the file's ctime, so incremental
504          * dumps will think the file has changed. */
505         my_argv[i++] = "--atime-preserve";
506 #endif
507         my_argv[i++] = "--sparse";
508         my_argv[i++] = "--ignore-failed-read";
509         my_argv[i++] = "--totals";
510
511         if(file_exclude) {
512             my_argv[i++] = "--exclude-from";
513             my_argv[i++] = file_exclude;
514         }
515
516         if(file_include) {
517             my_argv[i++] = "--files-from";
518             my_argv[i++] = file_include;
519         }
520         else {
521             my_argv[i++] = ".";
522         }
523         my_argv[i++] = NULL;
524         dumppid = pipespawnv(cmd, STDIN_PIPE,
525                              &dumpin, &dumpout, &mesgf, my_argv);
526         tarpid = dumppid;
527         amfree(file_exclude);
528         amfree(file_include);
529         amfree(my_argv);
530     }
531     dbprintf(("%s: %s: pid %ld\n",
532               debug_prefix_time("-gnutar"),
533               cmd,
534               (long)dumppid));
535
536     amfree(dirname);
537     amfree(cmd);
538     amfree(indexcmd);
539     amfree(error_pn);
540
541     /* close the write ends of the pipes */
542
543     aclose(dumpin);
544     aclose(dumpout);
545     aclose(dataf);
546     aclose(mesgf);
547     if (options->createindex)
548         aclose(indexf);
549 }
550
551 static void end_backup(goterror)
552 int goterror;
553 {
554     if(!options->no_record && !goterror) {
555 #ifdef GNUTAR_LISTED_INCREMENTAL_DIR
556         if (incrname != NULL && strlen(incrname) > 4) {
557             char *nodotnew;
558         
559             nodotnew = stralloc(incrname);
560             nodotnew[strlen(nodotnew)-4] = '\0';
561             if (rename(incrname, nodotnew) != 0) {
562                 fprintf(stderr, "%s: warning [renaming %s to %s: %s]\n",
563                         get_pname(), incrname, nodotnew, strerror(errno));
564             }
565             amfree(nodotnew);
566             amfree(incrname);
567         }
568 #endif
569
570         if(!start_amandates(1)) {
571             fprintf(stderr, "%s: warning [opening %s: %s]\n", get_pname(),
572                     AMANDATES_FILE, strerror(errno));
573         }
574         else {
575             amandates_updateone(cur_disk, cur_level, cur_dumptime);
576             finish_amandates();
577             free_amandates();
578         }
579     }
580 }
581
582 backup_program_t gnutar_program = {
583   "GNUTAR",
584 #ifdef GNUTAR
585   GNUTAR, GNUTAR,
586 #else
587   "gtar", "gtar",
588 #endif
589   re_table, start_backup, end_backup
590 };