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