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