/*
* iSCSI sysfs
*
* Copyright (C) 2006 Mike Christie
* Copyright (C) 2006 Red Hat, Inc. All rights reserved.
* Copyright (C) 2015 Peter Hatina <phatina@redhat.com>
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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 <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "log.h"
#include "initiator.h"
#include "transport.h"
#include "idbm.h"
#include "idbm_fields.h"
#include "version.h"
#include "iscsi_sysfs.h"
#include "sysdeps.h"
#include "iscsi_settings.h"
#include "iface.h"
#include "session_info.h"
#include "host.h"
#include "iscsi_err.h"
#include "flashnode.h"
/*
* TODO: remove the _DIR defines and search for subsys dirs like
* is done in sysfs.c.
*/
#define ISCSI_TRANSPORT_DIR "/sys/class/iscsi_transport"
#define ISCSI_SESSION_DIR "/sys/class/iscsi_session"
#define ISCSI_HOST_DIR "/sys/class/iscsi_host"
#define ISCSI_FLASHNODE_DIR "/sys/bus/iscsi_flashnode/devices"
#define ISCSI_SESSION_SUBSYS "iscsi_session"
#define ISCSI_CONN_SUBSYS "iscsi_connection"
#define ISCSI_HOST_SUBSYS "iscsi_host"
#define ISCSI_TRANSPORT_SUBSYS "iscsi_transport"
#define ISCSI_IFACE_SUBSYS "iscsi_iface"
#define ISCSI_FLASHNODE_SUBSYS "iscsi_flashnode"
#define SCSI_HOST_SUBSYS "scsi_host"
#define SCSI_SUBSYS "scsi"
#define ISCSI_SESSION_ID "session%d"
#define ISCSI_CONN_ID "connection%d:0"
#define ISCSI_HOST_ID "host%d"
#define ISCSI_FLASHNODE_SESS "flashnode_sess-%d:%d"
#define ISCSI_FLASHNODE_CONN "flashnode_conn-%d:%d:0"
/*
* TODO: make this into a real API and check inputs better and add doc.
*/
static int num_transports;
LIST_HEAD(transports);
void free_transports(void)
{
struct iscsi_transport *t, *tmp;
list_for_each_entry_safe(t, tmp, &transports, list) {
list_del(&t->list);
free(t);
}
}
static int trans_filter(const struct dirent *dir)
{
return strcmp(dir->d_name, ".") && strcmp(dir->d_name, "..");
}
static int read_transports(void)
{
struct dirent **namelist;
int i, n, found;
struct iscsi_transport *t;
log_debug(7, "in %s", __FUNCTION__);
n = scandir(ISCSI_TRANSPORT_DIR, &namelist, trans_filter,
alphasort);
if (n < 0) {
log_error("Could not scan %s.", ISCSI_TRANSPORT_DIR);
return n;
}
for (i = 0; i < n; i++) {
found = 0;
list_for_each_entry(t, &transports, list) {
if (!strcmp(t->name, namelist[i]->d_name)) {
found = 1;
break;
}
}
if (!found) {
/* copy new transport */
t = malloc(sizeof(*t));
if (!t)
continue;
log_debug(7, "Adding new transport %s",
namelist[i]->d_name);
INIT_LIST_HEAD(&t->sessions);
INIT_LIST_HEAD(&t->list);
strlcpy(t->name, namelist[i]->d_name,
ISCSI_TRANSPORT_NAME_MAXLEN);
if (set_transport_template(t)) {
free(t);
return -1;
}
} else
log_debug(7, "Updating transport %s",
namelist[i]->d_name);
if (sysfs_get_uint64(t->name, ISCSI_TRANSPORT_SUBSYS,
"handle", &t->handle)) {
if (list_empty(&t->list))
free(t);
else
log_error("Could not update %s.",
t->name);
continue;
}
if (sysfs_get_uint(t->name, ISCSI_TRANSPORT_SUBSYS,
"caps", &t->caps)) {
if (list_empty(&t->list))
free(t);
else
log_error("Could not update %s.",
t->name);
continue;
}
/*
* tmp hack for qla4xx compat
*/
if (!strcmp(t->name, "qla4xxx")) {
t->caps |= CAP_DATA_PATH_OFFLOAD;
}
if (list_empty(&t->list))
list_add_tail(&t->list, &transports);
}
for (i = 0; i < n; i++)
free(namelist[i]);
free(namelist);
num_transports = n;
return 0;
}
/* caller must check lengths */
void iscsi_sysfs_get_auth_conf(int sid, struct iscsi_auth_config *conf)
{
char id[NAME_SIZE];
memset(conf, 0, sizeof(*conf));
snprintf(id, sizeof(id), ISCSI_SESSION_ID, sid);
sysfs_get_str(id, ISCSI_SESSION_SUBSYS, "username", conf->username,
sizeof(conf->username));
sysfs_get_str(id, ISCSI_SESSION_SUBSYS, "username_in",
conf->username_in, sizeof(conf->username_in));
sysfs_get_str(id, ISCSI_SESSION_SUBSYS, "password",
(char *)conf->password, sizeof(conf->password));
if (strlen((char *)conf->password))
conf->password_length = strlen((char *)conf->password);
sysfs_get_str(id, ISCSI_SESSION_SUBSYS, "password_in",
(char *)conf->password_in, sizeof(conf->password_in));
if (strlen((char *)conf->password_in))
conf->password_in_length = strlen((char *)conf->password_in);
}
/* called must check for -1=invalid value */
void iscsi_sysfs_get_negotiated_conn_conf(int sid,
struct iscsi_conn_operational_config *conf)
{
char id[NAME_SIZE];
memset(conf, 0, sizeof(*conf));
snprintf(id, sizeof(id), ISCSI_CONN_ID, sid);
sysfs_get_int(id, ISCSI_CONN_SUBSYS, "data_digest", &conf->DataDigest);
sysfs_get_int(id, ISCSI_CONN_SUBSYS, "header_digest",
&conf->HeaderDigest);
sysfs_get_int(id, ISCSI_CONN_SUBSYS, "max_xmit_dlength",
&conf->MaxXmitDataSegmentLength);
sysfs_get_int(id, ISCSI_CONN_SUBSYS, "max_recv_dlength",
&conf->MaxRecvDataSegmentLength);
}
/* called must check for -1=invalid value */
void iscsi_sysfs_get_negotiated_session_conf(int sid,
struct iscsi_session_operational_config *conf)
{
char id[NAME_SIZE];
memset(conf, 0, sizeof(*conf));
snprintf(id, sizeof(id), ISCSI_SESSION_ID, sid);
sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "data_pdu_in_order",
&conf->DataPDUInOrder);
sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "data_seq_in_order",
&conf->DataSequenceInOrder);
sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "erl", &conf->ERL);
sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "first_burst_len",
&conf->FirstBurstLength);
sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "max_burst_len",
&conf->MaxBurstLength);
sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "immediate_data",
&conf->ImmediateData);
sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "initial_r2t",
&conf->InitialR2T);
sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "max_outstanding_r2t",
&conf->MaxOutstandingR2T);
}
/*
* iscsi_sysfs_session_user_created - return if session was setup by userspace
* @sid: id of session to test
*
* Returns -1 if we could not tell due to kernel not supporting the
* feature. 0 is returned if kernel created it. And 1 is returned
* if userspace created it.
*/
int iscsi_sysfs_session_user_created(int sid)
{
char id[NAME_SIZE];
pid_t pid;
snprintf(id, sizeof(id), ISCSI_SESSION_ID, sid);
if (sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "creator", &pid))
return -1;
if (pid == -1)
return 0;
else
return 1;
}
uint32_t iscsi_sysfs_get_host_no_from_sid(uint32_t sid, int *err)
{
struct sysfs_device *session_dev, *host_dev;
char devpath[PATH_SIZE];
char id[NAME_SIZE];
*err = 0;
snprintf(id, sizeof(id), "session%u", sid);
if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath),
ISCSI_SESSION_SUBSYS, id)) {
log_error("Could not lookup devpath for %s. Possible sysfs "
"incompatibility.", id);
*err = ISCSI_ERR_SYSFS_LOOKUP;
return 0;
}
session_dev = sysfs_device_get(devpath);
if (!session_dev) {
log_error("Could not get dev for %s. Possible sysfs "
"incompatibility.", id);
*err = ISCSI_ERR_SYSFS_LOOKUP;
return 0;
}
/*
* 2.6.27 moved from scsi_host to scsi for the subsys when
* sysfs compat is not on.
*/
host_dev = sysfs_device_get_parent_with_subsystem(session_dev,
SCSI_SUBSYS);
if (!host_dev) {
struct sysfs_device *dev_parent;
dev_parent = sysfs_device_get_parent(session_dev);
while (dev_parent != NULL) {
if (strncmp(dev_parent->kernel, "host", 4) == 0) {
host_dev = dev_parent;
break;
}
dev_parent = sysfs_device_get_parent(dev_parent);
}
if (!host_dev) {
log_error("Could not get host dev for %s. Possible "
"sysfs incompatibility.", id);
*err = ISCSI_ERR_SYSFS_LOOKUP;
return 0;
}
}
return atol(host_dev->kernel_number);
}
/* TODO: merge and make macro */
static int __get_host_no_from_netdev(void *data, struct host_info *info)
{
struct host_info *ret_info = data;
if (!strcmp(ret_info->iface.netdev, info->iface.netdev)) {
ret_info->host_no = info->host_no;
return 1;
}
return 0;
}
static uint32_t get_host_no_from_netdev(char *netdev, int *rc)
{
uint32_t host_no = -1;
struct host_info *info;
int nr_found, local_rc;
*rc = 0;
info = calloc(1, sizeof(*info));
if (!info) {
*rc = ISCSI_ERR_NOMEM;
return -1;
}
strcpy(info->iface.netdev, netdev);
local_rc = iscsi_sysfs_for_each_host(info, &nr_found,
__get_host_no_from_netdev);
if (local_rc == 1)
host_no = info->host_no;
else
*rc = ISCSI_ERR_HOST_NOT_FOUND;
free(info);
return host_no;
}
static int __get_host_no_from_hwaddress(void *data, struct host_info *info)
{
struct host_info *ret_info = data;
if (!strcasecmp(ret_info->iface.hwaddress, info->iface.hwaddress)) {
ret_info->host_no = info->host_no;
return 1;
}
return 0;
}
uint32_t iscsi_sysfs_get_host_no_from_hwaddress(char *hwaddress, int *rc)
{
uint32_t host_no = -1;
struct host_info *info;
int nr_found, local_rc;
*rc = 0;
info = calloc(1, sizeof(*info));
if (!info) {
*rc = ISCSI_ERR_NOMEM;
return -1;
}
strcpy(info->iface.hwaddress, hwaddress);
local_rc = iscsi_sysfs_for_each_host(info, &nr_found,
__get_host_no_from_hwaddress);
if (local_rc == 1)
host_no = info->host_no;
else
*rc = ISCSI_ERR_HOST_NOT_FOUND;
free(info);
return host_no;
}
static int __get_host_no_from_ipaddress(void *data, struct host_info *info)
{
struct host_info *ret_info = data;
if (!strcmp(ret_info->iface.ipaddress, info->iface.ipaddress)) {
ret_info->host_no = info->host_no;
return 1;
}
return 0;
}
static uint32_t get_host_no_from_ipaddress(char *address, int *rc)
{
uint32_t host_no = -1;
struct host_info *info;
int nr_found;
int local_rc;
*rc = 0;
info = calloc(1, sizeof(*info));
if (!info) {
*rc = ISCSI_ERR_NOMEM;
return -1;
}
strcpy(info->iface.ipaddress, address);
local_rc = iscsi_sysfs_for_each_host(info, &nr_found,
__get_host_no_from_ipaddress);
if (local_rc == 1)
host_no = info->host_no;
else
*rc = ISCSI_ERR_HOST_NOT_FOUND;
free(info);
return host_no;
}
uint32_t iscsi_sysfs_get_host_no_from_hwinfo(struct iface_rec *iface, int *rc)
{
int tmp_rc;
uint32_t host_no = -1;
if (strlen(iface->hwaddress) &&
strcasecmp(iface->hwaddress, DEFAULT_HWADDRESS))
host_no = iscsi_sysfs_get_host_no_from_hwaddress(
iface->hwaddress, &tmp_rc);
else if (strlen(iface->netdev) &&
strcasecmp(iface->netdev, DEFAULT_NETDEV))
host_no = get_host_no_from_netdev(iface->netdev, &tmp_rc);
else if (strlen(iface->ipaddress) &&
strcasecmp(iface->ipaddress, DEFAULT_IPADDRESS))
host_no = get_host_no_from_ipaddress(iface->ipaddress, &tmp_rc);
else
tmp_rc = ISCSI_ERR_INVAL;
*rc = tmp_rc;
return host_no;
}
/*
* Read the flash node attributes based on host and flash node index.
*/
int iscsi_sysfs_get_flashnode_info(struct flashnode_rec *fnode,
uint32_t host_no,
uint32_t flashnode_idx)
{
char sess_id[NAME_SIZE] = {'\0'};
char conn_id[NAME_SIZE] = {'\0'};
char fnode_path[PATH_SIZE] = {'\0'};
struct iscsi_transport *t;
int ret = 0;
t = iscsi_sysfs_get_transport_by_hba(host_no);
if (!t)
log_debug(7, "could not get transport name for host%d",
host_no);
else
strlcpy(fnode->transport_name, t->name,
ISCSI_TRANSPORT_NAME_MAXLEN);
snprintf(sess_id, sizeof(sess_id), ISCSI_FLASHNODE_SESS, host_no,
flashnode_idx);
snprintf(fnode_path, sizeof(fnode_path), ISCSI_FLASHNODE_DIR"/%s",
sess_id);
if (access(fnode_path, F_OK) != 0)
return errno;
snprintf(conn_id, sizeof(conn_id), ISCSI_FLASHNODE_CONN, host_no,
flashnode_idx);
snprintf(fnode_path, sizeof(fnode_path), ISCSI_FLASHNODE_DIR"/%s",
conn_id);
if (access(fnode_path, F_OK) != 0)
return errno;
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "is_fw_assigned_ipv6",
&((fnode->conn[0]).is_fw_assigned_ipv6));
sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "portal_type",
(fnode->sess).portal_type,
sizeof((fnode->sess).portal_type));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "auto_snd_tgt_disable",
&((fnode->sess).auto_snd_tgt_disable));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "discovery_session",
&((fnode->sess).discovery_session));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "entry_enable",
&((fnode->sess).entry_enable));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "header_digest",
&((fnode->conn[0]).header_digest_en));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "data_digest",
&((fnode->conn[0]).data_digest_en));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "immediate_data",
&((fnode->sess).immediate_data));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "initial_r2t",
&((fnode->sess).initial_r2t));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "data_seq_in_order",
&((fnode->sess).data_seq_in_order));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "data_pdu_in_order",
&((fnode->sess).data_pdu_in_order));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "chap_auth",
&((fnode->sess).chap_auth_en));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "snack_req",
&((fnode->conn[0]).snack_req_en));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "discovery_logout",
&((fnode->sess).discovery_logout_en));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "bidi_chap",
&((fnode->sess).bidi_chap_en));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS,
"discovery_auth_optional",
&((fnode->sess).discovery_auth_optional));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "erl",
&((fnode->sess).erl));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_timestamp_stat",
&((fnode->conn[0]).tcp_timestamp_stat));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_nagle_disable",
&((fnode->conn[0]).tcp_nagle_disable));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_wsf_disable",
&((fnode->conn[0]).tcp_wsf_disable));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_timer_scale",
&((fnode->conn[0]).tcp_timer_scale));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_timestamp_enable",
&((fnode->conn[0]).tcp_timestamp_en));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "fragment_disable",
&((fnode->conn[0]).fragment_disable));
sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "max_recv_dlength",
&((fnode->conn[0]).max_recv_dlength));
sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "max_xmit_dlength",
&((fnode->conn[0]).max_xmit_dlength));
sysfs_get_uint(sess_id, ISCSI_FLASHNODE_SUBSYS, "first_burst_len",
&((fnode->sess).first_burst_len));
sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "def_time2wait",
&((fnode->sess).def_time2wait));
sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "def_time2retain",
&((fnode->sess).def_time2retain));
sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "max_outstanding_r2t",
&((fnode->sess).max_outstanding_r2t));
sysfs_get_uint16(conn_id, ISCSI_FLASHNODE_SUBSYS, "keepalive_tmo",
&((fnode->conn[0]).keepalive_tmo));
sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "isid",
(fnode->sess).isid, sizeof((fnode->sess).isid));
sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "tsid",
&((fnode->sess).tsid));
sysfs_get_uint16(conn_id, ISCSI_FLASHNODE_SUBSYS, "port",
&((fnode->conn[0]).port));
sysfs_get_uint(sess_id, ISCSI_FLASHNODE_SUBSYS, "max_burst_len",
&((fnode->sess).max_burst_len));
sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "def_taskmgmt_tmo",
&((fnode->sess).def_taskmgmt_tmo));
sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "targetalias",
(fnode->sess).targetalias,
sizeof((fnode->sess).targetalias));
sysfs_get_str(conn_id, ISCSI_FLASHNODE_SUBSYS, "ipaddress",
(fnode->conn[0]).ipaddress,
sizeof((fnode->conn[0]).ipaddress));
sysfs_get_str(conn_id, ISCSI_FLASHNODE_SUBSYS, "redirect_ipaddr",
(fnode->conn[0]).redirect_ipaddr,
sizeof((fnode->conn[0]).redirect_ipaddr));
sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "max_segment_size",
&((fnode->conn[0]).max_segment_size));
sysfs_get_uint16(conn_id, ISCSI_FLASHNODE_SUBSYS, "local_port",
&((fnode->conn[0]).local_port));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "ipv4_tos",
&((fnode->conn[0]).ipv4_tos));
sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "ipv6_traffic_class",
&((fnode->conn[0]).ipv6_traffic_class));
sysfs_get_uint16(conn_id, ISCSI_FLASHNODE_SUBSYS, "ipv6_flow_label",
&((fnode->conn[0]).ipv6_flow_lbl));
sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "targetname",
(fnode->sess).targetname,
sizeof((fnode->sess).targetname));
sysfs_get_str(conn_id, ISCSI_FLASHNODE_SUBSYS, "link_local_ipv6",
(fnode->conn[0]).link_local_ipv6,
sizeof((fnode->conn[0]).link_local_ipv6));
sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS,
"discovery_parent_idx",
&((fnode->sess).discovery_parent_idx));
sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS,
"discovery_parent_type",
(fnode->sess).discovery_parent_type,
sizeof((fnode->sess).discovery_parent_type));
sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "tpgt",
&((fnode->sess).tpgt));
sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_xmit_wsf",
&((fnode->conn[0]).tcp_xmit_wsf));
sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_recv_wsf",
&((fnode->conn[0]).tcp_recv_wsf));
sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "chap_out_idx",
&((fnode->sess).chap_out_idx));
sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "chap_in_idx",
&((fnode->sess).chap_in_idx));
sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "username",
(fnode->sess).username, sizeof((fnode->sess).username));
sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "username_in",
(fnode->sess).username_in,
sizeof((fnode->sess).username_in));
sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "password",
(fnode->sess).password, sizeof((fnode->sess).password));
sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "password_in",
(fnode->sess).password_in,
sizeof((fnode->sess).password_in));
sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "statsn",
&((fnode->conn[0]).stat_sn));
sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "exp_statsn",
&((fnode->conn[0]).exp_stat_sn));
sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "is_boot_target",
&((fnode->sess).is_boot_target));
return ret;
}
/*
* For each flash node of the given host, perform operation specified in fn.
*/
int iscsi_sysfs_for_each_flashnode(void *data, uint32_t host_no, int *nr_found,
iscsi_sysfs_flashnode_op_fn *fn)
{
struct dirent **namelist;
int rc = 0, i, n;
struct flashnode_rec *fnode;
uint32_t flashnode_idx;
uint32_t hostno;
fnode = malloc(sizeof(*fnode));
if (!fnode)
return ISCSI_ERR_NOMEM;
n = scandir(ISCSI_FLASHNODE_DIR, &namelist, trans_filter, alphasort);
if (n <= 0)
goto free_fnode;
for (i = 0; i < n; i++) {
memset(fnode, 0, sizeof(*fnode));
if (!strncmp(namelist[i]->d_name, "flashnode_conn",
strlen("flashnode_conn")))
continue;
if (sscanf(namelist[i]->d_name, ISCSI_FLASHNODE_SESS,
&hostno, &flashnode_idx) != 2) {
log_error("Invalid iscsi target dir: %s",
namelist[i]->d_name);
break;
}
if (host_no != hostno)
continue;
rc = iscsi_sysfs_get_flashnode_info(fnode, host_no,
flashnode_idx);
if (rc)
break;
rc = fn(data, fnode, host_no, flashnode_idx);
if (rc != 0)
break;
(*nr_found)++;
}
for (i = 0; i < n; i++)
free(namelist[i]);
free(namelist);
free_fnode:
free(fnode);
return rc;
}
static int iscsi_sysfs_read_boot(struct iface_rec *iface, char *session)
{
char boot_root[BOOT_NAME_MAXLEN], boot_nic[BOOT_NAME_MAXLEN];
char boot_name[BOOT_NAME_MAXLEN], boot_content[BOOT_NAME_MAXLEN];
/* Extract boot info */
strlcpy(boot_name, "boot_target", sizeof(boot_name));
if (sysfs_get_str(session, ISCSI_SESSION_SUBSYS, boot_name,
boot_content, BOOT_NAME_MAXLEN))
return -1;
strlcpy(boot_name, "boot_nic", sizeof(boot_name));
if (sysfs_get_str(session, ISCSI_SESSION_SUBSYS, boot_name, boot_nic,
BOOT_NAME_MAXLEN))
return -1;
strlcpy(boot_name, "boot_root", sizeof(boot_name));
if (sysfs_get_str(session, ISCSI_SESSION_SUBSYS, boot_name, boot_root,
BOOT_NAME_MAXLEN))
return -1;
/* If all boot_root/boot_target/boot_nic exist, then extract the
info from the boot nic */
if (sysfs_get_str(boot_nic, boot_root, "vlan", boot_content,
BOOT_NAME_MAXLEN))
log_debug(5, "could not read %s/%s/vlan", boot_root, boot_nic);
else
iface->vlan_id = atoi(boot_content);
if (sysfs_get_str(boot_nic, boot_root, "subnet-mask",
iface->subnet_mask, NI_MAXHOST))
log_debug(5, "could not read %s/%s/subnet", boot_root,
boot_nic);
if (sysfs_get_str(boot_nic, boot_root, "gateway",
iface->gateway, NI_MAXHOST))
log_debug(5, "could not read %s/%s/gateway", boot_root,
boot_nic);
log_debug(5, "sysfs read boot returns %s/%s/ vlan = %d subnet = %s",
boot_root, boot_nic, iface->vlan_id, iface->subnet_mask);
return 0;
}
/*
* Read in iface settings based on host and session values. If
* session is not passed in, then the ifacename will not be set. And
* if the session is not passed in then iname will only be set for
* qla4xxx.
*/
static int iscsi_sysfs_read_iface(struct iface_rec *iface, int host_no,
char *session, char *iface_kern_id)
{
uint32_t tmp_host_no, iface_num;
char host_id[NAME_SIZE];
struct iscsi_transport *t;
int ret, iface_type;
t = iscsi_sysfs_get_transport_by_hba(host_no);
if (!t)
log_debug(7, "could not get transport name for host%d",
host_no);
else
strcpy(iface->transport_name, t->name);
snprintf(host_id, sizeof(host_id), ISCSI_HOST_ID, host_no);
/*
* backward compat
* If we cannot get the address we assume we are doing the old
* style and use default.
*/
ret = sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "hwaddress",
iface->hwaddress, sizeof(iface->hwaddress));
if (ret)
log_debug(7, "could not read hwaddress for host%d", host_no);
if (iface_kern_id)
ret = sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"ipaddress",
iface->ipaddress, sizeof(iface->ipaddress));
else
/* if not found just print out default */
ret = sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "ipaddress",
iface->ipaddress, sizeof(iface->ipaddress));
if (ret)
log_debug(7, "could not read local address for host%d",
host_no);
/* if not found just print out default */
ret = sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "netdev",
iface->netdev, sizeof(iface->netdev));
if (ret)
log_debug(7, "could not read netdev for host%d", host_no);
/*
* For drivers like qla4xxx we can only set the iname at the
* host level because we cannot create different initiator ports
* (cannot set isid either). The LLD also exports the iname at the
* hba level so apps can see it, but we no longer set the iname for
* each iscsid controlled host since bnx2i cxgbi can support multiple
* initiator names and of course software iscsi can support anything.
*/
ret = 1;
memset(iface->iname, 0, sizeof(iface->iname));
if (session) {
ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS,
"initiatorname",
iface->iname, sizeof(iface->iname));
/*
* qlaxxx will not set this at the session level so we
* always drop down for it.
*
* And.
*
* For older kernels/tools (2.6.26 and below and 2.0.870)
* we will not have a session level initiator name, so
* we will drop down.
*/
}
if (ret) {
ret = sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "initiatorname",
iface->iname, sizeof(iface->iname));
if (ret)
/*
* default iname is picked up later from
* initiatorname.iscsi if software/partial-offload.
*
* TODO: we should make it easier to get the
* global iname so we can just fill it in here.
*/
log_debug(7, "Could not read initiatorname for "
"host%d", host_no);
/* optional so do not return error */
ret = 0;
}
sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "port_state",
iface->port_state, sizeof(iface->port_state));
sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "port_speed",
iface->port_speed, sizeof(iface->port_speed));
/*
* this is on the session, because we support multiple bindings
* per device.
*/
memset(iface->name, 0, sizeof(iface->name));
if (session) {
/*
* this was added after 2.0.869 so we could be doing iscsi_tcp
* session binding, but there may not be an ifacename set
* if binding is not used.
*/
ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "ifacename",
iface->name, sizeof(iface->name));
if (ret) {
log_debug(7, "could not read iface name for "
"session %s", session);
/*
* if the ifacename file is not there then we are
* using a older kernel and can try to find the
* binding by the net info which was used on these
* older kernels.
*/
if (iface_get_by_net_binding(iface, iface))
log_debug(7, "Could not find iface for session "
"bound to:" iface_fmt "",
iface_str(iface));
}
}
if (session && t->template->use_boot_info)
iscsi_sysfs_read_boot(iface, session);
if (!iface_kern_id)
goto done;
strlcpy(iface->name, iface_kern_id, sizeof(iface->name));
if (!strncmp(iface_kern_id, "ipv4", 4)) {
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "bootproto",
iface->bootproto, sizeof(iface->bootproto));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "gateway",
iface->gateway, sizeof(iface->gateway));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "subnet",
iface->subnet_mask, sizeof(iface->subnet_mask));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"dhcp_alt_client_id_en",
iface->dhcp_alt_client_id_state,
sizeof(iface->dhcp_alt_client_id_state));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"dhcp_alt_client_id",
iface->dhcp_alt_client_id,
sizeof(iface->dhcp_alt_client_id));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"dhcp_dns_address_en",
iface->dhcp_dns, sizeof(iface->dhcp_dns));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"dhcp_learn_iqn_en",
iface->dhcp_learn_iqn,
sizeof(iface->dhcp_learn_iqn));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"dhcp_req_vendor_id_en",
iface->dhcp_req_vendor_id_state,
sizeof(iface->dhcp_req_vendor_id_state));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"dhcp_use_vendor_id_en",
iface->dhcp_vendor_id_state,
sizeof(iface->dhcp_vendor_id_state));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"dhcp_vendor_id",
iface->dhcp_vendor_id,
sizeof(iface->dhcp_vendor_id));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"dhcp_slp_da_info_en",
iface->dhcp_slp_da, sizeof(iface->dhcp_slp_da));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"fragment_disable",
iface->fragmentation,
sizeof(iface->fragmentation));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"grat_arp_en",
iface->gratuitous_arp,
sizeof(iface->gratuitous_arp));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"incoming_forwarding_en",
iface->incoming_forwarding,
sizeof(iface->incoming_forwarding));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"tos_en",
iface->tos_state, sizeof(iface->tos_state));
if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS,
"tos", &iface->tos))
iface->tos = 0;
if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS,
"ttl", &iface->ttl))
iface->ttl = 0;
} else {
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"ipaddr_autocfg",
iface->ipv6_autocfg, sizeof(iface->ipv6_autocfg));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"link_local_addr", iface->ipv6_linklocal,
sizeof(iface->ipv6_linklocal));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"link_local_autocfg", iface->linklocal_autocfg,
sizeof(iface->linklocal_autocfg));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "router_addr",
iface->ipv6_router,
sizeof(iface->ipv6_router));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "router_state",
iface->router_autocfg,
sizeof(iface->router_autocfg));
if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS,
"dup_addr_detect_cnt",
&iface->dup_addr_detect_cnt))
iface->dup_addr_detect_cnt = 0;
if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS,
"flow_label", &iface->flow_label))
iface->flow_label = 0;
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"grat_neighbor_adv_en",
iface->gratuitous_neighbor_adv,
sizeof(iface->gratuitous_neighbor_adv));
if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS,
"hop_limit", &iface->hop_limit))
iface->hop_limit = 0;
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "mld_en",
iface->mld, sizeof(iface->mld));
if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS,
"nd_reachable_tmo",
&iface->nd_reachable_tmo))
iface->nd_reachable_tmo = 0;
if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS,
"nd_rexmit_time", &iface->nd_rexmit_time))
iface->nd_rexmit_time = 0;
if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS,
"nd_stale_tmo", &iface->nd_stale_tmo))
iface->nd_stale_tmo = 0;
if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS,
"router_adv_link_mtu",
&iface->router_adv_link_mtu))
iface->router_adv_link_mtu = 0;
if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS,
"traffic_class", &iface->traffic_class))
iface->traffic_class = 0;
}
if (sysfs_get_uint16(iface_kern_id, ISCSI_IFACE_SUBSYS, "port",
&iface->port))
iface->port = 0;
if (sysfs_get_uint16(iface_kern_id, ISCSI_IFACE_SUBSYS, "mtu",
&iface->mtu))
iface->mtu = 0;
if (sysfs_get_uint16(iface_kern_id, ISCSI_IFACE_SUBSYS, "vlan_id",
&iface->vlan_id))
iface->vlan_id = UINT16_MAX;
if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, "vlan_priority",
&iface->vlan_priority))
iface->vlan_priority = UINT8_MAX;
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "vlan_enabled",
iface->vlan_state, sizeof(iface->vlan_state));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "enabled",
iface->state, sizeof(iface->state));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "delayed_ack_en",
iface->delayed_ack, sizeof(iface->delayed_ack));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "tcp_nagle_disable",
iface->nagle, sizeof(iface->nagle));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "tcp_wsf_disable",
iface->tcp_wsf_state, sizeof(iface->tcp_wsf_state));
if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, "tcp_wsf",
&iface->tcp_wsf))
iface->tcp_wsf = 0;
if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS,
"tcp_timer_scale", &iface->tcp_timer_scale))
iface->tcp_timer_scale = 0;
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "tcp_timestamp_en",
iface->tcp_timestamp, sizeof(iface->tcp_timestamp));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "redirect_en",
iface->redirect, sizeof(iface->redirect));
if (sysfs_get_uint16(iface_kern_id, ISCSI_IFACE_SUBSYS,
"def_taskmgmt_tmo", &iface->def_task_mgmt_tmo))
iface->def_task_mgmt_tmo = 0;
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "header_digest",
iface->header_digest, sizeof(iface->header_digest));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "data_digest",
iface->data_digest, sizeof(iface->data_digest));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "immediate_data",
iface->immediate_data, sizeof(iface->immediate_data));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "initial_r2t",
iface->initial_r2t, sizeof(iface->initial_r2t));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "data_seq_in_order",
iface->data_seq_inorder, sizeof(iface->data_seq_inorder));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "data_pdu_in_order",
iface->data_pdu_inorder, sizeof(iface->data_pdu_inorder));
if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, "erl",
&iface->erl))
iface->erl = 0;
if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS,
"max_recv_dlength", &iface->max_recv_dlength))
iface->max_recv_dlength = 0;
if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS,
"first_burst_len", &iface->first_burst_len))
iface->first_burst_len = 0;
if (sysfs_get_uint16(iface_kern_id, ISCSI_IFACE_SUBSYS,
"max_outstanding_r2t", &iface->max_out_r2t))
iface->max_out_r2t = 0;
if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS,
"max_burst_len", &iface->max_burst_len))
iface->max_burst_len = 0;
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "chap_auth",
iface->chap_auth, sizeof(iface->chap_auth));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "bidi_chap",
iface->bidi_chap, sizeof(iface->bidi_chap));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "strict_login_comp_en",
iface->strict_login_comp,
sizeof(iface->strict_login_comp));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS,
"discovery_auth_optional",
iface->discovery_auth, sizeof(iface->discovery_auth));
sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "discovery_logout",
iface->discovery_logout, sizeof(iface->discovery_logout));
if (sscanf(iface_kern_id, "ipv%d-iface-%u-%u", &iface_type,
&tmp_host_no, &iface_num) == 3)
iface->iface_num = iface_num;
done:
if (ret)
return ISCSI_ERR_SYSFS_LOOKUP;
else
return 0;
}
int iscsi_sysfs_get_hostinfo_by_host_no(struct host_info *hinfo)
{
return iscsi_sysfs_read_iface(&hinfo->iface, hinfo->host_no, NULL,
NULL);
}
int iscsi_sysfs_for_each_host(void *data, int *nr_found,
iscsi_sysfs_host_op_fn *fn)
{
struct dirent **namelist;
int rc = 0, i, n;
struct host_info *info;
info = malloc(sizeof(*info));
if (!info)
return ISCSI_ERR_NOMEM;
n = scandir(ISCSI_HOST_DIR, &namelist, trans_filter,
alphasort);
if (n <= 0)
goto free_info;
for (i = 0; i < n; i++) {
memset(info, 0, sizeof(*info));
if (sscanf(namelist[i]->d_name, "host%u", &info->host_no) !=
1) {
log_error("Invalid iscsi host dir: %s",
namelist[i]->d_name);
break;
}
iscsi_sysfs_get_hostinfo_by_host_no(info);
rc = fn(data, info);
if (rc != 0)
break;
(*nr_found)++;
}
for (i = 0; i < n; i++)
free(namelist[i]);
free(namelist);
free_info:
free(info);
return rc;
}
const char *iscsi_sysfs_get_session_path(void)
{
return ISCSI_SESSION_DIR;
}
int iscsi_sysfs_for_each_iface_on_host(void *data, uint32_t host_no,
int *nr_found,
iscsi_sysfs_iface_op_fn *fn)
{
struct dirent **namelist;
int rc = 0, i, n;
struct iface_rec iface;
char devpath[PATH_SIZE];
char sysfs_dev_iscsi_iface_path[PATH_SIZE];
char id[NAME_SIZE];
snprintf(id, sizeof(id), "host%u", host_no);
if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath),
SCSI_SUBSYS, id)) {
log_error("Could not look up host's ifaces via scsi bus.");
return ISCSI_ERR_SYSFS_LOOKUP;
}
sprintf(sysfs_dev_iscsi_iface_path, "/sys");
strlcat(sysfs_dev_iscsi_iface_path, devpath, sizeof(sysfs_dev_iscsi_iface_path));
strlcat(sysfs_dev_iscsi_iface_path, "/iscsi_iface", sizeof(sysfs_dev_iscsi_iface_path));
n = scandir(sysfs_dev_iscsi_iface_path, &namelist, trans_filter, alphasort);
if (n <= 0)
/* older kernels or some drivers will not have ifaces */
return 0;
for (i = 0; i < n; i++) {
memset(&iface, 0, sizeof(iface));
iscsi_sysfs_read_iface(&iface, host_no, NULL,
namelist[i]->d_name);
rc = fn(data, &iface);
if (rc != 0)
break;
(*nr_found)++;
}
for (i = 0; i < n; i++)
free(namelist[i]);
free(namelist);
return rc;
}
/**
* sysfs_session_has_leadconn - checks if session has lead conn in kernel
* @sid: session id
*
* return 1 if session has lead conn and 0 if not.
*/
int iscsi_sysfs_session_has_leadconn(uint32_t sid)
{
char devpath[PATH_SIZE];
char id[NAME_SIZE];
snprintf(id, sizeof(id), "connection%u:0", sid);
return sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath),
ISCSI_CONN_SUBSYS, id);
}
/*
* iscsi_sysfs_get_sid_from_path - parse a string for the sid
* @session: session path
*
* Given sysfs_device is a directory name of the form:
*
* /sys/devices/platform/hostH/sessionS/targetH:B:I/H:B:I:L
* /sys/devices/platform/hostH/sessionS/targetH:B:I
* /sys/devices/platform/hostH/sessionS
*
* return the sid S. If just the sid is passed in it will be converted
* to an int.
*/
int iscsi_sysfs_get_sid_from_path(char *session)
{
struct sysfs_device *dev_parent, *dev;
struct stat statb;
char devpath[PATH_SIZE];
char *end;
int sid;
sid = strtol(session, &end, 10);
if (sid > 0 && *session != '\0' && *end == '\0')
return sid;
if (lstat(session, &statb)) {
log_error("%s is an invalid session ID or path", session);
exit(1);
}
if (!S_ISDIR(statb.st_mode) && !S_ISLNK(statb.st_mode)) {
log_error("%s is not a directory", session);
exit(1);
}
if (!strncmp(session, "/sys", 4))
strlcpy(devpath, session + 4, sizeof(devpath));
else
strlcpy(devpath, session, sizeof(devpath));
dev = sysfs_device_get(devpath);
if (!dev) {
log_error("Could not get dev for %s. Possible sysfs "
"incompatibility.", devpath);
return -1;
}
if (!strncmp(dev->kernel, "session", 7))
return atoi(dev->kernel_number);
dev_parent = sysfs_device_get_parent(dev);
while (dev_parent != NULL) {
if (strncmp(dev_parent->kernel, "session", 7) == 0)
return atoi(dev_parent->kernel_number);
dev_parent = sysfs_device_get_parent(dev_parent);
}
log_error("Unable to find sid in path %s", session);
return -1;
}
int iscsi_sysfs_get_sessioninfo_by_id(struct session_info *info, char *session)
{
char id[NAME_SIZE];
int ret, pers_failed = 0;
uint32_t host_no;
if (sscanf(session, "session%d", &info->sid) != 1) {
log_error("invalid session '%s'", session);
return ISCSI_ERR_INVAL;
}
ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "targetname",
info->targetname, sizeof(info->targetname));
if (ret) {
log_error("could not read session targetname: %d", ret);
return ISCSI_ERR_SYSFS_LOOKUP;
}
ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "username",
(info->chap).username,
sizeof((info->chap).username));
if (ret)
log_debug(5, "could not read username: %d", ret);
ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "password",
(info->chap).password,
sizeof((info->chap).password));
if (ret)
log_debug(5, "could not read password: %d", ret);
ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "username_in",
(info->chap).username_in,
sizeof((info->chap).username_in));
if (ret)
log_debug(5, "could not read username in: %d", ret);
ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "password_in",
(info->chap).password_in,
sizeof((info->chap).password_in));
if (ret)
log_debug(5, "could not read password in: %d", ret);
ret = sysfs_get_int(session, ISCSI_SESSION_SUBSYS, "recovery_tmo",
&((info->tmo).recovery_tmo));
if (ret)
(info->tmo).recovery_tmo = -1;
ret = sysfs_get_int(session, ISCSI_SESSION_SUBSYS, "lu_reset_tmo",
&((info->tmo).lu_reset_tmo));
if (ret)
(info->tmo).lu_reset_tmo = -1;
ret = sysfs_get_int(session, ISCSI_SESSION_SUBSYS, "tgt_reset_tmo",
&((info->tmo).tgt_reset_tmo));
if (ret)
(info->tmo).lu_reset_tmo = -1;
sysfs_get_int(session, ISCSI_SESSION_SUBSYS, "abort_tmo",
&((info->tmo).abort_tmo));
if (ret)
(info->tmo).abort_tmo = -1;
ret = sysfs_get_int(session, ISCSI_SESSION_SUBSYS, "tpgt",
&info->tpgt);
if (ret) {
log_error("could not read session tpgt: %d", ret);
return ISCSI_ERR_SYSFS_LOOKUP;
}
snprintf(id, sizeof(id), ISCSI_CONN_ID, info->sid);
/* some HW drivers do not export addr and port */
memset(info->persistent_address, 0, NI_MAXHOST);
ret = sysfs_get_str(id, ISCSI_CONN_SUBSYS, "persistent_address",
info->persistent_address,
sizeof(info->persistent_address));
if (ret) {
pers_failed = 1;
/* older qlogic does not support this */
log_debug(5, "could not read pers conn addr: %d", ret);
}
memset(info->address, 0, NI_MAXHOST);
ret = sysfs_get_str(id, ISCSI_CONN_SUBSYS, "address",
info->address, sizeof(info->address));
if (ret) {
log_debug(5, "could not read curr addr: %d", ret);
/* iser did not export this */
if (!pers_failed)
strcpy(info->address, info->persistent_address);
} else if (pers_failed)
/*
* for qla if we could not get the persistent addr
* we will use the current for both addrs
*/
strcpy(info->persistent_address, info->address);
pers_failed = 0;
info->persistent_port = -1;
ret = sysfs_get_int(id, ISCSI_CONN_SUBSYS, "persistent_port",
&info->persistent_port);
if (ret) {
pers_failed = 1;
log_debug(5, "Could not read pers conn port %d", ret);
}
info->port = -1;
ret = sysfs_get_int(id, ISCSI_CONN_SUBSYS, "port", &info->port);
if (ret) {
/* iser did not export this */
if (!pers_failed)
info->port = info->persistent_port;
log_debug(5, "Could not read curr conn port %d", ret);
} else if (pers_failed)
/*
* for qla if we could not get the persistent addr
* we will use the current for both addrs
*/
info->persistent_port = info->port;
ret = 0;
host_no = iscsi_sysfs_get_host_no_from_sid(info->sid, &ret);
if (ret) {
log_error("could not get host_no for session%d: %s.",
info->sid, iscsi_err_to_str(ret));
return ret;
}
iscsi_sysfs_read_iface(&info->iface, host_no, session, NULL);
log_debug(7, "found targetname %s address %s pers address %s port %d "
"pers port %d driver %s iface name %s ipaddress %s "
"netdev %s hwaddress %s iname %s",
info->targetname, info->address ? info->address : "NA",
info->persistent_address ? info->persistent_address : "NA",
info->port, info->persistent_port, info->iface.transport_name,
info->iface.name, info->iface.ipaddress,
info->iface.netdev, info->iface.hwaddress,
info->iface.iname);
return 0;
}
int iscsi_sysfs_for_each_session(void *data, int *nr_found,
iscsi_sysfs_session_op_fn *fn,
int in_parallel)
{
struct dirent **namelist;
int rc = 0, n, i, chldrc = 0;
struct session_info *info;
pid_t pid = 0;
info = calloc(1, sizeof(*info));
if (!info)
return ISCSI_ERR_NOMEM;
info->iscsid_req_tmo = -1;
n = scandir(ISCSI_SESSION_DIR, &namelist, trans_filter,
alphasort);
if (n <= 0)
goto free_info;
for (i = 0; i < n; i++) {
rc = iscsi_sysfs_get_sessioninfo_by_id(info,
namelist[i]->d_name);
if (rc) {
log_error("could not find session info for %s",
namelist[i]->d_name);
/* raced. session was shutdown while looping */
rc = 0;
continue;
}
if (in_parallel) {
pid = fork();
}
if (pid == 0) {
rc = fn(data, info);
if (in_parallel) {
exit(rc);
} else {
if (rc > 0) {
break;
} else if (rc == 0) {
(*nr_found)++;
} else {
/* if less than zero it means it was not a match */
rc = 0;
}
}
} else if (pid < 0) {
log_error("could not fork() for session %s, err %d",
namelist[i]->d_name, errno);
}
}
if (in_parallel) {
while (1) {
if (wait(&chldrc) < 0) {
/*
* ECHILD means no more children which is
* expected to happen sooner or later.
*/
if (errno != ECHILD) {
rc = errno;
}
break;
}
if (!WIFEXITED(chldrc)) {
/*
* abnormal termination (signal, exception, etc.)
*
* The non-parallel code path returns the first
* error so this keeps the same semantics.
*/
if (rc == 0)
rc = ISCSI_ERR_CHILD_TERMINATED;
} else if ((WEXITSTATUS(chldrc) != 0) &&
(WEXITSTATUS(chldrc) != 255)) {
/*
* 0 is success
* 255 is a truncated return code from exit(-1)
* and means no match
* anything else (this case) is an error
*/
if (rc == 0)
rc = WEXITSTATUS(chldrc);
} else if (WEXITSTATUS(chldrc) == 0) {
/* success */
(*nr_found)++;
}
}
}
for (i = 0; i < n; i++)
free(namelist[i]);
free(namelist);
free_info:
free(info);
return rc;
}
/*
* count the number of sessions -- a much-simplified
* version of iscsi_sysfs_for_each_session
*
* TODO: return an array of the session info we find, for use
* by iscsi_sysfs_for_each_session(), so it doesn't have to
* do it all over again
*/
int iscsi_sysfs_count_sessions(void)
{
struct dirent **namelist = NULL;
int n, i;
struct session_info *info;
info = calloc(1, sizeof(*info));
if (!info)
/* no sessions found */
return 0;
info->iscsid_req_tmo = -1;
n = scandir(ISCSI_SESSION_DIR, &namelist, trans_filter, alphasort);
if (n <= 0)
/* no sessions found */
goto free_info;
/*
* try to get session info for each session found, but ignore
* errors if any since it may be a race condition
*/
for (i = 0; i < n; i++)
if (iscsi_sysfs_get_sessioninfo_by_id(info,
namelist[i]->d_name) != 0)
log_warning("could not find session info for %s",
namelist[i]->d_name);
for (i = 0; i < n; i++)
free(namelist[i]);
free(namelist);
free_info:
free(info);
return n;
}
int iscsi_sysfs_get_session_state(char *state, int sid)
{
char id[NAME_SIZE];
snprintf(id, sizeof(id), ISCSI_SESSION_ID, sid);
if (sysfs_get_str(id, ISCSI_SESSION_SUBSYS, "state", state,
SCSI_MAX_STATE_VALUE))
return ISCSI_ERR_SYSFS_LOOKUP;
return 0;
}
int iscsi_sysfs_get_host_state(char *state, int host_no)
{
char id[NAME_SIZE];
snprintf(id, sizeof(id), ISCSI_HOST_ID, host_no);
if (sysfs_get_str(id, SCSI_HOST_SUBSYS, "state", state,
SCSI_MAX_STATE_VALUE))
return ISCSI_ERR_SYSFS_LOOKUP;
return 0;
}
int iscsi_sysfs_get_device_state(char *state, int host_no, int target, int lun)
{
char id[NAME_SIZE];
snprintf(id, sizeof(id), "%d:0:%d:%d", host_no, target, lun);
if (sysfs_get_str(id, SCSI_SUBSYS, "state", state,
SCSI_MAX_STATE_VALUE)) {
log_debug(3, "Could not read attr state for %s", id);
return ISCSI_ERR_SYSFS_LOOKUP;
}
return 0;
}
char *iscsi_sysfs_get_blockdev_from_lun(int host_no, int target, int lun)
{
char devpath[PATH_SIZE];
char path_full[PATH_SIZE];
char id[NAME_SIZE];
DIR *dirfd;
struct dirent *dent;
size_t sysfs_len;
struct stat statbuf;
char *blockdev, *blockdup = NULL;
snprintf(id, sizeof(id), "%d:0:%d:%d", host_no, target, lun);
if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath),
SCSI_SUBSYS, id)) {
log_debug(3, "Could not lookup devpath for %s %s",
SCSI_SUBSYS, id);
return NULL;
}
sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full));
if (sysfs_len >= sizeof(path_full))
sysfs_len = sizeof(path_full) - 1;
strlcat(path_full, devpath, sizeof(path_full));
dirfd = opendir(path_full);
if (!dirfd)
return NULL;
while ((dent = readdir(dirfd))) {
if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
continue;
/* not sure what tape looks like */
if (strncmp(dent->d_name, "block:", 5))
continue;
strlcat(path_full, "/", sizeof(path_full));
strlcat(path_full, dent->d_name, sizeof(path_full));
/*
* 2.6.25 dropped the symlink and now block is a dir.
*/
if (lstat(path_full, &statbuf)) {
log_error("Could not stat block path %s err %d",
path_full, errno);
break;
}
if (S_ISLNK(statbuf.st_mode)) {
blockdev = strchr(dent->d_name, ':');
if (!blockdev)
break;
/* increment past colon */
blockdev++;
blockdup = strdup(blockdev);
} else if (S_ISDIR(statbuf.st_mode)) {
DIR *blk_dirfd;
struct dirent *blk_dent;
/* it should not be this hard should it? :) */
blk_dirfd = opendir(path_full);
if (!blk_dirfd) {
log_debug(3, "Could not open blk path %s",
path_full);
break;
}
while ((blk_dent = readdir(blk_dirfd))) {
if (!strcmp(blk_dent->d_name, ".") ||
!strcmp(blk_dent->d_name, ".."))
continue;
blockdup = strdup(blk_dent->d_name);
break;
}
closedir(blk_dirfd);
}
break;
}
closedir(dirfd);
return blockdup;
}
static uint32_t get_target_no_from_sid(uint32_t sid, int *err)
{
char devpath[PATH_SIZE];
char path_full[PATH_SIZE];
char id[NAME_SIZE];
DIR *dirfd;
struct dirent *dent;
uint32_t host, bus, target = 0;
size_t sysfs_len;
*err = ISCSI_ERR_SESS_NOT_FOUND;
snprintf(id, sizeof(id), "session%u", sid);
if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath),
ISCSI_SESSION_SUBSYS, id)) {
log_debug(3, "Could not lookup devpath for %s %s",
ISCSI_SESSION_SUBSYS, id);
return 0;
}
/*
* This does not seem safe from future changes, but we currently
* want /devices/platform/hostY/sessionX, but we come from the
* /class/iscsi_session/sessionX/device.
*/
sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full));
if (sysfs_len >= sizeof(path_full))
sysfs_len = sizeof(path_full) - 1;
strlcat(path_full, devpath, sizeof(path_full));
strlcat(path_full, "/device", sizeof(devpath));
dirfd = opendir(path_full);
if (!dirfd)
return 0;
while ((dent = readdir(dirfd))) {
if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
continue;
if (strncmp(dent->d_name, "target", 6))
continue;
if (sscanf(dent->d_name, "target%u:%u:%u",
&host, &bus, &target) != 3)
break;
*err = 0;
break;
}
closedir(dirfd);
return target;
}
int iscsi_sysfs_is_transport_loaded(char *transport_name)
{
struct iscsi_transport *t;
/* sync up kernel and userspace */
read_transports();
/* check if the transport is loaded and matches */
list_for_each_entry(t, &transports, list) {
if (t->handle && !strncmp(t->name, transport_name,
ISCSI_TRANSPORT_NAME_MAXLEN))
return 1;
}
return 0;
}
struct iscsi_transport *iscsi_sysfs_get_transport_by_name(char *transport_name)
{
struct iscsi_transport *t;
int retry = 0;
retry:
/* sync up kernel and userspace */
read_transports();
/* check if the transport is loaded and matches */
list_for_each_entry(t, &transports, list) {
if (t->handle && !strncmp(t->name, transport_name,
ISCSI_TRANSPORT_NAME_MAXLEN))
return t;
}
if (retry < 1) {
retry++;
if (!transport_load_kmod(transport_name))
goto retry;
}
return NULL;
}
/* TODO: replace the following functions with some decent sysfs links */
struct iscsi_transport *iscsi_sysfs_get_transport_by_hba(uint32_t host_no)
{
char name[ISCSI_TRANSPORT_NAME_MAXLEN];
char id[NAME_SIZE];
int rc;
if (host_no == -1)
return NULL;
snprintf(id, sizeof(id), ISCSI_HOST_ID, host_no);
rc = sysfs_get_str(id, SCSI_HOST_SUBSYS, "proc_name", name,
ISCSI_TRANSPORT_NAME_MAXLEN);
if (rc) {
log_error("Could not read proc_name for host%u rc %d.",
host_no, rc);
return NULL;
}
/*
* stupid, stupid. We named the transports tcp or iser, but the
* the modules are named iscsi_tcp and iscsi_iser
*/
if (strstr(name, "iscsi_"))
return iscsi_sysfs_get_transport_by_name(name + 6);
else
return iscsi_sysfs_get_transport_by_name(name);
}
struct iscsi_transport *iscsi_sysfs_get_transport_by_sid(uint32_t sid)
{
uint32_t host_no;
int err;
host_no = iscsi_sysfs_get_host_no_from_sid(sid, &err);
if (err)
return NULL;
return iscsi_sysfs_get_transport_by_hba(host_no);
}
/*
* For connection reinstatement we need to send the exp_statsn from
* the previous connection
*
* This is only called when the connection is halted so exp_statsn is safe
* to read without racing.
*/
int iscsi_sysfs_get_exp_statsn(int sid)
{
char id[NAME_SIZE];
uint32_t exp_statsn = 0;
snprintf(id, sizeof(id), ISCSI_CONN_ID, sid);
if (sysfs_get_uint(id, ISCSI_CONN_SUBSYS, "exp_statsn",
&exp_statsn)) {
log_error("Could not read expstatsn for sid %d. "
"Using zero for exp_statsn.", sid);
exp_statsn = 0;
}
return exp_statsn;
}
int iscsi_sysfs_session_supports_nop(int sid)
{
char id[NAME_SIZE];
uint32_t ping_tmo = 0;
snprintf(id, sizeof(id), ISCSI_CONN_ID, sid);
if (sysfs_get_uint(id, ISCSI_CONN_SUBSYS, "ping_tmo",
&ping_tmo)) {
return 0;
}
return 1;
}
int iscsi_sysfs_for_each_device(void *data, int host_no, uint32_t sid,
void (* fn)(void *data, int host_no,
int target, int lun))
{
struct dirent **namelist;
int h, b, t, l, i, n, err = 0, target;
char devpath[PATH_SIZE];
char id[NAME_SIZE];
char path_full[3*PATH_SIZE];
target = get_target_no_from_sid(sid, &err);
if (err)
return err;
snprintf(id, sizeof(id), "session%u", sid);
if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath),
ISCSI_SESSION_SUBSYS, id)) {
log_debug(3, "Could not lookup devpath for %s %s",
ISCSI_SESSION_SUBSYS, id);
return ISCSI_ERR_SYSFS_LOOKUP;
}
snprintf(path_full, sizeof(path_full), "%s%s/device/target%d:0:%d",
sysfs_path, devpath, host_no, target);
if (strlen(path_full) > PATH_SIZE) {
log_debug(3, "Could not lookup devpath for %s %s (too long)",
ISCSI_SESSION_SUBSYS, id);
return ISCSI_ERR_SYSFS_LOOKUP;
}
n = scandir(path_full, &namelist, trans_filter,
alphasort);
if (n <= 0)
return 0;
for (i = 0; i < n; i++) {
if (sscanf(namelist[i]->d_name, "%d:%d:%d:%d\n",
&h, &b, &t, &l) != 4)
continue;
fn(data, h, t, l);
}
for (i = 0; i < n; i++)
free(namelist[i]);
free(namelist);
return 0;
}
void iscsi_sysfs_set_queue_depth(void *data, int hostno, int target, int lun)
{
char id[NAME_SIZE];
char write_buf[20];
int err, qdepth = *((int *)data);
snprintf(id, sizeof(id), "%d:0:%d:%d", hostno, target, lun);
snprintf(write_buf, sizeof(write_buf), "%d", qdepth);
log_debug(4, "set queue depth for %s to %s", id, write_buf);
err = sysfs_set_param(id, SCSI_SUBSYS, "queue_depth", write_buf,
strlen(write_buf));
if (err && err != EINVAL)
log_error("Could not queue depth for LUN %d err %d.", lun, err);
}
void iscsi_sysfs_set_device_online(void *data, int hostno, int target, int lun)
{
char *write_buf = "running\n";
char id[NAME_SIZE];
int err;
snprintf(id, sizeof(id), "%d:0:%d:%d", hostno, target, lun);
log_debug(4, "online device %s", id);
err = sysfs_set_param(id, SCSI_SUBSYS, "state", write_buf,
strlen(write_buf));
if (err && err != EINVAL)
/* we should read the state */
log_error("Could not online LUN %d err %d.", lun, err);
}
void iscsi_sysfs_rescan_device(void *data, int hostno, int target, int lun)
{
char *write_buf = "1";
char id[NAME_SIZE];
snprintf(id, sizeof(id), "%d:0:%d:%d", hostno, target, lun);
log_debug(4, "rescanning device %s", id);
sysfs_set_param(id, SCSI_SUBSYS, "rescan", write_buf,
strlen(write_buf));
}
pid_t iscsi_sysfs_scan_host(int hostno, int async, int autoscan)
{
char id[NAME_SIZE];
char *write_buf = "- - -";
pid_t pid = 0;
if (async)
pid = fork();
if (pid >= 0 && !autoscan) {
if (pid)
log_debug(4, "host%d in manual scan mode, skipping scan", hostno);
} else if (pid == 0) {
/* child */
log_debug(4, "scanning host%d", hostno);
snprintf(id, sizeof(id), ISCSI_HOST_ID, hostno);
sysfs_set_param(id, SCSI_HOST_SUBSYS, "scan", write_buf,
strlen(write_buf));
log_debug(4, "scanning host%d completed", hostno);
} else if (pid > 0) {
log_debug(4, "scanning host%d from pid %d", hostno, pid);
} else
/*
* Session is fine, so log the error and let the user
* scan by hand
*/
log_error("Could not start scanning process for host %d "
"err %d. Try scanning through sysfs.", hostno,
errno);
return pid;
}
struct iscsi_transport *iscsi_sysfs_get_transport_by_session(char *sys_session)
{
uint32_t sid;
if (sscanf(sys_session, "session%u", &sid) != 1) {
log_error("invalid session '%s'.", sys_session);
return NULL;
}
return iscsi_sysfs_get_transport_by_sid(sid);
}
char *iscsi_sysfs_get_iscsi_kernel_version(void)
{
return sysfs_attr_get_value("/module/scsi_transport_iscsi", "version");
}