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;
}