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