Cannot modify /sys/block/bcache0/bcache/sequential_cutoff

I am following Tune bcache for large SSDs - Felix Mößbauer - Software Engineer to tune bcache.

I got error
tee: /sys/block/bcache0/bcache/sequential_cutoff: Invalid argument

doing
echo 64M | sudo tee /sys/block/bcache0/bcache/sequential_cutoff

While it is OK to
echo 4096 | sudo tee /sys/block/bcache0/queue/read_ahead_kb

I got similar error when using echo directly as well.

It looks like d_strtoi_h was replaced by sysfs_strtoul_clamp in February of 2019 ([09/19] bcache: fix input overflow to sequential_cutoff - Patchwork).

Following the call stack of sysfs_strtoul_clamp appears to lead to _parse_integer:

https://elixir.bootlin.com/linux/latest/source/drivers/md/bcache/sysfs.h#L92
https://elixir.bootlin.com/linux/latest/source/drivers/md/bcache/util.h#L336
https://elixir.bootlin.com/linux/latest/source/include/linux/kernel.h#L203
https://elixir.bootlin.com/linux/latest/source/arch/x86/boot/string.c#L331
https://elixir.bootlin.com/linux/latest/source/lib/kstrtox.c#L85
https://elixir.bootlin.com/linux/latest/source/lib/kstrtox.c#L40

_parse_integer has the comment “Don’t you dare use this function” and appears to stop parsing at any value outside of the range 0-f. It doesn’t appear to handle “M”.

/*
 * Convert non-negative integer string representation in explicitly given radix
 * to an integer.
 * Return number of characters consumed maybe or-ed with overflow bit.
 * If overflow occurs, result integer (incorrect) is still returned.
 *
 * Don't you dare use this function.
 */
unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *p)
{
	unsigned long long res;
	unsigned int rv;

	res = 0;
	rv = 0;
	while (1) {
		unsigned int c = *s;
		unsigned int lc = c | 0x20; /* don't tolower() this line */
		unsigned int val;

		if ('0' <= c && c <= '9')
			val = c - '0';
		else if ('a' <= lc && lc <= 'f')
			val = lc - 'a' + 10;
		else
			break;

		if (val >= base)
			break;
		/*
		 * Check for overflow only if we are within range of
		 * it in the max base we support (16)
		 */
		if (unlikely(res & (~0ull << 60))) {
			if (res > div_u64(ULLONG_MAX - val, base))
				rv |= KSTRTOX_OVERFLOW;
		}
		res = res * base + val;
		rv++;
		s++;
	}
	*p = res;
	return rv;
}

Since “M” is unparsed, The calling function (_kstrtoull) sees that there is still data in the string (*s) and returns “EINVAL”:

static int _kstrtoull(const char *s, unsigned int base, unsigned long long *res)
{
	unsigned long long _res;
	unsigned int rv;

	s = _parse_integer_fixup_radix(s, &base);
	rv = _parse_integer(s, base, &_res);
	if (rv & KSTRTOX_OVERFLOW)
		return -ERANGE;
	if (rv == 0)
		return -EINVAL;
	s += rv;
	if (*s == '\n')
		s++;
	if (*s)
		return -EINVAL;
	*res = _res;
	return 0;
}

P.S. As a workaround, it looks like there is a numfmt command that will do the conversion for you:

[/home/gregory]$ numfmt --from=iec 64M
67108864
1 Like

Thank you very much for your insights!

Indeed, using pure digits works.

$ echo $(numfmt --from=iec 64M) | sudo tee /sys/block/bcache0/bcache/sequential_cutoff
67108864

$ cat /sys/block/bcache0/bcache/sequential_cutoff 
64.0M

Interestingly when read it back, it returns “64.0M”.

Just FYI, you don’t need to embed it in echo like that since numfmt outputs to standard out anyway. Also, if it is something you think you might need from time-to-time, you can make an alias so you don’t have to remember the options:

alias nf='numfmt --from=iec'

Put the above line in a new file named /etc/profile.d/nf.sh and then all users on your system (including root) should have an nf command that will convert parameters like 64M (i.e. nf 64M).

1 Like

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.