aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/makefs/zfs/objset.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/makefs/zfs/objset.c')
-rw-r--r--usr.sbin/makefs/zfs/objset.c263
1 files changed, 263 insertions, 0 deletions
diff --git a/usr.sbin/makefs/zfs/objset.c b/usr.sbin/makefs/zfs/objset.c
new file mode 100644
index 000000000000..f47953ac4339
--- /dev/null
+++ b/usr.sbin/makefs/zfs/objset.c
@@ -0,0 +1,263 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 The FreeBSD Foundation
+ *
+ * This software was developed by Mark Johnston under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <util.h>
+
+#include "zfs.h"
+
+#define DNODES_PER_CHUNK (MAXBLOCKSIZE / sizeof(dnode_phys_t))
+
+struct objset_dnode_chunk {
+ dnode_phys_t buf[DNODES_PER_CHUNK];
+ unsigned int nextfree;
+ STAILQ_ENTRY(objset_dnode_chunk) next;
+};
+
+typedef struct zfs_objset {
+ /* Physical object set. */
+ objset_phys_t *phys;
+ off_t osloc;
+ off_t osblksz;
+ blkptr_t osbp; /* set in objset_write() */
+
+ /* Accounting. */
+ off_t space; /* bytes allocated to this objset */
+
+ /* dnode allocator. */
+ uint64_t dnodecount;
+ STAILQ_HEAD(, objset_dnode_chunk) dnodechunks;
+} zfs_objset_t;
+
+static void
+dnode_init(dnode_phys_t *dnode, uint8_t type, uint8_t bonustype,
+ uint16_t bonuslen)
+{
+ dnode->dn_indblkshift = MAXBLOCKSHIFT;
+ dnode->dn_type = type;
+ dnode->dn_bonustype = bonustype;
+ dnode->dn_bonuslen = bonuslen;
+ dnode->dn_checksum = ZIO_CHECKSUM_FLETCHER_4;
+ dnode->dn_nlevels = 1;
+ dnode->dn_nblkptr = 1;
+ dnode->dn_flags = DNODE_FLAG_USED_BYTES;
+}
+
+zfs_objset_t *
+objset_alloc(zfs_opt_t *zfs, uint64_t type)
+{
+ struct objset_dnode_chunk *chunk;
+ zfs_objset_t *os;
+
+ os = ecalloc(1, sizeof(*os));
+ os->osblksz = sizeof(objset_phys_t);
+ os->osloc = objset_space_alloc(zfs, os, &os->osblksz);
+
+ /*
+ * Object ID zero is always reserved for the meta dnode, which is
+ * embedded in the objset itself.
+ */
+ STAILQ_INIT(&os->dnodechunks);
+ chunk = ecalloc(1, sizeof(*chunk));
+ chunk->nextfree = 1;
+ STAILQ_INSERT_HEAD(&os->dnodechunks, chunk, next);
+ os->dnodecount = 1;
+
+ os->phys = ecalloc(1, os->osblksz);
+ os->phys->os_type = type;
+
+ dnode_init(&os->phys->os_meta_dnode, DMU_OT_DNODE, DMU_OT_NONE, 0);
+ os->phys->os_meta_dnode.dn_datablkszsec =
+ DNODE_BLOCK_SIZE >> MINBLOCKSHIFT;
+
+ return (os);
+}
+
+/*
+ * Write the dnode array and physical object set to disk.
+ */
+static void
+_objset_write(zfs_opt_t *zfs, zfs_objset_t *os, struct dnode_cursor *c,
+ off_t loc)
+{
+ struct objset_dnode_chunk *chunk, *tmp;
+ unsigned int total;
+
+ /*
+ * Write out the dnode array, i.e., the meta-dnode. For some reason its
+ * data blocks must be 16KB in size no matter how large the array is.
+ */
+ total = 0;
+ STAILQ_FOREACH_SAFE(chunk, &os->dnodechunks, next, tmp) {
+ unsigned int i;
+
+ assert(chunk->nextfree > 0);
+ assert(chunk->nextfree <= os->dnodecount);
+ assert(chunk->nextfree <= DNODES_PER_CHUNK);
+
+ for (i = 0; i < chunk->nextfree; i += DNODES_PER_BLOCK) {
+ blkptr_t *bp;
+ uint64_t fill;
+
+ if (chunk->nextfree - i < DNODES_PER_BLOCK)
+ fill = DNODES_PER_BLOCK - (chunk->nextfree - i);
+ else
+ fill = 0;
+ bp = dnode_cursor_next(zfs, c,
+ (total + i) * sizeof(dnode_phys_t));
+ vdev_pwrite_dnode_indir(zfs, &os->phys->os_meta_dnode,
+ 0, fill, chunk->buf + i, DNODE_BLOCK_SIZE, loc, bp);
+ loc += DNODE_BLOCK_SIZE;
+ }
+ total += i;
+
+ free(chunk);
+ }
+ dnode_cursor_finish(zfs, c);
+ STAILQ_INIT(&os->dnodechunks);
+
+ /*
+ * Write the object set itself. The saved block pointer will be copied
+ * into the referencing DSL dataset or the uberblocks.
+ */
+ vdev_pwrite_data(zfs, DMU_OT_OBJSET, ZIO_CHECKSUM_FLETCHER_4, 0,
+ os->dnodecount - 1, os->phys, os->osblksz, os->osloc, &os->osbp);
+}
+
+void
+objset_write(zfs_opt_t *zfs, zfs_objset_t *os)
+{
+ struct dnode_cursor *c;
+ off_t dnodeloc, dnodesz;
+ uint64_t dnodecount;
+
+ /*
+ * There is a chicken-and-egg problem here when writing the MOS: we
+ * cannot write space maps before we're finished allocating space from
+ * the vdev, and we can't write the MOS without having allocated space
+ * for indirect dnode blocks. Thus, rather than lazily allocating
+ * indirect blocks for the meta-dnode (which would be simpler), they are
+ * allocated up-front and before writing space maps.
+ */
+ dnodecount = os->dnodecount;
+ if (os == zfs->mos)
+ dnodecount += zfs->mscount;
+ dnodesz = dnodecount * sizeof(dnode_phys_t);
+ c = dnode_cursor_init(zfs, os, &os->phys->os_meta_dnode, dnodesz,
+ DNODE_BLOCK_SIZE);
+ dnodesz = roundup2(dnodesz, DNODE_BLOCK_SIZE);
+ dnodeloc = objset_space_alloc(zfs, os, &dnodesz);
+
+ if (os == zfs->mos) {
+ vdev_spacemap_write(zfs);
+
+ /*
+ * We've finished allocating space, account for it in $MOS and
+ * in the parent directory.
+ */
+ dsl_dir_root_finalize(zfs, os->space);
+ }
+ _objset_write(zfs, os, c, dnodeloc);
+}
+
+dnode_phys_t *
+objset_dnode_bonus_alloc(zfs_objset_t *os, uint8_t type, uint8_t bonustype,
+ uint16_t bonuslen, uint64_t *idp)
+{
+ struct objset_dnode_chunk *chunk;
+ dnode_phys_t *dnode;
+
+ assert(bonuslen <= DN_OLD_MAX_BONUSLEN);
+ assert(!STAILQ_EMPTY(&os->dnodechunks));
+
+ chunk = STAILQ_LAST(&os->dnodechunks, objset_dnode_chunk, next);
+ if (chunk->nextfree == DNODES_PER_CHUNK) {
+ chunk = ecalloc(1, sizeof(*chunk));
+ STAILQ_INSERT_TAIL(&os->dnodechunks, chunk, next);
+ }
+ *idp = os->dnodecount++;
+ dnode = &chunk->buf[chunk->nextfree++];
+ dnode_init(dnode, type, bonustype, bonuslen);
+ dnode->dn_datablkszsec = os->osblksz >> MINBLOCKSHIFT;
+ return (dnode);
+}
+
+dnode_phys_t *
+objset_dnode_alloc(zfs_objset_t *os, uint8_t type, uint64_t *idp)
+{
+ return (objset_dnode_bonus_alloc(os, type, DMU_OT_NONE, 0, idp));
+}
+
+/*
+ * Look up a physical dnode by ID. This is not used often so a linear search is
+ * fine.
+ */
+dnode_phys_t *
+objset_dnode_lookup(zfs_objset_t *os, uint64_t id)
+{
+ struct objset_dnode_chunk *chunk;
+
+ assert(id > 0);
+ assert(id < os->dnodecount);
+
+ STAILQ_FOREACH(chunk, &os->dnodechunks, next) {
+ if (id < DNODES_PER_CHUNK)
+ return (&chunk->buf[id]);
+ id -= DNODES_PER_CHUNK;
+ }
+ assert(0);
+ return (NULL);
+}
+
+off_t
+objset_space_alloc(zfs_opt_t *zfs, zfs_objset_t *os, off_t *lenp)
+{
+ off_t loc;
+
+ loc = vdev_space_alloc(zfs, lenp);
+ os->space += *lenp;
+ return (loc);
+}
+
+uint64_t
+objset_space(const zfs_objset_t *os)
+{
+ return (os->space);
+}
+
+void
+objset_root_blkptr_copy(const zfs_objset_t *os, blkptr_t *bp)
+{
+ memcpy(bp, &os->osbp, sizeof(blkptr_t));
+}