static int receive_sizes()

in drbd/drbd_receiver.c [4101:4271]


static int receive_sizes(struct drbd_connection *connection, struct packet_info *pi)
{
	struct drbd_peer_device *peer_device;
	struct drbd_device *device;
	struct p_sizes *p = pi->data;
	struct o_qlim *o = (connection->agreed_features & DRBD_FF_WSAME) ? p->qlim : NULL;
	enum determine_dev_size dd = DS_UNCHANGED;
	sector_t p_size, p_usize, p_csize, my_usize;
	sector_t new_size, cur_size;
	int ldsc = 0; /* local disk size changed */
	enum dds_flags ddsf;

	peer_device = conn_peer_device(connection, pi->vnr);
	if (!peer_device)
		return config_unknown_volume(connection, pi);
	device = peer_device->device;
	cur_size = get_capacity(device->vdisk);

	p_size = be64_to_cpu(p->d_size);
	p_usize = be64_to_cpu(p->u_size);
	p_csize = be64_to_cpu(p->c_size);

	/* just store the peer's disk size for now.
	 * we still need to figure out whether we accept that. */
	device->p_size = p_size;

	if (get_ldev(device)) {
		rcu_read_lock();
		my_usize = rcu_dereference(device->ldev->disk_conf)->disk_size;
		rcu_read_unlock();

		warn_if_differ_considerably(device, "lower level device sizes",
			   p_size, drbd_get_max_capacity(device->ldev));
		warn_if_differ_considerably(device, "user requested size",
					    p_usize, my_usize);

		/* if this is the first connect, or an otherwise expected
		 * param exchange, choose the minimum */
		if (device->state.conn == C_WF_REPORT_PARAMS)
			p_usize = min_not_zero(my_usize, p_usize);

		/* Never shrink a device with usable data during connect,
		 * or "attach" on the peer.
		 * But allow online shrinking if we are connected. */
		new_size = drbd_new_dev_size(device, device->ldev, p_usize, 0);
		if (new_size < cur_size &&
		    device->state.disk >= D_OUTDATED &&
		    (device->state.conn < C_CONNECTED || device->state.pdsk == D_DISKLESS)) {
			drbd_err(device, "The peer's disk size is too small! (%llu < %llu sectors)\n",
					(unsigned long long)new_size, (unsigned long long)cur_size);
			conn_request_state(peer_device->connection, NS(conn, C_DISCONNECTING), CS_HARD);
			put_ldev(device);
			return -EIO;
		}

		if (my_usize != p_usize) {
			struct disk_conf *old_disk_conf, *new_disk_conf = NULL;

			new_disk_conf = kzalloc(sizeof(struct disk_conf), GFP_KERNEL);
			if (!new_disk_conf) {
				put_ldev(device);
				return -ENOMEM;
			}

			mutex_lock(&connection->resource->conf_update);
			old_disk_conf = device->ldev->disk_conf;
			*new_disk_conf = *old_disk_conf;
			new_disk_conf->disk_size = p_usize;

			rcu_assign_pointer(device->ldev->disk_conf, new_disk_conf);
			mutex_unlock(&connection->resource->conf_update);
			synchronize_rcu();
			kfree(old_disk_conf);

			drbd_info(device, "Peer sets u_size to %lu sectors (old: %lu)\n",
				 (unsigned long)p_usize, (unsigned long)my_usize);
		}

		put_ldev(device);
	}

	device->peer_max_bio_size = be32_to_cpu(p->max_bio_size);
	/* Leave drbd_reconsider_queue_parameters() before drbd_determine_dev_size().
	   In case we cleared the QUEUE_FLAG_DISCARD from our queue in
	   drbd_reconsider_queue_parameters(), we can be sure that after
	   drbd_determine_dev_size() no REQ_DISCARDs are in the queue. */

	ddsf = be16_to_cpu(p->dds_flags);
	if (get_ldev(device)) {
		drbd_reconsider_queue_parameters(device, device->ldev, o);
		dd = drbd_determine_dev_size(device, ddsf, NULL);
		put_ldev(device);
		if (dd == DS_ERROR)
			return -EIO;
		drbd_md_sync(device);
	} else {
		/*
		 * I am diskless, need to accept the peer's *current* size.
		 * I must NOT accept the peers backing disk size,
		 * it may have been larger than mine all along...
		 *
		 * At this point, the peer knows more about my disk, or at
		 * least about what we last agreed upon, than myself.
		 * So if his c_size is less than his d_size, the most likely
		 * reason is that *my* d_size was smaller last time we checked.
		 *
		 * However, if he sends a zero current size,
		 * take his (user-capped or) backing disk size anyways.
		 *
		 * Unless of course he does not have a disk himself.
		 * In which case we ignore this completely.
		 */
		sector_t new_size = p_csize ?: p_usize ?: p_size;
		drbd_reconsider_queue_parameters(device, NULL, o);
		if (new_size == 0) {
			/* Ignore, peer does not know nothing. */
		} else if (new_size == cur_size) {
			/* nothing to do */
		} else if (cur_size != 0 && p_size == 0) {
			drbd_warn(device, "Ignored diskless peer device size (peer:%llu != me:%llu sectors)!\n",
					(unsigned long long)new_size, (unsigned long long)cur_size);
		} else if (new_size < cur_size && device->state.role == R_PRIMARY) {
			drbd_err(device, "The peer's device size is too small! (%llu < %llu sectors); demote me first!\n",
					(unsigned long long)new_size, (unsigned long long)cur_size);
			conn_request_state(peer_device->connection, NS(conn, C_DISCONNECTING), CS_HARD);
			return -EIO;
		} else {
			/* I believe the peer, if
			 *  - I don't have a current size myself
			 *  - we agree on the size anyways
			 *  - I do have a current size, am Secondary,
			 *    and he has the only disk
			 *  - I do have a current size, am Primary,
			 *    and he has the only disk,
			 *    which is larger than my current size
			 */
			drbd_set_my_capacity(device, new_size);
		}
	}

	if (get_ldev(device)) {
		if (device->ldev->known_size != drbd_get_capacity(device->ldev->backing_bdev)) {
			device->ldev->known_size = drbd_get_capacity(device->ldev->backing_bdev);
			ldsc = 1;
		}

		put_ldev(device);
	}

	if (device->state.conn > C_WF_REPORT_PARAMS) {
		if (be64_to_cpu(p->c_size) != get_capacity(device->vdisk) ||
		    ldsc) {
			/* we have different sizes, probably peer
			 * needs to know my new size... */
			drbd_send_sizes(peer_device, 0, ddsf);
		}
		if (test_and_clear_bit(RESIZE_PENDING, &device->flags) ||
		    (dd == DS_GREW && device->state.conn == C_CONNECTED)) {
			if (device->state.pdsk >= D_INCONSISTENT &&
			    device->state.disk >= D_INCONSISTENT) {
				if (ddsf & DDSF_NO_RESYNC)
					drbd_info(device, "Resync of new storage suppressed with --assume-clean\n");
				else
					resync_after_online_grow(device);
			} else
				set_bit(RESYNC_AFTER_NEG, &device->flags);
		}
	}

	return 0;
}