static int _crypt_format_verity(struct crypt_device *cd,
				 const char *uuid,
				 struct crypt_params_verity *params)
{
	int r = 0, hash_size;
	uint64_t data_device_size, hash_blocks_size;
	struct device *fec_device = NULL;
	char *fec_device_path = NULL, *hash_name = NULL, *root_hash = NULL, *salt = NULL;

	if (!crypt_metadata_device(cd)) {
		log_err(cd, _("Can't format VERITY without device."));
		return -EINVAL;
	}

	if (!params)
		return -EINVAL;

	if (!params->data_device && !cd->metadata_device)
		return -EINVAL;

	if (params->hash_type > VERITY_MAX_HASH_TYPE) {
		log_err(cd, _("Unsupported VERITY hash type %d."), params->hash_type);
		return -EINVAL;
	}

	if (VERITY_BLOCK_SIZE_OK(params->data_block_size) ||
	    VERITY_BLOCK_SIZE_OK(params->hash_block_size)) {
		log_err(cd, _("Unsupported VERITY block size."));
		return -EINVAL;
	}

	if (MISALIGNED_512(params->hash_area_offset)) {
		log_err(cd, _("Unsupported VERITY hash offset."));
		return -EINVAL;
	}

	if (MISALIGNED_512(params->fec_area_offset)) {
		log_err(cd, _("Unsupported VERITY FEC offset."));
		return -EINVAL;
	}

	if (!(cd->type = strdup(CRYPT_VERITY)))
		return -ENOMEM;

	if (params->data_device) {
		r = crypt_set_data_device(cd, params->data_device);
		if (r)
			return r;
	}

	if (!params->data_size) {
		r = device_size(cd->device, &data_device_size);
		if (r < 0)
			return r;

		cd->u.verity.hdr.data_size = data_device_size / params->data_block_size;
	} else
		cd->u.verity.hdr.data_size = params->data_size;

	if (device_is_identical(crypt_metadata_device(cd), crypt_data_device(cd)) > 0 &&
	   (cd->u.verity.hdr.data_size * params->data_block_size) > params->hash_area_offset) {
		log_err(cd, _("Data area overlaps with hash area."));
		return -EINVAL;
	}

	hash_size = crypt_hash_size(params->hash_name);
	if (hash_size <= 0) {
		log_err(cd, _("Hash algorithm %s not supported."),
			params->hash_name);
		return -EINVAL;
	}
	cd->u.verity.root_hash_size = hash_size;

	if (params->fec_device) {
		fec_device_path = strdup(params->fec_device);
		if (!fec_device_path)
			return -ENOMEM;
		r = device_alloc(cd, &fec_device, params->fec_device);
		if (r < 0) {
			r = -ENOMEM;
			goto out;
		}

		hash_blocks_size = VERITY_hash_blocks(cd, params) * params->hash_block_size;
		if (device_is_identical(crypt_metadata_device(cd), fec_device) > 0 &&
		    (params->hash_area_offset + hash_blocks_size) > params->fec_area_offset) {
			log_err(cd, _("Hash area overlaps with FEC area."));
			r = -EINVAL;
			goto out;
		}

		if (device_is_identical(crypt_data_device(cd), fec_device) > 0 &&
		    (cd->u.verity.hdr.data_size * params->data_block_size) > params->fec_area_offset) {
			log_err(cd, _("Data area overlaps with FEC area."));
			r = -EINVAL;
			goto out;
		}
	}

	root_hash = malloc(cd->u.verity.root_hash_size);
	hash_name = strdup(params->hash_name);
	salt = malloc(params->salt_size);

	if (!root_hash || !hash_name || !salt) {
		r = -ENOMEM;
		goto out;
	}

	cd->u.verity.hdr.flags = params->flags;
	cd->u.verity.root_hash = root_hash;
	cd->u.verity.hdr.hash_name = hash_name;
	cd->u.verity.hdr.data_device = NULL;
	cd->u.verity.fec_device = fec_device;
	cd->u.verity.hdr.fec_device = fec_device_path;
	cd->u.verity.hdr.fec_roots = params->fec_roots;
	cd->u.verity.hdr.data_block_size = params->data_block_size;
	cd->u.verity.hdr.hash_block_size = params->hash_block_size;
	cd->u.verity.hdr.hash_area_offset = params->hash_area_offset;
	cd->u.verity.hdr.fec_area_offset = params->fec_area_offset;
	cd->u.verity.hdr.hash_type = params->hash_type;
	cd->u.verity.hdr.flags = params->flags;
	cd->u.verity.hdr.salt_size = params->salt_size;
	cd->u.verity.hdr.salt = salt;

	if (params->salt)
		memcpy(salt, params->salt, params->salt_size);
	else
		r = crypt_random_get(cd, salt, params->salt_size, CRYPT_RND_SALT);
	if (r)
		goto out;

	if (params->flags & CRYPT_VERITY_CREATE_HASH) {
		r = VERITY_create(cd, &cd->u.verity.hdr,
				  cd->u.verity.root_hash, cd->u.verity.root_hash_size);
		if (!r && params->fec_device)
			r = VERITY_FEC_process(cd, &cd->u.verity.hdr, cd->u.verity.fec_device, 0, NULL);
		if (r)
			goto out;
	}

	if (!(params->flags & CRYPT_VERITY_NO_HEADER)) {
		if (uuid) {
			if (!(cd->u.verity.uuid = strdup(uuid)))
				r = -ENOMEM;
		} else
			r = VERITY_UUID_generate(cd, &cd->u.verity.uuid);

		if (!r)
			r = VERITY_write_sb(cd, cd->u.verity.hdr.hash_area_offset,
					    cd->u.verity.uuid,
					    &cd->u.verity.hdr);
	}

out:
	if (r) {
		device_free(cd, fec_device);
		free(root_hash);
		free(hash_name);
		free(fec_device_path);
		free(salt);
	}

	return r;
}