]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/bextract.c
2156894e66cca5594da4f1fb192d49a4951abe0b
[bacula/bacula] / bacula / src / stored / bextract.c
1 /*
2  *
3  *  Dumb program to extract files from a Bacula backup.
4  *
5  *   Kern E. Sibbald
6  *
7  *   Version $Id$
8  *
9  */
10 /*
11    Copyright (C) 2000, 2001, 2002 Kern Sibbald and John Walker
12
13    This program is free software; you can redistribute it and/or
14    modify it under the terms of the GNU General Public License as
15    published by the Free Software Foundation; either version 2 of
16    the License, or (at your option) any later version.
17
18    This program is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21    General Public License for more details.
22
23    You should have received a copy of the GNU General Public
24    License along with this program; if not, write to the Free
25    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
26    MA 02111-1307, USA.
27
28  */
29
30 #include "bacula.h"
31 #include "stored.h"
32 #include "findlib/find.h"
33
34 #ifdef HAVE_CYGWIN
35 int win32_client = 1;
36 #else
37 int win32_client = 0;
38 #endif
39
40
41 static void do_extract(char *fname, char *prefix);
42 static void print_ls_output(char *fname, char *link, int type, struct stat *statp);
43 static void get_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec);
44
45 static DEVICE *dev = NULL;
46 static int ofd = -1;
47
48 static JCR *jcr;
49 static FF_PKT my_ff;
50 static FF_PKT *ff = &my_ff;
51
52 static BSR *bsr = NULL;
53
54 static DEV_RECORD *rec;
55 static DEV_BLOCK *block;
56
57 static void usage()
58 {
59    fprintf(stderr,
60 "\nVersion: " VERSION " (" DATE ")\n\n"
61 "Usage: bextract [-d debug_level] <bacula-archive> <directory-to-store-files>\n"
62 "       -b <file>       specify a bootstrap file\n"
63 "       -dnn            set debug level to nn\n"
64 "       -e <file>       exclude list\n"
65 "       -i <file>       include list\n"
66 "       -?              print this message\n\n");
67    exit(1);
68 }
69
70 static void my_free_jcr(JCR *jcr)
71 {
72    return;
73 }
74
75
76 int main (int argc, char *argv[])
77 {
78    int ch;   
79    FILE *fd;
80    char line[1000];
81    int got_inc = FALSE;
82
83    my_name_is(argc, argv, "bextract");
84    init_msg(NULL, NULL);              /* setup message handler */
85
86    memset(ff, 0, sizeof(FF_PKT));
87    init_include_exclude_files(ff);
88
89    while ((ch = getopt(argc, argv, "b:d:e:i:?")) != -1) {
90       switch (ch) {
91          case 'b':                    /* bootstrap file */
92             bsr = parse_bsr(NULL, optarg);
93 //          dump_bsr(bsr);
94             break;
95
96          case 'd':                    /* debug level */
97             debug_level = atoi(optarg);
98             if (debug_level <= 0)
99                debug_level = 1; 
100             break;
101
102          case 'e':                    /* exclude list */
103             if ((fd = fopen(optarg, "r")) == NULL) {
104                Pmsg2(0, "Could not open exclude file: %s, ERR=%s\n",
105                   optarg, strerror(errno));
106                exit(1);
107             }
108             while (fgets(line, sizeof(line), fd) != NULL) {
109                strip_trailing_junk(line);
110                Dmsg1(900, "add_exclude %s\n", line);
111                add_fname_to_exclude_list(ff, line);
112             }
113             fclose(fd);
114             break;
115
116          case 'i':                    /* include list */
117             if ((fd = fopen(optarg, "r")) == NULL) {
118                Pmsg2(0, "Could not open include file: %s, ERR=%s\n",
119                   optarg, strerror(errno));
120                exit(1);
121             }
122             while (fgets(line, sizeof(line), fd) != NULL) {
123                strip_trailing_junk(line);
124                Dmsg1(900, "add_include %s\n", line);
125                add_fname_to_include_list(ff, 0, line);
126             }
127             fclose(fd);
128             got_inc = TRUE;
129             break;
130
131          case '?':
132          default:
133             usage();
134
135       }  
136    }
137    argc -= optind;
138    argv += optind;
139
140    if (argc != 2) {
141       Pmsg0(0, "Wrong number of arguments: \n");
142       usage();
143    }
144    if (!got_inc) {                            /* If no include file, */
145       add_fname_to_include_list(ff, 0, "/");  /*   include everything */
146    }
147
148    jcr = new_jcr(sizeof(JCR), my_free_jcr);
149    jcr->VolSessionId = 1;
150    jcr->VolSessionTime = (uint32_t)time(NULL);
151    jcr->bsr = bsr;
152    strcpy(jcr->Job, "bextract");
153    jcr->dev_name = get_pool_memory(PM_FNAME);
154    strcpy(jcr->dev_name, argv[0]);
155
156    do_extract(argv[0], argv[1]);
157
158    free_jcr(jcr);
159    if (bsr) {
160       free_bsr(bsr);
161    }
162    return 0;
163 }
164
165 /*
166  * Device got an error, attempt to analyse it
167  */
168 static void display_error_status()
169 {
170    uint32_t status;
171
172    Emsg0(M_ERROR, 0, dev->errmsg);
173    status_dev(dev, &status);
174    Dmsg1(20, "Device status: %x\n", status);
175    if (status & MT_EOD)
176       Emsg0(M_ERROR_TERM, 0, "Unexpected End of Data\n");
177    else if (status & MT_EOT)
178       Emsg0(M_ERROR_TERM, 0, "Unexpected End of Tape\n");
179    else if (status & MT_EOF)
180       Emsg0(M_ERROR_TERM, 0, "Unexpected End of File\n");
181    else if (status & MT_DR_OPEN)
182       Emsg0(M_ERROR_TERM, 0, "Tape Door is Open\n");
183    else if (!(status & MT_ONLINE))
184       Emsg0(M_ERROR_TERM, 0, "Unexpected Tape is Off-line\n");
185    else
186       Emsg2(M_ERROR_TERM, 0, "Read error on Record Header %s: %s\n", dev_name(dev), strerror(errno));
187 }
188   
189
190 static void do_extract(char *devname, char *where)
191 {
192    char VolName[100];
193    char *p;
194    struct stat statp;
195    int extract = FALSE;
196    int type;
197    long record_file_index;
198    long total = 0;
199    POOLMEM *fname;                    /* original file name */
200    POOLMEM *ofile;                    /* output name with prefix */
201    POOLMEM *lname;                    /* link name */
202    int wherelen;                      /* prefix length */
203    SESSION_LABEL sessrec;
204    uint32_t num_files = 0;
205
206    if (strncmp(devname, "/dev/", 5) != 0) {
207       /* Try stripping file part */
208       p = devname + strlen(devname);
209       while (p >= devname && *p != '/') {
210          p--;
211       }
212       if (*p == '/') {
213          strcpy(VolName, p+1);
214          *p = 0;
215       }
216    }
217    strcpy(jcr->VolumeName, VolName);
218
219    dev = init_dev(NULL, devname);
220    if (!dev || !open_device(dev)) {
221       Emsg1(M_ERROR_TERM, 0, "Cannot open %s\n", devname);
222    }
223    Dmsg0(90, "Device opened for read.\n");
224
225    if (stat(where, &statp) < 0) {
226       Emsg2(M_ERROR_TERM, 0, "Cannot stat %s. It must exist. ERR=%s\n",
227          where, strerror(errno));
228    }
229    if (!S_ISDIR(statp.st_mode)) {
230       Emsg1(M_ERROR_TERM, 0, "%s must be a directory.\n", where);
231    }
232
233    wherelen = strlen(where);
234    fname = get_pool_memory(PM_FNAME);
235    ofile = get_pool_memory(PM_FNAME);
236    lname = get_pool_memory(PM_FNAME);
237
238    block = new_block(dev);
239
240    create_vol_list(jcr);
241
242    Dmsg1(20, "Found %d volumes names to restore.\n", jcr->NumVolumes);
243
244    /* 
245     * Ready device for reading, and read records
246     */
247    if (!acquire_device_for_read(jcr, dev, block)) {
248       free_block(block);
249       free_vol_list(jcr);
250       return;
251    }
252
253    rec = new_record();
254    free_pool_memory(rec->data);
255    rec->data = get_memory(70000);
256
257    uint32_t compress_buf_size = 70000;
258    POOLMEM *compress_buf = get_memory(compress_buf_size);
259
260    for ( ;; ) {
261       if (!read_block_from_device(dev, block)) {
262          Dmsg1(500, "Main read record failed. rem=%d\n", rec->remainder);
263          if (dev->state & ST_EOT) {
264             DEV_RECORD *record;
265             if (!mount_next_read_volume(jcr, dev, block)) {
266                break;
267             }
268             record = new_record();
269             read_block_from_device(dev, block);
270             read_record_from_block(block, record);
271             get_session_record(dev, record, &sessrec);
272             free_record(record);
273             goto next_record;
274          }
275          if (dev->state & ST_EOF) {
276             continue;                 /* try again */
277          }
278          if (dev->state & ST_SHORT) {
279             continue;
280          }
281          display_error_status();
282       }
283
284 next_record:
285       for (rec->state=0; !is_block_empty(rec); ) {
286          if (!read_record_from_block(block, rec)) {
287             break;
288          }
289
290          if (rec->FileIndex == EOM_LABEL) { /* end of tape? */
291             Dmsg0(40, "Get EOM LABEL\n");
292             rec->remainder = 0;
293             break;                         /* yes, get out */
294          }
295
296          /* Some sort of label? */ 
297          if (rec->FileIndex < 0) {
298             get_session_record(dev, rec, &sessrec);
299             continue;
300          } /* end if label record */
301
302          /* Is this the file we want? */
303          if (bsr && !match_bsr(bsr, rec, &dev->VolHdr, &sessrec)) {
304             rec->remainder = 0;
305             continue;
306          }
307          if (is_partial_record(rec)) {
308             break;
309          }
310
311          /* File Attributes stream */
312          if (rec->Stream == STREAM_UNIX_ATTRIBUTES) {
313             char *ap, *lp, *fp;
314
315             /* If extracting, it was from previous stream, so
316              * close the output file.
317              */
318             if (extract) {
319                if (ofd < 0) {
320                   Emsg0(M_ERROR_TERM, 0, "Logic error output file should be open\n");
321                }
322                close(ofd);
323                ofd = -1;
324                extract = FALSE;
325                set_statp(jcr, fname, ofile, lname, type, &statp);
326             }
327
328             if (sizeof_pool_memory(fname) < rec->data_len) {
329                fname = realloc_pool_memory(fname, rec->data_len + 1);
330             }
331             if (sizeof_pool_memory(ofile) < rec->data_len + wherelen + 1) {
332                ofile = realloc_pool_memory(ofile, rec->data_len + wherelen + 1);
333             }
334             if (sizeof_pool_memory(lname) < rec->data_len) {
335                lname = realloc_pool_memory(lname, rec->data_len + wherelen + 1);
336             }
337             *fname = 0;
338             *lname = 0;
339
340             /*              
341              * An Attributes record consists of:
342              *    File_index
343              *    Type   (FT_types)
344              *    Filename
345              *    Attributes
346              *    Link name (if file linked i.e. FT_LNK)
347              *
348              */
349             sscanf(rec->data, "%ld %d", &record_file_index, &type);
350             if (record_file_index != rec->FileIndex)
351                Emsg2(M_ERROR_TERM, 0, "Record header file index %ld not equal record index %ld\n",
352                   rec->FileIndex, record_file_index);
353             ap = rec->data;
354             while (*ap++ != ' ')         /* skip record file index */
355                ;
356             while (*ap++ != ' ')         /* skip type */
357                ;
358             /* Save filename and position to attributes */
359             fp = fname;
360             while (*ap != 0) {
361                *fp++  = *ap++;
362             }
363             *fp = *ap++;                 /* terminate filename & point to attribs */
364
365             /* Skip to Link name */
366             if (type == FT_LNK || type == FT_LNKSAVED) {
367                lp = ap;
368                while (*lp++ != 0) {
369                   ;
370                }
371             } else {
372                lp = "";
373             }
374
375                
376             if (file_is_included(ff, fname) && !file_is_excluded(ff, fname)) {
377
378                decode_stat(ap, &statp);
379                /*
380                 * Prepend the where directory so that the
381                 * files are put where the user wants.
382                 *
383                 * We do a little jig here to handle Win32 files with
384                 * a drive letter.  
385                 *   If where is null and we are running on a win32 client,
386                 *      change nothing.
387                 *   Otherwise, if the second character of the filename is a
388                 *   colon (:), change it into a slash (/) -- this creates
389                 *   a reasonable pathname on most systems.
390                 */
391                if (where[0] == 0 && win32_client) {
392                   strcpy(ofile, fname);
393                   strcpy(lname, lp);
394                } else {
395                   strcpy(ofile, where);
396                   if (fname[1] == ':') {
397                      fname[1] = '/';
398                      strcat(ofile, fname);
399                      fname[1] = ':';
400                   } else {
401                      strcat(ofile, fname);
402                   }
403                   /* Fixup link name */
404                   if (type == FT_LNK || type == FT_LNKSAVED) {
405                      if (lp[0] == '/') {      /* if absolute path */
406                         strcpy(lname, where);
407                      }       
408                      /* ***FIXME**** we shouldn't have links on Windoz */
409                      if (lp[1] == ':') {
410                         lp[1] = '/';
411                         strcat(lname, lp);
412                         lp[1] = ':';
413                      } else {
414                         strcat(lname, lp);
415                      }
416                   }
417                }
418
419    /*          Pmsg1(000, "Restoring: %s\n", ofile); */
420
421                extract = create_file(jcr, fname, ofile, lname, type, &statp, &ofd);
422                num_files++;
423
424                if (extract) {
425                    print_ls_output(ofile, lname, type, &statp);   
426                }
427             }
428
429          /* Data stream and extracting */
430          } else if (rec->Stream == STREAM_FILE_DATA) {
431             if (extract) {
432                total += rec->data_len;
433                Dmsg2(8, "Write %ld bytes, total=%ld\n", rec->data_len, total);
434                if ((uint32_t)write(ofd, rec->data, rec->data_len) != rec->data_len) {
435                   Emsg1(M_ERROR_TERM, 0, "Write error: %s\n", strerror(errno));
436                }
437             }
438     
439          } else if (rec->Stream == STREAM_GZIP_DATA) {
440 #ifdef HAVE_LIBZ
441             if (extract) {
442                uLongf compress_len;
443
444                compress_len = compress_buf_size;
445                if (uncompress((Bytef *)compress_buf, &compress_len, 
446                      (const Bytef *)rec->data, (uLong)rec->data_len) != Z_OK) {
447                   Emsg0(M_ERROR_TERM, 0, _("Uncompression error.\n"));
448                }
449
450                Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
451                if ((uLongf)write(ofd, compress_buf, (size_t)compress_len) != compress_len) {
452                   Pmsg0(0, "===Write error===\n");
453                   Emsg2(M_ERROR_TERM, 0, "Write error on %s: %s\n", ofile, strerror(errno));
454                }
455                total += compress_len;
456                Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len,
457                   compress_len);
458             }
459 #else
460             if (extract) {
461                Emsg0(M_ERROR_TERM, 0, "GZIP data stream found, but GZIP not configured!\n");
462             }
463 #endif
464
465
466          /* If extracting, wierd stream (not 1 or 2), close output file anyway */
467          } else if (extract) {
468             if (ofd < 0) {
469                Emsg0(M_ERROR_TERM, 0, "Logic error output file should be open\n");
470             }
471             close(ofd);
472             ofd = -1;
473             extract = FALSE;
474             set_statp(jcr, fname, ofile, lname, type, &statp);
475          } else if (rec->Stream != STREAM_MD5_SIGNATURE) {
476             Pmsg2(0, "None of above!!! stream=%d data=%s\n", rec->Stream, rec->data);
477          }
478       }
479    }
480
481
482    /* If output file is still open, it was the last one in the
483     * archive since we just hit an end of file, so close the file. 
484     */
485    if (ofd >= 0) {
486       close(ofd);
487       set_statp(jcr, fname, ofile, lname, type, &statp);
488    }
489    release_device(jcr, dev, block);
490
491    free_pool_memory(fname);
492    free_pool_memory(ofile);
493    free_pool_memory(lname);
494    free_pool_memory(compress_buf);
495    term_dev(dev);
496    free_block(block);
497    free_record(rec);
498    printf("%u files restored.\n", num_files);
499    return;
500 }
501
502 extern char *getuser(uid_t uid);
503 extern char *getgroup(gid_t gid);
504
505 static void print_ls_output(char *fname, char *link, int type, struct stat *statp)
506 {
507    char buf[1000]; 
508    char ec1[30];
509    char *p, *f;
510    int n;
511
512    p = encode_mode(statp->st_mode, buf);
513    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
514    p += n;
515    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
516    p += n;
517    n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
518    p += n;
519    p = encode_time(statp->st_ctime, p);
520    *p++ = ' ';
521    *p++ = ' ';
522    /* Copy file name */
523    for (f=fname; *f && (p-buf) < (int)sizeof(buf); )
524       *p++ = *f++;
525    if (type == FT_LNK) {
526       *p++ = ' ';
527       *p++ = '-';
528       *p++ = '>';
529       *p++ = ' ';
530       /* Copy link name */
531       for (f=link; *f && (p-buf) < (int)sizeof(buf); )
532          *p++ = *f++;
533    }
534    *p++ = '\n';
535    *p = 0;
536    fputs(buf, stdout);
537 }
538
539 static void get_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec)
540 {
541    char *rtype;
542    memset(sessrec, 0, sizeof(sessrec));
543    switch (rec->FileIndex) {
544       case PRE_LABEL:
545          rtype = "Fresh Volume Label";   
546          break;
547       case VOL_LABEL:
548          rtype = "Volume Label";
549          unser_volume_label(dev, rec);
550          break;
551       case SOS_LABEL:
552          rtype = "Begin Session";
553          unser_session_label(sessrec, rec);
554          break;
555       case EOS_LABEL:
556          rtype = "End Session";
557          break;
558       case EOM_LABEL:
559          rtype = "End of Media";
560          break;
561       default:
562          rtype = "Unknown";
563          break;
564    }
565    Dmsg5(10, "%s Record: VolSessionId=%d VolSessionTime=%d JobId=%d DataLen=%d\n",
566          rtype, rec->VolSessionId, rec->VolSessionTime, rec->Stream, rec->data_len);
567 }
568
569 /* Dummies to replace askdir.c */
570 int     dir_get_volume_info(JCR *jcr) { return 1;}
571 int     dir_find_next_appendable_volume(JCR *jcr) { return 1;}
572 int     dir_update_volume_info(JCR *jcr, VOLUME_CAT_INFO *vol, int relabel) { return 1; }
573 int     dir_create_jobmedia_record(JCR *jcr) { return 1; }
574 int     dir_ask_sysop_to_mount_next_volume(JCR *jcr, DEVICE *dev) { return 1; }
575 int     dir_update_file_attributes(JCR *jcr, DEV_RECORD *rec) { return 1;}
576 int     dir_send_job_status(JCR *jcr) {return 1;}
577
578
579 int dir_ask_sysop_to_mount_volume(JCR *jcr, DEVICE *dev)
580 {
581    fprintf(stderr, "Mount Volume %s on device %s and press return when ready: ",
582       jcr->VolumeName, dev_name(dev));
583    getchar();   
584    return 1;
585 }