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.
31 * Convert a FileIndex into a printable
32 * ASCII string. Not reentrant.
33 * If the FileIndex is negative, it flags the
34 * record as a Label, otherwise it is simply
35 * the FileIndex of the current file.
37 const char *FI_to_ascii(char *buf, int fi)
40 sprintf(buf, "%d", fi);
58 sprintf(buf, _("unknown: %d"), fi);
65 * Convert a Stream ID into a printable
66 * ASCII string. Not reentrant.
68 * A negative stream number represents
69 * stream data that is continued from a
70 * record in the previous block.
71 * If the FileIndex is negative, we are
72 * dealing with a Label, hence the
73 * stream is the JobId.
75 const char *stream_to_ascii(char *buf, int stream, int fi)
78 sprintf(buf, "%d", stream);
82 case STREAM_UNIX_ATTRIBUTES:
84 case STREAM_FILE_DATA:
86 case STREAM_WIN32_DATA:
88 case STREAM_WIN32_GZIP_DATA:
90 case STREAM_MD5_DIGEST:
92 case STREAM_SHA1_DIGEST:
94 case STREAM_GZIP_DATA:
96 case STREAM_UNIX_ATTRIBUTES_EX:
97 return "UNIX-ATTR-EX";
98 case STREAM_SPARSE_DATA:
100 case STREAM_SPARSE_GZIP_DATA:
101 return "SPARSE-GZIP";
102 case STREAM_PROGRAM_NAMES:
104 case STREAM_PROGRAM_DATA:
106 case STREAM_MACOS_FORK_DATA:
108 case STREAM_HFSPLUS_ATTRIBUTES:
109 return "HFSPLUS-ATTR";
110 case STREAM_SHA256_DIGEST:
112 case STREAM_SHA512_DIGEST:
114 case STREAM_SIGNED_DIGEST:
115 return "SIGNED-DIGEST";
116 case STREAM_ENCRYPTED_SESSION_DATA:
117 return "ENCRYPTED-SESSION-DATA";
118 case STREAM_ENCRYPTED_FILE_DATA:
119 return "ENCRYPTED-FILE";
120 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
121 return "ENCRYPTED-GZIP";
122 case STREAM_ENCRYPTED_WIN32_DATA:
123 return "ENCRYPTED-WIN32-DATA";
124 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
125 return "ENCRYPTED-WIN32-GZIP";
126 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
127 return "ENCRYPTED-MACOS-RSRC";
128 case -STREAM_UNIX_ATTRIBUTES:
130 case -STREAM_FILE_DATA:
132 case -STREAM_WIN32_DATA:
133 return "contWIN32-DATA";
134 case -STREAM_WIN32_GZIP_DATA:
135 return "contWIN32-GZIP";
136 case -STREAM_MD5_DIGEST:
138 case -STREAM_SHA1_DIGEST:
140 case -STREAM_GZIP_DATA:
142 case -STREAM_UNIX_ATTRIBUTES_EX:
143 return "contUNIX-ATTR-EX";
144 case -STREAM_SPARSE_DATA:
145 return "contSPARSE-DATA";
146 case -STREAM_SPARSE_GZIP_DATA:
147 return "contSPARSE-GZIP";
148 case -STREAM_PROGRAM_NAMES:
149 return "contPROG-NAMES";
150 case -STREAM_PROGRAM_DATA:
151 return "contPROG-DATA";
152 case -STREAM_MACOS_FORK_DATA:
153 return "contMACOS-RSRC";
154 case -STREAM_HFSPLUS_ATTRIBUTES:
155 return "contHFSPLUS-ATTR";
156 case -STREAM_SHA256_DIGEST:
158 case -STREAM_SHA512_DIGEST:
160 case -STREAM_SIGNED_DIGEST:
161 return "contSIGNED-DIGEST";
162 case -STREAM_ENCRYPTED_SESSION_DATA:
163 return "contENCRYPTED-SESSION-DATA";
164 case -STREAM_ENCRYPTED_FILE_DATA:
165 return "contENCRYPTED-FILE";
166 case -STREAM_ENCRYPTED_FILE_GZIP_DATA:
167 return "contENCRYPTED-GZIP";
168 case -STREAM_ENCRYPTED_WIN32_DATA:
169 return "contENCRYPTED-WIN32-DATA";
170 case -STREAM_ENCRYPTED_WIN32_GZIP_DATA:
171 return "contENCRYPTED-WIN32-GZIP";
172 case -STREAM_ENCRYPTED_MACOS_FORK_DATA:
173 return "contENCRYPTED-MACOS-RSRC";
175 sprintf(buf, "%d", stream);
181 * Return a new record entity
183 DEV_RECORD *new_record(void)
187 rec = (DEV_RECORD *)get_memory(sizeof(DEV_RECORD));
188 memset(rec, 0, sizeof(DEV_RECORD));
189 rec->data = get_pool_memory(PM_MESSAGE);
193 void empty_record(DEV_RECORD *rec)
195 rec->File = rec->Block = 0;
196 rec->VolSessionId = rec->VolSessionTime = 0;
197 rec->FileIndex = rec->Stream = 0;
198 rec->data_len = rec->remainder = 0;
199 rec->state &= ~(REC_PARTIAL_RECORD|REC_BLOCK_EMPTY|REC_NO_MATCH|REC_CONTINUATION);
203 * Free the record entity
206 void free_record(DEV_RECORD *rec)
208 Dmsg0(950, "Enter free_record.\n");
210 free_pool_memory(rec->data);
212 Dmsg0(950, "Data buf is freed.\n");
213 free_pool_memory((POOLMEM *)rec);
214 Dmsg0(950, "Leave free_record.\n");
219 * Write a Record to the block
221 * Returns: false on failure (none or partially written)
222 * true on success (all bytes written)
224 * and remainder returned in packet.
226 * We require enough room for the header, and we deal with
227 * two special cases. 1. Only part of the record may have
228 * been transferred the last time (when remainder is
229 * non-zero), and 2. The remaining bytes to write may not
230 * all fit into the block.
232 bool write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec)
236 char buf1[100], buf2[100];
238 remlen = block->buf_len - block->binbuf;
240 ASSERT(block->binbuf == (uint32_t) (block->bufp - block->buf));
241 ASSERT(block->buf_len >= block->binbuf);
243 Dmsg6(890, "write_record_to_block() FI=%s SessId=%d Strm=%s len=%d\n"
244 "rem=%d remainder=%d\n",
245 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
246 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
247 remlen, rec->remainder);
250 * If rec->remainder is non-zero, we have been called a
251 * second (or subsequent) time to finish writing a record
252 * that did not previously fit into the block.
254 if (rec->remainder == 0) {
255 /* Require enough room to write a full header */
256 if (remlen >= WRITE_RECHDR_LENGTH) {
257 ser_begin(block->bufp, WRITE_RECHDR_LENGTH);
258 if (BLOCK_VER == 1) {
259 ser_uint32(rec->VolSessionId);
260 ser_uint32(rec->VolSessionTime);
262 block->VolSessionId = rec->VolSessionId;
263 block->VolSessionTime = rec->VolSessionTime;
265 ser_int32(rec->FileIndex);
266 ser_int32(rec->Stream);
267 ser_uint32(rec->data_len);
269 block->bufp += WRITE_RECHDR_LENGTH;
270 block->binbuf += WRITE_RECHDR_LENGTH;
271 remlen -= WRITE_RECHDR_LENGTH;
272 rec->remainder = rec->data_len;
273 if (rec->FileIndex > 0) {
274 /* If data record, update what we have in this block */
275 if (block->FirstIndex == 0) {
276 block->FirstIndex = rec->FileIndex;
278 block->LastIndex = rec->FileIndex;
281 rec->remainder = rec->data_len + WRITE_RECHDR_LENGTH;
286 * We are here to write unwritten bytes from a previous
287 * time. Presumably we have a new buffer (possibly
288 * containing a volume label), so the new header
289 * should be able to fit in the block -- otherwise we have
290 * an error. Note, we have to continue splitting the
291 * data record if it is longer than the block.
293 * First, write the header, then write as much as
294 * possible of the data record.
296 * Every time we write a header and it is a continuation
297 * of a previous partially written record, we store the
298 * Stream as -Stream in the record header.
300 ser_begin(block->bufp, WRITE_RECHDR_LENGTH);
301 if (BLOCK_VER == 1) {
302 ser_uint32(rec->VolSessionId);
303 ser_uint32(rec->VolSessionTime);
305 block->VolSessionId = rec->VolSessionId;
306 block->VolSessionTime = rec->VolSessionTime;
308 ser_int32(rec->FileIndex);
309 if (rec->remainder > rec->data_len) {
310 ser_int32(rec->Stream); /* normal full header */
311 ser_uint32(rec->data_len);
312 rec->remainder = rec->data_len; /* must still do data record */
314 ser_int32(-rec->Stream); /* mark this as a continuation record */
315 ser_uint32(rec->remainder); /* bytes to do */
318 /* Require enough room to write a full header */
319 ASSERT(remlen >= WRITE_RECHDR_LENGTH);
321 block->bufp += WRITE_RECHDR_LENGTH;
322 block->binbuf += WRITE_RECHDR_LENGTH;
323 remlen -= WRITE_RECHDR_LENGTH;
324 if (rec->FileIndex > 0) {
325 /* If data record, update what we have in this block */
326 if (block->FirstIndex == 0) {
327 block->FirstIndex = rec->FileIndex;
329 block->LastIndex = rec->FileIndex;
333 return false; /* partial transfer */
337 * Now deal with data record.
338 * Part of it may have already been transferred, and we
339 * may not have enough room to transfer the whole this time.
341 if (rec->remainder > 0) {
342 /* Write as much of data as possible */
343 if (remlen >= rec->remainder) {
344 memcpy(block->bufp, rec->data+rec->data_len-rec->remainder,
346 block->bufp += rec->remainder;
347 block->binbuf += rec->remainder;
349 memcpy(block->bufp, rec->data+rec->data_len-rec->remainder,
352 if (!sm_check_rtn(__FILE__, __LINE__, False)) {
353 /* We damaged a buffer */
354 Dmsg6(0, "Damaged block FI=%s SessId=%d Strm=%s len=%d\n"
355 "rem=%d remainder=%d\n",
356 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
357 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
358 remlen, rec->remainder);
359 Dmsg5(0, "Damaged block: bufp=%x binbuf=%d buf_len=%d rem=%d moved=%d\n",
360 block->bufp, block->binbuf, block->buf_len, block->buf_len-block->binbuf,
362 Dmsg2(0, "Damaged block: buf=%x binbuffrombuf=%d \n",
363 block->buf, block->bufp-block->buf);
365 Emsg0(M_ABORT, 0, _("Damaged buffer\n"));
369 block->bufp += remlen;
370 block->binbuf += remlen;
371 rec->remainder -= remlen;
372 return false; /* did partial transfer */
375 rec->remainder = 0; /* did whole transfer */
381 * Test if we can write whole record to the block
383 * Returns: false on failure
384 * true on success (all bytes can be written)
386 bool can_write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec)
390 remlen = block->buf_len - block->binbuf;
391 if (rec->remainder == 0) {
392 if (remlen >= WRITE_RECHDR_LENGTH) {
393 remlen -= WRITE_RECHDR_LENGTH;
394 rec->remainder = rec->data_len;
401 if (rec->remainder > 0 && remlen < rec->remainder) {
409 * Read a Record from the block
410 * Returns: false if nothing read or if the continuation record does not match.
411 * In both of these cases, a block read must be done.
412 * true if at least the record header was read, this
413 * routine may have to be called again with a new
414 * block if the entire record was not read.
416 bool read_record_from_block(DEV_BLOCK *block, DEV_RECORD *rec)
420 uint32_t VolSessionId;
421 uint32_t VolSessionTime;
426 char buf1[100], buf2[100];
428 remlen = block->binbuf;
429 rec->Block = block->BlockNumber;
430 rec->File = ((DEVICE *)block->dev)->file;
432 /* Clear state flags */
434 if (block->dev->is_tape()) {
435 rec->state |= REC_ISTAPE;
440 * Get the header. There is always a full header,
441 * otherwise we find it in the next block.
443 Dmsg3(450, "Block=%d Ver=%d size=%u\n", block->BlockNumber, block->BlockVer,
445 if (block->BlockVer == 1) {
446 rhl = RECHDR1_LENGTH;
448 rhl = RECHDR2_LENGTH;
451 Dmsg4(450, "Enter read_record_block: remlen=%d data_len=%d rem=%d blkver=%d\n",
452 remlen, rec->data_len, rec->remainder, block->BlockVer);
454 unser_begin(block->bufp, WRITE_RECHDR_LENGTH);
455 if (block->BlockVer == 1) {
456 unser_uint32(VolSessionId);
457 unser_uint32(VolSessionTime);
459 VolSessionId = block->VolSessionId;
460 VolSessionTime = block->VolSessionTime;
462 unser_int32(FileIndex);
464 unser_uint32(data_bytes);
467 block->binbuf -= rhl;
470 /* If we are looking for more (remainder!=0), we reject anything
471 * where the VolSessionId and VolSessionTime don't agree
473 if (rec->remainder && (rec->VolSessionId != VolSessionId ||
474 rec->VolSessionTime != VolSessionTime)) {
475 rec->state |= REC_NO_MATCH;
476 Dmsg0(450, "remainder and VolSession doesn't match\n");
477 return false; /* This is from some other Session */
480 /* if Stream is negative, it means that this is a continuation
481 * of a previous partially written record.
483 if (Stream < 0) { /* continuation record? */
484 Dmsg1(500, "Got negative Stream => continuation. remainder=%d\n",
486 rec->state |= REC_CONTINUATION;
487 if (!rec->remainder) { /* if we didn't read previously */
488 rec->data_len = 0; /* return data as if no continuation */
489 } else if (rec->Stream != -Stream) {
490 rec->state |= REC_NO_MATCH;
491 return false; /* This is from some other Session */
493 rec->Stream = -Stream; /* set correct Stream */
494 } else { /* Regular record */
495 rec->Stream = Stream;
496 rec->data_len = 0; /* transfer to beginning of data */
498 rec->VolSessionId = VolSessionId;
499 rec->VolSessionTime = VolSessionTime;
500 rec->FileIndex = FileIndex;
502 if (block->FirstIndex == 0) {
503 block->FirstIndex = FileIndex;
505 block->LastIndex = FileIndex;
508 Dmsg6(450, "rd_rec_blk() got FI=%s SessId=%d Strm=%s len=%u\n"
509 "remlen=%d data_len=%d\n",
510 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
511 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), data_bytes, remlen,
515 * No more records in this block because the number
516 * of remaining bytes are less than a record header
517 * length, so return empty handed, but indicate that
518 * he must read again. By returning, we allow the
519 * higher level routine to fetch the next block and
522 Dmsg0(450, "read_record_block: nothing\n");
523 rec->state |= (REC_NO_HEADER | REC_BLOCK_EMPTY);
524 empty_block(block); /* mark block empty */
528 ASSERT(data_bytes < MAX_BLOCK_LENGTH); /* temp sanity check */
530 rec->data = check_pool_memory_size(rec->data, rec->data_len+data_bytes);
533 * At this point, we have read the header, now we
534 * must transfer as much of the data record as
535 * possible taking into account: 1. A partial
536 * data record may have previously been transferred,
537 * 2. The current block may not contain the whole data
540 if (remlen >= data_bytes) {
541 /* Got whole record */
542 memcpy(rec->data+rec->data_len, block->bufp, data_bytes);
543 block->bufp += data_bytes;
544 block->binbuf -= data_bytes;
545 rec->data_len += data_bytes;
548 memcpy(rec->data+rec->data_len, block->bufp, remlen);
549 block->bufp += remlen;
550 block->binbuf -= remlen;
551 rec->data_len += remlen;
552 rec->remainder = 1; /* partial record transferred */
553 Dmsg1(450, "read_record_block: partial xfered=%d\n", rec->data_len);
554 rec->state |= (REC_PARTIAL_RECORD | REC_BLOCK_EMPTY);
558 Dmsg4(450, "Rtn full rd_rec_blk FI=%s SessId=%d Strm=%s len=%d\n",
559 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
560 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len);
561 return true; /* transferred full record */