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_UNIX_ATTRIBUTES:
122 case -STREAM_FILE_DATA:
124 case -STREAM_WIN32_DATA:
125 return "contWIN32-DATA";
126 case -STREAM_WIN32_GZIP_DATA:
127 return "contWIN32-GZIP";
128 case -STREAM_MD5_DIGEST:
130 case -STREAM_SHA1_DIGEST:
132 case -STREAM_GZIP_DATA:
134 case -STREAM_UNIX_ATTRIBUTES_EX:
135 return "contUNIX-ATTR-EX";
136 case -STREAM_SPARSE_DATA:
137 return "contSPARSE-DATA";
138 case -STREAM_SPARSE_GZIP_DATA:
139 return "contSPARSE-GZIP";
140 case -STREAM_PROGRAM_NAMES:
141 return "contPROG-NAMES";
142 case -STREAM_PROGRAM_DATA:
143 return "contPROG-DATA";
144 case -STREAM_MACOS_FORK_DATA:
145 return "contMACOS-RSRC";
146 case -STREAM_HFSPLUS_ATTRIBUTES:
147 return "contHFSPLUS-ATTR";
148 case -STREAM_SHA256_DIGEST:
150 case -STREAM_SHA512_DIGEST:
152 case -STREAM_SIGNED_DIGEST:
153 return "contSIGNED-DIGEST";
154 case -STREAM_ENCRYPTED_SESSION_DATA:
155 return "contENCRYPTED-SESSION-DATA";
157 sprintf(buf, "%d", stream);
163 * Return a new record entity
165 DEV_RECORD *new_record(void)
169 rec = (DEV_RECORD *)get_memory(sizeof(DEV_RECORD));
170 memset(rec, 0, sizeof(DEV_RECORD));
171 rec->data = get_pool_memory(PM_MESSAGE);
175 void empty_record(DEV_RECORD *rec)
177 rec->File = rec->Block = 0;
178 rec->VolSessionId = rec->VolSessionTime = 0;
179 rec->FileIndex = rec->Stream = 0;
180 rec->data_len = rec->remainder = 0;
181 rec->state &= ~(REC_PARTIAL_RECORD|REC_BLOCK_EMPTY|REC_NO_MATCH|REC_CONTINUATION);
185 * Free the record entity
188 void free_record(DEV_RECORD *rec)
190 Dmsg0(950, "Enter free_record.\n");
192 free_pool_memory(rec->data);
194 Dmsg0(950, "Data buf is freed.\n");
195 free_pool_memory((POOLMEM *)rec);
196 Dmsg0(950, "Leave free_record.\n");
201 * Write a Record to the block
203 * Returns: false on failure (none or partially written)
204 * true on success (all bytes written)
206 * and remainder returned in packet.
208 * We require enough room for the header, and we deal with
209 * two special cases. 1. Only part of the record may have
210 * been transferred the last time (when remainder is
211 * non-zero), and 2. The remaining bytes to write may not
212 * all fit into the block.
214 bool write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec)
218 char buf1[100], buf2[100];
220 remlen = block->buf_len - block->binbuf;
222 ASSERT(block->binbuf == (uint32_t) (block->bufp - block->buf));
223 ASSERT(block->buf_len >= block->binbuf);
225 Dmsg6(890, "write_record_to_block() FI=%s SessId=%d Strm=%s len=%d\n"
226 "rem=%d remainder=%d\n",
227 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
228 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
229 remlen, rec->remainder);
232 * If rec->remainder is non-zero, we have been called a
233 * second (or subsequent) time to finish writing a record
234 * that did not previously fit into the block.
236 if (rec->remainder == 0) {
237 /* Require enough room to write a full header */
238 if (remlen >= WRITE_RECHDR_LENGTH) {
239 ser_begin(block->bufp, WRITE_RECHDR_LENGTH);
240 if (BLOCK_VER == 1) {
241 ser_uint32(rec->VolSessionId);
242 ser_uint32(rec->VolSessionTime);
244 block->VolSessionId = rec->VolSessionId;
245 block->VolSessionTime = rec->VolSessionTime;
247 ser_int32(rec->FileIndex);
248 ser_int32(rec->Stream);
249 ser_uint32(rec->data_len);
251 block->bufp += WRITE_RECHDR_LENGTH;
252 block->binbuf += WRITE_RECHDR_LENGTH;
253 remlen -= WRITE_RECHDR_LENGTH;
254 rec->remainder = rec->data_len;
255 if (rec->FileIndex > 0) {
256 /* If data record, update what we have in this block */
257 if (block->FirstIndex == 0) {
258 block->FirstIndex = rec->FileIndex;
260 block->LastIndex = rec->FileIndex;
263 rec->remainder = rec->data_len + WRITE_RECHDR_LENGTH;
268 * We are here to write unwritten bytes from a previous
269 * time. Presumably we have a new buffer (possibly
270 * containing a volume label), so the new header
271 * should be able to fit in the block -- otherwise we have
272 * an error. Note, we have to continue splitting the
273 * data record if it is longer than the block.
275 * First, write the header, then write as much as
276 * possible of the data record.
278 * Every time we write a header and it is a continuation
279 * of a previous partially written record, we store the
280 * Stream as -Stream in the record header.
282 ser_begin(block->bufp, WRITE_RECHDR_LENGTH);
283 if (BLOCK_VER == 1) {
284 ser_uint32(rec->VolSessionId);
285 ser_uint32(rec->VolSessionTime);
287 block->VolSessionId = rec->VolSessionId;
288 block->VolSessionTime = rec->VolSessionTime;
290 ser_int32(rec->FileIndex);
291 if (rec->remainder > rec->data_len) {
292 ser_int32(rec->Stream); /* normal full header */
293 ser_uint32(rec->data_len);
294 rec->remainder = rec->data_len; /* must still do data record */
296 ser_int32(-rec->Stream); /* mark this as a continuation record */
297 ser_uint32(rec->remainder); /* bytes to do */
300 /* Require enough room to write a full header */
301 ASSERT(remlen >= WRITE_RECHDR_LENGTH);
303 block->bufp += WRITE_RECHDR_LENGTH;
304 block->binbuf += WRITE_RECHDR_LENGTH;
305 remlen -= WRITE_RECHDR_LENGTH;
306 if (rec->FileIndex > 0) {
307 /* If data record, update what we have in this block */
308 if (block->FirstIndex == 0) {
309 block->FirstIndex = rec->FileIndex;
311 block->LastIndex = rec->FileIndex;
315 return false; /* partial transfer */
319 * Now deal with data record.
320 * Part of it may have already been transferred, and we
321 * may not have enough room to transfer the whole this time.
323 if (rec->remainder > 0) {
324 /* Write as much of data as possible */
325 if (remlen >= rec->remainder) {
326 memcpy(block->bufp, rec->data+rec->data_len-rec->remainder,
328 block->bufp += rec->remainder;
329 block->binbuf += rec->remainder;
331 memcpy(block->bufp, rec->data+rec->data_len-rec->remainder,
334 if (!sm_check_rtn(__FILE__, __LINE__, False)) {
335 /* We damaged a buffer */
336 Dmsg6(0, "Damaged block FI=%s SessId=%d Strm=%s len=%d\n"
337 "rem=%d remainder=%d\n",
338 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
339 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
340 remlen, rec->remainder);
341 Dmsg5(0, "Damaged block: bufp=%x binbuf=%d buf_len=%d rem=%d moved=%d\n",
342 block->bufp, block->binbuf, block->buf_len, block->buf_len-block->binbuf,
344 Dmsg2(0, "Damaged block: buf=%x binbuffrombuf=%d \n",
345 block->buf, block->bufp-block->buf);
347 Emsg0(M_ABORT, 0, _("Damaged buffer\n"));
351 block->bufp += remlen;
352 block->binbuf += remlen;
353 rec->remainder -= remlen;
354 return false; /* did partial transfer */
357 rec->remainder = 0; /* did whole transfer */
363 * Test if we can write whole record to the block
365 * Returns: false on failure
366 * true on success (all bytes can be written)
368 bool can_write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec)
372 remlen = block->buf_len - block->binbuf;
373 if (rec->remainder == 0) {
374 if (remlen >= WRITE_RECHDR_LENGTH) {
375 remlen -= WRITE_RECHDR_LENGTH;
376 rec->remainder = rec->data_len;
383 if (rec->remainder > 0 && remlen < rec->remainder) {
391 * Read a Record from the block
392 * Returns: false if nothing read or if the continuation record does not match.
393 * In both of these cases, a block read must be done.
394 * true if at least the record header was read, this
395 * routine may have to be called again with a new
396 * block if the entire record was not read.
398 bool read_record_from_block(DEV_BLOCK *block, DEV_RECORD *rec)
402 uint32_t VolSessionId;
403 uint32_t VolSessionTime;
408 char buf1[100], buf2[100];
410 remlen = block->binbuf;
411 rec->Block = block->BlockNumber;
412 rec->File = ((DEVICE *)block->dev)->file;
414 /* Clear state flags */
416 if (block->dev->is_tape()) {
417 rec->state |= REC_ISTAPE;
422 * Get the header. There is always a full header,
423 * otherwise we find it in the next block.
425 Dmsg3(450, "Block=%d Ver=%d size=%u\n", block->BlockNumber, block->BlockVer,
427 if (block->BlockVer == 1) {
428 rhl = RECHDR1_LENGTH;
430 rhl = RECHDR2_LENGTH;
433 Dmsg4(450, "Enter read_record_block: remlen=%d data_len=%d rem=%d blkver=%d\n",
434 remlen, rec->data_len, rec->remainder, block->BlockVer);
436 unser_begin(block->bufp, WRITE_RECHDR_LENGTH);
437 if (block->BlockVer == 1) {
438 unser_uint32(VolSessionId);
439 unser_uint32(VolSessionTime);
441 VolSessionId = block->VolSessionId;
442 VolSessionTime = block->VolSessionTime;
444 unser_int32(FileIndex);
446 unser_uint32(data_bytes);
449 block->binbuf -= rhl;
452 /* If we are looking for more (remainder!=0), we reject anything
453 * where the VolSessionId and VolSessionTime don't agree
455 if (rec->remainder && (rec->VolSessionId != VolSessionId ||
456 rec->VolSessionTime != VolSessionTime)) {
457 rec->state |= REC_NO_MATCH;
458 Dmsg0(450, "remainder and VolSession doesn't match\n");
459 return false; /* This is from some other Session */
462 /* if Stream is negative, it means that this is a continuation
463 * of a previous partially written record.
465 if (Stream < 0) { /* continuation record? */
466 Dmsg1(500, "Got negative Stream => continuation. remainder=%d\n",
468 rec->state |= REC_CONTINUATION;
469 if (!rec->remainder) { /* if we didn't read previously */
470 rec->data_len = 0; /* return data as if no continuation */
471 } else if (rec->Stream != -Stream) {
472 rec->state |= REC_NO_MATCH;
473 return false; /* This is from some other Session */
475 rec->Stream = -Stream; /* set correct Stream */
476 } else { /* Regular record */
477 rec->Stream = Stream;
478 rec->data_len = 0; /* transfer to beginning of data */
480 rec->VolSessionId = VolSessionId;
481 rec->VolSessionTime = VolSessionTime;
482 rec->FileIndex = FileIndex;
484 if (block->FirstIndex == 0) {
485 block->FirstIndex = FileIndex;
487 block->LastIndex = FileIndex;
490 Dmsg6(450, "rd_rec_blk() got FI=%s SessId=%d Strm=%s len=%u\n"
491 "remlen=%d data_len=%d\n",
492 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
493 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), data_bytes, remlen,
497 * No more records in this block because the number
498 * of remaining bytes are less than a record header
499 * length, so return empty handed, but indicate that
500 * he must read again. By returning, we allow the
501 * higher level routine to fetch the next block and
504 Dmsg0(450, "read_record_block: nothing\n");
505 rec->state |= (REC_NO_HEADER | REC_BLOCK_EMPTY);
506 empty_block(block); /* mark block empty */
510 ASSERT(data_bytes < MAX_BLOCK_LENGTH); /* temp sanity check */
512 rec->data = check_pool_memory_size(rec->data, rec->data_len+data_bytes);
515 * At this point, we have read the header, now we
516 * must transfer as much of the data record as
517 * possible taking into account: 1. A partial
518 * data record may have previously been transferred,
519 * 2. The current block may not contain the whole data
522 if (remlen >= data_bytes) {
523 /* Got whole record */
524 memcpy(rec->data+rec->data_len, block->bufp, data_bytes);
525 block->bufp += data_bytes;
526 block->binbuf -= data_bytes;
527 rec->data_len += data_bytes;
530 memcpy(rec->data+rec->data_len, block->bufp, remlen);
531 block->bufp += remlen;
532 block->binbuf -= remlen;
533 rec->data_len += remlen;
534 rec->remainder = 1; /* partial record transferred */
535 Dmsg1(450, "read_record_block: partial xfered=%d\n", rec->data_len);
536 rec->state |= (REC_PARTIAL_RECORD | REC_BLOCK_EMPTY);
540 Dmsg4(450, "Rtn full rd_rec_blk FI=%s SessId=%d Strm=%s len=%d\n",
541 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
542 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len);
543 return true; /* transferred full record */