LPO64||%#llo
LPPID||%d
lprocfs_str_to_s64||kstrtoxxx_from_user
+lprocfs_str_with_units_to_s64||sysfs_memparse
+lu_str_to_s64|sysfs_memparse
LPROC_SEQ_FOPS||LUSTRE_RW_ATTR
LPROC_SEQ_FOPS_RO_TYPE||LUSTRE_RO_ATTR
LPROC_SEQ_FOPS_RO||LUSTRE_RO_ATTR
#include <linux/debugfs.h>
#include <linux/rwsem.h>
#include <linux/spinlock.h>
+#include <linux/string_helpers.h>
#include <linux/seq_file.h>
#include <libcfs/libcfs.h>
extern int lprocfs_str_with_units_to_u64(const char __user *buffer,
unsigned long count, __u64 *val,
char defunit);
-
+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_strnstr(const char *s1, const char *s2, size_t len);
char *lprocfs_find_named_value(const char *buffer, const char *name,
size_t *count);
const char __user *buffer, size_t count,
loff_t *off)
{
- __s64 value;
+ u64 value;
__u64 watermark;
__u64 *data = ((struct seq_file *)file->private_data)->private;
bool wm_low = (data == &ldlm_reclaim_threshold_mb) ? true : false;
+ char kernbuf[22] = "";
int rc;
- rc = lprocfs_str_with_units_to_s64(buffer, count, &value, 'M');
- 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, &value, "MiB");
+ if (rc < 0) {
CERROR("Failed to set %s, rc = %d.\n",
wm_low ? "lock_reclaim_threshold_mb" : "lock_limit_mb",
rc);
struct dt_device *dt = container_of(kobj, struct dt_device,
dd_kobj);
struct lod_device *lod = dt2lod_dev(dt);
- char tbuf[22] = "";
- s64 val;
+ u64 val;
int rc;
- if (count > (sizeof(tbuf) - 1))
- return -EINVAL;
-
- memcpy(tbuf, buffer, count);
-
- rc = lu_str_to_s64(tbuf, count, &val, '1');
- if (rc)
+ rc = sysfs_memparse(buffer, count, &val, "B");
+ if (rc < 0)
return rc;
- if (val < 0)
- return -ERANGE;
-
/* 1GB is the limit */
if (val > (1ULL << 30))
return -ERANGE;
struct dt_device *dt = container_of(kobj, struct dt_device,
dd_kobj);
struct lod_device *lod = dt2lod_dev(dt);
- char tbuf[22] = "";
- s64 val;
+ u64 val;
int rc;
- if (count > (sizeof(tbuf) - 1))
- return -EINVAL;
-
- memcpy(tbuf, buffer, count);
-
- rc = lu_str_to_s64(tbuf, count, &val, '1');
- if (rc)
+ rc = sysfs_memparse(buffer, count, &val, "B");
+ if (rc < 0)
return rc;
- if (val < 0)
- return -ERANGE;
-
lod_fix_desc_stripe_size(&val);
lod->lod_ost_descs.ltd_lov_desc.ld_default_stripe_size = val;
{
struct obd_device *dev = ((struct seq_file *)file->private_data)->private;
struct lov_desc *desc;
- s64 val;
+ char kernbuf[22] = "";
+ u64 val;
int rc;
LASSERT(dev != NULL);
desc = &dev->u.lov.desc;
- rc = lprocfs_str_with_units_to_s64(buffer, count, &val, '1');
- 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, &val, "B");
+ if (rc < 0)
return rc;
- if (val < 0)
- return -ERANGE;
lov_fix_desc_stripe_size(&val);
desc->ld_default_stripe_size = val;
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_str_with_units_to_s64(buffer, count, &pages_number, 'M');
- 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 */
struct seq_file *sfl = file->private_data;
struct obd_device *dev = sfl->private;
struct client_obd *cli = &dev->u.cli;
- __s64 pages_number;
+ u64 pages_number;
+ const char *tmp;
long rc;
char kernbuf[128];
return -EFAULT;
kernbuf[count] = 0;
- buffer += lprocfs_find_named_value(kernbuf, "used_mb:", &count) -
- kernbuf;
- rc = lprocfs_str_with_units_to_s64(buffer, count, &pages_number, 'M');
- 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;
.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;
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;
}
}
EXPORT_SYMBOL(lprocfs_read_helper);
+/**
+ * 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, sizeof(param));
+ }
+ } 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);
+
/* Obtains the conversion factor for the unit specified */
static int get_mult(char unit, __u64 *mult)
{
struct seq_file *m = file->private_data;
struct obd_device *obd = m->private;
struct ofd_device *ofd = ofd_dev(obd->obd_lu_dev);
- __s64 val;
+ char kernbuf[22] = "";
+ u64 val;
int rc;
- rc = lprocfs_str_with_units_to_s64(buffer, count, &val, 'M');
- 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, &val, "MiB");
+ if (rc < 0)
return rc;
- if (val <= 0)
+ if (val == 0)
return -EINVAL;
if (val > DT_MAX_BRW_SIZE ||
{
struct obd_device *dev = ((struct seq_file *)file->private_data)->private;
struct client_obd *cli = &dev->u.cli;
- __s64 pages_number;
+ u64 pages_number;
+ const char *tmp;
long rc;
char kernbuf[128];
return -EFAULT;
kernbuf[count] = 0;
- buffer += lprocfs_find_named_value(kernbuf, "used_mb:", &count) -
- kernbuf;
- rc = lprocfs_str_with_units_to_s64(buffer, count, &pages_number, 'M');
- 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;
{
struct obd_device *obd = ((struct seq_file *)file->private_data)->private;
struct client_obd *cli = &obd->u.cli;
- s64 val;
+ char kernbuf[22] = "";
+ u64 val;
int rc;
if (obd == NULL)
return 0;
- rc = lprocfs_str_with_units_to_s64(buffer, count, &val, '1');
- 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, &val, "MiB");
+ if (rc < 0)
return rc;
- if (val < 0)
- return val;
/* this is only for shrinking grant */
spin_lock(&cli->cl_loi_list_lock);
struct seq_file *m = file->private_data;
struct dt_device *dt = m->private;
struct osd_device *osd = osd_dt_dev(dt);
+ char kernbuf[22] = "";
u64 val;
int rc;
if (unlikely(osd->od_mnt == NULL))
return -EINPROGRESS;
- rc = lprocfs_str_with_units_to_u64(buffer, count, &val, '1');
- 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, &val, "B");
+ if (rc < 0)
return rc;
osd->od_readcache_max_filesize = val > OSD_MAX_CACHE_SIZE ?
struct seq_file *m = file->private_data;
struct dt_device *dt = m->private;
struct osd_device *osd = osd_dt_dev(dt);
- s64 val;
+ char kernbuf[22] = "";
+ u64 val;
int rc;
LASSERT(osd != NULL);
if (unlikely(osd->od_mnt == NULL))
return -EINPROGRESS;
- rc = lprocfs_str_with_units_to_s64(buffer, count, &val, 'M');
- 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, &val, "MiB");
+ if (rc < 0)
return rc;
- if (val < 0)
- return -ERANGE;
if (val > PTLRPC_MAX_BRW_SIZE)
return -ERANGE;
struct seq_file *m = file->private_data;
struct dt_device *dt = m->private;
struct osd_device *osd = osd_dt_dev(dt);
- s64 val;
+ char kernbuf[22] = "";
+ u64 val;
int rc;
LASSERT(osd != NULL);
if (unlikely(osd->od_mnt == NULL))
return -EINPROGRESS;
- rc = lprocfs_str_with_units_to_s64(buffer, count, &val, 'M');
- 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, &val, "MiB");
+ if (rc < 0)
return rc;
- if (val < 0)
- return -ERANGE;
if (val > PTLRPC_MAX_BRW_SIZE)
return -ERANGE;
struct seq_file *m = file->private_data;
struct dt_device *dt = m->private;
struct osd_device *osd = osd_dt_dev(dt);
+ char kernbuf[22] = "";
u64 val;
int rc;
if (unlikely(osd->od_os == NULL))
return -EINPROGRESS;
- rc = lprocfs_str_with_units_to_u64(buffer, count, &val, '1');
- 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, &val, "B");
+ if (rc < 0)
return rc;
osd->od_readcache_max_filesize = val > OSD_MAX_CACHE_SIZE ?
struct seq_file *m = file->private_data;
struct obd_device *dev = m->private;
struct osp_device *osp = lu2osp_dev(dev->obd_lu_dev);
- __s64 val;
+ char kernbuf[22] = "";
+ u64 val;
int rc;
if (osp == NULL || osp->opd_pre == NULL)
return -EINVAL;
- rc = lprocfs_str_with_units_to_s64(buffer, count, &val, 'M');
- 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, &val, "MiB");
+ if (rc < 0)
return rc;
val >>= 20;
if (val < 1)
struct seq_file *m = file->private_data;
struct obd_device *dev = m->private;
struct osp_device *osp = lu2osp_dev(dev->obd_lu_dev);
- __s64 val;
+ char kernbuf[22] = "";
+ u64 val;
int rc;
if (osp == NULL || osp->opd_pre == NULL)
return -EINVAL;
- rc = lprocfs_str_with_units_to_s64(buffer, count, &val, 'M');
- 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, &val, "MiB");
+ if (rc < 0)
return rc;
val >>= 20;