3 * record.c -- tape record handling functions
5 * Kern Sibbald, April MMI
6 * added BB02 format October MMII
12 Copyright (C) 2001-2005 Kern Sibbald
14 This program is free software; you can redistribute it and/or
15 modify it under the terms of the GNU General Public License
16 version 2 as amended with additional clauses defined in the
17 file LICENSE in the main source directory.
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 the file LICENSE for additional details.
30 extern int debug_level;
33 * Convert a FileIndex into a printable
34 * ASCII string. Not reentrant.
35 * If the FileIndex is negative, it flags the
36 * record as a Label, otherwise it is simply
37 * the FileIndex of the current file.
39 const char *FI_to_ascii(char *buf, int fi)
42 sprintf(buf, "%d", fi);
60 sprintf(buf, _("unknown: %d"), fi);
67 * Convert a Stream ID into a printable
68 * ASCII string. Not reentrant.
70 * A negative stream number represents
71 * stream data that is continued from a
72 * record in the previous block.
73 * If the FileIndex is negative, we are
74 * dealing with a Label, hence the
75 * stream is the JobId.
77 const char *stream_to_ascii(char *buf, int stream, int fi)
80 sprintf(buf, "%d", stream);
84 case STREAM_UNIX_ATTRIBUTES:
86 case STREAM_FILE_DATA:
88 case STREAM_WIN32_DATA:
90 case STREAM_WIN32_GZIP_DATA:
92 case STREAM_MD5_DIGEST:
94 case STREAM_SHA1_DIGEST:
96 case STREAM_GZIP_DATA:
98 case STREAM_UNIX_ATTRIBUTES_EX:
99 return "UNIX-ATTR-EX";
100 case STREAM_SPARSE_DATA:
101 return "SPARSE-DATA";
102 case STREAM_SPARSE_GZIP_DATA:
103 return "SPARSE-GZIP";
104 case STREAM_PROGRAM_NAMES:
106 case STREAM_PROGRAM_DATA:
108 case STREAM_MACOS_FORK_DATA:
110 case STREAM_HFSPLUS_ATTRIBUTES:
111 return "HFSPLUS-ATTR";
112 case STREAM_SHA256_DIGEST:
114 case STREAM_SHA512_DIGEST:
116 case STREAM_SIGNED_DIGEST:
117 return "SIGNED-DIGEST";
118 case STREAM_ENCRYPTED_SESSION_DATA:
119 return "ENCRYPTED-SESSION-DATA";
120 case STREAM_ENCRYPTED_FILE_DATA:
121 return "ENCRYPTED-FILE";
122 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
123 return "ENCRYPTED-GZIP";
124 case STREAM_ENCRYPTED_WIN32_DATA:
125 return "ENCRYPTED-WIN32-DATA";
126 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
127 return "ENCRYPTED-WIN32-GZIP";
128 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
129 return "ENCRYPTED-MACOS-RSRC";
130 case -STREAM_UNIX_ATTRIBUTES:
132 case -STREAM_FILE_DATA:
134 case -STREAM_WIN32_DATA:
135 return "contWIN32-DATA";
136 case -STREAM_WIN32_GZIP_DATA:
137 return "contWIN32-GZIP";
138 case -STREAM_MD5_DIGEST:
140 case -STREAM_SHA1_DIGEST:
142 case -STREAM_GZIP_DATA:
144 case -STREAM_UNIX_ATTRIBUTES_EX:
145 return "contUNIX-ATTR-EX";
146 case -STREAM_SPARSE_DATA:
147 return "contSPARSE-DATA";
148 case -STREAM_SPARSE_GZIP_DATA:
149 return "contSPARSE-GZIP";
150 case -STREAM_PROGRAM_NAMES:
151 return "contPROG-NAMES";
152 case -STREAM_PROGRAM_DATA:
153 return "contPROG-DATA";
154 case -STREAM_MACOS_FORK_DATA:
155 return "contMACOS-RSRC";
156 case -STREAM_HFSPLUS_ATTRIBUTES:
157 return "contHFSPLUS-ATTR";
158 case -STREAM_SHA256_DIGEST:
160 case -STREAM_SHA512_DIGEST:
162 case -STREAM_SIGNED_DIGEST:
163 return "contSIGNED-DIGEST";
164 case -STREAM_ENCRYPTED_SESSION_DATA:
165 return "contENCRYPTED-SESSION-DATA";
166 case -STREAM_ENCRYPTED_FILE_DATA:
167 return "contENCRYPTED-FILE";
168 case -STREAM_ENCRYPTED_FILE_GZIP_DATA:
169 return "contENCRYPTED-GZIP";
170 case -STREAM_ENCRYPTED_WIN32_DATA:
171 return "contENCRYPTED-WIN32-DATA";
172 case -STREAM_ENCRYPTED_WIN32_GZIP_DATA:
173 return "contENCRYPTED-WIN32-GZIP";
174 case -STREAM_ENCRYPTED_MACOS_FORK_DATA:
175 return "contENCRYPTED-MACOS-RSRC";
177 sprintf(buf, "%d", stream);
183 * Return a new record entity
185 DEV_RECORD *new_record(void)
189 rec = (DEV_RECORD *)get_memory(sizeof(DEV_RECORD));
190 memset(rec, 0, sizeof(DEV_RECORD));
191 rec->data = get_pool_memory(PM_MESSAGE);
195 void empty_record(DEV_RECORD *rec)
197 rec->File = rec->Block = 0;
198 rec->VolSessionId = rec->VolSessionTime = 0;
199 rec->FileIndex = rec->Stream = 0;
200 rec->data_len = rec->remainder = 0;
201 rec->state &= ~(REC_PARTIAL_RECORD|REC_BLOCK_EMPTY|REC_NO_MATCH|REC_CONTINUATION);
205 * Free the record entity
208 void free_record(DEV_RECORD *rec)
210 Dmsg0(950, "Enter free_record.\n");
212 free_pool_memory(rec->data);
214 Dmsg0(950, "Data buf is freed.\n");
215 free_pool_memory((POOLMEM *)rec);
216 Dmsg0(950, "Leave free_record.\n");
221 * Write a Record to the block
223 * Returns: false on failure (none or partially written)
224 * true on success (all bytes written)
226 * and remainder returned in packet.
228 * We require enough room for the header, and we deal with
229 * two special cases. 1. Only part of the record may have
230 * been transferred the last time (when remainder is
231 * non-zero), and 2. The remaining bytes to write may not
232 * all fit into the block.
234 bool write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec)
238 char buf1[100], buf2[100];
240 remlen = block->buf_len - block->binbuf;
242 ASSERT(block->binbuf == (uint32_t) (block->bufp - block->buf));
243 ASSERT(block->buf_len >= block->binbuf);
245 Dmsg6(890, "write_record_to_block() FI=%s SessId=%d Strm=%s len=%d\n"
246 "rem=%d remainder=%d\n",
247 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
248 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
249 remlen, rec->remainder);
252 * If rec->remainder is non-zero, we have been called a
253 * second (or subsequent) time to finish writing a record
254 * that did not previously fit into the block.
256 if (rec->remainder == 0) {
257 /* Require enough room to write a full header */
258 if (remlen >= WRITE_RECHDR_LENGTH) {
259 ser_begin(block->bufp, WRITE_RECHDR_LENGTH);
260 if (BLOCK_VER == 1) {
261 ser_uint32(rec->VolSessionId);
262 ser_uint32(rec->VolSessionTime);
264 block->VolSessionId = rec->VolSessionId;
265 block->VolSessionTime = rec->VolSessionTime;
267 ser_int32(rec->FileIndex);
268 ser_int32(rec->Stream);
269 ser_uint32(rec->data_len);
271 block->bufp += WRITE_RECHDR_LENGTH;
272 block->binbuf += WRITE_RECHDR_LENGTH;
273 remlen -= WRITE_RECHDR_LENGTH;
274 rec->remainder = rec->data_len;
275 if (rec->FileIndex > 0) {
276 /* If data record, update what we have in this block */
277 if (block->FirstIndex == 0) {
278 block->FirstIndex = rec->FileIndex;
280 block->LastIndex = rec->FileIndex;
283 rec->remainder = rec->data_len + WRITE_RECHDR_LENGTH;
288 * We are here to write unwritten bytes from a previous
289 * time. Presumably we have a new buffer (possibly
290 * containing a volume label), so the new header
291 * should be able to fit in the block -- otherwise we have
292 * an error. Note, we have to continue splitting the
293 * data record if it is longer than the block.
295 * First, write the header, then write as much as
296 * possible of the data record.
298 * Every time we write a header and it is a continuation
299 * of a previous partially written record, we store the
300 * Stream as -Stream in the record header.
302 ser_begin(block->bufp, WRITE_RECHDR_LENGTH);
303 if (BLOCK_VER == 1) {
304 ser_uint32(rec->VolSessionId);
305 ser_uint32(rec->VolSessionTime);
307 block->VolSessionId = rec->VolSessionId;
308 block->VolSessionTime = rec->VolSessionTime;
310 ser_int32(rec->FileIndex);
311 if (rec->remainder > rec->data_len) {
312 ser_int32(rec->Stream); /* normal full header */
313 ser_uint32(rec->data_len);
314 rec->remainder = rec->data_len; /* must still do data record */
316 ser_int32(-rec->Stream); /* mark this as a continuation record */
317 ser_uint32(rec->remainder); /* bytes to do */
320 /* Require enough room to write a full header */
321 ASSERT(remlen >= WRITE_RECHDR_LENGTH);
323 block->bufp += WRITE_RECHDR_LENGTH;
324 block->binbuf += WRITE_RECHDR_LENGTH;
325 remlen -= WRITE_RECHDR_LENGTH;
326 if (rec->FileIndex > 0) {
327 /* If data record, update what we have in this block */
328 if (block->FirstIndex == 0) {
329 block->FirstIndex = rec->FileIndex;
331 block->LastIndex = rec->FileIndex;
335 return false; /* partial transfer */
339 * Now deal with data record.
340 * Part of it may have already been transferred, and we
341 * may not have enough room to transfer the whole this time.
343 if (rec->remainder > 0) {
344 /* Write as much of data as possible */
345 if (remlen >= rec->remainder) {
346 memcpy(block->bufp, rec->data+rec->data_len-rec->remainder,
348 block->bufp += rec->remainder;
349 block->binbuf += rec->remainder;
351 memcpy(block->bufp, rec->data+rec->data_len-rec->remainder,
354 if (!sm_check_rtn(__FILE__, __LINE__, False)) {
355 /* We damaged a buffer */
356 Dmsg6(0, "Damaged block FI=%s SessId=%d Strm=%s len=%d\n"
357 "rem=%d remainder=%d\n",
358 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
359 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
360 remlen, rec->remainder);
361 Dmsg5(0, "Damaged block: bufp=%x binbuf=%d buf_len=%d rem=%d moved=%d\n",
362 block->bufp, block->binbuf, block->buf_len, block->buf_len-block->binbuf,
364 Dmsg2(0, "Damaged block: buf=%x binbuffrombuf=%d \n",
365 block->buf, block->bufp-block->buf);
367 Emsg0(M_ABORT, 0, _("Damaged buffer\n"));
371 block->bufp += remlen;
372 block->binbuf += remlen;
373 rec->remainder -= remlen;
374 return false; /* did partial transfer */
377 rec->remainder = 0; /* did whole transfer */
383 * Test if we can write whole record to the block
385 * Returns: false on failure
386 * true on success (all bytes can be written)
388 bool can_write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec)
392 remlen = block->buf_len - block->binbuf;
393 if (rec->remainder == 0) {
394 if (remlen >= WRITE_RECHDR_LENGTH) {
395 remlen -= WRITE_RECHDR_LENGTH;
396 rec->remainder = rec->data_len;
403 if (rec->remainder > 0 && remlen < rec->remainder) {
411 * Read a Record from the block
412 * Returns: false if nothing read or if the continuation record does not match.
413 * In both of these cases, a block read must be done.
414 * true if at least the record header was read, this
415 * routine may have to be called again with a new
416 * block if the entire record was not read.
418 bool read_record_from_block(DEV_BLOCK *block, DEV_RECORD *rec)
422 uint32_t VolSessionId;
423 uint32_t VolSessionTime;
428 char buf1[100], buf2[100];
430 remlen = block->binbuf;
431 rec->Block = block->BlockNumber;
432 rec->File = ((DEVICE *)block->dev)->file;
434 /* Clear state flags */
436 if (block->dev->is_tape()) {
437 rec->state |= REC_ISTAPE;
442 * Get the header. There is always a full header,
443 * otherwise we find it in the next block.
445 Dmsg3(450, "Block=%d Ver=%d size=%u\n", block->BlockNumber, block->BlockVer,
447 if (block->BlockVer == 1) {
448 rhl = RECHDR1_LENGTH;
450 rhl = RECHDR2_LENGTH;
453 Dmsg4(450, "Enter read_record_block: remlen=%d data_len=%d rem=%d blkver=%d\n",
454 remlen, rec->data_len, rec->remainder, block->BlockVer);
456 unser_begin(block->bufp, WRITE_RECHDR_LENGTH);
457 if (block->BlockVer == 1) {
458 unser_uint32(VolSessionId);
459 unser_uint32(VolSessionTime);
461 VolSessionId = block->VolSessionId;
462 VolSessionTime = block->VolSessionTime;
464 unser_int32(FileIndex);
466 unser_uint32(data_bytes);
469 block->binbuf -= rhl;
472 /* If we are looking for more (remainder!=0), we reject anything
473 * where the VolSessionId and VolSessionTime don't agree
475 if (rec->remainder && (rec->VolSessionId != VolSessionId ||
476 rec->VolSessionTime != VolSessionTime)) {
477 rec->state |= REC_NO_MATCH;
478 Dmsg0(450, "remainder and VolSession doesn't match\n");
479 return false; /* This is from some other Session */
482 /* if Stream is negative, it means that this is a continuation
483 * of a previous partially written record.
485 if (Stream < 0) { /* continuation record? */
486 Dmsg1(500, "Got negative Stream => continuation. remainder=%d\n",
488 rec->state |= REC_CONTINUATION;
489 if (!rec->remainder) { /* if we didn't read previously */
490 rec->data_len = 0; /* return data as if no continuation */
491 } else if (rec->Stream != -Stream) {
492 rec->state |= REC_NO_MATCH;
493 return false; /* This is from some other Session */
495 rec->Stream = -Stream; /* set correct Stream */
496 } else { /* Regular record */
497 rec->Stream = Stream;
498 rec->data_len = 0; /* transfer to beginning of data */
500 rec->VolSessionId = VolSessionId;
501 rec->VolSessionTime = VolSessionTime;
502 rec->FileIndex = FileIndex;
504 if (block->FirstIndex == 0) {
505 block->FirstIndex = FileIndex;
507 block->LastIndex = FileIndex;
510 Dmsg6(450, "rd_rec_blk() got FI=%s SessId=%d Strm=%s len=%u\n"
511 "remlen=%d data_len=%d\n",
512 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
513 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), data_bytes, remlen,
517 * No more records in this block because the number
518 * of remaining bytes are less than a record header
519 * length, so return empty handed, but indicate that
520 * he must read again. By returning, we allow the
521 * higher level routine to fetch the next block and
524 Dmsg0(450, "read_record_block: nothing\n");
525 rec->state |= (REC_NO_HEADER | REC_BLOCK_EMPTY);
526 empty_block(block); /* mark block empty */
530 ASSERT(data_bytes < MAX_BLOCK_LENGTH); /* temp sanity check */
532 rec->data = check_pool_memory_size(rec->data, rec->data_len+data_bytes);
535 * At this point, we have read the header, now we
536 * must transfer as much of the data record as
537 * possible taking into account: 1. A partial
538 * data record may have previously been transferred,
539 * 2. The current block may not contain the whole data
542 if (remlen >= data_bytes) {
543 /* Got whole record */
544 memcpy(rec->data+rec->data_len, block->bufp, data_bytes);
545 block->bufp += data_bytes;
546 block->binbuf -= data_bytes;
547 rec->data_len += data_bytes;
550 memcpy(rec->data+rec->data_len, block->bufp, remlen);
551 block->bufp += remlen;
552 block->binbuf -= remlen;
553 rec->data_len += remlen;
554 rec->remainder = 1; /* partial record transferred */
555 Dmsg1(450, "read_record_block: partial xfered=%d\n", rec->data_len);
556 rec->state |= (REC_PARTIAL_RECORD | REC_BLOCK_EMPTY);
560 Dmsg4(450, "Rtn full rd_rec_blk FI=%s SessId=%d Strm=%s len=%d\n",
561 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
562 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len);
563 return true; /* transferred full record */