HAMMER - Implement experimental volume removal
authorMichael Neumann <mneumann@ntecs.de>
Fri, 11 Dec 2009 21:59:21 +0000 (22:59 +0100)
committerMichael Neumann <mneumann@ntecs.de>
Fri, 11 Dec 2009 21:59:21 +0000 (22:59 +0100)
A volume other than the root volume can be removed with:

    hammer volume-del device filesystem

WARNING: Experimental!

sbin/hammer/cmd_volume.c
sbin/hammer/hammer.8
sbin/hammer/hammer.c
sbin/hammer/hammer.h
sys/vfs/hammer/hammer.h
sys/vfs/hammer/hammer_blockmap.c
sys/vfs/hammer/hammer_ioctl.c
sys/vfs/hammer/hammer_ioctl.h
sys/vfs/hammer/hammer_vfsops.c
sys/vfs/hammer/hammer_volume.c

index 1dbe2c4..2e2846d 100644 (file)
  * Volume operations:
  *
  *   - volume-add: Add new volume to HAMMER filesystem
+ *   - volume-del: Remove volume from HAMMER filesystem
  */
 
 #include "hammer.h"
 #include <string.h>
+#include <stdlib.h>
 
 static uint64_t check_volume(const char *vol_name);
-static void volume_add_usage(int exit_code);
 
 /*
  * volume-add <device> <filesystem>
@@ -51,14 +52,17 @@ static void volume_add_usage(int exit_code);
 void
 hammer_cmd_volume_add(char **av, int ac)
 {
-       struct hammer_ioc_volume_add ioc;
+       struct hammer_ioc_volume ioc;
        int fd;
 
+       if (ac != 2) {
+               fprintf(stderr, "hammer volume-add <device> <filesystem>\n");
+               exit(1);
+       }
+
        char *device = av[0];
        char *filesystem = av[1];
 
-       if (ac != 2)
-               volume_add_usage(1);
         fd = open(filesystem, O_RDONLY);
        if (fd < 0) {
                fprintf(stderr, "hammer volume-add: unable to access %s: %s\n",
@@ -84,12 +88,44 @@ hammer_cmd_volume_add(char **av, int ac)
        close(fd);
 }
 
-static
+/*
+ * volume-del <device> <filesystem>
+ */
 void
