static int hammer_check_tail_signature(hammer_fifo_tail_t tail,
hammer_off_t end_off);
+static int hammer_check_head_signature(hammer_fifo_head_t head,
+ hammer_off_t beg_off);
static void hammer_recover_copy_undo(hammer_off_t undo_offset,
char *src, char *dst, int bytes);
+static hammer_fifo_any_t hammer_recover_scan_fwd(hammer_mount_t hmp,
+ hammer_volume_t root_volume,
+ hammer_off_t *scan_offsetp,
+ int *errorp, struct hammer_buffer **bufferp);
+static hammer_fifo_any_t hammer_recover_scan_rev(hammer_mount_t hmp,
+ hammer_volume_t root_volume,
+ hammer_off_t *scan_offsetp,
+ int *errorp, struct hammer_buffer **bufferp);
#if 0
static void hammer_recover_debug_dump(int w, char *buf, int bytes);
#endif
static int hammer_recover_undo(hammer_mount_t hmp, hammer_volume_t root_volume,
- hammer_fifo_undo_t undo, int bytes);
+ hammer_fifo_undo_t undo);
/*
- * Recover a filesystem on mount
+ * Recover filesystem meta-data on mount. This procedure figures out the
+ * UNDO FIFO range and runs the UNDOs backwards. The FIFO pointers are not
+ * resynchronized by this procedure.
+ *
+ * This procedure is run near the beginning of the mount sequence, before
+ * any B-Tree or high-level accesses are enabled, and is responsible for
+ * restoring the meta-data to a consistent state. High level HAMMER data
+ * structures (such as the B-Tree) cannot be accessed here.
*
* NOTE: No information from the root volume has been cached in the
- * hammer_mount structure yet, so we need to access the root volume's
- * buffer directly.
+ * hammer_mount structure yet, so we need to access the root volume's
+ * buffer directly.
+ *
+ * NOTE:
*/
int
-hammer_recover(hammer_mount_t hmp, hammer_volume_t root_volume)
+hammer_recover_stage1(hammer_mount_t hmp, hammer_volume_t root_volume)
{
hammer_blockmap_t rootmap;
hammer_buffer_t buffer;
hammer_off_t scan_offset;
+ hammer_off_t scan_offset_save;
hammer_off_t bytes;
- hammer_fifo_tail_t tail;
- hammer_fifo_undo_t undo;
+ hammer_fifo_any_t head;
hammer_off_t first_offset;
hammer_off_t last_offset;
+ u_int32_t seqno;
int error;
/*
- * Examine the UNDO FIFO. If it is empty the filesystem is clean
- * and no action need be taken.
+ * Examine the UNDO FIFO indices in the volume header.
*/
rootmap = &root_volume->ondisk->vol0_blockmap[HAMMER_ZONE_UNDO_INDEX];
-
- if (rootmap->first_offset == rootmap->next_offset)
- return(0);
-
first_offset = rootmap->first_offset;
last_offset = rootmap->next_offset;
+ buffer = NULL;
+ error = 0;
+
+ if (first_offset > rootmap->alloc_offset ||
+ last_offset > rootmap->alloc_offset) {
+ kprintf("HAMMER(%s) Illegal UNDO FIFO index range "
+ "%016jx, %016jx limit %016jx\n",
+ root_volume->ondisk->vol_name,
+ (intmax_t)first_offset,
+ (intmax_t)last_offset,
+ (intmax_t)rootmap->alloc_offset);
+ error = EIO;
+ goto done;
+ }
+
+ /*
+ * In HAMMER version 4+ filesystems the volume header does NOT
+ * contain definitive UNDO FIFO state. In particular, the
+ * rootmap->next_offset may not be indexed completely to the
+ * end of the active UNDO FIFO.
+ */
+ if (hmp->version >= HAMMER_VOL_VERSION_FOUR) {
+ /*
+ * To find the definitive range we must first scan backwards
+ * from first_offset to locate the first real record and
+ * extract the sequence number from it. This record is not
+ * part of the active undo space.
+ */
+ scan_offset = first_offset;
+ seqno = 0;
+
+ for (;;) {
+ head = hammer_recover_scan_rev(hmp, root_volume,
+ &scan_offset,
+ &error, &buffer);
+ if (error)
+ break;
+ if (head->head.hdr_type != HAMMER_HEAD_TYPE_PAD) {
+ seqno = head->head.hdr_seq;
+ break;
+ }
+ }
+ if (error) {
+ kprintf("HAMMER(%s) meta-data recovery failure "
+ "during seqno backscan\n",
+ root_volume->ondisk->vol_name);
+ goto done;
+ }
+
+ /*
+ * Scan forwards from first_offset and (seqno+1) looking
+ * for a sequence space discontinuity. This denotes the
+ * end of the active FIFO area.
+ *
+ * NOTE: For the case where the FIFO is empty the very first
+ * record we find will be discontinuous.
+ *
+ * NOTE: Do not include trailing PADs in the scan range,
+ * and remember the returned scan_offset after a
+ * fwd iteration points to the end of the returned
+ * record.
+ */
+ kprintf("HAMMER(%s) meta-data recovery check seqno=%08x\n",
+ root_volume->ondisk->vol_name,
+ seqno);
+
+ scan_offset = first_offset;
+ scan_offset_save = scan_offset;
+ ++seqno;
+ for (;;) {
+ head = hammer_recover_scan_fwd(hmp, root_volume,
+ &scan_offset,
+ &error, &buffer);
+ if (error)
+ break;
+ if (head->head.hdr_type != HAMMER_HEAD_TYPE_PAD) {
+ if (seqno != head->head.hdr_seq) {
+ scan_offset = scan_offset_save;
+ break;
+ }
+ scan_offset_save = scan_offset;
+ ++seqno;
+ }
+
+#if 0
+ /*
+ * If the forward scan is grossly ahead of last_offset
+ * then something is wrong. last_offset is supposed
+ * to be flushed out
+ */
+ if (last_offset >= scan_offset) {
+ bytes = last_offset - scan_offset;
+ } else {
+ bytes = rootmap->alloc_offset - scan_offset +
+ (last_offset & HAMMER_OFF_LONG_MASK);
+ }
+ if (bytes >
+ (rootmap->alloc_offset & HAMMER_OFF_LONG_MASK) *
+ 4 / 5) {
+ kprintf("HAMMER(%s) meta-data forward scan is "
+ "grossly beyond the last_offset in "
+ "the volume header, this can't be "
+ "right.\n",
+ root_volume->ondisk->vol_name);
+ error = EIO;
+ break;
+ }
+#endif
+ }
+ /*
+ * Store the seqno. This will be the next seqno we lay down
+ * when generating new UNDOs.
+ */
+ hmp->undo_seqno = seqno;
+ if (error) {
+ kprintf("HAMMER(%s) meta-data recovery failure "
+ "during seqno fwdscan\n",
+ root_volume->ondisk->vol_name);
+ goto done;
+ }
+ last_offset = scan_offset;
+ kprintf("HAMMER(%s) meta-data recovery range %016jx-%016jx "
+ "(invol %016jx) endseqno=%08x\n",
+ root_volume->ondisk->vol_name,
+ (intmax_t)first_offset,
+ (intmax_t)last_offset,
+ (intmax_t)rootmap->next_offset,
+ seqno);
+ }
+
+ /*
+ * Calculate the size of the active portion of the FIFO. If the
+ * FIFO is empty the filesystem is clean and no further action is
+ * needed.
+ */
if (last_offset >= first_offset) {
bytes = last_offset - first_offset;
} else {
bytes = rootmap->alloc_offset - first_offset +
(last_offset & HAMMER_OFF_LONG_MASK);
}
- kprintf("HAMMER(%s) Start Recovery %016llx - %016llx "
- "(%lld bytes of UNDO)%s\n",
+ if (bytes == 0) {
+ error = 0;
+ goto done;
+ }
+
+ kprintf("HAMMER(%s) Start meta-data recovery %016jx - %016jx "
+ "(%jd bytes of UNDO)%s\n",
root_volume->ondisk->vol_name,
- (long long)first_offset,
- (long long)last_offset,
- (long long)bytes,
+ (intmax_t)first_offset,
+ (intmax_t)last_offset,
+ (intmax_t)bytes,
(hmp->ronly ? " (RO)" : "(RW)"));
if (bytes > (rootmap->alloc_offset & HAMMER_OFF_LONG_MASK)) {
kprintf("Undo size is absurd, unable to mount\n");
- return(EIO);
+ error = EIO;
+ goto done;
}
/*
* Scan the UNDOs backwards.
*/
scan_offset = last_offset;
- buffer = NULL;
- if (scan_offset > rootmap->alloc_offset) {
- kprintf("HAMMER(%s) UNDO record at %016llx FIFO overflow\n",
- root_volume->ondisk->vol_name,
- (long long)scan_offset);
- error = EIO;
- goto done;
- }
while ((int64_t)bytes > 0) {
- if (hammer_debug_general & 0x0080)
- kprintf("scan_offset %016llx\n",
- (long long)scan_offset);
- if (scan_offset == HAMMER_ZONE_ENCODE(HAMMER_ZONE_UNDO_INDEX, 0)) {
- scan_offset = rootmap->alloc_offset;
- continue;
- }
- if (scan_offset - sizeof(*tail) <
- HAMMER_ZONE_ENCODE(HAMMER_ZONE_UNDO_INDEX, 0)) {
- kprintf("HAMMER(%s) UNDO record at %016llx FIFO "
- "underflow\n",
- root_volume->ondisk->vol_name,
- (long long)scan_offset);
- error = EIO;
- break;
- }
- tail = hammer_bread(hmp, scan_offset - sizeof(*tail),
- &error, &buffer);
- if (error) {
- kprintf("HAMMER(%s) Unable to read UNDO TAIL "
- "at %016llx\n",
- root_volume->ondisk->vol_name,
- (long long)scan_offset - sizeof(*tail));
- break;
- }
-
- if (hammer_check_tail_signature(tail, scan_offset) != 0) {
- kprintf("HAMMER(%s) Illegal UNDO TAIL signature "
- "at %016llx\n",
- root_volume->ondisk->vol_name,
- (long long)scan_offset - sizeof(*tail));
- error = EIO;
+ KKASSERT(scan_offset != first_offset);
+ head = hammer_recover_scan_rev(hmp, root_volume,
+ &scan_offset, &error, &buffer);
+ if (error)
break;
- }
- undo = (void *)((char *)tail + sizeof(*tail) - tail->tail_size);
-
- error = hammer_recover_undo(hmp, root_volume, undo,
- HAMMER_BUFSIZE -
- (int)((char *)undo - (char *)buffer->ondisk));
+ error = hammer_recover_undo(hmp, root_volume, &head->undo);
if (error) {
- kprintf("HAMMER(%s) UNDO record at %016llx failed\n",
+ kprintf("HAMMER(%s) UNDO record at %016jx failed\n",
root_volume->ondisk->vol_name,
- (long long)scan_offset - tail->tail_size);
+ (intmax_t)scan_offset - head->head.hdr_size);
break;
}
- scan_offset -= tail->tail_size;
- bytes -= tail->tail_size;
+ bytes -= head->head.hdr_size;
/*
* If too many dirty buffers have built up we have to flush'm
}
}
done:
- if (buffer)
+ if (buffer) {
hammer_rel_buffer(buffer, 0);
+ buffer = NULL;
+ }
/*
* After completely flushing all the recovered buffers the volume
- * header will also be flushed. Force the UNDO FIFO to 0-length.
+ * header will also be flushed.
*/
if (root_volume->io.recovered == 0) {
hammer_ref_volume(root_volume);
}
/*
- * Finish up flushing (or discarding) recovered buffers
+ * Finish up flushing (or discarding) recovered buffers. FIFO
+ * indices in the volume header are updated to the actual undo
+ * range but will not be collapsed until stage 2.
*/
if (error == 0) {
hammer_modify_volume(NULL, root_volume, NULL, 0);
rootmap = &root_volume->ondisk->vol0_blockmap[HAMMER_ZONE_UNDO_INDEX];
- rootmap->first_offset = last_offset;
+ rootmap->first_offset = first_offset;
rootmap->next_offset = last_offset;
hammer_modify_volume_done(root_volume);
if (hmp->ronly == 0)
} else {
hammer_recover_flush_buffers(hmp, root_volume, -1);
}
- kprintf("HAMMER(%s) End Recovery\n", root_volume->ondisk->vol_name);
+ kprintf("HAMMER(%s) End meta-data recovery\n",
+ root_volume->ondisk->vol_name);
return (error);
}
-static int
-hammer_check_tail_signature(hammer_fifo_tail_t tail, hammer_off_t end_off)
+/*
+ * Execute redo operations
+ *
+ * This procedure is run at the end of the mount sequence, after the hammer
+ * mount structure has been completely initialized but before the filesystem
+ * goes live. It can access standard cursors, the B-Tree, flush the
+ * filesystem, and so forth.
+ *
+ * This code may only be called for read-write mounts or when a mount
+ * switches from read-only to read-write.
+ *
+ * The stage1 code will have already calculated the correct FIFO range
+ * and stored it in the rootmap.
+ */
+int
+hammer_recover_stage2(hammer_mount_t hmp, hammer_volume_t root_volume)
{
- int max_bytes;
+ hammer_blockmap_t rootmap;
+ hammer_buffer_t buffer;
+ hammer_off_t scan_offset;
+ hammer_off_t bytes;
+ hammer_fifo_any_t head;
+ hammer_off_t first_offset;
+ hammer_off_t last_offset;
+ int error;
+
+ /*
+ * Stage 2 can only be run on a RW mount, or when the mount is
+ * switched from RO to RW. It must be run only once.
+ */
+ KKASSERT(hmp->ronly == 0);
- max_bytes = ((end_off - sizeof(*tail)) & HAMMER_BUFMASK);
- max_bytes += sizeof(*tail);
+ if (hmp->hflags & HMNT_STAGE2)
+ return(0);
+ hmp->hflags |= HMNT_STAGE2;
/*
- * tail overlaps buffer boundary
+ * Examine the UNDO FIFO. If it is empty the filesystem is clean
+ * and no action need be taken.
*/
- if (((end_off - sizeof(*tail)) ^ (end_off - 1)) & ~HAMMER_BUFMASK64) {
- return(1);
+ rootmap = &root_volume->ondisk->vol0_blockmap[HAMMER_ZONE_UNDO_INDEX];
+ first_offset = rootmap->first_offset;
+ last_offset = rootmap->next_offset;
+ if (first_offset == last_offset)
+ return(0);
+
+ if (last_offset >= first_offset) {
+ bytes = last_offset - first_offset;
+ } else {
+ bytes = rootmap->alloc_offset - first_offset +
+ (last_offset & HAMMER_OFF_LONG_MASK);
+ }
+ kprintf("HAMMER(%s) Start redo recovery %016jx - %016jx "
+ "(%jd bytes of UNDO)%s\n",
+ root_volume->ondisk->vol_name,
+ (intmax_t)first_offset,
+ (intmax_t)last_offset,
+ (intmax_t)bytes,
+ (hmp->ronly ? " (RO)" : "(RW)"));
+ if (bytes > (rootmap->alloc_offset & HAMMER_OFF_LONG_MASK)) {
+ kprintf("Undo size is absurd, unable to mount\n");
+ return(EIO);
}
/*
- * signature check, the tail signature is allowed to be the head
- * signature only for 8-byte PADs.
+ * Scan the REDOs forwards.
*/
- switch(tail->tail_signature) {
- case HAMMER_TAIL_SIGNATURE:
- break;
- case HAMMER_HEAD_SIGNATURE:
- if (tail->tail_type != HAMMER_HEAD_TYPE_PAD ||
- tail->tail_size != sizeof(*tail)) {
- return(2);
+ scan_offset = first_offset;
+ buffer = NULL;
+
+ while (bytes) {
+ KKASSERT(scan_offset != last_offset);
+
+ head = hammer_recover_scan_fwd(hmp, root_volume,
+ &scan_offset, &error, &buffer);
+ if (error)
+ break;
+
+#if 0
+ error = hammer_recover_redo(hmp, root_volume, &head->redo);
+#endif
+ if (error) {
+ kprintf("HAMMER(%s) UNDO record at %016jx failed\n",
+ root_volume->ondisk->vol_name,
+ (intmax_t)scan_offset - head->head.hdr_size);
+ break;
}
- break;
+ bytes -= head->head.hdr_size;
+ }
+ if (buffer) {
+ hammer_rel_buffer(buffer, 0);
+ buffer = NULL;
}
/*
- * The undo structure must not overlap a buffer boundary.
+ * Finish up flushing (or discarding) recovered buffers by executing
+ * a normal flush cycle. Setting HMNT_UNDO_DIRTY bypasses degenerate
+ * case tests and forces the flush in order to update the FIFO indices.
+ *
+ * If a crash occurs during the flush the entire undo/redo will be
+ * re-run during recovery on the next mount.
*/
- if (tail->tail_size < sizeof(*tail) || tail->tail_size > max_bytes) {
- return(3);
+ if (error == 0) {
+ if (rootmap->first_offset != rootmap->next_offset)
+ hmp->hflags |= HMNT_UNDO_DIRTY;
+ hammer_flusher_sync(hmp);
}
- return(0);
+ kprintf("HAMMER(%s) End redo recovery\n",
+ root_volume->ondisk->vol_name);
+ return (error);
}
-static int
-hammer_recover_undo(hammer_mount_t hmp, hammer_volume_t root_volume,
- hammer_fifo_undo_t undo, int bytes)
+/*
+ * Scan backwards from *scan_offsetp, return the FIFO record prior to the
+ * record at *scan_offsetp or NULL if an error occured.
+ *
+ * On return *scan_offsetp will be the offset of the returned record.
+ */
+hammer_fifo_any_t
+hammer_recover_scan_rev(hammer_mount_t hmp, hammer_volume_t root_volume,
+ hammer_off_t *scan_offsetp,
+ int *errorp, struct hammer_buffer **bufferp)
{
+ hammer_off_t scan_offset;
+ hammer_blockmap_t rootmap;
+ hammer_fifo_any_t head;
hammer_fifo_tail_t tail;
- hammer_volume_t volume;
- hammer_buffer_t buffer;
- hammer_off_t buf_offset;
- int zone;
- int error;
- int vol_no;
- int max_bytes;
- u_int32_t offset;
- u_int32_t crc;
- /*
- * Basic sanity checks
- */
- if (bytes < HAMMER_HEAD_ALIGN) {
- kprintf("HAMMER: Undo alignment error (%d)\n", bytes);
- return(EIO);
+ rootmap = &root_volume->ondisk->vol0_blockmap[HAMMER_ZONE_UNDO_INDEX];
+ scan_offset = *scan_offsetp;
+
+ if (hammer_debug_general & 0x0080)
+ kprintf("rev scan_offset %016jx\n", (intmax_t)scan_offset);
+ if (scan_offset == HAMMER_ZONE_ENCODE(HAMMER_ZONE_UNDO_INDEX, 0))
+ scan_offset = rootmap->alloc_offset;
+ if (scan_offset - sizeof(*tail) <
+ HAMMER_ZONE_ENCODE(HAMMER_ZONE_UNDO_INDEX, 0)) {
+ kprintf("HAMMER(%s) UNDO record at %016jx FIFO underflow\n",
+ root_volume->ondisk->vol_name,
+ (intmax_t)scan_offset);
+ *errorp = EIO;
+ return (NULL);
}
- if (undo->head.hdr_signature != HAMMER_HEAD_SIGNATURE) {
- kprintf("HAMMER: Bad head signature %04x\n",
- undo->head.hdr_signature);
- return(EIO);
+ tail = hammer_bread(hmp, scan_offset - sizeof(*tail),
+ errorp, bufferp);
+ if (*errorp) {
+ kprintf("HAMMER(%s) Unable to read UNDO TAIL "
+ "at %016jx\n",
+ root_volume->ondisk->vol_name,
+ (intmax_t)scan_offset - sizeof(*tail));
+ return (NULL);
}
- if (undo->head.hdr_size < HAMMER_HEAD_ALIGN ||
- undo->head.hdr_size > bytes) {
- kprintf("HAMMER: Bad size %d\n", bytes);
- return(EIO);
+
+ if (hammer_check_tail_signature(tail, scan_offset) != 0) {
+ kprintf("HAMMER(%s) Illegal UNDO TAIL signature "
+ "at %016jx\n",
+ root_volume->ondisk->vol_name,
+ (intmax_t)scan_offset - sizeof(*tail));
+ *errorp = EIO;
+ return (NULL);
}
+ head = (void *)((char *)tail + sizeof(*tail) - tail->tail_size);
+ *scan_offsetp = scan_offset - head->head.hdr_size;
+
+ return (head);
+}
+
+/*
+ * Scan forwards from *scan_offsetp, return the FIFO record or NULL if
+ * an error occured.
+ *
+ * On return *scan_offsetp will be the offset of the record following
+ * the returned record.
+ */
+hammer_fifo_any_t
+hammer_recover_scan_fwd(hammer_mount_t hmp, hammer_volume_t root_volume,
+ hammer_off_t *scan_offsetp,
+ int *errorp, struct hammer_buffer **bufferp)
+{
+ hammer_off_t scan_offset;
+ hammer_blockmap_t rootmap;
+ hammer_fifo_any_t head;
+
+ rootmap = &root_volume->ondisk->vol0_blockmap[HAMMER_ZONE_UNDO_INDEX];
+ scan_offset = *scan_offsetp;
+
+ if (hammer_debug_general & 0x0080)
+ kprintf("fwd scan_offset %016jx\n", (intmax_t)scan_offset);
+ if (scan_offset == rootmap->alloc_offset)
+ scan_offset = HAMMER_ZONE_ENCODE(HAMMER_ZONE_UNDO_INDEX, 0);
+
+ head = hammer_bread(hmp, scan_offset, errorp, bufferp);
+ if (*errorp) {
+ kprintf("HAMMER(%s) Unable to read UNDO HEAD at %016jx\n",
+ root_volume->ondisk->vol_name,
+ (intmax_t)scan_offset);
+ return (NULL);
+ }
+
+ if (hammer_check_head_signature(&head->head, scan_offset) != 0) {
+ kprintf("HAMMER(%s) Illegal UNDO TAIL signature "
+ "at %016jx\n",
+ root_volume->ondisk->vol_name,
+ (intmax_t)scan_offset);
+ *errorp = EIO;
+ return (NULL);
+ }
+ scan_offset += head->head.hdr_size;
+ if (scan_offset == rootmap->alloc_offset)
+ scan_offset = HAMMER_ZONE_ENCODE(HAMMER_ZONE_UNDO_INDEX, 0);
+ *scan_offsetp = scan_offset;
+
+ return (head);
+}
+
+/*
+ * Helper function for hammer_check_{head,tail}_signature(). Check stuff
+ * once the head and tail has been established.
+ *
+ * This function validates the entire FIFO record wrapper.
+ */
+static __inline
+int
+_hammer_check_signature(hammer_fifo_head_t head, hammer_fifo_tail_t tail,
+ hammer_off_t beg_off)
+{
+ hammer_off_t end_off;
+ u_int32_t crc;
+ int bytes;
/*
- * Skip PAD records. Note that PAD records also do not require
- * a tail and may have a truncated structure.
+ * Check signatures. The tail signature is allowed to be the
+ * head signature only for 8-byte PADs.
*/
- if (undo->head.hdr_type == HAMMER_HEAD_TYPE_PAD)
- return(0);
+ if (head->hdr_signature != HAMMER_HEAD_SIGNATURE) {
+ kprintf("HAMMER: FIFO record bad head signature "
+ "%04x at %016jx\n",
+ head->hdr_signature,
+ (intmax_t)beg_off);
+ return(2);
+ }
+ if (head->hdr_size < HAMMER_HEAD_ALIGN ||
+ (head->hdr_size & HAMMER_HEAD_ALIGN_MASK)) {
+ kprintf("HAMMER: FIFO record unaligned or bad size"
+ "%04x at %016jx\n",
+ head->hdr_size,
+ (intmax_t)beg_off);
+ return(2);
+ }
+ end_off = beg_off + head->hdr_size;
+
+ if (head->hdr_type != HAMMER_HEAD_TYPE_PAD ||
+ (size_t)(end_off - beg_off) != sizeof(*tail)) {
+ if (head->hdr_type != tail->tail_type) {
+ kprintf("HAMMER: FIFO record head/tail type mismatch "
+ "%04x %04x at %016jx\n",
+ head->hdr_type, tail->tail_type,
+ (intmax_t)beg_off);
+ return(2);
+ }
+ if (head->hdr_size != tail->tail_size) {
+ kprintf("HAMMER: FIFO record head/tail size mismatch "
+ "%04x %04x at %016jx\n",
+ head->hdr_size, tail->tail_size,
+ (intmax_t)beg_off);
+ return(2);
+ }
+ if (tail->tail_signature != HAMMER_TAIL_SIGNATURE) {
+ kprintf("HAMMER: FIFO record bad tail signature "
+ "%04x at %016jx\n",
+ tail->tail_signature,
+ (intmax_t)beg_off);
+ return(3);
+ }
+ }
/*
- * Check the CRC
+ * Non-PAD records must have a CRC and must be sized at
+ * least large enough to fit the head and tail.
*/
- crc = crc32(undo, HAMMER_FIFO_HEAD_CRCOFF) ^
- crc32(&undo->head + 1, undo->head.hdr_size - sizeof(undo->head));
- if (undo->head.hdr_crc != crc) {
- kprintf("HAMMER: Undo record CRC failed %08x %08x\n",
- undo->head.hdr_crc, crc);
- return(EIO);
+ if (head->hdr_type != HAMMER_HEAD_TYPE_PAD) {
+ crc = crc32(head, HAMMER_FIFO_HEAD_CRCOFF) ^
+ crc32(head + 1, head->hdr_size - sizeof(*head));
+ if (head->hdr_crc != crc) {
+ kprintf("HAMMER: FIFO record CRC failed %08x %08x "
+ "at %016jx\n",
+ head->hdr_crc, crc,
+ (intmax_t)beg_off);
+ return(EIO);
+ }
+ if (head->hdr_size < sizeof(*head) + sizeof(*tail)) {
+ kprintf("HAMMER: FIFO record too small "
+ "%04x at %016jx\n",
+ head->hdr_size,
+ (intmax_t)beg_off);
+ return(EIO);
+ }
}
-
/*
* Check the tail
*/
- bytes = undo->head.hdr_size;
- tail = (void *)((char *)undo + bytes - sizeof(*tail));
- if (tail->tail_size != undo->head.hdr_size) {
- kprintf("HAMMER: Bad tail size %d\n", tail->tail_size);
+ bytes = head->hdr_size;
+ tail = (void *)((char *)head + bytes - sizeof(*tail));
+ if (tail->tail_size != head->hdr_size) {
+ kprintf("HAMMER: Bad tail size %04x vs %04x at %016jx\n",
+ tail->tail_size, head->hdr_size,
+ (intmax_t)beg_off);
return(EIO);
}
- if (tail->tail_type != undo->head.hdr_type) {
- kprintf("HAMMER: Bad tail type %d\n", tail->tail_type);
+ if (tail->tail_type != head->hdr_type) {
+ kprintf("HAMMER: Bad tail type %04x vs %04x at %016jx\n",
+ tail->tail_type, head->hdr_type,
+ (intmax_t)beg_off);
return(EIO);
}
+ return(0);
+}
+
+/*
+ * Check that the FIFO record is in-bounds given the head and the
+ * hammer offset.
+ *
+ * Also checks that the head and tail structures agree with each other,
+ * but does not check beyond the signature, type, and size.
+ */
+static int
+hammer_check_head_signature(hammer_fifo_head_t head, hammer_off_t beg_off)
+{
+ hammer_fifo_tail_t tail;
+ hammer_off_t end_off;
+
+ /*
+ * head overlaps buffer boundary. This could be a PAD so only
+ * check the minimum PAD size here.
+ */
+ if (((beg_off + sizeof(*tail) - 1) ^ (beg_off)) & ~HAMMER_BUFMASK64)
+ return(1);
+
+ /*
+ * Calculate the ending offset and make sure the record does
+ * not cross a buffer boundary.
+ */
+ end_off = beg_off + head->hdr_size;
+ if ((beg_off ^ (end_off - 1)) & ~HAMMER_BUFMASK64)
+ return(1);
+ tail = (void *)((char *)head + head->hdr_size - sizeof(*tail));
+ return (_hammer_check_signature(head, tail, beg_off));
+}
+
+/*
+ * Check that the FIFO record is in-bounds given the tail and the
+ * hammer offset. The offset is pointing at the ending boundary of the
+ * record.
+ *
+ * Also checks that the head and tail structures agree with each other,
+ * but does not check beyond the signature, type, and size.
+ */
+static int
+hammer_check_tail_signature(hammer_fifo_tail_t tail, hammer_off_t end_off)
+{
+ hammer_fifo_head_t head;
+ hammer_off_t beg_off;
+
/*
- * Only process UNDO records
+ * tail overlaps buffer boundary
+ */
+ if (((end_off - sizeof(*tail)) ^ (end_off - 1)) & ~HAMMER_BUFMASK64)
+ return(1);
+
+ /*
+ * Calculate the begining offset and make sure the record does
+ * not cross a buffer boundary.
*/
- if (undo->head.hdr_type != HAMMER_HEAD_TYPE_UNDO)
+ beg_off = end_off - tail->tail_size;
+ if ((beg_off ^ (end_off - 1)) & ~HAMMER_BUFMASK64)
+ return(1);
+ head = (void *)((char *)tail + sizeof(*tail) - tail->tail_size);
+ return (_hammer_check_signature(head, tail, beg_off));
+}
+
+static int
+hammer_recover_undo(hammer_mount_t hmp, hammer_volume_t root_volume,
+ hammer_fifo_undo_t undo)
+{
+ hammer_volume_t volume;
+ hammer_buffer_t buffer;
+ hammer_off_t buf_offset;
+ int zone;
+ int error;
+ int vol_no;
+ int bytes;
+ u_int32_t offset;
+
+ /*
+ * Only process UNDO records. Flag if we find other records to
+ * optimize stage2 recovery.
+ */
+ if (undo->head.hdr_type != HAMMER_HEAD_TYPE_UNDO) {
+ if (undo->head.hdr_type == HAMMER_HEAD_TYPE_REDO)
+ hmp->hflags |= HMNT_HASREDO;
return(0);
+ }
/*
* Validate the UNDO record.
*/
- max_bytes = undo->head.hdr_size - sizeof(*undo) - sizeof(*tail);
- if (undo->undo_data_bytes < 0 || undo->undo_data_bytes > max_bytes) {
+ bytes = undo->head.hdr_size - sizeof(*undo) -
+ sizeof(struct hammer_fifo_tail);
+ if (bytes < 0 || undo->undo_data_bytes < 0 ||
+ undo->undo_data_bytes > bytes) {
kprintf("HAMMER: Corrupt UNDO record, undo_data_bytes %d/%d\n",
- undo->undo_data_bytes, max_bytes);
+ undo->undo_data_bytes, bytes);
return(EIO);
}
+ bytes = undo->undo_data_bytes;
+
/*
* The undo offset may only be a zone-1 or zone-2 offset.
*
zone = HAMMER_ZONE_DECODE(undo->undo_offset);
offset = undo->undo_offset & HAMMER_BUFMASK;
- if (offset + undo->undo_data_bytes > HAMMER_BUFSIZE) {
+ if (offset + bytes > HAMMER_BUFSIZE) {
kprintf("HAMMER: Corrupt UNDO record, bad offset\n");
return (EIO);
}
hammer_recover_copy_undo(undo->undo_offset,
(char *)(undo + 1),
(char *)volume->ondisk + offset,
- undo->undo_data_bytes);
+ bytes);
hammer_modify_volume_done(volume);
/*
0, &error);
if (buffer == NULL) {
kprintf("HAMMER: UNDO record, "
- "cannot access buffer %016llx\n",
- (long long)undo->undo_offset);
+ "cannot access buffer %016jx\n",
+ (intmax_t)undo->undo_offset);
break;
}
hammer_modify_buffer(NULL, buffer, NULL, 0);
hammer_recover_copy_undo(undo->undo_offset,
(char *)(undo + 1),
(char *)buffer->ondisk + offset,
- undo->undo_data_bytes);
+ bytes);
hammer_modify_buffer_done(buffer);
/*
char *src, char *dst, int bytes)
{
if (hammer_debug_general & 0x0080) {
- kprintf("UNDO %016llx: %d\n",
- (long long)undo_offset, bytes);
+ kprintf("UNDO %016jx: %d\n",
+ (intmax_t)undo_offset, bytes);
}
#if 0
- kprintf("UNDO %016llx:", (long long)undo_offset);
+ kprintf("UNDO %016jx:", (intmax_t)undo_offset);
hammer_recover_debug_dump(22, dst, bytes);
kprintf("%22s", "to:");
hammer_recover_debug_dump(22, src, bytes);
#include "hammer.h"
static int hammer_und_rb_compare(hammer_undo_t node1, hammer_undo_t node2);
+static void hammer_format_undo(void *base, u_int32_t seqno);
RB_GENERATE2(hammer_und_rb_tree, hammer_undo, rb_node,
hammer_und_rb_compare, hammer_off_t, offset);
}
/*
- * Generate an UNDO record for the block of data at the specified zone1
+ * Generate UNDO record(s) for the block of data at the specified zone1
* or zone2 offset.
*
* The recovery code will execute UNDOs in reverse order, allowing overlaps.
* All the UNDOs are executed together so if we already laid one down we
* do not have to lay another one down for the same range.
+ *
+ * For HAMMER version 4+ UNDO a 512 byte boundary is enforced and a PAD
+ * will be laid down for any unused space. UNDO FIFO media structures
+ * will implement the hdr_seq field (it used to be reserved01), and
+ * both flush and recovery mechanics will be very different.
*/
int
-hammer_generate_undo(hammer_transaction_t trans, hammer_io_t io,
+hammer_generate_undo(hammer_transaction_t trans,
hammer_off_t zone_off, void *base, int len)
{
hammer_mount_t hmp;
hammer_off_t next_offset;
int error;
int bytes;
+ int n;
hmp = trans->hmp;
/* no undo recursion */
hammer_modify_volume(NULL, root_volume, NULL, 0);
-
hammer_lock_ex(&hmp->undo_lock);
-again:
- /*
- * Allocate space in the FIFO
- */
- bytes = ((len + HAMMER_HEAD_ALIGN_MASK) & ~HAMMER_HEAD_ALIGN_MASK) +
- sizeof(struct hammer_fifo_undo) +
- sizeof(struct hammer_fifo_tail);
- if (hammer_undo_space(trans) < bytes + HAMMER_BUFSIZE*2)
- panic("hammer: insufficient undo FIFO space!");
- next_offset = undomap->next_offset;
+ /* undo had better not roll over (loose test) */
+ if (hammer_undo_space(trans) < len + HAMMER_BUFSIZE*3)
+ panic("hammer: insufficient undo FIFO space!");
/*
- * Wrap next_offset
+ * Loop until the undo for the entire range has been laid down.
*/
- if (undomap->next_offset == undomap->alloc_offset) {
- next_offset = HAMMER_ZONE_ENCODE(HAMMER_ZONE_UNDO_INDEX, 0);
- undomap->next_offset = next_offset;
+ while (len) {
+ /*
+ * Fetch the layout offset in the UNDO FIFO, wrap it as
+ * necessary.
+ */
+ if (undomap->next_offset == undomap->alloc_offset) {
+ undomap->next_offset =
+ HAMMER_ZONE_ENCODE(HAMMER_ZONE_UNDO_INDEX, 0);
+ }
+ next_offset = undomap->next_offset;
+
+ /*
+ * This is a tail-chasing FIFO, when we hit the start of a new
+ * buffer we don't have to read it in.
+ */
+ if ((next_offset & HAMMER_BUFMASK) == 0) {
+ undo = hammer_bnew(hmp, next_offset, &error, &buffer);
+ hammer_format_undo(undo, hmp->undo_seqno ^ 0x40000000);
+ } else {
+ undo = hammer_bread(hmp, next_offset, &error, &buffer);
+ }
+ if (error)
+ break;
+ hammer_modify_buffer(NULL, buffer, NULL, 0);
+
+ /*
+ * Calculate how big a media structure fits up to the next
+ * alignment point and how large a data payload we can
+ * accomodate.
+ *
+ * If n calculates to 0 or negative there is no room for
+ * anything but a PAD.
+ */
+ bytes = HAMMER_UNDO_ALIGN -
+ ((int)next_offset & HAMMER_UNDO_MASK);
+ n = bytes -
+ (int)sizeof(struct hammer_fifo_undo) -
+ (int)sizeof(struct hammer_fifo_tail);
+
+ /*
+ * If available space is insufficient for any payload
+ * we have to lay down a PAD.
+ *
+ * The minimum PAD is 8 bytes and the head and tail will
+ * overlap each other in that case. PADs do not have
+ * sequence numbers or CRCs.
+ *
+ * A PAD may not start on a boundary. That is, every
+ * 512-byte block in the UNDO/REDO FIFO must begin with
+ * a record containing a sequence number.
+ */
+ if (n <= 0) {
+ KKASSERT(bytes >= sizeof(struct hammer_fifo_tail));
+ KKASSERT(((int)next_offset & HAMMER_UNDO_MASK) != 0);
+ tail = (void *)((char *)undo + bytes - sizeof(*tail));
+ if ((void *)undo != (void *)tail) {
+ tail->tail_signature = HAMMER_TAIL_SIGNATURE;
+ tail->tail_type = HAMMER_HEAD_TYPE_PAD;
+ tail->tail_size = bytes;
+ }
+ undo->head.hdr_signature = HAMMER_HEAD_SIGNATURE;
+ undo->head.hdr_type = HAMMER_HEAD_TYPE_PAD;
+ undo->head.hdr_size = bytes;
+ /* NO CRC OR SEQ NO */
+ undomap->next_offset += bytes;
+ hammer_modify_buffer_done(buffer);
+ hammer_stats_undo += bytes;
+ continue;
+ }
+
+ /*
+ * Calculate the actual payload and recalculate the size
+ * of the media structure as necessary.
+ */
+ if (n > len) {
+ n = len;
+ bytes = ((n + HAMMER_HEAD_ALIGN_MASK) &
+ ~HAMMER_HEAD_ALIGN_MASK) +
+ (int)sizeof(struct hammer_fifo_undo) +
+ (int)sizeof(struct hammer_fifo_tail);
+ }
+ if (hammer_debug_general & 0x0080) {
+ kprintf("undo %016llx %d %d\n",
+ (long long)next_offset, bytes, n);
+ }
+
+ undo->head.hdr_signature = HAMMER_HEAD_SIGNATURE;
+ undo->head.hdr_type = HAMMER_HEAD_TYPE_UNDO;
+ undo->head.hdr_size = bytes;
+ undo->head.hdr_seq = hmp->undo_seqno++;
+ undo->head.hdr_crc = 0;
+ undo->undo_offset = zone_off;
+ undo->undo_data_bytes = n;
+ bcopy(base, undo + 1, n);
+
+ tail = (void *)((char *)undo + bytes - sizeof(*tail));
+ tail->tail_signature = HAMMER_TAIL_SIGNATURE;
+ tail->tail_type = HAMMER_HEAD_TYPE_UNDO;
+ tail->tail_size = bytes;
+
+ KKASSERT(bytes >= sizeof(undo->head));
+ undo->head.hdr_crc = crc32(undo, HAMMER_FIFO_HEAD_CRCOFF) ^
+ crc32(&undo->head + 1, bytes - sizeof(undo->head));
+ undomap->next_offset += bytes;
+ hammer_stats_undo += bytes;
+
+ /*
+ * Before we finish off the buffer we have to deal with any
+ * junk between the end of the media structure we just laid
+ * down and the UNDO alignment boundary. We do this by laying
+ * down a dummy PAD. Even though we will probably overwrite
+ * it almost immediately we have to do this so recovery runs
+ * can iterate the UNDO space without having to depend on
+ * the indices in the volume header.
+ *
+ * This dummy PAD will be overwritten on the next undo so
+ * we do not adjust undomap->next_offset.
+ */
+ bytes = HAMMER_UNDO_ALIGN -
+ ((int)undomap->next_offset & HAMMER_UNDO_MASK);
+ if (bytes != HAMMER_UNDO_ALIGN) {
+ KKASSERT(bytes >= sizeof(struct hammer_fifo_tail));
+ undo = (void *)(tail + 1);
+ tail = (void *)((char *)undo + bytes - sizeof(*tail));
+ if ((void *)undo != (void *)tail) {
+ tail->tail_signature = HAMMER_TAIL_SIGNATURE;
+ tail->tail_type = HAMMER_HEAD_TYPE_PAD;
+ tail->tail_size = bytes;
+ }
+ undo->head.hdr_signature = HAMMER_HEAD_SIGNATURE;
+ undo->head.hdr_type = HAMMER_HEAD_TYPE_PAD;
+ undo->head.hdr_size = bytes;
+ /* NO CRC OR SEQ NO */
+ }
+ hammer_modify_buffer_done(buffer);
+
+ /*
+ * Adjust for loop
+ */
+ len -= n;
+ base = (char *)base + n;
+ zone_off += n;
+ }
+ hammer_modify_volume_done(root_volume);
+ hammer_unlock(&hmp->undo_lock);
+ /* XXX flush volume header */
+
+ if (buffer)
+ hammer_rel_buffer(buffer, 0);
+ return(error);
+}
+
+#if 0
+/*
+ * HAMMER version 4+ REDO support.
+ *
+ * Generate REDO record(s) for logical data writes to a file. REDO records
+ * are only created if the created inode was previously synced (such that
+ * it will still exist after any recovery), and also only for a limited
+ * amount of write data between fsyncs.
+ *
+ * REDO records are used to improve fsync() performance. Instead of having
+ * to go through a complete flush cycle involving at least two disk
+ * synchronizations the fsync need only flush UNDO FIFO buffers through
+ * the related REDO records, which is a single synchronization requiring
+ * no track seeking. If a recovery becomes necessary the recovery code
+ * will generate logical data writes based on the REDO records encountered.
+ * That is, the recovery code will UNDO any partial meta-data/data writes
+ * at the raw disk block level and then REDO the data writes at the logical
+ * level.
+ */
+int
+hammer_generate_redo(hammer_transaction_t trans, hammer_inode_t ip,
+ hammer_off_t file_off, hammer_off_t zone_off,
+ void *base, int len)
+{
+}
+#endif
+
+/*
+ * Preformat a new UNDO block. We could read the old one in but we get
+ * better performance if we just pre-format a new one.
+ *
+ * The recovery code always works forwards so the caller just makes sure the
+ * seqno is not contiguous with prior UNDOs or ancient UNDOs now being
+ * overwritten.
+ */
+static
+void
+hammer_format_undo(void *base, u_int32_t seqno)
+{
+ hammer_fifo_head_t head;
+ hammer_fifo_tail_t tail;
+ int i;
+ int bytes = HAMMER_UNDO_ALIGN;
+
+ bzero(base, HAMMER_BUFSIZE);
+
+ for (i = 0; i < HAMMER_BUFSIZE; i += bytes) {
+ head = (void *)((char *)base + i);
+ tail = (void *)((char *)head + bytes - sizeof(*tail));
+
+ head->hdr_signature = HAMMER_HEAD_SIGNATURE;
+ head->hdr_type = HAMMER_HEAD_TYPE_DUMMY;
+ head->hdr_size = bytes;
+ head->hdr_seq = seqno++;
+ head->hdr_crc = 0;
+
+ tail->tail_signature = HAMMER_TAIL_SIGNATURE;
+ tail->tail_type = HAMMER_HEAD_TYPE_DUMMY;
+ tail->tail_size = bytes;
+
+ head->hdr_crc = crc32(head, HAMMER_FIFO_HEAD_CRCOFF) ^
+ crc32(head + 1, bytes - sizeof(*head));
}
+}
+
+/*
+ * HAMMER version 4+ conversion support.
+ *
+ * Convert a HAMMER version < 4 UNDO FIFO area to a 4+ UNDO FIFO area.
+ * The 4+ UNDO FIFO area is backwards compatible. The conversion is
+ * needed to initialize the sequence space and place headers on the
+ * new 512-byte undo boundary.
+ */
+int
+hammer_upgrade_undo_4(hammer_transaction_t trans)
+{
+ hammer_mount_t hmp;
+ hammer_volume_t root_volume;
+ hammer_blockmap_t undomap;
+ hammer_buffer_t buffer = NULL;
+ hammer_fifo_head_t head;
+ hammer_fifo_tail_t tail;
+ hammer_off_t next_offset;
+ u_int32_t seqno;
+ int error;
+ int bytes;
+
+ hmp = trans->hmp;
+
+ root_volume = trans->rootvol;
+
+ /* no undo recursion */
+ hammer_lock_ex(&hmp->undo_lock);
+ hammer_modify_volume(NULL, root_volume, NULL, 0);
/*
- * This is a tail-chasing FIFO, when we hit the start of a new
- * buffer we don't have to read it in.
+ * Adjust the in-core undomap and the on-disk undomap.
*/
- if ((next_offset & HAMMER_BUFMASK) == 0)
- undo = hammer_bnew(hmp, next_offset, &error, &buffer);
- else
- undo = hammer_bread(hmp, next_offset, &error, &buffer);
- if (error)
- goto done;
-
- hammer_modify_buffer(NULL, buffer, NULL, 0);
+ next_offset = HAMMER_ZONE_ENCODE(HAMMER_ZONE_UNDO_INDEX, 0);
+ undomap = &hmp->blockmap[HAMMER_ZONE_UNDO_INDEX];
+ undomap->next_offset = next_offset;
+ undomap->first_offset = next_offset;
- KKASSERT(undomap->next_offset == next_offset);
+ undomap = &root_volume->ondisk->vol0_blockmap[HAMMER_ZONE_UNDO_INDEX];
+ undomap->next_offset = next_offset;
+ undomap->first_offset = next_offset;
/*
- * The FIFO entry would cross a buffer boundary, PAD to the end
- * of the buffer and try again. Due to our data alignment, the
- * worst case (smallest) PAD record is 8 bytes. PAD records only
- * populate the first 8 bytes of hammer_fifo_head and the tail may
- * be at the same offset as the head.
+ * Loop over the entire UNDO space creating DUMMY entries. Sequence
+ * numbers are assigned.
*/
- if ((next_offset ^ (next_offset + bytes)) & ~HAMMER_BUFMASK64) {
- bytes = HAMMER_BUFSIZE - ((int)next_offset & HAMMER_BUFMASK);
- tail = (void *)((char *)undo + bytes - sizeof(*tail));
- if ((void *)undo != (void *)tail) {
- tail->tail_signature = HAMMER_TAIL_SIGNATURE;
- tail->tail_type = HAMMER_HEAD_TYPE_PAD;
- tail->tail_size = bytes;
- }
- undo->head.hdr_signature = HAMMER_HEAD_SIGNATURE;
- undo->head.hdr_type = HAMMER_HEAD_TYPE_PAD;
- undo->head.hdr_size = bytes;
- /* NO CRC */
- undomap->next_offset += bytes;
+ seqno = 0;
+ bytes = HAMMER_UNDO_ALIGN;
+
+ while (next_offset != undomap->alloc_offset) {
+ head = hammer_bnew(hmp, next_offset, &error, &buffer);
+ if (error)
+ break;
+ hammer_modify_buffer(NULL, buffer, NULL, 0);
+ tail = (void *)((char *)head + bytes - sizeof(*tail));
+
+ head->hdr_signature = HAMMER_HEAD_SIGNATURE;
+ head->hdr_type = HAMMER_HEAD_TYPE_DUMMY;
+ head->hdr_size = bytes;
+ head->hdr_seq = seqno;
+ head->hdr_crc = 0;
+
+ tail = (void *)((char *)head + bytes - sizeof(*tail));
+ tail->tail_signature = HAMMER_TAIL_SIGNATURE;
+ tail->tail_type = HAMMER_HEAD_TYPE_DUMMY;
+ tail->tail_size = bytes;
+
+ head->hdr_crc = crc32(head, HAMMER_FIFO_HEAD_CRCOFF) ^
+ crc32(head + 1, bytes - sizeof(*head));
hammer_modify_buffer_done(buffer);
+
hammer_stats_undo += bytes;
- goto again;
- }
- if (hammer_debug_general & 0x0080) {
- kprintf("undo %016llx %d %d\n",
- (long long)next_offset, bytes, len);
+ next_offset += HAMMER_UNDO_ALIGN;
+ ++seqno;
}
/*
- * We're good, create the entry.
+ * The sequence number will be the next sequence number to lay down.
*/
- undo->head.hdr_signature = HAMMER_HEAD_SIGNATURE;
- undo->head.hdr_type = HAMMER_HEAD_TYPE_UNDO;
- undo->head.hdr_size = bytes;
- undo->head.reserved01 = 0;
- undo->head.hdr_crc = 0;
- undo->undo_offset = zone_off;
- undo->undo_data_bytes = len;
- bcopy(base, undo + 1, len);
-
- tail = (void *)((char *)undo + bytes - sizeof(*tail));
- tail->tail_signature = HAMMER_TAIL_SIGNATURE;
- tail->tail_type = HAMMER_HEAD_TYPE_UNDO;
- tail->tail_size = bytes;
-
- KKASSERT(bytes >= sizeof(undo->head));
- undo->head.hdr_crc = crc32(undo, HAMMER_FIFO_HEAD_CRCOFF) ^
- crc32(&undo->head + 1, bytes - sizeof(undo->head));
- undomap->next_offset += bytes;
- hammer_stats_undo += bytes;
+ hmp->undo_seqno = seqno;
+ kprintf("version upgrade seqno start %08x\n", seqno);
- hammer_modify_buffer_done(buffer);
-done:
hammer_modify_volume_done(root_volume);
hammer_unlock(&hmp->undo_lock);
if (buffer)
hammer_rel_buffer(buffer, 0);
- return(error);
+ return (error);
}
/*