mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-12-28 03:49:38 +03:00
69830602de
This patch fixes two problems with the encryption code. First, the current code does not correctly prohibit the DMU from updating dn_maxblkid during object truncation within a raw receive. This usually only causes issues when the truncating DRR_FREE record is aggregated with DRR_FREE records later in the receive, so it is relatively hard to hit. Second, this patch fixes a security issue where reading blocks within an encrypted object did not guarantee that the dnode block itself had ever been verified against its MAC. Usually the verification happened anyway when the bonus buffer was read, but some use cases (notably zvols) might never perform the check. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed by: Matthew Ahrens <mahrens@delphix.com> Signed-off-by: Tom Caputi <tcaputi@datto.com> Closes #7632
791 lines
21 KiB
C
791 lines
21 KiB
C
/*
|
|
* CDDL HEADER START
|
|
*
|
|
* The contents of this file are subject to the terms of the
|
|
* Common Development and Distribution License (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
*
|
|
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
|
* or http://www.opensolaris.org/os/licensing.
|
|
* See the License for the specific language governing permissions
|
|
* and limitations under the License.
|
|
*
|
|
* When distributing Covered Code, include this CDDL HEADER in each
|
|
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
|
* If applicable, add the following below this CDDL HEADER, with the
|
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
|
* information: Portions Copyright [yyyy] [name of copyright owner]
|
|
*
|
|
* CDDL HEADER END
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
|
* Copyright (c) 2012, 2018 by Delphix. All rights reserved.
|
|
* Copyright (c) 2014 Spectra Logic Corporation, All rights reserved.
|
|
*/
|
|
|
|
#include <sys/zfs_context.h>
|
|
#include <sys/dbuf.h>
|
|
#include <sys/dnode.h>
|
|
#include <sys/dmu.h>
|
|
#include <sys/dmu_tx.h>
|
|
#include <sys/dmu_objset.h>
|
|
#include <sys/dmu_send.h>
|
|
#include <sys/dsl_dataset.h>
|
|
#include <sys/spa.h>
|
|
#include <sys/range_tree.h>
|
|
#include <sys/zfeature.h>
|
|
|
|
static void
|
|
dnode_increase_indirection(dnode_t *dn, dmu_tx_t *tx)
|
|
{
|
|
dmu_buf_impl_t *db;
|
|
int txgoff = tx->tx_txg & TXG_MASK;
|
|
int nblkptr = dn->dn_phys->dn_nblkptr;
|
|
int old_toplvl = dn->dn_phys->dn_nlevels - 1;
|
|
int new_level = dn->dn_next_nlevels[txgoff];
|
|
int i;
|
|
|
|
rw_enter(&dn->dn_struct_rwlock, RW_WRITER);
|
|
|
|
/* this dnode can't be paged out because it's dirty */
|
|
ASSERT(dn->dn_phys->dn_type != DMU_OT_NONE);
|
|
ASSERT(RW_WRITE_HELD(&dn->dn_struct_rwlock));
|
|
ASSERT(new_level > 1 && dn->dn_phys->dn_nlevels > 0);
|
|
|
|
db = dbuf_hold_level(dn, dn->dn_phys->dn_nlevels, 0, FTAG);
|
|
ASSERT(db != NULL);
|
|
|
|
dn->dn_phys->dn_nlevels = new_level;
|
|
dprintf("os=%p obj=%llu, increase to %d\n", dn->dn_objset,
|
|
dn->dn_object, dn->dn_phys->dn_nlevels);
|
|
|
|
/* transfer dnode's block pointers to new indirect block */
|
|
(void) dbuf_read(db, NULL, DB_RF_MUST_SUCCEED|DB_RF_HAVESTRUCT);
|
|
ASSERT(db->db.db_data);
|
|
ASSERT(arc_released(db->db_buf));
|
|
ASSERT3U(sizeof (blkptr_t) * nblkptr, <=, db->db.db_size);
|
|
bcopy(dn->dn_phys->dn_blkptr, db->db.db_data,
|
|
sizeof (blkptr_t) * nblkptr);
|
|
arc_buf_freeze(db->db_buf);
|
|
|
|
/* set dbuf's parent pointers to new indirect buf */
|
|
for (i = 0; i < nblkptr; i++) {
|
|
dmu_buf_impl_t *child =
|
|
dbuf_find(dn->dn_objset, dn->dn_object, old_toplvl, i);
|
|
|
|
if (child == NULL)
|
|
continue;
|
|
#ifdef DEBUG
|
|
DB_DNODE_ENTER(child);
|
|
ASSERT3P(DB_DNODE(child), ==, dn);
|
|
DB_DNODE_EXIT(child);
|
|
#endif /* DEBUG */
|
|
if (child->db_parent && child->db_parent != dn->dn_dbuf) {
|
|
ASSERT(child->db_parent->db_level == db->db_level);
|
|
ASSERT(child->db_blkptr !=
|
|
&dn->dn_phys->dn_blkptr[child->db_blkid]);
|
|
mutex_exit(&child->db_mtx);
|
|
continue;
|
|
}
|
|
ASSERT(child->db_parent == NULL ||
|
|
child->db_parent == dn->dn_dbuf);
|
|
|
|
child->db_parent = db;
|
|
dbuf_add_ref(db, child);
|
|
if (db->db.db_data)
|
|
child->db_blkptr = (blkptr_t *)db->db.db_data + i;
|
|
else
|
|
child->db_blkptr = NULL;
|
|
dprintf_dbuf_bp(child, child->db_blkptr,
|
|
"changed db_blkptr to new indirect %s", "");
|
|
|
|
mutex_exit(&child->db_mtx);
|
|
}
|
|
|
|
bzero(dn->dn_phys->dn_blkptr, sizeof (blkptr_t) * nblkptr);
|
|
|
|
dbuf_rele(db, FTAG);
|
|
|
|
rw_exit(&dn->dn_struct_rwlock);
|
|
}
|
|
|
|
static void
|
|
free_blocks(dnode_t *dn, blkptr_t *bp, int num, dmu_tx_t *tx)
|
|
{
|
|
dsl_dataset_t *ds = dn->dn_objset->os_dsl_dataset;
|
|
uint64_t bytesfreed = 0;
|
|
|
|
dprintf("ds=%p obj=%llx num=%d\n", ds, dn->dn_object, num);
|
|
|
|
for (int i = 0; i < num; i++, bp++) {
|
|
if (BP_IS_HOLE(bp))
|
|
continue;
|
|
|
|
bytesfreed += dsl_dataset_block_kill(ds, bp, tx, B_FALSE);
|
|
ASSERT3U(bytesfreed, <=, DN_USED_BYTES(dn->dn_phys));
|
|
|
|
/*
|
|
* Save some useful information on the holes being
|
|
* punched, including logical size, type, and indirection
|
|
* level. Retaining birth time enables detection of when
|
|
* holes are punched for reducing the number of free
|
|
* records transmitted during a zfs send.
|
|
*/
|
|
|
|
uint64_t lsize = BP_GET_LSIZE(bp);
|
|
dmu_object_type_t type = BP_GET_TYPE(bp);
|
|
uint64_t lvl = BP_GET_LEVEL(bp);
|
|
|
|
bzero(bp, sizeof (blkptr_t));
|
|
|
|
if (spa_feature_is_active(dn->dn_objset->os_spa,
|
|
SPA_FEATURE_HOLE_BIRTH)) {
|
|
BP_SET_LSIZE(bp, lsize);
|
|
BP_SET_TYPE(bp, type);
|
|
BP_SET_LEVEL(bp, lvl);
|
|
BP_SET_BIRTH(bp, dmu_tx_get_txg(tx), 0);
|
|
}
|
|
}
|
|
dnode_diduse_space(dn, -bytesfreed);
|
|
}
|
|
|
|
#ifdef ZFS_DEBUG
|
|
static void
|
|
free_verify(dmu_buf_impl_t *db, uint64_t start, uint64_t end, dmu_tx_t *tx)
|
|
{
|
|
int off, num;
|
|
int i, err, epbs;
|
|
uint64_t txg = tx->tx_txg;
|
|
dnode_t *dn;
|
|
|
|
DB_DNODE_ENTER(db);
|
|
dn = DB_DNODE(db);
|
|
epbs = dn->dn_phys->dn_indblkshift - SPA_BLKPTRSHIFT;
|
|
off = start - (db->db_blkid * 1<<epbs);
|
|
num = end - start + 1;
|
|
|
|
ASSERT3U(off, >=, 0);
|
|
ASSERT3U(num, >=, 0);
|
|
ASSERT3U(db->db_level, >, 0);
|
|
ASSERT3U(db->db.db_size, ==, 1 << dn->dn_phys->dn_indblkshift);
|
|
ASSERT3U(off+num, <=, db->db.db_size >> SPA_BLKPTRSHIFT);
|
|
ASSERT(db->db_blkptr != NULL);
|
|
|
|
for (i = off; i < off+num; i++) {
|
|
uint64_t *buf;
|
|
dmu_buf_impl_t *child;
|
|
dbuf_dirty_record_t *dr;
|
|
int j;
|
|
|
|
ASSERT(db->db_level == 1);
|
|
|
|
rw_enter(&dn->dn_struct_rwlock, RW_READER);
|
|
err = dbuf_hold_impl(dn, db->db_level-1,
|
|
(db->db_blkid << epbs) + i, TRUE, FALSE, FTAG, &child);
|
|
rw_exit(&dn->dn_struct_rwlock);
|
|
if (err == ENOENT)
|
|
continue;
|
|
ASSERT(err == 0);
|
|
ASSERT(child->db_level == 0);
|
|
dr = child->db_last_dirty;
|
|
while (dr && dr->dr_txg > txg)
|
|
dr = dr->dr_next;
|
|
ASSERT(dr == NULL || dr->dr_txg == txg);
|
|
|
|
/* data_old better be zeroed */
|
|
if (dr) {
|
|
buf = dr->dt.dl.dr_data->b_data;
|
|
for (j = 0; j < child->db.db_size >> 3; j++) {
|
|
if (buf[j] != 0) {
|
|
panic("freed data not zero: "
|
|
"child=%p i=%d off=%d num=%d\n",
|
|
(void *)child, i, off, num);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* db_data better be zeroed unless it's dirty in a
|
|
* future txg.
|
|
*/
|
|
mutex_enter(&child->db_mtx);
|
|
buf = child->db.db_data;
|
|
if (buf != NULL && child->db_state != DB_FILL &&
|
|
child->db_last_dirty == NULL) {
|
|
for (j = 0; j < child->db.db_size >> 3; j++) {
|
|
if (buf[j] != 0) {
|
|
panic("freed data not zero: "
|
|
"child=%p i=%d off=%d num=%d\n",
|
|
(void *)child, i, off, num);
|
|
}
|
|
}
|
|
}
|
|
mutex_exit(&child->db_mtx);
|
|
|
|
dbuf_rele(child, FTAG);
|
|
}
|
|
DB_DNODE_EXIT(db);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
free_children(dmu_buf_impl_t *db, uint64_t blkid, uint64_t nblks,
|
|
dmu_tx_t *tx)
|
|
{
|
|
dnode_t *dn;
|
|
blkptr_t *bp;
|
|
dmu_buf_impl_t *subdb;
|
|
uint64_t start, end, dbstart, dbend;
|
|
unsigned int epbs, shift, i;
|
|
|
|
/*
|
|
* There is a small possibility that this block will not be cached:
|
|
* 1 - if level > 1 and there are no children with level <= 1
|
|
* 2 - if this block was evicted since we read it from
|
|
* dmu_tx_hold_free().
|
|
*/
|
|
if (db->db_state != DB_CACHED)
|
|
(void) dbuf_read(db, NULL, DB_RF_MUST_SUCCEED);
|
|
|
|
dbuf_release_bp(db);
|
|
bp = db->db.db_data;
|
|
|
|
DB_DNODE_ENTER(db);
|
|
dn = DB_DNODE(db);
|
|
epbs = dn->dn_phys->dn_indblkshift - SPA_BLKPTRSHIFT;
|
|
ASSERT3U(epbs, <, 31);
|
|
shift = (db->db_level - 1) * epbs;
|
|
dbstart = db->db_blkid << epbs;
|
|
start = blkid >> shift;
|
|
if (dbstart < start) {
|
|
bp += start - dbstart;
|
|
} else {
|
|
start = dbstart;
|
|
}
|
|
dbend = ((db->db_blkid + 1) << epbs) - 1;
|
|
end = (blkid + nblks - 1) >> shift;
|
|
if (dbend <= end)
|
|
end = dbend;
|
|
|
|
ASSERT3U(start, <=, end);
|
|
|
|
if (db->db_level == 1) {
|
|
FREE_VERIFY(db, start, end, tx);
|
|
free_blocks(dn, bp, end-start+1, tx);
|
|
} else {
|
|
for (uint64_t id = start; id <= end; id++, bp++) {
|
|
if (BP_IS_HOLE(bp))
|
|
continue;
|
|
rw_enter(&dn->dn_struct_rwlock, RW_READER);
|
|
VERIFY0(dbuf_hold_impl(dn, db->db_level - 1,
|
|
id, TRUE, FALSE, FTAG, &subdb));
|
|
rw_exit(&dn->dn_struct_rwlock);
|
|
ASSERT3P(bp, ==, subdb->db_blkptr);
|
|
|
|
free_children(subdb, blkid, nblks, tx);
|
|
dbuf_rele(subdb, FTAG);
|
|
}
|
|
}
|
|
|
|
/* If this whole block is free, free ourself too. */
|
|
for (i = 0, bp = db->db.db_data; i < 1ULL << epbs; i++, bp++) {
|
|
if (!BP_IS_HOLE(bp))
|
|
break;
|
|
}
|
|
if (i == 1 << epbs) {
|
|
/*
|
|
* We only found holes. Grab the rwlock to prevent
|
|
* anybody from reading the blocks we're about to
|
|
* zero out.
|
|
*/
|
|
rw_enter(&dn->dn_struct_rwlock, RW_WRITER);
|
|
bzero(db->db.db_data, db->db.db_size);
|
|
rw_exit(&dn->dn_struct_rwlock);
|
|
free_blocks(dn, db->db_blkptr, 1, tx);
|
|
} else {
|
|
/*
|
|
* Partial block free; must be marked dirty so that it
|
|
* will be written out.
|
|
*/
|
|
ASSERT(db->db_dirtycnt > 0);
|
|
}
|
|
|
|
DB_DNODE_EXIT(db);
|
|
arc_buf_freeze(db->db_buf);
|
|
}
|
|
|
|
/*
|
|
* Traverse the indicated range of the provided file
|
|
* and "free" all the blocks contained there.
|
|
*/
|
|
static void
|
|
dnode_sync_free_range_impl(dnode_t *dn, uint64_t blkid, uint64_t nblks,
|
|
dmu_tx_t *tx)
|
|
{
|
|
blkptr_t *bp = dn->dn_phys->dn_blkptr;
|
|
int dnlevel = dn->dn_phys->dn_nlevels;
|
|
boolean_t trunc = B_FALSE;
|
|
|
|
if (blkid > dn->dn_phys->dn_maxblkid)
|
|
return;
|
|
|
|
ASSERT(dn->dn_phys->dn_maxblkid < UINT64_MAX);
|
|
if (blkid + nblks > dn->dn_phys->dn_maxblkid) {
|
|
nblks = dn->dn_phys->dn_maxblkid - blkid + 1;
|
|
trunc = B_TRUE;
|
|
}
|
|
|
|
/* There are no indirect blocks in the object */
|
|
if (dnlevel == 1) {
|
|
if (blkid >= dn->dn_phys->dn_nblkptr) {
|
|
/* this range was never made persistent */
|
|
return;
|
|
}
|
|
ASSERT3U(blkid + nblks, <=, dn->dn_phys->dn_nblkptr);
|
|
free_blocks(dn, bp + blkid, nblks, tx);
|
|
} else {
|
|
int shift = (dnlevel - 1) *
|
|
(dn->dn_phys->dn_indblkshift - SPA_BLKPTRSHIFT);
|
|
int start = blkid >> shift;
|
|
int end = (blkid + nblks - 1) >> shift;
|
|
dmu_buf_impl_t *db;
|
|
|
|
ASSERT(start < dn->dn_phys->dn_nblkptr);
|
|
bp += start;
|
|
for (int i = start; i <= end; i++, bp++) {
|
|
if (BP_IS_HOLE(bp))
|
|
continue;
|
|
rw_enter(&dn->dn_struct_rwlock, RW_READER);
|
|
VERIFY0(dbuf_hold_impl(dn, dnlevel - 1, i,
|
|
TRUE, FALSE, FTAG, &db));
|
|
rw_exit(&dn->dn_struct_rwlock);
|
|
|
|
free_children(db, blkid, nblks, tx);
|
|
dbuf_rele(db, FTAG);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Do not truncate the maxblkid if we are performing a raw
|
|
* receive. The raw receive sets the mablkid manually and
|
|
* must not be overriden.
|
|
*/
|
|
if (trunc && !dn->dn_objset->os_raw_receive) {
|
|
ASSERTV(uint64_t off);
|
|
dn->dn_phys->dn_maxblkid = blkid == 0 ? 0 : blkid - 1;
|
|
|
|
ASSERTV(off = (dn->dn_phys->dn_maxblkid + 1) *
|
|
(dn->dn_phys->dn_datablkszsec << SPA_MINBLOCKSHIFT));
|
|
ASSERT(off < dn->dn_phys->dn_maxblkid ||
|
|
dn->dn_phys->dn_maxblkid == 0 ||
|
|
dnode_next_offset(dn, 0, &off, 1, 1, 0) != 0);
|
|
}
|
|
}
|
|
|
|
typedef struct dnode_sync_free_range_arg {
|
|
dnode_t *dsfra_dnode;
|
|
dmu_tx_t *dsfra_tx;
|
|
} dnode_sync_free_range_arg_t;
|
|
|
|
static void
|
|
dnode_sync_free_range(void *arg, uint64_t blkid, uint64_t nblks)
|
|
{
|
|
dnode_sync_free_range_arg_t *dsfra = arg;
|
|
dnode_t *dn = dsfra->dsfra_dnode;
|
|
|
|
mutex_exit(&dn->dn_mtx);
|
|
dnode_sync_free_range_impl(dn, blkid, nblks, dsfra->dsfra_tx);
|
|
mutex_enter(&dn->dn_mtx);
|
|
}
|
|
|
|
/*
|
|
* Try to kick all the dnode's dbufs out of the cache...
|
|
*/
|
|
void
|
|
dnode_evict_dbufs(dnode_t *dn)
|
|
{
|
|
dmu_buf_impl_t *db_marker;
|
|
dmu_buf_impl_t *db, *db_next;
|
|
|
|
db_marker = kmem_alloc(sizeof (dmu_buf_impl_t), KM_SLEEP);
|
|
|
|
mutex_enter(&dn->dn_dbufs_mtx);
|
|
for (db = avl_first(&dn->dn_dbufs); db != NULL; db = db_next) {
|
|
|
|
#ifdef DEBUG
|
|
DB_DNODE_ENTER(db);
|
|
ASSERT3P(DB_DNODE(db), ==, dn);
|
|
DB_DNODE_EXIT(db);
|
|
#endif /* DEBUG */
|
|
|
|
mutex_enter(&db->db_mtx);
|
|
if (db->db_state != DB_EVICTING &&
|
|
refcount_is_zero(&db->db_holds)) {
|
|
db_marker->db_level = db->db_level;
|
|
db_marker->db_blkid = db->db_blkid;
|
|
db_marker->db_state = DB_SEARCH;
|
|
avl_insert_here(&dn->dn_dbufs, db_marker, db,
|
|
AVL_BEFORE);
|
|
|
|
/*
|
|
* We need to use the "marker" dbuf rather than
|
|
* simply getting the next dbuf, because
|
|
* dbuf_destroy() may actually remove multiple dbufs.
|
|
* It can call itself recursively on the parent dbuf,
|
|
* which may also be removed from dn_dbufs. The code
|
|
* flow would look like:
|
|
*
|
|
* dbuf_destroy():
|
|
* dnode_rele_and_unlock(parent_dbuf, evicting=TRUE):
|
|
* if (!cacheable || pending_evict)
|
|
* dbuf_destroy()
|
|
*/
|
|
dbuf_destroy(db);
|
|
|
|
db_next = AVL_NEXT(&dn->dn_dbufs, db_marker);
|
|
avl_remove(&dn->dn_dbufs, db_marker);
|
|
} else {
|
|
db->db_pending_evict = TRUE;
|
|
mutex_exit(&db->db_mtx);
|
|
db_next = AVL_NEXT(&dn->dn_dbufs, db);
|
|
}
|
|
}
|
|
mutex_exit(&dn->dn_dbufs_mtx);
|
|
|
|
kmem_free(db_marker, sizeof (dmu_buf_impl_t));
|
|
|
|
dnode_evict_bonus(dn);
|
|
}
|
|
|
|
void
|
|
dnode_evict_bonus(dnode_t *dn)
|
|
{
|
|
rw_enter(&dn->dn_struct_rwlock, RW_WRITER);
|
|
if (dn->dn_bonus != NULL) {
|
|
if (refcount_is_zero(&dn->dn_bonus->db_holds)) {
|
|
mutex_enter(&dn->dn_bonus->db_mtx);
|
|
dbuf_destroy(dn->dn_bonus);
|
|
dn->dn_bonus = NULL;
|
|
} else {
|
|
dn->dn_bonus->db_pending_evict = TRUE;
|
|
}
|
|
}
|
|
rw_exit(&dn->dn_struct_rwlock);
|
|
}
|
|
|
|
static void
|
|
dnode_undirty_dbufs(list_t *list)
|
|
{
|
|
dbuf_dirty_record_t *dr;
|
|
|
|
while ((dr = list_head(list))) {
|
|
dmu_buf_impl_t *db = dr->dr_dbuf;
|
|
uint64_t txg = dr->dr_txg;
|
|
|
|
if (db->db_level != 0)
|
|
dnode_undirty_dbufs(&dr->dt.di.dr_children);
|
|
|
|
mutex_enter(&db->db_mtx);
|
|
/* XXX - use dbuf_undirty()? */
|
|
list_remove(list, dr);
|
|
ASSERT(db->db_last_dirty == dr);
|
|
db->db_last_dirty = NULL;
|
|
db->db_dirtycnt -= 1;
|
|
if (db->db_level == 0) {
|
|
ASSERT(db->db_blkid == DMU_BONUS_BLKID ||
|
|
dr->dt.dl.dr_data == db->db_buf);
|
|
dbuf_unoverride(dr);
|
|
} else {
|
|
mutex_destroy(&dr->dt.di.dr_mtx);
|
|
list_destroy(&dr->dt.di.dr_children);
|
|
}
|
|
kmem_free(dr, sizeof (dbuf_dirty_record_t));
|
|
dbuf_rele_and_unlock(db, (void *)(uintptr_t)txg, B_FALSE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dnode_sync_free(dnode_t *dn, dmu_tx_t *tx)
|
|
{
|
|
int txgoff = tx->tx_txg & TXG_MASK;
|
|
|
|
ASSERT(dmu_tx_is_syncing(tx));
|
|
|
|
/*
|
|
* Our contents should have been freed in dnode_sync() by the
|
|
* free range record inserted by the caller of dnode_free().
|
|
*/
|
|
ASSERT0(DN_USED_BYTES(dn->dn_phys));
|
|
ASSERT(BP_IS_HOLE(dn->dn_phys->dn_blkptr));
|
|
|
|
dnode_undirty_dbufs(&dn->dn_dirty_records[txgoff]);
|
|
dnode_evict_dbufs(dn);
|
|
|
|
/*
|
|
* XXX - It would be nice to assert this, but we may still
|
|
* have residual holds from async evictions from the arc...
|
|
*
|
|
* zfs_obj_to_path() also depends on this being
|
|
* commented out.
|
|
*
|
|
* ASSERT3U(refcount_count(&dn->dn_holds), ==, 1);
|
|
*/
|
|
|
|
/* Undirty next bits */
|
|
dn->dn_next_nlevels[txgoff] = 0;
|
|
dn->dn_next_indblkshift[txgoff] = 0;
|
|
dn->dn_next_blksz[txgoff] = 0;
|
|
dn->dn_next_maxblkid[txgoff] = 0;
|
|
|
|
/* ASSERT(blkptrs are zero); */
|
|
ASSERT(dn->dn_phys->dn_type != DMU_OT_NONE);
|
|
ASSERT(dn->dn_type != DMU_OT_NONE);
|
|
|
|
ASSERT(dn->dn_free_txg > 0);
|
|
if (dn->dn_allocated_txg != dn->dn_free_txg)
|
|
dmu_buf_will_dirty(&dn->dn_dbuf->db, tx);
|
|
bzero(dn->dn_phys, sizeof (dnode_phys_t) * dn->dn_num_slots);
|
|
dnode_free_interior_slots(dn);
|
|
|
|
mutex_enter(&dn->dn_mtx);
|
|
dn->dn_type = DMU_OT_NONE;
|
|
dn->dn_maxblkid = 0;
|
|
dn->dn_allocated_txg = 0;
|
|
dn->dn_free_txg = 0;
|
|
dn->dn_have_spill = B_FALSE;
|
|
dn->dn_num_slots = 1;
|
|
mutex_exit(&dn->dn_mtx);
|
|
|
|
ASSERT(dn->dn_object != DMU_META_DNODE_OBJECT);
|
|
|
|
dnode_rele(dn, (void *)(uintptr_t)tx->tx_txg);
|
|
/*
|
|
* Now that we've released our hold, the dnode may
|
|
* be evicted, so we mustn't access it.
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* Write out the dnode's dirty buffers.
|
|
*/
|
|
void
|
|
dnode_sync(dnode_t *dn, dmu_tx_t *tx)
|
|
{
|
|
objset_t *os = dn->dn_objset;
|
|
dnode_phys_t *dnp = dn->dn_phys;
|
|
int txgoff = tx->tx_txg & TXG_MASK;
|
|
list_t *list = &dn->dn_dirty_records[txgoff];
|
|
ASSERTV(static const dnode_phys_t zerodn = { 0 });
|
|
boolean_t kill_spill = B_FALSE;
|
|
|
|
ASSERT(dmu_tx_is_syncing(tx));
|
|
ASSERT(dnp->dn_type != DMU_OT_NONE || dn->dn_allocated_txg);
|
|
ASSERT(dnp->dn_type != DMU_OT_NONE ||
|
|
bcmp(dnp, &zerodn, DNODE_MIN_SIZE) == 0);
|
|
DNODE_VERIFY(dn);
|
|
|
|
ASSERT(dn->dn_dbuf == NULL || arc_released(dn->dn_dbuf->db_buf));
|
|
|
|
/*
|
|
* Do user accounting if it is enabled and this is not
|
|
* an encrypted receive.
|
|
*/
|
|
if (dmu_objset_userused_enabled(os) &&
|
|
!DMU_OBJECT_IS_SPECIAL(dn->dn_object) &&
|
|
(!os->os_encrypted || !dmu_objset_is_receiving(os))) {
|
|
mutex_enter(&dn->dn_mtx);
|
|
dn->dn_oldused = DN_USED_BYTES(dn->dn_phys);
|
|
dn->dn_oldflags = dn->dn_phys->dn_flags;
|
|
dn->dn_phys->dn_flags |= DNODE_FLAG_USERUSED_ACCOUNTED;
|
|
if (dmu_objset_userobjused_enabled(dn->dn_objset))
|
|
dn->dn_phys->dn_flags |=
|
|
DNODE_FLAG_USEROBJUSED_ACCOUNTED;
|
|
mutex_exit(&dn->dn_mtx);
|
|
dmu_objset_userquota_get_ids(dn, B_FALSE, tx);
|
|
} else {
|
|
/* Once we account for it, we should always account for it */
|
|
ASSERT(!(dn->dn_phys->dn_flags &
|
|
DNODE_FLAG_USERUSED_ACCOUNTED));
|
|
ASSERT(!(dn->dn_phys->dn_flags &
|
|
DNODE_FLAG_USEROBJUSED_ACCOUNTED));
|
|
}
|
|
|
|
mutex_enter(&dn->dn_mtx);
|
|
if (dn->dn_allocated_txg == tx->tx_txg) {
|
|
/* The dnode is newly allocated or reallocated */
|
|
if (dnp->dn_type == DMU_OT_NONE) {
|
|
/* this is a first alloc, not a realloc */
|
|
dnp->dn_nlevels = 1;
|
|
dnp->dn_nblkptr = dn->dn_nblkptr;
|
|
}
|
|
|
|
dnp->dn_type = dn->dn_type;
|
|
dnp->dn_bonustype = dn->dn_bonustype;
|
|
dnp->dn_bonuslen = dn->dn_bonuslen;
|
|
}
|
|
|
|
dnp->dn_extra_slots = dn->dn_num_slots - 1;
|
|
|
|
ASSERT(dnp->dn_nlevels > 1 ||
|
|
BP_IS_HOLE(&dnp->dn_blkptr[0]) ||
|
|
BP_IS_EMBEDDED(&dnp->dn_blkptr[0]) ||
|
|
BP_GET_LSIZE(&dnp->dn_blkptr[0]) ==
|
|
dnp->dn_datablkszsec << SPA_MINBLOCKSHIFT);
|
|
ASSERT(dnp->dn_nlevels < 2 ||
|
|
BP_IS_HOLE(&dnp->dn_blkptr[0]) ||
|
|
BP_GET_LSIZE(&dnp->dn_blkptr[0]) == 1 << dnp->dn_indblkshift);
|
|
|
|
if (dn->dn_next_type[txgoff] != 0) {
|
|
dnp->dn_type = dn->dn_type;
|
|
dn->dn_next_type[txgoff] = 0;
|
|
}
|
|
|
|
if (dn->dn_next_blksz[txgoff] != 0) {
|
|
ASSERT(P2PHASE(dn->dn_next_blksz[txgoff],
|
|
SPA_MINBLOCKSIZE) == 0);
|
|
ASSERT(BP_IS_HOLE(&dnp->dn_blkptr[0]) ||
|
|
dn->dn_maxblkid == 0 || list_head(list) != NULL ||
|
|
dn->dn_next_blksz[txgoff] >> SPA_MINBLOCKSHIFT ==
|
|
dnp->dn_datablkszsec ||
|
|
!range_tree_is_empty(dn->dn_free_ranges[txgoff]));
|
|
dnp->dn_datablkszsec =
|
|
dn->dn_next_blksz[txgoff] >> SPA_MINBLOCKSHIFT;
|
|
dn->dn_next_blksz[txgoff] = 0;
|
|
}
|
|
|
|
if (dn->dn_next_bonuslen[txgoff] != 0) {
|
|
if (dn->dn_next_bonuslen[txgoff] == DN_ZERO_BONUSLEN)
|
|
dnp->dn_bonuslen = 0;
|
|
else
|
|
dnp->dn_bonuslen = dn->dn_next_bonuslen[txgoff];
|
|
ASSERT(dnp->dn_bonuslen <=
|
|
DN_SLOTS_TO_BONUSLEN(dnp->dn_extra_slots + 1));
|
|
dn->dn_next_bonuslen[txgoff] = 0;
|
|
}
|
|
|
|
if (dn->dn_next_bonustype[txgoff] != 0) {
|
|
ASSERT(DMU_OT_IS_VALID(dn->dn_next_bonustype[txgoff]));
|
|
dnp->dn_bonustype = dn->dn_next_bonustype[txgoff];
|
|
dn->dn_next_bonustype[txgoff] = 0;
|
|
}
|
|
|
|
boolean_t freeing_dnode = dn->dn_free_txg > 0 &&
|
|
dn->dn_free_txg <= tx->tx_txg;
|
|
|
|
/*
|
|
* Remove the spill block if we have been explicitly asked to
|
|
* remove it, or if the object is being removed.
|
|
*/
|
|
if (dn->dn_rm_spillblk[txgoff] || freeing_dnode) {
|
|
if (dnp->dn_flags & DNODE_FLAG_SPILL_BLKPTR)
|
|
kill_spill = B_TRUE;
|
|
dn->dn_rm_spillblk[txgoff] = 0;
|
|
}
|
|
|
|
if (dn->dn_next_indblkshift[txgoff] != 0) {
|
|
ASSERT(dnp->dn_nlevels == 1);
|
|
dnp->dn_indblkshift = dn->dn_next_indblkshift[txgoff];
|
|
dn->dn_next_indblkshift[txgoff] = 0;
|
|
}
|
|
|
|
/*
|
|
* Just take the live (open-context) values for checksum and compress.
|
|
* Strictly speaking it's a future leak, but nothing bad happens if we
|
|
* start using the new checksum or compress algorithm a little early.
|
|
*/
|
|
dnp->dn_checksum = dn->dn_checksum;
|
|
dnp->dn_compress = dn->dn_compress;
|
|
|
|
mutex_exit(&dn->dn_mtx);
|
|
|
|
if (kill_spill) {
|
|
free_blocks(dn, DN_SPILL_BLKPTR(dn->dn_phys), 1, tx);
|
|
mutex_enter(&dn->dn_mtx);
|
|
dnp->dn_flags &= ~DNODE_FLAG_SPILL_BLKPTR;
|
|
mutex_exit(&dn->dn_mtx);
|
|
}
|
|
|
|
/* process all the "freed" ranges in the file */
|
|
if (dn->dn_free_ranges[txgoff] != NULL) {
|
|
dnode_sync_free_range_arg_t dsfra;
|
|
dsfra.dsfra_dnode = dn;
|
|
dsfra.dsfra_tx = tx;
|
|
mutex_enter(&dn->dn_mtx);
|
|
range_tree_vacate(dn->dn_free_ranges[txgoff],
|
|
dnode_sync_free_range, &dsfra);
|
|
range_tree_destroy(dn->dn_free_ranges[txgoff]);
|
|
dn->dn_free_ranges[txgoff] = NULL;
|
|
mutex_exit(&dn->dn_mtx);
|
|
}
|
|
|
|
if (freeing_dnode) {
|
|
dn->dn_objset->os_freed_dnodes++;
|
|
dnode_sync_free(dn, tx);
|
|
return;
|
|
}
|
|
|
|
if (dn->dn_num_slots > DNODE_MIN_SLOTS) {
|
|
dsl_dataset_t *ds = dn->dn_objset->os_dsl_dataset;
|
|
mutex_enter(&ds->ds_lock);
|
|
ds->ds_feature_activation_needed[SPA_FEATURE_LARGE_DNODE] =
|
|
B_TRUE;
|
|
mutex_exit(&ds->ds_lock);
|
|
}
|
|
|
|
if (dn->dn_next_nlevels[txgoff]) {
|
|
dnode_increase_indirection(dn, tx);
|
|
dn->dn_next_nlevels[txgoff] = 0;
|
|
}
|
|
|
|
/*
|
|
* This must be done after dnode_sync_free_range()
|
|
* and dnode_increase_indirection().
|
|
*/
|
|
if (dn->dn_next_maxblkid[txgoff]) {
|
|
mutex_enter(&dn->dn_mtx);
|
|
dnp->dn_maxblkid = dn->dn_next_maxblkid[txgoff];
|
|
dn->dn_next_maxblkid[txgoff] = 0;
|
|
mutex_exit(&dn->dn_mtx);
|
|
}
|
|
|
|
if (dn->dn_next_nblkptr[txgoff]) {
|
|
/* this should only happen on a realloc */
|
|
ASSERT(dn->dn_allocated_txg == tx->tx_txg);
|
|
if (dn->dn_next_nblkptr[txgoff] > dnp->dn_nblkptr) {
|
|
/* zero the new blkptrs we are gaining */
|
|
bzero(dnp->dn_blkptr + dnp->dn_nblkptr,
|
|
sizeof (blkptr_t) *
|
|
(dn->dn_next_nblkptr[txgoff] - dnp->dn_nblkptr));
|
|
#ifdef ZFS_DEBUG
|
|
} else {
|
|
int i;
|
|
ASSERT(dn->dn_next_nblkptr[txgoff] < dnp->dn_nblkptr);
|
|
/* the blkptrs we are losing better be unallocated */
|
|
for (i = 0; i < dnp->dn_nblkptr; i++) {
|
|
if (i >= dn->dn_next_nblkptr[txgoff])
|
|
ASSERT(BP_IS_HOLE(&dnp->dn_blkptr[i]));
|
|
}
|
|
#endif
|
|
}
|
|
mutex_enter(&dn->dn_mtx);
|
|
dnp->dn_nblkptr = dn->dn_next_nblkptr[txgoff];
|
|
dn->dn_next_nblkptr[txgoff] = 0;
|
|
mutex_exit(&dn->dn_mtx);
|
|
}
|
|
|
|
dbuf_sync_list(list, dn->dn_phys->dn_nlevels - 1, tx);
|
|
|
|
if (!DMU_OBJECT_IS_SPECIAL(dn->dn_object)) {
|
|
ASSERT3P(list_head(list), ==, NULL);
|
|
dnode_rele(dn, (void *)(uintptr_t)tx->tx_txg);
|
|
}
|
|
|
|
/*
|
|
* Although we have dropped our reference to the dnode, it
|
|
* can't be evicted until its written, and we haven't yet
|
|
* initiated the IO for the dnode's dbuf.
|
|
*/
|
|
}
|