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