/* * Copyright (C) IBM Corporation. 2007 * Author: Konrad Rzeszutek * Copyright (C) Red Hat, Inc. All rights reserved. 2008 - 2010 * Copyright (C) Mike Christie 2008 - 2010 * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _XOPEN_SOURCE 500 #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #include #include "sysfs.h" #include "fw_context.h" #include "fwparam.h" #include "sysdeps.h" #include "iscsi_net_util.h" #include "iscsi_err.h" #define ISCSI_BOOT_MAX 255 #define IBFT_SYSFS_ROOT "/sys/firmware/ibft/" #define IBFT_SUBSYS "ibft" #define ISCSI_LLD_ROOT "/sys/firmware/" #define ISCSI_LLD_SUBSYS_PREFIX "iscsi_boot" static char *target_list[ISCSI_BOOT_MAX]; static char *nic_list[ISCSI_BOOT_MAX]; static int nic_cnt; static int tgt_cnt; static int file_exist(const char *file) { struct stat bootpath_stat; return !stat(file, &bootpath_stat); } /* * Finds the etherrnetX and targetX under the sysfs directory. */ static int find_sysfs_dirs(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftw) { if (tflag == FTW_D && (strstr(fpath + ftw->base, "target"))) { if (tgt_cnt == ISCSI_BOOT_MAX) { printf("Too many targets found in iSCSI boot data." "Max number of targets %d\n", ISCSI_BOOT_MAX); return 0; } target_list[tgt_cnt++] = strdup(strstr(fpath, "target")); } if (tflag == FTW_D && (strstr(fpath + ftw->base, "ethernet"))) { if (nic_cnt == ISCSI_BOOT_MAX) { printf("Too many nics found in iSCSI boot data." "Max number of nics %d\n", ISCSI_BOOT_MAX); return 0; } nic_list[nic_cnt++] = strdup(strstr(fpath, "ethernet")); } return 0; } static int get_iface_from_device(char *id, struct boot_context *context) { char dev_dir[FILENAMESZ]; int rc = ENODEV; DIR *dirfd; struct dirent *dent; memset(dev_dir, 0, FILENAMESZ); snprintf(dev_dir, FILENAMESZ, IBFT_SYSFS_ROOT"/%s/device", id); if (!file_exist(dev_dir)) return 0; dirfd = opendir(dev_dir); if (!dirfd) return errno; while ((dent = readdir(dirfd))) { if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..") || strncmp(dent->d_name, "net:", 4)) continue; if (!strncmp(dent->d_name, "net:", 4)) { if ((strlen(dent->d_name) - 4) > (sizeof(context->iface) - 1)) { rc = EINVAL; printf("Net device %s too big for iface " "buffer.\n", dent->d_name); break; } if (sscanf(dent->d_name, "net:%s", context->iface) != 1) rc = EINVAL; rc = 0; break; } else { printf("Could not read ethernet to net link.\n"); rc = EOPNOTSUPP; break; } } closedir(dirfd); if (rc != ENODEV) return rc; /* If not found try again with newer kernel networkdev sysfs layout */ strlcat(dev_dir, "/net", FILENAMESZ); if (!file_exist(dev_dir)) return rc; dirfd = opendir(dev_dir); if (!dirfd) return errno; while ((dent = readdir(dirfd))) { if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) continue; /* Take the first "regular" directory entry */ if (strlen(dent->d_name) > (sizeof(context->iface) - 1)) { rc = EINVAL; printf("Net device %s too big for iface buffer.\n", dent->d_name); break; } strcpy(context->iface, dent->d_name); rc = 0; break; } closedir(dirfd); return rc; } /* * Routines to fill in the context values. */ static int fill_nic_context(char *subsys, char *id, struct boot_context *context) { int rc; rc = sysfs_get_int(id, subsys, "flags", &context->nic_flags); /* * Per spec we would need to check against Bit 0 * (Block Valid Flag), but some firmware only * sets Bit 1 (Firmware Booting Selected). * So any setting is deemed okay. */ if (!rc && (context->nic_flags == 0)) rc = ENODEV; if (rc) return rc; rc = sysfs_get_str(id, subsys, "mac", context->mac, sizeof(context->mac)); if (rc) return rc; /* * Some offload cards like bnx2i use different MACs for the net and * iscsi functions, so we have to follow the sysfs links. * * Other ibft implementations may not be tied to a pci function, * so there will not be any device/net link, so we drop down to * the MAC matching. * * And finally, some cards like be2iscsi and qla4xxx do not have * any linux network subsys representation. These hosts will * not have the ibft subsys. Instead the subsys is the scsi host * number. */ if (!strcmp(IBFT_SUBSYS, subsys)) { rc = get_iface_from_device(id, context); if (rc) { rc = net_get_netdev_from_hwaddress(context->mac, context->iface); if (rc) return rc; } } else strlcpy(context->scsi_host_name, subsys, sizeof(context->scsi_host_name)); memset(&context->boot_nic, 0, sizeof(context->boot_nic)); snprintf(context->boot_nic, sizeof(context->boot_nic), "%s", id); sysfs_get_str(id, subsys, "ip-addr", context->ipaddr, sizeof(context->ipaddr)); sysfs_get_str(id, subsys, "vlan", context->vlan, sizeof(context->vlan)); sysfs_get_str(id, subsys, "subnet-mask", context->mask, sizeof(context->mask)); sysfs_get_int(id, subsys, "prefix-len", &context->prefix); sysfs_get_str(id, subsys, "gateway", context->gateway, sizeof(context->gateway)); sysfs_get_str(id, subsys, "primary-dns", context->primary_dns, sizeof(context->primary_dns)); sysfs_get_str(id, subsys, "secondary-dns", context->secondary_dns, sizeof(context->secondary_dns)); sysfs_get_str(id, subsys, "dhcp", context->dhcp, sizeof(context->dhcp)); sysfs_get_int(id, subsys, "origin", (int *)&context->origin); return 0; } static void fill_initiator_context(char *subsys, struct boot_context *context) { sysfs_get_str("initiator", subsys, "initiator-name", context->initiatorname, sizeof(context->initiatorname)); sysfs_get_str("initiator", subsys, "isid", context->isid, sizeof(context->isid)); strlcpy(context->boot_root, subsys, sizeof(context->boot_root)); } static int fill_tgt_context(char *subsys, char *id, struct boot_context *context) { int rc; rc = sysfs_get_int(id, subsys, "flags", &context->target_flags); /* * Per spec we would need to check against Bit 0 * (Block Valid Flag), but some firmware only * sets Bit 1 (Firmware Booting Selected). * So any setting is deemed okay. */ if (!rc && (context->target_flags == 0)) rc = ENODEV; if (rc) return rc; rc = sysfs_get_str(id, subsys, "target-name", context->targetname, sizeof(context->targetname)); if (rc) return rc; rc = sysfs_get_str(id, subsys, "ip-addr", context->target_ipaddr, sizeof(context->target_ipaddr)); if (rc) return rc; memset(&context->boot_target, 0, sizeof(context->boot_target)); snprintf(context->boot_target, sizeof(context->boot_target), "%s", id); /* * We can live without the rest of they do not exist. If we * failed to get them we will figure it out when we login. */ if (sysfs_get_int(id, subsys, "port", &context->target_port)) context->target_port = ISCSI_LISTEN_PORT; sysfs_get_str(id, subsys, "lun", context->lun, sizeof(context->lun)); sysfs_get_str(id, subsys, "chap-name", context->chap_name, sizeof(context->chap_name)); sysfs_get_str(id, subsys, "chap-secret", context->chap_password, sizeof(context->chap_password)); sysfs_get_str(id, subsys, "rev-chap-name", context->chap_name_in, sizeof(context->chap_name_in)); sysfs_get_str(id, subsys, "rev-chap-name-secret", context->chap_password_in, sizeof(context->chap_password_in)); return 0; } #define IBFT_SYSFS_FLAG_FW_SEL_BOOT 2 static int find_boot_flag(char *subsys, char *list[], ssize_t size, int *boot_idx) { int rc = ENODEV; int i, flag = 0; for (i = 0; i < size; i++, flag = -1) { rc = sysfs_get_int(list[i], subsys, "flags", &flag); if (rc) continue; if (flag & IBFT_SYSFS_FLAG_FW_SEL_BOOT) { *boot_idx = i; rc = 0; break; } rc = ENODEV; flag = 0; } return rc; } static void deallocate_lists(void) { int i; for (i = 0; i < nic_cnt; i++) free(nic_list[i]); nic_cnt = 0; for (i = 0; i < tgt_cnt; i++) free(target_list[i]); tgt_cnt = 0; } static int get_boot_info(struct boot_context *context, char *rootdir, char *subsys) { char initiator_dir[FILENAMESZ]; int rc = ENODEV; int nic_idx = -1, tgt_idx = -1; memset(&initiator_dir, 0 , FILENAMESZ); snprintf(initiator_dir, FILENAMESZ, "%sinitiator", rootdir); nic_cnt = 0; tgt_cnt = 0; if (file_exist(initiator_dir)) { /* Find the targets and the ethernets */ rc = nftw(rootdir, find_sysfs_dirs, 20, 1); /* Find wihch target and which ethernet have the boot flag set. */ rc = find_boot_flag(subsys, nic_list, nic_cnt, &nic_idx); if (rc) goto free; rc = find_boot_flag(subsys, target_list, tgt_cnt, &tgt_idx); if (rc) goto free; /* Fill in the context values */ rc = fill_nic_context(subsys, nic_list[nic_idx], context); rc |= fill_tgt_context(subsys, target_list[tgt_idx], context); fill_initiator_context(subsys, context); } free: deallocate_lists(); return rc; } int fwparam_sysfs_boot_info(struct boot_context *context) { struct dirent *dent; DIR *dirfd; int rc = 0; if (!get_boot_info(context, IBFT_SYSFS_ROOT, IBFT_SUBSYS)) return 0; /* * We could have multiple iscsi llds and each lld could have * multiple targets/ethernet ports */ dirfd = opendir(ISCSI_LLD_ROOT); if (!dirfd) return ISCSI_ERR_SYSFS_LOOKUP; while ((dent = readdir(dirfd))) { char lld_root[FILENAMESZ]; memset(&lld_root, 0 , FILENAMESZ); if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) continue; if (strncmp(dent->d_name, ISCSI_LLD_SUBSYS_PREFIX, 10)) continue; snprintf(lld_root, FILENAMESZ, ISCSI_LLD_ROOT"%s/", dent->d_name); if (!get_boot_info(context, lld_root, dent->d_name)) goto done; } rc = ISCSI_ERR_NO_OBJS_FOUND; done: closedir(dirfd); return rc; } static int get_targets(struct list_head *list, char *rootdir, char *subsys) { struct boot_context *context; int rc = 0, i, nic_idx, nic; char initiator_dir[FILENAMESZ]; memset(&initiator_dir, 0 , FILENAMESZ); snprintf(initiator_dir, FILENAMESZ, "%sinitiator", rootdir); if (!file_exist(initiator_dir)) return ENODEV; nic_cnt = 0; tgt_cnt = 0; /* Find the targets and the ethernets */ nftw(rootdir, find_sysfs_dirs, 20, 1); for (i = 0; i < tgt_cnt; i++) { context = calloc(1, sizeof(*context)); if (!context) { rc = ENOMEM; break; } rc = fill_tgt_context(subsys, target_list[i], context); if (rc) goto cleanup; rc = sysfs_get_int(target_list[i], subsys, "nic-assoc", &nic_idx); if (rc) goto cleanup; for (nic = 0; nic < nic_cnt; nic++) { int id; rc = sysfs_get_int(nic_list[nic], subsys, "index", &id); if (!rc && (id == nic_idx)) break; } if (nic == nic_cnt) { printf("Invalid nic-assoc of %d. Max id %d.\n", nic_idx, nic_cnt); goto cleanup; } rc = fill_nic_context(subsys, nic_list[nic], context); if (rc) goto cleanup; fill_initiator_context(subsys, context); list_add_tail(&context->list, list); continue; cleanup: free(context); context = NULL; } if (rc) { if (context) free(context); /* * If there are some valid targets return them. Most likely, * the driver/ibft-implementation reported partial info * for targets/initiators that were not used for boot. */ if (!list_empty(list)) rc = 0; } deallocate_lists(); return rc; } int fwparam_sysfs_get_targets(struct list_head *list) { struct dirent *dent; DIR *dirfd; int rc = 0; /* ibft only has one instance */ get_targets(list, IBFT_SYSFS_ROOT, IBFT_SUBSYS); /* * We could have multiple iscsi llds and each lld could have * multiple targets/ethernet ports */ dirfd = opendir(ISCSI_LLD_ROOT); if (!dirfd) { rc = ISCSI_ERR_SYSFS_LOOKUP; goto done; } while ((dent = readdir(dirfd))) { char lld_root[FILENAMESZ]; memset(&lld_root, 0 , FILENAMESZ); if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) continue; if (strncmp(dent->d_name, ISCSI_LLD_SUBSYS_PREFIX, 10)) continue; snprintf(lld_root, FILENAMESZ, ISCSI_LLD_ROOT"%s/", dent->d_name); get_targets(list, lld_root, dent->d_name); } closedir(dirfd); done: if (!rc && list_empty(list)) rc = ISCSI_ERR_NO_OBJS_FOUND; if (rc) fw_free_targets(list); return rc; }