]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/bscan.c
kes Put attach_dcr_to_dev in a subroutine.
[bacula/bacula] / bacula / src / stored / bscan.c
1 /*
2  *
3  *  Program to scan a Bacula Volume and compare it with
4  *    the catalog and optionally synchronize the catalog
5  *    with the tape.
6  *
7  *   Kern E. Sibbald, December 2001
8  *
9  *
10  *   Version $Id$
11  */
12 /*
13    Copyright (C) 2001-2006 Kern Sibbald
14
15    This program is free software; you can redistribute it and/or
16    modify it under the terms of the GNU General Public License
17    version 2 as amended with additional clauses defined in the
18    file LICENSE in the main source directory.
19
20    This program is distributed in the hope that it will be useful,
21    but WITHOUT ANY WARRANTY; without even the implied warranty of
22    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
23    the file LICENSE for additional details.
24
25  */
26
27 #include "bacula.h"
28 #include "stored.h"
29 #include "findlib/find.h"
30 #include "cats/cats.h"
31  
32 /* Dummy functions */
33 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
34
35 /* Forward referenced functions */
36 static void do_scan(void);
37 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
38 static int  create_file_attributes_record(B_DB *db, JCR *mjcr,
39                                char *fname, char *lname, int type,
40                                char *ap, DEV_RECORD *rec);
41 static int  create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
42 static bool update_media_record(B_DB *db, MEDIA_DBR *mr);
43 static int  create_pool_record(B_DB *db, POOL_DBR *pr);
44 static JCR *create_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
45 static int  update_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
46                               DEV_RECORD *rec);
47 static int  create_client_record(B_DB *db, CLIENT_DBR *cr);
48 static int  create_fileset_record(B_DB *db, FILESET_DBR *fsr);
49 static int  create_jobmedia_record(B_DB *db, JCR *jcr);
50 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
51 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type);
52
53
54 /* Local variables */
55 static DEVICE *dev = NULL;
56 static B_DB *db;
57 static JCR *bjcr;                     /* jcr for bscan */
58 static BSR *bsr = NULL;
59 static MEDIA_DBR mr;
60 static POOL_DBR pr;
61 static JOB_DBR jr;
62 static CLIENT_DBR cr;
63 static FILESET_DBR fsr;
64 static ATTR_DBR ar;
65 static FILE_DBR fr;
66 static SESSION_LABEL label;
67 static SESSION_LABEL elabel;
68 static ATTR *attr;
69
70 static time_t lasttime = 0;
71
72 static const char *db_name = "bacula";
73 static const char *db_user = "bacula";
74 static const char *db_password = "";
75 static const char *db_host = NULL;
76 static const char *wd = NULL;
77 static bool update_db = false;
78 static bool update_vol_info = false;
79 static bool list_records = false;
80 static int ignored_msgs = 0;
81
82 static uint64_t currentVolumeSize;
83 static int last_pct = -1;
84 static bool showProgress = false;
85 static int num_jobs = 0;
86 static int num_pools = 0;
87 static int num_media = 0;
88 static int num_files = 0;
89
90 #define CONFIG_FILE "bacula-sd.conf"
91 char *configfile = NULL;
92 STORES *me = NULL;                    /* our Global resource */
93 bool forge_on = false;                /* proceed inspite of I/O errors */
94 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
95 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
96
97
98 static void usage()
99 {
100    fprintf(stderr, _(
101 "Copyright (C) 2001-%s Kern Sibbald.\n"
102 "\nVersion: %s (%s)\n\n"
103 "Usage: bscan [ options ] <bacula-archive>\n"
104 "       -b bootstrap      specify a bootstrap file\n"
105 "       -c <file>         specify configuration file\n"
106 "       -d <nn>           set debug level to nn\n"
107 "       -m                update media info in database\n"
108 "       -n <name>         specify the database name (default bacula)\n"
109 "       -u <user>         specify database user name (default bacula)\n"
110 "       -P <password      specify database password (default none)\n"
111 "       -h <host>         specify database host (default NULL)\n"
112 "       -p                proceed inspite of I/O errors\n"
113 "       -r                list records\n"
114 "       -s                synchronize or store in database\n"
115 "       -S                show scan progress periodically\n"
116 "       -v                verbose\n"
117 "       -V <Volumes>      specify Volume names (separated by |)\n"
118 "       -w <dir>          specify working directory (default from conf file)\n"
119 "       -?                print this message\n\n"), BYEAR, VERSION, BDATE);
120    exit(1);
121 }
122
123 int main (int argc, char *argv[])
124 {
125    int ch;
126    struct stat stat_buf;
127    char *VolumeName = NULL;
128
129    setlocale(LC_ALL, "");
130    bindtextdomain("bacula", LOCALEDIR);
131    textdomain("bacula");
132    init_stack_dump();
133
134    my_name_is(argc, argv, "bscan");
135    init_msg(NULL, NULL);
136
137
138    while ((ch = getopt(argc, argv, "b:c:d:h:mn:pP:rsSu:vV:w:?")) != -1) {
139       switch (ch) {
140       case 'S' :
141          showProgress = true;
142          break;
143       case 'b':
144          bsr = parse_bsr(NULL, optarg);
145          break;
146
147       case 'c':                    /* specify config file */
148          if (configfile != NULL) {
149             free(configfile);
150          }
151          configfile = bstrdup(optarg);
152          break;
153
154       case 'd':                    /* debug level */
155          debug_level = atoi(optarg);
156          if (debug_level <= 0)
157             debug_level = 1;
158          break;
159
160       case 'h':
161          db_host = optarg;
162          break;
163
164       case 'm':
165          update_vol_info = true;
166          break;
167
168       case 'n':
169          db_name = optarg;
170          break;
171
172       case 'u':
173          db_user = optarg;
174          break;
175
176       case 'P':
177          db_password = optarg;
178          break;
179
180       case 'p':
181          forge_on = true;
182          break;
183
184       case 'r':
185          list_records = true;
186          break;
187
188       case 's':
189          update_db = true;
190          break;
191
192       case 'v':
193          verbose++;
194          break;
195
196       case 'V':                    /* Volume name */
197          VolumeName = optarg;
198          break;
199
200       case 'w':
201          wd = optarg;
202          break;
203
204       case '?':
205       default:
206          usage();
207
208       }
209    }
210    argc -= optind;
211    argv += optind;
212
213    if (argc != 1) {
214       Pmsg0(0, _("Wrong number of arguments: \n"));
215       usage();
216    }
217
218    if (configfile == NULL) {
219       configfile = bstrdup(CONFIG_FILE);
220    }
221
222    parse_config(configfile);
223    LockRes();
224    me = (STORES *)GetNextRes(R_STORAGE, NULL);
225    if (!me) {
226       UnlockRes();
227       Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
228          configfile);
229    }
230    UnlockRes();
231    /* Check if -w option given, otherwise use resource for working directory */
232    if (wd) {
233       working_directory = wd;
234    } else if (!me->working_directory) {
235       Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
236          configfile);
237    } else {
238       working_directory = me->working_directory;
239    }
240
241    /* Check that working directory is good */
242    if (stat(working_directory, &stat_buf) != 0) {
243       Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
244          working_directory);
245    }
246    if (!S_ISDIR(stat_buf.st_mode)) {
247       Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
248          working_directory);
249    }
250
251    bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
252    if (!bjcr) {
253       exit(1);
254    }
255    dev = bjcr->read_dcr->dev;
256    if (showProgress) {
257       char ed1[50];
258       struct stat sb;
259       fstat(dev->fd, &sb);
260       currentVolumeSize = sb.st_size;
261       Pmsg1(000, _("First Volume Size = %sn"), 
262          edit_uint64(currentVolumeSize, ed1));
263    }
264
265    if ((db=db_init_database(NULL, db_name, db_user, db_password,
266         db_host, 0, NULL, 0)) == NULL) {
267       Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
268    }
269    if (!db_open_database(NULL, db)) {
270       Emsg0(M_ERROR_TERM, 0, db_strerror(db));
271    }
272    Dmsg0(200, "Database opened\n");
273    if (verbose) {
274       Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
275    }
276
277    do_scan();
278    if (update_db) {
279       printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
280          num_media, num_pools, num_jobs, num_files);
281    }
282    else {
283       printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
284          num_media, num_pools, num_jobs, num_files);
285    }
286
287    free_jcr(bjcr);
288    dev->term();
289    return 0;
290 }
291
292 /*
293  * We are at the end of reading a tape. Now, we simulate handling
294  *   the end of writing a tape by wiffling through the attached
295  *   jcrs creating jobmedia records.
296  */
297 static bool bscan_mount_next_read_volume(DCR *dcr)
298 {
299    DEVICE *dev = dcr->dev;
300    DCR *mdcr;
301    Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
302    foreach_dlist(mdcr, dev->attached_dcrs) {
303       JCR *mjcr = mdcr->jcr;
304       if (mjcr->JobId == 0) {
305          continue;
306       }
307       if (verbose) {
308          Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
309       }
310       if (dev->is_tape()) {
311          mdcr->EndBlock = dcr->EndBlock;
312          mdcr->EndFile = dcr->EndFile;
313 //    } else {
314 //       mdcr->EndBlock = (uint32_t)dcr->file_addr;
315 //       mdcr->EndFile = (uint32_t)(dcr->file_addr >> 32);
316       }
317       mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
318       if (!create_jobmedia_record(db, mjcr)) {
319          Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
320             dev->VolCatInfo.VolCatName, mjcr->Job);
321       }
322    }
323    /* Now let common read routine get up next tape. Note,
324     * we call mount_next... with bscan's jcr because that is where we
325     * have the Volume list, but we get attached.
326     */
327    bool stat = mount_next_read_volume(dcr);
328
329    if (showProgress) {
330       char ed1[50];
331       struct stat sb;
332       fstat(dev->fd, &sb);
333       currentVolumeSize = sb.st_size;
334       Pmsg1(000, _("First Volume Size = %sn"), 
335          edit_uint64(currentVolumeSize, ed1));
336    }
337    return stat;
338 }
339
340 static void do_scan()
341 {
342    attr = new_attr();
343
344    memset(&ar, 0, sizeof(ar));
345    memset(&pr, 0, sizeof(pr));
346    memset(&jr, 0, sizeof(jr));
347    memset(&cr, 0, sizeof(cr));
348    memset(&fsr, 0, sizeof(fsr));
349    memset(&fr, 0, sizeof(fr));
350
351    /* Detach bscan's jcr as we are not a real Job on the tape */
352
353    read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
354
355    free_attr(attr);
356 }
357
358 /*
359  * Returns: true  if OK
360  *          false if error
361  */
362 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
363 {
364    JCR *mjcr;
365    char ec1[30];
366    DEVICE *dev = dcr->dev;
367    JCR *bjcr = dcr->jcr;
368    DEV_BLOCK *block = dcr->block;
369    char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
370
371    if (rec->data_len > 0) {
372       mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
373       if (showProgress && currentVolumeSize > 0) {
374          int pct = (mr.VolBytes * 100) / currentVolumeSize;
375          if (pct != last_pct) {
376             fprintf(stdout, _("done: %d%%\n"), pct);
377             fflush(stdout);
378             last_pct = pct;
379          }
380       }
381    }
382
383    if (list_records) {
384       Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
385             rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
386             rec->Stream, rec->data_len);
387    }
388    /*
389     * Check for Start or End of Session Record
390     *
391     */
392    if (rec->FileIndex < 0) {
393       bool save_update_db = update_db;
394
395       if (verbose > 1) {
396          dump_label_record(dev, rec, 1);
397       }
398       switch (rec->FileIndex) {
399       case PRE_LABEL:
400          Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
401          return false;
402          break;
403
404       case VOL_LABEL:
405          unser_volume_label(dev, rec);
406          /* Check Pool info */
407          bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
408          bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
409          num_pools++;
410          if (db_get_pool_record(bjcr, db, &pr)) {
411             if (verbose) {
412                Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
413             }
414          } else {
415             if (!update_db) {
416                Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
417                   pr.Name);
418             }
419             create_pool_record(db, &pr);
420          }
421          if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
422             Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
423                pr.PoolType, dev->VolHdr.PoolType);
424             return true;
425          } else if (verbose) {
426             Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
427          }
428
429          /* Check Media Info */
430          memset(&mr, 0, sizeof(mr));
431          bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
432          mr.PoolId = pr.PoolId;
433          num_media++;
434          if (db_get_media_record(bjcr, db, &mr)) {
435             if (verbose) {
436                Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
437             }
438             /* Clear out some volume statistics that will be updated */
439             mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
440             mr.VolBytes = rec->data_len + 20;
441          } else {
442             if (!update_db) {
443                Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
444                   mr.VolumeName);
445             }
446             bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
447             create_media_record(db, &mr, &dev->VolHdr);
448          }
449          if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
450             Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
451                mr.MediaType, dev->VolHdr.MediaType);
452             return true;              /* ignore error */
453          } else if (verbose) {
454             Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
455          }
456          /* Reset some DCR variables */
457          foreach_dlist(dcr, dev->attached_dcrs) {
458             dcr->VolFirstIndex = dcr->FileIndex = 0;
459             dcr->StartBlock = dcr->EndBlock = 0;
460             dcr->StartFile = dcr->EndFile = 0;
461          }
462
463          Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
464          break;
465
466       case SOS_LABEL:
467          mr.VolJobs++;
468          num_jobs++;
469          if (ignored_msgs > 0) {
470             Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
471                   ignored_msgs);
472             ignored_msgs = 0;
473          }
474          unser_session_label(&label, rec);
475          memset(&jr, 0, sizeof(jr));
476          bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
477          if (db_get_job_record(bjcr, db, &jr)) {
478             /* Job record already exists in DB */
479             update_db = false;  /* don't change db in create_job_record */
480             if (verbose) {
481                Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
482             }
483          } else {
484             /* Must create a Job record in DB */
485             if (!update_db) {
486                Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
487                   jr.JobId);
488             }
489          }
490          /* Create Client record if not already there */
491             bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
492             create_client_record(db, &cr);
493             jr.ClientId = cr.ClientId;
494
495          /* process label, if Job record exists don't update db */
496          mjcr = create_job_record(db, &jr, &label, rec);
497          dcr = mjcr->read_dcr;
498          update_db = save_update_db;
499
500          jr.PoolId = pr.PoolId;
501 #ifdef xxx
502          /* Set start positions into JCR */
503          if (dev->is_tape()) {
504             /*
505              * Note, we have already advanced past current block,
506              *  so the correct number is block_num - 1
507              */
508             dcr->StartBlock = dev->block_num - 1;
509             dcr->StartFile = dev->file;
510          } else {
511             dcr->StartBlock = (uint32_t)dev->file_addr;
512             dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
513          }
514 #endif
515          mjcr->start_time = jr.StartTime;
516          mjcr->JobLevel = jr.JobLevel;
517
518          mjcr->client_name = get_pool_memory(PM_FNAME);
519          pm_strcpy(mjcr->client_name, label.ClientName);
520          mjcr->fileset_name = get_pool_memory(PM_FNAME);
521          pm_strcpy(mjcr->fileset_name, label.FileSetName);
522          bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
523          bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
524
525          if (rec->VolSessionId != jr.VolSessionId) {
526             Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
527                jr.JobId,
528                jr.VolSessionId, rec->VolSessionId);
529             return true;              /* ignore error */
530          }
531          if (rec->VolSessionTime != jr.VolSessionTime) {
532             Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
533                jr.JobId,
534                jr.VolSessionTime, rec->VolSessionTime);
535             return true;              /* ignore error */
536          }
537          if (jr.PoolId != pr.PoolId) {
538             Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
539                jr.JobId,
540                jr.PoolId, pr.PoolId);
541             return true;              /* ignore error */
542          }
543          break;
544
545       case EOS_LABEL:
546          unser_session_label(&elabel, rec);
547
548          /* Create FileSet record */
549          bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
550          bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
551          create_fileset_record(db, &fsr);
552          jr.FileSetId = fsr.FileSetId;
553
554          mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
555          if (!mjcr) {
556             Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
557                   rec->VolSessionId, rec->VolSessionTime);
558             break;
559          }
560
561          /* Do the final update to the Job record */
562          update_job_record(db, &jr, &elabel, rec);
563
564          mjcr->end_time = jr.EndTime;
565          mjcr->JobStatus = JS_Terminated;
566
567          /* Create JobMedia record */
568          mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
569          create_jobmedia_record(db, mjcr);
570          detach_dcr_from_dev(mjcr->read_dcr);
571          free_jcr(mjcr);
572
573          break;
574
575       case EOM_LABEL:
576          break;
577
578       case EOT_LABEL:              /* end of all tapes */
579          /*
580           * Wiffle through all jobs still open and close
581           *   them.
582           */
583          if (update_db) {
584             DCR *mdcr;
585             foreach_dlist(mdcr, dev->attached_dcrs) {
586                JCR *mjcr = mdcr->jcr;
587                if (!mjcr || mjcr->JobId == 0) {
588                   continue;
589                }
590                jr.JobId = mjcr->JobId;
591                /* Mark Job as Error Terimined */
592                jr.JobStatus = JS_ErrorTerminated;
593                jr.JobFiles = mjcr->JobFiles;
594                jr.JobBytes = mjcr->JobBytes;
595                jr.VolSessionId = mjcr->VolSessionId;
596                jr.VolSessionTime = mjcr->VolSessionTime;
597                jr.JobTDate = (utime_t)mjcr->start_time;
598                jr.ClientId = mjcr->ClientId;
599                if (!db_update_job_end_record(bjcr, db, &jr)) {
600                   Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
601                }
602                mjcr->read_dcr = NULL;
603                free_jcr(mjcr);
604             }
605          }
606          mr.VolFiles = rec->File;
607          mr.VolBlocks = rec->Block;
608          mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
609          mr.VolMounts++;
610          update_media_record(db, &mr);
611          Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
612                     mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
613          break;
614       default:
615          break;
616       } /* end switch */
617       return true;
618    }
619
620    mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
621    if (!mjcr) {
622       if (mr.VolJobs > 0) {
623          Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
624                       rec->VolSessionId, rec->VolSessionTime);
625       } else {
626          ignored_msgs++;
627       }
628       return true;
629    }
630    dcr = mjcr->read_dcr;
631    if (dcr->VolFirstIndex == 0) {
632       dcr->VolFirstIndex = block->FirstIndex;
633    }
634
635    /* File Attributes stream */
636    switch (rec->Stream) {
637    case STREAM_UNIX_ATTRIBUTES:
638    case STREAM_UNIX_ATTRIBUTES_EX:
639
640       if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
641          Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
642       }
643
644       if (attr->file_index != rec->FileIndex) {
645          Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
646             rec->FileIndex, attr->file_index);
647       }
648
649       if (verbose > 1) {
650          decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
651          build_attr_output_fnames(bjcr, attr);
652          print_ls_output(bjcr, attr);
653       }
654       fr.JobId = mjcr->JobId;
655       fr.FileId = 0;
656       num_files++;
657       if (verbose && (num_files & 0x7FFF) == 0) {
658          char ed1[30], ed2[30], ed3[30], ed4[30];
659          Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
660                      edit_uint64_with_commas(num_files, ed1),
661                      edit_uint64_with_commas(rec->File, ed2),
662                      edit_uint64_with_commas(rec->Block, ed3),
663                      edit_uint64_with_commas(mr.VolBytes, ed4));
664       }
665       create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
666             attr->type, attr->attr, rec);
667       free_jcr(mjcr);
668       break;
669
670    /* Data stream */
671    case STREAM_WIN32_DATA:
672    case STREAM_FILE_DATA:
673    case STREAM_SPARSE_DATA:
674    case STREAM_ENCRYPTED_FILE_DATA:
675    case STREAM_ENCRYPTED_WIN32_DATA:
676    case STREAM_ENCRYPTED_MACOS_FORK_DATA:
677       /*
678        * For encrypted stream, this is an approximation.
679        * The data must be decrypted to know the correct length.
680        */
681       mjcr->JobBytes += rec->data_len;
682       if (rec->Stream == STREAM_SPARSE_DATA) {
683          mjcr->JobBytes -= sizeof(uint64_t);
684       }
685
686       free_jcr(mjcr);                 /* done using JCR */
687       break;
688
689    case STREAM_GZIP_DATA:
690    case STREAM_ENCRYPTED_FILE_GZIP_DATA:
691    case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
692       /* No correct, we should (decrypt and) expand it 
693          done using JCR 
694       */
695       mjcr->JobBytes += rec->data_len;
696       free_jcr(mjcr);                 
697       break;
698
699    case STREAM_SPARSE_GZIP_DATA:
700       mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
701       free_jcr(mjcr);                 /* done using JCR */
702       break;
703
704    /* Win32 GZIP stream */
705    case STREAM_WIN32_GZIP_DATA:
706       mjcr->JobBytes += rec->data_len;
707       free_jcr(mjcr);                 /* done using JCR */
708       break;
709
710    case STREAM_MD5_DIGEST:
711       bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
712       if (verbose > 1) {
713          Pmsg1(000, _("Got MD5 record: %s\n"), digest);
714       }
715       update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
716       break;
717
718    case STREAM_SHA1_DIGEST:
719       bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
720       if (verbose > 1) {
721          Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
722       }
723       update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
724       break;
725
726    case STREAM_SHA256_DIGEST:
727       bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
728       if (verbose > 1) {
729          Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
730       }
731       update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
732       break;
733
734    case STREAM_SHA512_DIGEST:
735       bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
736       if (verbose > 1) {
737          Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
738       }
739       update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
740       break;
741
742    case STREAM_ENCRYPTED_SESSION_DATA:
743       // TODO landonf: Investigate crypto support in bscan
744       if (verbose > 1) {
745          Pmsg0(000, _("Got signed digest record\n"));
746       }
747       break;
748
749    case STREAM_SIGNED_DIGEST:
750       // TODO landonf: Investigate crypto support in bscan
751       if (verbose > 1) {
752          Pmsg0(000, _("Got signed digest record\n"));
753       }
754       break;
755
756    case STREAM_PROGRAM_NAMES:
757       if (verbose) {
758          Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
759       }
760       break;
761
762    case STREAM_PROGRAM_DATA:
763       if (verbose > 1) {
764          Pmsg0(000, _("Got Prog Data Stream record.\n"));
765       }
766       break;
767
768    case STREAM_UNIX_ATTRIBUTES_ACCESS_ACL:   /* Standard ACL attributes on UNIX */
769    case STREAM_UNIX_ATTRIBUTES_DEFAULT_ACL:  /* Default ACL attributes on UNIX */
770       /* Ignore Unix attributes */
771       break;
772
773    default:
774       Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
775       break;
776    }
777    return true;
778 }
779
780 /*
781  * Free the Job Control Record if no one is still using it.
782  *  Called from main free_jcr() routine in src/lib/jcr.c so
783  *  that we can do our Director specific cleanup of the jcr.
784  */
785 static void bscan_free_jcr(JCR *jcr)
786 {
787    Dmsg0(200, "Start bscan free_jcr\n");
788
789    if (jcr->file_bsock) {
790       Dmsg0(200, "Close File bsock\n");
791       bnet_close(jcr->file_bsock);
792    }
793    if (jcr->store_bsock) {
794       Dmsg0(200, "Close Store bsock\n");
795       bnet_close(jcr->store_bsock);
796    }
797    if (jcr->RestoreBootstrap) {
798       free(jcr->RestoreBootstrap);
799    }
800    if (jcr->dcr) {
801       free_dcr(jcr->dcr);
802       jcr->dcr = NULL;
803    }
804    if (jcr->read_dcr) {
805       free_dcr(jcr->read_dcr);
806       jcr->read_dcr = NULL;
807    }
808    Dmsg0(200, "End bscan free_jcr\n");
809 }
810
811 /*
812  * We got a File Attributes record on the tape.  Now, lookup the Job
813  *   record, and then create the attributes record.
814  */
815 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
816                                char *fname, char *lname, int type,
817                                char *ap, DEV_RECORD *rec)
818 {
819    DCR *dcr = mjcr->read_dcr;
820    ar.fname = fname;
821    ar.link = lname;
822    ar.ClientId = mjcr->ClientId;
823    ar.JobId = mjcr->JobId;
824    ar.Stream = rec->Stream;
825    ar.FileIndex = rec->FileIndex;
826    ar.attr = ap;
827    if (dcr->VolFirstIndex == 0) {
828       dcr->VolFirstIndex = rec->FileIndex;
829    }
830    dcr->FileIndex = rec->FileIndex;
831    mjcr->JobFiles++;
832
833    if (!update_db) {
834       return 1;
835    }
836
837    if (!db_create_file_attributes_record(bjcr, db, &ar)) {
838       Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
839       return 0;
840    }
841    mjcr->FileId = ar.FileId;
842
843    if (verbose > 1) {
844       Pmsg1(000, _("Created File record: %s\n"), fname);
845    }
846    return 1;
847 }
848
849 /*
850  * For each Volume we see, we create a Medium record
851  */
852 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
853 {
854    struct date_time dt;
855    struct tm tm;
856
857    /* We mark Vols as Archive to keep them from being re-written */
858    bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
859    mr->VolRetention = 365 * 3600 * 24; /* 1 year */
860    mr->Enabled = 1;
861    if (vl->VerNum >= 11) {
862       mr->FirstWritten = btime_to_utime(vl->write_btime);
863       mr->LabelDate    = btime_to_utime(vl->label_btime);
864    } else {
865       /* DEPRECATED DO NOT USE */
866       dt.julian_day_number = vl->write_date;
867       dt.julian_day_fraction = vl->write_time;
868       tm_decode(&dt, &tm);
869       mr->FirstWritten = mktime(&tm);
870       dt.julian_day_number = vl->label_date;
871       dt.julian_day_fraction = vl->label_time;
872       tm_decode(&dt, &tm);
873       mr->LabelDate = mktime(&tm);
874    }
875    lasttime = mr->LabelDate;
876
877    if (!update_db) {
878       return 1;
879    }
880
881    if (!db_create_media_record(bjcr, db, mr)) {
882       Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
883       return 0;
884    }
885    if (!db_update_media_record(bjcr, db, mr)) {
886       Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
887       return 0;
888    }
889    if (verbose) {
890       Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
891    }
892    return 1;
893
894 }
895
896 /*
897  * Called at end of media to update it
898  */
899 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
900 {
901    if (!update_db && !update_vol_info) {
902       return true;
903    }
904
905    mr->LastWritten = lasttime;
906    if (!db_update_media_record(bjcr, db, mr)) {
907       Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
908       return false;;
909    }
910    if (verbose) {
911       Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
912    }
913    return true;
914
915 }
916
917
918 static int create_pool_record(B_DB *db, POOL_DBR *pr)
919 {
920    pr->NumVols++;
921    pr->UseCatalog = 1;
922    pr->VolRetention = 355 * 3600 * 24; /* 1 year */
923
924    if (!update_db) {
925       return 1;
926    }
927    if (!db_create_pool_record(bjcr, db, pr)) {
928       Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
929       return 0;
930    }
931    if (verbose) {
932       Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
933    }
934    return 1;
935
936 }
937
938
939 /*
940  * Called from SOS to create a client for the current Job
941  */
942 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
943 {
944    if (!update_db) {
945       return 1;
946    }
947    if (!db_create_client_record(bjcr, db, cr)) {
948       Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
949       return 0;
950    }
951    if (verbose) {
952       Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
953    }
954    return 1;
955 }
956
957 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
958 {
959    if (!update_db) {
960       return 1;
961    }
962    fsr->FileSetId = 0;
963    if (fsr->MD5[0] == 0) {
964       fsr->MD5[0] = ' ';              /* Equivalent to nothing */
965       fsr->MD5[1] = 0;
966    }
967    if (db_get_fileset_record(bjcr, db, fsr)) {
968       if (verbose) {
969          Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
970       }
971    } else {
972       if (!db_create_fileset_record(bjcr, db, fsr)) {
973          Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
974             fsr->FileSet, db_strerror(db));
975          return 0;
976       }
977       if (verbose) {
978          Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
979       }
980    }
981    return 1;
982 }
983
984 /*
985  * Simulate the two calls on the database to create
986  *  the Job record and to update it when the Job actually
987  *  begins running.
988  */
989 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
990                              DEV_RECORD *rec)
991 {
992    JCR *mjcr;
993    struct date_time dt;
994    struct tm tm;
995
996    jr->JobId = label->JobId;
997    jr->JobType = label->JobType;
998    jr->JobLevel = label->JobLevel;
999    jr->JobStatus = JS_Created;
1000    bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1001    bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1002    if (label->VerNum >= 11) {
1003       jr->SchedTime = btime_to_unix(label->write_btime);
1004    } else {
1005       dt.julian_day_number = label->write_date;
1006       dt.julian_day_fraction = label->write_time;
1007       tm_decode(&dt, &tm);
1008       jr->SchedTime = mktime(&tm);
1009    }
1010
1011    jr->StartTime = jr->SchedTime;
1012    jr->JobTDate = (utime_t)jr->SchedTime;
1013    jr->VolSessionId = rec->VolSessionId;
1014    jr->VolSessionTime = rec->VolSessionTime;
1015
1016    /* Now create a JCR as if starting the Job */
1017    mjcr = create_jcr(jr, rec, label->JobId);
1018
1019    if (!update_db) {
1020       return mjcr;
1021    }
1022
1023    /* This creates the bare essentials */
1024    if (!db_create_job_record(bjcr, db, jr)) {
1025       Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1026       return mjcr;
1027    }
1028
1029    /* This adds the client, StartTime, JobTDate, ... */
1030    if (!db_update_job_start_record(bjcr, db, jr)) {
1031       Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1032       return mjcr;
1033    }
1034    Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1035          label->JobId);
1036    mjcr->JobId = jr->JobId;           /* set new JobId */
1037    return mjcr;
1038 }
1039
1040 /*
1041  * Simulate the database call that updates the Job
1042  *  at Job termination time.
1043  */
1044 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1045                               DEV_RECORD *rec)
1046 {
1047    struct date_time dt;
1048    struct tm tm;
1049    JCR *mjcr;
1050
1051    mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1052    if (!mjcr) {
1053       Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1054                    rec->VolSessionId, rec->VolSessionTime);
1055       return 0;
1056    }
1057    if (elabel->VerNum >= 11) {
1058       jr->EndTime = btime_to_unix(elabel->write_btime);
1059    } else {
1060       dt.julian_day_number = elabel->write_date;
1061       dt.julian_day_fraction = elabel->write_time;
1062       tm_decode(&dt, &tm);
1063       jr->EndTime = mktime(&tm);
1064    }
1065    lasttime = jr->EndTime;
1066    mjcr->end_time = jr->EndTime;
1067
1068    jr->JobId = mjcr->JobId;
1069    jr->JobStatus = elabel->JobStatus;
1070    mjcr->JobStatus = elabel->JobStatus;
1071    jr->JobFiles = elabel->JobFiles;
1072    jr->JobBytes = elabel->JobBytes;
1073    jr->VolSessionId = rec->VolSessionId;
1074    jr->VolSessionTime = rec->VolSessionTime;
1075    jr->JobTDate = (utime_t)mjcr->start_time;
1076    jr->ClientId = mjcr->ClientId;
1077
1078    if (!update_db) {
1079       free_jcr(mjcr);
1080       return 1;
1081    }
1082
1083    if (!db_update_job_end_record(bjcr, db, jr)) {
1084       Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId,  db_strerror(db));
1085       free_jcr(mjcr);
1086       return 0;
1087    }
1088    if (verbose) {
1089       Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"), 
1090          jr->JobId, job_level_to_str(mjcr->JobLevel), jr->JobStatus);
1091    }
1092    if (verbose > 1) {
1093       const char *term_msg;
1094       static char term_code[70];
1095       char sdt[50], edt[50];
1096       char ec1[30], ec2[30], ec3[30];
1097
1098       switch (mjcr->JobStatus) {
1099       case JS_Terminated:
1100          term_msg = _("Backup OK");
1101          break;
1102       case JS_FatalError:
1103       case JS_ErrorTerminated:
1104          term_msg = _("*** Backup Error ***");
1105          break;
1106       case JS_Canceled:
1107          term_msg = _("Backup Canceled");
1108          break;
1109       default:
1110          term_msg = term_code;
1111          sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1112          break;
1113       }
1114       bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1115       bstrftime(edt, sizeof(edt), mjcr->end_time);
1116       Pmsg14(000,  _("%s\n"
1117 "JobId:                  %d\n"
1118 "Job:                    %s\n"
1119 "FileSet:                %s\n"
1120 "Backup Level:           %s\n"
1121 "Client:                 %s\n"
1122 "Start time:             %s\n"
1123 "End time:               %s\n"
1124 "Files Written:          %s\n"
1125 "Bytes Written:          %s\n"
1126 "Volume Session Id:      %d\n"
1127 "Volume Session Time:    %d\n"
1128 "Last Volume Bytes:      %s\n"
1129 "Termination:            %s\n\n"),
1130         edt,
1131         mjcr->JobId,
1132         mjcr->Job,
1133         mjcr->fileset_name,
1134         job_level_to_str(mjcr->JobLevel),
1135         mjcr->client_name,
1136         sdt,
1137         edt,
1138         edit_uint64_with_commas(mjcr->JobFiles, ec1),
1139         edit_uint64_with_commas(mjcr->JobBytes, ec2),
1140         mjcr->VolSessionId,
1141         mjcr->VolSessionTime,
1142         edit_uint64_with_commas(mr.VolBytes, ec3),
1143         term_msg);
1144    }
1145    free_jcr(mjcr);
1146    return 1;
1147 }
1148
1149 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1150 {
1151    JOBMEDIA_DBR jmr;
1152    DCR *dcr = mjcr->read_dcr;
1153
1154    if (dev->is_tape()) {
1155       dcr->EndBlock = dev->EndBlock;
1156       dcr->EndFile  = dev->EndFile;
1157 #ifdef needed
1158    } else {
1159       dcr->EndBlock = (uint32_t)dev->file_addr;
1160       dcr->EndFile = (uint32_t)(dev->file_addr >> 32);
1161 #endif
1162    } 
1163
1164    memset(&jmr, 0, sizeof(jmr));
1165    jmr.JobId = mjcr->JobId;
1166    jmr.MediaId = mr.MediaId;
1167    jmr.FirstIndex = dcr->VolFirstIndex;
1168    jmr.LastIndex = dcr->VolLastIndex;
1169    jmr.StartFile = dcr->StartFile;
1170    jmr.EndFile = dcr->EndFile;
1171    jmr.StartBlock = dcr->StartBlock;
1172    jmr.EndBlock = dcr->EndBlock;
1173
1174
1175    if (!update_db) {
1176       return 1;
1177    }
1178
1179    if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1180       Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1181       return 0;
1182    }
1183    if (verbose) {
1184       Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1185                 jmr.JobId, jmr.MediaId);
1186    }
1187    return 1;
1188 }
1189
1190 /*
1191  * Simulate the database call that updates the MD5/SHA1 record
1192  */
1193 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1194 {
1195    JCR *mjcr;
1196
1197    mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1198    if (!mjcr) {
1199       if (mr.VolJobs > 0) {
1200          Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1201                       rec->VolSessionId, rec->VolSessionTime);
1202       } else {
1203          ignored_msgs++;
1204       }
1205       return 0;
1206    }
1207
1208    if (!update_db || mjcr->FileId == 0) {
1209       free_jcr(mjcr);
1210       return 1;
1211    }
1212
1213    if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1214       Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1215       free_jcr(mjcr);
1216       return 0;
1217    }
1218    if (verbose > 1) {
1219       Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1220    }
1221    free_jcr(mjcr);
1222    return 1;
1223 }
1224
1225
1226 /*
1227  * Create a JCR as if we are really starting the job
1228  */
1229 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1230 {
1231    JCR *jobjcr;
1232    /*
1233     * Transfer as much as possible to the Job JCR. Most important is
1234     *   the JobId and the ClientId.
1235     */
1236    jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1237    jobjcr->JobType = jr->JobType;
1238    jobjcr->JobLevel = jr->JobLevel;
1239    jobjcr->JobStatus = jr->JobStatus;
1240    bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1241    jobjcr->JobId = JobId;      /* this is JobId on tape */
1242    jobjcr->sched_time = jr->SchedTime;
1243    jobjcr->start_time = jr->StartTime;
1244    jobjcr->VolSessionId = rec->VolSessionId;
1245    jobjcr->VolSessionTime = rec->VolSessionTime;
1246    jobjcr->ClientId = jr->ClientId;
1247    jobjcr->read_dcr = new_dcr(jobjcr, dev);
1248
1249    return jobjcr;
1250 }
1251
1252 /* Dummies to replace askdir.c */
1253 bool    dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1254 bool    dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
1255 bool    dir_create_jobmedia_record(DCR *dcr) { return 1; }
1256 bool    dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1257 bool    dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1258 bool    dir_send_job_status(JCR *jcr) {return 1;}
1259 int     generate_job_event(JCR *jcr, const char *event) { return 1; }
1260
1261 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
1262 {
1263    DEVICE *dev = dcr->dev;
1264    Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1265    /* Close device so user can use autochanger if desired */
1266    fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1267          dcr->VolumeName, dev->print_name());
1268    dev->close();
1269    getchar();
1270    return true;
1271 }
1272
1273 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw  writing)
1274 {
1275    Dmsg0(100, "Fake dir_get_volume_info\n");
1276    bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
1277    dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1278    Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);
1279    return 1;
1280 }