mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-14 09:11:27 +00:00
This is the result of running the Linux kernel Coccinelle script from scripts/coccinelle/api/kmalloc_objs.cocci against the SCST tree. This patch doesn't change any functionality.
625 lines
14 KiB
C
625 lines
14 KiB
C
/*
|
|
* Copyright (C) 2002 - 2003 Ardis Technologies <roman@ardistech.com>
|
|
* Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
|
|
* Copyright (C) 2007 - 2018 Western Digital Corporation
|
|
*
|
|
* 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 <linux/module.h>
|
|
|
|
#include "iscsi_trace_flag.h"
|
|
#include "iscsi.h"
|
|
#include "digest.h"
|
|
|
|
#undef DEFAULT_SYMBOL_NAMESPACE
|
|
#define DEFAULT_SYMBOL_NAMESPACE SCST_NAMESPACE
|
|
|
|
#define MAX_NR_TARGETS (1UL << 30)
|
|
|
|
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;
|
|
|
|
lockdep_assert_held(&target_mgmt_mutex);
|
|
|
|
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(const char *name)
|
|
{
|
|
struct iscsi_target *target;
|
|
|
|
lockdep_assert_held(&target_mgmt_mutex);
|
|
|
|
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,
|
|
struct iscsi_target **out_target)
|
|
{
|
|
int err = -EINVAL, len;
|
|
char *name = info->name;
|
|
struct iscsi_target *target;
|
|
|
|
TRACE_MGMT_DBG("Creating target tid %u, name %s", tid, name);
|
|
|
|
lockdep_assert_held(&target_mgmt_mutex);
|
|
|
|
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_obj(*target);
|
|
if (!target) {
|
|
err = -ENOMEM;
|
|
goto out_put;
|
|
}
|
|
|
|
info->tid = tid;
|
|
target->tid = info->tid;
|
|
|
|
strscpy(target->name, name, sizeof(target->name));
|
|
|
|
mutex_init(&target->target_mutex);
|
|
INIT_LIST_HEAD(&target->session_list);
|
|
INIT_LIST_HEAD(&target->attrs_list);
|
|
|
|
target->scst_tgt = scst_register_target(&iscsi_template, target->name);
|
|
if (!target->scst_tgt) {
|
|
PRINT_ERROR("%s", "scst_register_target() failed");
|
|
err = -EBUSY;
|
|
goto out_free;
|
|
}
|
|
|
|
scst_tgt_set_tgt_priv(target->scst_tgt, target);
|
|
|
|
list_add_tail(&target->target_list_entry, &target_list);
|
|
|
|
*out_target = target;
|
|
|
|
return 0;
|
|
|
|
out_free:
|
|
kfree(target);
|
|
|
|
out_put:
|
|
module_put(THIS_MODULE);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/* target_mgmt_mutex supposed to be locked */
|
|
int __add_target(struct iscsi_kern_target_info *info)
|
|
{
|
|
int err;
|
|
u32 tid = info->tid;
|
|
struct iscsi_target *target = NULL; /* to calm down sparse */
|
|
union add_info_union {
|
|
struct iscsi_kern_params_info params_info;
|
|
struct iscsi_kern_attr attr_info;
|
|
} *add_info;
|
|
int i, rc;
|
|
unsigned long attrs_ptr_long;
|
|
struct iscsi_kern_attr __user *attrs_ptr;
|
|
|
|
lockdep_assert_held(&target_mgmt_mutex);
|
|
|
|
if (nr_targets > MAX_NR_TARGETS) {
|
|
err = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
if (target_lookup_by_name(info->name)) {
|
|
PRINT_ERROR("Target %s already exist!", info->name);
|
|
err = -EEXIST;
|
|
goto out;
|
|
}
|
|
|
|
if (tid && target_lookup_by_id(tid)) {
|
|
PRINT_ERROR("Target %u already exist!", tid);
|
|
err = -EEXIST;
|
|
goto out;
|
|
}
|
|
|
|
add_info = kmalloc_obj(*add_info);
|
|
if (!add_info) {
|
|
PRINT_ERROR("Unable to allocate additional info (size %zd)",
|
|
sizeof(*add_info));
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
if (tid == 0) {
|
|
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, &target);
|
|
if (err != 0)
|
|
goto out_free;
|
|
|
|
nr_targets++;
|
|
|
|
{
|
|
struct iscsi_kern_attr *attr_info = &add_info->attr_info;
|
|
|
|
mutex_lock(&target->target_mutex);
|
|
|
|
attrs_ptr_long = info->attrs_ptr;
|
|
attrs_ptr = (struct iscsi_kern_attr __user *)attrs_ptr_long;
|
|
for (i = 0; i < info->attrs_num; i++) {
|
|
memset(attr_info, 0, sizeof(*attr_info));
|
|
|
|
rc = copy_from_user(attr_info, attrs_ptr, sizeof(*attr_info));
|
|
if (rc != 0) {
|
|
PRINT_ERROR("Failed to copy users of target %s failed",
|
|
info->name);
|
|
err = -EFAULT;
|
|
goto out_del_unlock;
|
|
}
|
|
|
|
attr_info->name[sizeof(attr_info->name) - 1] = '\0';
|
|
|
|
err = iscsi_add_attr(target, attr_info);
|
|
if (err != 0)
|
|
goto out_del_unlock;
|
|
|
|
attrs_ptr++;
|
|
}
|
|
|
|
mutex_unlock(&target->target_mutex);
|
|
}
|
|
|
|
err = tid;
|
|
|
|
out_free:
|
|
kfree(add_info);
|
|
|
|
out:
|
|
return err;
|
|
|
|
out_del_unlock:
|
|
mutex_unlock(&target->target_mutex);
|
|
__del_target(tid);
|
|
goto out_free;
|
|
}
|
|
|
|
static void target_destroy(struct iscsi_target *target)
|
|
{
|
|
struct iscsi_attr *attr, *t;
|
|
|
|
TRACE_MGMT_DBG("Destroying target tid %u", target->tid);
|
|
|
|
list_for_each_entry_safe(attr, t, &target->attrs_list, attrs_list_entry)
|
|
__iscsi_del_attr(target, attr);
|
|
|
|
scst_unregister_target(target->scst_tgt);
|
|
|
|
kfree(target);
|
|
|
|
module_put(THIS_MODULE);
|
|
}
|
|
|
|
/* target_mgmt_mutex supposed to be locked */
|
|
int __del_target(u32 id)
|
|
{
|
|
struct iscsi_target *target;
|
|
int err;
|
|
|
|
lockdep_assert_held(&target_mgmt_mutex);
|
|
|
|
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(TRACE_MGMT, "Deleting session %p (initiator %s)",
|
|
session, session->scst_sess->initiator_name);
|
|
|
|
lockdep_assert_held(&target->target_mutex);
|
|
|
|
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("Del session: closing conn %p", conn);
|
|
__mark_conn_closed(conn, flags);
|
|
}
|
|
} else {
|
|
TRACE_MGMT_DBG("Freeing session %p without connections",
|
|
session);
|
|
__del_session(target, session->sid);
|
|
}
|
|
|
|
TRACE_EXIT();
|
|
}
|
|
|
|
/* target_mutex supposed to be locked */
|
|
void target_del_all_sess(struct iscsi_target *target, int flags)
|
|
{
|
|
struct iscsi_session *session, *ts;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
lockdep_assert_held(&target->target_mutex);
|
|
|
|
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();
|
|
}
|
|
EXPORT_SYMBOL(target_del_all_sess);
|
|
|
|
void target_del_all(void)
|
|
{
|
|
struct iscsit_transport *transport;
|
|
struct iscsi_target *target, *t;
|
|
bool first = true;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
TRACE_MGMT_DBG("%s", "Deleting all targets");
|
|
|
|
transport = iscsit_get_transport(ISCSI_TCP);
|
|
if (transport && transport->iscsit_close_all_portals)
|
|
transport->iscsit_close_all_portals();
|
|
|
|
transport = iscsit_get_transport(ISCSI_RDMA);
|
|
if (transport && transport->iscsit_close_all_portals)
|
|
transport->iscsit_close_all_portals();
|
|
|
|
/* Not the best, ToDo */
|
|
while (1) {
|
|
mutex_lock(&target_mgmt_mutex);
|
|
|
|
if (list_empty(&target_list))
|
|
break;
|
|
|
|
/*
|
|
* In the first iteration we won't delete targets to go at
|
|
* first through all sessions of all targets and close their
|
|
* connections. Otherwise we can stuck for noticeable time
|
|
* waiting during a target's unregistration for the activities
|
|
* suspending over active connection. This can especially got
|
|
* bad if any being wait connection itself stuck waiting for
|
|
* something and can be recovered only by connection close.
|
|
* Let's for such cases not wait while such connection recover
|
|
* theyself, but act in advance.
|
|
*/
|
|
|
|
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);
|
|
} else if (!first) {
|
|
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->target_mutex);
|
|
}
|
|
mutex_unlock(&target_mgmt_mutex);
|
|
msleep(100);
|
|
|
|
first = false;
|
|
}
|
|
|
|
mutex_unlock(&target_mgmt_mutex);
|
|
|
|
TRACE_MGMT_DBG("%s", "Deleting all targets finished");
|
|
|
|
TRACE_EXIT();
|
|
}
|
|
|
|
static ssize_t iscsi_tgt_tid_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct scst_tgt *scst_tgt;
|
|
struct iscsi_target *tgt;
|
|
ssize_t res = -E_TGT_PRIV_NOT_YET_SET;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
|
|
tgt = scst_tgt_get_tgt_priv(scst_tgt);
|
|
if (!tgt)
|
|
goto out;
|
|
|
|
res = sysfs_emit(buf, "%u\n", tgt->tid);
|
|
|
|
out:
|
|
TRACE_EXIT_RES(res);
|
|
return res;
|
|
}
|
|
|
|
static struct kobj_attribute iscsi_tgt_attr_tid =
|
|
__ATTR(tid, 0444, iscsi_tgt_tid_show, NULL);
|
|
|
|
const struct attribute *iscsi_tgt_attrs[] = {
|
|
&iscsi_tgt_attr_tid.attr,
|
|
NULL,
|
|
};
|
|
|
|
ssize_t iscsi_sysfs_send_event(uint32_t tid, enum iscsi_kern_event_code code, const char *param1,
|
|
const char *param2, void **data)
|
|
{
|
|
int res;
|
|
struct scst_sysfs_user_info *info;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
if (ctr_open_state != ISCSI_CTR_OPEN_STATE_OPEN) {
|
|
PRINT_ERROR("User space process is not connected. Is iscsi-scstd running?");
|
|
res = -EPERM;
|
|
goto out;
|
|
}
|
|
|
|
res = scst_sysfs_user_add_info(&info);
|
|
if (res != 0)
|
|
goto out;
|
|
|
|
TRACE_DBG("Sending event %d (tid %d, param1 %s, param2 %s, cookie %d, info %p)",
|
|
code, tid, param1, param2, info->info_cookie, info);
|
|
|
|
res = event_send(tid, 0, 0, info->info_cookie, code, param1, param2);
|
|
if (res <= 0) {
|
|
PRINT_ERROR("event_send() failed: %d", res);
|
|
if (res == 0)
|
|
res = -EFAULT;
|
|
goto out_free;
|
|
}
|
|
|
|
/*
|
|
* It may wait 30 secs in blocking connect to an unreacheable
|
|
* iSNS server. It must be fixed, but not now. ToDo.
|
|
*/
|
|
res = scst_wait_info_completion(info, 31 * HZ);
|
|
|
|
if (data)
|
|
*data = info->data;
|
|
|
|
out_free:
|
|
scst_sysfs_user_del_info(info);
|
|
|
|
out:
|
|
TRACE_EXIT_RES(res);
|
|
return res;
|
|
}
|
|
|
|
int iscsi_enable_target(struct scst_tgt *scst_tgt, bool enable)
|
|
{
|
|
struct iscsi_target *tgt =
|
|
(struct iscsi_target *)scst_tgt_get_tgt_priv(scst_tgt);
|
|
int res;
|
|
uint32_t type;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
if (!tgt) {
|
|
res = -E_TGT_PRIV_NOT_YET_SET;
|
|
goto out;
|
|
}
|
|
|
|
if (enable)
|
|
type = E_ENABLE_TARGET;
|
|
else
|
|
type = E_DISABLE_TARGET;
|
|
|
|
TRACE_DBG("%s target %d", enable ? "Enabling" : "Disabling", tgt->tid);
|
|
|
|
res = iscsi_sysfs_send_event(tgt->tid, type, NULL, NULL, NULL);
|
|
|
|
out:
|
|
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);
|
|
|
|
if (tgt)
|
|
return tgt->tgt_enabled;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
ssize_t iscsi_sysfs_add_target(const char *target_name, char *params)
|
|
{
|
|
int res;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
res = iscsi_sysfs_send_event(0, E_ADD_TARGET, target_name, params, NULL);
|
|
if (res > 0) {
|
|
/* It's tid */
|
|
res = 0;
|
|
}
|
|
|
|
TRACE_EXIT_RES(res);
|
|
return res;
|
|
}
|
|
|
|
ssize_t iscsi_sysfs_del_target(const char *target_name)
|
|
{
|
|
int res = 0, tid;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
/* We don't want to have tgt visible after the mutex unlock */
|
|
{
|
|
struct iscsi_target *tgt;
|
|
|
|
mutex_lock(&target_mgmt_mutex);
|
|
tgt = target_lookup_by_name(target_name);
|
|
if (!tgt) {
|
|
PRINT_ERROR("Target %s not found", target_name);
|
|
mutex_unlock(&target_mgmt_mutex);
|
|
res = -ENOENT;
|
|
goto out;
|
|
}
|
|
tid = tgt->tid;
|
|
mutex_unlock(&target_mgmt_mutex);
|
|
}
|
|
|
|
TRACE_DBG("Deleting target %s (tid %d)", target_name, tid);
|
|
|
|
res = iscsi_sysfs_send_event(tid, E_DEL_TARGET, NULL, NULL, NULL);
|
|
|
|
out:
|
|
TRACE_EXIT_RES(res);
|
|
return res;
|
|
}
|
|
|
|
ssize_t iscsi_sysfs_mgmt_cmd(char *cmd)
|
|
{
|
|
int res;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
TRACE_DBG("Sending mgmt cmd %s", cmd);
|
|
|
|
res = iscsi_sysfs_send_event(0, E_MGMT_CMD, cmd, NULL, NULL);
|
|
|
|
TRACE_EXIT_RES(res);
|
|
return res;
|
|
}
|
|
|
|
static ssize_t iscsi_acg_sess_dedicated_threads_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct scst_acg *acg;
|
|
bool dedicated;
|
|
ssize_t ret;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
acg = container_of(kobj, struct scst_acg, acg_kobj);
|
|
dedicated = scst_get_acg_tgt_priv(acg);
|
|
|
|
ret = sysfs_emit(buf, "%d\n", dedicated);
|
|
|
|
if (dedicated)
|
|
ret += sysfs_emit_at(buf, ret, "%s\n", SCST_SYSFS_KEY_MARK);
|
|
|
|
TRACE_EXIT_RES(ret);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t iscsi_acg_sess_dedicated_threads_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr, const char *buf,
|
|
size_t count)
|
|
{
|
|
int res;
|
|
struct scst_acg *acg;
|
|
unsigned long val;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
acg = container_of(kobj, struct scst_acg, acg_kobj);
|
|
|
|
res = kstrtoul(buf, 0, &val);
|
|
if (res != 0) {
|
|
PRINT_ERROR("kstrtoul() for %s failed: %d ", buf, res);
|
|
goto out;
|
|
}
|
|
|
|
scst_set_acg_tgt_priv(acg, (void *)(unsigned long)(val != 0));
|
|
|
|
res = count;
|
|
|
|
out:
|
|
TRACE_EXIT_RES(res);
|
|
return res;
|
|
}
|
|
|
|
static struct kobj_attribute iscsi_acg_attr_sess_dedicated_threads =
|
|
__ATTR(per_sess_dedicated_tgt_threads, 0644,
|
|
iscsi_acg_sess_dedicated_threads_show,
|
|
iscsi_acg_sess_dedicated_threads_store);
|
|
|
|
const struct attribute *iscsi_acg_attrs[] = {
|
|
&iscsi_acg_attr_sess_dedicated_threads.attr,
|
|
NULL,
|
|
};
|