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