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(int fi)
43 sprintf(buf, "%d", fi);
61 sprintf(buf, "unknown: %d", fi);
68 * Convert a Stream ID into a printable
69 * ASCII string. Not reentrant.
71 * A negative stream number represents
72 * stream data that is continued from a
73 * record in the previous block.
74 * If the FileIndex is negative, we are
75 * dealing with a Label, hence the
76 * stream is the JobId.
78 const char *stream_to_ascii(int stream, int fi)
82 sprintf(buf, "%d", stream);
86 case STREAM_UNIX_ATTRIBUTES:
88 case STREAM_FILE_DATA:
90 case STREAM_WIN32_DATA:
92 case STREAM_WIN32_GZIP_DATA:
94 case STREAM_MD5_SIGNATURE:
96 case STREAM_SHA1_SIGNATURE:
98 case STREAM_GZIP_DATA:
100 case STREAM_UNIX_ATTRIBUTES_EX:
101 return "UNIX-ATTR-EX";
102 case STREAM_SPARSE_DATA:
103 return "SPARSE-DATA";
104 case STREAM_SPARSE_GZIP_DATA:
105 return "SPARSE-GZIP";
106 case STREAM_PROGRAM_NAMES:
108 case STREAM_PROGRAM_DATA:
110 case STREAM_MACOS_FORK_DATA:
112 case STREAM_HFSPLUS_ATTRIBUTES:
113 return "HFSPLUS-ATTR";
114 case -STREAM_UNIX_ATTRIBUTES:
116 case -STREAM_FILE_DATA:
118 case -STREAM_WIN32_DATA:
119 return "contWIN32-DATA";
120 case -STREAM_WIN32_GZIP_DATA:
121 return "contWIN32-GZIP";
122 case -STREAM_MD5_SIGNATURE:
124 case -STREAM_SHA1_SIGNATURE:
126 case -STREAM_GZIP_DATA:
128 case -STREAM_UNIX_ATTRIBUTES_EX:
129 return "contUNIX-ATTR-EX";
130 case -STREAM_SPARSE_DATA:
131 return "contSPARSE-DATA";
132 case -STREAM_SPARSE_GZIP_DATA:
133 return "contSPARSE-GZIP";
134 case -STREAM_PROGRAM_NAMES:
135 return "contPROG-NAMES";
136 case -STREAM_PROGRAM_DATA:
137 return "contPROG-DATA";
138 case -STREAM_MACOS_FORK_DATA:
139 return "contMACOS-RSRC";
140 case -STREAM_HFSPLUS_ATTRIBUTES:
141 return "contHFSPLUS-ATTR";
143 sprintf(buf, "%d", stream);
149 * Return a new record entity
151 DEV_RECORD *new_record(void)
155 rec = (DEV_RECORD *)get_memory(sizeof(DEV_RECORD));
156 memset(rec, 0, sizeof(DEV_RECORD));
157 rec->data = get_pool_memory(PM_MESSAGE);
161 void empty_record(DEV_RECORD *rec)
163 rec->File = rec->Block = 0;
164 rec->VolSessionId = rec->VolSessionTime = 0;
165 rec->FileIndex = rec->Stream = 0;
166 rec->data_len = rec->remainder = 0;
167 rec->state &= ~(REC_PARTIAL_RECORD|REC_BLOCK_EMPTY|REC_NO_MATCH|REC_CONTINUATION);
171 * Free the record entity
174 void free_record(DEV_RECORD *rec)
176 Dmsg0(950, "Enter free_record.\n");
178 free_pool_memory(rec->data);
180 Dmsg0(950, "Data buf is freed.\n");
181 free_pool_memory((POOLMEM *)rec);
182 Dmsg0(950, "Leave free_record.\n");
187 * Write a Record to the block
189 * Returns: false on failure (none or partially written)
190 * true on success (all bytes written)
192 * and remainder returned in packet.
194 * We require enough room for the header, and we deal with
195 * two special cases. 1. Only part of the record may have
196 * been transferred the last time (when remainder is
197 * non-zero), and 2. The remaining bytes to write may not
198 * all fit into the block.
200 bool write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec)
205 remlen = block->buf_len - block->binbuf;
207 ASSERT(block->binbuf == (uint32_t) (block->bufp - block->buf));
208 ASSERT(block->buf_len >= block->binbuf);
210 Dmsg6(890, "write_record_to_block() FI=%s SessId=%d Strm=%s len=%d\n"
211 "rem=%d remainder=%d\n",
212 FI_to_ascii(rec->FileIndex), rec->VolSessionId,
213 stream_to_ascii(rec->Stream, rec->FileIndex), rec->data_len,
214 remlen, rec->remainder);
217 * If rec->remainder is non-zero, we have been called a
218 * second (or subsequent) time to finish writing a record
219 * that did not previously fit into the block.
221 if (rec->remainder == 0) {
222 /* Require enough room to write a full header */
223 if (remlen >= WRITE_RECHDR_LENGTH) {
224 ser_begin(block->bufp, WRITE_RECHDR_LENGTH);
225 if (BLOCK_VER == 1) {
226 ser_uint32(rec->VolSessionId);
227 ser_uint32(rec->VolSessionTime);
229 block->VolSessionId = rec->VolSessionId;
230 block->VolSessionTime = rec->VolSessionTime;
232 ser_int32(rec->FileIndex);
233 ser_int32(rec->Stream);
234 ser_uint32(rec->data_len);
236 block->bufp += WRITE_RECHDR_LENGTH;
237 block->binbuf += WRITE_RECHDR_LENGTH;
238 remlen -= WRITE_RECHDR_LENGTH;
239 rec->remainder = rec->data_len;
240 if (rec->FileIndex > 0) {
241 /* If data record, update what we have in this block */
242 if (block->FirstIndex == 0) {
243 block->FirstIndex = rec->FileIndex;
245 block->LastIndex = rec->FileIndex;
248 rec->remainder = rec->data_len + WRITE_RECHDR_LENGTH;
253 * We are here to write unwritten bytes from a previous
254 * time. Presumably we have a new buffer (possibly
255 * containing a volume label), so the new header
256 * should be able to fit in the block -- otherwise we have
257 * an error. Note, we have to continue splitting the
258 * data record if it is longer than the block.
260 * First, write the header, then write as much as
261 * possible of the data record.
263 * Every time we write a header and it is a continuation
264 * of a previous partially written record, we store the
265 * Stream as -Stream in the record header.
267 ser_begin(block->bufp, WRITE_RECHDR_LENGTH);
268 if (BLOCK_VER == 1) {
269 ser_uint32(rec->VolSessionId);
270 ser_uint32(rec->VolSessionTime);
272 block->VolSessionId = rec->VolSessionId;
273 block->VolSessionTime = rec->VolSessionTime;
275 ser_int32(rec->FileIndex);
276 if (rec->remainder > rec->data_len) {
277 ser_int32(rec->Stream); /* normal full header */
278 ser_uint32(rec->data_len);
279 rec->remainder = rec->data_len; /* must still do data record */
281 ser_int32(-rec->Stream); /* mark this as a continuation record */
282 ser_uint32(rec->remainder); /* bytes to do */
285 /* Require enough room to write a full header */
286 ASSERT(remlen >= WRITE_RECHDR_LENGTH);
288 block->bufp += WRITE_RECHDR_LENGTH;
289 block->binbuf += WRITE_RECHDR_LENGTH;
290 remlen -= WRITE_RECHDR_LENGTH;
291 if (rec->FileIndex > 0) {
292 /* If data record, update what we have in this block */
293 if (block->FirstIndex == 0) {
294 block->FirstIndex = rec->FileIndex;
296 block->LastIndex = rec->FileIndex;
300 return false; /* partial transfer */
304 * Now deal with data record.
305 * Part of it may have already been transferred, and we
306 * may not have enough room to transfer the whole this time.
308 if (rec->remainder > 0) {
309 /* Write as much of data as possible */
310 if (remlen >= rec->remainder) {
311 memcpy(block->bufp, rec->data+rec->data_len-rec->remainder,
313 block->bufp += rec->remainder;
314 block->binbuf += rec->remainder;
316 memcpy(block->bufp, rec->data+rec->data_len-rec->remainder,
319 if (!sm_check_rtn(__FILE__, __LINE__, False)) {
320 /* We damaged a buffer */
321 Dmsg6(0, "Damaged block FI=%s SessId=%d Strm=%s len=%d\n"
322 "rem=%d remainder=%d\n",
323 FI_to_ascii(rec->FileIndex), rec->VolSessionId,
324 stream_to_ascii(rec->Stream, rec->FileIndex), rec->data_len,
325 remlen, rec->remainder);
326 Dmsg5(0, "Damaged block: bufp=%x binbuf=%d buf_len=%d rem=%d moved=%d\n",
327 block->bufp, block->binbuf, block->buf_len, block->buf_len-block->binbuf,
329 Dmsg2(0, "Damaged block: buf=%x binbuffrombuf=%d \n",
330 block->buf, block->bufp-block->buf);
332 Emsg0(M_ABORT, 0, "Damaged buffer\n");
336 block->bufp += remlen;
337 block->binbuf += remlen;
338 rec->remainder -= remlen;
339 return false; /* did partial transfer */
342 rec->remainder = 0; /* did whole transfer */
348 * Test if we can write whole record to the block
350 * Returns: false on failure
351 * true on success (all bytes can be written)
353 bool can_write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec)
357 remlen = block->buf_len - block->binbuf;
358 if (rec->remainder == 0) {
359 if (remlen >= WRITE_RECHDR_LENGTH) {
360 remlen -= WRITE_RECHDR_LENGTH;
361 rec->remainder = rec->data_len;
368 if (rec->remainder > 0 && remlen < rec->remainder) {
376 * Read a Record from the block
377 * Returns: false if nothing read or if the continuation record does not match.
378 * In both of these cases, a block read must be done.
379 * true if at least the record header was read, this
380 * routine may have to be called again with a new
381 * block if the entire record was not read.
383 bool read_record_from_block(DEV_BLOCK *block, DEV_RECORD *rec)
387 uint32_t VolSessionId;
388 uint32_t VolSessionTime;
394 remlen = block->binbuf;
395 rec->Block = block->BlockNumber;
396 rec->File = ((DEVICE *)block->dev)->file;
398 /* Clear state flags */
400 if (block->dev->is_tape()) {
401 rec->state |= REC_ISTAPE;
406 * Get the header. There is always a full header,
407 * otherwise we find it in the next block.
409 Dmsg3(450, "Block=%d Ver=%d size=%u\n", block->BlockNumber, block->BlockVer,
411 if (block->BlockVer == 1) {
412 rhl = RECHDR1_LENGTH;
414 rhl = RECHDR2_LENGTH;
417 Dmsg4(450, "Enter read_record_block: remlen=%d data_len=%d rem=%d blkver=%d\n",
418 remlen, rec->data_len, rec->remainder, block->BlockVer);
420 unser_begin(block->bufp, WRITE_RECHDR_LENGTH);
421 if (block->BlockVer == 1) {
422 unser_uint32(VolSessionId);
423 unser_uint32(VolSessionTime);
425 VolSessionId = block->VolSessionId;
426 VolSessionTime = block->VolSessionTime;
428 unser_int32(FileIndex);
430 unser_uint32(data_bytes);
433 block->binbuf -= rhl;
436 /* If we are looking for more (remainder!=0), we reject anything
437 * where the VolSessionId and VolSessionTime don't agree
439 if (rec->remainder && (rec->VolSessionId != VolSessionId ||
440 rec->VolSessionTime != VolSessionTime)) {
441 rec->state |= REC_NO_MATCH;
442 Dmsg0(450, "remainder and VolSession doesn't match\n");
443 return false; /* This is from some other Session */
446 /* if Stream is negative, it means that this is a continuation
447 * of a previous partially written record.
449 if (Stream < 0) { /* continuation record? */
450 Dmsg1(500, "Got negative Stream => continuation. remainder=%d\n",
452 rec->state |= REC_CONTINUATION;
453 if (!rec->remainder) { /* if we didn't read previously */
454 rec->data_len = 0; /* return data as if no continuation */
455 } else if (rec->Stream != -Stream) {
456 rec->state |= REC_NO_MATCH;
457 return false; /* This is from some other Session */
459 rec->Stream = -Stream; /* set correct Stream */
460 } else { /* Regular record */
461 rec->Stream = Stream;
462 rec->data_len = 0; /* transfer to beginning of data */
464 rec->VolSessionId = VolSessionId;
465 rec->VolSessionTime = VolSessionTime;
466 rec->FileIndex = FileIndex;
468 if (block->FirstIndex == 0) {
469 block->FirstIndex = FileIndex;
471 block->LastIndex = FileIndex;
474 Dmsg6(450, "rd_rec_blk() got FI=%s SessId=%d Strm=%s len=%u\n"
475 "remlen=%d data_len=%d\n",
476 FI_to_ascii(rec->FileIndex), rec->VolSessionId,
477 stream_to_ascii(rec->Stream, rec->FileIndex), data_bytes, remlen,
481 * No more records in this block because the number
482 * of remaining bytes are less than a record header
483 * length, so return empty handed, but indicate that
484 * he must read again. By returning, we allow the
485 * higher level routine to fetch the next block and
488 Dmsg0(450, "read_record_block: nothing\n");
489 rec->state |= (REC_NO_HEADER | REC_BLOCK_EMPTY);
490 empty_block(block); /* mark block empty */
494 ASSERT(data_bytes < MAX_BLOCK_LENGTH); /* temp sanity check */
496 rec->data = check_pool_memory_size(rec->data, rec->data_len+data_bytes);
499 * At this point, we have read the header, now we
500 * must transfer as much of the data record as
501 * possible taking into account: 1. A partial
502 * data record may have previously been transferred,
503 * 2. The current block may not contain the whole data
506 if (remlen >= data_bytes) {
507 /* Got whole record */
508 memcpy(rec->data+rec->data_len, block->bufp, data_bytes);
509 block->bufp += data_bytes;
510 block->binbuf -= data_bytes;
511 rec->data_len += data_bytes;
514 memcpy(rec->data+rec->data_len, block->bufp, remlen);
515 block->bufp += remlen;
516 block->binbuf -= remlen;
517 rec->data_len += remlen;
518 rec->remainder = 1; /* partial record transferred */
519 Dmsg1(450, "read_record_block: partial xfered=%d\n", rec->data_len);
520 rec->state |= (REC_PARTIAL_RECORD | REC_BLOCK_EMPTY);
524 Dmsg4(450, "Rtn full rd_rec_blk FI=%s SessId=%d Strm=%s len=%d\n",
525 FI_to_ascii(rec->FileIndex), rec->VolSessionId,
526 stream_to_ascii(rec->Stream, rec->FileIndex), rec->data_len);
527 return true; /* transferred full record */