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