| /* |
| * Copyright (C) 2005 FUJITA Tomonori <tomof@acm.org> |
| * 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. |
| * |
| * 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 <errno.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| |
| #include "iscsid.h" |
| |
| /* Taken from Linux kernel sources */ |
| size_t strlcpy(char *dest, const char *src, size_t size) |
| { |
| size_t ret = strlen(src); |
| |
| if (size) { |
| size_t len = (ret >= size) ? size - 1 : ret; |
| |
| memcpy(dest, src, len); |
| dest[len] = '\0'; |
| } |
| return ret; |
| } |
| |
| int params_index_by_name(const char *name, const struct iscsi_key *keys) |
| { |
| int i, err = -ENOENT; |
| |
| for (i = 0; keys[i].name; i++) { |
| if (!strcasecmp(keys[i].name, name)) { |
| err = i; |
| break; |
| } |
| } |
| |
| return err; |
| } |
| |
| int params_index_by_name_numwild(const char *name, const struct iscsi_key *keys) |
| { |
| int i, err = -ENOENT; |
| |
| for (i = 0; keys[i].name; i++) { |
| if (!strncasecmp(keys[i].name, name, strlen(keys[i].name))) { |
| int j; |
| |
| if (strlen(keys[i].name) > strlen(name)) |
| continue; |
| for (j = strlen(keys[i].name); j < strlen(name); j++) { |
| if (!isdigit(name[j])) |
| goto next; |
| } |
| err = i; |
| break; |
| next: |
| continue; |
| } |
| } |
| |
| return err; |
| } |
| |
| void params_set_defaults(unsigned int *params, const struct iscsi_key *keys) |
| { |
| int i; |
| |
| for (i = 0; keys[i].name; i++) |
| params[i] = keys[i].local_def; |
| |
| return; |
| } |
| |
| static int range_val_to_str(unsigned int val, char *str, int len) |
| { |
| snprintf(str, len, "%u", val); |
| return 0; |
| } |
| |
| static int range_str_to_val(const char *str, unsigned int *val) |
| { |
| *val = strtol(str, NULL, 0); |
| return 0; |
| } |
| |
| static int bool_val_to_str(unsigned int val, char *str, int len) |
| { |
| int err = 0; |
| |
| switch (val) { |
| case 0: |
| strlcpy(str, "No", len); |
| break; |
| case 1: |
| strlcpy(str, "Yes", len); |
| break; |
| default: |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int bool_str_to_val(const char *str, unsigned int *val) |
| { |
| int err = 0; |
| |
| if (!strcmp(str, "Yes")) |
| *val = 1; |
| else if (!strcmp(str, "No")) |
| *val = 0; |
| else |
| err = -EINVAL; |
| |
| return err; |
| } |
| |
| static int or_set_val(struct iscsi_param *param, int idx, unsigned int *val) |
| { |
| *val |= param[idx].val; |
| param[idx].val = *val; |
| |
| return 0; |
| } |
| |
| static int and_set_val(struct iscsi_param *param, int idx, unsigned int *val) |
| { |
| *val &= param[idx].val; |
| param[idx].val = *val; |
| |
| return 0; |
| } |
| |
| static int num_check_val(const struct iscsi_key *key, unsigned int *val) |
| { |
| int err = 0; |
| |
| if (*val < key->min) { |
| *val = key->min; |
| err = -EINVAL; |
| } else if (*val > key->max) { |
| *val = key->max; |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int minimum_set_val(struct iscsi_param *param, int idx, unsigned int *val) |
| { |
| if (*val > param[idx].val) |
| *val = param[idx].val; |
| param[idx].val = *val; |
| return 0; |
| } |
| |
| static int maximum_set_val(struct iscsi_param *param, int idx, unsigned int *val) |
| { |
| if (param[idx].val > *val) |
| *val = param[idx].val; |
| param[idx].val = *val; |
| return 0; |
| } |
| |
| static int digest_val_to_str(unsigned int val, char *str, int len) |
| { |
| int pos = 0; |
| |
| if (val & DIGEST_NONE) { |
| len -= pos; |
| pos = snprintf(&str[pos], len, "None"); |
| } |
| |
| if (pos >= len) |
| goto out; |
| |
| if (val & DIGEST_CRC32C) { |
| len -= pos; |
| pos = snprintf(&str[pos], len, "%s%s", |
| (pos != 0) ? "," : "", "CRC32C"); |
| } |
| |
| if (pos >= len) |
| goto out; |
| |
| if (pos == 0) |
| pos = snprintf(&str[0], len, "Unknown"); |
| |
| out: |
| return 0; |
| } |
| |
| static int digest_str_to_val(const char *str, unsigned int *val) |
| { |
| int err = 0; |
| const char *p, *q; |
| |
| p = str; |
| *val = 0; |
| do { |
| while ((*p != '\0') && isspace(*p)) |
| p++; |
| if (!strncasecmp(p, "None", strlen("None"))) |
| *val |= DIGEST_NONE; |
| else if (!strncasecmp(p, "CRC32C", strlen("CRC32C"))) |
| *val |= DIGEST_CRC32C; |
| else { |
| err = -EINVAL; |
| break; |
| } |
| |
| if ((q = strchr(p, ','))) |
| p = q + 1; |
| } while (q); |
| |
| if (*val == 0) |
| *val = DIGEST_NONE; |
| |
| return err; |
| } |
| |
| static int digest_set_val(struct iscsi_param *param, int idx, unsigned int *val) |
| { |
| if (*val & DIGEST_CRC32C && param[idx].val & DIGEST_CRC32C) |
| *val = DIGEST_CRC32C; |
| else |
| *val = DIGEST_NONE; |
| |
| param[idx].val = *val; |
| |
| return 0; |
| } |
| |
| static int marker_val_to_str(unsigned int val, char *str, int len) |
| { |
| if (val == 0) |
| strlcpy(str, "Irrelevant", len); |
| else |
| strlcpy(str, "Reject", len); |
| |
| return 0; |
| } |
| |
| static int marker_set_val(struct iscsi_param *params, int idx, unsigned int *val) |
| { |
| if ((idx == key_ofmarkint && params[key_ofmarker].key_state == KEY_STATE_DONE) || |
| (idx == key_ifmarkint && params[key_ifmarker].key_state == KEY_STATE_DONE)) |
| *val = 0; |
| else |
| *val = 1; |
| |
| params[idx].val = *val; |
| |
| return 0; |
| } |
| |
| int params_val_to_str(const struct iscsi_key *keys, int idx, unsigned int val, |
| char *str, int len) |
| { |
| if (keys[idx].ops->val_to_str) |
| return keys[idx].ops->val_to_str(val, str, len); |
| else |
| return 0; |
| } |
| |
| int params_str_to_val(const struct iscsi_key *keys, int idx, const char *str, |
| unsigned int *val) |
| { |
| if (keys[idx].ops->str_to_val) |
| return keys[idx].ops->str_to_val(str, val); |
| else |
| return 0; |
| } |
| |
| int params_check_val(const struct iscsi_key *keys, int idx, unsigned int *val) |
| { |
| if (keys[idx].ops->check_val) |
| return keys[idx].ops->check_val(&keys[idx], val); |
| else |
| return 0; |
| } |
| |
| int params_set_val(struct iscsi_key *keys, struct iscsi_param *param, |
| int idx, unsigned int *val) |
| { |
| if (keys[idx].ops->set_val) |
| return keys[idx].ops->set_val(param, idx, val); |
| else |
| return 0; |
| } |
| |
| static struct iscsi_key_ops minimum_ops = { |
| .val_to_str = range_val_to_str, |
| .str_to_val = range_str_to_val, |
| .check_val = num_check_val, |
| .set_val = minimum_set_val, |
| }; |
| |
| static struct iscsi_key_ops maximum_ops = { |
| .val_to_str = range_val_to_str, |
| .str_to_val = range_str_to_val, |
| .check_val = num_check_val, |
| .set_val = maximum_set_val, |
| }; |
| |
| static struct iscsi_key_ops or_ops = { |
| .val_to_str = bool_val_to_str, |
| .str_to_val = bool_str_to_val, |
| .set_val = or_set_val, |
| }; |
| |
| static struct iscsi_key_ops and_ops = { |
| .val_to_str = bool_val_to_str, |
| .str_to_val = bool_str_to_val, |
| .set_val = and_set_val, |
| }; |
| |
| static struct iscsi_key_ops digest_ops = { |
| .val_to_str = digest_val_to_str, |
| .str_to_val = digest_str_to_val, |
| .set_val = digest_set_val, |
| }; |
| |
| static struct iscsi_key_ops marker_ops = { |
| .val_to_str = marker_val_to_str, |
| .set_val = marker_set_val, |
| }; |
| |
| #define SET_KEY_VALUES(x) DEFAULT_##x, DEFAULT_##x, MIN_##x, MAX_##x |
| |
| /* |
| * List of local target keys with initial values. |
| * Must match corresponding key_* enum in iscsi_scst.h!! |
| * |
| * Updating this array don't forget to update tgt_params_check() in |
| * the kernel as well! |
| */ |
| struct iscsi_key target_keys[] = { |
| /* name, rfc_def, local_def, min, max, show_in_sysfs, ops */ |
| {"QueuedCommands", SET_KEY_VALUES(NR_QUEUED_CMNDS), 1, &minimum_ops}, |
| {"RspTimeout", SET_KEY_VALUES(RSP_TIMEOUT), 1, &minimum_ops}, |
| {"NopInInterval", SET_KEY_VALUES(NOP_IN_INTERVAL), 1, &minimum_ops}, |
| {"NopInTimeout", SET_KEY_VALUES(NOP_IN_TIMEOUT), 1, &minimum_ops}, |
| {"MaxSessions", 0, 0, 0, 65535, 1, &minimum_ops}, |
| {NULL,}, |
| }; |
| |
| /* |
| * List of iSCSI RFC specified session keys with initial values. |
| * Must match corresponding key_* enum in iscsi_scst.h!! |
| * |
| * Updating this array don't forget to update sess_params_check() in |
| * the kernel as well! |
| */ |
| struct iscsi_key session_keys[] = { |
| /* name, rfc_def, local_def, min, max, show_in_sysfs, ops */ |
| {"InitialR2T", 1, 0, 0, 1, 1, &or_ops}, |
| {"ImmediateData", 1, 1, 0, 1, 1, &and_ops}, |
| {"MaxConnections", 1, 1, 1, 1, 0, &minimum_ops}, |
| {"MaxRecvDataSegmentLength", 8192, -1, 512, -1, 1, &minimum_ops}, |
| {"MaxXmitDataSegmentLength", 8192, -1, 512, -1, 1, &minimum_ops}, |
| {"MaxBurstLength", 262144, -1, 512, -1, 1, &minimum_ops}, |
| {"FirstBurstLength", 65536, 65536, 512, -1, 1, &minimum_ops}, |
| {"DefaultTime2Wait", 2, 0, 0, 0, 0, &maximum_ops}, |
| {"DefaultTime2Retain", 20, 0, 0, 0, 0, &minimum_ops}, |
| {"MaxOutstandingR2T", 1, 32, 1, 65535, 1, &minimum_ops}, |
| {"DataPDUInOrder", 1, 0, 0, 1, 0, &or_ops}, |
| {"DataSequenceInOrder", 1, 0, 0, 1, 0, &or_ops}, |
| {"ErrorRecoveryLevel", 0, 0, 0, 0, 0, &minimum_ops}, |
| {"HeaderDigest", DIGEST_NONE, DIGEST_NONE, DIGEST_NONE, DIGEST_ALL, 1, &digest_ops}, |
| {"DataDigest", DIGEST_NONE, DIGEST_NONE, DIGEST_NONE, DIGEST_ALL, 1, &digest_ops}, |
| {"OFMarker", 0, 0, 0, 0, 0, &and_ops}, |
| {"IFMarker", 0, 0, 0, 0, 0, &and_ops}, |
| {"OFMarkInt", 2048, 2048, 1, 65535, 0, &marker_ops}, |
| {"IFMarkInt", 2048, 2048, 1, 65535, 0, &marker_ops}, |
| {"RDMAExtensions", 0, 0, 0, 0, 1, &and_ops}, |
| {"TargetRecvDataSegmentLength", 8192, 512, 512, -1, 0, &minimum_ops}, |
| {"InitiatorRecvDataSegmentLength", 8192, -1, 512, -1, 0, &minimum_ops}, |
| {"MaxAHSLength", 256, 0, 0, -1, 0, &minimum_ops}, |
| {"TaggedBufferForSolicitedDataOnly", 0, 0, 0, 0, 0, &and_ops}, |
| {"iSERHelloRequired", 0, 0, 0, 0, 0, &and_ops}, |
| {"MaxOutstandingUnexpectedPDUs", 0, 0, 0, -1, 0, &minimum_ops}, |
| {NULL,}, |
| }; |