]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/label.c
bf7281b1982c83ec78cb5f5a20312816824be4e7
[bacula/bacula] / bacula / src / stored / label.c
1 /*
2  *
3  *  label.c  Bacula routines to handle labels 
4  *
5  *   Kern Sibbald, MM
6  *                            
7  *
8  *   Version $Id$
9  */
10 /*
11    Copyright (C) 2000, 2001, 2002 Kern Sibbald and John Walker
12
13    This program is free software; you can redistribute it and/or
14    modify it under the terms of the GNU General Public License as
15    published by the Free Software Foundation; either version 2 of
16    the License, or (at your option) any later version.
17
18    This program is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21    General Public License for more details.
22
23    You should have received a copy of the GNU General Public
24    License along with this program; if not, write to the Free
25    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
26    MA 02111-1307, USA.
27
28  */
29
30 #include "bacula.h"                   /* pull in global headers */
31 #include "stored.h"                   /* pull in Storage Deamon headers */
32
33 /* Forward referenced functions */
34 static int create_volume_label(DEVICE *dev, char *VolName);
35 static void create_volume_label_record(JCR *jcr, DEVICE *dev, DEV_RECORD *rec);
36
37 extern char my_name[];
38 extern int debug_level;
39
40 /*
41  * Read the volume label
42  *
43  *  If jcr->VolumeName == NULL, we accept any Bacula Volume
44  *  If jcr->VolumeName[0] == 0, we accept any Bacula Volume
45  *  otherwise jcr->VolumeName must match the Volume.
46  *
47  *  If VolName given, ensure that it matches   
48  *
49  *  Returns VOL_  code as defined in record.h
50  *    VOL_NOT_READ
51  *    VOL_OK
52  *    VOL_NO_LABEL
53  *    VOL_IO_ERROR
54  *    VOL_NAME_ERROR
55  *    VOL_CREATE_ERROR
56  *    VOL_VERSION_ERROR
57  *    VOL_LABEL_ERROR
58  */  
59 int read_dev_volume_label(JCR *jcr, DEVICE *dev, DEV_BLOCK *block)
60 {
61    char *VolName = jcr->VolumeName;
62    DEV_RECORD *record;
63    int ok = 0;
64
65    Dmsg2(30, "Enter read_volume_label device=%s vol=%s\n", 
66       dev_name(dev), VolName);
67
68    if (dev->state & ST_LABEL) {       /* did we already read label? */
69       /* Compare Volume Names */
70       if (VolName && *VolName && strcmp(dev->VolHdr.VolName, VolName) != 0) {
71          Mmsg(&jcr->errmsg, _("Volume name mismatch on device %s: Wanted %s got %s\n"),
72             dev_name(dev), VolName, dev->VolHdr.VolName);
73          /*
74           * Cancel Job if too many label errors
75           *  => we are in a loop
76           */
77          if (jcr->label_errors > 100) {
78             jcr->JobStatus = JS_Cancelled;
79             Jmsg(jcr, M_FATAL, 0, "%s", jcr->errmsg);
80          }
81          return jcr->label_status = VOL_NAME_ERROR;
82       }
83       Dmsg0(30, "Leave read_volume_label() VOL_OK\n");
84       return jcr->label_status = VOL_OK;       /* label already read */
85    }
86
87    dev->state &= ~(ST_LABEL|ST_APPEND|ST_READ);  /* set no label, no append */
88
89    if (!rewind_dev(dev)) {
90       Mmsg(&jcr->errmsg, _("Couldn't rewind device %s ERR=%s\n"), dev_name(dev),
91          strerror_dev(dev));
92       return jcr->label_status = VOL_IO_ERROR;
93    }
94    strcpy(dev->VolHdr.Id, "**error**");
95
96    /* Read the device label block */
97    record = new_record();
98    Dmsg0(90, "Big if statement in read_volume_label\n");
99    if (!read_block_from_dev(dev, block)) { 
100       Mmsg(&jcr->errmsg, _("Volume on %s is not a Bacula labeled Volume, \
101 because:\n   %s"), dev_name(dev), strerror_dev(dev));
102    } else if (!read_record_from_block(block, record)) {
103       Mmsg(&jcr->errmsg, _("Could not read Volume label from block.\n"));
104    } else if (!unser_volume_label(dev, record)) {
105       Mmsg(&jcr->errmsg, _("Could not unserialize Volume label: %s\n"),
106          strerror_dev(dev));
107    } else if (strcmp(dev->VolHdr.Id, BaculaId) != 0) {
108       Mmsg(&jcr->errmsg, _("Volume Header Id bad: %s\n"), dev->VolHdr.Id);
109    } else {
110       ok = 1;
111    }
112    if (!ok) {
113       free_record(record);
114       empty_block(block);
115       rewind_dev(dev);
116       return jcr->label_status = VOL_NO_LABEL;
117    }
118
119    free_record(record);
120    empty_block(block);
121    rewind_dev(dev);
122
123    if (dev->VolHdr.VerNum != BaculaTapeVersion && 
124        dev->VolHdr.VerNum != OldCompatibleBaculaTapeVersion1 &&  
125        dev->VolHdr.VerNum != OldCompatibleBaculaTapeVersion2) {
126       Mmsg(&jcr->errmsg, _("Volume on %s has wrong Bacula version. Wanted %d got %d\n"),
127          dev_name(dev), BaculaTapeVersion, dev->VolHdr.VerNum);
128       return jcr->label_status = VOL_VERSION_ERROR;
129    }
130
131    /* Compare Volume Names */
132    Dmsg2(30, "Compare Vol names: VolName=%s hdr=%s\n", VolName?VolName:"*", dev->VolHdr.VolName);
133    if (VolName && *VolName && strcmp(dev->VolHdr.VolName, VolName) != 0) {
134       Mmsg(&jcr->errmsg, _("Volume name mismatch. Wanted %s got %s\n"),
135          VolName, dev->VolHdr.VolName);
136       /*
137        * Cancel Job if too many label errors
138        *  => we are in a loop
139        */
140       if (jcr->label_errors > 100) {
141          jcr->JobStatus = JS_Cancelled;
142          Jmsg(jcr, M_FATAL, 0, "%s", jcr->errmsg);
143       }
144       return jcr->label_status = VOL_NAME_ERROR;
145    }
146    Dmsg1(30, "Copy vol_name=%s\n", dev->VolHdr.VolName);
147
148    /* We are looking for either an unused Bacula tape (PRE_LABEL) or
149     * a Bacula volume label (VOL_LABEL)
150     */
151    if (dev->VolHdr.LabelType != PRE_LABEL && dev->VolHdr.LabelType != VOL_LABEL) {
152       Mmsg(&jcr->errmsg, _("Volume on %s has bad Bacula label type: %x\n"), 
153           dev_name(dev), dev->VolHdr.LabelType);
154       return jcr->label_status = VOL_LABEL_ERROR;
155    }
156
157    dev->state |= ST_LABEL;            /* set has Bacula label */
158    if (debug_level >= 10) {
159       dump_volume_label(dev);
160    }
161    Dmsg0(30, "Leave read_volume_label() VOL_OK\n");
162    return jcr->label_status = VOL_OK;
163 }
164
165 /*  unser_volume_label 
166  *  
167  * Unserialize the Volume label into the device Volume_Label
168  * structure.
169  *
170  * Assumes that the record is already read.
171  *
172  * Returns: 0 on error
173  *          1 on success
174 */
175
176 int unser_volume_label(DEVICE *dev, DEV_RECORD *rec)
177 {
178    ser_declare;
179
180    if (rec->FileIndex != VOL_LABEL && rec->FileIndex != PRE_LABEL) {
181       Mmsg3(&dev->errmsg, _("Expecting Volume Label, got FI=%s Stream=%s len=%d\n"), 
182               FI_to_ascii(rec->FileIndex), stream_to_ascii(rec->Stream), 
183               rec->data_len);
184       return 0;
185    }
186
187    dev->VolHdr.LabelType = rec->FileIndex;
188    dev->VolHdr.LabelSize = rec->data_len;
189
190
191   /* Unserialize the record into the Volume Header */
192   ser_begin(rec->data, SER_LENGTH_Volume_Label);
193 #define Fld(x)  (dev->VolHdr.x)
194    unser_string(Fld(Id));
195
196    unser_uint32(Fld(VerNum));
197
198    if (Fld(VerNum) >= 11) {
199       unser_btime(Fld(label_btime));
200       unser_btime(Fld(write_btime));
201    } else { /* old way */ 
202    unser_float64(Fld(label_date));
203    unser_float64(Fld(label_time));
204    }
205    unser_float64(Fld(write_date));    /* Unused with VerNum >= 11 */
206    unser_float64(Fld(write_time));    /* Unused with VerNum >= 11 */
207
208    unser_string(Fld(VolName));
209    unser_string(Fld(PrevVolName));
210    unser_string(Fld(PoolName));
211    unser_string(Fld(PoolType));
212    unser_string(Fld(MediaType));
213
214    unser_string(Fld(HostName));
215    unser_string(Fld(LabelProg));
216    unser_string(Fld(ProgVersion));
217    unser_string(Fld(ProgDate));
218
219    ser_end(rec->data, SER_LENGTH_Volume_Label);
220 #undef Fld
221    Dmsg0(90, "ser_read_vol\n");
222    if (debug_level >= 90) {
223       dump_volume_label(dev);      
224    }
225    return 1;
226 }
227
228 /*
229  * Put a volume label into the block
230  *
231  *  Returns: 0 on failure
232  *           1 on success
233  */
234 int write_volume_label_to_block(JCR *jcr, DEVICE *dev, DEV_BLOCK *block)
235 {
236    DEV_RECORD rec;
237
238    Dmsg0(20, "write Label in write_volume_label_to_block()\n");
239    memset(&rec, 0, sizeof(rec));
240    rec.data = get_memory(SER_LENGTH_Volume_Label);
241
242    create_volume_label_record(jcr, dev, &rec);
243
244    empty_block(block);                /* Volume label always at beginning */
245    if (!write_record_to_block(block, &rec)) {
246       free_pool_memory(rec.data);
247       Jmsg1(jcr, M_FATAL, 0, _("Cannot write Volume label to block for device %s\n"),
248          dev_name(dev));
249       return 0;
250    } else {
251       Dmsg1(90, "Wrote label of %d bytes to block\n", rec.data_len);
252    }
253    free_pool_memory(rec.data);
254    return 1;
255 }
256
257 /* 
258  *  create_volume_label_record
259  *   Serialize label (from dev->VolHdr structure) into device record.
260  *   Assumes that the dev->VolHdr structure is properly 
261  *   initialized.
262 */
263 static void create_volume_label_record(JCR *jcr, DEVICE *dev, DEV_RECORD *rec)
264 {
265    ser_declare;
266    struct date_time dt;
267
268    /* Serialize the label into the device record. */
269
270    ser_begin(rec->data, SER_LENGTH_Volume_Label);
271 #define Fld(x)  (dev->VolHdr.x)
272    ser_string(Fld(Id));
273
274    ser_uint32(Fld(VerNum));
275
276    if (Fld(VerNum >= 11)) {
277       ser_btime(Fld(label_btime));
278       Fld(write_btime) = get_current_btime();
279       ser_btime(Fld(write_btime));
280       Fld(write_date) = 0;
281       Fld(write_time) = 0;
282    } else {
283    ser_float64(Fld(label_date));
284    ser_float64(Fld(label_time));
285    get_current_time(&dt);
286    Fld(write_date) = dt.julian_day_number;
287    Fld(write_time) = dt.julian_day_fraction;
288    }
289
290    ser_float64(Fld(write_date));   /* unused if VerNum >= 11 */
291    ser_float64(Fld(write_time));   /* unused if VerNum >= 11 */
292
293    ser_string(Fld(VolName));
294    ser_string(Fld(PrevVolName));
295    ser_string(Fld(PoolName));
296    ser_string(Fld(PoolType));
297    ser_string(Fld(MediaType));
298
299    ser_string(Fld(HostName));
300    ser_string(Fld(LabelProg));
301    ser_string(Fld(ProgVersion));
302    ser_string(Fld(ProgDate));
303
304    ser_end(rec->data, SER_LENGTH_Volume_Label);
305    rec->data_len = ser_length(rec->data);
306    rec->FileIndex = Fld(LabelType);
307    rec->VolSessionId = jcr->VolSessionId;
308    rec->VolSessionTime = jcr->VolSessionTime;
309    rec->Stream = jcr->NumVolumes;
310    Dmsg2(100, "Created Vol label rec: FI=%s len=%d\n", FI_to_ascii(rec->FileIndex),
311       rec->data_len);
312 #undef Fld
313 }     
314
315
316 /*
317  * Create a volume label in memory
318  *  Returns: 0 on error
319  *           1 on success
320  */
321 static int create_volume_label(DEVICE *dev, char *VolName)
322 {
323    struct date_time dt;
324    DEVRES *device = (DEVRES *)dev->device;
325
326    Dmsg0(90, "Start create_volume_label()\n");
327
328    ASSERT(dev != NULL);
329
330    memset(&dev->VolHdr, 0, sizeof(dev->VolHdr));
331
332    /* ***FIXME*** we really need to get the volume name,    
333     * pool name, and pool type from the database.
334     * We also need to pickup the MediaType.
335     */
336    strcpy(dev->VolHdr.Id, BaculaId);
337    dev->VolHdr.VerNum = BaculaTapeVersion;
338    dev->VolHdr.LabelType = PRE_LABEL;  /* Mark tape as unused */
339    strcpy(dev->VolHdr.VolName, VolName);
340    strcpy(dev->VolHdr.PoolName, "Default");
341    strcpy(dev->VolHdr.MediaType, device->media_type);
342    strcpy(dev->VolHdr.PoolType, "Backup");
343
344    /* Put label time/date in header */
345    if (BaculaTapeVersion >= 11) {
346       dev->VolHdr.label_btime = get_current_btime();
347       dev->VolHdr.label_date = 0;
348       dev->VolHdr.label_time = 0;
349    } else {
350    get_current_time(&dt);
351    dev->VolHdr.label_date = dt.julian_day_number;
352    dev->VolHdr.label_time = dt.julian_day_fraction;
353    }
354
355    strcpy(dev->VolHdr.LabelProg, my_name);
356    sprintf(dev->VolHdr.ProgVersion, "Ver. %s %s", VERSION, DATE);
357    sprintf(dev->VolHdr.ProgDate, "Build %s %s", __DATE__, __TIME__);
358    dev->state |= ST_LABEL;            /* set has Bacula label */
359    if (debug_level >= 90) {
360       dump_volume_label(dev);
361    }
362    return 1;
363 }
364
365 /*
366  * Write a Volume Label
367  *  !!! Note, this is ONLY used for writing
368  *            a fresh volume label.  Any data
369  *            after the label will be destroyed,
370  *            in fact, we write the label 5 times !!!!
371  * 
372  *  This routine expects that open_device() was previously called.
373  *
374  *  This routine should be used only when labeling a blank tape.
375  */
376 int write_volume_label_to_dev(JCR *jcr, DEVRES *device, char *VolName, char *PoolName)
377 {
378    DEVICE *dev = device->dev;
379    DEV_RECORD rec;   
380    DEV_BLOCK *block;
381    int stat = 1;
382
383
384    Dmsg0(99, "write_volume_label()\n");
385    if (!create_volume_label(dev, VolName)) {
386       return 0;
387    }
388    strcpy(dev->VolHdr.MediaType, device->media_type);
389    strcpy(dev->VolHdr.VolName, VolName);
390    strcpy(dev->VolHdr.PoolName, PoolName);
391
392    if (!rewind_dev(dev)) {
393       Dmsg2(30, "Bad status on %s from rewind. ERR=%s\n", dev_name(dev), strerror_dev(dev));
394       return 0;
395    }
396
397    block = new_block(dev);
398    memset(&rec, 0, sizeof(rec));
399    rec.data = get_memory(SER_LENGTH_Volume_Label);
400    create_volume_label_record(jcr, dev, &rec);
401    rec.Stream = 0;
402
403    if (!write_record_to_block(block, &rec)) {
404       Dmsg2(30, "Bad Label write on %s. ERR=%s\n", dev_name(dev), strerror_dev(dev));
405       free_block(block);
406       free_pool_memory(rec.data);
407       return 0;
408    } else {
409       Dmsg2(30, "Wrote label of %d bytes to %s\n", rec.data_len, dev_name(dev));
410    }
411    free_pool_memory(rec.data);
412       
413    Dmsg0(99, "Call write_block_to_device()\n");
414    if (!write_block_to_dev(dev, block)) {
415       Dmsg2(30, "Bad Label write on %s. ERR=%s\n", dev_name(dev), strerror_dev(dev));
416       stat = 9;
417    }
418    Dmsg0(99, " Wrote block to device\n");
419      
420    flush_dev(dev);
421    weof_dev(dev, 1);
422    dev->state |= ST_LABEL;
423
424    if (debug_level >= 20)  {
425       dump_volume_label(dev);
426    }
427    free_block(block);
428    return stat;
429 }     
430
431
432 /*
433  * Create session label
434  *  The pool memory must be released by the calling program
435  */
436 void create_session_label(JCR *jcr, DEV_RECORD *rec, int label)
437 {
438    ser_declare;
439    struct date_time dt;
440
441    rec->sync           = 1;         /* wait for completion */
442    rec->VolSessionId   = jcr->VolSessionId;
443    rec->VolSessionTime = jcr->VolSessionTime;
444    rec->Stream         = jcr->JobId;
445
446    ser_begin(rec->data, SER_LENGTH_Session_Label);
447    ser_string(BaculaId);
448    ser_uint32(BaculaTapeVersion);
449
450    ser_uint32(jcr->JobId);
451
452    if (BaculaTapeVersion >= 11) {
453       ser_btime(get_current_btime());
454       ser_float64(0);
455    } else {
456    get_current_time(&dt);
457    ser_float64(dt.julian_day_number);
458    ser_float64(dt.julian_day_fraction);
459    }
460
461    ser_string(jcr->pool_name);
462    ser_string(jcr->pool_type);
463    ser_string(jcr->job_name);         /* base Job name */
464    ser_string(jcr->client_name);
465    /* Added in VerNum 10 */
466    ser_string(jcr->Job);              /* Unique name of this Job */
467    ser_string(jcr->fileset_name);
468    ser_uint32(jcr->JobType);
469    ser_uint32(jcr->JobLevel);
470
471    if (label == EOS_LABEL) {
472       ser_uint32(jcr->JobFiles);
473       ser_uint64(jcr->JobBytes);
474       ser_uint32(jcr->StartBlock);
475       ser_uint32(jcr->EndBlock);
476       ser_uint32(jcr->StartFile);
477       ser_uint32(jcr->EndFile);
478       ser_uint32(jcr->JobErrors);
479       /* Added in VerNum 11 */
480       ser_uint32(jcr->JobStatus);
481    }
482    ser_end(rec->data, SER_LENGTH_Session_Label);
483    rec->data_len = ser_length(rec->data);
484 }
485
486 /* Write session label
487  *  Returns: 0 on failure
488  *           1 on success 
489  */
490 int write_session_label(JCR *jcr, DEV_BLOCK *block, int label)
491 {
492    DEVICE *dev = jcr->device->dev;
493    DEV_RECORD *rec;
494
495    rec = new_record();
496    Dmsg1(90, "session_label record=%x\n", rec);
497    switch (label) {
498       case SOS_LABEL:
499          jcr->StartBlock = dev->block_num;
500          jcr->StartFile  = dev->file;
501          break;
502       case EOS_LABEL:
503          jcr->EndBlock = dev->block_num;
504          jcr->EndFile = dev->file;
505          break;
506       default:
507          Jmsg1(jcr, M_ABORT, 0, _("Bad session label = %d\n"), label);
508          break;
509    }
510    create_session_label(jcr, rec, label);
511    rec->FileIndex = label;
512
513    /* 
514     * We guarantee that the session record can totally fit
515     *  into a block. If not, write the block, and put it in
516     *  the next block. Having the sesssion record totally in
517     *  one block makes reading them much easier (no need to
518     *  read the next block).
519     */
520    if (!can_write_record_to_block(block, rec)) {
521       Dmsg0(100, "Cannot write session label to block.\n");
522       if (!write_block_to_device(jcr, dev, block)) {
523          Dmsg0(90, "Got session label write_block_to_dev error.\n");
524          Jmsg(jcr, M_FATAL, 0, _("Error writing Session label to %s: %s\n"), 
525                            dev_vol_name(dev), strerror(errno));
526          free_record(rec);
527          return 0;
528       }
529    }
530    if (!write_record_to_block(block, rec)) {
531       Jmsg(jcr, M_FATAL, 0, _("Error writing Session label to %s: %s\n"), 
532                         dev_vol_name(dev), strerror(errno));
533       free_record(rec);
534       return 0;
535    }
536
537    Dmsg6(20, "Write sesson_label record JobId=%d FI=%s SessId=%d Strm=%s len=%d\n\
538 remainder=%d\n", jcr->JobId,
539       FI_to_ascii(rec->FileIndex), rec->VolSessionId, 
540       stream_to_ascii(rec->Stream), rec->data_len,
541       rec->remainder);
542
543    free_record(rec);
544    Dmsg2(20, "Leave write_session_label Block=%d File=%d\n", 
545       dev->block_num, dev->file);
546    return 1;
547 }
548
549 void dump_volume_label(DEVICE *dev)
550 {
551    int dbl = debug_level;
552    uint32_t File;
553    char *LabelType, buf[30];
554    struct tm tm;
555    struct date_time dt;
556
557    debug_level = 1;
558    File = dev->file;
559    switch (dev->VolHdr.LabelType) {
560       case PRE_LABEL:
561          LabelType = "PRE_LABEL";
562          break;
563       case VOL_LABEL:
564          LabelType = "VOL_LABEL";
565          break;
566       case EOM_LABEL:
567          LabelType = "EOM_LABEL";
568          break;
569       case SOS_LABEL:
570          LabelType = "SOS_LABEL";
571          break;
572       case EOS_LABEL:
573          LabelType = "EOS_LABEL";
574          break;
575       case EOT_LABEL:
576          goto bail_out;
577       default:
578          LabelType = buf;
579          sprintf(buf, "Unknown %d", dev->VolHdr.LabelType);
580          break;
581    }
582               
583    
584    Pmsg11(-1, "\nVolume Label:\n\
585 Id                : %s\
586 VerNo             : %d\n\
587 VolName           : %s\n\
588 PrevVolName       : %s\n\
589 VolFile           : %d\n\
590 LabelType         : %s\n\
591 LabelSize         : %d\n\
592 PoolName          : %s\n\
593 MediaType         : %s\n\
594 PoolType          : %s\n\
595 HostName          : %s\n\
596 ",
597              dev->VolHdr.Id, dev->VolHdr.VerNum,
598              dev->VolHdr.VolName, dev->VolHdr.PrevVolName,
599              File, LabelType, dev->VolHdr.LabelSize, 
600              dev->VolHdr.PoolName, dev->VolHdr.MediaType, 
601              dev->VolHdr.PoolType, dev->VolHdr.HostName);
602
603    if (dev->VolHdr.VerNum >= 11) {
604       char dt[50];
605       bstrftime(dt, sizeof(dt), (time_t)dev->VolHdr.label_btime);
606       Pmsg1(-1, "Date label written: %s\n", dt);
607    } else {
608    dt.julian_day_number   = dev->VolHdr.label_date;
609    dt.julian_day_fraction = dev->VolHdr.label_time;
610    tm_decode(&dt, &tm);
611    Pmsg5(-1, "\
612 Date label written: %04d-%02d-%02d at %02d:%02d\n", 
613       tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min);
614    }
615
616 bail_out:
617    debug_level = dbl;
618 }
619
620 int unser_session_label(SESSION_LABEL *label, DEV_RECORD *rec) 
621 {
622    ser_declare;
623
624    unser_begin(rec->data, SER_LENGTH_Session_Label);
625    unser_string(label->Id);
626    unser_uint32(label->VerNum);
627    unser_uint32(label->JobId);
628    if (label->VerNum >= 11) {
629       unser_btime(label->write_btime);
630    } else {
631    unser_float64(label->write_date);
632    }
633    unser_float64(label->write_time);
634    unser_string(label->PoolName);
635    unser_string(label->PoolType);
636    unser_string(label->JobName);
637    unser_string(label->ClientName);
638    if (label->VerNum >= 10) {
639       unser_string(label->Job);          /* Unique name of this Job */
640       unser_string(label->FileSetName);
641       unser_uint32(label->JobType);
642       unser_uint32(label->JobLevel);
643    }
644    if (rec->FileIndex == EOS_LABEL) {
645       unser_uint32(label->JobFiles);
646       unser_uint64(label->JobBytes);
647       unser_uint32(label->StartBlock);
648       unser_uint32(label->EndBlock);
649       unser_uint32(label->StartFile);
650       unser_uint32(label->EndFile);
651       unser_uint32(label->JobErrors);
652       if (label->VerNum >= 11) {
653          unser_uint32(label->JobStatus);
654       } else {
655          label->JobStatus = JS_Terminated; /* kludge */
656       }
657    }      
658    return 1;
659 }
660
661
662 static void dump_session_label(DEV_RECORD *rec, char *type)
663 {
664    int dbl;
665    struct date_time dt;
666    struct tm tm;
667    SESSION_LABEL label;
668    char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], ec6[30], ec7[30];
669
670    unser_session_label(&label, rec);
671    dbl = debug_level;
672    debug_level = 1;
673    Pmsg7(-1, "\n%s Record:\n\
674 JobId             : %d\n\
675 VerNum            : %d\n\
676 PoolName          : %s\n\
677 PoolType          : %s\n\
678 JobName           : %s\n\
679 ClientName        : %s\n\
680 ",    type, label.JobId, label.VerNum,
681       label.PoolName, label.PoolType,
682       label.JobName, label.ClientName);
683
684    if (label.VerNum >= 10) {
685       Pmsg4(-1, "\
686 Job (unique name) : %s\n\
687 FileSet           : %s\n\
688 JobType           : %c\n\
689 JobLevel          : %c\n\
690 ", label.Job, label.FileSetName, label.JobType, label.JobLevel);
691    }
692
693    if (rec->FileIndex == EOS_LABEL) {
694       Pmsg8(-1, "\
695 JobFiles          : %s\n\
696 JobBytes          : %s\n\
697 StartBlock        : %s\n\
698 EndBlock          : %s\n\
699 StartFile         : %s\n\
700 EndFile           : %s\n\
701 JobErrors         : %s\n\
702 JobStatus         : %c\n\
703 ",
704          edit_uint64_with_commas(label.JobFiles, ec1),
705          edit_uint64_with_commas(label.JobBytes, ec2),
706          edit_uint64_with_commas(label.StartBlock, ec3),
707          edit_uint64_with_commas(label.EndBlock, ec4),
708          edit_uint64_with_commas(label.StartFile, ec5),
709          edit_uint64_with_commas(label.EndFile, ec6),
710          edit_uint64_with_commas(label.JobErrors, ec7), 
711          label.JobStatus);
712    }
713    if (label.VerNum >= 11) {
714       char dt[50];
715       bstrftime(dt, sizeof(dt), (time_t)label.write_btime);
716       Pmsg1(-1, _("Date written      : %s\n"), dt);
717    } else {
718    dt.julian_day_number   = label.write_date;
719    dt.julian_day_fraction = label.write_time;
720    tm_decode(&dt, &tm);
721       Pmsg5(-1, _("\
722 Date written      : %04d-%02d-%02d at %02d:%02d\n"),
723       tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min);
724    }
725
726    debug_level = dbl;
727 }
728
729 void dump_label_record(DEVICE *dev, DEV_RECORD *rec, int verbose)
730 {
731    char *type;
732    int dbl;
733
734    dbl = debug_level;
735    debug_level = 1;
736    switch (rec->FileIndex) {
737       case PRE_LABEL:
738          type = _("Fresh Volume");   
739          break;
740       case VOL_LABEL:
741          type = _("Volume");
742          break;
743       case SOS_LABEL:
744          type = _("Begin Session");
745          break;
746       case EOS_LABEL:
747          type = _("End Session");
748          break;
749       case EOM_LABEL:
750          type = _("End of Media");
751          break;
752       case EOT_LABEL:
753          type = ("End of Tape");
754          break;
755       default:
756          type = _("Unknown");
757          break;
758    }
759    if (verbose) {
760       switch (rec->FileIndex) {
761          case PRE_LABEL:
762          case VOL_LABEL:
763             unser_volume_label(dev, rec);
764             dump_volume_label(dev);
765             break;
766          case SOS_LABEL:
767             dump_session_label(rec, type);
768             break;
769          case EOS_LABEL:
770             dump_session_label(rec, type);
771             break;
772          case EOM_LABEL:
773             Pmsg5(-1, "%s Record: SessId=%d SessTime=%d JobId=%d DataLen=%d\n",
774                type, rec->VolSessionId, rec->VolSessionTime, rec->Stream, rec->data_len);
775             break;
776          case EOT_LABEL:
777             Pmsg0(-1, _("End of physical tape.\n"));
778             break;
779          default:
780             Pmsg5(-1, "%s Record: SessId=%d SessTime=%d JobId=%d DataLen=%d\n",
781                type, rec->VolSessionId, rec->VolSessionTime, rec->Stream, rec->data_len);
782             break;
783       }
784    } else {
785       switch (rec->FileIndex) {
786          case SOS_LABEL:
787          case EOS_LABEL:
788             SESSION_LABEL label;
789             unser_session_label(&label, rec);
790             Pmsg6(-1, "%s Record: SessId=%d SessTime=%d JobId=%d Level=%c \
791 Type=%c\n",
792                type, rec->VolSessionId, rec->VolSessionTime, rec->Stream, 
793                label.JobLevel, label.JobType);
794             break;
795          case EOM_LABEL:
796          case PRE_LABEL:
797          case VOL_LABEL:
798          default:
799             Pmsg5(-1, "%s Record: SessId=%d SessTime=%d JobId=%d DataLen=%d\n",
800          type, rec->VolSessionId, rec->VolSessionTime, rec->Stream, rec->data_len);
801             break;
802          case EOT_LABEL:
803             break;
804       }
805    }
806    debug_level = dbl;
807 }