-volume_add_usage(int exit_code)
+hammer_cmd_volume_del(char **av, int ac)
 {
-       fprintf(stderr, "hammer volume-add <device> <filesystem>\n");
-       exit(exit_code);
+       struct hammer_ioc_volume ioc;
+       int fd;
+
+       if (ac != 2) {
+               fprintf(stderr, "hammer volume-del <device> <filesystem>\n");
+               exit(1);
+       }
+
+
+       char *device = av[0];
+       char *filesystem = av[1];
+
+        fd = open(filesystem, O_RDONLY);
+       if (fd < 0) {
+               fprintf(stderr, "hammer volume-add: unable to access %s: %s\n",
+                       filesystem, strerror(errno));
+               exit(1);
+       }
+
+       /*
+        * volume-del ioctl
+        */
+       bzero(&ioc, sizeof(ioc));
+       strncpy(ioc.device_name, device, MAXPATHLEN);
+
+       if (ioctl(fd, HAMMERIOC_DEL_VOLUME, &ioc) < 0) {
+               fprintf(stderr, "hammer volume-del ioctl: %s\n",
+                       strerror(errno));
+               exit(1);
+       }
+
+       close(fd);
 }
 
 /*
index 43edbb1..733086b 100644 (file)
@@ -505,6 +505,19 @@ colon-separated to
 .Pa /etc/fstab
 and
 .Xr mount_hammer 8 .
+.\" ==== volume-del ====
+.It Cm volume-del Ar device Ar filesystem
+This command will remove volume
+.Ar device
+from
+.Ar filesystem .
+.Pp
+Remember that you have to remove
+.Ar device
+from the colon-separated list in
+.Pa /etc/fstab
+and
+.Xr mount_hammer 8 .
 .\" ==== snapshot ====
 .It Cm snapshot Oo Ar filesystem Oc Ar snapshot-dir
 .It Cm snapshot Ar filesystem Ar snapshot-dir Op Ar note
index 994957b..6ca4447 100644 (file)
@@ -401,6 +401,10 @@ main(int ac, char **av)
                hammer_cmd_volume_add(av + 1, ac - 1);
                exit(0);
        }
+       if (strcmp(av[0], "volume-del") == 0) {
+               hammer_cmd_volume_del(av + 1, ac - 1);
+               exit(0);
+       }
 
        uuid_name_lookup(&Hammer_FSType, "DragonFly HAMMER", &status);
        if (status != uuid_s_ok) {
@@ -526,6 +530,7 @@ usage(int exit_code)
                "hammer version <filesystem>\n"
                "hammer version-upgrade <filesystem> <version> [force]\n"
                "hammer volume-add <device> <filesystem>\n"
+               "hammer volume-del <device> <filesystem>\n"
        );
 
        fprintf(stderr, "\nHAMMER utility version 3+ commands:\n");
index 6a486a7..01b847e 100644 (file)
@@ -111,6 +111,7 @@ void hammer_cmd_info(void);
 void hammer_cmd_get_version(char **av, int ac);
 void hammer_cmd_set_version(char **av, int ac);
 void hammer_cmd_volume_add(char **av, int ac);
+void hammer_cmd_volume_del(char **av, int ac);
 
 void hammer_get_cycle(hammer_base_elm_t base, hammer_tid_t *tidp);
 void hammer_set_cycle(hammer_base_elm_t base, hammer_tid_t tid);
index ad82881..1192b84 100644 (file)
@@ -750,6 +750,8 @@ struct hammer_mount {
        int     last_newrecords;
        int     count_newrecords;
 
+       int     volume_to_remove; /* volume that is currently being removed */
+
        int     inode_reclaims; /* inodes pending reclaim by flusher */
        int     count_inodes;   /* total number of inodes */
        int     count_iqueued;  /* inodes queued to flusher */
@@ -1242,7 +1244,9 @@ int hammer_ioc_upgrade_pseudofs(hammer_transaction_t trans, hammer_inode_t ip,
 int hammer_ioc_wait_pseudofs(hammer_transaction_t trans, hammer_inode_t ip,
                         struct hammer_ioc_pseudofs_rw *pfs);
 int hammer_ioc_volume_add(hammer_transaction_t trans, hammer_inode_t ip,
-                        struct hammer_ioc_volume_add *ioc);
+                        struct hammer_ioc_volume *ioc);
+int hammer_ioc_volume_del(hammer_transaction_t trans, hammer_inode_t ip,
+                        struct hammer_ioc_volume *ioc);
 
 int hammer_signal_check(hammer_mount_t hmp);
 
index ef77d72..bff567f 100644 (file)
@@ -164,6 +164,17 @@ again:
         */
        layer1_offset = freemap->phys_offset +
                        HAMMER_BLOCKMAP_LAYER1_OFFSET(next_offset);
+
+       /*
+        * Skip this block if it is belonging to a volume that we are
+         * currently trying to remove from the file-system.
+        */
+       if ((int)HAMMER_VOL_DECODE(layer1_offset) == hmp->volume_to_remove) {
+               next_offset = (next_offset + HAMMER_BLOCKMAP_LAYER2) &
+                             ~HAMMER_BLOCKMAP_LAYER2_MASK;
+               goto again;
+       }
+
        layer1 = hammer_bread(hmp, layer1_offset, errorp, &buffer1);
        if (*errorp) {
                result_offset = 0;
index 3e9660c..2e039f0 100644 (file)
@@ -162,7 +162,15 @@ hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag,
                        error = priv_check_cred(cred, PRIV_HAMMER_VOLUME, 0);
                        if (error == 0)
                                error = hammer_ioc_volume_add(&trans, ip,
-                                           (struct hammer_ioc_volume_add *)data);
+                                           (struct hammer_ioc_volume *)data);
+               }
+               break;
+       case HAMMERIOC_DEL_VOLUME:
+               if (error == 0) {
+                       error = priv_check_cred(cred, PRIV_HAMMER_VOLUME, 0);
+                       if (error == 0)
+                               error = hammer_ioc_volume_del(&trans, ip,
+                                           (struct hammer_ioc_volume *)data);
                }
                break;
        case HAMMERIOC_ADD_SNAPSHOT:
