| /* |
| * GPL HEADER START |
| * |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 only, |
| * 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 version 2 for more details (a copy is included |
| * in the LICENSE file that accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License |
| * version 2 along with this program; If not, see |
| * http://www.sun.com/software/products/lustre/docs/GPLv2.pdf |
| * |
| * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.com if you need additional information or |
| * have any questions. |
| * |
| * GPL HEADER END |
| */ |
| /* |
| * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. |
| * Use is subject to license terms. |
| * |
| * Copyright (c) 2012, 2015 Intel Corporation. |
| */ |
| /* |
| * This file is part of Lustre, http://www.lustre.org/ |
| * Lustre is a trademark of Sun Microsystems, Inc. |
| * |
| * String manipulation functions. |
| * |
| * libcfs/libcfs/libcfs_string.c |
| * |
| * Author: Nathan Rutman <nathan.rutman@sun.com> |
| */ |
| |
| #include "../../include/linux/libcfs/libcfs.h" |
| |
| /* Convert a text string to a bitmask */ |
| int cfs_str2mask(const char *str, const char *(*bit2str)(int bit), |
| int *oldmask, int minmask, int allmask) |
| { |
| const char *debugstr; |
| char op = '\0'; |
| int newmask = minmask, i, len, found = 0; |
| |
| /* <str> must be a list of tokens separated by whitespace |
| * and optionally an operator ('+' or '-'). If an operator |
| * appears first in <str>, '*oldmask' is used as the starting point |
| * (relative), otherwise minmask is used (absolute). An operator |
| * applies to all following tokens up to the next operator. |
| */ |
| while (*str != '\0') { |
| while (isspace(*str)) |
| str++; |
| if (*str == '\0') |
| break; |
| if (*str == '+' || *str == '-') { |
| op = *str++; |
| if (!found) |
| /* only if first token is relative */ |
| newmask = *oldmask; |
| while (isspace(*str)) |
| str++; |
| if (*str == '\0') /* trailing op */ |
| return -EINVAL; |
| } |
| |
| /* find token length */ |
| len = 0; |
| while (str[len] != '\0' && !isspace(str[len]) && |
| str[len] != '+' && str[len] != '-') |
| len++; |
| |
| /* match token */ |
| found = 0; |
| for (i = 0; i < 32; i++) { |
| debugstr = bit2str(i); |
| if (debugstr && strlen(debugstr) == len && |
| strncasecmp(str, debugstr, len) == 0) { |
| if (op == '-') |
| newmask &= ~(1 << i); |
| else |
| newmask |= (1 << i); |
| found = 1; |
| break; |
| } |
| } |
| if (!found && len == 3 && |
| (strncasecmp(str, "ALL", len) == 0)) { |
| if (op == '-') |
| newmask = minmask; |
| else |
| newmask = allmask; |
| found = 1; |
| } |
| if (!found) { |
| CWARN("unknown mask '%.*s'.\n" |
| "mask usage: [+|-]<all|type> ...\n", len, str); |
| return -EINVAL; |
| } |
| str += len; |
| } |
| |
| *oldmask = newmask; |
| return 0; |
| } |
| |
| /* get the first string out of @str */ |
| char *cfs_firststr(char *str, size_t size) |
| { |
| size_t i = 0; |
| char *end; |
| |
| /* trim leading spaces */ |
| while (i < size && *str && isspace(*str)) { |
| ++i; |
| ++str; |
| } |
| |
| /* string with all spaces */ |
| if (*str == '\0') |
| goto out; |
| |
| end = str; |
| while (i < size && *end != '\0' && !isspace(*end)) { |
| ++i; |
| ++end; |
| } |
| |
| *end = '\0'; |
| out: |
| return str; |
| } |
| EXPORT_SYMBOL(cfs_firststr); |
| |
| char * |
| cfs_trimwhite(char *str) |
| { |
| char *end; |
| |
| while (isspace(*str)) |
| str++; |
| |
| end = str + strlen(str); |
| while (end > str) { |
| if (!isspace(end[-1])) |
| break; |
| end--; |
| } |
| |
| *end = 0; |
| return str; |
| } |
| EXPORT_SYMBOL(cfs_trimwhite); |
| |
| /** |
| * Extracts tokens from strings. |
| * |
| * Looks for \a delim in string \a next, sets \a res to point to |
| * substring before the delimiter, sets \a next right after the found |
| * delimiter. |
| * |
| * \retval 1 if \a res points to a string of non-whitespace characters |
| * \retval 0 otherwise |
| */ |
| int |
| cfs_gettok(struct cfs_lstr *next, char delim, struct cfs_lstr *res) |
| { |
| char *end; |
| |
| if (!next->ls_str) |
| return 0; |
| |
| /* skip leading white spaces */ |
| while (next->ls_len) { |
| if (!isspace(*next->ls_str)) |
| break; |
| next->ls_str++; |
| next->ls_len--; |
| } |
| |
| if (next->ls_len == 0) /* whitespaces only */ |
| return 0; |
| |
| if (*next->ls_str == delim) { |
| /* first non-writespace is the delimiter */ |
| return 0; |
| } |
| |
| res->ls_str = next->ls_str; |
| end = memchr(next->ls_str, delim, next->ls_len); |
| if (!end) { |
| /* there is no the delimeter in the string */ |
| end = next->ls_str + next->ls_len; |
| next->ls_str = NULL; |
| } else { |
| next->ls_str = end + 1; |
| next->ls_len -= (end - res->ls_str + 1); |
| } |
| |
| /* skip ending whitespaces */ |
| while (--end != res->ls_str) { |
| if (!isspace(*end)) |
| break; |
| } |
| |
| res->ls_len = end - res->ls_str + 1; |
| return 1; |
| } |
| EXPORT_SYMBOL(cfs_gettok); |
| |
| /** |
| * Converts string to integer. |
| * |
| * Accepts decimal and hexadecimal number recordings. |
| * |
| * \retval 1 if first \a nob chars of \a str convert to decimal or |
| * hexadecimal integer in the range [\a min, \a max] |
| * \retval 0 otherwise |
| */ |
| int |
| cfs_str2num_check(char *str, int nob, unsigned *num, |
| unsigned min, unsigned max) |
| { |
| bool all_numbers = true; |
| char *endp, cache; |
| int rc; |
| |
| str = cfs_trimwhite(str); |
| |
| /** |
| * kstrouint can only handle strings composed |
| * of only numbers. We need to scan the string |
| * passed in for the first non-digit character |
| * and end the string at that location. If we |
| * don't find any non-digit character we still |
| * need to place a '\0' at position nob since |
| * we are not interested in the rest of the |
| * string which is longer than nob in size. |
| * After we are done the character at the |
| * position we placed '\0' must be restored. |
| */ |
| for (endp = str; endp < str + nob; endp++) { |
| if (!isdigit(*endp)) { |
| all_numbers = false; |
| break; |
| } |
| } |
| cache = *endp; |
| *endp = '\0'; |
| |
| rc = kstrtouint(str, 10, num); |
| *endp = cache; |
| if (rc || !all_numbers) |
| return 0; |
| |
| return (*num >= min && *num <= max); |
| } |
| EXPORT_SYMBOL(cfs_str2num_check); |
| |
| /** |
| * Parses \<range_expr\> token of the syntax. If \a bracketed is false, |
| * \a src should only have a single token which can be \<number\> or \* |
| * |
| * \retval pointer to allocated range_expr and initialized |
| * range_expr::re_lo, range_expr::re_hi and range_expr:re_stride if \a |
| `* src parses to |
| * \<number\> | |
| * \<number\> '-' \<number\> | |
| * \<number\> '-' \<number\> '/' \<number\> |
| * \retval 0 will be returned if it can be parsed, otherwise -EINVAL or |
| * -ENOMEM will be returned. |
| */ |
| static int |
| cfs_range_expr_parse(struct cfs_lstr *src, unsigned min, unsigned max, |
| int bracketed, struct cfs_range_expr **expr) |
| { |
| struct cfs_range_expr *re; |
| struct cfs_lstr tok; |
| |
| LIBCFS_ALLOC(re, sizeof(*re)); |
| if (!re) |
| return -ENOMEM; |
| |
| if (src->ls_len == 1 && src->ls_str[0] == '*') { |
| re->re_lo = min; |
| re->re_hi = max; |
| re->re_stride = 1; |
| goto out; |
| } |
| |
| if (cfs_str2num_check(src->ls_str, src->ls_len, |
| &re->re_lo, min, max)) { |
| /* <number> is parsed */ |
| re->re_hi = re->re_lo; |
| re->re_stride = 1; |
| goto out; |
| } |
| |
| if (!bracketed || !cfs_gettok(src, '-', &tok)) |
| goto failed; |
| |
| if (!cfs_str2num_check(tok.ls_str, tok.ls_len, |
| &re->re_lo, min, max)) |
| goto failed; |
| |
| /* <number> - */ |
| if (cfs_str2num_check(src->ls_str, src->ls_len, |
| &re->re_hi, min, max)) { |
| /* <number> - <number> is parsed */ |
| re->re_stride = 1; |
| goto out; |
| } |
| |
| /* go to check <number> '-' <number> '/' <number> */ |
| if (cfs_gettok(src, '/', &tok)) { |
| if (!cfs_str2num_check(tok.ls_str, tok.ls_len, |
| &re->re_hi, min, max)) |
| goto failed; |
| |
| /* <number> - <number> / ... */ |
| if (cfs_str2num_check(src->ls_str, src->ls_len, |
| &re->re_stride, min, max)) { |
| /* <number> - <number> / <number> is parsed */ |
| goto out; |
| } |
| } |
| |
| out: |
| *expr = re; |
| return 0; |
| |
| failed: |
| LIBCFS_FREE(re, sizeof(*re)); |
| return -EINVAL; |
| } |
| |
| /** |
| * Print the range expression \a re into specified \a buffer. |
| * If \a bracketed is true, expression does not need additional |
| * brackets. |
| * |
| * \retval number of characters written |
| */ |
| static int |
| cfs_range_expr_print(char *buffer, int count, struct cfs_range_expr *expr, |
| bool bracketed) |
| { |
| int i; |
| char s[] = "["; |
| char e[] = "]"; |
| |
| if (bracketed) { |
| s[0] = '\0'; |
| e[0] = '\0'; |
| } |
| |
| if (expr->re_lo == expr->re_hi) |
| i = scnprintf(buffer, count, "%u", expr->re_lo); |
| else if (expr->re_stride == 1) |
| i = scnprintf(buffer, count, "%s%u-%u%s", |
| s, expr->re_lo, expr->re_hi, e); |
| else |
| i = scnprintf(buffer, count, "%s%u-%u/%u%s", |
| s, expr->re_lo, expr->re_hi, expr->re_stride, e); |
| return i; |
| } |
| |
| /** |
| * Print a list of range expressions (\a expr_list) into specified \a buffer. |
| * If the list contains several expressions, separate them with comma |
| * and surround the list with brackets. |
| * |
| * \retval number of characters written |
| */ |
| int |
| cfs_expr_list_print(char *buffer, int count, struct cfs_expr_list *expr_list) |
| { |
| struct cfs_range_expr *expr; |
| int i = 0, j = 0; |
| int numexprs = 0; |
| |
| if (count <= 0) |
| return 0; |
| |
| list_for_each_entry(expr, &expr_list->el_exprs, re_link) |
| numexprs++; |
| |
| if (numexprs > 1) |
| i += scnprintf(buffer + i, count - i, "["); |
| |
| list_for_each_entry(expr, &expr_list->el_exprs, re_link) { |
| if (j++ != 0) |
| i += scnprintf(buffer + i, count - i, ","); |
| i += cfs_range_expr_print(buffer + i, count - i, expr, |
| numexprs > 1); |
| } |
| |
| if (numexprs > 1) |
| i += scnprintf(buffer + i, count - i, "]"); |
| |
| return i; |
| } |
| EXPORT_SYMBOL(cfs_expr_list_print); |
| |
| /** |
| * Matches value (\a value) against ranges expression list \a expr_list. |
| * |
| * \retval 1 if \a value matches |
| * \retval 0 otherwise |
| */ |
| int |
| cfs_expr_list_match(__u32 value, struct cfs_expr_list *expr_list) |
| { |
| struct cfs_range_expr *expr; |
| |
| list_for_each_entry(expr, &expr_list->el_exprs, re_link) { |
| if (value >= expr->re_lo && value <= expr->re_hi && |
| ((value - expr->re_lo) % expr->re_stride) == 0) |
| return 1; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cfs_expr_list_match); |
| |
| /** |
| * Convert express list (\a expr_list) to an array of all matched values |
| * |
| * \retval N N is total number of all matched values |
| * \retval 0 if expression list is empty |
| * \retval < 0 for failure |
| */ |
| int |
| cfs_expr_list_values(struct cfs_expr_list *expr_list, int max, __u32 **valpp) |
| { |
| struct cfs_range_expr *expr; |
| __u32 *val; |
| int count = 0; |
| int i; |
| |
| list_for_each_entry(expr, &expr_list->el_exprs, re_link) { |
| for (i = expr->re_lo; i <= expr->re_hi; i++) { |
| if (((i - expr->re_lo) % expr->re_stride) == 0) |
| count++; |
| } |
| } |
| |
| if (count == 0) /* empty expression list */ |
| return 0; |
| |
| if (count > max) { |
| CERROR("Number of values %d exceeds max allowed %d\n", |
| max, count); |
| return -EINVAL; |
| } |
| |
| LIBCFS_ALLOC(val, sizeof(val[0]) * count); |
| if (!val) |
| return -ENOMEM; |
| |
| count = 0; |
| list_for_each_entry(expr, &expr_list->el_exprs, re_link) { |
| for (i = expr->re_lo; i <= expr->re_hi; i++) { |
| if (((i - expr->re_lo) % expr->re_stride) == 0) |
| val[count++] = i; |
| } |
| } |
| |
| *valpp = val; |
| return count; |
| } |
| EXPORT_SYMBOL(cfs_expr_list_values); |
| |
| /** |
| * Frees cfs_range_expr structures of \a expr_list. |
| * |
| * \retval none |
| */ |
| void |
| cfs_expr_list_free(struct cfs_expr_list *expr_list) |
| { |
| while (!list_empty(&expr_list->el_exprs)) { |
| struct cfs_range_expr *expr; |
| |
| expr = list_entry(expr_list->el_exprs.next, |
| struct cfs_range_expr, re_link); |
| list_del(&expr->re_link); |
| LIBCFS_FREE(expr, sizeof(*expr)); |
| } |
| |
| LIBCFS_FREE(expr_list, sizeof(*expr_list)); |
| } |
| EXPORT_SYMBOL(cfs_expr_list_free); |
| |
| /** |
| * Parses \<cfs_expr_list\> token of the syntax. |
| * |
| * \retval 0 if \a str parses to \<number\> | \<expr_list\> |
| * \retval -errno otherwise |
| */ |
| int |
| cfs_expr_list_parse(char *str, int len, unsigned min, unsigned max, |
| struct cfs_expr_list **elpp) |
| { |
| struct cfs_expr_list *expr_list; |
| struct cfs_range_expr *expr; |
| struct cfs_lstr src; |
| int rc; |
| |
| LIBCFS_ALLOC(expr_list, sizeof(*expr_list)); |
| if (!expr_list) |
| return -ENOMEM; |
| |
| src.ls_str = str; |
| src.ls_len = len; |
| |
| INIT_LIST_HEAD(&expr_list->el_exprs); |
| |
| if (src.ls_str[0] == '[' && |
| src.ls_str[src.ls_len - 1] == ']') { |
| src.ls_str++; |
| src.ls_len -= 2; |
| |
| rc = -EINVAL; |
| while (src.ls_str) { |
| struct cfs_lstr tok; |
| |
| if (!cfs_gettok(&src, ',', &tok)) { |
| rc = -EINVAL; |
| break; |
| } |
| |
| rc = cfs_range_expr_parse(&tok, min, max, 1, &expr); |
| if (rc != 0) |
| break; |
| |
| list_add_tail(&expr->re_link, &expr_list->el_exprs); |
| } |
| } else { |
| rc = cfs_range_expr_parse(&src, min, max, 0, &expr); |
| if (rc == 0) |
| list_add_tail(&expr->re_link, &expr_list->el_exprs); |
| } |
| |
| if (rc != 0) |
| cfs_expr_list_free(expr_list); |
| else |
| *elpp = expr_list; |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(cfs_expr_list_parse); |
| |
| /** |
| * Frees cfs_expr_list structures of \a list. |
| * |
| * For each struct cfs_expr_list structure found on \a list it frees |
| * range_expr list attached to it and frees the cfs_expr_list itself. |
| * |
| * \retval none |
| */ |
| void |
| cfs_expr_list_free_list(struct list_head *list) |
| { |
| struct cfs_expr_list *el; |
| |
| while (!list_empty(list)) { |
| el = list_entry(list->next, struct cfs_expr_list, el_link); |
| list_del(&el->el_link); |
| cfs_expr_list_free(el); |
| } |
| } |
| EXPORT_SYMBOL(cfs_expr_list_free_list); |