static int dm_integrity_ctr()

in dm-integrity.c [3962:4541]


static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
{
	struct dm_integrity_c *ic;
	char dummy;
	int r;
	unsigned extra_args;
	struct dm_arg_set as;
	static const struct dm_arg _args[] = {
		{0, 18, "Invalid number of feature args"},
	};
	unsigned journal_sectors, interleave_sectors, buffer_sectors, journal_watermark, sync_msec;
	bool should_write_sb;
	__u64 threshold;
	unsigned long long start;
	__s8 log2_sectors_per_bitmap_bit = -1;
	__s8 log2_blocks_per_bitmap_bit;
	__u64 bits_in_journal;
	__u64 n_bitmap_bits;

#define DIRECT_ARGUMENTS	4

	if (argc <= DIRECT_ARGUMENTS) {
		ti->error = "Invalid argument count";
		return -EINVAL;
	}

	ic = kzalloc(sizeof(struct dm_integrity_c), GFP_KERNEL);
	if (!ic) {
		ti->error = "Cannot allocate integrity context";
		return -ENOMEM;
	}
	ti->private = ic;
	ti->per_io_data_size = sizeof(struct dm_integrity_io);
	ic->ti = ti;

	ic->in_progress = RB_ROOT;
	INIT_LIST_HEAD(&ic->wait_list);
	init_waitqueue_head(&ic->endio_wait);
	bio_list_init(&ic->flush_bio_list);
	init_waitqueue_head(&ic->copy_to_journal_wait);
	init_completion(&ic->crypto_backoff);
	atomic64_set(&ic->number_of_mismatches, 0);
	ic->bitmap_flush_interval = BITMAP_FLUSH_INTERVAL;

	r = dm_get_device(ti, argv[0], dm_table_get_mode(ti->table), &ic->dev);
	if (r) {
		ti->error = "Device lookup failed";
		goto bad;
	}

	if (sscanf(argv[1], "%llu%c", &start, &dummy) != 1 || start != (sector_t)start) {
		ti->error = "Invalid starting offset";
		r = -EINVAL;
		goto bad;
	}
	ic->start = start;

	if (strcmp(argv[2], "-")) {
		if (sscanf(argv[2], "%u%c", &ic->tag_size, &dummy) != 1 || !ic->tag_size) {
			ti->error = "Invalid tag size";
			r = -EINVAL;
			goto bad;
		}
	}

	if (!strcmp(argv[3], "J") || !strcmp(argv[3], "B") ||
	    !strcmp(argv[3], "D") || !strcmp(argv[3], "R")) {
		ic->mode = argv[3][0];
	} else {
		ti->error = "Invalid mode (expecting J, B, D, R)";
		r = -EINVAL;
		goto bad;
	}

	journal_sectors = 0;
	interleave_sectors = DEFAULT_INTERLEAVE_SECTORS;
	buffer_sectors = DEFAULT_BUFFER_SECTORS;
	journal_watermark = DEFAULT_JOURNAL_WATERMARK;
	sync_msec = DEFAULT_SYNC_MSEC;
	ic->sectors_per_block = 1;

	as.argc = argc - DIRECT_ARGUMENTS;
	as.argv = argv + DIRECT_ARGUMENTS;
	r = dm_read_arg_group(_args, &as, &extra_args, &ti->error);
	if (r)
		goto bad;

	while (extra_args--) {
		const char *opt_string;
		unsigned val;
		unsigned long long llval;
		opt_string = dm_shift_arg(&as);
		if (!opt_string) {
			r = -EINVAL;
			ti->error = "Not enough feature arguments";
			goto bad;
		}
		if (sscanf(opt_string, "journal_sectors:%u%c", &val, &dummy) == 1)
			journal_sectors = val ? val : 1;
		else if (sscanf(opt_string, "interleave_sectors:%u%c", &val, &dummy) == 1)
			interleave_sectors = val;
		else if (sscanf(opt_string, "buffer_sectors:%u%c", &val, &dummy) == 1)
			buffer_sectors = val;
		else if (sscanf(opt_string, "journal_watermark:%u%c", &val, &dummy) == 1 && val <= 100)
			journal_watermark = val;
		else if (sscanf(opt_string, "commit_time:%u%c", &val, &dummy) == 1)
			sync_msec = val;
		else if (!strncmp(opt_string, "meta_device:", strlen("meta_device:"))) {
			if (ic->meta_dev) {
				dm_put_device(ti, ic->meta_dev);
				ic->meta_dev = NULL;
			}
			r = dm_get_device(ti, strchr(opt_string, ':') + 1,
					  dm_table_get_mode(ti->table), &ic->meta_dev);
			if (r) {
				ti->error = "Device lookup failed";
				goto bad;
			}
		} else if (sscanf(opt_string, "block_size:%u%c", &val, &dummy) == 1) {
			if (val < 1 << SECTOR_SHIFT ||
			    val > MAX_SECTORS_PER_BLOCK << SECTOR_SHIFT ||
			    (val & (val -1))) {
				r = -EINVAL;
				ti->error = "Invalid block_size argument";
				goto bad;
			}
			ic->sectors_per_block = val >> SECTOR_SHIFT;
		} else if (sscanf(opt_string, "sectors_per_bit:%llu%c", &llval, &dummy) == 1) {
			log2_sectors_per_bitmap_bit = !llval ? 0 : __ilog2_u64(llval);
		} else if (sscanf(opt_string, "bitmap_flush_interval:%u%c", &val, &dummy) == 1) {
			if (val >= (uint64_t)UINT_MAX * 1000 / HZ) {
				r = -EINVAL;
				ti->error = "Invalid bitmap_flush_interval argument";
				goto bad;
			}
			ic->bitmap_flush_interval = msecs_to_jiffies(val);
		} else if (!strncmp(opt_string, "internal_hash:", strlen("internal_hash:"))) {
			r = get_alg_and_key(opt_string, &ic->internal_hash_alg, &ti->error,
					    "Invalid internal_hash argument");
			if (r)
				goto bad;
		} else if (!strncmp(opt_string, "journal_crypt:", strlen("journal_crypt:"))) {
			r = get_alg_and_key(opt_string, &ic->journal_crypt_alg, &ti->error,
					    "Invalid journal_crypt argument");
			if (r)
				goto bad;
		} else if (!strncmp(opt_string, "journal_mac:", strlen("journal_mac:"))) {
			r = get_alg_and_key(opt_string, &ic->journal_mac_alg, &ti->error,
					    "Invalid journal_mac argument");
			if (r)
				goto bad;
		} else if (!strcmp(opt_string, "recalculate")) {
			ic->recalculate_flag = true;
		} else if (!strcmp(opt_string, "reset_recalculate")) {
			ic->recalculate_flag = true;
			ic->reset_recalculate_flag = true;
		} else if (!strcmp(opt_string, "allow_discards")) {
			ic->discard = true;
		} else if (!strcmp(opt_string, "fix_padding")) {
			ic->fix_padding = true;
		} else if (!strcmp(opt_string, "fix_hmac")) {
			ic->fix_hmac = true;
		} else if (!strcmp(opt_string, "legacy_recalculate")) {
			ic->legacy_recalculate = true;
		} else {
			r = -EINVAL;
			ti->error = "Invalid argument";
			goto bad;
		}
	}

	ic->data_device_sectors = bdev_nr_sectors(ic->dev->bdev);
	if (!ic->meta_dev)
		ic->meta_device_sectors = ic->data_device_sectors;
	else
		ic->meta_device_sectors = bdev_nr_sectors(ic->meta_dev->bdev);

	if (!journal_sectors) {
		journal_sectors = min((sector_t)DEFAULT_MAX_JOURNAL_SECTORS,
				      ic->data_device_sectors >> DEFAULT_JOURNAL_SIZE_FACTOR);
	}

	if (!buffer_sectors)
		buffer_sectors = 1;
	ic->log2_buffer_sectors = min((int)__fls(buffer_sectors), 31 - SECTOR_SHIFT);

	r = get_mac(&ic->internal_hash, &ic->internal_hash_alg, &ti->error,
		    "Invalid internal hash", "Error setting internal hash key");
	if (r)
		goto bad;

	r = get_mac(&ic->journal_mac, &ic->journal_mac_alg, &ti->error,
		    "Invalid journal mac", "Error setting journal mac key");
	if (r)
		goto bad;

	if (!ic->tag_size) {
		if (!ic->internal_hash) {
			ti->error = "Unknown tag size";
			r = -EINVAL;
			goto bad;
		}
		ic->tag_size = crypto_shash_digestsize(ic->internal_hash);
	}
	if (ic->tag_size > MAX_TAG_SIZE) {
		ti->error = "Too big tag size";
		r = -EINVAL;
		goto bad;
	}
	if (!(ic->tag_size & (ic->tag_size - 1)))
		ic->log2_tag_size = __ffs(ic->tag_size);
	else
		ic->log2_tag_size = -1;

	if (ic->mode == 'B' && !ic->internal_hash) {
		r = -EINVAL;
		ti->error = "Bitmap mode can be only used with internal hash";
		goto bad;
	}

	if (ic->discard && !ic->internal_hash) {
		r = -EINVAL;
		ti->error = "Discard can be only used with internal hash";
		goto bad;
	}

	ic->autocommit_jiffies = msecs_to_jiffies(sync_msec);
	ic->autocommit_msec = sync_msec;
	timer_setup(&ic->autocommit_timer, autocommit_fn, 0);

	ic->io = dm_io_client_create();
	if (IS_ERR(ic->io)) {
		r = PTR_ERR(ic->io);
		ic->io = NULL;
		ti->error = "Cannot allocate dm io";
		goto bad;
	}

	r = mempool_init_slab_pool(&ic->journal_io_mempool, JOURNAL_IO_MEMPOOL, journal_io_cache);
	if (r) {
		ti->error = "Cannot allocate mempool";
		goto bad;
	}

	ic->metadata_wq = alloc_workqueue("dm-integrity-metadata",
					  WQ_MEM_RECLAIM, METADATA_WORKQUEUE_MAX_ACTIVE);
	if (!ic->metadata_wq) {
		ti->error = "Cannot allocate workqueue";
		r = -ENOMEM;
		goto bad;
	}

	/*
	 * If this workqueue were percpu, it would cause bio reordering
	 * and reduced performance.
	 */
	ic->wait_wq = alloc_workqueue("dm-integrity-wait", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
	if (!ic->wait_wq) {
		ti->error = "Cannot allocate workqueue";
		r = -ENOMEM;
		goto bad;
	}

	ic->offload_wq = alloc_workqueue("dm-integrity-offload", WQ_MEM_RECLAIM,
					  METADATA_WORKQUEUE_MAX_ACTIVE);
	if (!ic->offload_wq) {
		ti->error = "Cannot allocate workqueue";
		r = -ENOMEM;
		goto bad;
	}

	ic->commit_wq = alloc_workqueue("dm-integrity-commit", WQ_MEM_RECLAIM, 1);
	if (!ic->commit_wq) {
		ti->error = "Cannot allocate workqueue";
		r = -ENOMEM;
		goto bad;
	}
	INIT_WORK(&ic->commit_work, integrity_commit);

	if (ic->mode == 'J' || ic->mode == 'B') {
		ic->writer_wq = alloc_workqueue("dm-integrity-writer", WQ_MEM_RECLAIM, 1);
		if (!ic->writer_wq) {
			ti->error = "Cannot allocate workqueue";
			r = -ENOMEM;
			goto bad;
		}
		INIT_WORK(&ic->writer_work, integrity_writer);
	}

	ic->sb = alloc_pages_exact(SB_SECTORS << SECTOR_SHIFT, GFP_KERNEL);
	if (!ic->sb) {
		r = -ENOMEM;
		ti->error = "Cannot allocate superblock area";
		goto bad;
	}

	r = sync_rw_sb(ic, REQ_OP_READ, 0);
	if (r) {
		ti->error = "Error reading superblock";
		goto bad;
	}
	should_write_sb = false;
	if (memcmp(ic->sb->magic, SB_MAGIC, 8)) {
		if (ic->mode != 'R') {
			if (memchr_inv(ic->sb, 0, SB_SECTORS << SECTOR_SHIFT)) {
				r = -EINVAL;
				ti->error = "The device is not initialized";
				goto bad;
			}
		}

		r = initialize_superblock(ic, journal_sectors, interleave_sectors);
		if (r) {
			ti->error = "Could not initialize superblock";
			goto bad;
		}
		if (ic->mode != 'R')
			should_write_sb = true;
	}

	if (!ic->sb->version || ic->sb->version > SB_VERSION_5) {
		r = -EINVAL;
		ti->error = "Unknown version";
		goto bad;
	}
	if (le16_to_cpu(ic->sb->integrity_tag_size) != ic->tag_size) {
		r = -EINVAL;
		ti->error = "Tag size doesn't match the information in superblock";
		goto bad;
	}
	if (ic->sb->log2_sectors_per_block != __ffs(ic->sectors_per_block)) {
		r = -EINVAL;
		ti->error = "Block size doesn't match the information in superblock";
		goto bad;
	}
	if (!le32_to_cpu(ic->sb->journal_sections)) {
		r = -EINVAL;
		ti->error = "Corrupted superblock, journal_sections is 0";
		goto bad;
	}
	/* make sure that ti->max_io_len doesn't overflow */
	if (!ic->meta_dev) {
		if (ic->sb->log2_interleave_sectors < MIN_LOG2_INTERLEAVE_SECTORS ||
		    ic->sb->log2_interleave_sectors > MAX_LOG2_INTERLEAVE_SECTORS) {
			r = -EINVAL;
			ti->error = "Invalid interleave_sectors in the superblock";
			goto bad;
		}
	} else {
		if (ic->sb->log2_interleave_sectors) {
			r = -EINVAL;
			ti->error = "Invalid interleave_sectors in the superblock";
			goto bad;
		}
	}
	if (!!(ic->sb->flags & cpu_to_le32(SB_FLAG_HAVE_JOURNAL_MAC)) != !!ic->journal_mac_alg.alg_string) {
		r = -EINVAL;
		ti->error = "Journal mac mismatch";
		goto bad;
	}

	get_provided_data_sectors(ic);
	if (!ic->provided_data_sectors) {
		r = -EINVAL;
		ti->error = "The device is too small";
		goto bad;
	}

try_smaller_buffer:
	r = calculate_device_limits(ic);
	if (r) {
		if (ic->meta_dev) {
			if (ic->log2_buffer_sectors > 3) {
				ic->log2_buffer_sectors--;
				goto try_smaller_buffer;
			}
		}
		ti->error = "The device is too small";
		goto bad;
	}

	if (log2_sectors_per_bitmap_bit < 0)
		log2_sectors_per_bitmap_bit = __fls(DEFAULT_SECTORS_PER_BITMAP_BIT);
	if (log2_sectors_per_bitmap_bit < ic->sb->log2_sectors_per_block)
		log2_sectors_per_bitmap_bit = ic->sb->log2_sectors_per_block;

	bits_in_journal = ((__u64)ic->journal_section_sectors * ic->journal_sections) << (SECTOR_SHIFT + 3);
	if (bits_in_journal > UINT_MAX)
		bits_in_journal = UINT_MAX;
	while (bits_in_journal < (ic->provided_data_sectors + ((sector_t)1 << log2_sectors_per_bitmap_bit) - 1) >> log2_sectors_per_bitmap_bit)
		log2_sectors_per_bitmap_bit++;

	log2_blocks_per_bitmap_bit = log2_sectors_per_bitmap_bit - ic->sb->log2_sectors_per_block;
	ic->log2_blocks_per_bitmap_bit = log2_blocks_per_bitmap_bit;
	if (should_write_sb) {
		ic->sb->log2_blocks_per_bitmap_bit = log2_blocks_per_bitmap_bit;
	}
	n_bitmap_bits = ((ic->provided_data_sectors >> ic->sb->log2_sectors_per_block)
				+ (((sector_t)1 << log2_blocks_per_bitmap_bit) - 1)) >> log2_blocks_per_bitmap_bit;
	ic->n_bitmap_blocks = DIV_ROUND_UP(n_bitmap_bits, BITMAP_BLOCK_SIZE * 8);

	if (!ic->meta_dev)
		ic->log2_buffer_sectors = min(ic->log2_buffer_sectors, (__u8)__ffs(ic->metadata_run));

	if (ti->len > ic->provided_data_sectors) {
		r = -EINVAL;
		ti->error = "Not enough provided sectors for requested mapping size";
		goto bad;
	}


	threshold = (__u64)ic->journal_entries * (100 - journal_watermark);
	threshold += 50;
	do_div(threshold, 100);
	ic->free_sectors_threshold = threshold;

	DEBUG_print("initialized:\n");
	DEBUG_print("	integrity_tag_size %u\n", le16_to_cpu(ic->sb->integrity_tag_size));
	DEBUG_print("	journal_entry_size %u\n", ic->journal_entry_size);
	DEBUG_print("	journal_entries_per_sector %u\n", ic->journal_entries_per_sector);
	DEBUG_print("	journal_section_entries %u\n", ic->journal_section_entries);
	DEBUG_print("	journal_section_sectors %u\n", ic->journal_section_sectors);
	DEBUG_print("	journal_sections %u\n", (unsigned)le32_to_cpu(ic->sb->journal_sections));
	DEBUG_print("	journal_entries %u\n", ic->journal_entries);
	DEBUG_print("	log2_interleave_sectors %d\n", ic->sb->log2_interleave_sectors);
	DEBUG_print("	data_device_sectors 0x%llx\n", bdev_nr_sectors(ic->dev->bdev));
	DEBUG_print("	initial_sectors 0x%x\n", ic->initial_sectors);
	DEBUG_print("	metadata_run 0x%x\n", ic->metadata_run);
	DEBUG_print("	log2_metadata_run %d\n", ic->log2_metadata_run);
	DEBUG_print("	provided_data_sectors 0x%llx (%llu)\n", ic->provided_data_sectors, ic->provided_data_sectors);
	DEBUG_print("	log2_buffer_sectors %u\n", ic->log2_buffer_sectors);
	DEBUG_print("	bits_in_journal %llu\n", bits_in_journal);

	if (ic->recalculate_flag && !(ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING))) {
		ic->sb->flags |= cpu_to_le32(SB_FLAG_RECALCULATING);
		ic->sb->recalc_sector = cpu_to_le64(0);
	}

	if (ic->internal_hash) {
		ic->recalc_wq = alloc_workqueue("dm-integrity-recalc", WQ_MEM_RECLAIM, 1);
		if (!ic->recalc_wq ) {
			ti->error = "Cannot allocate workqueue";
			r = -ENOMEM;
			goto bad;
		}
		INIT_WORK(&ic->recalc_work, integrity_recalc);
		ic->recalc_buffer = vmalloc(RECALC_SECTORS << SECTOR_SHIFT);
		if (!ic->recalc_buffer) {
			ti->error = "Cannot allocate buffer for recalculating";
			r = -ENOMEM;
			goto bad;
		}
		ic->recalc_tags = kvmalloc_array(RECALC_SECTORS >> ic->sb->log2_sectors_per_block,
						 ic->tag_size, GFP_KERNEL);
		if (!ic->recalc_tags) {
			ti->error = "Cannot allocate tags for recalculating";
			r = -ENOMEM;
			goto bad;
		}
	} else {
		if (ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING)) {
			ti->error = "Recalculate can only be specified with internal_hash";
			r = -EINVAL;
			goto bad;
		}
	}

	if (ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING) &&
	    le64_to_cpu(ic->sb->recalc_sector) < ic->provided_data_sectors &&
	    dm_integrity_disable_recalculate(ic)) {
		ti->error = "Recalculating with HMAC is disabled for security reasons - if you really need it, use the argument \"legacy_recalculate\"";
		r = -EOPNOTSUPP;
		goto bad;
	}

	ic->bufio = dm_bufio_client_create(ic->meta_dev ? ic->meta_dev->bdev : ic->dev->bdev,
			1U << (SECTOR_SHIFT + ic->log2_buffer_sectors), 1, 0, NULL, NULL);
	if (IS_ERR(ic->bufio)) {
		r = PTR_ERR(ic->bufio);
		ti->error = "Cannot initialize dm-bufio";
		ic->bufio = NULL;
		goto bad;
	}
	dm_bufio_set_sector_offset(ic->bufio, ic->start + ic->initial_sectors);

	if (ic->mode != 'R') {
		r = create_journal(ic, &ti->error);
		if (r)
			goto bad;

	}

	if (ic->mode == 'B') {
		unsigned i;
		unsigned n_bitmap_pages = DIV_ROUND_UP(ic->n_bitmap_blocks, PAGE_SIZE / BITMAP_BLOCK_SIZE);

		ic->recalc_bitmap = dm_integrity_alloc_page_list(n_bitmap_pages);
		if (!ic->recalc_bitmap) {
			r = -ENOMEM;
			goto bad;
		}
		ic->may_write_bitmap = dm_integrity_alloc_page_list(n_bitmap_pages);
		if (!ic->may_write_bitmap) {
			r = -ENOMEM;
			goto bad;
		}
		ic->bbs = kvmalloc_array(ic->n_bitmap_blocks, sizeof(struct bitmap_block_status), GFP_KERNEL);
		if (!ic->bbs) {
			r = -ENOMEM;
			goto bad;
		}
		INIT_DELAYED_WORK(&ic->bitmap_flush_work, bitmap_flush_work);
		for (i = 0; i < ic->n_bitmap_blocks; i++) {
			struct bitmap_block_status *bbs = &ic->bbs[i];
			unsigned sector, pl_index, pl_offset;

			INIT_WORK(&bbs->work, bitmap_block_work);
			bbs->ic = ic;
			bbs->idx = i;
			bio_list_init(&bbs->bio_queue);
			spin_lock_init(&bbs->bio_queue_lock);

			sector = i * (BITMAP_BLOCK_SIZE >> SECTOR_SHIFT);
			pl_index = sector >> (PAGE_SHIFT - SECTOR_SHIFT);
			pl_offset = (sector << SECTOR_SHIFT) & (PAGE_SIZE - 1);

			bbs->bitmap = lowmem_page_address(ic->journal[pl_index].page) + pl_offset;
		}
	}

	if (should_write_sb) {
		int r;

		init_journal(ic, 0, ic->journal_sections, 0);
		r = dm_integrity_failed(ic);
		if (unlikely(r)) {
			ti->error = "Error initializing journal";
			goto bad;
		}
		r = sync_rw_sb(ic, REQ_OP_WRITE, REQ_FUA);
		if (r) {
			ti->error = "Error initializing superblock";
			goto bad;
		}
		ic->just_formatted = true;
	}

	if (!ic->meta_dev) {
		r = dm_set_target_max_io_len(ti, 1U << ic->sb->log2_interleave_sectors);
		if (r)
			goto bad;
	}
	if (ic->mode == 'B') {
		unsigned max_io_len = ((sector_t)ic->sectors_per_block << ic->log2_blocks_per_bitmap_bit) * (BITMAP_BLOCK_SIZE * 8);
		if (!max_io_len)
			max_io_len = 1U << 31;
		DEBUG_print("max_io_len: old %u, new %u\n", ti->max_io_len, max_io_len);
		if (!ti->max_io_len || ti->max_io_len > max_io_len) {
			r = dm_set_target_max_io_len(ti, max_io_len);
			if (r)
				goto bad;
		}
	}

	if (!ic->internal_hash)
		dm_integrity_set(ti, ic);

	ti->num_flush_bios = 1;
	ti->flush_supported = true;
	if (ic->discard)
		ti->num_discard_bios = 1;

	dm_audit_log_ctr(DM_MSG_PREFIX, ti, 1);
	return 0;

bad:
	dm_audit_log_ctr(DM_MSG_PREFIX, ti, 0);
	dm_integrity_dtr(ti);
	return r;
}