/* * Copyright (C) 2017 Red Hat, Inc. * * 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 3 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 . * * Author: Gris Ge */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libopeniscsiusr/libopeniscsiusr_common.h" #include "sysfs.h" #include "misc.h" #define _INT32_STR_MAX_LEN 12 /* ^ The max uint32_t is 4294967296 which requires 11 bytes for string. * The max/min in32_t is 2147483647 or -2147483646 which requires 12 bytes. */ #define _SYS_NULL_STR "(null)" #define _sysfs_prop_get_uint_func_gen(func_name, out_type, type_max_value) \ int func_name(struct iscsi_context *ctx, const char *dir_path, \ const char *prop_name, out_type *val, \ out_type default_value, bool ignore_error) \ { \ long long int tmp_val = 0; \ int rc = LIBISCSI_OK; \ long long int dv = default_value; \ rc = iscsi_sysfs_prop_get_ll(ctx, dir_path, prop_name, \ &tmp_val, (long long int) dv, \ ignore_error); \ if (rc == LIBISCSI_OK) \ *val = tmp_val & type_max_value; \ return rc; \ } #define _sysfs_prop_get_int_func_gen(func_name, out_type, type_min_value, type_max_value) \ int func_name(struct iscsi_context *ctx, const char *dir_path, \ const char *prop_name, out_type *val, \ out_type default_value, bool ignore_error) \ { \ long long int tmp_val = 0; \ int rc = LIBISCSI_OK; \ long long int dv = default_value; \ rc = iscsi_sysfs_prop_get_ll(ctx, dir_path, prop_name, \ &tmp_val, (long long int) dv, \ ignore_error); \ if (rc == LIBISCSI_OK) { \ if (tmp_val > type_max_value) \ *val = type_max_value; \ else if (tmp_val < type_min_value) \ *val = type_min_value; \ else \ *val = tmp_val; \ } \ return rc; \ } enum _sysfs_dev_class { _SYSFS_DEV_CLASS_ISCSI_SESSION, _SYSFS_DEV_CLASS_ISCSI_HOST, }; static int sysfs_read_file(const char *path, uint8_t *buff, size_t buff_size); static int iscsi_sysfs_prop_get_ll(struct iscsi_context *ctx, const char *dir_path, const char *prop_name, long long int *val, long long int default_value, bool ignore_error); /* * dev_path needs to be freed by the caller on success */ static int sysfs_get_dev_path(struct iscsi_context *ctx, const char *path, enum _sysfs_dev_class class, char **dev_path); _sysfs_prop_get_uint_func_gen(_sysfs_prop_get_u8, uint8_t, UINT8_MAX); _sysfs_prop_get_uint_func_gen(_sysfs_prop_get_u16, uint16_t, UINT16_MAX); _sysfs_prop_get_int_func_gen(_sysfs_prop_get_i32, int32_t, INT32_MIN, INT32_MAX); _sysfs_prop_get_uint_func_gen(_sysfs_prop_get_u32, uint32_t, UINT32_MAX); static int sysfs_read_file(const char *path, uint8_t *buff, size_t buff_size) { int fd = -1; int errno_save = 0; ssize_t readed = 0; ssize_t i = 0; assert(path != NULL); assert(buff != NULL); assert(buff_size != 0); memset(buff, 0, buff_size); fd = open(path, O_RDONLY); if (fd < 0) return errno; readed = read(fd, buff, buff_size); errno_save = errno; close(fd); if (readed < 0) { buff[0] = '\0'; return errno_save; } buff[buff_size - 1] = '\0'; /* Remove the trailing \n */ for (i = readed - 1; i >= 0; --i) { if (buff[i] == '\n') { buff[i] = '\0'; break; } } if (strcmp((char *) buff, _SYS_NULL_STR) == 0) buff[0] = '\0'; return 0; } int _sysfs_prop_get_str(struct iscsi_context *ctx, const char *dir_path, const char *prop_name, char *buff, size_t buff_size, const char *default_value) { char *file_path = NULL; int rc = LIBISCSI_OK; int errno_save = 0; assert(dir_path != NULL); assert(prop_name != NULL); assert(buff != NULL); _good(_asprintf(&file_path, "%s/%s", dir_path, prop_name), rc, out); errno_save = sysfs_read_file(file_path, (uint8_t *) buff, buff_size); if (errno_save != 0) { if (errno_save == ENOENT) { if (default_value == NULL) { rc = LIBISCSI_ERR_SYSFS_LOOKUP; _error(ctx, "Failed to read '%s': " "file '%s' does not exists", prop_name, file_path); } else { _info(ctx, "Failed to read '%s': " "file '%s' does not exists, " "using default value %s", prop_name, file_path, default_value); memcpy(buff, (void *) default_value, strlen(default_value) + 1); } } else if (errno_save == EACCES) { rc = LIBISCSI_ERR_ACCESS; _error(ctx, "Failed to read '%s': " "permission deny when reading '%s'", prop_name, file_path); } else if (errno_save == ENOTCONN) { if (default_value == NULL) { rc = LIBISCSI_ERR_SYSFS_LOOKUP; _error(ctx, "Failed to read '%s': " "error when reading '%s': " "Target unavailable", prop_name, file_path); } else { _info(ctx, "Failed to read '%s': " "error when reading '%s': " "Target unavailable, using default value '%s'", prop_name, file_path, default_value); memcpy(buff, (void *) default_value, strlen(default_value) + 1); } } else { rc = LIBISCSI_ERR_BUG; _error(ctx, "Failed to read '%s': " "error when reading '%s': %d", prop_name, file_path, errno_save); } } else { if ((buff[0] == '\0') && (default_value != NULL)) { memcpy(buff, (void *) default_value, strlen(default_value) + 1); _debug(ctx, "Open '%s', got NULL, using default value", file_path, default_value); } else _debug(ctx, "Open '%s', got '%s'", file_path, buff); } out: free(file_path); return rc; } static int iscsi_sysfs_prop_get_ll(struct iscsi_context *ctx, const char *dir_path, const char *prop_name, long long int *val, long long int default_value, bool ignore_error) { char *file_path = NULL; int rc = LIBISCSI_OK; int errno_save = 0; uint8_t buff[_INT32_STR_MAX_LEN]; long long int tmp_val = 0; assert(dir_path != NULL); assert(prop_name != NULL); assert(val != NULL); *val = 0; _good(_asprintf(&file_path, "%s/%s", dir_path, prop_name), rc, out); errno_save = sysfs_read_file(file_path, buff, _INT32_STR_MAX_LEN); if (errno_save != 0) { if (errno_save == ENOENT) { if (! ignore_error) { rc = LIBISCSI_ERR_SYSFS_LOOKUP; _error(ctx, "Failed to read '%s': " "file '%s' does not exists", prop_name, file_path); goto out; } else { _info(ctx, "Failed to read '%s': " "File '%s' does not exists, using ", "default value %lld", prop_name, file_path, default_value); *val = default_value; goto out; } } else if (errno_save == EACCES) { rc = LIBISCSI_ERR_ACCESS; _error(ctx, "Permission deny when reading '%s'", file_path); goto out; } else if (errno_save == ENOTCONN) { if (!ignore_error) { rc = LIBISCSI_ERR_SYSFS_LOOKUP; _error(ctx, "Failed to read '%s': " "error when reading '%s': " "Target unavailable", prop_name, file_path); goto out; } else { _info(ctx, "Failed to read '%s': " "error when reading '%s': " "Target unavailable, using default value %lld", prop_name, file_path, default_value); *val = default_value; goto out; } } else { rc = LIBISCSI_ERR_BUG; _error(ctx, "Error when reading '%s': %d", file_path, errno_save); goto out; } } errno = 0; tmp_val = strtoll((const char *) buff, NULL, 10 /* base */); errno_save = errno; if ((errno_save != 0) && (! ignore_error)) { rc = LIBISCSI_ERR_BUG; _error(ctx, "Sysfs: %s: Error when converting '%s' " "to number", file_path, (char *) buff, errno_save); goto out; } *val = tmp_val; _debug(ctx, "Open '%s', got %lld", file_path, tmp_val); out: free(file_path); return rc; } static int sysfs_get_dev_path(struct iscsi_context *ctx, const char *path, enum _sysfs_dev_class class, char **dev_path) { int rc = LIBISCSI_OK; int errno_save = 0; regex_t regex; regmatch_t reg_match[2]; int reg_rc = 0; int need_free_reg = 0; assert(ctx != NULL); assert(path != NULL); assert(dev_path != NULL); *dev_path = realpath(path, NULL); if (*dev_path == NULL) { errno_save = errno; rc = LIBISCSI_ERR_SYSFS_LOOKUP; _error(ctx, "realpath() failed on %s with error %d", path, errno_save); goto out; } switch (class) { case _SYSFS_DEV_CLASS_ISCSI_SESSION: reg_rc = regcomp(®ex, "\\(.\\{1,\\}/devices/.\\{1,\\}/" "host[0-9]\\{1,\\}\\)/" "session[0-9]\\{1,\\}/iscsi_session/", 0 /* no flag */); break; case _SYSFS_DEV_CLASS_ISCSI_HOST: reg_rc = regcomp(®ex, "\\(.\\{1,\\}/devices/.\\{1,\\}/" "host[0-9]\\{1,\\}\\)/" "iscsi_host/", 0 /* no flag */); break; default: rc = LIBISCSI_ERR_BUG; _error(ctx, "BUG: sysfs_get_dev_path(): got unknown class %d", class); goto out; } if (reg_rc != 0) { rc = LIBISCSI_ERR_SYSFS_LOOKUP; _error(ctx, "regcomp() failed %d", reg_rc); goto out; } need_free_reg = 1; if (regexec(®ex, *dev_path, 2 /* count of max matches */, reg_match, 0 /* no flags */) != 0) { rc = LIBISCSI_ERR_SYSFS_LOOKUP; _error(ctx, "regexec() not match for %s", *dev_path); goto out; } *(*dev_path + reg_match[1].rm_eo ) = '\0'; _debug(ctx, "Got dev path of '%s': '%s'", path, *dev_path); out: if (need_free_reg) regfree(®ex); if (rc != LIBISCSI_OK) { free(*dev_path); *dev_path = NULL; } return rc; } int _iscsi_host_id_of_session(struct iscsi_context *ctx, uint32_t sid, uint32_t *host_id) { int rc = LIBISCSI_OK; char *sys_se_dir_path = NULL; char *sys_dev_path = NULL; char *sys_scsi_host_dir_path = NULL; struct dirent **namelist = NULL; int n = 0; const char *host_id_str = NULL; const char iscsi_host_dir_str[] = "/iscsi_host/"; assert(ctx != NULL); assert(sid != 0); assert(host_id != NULL); _good(_asprintf(&sys_se_dir_path, "%s/session%" PRIu32, _ISCSI_SYS_SESSION_DIR, sid), rc, out); *host_id = 0; _good(sysfs_get_dev_path(ctx, sys_se_dir_path, _SYSFS_DEV_CLASS_ISCSI_SESSION, &sys_dev_path), rc, out); _good(_asprintf(&sys_scsi_host_dir_path, "%s%s", sys_dev_path, iscsi_host_dir_str), rc, out); _good(_scandir(ctx, sys_scsi_host_dir_path, &namelist, &n), rc, out); if (n != 1) { rc = LIBISCSI_ERR_SYSFS_LOOKUP; _error(ctx, "Got unexpected(should be 1) file in folder %s", sys_scsi_host_dir_path); goto out; } host_id_str = namelist[0]->d_name; if (sscanf(host_id_str, "host%" SCNu32, host_id) != 1) { rc = LIBISCSI_ERR_SYSFS_LOOKUP; _error(ctx, "sscanf() failed on string %s", host_id_str); goto out; } out: _scandir_free(namelist, n); free(sys_se_dir_path); free(sys_dev_path); free(sys_scsi_host_dir_path); return rc; } static int _iscsi_ids_get(struct iscsi_context *ctx, uint32_t **ids, uint32_t *id_count, const char *dir_path, const char *file_prefix) { int rc = LIBISCSI_OK; struct dirent **namelist = NULL; int n = 0; uint32_t i = 0; const char *id_str = NULL; char fmt_buff[128]; assert(ctx != NULL); assert(ids != 0); assert(id_count != NULL); *ids = NULL; *id_count = 0; _good(_scandir(ctx, dir_path, &namelist, &n), rc, out); _debug(ctx, "Got %d iSCSI %s", n, file_prefix); *id_count = n & UINT32_MAX; *ids = calloc(*id_count, sizeof(uint32_t)); _alloc_null_check(ctx, *ids, rc, out); snprintf(fmt_buff, sizeof(fmt_buff)/sizeof(char), "%s%%" SCNu32, file_prefix); for (i = 0; i < *id_count; ++i) { id_str = namelist[i]->d_name; if (sscanf(id_str, fmt_buff, &((*ids)[i])) != 1) { rc = LIBISCSI_ERR_SYSFS_LOOKUP; _error(ctx, "sscanf() failed on string %s", id_str); goto out; } _debug(ctx, "Got iSCSI %s id %" PRIu32, file_prefix, (*ids)[i]); } out: _scandir_free(namelist, n); if (rc != LIBISCSI_OK) { free(*ids); *ids = NULL; *id_count = 0; } return rc; } int _iscsi_sids_get(struct iscsi_context *ctx, uint32_t **sids, uint32_t *sid_count) { return _iscsi_ids_get(ctx, sids, sid_count, _ISCSI_SYS_SESSION_DIR, "session"); } int _iscsi_hids_get(struct iscsi_context *ctx, uint32_t **hids, uint32_t *hid_count) { return _iscsi_ids_get(ctx, hids, hid_count, _ISCSI_SYS_HOST_DIR, "host"); } bool _iscsi_transport_is_loaded(const char *transport_name) { int rc = LIBISCSI_OK; char *path = NULL; if (transport_name == NULL) return false; _good(_asprintf(&path, "%s/%s", _ISCSI_SYS_TRANSPORT_DIR, transport_name), rc, out); if (access(path, F_OK) == 0) { free(path); return true; } out: free(path); return false; } int _iscsi_iface_kern_ids_of_host_id(struct iscsi_context *ctx, uint32_t host_id, char ***iface_kern_ids, uint32_t *iface_count) { char *sysfs_sh_path = NULL; char *dev_path = NULL; char *sysfs_iface_path = NULL; int rc = LIBISCSI_OK; struct dirent **namelist = NULL; int n = 0; uint32_t i = 0; _good(_asprintf(&sysfs_sh_path, "%s/host%" PRIu32, _ISCSI_SYS_HOST_DIR, host_id), rc, out); _good(sysfs_get_dev_path(ctx, sysfs_sh_path, _SYSFS_DEV_CLASS_ISCSI_HOST, &dev_path), rc, out); _good(_asprintf(&sysfs_iface_path, "%s/iscsi_iface", dev_path), rc, out); _good(_scandir(ctx, sysfs_iface_path, &namelist, &n), rc, out); if (n == 0) { /* this is OK, and needed for transport drivers like * bnx2i and qedi */ rc = LIBISCSI_OK; _debug(ctx, "No iSCSI interface for iSCSI host %" PRIu32, host_id); goto out; } *iface_count = n; *iface_kern_ids = calloc(*iface_count, sizeof(char *)); _alloc_null_check(ctx, *iface_kern_ids, rc, out); for (i = 0; i < *iface_count; i++) { (*iface_kern_ids)[i] = strdup(namelist[i]->d_name); _alloc_null_check(ctx, (*iface_kern_ids)[i], rc, out); _debug(ctx, "Found iSCSI iface '%s' for iSCSI host %" PRIu32, (*iface_kern_ids)[i], host_id); } out: if (rc != LIBISCSI_OK) { for (i = 0; i < *iface_count; i++ ) { free((*iface_kern_ids)[i]); } free(*iface_kern_ids); *iface_kern_ids = NULL; *iface_count = 0; } _scandir_free(namelist, n); free(sysfs_sh_path); free(dev_path); free(sysfs_iface_path); return rc; }