printk changes for 5.14
-----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEESH4wyp42V4tXvYsjUqAMR0iAlPIFAmDa2mEACgkQUqAMR0iA lPKBiBAAgvhNnaRVR6/GBVrv5jYM8obJM7PHPxp8dh+ZRb1mDyL1ZDU2r7lmQjMD ORBN5eK6pXk/gVabXR5lY0B7vQ8phJmYO98Lk2E3n9ZTgMkTHQ5WOHzBpt93gd/y l9m00ZD0YcHrkmM1fq73FuZVEMzPk85cjTe8n6JeHJgSAdoOY/rl61Cn57ZHFIa4 DkpdNGkJaf77UIWOc8NLAXOdSD9NxSGycHXpU0q8QO9UFq+Le2qN4OPj3S1CNCO2 ciy+VcW1VQ/BesPPlBIk3ImHWPS4ty3n4EYFzNm+saElIaWxyhNBXAvcBAK/x9LK 3QibfBFwbS3sllhnk96Z24UaWWMg2AygbV2aqd3xMLpW3XD6q/MVnWGHfayhnmYj aNcWpldIjwDH4iZJ5vnD4KewQpYp+Jc5Hqv6UyIf1O8nEvvQubrDXjSDLLcbZFI1 m2cs9DTc5ezyX/DifBsViDbw8hPjJg7QAbRjVk1EfVQrH090mRQoSoQQI4QtuMEi pPiTALNG1HRKIoYrKxQMB43JvZ1zjaSbtNbW4JJ9Bm3kxFZ/Oa8NXzE5BOjeykZm bCePtc018GZaGNW0z/Zr460c/Tuaj8fZSzUOj9Xnw5Hv4G3W5+5EqDy7sU/GTPjL It9rAZYo+cM9vp1BD2343YPZgnChWHaW0BF/WDqFAhLd9av/WKI= =Oa1c -----END PGP SIGNATURE----- Merge tag 'printk-for-5.14' of git://git.kernel.org/pub/scm/linux/kernel/git/printk/linux Pull printk updates from Petr Mladek: - Add %pt[RT]s modifier to vsprintf(). It overrides ISO 8601 separator by using ' ' (space). It produces "YYYY-mm-dd HH:MM:SS" instead of "YYYY-mm-ddTHH:MM:SS". - Correctly parse long row of numbers by sscanf() when using the field width. Add extensive sscanf() selftest. - Generalize re-entrant CPU lock that has already been used to serialize dump_stack() output. It is part of the ongoing printk rework. It will allow to remove the obsoleted printk_safe buffers and introduce atomic consoles. - Some code clean up and sparse warning fixes. * tag 'printk-for-5.14' of git://git.kernel.org/pub/scm/linux/kernel/git/printk/linux: printk: fix cpu lock ordering lib/dump_stack: move cpu lock to printk.c printk: Remove trailing semicolon in macros random32: Fix implicit truncation warning in prandom_seed_state() lib: test_scanf: Remove pointless use of type_min() with unsigned types selftests: lib: Add wrapper script for test_scanf lib: test_scanf: Add tests for sscanf number conversion lib: vsprintf: Fix handling of number field widths in vsscanf lib: vsprintf: scanf: Negative number must have field width > 1 usb: host: xhci-tegra: Switch to use %ptTs nilfs2: Switch to use %ptTs kdb: Switch to use %ptTs lib/vsprintf: Allow to override ISO 8601 date and time separator
This commit is contained in:
commit
e563592c3e
@ -513,9 +513,10 @@ Time and date
|
||||
::
|
||||
|
||||
%pt[RT] YYYY-mm-ddTHH:MM:SS
|
||||
%pt[RT]s YYYY-mm-dd HH:MM:SS
|
||||
%pt[RT]d YYYY-mm-dd
|
||||
%pt[RT]t HH:MM:SS
|
||||
%pt[RT][dt][r]
|
||||
%pt[RT][dt][r][s]
|
||||
|
||||
For printing date and time as represented by::
|
||||
|
||||
@ -527,6 +528,10 @@ in human readable format.
|
||||
By default year will be incremented by 1900 and month by 1.
|
||||
Use %pt[RT]r (raw) to suppress this behaviour.
|
||||
|
||||
The %pt[RT]s (space) will override ISO 8601 separator by using ' ' (space)
|
||||
instead of 'T' (Capital T) between date and time. It won't have any effect
|
||||
when date or time is omitted.
|
||||
|
||||
Passed by reference.
|
||||
|
||||
struct clk
|
||||
|
@ -19629,6 +19629,7 @@ S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pmladek/printk.git
|
||||
F: Documentation/core-api/printk-formats.rst
|
||||
F: lib/test_printf.c
|
||||
F: lib/test_scanf.c
|
||||
F: lib/vsprintf.c
|
||||
|
||||
VT1211 HARDWARE MONITOR DRIVER
|
||||
|
@ -917,7 +917,6 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
|
||||
struct xhci_op_regs __iomem *op;
|
||||
unsigned long timeout;
|
||||
time64_t timestamp;
|
||||
struct tm time;
|
||||
u64 address;
|
||||
u32 value;
|
||||
int err;
|
||||
@ -1014,11 +1013,8 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
|
||||
}
|
||||
|
||||
timestamp = le32_to_cpu(header->fwimg_created_time);
|
||||
time64_to_tm(timestamp, 0, &time);
|
||||
|
||||
dev_info(dev, "Firmware timestamp: %ld-%02d-%02d %02d:%02d:%02d UTC\n",
|
||||
time.tm_year + 1900, time.tm_mon + 1, time.tm_mday,
|
||||
time.tm_hour, time.tm_min, time.tm_sec);
|
||||
dev_info(dev, "Firmware timestamp: %ptTs UTC\n", ×tamp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -19,19 +19,6 @@
|
||||
/* /sys/fs/<nilfs>/ */
|
||||
static struct kset *nilfs_kset;
|
||||
|
||||
#define NILFS_SHOW_TIME(time_t_val, buf) ({ \
|
||||
struct tm res; \
|
||||
int count = 0; \
|
||||
time64_to_tm(time_t_val, 0, &res); \
|
||||
res.tm_year += 1900; \
|
||||
res.tm_mon += 1; \
|
||||
count = scnprintf(buf, PAGE_SIZE, \
|
||||
"%ld-%.2d-%.2d %.2d:%.2d:%.2d\n", \
|
||||
res.tm_year, res.tm_mon, res.tm_mday, \
|
||||
res.tm_hour, res.tm_min, res.tm_sec);\
|
||||
count; \
|
||||
})
|
||||
|
||||
#define NILFS_DEV_INT_GROUP_OPS(name, parent_name) \
|
||||
static ssize_t nilfs_##name##_attr_show(struct kobject *kobj, \
|
||||
struct attribute *attr, char *buf) \
|
||||
@ -576,7 +563,7 @@ nilfs_segctor_last_seg_write_time_show(struct nilfs_segctor_attr *attr,
|
||||
ctime = nilfs->ns_ctime;
|
||||
up_read(&nilfs->ns_segctor_sem);
|
||||
|
||||
return NILFS_SHOW_TIME(ctime, buf);
|
||||
return sysfs_emit(buf, "%ptTs\n", &ctime);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
@ -604,7 +591,7 @@ nilfs_segctor_last_nongc_write_time_show(struct nilfs_segctor_attr *attr,
|
||||
nongc_ctime = nilfs->ns_nongc_ctime;
|
||||
up_read(&nilfs->ns_segctor_sem);
|
||||
|
||||
return NILFS_SHOW_TIME(nongc_ctime, buf);
|
||||
return sysfs_emit(buf, "%ptTs\n", &nongc_ctime);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
@ -724,7 +711,7 @@ nilfs_superblock_sb_write_time_show(struct nilfs_superblock_attr *attr,
|
||||
sbwtime = nilfs->ns_sbwtime;
|
||||
up_read(&nilfs->ns_sem);
|
||||
|
||||
return NILFS_SHOW_TIME(sbwtime, buf);
|
||||
return sysfs_emit(buf, "%ptTs\n", &sbwtime);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
|
@ -236,7 +236,7 @@ do { \
|
||||
* using WARN/WARN_ONCE to include file/line information and a backtrace.
|
||||
*/
|
||||
#define dev_WARN(dev, format, arg...) \
|
||||
WARN(1, "%s %s: " format, dev_driver_string(dev), dev_name(dev), ## arg);
|
||||
WARN(1, "%s %s: " format, dev_driver_string(dev), dev_name(dev), ## arg)
|
||||
|
||||
#define dev_WARN_ONCE(dev, condition, format, arg...) \
|
||||
WARN_ONCE(condition, "%s %s: " format, \
|
||||
|
@ -111,7 +111,7 @@ static inline u32 __seed(u32 x, u32 m)
|
||||
*/
|
||||
static inline void prandom_seed_state(struct rnd_state *state, u64 seed)
|
||||
{
|
||||
u32 i = (seed >> 32) ^ (seed << 10) ^ seed;
|
||||
u32 i = ((seed >> 32) ^ (seed << 10) ^ seed) & 0xffffffffUL;
|
||||
|
||||
state->s1 = __seed(i, 2U);
|
||||
state->s2 = __seed(i, 8U);
|
||||
|
@ -282,6 +282,47 @@ static inline void printk_safe_flush_on_panic(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
extern int __printk_cpu_trylock(void);
|
||||
extern void __printk_wait_on_cpu_lock(void);
|
||||
extern void __printk_cpu_unlock(void);
|
||||
|
||||
/**
|
||||
* printk_cpu_lock_irqsave() - Acquire the printk cpu-reentrant spinning
|
||||
* lock and disable interrupts.
|
||||
* @flags: Stack-allocated storage for saving local interrupt state,
|
||||
* to be passed to printk_cpu_unlock_irqrestore().
|
||||
*
|
||||
* If the lock is owned by another CPU, spin until it becomes available.
|
||||
* Interrupts are restored while spinning.
|
||||
*/
|
||||
#define printk_cpu_lock_irqsave(flags) \
|
||||
for (;;) { \
|
||||
local_irq_save(flags); \
|
||||
if (__printk_cpu_trylock()) \
|
||||
break; \
|
||||
local_irq_restore(flags); \
|
||||
__printk_wait_on_cpu_lock(); \
|
||||
}
|
||||
|
||||
/**
|
||||
* printk_cpu_unlock_irqrestore() - Release the printk cpu-reentrant spinning
|
||||
* lock and restore interrupts.
|
||||
* @flags: Caller's saved interrupt state, from printk_cpu_lock_irqsave().
|
||||
*/
|
||||
#define printk_cpu_unlock_irqrestore(flags) \
|
||||
do { \
|
||||
__printk_cpu_unlock(); \
|
||||
local_irq_restore(flags); \
|
||||
} while (0) \
|
||||
|
||||
#else
|
||||
|
||||
#define printk_cpu_lock_irqsave(flags) ((void)flags)
|
||||
#define printk_cpu_unlock_irqrestore(flags) ((void)flags)
|
||||
|
||||
#endif /* CONFIG_SMP */
|
||||
|
||||
extern int kptr_restrict;
|
||||
|
||||
/**
|
||||
|
@ -2488,7 +2488,6 @@ static void kdb_sysinfo(struct sysinfo *val)
|
||||
static int kdb_summary(int argc, const char **argv)
|
||||
{
|
||||
time64_t now;
|
||||
struct tm tm;
|
||||
struct sysinfo val;
|
||||
|
||||
if (argc)
|
||||
@ -2502,13 +2501,7 @@ static int kdb_summary(int argc, const char **argv)
|
||||
kdb_printf("domainname %s\n", init_uts_ns.name.domainname);
|
||||
|
||||
now = __ktime_get_real_seconds();
|
||||
time64_to_tm(now, 0, &tm);
|
||||
kdb_printf("date %04ld-%02d-%02d %02d:%02d:%02d "
|
||||
"tz_minuteswest %d\n",
|
||||
1900+tm.tm_year, tm.tm_mon+1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
sys_tz.tz_minuteswest);
|
||||
|
||||
kdb_printf("date %ptTs tz_minuteswest %d\n", &now, sys_tz.tz_minuteswest);
|
||||
kdb_sysinfo(&val);
|
||||
kdb_printf("uptime ");
|
||||
if (val.uptime > (24*60*60)) {
|
||||
|
@ -3531,3 +3531,119 @@ void kmsg_dump_rewind(struct kmsg_dump_iter *iter)
|
||||
EXPORT_SYMBOL_GPL(kmsg_dump_rewind);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
static atomic_t printk_cpulock_owner = ATOMIC_INIT(-1);
|
||||
static atomic_t printk_cpulock_nested = ATOMIC_INIT(0);
|
||||
|
||||
/**
|
||||
* __printk_wait_on_cpu_lock() - Busy wait until the printk cpu-reentrant
|
||||
* spinning lock is not owned by any CPU.
|
||||
*
|
||||
* Context: Any context.
|
||||
*/
|
||||
void __printk_wait_on_cpu_lock(void)
|
||||
{
|
||||
do {
|
||||
cpu_relax();
|
||||
} while (atomic_read(&printk_cpulock_owner) != -1);
|
||||
}
|
||||
EXPORT_SYMBOL(__printk_wait_on_cpu_lock);
|
||||
|
||||
/**
|
||||
* __printk_cpu_trylock() - Try to acquire the printk cpu-reentrant
|
||||
* spinning lock.
|
||||
*
|
||||
* If no processor has the lock, the calling processor takes the lock and
|
||||
* becomes the owner. If the calling processor is already the owner of the
|
||||
* lock, this function succeeds immediately.
|
||||
*
|
||||
* Context: Any context. Expects interrupts to be disabled.
|
||||
* Return: 1 on success, otherwise 0.
|
||||
*/
|
||||
int __printk_cpu_trylock(void)
|
||||
{
|
||||
int cpu;
|
||||
int old;
|
||||
|
||||
cpu = smp_processor_id();
|
||||
|
||||
/*
|
||||
* Guarantee loads and stores from this CPU when it is the lock owner
|
||||
* are _not_ visible to the previous lock owner. This pairs with
|
||||
* __printk_cpu_unlock:B.
|
||||
*
|
||||
* Memory barrier involvement:
|
||||
*
|
||||
* If __printk_cpu_trylock:A reads from __printk_cpu_unlock:B, then
|
||||
* __printk_cpu_unlock:A can never read from __printk_cpu_trylock:B.
|
||||
*
|
||||
* Relies on:
|
||||
*
|
||||
* RELEASE from __printk_cpu_unlock:A to __printk_cpu_unlock:B
|
||||
* of the previous CPU
|
||||
* matching
|
||||
* ACQUIRE from __printk_cpu_trylock:A to __printk_cpu_trylock:B
|
||||
* of this CPU
|
||||
*/
|
||||
old = atomic_cmpxchg_acquire(&printk_cpulock_owner, -1,
|
||||
cpu); /* LMM(__printk_cpu_trylock:A) */
|
||||
if (old == -1) {
|
||||
/*
|
||||
* This CPU is now the owner and begins loading/storing
|
||||
* data: LMM(__printk_cpu_trylock:B)
|
||||
*/
|
||||
return 1;
|
||||
|
||||
} else if (old == cpu) {
|
||||
/* This CPU is already the owner. */
|
||||
atomic_inc(&printk_cpulock_nested);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(__printk_cpu_trylock);
|
||||
|
||||
/**
|
||||
* __printk_cpu_unlock() - Release the printk cpu-reentrant spinning lock.
|
||||
*
|
||||
* The calling processor must be the owner of the lock.
|
||||
*
|
||||
* Context: Any context. Expects interrupts to be disabled.
|
||||
*/
|
||||
void __printk_cpu_unlock(void)
|
||||
{
|
||||
if (atomic_read(&printk_cpulock_nested)) {
|
||||
atomic_dec(&printk_cpulock_nested);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* This CPU is finished loading/storing data:
|
||||
* LMM(__printk_cpu_unlock:A)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Guarantee loads and stores from this CPU when it was the
|
||||
* lock owner are visible to the next lock owner. This pairs
|
||||
* with __printk_cpu_trylock:A.
|
||||
*
|
||||
* Memory barrier involvement:
|
||||
*
|
||||
* If __printk_cpu_trylock:A reads from __printk_cpu_unlock:B,
|
||||
* then __printk_cpu_trylock:B reads from __printk_cpu_unlock:A.
|
||||
*
|
||||
* Relies on:
|
||||
*
|
||||
* RELEASE from __printk_cpu_unlock:A to __printk_cpu_unlock:B
|
||||
* of this CPU
|
||||
* matching
|
||||
* ACQUIRE from __printk_cpu_trylock:A to __printk_cpu_trylock:B
|
||||
* of the next CPU
|
||||
*/
|
||||
atomic_set_release(&printk_cpulock_owner,
|
||||
-1); /* LMM(__printk_cpu_unlock:B) */
|
||||
}
|
||||
EXPORT_SYMBOL(__printk_cpu_unlock);
|
||||
#endif /* CONFIG_SMP */
|
||||
|
@ -2180,6 +2180,9 @@ config TEST_KSTRTOX
|
||||
config TEST_PRINTF
|
||||
tristate "Test printf() family of functions at runtime"
|
||||
|
||||
config TEST_SCANF
|
||||
tristate "Test scanf() family of functions at runtime"
|
||||
|
||||
config TEST_BITMAP
|
||||
tristate "Test bitmap_*() family of functions at runtime"
|
||||
help
|
||||
|
@ -83,6 +83,7 @@ obj-$(CONFIG_TEST_USER_COPY) += test_user_copy.o
|
||||
obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_keys.o
|
||||
obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_key_base.o
|
||||
obj-$(CONFIG_TEST_PRINTF) += test_printf.o
|
||||
obj-$(CONFIG_TEST_SCANF) += test_scanf.o
|
||||
obj-$(CONFIG_TEST_BITMAP) += test_bitmap.o
|
||||
obj-$(CONFIG_TEST_STRSCPY) += test_strscpy.o
|
||||
obj-$(CONFIG_TEST_UUID) += test_uuid.o
|
||||
|
@ -84,50 +84,16 @@ static void __dump_stack(void)
|
||||
*
|
||||
* Architectures can override this implementation by implementing its own.
|
||||
*/
|
||||
#ifdef CONFIG_SMP
|
||||
static atomic_t dump_lock = ATOMIC_INIT(-1);
|
||||
|
||||
asmlinkage __visible void dump_stack(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
int was_locked;
|
||||
int old;
|
||||
int cpu;
|
||||
|
||||
/*
|
||||
* Permit this cpu to perform nested stack dumps while serialising
|
||||
* against other CPUs
|
||||
*/
|
||||
retry:
|
||||
local_irq_save(flags);
|
||||
cpu = smp_processor_id();
|
||||
old = atomic_cmpxchg(&dump_lock, -1, cpu);
|
||||
if (old == -1) {
|
||||
was_locked = 0;
|
||||
} else if (old == cpu) {
|
||||
was_locked = 1;
|
||||
} else {
|
||||
local_irq_restore(flags);
|
||||
/*
|
||||
* Wait for the lock to release before jumping to
|
||||
* atomic_cmpxchg() in order to mitigate the thundering herd
|
||||
* problem.
|
||||
*/
|
||||
do { cpu_relax(); } while (atomic_read(&dump_lock) != -1);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
printk_cpu_lock_irqsave(flags);
|
||||
__dump_stack();
|
||||
|
||||
if (!was_locked)
|
||||
atomic_set(&dump_lock, -1);
|
||||
|
||||
local_irq_restore(flags);
|
||||
printk_cpu_unlock_irqrestore(flags);
|
||||
}
|
||||
#else
|
||||
asmlinkage __visible void dump_stack(void)
|
||||
{
|
||||
__dump_stack();
|
||||
}
|
||||
#endif
|
||||
EXPORT_SYMBOL(dump_stack);
|
||||
|
@ -39,20 +39,22 @@ const char *_parse_integer_fixup_radix(const char *s, unsigned int *base)
|
||||
|
||||
/*
|
||||
* Convert non-negative integer string representation in explicitly given radix
|
||||
* to an integer.
|
||||
* to an integer. A maximum of max_chars characters will be converted.
|
||||
*
|
||||
* 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 int _parse_integer_limit(const char *s, unsigned int base, unsigned long long *p,
|
||||
size_t max_chars)
|
||||
{
|
||||
unsigned long long res;
|
||||
unsigned int rv;
|
||||
|
||||
res = 0;
|
||||
rv = 0;
|
||||
while (1) {
|
||||
while (max_chars--) {
|
||||
unsigned int c = *s;
|
||||
unsigned int lc = c | 0x20; /* don't tolower() this line */
|
||||
unsigned int val;
|
||||
@ -82,6 +84,11 @@ unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long
|
||||
return rv;
|
||||
}
|
||||
|
||||
unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *p)
|
||||
{
|
||||
return _parse_integer_limit(s, base, p, INT_MAX);
|
||||
}
|
||||
|
||||
static int _kstrtoull(const char *s, unsigned int base, unsigned long long *res)
|
||||
{
|
||||
unsigned long long _res;
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#define KSTRTOX_OVERFLOW (1U << 31)
|
||||
const char *_parse_integer_fixup_radix(const char *s, unsigned int *base);
|
||||
unsigned int _parse_integer_limit(const char *s, unsigned int base, unsigned long long *res,
|
||||
size_t max_chars);
|
||||
unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *res);
|
||||
|
||||
#endif
|
||||
|
@ -528,6 +528,11 @@ time_and_date(void)
|
||||
test("0119-00-04T15:32:23", "%ptTr", &t);
|
||||
test("15:32:23|2019-01-04", "%ptTt|%ptTd", &t, &t);
|
||||
test("15:32:23|0119-00-04", "%ptTtr|%ptTdr", &t, &t);
|
||||
|
||||
test("2019-01-04 15:32:23", "%ptTs", &t);
|
||||
test("0119-00-04 15:32:23", "%ptTsr", &t);
|
||||
test("15:32:23|2019-01-04", "%ptTts|%ptTds", &t, &t);
|
||||
test("15:32:23|0119-00-04", "%ptTtrs|%ptTdrs", &t, &t);
|
||||
}
|
||||
|
||||
static void __init
|
||||
|
750
lib/test_scanf.c
Normal file
750
lib/test_scanf.c
Normal file
@ -0,0 +1,750 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Test cases for sscanf facility.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include "../tools/testing/selftests/kselftest_module.h"
|
||||
|
||||
#define BUF_SIZE 1024
|
||||
|
||||
KSTM_MODULE_GLOBALS();
|
||||
static char *test_buffer __initdata;
|
||||
static char *fmt_buffer __initdata;
|
||||
static struct rnd_state rnd_state __initdata;
|
||||
|
||||
typedef int (*check_fn)(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap);
|
||||
|
||||
static void __scanf(4, 6) __init
|
||||
_test(check_fn fn, const void *check_data, const char *string, const char *fmt,
|
||||
int n_args, ...)
|
||||
{
|
||||
va_list ap, ap_copy;
|
||||
int ret;
|
||||
|
||||
total_tests++;
|
||||
|
||||
va_start(ap, n_args);
|
||||
va_copy(ap_copy, ap);
|
||||
ret = vsscanf(string, fmt, ap_copy);
|
||||
va_end(ap_copy);
|
||||
|
||||
if (ret != n_args) {
|
||||
pr_warn("vsscanf(\"%s\", \"%s\", ...) returned %d expected %d\n",
|
||||
string, fmt, ret, n_args);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = (*fn)(check_data, string, fmt, n_args, ap);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
va_end(ap);
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
failed_tests++;
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#define _check_numbers_template(arg_fmt, expect, str, fmt, n_args, ap) \
|
||||
do { \
|
||||
pr_debug("\"%s\", \"%s\" ->\n", str, fmt); \
|
||||
for (; n_args > 0; n_args--, expect++) { \
|
||||
typeof(*expect) got = *va_arg(ap, typeof(expect)); \
|
||||
pr_debug("\t" arg_fmt "\n", got); \
|
||||
if (got != *expect) { \
|
||||
pr_warn("vsscanf(\"%s\", \"%s\", ...) expected " arg_fmt " got " arg_fmt "\n", \
|
||||
str, fmt, *expect, got); \
|
||||
return 1; \
|
||||
} \
|
||||
} \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
static int __init check_ull(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap)
|
||||
{
|
||||
const unsigned long long *pval = check_data;
|
||||
|
||||
_check_numbers_template("%llu", pval, string, fmt, n_args, ap);
|
||||
}
|
||||
|
||||
static int __init check_ll(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap)
|
||||
{
|
||||
const long long *pval = check_data;
|
||||
|
||||
_check_numbers_template("%lld", pval, string, fmt, n_args, ap);
|
||||
}
|
||||
|
||||
static int __init check_ulong(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap)
|
||||
{
|
||||
const unsigned long *pval = check_data;
|
||||
|
||||
_check_numbers_template("%lu", pval, string, fmt, n_args, ap);
|
||||
}
|
||||
|
||||
static int __init check_long(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap)
|
||||
{
|
||||
const long *pval = check_data;
|
||||
|
||||
_check_numbers_template("%ld", pval, string, fmt, n_args, ap);
|
||||
}
|
||||
|
||||
static int __init check_uint(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap)
|
||||
{
|
||||
const unsigned int *pval = check_data;
|
||||
|
||||
_check_numbers_template("%u", pval, string, fmt, n_args, ap);
|
||||
}
|
||||
|
||||
static int __init check_int(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap)
|
||||
{
|
||||
const int *pval = check_data;
|
||||
|
||||
_check_numbers_template("%d", pval, string, fmt, n_args, ap);
|
||||
}
|
||||
|
||||
static int __init check_ushort(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap)
|
||||
{
|
||||
const unsigned short *pval = check_data;
|
||||
|
||||
_check_numbers_template("%hu", pval, string, fmt, n_args, ap);
|
||||
}
|
||||
|
||||
static int __init check_short(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap)
|
||||
{
|
||||
const short *pval = check_data;
|
||||
|
||||
_check_numbers_template("%hd", pval, string, fmt, n_args, ap);
|
||||
}
|
||||
|
||||
static int __init check_uchar(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap)
|
||||
{
|
||||
const unsigned char *pval = check_data;
|
||||
|
||||
_check_numbers_template("%hhu", pval, string, fmt, n_args, ap);
|
||||
}
|
||||
|
||||
static int __init check_char(const void *check_data, const char *string,
|
||||
const char *fmt, int n_args, va_list ap)
|
||||
{
|
||||
const signed char *pval = check_data;
|
||||
|
||||
_check_numbers_template("%hhd", pval, string, fmt, n_args, ap);
|
||||
}
|
||||
|
||||
/* Selection of interesting numbers to test, copied from test-kstrtox.c */
|
||||
static const unsigned long long numbers[] __initconst = {
|
||||
0x0ULL,
|
||||
0x1ULL,
|
||||
0x7fULL,
|
||||
0x80ULL,
|
||||
0x81ULL,
|
||||
0xffULL,
|
||||
0x100ULL,
|
||||
0x101ULL,
|
||||
0x7fffULL,
|
||||
0x8000ULL,
|
||||
0x8001ULL,
|
||||
0xffffULL,
|
||||
0x10000ULL,
|
||||
0x10001ULL,
|
||||
0x7fffffffULL,
|
||||
0x80000000ULL,
|
||||
0x80000001ULL,
|
||||
0xffffffffULL,
|
||||
0x100000000ULL,
|
||||
0x100000001ULL,
|
||||
0x7fffffffffffffffULL,
|
||||
0x8000000000000000ULL,
|
||||
0x8000000000000001ULL,
|
||||
0xfffffffffffffffeULL,
|
||||
0xffffffffffffffffULL,
|
||||
};
|
||||
|
||||
#define value_representable_in_type(T, val) \
|
||||
(is_signed_type(T) \
|
||||
? ((long long)(val) >= type_min(T)) && ((long long)(val) <= type_max(T)) \
|
||||
: ((unsigned long long)(val) <= type_max(T)))
|
||||
|
||||
|
||||
#define test_one_number(T, gen_fmt, scan_fmt, val, fn) \
|
||||
do { \
|
||||
const T expect_val = (T)(val); \
|
||||
T result = ~expect_val; /* should be overwritten */ \
|
||||
\
|
||||
snprintf(test_buffer, BUF_SIZE, gen_fmt, expect_val); \
|
||||
_test(fn, &expect_val, test_buffer, "%" scan_fmt, 1, &result); \
|
||||
} while (0)
|
||||
|
||||
#define simple_numbers_loop(T, gen_fmt, scan_fmt, fn) \
|
||||
do { \
|
||||
int i; \
|
||||
\
|
||||
for (i = 0; i < ARRAY_SIZE(numbers); i++) { \
|
||||
if (value_representable_in_type(T, numbers[i])) \
|
||||
test_one_number(T, gen_fmt, scan_fmt, \
|
||||
numbers[i], fn); \
|
||||
\
|
||||
if (value_representable_in_type(T, -numbers[i])) \
|
||||
test_one_number(T, gen_fmt, scan_fmt, \
|
||||
-numbers[i], fn); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void __init numbers_simple(void)
|
||||
{
|
||||
simple_numbers_loop(unsigned long long, "%llu", "llu", check_ull);
|
||||
simple_numbers_loop(long long, "%lld", "lld", check_ll);
|
||||
simple_numbers_loop(long long, "%lld", "lli", check_ll);
|
||||
simple_numbers_loop(unsigned long long, "%llx", "llx", check_ull);
|
||||
simple_numbers_loop(long long, "%llx", "llx", check_ll);
|
||||
simple_numbers_loop(long long, "0x%llx", "lli", check_ll);
|
||||
simple_numbers_loop(unsigned long long, "0x%llx", "llx", check_ull);
|
||||
simple_numbers_loop(long long, "0x%llx", "llx", check_ll);
|
||||
|
||||
simple_numbers_loop(unsigned long, "%lu", "lu", check_ulong);
|
||||
simple_numbers_loop(long, "%ld", "ld", check_long);
|
||||
simple_numbers_loop(long, "%ld", "li", check_long);
|
||||
simple_numbers_loop(unsigned long, "%lx", "lx", check_ulong);
|
||||
simple_numbers_loop(long, "%lx", "lx", check_long);
|
||||
simple_numbers_loop(long, "0x%lx", "li", check_long);
|
||||
simple_numbers_loop(unsigned long, "0x%lx", "lx", check_ulong);
|
||||
simple_numbers_loop(long, "0x%lx", "lx", check_long);
|
||||
|
||||
simple_numbers_loop(unsigned int, "%u", "u", check_uint);
|
||||
simple_numbers_loop(int, "%d", "d", check_int);
|
||||
simple_numbers_loop(int, "%d", "i", check_int);
|
||||
simple_numbers_loop(unsigned int, "%x", "x", check_uint);
|
||||
simple_numbers_loop(int, "%x", "x", check_int);
|
||||
simple_numbers_loop(int, "0x%x", "i", check_int);
|
||||
simple_numbers_loop(unsigned int, "0x%x", "x", check_uint);
|
||||
simple_numbers_loop(int, "0x%x", "x", check_int);
|
||||
|
||||
simple_numbers_loop(unsigned short, "%hu", "hu", check_ushort);
|
||||
simple_numbers_loop(short, "%hd", "hd", check_short);
|
||||
simple_numbers_loop(short, "%hd", "hi", check_short);
|
||||
simple_numbers_loop(unsigned short, "%hx", "hx", check_ushort);
|
||||
simple_numbers_loop(short, "%hx", "hx", check_short);
|
||||
simple_numbers_loop(short, "0x%hx", "hi", check_short);
|
||||
simple_numbers_loop(unsigned short, "0x%hx", "hx", check_ushort);
|
||||
simple_numbers_loop(short, "0x%hx", "hx", check_short);
|
||||
|
||||
simple_numbers_loop(unsigned char, "%hhu", "hhu", check_uchar);
|
||||
simple_numbers_loop(signed char, "%hhd", "hhd", check_char);
|
||||
simple_numbers_loop(signed char, "%hhd", "hhi", check_char);
|
||||
simple_numbers_loop(unsigned char, "%hhx", "hhx", check_uchar);
|
||||
simple_numbers_loop(signed char, "%hhx", "hhx", check_char);
|
||||
simple_numbers_loop(signed char, "0x%hhx", "hhi", check_char);
|
||||
simple_numbers_loop(unsigned char, "0x%hhx", "hhx", check_uchar);
|
||||
simple_numbers_loop(signed char, "0x%hhx", "hhx", check_char);
|
||||
}
|
||||
|
||||
/*
|
||||
* This gives a better variety of number "lengths" in a small sample than
|
||||
* the raw prandom*() functions (Not mathematically rigorous!!).
|
||||
* Variabilty of length and value is more important than perfect randomness.
|
||||
*/
|
||||
static u32 __init next_test_random(u32 max_bits)
|
||||
{
|
||||
u32 n_bits = hweight32(prandom_u32_state(&rnd_state)) % (max_bits + 1);
|
||||
|
||||
return prandom_u32_state(&rnd_state) & (UINT_MAX >> (32 - n_bits));
|
||||
}
|
||||
|
||||
static unsigned long long __init next_test_random_ull(void)
|
||||
{
|
||||
u32 rand1 = prandom_u32_state(&rnd_state);
|
||||
u32 n_bits = (hweight32(rand1) * 3) % 64;
|
||||
u64 val = (u64)prandom_u32_state(&rnd_state) * rand1;
|
||||
|
||||
return val & (ULLONG_MAX >> (64 - n_bits));
|
||||
}
|
||||
|
||||
#define random_for_type(T) \
|
||||
((T)(sizeof(T) <= sizeof(u32) \
|
||||
? next_test_random(BITS_PER_TYPE(T)) \
|
||||
: next_test_random_ull()))
|
||||
|
||||
/*
|
||||
* Define a pattern of negative and positive numbers to ensure we get
|
||||
* some of both within the small number of samples in a test string.
|
||||
*/
|
||||
#define NEGATIVES_PATTERN 0x3246 /* 00110010 01000110 */
|
||||
|
||||
#define fill_random_array(arr) \
|
||||
do { \
|
||||
unsigned int neg_pattern = NEGATIVES_PATTERN; \
|
||||
int i; \
|
||||
\
|
||||
for (i = 0; i < ARRAY_SIZE(arr); i++, neg_pattern >>= 1) { \
|
||||
(arr)[i] = random_for_type(typeof((arr)[0])); \
|
||||
if (is_signed_type(typeof((arr)[0])) && (neg_pattern & 1)) \
|
||||
(arr)[i] = -(arr)[i]; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Convenience wrapper around snprintf() to append at buf_pos in buf,
|
||||
* updating buf_pos and returning the number of characters appended.
|
||||
* On error buf_pos is not changed and return value is 0.
|
||||
*/
|
||||
static int __init __printf(4, 5)
|
||||
append_fmt(char *buf, int *buf_pos, int buf_len, const char *val_fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int field_len;
|
||||
|
||||
va_start(ap, val_fmt);
|
||||
field_len = vsnprintf(buf + *buf_pos, buf_len - *buf_pos, val_fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (field_len < 0)
|
||||
field_len = 0;
|
||||
|
||||
*buf_pos += field_len;
|
||||
|
||||
return field_len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convenience function to append the field delimiter string
|
||||
* to both the value string and format string buffers.
|
||||
*/
|
||||
static void __init append_delim(char *str_buf, int *str_buf_pos, int str_buf_len,
|
||||
char *fmt_buf, int *fmt_buf_pos, int fmt_buf_len,
|
||||
const char *delim_str)
|
||||
{
|
||||
append_fmt(str_buf, str_buf_pos, str_buf_len, delim_str);
|
||||
append_fmt(fmt_buf, fmt_buf_pos, fmt_buf_len, delim_str);
|
||||
}
|
||||
|
||||
#define test_array_8(fn, check_data, string, fmt, arr) \
|
||||
do { \
|
||||
BUILD_BUG_ON(ARRAY_SIZE(arr) != 8); \
|
||||
_test(fn, check_data, string, fmt, 8, \
|
||||
&(arr)[0], &(arr)[1], &(arr)[2], &(arr)[3], \
|
||||
&(arr)[4], &(arr)[5], &(arr)[6], &(arr)[7]); \
|
||||
} while (0)
|
||||
|
||||
#define numbers_list_8(T, gen_fmt, field_sep, scan_fmt, fn) \
|
||||
do { \
|
||||
int i, pos = 0, fmt_pos = 0; \
|
||||
T expect[8], result[8]; \
|
||||
\
|
||||
fill_random_array(expect); \
|
||||
\
|
||||
for (i = 0; i < ARRAY_SIZE(expect); i++) { \
|
||||
if (i != 0) \
|
||||
append_delim(test_buffer, &pos, BUF_SIZE, \
|
||||
fmt_buffer, &fmt_pos, BUF_SIZE, \
|
||||
field_sep); \
|
||||
\
|
||||
append_fmt(test_buffer, &pos, BUF_SIZE, gen_fmt, expect[i]); \
|
||||
append_fmt(fmt_buffer, &fmt_pos, BUF_SIZE, "%%%s", scan_fmt); \
|
||||
} \
|
||||
\
|
||||
test_array_8(fn, expect, test_buffer, fmt_buffer, result); \
|
||||
} while (0)
|
||||
|
||||
#define numbers_list_fix_width(T, gen_fmt, field_sep, width, scan_fmt, fn) \
|
||||
do { \
|
||||
char full_fmt[16]; \
|
||||
\
|
||||
snprintf(full_fmt, sizeof(full_fmt), "%u%s", width, scan_fmt); \
|
||||
numbers_list_8(T, gen_fmt, field_sep, full_fmt, fn); \
|
||||
} while (0)
|
||||
|
||||
#define numbers_list_val_width(T, gen_fmt, field_sep, scan_fmt, fn) \
|
||||
do { \
|
||||
int i, val_len, pos = 0, fmt_pos = 0; \
|
||||
T expect[8], result[8]; \
|
||||
\
|
||||
fill_random_array(expect); \
|
||||
\
|
||||
for (i = 0; i < ARRAY_SIZE(expect); i++) { \
|
||||
if (i != 0) \
|
||||
append_delim(test_buffer, &pos, BUF_SIZE, \
|
||||
fmt_buffer, &fmt_pos, BUF_SIZE, field_sep);\
|
||||
\
|
||||
val_len = append_fmt(test_buffer, &pos, BUF_SIZE, gen_fmt, \
|
||||
expect[i]); \
|
||||
append_fmt(fmt_buffer, &fmt_pos, BUF_SIZE, \
|
||||
"%%%u%s", val_len, scan_fmt); \
|
||||
} \
|
||||
\
|
||||
test_array_8(fn, expect, test_buffer, fmt_buffer, result); \
|
||||
} while (0)
|
||||
|
||||
static void __init numbers_list(const char *delim)
|
||||
{
|
||||
numbers_list_8(unsigned long long, "%llu", delim, "llu", check_ull);
|
||||
numbers_list_8(long long, "%lld", delim, "lld", check_ll);
|
||||
numbers_list_8(long long, "%lld", delim, "lli", check_ll);
|
||||
numbers_list_8(unsigned long long, "%llx", delim, "llx", check_ull);
|
||||
numbers_list_8(unsigned long long, "0x%llx", delim, "llx", check_ull);
|
||||
numbers_list_8(long long, "0x%llx", delim, "lli", check_ll);
|
||||
|
||||
numbers_list_8(unsigned long, "%lu", delim, "lu", check_ulong);
|
||||
numbers_list_8(long, "%ld", delim, "ld", check_long);
|
||||
numbers_list_8(long, "%ld", delim, "li", check_long);
|
||||
numbers_list_8(unsigned long, "%lx", delim, "lx", check_ulong);
|
||||
numbers_list_8(unsigned long, "0x%lx", delim, "lx", check_ulong);
|
||||
numbers_list_8(long, "0x%lx", delim, "li", check_long);
|
||||
|
||||
numbers_list_8(unsigned int, "%u", delim, "u", check_uint);
|
||||
numbers_list_8(int, "%d", delim, "d", check_int);
|
||||
numbers_list_8(int, "%d", delim, "i", check_int);
|
||||
numbers_list_8(unsigned int, "%x", delim, "x", check_uint);
|
||||
numbers_list_8(unsigned int, "0x%x", delim, "x", check_uint);
|
||||
numbers_list_8(int, "0x%x", delim, "i", check_int);
|
||||
|
||||
numbers_list_8(unsigned short, "%hu", delim, "hu", check_ushort);
|
||||
numbers_list_8(short, "%hd", delim, "hd", check_short);
|
||||
numbers_list_8(short, "%hd", delim, "hi", check_short);
|
||||
numbers_list_8(unsigned short, "%hx", delim, "hx", check_ushort);
|
||||
numbers_list_8(unsigned short, "0x%hx", delim, "hx", check_ushort);
|
||||
numbers_list_8(short, "0x%hx", delim, "hi", check_short);
|
||||
|
||||
numbers_list_8(unsigned char, "%hhu", delim, "hhu", check_uchar);
|
||||
numbers_list_8(signed char, "%hhd", delim, "hhd", check_char);
|
||||
numbers_list_8(signed char, "%hhd", delim, "hhi", check_char);
|
||||
numbers_list_8(unsigned char, "%hhx", delim, "hhx", check_uchar);
|
||||
numbers_list_8(unsigned char, "0x%hhx", delim, "hhx", check_uchar);
|
||||
numbers_list_8(signed char, "0x%hhx", delim, "hhi", check_char);
|
||||
}
|
||||
|
||||
/*
|
||||
* List of numbers separated by delim. Each field width specifier is the
|
||||
* maximum possible digits for the given type and base.
|
||||
*/
|
||||
static void __init numbers_list_field_width_typemax(const char *delim)
|
||||
{
|
||||
numbers_list_fix_width(unsigned long long, "%llu", delim, 20, "llu", check_ull);
|
||||
numbers_list_fix_width(long long, "%lld", delim, 20, "lld", check_ll);
|
||||
numbers_list_fix_width(long long, "%lld", delim, 20, "lli", check_ll);
|
||||
numbers_list_fix_width(unsigned long long, "%llx", delim, 16, "llx", check_ull);
|
||||
numbers_list_fix_width(unsigned long long, "0x%llx", delim, 18, "llx", check_ull);
|
||||
numbers_list_fix_width(long long, "0x%llx", delim, 18, "lli", check_ll);
|
||||
|
||||
#if BITS_PER_LONG == 64
|
||||
numbers_list_fix_width(unsigned long, "%lu", delim, 20, "lu", check_ulong);
|
||||
numbers_list_fix_width(long, "%ld", delim, 20, "ld", check_long);
|
||||
numbers_list_fix_width(long, "%ld", delim, 20, "li", check_long);
|
||||
numbers_list_fix_width(unsigned long, "%lx", delim, 16, "lx", check_ulong);
|
||||
numbers_list_fix_width(unsigned long, "0x%lx", delim, 18, "lx", check_ulong);
|
||||
numbers_list_fix_width(long, "0x%lx", delim, 18, "li", check_long);
|
||||
#else
|
||||
numbers_list_fix_width(unsigned long, "%lu", delim, 10, "lu", check_ulong);
|
||||
numbers_list_fix_width(long, "%ld", delim, 11, "ld", check_long);
|
||||
numbers_list_fix_width(long, "%ld", delim, 11, "li", check_long);
|
||||
numbers_list_fix_width(unsigned long, "%lx", delim, 8, "lx", check_ulong);
|
||||
numbers_list_fix_width(unsigned long, "0x%lx", delim, 10, "lx", check_ulong);
|
||||
numbers_list_fix_width(long, "0x%lx", delim, 10, "li", check_long);
|
||||
#endif
|
||||
|
||||
numbers_list_fix_width(unsigned int, "%u", delim, 10, "u", check_uint);
|
||||
numbers_list_fix_width(int, "%d", delim, 11, "d", check_int);
|
||||
numbers_list_fix_width(int, "%d", delim, 11, "i", check_int);
|
||||
numbers_list_fix_width(unsigned int, "%x", delim, 8, "x", check_uint);
|
||||
numbers_list_fix_width(unsigned int, "0x%x", delim, 10, "x", check_uint);
|
||||
numbers_list_fix_width(int, "0x%x", delim, 10, "i", check_int);
|
||||
|
||||
numbers_list_fix_width(unsigned short, "%hu", delim, 5, "hu", check_ushort);
|
||||
numbers_list_fix_width(short, "%hd", delim, 6, "hd", check_short);
|
||||
numbers_list_fix_width(short, "%hd", delim, 6, "hi", check_short);
|
||||
numbers_list_fix_width(unsigned short, "%hx", delim, 4, "hx", check_ushort);
|
||||
numbers_list_fix_width(unsigned short, "0x%hx", delim, 6, "hx", check_ushort);
|
||||
numbers_list_fix_width(short, "0x%hx", delim, 6, "hi", check_short);
|
||||
|
||||
numbers_list_fix_width(unsigned char, "%hhu", delim, 3, "hhu", check_uchar);
|
||||
numbers_list_fix_width(signed char, "%hhd", delim, 4, "hhd", check_char);
|
||||
numbers_list_fix_width(signed char, "%hhd", delim, 4, "hhi", check_char);
|
||||
numbers_list_fix_width(unsigned char, "%hhx", delim, 2, "hhx", check_uchar);
|
||||
numbers_list_fix_width(unsigned char, "0x%hhx", delim, 4, "hhx", check_uchar);
|
||||
numbers_list_fix_width(signed char, "0x%hhx", delim, 4, "hhi", check_char);
|
||||
}
|
||||
|
||||
/*
|
||||
* List of numbers separated by delim. Each field width specifier is the
|
||||
* exact length of the corresponding value digits in the string being scanned.
|
||||
*/
|
||||
static void __init numbers_list_field_width_val_width(const char *delim)
|
||||
{
|
||||
numbers_list_val_width(unsigned long long, "%llu", delim, "llu", check_ull);
|
||||
numbers_list_val_width(long long, "%lld", delim, "lld", check_ll);
|
||||
numbers_list_val_width(long long, "%lld", delim, "lli", check_ll);
|
||||
numbers_list_val_width(unsigned long long, "%llx", delim, "llx", check_ull);
|
||||
numbers_list_val_width(unsigned long long, "0x%llx", delim, "llx", check_ull);
|
||||
numbers_list_val_width(long long, "0x%llx", delim, "lli", check_ll);
|
||||
|
||||
numbers_list_val_width(unsigned long, "%lu", delim, "lu", check_ulong);
|
||||
numbers_list_val_width(long, "%ld", delim, "ld", check_long);
|
||||
numbers_list_val_width(long, "%ld", delim, "li", check_long);
|
||||
numbers_list_val_width(unsigned long, "%lx", delim, "lx", check_ulong);
|
||||
numbers_list_val_width(unsigned long, "0x%lx", delim, "lx", check_ulong);
|
||||
numbers_list_val_width(long, "0x%lx", delim, "li", check_long);
|
||||
|
||||
numbers_list_val_width(unsigned int, "%u", delim, "u", check_uint);
|
||||
numbers_list_val_width(int, "%d", delim, "d", check_int);
|
||||
numbers_list_val_width(int, "%d", delim, "i", check_int);
|
||||
numbers_list_val_width(unsigned int, "%x", delim, "x", check_uint);
|
||||
numbers_list_val_width(unsigned int, "0x%x", delim, "x", check_uint);
|
||||
numbers_list_val_width(int, "0x%x", delim, "i", check_int);
|
||||
|
||||
numbers_list_val_width(unsigned short, "%hu", delim, "hu", check_ushort);
|
||||
numbers_list_val_width(short, "%hd", delim, "hd", check_short);
|
||||
numbers_list_val_width(short, "%hd", delim, "hi", check_short);
|
||||
numbers_list_val_width(unsigned short, "%hx", delim, "hx", check_ushort);
|
||||
numbers_list_val_width(unsigned short, "0x%hx", delim, "hx", check_ushort);
|
||||
numbers_list_val_width(short, "0x%hx", delim, "hi", check_short);
|
||||
|
||||
numbers_list_val_width(unsigned char, "%hhu", delim, "hhu", check_uchar);
|
||||
numbers_list_val_width(signed char, "%hhd", delim, "hhd", check_char);
|
||||
numbers_list_val_width(signed char, "%hhd", delim, "hhi", check_char);
|
||||
numbers_list_val_width(unsigned char, "%hhx", delim, "hhx", check_uchar);
|
||||
numbers_list_val_width(unsigned char, "0x%hhx", delim, "hhx", check_uchar);
|
||||
numbers_list_val_width(signed char, "0x%hhx", delim, "hhi", check_char);
|
||||
}
|
||||
|
||||
/*
|
||||
* Slice a continuous string of digits without field delimiters, containing
|
||||
* numbers of varying length, using the field width to extract each group
|
||||
* of digits. For example the hex values c0,3,bf01,303 would have a
|
||||
* string representation of "c03bf01303" and extracted with "%2x%1x%4x%3x".
|
||||
*/
|
||||
static void __init numbers_slice(void)
|
||||
{
|
||||
numbers_list_field_width_val_width("");
|
||||
}
|
||||
|
||||
#define test_number_prefix(T, str, scan_fmt, expect0, expect1, n_args, fn) \
|
||||
do { \
|
||||
const T expect[2] = { expect0, expect1 }; \
|
||||
T result[2] = {~expect[0], ~expect[1]}; \
|
||||
\
|
||||
_test(fn, &expect, str, scan_fmt, n_args, &result[0], &result[1]); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Number prefix is >= field width.
|
||||
* Expected behaviour is derived from testing userland sscanf.
|
||||
*/
|
||||
static void __init numbers_prefix_overflow(void)
|
||||
{
|
||||
/*
|
||||
* Negative decimal with a field of width 1, should quit scanning
|
||||
* and return 0.
|
||||
*/
|
||||
test_number_prefix(long long, "-1 1", "%1lld %lld", 0, 0, 0, check_ll);
|
||||
test_number_prefix(long, "-1 1", "%1ld %ld", 0, 0, 0, check_long);
|
||||
test_number_prefix(int, "-1 1", "%1d %d", 0, 0, 0, check_int);
|
||||
test_number_prefix(short, "-1 1", "%1hd %hd", 0, 0, 0, check_short);
|
||||
test_number_prefix(signed char, "-1 1", "%1hhd %hhd", 0, 0, 0, check_char);
|
||||
|
||||
test_number_prefix(long long, "-1 1", "%1lli %lli", 0, 0, 0, check_ll);
|
||||
test_number_prefix(long, "-1 1", "%1li %li", 0, 0, 0, check_long);
|
||||
test_number_prefix(int, "-1 1", "%1i %i", 0, 0, 0, check_int);
|
||||
test_number_prefix(short, "-1 1", "%1hi %hi", 0, 0, 0, check_short);
|
||||
test_number_prefix(signed char, "-1 1", "%1hhi %hhi", 0, 0, 0, check_char);
|
||||
|
||||
/*
|
||||
* 0x prefix in a field of width 1: 0 is a valid digit so should
|
||||
* convert. Next field scan starts at the 'x' which isn't a digit so
|
||||
* scan quits with one field converted.
|
||||
*/
|
||||
test_number_prefix(unsigned long long, "0xA7", "%1llx%llx", 0, 0, 1, check_ull);
|
||||
test_number_prefix(unsigned long, "0xA7", "%1lx%lx", 0, 0, 1, check_ulong);
|
||||
test_number_prefix(unsigned int, "0xA7", "%1x%x", 0, 0, 1, check_uint);
|
||||
test_number_prefix(unsigned short, "0xA7", "%1hx%hx", 0, 0, 1, check_ushort);
|
||||
test_number_prefix(unsigned char, "0xA7", "%1hhx%hhx", 0, 0, 1, check_uchar);
|
||||
test_number_prefix(long long, "0xA7", "%1lli%llx", 0, 0, 1, check_ll);
|
||||
test_number_prefix(long, "0xA7", "%1li%lx", 0, 0, 1, check_long);
|
||||
test_number_prefix(int, "0xA7", "%1i%x", 0, 0, 1, check_int);
|
||||
test_number_prefix(short, "0xA7", "%1hi%hx", 0, 0, 1, check_short);
|
||||
test_number_prefix(char, "0xA7", "%1hhi%hhx", 0, 0, 1, check_char);
|
||||
|
||||
/*
|
||||
* 0x prefix in a field of width 2 using %x conversion: first field
|
||||
* converts to 0. Next field scan starts at the character after "0x".
|
||||
* Both fields will convert.
|
||||
*/
|
||||
test_number_prefix(unsigned long long, "0xA7", "%2llx%llx", 0, 0xa7, 2, check_ull);
|
||||
test_number_prefix(unsigned long, "0xA7", "%2lx%lx", 0, 0xa7, 2, check_ulong);
|
||||
test_number_prefix(unsigned int, "0xA7", "%2x%x", 0, 0xa7, 2, check_uint);
|
||||
test_number_prefix(unsigned short, "0xA7", "%2hx%hx", 0, 0xa7, 2, check_ushort);
|
||||
test_number_prefix(unsigned char, "0xA7", "%2hhx%hhx", 0, 0xa7, 2, check_uchar);
|
||||
|
||||
/*
|
||||
* 0x prefix in a field of width 2 using %i conversion: first field
|
||||
* converts to 0. Next field scan starts at the character after "0x",
|
||||
* which will convert if can be intepreted as decimal but will fail
|
||||
* if it contains any hex digits (since no 0x prefix).
|
||||
*/
|
||||
test_number_prefix(long long, "0x67", "%2lli%lli", 0, 67, 2, check_ll);
|
||||
test_number_prefix(long, "0x67", "%2li%li", 0, 67, 2, check_long);
|
||||
test_number_prefix(int, "0x67", "%2i%i", 0, 67, 2, check_int);
|
||||
test_number_prefix(short, "0x67", "%2hi%hi", 0, 67, 2, check_short);
|
||||
test_number_prefix(char, "0x67", "%2hhi%hhi", 0, 67, 2, check_char);
|
||||
|
||||
test_number_prefix(long long, "0xA7", "%2lli%lli", 0, 0, 1, check_ll);
|
||||
test_number_prefix(long, "0xA7", "%2li%li", 0, 0, 1, check_long);
|
||||
test_number_prefix(int, "0xA7", "%2i%i", 0, 0, 1, check_int);
|
||||
test_number_prefix(short, "0xA7", "%2hi%hi", 0, 0, 1, check_short);
|
||||
test_number_prefix(char, "0xA7", "%2hhi%hhi", 0, 0, 1, check_char);
|
||||
}
|
||||
|
||||
#define _test_simple_strtoxx(T, fn, gen_fmt, expect, base) \
|
||||
do { \
|
||||
T got; \
|
||||
char *endp; \
|
||||
int len; \
|
||||
bool fail = false; \
|
||||
\
|
||||
total_tests++; \
|
||||
len = snprintf(test_buffer, BUF_SIZE, gen_fmt, expect); \
|
||||
got = (fn)(test_buffer, &endp, base); \
|
||||
pr_debug(#fn "(\"%s\", %d) -> " gen_fmt "\n", test_buffer, base, got); \
|
||||
if (got != (expect)) { \
|
||||
fail = true; \
|
||||
pr_warn(#fn "(\"%s\", %d): got " gen_fmt " expected " gen_fmt "\n", \
|
||||
test_buffer, base, got, expect); \
|
||||
} else if (endp != test_buffer + len) { \
|
||||
fail = true; \
|
||||
pr_warn(#fn "(\"%s\", %d) startp=0x%px got endp=0x%px expected 0x%px\n", \
|
||||
test_buffer, base, test_buffer, \
|
||||
test_buffer + len, endp); \
|
||||
} \
|
||||
\
|
||||
if (fail) \
|
||||
failed_tests++; \
|
||||
} while (0)
|
||||
|
||||
#define test_simple_strtoxx(T, fn, gen_fmt, base) \
|
||||
do { \
|
||||
int i; \
|
||||
\
|
||||
for (i = 0; i < ARRAY_SIZE(numbers); i++) { \
|
||||
_test_simple_strtoxx(T, fn, gen_fmt, (T)numbers[i], base); \
|
||||
\
|
||||
if (is_signed_type(T)) \
|
||||
_test_simple_strtoxx(T, fn, gen_fmt, \
|
||||
-(T)numbers[i], base); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void __init test_simple_strtoull(void)
|
||||
{
|
||||
test_simple_strtoxx(unsigned long long, simple_strtoull, "%llu", 10);
|
||||
test_simple_strtoxx(unsigned long long, simple_strtoull, "%llu", 0);
|
||||
test_simple_strtoxx(unsigned long long, simple_strtoull, "%llx", 16);
|
||||
test_simple_strtoxx(unsigned long long, simple_strtoull, "0x%llx", 16);
|
||||
test_simple_strtoxx(unsigned long long, simple_strtoull, "0x%llx", 0);
|
||||
}
|
||||
|
||||
static void __init test_simple_strtoll(void)
|
||||
{
|
||||
test_simple_strtoxx(long long, simple_strtoll, "%lld", 10);
|
||||
test_simple_strtoxx(long long, simple_strtoll, "%lld", 0);
|
||||
test_simple_strtoxx(long long, simple_strtoll, "%llx", 16);
|
||||
test_simple_strtoxx(long long, simple_strtoll, "0x%llx", 16);
|
||||
test_simple_strtoxx(long long, simple_strtoll, "0x%llx", 0);
|
||||
}
|
||||
|
||||
static void __init test_simple_strtoul(void)
|
||||
{
|
||||
test_simple_strtoxx(unsigned long, simple_strtoul, "%lu", 10);
|
||||
test_simple_strtoxx(unsigned long, simple_strtoul, "%lu", 0);
|
||||
test_simple_strtoxx(unsigned long, simple_strtoul, "%lx", 16);
|
||||
test_simple_strtoxx(unsigned long, simple_strtoul, "0x%lx", 16);
|
||||
test_simple_strtoxx(unsigned long, simple_strtoul, "0x%lx", 0);
|
||||
}
|
||||
|
||||
static void __init test_simple_strtol(void)
|
||||
{
|
||||
test_simple_strtoxx(long, simple_strtol, "%ld", 10);
|
||||
test_simple_strtoxx(long, simple_strtol, "%ld", 0);
|
||||
test_simple_strtoxx(long, simple_strtol, "%lx", 16);
|
||||
test_simple_strtoxx(long, simple_strtol, "0x%lx", 16);
|
||||
test_simple_strtoxx(long, simple_strtol, "0x%lx", 0);
|
||||
}
|
||||
|
||||
/* Selection of common delimiters/separators between numbers in a string. */
|
||||
static const char * const number_delimiters[] __initconst = {
|
||||
" ", ":", ",", "-", "/",
|
||||
};
|
||||
|
||||
static void __init test_numbers(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* String containing only one number. */
|
||||
numbers_simple();
|
||||
|
||||
/* String with multiple numbers separated by delimiter. */
|
||||
for (i = 0; i < ARRAY_SIZE(number_delimiters); i++) {
|
||||
numbers_list(number_delimiters[i]);
|
||||
|
||||
/* Field width may be longer than actual field digits. */
|
||||
numbers_list_field_width_typemax(number_delimiters[i]);
|
||||
|
||||
/* Each field width exactly length of actual field digits. */
|
||||
numbers_list_field_width_val_width(number_delimiters[i]);
|
||||
}
|
||||
|
||||
/* Slice continuous sequence of digits using field widths. */
|
||||
numbers_slice();
|
||||
|
||||
numbers_prefix_overflow();
|
||||
}
|
||||
|
||||
static void __init selftest(void)
|
||||
{
|
||||
test_buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
|
||||
if (!test_buffer)
|
||||
return;
|
||||
|
||||
fmt_buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
|
||||
if (!fmt_buffer) {
|
||||
kfree(test_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
prandom_seed_state(&rnd_state, 3141592653589793238ULL);
|
||||
|
||||
test_numbers();
|
||||
|
||||
test_simple_strtoull();
|
||||
test_simple_strtoll();
|
||||
test_simple_strtoul();
|
||||
test_simple_strtol();
|
||||
|
||||
kfree(fmt_buffer);
|
||||
kfree(test_buffer);
|
||||
}
|
||||
|
||||
KSTM_MODULE_LOADERS(test_scanf);
|
||||
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
110
lib/vsprintf.c
110
lib/vsprintf.c
@ -53,6 +53,31 @@
|
||||
#include <linux/string_helpers.h>
|
||||
#include "kstrtox.h"
|
||||
|
||||
static unsigned long long simple_strntoull(const char *startp, size_t max_chars,
|
||||
char **endp, unsigned int base)
|
||||
{
|
||||
const char *cp;
|
||||
unsigned long long result = 0ULL;
|
||||
size_t prefix_chars;
|
||||
unsigned int rv;
|
||||
|
||||
cp = _parse_integer_fixup_radix(startp, &base);
|
||||
prefix_chars = cp - startp;
|
||||
if (prefix_chars < max_chars) {
|
||||
rv = _parse_integer_limit(cp, base, &result, max_chars - prefix_chars);
|
||||
/* FIXME */
|
||||
cp += (rv & ~KSTRTOX_OVERFLOW);
|
||||
} else {
|
||||
/* Field too short for prefix + digit, skip over without converting */
|
||||
cp = startp + max_chars;
|
||||
}
|
||||
|
||||
if (endp)
|
||||
*endp = (char *)cp;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* simple_strtoull - convert a string to an unsigned long long
|
||||
* @cp: The start of the string
|
||||
@ -63,18 +88,7 @@
|
||||
*/
|
||||
unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base)
|
||||
{
|
||||
unsigned long long result;
|
||||
unsigned int rv;
|
||||
|
||||
cp = _parse_integer_fixup_radix(cp, &base);
|
||||
rv = _parse_integer(cp, base, &result);
|
||||
/* FIXME */
|
||||
cp += (rv & ~KSTRTOX_OVERFLOW);
|
||||
|
||||
if (endp)
|
||||
*endp = (char *)cp;
|
||||
|
||||
return result;
|
||||
return simple_strntoull(cp, INT_MAX, endp, base);
|
||||
}
|
||||
EXPORT_SYMBOL(simple_strtoull);
|
||||
|
||||
@ -109,6 +123,21 @@ long simple_strtol(const char *cp, char **endp, unsigned int base)
|
||||
}
|
||||
EXPORT_SYMBOL(simple_strtol);
|
||||
|
||||
static long long simple_strntoll(const char *cp, size_t max_chars, char **endp,
|
||||
unsigned int base)
|
||||
{
|
||||
/*
|
||||
* simple_strntoull() safely handles receiving max_chars==0 in the
|
||||
* case cp[0] == '-' && max_chars == 1.
|
||||
* If max_chars == 0 we can drop through and pass it to simple_strntoull()
|
||||
* and the content of *cp is irrelevant.
|
||||
*/
|
||||
if (*cp == '-' && max_chars > 0)
|
||||
return -simple_strntoull(cp + 1, max_chars - 1, endp, base);
|
||||
|
||||
return simple_strntoull(cp, max_chars, endp, base);
|
||||
}
|
||||
|
||||
/**
|
||||
* simple_strtoll - convert a string to a signed long long
|
||||
* @cp: The start of the string
|
||||
@ -119,10 +148,7 @@ EXPORT_SYMBOL(simple_strtol);
|
||||
*/
|
||||
long long simple_strtoll(const char *cp, char **endp, unsigned int base)
|
||||
{
|
||||
if (*cp == '-')
|
||||
return -simple_strtoull(cp + 1, endp, base);
|
||||
|
||||
return simple_strtoull(cp, endp, base);
|
||||
return simple_strntoll(cp, INT_MAX, endp, base);
|
||||
}
|
||||
EXPORT_SYMBOL(simple_strtoll);
|
||||
|
||||
@ -1834,7 +1860,8 @@ char *rtc_str(char *buf, char *end, const struct rtc_time *tm,
|
||||
struct printf_spec spec, const char *fmt)
|
||||
{
|
||||
bool have_t = true, have_d = true;
|
||||
bool raw = false;
|
||||
bool raw = false, iso8601_separator = true;
|
||||
bool found = true;
|
||||
int count = 2;
|
||||
|
||||
if (check_pointer(&buf, end, tm, spec))
|
||||
@ -1851,14 +1878,25 @@ char *rtc_str(char *buf, char *end, const struct rtc_time *tm,
|
||||
break;
|
||||
}
|
||||
|
||||
raw = fmt[count] == 'r';
|
||||
do {
|
||||
switch (fmt[count++]) {
|
||||
case 'r':
|
||||
raw = true;
|
||||
break;
|
||||
case 's':
|
||||
iso8601_separator = false;
|
||||
break;
|
||||
default:
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
} while (found);
|
||||
|
||||
if (have_d)
|
||||
buf = date_str(buf, end, tm, raw);
|
||||
if (have_d && have_t) {
|
||||
/* Respect ISO 8601 */
|
||||
if (buf < end)
|
||||
*buf = 'T';
|
||||
*buf = iso8601_separator ? 'T' : ' ';
|
||||
buf++;
|
||||
}
|
||||
if (have_t)
|
||||
@ -2298,7 +2336,7 @@ early_param("no_hash_pointers", no_hash_pointers_enable);
|
||||
* - 'd[234]' For a dentry name (optionally 2-4 last components)
|
||||
* - 'D[234]' Same as 'd' but for a struct file
|
||||
* - 'g' For block_device name (gendisk + partition number)
|
||||
* - 't[RT][dt][r]' For time and date as represented by:
|
||||
* - 't[RT][dt][r][s]' For time and date as represented by:
|
||||
* R struct rtc_time
|
||||
* T time64_t
|
||||
* - 'C' For a clock, it prints the name (Common Clock Framework) or address
|
||||
@ -3565,8 +3603,12 @@ int vsscanf(const char *buf, const char *fmt, va_list args)
|
||||
str = skip_spaces(str);
|
||||
|
||||
digit = *str;
|
||||
if (is_sign && digit == '-')
|
||||
if (is_sign && digit == '-') {
|
||||
if (field_width == 1)
|
||||
break;
|
||||
|
||||
digit = *(str + 1);
|
||||
}
|
||||
|
||||
if (!digit
|
||||
|| (base == 16 && !isxdigit(digit))
|
||||
@ -3576,25 +3618,13 @@ int vsscanf(const char *buf, const char *fmt, va_list args)
|
||||
break;
|
||||
|
||||
if (is_sign)
|
||||
val.s = qualifier != 'L' ?
|
||||
simple_strtol(str, &next, base) :
|
||||
simple_strtoll(str, &next, base);
|
||||
val.s = simple_strntoll(str,
|
||||
field_width >= 0 ? field_width : INT_MAX,
|
||||
&next, base);
|
||||
else
|
||||
val.u = qualifier != 'L' ?
|
||||
simple_strtoul(str, &next, base) :
|
||||
simple_strtoull(str, &next, base);
|
||||
|
||||
if (field_width > 0 && next - str > field_width) {
|
||||
if (base == 0)
|
||||
_parse_integer_fixup_radix(str, &base);
|
||||
while (next - str > field_width) {
|
||||
if (is_sign)
|
||||
val.s = div_s64(val.s, base);
|
||||
else
|
||||
val.u = div_u64(val.u, base);
|
||||
--next;
|
||||
}
|
||||
}
|
||||
val.u = simple_strntoull(str,
|
||||
field_width >= 0 ? field_width : INT_MAX,
|
||||
&next, base);
|
||||
|
||||
switch (qualifier) {
|
||||
case 'H': /* that's 'hh' in format */
|
||||
|
@ -4,6 +4,6 @@
|
||||
# No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
|
||||
all:
|
||||
|
||||
TEST_PROGS := printf.sh bitmap.sh prime_numbers.sh strscpy.sh
|
||||
TEST_PROGS := printf.sh bitmap.sh prime_numbers.sh scanf.sh strscpy.sh
|
||||
|
||||
include ../lib.mk
|
||||
|
@ -1,4 +1,5 @@
|
||||
CONFIG_TEST_PRINTF=m
|
||||
CONFIG_TEST_SCANF=m
|
||||
CONFIG_TEST_BITMAP=m
|
||||
CONFIG_PRIME_NUMBERS=m
|
||||
CONFIG_TEST_STRSCPY=m
|
||||
|
4
tools/testing/selftests/lib/scanf.sh
Executable file
4
tools/testing/selftests/lib/scanf.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Tests the scanf infrastructure using test_scanf kernel module.
|
||||
$(dirname $0)/../kselftest/module.sh "scanf" test_scanf
|
Loading…
Reference in New Issue
Block a user