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