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