]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/bextract.c
b0b16df9549a99556f56fc0eaa5c2d6d188996ea
[bacula/bacula] / bacula / src / stored / bextract.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2015 Kern Sibbald
5    Copyright (C) 2000-2014 Free Software Foundation Europe e.V.
6
7    The original author of Bacula is Kern Sibbald, with contributions
8    from many others, a complete list can be found in the file AUTHORS.
9
10    You may use this file and others of this release according to the
11    license defined in the LICENSE file, which includes the Affero General
12    Public License, v3.0 ("AGPLv3") and some additional permissions and
13    terms pursuant to its AGPLv3 Section 7.
14
15    This notice must be preserved when any source code is 
16    conveyed and/or propagated.
17
18    Bacula(R) is a registered trademark of Kern Sibbald.
19 */
20 /*
21  *
22  *  Dumb program to extract files from a Bacula backup.
23  *
24  *   Kern E. Sibbald, MM
25  *
26  */
27
28 #include "bacula.h"
29 #include "stored.h"
30 #include "ch.h"
31 #include "findlib/find.h"
32
33 #ifdef HAVE_LZO
34 #include <lzo/lzoconf.h>
35 #include <lzo/lzo1x.h>
36 #endif
37
38 extern bool parse_sd_config(CONFIG *config, const char *configfile, int exit_code);
39
40 static void do_extract(char *fname);
41 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
42
43 static DEVICE *dev = NULL;
44 static DCR *dcr;
45 static BFILE bfd;
46 static JCR *jcr;
47 static FF_PKT *ff;
48 static BSR *bsr = NULL;
49 static bool extract = false;
50 static int non_support_data = 0;
51 static long total = 0;
52 static ATTR *attr;
53 static POOLMEM *curr_fname;
54 static char *where;
55 static uint64_t num_errors = 0;
56 static uint64_t num_records = 0;
57 static uint32_t num_files = 0;
58 static uint32_t compress_buf_size = 70000;
59 static POOLMEM *compress_buf;
60 static int prog_name_msg = 0;
61 static int win32_data_msg = 0;
62 static char *VolumeName = NULL;
63
64 static char *wbuf;                    /* write buffer address */
65 static uint32_t wsize;                /* write size */
66 static uint64_t fileAddr = 0;         /* file write address */
67
68 static CONFIG *config;
69 #define CONFIG_FILE "bacula-sd.conf"
70
71 void *start_heap;
72 char *configfile = NULL;
73 STORES *me = NULL;                    /* our Global resource */
74 bool forge_on = false;
75 bool skip_extract = false;
76 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
77 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
78
79 static void usage()
80 {
81    fprintf(stderr, _(
82 PROG_COPYRIGHT
83 "\n%sVersion: %s (%s)\n\n"
84 "Usage: bextract <options> <bacula-archive-device-name> <directory-to-store-files>\n"
85 "       -b <file>       specify a bootstrap file\n"
86 "       -c <file>       specify a Storage configuration file\n"
87 "       -d <nn>         set debug level to <nn>\n"
88 "       -dt             print timestamp in debug output\n"
89 "       -T              send debug traces to trace file (stored in /tmp)\n"
90 "       -e <file>       exclude list\n"
91 "       -i <file>       include list\n"
92 "       -p              proceed inspite of I/O errors\n"
93 "       -t              read data from volume, do not write anything\n"
94 "       -v              verbose\n"
95 "       -V <volumes>    specify Volume names (separated by |)\n"
96 "       -?              print this message\n\n"), 2000, "", VERSION, BDATE);
97    exit(1);
98 }
99
100
101 int main (int argc, char *argv[])
102 {
103    int ch;
104    FILE *fd;
105    char line[1000];
106    bool got_inc = false;
107
108    setlocale(LC_ALL, "");
109    bindtextdomain("bacula", LOCALEDIR);
110    textdomain("bacula");
111    init_stack_dump();
112    lmgr_init_thread();
113
114    working_directory = "/tmp";
115    my_name_is(argc, argv, "bextract");
116    init_msg(NULL, NULL);              /* setup message handler */
117
118    OSDependentInit();
119
120    ff = init_find_files();
121    binit(&bfd);
122
123    while ((ch = getopt(argc, argv, "Ttb:c:d:e:i:pvV:?")) != -1) {
124       switch (ch) {
125       case 't':
126          skip_extract = true;
127          break;
128
129       case 'b':                    /* bootstrap file */
130          bsr = parse_bsr(NULL, optarg);
131 //       dump_bsr(bsr, true);
132          break;
133
134       case 'T':                 /* Send debug to trace file */
135          set_trace(1);
136          break;
137
138       case 'c':                    /* specify config file */
139          if (configfile != NULL) {
140             free(configfile);
141          }
142          configfile = bstrdup(optarg);
143          break;
144
145       case 'd':                    /* debug level */
146          if (*optarg == 't') {
147             dbg_timestamp = true;
148          } else {
149             debug_level = atoi(optarg);
150             if (debug_level <= 0) {
151                debug_level = 1;
152             }
153          }
154          break;
155
156       case 'e':                    /* exclude list */
157          if ((fd = fopen(optarg, "rb")) == NULL) {
158             berrno be;
159             Pmsg2(0, _("Could not open exclude file: %s, ERR=%s\n"),
160                optarg, be.bstrerror());
161             exit(1);
162          }
163          while (fgets(line, sizeof(line), fd) != NULL) {
164             strip_trailing_junk(line);
165             if (line[0] == 0) { /* skip blank lines */
166                continue;
167             }
168             Dmsg1(900, "add_exclude %s\n", line);
169             add_fname_to_exclude_list(ff, line);
170          }
171          fclose(fd);
172          break;
173
174       case 'i':                    /* include list */
175          if ((fd = fopen(optarg, "rb")) == NULL) {
176             berrno be;
177             Pmsg2(0, _("Could not open include file: %s, ERR=%s\n"),
178                optarg, be.bstrerror());
179             exit(1);
180          }
181          while (fgets(line, sizeof(line), fd) != NULL) {
182             strip_trailing_junk(line);
183             if (line[0] == 0) { /* skip blank lines */
184                continue;
185             }
186             Dmsg1(900, "add_include %s\n", line);
187             add_fname_to_include_list(ff, 0, line);
188          }
189          fclose(fd);
190          got_inc = true;
191          break;
192
193       case 'p':
194          forge_on = true;
195          break;
196
197       case 'v':
198          verbose++;
199          break;
200
201       case 'V':                    /* Volume name */
202          VolumeName = optarg;
203          break;
204
205       case '?':
206       default:
207          usage();
208
209       } /* end switch */
210    } /* end while */
211    argc -= optind;
212    argv += optind;
213
214    if (argc != 2) {
215       Pmsg0(0, _("Wrong number of arguments: \n"));
216       usage();
217    }
218
219    if (configfile == NULL) {
220       configfile = bstrdup(CONFIG_FILE);
221    }
222
223    config = new_config_parser();
224    parse_sd_config(config, configfile, M_ERROR_TERM);
225    setup_me();
226    load_sd_plugins(me->plugin_directory);
227
228    if (!got_inc) {                            /* If no include file, */
229       add_fname_to_include_list(ff, 0, "/");  /*   include everything */
230    }
231
232    where = argv[1];
233    do_extract(argv[0]);
234
235    if (bsr) {
236       free_bsr(bsr);
237    }
238    if (prog_name_msg) {
239       Pmsg1(000, _("%d Program Name and/or Program Data Stream records ignored.\n"),
240          prog_name_msg);
241    }
242    if (win32_data_msg) {
243       Pmsg1(000, _("%d Win32 data or Win32 gzip data stream records. Ignored.\n"),
244          win32_data_msg);
245    }
246    term_include_exclude_files(ff);
247    term_find_files(ff);
248    return 0;
249 }
250
251 static void do_extract(char *devname)
252 {
253    char ed1[50];
254    struct stat statp;
255
256    enable_backup_privileges(NULL, 1);
257
258    jcr = setup_jcr("bextract", devname, bsr, VolumeName, SD_READ, false/*read dedup data*/);
259    if (!jcr) {
260       exit(1);
261    }
262    dev = jcr->read_dcr->dev;
263    if (!dev) {
264       exit(1);
265    }
266    dcr = jcr->read_dcr;
267
268    /* Make sure where directory exists and that it is a directory */
269    if (stat(where, &statp) < 0) {
270       berrno be;
271       Emsg2(M_ERROR_TERM, 0, _("Cannot stat %s. It must exist. ERR=%s\n"),
272          where, be.bstrerror());
273    }
274    if (!S_ISDIR(statp.st_mode)) {
275       Emsg1(M_ERROR_TERM, 0, _("%s must be a directory.\n"), where);
276    }
277
278    free(jcr->where);
279    jcr->where = bstrdup(where);
280    attr = new_attr(jcr);
281
282    compress_buf = get_memory(compress_buf_size);
283    curr_fname = get_pool_memory(PM_FNAME);
284    *curr_fname = 0;
285
286    read_records(dcr, record_cb, mount_next_read_volume);
287    /* If output file is still open, it was the last one in the
288     * archive since we just hit an end of file, so close the file.
289     */
290    if (is_bopen(&bfd)) {
291       set_attributes(jcr, attr, &bfd);
292    }
293    release_device(dcr);
294    free_attr(attr);
295    free_jcr(jcr);
296    dev->term();
297    free_pool_memory(curr_fname);
298
299    printf(_("%u files restored.\n"), num_files);
300    if (num_errors) {
301       printf(_("Found %s error%s\n"), edit_uint64(num_errors, ed1), num_errors>1? "s":"");
302    }
303    return;
304 }
305
306 static bool store_data(BFILE *bfd, char *data, const int32_t length)
307 {
308    if (is_win32_stream(attr->data_stream) && !have_win32_api()) {
309       set_portable_backup(bfd);
310       if (!processWin32BackupAPIBlock(bfd, data, length)) {
311          berrno be;
312          Emsg2(M_ERROR_TERM, 0, _("Write error on %s: %s\n"),
313                attr->ofname, be.bstrerror());
314          return false;
315       }
316    } else if (bwrite(bfd, data, length) != (ssize_t)length) {
317       berrno be;
318       Emsg2(M_ERROR_TERM, 0, _("Write error on %s: %s\n"),
319             attr->ofname, be.bstrerror());
320       return false;
321    }
322
323    return true;
324 }
325
326 /*
327  * Called here for each record from read_records()
328  */
329 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
330 {
331    int stat, ret=true;
332    JCR *jcr = dcr->jcr;
333    char ed1[50];
334
335    bool     restoredatap = false;
336    POOLMEM *orgdata = NULL;
337    uint32_t orgdata_len = 0;
338
339    if (rec->FileIndex < 0) {
340       return true;                    /* we don't want labels */
341    }
342
343    /* In this mode, we do not create any file on disk, just read
344     * everything from the volume.
345     */
346    if (skip_extract) {
347       switch (rec->maskedStream) {
348       case STREAM_UNIX_ATTRIBUTES:
349       case STREAM_UNIX_ATTRIBUTES_EX:
350          if (!unpack_attributes_record(jcr, rec->Stream, rec->data, rec->data_len, attr)) {
351             Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
352          }
353          if (verbose) {
354             attr->data_stream = decode_stat(attr->attr, &attr->statp, sizeof(attr->statp), &attr->LinkFI);
355             build_attr_output_fnames(jcr, attr);
356             print_ls_output(jcr, attr);
357          }
358          pm_strcpy(curr_fname, attr->fname);
359          num_files++;
360          break;
361       }
362       num_records++;
363
364       /* We display some progress information if verbose not set or set to 2 */
365       if (verbose != 1 && (num_records % 200000) == 0L) {
366          fprintf(stderr, "\rfiles=%d records=%s\n", num_files, edit_uint64(num_records, ed1));
367       }
368       ret = true;
369       goto bail_out;
370    }
371
372    /* File Attributes stream */
373
374    switch (rec->maskedStream) {
375    case STREAM_UNIX_ATTRIBUTES:
376    case STREAM_UNIX_ATTRIBUTES_EX:
377
378       /* If extracting, it was from previous stream, so
379        * close the output file.
380        */
381       if (extract) {
382          if (!is_bopen(&bfd)) {
383             Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
384          }
385          set_attributes(jcr, attr, &bfd);
386          extract = false;
387       }
388
389       if (!unpack_attributes_record(jcr, rec->Stream, rec->data, rec->data_len, attr)) {
390          Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
391       }
392
393       /* Keep the name of the current file if we find a bad block */
394       pm_strcpy(curr_fname, attr->fname);
395
396       if (file_is_included(ff, attr->fname) && !file_is_excluded(ff, attr->fname)) {
397          attr->data_stream = decode_stat(attr->attr, &attr->statp, sizeof(attr->statp), &attr->LinkFI);
398          if (!is_restore_stream_supported(attr->data_stream)) {
399             if (!non_support_data++) {
400                Jmsg(jcr, M_ERROR, 0, _("%s stream not supported on this Client.\n"),
401                   stream_to_ascii(attr->data_stream));
402             }
403             extract = false;
404             goto bail_out;
405          }
406
407          build_attr_output_fnames(jcr, attr);
408
409          if (attr->type == FT_DELETED) { /* TODO: choose the right fname/ofname */
410             Jmsg(jcr, M_INFO, 0, _("%s was deleted.\n"), attr->fname);
411             extract = false;
412             goto bail_out;
413          }
414
415          extract = false;
416          stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
417
418          switch (stat) {
419          case CF_ERROR:
420          case CF_SKIP:
421             break;
422          case CF_EXTRACT:
423             extract = true;
424             print_ls_output(jcr, attr);
425             num_files++;
426             fileAddr = 0;
427             break;
428          case CF_CREATED:
429             set_attributes(jcr, attr, &bfd);
430             print_ls_output(jcr, attr);
431             num_files++;
432             fileAddr = 0;
433             break;
434          }
435       }
436       break;
437
438    case STREAM_RESTORE_OBJECT:
439       /* nothing to do */
440       break;
441
442    /* Data stream and extracting */
443    case STREAM_FILE_DATA:
444    case STREAM_SPARSE_DATA:
445    case STREAM_WIN32_DATA:
446
447       if (extract) {
448          if (rec->maskedStream == STREAM_SPARSE_DATA) {
449             ser_declare;
450             uint64_t faddr;
451             wbuf = rec->data + OFFSET_FADDR_SIZE;
452             wsize = rec->data_len - OFFSET_FADDR_SIZE;
453             ser_begin(rec->data, OFFSET_FADDR_SIZE);
454             unser_uint64(faddr);
455             if (fileAddr != faddr) {
456                fileAddr = faddr;
457                if (blseek(&bfd, (boffset_t)fileAddr, SEEK_SET) < 0) {
458                   berrno be;
459                   Emsg2(M_ERROR_TERM, 0, _("Seek error on %s: %s\n"),
460                      attr->ofname, be.bstrerror());
461                }
462             }
463          } else {
464             wbuf = rec->data;
465             wsize = rec->data_len;
466          }
467          total += wsize;
468          Dmsg2(8, "Write %u bytes, total=%u\n", wsize, total);
469          store_data(&bfd, wbuf, wsize);
470          fileAddr += wsize;
471       }
472       break;
473
474    /* GZIP data stream */
475    case STREAM_GZIP_DATA:
476    case STREAM_SPARSE_GZIP_DATA:
477    case STREAM_WIN32_GZIP_DATA:
478 #ifdef HAVE_LIBZ
479       if (extract) {
480          uLong compress_len = compress_buf_size;
481          int stat = Z_BUF_ERROR;
482
483          if (rec->maskedStream == STREAM_SPARSE_GZIP_DATA) {
484             ser_declare;
485             uint64_t faddr;
486             char ec1[50];
487             wbuf = rec->data + OFFSET_FADDR_SIZE;
488             wsize = rec->data_len - OFFSET_FADDR_SIZE;
489             ser_begin(rec->data, OFFSET_FADDR_SIZE);
490             unser_uint64(faddr);
491             if (fileAddr != faddr) {
492                fileAddr = faddr;
493                if (blseek(&bfd, (boffset_t)fileAddr, SEEK_SET) < 0) {
494                   berrno be;
495                   Emsg3(M_ERROR, 0, _("Seek to %s error on %s: ERR=%s\n"),
496                      edit_uint64(fileAddr, ec1), attr->ofname, be.bstrerror());
497                   extract = false;
498                   goto bail_out;
499                }
500             }
501          } else {
502             wbuf = rec->data;
503             wsize = rec->data_len;
504          }
505
506          while (compress_len < 10000000 && (stat=uncompress((Byte *)compress_buf, &compress_len,
507                                  (const Byte *)wbuf, (uLong)wsize)) == Z_BUF_ERROR) {
508             /* The buffer size is too small, try with a bigger one */
509             compress_len = 2 * compress_len;
510             compress_buf = check_pool_memory_size(compress_buf,
511                                                   compress_len);
512          }
513          if (stat != Z_OK) {
514             Emsg1(M_ERROR, 0, _("Uncompression error. ERR=%d\n"), stat);
515             extract = false;
516             goto bail_out;
517          }
518
519          Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
520          store_data(&bfd, compress_buf, compress_len);
521          total += compress_len;
522          fileAddr += compress_len;
523          Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len,
524             compress_len);
525       }
526 #else
527       if (extract) {
528          Emsg0(M_ERROR, 0, _("GZIP data stream found, but GZIP not configured!\n"));
529          extract = false;
530          goto bail_out;
531       }
532 #endif
533       break;
534
535    /* Compressed data stream */
536    case STREAM_COMPRESSED_DATA:
537    case STREAM_SPARSE_COMPRESSED_DATA:
538    case STREAM_WIN32_COMPRESSED_DATA:
539       if (extract) {
540          uint32_t comp_magic, comp_len;
541          uint16_t comp_level, comp_version;
542 #ifdef HAVE_LZO
543          lzo_uint compress_len;
544          const unsigned char *cbuf;
545          int r, real_compress_len;
546 #endif
547
548          if (rec->maskedStream == STREAM_SPARSE_COMPRESSED_DATA) {
549             ser_declare;
550             uint64_t faddr;
551             char ec1[50];
552             wbuf = rec->data + OFFSET_FADDR_SIZE;
553             wsize = rec->data_len - OFFSET_FADDR_SIZE;
554             ser_begin(rec->data, OFFSET_FADDR_SIZE);
555             unser_uint64(faddr);
556             if (fileAddr != faddr) {
557                fileAddr = faddr;
558                if (blseek(&bfd, (boffset_t)fileAddr, SEEK_SET) < 0) {
559                   berrno be;
560                   Emsg3(M_ERROR, 0, _("Seek to %s error on %s: ERR=%s\n"),
561                      edit_uint64(fileAddr, ec1), attr->ofname, be.bstrerror());
562                   extract = false;
563                   goto bail_out;
564                }
565             }
566          } else {
567             wbuf = rec->data;
568             wsize = rec->data_len;
569          }
570
571          /* read compress header */
572          unser_declare;
573          unser_begin(wbuf, sizeof(comp_stream_header));
574          unser_uint32(comp_magic);
575          unser_uint32(comp_len);
576          unser_uint16(comp_level);
577          unser_uint16(comp_version);
578          Dmsg4(200, "Compressed data stream found: magic=0x%x, len=%d, level=%d, ver=0x%x\n", comp_magic, comp_len,
579                                  comp_level, comp_version);
580
581          /* version check */
582          if (comp_version != COMP_HEAD_VERSION) {
583             Emsg1(M_ERROR, 0, _("Compressed header version error. version=0x%x\n"), comp_version);
584             ret = false;
585             goto bail_out;
586          }
587          /* size check */
588          if (comp_len + sizeof(comp_stream_header) != wsize) {
589             Emsg2(M_ERROR, 0, _("Compressed header size error. comp_len=%d, msglen=%d\n"),
590                  comp_len, wsize);
591             ret = false;
592             goto bail_out;
593          }
594
595           switch(comp_magic) {
596 #ifdef HAVE_LZO
597             case COMPRESS_LZO1X:
598                compress_len = compress_buf_size;
599                cbuf = (const unsigned char*) wbuf + sizeof(comp_stream_header);
600                real_compress_len = wsize - sizeof(comp_stream_header);
601                Dmsg2(200, "Comp_len=%d msglen=%d\n", compress_len, wsize);
602                while ((r=lzo1x_decompress_safe(cbuf, real_compress_len,
603                                                (unsigned char *)compress_buf, &compress_len, NULL)) == LZO_E_OUTPUT_OVERRUN)
604                {
605
606                   /* The buffer size is too small, try with a bigger one */
607                   compress_len = 2 * compress_len;
608                   compress_buf = check_pool_memory_size(compress_buf,
609                                                   compress_len);
610                }
611                if (r != LZO_E_OK) {
612                   Emsg1(M_ERROR, 0, _("LZO uncompression error. ERR=%d\n"), r);
613                   extract = false;
614                   goto bail_out;
615                }
616                Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
617                store_data(&bfd, compress_buf, compress_len);
618                total += compress_len;
619                fileAddr += compress_len;
620                Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len, compress_len);
621                break;
622 #endif
623             default:
624                Emsg1(M_ERROR, 0, _("Compression algorithm 0x%x found, but not supported!\n"), comp_magic);
625                extract = false;
626                goto bail_out;
627          }
628
629       }
630       break;
631
632    case STREAM_MD5_DIGEST:
633    case STREAM_SHA1_DIGEST:
634    case STREAM_SHA256_DIGEST:
635    case STREAM_SHA512_DIGEST:
636       break;
637
638    case STREAM_SIGNED_DIGEST:
639    case STREAM_ENCRYPTED_SESSION_DATA:
640       // TODO landonf: Investigate crypto support in the storage daemon
641       break;
642
643    case STREAM_PROGRAM_NAMES:
644    case STREAM_PROGRAM_DATA:
645       if (!prog_name_msg) {
646          Pmsg0(000, _("Got Program Name or Data Stream. Ignored.\n"));
647          prog_name_msg++;
648       }
649       break;
650
651    default:
652       /* If extracting, weird stream (not 1 or 2), close output file anyway */
653       if (extract) {
654          if (!is_bopen(&bfd)) {
655             Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
656          }
657          set_attributes(jcr, attr, &bfd);
658          extract = false;
659       }
660       Jmsg(jcr, M_ERROR, 0, _("Unknown stream=%d ignored. This shouldn't happen!\n"),
661          rec->Stream);
662       break;
663
664    } /* end switch */
665 bail_out:
666    if (restoredatap) {
667       rec->data = orgdata;
668       rec->data_len = orgdata_len;
669    }
670    return ret;
671 }
672
673 /* Dummies to replace askdir.c */
674 bool    dir_find_next_appendable_volume(DCR *dcr) { return 1;}
675 bool    dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
676 bool    dir_create_jobmedia_record(DCR *dcr, bool zero) { return 1; }
677 bool    flush_jobmedia_queue(JCR *jcr) { return true; }
678 bool    dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
679 bool    dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
680 bool    dir_send_job_status(JCR *jcr) {return 1;}
681
682
683 bool dir_ask_sysop_to_mount_volume(DCR *dcr, bool /*writing*/)
684 {
685    DEVICE *dev = dcr->dev;
686    fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
687       dcr->VolumeName, dev->print_name());
688    dev->close();
689    getchar();
690    return true;
691 }
692
693 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw  writing)
694 {
695    Dmsg0(100, "Fake dir_get_volume_info\n");
696    dcr->setVolCatName(dcr->VolumeName);
697    Dmsg2(500, "Vol=%s VolType=%d\n", dcr->getVolCatName(), dcr->VolCatInfo.VolCatType);
698    return 1;
699 }