]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/bscan.c
kes Fix bug #935, and probably also bug #903 where files were not
[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 "       -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       Dmsg1(000, "========== JobId=%u ========\n", mjcr->JobId);
319       if (mjcr->JobId == 0) {
320          continue;
321       }
322       if (verbose) {
323          Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
324       }
325       mdcr->StartBlock = dcr->StartBlock;
326       mdcr->StartFile = dcr->StartFile;
327       mdcr->EndBlock = dcr->EndBlock;
328       mdcr->EndFile = dcr->EndFile;
329       mdcr->VolMediaId = dcr->VolMediaId;
330       mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
331       if (!create_jobmedia_record(db, mjcr)) {
332          Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
333             dev->VolCatInfo.VolCatName, mjcr->Job);
334       }
335    }
336
337    update_media_record(db, &mr);
338
339    /* Now let common read routine get up next tape. Note,
340     * we call mount_next... with bscan's jcr because that is where we
341     * have the Volume list, but we get attached.
342     */
343    bool stat = mount_next_read_volume(dcr);
344
345    if (showProgress) {
346       char ed1[50];
347       struct stat sb;
348       fstat(dev->fd(), &sb);
349       currentVolumeSize = sb.st_size;
350       Pmsg1(000, _("First Volume Size = %sn"), 
351          edit_uint64(currentVolumeSize, ed1));
352    }
353    return stat;
354 }
355
356 static void do_scan()
357 {
358    attr = new_attr();
359
360    memset(&ar, 0, sizeof(ar));
361    memset(&pr, 0, sizeof(pr));
362    memset(&jr, 0, sizeof(jr));
363    memset(&cr, 0, sizeof(cr));
364    memset(&fsr, 0, sizeof(fsr));
365    memset(&fr, 0, sizeof(fr));
366
367    /* Detach bscan's jcr as we are not a real Job on the tape */
368
369    read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
370
371    if (update_db) {
372       db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
373    }
374    free_attr(attr);
375 }
376
377 /*
378  * Returns: true  if OK
379  *          false if error
380  */
381 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
382 {
383    JCR *mjcr;
384    char ec1[30];
385    DEVICE *dev = dcr->dev;
386    JCR *bjcr = dcr->jcr;
387    DEV_BLOCK *block = dcr->block;
388    char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
389
390    if (rec->data_len > 0) {
391       mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
392       if (showProgress && currentVolumeSize > 0) {
393          int pct = (mr.VolBytes * 100) / currentVolumeSize;
394          if (pct != last_pct) {
395             fprintf(stdout, _("done: %d%%\n"), pct);
396             fflush(stdout);
397             last_pct = pct;
398          }
399       }
400    }
401
402    if (list_records) {
403       Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
404             rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
405             rec->Stream, rec->data_len);
406    }
407    /*
408     * Check for Start or End of Session Record
409     *
410     */
411    if (rec->FileIndex < 0) {
412       bool save_update_db = update_db;
413
414       if (verbose > 1) {
415          dump_label_record(dev, rec, 1);
416       }
417       switch (rec->FileIndex) {
418       case PRE_LABEL:
419          Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
420          return false;
421          break;
422
423       case VOL_LABEL:
424          unser_volume_label(dev, rec);
425          /* Check Pool info */
426          bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
427          bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
428          num_pools++;
429          if (db_get_pool_record(bjcr, db, &pr)) {
430             if (verbose) {
431                Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
432             }
433          } else {
434             if (!update_db) {
435                Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
436                   pr.Name);
437             }
438             create_pool_record(db, &pr);
439          }
440          if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
441             Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
442                pr.PoolType, dev->VolHdr.PoolType);
443             return true;
444          } else if (verbose) {
445             Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
446          }
447
448          /* Check Media Info */
449          memset(&mr, 0, sizeof(mr));
450          bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
451          mr.PoolId = pr.PoolId;
452          num_media++;
453          if (db_get_media_record(bjcr, db, &mr)) {
454             if (verbose) {
455                Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
456             }
457             /* Clear out some volume statistics that will be updated */
458             mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
459             mr.VolBytes = rec->data_len + 20;
460          } else {
461             if (!update_db) {
462                Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
463                   mr.VolumeName);
464             }
465             bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
466             create_media_record(db, &mr, &dev->VolHdr);
467          }
468          if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
469             Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
470                mr.MediaType, dev->VolHdr.MediaType);
471             return true;              /* ignore error */
472          } else if (verbose) {
473             Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
474          }
475          /* Reset some DCR variables */
476          foreach_dlist(dcr, dev->attached_dcrs) {
477             dcr->VolFirstIndex = dcr->FileIndex = 0;
478             dcr->StartBlock = dcr->EndBlock = 0;
479             dcr->StartFile = dcr->EndFile = 0;
480             dcr->VolMediaId = 0;
481          }
482
483          Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
484          break;
485
486       case SOS_LABEL:
487          mr.VolJobs++;
488          num_jobs++;
489          if (ignored_msgs > 0) {
490             Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
491                   ignored_msgs);
492             ignored_msgs = 0;
493          }
494          unser_session_label(&label, rec);
495          memset(&jr, 0, sizeof(jr));
496          bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
497          if (db_get_job_record(bjcr, db, &jr)) {
498             /* Job record already exists in DB */
499             update_db = false;  /* don't change db in create_job_record */
500             if (verbose) {
501                Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
502             }
503          } else {
504             /* Must create a Job record in DB */
505             if (!update_db) {
506                Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
507                   jr.JobId);
508             }
509          }
510          /* Create Client record if not already there */
511             bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
512             create_client_record(db, &cr);
513             jr.ClientId = cr.ClientId;
514
515          /* process label, if Job record exists don't update db */
516          mjcr = create_job_record(db, &jr, &label, rec);
517          dcr = mjcr->read_dcr;
518          update_db = save_update_db;
519
520          jr.PoolId = pr.PoolId;
521 #ifdef xxx
522          /* Set start positions into JCR */
523          if (dev->is_tape()) {
524             /*
525              * Note, we have already advanced past current block,
526              *  so the correct number is block_num - 1
527              */
528             dcr->StartBlock = dev->block_num - 1;
529             dcr->StartFile = dev->file;
530          } else {
531             dcr->StartBlock = (uint32_t)dev->file_addr;
532             dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
533          }
534 #endif
535          mjcr->start_time = jr.StartTime;
536          mjcr->JobLevel = jr.JobLevel;
537
538          mjcr->client_name = get_pool_memory(PM_FNAME);
539          pm_strcpy(mjcr->client_name, label.ClientName);
540          mjcr->fileset_name = get_pool_memory(PM_FNAME);
541          pm_strcpy(mjcr->fileset_name, label.FileSetName);
542          bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
543          bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
544
545          if (rec->VolSessionId != jr.VolSessionId) {
546             Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
547                jr.JobId,
548                jr.VolSessionId, rec->VolSessionId);
549             return true;              /* ignore error */
550          }
551          if (rec->VolSessionTime != jr.VolSessionTime) {
552             Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
553                jr.JobId,
554                jr.VolSessionTime, rec->VolSessionTime);
555             return true;              /* ignore error */
556          }
557          if (jr.PoolId != pr.PoolId) {
558             Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
559                jr.JobId,
560                jr.PoolId, pr.PoolId);
561             return true;              /* ignore error */
562          }
563          break;
564
565       case EOS_LABEL:
566          unser_session_label(&elabel, rec);
567
568          /* Create FileSet record */
569          bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
570          bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
571          create_fileset_record(db, &fsr);
572          jr.FileSetId = fsr.FileSetId;
573
574          mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
575          if (!mjcr) {
576             Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
577                   rec->VolSessionId, rec->VolSessionTime);
578             break;
579          }
580
581          /* Do the final update to the Job record */
582          update_job_record(db, &jr, &elabel, rec);
583
584          mjcr->end_time = jr.EndTime;
585          mjcr->JobStatus = JS_Terminated;
586
587          /* Create JobMedia record */
588          mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
589          create_jobmedia_record(db, mjcr);
590          detach_dcr_from_dev(mjcr->read_dcr);
591          free_jcr(mjcr);
592
593          break;
594
595       case EOM_LABEL:
596          break;
597
598       case EOT_LABEL:              /* end of all tapes */
599          /*
600           * Wiffle through all jobs still open and close
601           *   them.
602           */
603          if (update_db) {
604             DCR *mdcr;
605             foreach_dlist(mdcr, dev->attached_dcrs) {
606                JCR *mjcr = mdcr->jcr;
607                if (!mjcr || mjcr->JobId == 0) {
608                   continue;
609                }
610                jr.JobId = mjcr->JobId;
611                /* Mark Job as Error Terimined */
612                jr.JobStatus = JS_ErrorTerminated;
613                jr.JobFiles = mjcr->JobFiles;
614                jr.JobBytes = mjcr->JobBytes;
615                jr.VolSessionId = mjcr->VolSessionId;
616                jr.VolSessionTime = mjcr->VolSessionTime;
617                jr.JobTDate = (utime_t)mjcr->start_time;
618                jr.ClientId = mjcr->ClientId;
619                if (!db_update_job_end_record(bjcr, db, &jr)) {
620                   Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
621                }
622                mjcr->read_dcr = NULL;
623                free_jcr(mjcr);
624             }
625          }
626          mr.VolFiles = rec->File;
627          mr.VolBlocks = rec->Block;
628          mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
629          mr.VolMounts++;
630          update_media_record(db, &mr);
631          Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
632                     mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
633          break;
634       default:
635          break;
636       } /* end switch */
637       return true;
638    }
639
640    mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
641    if (!mjcr) {
642       if (mr.VolJobs > 0) {
643          Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
644                       rec->VolSessionId, rec->VolSessionTime);
645       } else {
646          ignored_msgs++;
647       }
648       return true;
649    }
650    dcr = mjcr->read_dcr;
651    if (dcr->VolFirstIndex == 0) {
652       dcr->VolFirstIndex = block->FirstIndex;
653    }
654
655    /* File Attributes stream */
656    switch (rec->Stream) {
657    case STREAM_UNIX_ATTRIBUTES:
658    case STREAM_UNIX_ATTRIBUTES_EX:
659
660       if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
661          Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
662       }
663
664       if (attr->file_index != rec->FileIndex) {
665          Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
666             rec->FileIndex, attr->file_index);
667       }
668
669       if (verbose > 1) {
670          decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
671          build_attr_output_fnames(bjcr, attr);
672          print_ls_output(bjcr, attr);
673       }
674       fr.JobId = mjcr->JobId;
675       fr.FileId = 0;
676       num_files++;
677       if (verbose && (num_files & 0x7FFF) == 0) {
678          char ed1[30], ed2[30], ed3[30], ed4[30];
679          Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
680                      edit_uint64_with_commas(num_files, ed1),
681                      edit_uint64_with_commas(rec->File, ed2),
682                      edit_uint64_with_commas(rec->Block, ed3),
683                      edit_uint64_with_commas(mr.VolBytes, ed4));
684       }
685       create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
686             attr->type, attr->attr, rec);
687       free_jcr(mjcr);
688       break;
689
690    /* Data stream */
691    case STREAM_WIN32_DATA:
692    case STREAM_FILE_DATA:
693    case STREAM_SPARSE_DATA:
694    case STREAM_ENCRYPTED_FILE_DATA:
695    case STREAM_ENCRYPTED_WIN32_DATA:
696    case STREAM_ENCRYPTED_MACOS_FORK_DATA:
697       /*
698        * For encrypted stream, this is an approximation.
699        * The data must be decrypted to know the correct length.
700        */
701       mjcr->JobBytes += rec->data_len;
702       if (rec->Stream == STREAM_SPARSE_DATA) {
703          mjcr->JobBytes -= sizeof(uint64_t);
704       }
705
706       free_jcr(mjcr);                 /* done using JCR */
707       break;
708
709    case STREAM_GZIP_DATA:
710    case STREAM_ENCRYPTED_FILE_GZIP_DATA:
711    case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
712       /* No correct, we should (decrypt and) expand it 
713          done using JCR 
714       */
715       mjcr->JobBytes += rec->data_len;
716       free_jcr(mjcr);                 
717       break;
718
719    case STREAM_SPARSE_GZIP_DATA:
720       mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
721       free_jcr(mjcr);                 /* done using JCR */
722       break;
723
724    /* Win32 GZIP stream */
725    case STREAM_WIN32_GZIP_DATA:
726       mjcr->JobBytes += rec->data_len;
727       free_jcr(mjcr);                 /* done using JCR */
728       break;
729
730    case STREAM_MD5_DIGEST:
731       bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
732       if (verbose > 1) {
733          Pmsg1(000, _("Got MD5 record: %s\n"), digest);
734       }
735       update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
736       break;
737
738    case STREAM_SHA1_DIGEST:
739       bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
740       if (verbose > 1) {
741          Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
742       }
743       update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
744       break;
745
746    case STREAM_SHA256_DIGEST:
747       bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
748       if (verbose > 1) {
749          Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
750       }
751       update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
752       break;
753
754    case STREAM_SHA512_DIGEST:
755       bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
756       if (verbose > 1) {
757          Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
758       }
759       update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
760       break;
761
762    case STREAM_ENCRYPTED_SESSION_DATA:
763       // TODO landonf: Investigate crypto support in bscan
764       if (verbose > 1) {
765          Pmsg0(000, _("Got signed digest record\n"));
766       }
767       break;
768
769    case STREAM_SIGNED_DIGEST:
770       // TODO landonf: Investigate crypto support in bscan
771       if (verbose > 1) {
772          Pmsg0(000, _("Got signed digest record\n"));
773       }
774       break;
775
776    case STREAM_PROGRAM_NAMES:
777       if (verbose) {
778          Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
779       }
780       break;
781
782    case STREAM_PROGRAM_DATA:
783       if (verbose > 1) {
784          Pmsg0(000, _("Got Prog Data Stream record.\n"));
785       }
786       break;
787
788    case STREAM_UNIX_ATTRIBUTES_ACCESS_ACL:   /* Standard ACL attributes on UNIX */
789    case STREAM_UNIX_ATTRIBUTES_DEFAULT_ACL:  /* Default ACL attributes on UNIX */
790       /* Ignore Unix attributes */
791       break;
792
793    default:
794       Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
795       break;
796    }
797    return true;
798 }
799
800 /*
801  * Free the Job Control Record if no one is still using it.
802  *  Called from main free_jcr() routine in src/lib/jcr.c so
803  *  that we can do our Director specific cleanup of the jcr.
804  */
805 static void bscan_free_jcr(JCR *jcr)
806 {
807    Dmsg0(200, "Start bscan free_jcr\n");
808
809    if (jcr->file_bsock) {
810       Dmsg0(200, "Close File bsock\n");
811       bnet_close(jcr->file_bsock);
812    }
813    if (jcr->store_bsock) {
814       Dmsg0(200, "Close Store bsock\n");
815       bnet_close(jcr->store_bsock);
816    }
817    if (jcr->RestoreBootstrap) {
818       free(jcr->RestoreBootstrap);
819    }
820    if (jcr->dcr) {
821       free_dcr(jcr->dcr);
822       jcr->dcr = NULL;
823    }
824    if (jcr->read_dcr) {
825       free_dcr(jcr->read_dcr);
826       jcr->read_dcr = NULL;
827    }
828    Dmsg0(200, "End bscan free_jcr\n");
829 }
830
831 /*
832  * We got a File Attributes record on the tape.  Now, lookup the Job
833  *   record, and then create the attributes record.
834  */
835 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
836                                char *fname, char *lname, int type,
837                                char *ap, DEV_RECORD *rec)
838 {
839    DCR *dcr = mjcr->read_dcr;
840    ar.fname = fname;
841    ar.link = lname;
842    ar.ClientId = mjcr->ClientId;
843    ar.JobId = mjcr->JobId;
844    ar.Stream = rec->Stream;
845    ar.FileIndex = rec->FileIndex;
846    ar.attr = ap;
847    if (dcr->VolFirstIndex == 0) {
848       dcr->VolFirstIndex = rec->FileIndex;
849    }
850    dcr->FileIndex = rec->FileIndex;
851    mjcr->JobFiles++;
852
853    if (!update_db) {
854       return 1;
855    }
856
857    if (!db_create_file_attributes_record(bjcr, db, &ar)) {
858       Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
859       return 0;
860    }
861    mjcr->FileId = ar.FileId;
862
863    if (verbose > 1) {
864       Pmsg1(000, _("Created File record: %s\n"), fname);
865    }
866    return 1;
867 }
868
869 /*
870  * For each Volume we see, we create a Medium record
871  */
872 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
873 {
874    struct date_time dt;
875    struct tm tm;
876
877    /* We mark Vols as Archive to keep them from being re-written */
878    bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
879    mr->VolRetention = 365 * 3600 * 24; /* 1 year */
880    mr->Enabled = 1;
881    if (vl->VerNum >= 11) {
882       mr->FirstWritten = btime_to_utime(vl->write_btime);
883       mr->LabelDate    = btime_to_utime(vl->label_btime);
884    } else {
885       /* DEPRECATED DO NOT USE */
886       dt.julian_day_number = vl->write_date;
887       dt.julian_day_fraction = vl->write_time;
888       tm_decode(&dt, &tm);
889       mr->FirstWritten = mktime(&tm);
890       dt.julian_day_number = vl->label_date;
891       dt.julian_day_fraction = vl->label_time;
892       tm_decode(&dt, &tm);
893       mr->LabelDate = mktime(&tm);
894    }
895    lasttime = mr->LabelDate;
896    if (mr->VolJobs == 0) {
897       mr->VolJobs = 1;
898    }
899    if (mr->VolMounts == 0) {
900       mr->VolMounts = 1;
901    }
902
903    if (!update_db) {
904       return 1;
905    }
906
907    if (!db_create_media_record(bjcr, db, mr)) {
908       Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
909       return 0;
910    }
911    if (!db_update_media_record(bjcr, db, mr)) {
912       Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
913       return 0;
914    }
915    if (verbose) {
916       Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
917    }
918    return 1;
919
920 }
921
922 /*
923  * Called at end of media to update it
924  */
925 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
926 {
927    if (!update_db && !update_vol_info) {
928       return true;
929    }
930
931    mr->LastWritten = lasttime;
932    if (!db_update_media_record(bjcr, db, mr)) {
933       Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
934       return false;;
935    }
936    if (verbose) {
937       Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
938    }
939    return true;
940
941 }
942
943
944 static int create_pool_record(B_DB *db, POOL_DBR *pr)
945 {
946    pr->NumVols++;
947    pr->UseCatalog = 1;
948    pr->VolRetention = 355 * 3600 * 24; /* 1 year */
949
950    if (!update_db) {
951       return 1;
952    }
953    if (!db_create_pool_record(bjcr, db, pr)) {
954       Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
955       return 0;
956    }
957    if (verbose) {
958       Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
959    }
960    return 1;
961
962 }
963
964
965 /*
966  * Called from SOS to create a client for the current Job
967  */
968 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
969 {
970    if (!update_db) {
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) { 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)
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 }