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_UNIX_ATTRIBUTES:
120 case -STREAM_FILE_DATA:
122 case -STREAM_WIN32_DATA:
123 return "contWIN32-DATA";
124 case -STREAM_WIN32_GZIP_DATA:
125 return "contWIN32-GZIP";
126 case -STREAM_MD5_DIGEST:
128 case -STREAM_SHA1_DIGEST:
130 case -STREAM_GZIP_DATA:
132 case -STREAM_UNIX_ATTRIBUTES_EX:
133 return "contUNIX-ATTR-EX";
134 case -STREAM_SPARSE_DATA:
135 return "contSPARSE-DATA";
136 case -STREAM_SPARSE_GZIP_DATA:
137 return "contSPARSE-GZIP";
138 case -STREAM_PROGRAM_NAMES:
139 return "contPROG-NAMES";
140 case -STREAM_PROGRAM_DATA:
141 return "contPROG-DATA";
142 case -STREAM_MACOS_FORK_DATA:
143 return "contMACOS-RSRC";
144 case -STREAM_HFSPLUS_ATTRIBUTES:
145 return "contHFSPLUS-ATTR";
146 case -STREAM_SHA256_DIGEST:
148 case -STREAM_SHA512_DIGEST:
150 case -STREAM_SIGNED_DIGEST:
151 return "contSIGNED-DIGEST";
153 sprintf(buf, "%d", stream);
159 * Return a new record entity
161 DEV_RECORD *new_record(void)
165 rec = (DEV_RECORD *)get_memory(sizeof(DEV_RECORD));
166 memset(rec, 0, sizeof(DEV_RECORD));
167 rec->data = get_pool_memory(PM_MESSAGE);
171 void empty_record(DEV_RECORD *rec)
173 rec->File = rec->Block = 0;
174 rec->VolSessionId = rec->VolSessionTime = 0;
175 rec->FileIndex = rec->Stream = 0;
176 rec->data_len = rec->remainder = 0;
177 rec->state &= ~(REC_PARTIAL_RECORD|REC_BLOCK_EMPTY|REC_NO_MATCH|REC_CONTINUATION);
181 * Free the record entity
184 void free_record(DEV_RECORD *rec)
186 Dmsg0(950, "Enter free_record.\n");
188 free_pool_memory(rec->data);
190 Dmsg0(950, "Data buf is freed.\n");
191 free_pool_memory((POOLMEM *)rec);
192 Dmsg0(950, "Leave free_record.\n");
197 * Write a Record to the block
199 * Returns: false on failure (none or partially written)
200 * true on success (all bytes written)
202 * and remainder returned in packet.
204 * We require enough room for the header, and we deal with
205 * two special cases. 1. Only part of the record may have
206 * been transferred the last time (when remainder is
207 * non-zero), and 2. The remaining bytes to write may not
208 * all fit into the block.
210 bool write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec)
214 char buf1[100], buf2[100];
216 remlen = block->buf_len - block->binbuf;
218 ASSERT(block->binbuf == (uint32_t) (block->bufp - block->buf));
219 ASSERT(block->buf_len >= block->binbuf);
221 Dmsg6(890, "write_record_to_block() FI=%s SessId=%d Strm=%s len=%d\n"
222 "rem=%d remainder=%d\n",
223 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
224 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
225 remlen, rec->remainder);
228 * If rec->remainder is non-zero, we have been called a
229 * second (or subsequent) time to finish writing a record
230 * that did not previously fit into the block.
232 if (rec->remainder == 0) {
233 /* Require enough room to write a full header */
234 if (remlen >= WRITE_RECHDR_LENGTH) {
235 ser_begin(block->bufp, WRITE_RECHDR_LENGTH);
236 if (BLOCK_VER == 1) {
237 ser_uint32(rec->VolSessionId);
238 ser_uint32(rec->VolSessionTime);
240 block->VolSessionId = rec->VolSessionId;
241 block->VolSessionTime = rec->VolSessionTime;
243 ser_int32(rec->FileIndex);
244 ser_int32(rec->Stream);
245 ser_uint32(rec->data_len);
247 block->bufp += WRITE_RECHDR_LENGTH;
248 block->binbuf += WRITE_RECHDR_LENGTH;
249 remlen -= WRITE_RECHDR_LENGTH;
250 rec->remainder = rec->data_len;
251 if (rec->FileIndex > 0) {
252 /* If data record, update what we have in this block */
253 if (block->FirstIndex == 0) {
254 block->FirstIndex = rec->FileIndex;
256 block->LastIndex = rec->FileIndex;
259 rec->remainder = rec->data_len + WRITE_RECHDR_LENGTH;
264 * We are here to write unwritten bytes from a previous
265 * time. Presumably we have a new buffer (possibly
266 * containing a volume label), so the new header
267 * should be able to fit in the block -- otherwise we have
268 * an error. Note, we have to continue splitting the
269 * data record if it is longer than the block.
271 * First, write the header, then write as much as
272 * possible of the data record.
274 * Every time we write a header and it is a continuation
275 * of a previous partially written record, we store the
276 * Stream as -Stream in the record header.
278 ser_begin(block->bufp, WRITE_RECHDR_LENGTH);
279 if (BLOCK_VER == 1) {
280 ser_uint32(rec->VolSessionId);
281 ser_uint32(rec->VolSessionTime);
283 block->VolSessionId = rec->VolSessionId;
284 block->VolSessionTime = rec->VolSessionTime;
286 ser_int32(rec->FileIndex);
287 if (rec->remainder > rec->data_len) {
288 ser_int32(rec->Stream); /* normal full header */
289 ser_uint32(rec->data_len);
290 rec->remainder = rec->data_len; /* must still do data record */
292 ser_int32(-rec->Stream); /* mark this as a continuation record */
293 ser_uint32(rec->remainder); /* bytes to do */
296 /* Require enough room to write a full header */
297 ASSERT(remlen >= WRITE_RECHDR_LENGTH);
299 block->bufp += WRITE_RECHDR_LENGTH;
300 block->binbuf += WRITE_RECHDR_LENGTH;
301 remlen -= WRITE_RECHDR_LENGTH;
302 if (rec->FileIndex > 0) {
303 /* If data record, update what we have in this block */
304 if (block->FirstIndex == 0) {
305 block->FirstIndex = rec->FileIndex;
307 block->LastIndex = rec->FileIndex;
311 return false; /* partial transfer */
315 * Now deal with data record.
316 * Part of it may have already been transferred, and we
317 * may not have enough room to transfer the whole this time.
319 if (rec->remainder > 0) {
320 /* Write as much of data as possible */
321 if (remlen >= rec->remainder) {
322 memcpy(block->bufp, rec->data+rec->data_len-rec->remainder,
324 block->bufp += rec->remainder;
325 block->binbuf += rec->remainder;
327 memcpy(block->bufp, rec->data+rec->data_len-rec->remainder,
330 if (!sm_check_rtn(__FILE__, __LINE__, False)) {
331 /* We damaged a buffer */
332 Dmsg6(0, "Damaged block FI=%s SessId=%d Strm=%s len=%d\n"
333 "rem=%d remainder=%d\n",
334 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
335 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
336 remlen, rec->remainder);
337 Dmsg5(0, "Damaged block: bufp=%x binbuf=%d buf_len=%d rem=%d moved=%d\n",
338 block->bufp, block->binbuf, block->buf_len, block->buf_len-block->binbuf,
340 Dmsg2(0, "Damaged block: buf=%x binbuffrombuf=%d \n",
341 block->buf, block->bufp-block->buf);
343 Emsg0(M_ABORT, 0, _("Damaged buffer\n"));
347 block->bufp += remlen;
348 block->binbuf += remlen;
349 rec->remainder -= remlen;
350 return false; /* did partial transfer */
353 rec->remainder = 0; /* did whole transfer */
359 * Test if we can write whole record to the block
361 * Returns: false on failure
362 * true on success (all bytes can be written)
364 bool can_write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec)
368 remlen = block->buf_len - block->binbuf;
369 if (rec->remainder == 0) {
370 if (remlen >= WRITE_RECHDR_LENGTH) {
371 remlen -= WRITE_RECHDR_LENGTH;
372 rec->remainder = rec->data_len;
379 if (rec->remainder > 0 && remlen < rec->remainder) {
387 * Read a Record from the block
388 * Returns: false if nothing read or if the continuation record does not match.
389 * In both of these cases, a block read must be done.
390 * true if at least the record header was read, this
391 * routine may have to be called again with a new
392 * block if the entire record was not read.
394 bool read_record_from_block(DEV_BLOCK *block, DEV_RECORD *rec)
398 uint32_t VolSessionId;
399 uint32_t VolSessionTime;
404 char buf1[100], buf2[100];
406 remlen = block->binbuf;
407 rec->Block = block->BlockNumber;
408 rec->File = ((DEVICE *)block->dev)->file;
410 /* Clear state flags */
412 if (block->dev->is_tape()) {
413 rec->state |= REC_ISTAPE;
418 * Get the header. There is always a full header,
419 * otherwise we find it in the next block.
421 Dmsg3(450, "Block=%d Ver=%d size=%u\n", block->BlockNumber, block->BlockVer,
423 if (block->BlockVer == 1) {
424 rhl = RECHDR1_LENGTH;
426 rhl = RECHDR2_LENGTH;
429 Dmsg4(450, "Enter read_record_block: remlen=%d data_len=%d rem=%d blkver=%d\n",
430 remlen, rec->data_len, rec->remainder, block->BlockVer);
432 unser_begin(block->bufp, WRITE_RECHDR_LENGTH);
433 if (block->BlockVer == 1) {
434 unser_uint32(VolSessionId);
435 unser_uint32(VolSessionTime);
437 VolSessionId = block->VolSessionId;
438 VolSessionTime = block->VolSessionTime;
440 unser_int32(FileIndex);
442 unser_uint32(data_bytes);
445 block->binbuf -= rhl;
448 /* If we are looking for more (remainder!=0), we reject anything
449 * where the VolSessionId and VolSessionTime don't agree
451 if (rec->remainder && (rec->VolSessionId != VolSessionId ||
452 rec->VolSessionTime != VolSessionTime)) {
453 rec->state |= REC_NO_MATCH;
454 Dmsg0(450, "remainder and VolSession doesn't match\n");
455 return false; /* This is from some other Session */
458 /* if Stream is negative, it means that this is a continuation
459 * of a previous partially written record.
461 if (Stream < 0) { /* continuation record? */
462 Dmsg1(500, "Got negative Stream => continuation. remainder=%d\n",
464 rec->state |= REC_CONTINUATION;
465 if (!rec->remainder) { /* if we didn't read previously */
466 rec->data_len = 0; /* return data as if no continuation */
467 } else if (rec->Stream != -Stream) {
468 rec->state |= REC_NO_MATCH;
469 return false; /* This is from some other Session */
471 rec->Stream = -Stream; /* set correct Stream */
472 } else { /* Regular record */
473 rec->Stream = Stream;
474 rec->data_len = 0; /* transfer to beginning of data */
476 rec->VolSessionId = VolSessionId;
477 rec->VolSessionTime = VolSessionTime;
478 rec->FileIndex = FileIndex;
480 if (block->FirstIndex == 0) {
481 block->FirstIndex = FileIndex;
483 block->LastIndex = FileIndex;
486 Dmsg6(450, "rd_rec_blk() got FI=%s SessId=%d Strm=%s len=%u\n"
487 "remlen=%d data_len=%d\n",
488 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
489 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), data_bytes, remlen,
493 * No more records in this block because the number
494 * of remaining bytes are less than a record header
495 * length, so return empty handed, but indicate that
496 * he must read again. By returning, we allow the
497 * higher level routine to fetch the next block and
500 Dmsg0(450, "read_record_block: nothing\n");
501 rec->state |= (REC_NO_HEADER | REC_BLOCK_EMPTY);
502 empty_block(block); /* mark block empty */
506 ASSERT(data_bytes < MAX_BLOCK_LENGTH); /* temp sanity check */
508 rec->data = check_pool_memory_size(rec->data, rec->data_len+data_bytes);
511 * At this point, we have read the header, now we
512 * must transfer as much of the data record as
513 * possible taking into account: 1. A partial
514 * data record may have previously been transferred,
515 * 2. The current block may not contain the whole data
518 if (remlen >= data_bytes) {
519 /* Got whole record */
520 memcpy(rec->data+rec->data_len, block->bufp, data_bytes);
521 block->bufp += data_bytes;
522 block->binbuf -= data_bytes;
523 rec->data_len += data_bytes;
526 memcpy(rec->data+rec->data_len, block->bufp, remlen);
527 block->bufp += remlen;
528 block->binbuf -= remlen;
529 rec->data_len += remlen;
530 rec->remainder = 1; /* partial record transferred */
531 Dmsg1(450, "read_record_block: partial xfered=%d\n", rec->data_len);
532 rec->state |= (REC_PARTIAL_RECORD | REC_BLOCK_EMPTY);
536 Dmsg4(450, "Rtn full rd_rec_blk FI=%s SessId=%d Strm=%s len=%d\n",
537 FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
538 stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len);
539 return true; /* transferred full record */