]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/bextract.c
e84e55e781e7eb41567cd394a2adb18adce65e05
[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             Dmsg1(900, "add_exclude %s\n", line);
166             add_fname_to_exclude_list(ff, line);
167          }
168          fclose(fd);
169          break;
170
171       case 'i':                    /* include list */
172          if ((fd = fopen(optarg, "rb")) == NULL) {
173             berrno be;
174             Pmsg2(0, _("Could not open include file: %s, ERR=%s\n"),
175                optarg, be.bstrerror());
176             exit(1);
177          }
178          while (fgets(line, sizeof(line), fd) != NULL) {
179             strip_trailing_junk(line);
180             Dmsg1(900, "add_include %s\n", line);
181             add_fname_to_include_list(ff, 0, line);
182          }
183          fclose(fd);
184          got_inc = true;
185          break;
186
187       case 'p':
188          forge_on = true;
189          break;
190
191       case 'v':
192          verbose++;
193          break;
194
195       case 'V':                    /* Volume name */
196          VolumeName = optarg;
197          break;
198
199       case '?':
200       default:
201          usage();
202
203       } /* end switch */
204    } /* end while */
205    argc -= optind;
206    argv += optind;
207
208    if (argc != 2) {
209       Pmsg0(0, _("Wrong number of arguments: \n"));
210       usage();
211    }
212
213    if (configfile == NULL) {
214       configfile = bstrdup(CONFIG_FILE);
215    }
216
217    config = new_config_parser();
218    parse_sd_config(config, configfile, M_ERROR_TERM);
219    setup_me();
220    load_sd_plugins(me->plugin_directory);
221
222    if (!got_inc) {                            /* If no include file, */
223       add_fname_to_include_list(ff, 0, "/");  /*   include everything */
224    }
225
226    where = argv[1];
227    do_extract(argv[0]);
228
229    if (bsr) {
230       free_bsr(bsr);
231    }
232    if (prog_name_msg) {
233       Pmsg1(000, _("%d Program Name and/or Program Data Stream records ignored.\n"),
234          prog_name_msg);
235    }
236    if (win32_data_msg) {
237       Pmsg1(000, _("%d Win32 data or Win32 gzip data stream records. Ignored.\n"),
238          win32_data_msg);
239    }
240    term_include_exclude_files(ff);
241    term_find_files(ff);
242    return 0;
243 }
244
245 static void do_extract(char *devname)
246 {
247    char ed1[50];
248    struct stat statp;
249
250    enable_backup_privileges(NULL, 1);
251
252    jcr = setup_jcr("bextract", devname, bsr, VolumeName, SD_READ, false/*read dedup data*/);
253    if (!jcr) {
254       exit(1);
255    }
256    dev = jcr->read_dcr->dev;
257    if (!dev) {
258       exit(1);
259    }
260    dcr = jcr->read_dcr;
261
262    /* Make sure where directory exists and that it is a directory */
263    if (stat(where, &statp) < 0) {
264       berrno be;
265       Emsg2(M_ERROR_TERM, 0, _("Cannot stat %s. It must exist. ERR=%s\n"),
266          where, be.bstrerror());
267    }
268    if (!S_ISDIR(statp.st_mode)) {
269       Emsg1(M_ERROR_TERM, 0, _("%s must be a directory.\n"), where);
270    }
271
272    free(jcr->where);
273    jcr->where = bstrdup(where);
274    attr = new_attr(jcr);
275
276    compress_buf = get_memory(compress_buf_size);
277    curr_fname = get_pool_memory(PM_FNAME);
278    *curr_fname = 0;
279
280    read_records(dcr, record_cb, mount_next_read_volume);
281    /* If output file is still open, it was the last one in the
282     * archive since we just hit an end of file, so close the file.
283     */
284    if (is_bopen(&bfd)) {
285       set_attributes(jcr, attr, &bfd);
286    }
287    release_device(dcr);
288    free_attr(attr);
289    free_jcr(jcr);
290    dev->term();
291    free_pool_memory(curr_fname);
292
293    printf(_("%u files restored.\n"), num_files);
294    if (num_errors) {
295       printf(_("Found %s error%s\n"), edit_uint64(num_errors, ed1), num_errors>1? "s":"");
296    }
297    return;
298 }
299
300 static bool store_data(BFILE *bfd, char *data, const int32_t length)
301 {
302    if (is_win32_stream(attr->data_stream) && !have_win32_api()) {
303       set_portable_backup(bfd);
304       if (!processWin32BackupAPIBlock(bfd, data, length)) {
305          berrno be;
306          Emsg2(M_ERROR_TERM, 0, _("Write error on %s: %s\n"),
307                attr->ofname, be.bstrerror());
308          return false;
309       }
310    } else if (bwrite(bfd, data, length) != (ssize_t)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
317    return true;
318 }
319
320 /*
321  * Called here for each record from read_records()
322  */
323 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
324 {
325    int stat, ret=true;
326    JCR *jcr = dcr->jcr;
327    char ed1[50];
328
329    bool     restoredatap = false;
330    POOLMEM *orgdata = NULL;
331    uint32_t orgdata_len = 0;
332
333    if (rec->FileIndex < 0) {
334       return true;                    /* we don't want labels */
335    }
336
337    /* In this mode, we do not create any file on disk, just read
338     * everything from the volume.
339     */
340    if (skip_extract) {
341       switch (rec->maskedStream) {
342       case STREAM_UNIX_ATTRIBUTES:
343       case STREAM_UNIX_ATTRIBUTES_EX:
344          if (!unpack_attributes_record(jcr, rec->Stream, rec->data, rec->data_len, attr)) {
345             Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
346          }
347          if (verbose) {
348             attr->data_stream = decode_stat(attr->attr, &attr->statp, sizeof(attr->statp), &attr->LinkFI);
349             build_attr_output_fnames(jcr, attr);
350             print_ls_output(jcr, attr);
351          }
352          pm_strcpy(curr_fname, attr->fname);
353          num_files++;
354          break;
355       }
356       num_records++;
357
358       /* We display some progress information if verbose not set or set to 2 */
359       if (verbose != 1 && (num_records % 200000) == 0L) {
360          fprintf(stderr, "\rfiles=%d records=%s\n", num_files, edit_uint64(num_records, ed1));
361       }
362       ret = true;
363       goto bail_out;
364    }
365
366    /* File Attributes stream */
367
368    switch (rec->maskedStream) {
369    case STREAM_UNIX_ATTRIBUTES:
370    case STREAM_UNIX_ATTRIBUTES_EX:
371
372       /* If extracting, it was from previous stream, so
373        * close the output file.
374        */
375       if (extract) {
376          if (!is_bopen(&bfd)) {
377             Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
378          }
379          set_attributes(jcr, attr, &bfd);
380          extract = false;
381       }
382
383       if (!unpack_attributes_record(jcr, rec->Stream, rec->data, rec->data_len, attr)) {
384          Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
385       }
386
387       /* Keep the name of the current file if we find a bad block */
388       pm_strcpy(curr_fname, attr->fname);
389
390       if (file_is_included(ff, attr->fname) && !file_is_excluded(ff, attr->fname)) {
391          attr->data_stream = decode_stat(attr->attr, &attr->statp, sizeof(attr->statp), &attr->LinkFI);
392          if (!is_restore_stream_supported(attr->data_stream)) {
393             if (!non_support_data++) {
394                Jmsg(jcr, M_ERROR, 0, _("%s stream not supported on this Client.\n"),
395                   stream_to_ascii(attr->data_stream));
396             }
397             extract = false;
398             goto bail_out;
399          }
400
401          build_attr_output_fnames(jcr, attr);
402
403          if (attr->type == FT_DELETED) { /* TODO: choose the right fname/ofname */
404             Jmsg(jcr, M_INFO, 0, _("%s was deleted.\n"), attr->fname);
405             extract = false;
406             goto bail_out;
407          }
408
409          extract = false;
410          stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
411
412          switch (stat) {
413          case CF_ERROR:
414          case CF_SKIP:
415             break;
416          case CF_EXTRACT:
417             extract = true;
418             print_ls_output(jcr, attr);
419             num_files++;
420             fileAddr = 0;
421             break;
422          case CF_CREATED:
423             set_attributes(jcr, attr, &bfd);
424             print_ls_output(jcr, attr);
425             num_files++;
426             fileAddr = 0;
427             break;
428          }
429       }
430       break;
431
432    case STREAM_RESTORE_OBJECT:
433       /* nothing to do */
434       break;
435
436    /* Data stream and extracting */
437    case STREAM_FILE_DATA:
438    case STREAM_SPARSE_DATA:
439    case STREAM_WIN32_DATA:
440
441       if (extract) {
442          if (rec->maskedStream == STREAM_SPARSE_DATA) {
443             ser_declare;
444             uint64_t faddr;
445             wbuf = rec->data + OFFSET_FADDR_SIZE;
446             wsize = rec->data_len - OFFSET_FADDR_SIZE;
447             ser_begin(rec->data, OFFSET_FADDR_SIZE);
448             unser_uint64(faddr);
449             if (fileAddr != faddr) {
450                fileAddr = faddr;
451                if (blseek(&bfd, (boffset_t)fileAddr, SEEK_SET) < 0) {
452                   berrno be;
453                   Emsg2(M_ERROR_TERM, 0, _("Seek error on %s: %s\n"),
454                      attr->ofname, be.bstrerror());
455                }
456             }
457          } else {
458             wbuf = rec->data;
459             wsize = rec->data_len;
460          }
461          total += wsize;
462          Dmsg2(8, "Write %u bytes, total=%u\n", wsize, total);
463          store_data(&bfd, wbuf, wsize);
464          fileAddr += wsize;
465       }
466       break;
467
468    /* GZIP data stream */
469    case STREAM_GZIP_DATA:
470    case STREAM_SPARSE_GZIP_DATA:
471    case STREAM_WIN32_GZIP_DATA:
472 #ifdef HAVE_LIBZ
473       if (extract) {
474          uLong compress_len = compress_buf_size;
475          int stat = Z_BUF_ERROR;
476
477          if (rec->maskedStream == STREAM_SPARSE_GZIP_DATA) {
478             ser_declare;
479             uint64_t faddr;
480             char ec1[50];
481             wbuf = rec->data + OFFSET_FADDR_SIZE;
482             wsize = rec->data_len - OFFSET_FADDR_SIZE;
483             ser_begin(rec->data, OFFSET_FADDR_SIZE);
484             unser_uint64(faddr);
485             if (fileAddr != faddr) {
486                fileAddr = faddr;
487                if (blseek(&bfd, (boffset_t)fileAddr, SEEK_SET) < 0) {
488                   berrno be;
489                   Emsg3(M_ERROR, 0, _("Seek to %s error on %s: ERR=%s\n"),
490                      edit_uint64(fileAddr, ec1), attr->ofname, be.bstrerror());
491                   extract = false;
492                   goto bail_out;
493                }
494             }
495          } else {
496             wbuf = rec->data;
497             wsize = rec->data_len;
498          }
499
500          while (compress_len < 10000000 && (stat=uncompress((Byte *)compress_buf, &compress_len,
501                                  (const Byte *)wbuf, (uLong)wsize)) == Z_BUF_ERROR) {
502             /* The buffer size is too small, try with a bigger one */
503             compress_len = 2 * compress_len;
504             compress_buf = check_pool_memory_size(compress_buf,
505                                                   compress_len);
506          }
507          if (stat != Z_OK) {
508             Emsg1(M_ERROR, 0, _("Uncompression error. ERR=%d\n"), stat);
509             extract = false;
510             goto bail_out;
511          }
512
513          Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
514          store_data(&bfd, compress_buf, compress_len);
515          total += compress_len;
516          fileAddr += compress_len;
517          Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len,
518             compress_len);
519       }
520 #else
521       if (extract) {
522          Emsg0(M_ERROR, 0, _("GZIP data stream found, but GZIP not configured!\n"));
523          extract = false;
524          goto bail_out;
525       }
526 #endif
527       break;
528
529    /* Compressed data stream */
530    case STREAM_COMPRESSED_DATA:
531    case STREAM_SPARSE_COMPRESSED_DATA:
532    case STREAM_WIN32_COMPRESSED_DATA:
533       if (extract) {
534          uint32_t comp_magic, comp_len;
535          uint16_t comp_level, comp_version;
536 #ifdef HAVE_LZO
537          lzo_uint compress_len;
538          const unsigned char *cbuf;
539          int r, real_compress_len;
540 #endif
541
542          if (rec->maskedStream == STREAM_SPARSE_COMPRESSED_DATA) {
543             ser_declare;
544             uint64_t faddr;
545             char ec1[50];
546             wbuf = rec->data + OFFSET_FADDR_SIZE;
547             wsize = rec->data_len - OFFSET_FADDR_SIZE;
548             ser_begin(rec->data, OFFSET_FADDR_SIZE);
549             unser_uint64(faddr);
550             if (fileAddr != faddr) {
551                fileAddr = faddr;
552                if (blseek(&bfd, (boffset_t)fileAddr, SEEK_SET) < 0) {
553                   berrno be;
554                   Emsg3(M_ERROR, 0, _("Seek to %s error on %s: ERR=%s\n"),
555                      edit_uint64(fileAddr, ec1), attr->ofname, be.bstrerror());
556                   extract = false;
557                   goto bail_out;
558                }
559             }
560          } else {
561             wbuf = rec->data;
562             wsize = rec->data_len;
563          }
564
565          /* read compress header */
566          unser_declare;
567          unser_begin(wbuf, sizeof(comp_stream_header));
568          unser_uint32(comp_magic);
569          unser_uint32(comp_len);
570          unser_uint16(comp_level);
571          unser_uint16(comp_version);
572          Dmsg4(200, "Compressed data stream found: magic=0x%x, len=%d, level=%d, ver=0x%x\n", comp_magic, comp_len,
573                                  comp_level, comp_version);
574
575          /* version check */
576          if (comp_version != COMP_HEAD_VERSION) {
577             Emsg1(M_ERROR, 0, _("Compressed header version error. version=0x%x\n"), comp_version);
578             ret = false;
579             goto bail_out;
580          }
581          /* size check */
582          if (comp_len + sizeof(comp_stream_header) != wsize) {
583             Emsg2(M_ERROR, 0, _("Compressed header size error. comp_len=%d, msglen=%d\n"),
584                  comp_len, wsize);
585             ret = false;
586             goto bail_out;
587          }
588
589           switch(comp_magic) {
590 #ifdef HAVE_LZO
591             case COMPRESS_LZO1X:
592                compress_len = compress_buf_size;
593                cbuf = (const unsigned char*) wbuf + sizeof(comp_stream_header);
594                real_compress_len = wsize - sizeof(comp_stream_header);
595                Dmsg2(200, "Comp_len=%d msglen=%d\n", compress_len, wsize);
596                while ((r=lzo1x_decompress_safe(cbuf, real_compress_len,
597                                                (unsigned char *)compress_buf, &compress_len, NULL)) == LZO_E_OUTPUT_OVERRUN)
598                {
599
600                   /* The buffer size is too small, try with a bigger one */
601                   compress_len = 2 * compress_len;
602                   compress_buf = check_pool_memory_size(compress_buf,
603                                                   compress_len);
604                }
605                if (r != LZO_E_OK) {
606                   Emsg1(M_ERROR, 0, _("LZO uncompression error. ERR=%d\n"), r);
607                   extract = false;
608                   goto bail_out;
609                }
610                Dmsg2(100, "Write uncompressed %d bytes, total before write=%d\n", compress_len, total);
611                store_data(&bfd, compress_buf, compress_len);
612                total += compress_len;
613                fileAddr += compress_len;
614                Dmsg2(100, "Compress len=%d uncompressed=%d\n", rec->data_len, compress_len);
615                break;
616 #endif
617             default:
618                Emsg1(M_ERROR, 0, _("Compression algorithm 0x%x found, but not supported!\n"), comp_magic);
619                extract = false;
620                goto bail_out;
621          }
622
623       }
624       break;
625
626    case STREAM_MD5_DIGEST:
627    case STREAM_SHA1_DIGEST:
628    case STREAM_SHA256_DIGEST:
629    case STREAM_SHA512_DIGEST:
630       break;
631
632    case STREAM_SIGNED_DIGEST:
633    case STREAM_ENCRYPTED_SESSION_DATA:
634       // TODO landonf: Investigate crypto support in the storage daemon
635       break;
636
637    case STREAM_PROGRAM_NAMES:
638    case STREAM_PROGRAM_DATA:
639       if (!prog_name_msg) {
640          Pmsg0(000, _("Got Program Name or Data Stream. Ignored.\n"));
641          prog_name_msg++;
642       }
643       break;
644
645    default:
646       /* If extracting, weird stream (not 1 or 2), close output file anyway */
647       if (extract) {
648          if (!is_bopen(&bfd)) {
649             Emsg0(M_ERROR, 0, _("Logic error output file should be open but is not.\n"));
650          }
651          set_attributes(jcr, attr, &bfd);
652          extract = false;
653       }
654       Jmsg(jcr, M_ERROR, 0, _("Unknown stream=%d ignored. This shouldn't happen!\n"),
655          rec->Stream);
656       break;
657
658    } /* end switch */
659 bail_out:
660    if (restoredatap) {
661       rec->data = orgdata;
662       rec->data_len = orgdata_len;
663    }
664    return ret;
665 }
666
667 /* Dummies to replace askdir.c */
668 bool    dir_find_next_appendable_volume(DCR *dcr) { return 1;}
669 bool    dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
670 bool    dir_create_jobmedia_record(DCR *dcr, bool zero) { return 1; }
671 bool    flush_jobmedia_queue(JCR *jcr) { return true; }
672 bool    dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
673 bool    dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
674 bool    dir_send_job_status(JCR *jcr) {return 1;}
675
676
677 bool dir_ask_sysop_to_mount_volume(DCR *dcr, bool /*writing*/)
678 {
679    DEVICE *dev = dcr->dev;
680    fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
681       dcr->VolumeName, dev->print_name());
682    dev->close();
683    getchar();
684    return true;
685 }
686
687 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw  writing)
688 {
689    Dmsg0(100, "Fake dir_get_volume_info\n");
690    dcr->setVolCatName(dcr->VolumeName);
691    Dmsg2(500, "Vol=%s VolType=%d\n", dcr->getVolCatName(), dcr->VolCatInfo.VolCatType);
692    return 1;
693 }