Files
scst/iscsi-scst/kernel/target.c
Vladislav Bolkhovitin 6153a079a3 iSCSI target sysfs "enabled" attribute implemented
git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@1324 d57e44dd-8a1f-0410-8b47-8ef2f437770f
2009-11-06 18:41:37 +00:00

571 lines
12 KiB
C

/*
* Copyright (C) 2002 - 2003 Ardis Technolgies <roman@ardistech.com>
* Copyright (C) 2007 - 2009 Vladislav Bolkhovitin
* Copyright (C) 2007 - 2009 ID7 Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2
* of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/delay.h>
#include "iscsi.h"
#include "digest.h"
#define MAX_NR_TARGETS (1UL << 30)
#define SYSFS_WAIT_TIMEOUT (15 * HZ)
DEFINE_MUTEX(target_mgmt_mutex);
/* All 3 protected by target_mgmt_mutex */
static LIST_HEAD(target_list);
static u32 next_target_id;
static u32 nr_targets;
/* target_mgmt_mutex supposed to be locked */
struct iscsi_target *target_lookup_by_id(u32 id)
{
struct iscsi_target *target;
list_for_each_entry(target, &target_list, target_list_entry) {
if (target->tid == id)
return target;
}
return NULL;
}
/* target_mgmt_mutex supposed to be locked */
static struct iscsi_target *target_lookup_by_name(char *name)
{
struct iscsi_target *target;
list_for_each_entry(target, &target_list, target_list_entry) {
if (!strcmp(target->name, name))
return target;
}
return NULL;
}
/* target_mgmt_mutex supposed to be locked */
static int iscsi_target_create(struct iscsi_kern_target_info *info, u32 tid)
{
int err = -EINVAL, len;
char *name = info->name;
struct iscsi_target *target;
TRACE_MGMT_DBG("Creating target tid %u, name %s", tid, name);
len = strlen(name);
if (!len) {
PRINT_ERROR("The length of the target name is zero %u", tid);
goto out;
}
if (!try_module_get(THIS_MODULE)) {
PRINT_ERROR("Fail to get module %u", tid);
goto out;
}
target = kzalloc(sizeof(*target), GFP_KERNEL);
if (!target) {
err = -ENOMEM;
goto out_put;
}
target->tid = info->tid = tid;
strncpy(target->name, name, sizeof(target->name) - 1);
mutex_init(&target->target_mutex);
mutex_init(&target->target_sysfs_mutex);
INIT_LIST_HEAD(&target->session_list);
target->scst_tgt = scst_register(&iscsi_template, target->name);
if (!target->scst_tgt) {
PRINT_ERROR("%s", "scst_register() failed");
err = -EBUSY;
goto out_free;
}
scst_tgt_set_tgt_priv(target->scst_tgt, target);
list_add_tail(&target->target_list_entry, &target_list);
return 0;
out_free:
kfree(target);
out_put:
module_put(THIS_MODULE);
out:
return err;
}
/* target_mgmt_mutex supposed to be locked */
int target_add(struct iscsi_kern_target_info *info)
{
int err = -EEXIST;
u32 tid = info->tid;
if (nr_targets > MAX_NR_TARGETS) {
err = -EBUSY;
goto out;
}
if (target_lookup_by_name(info->name))
goto out;
if (tid && target_lookup_by_id(tid))
goto out;
if (!tid) {
do {
if (!++next_target_id)
++next_target_id;
} while (target_lookup_by_id(next_target_id));
tid = next_target_id;
}
err = iscsi_target_create(info, tid);
if (!err)
nr_targets++;
out:
return err;
}
static void target_destroy(struct iscsi_target *target)
{
TRACE_MGMT_DBG("Destroying target tid %u", target->tid);
scst_unregister(target->scst_tgt);
kfree(target);
module_put(THIS_MODULE);
}
/* target_mgmt_mutex supposed to be locked */
int target_enable(struct iscsi_kern_target_info *info)
{
int res = 0;
struct iscsi_target *tgt;
TRACE_ENTRY();
tgt = target_lookup_by_id(info->tid);
if (tgt == NULL) {
PRINT_ERROR("Target %d not found", info->tid);
res = -EINVAL;
goto out;
}
mutex_lock(&tgt->target_sysfs_mutex);
if (tgt->expected_ioctl != ENABLE_TARGET) {
PRINT_ERROR("Unexpected ENABLE_TARGET IOCTL for target %d",
tgt->tid);
res = -EINVAL;
goto out_unlock;
}
tgt->expected_ioctl = 0;
WARN_ON(tgt->tgt_enabled);
tgt->tgt_enabled = 1;
tgt->ioctl_res = 0;
complete_all(tgt->target_enabling_cmpl);
out_unlock:
mutex_unlock(&tgt->target_sysfs_mutex);
out:
TRACE_EXIT_RES(res);
return res;
}
/* target_mgmt_mutex supposed to be locked */
int target_disable(struct iscsi_kern_target_info *info)
{
int res = 0;
struct iscsi_target *tgt;
TRACE_ENTRY();
tgt = target_lookup_by_id(info->tid);
if (tgt == NULL) {
PRINT_ERROR("Target %d not found", info->tid);
res = -EINVAL;
goto out;
}
mutex_lock(&tgt->target_sysfs_mutex);
if (tgt->expected_ioctl != DISABLE_TARGET) {
PRINT_ERROR("Unexpected DISABLE_TARGET IOCTL for target %d",
tgt->tid);
res = -EINVAL;
goto out_unlock;
}
mutex_unlock(&tgt->target_sysfs_mutex);
mutex_lock(&tgt->target_mutex);
target_del_all_sess(tgt, ISCSI_CONN_ACTIVE_CLOSE | ISCSI_CONN_DELETING);
mutex_unlock(&tgt->target_mutex);
mutex_lock(&tgt->target_sysfs_mutex);
tgt->expected_ioctl = 0;
WARN_ON(!tgt->tgt_enabled);
tgt->tgt_enabled = 0;
tgt->ioctl_res = 0;
complete_all(tgt->target_enabling_cmpl);
out_unlock:
mutex_unlock(&tgt->target_sysfs_mutex);
out:
TRACE_EXIT_RES(res);
return res;
}
/* target_mgmt_mutex supposed to be locked */
int target_del(u32 id)
{
struct iscsi_target *target;
int err;
target = target_lookup_by_id(id);
if (!target) {
err = -ENOENT;
goto out;
}
mutex_lock(&target->target_mutex);
if (!list_empty(&target->session_list)) {
err = -EBUSY;
goto out_unlock;
}
list_del(&target->target_list_entry);
nr_targets--;
mutex_unlock(&target->target_mutex);
target_destroy(target);
return 0;
out_unlock:
mutex_unlock(&target->target_mutex);
out:
return err;
}
/* target_mutex supposed to be locked */
void target_del_session(struct iscsi_target *target,
struct iscsi_session *session, int flags)
{
TRACE_ENTRY();
TRACE_MGMT_DBG("Deleting session %p", session);
if (!list_empty(&session->conn_list)) {
struct iscsi_conn *conn, *tc;
list_for_each_entry_safe(conn, tc, &session->conn_list,
conn_list_entry) {
TRACE_MGMT_DBG("Mark conn %p closing", conn);
__mark_conn_closed(conn, flags);
}
} else {
TRACE_MGMT_DBG("Freeing session %p without connections",
session);
session_del(target, session->sid);
}
TRACE_EXIT();
return;
}
/* target_mutex supposed to be locked */
void target_del_all_sess(struct iscsi_target *target, int flags)
{
struct iscsi_session *session, *ts;
TRACE_ENTRY();
if (!list_empty(&target->session_list)) {
TRACE_MGMT_DBG("Deleting all sessions from target %p", target);
list_for_each_entry_safe(session, ts, &target->session_list,
session_list_entry) {
target_del_session(target, session, flags);
}
}
TRACE_EXIT();
return;
}
void target_del_all(void)
{
struct iscsi_target *target, *t;
TRACE_ENTRY();
TRACE_MGMT_DBG("%s", "Deleting all targets");
/* Not the best, ToDo */
while (1) {
mutex_lock(&target_mgmt_mutex);
if (list_empty(&target_list))
break;
list_for_each_entry_safe(target, t, &target_list,
target_list_entry) {
mutex_lock(&target->target_mutex);
if (!list_empty(&target->session_list)) {
target_del_all_sess(target,
ISCSI_CONN_ACTIVE_CLOSE |
ISCSI_CONN_DELETING);
mutex_unlock(&target->target_mutex);
} else {
TRACE_MGMT_DBG("Deleting target %p", target);
list_del(&target->target_list_entry);
nr_targets--;
mutex_unlock(&target->target_mutex);
target_destroy(target);
continue;
}
}
mutex_unlock(&target_mgmt_mutex);
msleep(100);
}
mutex_unlock(&target_mgmt_mutex);
TRACE_MGMT_DBG("%s", "Deleting all targets finished");
TRACE_EXIT();
return;
}
#ifdef CONFIG_SCST_PROC
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
static struct list_head *seq_list_start(struct list_head *head, loff_t pos)
{
struct list_head *lh;
list_for_each(lh, head)
if (pos-- == 0)
return lh;
return NULL;
}
static struct list_head *seq_list_next(void *v, struct list_head *head,
loff_t *ppos)
{
struct list_head *lh;
lh = ((struct list_head *)v)->next;
++*ppos;
return lh == head ? NULL : lh;
}
#endif
static void *iscsi_seq_start(struct seq_file *m, loff_t *pos)
{
int err;
err = mutex_lock_interruptible(&target_mgmt_mutex);
if (err < 0)
return ERR_PTR(err);
return seq_list_start(&target_list, *pos);
}
static void *iscsi_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
return seq_list_next(v, &target_list, pos);
}
static void iscsi_seq_stop(struct seq_file *m, void *v)
{
mutex_unlock(&target_mgmt_mutex);
}
static int iscsi_seq_show(struct seq_file *m, void *p)
{
iscsi_show_info_t *func = (iscsi_show_info_t *)m->private;
struct iscsi_target *target =
list_entry(p, struct iscsi_target, target_list_entry);
seq_printf(m, "tid:%u name:%s\n", target->tid, target->name);
mutex_lock(&target->target_mutex);
func(m, target);
mutex_unlock(&target->target_mutex);
return 0;
}
const struct seq_operations iscsi_seq_op = {
.start = iscsi_seq_start,
.next = iscsi_seq_next,
.stop = iscsi_seq_stop,
.show = iscsi_seq_show,
};
#else /* CONFIG_SCST_PROC */
static ssize_t iscsi_tgt_tid_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int pos;
struct scst_tgt *scst_tgt;
struct iscsi_target *tgt;
TRACE_ENTRY();
scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
tgt = (struct iscsi_target *)scst_tgt_get_tgt_priv(scst_tgt);
pos = sprintf(buf, "%u\n", tgt->tid);
TRACE_EXIT_RES(pos);
return pos;
}
static struct kobj_attribute iscsi_tgt_tid_attr =
__ATTR(tid, S_IRUGO, iscsi_tgt_tid_show, NULL);
const struct attribute *iscsi_tgt_attrs[] = {
&iscsi_tgt_tid_attr.attr,
NULL,
};
ssize_t iscsi_enable_target(struct scst_tgt *scst_tgt, const char *buf,
size_t size)
{
struct iscsi_target *tgt =
(struct iscsi_target *)scst_tgt_get_tgt_priv(scst_tgt);
DECLARE_COMPLETION_ONSTACK(enabling_cmpl);
int res = 0, rc, ioctl_res = 0;
bool enable;
TRACE_ENTRY();
mutex_lock(&tgt->target_sysfs_mutex);
if (tgt->target_enabling_cmpl != NULL) {
TRACE_DBG("A sysfs command is being processed for target %d",
tgt->tid);
res = -ETXTBSY;
goto out_unlock;
}
tgt->target_enabling_cmpl = &enabling_cmpl;
switch (buf[0]) {
case '0':
if (!tgt->tgt_enabled) {
TRACE_DBG("Target %d already disabled", tgt->tid);
goto out_null_unlock;
}
enable = true;
tgt->expected_ioctl = DISABLE_TARGET;
res = event_send(tgt->tid, 0, 0, E_DISABLE_TARGET);
if (res <= 0) {
PRINT_ERROR("event_send() failed: %d", res);
goto out_null_unlock;
}
break;
case '1':
if (tgt->tgt_enabled) {
TRACE_DBG("Target %d already enabled", tgt->tid);
goto out_null_unlock;
}
enable = false;
tgt->expected_ioctl = ENABLE_TARGET;
res = event_send(tgt->tid, 0, 0, E_ENABLE_TARGET);
if (res <= 0) {
PRINT_ERROR("event_send() failed: %d", res);
goto out_null_unlock;
}
break;
default:
PRINT_ERROR("%s: Requested action not understood: %s",
__func__, buf);
res = -EINVAL;
goto out_null_unlock;
}
mutex_unlock(&tgt->target_sysfs_mutex);
TRACE_DBG("Waiting for completion of enable/disable (%d) "
"target %d", enable, tgt->tid);
rc = wait_for_completion_interruptible_timeout(&enabling_cmpl,
SYSFS_WAIT_TIMEOUT);
if (res == 0) {
PRINT_ERROR("Timeout attempting to %s target %d",
enable ? "enable" : "disable", tgt->tid);
res = -EBUSY;
/* go through */
} else if (res < 0) {
if (res != -ERESTARTSYS)
PRINT_ERROR("wait_for_completion() failed: %d", res);
/* go through */
}
TRACE_DBG("Waiting for completion of enable/disable (%d) "
"target %d finished with res %d", enable, tgt->tid, res);
mutex_lock(&tgt->target_sysfs_mutex);
ioctl_res = tgt->ioctl_res;
out_null_unlock:
tgt->target_enabling_cmpl = NULL;
out_unlock:
mutex_unlock(&tgt->target_sysfs_mutex);
if (res == 0) {
res = ioctl_res;
if (res == 0)
res = size;
}
TRACE_EXIT_RES(res);
return res;
}
bool iscsi_is_target_enabled(struct scst_tgt *scst_tgt)
{
struct iscsi_target *tgt =
(struct iscsi_target *)scst_tgt_get_tgt_priv(scst_tgt);
return tgt->tgt_enabled;
}
#endif /* CONFIG_SCST_PROC */