summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/block/drbd/drbd_main.c2
-rw-r--r--drivers/block/drbd/drbd_nl.c203
-rw-r--r--include/linux/drbd_genl.h2
3 files changed, 154 insertions, 53 deletions
diff --git a/drivers/block/drbd/drbd_main.c b/drivers/block/drbd/drbd_main.c
index 113c7b465384..40b7b93def75 100644
--- a/drivers/block/drbd/drbd_main.c
+++ b/drivers/block/drbd/drbd_main.c
@@ -2303,9 +2303,7 @@ fail:
void drbd_free_tconn(struct drbd_tconn *tconn)
{
- mutex_lock(&drbd_cfg_mutex);
list_del(&tconn->all_tconn);
- mutex_unlock(&drbd_cfg_mutex);
idr_destroy(&tconn->volumes);
free_cpumask_var(tconn->cpu_mask);
diff --git a/drivers/block/drbd/drbd_nl.c b/drivers/block/drbd/drbd_nl.c
index f965dfe4b5ff..d952e877f8d5 100644
--- a/drivers/block/drbd/drbd_nl.c
+++ b/drivers/block/drbd/drbd_nl.c
@@ -49,6 +49,7 @@ int drbd_adm_delete_minor(struct sk_buff *skb, struct genl_info *info);
int drbd_adm_create_connection(struct sk_buff *skb, struct genl_info *info);
int drbd_adm_delete_connection(struct sk_buff *skb, struct genl_info *info);
+int drbd_adm_down(struct sk_buff *skb, struct genl_info *info);
int drbd_adm_set_role(struct sk_buff *skb, struct genl_info *info);
int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info);
@@ -1416,6 +1417,18 @@ int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info)
return 0;
}
+static int adm_detach(struct drbd_conf *mdev)
+{
+ enum drbd_ret_code retcode;
+ drbd_suspend_io(mdev); /* so no-one is stuck in drbd_al_begin_io */
+ retcode = drbd_request_state(mdev, NS(disk, D_DISKLESS));
+ wait_event(mdev->misc_wait,
+ mdev->state.disk != D_DISKLESS ||
+ !atomic_read(&mdev->local_cnt));
+ drbd_resume_io(mdev);
+ return retcode;
+}
+
/* Detaching the disk is a process in multiple stages. First we need to lock
* out application IO, in-flight IO, IO stuck in drbd_al_begin_io.
* Then we transition to D_DISKLESS, and wait for put_ldev() to return all
@@ -1423,7 +1436,6 @@ int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info)
* Only then we have finally detached. */
int drbd_adm_detach(struct sk_buff *skb, struct genl_info *info)
{
- struct drbd_conf *mdev;
enum drbd_ret_code retcode;
retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
@@ -1432,13 +1444,7 @@ int drbd_adm_detach(struct sk_buff *skb, struct genl_info *info)
if (retcode != NO_ERROR)
goto out;
- mdev = adm_ctx.mdev;
- drbd_suspend_io(mdev); /* so no-one is stuck in drbd_al_begin_io */
- retcode = drbd_request_state(mdev, NS(disk, D_DISKLESS));
- wait_event(mdev->misc_wait,
- mdev->state.disk != D_DISKLESS ||
- !atomic_read(&mdev->local_cnt));
- drbd_resume_io(mdev);
+ retcode = adm_detach(adm_ctx.mdev);
out:
drbd_adm_finish(info, retcode);
return 0;
@@ -1680,10 +1686,49 @@ out:
return 0;
}
+static enum drbd_state_rv conn_try_disconnect(struct drbd_tconn *tconn, bool force)
+{
+ enum drbd_state_rv rv;
+ if (force) {
+ spin_lock_irq(&tconn->req_lock);
+ if (tconn->cstate >= C_WF_CONNECTION)
+ _conn_request_state(tconn, NS(conn, C_DISCONNECTING), CS_HARD);
+ spin_unlock_irq(&tconn->req_lock);
+ return SS_SUCCESS;
+ }
+
+ rv = conn_request_state(tconn, NS(conn, C_DISCONNECTING), 0);
+
+ switch (rv) {
+ case SS_NOTHING_TO_DO:
+ case SS_ALREADY_STANDALONE:
+ return SS_SUCCESS;
+ case SS_PRIMARY_NOP:
+ /* Our state checking code wants to see the peer outdated. */
+ rv = conn_request_state(tconn, NS2(conn, C_DISCONNECTING,
+ pdsk, D_OUTDATED), CS_VERBOSE);
+ break;
+ case SS_CW_FAILED_BY_PEER:
+ /* The peer probably wants to see us outdated. */
+ rv = conn_request_state(tconn, NS2(conn, C_DISCONNECTING,
+ disk, D_OUTDATED), 0);
+ if (rv == SS_IS_DISKLESS || rv == SS_LOWER_THAN_OUTDATED) {
+ conn_request_state(tconn, NS(conn, C_DISCONNECTING), CS_HARD);
+ rv = SS_SUCCESS;
+ }
+ break;
+ default:;
+ /* no special handling necessary */
+ }
+
+ return rv;
+}
+
int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info)
{
struct disconnect_parms parms;
struct drbd_tconn *tconn;
+ enum drbd_state_rv rv;
enum drbd_ret_code retcode;
int err;
@@ -1704,35 +1749,8 @@ int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info)
}
}
- if (parms.force_disconnect) {
- spin_lock_irq(&tconn->req_lock);
- if (tconn->cstate >= C_WF_CONNECTION)
- _conn_request_state(tconn, NS(conn, C_DISCONNECTING), CS_HARD);
- spin_unlock_irq(&tconn->req_lock);
- goto done;
- }
-
- retcode = conn_request_state(tconn, NS(conn, C_DISCONNECTING), 0);
-
- if (retcode == SS_NOTHING_TO_DO)
- goto done;
- else if (retcode == SS_ALREADY_STANDALONE)
- goto done;
- else if (retcode == SS_PRIMARY_NOP) {
- /* Our state checking code wants to see the peer outdated. */
- retcode = conn_request_state(tconn, NS2(conn, C_DISCONNECTING,
- pdsk, D_OUTDATED), CS_VERBOSE);
- } else if (retcode == SS_CW_FAILED_BY_PEER) {
- /* The peer probably wants to see us outdated. */
- retcode = conn_request_state(tconn, NS2(conn, C_DISCONNECTING,
- disk, D_OUTDATED), 0);
- if (retcode == SS_IS_DISKLESS || retcode == SS_LOWER_THAN_OUTDATED) {
- conn_request_state(tconn, NS(conn, C_DISCONNECTING), CS_HARD);
- retcode = SS_SUCCESS;
- }
- }
-
- if (retcode < SS_SUCCESS)
+ rv = conn_try_disconnect(tconn, parms.force_disconnect);
+ if (rv < SS_SUCCESS)
goto fail;
if (wait_event_interruptible(tconn->ping_wait,
@@ -1743,7 +1761,6 @@ int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info)
goto fail;
}
- done:
retcode = NO_ERROR;
fail:
drbd_adm_finish(info, retcode);
@@ -2644,9 +2661,21 @@ out:
return 0;
}
+static enum drbd_ret_code adm_delete_minor(struct drbd_conf *mdev)
+{
+ if (mdev->state.disk == D_DISKLESS &&
+ /* no need to be mdev->state.conn == C_STANDALONE &&
+ * we may want to delete a minor from a live replication group.
+ */
+ mdev->state.role == R_SECONDARY) {
+ drbd_delete_device(mdev_to_minor(mdev));
+ return NO_ERROR;
+ } else
+ return ERR_MINOR_CONFIGURED;
+}
+
int drbd_adm_delete_minor(struct sk_buff *skb, struct genl_info *info)
{
- struct drbd_conf *mdev;
enum drbd_ret_code retcode;
retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
@@ -2655,19 +2684,89 @@ int drbd_adm_delete_minor(struct sk_buff *skb, struct genl_info *info)
if (retcode != NO_ERROR)
goto out;
- mdev = adm_ctx.mdev;
- if (mdev->state.disk == D_DISKLESS &&
- /* no need to be mdev->state.conn == C_STANDALONE &&
- * we may want to delete a minor from a live replication group.
- */
- mdev->state.role == R_SECONDARY) {
- drbd_delete_device(mdev_to_minor(mdev));
- retcode = NO_ERROR;
- /* if this was the last volume of this connection,
- * this will terminate all threads */
+ mutex_lock(&drbd_cfg_mutex);
+ retcode = adm_delete_minor(adm_ctx.mdev);
+ mutex_unlock(&drbd_cfg_mutex);
+ /* if this was the last volume of this connection,
+ * this will terminate all threads */
+ if (retcode == NO_ERROR)
conn_reconfig_done(adm_ctx.tconn);
- } else
- retcode = ERR_MINOR_CONFIGURED;
+out:
+ drbd_adm_finish(info, retcode);
+ return 0;
+}
+
+int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
+{
+ enum drbd_ret_code retcode;
+ enum drbd_state_rv rv;
+ struct drbd_conf *mdev;
+ unsigned i;
+
+ retcode = drbd_adm_prepare(skb, info, 0);
+ if (!adm_ctx.reply_skb)
+ return retcode;
+ if (retcode != NO_ERROR)
+ goto out;
+
+ if (!adm_ctx.tconn) {
+ retcode = ERR_CONN_NOT_KNOWN;
+ goto out;
+ }
+
+ mutex_lock(&drbd_cfg_mutex);
+ /* demote */
+ idr_for_each_entry(&adm_ctx.tconn->volumes, mdev, i) {
+ retcode = drbd_set_role(mdev, R_SECONDARY, 0);
+ if (retcode < SS_SUCCESS) {
+ drbd_msg_put_info("failed to demote");
+ goto out_unlock;
+ }
+ }
+
+ /* disconnect */
+ rv = conn_try_disconnect(adm_ctx.tconn, 0);
+ if (rv < SS_SUCCESS) {
+ retcode = rv; /* enum type mismatch! */
+ drbd_msg_put_info("failed to disconnect");
+ goto out_unlock;
+ }
+
+ /* detach */
+ idr_for_each_entry(&adm_ctx.tconn->volumes, mdev, i) {
+ rv = adm_detach(mdev);
+ if (rv < SS_SUCCESS) {
+ retcode = rv; /* enum type mismatch! */
+ drbd_msg_put_info("failed to detach");
+ goto out_unlock;
+ }
+ }
+
+ /* delete volumes */
+ idr_for_each_entry(&adm_ctx.tconn->volumes, mdev, i) {
+ retcode = adm_delete_minor(mdev);
+ if (retcode != NO_ERROR) {
+ /* "can not happen" */
+ drbd_msg_put_info("failed to delete volume");
+ goto out_unlock;
+ }
+ }
+
+ /* stop all threads */
+ conn_reconfig_done(adm_ctx.tconn);
+
+ /* delete connection */
+ if (conn_lowest_minor(adm_ctx.tconn) < 0) {
+ drbd_free_tconn(adm_ctx.tconn);
+ retcode = NO_ERROR;
+ } else {
+ /* "can not happen" */
+ retcode = ERR_CONN_IN_USE;
+ drbd_msg_put_info("failed to delete connection");
+ goto out_unlock;
+ }
+out_unlock:
+ mutex_unlock(&drbd_cfg_mutex);
out:
drbd_adm_finish(info, retcode);
return 0;
@@ -2683,12 +2782,14 @@ int drbd_adm_delete_connection(struct sk_buff *skb, struct genl_info *info)
if (retcode != NO_ERROR)
goto out;
+ mutex_lock(&drbd_cfg_mutex);
if (conn_lowest_minor(adm_ctx.tconn) < 0) {
drbd_free_tconn(adm_ctx.tconn);
retcode = NO_ERROR;
} else {
retcode = ERR_CONN_IN_USE;
}
+ mutex_unlock(&drbd_cfg_mutex);
out:
drbd_adm_finish(info, retcode);
diff --git a/include/linux/drbd_genl.h b/include/linux/drbd_genl.h
index 84e16848f7a1..a07d69279b1a 100644
--- a/include/linux/drbd_genl.h
+++ b/include/linux/drbd_genl.h
@@ -347,3 +347,5 @@ GENL_op(DRBD_ADM_OUTDATE, 25, GENL_doit(drbd_adm_outdate),
GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, GENLA_F_REQUIRED))
GENL_op(DRBD_ADM_GET_TIMEOUT_TYPE, 26, GENL_doit(drbd_adm_get_timeout_type),
GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, GENLA_F_REQUIRED))
+GENL_op(DRBD_ADM_DOWN, 27, GENL_doit(drbd_adm_down),
+ GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, GENLA_F_REQUIRED))