]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/bextract.c
bextract fix
[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    VolName[0] = 0;
207    if (strncmp(devname, "/dev/", 5) != 0) {
208       /* Try stripping file part */
209       p = devname + strlen(devname);
210       while (p >= devname && *p != '/') {
211          p--;
212       }
213       if (*p == '/') {
214          strcpy(VolName, p+1);
215          *p = 0;
216       }
217    }
218    strcpy(jcr->VolumeName, VolName);
219
220    dev = init_dev(NULL, devname);
221    if (!dev || !open_device(dev)) {
222       Emsg1(M_ERROR_TERM, 0, "Cannot open %s\n", devname);
223    }
224    Dmsg0(90, "Device opened for read.\n");
225
226    if (stat(where, &statp) < 0) {
227       Emsg2(M_ERROR_TERM, 0, "Cannot stat %s. It must exist. ERR=%s\n",
228          where, strerror(errno));
229    }
230    if (!S_ISDIR(statp.st_mode)) {
231       Emsg1(M_ERROR_TERM, 0, "%s must be a directory.\n", where);
232    }
233
234    wherelen = strlen(where);
235    fname = get_pool_memory(PM_FNAME);
236    ofile = get_pool_memory(PM_FNAME);
237    lname = get_pool_memory(PM_FNAME);
238
239    block = new_block(dev);
240
241    create_vol_list(jcr);
242
243    Dmsg1(20, "Found %d volumes names to restore.\n", jcr->NumVolumes);
244
245    /* 
246     * Ready device for reading, and read records
247     */
248    if (!acquire_device_for_read(jcr, dev, block)) {
249       free_block(block);
250       free_vol_list(jcr);
251       return;
252    }
253
254    rec = new_record();
255    free_pool_memory(rec->data);
256    rec->data = get_memory(70000);
257
258    uint32_t compress_buf_size = 70000;
259    POOLMEM *compress_buf = get_memory(compress_buf_size);
260
261    for ( ;; ) {
262       if (!read_block_from_device(dev, block)) {
263          Dmsg1(500, "Main read record failed. rem=%d\n", rec->remainder);
264          if (dev->state & ST_EOT) {
265             DEV_RECORD *record;
266             if (!mount_next_read_volume(jcr, dev, block)) {
267                break;
268             }
269             record = new_record();
270             read_block_from_device(dev, block);
271             read_record_from_block(block, record);
272             get_session_record(dev, record, &sessrec);
273             free_record(record);
274             goto next_record;
275          }
276          if (dev->state & ST_EOF) {
277             continue;                 /* try again */
278          }
279          if (dev->state & ST_SHORT) {
280             continue;
281          }
282          display_error_status();
283       }
284
285 next_record:
286       for (rec->state=0; !is_block_empty(rec); ) {
287          if (!read_record_from_block(block, rec)) {
288             break;
289          }
290
291          if (rec->FileIndex == EOM_LABEL) { /* end of tape? */
292             Dmsg0(40, "Get EOM LABEL\n");
293             rec->remainder = 0;
294             break;                         /* yes, get out */
295          }
296
297          /* Some sort of label? */ 
298          if (rec->FileIndex < 0) {
299             get_session_record(dev, rec, &sessrec);
300             continue;
301          } /* end if label record */
302
303          /* Is this the file we want? */
304          if (bsr && !match_bsr(bsr, rec, &dev->VolHdr, &sessrec)) {
305             rec->remainder = 0;
306             continue;
307          }
308          if (is_partial_record(rec)) {
309             break;
310          }
311
312          /* File Attributes stream */
313          if (rec->Stream == STREAM_UNIX_ATTRIBUTES) {
314             char *ap, *lp, *fp;
315
316             /* If extracting, it was from previous stream, so
317              * close the output file.
318              */
319             if (extract) {
320                if (ofd < 0) {
321                   Emsg0(M_ERROR_TERM, 0, "Logic error output file should be open\n");
322                }
323                close(ofd);
324                ofd = -1;
325                extract = FALSE;
326                set_statp(jcr, fname, ofile, lname, type, &statp);
327             }
328
329             if (sizeof_pool_memory(fname) < rec->data_len) {
330                fname = realloc_pool_memory(fname, rec->data_len + 1);
331             }
332             if (sizeof_pool_memory(ofile) < rec->data_len + wherelen + 1) {
333                ofile = realloc_pool_memory(ofile, rec->data_len + wherelen + 1);
334             }
335             if (sizeof_pool_memory(lname) < rec->data_len) {
336                lname = realloc_pool_memory(lname, rec->data_len + wherelen + 1);
337             }
338             *fname = 0;
339             *lname = 0;
340
341             /*              
342              * An Attributes record consists of:
343              *    File_index
344              *    Type   (FT_types)
345              *    Filename
346              *    Attributes
347              *    Link name (if file linked i.e. FT_LNK)
348              *
349              */
350             sscanf(rec->data, "%ld %d", &record_file_index, &type);
351             if (record_file_index != rec->FileIndex)
352                Emsg2(M_ERROR_TERM, 0, "Record header file index %ld not equal record index %ld\n",
353                   rec->FileIndex, record_file_index);
354             ap = rec->data;
355             while (*ap++ != ' ')         /* skip record file index */
356                ;
357             while (*ap++ != ' ')         /* skip type */
358                ;
359             /* Save filename and position to attributes */
360             fp = fname;
361             while (*ap != 0) {
362                *fp++  = *ap++;
363             }
364             *fp = *ap++;                 /* terminate filename & point to attribs */
365
366             /* Skip to Link name */
367             if (type == FT_LNK || type == FT_LNKSAVED) {
368                lp = ap;
369                while (*lp++ != 0) {
370                   ;
371                }
372             } else {
373                lp = "";
374             }
375
376                
377             if (file_is_included(ff, fname) && !file_is_excluded(ff, fname)) {
378
379                decode_stat(ap, &statp);
380                /*
381                 * Prepend the where directory so that the
382                 * files are put where the user wants.
383                 *
384                 * We do a little jig here to handle Win32 files with
385                 * a drive letter.  
386                 *   If where is null and we are running on a win32 client,
387                 *      change nothing.
388                 *   Otherwise, if the second character of the filename is a
389                 *   colon (:), change it into a slash (/) -- this creates
390                 *   a reasonable pathname on most systems.
391                 */
392                if (where[0] == 0 && win32_client) {
393                   strcpy(ofile, fname);
394                   strcpy(lname, lp);
395                } else {
396                   strcpy(ofile, where);
397                   if (fname[1] == ':') {
398                      fname[1] = '/';
399                      strcat(ofile, fname);
400                      fname[1] = ':';
401                   } else {
402                      strcat(ofile, fname);
403                   }
404                   /* Fixup link name */
405                   if (type == FT_LNK || type == FT_LNKSAVED) {
406                      if (lp[0] == '/') {      /* if absolute path */
407                         strcpy(lname, where);
408                      }       
409                      /* ***FIXME**** we shouldn't have links on Windoz */
410                      if (lp[1] == ':') {
411                         lp[1] = '/';
412                         strcat(lname, lp);
413                         lp[1] = ':';
414                      } else {
415                         strcat(lname, lp);
416                      }
417                   }
418                }
419
420    /*          Pmsg1(000, "Restoring: %s\n", ofile); */
421
422                extract = create_file(jcr, fname, ofile, lname, type, &statp, &ofd);
423                num_files++;
424
425                if (extract) {
426                    print_ls_output(ofile, lname, type, &statp);   
427                }
428             }
429
430          /* Data stream and extracting */
431          } else if (rec->Stream == STREAM_FILE_DATA) {
432             if (extract) {
433                total += rec->data_len;
434                Dmsg2(8, "Write %ld bytes, total=%ld\n", rec->data_len, total);
435                if ((uint32_t)write(ofd, rec->data, rec->data_len) != rec->data_len) {
436                   Emsg1(M_ERROR_TERM, 0, "Write error: %s\n", strerror(errno));
437                }
438             }
439     
440          } else if (rec->Stream == STREAM_GZIP_DATA) {
441 #ifdef HAVE_LIBZ
442             if (extract) {
443                uLongf compress_len;
444
445                compress_len = compress_buf_size;
446                if (uncompress((Bytef *)compress_buf, &compress_len, 
447                      (const Bytef *)rec->data, (uLong)rec->data_len) != Z_OK) {
448                   Emsg0(M_ERROR_TERM, 0, _("Uncompression error.\n"));
449                }
450
451                Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
452                if ((uLongf)write(ofd, compress_buf, (size_t)compress_len) != compress_len) {
453                   Pmsg0(0, "===Write error===\n");
454                   Emsg2(M_ERROR_TERM, 0, "Write error on %s: %s\n", ofile, strerror(errno));
455                }
456                total += compress_len;
457                Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len,
458                   compress_len);
459             }
460 #else
461             if (extract) {
462                Emsg0(M_ERROR_TERM, 0, "GZIP data stream found, but GZIP not configured!\n");
463             }
464 #endif
465
466
467          /* If extracting, wierd stream (not 1 or 2), close output file anyway */
468          } else if (extract) {
469             if (ofd < 0) {
470                Emsg0(M_ERROR_TERM, 0, "Logic error output file should be open\n");
471             }
472             close(ofd);
473             ofd = -1;
474             extract = FALSE;
475             set_statp(jcr, fname, ofile, lname, type, &statp);
476          } else if (rec->Stream != STREAM_MD5_SIGNATURE) {
477             Pmsg2(0, "None of above!!! stream=%d data=%s\n", rec->Stream, rec->data);
478          }
479       }
480    }
481
482
483    /* If output file is still open, it was the last one in the
484     * archive since we just hit an end of file, so close the file. 
485     */
486    if (ofd >= 0) {
487       close(ofd);
488       set_statp(jcr, fname, ofile, lname, type, &statp);
489    }
490    release_device(jcr, dev, block);
491
492    free_pool_memory(fname);
493    free_pool_memory(ofile);
494    free_pool_memory(lname);
495    free_pool_memory(compress_buf);
496    term_dev(dev);
497    free_block(block);
498    free_record(rec);
499    printf("%u files restored.\n", num_files);
500    return;
501 }
502
503 extern char *getuser(uid_t uid);
504 extern char *getgroup(gid_t gid);
505
506 static void print_ls_output(char *fname, char *link, int type, struct stat *statp)
507 {
508    char buf[1000]; 
509    char ec1[30];
510    char *p, *f;
511    int n;
512
513    p = encode_mode(statp->st_mode, buf);
514    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
515    p += n;
516    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
517    p += n;
518    n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
519    p += n;
520    p = encode_time(statp->st_ctime, p);
521    *p++ = ' ';
522    *p++ = ' ';
523    /* Copy file name */
524    for (f=fname; *f && (p-buf) < (int)sizeof(buf); )
525       *p++ = *f++;
526    if (type == FT_LNK) {
527       *p++ = ' ';
528       *p++ = '-';
529       *p++ = '>';
530       *p++ = ' ';
531       /* Copy link name */
532       for (f=link; *f && (p-buf) < (int)sizeof(buf); )
533          *p++ = *f++;
534    }
535    *p++ = '\n';
536    *p = 0;
537    fputs(buf, stdout);
538 }
539
540 static void get_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec)
541 {
542    char *rtype;
543    memset(sessrec, 0, sizeof(sessrec));
544    switch (rec->FileIndex) {
545       case PRE_LABEL:
546          rtype = "Fresh Volume Label";   
547          break;
548       case VOL_LABEL:
549          rtype = "Volume Label";
550          unser_volume_label(dev, rec);
551          break;
552       case SOS_LABEL:
553          rtype = "Begin Session";
554          unser_session_label(sessrec, rec);
555          break;
556       case EOS_LABEL:
557          rtype = "End Session";
558          break;
559       case EOM_LABEL:
560          rtype = "End of Media";
561          break;
562       default:
563          rtype = "Unknown";
564          break;
565    }
566    Dmsg5(10, "%s Record: VolSessionId=%d VolSessionTime=%d JobId=%d DataLen=%d\n",
567          rtype, rec->VolSessionId, rec->VolSessionTime, rec->Stream, rec->data_len);
568 }
569
570 /* Dummies to replace askdir.c */
571 int     dir_get_volume_info(JCR *jcr) { return 1;}
572 int     dir_find_next_appendable_volume(JCR *jcr) { return 1;}
573 int     dir_update_volume_info(JCR *jcr, VOLUME_CAT_INFO *vol, int relabel) { return 1; }
574 int     dir_create_jobmedia_record(JCR *jcr) { return 1; }
575 int     dir_ask_sysop_to_mount_next_volume(JCR *jcr, DEVICE *dev) { return 1; }
576 int     dir_update_file_attributes(JCR *jcr, DEV_RECORD *rec) { return 1;}
577 int     dir_send_job_status(JCR *jcr) {return 1;}
578
579
580 int dir_ask_sysop_to_mount_volume(JCR *jcr, DEVICE *dev)
581 {
582    fprintf(stderr, "Mount Volume %s on device %s and press return when ready: ",
583       jcr->VolumeName, dev_name(dev));
584    getchar();   
585    return 1;
586 }