Commit 8fdbfe8d authored by Nikos Tsironis's avatar Nikos Tsironis Committed by Mike Snitzer
Browse files

dm clone metadata: Use a two phase commit



Split the metadata commit in two parts:

1. dm_clone_metadata_pre_commit(): Prepare the current transaction for
   committing. After this is called, all subsequent metadata updates,
   done through either dm_clone_set_region_hydrated() or
   dm_clone_cond_set_range(), will be part of the next transaction.

2. dm_clone_metadata_commit(): Actually commit the current transaction
   to disk and start a new transaction.

This is required by the following commit. It allows dm-clone to flush
the destination device after step (1) to ensure that all freshly
hydrated regions, for which we are updating the metadata, are properly
written to non-volatile storage and won't be lost in case of a crash.

Fixes: 7431b783 ("dm: add clone target")
Cc: stable@vger.kernel.org # v5.4+
Signed-off-by: default avatarNikos Tsironis <ntsironis@arrikto.com>
Signed-off-by: default avatarMike Snitzer <snitzer@redhat.com>
parent e6a505f3
Loading
Loading
Loading
Loading
+37 −9
Original line number Original line Diff line number Diff line
@@ -127,6 +127,9 @@ struct dm_clone_metadata {
	struct dirty_map dmap[2];
	struct dirty_map dmap[2];
	struct dirty_map *current_dmap;
	struct dirty_map *current_dmap;


	/* Protected by lock */
	struct dirty_map *committing_dmap;

	/*
	/*
	 * In core copy of the on-disk bitmap to save constantly doing look ups
	 * In core copy of the on-disk bitmap to save constantly doing look ups
	 * on disk.
	 * on disk.
@@ -511,6 +514,7 @@ static int dirty_map_init(struct dm_clone_metadata *cmd)
	}
	}


	cmd->current_dmap = &cmd->dmap[0];
	cmd->current_dmap = &cmd->dmap[0];
	cmd->committing_dmap = NULL;


	return 0;
	return 0;
}
}
@@ -775,15 +779,17 @@ static int __flush_dmap(struct dm_clone_metadata *cmd, struct dirty_map *dmap)
	return 0;
	return 0;
}
}


int dm_clone_metadata_commit(struct dm_clone_metadata *cmd)
int dm_clone_metadata_pre_commit(struct dm_clone_metadata *cmd)
{
{
	int r = -EPERM;
	int r = 0;
	struct dirty_map *dmap, *next_dmap;
	struct dirty_map *dmap, *next_dmap;


	down_write(&cmd->lock);
	down_write(&cmd->lock);


	if (cmd->fail_io || dm_bm_is_read_only(cmd->bm))
	if (cmd->fail_io || dm_bm_is_read_only(cmd->bm)) {
		r = -EPERM;
		goto out;
		goto out;
	}


	/* Get current dirty bitmap */
	/* Get current dirty bitmap */
	dmap = cmd->current_dmap;
	dmap = cmd->current_dmap;
@@ -795,7 +801,7 @@ int dm_clone_metadata_commit(struct dm_clone_metadata *cmd)
	 * The last commit failed, so we don't have a clean dirty-bitmap to
	 * The last commit failed, so we don't have a clean dirty-bitmap to
	 * use.
	 * use.
	 */
	 */
	if (WARN_ON(next_dmap->changed)) {
	if (WARN_ON(next_dmap->changed || cmd->committing_dmap)) {
		r = -EINVAL;
		r = -EINVAL;
		goto out;
		goto out;
	}
	}
@@ -805,11 +811,33 @@ int dm_clone_metadata_commit(struct dm_clone_metadata *cmd)
	cmd->current_dmap = next_dmap;
	cmd->current_dmap = next_dmap;
	spin_unlock_irq(&cmd->bitmap_lock);
	spin_unlock_irq(&cmd->bitmap_lock);


	/*
	/* Set old dirty bitmap as currently committing */
	 * No one is accessing the old dirty bitmap anymore, so we can flush
	cmd->committing_dmap = dmap;
	 * it.
out:
	 */
	up_write(&cmd->lock);
	r = __flush_dmap(cmd, dmap);

	return r;
}

int dm_clone_metadata_commit(struct dm_clone_metadata *cmd)
{
	int r = -EPERM;

	down_write(&cmd->lock);

	if (cmd->fail_io || dm_bm_is_read_only(cmd->bm))
		goto out;

	if (WARN_ON(!cmd->committing_dmap)) {
		r = -EINVAL;
		goto out;
	}

	r = __flush_dmap(cmd, cmd->committing_dmap);
	if (!r) {
		/* Clear committing dmap */
		cmd->committing_dmap = NULL;
	}
out:
out:
	up_write(&cmd->lock);
	up_write(&cmd->lock);


+17 −0
Original line number Original line Diff line number Diff line
@@ -75,7 +75,23 @@ void dm_clone_metadata_close(struct dm_clone_metadata *cmd);


/*
/*
 * Commit dm-clone metadata to disk.
 * Commit dm-clone metadata to disk.
 *
 * We use a two phase commit:
 *
 * 1. dm_clone_metadata_pre_commit(): Prepare the current transaction for
 *    committing. After this is called, all subsequent metadata updates, done
 *    through either dm_clone_set_region_hydrated() or
 *    dm_clone_cond_set_range(), will be part of the **next** transaction.
 *
 * 2. dm_clone_metadata_commit(): Actually commit the current transaction to
 *    disk and start a new transaction.
 *
 * This allows dm-clone to flush the destination device after step (1) to
 * ensure that all freshly hydrated regions, for which we are updating the
 * metadata, are properly written to non-volatile storage and won't be lost in
 * case of a crash.
 */
 */
int dm_clone_metadata_pre_commit(struct dm_clone_metadata *cmd);
int dm_clone_metadata_commit(struct dm_clone_metadata *cmd);
int dm_clone_metadata_commit(struct dm_clone_metadata *cmd);


/*
/*
@@ -112,6 +128,7 @@ int dm_clone_metadata_abort(struct dm_clone_metadata *cmd);
 * Switches metadata to a read only mode. Once read-only mode has been entered
 * Switches metadata to a read only mode. Once read-only mode has been entered
 * the following functions will return -EPERM:
 * the following functions will return -EPERM:
 *
 *
 *   dm_clone_metadata_pre_commit()
 *   dm_clone_metadata_commit()
 *   dm_clone_metadata_commit()
 *   dm_clone_set_region_hydrated()
 *   dm_clone_set_region_hydrated()
 *   dm_clone_cond_set_range()
 *   dm_clone_cond_set_range()
+6 −1
Original line number Original line Diff line number Diff line
@@ -1122,8 +1122,13 @@ static int commit_metadata(struct clone *clone)
		goto out;
		goto out;
	}
	}


	r = dm_clone_metadata_commit(clone->cmd);
	r = dm_clone_metadata_pre_commit(clone->cmd);
	if (unlikely(r)) {
		__metadata_operation_failed(clone, "dm_clone_metadata_pre_commit", r);
		goto out;
	}


	r = dm_clone_metadata_commit(clone->cmd);
	if (unlikely(r)) {
	if (unlikely(r)) {
		__metadata_operation_failed(clone, "dm_clone_metadata_commit", r);
		__metadata_operation_failed(clone, "dm_clone_metadata_commit", r);
		goto out;
		goto out;