@@ -42,6 +42,7 @@
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/spinlock.h>
+#include <linux/string_helpers.h>
#include <linux/types.h>
#include <linux/device.h>
@@ -484,6 +485,9 @@ int lprocfs_write_u64_helper(const char __user *buffer,
int lprocfs_write_frac_u64_helper(const char __user *buffer,
unsigned long count,
u64 *val, int mult);
+int string_to_size(u64 *size, const char *buffer, size_t count);
+int sysfs_memparse(const char *buffer, size_t count, u64 *val,
+ const char *defunit);
char *lprocfs_find_named_value(const char *buffer, const char *name,
size_t *count);
void lprocfs_oh_tally(struct obd_histogram *oh, unsigned int value);
@@ -57,8 +57,8 @@ static ssize_t stripesize_store(struct kobject *kobj, struct attribute *attr,
u64 val;
int rc;
- rc = kstrtoull(buf, 10, &val);
- if (rc)
+ rc = sysfs_memparse(buf, count, &val, "B");
+ if (rc < 0)
return rc;
lov_fix_desc_stripe_size(&val);
@@ -61,12 +61,19 @@ static ssize_t mdc_max_dirty_mb_seq_write(struct file *file,
struct seq_file *sfl = file->private_data;
struct obd_device *dev = sfl->private;
struct client_obd *cli = &dev->u.cli;
- __s64 pages_number;
+ char kernbuf[22] = "";
+ u64 pages_number;
int rc;
- rc = lprocfs_write_frac_u64_helper(buffer, count, &pages_number,
- 1 << (20 - PAGE_SHIFT));
- if (rc)
+ if (count >= sizeof(kernbuf))
+ return -EINVAL;
+
+ if (copy_from_user(kernbuf, buffer, count))
+ return -EFAULT;
+ kernbuf[count] = 0;
+
+ rc = sysfs_memparse(kernbuf, count, &pages_number, "MiB");
+ if (rc < 0)
return rc;
/* MB -> pages */
@@ -111,6 +118,7 @@ static int mdc_cached_mb_seq_show(struct seq_file *m, void *v)
struct obd_device *dev = sfl->private;
struct client_obd *cli = &dev->u.cli;
u64 pages_number;
+ const char *tmp;
long rc;
char kernbuf[128];
@@ -121,18 +129,13 @@ static int mdc_cached_mb_seq_show(struct seq_file *m, void *v)
return -EFAULT;
kernbuf[count] = 0;
- buffer += lprocfs_find_named_value(kernbuf, "used_mb:", &count) -
- kernbuf;
- rc = lprocfs_write_frac_u64_helper(buffer, count, &pages_number,
- 1 << (20 - PAGE_SHIFT));
- if (rc)
+ tmp = lprocfs_find_named_value(kernbuf, "used_mb:", &count);
+ rc = sysfs_memparse(tmp, count, &pages_number, "MiB");
+ if (rc < 0)
return rc;
pages_number >>= PAGE_SHIFT;
- if (pages_number < 0)
- return -ERANGE;
-
rc = atomic_long_read(&cli->cl_lru_in_list) - pages_number;
if (rc > 0) {
struct lu_env *env;
@@ -524,6 +524,20 @@ static long obd_class_ioctl(struct file *filp, unsigned int cmd,
.fops = &obd_psdev_fops,
};
+#define test_string_to_size_one(value, result, def_unit) \
+({ \
+ u64 __size; \
+ int __ret; \
+ \
+ BUILD_BUG_ON(strlen(value) >= 23); \
+ __ret = sysfs_memparse((value), (result), &__size, \
+ (def_unit)); \
+ if (__ret == 0 && (u64)result != __size) \
+ CERROR("string_helper: size %llu != result %llu\n", \
+ __size, (u64)result); \
+ __ret; \
+})
+
static int obd_init_checks(void)
{
u64 u64val, div64val;
@@ -590,6 +604,53 @@ static int obd_init_checks(void)
ret = -EINVAL;
}
+ /* invalid string */
+ ret = test_string_to_size_one("256B34", 256, "B");
+ if (ret == 0)
+ CERROR("string_helpers: format should be number then units\n");
+ ret = test_string_to_size_one("132OpQ", 132, "B");
+ if (ret == 0)
+ CERROR("string_helpers: invalid units should be rejected\n");
+ ret = 0;
+
+ /* small values */
+ test_string_to_size_one("0B", 0, "B");
+ ret = test_string_to_size_one("1.82B", 1, "B");
+ if (ret == 0)
+ CERROR("string_helpers: number string with 'B' and '.' should be invalid\n");
+ ret = 0;
+ test_string_to_size_one("512B", 512, "B");
+ test_string_to_size_one("1.067kB", 1067, "B");
+ test_string_to_size_one("1.042KiB", 1067, "B");
+
+ /* Lustre special handling */
+ test_string_to_size_one("16", 16777216, "MiB");
+ test_string_to_size_one("65536", 65536, "B");
+ test_string_to_size_one("128K", 131072, "B");
+ test_string_to_size_one("1M", 1048576, "B");
+ test_string_to_size_one("256.5G", 275414777856ULL, "GiB");
+
+ /* normal values */
+ test_string_to_size_one("8.39MB", 8390000, "MiB");
+ test_string_to_size_one("8.00MiB", 8388608, "MiB");
+ test_string_to_size_one("256GB", 256000000, "GiB");
+ test_string_to_size_one("238.731 GiB", 256335459385ULL, "GiB");
+
+ /* huge values */
+ test_string_to_size_one("0.4TB", 400000000000ULL, "TiB");
+ test_string_to_size_one("12.5TiB", 13743895347200ULL, "TiB");
+ test_string_to_size_one("2PB", 2000000000000000ULL, "PiB");
+ test_string_to_size_one("16PiB", 18014398509481984ULL, "PiB");
+
+ /* huge values should overflow */
+ ret = test_string_to_size_one("1000EiB", 0, "EiB");
+ if (ret != -EOVERFLOW)
+ CERROR("string_helpers: Failed to detect overflow\n");
+ ret = test_string_to_size_one("1000EB", 0, "EiB");
+ if (ret != -EOVERFLOW)
+ CERROR("string_helpers: Failed to detect overflow\n");
+ ret = 0;
+
return ret;
}
@@ -217,6 +217,185 @@ static void obd_connect_data_seqprint(struct seq_file *m,
ocd->ocd_maxmodrpcs);
}
+/**
+ * string_to_size - convert ASCII string representing a numerical
+ * value with optional units to 64-bit binary value
+ *
+ * @size: The numerical value extract out of @buffer
+ * @buffer: passed in string to parse
+ * @count: length of the @buffer
+ *
+ * This function returns a 64-bit binary value if @buffer contains a valid
+ * numerical string. The string is parsed to 3 significant figures after
+ * the decimal point. Support the string containing an optional units at
+ * the end which can be base 2 or base 10 in value. If no units are given
+ * the string is assumed to just a numerical value.
+ *
+ * Returns: @count if the string is successfully parsed,
+ * -errno on invalid input strings. Error values:
+ *
+ * - ``-EINVAL``: @buffer is not a proper numerical string
+ * - ``-EOVERFLOW``: results does not fit into 64 bits.
+ * - ``-E2BIG ``: @buffer is not large
+ */
+int string_to_size(u64 *size, const char *buffer, size_t count)
+{
+ /* For string_get_size() it can support values above exabytes,
+ * (ZiB, YiB) due to breaking the return value into a size and
+ * bulk size to avoid 64 bit overflow. We don't break the size
+ * up into block size units so we don't support ZiB or YiB.
+ */
+ static const char *const units_10[] = {
+ "kB", "MB", "GB", "TB", "PB", "EB"
+ };
+ static const char *const units_2[] = {
+ "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"
+ };
+ static const char *const *const units_str[] = {
+ [STRING_UNITS_2] = units_2,
+ [STRING_UNITS_10] = units_10,
+ };
+ static const unsigned int coeff[] = {
+ [STRING_UNITS_10] = 1000,
+ [STRING_UNITS_2] = 1024,
+ };
+ enum string_size_units unit;
+ u64 whole, blk_size = 1;
+ char kernbuf[22], *end;
+ size_t len = count;
+ int rc;
+ int i;
+
+ if (count >= sizeof(kernbuf))
+ return -E2BIG;
+
+ *size = 0;
+ /* 'iB' is used for based 2 numbers. If @buffer contains only a 'B'
+ * or only numbers then we treat it as a direct number which doesn't
+ * matter if its STRING_UNITS_2 or STRING_UNIT_10.
+ */
+ unit = strstr(buffer, "iB") ? STRING_UNITS_2 : STRING_UNITS_10;
+ i = unit == STRING_UNITS_2 ? ARRAY_SIZE(units_2) - 1 :
+ ARRAY_SIZE(units_10) - 1;
+ do {
+ end = strstr(buffer, units_str[unit][i]);
+ if (end) {
+ for (; i >= 0; i--)
+ blk_size *= coeff[unit];
+ len -= strlen(end);
+ break;
+ }
+ } while (i--);
+
+ /* as 'B' is a substring of all units, we need to handle it
+ * separately.
+ */
+ if (!end) {
+ /* 'B' is only acceptable letter at this point */
+ end = strchr(buffer, 'B');
+ if (end) {
+ len -= strlen(end);
+
+ if (count - len > 2 ||
+ (count - len == 2 && strcmp(end, "B\n") != 0))
+ return -EINVAL;
+ }
+ /* kstrtoull will error out if it has non digits */
+ goto numbers_only;
+ }
+
+ end = strchr(buffer, '.');
+ if (end) {
+ /* need to limit 3 decimal places */
+ char rem[4] = "000";
+ u64 frac = 0;
+ size_t off;
+
+ len = end - buffer;
+ end++;
+
+ /* limit to 3 decimal points */
+ off = min_t(size_t, 3, strspn(end, "0123456789"));
+ /* need to limit frac_d to a u32 */
+ memcpy(rem, end, off);
+ rc = kstrtoull(rem, 10, &frac);
+ if (rc)
+ return rc;
+
+ if (fls64(frac) + fls64(blk_size) - 1 > 64)
+ return -EOVERFLOW;
+
+ frac *= blk_size;
+ do_div(frac, 1000);
+ *size += frac;
+ }
+numbers_only:
+ snprintf(kernbuf, sizeof(kernbuf), "%.*s", (int)len, buffer);
+ rc = kstrtoull(kernbuf, 10, &whole);
+ if (rc)
+ return rc;
+
+ if (whole != 0 && fls64(whole) + fls64(blk_size) - 1 > 64)
+ return -EOVERFLOW;
+
+ *size += whole * blk_size;
+
+ return count;
+}
+EXPORT_SYMBOL(string_to_size);
+
+/**
+ * sysfs_memparse - parse a ASCII string to 64-bit binary value,
+ * with optional units
+ *
+ * @buffer: kernel pointer to input string
+ * @count: number of bytes in the input @buffer
+ * @val: (output) binary value returned to caller
+ * @defunit: default unit suffix to use if none is provided
+ *
+ * Parses a string into a number. The number stored at @buffer is
+ * potentially suffixed with K, M, G, T, P, E. Besides these other
+ * valid suffix units are shown in the string_to_size() function.
+ * If the string lacks a suffix then the defunit is used. The defunit
+ * should be given as a binary unit (e.g. MiB) as that is the standard
+ * for tunables in Lustre. If no unit suffix is given (e.g. 'G'), then
+ * it is assumed to be in binary units.
+ *
+ * Returns: 0 on success or -errno on failure.
+ */
+int sysfs_memparse(const char *buffer, size_t count, u64 *val,
+ const char *defunit)
+{
+ char param[23];
+ int rc;
+
+ if (count >= sizeof(param))
+ return -E2BIG;
+
+ count = strlen(buffer);
+ if (count && buffer[count - 1] == '\n')
+ count--;
+
+ if (!count)
+ return -EINVAL;
+
+ if (isalpha(buffer[count - 1])) {
+ if (buffer[count - 1] != 'B') {
+ scnprintf(param, sizeof(param), "%.*siB",
+ (int)count, buffer);
+ } else {
+ memcpy(param, buffer, count + 1);
+ }
+ } else {
+ scnprintf(param, sizeof(param), "%.*s%s", (int)count,
+ buffer, defunit);
+ }
+
+ rc = string_to_size(val, param, strlen(param));
+ return rc < 0 ? rc : 0;
+}
+EXPORT_SYMBOL(sysfs_memparse);
+
int lprocfs_read_frac_helper(char *buffer, unsigned long count, long val,
int mult)
{
@@ -203,10 +203,10 @@ static ssize_t osc_cached_mb_seq_write(struct file *file,
struct seq_file *m = file->private_data;
struct obd_device *dev = m->private;
struct client_obd *cli = &dev->u.cli;
- long pages_number, rc;
+ u64 pages_number;
+ const char *tmp;
+ long rc;
char kernbuf[128];
- int mult;
- u64 val;
if (count >= sizeof(kernbuf))
return -EINVAL;
@@ -215,19 +215,12 @@ static ssize_t osc_cached_mb_seq_write(struct file *file,
return -EFAULT;
kernbuf[count] = 0;
- mult = 1 << (20 - PAGE_SHIFT);
- buffer += lprocfs_find_named_value(kernbuf, "used_mb:", &count) -
- kernbuf;
- rc = lprocfs_write_frac_u64_helper(buffer, count, &val, mult);
- if (rc)
+ tmp = lprocfs_find_named_value(kernbuf, "used_mb:", &count);
+ rc = sysfs_memparse(tmp, count, &pages_number, "MiB");
+ if (rc < 0)
return rc;
- if (val > LONG_MAX)
- return -ERANGE;
- pages_number = (long)val;
-
- if (pages_number < 0)
- return -ERANGE;
+ pages_number >>= PAGE_SHIFT;
rc = atomic_long_read(&cli->cl_lru_in_list) - pages_number;
if (rc > 0) {
@@ -277,11 +270,11 @@ static ssize_t cur_grant_bytes_store(struct kobject *kobj,
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
struct client_obd *cli = &obd->u.cli;
+ u64 val;
int rc;
- unsigned long long val;
- rc = kstrtoull(buffer, 10, &val);
- if (rc)
+ rc = sysfs_memparse(buffer, count, &val, "MiB");
+ if (rc < 0)
return rc;
/* this is only for shrinking grant */