Imported Upstream version 2.5.1
[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 "clientconf.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),
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     int dumpin, dumpout, compout;
145     char *cmd = NULL;
146     char *indexcmd = NULL;
147     char *dirname = NULL;
148     int l;
149     char dumptimestr[80];
150     struct tm *gmtm;
151     amandates_t *amdates;
152     time_t prev_dumptime;
153     char *error_pn = NULL;
154     char *compopt  = NULL;
155     char *encryptopt = skip_argument;
156     char *quoted;
157     char *qdisk;
158     int infd, outfd;
159     ssize_t nb;
160     char buf[32768];
161
162     (void)dumpdate;     /* Quiet unused parameter warning */
163
164     error_pn = stralloc2(get_pname(), "-smbclient");
165
166     qdisk = quote_string(disk);
167     dbprintf(("%s: start: %s:%s lev %d\n",
168               get_pname(), host, qdisk, level));
169
170     fprintf(stderr, "%s: start [%s:%s level %d]\n",
171             get_pname(), host, qdisk, level);
172
173      /*  apply client-side encryption here */
174      if ( options->encrypt == ENCRYPT_CUST ) {
175          encpid = pipespawn(options->clnt_encrypt, STDIN_PIPE,
176                         &compout, &dataf, &mesgf, 
177                         options->clnt_encrypt, encryptopt, NULL);
178          dbprintf(("%s: pid %ld: %s\n",
179                   debug_prefix_time("-gnutar"), (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 == COMPR_FAST || options->compress == COMPR_BEST) {
186           compopt = skip_argument;
187 #if defined(COMPRESS_BEST_OPT) && defined(COMPRESS_FAST_OPT)
188         if(options->compress == COMPR_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(("%s: pid %ld: %s",
198                   debug_prefix_time("-gnutar"), (long)comppid, COMPRESS_PATH));
199         if(compopt != skip_argument) {
200             dbprintf((" %s", compopt));
201         }
202         dbprintf(("\n"));
203      } else if (options->compress == COMPR_CUST) {
204         compopt = skip_argument;
205         comppid = pipespawn(options->clntcompprog, STDIN_PIPE,
206                             &dumpout, &compout, &mesgf,
207                             options->clntcompprog, compopt, NULL);
208         dbprintf(("%s: pid %ld: %s",
209                   debug_prefix_time("-gnutar-cust"), (long)comppid, options->clntcompprog));
210         if(compopt != skip_argument) {
211             dbprintf((" %s", compopt));
212         }
213         dbprintf(("\n"));
214     } else {
215         dumpout = compout;
216         comppid = -1;
217     }
218
219     gnutar_list_dir = client_getconf_str(CLN_GNUTAR_LIST_DIR);
220     if (strlen(gnutar_list_dir) == 0)
221         gnutar_list_dir = NULL;
222
223 #ifdef SAMBA_CLIENT                                                     /* { */
224     if (amdevice[0] == '/' && amdevice[1]=='/')
225         amfree(incrname);
226     else
227 #endif                                                                  /* } */
228     if (gnutar_list_dir) {
229         char *basename = NULL;
230         char number[NUM_STR_SIZE];
231         char *s;
232         int ch;
233         char *inputname = NULL;
234         int baselevel;
235
236         basename = vstralloc(gnutar_list_dir,
237                              "/",
238                              host,
239                              disk,
240                              NULL);
241         /*
242          * The loop starts at the first character of the host name,
243          * not the '/'.
244          */
245         s = basename + strlen(gnutar_list_dir) + 1;
246         while((ch = *s++) != '\0') {
247             if(ch == '/')
248                 s[-1] = '_';
249         }
250
251         snprintf(number, SIZEOF(number), "%d", level);
252         incrname = vstralloc(basename, "_", number, ".new", NULL);
253         unlink(incrname);
254
255         /*
256          * Open the listed incremental file from the previous level.  Search
257          * backward until one is found.  If none are found (which will also
258          * be true for a level 0), arrange to read from /dev/null.
259          */
260         baselevel = level;
261         infd = -1;
262         while (infd == -1) {
263             if (--baselevel >= 0) {
264                 snprintf(number, SIZEOF(number), "%d", baselevel);
265                 inputname = newvstralloc(inputname,
266                                          basename, "_", number, NULL);
267             } else {
268                 inputname = newstralloc(inputname, "/dev/null");
269             }
270             if ((infd = open(inputname, O_RDONLY)) == -1) {
271                 int save_errno = errno;
272                 char *qname = quote_string(inputname);
273
274                 dbprintf(("%s: error opening '%s': %s\n",
275                           debug_prefix_time("-gnutar"),
276                           qname,
277                           strerror(save_errno)));
278                 if (baselevel < 0) {
279                     error("error [opening '%s': %s]", qname, strerror(save_errno));
280                     /*NOTREACHED*/
281                 }
282                 amfree(qname);
283             }
284         }
285
286         /*
287          * Copy the previous listed incremental file to the new one.
288          */
289         if ((outfd = open(incrname, O_WRONLY|O_CREAT, 0600)) == -1) {
290             error("error [opening '%s': %s]", incrname, strerror(errno));
291             /*NOTREACHED*/
292         }
293
294         while ((nb = read(infd, &buf, SIZEOF(buf))) > 0) {
295             if (fullwrite(outfd, &buf, (size_t)nb) < nb) {
296                 error("error [writing to '%s': %s]", incrname,
297                        strerror(errno));
298                 /*NOTREACHED*/
299             }
300         }
301
302         if (nb < 0) {
303             error("error [reading from '%s': %s]", inputname, strerror(errno));
304             /*NOTREACHED*/
305         }
306
307         if (close(infd) != 0) {
308             error("error [closing '%s': %s]", inputname, strerror(errno));
309             /*NOTREACHED*/
310         }
311         if (close(outfd) != 0) {
312             error("error [closing '%s': %s]", incrname, strerror(errno));
313             /*NOTREACHED*/
314         }
315
316         dbprintf(("%s: doing level %d dump as listed-incremental",
317                   debug_prefix_time("-gnutar"), level));
318         if(baselevel >= 0) {
319             quoted = quote_string(inputname);
320             dbprintf((" from '%s'", quoted));
321             amfree(quoted);
322         }
323         quoted = quote_string(incrname);
324         dbprintf((" to '%s'\n", quoted));
325         amfree(quoted);
326         amfree(inputname);
327         amfree(basename);
328     }
329
330     /* find previous dump time */
331
332     amandates_file = client_getconf_str(CLN_AMANDATES);
333     if(!start_amandates(amandates_file, 0)) {
334         error("error [opening %s: %s]", amandates_file, strerror(errno));
335         /*NOTREACHED*/
336     }
337
338     amdates = amandates_lookup(disk);
339
340     prev_dumptime = EPOCH;
341     for(l = 0; l < level; l++) {
342         if(amdates->dates[l] > prev_dumptime)
343             prev_dumptime = amdates->dates[l];
344     }
345
346     finish_amandates();
347     free_amandates();
348
349     gmtm = gmtime(&prev_dumptime);
350     snprintf(dumptimestr, SIZEOF(dumptimestr),
351                 "%04d-%02d-%02d %2d:%02d:%02d GMT",
352                 gmtm->tm_year + 1900, gmtm->tm_mon+1, gmtm->tm_mday,
353                 gmtm->tm_hour, gmtm->tm_min, gmtm->tm_sec);
354
355     dbprintf(("%s: doing level %d dump from date: %s\n",
356               debug_prefix_time("-gnutar"), level, dumptimestr));
357
358     dirname = amname_to_dirname(amdevice);
359
360     cur_dumptime = time(0);
361     cur_level = level;
362     cur_disk = stralloc(disk);
363     indexcmd = vstralloc(
364 #ifdef GNUTAR
365                          GNUTAR,
366 #else
367                          "tar",
368 #endif
369                          " -tf", " -",
370                          " 2>/dev/null",
371                          " | sed", " -e",
372                          " \'s/^\\.//\'",
373                          NULL);
374
375 #ifdef SAMBA_CLIENT                                                     /* { */
376     /* Use sambatar if the disk to back up is a PC disk */
377     if (amdevice[0] == '/' && amdevice[1]=='/') {
378         char *sharename = NULL, *user_and_password = NULL, *domain = NULL;
379         char *share = NULL, *subdir = NULL;
380         char *pwtext = NULL;
381         char *taropt;
382         int passwdf = -1;
383         size_t lpass;
384         size_t pwtext_len;
385         char *pw_fd_env;
386
387         parsesharename(amdevice, &share, &subdir);
388         if (!share) {
389             amfree(share);
390             amfree(subdir);
391             set_pname(error_pn);
392             amfree(error_pn);
393             error("cannot parse disk entry %s for share/subdir", qdisk);
394             /*NOTREACHED*/
395         }
396         if ((subdir) && (SAMBA_VERSION < 2)) {
397             amfree(share);
398             amfree(subdir);
399             set_pname(error_pn);
400             amfree(error_pn);
401             error("subdirectory specified for share %s but samba not v2 or better", qdisk);
402             /*NOTREACHED*/
403         }
404         if ((user_and_password = findpass(share, &domain)) == NULL) {
405             if(domain) {
406                 memset(domain, '\0', strlen(domain));
407                 amfree(domain);
408             }
409             set_pname(error_pn);
410             amfree(error_pn);
411             error("error [invalid samba host or password not found?]");
412             /*NOTREACHED*/
413         }
414         lpass = strlen(user_and_password);
415         if ((pwtext = strchr(user_and_password, '%')) == NULL) {
416             memset(user_and_password, '\0', lpass);
417             amfree(user_and_password);
418             if(domain) {
419                 memset(domain, '\0', strlen(domain));
420                 amfree(domain);
421             }
422             set_pname(error_pn);
423             amfree(error_pn);
424             error("password field not \'user%%pass\' for %s", qdisk);
425             /*NOTREACHED*/
426         }
427         *pwtext++ = '\0';
428         pwtext_len = strlen(pwtext);
429         if ((sharename = makesharename(share, 0)) == 0) {
430             memset(user_and_password, '\0', lpass);
431             amfree(user_and_password);
432             if(domain) {
433                 memset(domain, '\0', strlen(domain));
434                 amfree(domain);
435             }
436             set_pname(error_pn);
437             amfree(error_pn);
438             error("error [can't make share name of %s]", share);
439             /*NOTREACHED*/
440         }
441
442         taropt = stralloc("-T");
443         if(options->exclude_file && options->exclude_file->nb_element == 1) {
444             strappend(taropt, "X");
445         }
446 #if SAMBA_VERSION >= 2
447         strappend(taropt, "q");
448 #endif
449         strappend(taropt, "c");
450         if (level != 0) {
451             strappend(taropt, "g");
452         } else if (!options->no_record) {
453             strappend(taropt, "a");
454         }
455
456         dbprintf(("%s: backup of %s", debug_prefix_time("-gnutar"), sharename));
457         if (subdir) {
458             dbprintf(("/%s",subdir));
459         }
460         dbprintf(("\n"));
461
462         program->backup_name = program->restore_name = SAMBA_CLIENT;
463         cmd = stralloc(program->backup_name);
464         info_tapeheader();
465
466         start_index(options->createindex, dumpout, mesgf, indexf, indexcmd);
467
468         if (pwtext_len > 0) {
469             pw_fd_env = "PASSWD_FD";
470         } else {
471             pw_fd_env = "dummy_PASSWD_FD";
472         }
473         dumppid = pipespawn(cmd, STDIN_PIPE|PASSWD_PIPE,
474                             &dumpin, &dumpout, &mesgf,
475                             pw_fd_env, &passwdf,
476                             "smbclient",
477                             sharename,
478                             *user_and_password ? "-U" : skip_argument,
479                             *user_and_password ? user_and_password : skip_argument,
480                             "-E",
481                             domain ? "-W" : skip_argument,
482                             domain ? domain : skip_argument,
483 #if SAMBA_VERSION >= 2
484                             subdir ? "-D" : skip_argument,
485                             subdir ? subdir : skip_argument,
486 #endif
487                             "-d0",
488                             taropt,
489                             "-",
490                             options->exclude_file && options->exclude_file->nb_element == 1 ? options->exclude_file->first->name : skip_argument,
491                             NULL);
492         if(domain) {
493             memset(domain, '\0', strlen(domain));
494             amfree(domain);
495         }
496         if(pwtext_len > 0 && fullwrite(passwdf, pwtext, pwtext_len) < 0) {
497             int save_errno = errno;
498
499             aclose(passwdf);
500             memset(user_and_password, '\0', lpass);
501             amfree(user_and_password);
502             set_pname(error_pn);
503             amfree(error_pn);
504             error("error [password write failed: %s]", strerror(save_errno));
505             /*NOTREACHED*/
506         }
507         memset(user_and_password, '\0', lpass);
508         amfree(user_and_password);
509         aclose(passwdf);
510         amfree(sharename);
511         amfree(share);
512         amfree(subdir);
513         amfree(taropt);
514         tarpid = dumppid;
515     } else
516 #endif                  /*end of samba */
517     {
518
519         int nb_exclude = 0;
520         int nb_include = 0;
521         char **my_argv;
522         int i = 0;
523         char *file_exclude = NULL;
524         char *file_include = NULL;
525
526         if(options->exclude_file) nb_exclude+=options->exclude_file->nb_element;
527         if(options->exclude_list) nb_exclude+=options->exclude_list->nb_element;
528         if(options->include_file) nb_include+=options->include_file->nb_element;
529         if(options->include_list) nb_include+=options->include_list->nb_element;
530
531         if(nb_exclude > 0) file_exclude = build_exclude(disk, amdevice, options, 0);
532         if(nb_include > 0) file_include = build_include(disk, amdevice, options, 0);
533
534         my_argv = alloc(SIZEOF(char *) * (22 + (nb_exclude*2)+(nb_include*2)));
535
536         cmd = vstralloc(libexecdir, "/", "runtar", versionsuffix(), NULL);
537         info_tapeheader();
538
539         start_index(options->createindex, dumpout, mesgf, indexf, indexcmd);
540
541         my_argv[i++] = "runtar";
542         if (g_options->config)
543             my_argv[i++] = g_options->config;
544         else
545             my_argv[i++] = "NOCONFIG";
546         my_argv[i++] = "gtar";
547         my_argv[i++] = "--create";
548         my_argv[i++] = "--file";
549         my_argv[i++] = "-";
550         my_argv[i++] = "--directory";
551         my_argv[i++] = dirname;
552         my_argv[i++] = "--one-file-system";
553         if (gnutar_list_dir && incrname) {
554             my_argv[i++] = "--listed-incremental";
555             my_argv[i++] = incrname;
556         } else {
557             my_argv[i++] = "--incremental";
558             my_argv[i++] = "--newer";
559             my_argv[i++] = dumptimestr;
560         }
561 #ifdef ENABLE_GNUTAR_ATIME_PRESERVE
562         /* --atime-preserve causes gnutar to call
563          * utime() after reading files in order to
564          * adjust their atime.  However, utime()
565          * updates the file's ctime, so incremental
566          * dumps will think the file has changed. */
567         my_argv[i++] = "--atime-preserve";
568 #endif
569         my_argv[i++] = "--sparse";
570         my_argv[i++] = "--ignore-failed-read";
571         my_argv[i++] = "--totals";
572
573         if(file_exclude) {
574             my_argv[i++] = "--exclude-from";
575             my_argv[i++] = file_exclude;
576         }
577
578         if(file_include) {
579             my_argv[i++] = "--files-from";
580             my_argv[i++] = file_include;
581         }
582         else {
583             my_argv[i++] = ".";
584         }
585         my_argv[i++] = NULL;
586         dumppid = pipespawnv(cmd, STDIN_PIPE,
587                              &dumpin, &dumpout, &mesgf, my_argv);
588         tarpid = dumppid;
589         amfree(file_exclude);
590         amfree(file_include);
591         amfree(my_argv);
592     }
593     dbprintf(("%s: %s: pid %ld\n",
594               debug_prefix_time("-gnutar"),
595               cmd,
596               (long)dumppid));
597
598     amfree(qdisk);
599     amfree(dirname);
600     amfree(cmd);
601     amfree(indexcmd);
602     amfree(error_pn);
603
604     /* close the write ends of the pipes */
605
606     aclose(dumpin);
607     aclose(dumpout);
608     aclose(compout);
609     aclose(dataf);
610     aclose(mesgf);
611     if (options->createindex)
612         aclose(indexf);
613 }
614
615 static void
616 end_backup(
617     int         goterror)
618 {
619     if(!options->no_record && !goterror) {
620         if (incrname != NULL && strlen(incrname) > 4) {
621             char *nodotnew;
622         
623             nodotnew = stralloc(incrname);
624             nodotnew[strlen(nodotnew)-4] = '\0';
625             if (rename(incrname, nodotnew)) {
626                 fprintf(stderr, "%s: warning [renaming %s to %s: %s]\n", 
627                         get_pname(), incrname, nodotnew, strerror(errno));
628             }
629             amfree(nodotnew);
630             amfree(incrname);
631         }
632
633         if(!start_amandates(amandates_file, 1)) {
634             fprintf(stderr, "%s: warning [opening %s: %s]", get_pname(),
635                     amandates_file, strerror(errno));
636         }
637         else {
638             amandates_updateone(cur_disk, cur_level, cur_dumptime);
639             finish_amandates();
640             free_amandates();
641         }
642     }
643 }
644
645 backup_program_t gnutar_program = {
646   "GNUTAR",
647 #ifdef GNUTAR
648   GNUTAR, GNUTAR,
649 #else
650   "gtar", "gtar",
651 #endif
652   re_table, start_backup, end_backup
653 };