/* * Copyright (C) 2002 - 2003 Ardis Technologies * 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 #include #include "iscsi_trace_flag.h" #include "iscsi.h" #include "digest.h" #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(sizeof(*target), GFP_KERNEL); if (!target) { err = -ENOMEM; goto out_put; } target->tid = info->tid = tid; strlcpy(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(sizeof(*add_info), GFP_KERNEL); if (add_info == NULL) { 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); return; } /* 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(); 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(); 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(); return; } 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(); return; } static ssize_t iscsi_tgt_tid_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int res = -E_TGT_PRIV_NOT_YET_SET; struct scst_tgt *scst_tgt; struct iscsi_target *tgt; 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 = sprintf(buf, "%u\n", tgt->tid); out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute iscsi_tgt_attr_tid = __ATTR(tid, S_IRUGO, 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 != NULL) *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 == NULL) { 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 != NULL) 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 == NULL) { 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) { int pos; struct scst_acg *acg; bool dedicated; TRACE_ENTRY(); acg = container_of(kobj, struct scst_acg, acg_kobj); dedicated = scst_get_acg_tgt_priv(acg) != NULL; pos = sprintf(buf, "%d\n%s", dedicated, dedicated ? SCST_SYSFS_KEY_MARK "\n" : ""); TRACE_EXIT_RES(pos); return pos; } 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, S_IRUGO | S_IWUSR, 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, };