index 951c925..ace6fef 100644 (file)
@@ -335,7 +335,7 @@ struct hammer_ioc_version {
        char                    description[64];
 };
 
-struct hammer_ioc_volume_add {
+struct hammer_ioc_volume {
        struct hammer_ioc_head head;
        char                    device_name[MAXPATHLEN];
        int64_t                 vol_size;
@@ -451,12 +451,13 @@ struct hammer_ioc_config {
 #define HAMMERIOC_SET_VERSION  _IOWR('h',14,struct hammer_ioc_version)
 #define HAMMERIOC_REBALANCE    _IOWR('h',15,struct hammer_ioc_rebalance)
 #define HAMMERIOC_GET_INFO     _IOR('h',16,struct hammer_ioc_info)
-#define HAMMERIOC_ADD_VOLUME   _IOWR('h',17,struct hammer_ioc_volume_add)
+#define HAMMERIOC_ADD_VOLUME   _IOWR('h',17,struct hammer_ioc_volume)
 #define HAMMERIOC_ADD_SNAPSHOT _IOWR('h',18,struct hammer_ioc_snapshot)
 #define HAMMERIOC_DEL_SNAPSHOT _IOWR('h',19,struct hammer_ioc_snapshot)
 #define HAMMERIOC_GET_SNAPSHOT _IOWR('h',20,struct hammer_ioc_snapshot)
 #define HAMMERIOC_GET_CONFIG   _IOWR('h',21,struct hammer_ioc_config)
 #define HAMMERIOC_SET_CONFIG   _IOWR('h',22,struct hammer_ioc_config)
+#define HAMMERIOC_DEL_VOLUME   _IOWR('h',24,struct hammer_ioc_volume)
 
 #endif
 
index 674ec2e..c78c33e 100644 (file)
@@ -434,6 +434,8 @@ hammer_vfs_mount(struct mount *mp, char *mntpt, caddr_t data,
                hmp->asof = HAMMER_MAX_TID;
        }
 
+       hmp->volume_to_remove = -1;
+
        /*
         * Re-open read-write if originally read-only, or vise-versa.
         *
index 50af62c..ff6b64f 100644 (file)
@@ -51,35 +51,19 @@ hammer_format_volume_header(struct hammer_mount *hmp, struct vnode *devvp,
        int64_t vol_size, int64_t boot_area_size, int64_t mem_area_size);
 
 static uint64_t
-hammer_format_freemap(struct hammer_mount *hmp,
-       hammer_transaction_t trans,
-       hammer_volume_t volume);
+hammer_format_freemap(hammer_transaction_t trans, hammer_volume_t volume);
 
-static uint64_t
-hammer_format_layer2_chunk(struct hammer_mount *hmp,
-       hammer_transaction_t trans,
-       hammer_off_t phys_offset,
-       hammer_off_t aligned_buf_end_off,
-       hammer_buffer_t *bufferp,
-       int *errorp);
+static int
+hammer_free_freemap(hammer_transaction_t trans, hammer_volume_t volume);
 
-static void
-hammer_set_layer1_entry(struct hammer_mount *hmp,
-       hammer_transaction_t trans,
-       hammer_off_t phys_offset,
-       uint64_t free_bigblocks,
-       hammer_blockmap_t freemap,
-       hammer_buffer_t *bufferp,
-       int *errorp);
 
 int
 hammer_ioc_volume_add(hammer_transaction_t trans, hammer_inode_t ip,
-               struct hammer_ioc_volume_add *ioc)
+               struct hammer_ioc_volume *ioc)
 {
        struct hammer_mount *hmp = trans->hmp;
        struct mount *mp = hmp->mp;
        hammer_volume_t volume;
-       hammer_volume_t root_volume;
        int error;
 
        if (mp->mnt_flag & MNT_RDONLY) {
@@ -156,32 +140,29 @@ hammer_ioc_volume_add(hammer_transaction_t trans, hammer_inode_t ip,
 
        volume = hammer_get_volume(hmp, free_vol_no, &error);
        KKASSERT(volume != NULL && error == 0);
-       root_volume = hammer_get_root_volume(hmp, &error);
-       KKASSERT(root_volume != NULL && error == 0);
 
        uint64_t total_free_bigblocks =
-               hammer_format_freemap(hmp, trans, volume);
+               hammer_format_freemap(trans, volume);
 
        /*
         * Increase the total number of bigblocks
         */
-       hammer_modify_volume_field(trans, root_volume,
+       hammer_modify_volume_field(trans, trans->rootvol,
                vol0_stat_bigblocks);
-       root_volume->ondisk->vol0_stat_bigblocks += total_free_bigblocks;
-       hammer_modify_volume_done(root_volume);
+       trans->rootvol->ondisk->vol0_stat_bigblocks += total_free_bigblocks;
+       hammer_modify_volume_done(trans->rootvol);
 
        /*
         * Increase the number of free bigblocks
         * (including the copy in hmp)
         */
-       hammer_modify_volume_field(trans, root_volume,
+       hammer_modify_volume_field(trans, trans->rootvol,
                vol0_stat_freebigblocks);
-       root_volume->ondisk->vol0_stat_freebigblocks += total_free_bigblocks;
+       trans->rootvol->ondisk->vol0_stat_freebigblocks += total_free_bigblocks;
        hmp->copy_stat_freebigblocks =
-               root_volume->ondisk->vol0_stat_freebigblocks;
-       hammer_modify_volume_done(root_volume);
+               trans->rootvol->ondisk->vol0_stat_freebigblocks;
+       hammer_modify_volume_done(trans->rootvol);
 
-       hammer_rel_volume(root_volume, 0);
        hammer_rel_volume(volume, 0);
 
        hammer_unlock(&hmp->blkmap_lock);
@@ -193,151 +174,374 @@ end:
        return (error);
 }
 
-static uint64_t
-hammer_format_freemap(struct hammer_mount *hmp,
-       hammer_transaction_t trans,
-       hammer_volume_t volume)
+
+/*
+ * Remove a volume.
+ */
+int
+hammer_ioc_volume_del(hammer_transaction_t trans, hammer_inode_t ip,
+               struct hammer_ioc_volume *ioc)
 {
-       hammer_off_t phys_offset;
+       struct hammer_mount *hmp = trans->hmp;
+       struct mount *mp = hmp->mp;
+       hammer_volume_t volume;
+       int error = 0;
+
+       if (mp->mnt_flag & MNT_RDONLY) {
+               kprintf("Cannot del volume from read-only HAMMER filesystem\n");
+               return (EINVAL);
+       }
+
+
+       volume = NULL;
+
+       /*
+        * find volume by volname
+        */
+       for (int vol_no = 0; vol_no < HAMMER_MAX_VOLUMES; ++vol_no) {
+               volume = hammer_get_volume(hmp, vol_no, &error);
+               if (volume == NULL && error == ENOENT) {
+                       /*
+                        * Skip unused volume numbers
+                        */
+                       error = 0;
+                       continue;
+               }
+               KKASSERT(volume != NULL && error == 0);
+               if (strcmp(volume->vol_name, ioc->device_name) == 0) {
+                       break;
+               }
+               volume = NULL;
+       }
+
+       if (!volume) {
+               kprintf("Couldn't find volume\n");
+               return (EINVAL);
+       }
+
+       if (volume == trans->rootvol) {
+               kprintf("Cannot remove root-volume\n");
+               hammer_rel_volume(volume, 0);
+               return (EINVAL);
+       }
+
+       /*
+        *
+        */
+
+       hmp->volume_to_remove = volume->vol_no;
+
+       struct hammer_ioc_reblock reblock;
+       bzero(&reblock, sizeof(reblock));
+
+       reblock.key_beg.localization = HAMMER_MIN_LOCALIZATION;
+       reblock.key_beg.obj_id = HAMMER_MIN_OBJID;
+       reblock.key_end.localization = HAMMER_MAX_LOCALIZATION;
+       reblock.key_end.obj_id = HAMMER_MAX_OBJID;
+       //reblock.head.flags = flags & HAMMER_IOC_DO_FLAGS;
+       reblock.free_level = HAMMER_LARGEBLOCK_SIZE;
+       reblock.free_level = 0;
+
+       error = hammer_ioc_reblock(trans, ip, &reblock);
+
+       if (error) {
+               kprintf("reblock failed: %d\n", error);
+               hmp->volume_to_remove = -1;
+               hammer_rel_volume(volume, 0);
+               return (error);
+       }
+
+       hammer_sync_lock_sh(trans);
+       hammer_lock_ex(&hmp->blkmap_lock);
+
+       error = hammer_free_freemap(trans, volume);
+       if (error) {
+               kprintf("Failed to free volume\n");
+               hmp->volume_to_remove = -1;
+               hammer_rel_volume(volume, 0);
+               hammer_unlock(&hmp->blkmap_lock);
+               hammer_sync_unlock(trans);
+               return (error);
+       }
+
+       hmp->volume_to_remove = -1;
+       hammer_rel_volume(volume, 1);
+
+       /* XXX: unload volume! */
+       /*error = hammer_unload_volume(volume, NULL);
+       if (error == -1) {
+               kprintf("Failed to unload volume\n");
+               hammer_unlock(&hmp->blkmap_lock);
+               hammer_sync_unlock(trans);
+               return (error);
+       }*/
+
+       --hmp->nvolumes;
+
+       /*
+        * Set each volume's new value of the vol_count field.
+        */
+       for (int vol_no = 0; vol_no < HAMMER_MAX_VOLUMES; ++vol_no) {
+               volume = hammer_get_volume(hmp, vol_no, &error);
+               if (volume == NULL && error == ENOENT) {
+                       /*
+                        * Skip unused volume numbers
+                        */
+                       error = 0;
+                       continue;
+               }
+               KKASSERT(volume != NULL && error == 0);
+               hammer_modify_volume_field(trans, volume, vol_count);
+               volume->ondisk->vol_count = hmp->nvolumes;
+               hammer_modify_volume_done(volume);
+               hammer_rel_volume(volume, 0);
+       }
+
+       hammer_unlock(&hmp->blkmap_lock);
+       hammer_sync_unlock(trans);
+
+       return (0);
+}
+
+
+/*
+ * Iterate over all usable L1 entries of the volume and
+ * the corresponding L2 entries.
+ */
+static int
+hammer_iterate_l1l2_entries(hammer_transaction_t trans, hammer_volume_t volume,
+       int (*callback)(hammer_transaction_t, hammer_buffer_t *,
+               struct hammer_blockmap_layer1*, struct hammer_blockmap_layer2 *,
+               hammer_off_t, int, void *),
+       void *data)
+{
+       struct hammer_mount *hmp = trans->hmp;
+       hammer_blockmap_t freemap = &hmp->blockmap[HAMMER_ZONE_FREEMAP_INDEX];
        hammer_buffer_t buffer = NULL;
-       hammer_blockmap_t freemap;
-       hammer_off_t aligned_buf_end_off;
-       uint64_t free_bigblocks;
-       uint64_t total_free_bigblocks;
        int error = 0;
 
-       total_free_bigblocks = 0;
+       hammer_off_t phys_off;
+       hammer_off_t block_off;
+       hammer_off_t layer1_off;
+       hammer_off_t layer2_off;
+       hammer_off_t aligned_buf_end_off;
+       struct hammer_blockmap_layer1 *layer1;
+       struct hammer_blockmap_layer2 *layer2;
 
        /*
-        * Calculate the usable size of the new volume, which
+        * Calculate the usable size of the volume, which
         * must be aligned at a bigblock (8 MB) boundary.
         */
-       aligned_buf_end_off = HAMMER_ENCODE_RAW_BUFFER(volume->ondisk->vol_no,
+       aligned_buf_end_off = (HAMMER_ENCODE_RAW_BUFFER(volume->ondisk->vol_no,
                (volume->ondisk->vol_buf_end - volume->ondisk->vol_buf_beg)
-               & ~HAMMER_LARGEBLOCK_MASK64);
-
-       freemap = &hmp->blockmap[HAMMER_ZONE_FREEMAP_INDEX];
+               & ~HAMMER_LARGEBLOCK_MASK64));
 
        /*
-        * Iterate the volume's address space in chunks of 4 TB,
-        * where each chunk consists of at least one physically
-        * available 8 MB bigblock.
+        * Iterate the volume's address space in chunks of 4 TB, where each
+        * chunk consists of at least one physically available 8 MB bigblock.
         *
         * For each chunk we need one L1 entry and one L2 bigblock.
         * We use the first bigblock of each chunk as L2 block.
         */
-       for (phys_offset = HAMMER_ENCODE_RAW_BUFFER(volume->ondisk->vol_no, 0);
-            phys_offset < aligned_buf_end_off;
-            phys_offset += HAMMER_BLOCKMAP_LAYER2) {
-
-               free_bigblocks = hammer_format_layer2_chunk(hmp, trans,
-                       phys_offset, aligned_buf_end_off, &buffer, &error);
-               KKASSERT(error == 0);
+       for (phys_off = HAMMER_ENCODE_RAW_BUFFER(volume->ondisk->vol_no, 0);
+            phys_off < aligned_buf_end_off;
+            phys_off += HAMMER_BLOCKMAP_LAYER2) {
+               for (block_off = 0;
+                    block_off < HAMMER_BLOCKMAP_LAYER2;
+                    block_off += HAMMER_LARGEBLOCK_SIZE) {
+                       layer2_off = phys_off +
+                               HAMMER_BLOCKMAP_LAYER2_OFFSET(block_off);
+                       layer2 = hammer_bread(hmp, layer2_off, &error,
+                                               &buffer);
+                       if (error)
+                               goto end;
+
+                       int zone;
+                       if (block_off == 0) {
+                               /*
+                                * The first entry represents the L2 bigblock
+                                * itself.
+                                */
+                               zone = HAMMER_ZONE_FREEMAP_INDEX;
+                       } else if (phys_off + block_off < aligned_buf_end_off) {
+                               /*
+                                * Available bigblock
+                                */
+                               zone = 0;
+                       } else {
+                               /*
+                                * Bigblock outside of physically available
+                                * space
+                                */
+                               zone = HAMMER_ZONE_UNAVAIL_INDEX;
+                       }
+
+                       error = callback(trans, &buffer, NULL, layer2, 0, zone,
+                                       data);
+                       if (error)
+                               goto end;
+               }
 
-               hammer_set_layer1_entry(hmp, trans, phys_offset,
-                       free_bigblocks, freemap, &buffer, &error);
-               KKASSERT(error == 0);
+               layer1_off = freemap->phys_offset +
+                               HAMMER_BLOCKMAP_LAYER1_OFFSET(phys_off);
+               layer1 = hammer_bread(hmp, layer1_off, &error, &buffer);
+               if (error)
+                       goto end;
 
-               total_free_bigblocks += free_bigblocks;
+               error = callback(trans, &buffer, layer1, NULL, phys_off, 0,
+                               data);
+               if (error)
+                       goto end;
        }
 
+end:
        if (buffer) {
                hammer_rel_buffer(buffer, 0);
                buffer = NULL;
        }
 
-       return total_free_bigblocks;
+       return error;
 }
 
-/*
- * Format the L2 bigblock representing a 4 TB chunk.
- *
- * Returns the number of free bigblocks.
- */
-static uint64_t
-hammer_format_layer2_chunk(struct hammer_mount *hmp,
-       hammer_transaction_t trans,
-       hammer_off_t phys_offset,
-       hammer_off_t aligned_buf_end_off,
-       hammer_buffer_t *bufferp,
-       int *errorp)
+struct format_bigblock_stat {
+       uint64_t total_free_bigblocks;
+       uint64_t free_bigblocks;
+};
+
+static int
+format_callback(hammer_transaction_t trans, hammer_buffer_t *bufferp,
+       struct hammer_blockmap_layer1 *layer1,
+       struct hammer_blockmap_layer2 *layer2,
+       hammer_off_t phys_off,
+       int layer2_zone,
+       void *data)
 {
-       uint64_t free_bigblocks = 0;
-       hammer_off_t block_off;
-       hammer_off_t layer2_offset;
-       struct hammer_blockmap_layer2 *layer2;
+       struct format_bigblock_stat *stat = (struct format_bigblock_stat*)data;
 
-       for (block_off = 0;
-            block_off < HAMMER_BLOCKMAP_LAYER2;
-            block_off += HAMMER_LARGEBLOCK_SIZE) {
-               layer2_offset = phys_offset +
-                               HAMMER_BLOCKMAP_LAYER2_OFFSET(block_off);
-               layer2 = hammer_bread(hmp, layer2_offset, errorp, bufferp);
-               if (*errorp)
-                       return free_bigblocks;
+       if (layer1) {
+               KKASSERT(layer1->phys_offset == HAMMER_BLOCKMAP_UNAVAIL);
 
-               KKASSERT(layer2);
+               hammer_modify_buffer(trans, *bufferp, layer1, sizeof(*layer1));
+               bzero(layer1, sizeof(layer1));
+               layer1->phys_offset = phys_off;
+               layer1->blocks_free = stat->free_bigblocks;
+               layer1->layer1_crc = crc32(layer1, HAMMER_LAYER1_CRCSIZE);
+               hammer_modify_buffer_done(*bufferp);
 
+               stat->total_free_bigblocks += stat->free_bigblocks;
+               stat->free_bigblocks = 0; /* reset */
+       } else if (layer2) {
                hammer_modify_buffer(trans, *bufferp, layer2, sizeof(*layer2));
                bzero(layer2, sizeof(*layer2));
 
-               if (block_off == 0) {
+               layer2->zone = layer2_zone;
+
+               switch (layer2->zone) {
+               case HAMMER_ZONE_FREEMAP_INDEX:
                        /*
                         * The first entry represents the L2 bigblock itself.
                         */
-                       layer2->zone = HAMMER_ZONE_FREEMAP_INDEX;
                        layer2->append_off = HAMMER_LARGEBLOCK_SIZE;
                        layer2->bytes_free = 0;
-               } else if (phys_offset + block_off < aligned_buf_end_off) {
-                       layer2->zone = 0;
+                       break;
+
+               case 0:
+                       /*
+                        * Available bigblock
+                        */
                        layer2->append_off = 0;
                        layer2->bytes_free = HAMMER_LARGEBLOCK_SIZE;
-                       ++free_bigblocks;
-               } else {
+                       ++stat->free_bigblocks;
+                       break;
+
+               case HAMMER_ZONE_UNAVAIL_INDEX:
                        /*
                         * Bigblock outside of physically available space
                         */
-                       layer2->zone = HAMMER_ZONE_UNAVAIL_INDEX;
                        layer2->append_off = HAMMER_LARGEBLOCK_SIZE;
                        layer2->bytes_free = 0;
+                       break;
+               default:
+                       KKASSERT(0);
                }
+
                layer2->entry_crc = crc32(layer2, HAMMER_LAYER2_CRCSIZE);
+               hammer_modify_buffer_done(*bufferp);
+       } else {
+               KKASSERT(0);
+       }
+
+       return 0;
+}
 
+static uint64_t
+hammer_format_freemap(hammer_transaction_t trans, hammer_volume_t volume)
+{
+       int error = 0;
+
+       struct format_bigblock_stat stat;
+       stat.total_free_bigblocks = 0;
+       stat.free_bigblocks = 0;
+
+       error = hammer_iterate_l1l2_entries(trans, volume, format_callback,
+                                       (void*)&stat);
+       KKASSERT(error == 0);
+
+       return stat.total_free_bigblocks;
+}
+
+static int
+free_callback(hammer_transaction_t trans, hammer_buffer_t *bufferp,
+       struct hammer_blockmap_layer1 *layer1,
+       struct hammer_blockmap_layer2 *layer2,
+       hammer_off_t phys_off,
+       int layer2_zone,
+       void *data __unused)
+{
+       if (layer1) {
+               /*
+                * Free the L1 entry
+                */
+               hammer_modify_buffer(trans, *bufferp, layer1, sizeof(*layer1));
+               bzero(layer1, sizeof(layer1));
+               layer1->phys_offset = HAMMER_BLOCKMAP_UNAVAIL;
+               layer1->layer1_crc = crc32(layer1, HAMMER_LAYER1_CRCSIZE);
                hammer_modify_buffer_done(*bufferp);
+
+               return 0;
+       } else if (layer2) {
+               switch (layer2_zone) {
+               case HAMMER_ZONE_FREEMAP_INDEX:
+               case HAMMER_ZONE_UNAVAIL_INDEX:
+                       return 0;
+               case 0:
+                       if (layer2->append_off == 0 &&
+                           layer2->bytes_free == HAMMER_LARGEBLOCK_SIZE) {
+                               return 0;
+                       } else {
+                               return EINVAL; /* FIXME */
+                       }
+               default:
+                       return EINVAL;  /* FIXME */
+               }
+       } else {
+               KKASSERT(0);
        }
 
-       return free_bigblocks;
+       return EINVAL;
 }
 
-static void
-hammer_set_layer1_entry(struct hammer_mount *hmp,
-       hammer_transaction_t trans,
-       hammer_off_t phys_offset,
-       uint64_t free_bigblocks,
-       hammer_blockmap_t freemap,
-       hammer_buffer_t *bufferp,
-       int *errorp)
+static int
+hammer_free_freemap(hammer_transaction_t trans, hammer_volume_t volume)
 {
-       struct hammer_blockmap_layer1 *layer1;
-       hammer_off_t layer1_offset;
-
-       layer1_offset = freemap->phys_offset +
-                       HAMMER_BLOCKMAP_LAYER1_OFFSET(phys_offset);
-       layer1 = hammer_bread(hmp, layer1_offset, errorp, bufferp);
-       if (*errorp)
-               return;
-       KKASSERT(layer1);
-       KKASSERT(layer1->phys_offset == HAMMER_BLOCKMAP_UNAVAIL);
-
-       hammer_modify_buffer(trans, *bufferp, layer1, sizeof(*layer1));
-       bzero(layer1, sizeof(*layer1));
-       layer1->phys_offset = phys_offset;
-       layer1->blocks_free = free_bigblocks;
-       layer1->layer1_crc = crc32(layer1, HAMMER_LAYER1_CRCSIZE);
-
-       hammer_modify_buffer_done(*bufferp);
+       return hammer_iterate_l1l2_entries(trans, volume, free_callback, NULL);
 }
 
+/************************************************************************
+ *                             MISC                                    *
+ ************************************************************************
+ */
+
 static int
 hammer_setup_device(struct vnode **devvpp, const char *dev_path, int ronly)
 {