summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-09-30 13:52:57 +0100
committerMark Brown <broonie@kernel.org>2026-09-30 13:52:57 +0100
commit5ea4b2dc75234871d652b398076868ef68f307bc (patch)
tree549b1093514445094d515a28a217d64624f89d7e
parent40ef4277c51f6fdf33003a352eb952f8e8e4202c (diff)
parent4991c8b72b564cd16cb48124f880617fd49f1013 (diff)
downloadlinux-next-5ea4b2dc75234871d652b398076868ef68f307bc.tar.gz
linux-next-5ea4b2dc75234871d652b398076868ef68f307bc.zip
Merge branch 'sysctl-next' of https://git.kernel.org/pub/scm/linux/kernel/git/sysctl/sysctl.git
-rw-r--r--drivers/parport/procfs.c6
-rw-r--r--fs/dcache.c2
-rw-r--r--fs/file_table.c2
-rw-r--r--fs/proc/proc_sysctl.c2
-rw-r--r--kernel/sysctl.c419
-rw-r--r--kernel/time/jiffies.c2
-rw-r--r--lib/test_sysctl.c33
-rwxr-xr-xtools/testing/selftests/sysctl/sysctl.sh1434
8 files changed, 842 insertions, 1058 deletions
diff --git a/drivers/parport/procfs.c b/drivers/parport/procfs.c
index 3880460e67f2..1bfafc2a5724 100644
--- a/drivers/parport/procfs.c
+++ b/drivers/parport/procfs.c
@@ -26,7 +26,7 @@
#include <linux/uaccess.h>
-#if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS)
+#ifdef CONFIG_SYSCTL
#define PARPORT_MIN_TIMESLICE_VALUE 1ul
#define PARPORT_MAX_TIMESLICE_VALUE ((unsigned long) HZ)
@@ -560,7 +560,7 @@ static void __exit parport_default_proc_unregister(void)
parport_bus_exit();
}
-#else /* no sysctl or no procfs*/
+#else /* CONFIG_SYSCTL */
int parport_proc_register(struct parport *pp)
{
@@ -591,7 +591,7 @@ static void __exit parport_default_proc_unregister (void)
{
parport_bus_exit();
}
-#endif
+#endif /* CONFIG_SYSCTL */
subsys_initcall(parport_default_proc_register)
module_exit(parport_default_proc_unregister)
diff --git a/fs/dcache.c b/fs/dcache.c
index 7a9346c4f2e4..71918fe19d49 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -145,7 +145,7 @@ static DEFINE_PER_CPU(long, nr_dentry_unused);
static DEFINE_PER_CPU(long, nr_dentry_negative);
static int dentry_negative_policy;
-#if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS)
+#ifdef CONFIG_SYSCTL
/* Statistics gathering. */
static struct dentry_stat_t dentry_stat = {
.age_limit = 45,
diff --git a/fs/file_table.c b/fs/file_table.c
index c68b8c0a4097..08ca3631b726 100644
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -122,7 +122,7 @@ unsigned long get_max_files(void)
}
EXPORT_SYMBOL_GPL(get_max_files);
-#if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS)
+#ifdef CONFIG_SYSCTL
/*
* Handle nr_files sysctl
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index d1cfd2941359..6c0859c66563 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -206,7 +206,7 @@ static void init_header(struct ctl_table_header *head,
head->node = node;
INIT_HLIST_HEAD(&head->inodes);
if (node) {
- const struct ctl_table *entry;
+ const struct ctl_table *entry __maybe_unused;
list_for_each_table_entry(entry, head) {
node->header = head;
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index f7b75985d542..f5145b32f6be 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -29,14 +29,12 @@ EXPORT_SYMBOL(sysctl_vals);
const unsigned long sysctl_long_vals[] = { 0, 1, LONG_MAX };
EXPORT_SYMBOL_GPL(sysctl_long_vals);
-#if defined(CONFIG_SYSCTL)
+#ifdef CONFIG_SYSCTL
/* Constants used for minimum and maximum */
static const int ngroups_max = NGROUPS_MAX;
static const int cap_last_cap = CAP_LAST_CAP;
-#ifdef CONFIG_SYSCTL
-
/**
* enum sysctl_writes_mode - supported sysctl write modes
*
@@ -64,14 +62,6 @@ enum sysctl_writes_mode {
};
static enum sysctl_writes_mode sysctl_writes_strict = SYSCTL_WRITES_STRICT;
-#endif /* CONFIG_SYSCTL */
-#endif /* CONFIG_SYSCTL */
-
-/*
- * /proc/sys support
- */
-
-#ifdef CONFIG_SYSCTL
static int _proc_do_string(char *data, int maxlen, int dir,
char *buffer, size_t *lenp, loff_t *ppos)
@@ -483,7 +473,7 @@ int proc_int_k2u_conv_kop(ulong *u_ptr, const int *k_ptr, bool *negp,
if (val < 0) {
*negp = true;
- *u_ptr = k_ptr_op ? -k_ptr_op((ulong)val) : -(ulong)val;
+ *u_ptr = k_ptr_op ? k_ptr_op(-(ulong)val) : -(ulong)val;
} else {
*negp = false;
*u_ptr = k_ptr_op ? k_ptr_op((ulong)val) : (ulong) val;
@@ -595,7 +585,7 @@ static int do_proc_int_conv_minmax(bool *negp, unsigned long *u_ptr, int *k_ptr,
static const char proc_wspace_sep[] = { ' ', '\t', '\n' };
/*
- * Element type processed by do_proc_vec(). The tag selects the element size
+ * Element type processed by proc_vec(). The tag selects the element size
* and signedness, and it selects which member of union proc_vec_conv is live.
*/
enum proc_vec_type {
@@ -605,7 +595,7 @@ enum proc_vec_type {
};
/*
- * Converter passed to do_proc_vec(). Only the member matching the
+ * Converter passed to proc_vec(). Only the member matching the
* enum proc_vec_type tag is ever read, so every dispatch stays fully typed and
* no void * converter pointer is needed.
*/
@@ -637,101 +627,177 @@ static int proc_vec_conv(enum proc_vec_type type, union proc_vec_conv conv,
return -EINVAL;
}
-/*
- * Read/write a vector of @type elements. The element size and signedness are
- * derived from @type, so a single runtime function replaces the per-type
- * variants. table->data is walked as raw bytes (@i) advanced by @size; the
- * converter performs the actual typed load/store.
- */
-static int do_proc_vec(const struct ctl_table *table, int dir,
- void *buffer, size_t *lenp, loff_t *ppos,
- enum proc_vec_type type, union proc_vec_conv conv)
+static int commit_conv_vec(const enum proc_vec_type data_type, void *dst,
+ const void *src, size_t nr)
{
- int vleft, first = 1, err = 0;
- size_t left, size;
- bool is_unsigned;
- char *i, *p;
+ size_t i;
- switch (type) {
+ switch (data_type) {
case PROC_VEC_INT:
- size = sizeof(int);
- is_unsigned = false;
- break;
- case PROC_VEC_UINT:
- size = sizeof(uint);
- is_unsigned = true;
- break;
+ for (i = 0; i < nr; i++)
+ WRITE_ONCE(((int *)dst)[i], ((const int *)src)[i]);
+ return 0;
+
case PROC_VEC_ULONG:
- size = sizeof(ulong);
- is_unsigned = true;
- break;
+ for (i = 0; i < nr; i++)
+ WRITE_ONCE(((ulong *)dst)[i], ((const ulong *)src)[i]);
+ return 0;
default:
return -EINVAL;
}
+}
- if (!table->data || !table->maxlen || !*lenp ||
- (*ppos && SYSCTL_KERN_TO_USER(dir))) {
- *lenp = 0;
- return 0;
- }
-
- i = table->data;
- vleft = table->maxlen / size;
- left = *lenp;
-
- /* uint arrays are not supported, *Do not* add support for them. */
- if (type == PROC_VEC_UINT && vleft != 1)
- return -EINVAL;
-
- if (SYSCTL_USER_TO_KERN(dir)) {
- if (proc_first_pos_non_zero_ignore(ppos, table))
- goto out;
-
- if (left > PAGE_SIZE - 1)
- left = PAGE_SIZE - 1;
- p = buffer;
+/**
+ * apply_conv_on_vec - Apply converter function on data vector
+ *
+ * @conv: The converter to be applied
+ * @table: The sysctl table
+ * @data_type: Type used in converter appliation (INT, UINT or ULONG)
+ * @data_size: Number of data elements.
+ * @conv_dir: %TRUE if this is a write to the sysctl file
+ * @buf_nbyte: Number of bytes for buf
+ * @buf: The user buffer
+ * @buf_left_final: Number of outstanding (non converted) bytes.
+ *
+ * Element signedness is derived from @data_type. table->data is walked
+ * as raw bytes (@data) advanced by @data_size; the converter performs
+ * the actual typed load/store. Sets buf_left_final to the number of
+ * bytes that where left outstanding after conversion. Can be > 0.
+ *
+ * Returns: %0 on success. Non-zero on error.
+ */
+static int apply_conv_on_vec(const union proc_vec_conv conv,
+ const struct ctl_table *table,
+ const enum proc_vec_type data_type,
+ const size_t data_size, const int conv_dir,
+ const size_t buf_nbyte, void *buf,
+ size_t *buf_left_final)
+{
+ int vec_left, err = 0;
+ size_t buf_left, nr_conv = 0;
+ char *data, *data_stage = NULL, *p;
+ bool is_unsigned = data_type == PROC_VEC_UINT || data_type == PROC_VEC_ULONG;
+
+ buf_left = buf_nbyte;
+
+ data = table->data;
+ if (SYSCTL_USER_TO_KERN(conv_dir)) {
+ if (buf_left > PAGE_SIZE - 1)
+ buf_left = PAGE_SIZE - 1;
+ p = buf;
+
+ if (table->maxlen > data_size) {
+ data_stage = kmemdup(table->data, table->maxlen, GFP_KERNEL);
+ if (!data_stage) {
+ err = -ENOMEM;
+ goto out;
+ }
+ data = data_stage;
+ }
}
- for (; left && vleft--; i += size, first = 0) {
+ vec_left = table->maxlen / data_size;
+ for (; buf_left && vec_left--; data += data_size, nr_conv++) {
unsigned long lval;
bool neg = false;
- if (SYSCTL_USER_TO_KERN(dir)) {
- proc_skip_spaces(&p, &left);
+ if (SYSCTL_USER_TO_KERN(conv_dir)) {
+ proc_skip_spaces(&p, &buf_left);
- if (!left)
+ if (!buf_left)
break;
- err = proc_get_long(&p, &left, &lval, &neg,
+ err = proc_get_long(&p, &buf_left, &lval, &neg,
proc_wspace_sep,
sizeof(proc_wspace_sep), NULL);
if (!err && neg && is_unsigned)
err = -EINVAL;
if (err)
break;
- if (proc_vec_conv(type, conv, &neg, &lval, i, dir, table)) {
+ if (proc_vec_conv(data_type, conv, &neg, &lval, data, conv_dir, table)) {
err = -EINVAL;
break;
}
} else {
- if (proc_vec_conv(type, conv, &neg, &lval, i, dir, table)) {
+ if (proc_vec_conv(data_type, conv, &neg, &lval, data, conv_dir, table)) {
err = -EINVAL;
break;
}
- if (!first)
- proc_put_char(&buffer, &left, '\t');
- proc_put_long(&buffer, &left, lval, neg);
+ if (nr_conv)
+ proc_put_char(&buf, &buf_left, '\t');
+ proc_put_long(&buf, &buf_left, lval, neg);
}
}
- if (SYSCTL_KERN_TO_USER(dir) && !first && left && !err)
- proc_put_char(&buffer, &left, '\n');
- if (SYSCTL_USER_TO_KERN(dir) && !err && left)
- proc_skip_spaces(&p, &left);
- if (SYSCTL_USER_TO_KERN(dir) && first)
- return err ? : -EINVAL;
- *lenp -= left;
+ if (SYSCTL_USER_TO_KERN(conv_dir)) {
+ if (!err && buf_left)
+ proc_skip_spaces(&p, &buf_left);
+ if (!nr_conv) {
+ err = err ? : -EINVAL;
+ goto out;
+ }
+ if (!err && data_stage)
+ err = commit_conv_vec(data_type, table->data, data_stage, nr_conv);
+ } else {
+ if (nr_conv && buf_left && !err)
+ proc_put_char(&buf, &buf_left, '\n');
+ }
+
+ *buf_left_final = buf_left;
+
+out:
+ kfree(data_stage);
+ return err;
+}
+
+/* Read/write a vector of @type elements. */
+static int proc_vec(const struct ctl_table *table, int dir, void *buffer,
+ size_t *lenp, loff_t *ppos, enum proc_vec_type type,
+ union proc_vec_conv conv)
+{
+ int err = 0;
+ size_t data_size, left_nbyte = SIZE_MAX;
+
+ switch (type) {
+ case PROC_VEC_INT:
+ data_size = sizeof(int);
+ break;
+ case PROC_VEC_UINT:
+ data_size = sizeof(uint);
+ break;
+ case PROC_VEC_ULONG:
+ data_size = sizeof(ulong);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!table->data || !table->maxlen || !*lenp ||
+ (*ppos && SYSCTL_KERN_TO_USER(dir))) {
+ *lenp = 0;
+ return 0;
+ }
+
+ if (SYSCTL_USER_TO_KERN(dir)) {
+ if (proc_first_pos_non_zero_ignore(ppos, table))
+ goto out;
+ }
+
+ err = apply_conv_on_vec(conv, table, type, data_size, dir, *lenp, buffer,
+ &left_nbyte);
+
+ /*
+ * An unchanged left_nbyte signals a write with no parsed element; which
+ * is an error. Using SIZE_MAX to detect this error is possible because:
+ * 1. lenp is bounded by KMALLOC_MAX_SIZE in proc_sys_call_handler
+ * 2. lenp could never be SIZE_MAX as it is a "ridiculous" (exabyte) allocation.
+ */
+ if (left_nbyte == SIZE_MAX)
+ return err;
+
+ *lenp -= left_nbyte;
out:
*ppos += *lenp;
+
return err;
}
@@ -756,12 +822,15 @@ int proc_douintvec_conv(const struct ctl_table *table, int dir, void *buffer,
int (*conv)(bool *negp, ulong *u_ptr, uint *k_ptr,
int dir, const struct ctl_table *table))
{
+ /* uint arrays are not supported, *Do not* add support for them. */
+ if (table->maxlen && (table->maxlen / sizeof(uint)) != 1)
+ return -EINVAL;
if (!conv)
conv = do_proc_uint_conv;
- return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT,
- (union proc_vec_conv){ .uint_conv = conv });
+ return proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT,
+ (union proc_vec_conv){ .uint_conv = conv });
}
/**
@@ -820,8 +889,8 @@ int proc_dobool(const struct ctl_table *table, int dir, void *buffer,
int proc_dointvec(const struct ctl_table *table, int dir, void *buffer,
size_t *lenp, loff_t *ppos)
{
- return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT,
- (union proc_vec_conv){ .int_conv = do_proc_int_conv });
+ return proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT,
+ (union proc_vec_conv){ .int_conv = do_proc_int_conv });
}
/**
@@ -840,8 +909,7 @@ int proc_dointvec(const struct ctl_table *table, int dir, void *buffer,
int proc_douintvec(const struct ctl_table *table, int dir, void *buffer,
size_t *lenp, loff_t *ppos)
{
- return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT,
- (union proc_vec_conv){ .uint_conv = do_proc_uint_conv });
+ return proc_douintvec_conv(table, dir, buffer, lenp, ppos, do_proc_uint_conv);
}
/**
@@ -864,8 +932,8 @@ int proc_douintvec(const struct ctl_table *table, int dir, void *buffer,
int proc_dointvec_minmax(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
{
- return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT,
- (union proc_vec_conv){ .int_conv = do_proc_int_conv_minmax });
+ return proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT,
+ (union proc_vec_conv){ .int_conv = do_proc_int_conv_minmax });
}
/**
@@ -891,8 +959,8 @@ int proc_dointvec_minmax(const struct ctl_table *table, int dir,
int proc_douintvec_minmax(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
{
- return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT,
- (union proc_vec_conv){ .uint_conv = do_proc_uint_conv_minmax });
+ return proc_douintvec_conv(table, dir, buffer, lenp, ppos,
+ do_proc_uint_conv_minmax);
}
/**
@@ -935,8 +1003,7 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int dir,
tmp.extra2 = (unsigned int *) &max;
val = READ_ONCE(*data);
- res = do_proc_vec(&tmp, dir, buffer, lenp, ppos, PROC_VEC_UINT,
- (union proc_vec_conv){ .uint_conv = do_proc_uint_conv_minmax });
+ res = proc_douintvec_minmax(&tmp, dir, buffer, lenp, ppos);
if (res)
return res;
if (SYSCTL_USER_TO_KERN(dir))
@@ -1066,8 +1133,8 @@ int proc_doulongvec_conv(const struct ctl_table *table, int dir,
int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr,
int dir, const struct ctl_table *table))
{
- return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_ULONG,
- (union proc_vec_conv){ .ulong_conv = conv });
+ return proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_ULONG,
+ (union proc_vec_conv){ .ulong_conv = conv });
}
/**
@@ -1087,10 +1154,10 @@ int proc_doulongvec_conv(const struct ctl_table *table, int dir,
* Returns: %0 on success.
*/
int proc_doulongvec_minmax(const struct ctl_table *table, int dir,
- void *buffer, size_t *lenp, loff_t *ppos)
+ void *buffer, size_t *lenp, loff_t *ppos)
{
- return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_ULONG,
- (union proc_vec_conv){ .ulong_conv = do_proc_ulong_conv });
+ return proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_ULONG,
+ (union proc_vec_conv){ .ulong_conv = do_proc_ulong_conv });
}
/**
@@ -1114,8 +1181,8 @@ int proc_dointvec_conv(const struct ctl_table *table, int dir, void *buffer,
{
if (!conv)
conv = do_proc_int_conv;
- return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT,
- (union proc_vec_conv){ .int_conv = conv });
+ return proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT,
+ (union proc_vec_conv){ .int_conv = conv });
}
/**
@@ -1265,6 +1332,87 @@ int proc_do_large_bitmap(const struct ctl_table *table, int dir,
return err;
}
+int proc_do_static_key(const struct ctl_table *table, int dir,
+ void *buffer, size_t *lenp, loff_t *ppos)
+{
+ struct static_key *key = (struct static_key *)table->data;
+ static DEFINE_MUTEX(static_key_mutex);
+ int val, ret;
+ struct ctl_table tmp = {
+ .data = &val,
+ .maxlen = sizeof(val),
+ .mode = table->mode,
+ .extra1 = SYSCTL_ZERO,
+ .extra2 = SYSCTL_ONE,
+ };
+
+ if (SYSCTL_USER_TO_KERN(dir) && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ mutex_lock(&static_key_mutex);
+ val = static_key_enabled(key);
+ ret = proc_dointvec_minmax(&tmp, dir, buffer, lenp, ppos);
+ if (SYSCTL_USER_TO_KERN(dir) && !ret) {
+ if (val)
+ static_key_enable(key);
+ else
+ static_key_disable(key);
+ }
+ mutex_unlock(&static_key_mutex);
+ return ret;
+}
+
+static const struct ctl_table sysctl_subsys_table[] = {
+ {
+ .procname = "sysctl_writes_strict",
+ .data = &sysctl_writes_strict,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = SYSCTL_NEG_ONE,
+ .extra2 = SYSCTL_ONE,
+ },
+ {
+ .procname = "ngroups_max",
+ .data = (void *)&ngroups_max,
+ .maxlen = sizeof (int),
+ .mode = 0444,
+ .proc_handler = proc_dointvec,
+ },
+ {
+ .procname = "cap_last_cap",
+ .data = (void *)&cap_last_cap,
+ .maxlen = sizeof(int),
+ .mode = 0444,
+ .proc_handler = proc_dointvec,
+ },
+#ifdef CONFIG_SYSCTL_ARCH_UNALIGN_ALLOW
+ {
+ .procname = "unaligned-trap",
+ .data = &unaligned_enabled,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+#endif
+#ifdef CONFIG_SYSCTL_ARCH_UNALIGN_NO_WARN
+ {
+ .procname = "ignore-unaligned-usertrap",
+ .data = &no_unaligned_warning,
+ .maxlen = sizeof (int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+#endif
+};
+
+int __init sysctl_init_bases(void)
+{
+ register_sysctl_init("kernel", sysctl_subsys_table);
+
+ return 0;
+}
+
#else /* CONFIG_SYSCTL */
int proc_dostring(const struct ctl_table *table, int dir,
@@ -1364,91 +1512,12 @@ int proc_do_large_bitmap(const struct ctl_table *table, int dir,
return -ENOSYS;
}
-#endif /* CONFIG_SYSCTL */
-
-#if defined(CONFIG_SYSCTL)
int proc_do_static_key(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
{
- struct static_key *key = (struct static_key *)table->data;
- static DEFINE_MUTEX(static_key_mutex);
- int val, ret;
- struct ctl_table tmp = {
- .data = &val,
- .maxlen = sizeof(val),
- .mode = table->mode,
- .extra1 = SYSCTL_ZERO,
- .extra2 = SYSCTL_ONE,
- };
-
- if (SYSCTL_USER_TO_KERN(dir) && !capable(CAP_SYS_ADMIN))
- return -EPERM;
-
- mutex_lock(&static_key_mutex);
- val = static_key_enabled(key);
- ret = proc_dointvec_minmax(&tmp, dir, buffer, lenp, ppos);
- if (SYSCTL_USER_TO_KERN(dir) && !ret) {
- if (val)
- static_key_enable(key);
- else
- static_key_disable(key);
- }
- mutex_unlock(&static_key_mutex);
- return ret;
+ return -ENOSYS;
}
-static const struct ctl_table sysctl_subsys_table[] = {
-#ifdef CONFIG_SYSCTL
- {
- .procname = "sysctl_writes_strict",
- .data = &sysctl_writes_strict,
- .maxlen = sizeof(int),
- .mode = 0644,
- .proc_handler = proc_dointvec_minmax,
- .extra1 = SYSCTL_NEG_ONE,
- .extra2 = SYSCTL_ONE,
- },
-#endif
- {
- .procname = "ngroups_max",
- .data = (void *)&ngroups_max,
- .maxlen = sizeof (int),
- .mode = 0444,
- .proc_handler = proc_dointvec,
- },
- {
- .procname = "cap_last_cap",
- .data = (void *)&cap_last_cap,
- .maxlen = sizeof(int),
- .mode = 0444,
- .proc_handler = proc_dointvec,
- },
-#ifdef CONFIG_SYSCTL_ARCH_UNALIGN_ALLOW
- {
- .procname = "unaligned-trap",
- .data = &unaligned_enabled,
- .maxlen = sizeof(int),
- .mode = 0644,
- .proc_handler = proc_dointvec,
- },
-#endif
-#ifdef CONFIG_SYSCTL_ARCH_UNALIGN_NO_WARN
- {
- .procname = "ignore-unaligned-usertrap",
- .data = &no_unaligned_warning,
- .maxlen = sizeof (int),
- .mode = 0644,
- .proc_handler = proc_dointvec,
- },
-#endif
-};
-
-int __init sysctl_init_bases(void)
-{
- register_sysctl_init("kernel", sysctl_subsys_table);
-
- return 0;
-}
#endif /* CONFIG_SYSCTL */
/*
* No sense putting this after each symbol definition, twice,
diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c
index 1ee9e0a1dd6e..af1c69bf0c23 100644
--- a/kernel/time/jiffies.c
+++ b/kernel/time/jiffies.c
@@ -102,6 +102,8 @@ void __init register_refined_jiffies(long cycles_per_second)
#ifdef CONFIG_SYSCTL
static ulong mult_hz(const ulong val)
{
+ if (val >= ULONG_MAX / HZ)
+ return ULONG_MAX;
return val * HZ;
}
diff --git a/lib/test_sysctl.c b/lib/test_sysctl.c
index 909cfcf76dbf..65ce42346170 100644
--- a/lib/test_sysctl.c
+++ b/lib/test_sysctl.c
@@ -15,6 +15,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
+#include <linux/jiffies.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/printk.h>
@@ -48,6 +49,10 @@ struct test_sysctl_data {
int boot_int;
+ int int_jiffies;
+ int int_ms_jiffies;
+ int int_userhz_jiffies;
+
unsigned int uint_0001;
char string_0001[65];
@@ -67,6 +72,11 @@ static struct test_sysctl_data test_data = {
.boot_int = 0,
+ /* One second in each converter's unit; all three store HZ jiffies. */
+ .int_jiffies = HZ,
+ .int_ms_jiffies = HZ,
+ .int_userhz_jiffies = HZ,
+
.uint_0001 = 314,
.string_0001 = "(none)",
@@ -114,6 +124,27 @@ static const struct ctl_table test_table[] = {
.extra2 = SYSCTL_ONE,
},
{
+ .procname = "int_jiffies",
+ .data = &test_data.int_jiffies,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_jiffies,
+ },
+ {
+ .procname = "int_ms_jiffies",
+ .data = &test_data.int_ms_jiffies,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_ms_jiffies,
+ },
+ {
+ .procname = "int_userhz_jiffies",
+ .data = &test_data.int_userhz_jiffies,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_userhz_jiffies,
+ },
+ {
.procname = "uint_0001",
.data = &test_data.uint_0001,
.maxlen = sizeof(unsigned int),
@@ -321,11 +352,11 @@ module_init(test_sysctl_init);
static void __exit test_sysctl_exit(void)
{
- kfree(test_data.bitmap_0001);
for (int i = 0; i < TEST_H_SIZE; i++) {
if (ctl_headers[i])
unregister_sysctl_table(ctl_headers[i]);
}
+ kfree(test_data.bitmap_0001);
}
module_exit(test_sysctl_exit);
diff --git a/tools/testing/selftests/sysctl/sysctl.sh b/tools/testing/selftests/sysctl/sysctl.sh
index b2d8bd9026a7..ad1ee0fccd7c 100755
--- a/tools/testing/selftests/sysctl/sysctl.sh
+++ b/tools/testing/selftests/sysctl/sysctl.sh
@@ -2,345 +2,239 @@
# SPDX-License-Identifier: GPL-2.0-or-later OR copyleft-next-0.3.1
# Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
-# This performs a series tests against the proc sysctl interface.
-
-# Kselftest framework requirement - SKIP code is 4.
-ksft_skip=4
-
-TEST_NAME="sysctl"
-TEST_DRIVER="test_${TEST_NAME}"
-TEST_DIR=$(dirname $0)
-TEST_FILE=$(mktemp)
-
-# This represents
-#
-# TEST_ID:TEST_COUNT:ENABLED:TARGET:SKIP_NO_TARGET
-#
-# TEST_ID: is the test id number
-# TEST_COUNT: number of times we should run the test
-# ENABLED: 1 if enabled, 0 otherwise
-# TARGET: test target file required on the test_sysctl module
-# SKIP_NO_TARGET: 1 skip if TARGET not there
-# 0 run even though TARGET not there
-#
-# Once these are enabled please leave them as-is. Write your own test,
-# we have tons of space.
-ALL_TESTS="0001:1:1:int_0001:1"
-ALL_TESTS="$ALL_TESTS 0002:1:1:string_0001:1"
-ALL_TESTS="$ALL_TESTS 0003:1:1:int_0002:1"
-ALL_TESTS="$ALL_TESTS 0004:1:1:uint_0001:1"
-ALL_TESTS="$ALL_TESTS 0005:3:1:int_0003:1"
-ALL_TESTS="$ALL_TESTS 0006:50:1:bitmap_0001:1"
-ALL_TESTS="$ALL_TESTS 0007:1:1:boot_int:1"
-ALL_TESTS="$ALL_TESTS 0008:1:1:match_int:1"
-ALL_TESTS="$ALL_TESTS 0009:1:1:unregister_error:0"
-ALL_TESTS="$ALL_TESTS 0010:1:1:mnt/mnt_error:0"
-ALL_TESTS="$ALL_TESTS 0011:1:1:empty_add:0"
-ALL_TESTS="$ALL_TESTS 0012:1:1:u8_valid:0"
-
-function allow_user_defaults()
+# Reduce noise by disabling "unreachable" check.
+# shellcheck disable=SC2317
+
+# This performs a series of tests against the proc sysctl interface.
+
+DIR="$(dirname "$(readlink -f "$0")")"
+source "${DIR}"/../kselftest/ktap_helpers.sh
+
+TEST_DRIVER="test_sysctl"
+
+assert_write_rejected() # <file> <value>
{
- if [ -z $DIR ]; then
- DIR="/sys/module/test_sysctl/"
- fi
- if [ -z $DEFAULT_NUM_TESTS ]; then
- DEFAULT_NUM_TESTS=50
- fi
- if [ -z $SYSCTL ]; then
- SYSCTL="/proc/sys/debug/test_sysctl"
- fi
- if [ -z $PROD_SYSCTL ]; then
- PROD_SYSCTL="/proc/sys"
- fi
- if [ -z $WRITES_STRICT ]; then
- WRITES_STRICT="${PROD_SYSCTL}/kernel/sysctl_writes_strict"
+ local file=$1 val=$2
+ if printf '%s' "$val" > "$file" 2>/dev/null; then
+ ktap_print_msg "$file: write of '$val' succeeded, expected rejection"
+ return 1
fi
+ return 0
}
-function check_production_sysctl_writes_strict()
+assert_content() # <file> <want-file>
{
- echo -n "Checking production write strict setting ... "
- if [ ! -e ${WRITES_STRICT} ]; then
- echo "FAIL, but skip in case of old kernel" >&2
- else
- old_strict=$(cat ${WRITES_STRICT})
- if [ "$old_strict" = "1" ]; then
- echo "OK"
- else
- echo "FAIL, strict value is 0 but force to 1 to continue" >&2
- echo "1" > ${WRITES_STRICT}
- fi
- fi
-
- if [ -z $PAGE_SIZE ]; then
- PAGE_SIZE=$(getconf PAGESIZE)
- fi
- if [ -z $MAX_DIGITS ]; then
- MAX_DIGITS=$(($PAGE_SIZE/8))
- fi
- if [ -z $INT_MAX ]; then
- INT_MAX=$(getconf INT_MAX)
- fi
- if [ -z $UINT_MAX ]; then
- UINT_MAX=$(getconf UINT_MAX)
+ local file=$1 want_file=$2 got
+ got=$(cat "$file")
+ if [ "$got" == "$want_file" ]; then
+ return 0
fi
+ ktap_print_msg "$file: got '$got', want '$want_file'"
+ return 1
}
-test_reqs()
+# compare ignoring whitespaces
+assert_content_loose() # <file> <want-file>
{
- uid=$(id -u)
- if [ $uid -ne 0 ]; then
- echo $msg must be run as root >&2
- exit $ksft_skip
+ local file=$1 want_file=$2
+ if echo "$want_file" | diff -q -w -u - "$file" > /dev/null; then
+ return 0
fi
+ ktap_print_msg "$file: content does not match '$want_file' (whitespace-insensitive)"
+ return 1
+}
- if ! which perl 2> /dev/null > /dev/null; then
- echo "$0: You need perl installed"
- exit $ksft_skip
- fi
- if ! which getconf 2> /dev/null > /dev/null; then
- echo "$0: You need getconf installed"
- exit $ksft_skip
- fi
- if ! which diff 2> /dev/null > /dev/null; then
- echo "$0: You need diff installed"
- exit $ksft_skip
+# For checks where writes must NOT take effect
+assert_content_rejected() # <file> <bad>
+{
+ local file=$1 bad=$2
+ if echo "$bad" | diff -q -w -u - "$file" > /dev/null; then
+ ktap_print_msg "$file: unexpectedly now holds rejected value '$bad'"
+ return 1
fi
+ return 0
}
-function load_req_mod()
+# proc files get read a page at a time, which can confuse diff resulting in
+# incorrect results. Use a temp file to diff.
+assert_diff_proc_file() # <file> <want-file>
{
- if [ ! -d $SYSCTL ]; then
- if ! modprobe -q -n $TEST_DRIVER; then
- echo "$0: module $TEST_DRIVER not found [SKIP]"
- echo "You must set CONFIG_TEST_SYSCTL=m in your kernel" >&2
- exit $ksft_skip
- fi
- modprobe $TEST_DRIVER
- if [ $? -ne 0 ]; then
- echo "$0: modprobe $TEST_DRIVER failed."
- exit
- fi
+ local file=$1 want_file=$2 tmp
+ tmp=$(mktemp)
+ cat "$file" > "$tmp"
+
+ if diff -w -q "$tmp" "$want_file" > /dev/null; then
+ rm -f "$tmp"
+ return 0
fi
+ ktap_print_msg "$file: content does not match expected file $want_file"
+ rm -f "$tmp"
+ return 1
}
-reset_vals()
+# Verify that an erroneous update fails and does not change the baseline.
+assert_no_partial_update() # <file> <baseline> <bad-value>
{
- VAL=""
- TRIGGER=$(basename ${TARGET})
- case "$TRIGGER" in
- int_0001)
- VAL="60"
- ;;
- int_0002)
- VAL="1"
- ;;
- uint_0001)
- VAL="314"
- ;;
- string_0001)
- VAL="(none)"
- ;;
- bitmap_0001)
- VAL=""
- ;;
- *)
- ;;
- esac
- echo -n $VAL > $TARGET
+ local file=$1 baseline=$2 bad=$3
+
+ printf '%s' "$baseline" > "$file"
+ assert_write_rejected "$file" "$bad" || return 1
+ assert_content_loose "$file" "$baseline"
}
-set_orig()
+assert_grep() # <file> <pattern> - <pattern> must appear in <file>
{
- if [ ! -z $TARGET ] && [ ! -z $ORIG ]; then
- if [ -f ${TARGET} ]; then
- echo "${ORIG}" > "${TARGET}"
- fi
+ local file=$1 pattern=$2
+ if grep -q "$pattern" "$file"; then
+ return 0
fi
+ ktap_print_msg "$file: pattern '$pattern' not found"
+ return 1
}
-set_test()
+assert_grep_absent() # <file> <pattern> - <pattern> must NOT appear
{
- echo "${TEST_STR}" > "${TARGET}"
+ local file=$1 pattern=$2
+ if ! grep -q "$pattern" "$file"; then
+ return 0
+ fi
+ ktap_print_msg "$file: pattern '$pattern' unexpectedly found"
+ return 1
}
-verify()
+assert_dmesg_count() # <pattern> <want-count>
{
- local seen
- seen=$(cat "$1")
- if [ "${seen}" != "${TEST_STR}" ]; then
- return 1
+ local pattern=$1 want=$2 got
+ got=$(dmesg | grep -c "$pattern")
+ if [ "$got" -eq "$want" ]; then
+ return 0
fi
- return 0
+ ktap_print_msg "dmesg: pattern '$pattern' seen $got times, want $want"
+ return 1
}
-# proc files get read a page at a time, which can confuse diff,
-# and get you incorrect results on proc files with long data. To use
-# diff against them you must first extract the output to a file, and
-# then compare against that file.
-verify_diff_proc_file()
+# write <str> through dd in <bs>-sized chunks
+write_in_chunks() # <file> <str> <bs>
{
- TMP_DUMP_FILE=$(mktemp)
- cat $1 > $TMP_DUMP_FILE
+ local file=$1 str=$2 bs=$3
+ printf '%s' "$str" | dd of="$file" bs="$bs" 2>/dev/null
+}
- if ! diff -w -q $TMP_DUMP_FILE $2; then
- return 1
+# write <str> through dd in <bs>-sized chunks. seeking <seek> blocks of size
+# <bs> into the output. skipping <skip> blocks of size <bs> from the input.
+# Unsyncrhonized when skip is not passed.
+write_at_offset() # <file> <str> <bs> <seek> [skip]
+{
+ local file=$1 str=$2 bs=$3 seek=$4 skip=${5:-}
+ if [ -n "$skip" ]; then
+ printf '%s' "$str" | dd of="$file" bs="$bs" seek="$seek" skip="$skip" 2>/dev/null
else
- return 0
+ printf '%s' "$str" | dd of="$file" bs="$bs" seek="$seek" 2>/dev/null
fi
}
-verify_diff_w()
+# pad (prefix) <str> with <pad> 'A' characters, then write it all to <file>
+# with dd block size <bs> (defaults to <pad>).
+write_overlong() # <file> <pad> <str> [bs]
{
- echo "$TEST_STR" | diff -q -w -u - $1 > /dev/null
- return $?
+ local file=$1 pad=$2 str=$3 bs=$4 p
+ : "${bs:=$pad}"
+
+ # save a string with $pad spaces in $p
+ printf -v p '%*s' "$pad" ''
+ # replace " " with "A"
+ printf '%s%s' "${p// /A}" "$str" | dd of="$file" bs="$bs" 2>/dev/null
}
-test_rc()
+# pad (prefix) <str> with <pad> ASCII spaces, then write with default block size.
+write_padded_digits() # <file> <pad> <val>
{
- if [[ $rc != 0 ]]; then
- echo "Failed test, return value: $rc" >&2
- exit $rc
- fi
+ local file=$1 pad=$2 val=$3 p
+ printf -v p '%*s' "$pad" ''
+ printf '%s%s' "$p" "$val" | dd of="$file" 2>/dev/null
}
-test_finish()
+test_int_array_writes()
{
- set_orig
- rm -f "${TEST_FILE}"
+ local t="${SYSCTL}/int_0003"
+ local limit
- if [ ! -z ${old_strict} ]; then
- echo ${old_strict} > ${WRITES_STRICT}
- fi
- exit $rc
+ [[ -f "$t" ]] || { RET=$KSFT_SKIP; return; }
+
+ printf '%s' "4 3 2 1" > "$t"
+ assert_content_loose "$t" "4 3 2 1" || RET=$KSFT_FAIL
+
+ # Skipping trailing elements leaves them intact
+ printf '%s' "100 101" > "$t"
+ assert_content_loose "$t" "100 101 2 1" || RET=$KSFT_FAIL
+
+ # Even for an int array, a single write is still capped at MAX_DIGITS
+ # (PAGE_SIZE/8) bytes; check right at, and just past, that boundary.
+ # Carries on the state from the writes above.
+ limit=$((MAX_DIGITS - 1))
+ write_padded_digits "$t" "$limit" "9"
+ assert_content_loose "$t" "9 101 2 1" || RET=$KSFT_FAIL
+
+ limit=$((MAX_DIGITS))
+ write_padded_digits "$t" "$limit" "7"
+ assert_content_rejected "$t" "7 101 2 1" || RET=$KSFT_FAIL
}
-run_numerictests()
+test_int_array_no_partial_update()
{
- echo "== Testing sysctl behavior against ${TARGET} =="
+ local t="${SYSCTL}/int_0003"
- rc=0
+ [[ -f "$t" ]] || { RET=$KSFT_SKIP; return; }
- echo -n "Writing test file ... "
- echo "${TEST_STR}" > "${TEST_FILE}"
- if ! verify "${TEST_FILE}"; then
- echo "FAIL" >&2
- exit 1
- else
- echo "OK"
- fi
+ # bad 3rd element (not a number)
+ assert_no_partial_update "$t" "1 2 3 4" "10 20 abc 40" || RET=$KSFT_FAIL
- echo -n "Checking sysctl is not set to test value ... "
- if verify "${TARGET}"; then
- echo "FAIL" >&2
- exit 1
- else
- echo "OK"
- fi
+ # bad 3rd element (overflows int)
+ assert_no_partial_update "$t" "1 2 3 4" "10 20 $((INT_MAX + 1)) 40" || RET=$KSFT_FAIL
- echo -n "Writing sysctl from shell ... "
- set_test
- if ! verify "${TARGET}"; then
- echo "FAIL" >&2
- exit 1
- else
- echo "OK"
- fi
+ # bad 1st element
+ assert_no_partial_update "$t" "1 2 3 4" "abc 20 30 40" || RET=$KSFT_FAIL
+}
- echo -n "Resetting sysctl to original value ... "
- set_orig
- if verify "${TARGET}"; then
- echo "FAIL" >&2
- exit 1
- else
- echo "OK"
- fi
+# Exercise the write patterns
+assert_write_patterns() # <file> <baseline> <val>
+{
+ local file=$1 baseline=$2 val=$3 status=0
- # Now that we've validated the sanity of "set_test" and "set_orig",
- # we can use those functions to set starting states before running
- # specific behavioral tests.
+ printf '%s' "$val" > "$file"
+ assert_content "$file" "$val" || status=1
+ printf '%s' "$baseline" > "$file"
+ assert_content "$file" "$baseline" || status=1
- echo -n "Writing entire sysctl in single write ... "
- set_orig
- dd if="${TEST_FILE}" of="${TARGET}" bs=4096 2>/dev/null
- if ! verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
+ printf '%s' "$baseline" > "$file"
+ write_in_chunks "$file" "$val" 4096
+ assert_content "$file" "$val" || status=1
- echo -n "Writing middle of sysctl after synchronized seek ... "
- set_test
- dd if="${TEST_FILE}" of="${TARGET}" bs=1 seek=1 skip=1 2>/dev/null
- if ! verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
+ printf '%s' "$val" > "$file"
+ write_at_offset "$file" "$val" 1 1 1
+ assert_content "$file" "$val" || status=1
- echo -n "Writing beyond end of sysctl ... "
- set_orig
- dd if="${TEST_FILE}" of="${TARGET}" bs=20 seek=2 2>/dev/null
- if verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
+ printf '%s' "$baseline" > "$file"
+ write_at_offset "$file" "$val" 20 2
+ assert_content_rejected "$file" "$val" || status=1
- echo -n "Writing sysctl with multiple long writes ... "
- set_orig
- (perl -e 'print "A" x 50;'; echo "${TEST_STR}") | \
- dd of="${TARGET}" bs=50 2>/dev/null
- if verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
-}
+ printf '%s' "$baseline" > "$file"
+ write_overlong "$file" 50 "$val"
+ assert_content_rejected "$file" "$val" || status=1
-check_failure()
-{
- echo -n "Testing that $1 fails as expected ... "
- reset_vals
- TEST_STR="$1"
- orig="$(cat $TARGET)"
- echo -n "$TEST_STR" > $TARGET 2> /dev/null
-
- # write should fail and $TARGET should retain its original value
- if [ $? = 0 ] || [ "$(cat $TARGET)" != "$orig" ]; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
+ return "$status"
}
-run_wideint_tests()
+# None of these fit in 32 bits, so writing them to an int/uint sysctl must fail
+assert_wideint_writes_rejected() # <file> <baseline>
{
- # sysctl conversion functions receive a boolean sign and ulong
- # magnitude; here we list the magnitudes we want to test (each of
- # which will be tested in both positive and negative forms). Since
- # none of these values fit in 32 bits, writing them to an int- or
- # uint-typed sysctl should fail.
+ local file=$1 baseline=$2 sign mag status=0
local magnitudes=(
- # common boundary-condition values (zero, +1, -1, INT_MIN,
- # and INT_MAX respectively) if truncated to lower 32 bits
- # (potential for being falsely deemed in range)
0x0000000100000000
0x0000000100000001
0x00000001ffffffff
0x0000000180000000
0x000000017fffffff
-
- # these look like negatives, but without a leading '-' are
- # actually large positives (should be rejected as above
- # despite being zero/+1/-1/INT_MIN/INT_MAX in the lower 32)
0xffffffff00000000
0xffffffff00000001
0xffffffffffffffff
@@ -350,750 +244,538 @@ run_wideint_tests()
for sign in '' '-'; do
for mag in "${magnitudes[@]}"; do
- check_failure "${sign}${mag}"
+ assert_no_partial_update "$file" "$baseline" "${sign}${mag}" || status=1
done
done
+ return "$status"
}
-# Your test must accept digits 3 and 4 to use this
-run_limit_digit()
+# A single write is capped at MAX_DIGITS (PAGE_SIZE/8) bytes; check that
+# leading whitespace up to that limit is ignored, and that PAGE_SIZE of
+# leading whitespace pushes the value past the limit and fails.
+assert_digit_limit() # <file> <baseline>
{
- echo -n "Checking ignoring spaces up to PAGE_SIZE works on write ... "
- reset_vals
+ local file=$1 baseline=$2 status=0
- LIMIT=$((MAX_DIGITS -1))
- TEST_STR="3"
- (perl -e 'print " " x '$LIMIT';'; echo "${TEST_STR}") | \
- dd of="${TARGET}" 2>/dev/null
+ printf '%s' "$baseline" > "$file"
+ write_padded_digits "$file" "$((MAX_DIGITS - 1))" "3"
+ assert_content "$file" "3" || status=1
- if ! verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
-
- echo -n "Checking passing PAGE_SIZE of spaces fails on write ... "
- reset_vals
-
- LIMIT=$((MAX_DIGITS))
- TEST_STR="4"
- (perl -e 'print " " x '$LIMIT';'; echo "${TEST_STR}") | \
- dd of="${TARGET}" 2>/dev/null
+ printf '%s' "$baseline" > "$file"
+ write_padded_digits "$file" "$MAX_DIGITS" "4"
+ assert_content_rejected "$file" "4" || status=1
- if verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
+ return "$status"
}
-# You are using an int
-run_limit_digit_int()
+# proc_dointvec()-specific range checks: INT_MAX must be accepted, INT_MAX+1
+# must be rejected, and negative values must be accepted.
+assert_int_range() # <file> <baseline>
{
- echo -n "Testing INT_MAX works ... "
- reset_vals
- TEST_STR="$INT_MAX"
- echo -n $TEST_STR > $TARGET
-
- if ! verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
+ local file=$1 baseline=$2 status=0
- echo -n "Testing INT_MAX + 1 will fail as expected ... "
- reset_vals
- let TEST_STR=$INT_MAX+1
- echo -n $TEST_STR > $TARGET 2> /dev/null
+ printf '%s' "$baseline" > "$file"
+ printf '%s' "$INT_MAX" > "$file"
+ assert_content "$file" "$INT_MAX" || status=1
- if verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
-
- echo -n "Testing negative values will work as expected ... "
- reset_vals
- TEST_STR="-3"
- echo -n $TEST_STR > $TARGET 2> /dev/null
- if ! verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
-}
+ assert_no_partial_update "$file" "$baseline" "$((INT_MAX + 1))" || status=1
-# You used an int array
-run_limit_digit_int_array()
-{
- echo -n "Testing array works as expected ... "
- TEST_STR="4 3 2 1"
- echo -n $TEST_STR > $TARGET
+ printf '%s' "$baseline" > "$file"
+ printf '%s' "-3" > "$file" 2>/dev/null
+ assert_content "$file" "-3" || status=1
- if ! verify_diff_w "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
-
- echo -n "Testing skipping trailing array elements works ... "
- # Do not reset_vals, carry on the values from the last test.
- # If we only echo in two digits the last two are left intact
- TEST_STR="100 101"
- echo -n $TEST_STR > $TARGET
- # After we echo in, to help diff we need to set on TEST_STR what
- # we expect the result to be.
- TEST_STR="100 101 2 1"
-
- if ! verify_diff_w "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
-
- echo -n "Testing PAGE_SIZE limit on array works ... "
- # Do not reset_vals, carry on the values from the last test.
- # Even if you use an int array, you are still restricted to
- # MAX_DIGITS, this is a known limitation. Test limit works.
- LIMIT=$((MAX_DIGITS -1))
- TEST_STR="9"
- (perl -e 'print " " x '$LIMIT';'; echo "${TEST_STR}") | \
- dd of="${TARGET}" 2>/dev/null
-
- TEST_STR="9 101 2 1"
- if ! verify_diff_w "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
-
- echo -n "Testing exceeding PAGE_SIZE limit fails as expected ... "
- # Do not reset_vals, carry on the values from the last test.
- # Now go over limit.
- LIMIT=$((MAX_DIGITS))
- TEST_STR="7"
- (perl -e 'print " " x '$LIMIT';'; echo "${TEST_STR}") | \
- dd of="${TARGET}" 2>/dev/null
-
- TEST_STR="7 101 2 1"
- if verify_diff_w "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
+ return "$status"
}
-# You are using an unsigned int
-run_limit_digit_uint()
+# proc_douintvec()-specific range checks: UINT_MAX must be accepted,
+# UINT_MAX+1 must be rejected, and negative values must be rejected too.
+assert_uint_range() # <file> <baseline>
{
- echo -n "Testing UINT_MAX works ... "
- reset_vals
- TEST_STR="$UINT_MAX"
- echo -n $TEST_STR > $TARGET
-
- if ! verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
+ local file=$1 baseline=$2 status=0
- echo -n "Testing UINT_MAX + 1 will fail as expected ... "
- reset_vals
- TEST_STR=$(($UINT_MAX+1))
- echo -n $TEST_STR > $TARGET 2> /dev/null
+ printf '%s' "$baseline" > "$file"
+ printf '%s' "$UINT_MAX" > "$file"
+ assert_content "$file" "$UINT_MAX" || status=1
- if verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
+ assert_no_partial_update "$file" "$baseline" "$((UINT_MAX + 1))" || status=1
+ assert_no_partial_update "$file" "$baseline" "-3" || status=1
- echo -n "Testing negative values will not work as expected ... "
- reset_vals
- TEST_STR="-3"
- echo -n $TEST_STR > $TARGET 2> /dev/null
+ return "$status"
+}
- if verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
- test_rc
+test_int_minmax()
+{
+ local t="${SYSCTL}/int_0001"
+ local baseline="60" val="61"
+
+ [[ -f "$t" ]] || { RET=$KSFT_SKIP; return; }
+
+ assert_write_patterns "$t" "$baseline" "$val" || RET=$KSFT_FAIL
+ assert_wideint_writes_rejected "$t" "$baseline" || RET=$KSFT_FAIL
+ assert_digit_limit "$t" "$baseline" || RET=$KSFT_FAIL
}
-run_stringtests()
+test_int_plain()
{
- echo -n "Writing entire sysctl in short writes ... "
- set_orig
- dd if="${TEST_FILE}" of="${TARGET}" bs=1 2>/dev/null
- if ! verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
+ local t="${SYSCTL}/int_0002"
+ local baseline="1" val="2"
- echo -n "Writing middle of sysctl after unsynchronized seek ... "
- set_test
- dd if="${TEST_FILE}" of="${TARGET}" bs=1 seek=1 2>/dev/null
- if verify "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
+ [[ -f "$t" ]] || { RET=$KSFT_SKIP; return; }
- echo -n "Checking sysctl maxlen is at least $MAXLEN ... "
- set_orig
- perl -e 'print "A" x ('"${MAXLEN}"'-2), "B";' | \
- dd of="${TARGET}" bs="${MAXLEN}" 2>/dev/null
- if ! grep -q B "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
+ assert_write_patterns "$t" "$baseline" "$val" || RET=$KSFT_FAIL
+ assert_wideint_writes_rejected "$t" "$baseline" || RET=$KSFT_FAIL
+ assert_digit_limit "$t" "$baseline" || RET=$KSFT_FAIL
+ assert_int_range "$t" "$baseline" || RET=$KSFT_FAIL
+}
- echo -n "Checking sysctl keeps original string on overflow append ... "
- set_orig
- perl -e 'print "A" x ('"${MAXLEN}"'-1), "B";' | \
- dd of="${TARGET}" bs=$(( MAXLEN - 1 )) 2>/dev/null
- if grep -q B "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
+# The jiffies converters report the sign separately from the magnitude, so a
+# negative value must survive the round trip through the converter. Each
+# value below is chosen to convert exactly at every CONFIG_HZ.
+test_int_jiffies_negative()
+{
+ local t="${SYSCTL}/int_jiffies"
+ local m="${SYSCTL}/int_ms_jiffies"
+ local u="${SYSCTL}/int_userhz_jiffies"
- echo -n "Checking sysctl stays NULL terminated on write ... "
- set_orig
- perl -e 'print "A" x ('"${MAXLEN}"'-1), "B";' | \
- dd of="${TARGET}" bs="${MAXLEN}" 2>/dev/null
- if grep -q B "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
+ [[ -f "$t" && -f "$m" && -f "$u" ]] || { RET=$KSFT_SKIP; return; }
- echo -n "Checking sysctl stays NULL terminated on overwrite ... "
- set_orig
- perl -e 'print "A" x ('"${MAXLEN}"'-1), "BB";' | \
- dd of="${TARGET}" bs=$(( $MAXLEN + 1 )) 2>/dev/null
- if grep -q B "${TARGET}"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- fi
+ printf '%s' "-1" > "$t"
+ assert_content "$t" "-1" || RET=$KSFT_FAIL
+ printf '%s' "-1000" > "$m"
+ assert_content "$m" "-1000" || RET=$KSFT_FAIL
+ printf '%s' "-100" > "$u"
+ assert_content "$u" "-100" || RET=$KSFT_FAIL
- test_rc
+ # The positive path was never broken; keep it covered.
+ printf '%s' "1" > "$t"
+ assert_content "$t" "1" || RET=$KSFT_FAIL
}
-target_exists()
+test_uint_plain()
{
- TARGET="${SYSCTL}/$1"
- TEST_ID="$2"
+ local t="${SYSCTL}/uint_0001"
+ local baseline="314" val="315"
- if [ ! -f ${TARGET} ] ; then
- return 0
- fi
- return 1
-}
+ [[ -f "$t" ]] || { RET=$KSFT_SKIP; return; }
-run_bitmaptest() {
- # Total length of bitmaps string to use, a bit under
- # the maximum input size of the test node
- LENGTH=$((RANDOM % 65000))
+ assert_write_patterns "$t" "$baseline" "$val" || RET=$KSFT_FAIL
+ assert_wideint_writes_rejected "$t" "$baseline" || RET=$KSFT_FAIL
+ assert_digit_limit "$t" "$baseline" || RET=$KSFT_FAIL
+ assert_uint_range "$t" "$baseline" || RET=$KSFT_FAIL
+}
- # First bit to set
- BIT=$((RANDOM % 1024))
+# proc_dostring()-specific write patterns
+assert_string_write_patterns() # <file> <baseline> <val> <maxlen>
+{
+ local file=$1 baseline=$2 val=$3 maxlen=$4 status=0
- # String containing our list of bits to set
- TEST_STR=$BIT
+ printf '%s' "$baseline" > "$file"
+ write_in_chunks "$file" "$val" 1
+ assert_content "$file" "$val" || status=1
- # build up the string
- while [ "${#TEST_STR}" -le "$LENGTH" ]; do
- # Make sure next entry is discontiguous,
- # skip ahead at least 2
- BIT=$((BIT + $((2 + RANDOM % 10))))
+ printf '%s' "$val" > "$file"
+ write_at_offset "$file" "$val" 1 1
+ assert_content_rejected "$file" "$val" || status=1
- # Add new bit to the list
- TEST_STR="${TEST_STR},${BIT}"
+ # sysctl maxlen is at least $maxlen
+ printf '%s' "$baseline" > "$file"
+ write_overlong "$file" "$((maxlen - 2))" "B" "$maxlen"
+ assert_grep "$file" "B" || status=1
- # Randomly make it a range
- if [ "$((RANDOM % 2))" -eq "1" ]; then
- RANGE_END=$((BIT + $((1 + RANDOM % 10))))
- TEST_STR="${TEST_STR}-${RANGE_END}"
- BIT=$RANGE_END
- fi
- done
+ # keep original string on overflow append
+ printf '%s' "$baseline" > "$file"
+ write_overlong "$file" "$((maxlen - 1))" "B" "$((maxlen - 1))"
+ assert_grep_absent "$file" "B" || status=1
- echo -n "Checking bitmap handler ... "
- TEST_FILE=$(mktemp)
- echo -n "$TEST_STR" > $TEST_FILE
+ # sysctl stays NULL terminated on write
+ printf '%s' "$baseline" > "$file"
+ write_overlong "$file" "$((maxlen - 1))" "B" "$maxlen"
+ assert_grep_absent "$file" "B" || status=1
- cat $TEST_FILE > $TARGET 2> /dev/null
- if [ $? -ne 0 ]; then
- echo "FAIL" >&2
- rc=1
- test_rc
- fi
+ # sysctl stays NULL terminated on overwrite
+ printf '%s' "$baseline" > "$file"
+ write_overlong "$file" "$((maxlen - 1))" "BB" "$((maxlen + 1))"
+ assert_grep_absent "$file" "B" || status=1
- if ! verify_diff_proc_file "$TARGET" "$TEST_FILE"; then
- echo "FAIL" >&2
- rc=1
- else
- echo "OK"
- rc=0
- fi
- test_rc
+ return "$status"
}
-sysctl_test_0001()
+test_string_dostring()
{
- TARGET="${SYSCTL}/$(get_test_target 0001)"
- reset_vals
- ORIG=$(cat "${TARGET}")
- TEST_STR=$(( $ORIG + 1 ))
-
- run_numerictests
- run_wideint_tests
- run_limit_digit
-}
+ local t="${SYSCTL}/string_0001"
+ local baseline="(none)" val="Testing sysctl" maxlen=65
-sysctl_test_0002()
-{
- TARGET="${SYSCTL}/$(get_test_target 0002)"
- reset_vals
- ORIG=$(cat "${TARGET}")
- TEST_STR="Testing sysctl"
- # Only string sysctls support seeking/appending.
- MAXLEN=65
-
- run_numerictests
- run_stringtests
-}
+ [[ -f "$t" ]] || { RET=$KSFT_SKIP; return; }
-sysctl_test_0003()
-{
- TARGET="${SYSCTL}/$(get_test_target 0003)"
- reset_vals
- ORIG=$(cat "${TARGET}")
- TEST_STR=$(( $ORIG + 1 ))
-
- run_numerictests
- run_wideint_tests
- run_limit_digit
- run_limit_digit_int
+ assert_write_patterns "$t" "$baseline" "$val" || RET=$KSFT_FAIL
+ assert_string_write_patterns "$t" "$baseline" "$val" "$maxlen" || RET=$KSFT_FAIL
}
-sysctl_test_0004()
+# Emit a random sysctl bit map
+random_bitmap_spec()
{
- TARGET="${SYSCTL}/$(get_test_target 0004)"
- reset_vals
- ORIG=$(cat "${TARGET}")
- TEST_STR=$(( $ORIG + 1 ))
-
- run_numerictests
- run_wideint_tests
- run_limit_digit
- run_limit_digit_uint
-}
+ local length=$((RANDOM % 65000))
+ local bit=$((RANDOM % 1024))
+ local spec=$bit
+ local range_end
-sysctl_test_0005()
-{
- TARGET="${SYSCTL}/$(get_test_target 0005)"
- reset_vals
- ORIG=$(cat "${TARGET}")
+ while [ "${#spec}" -le "$length" ]; do
+ # Keep entries discontiguous, skip ahead by at least 2.
+ bit=$((bit + 2 + RANDOM % 10))
+ spec="${spec},${bit}"
- run_limit_digit_int_array
-}
+ if [ "$((RANDOM % 2))" -eq 1 ]; then
+ range_end=$((bit + 1 + RANDOM % 10))
+ spec="${spec}-${range_end}"
+ bit=$range_end
+ fi
+ done
-sysctl_test_0006()
-{
- TARGET="${SYSCTL}/$(get_test_target 0006)"
- reset_vals
- ORIG=""
- run_bitmaptest
+ echo -n "$spec"
}
-sysctl_test_0007()
+# Do *not* prefix with "test_". All autogenerate bitmap tests call this helper
+bitmap_range_check_once() # <iter>
{
- TARGET="${SYSCTL}/$(get_test_target 0007)"
- echo -n "Testing if $TARGET is set to 1 ... "
-
- if [ ! -f $TARGET ]; then
- echo -e "SKIPPING\n$TARGET is not present"
- return $ksft_skip
- fi
-
- if [ -d $DIR ]; then
- echo -e "SKIPPING\nTest only possible if sysctl_test is built-in, not module:"
- cat $TEST_DIR/config >&2
- return $ksft_skip
- fi
+ local t="${SYSCTL}/bitmap_0001"
+ local spec_file seed
- ORIG=$(cat "${TARGET}")
+ [[ -f "$t" ]] || { RET=$KSFT_SKIP; return; }
- if [ x$ORIG = "x1" ]; then
- echo "OK"
- return 0
- fi
+ seed=$((BITMAP_SEED + $1))
+ RANDOM=$seed
- if [ ! -f /proc/cmdline ]; then
- echo -e "SKIPPING\nThere is no /proc/cmdline to check for parameter"
- return $ksft_skip
- fi
+ spec_file=$(mktemp)
+ random_bitmap_spec > "$spec_file"
- FOUND=$(grep -c "sysctl[./]debug[./]test_sysctl[./]boot_int=1" /proc/cmdline)
- if [ $FOUND = "1" ]; then
- echo -e "FAIL\nKernel param found but $TARGET is not 1." >&2
- rc=1
- test_rc
+ if ! cat "$spec_file" > "$t" 2>/dev/null; then
+ ktap_print_msg "$t: write of random bitmap spec rejected (seed $seed)"
+ RET=$KSFT_FAIL
+ elif ! assert_diff_proc_file "$t" "$spec_file"; then
+ ktap_print_msg "$t: readback mismatch (seed $seed)"
+ RET=$KSFT_FAIL
fi
- echo -e "SKIPPING\nExpected kernel parameter missing."
- echo "Kernel must be booted with parameter: sysctl.debug.test_sysctl.boot_int=1"
- return $ksft_skip
+ rm -f "$spec_file"
}
-sysctl_test_0008()
+test_unregister_removes_dir()
{
- TARGET="${SYSCTL}/$(get_test_target 0008)"
- echo -n "Testing if $TARGET is matched in kernel ... "
+ local t="${SYSCTL}/unregister_error"
- if [ ! -f $TARGET ]; then
- echo -e "SKIPPING\n$TARGET is not present"
- return $ksft_skip
+ if [[ -d "$t" ]]; then
+ ktap_print_msg "$t: directory still exists, expected it to be unregistered"
+ RET=$KSFT_FAIL
fi
+}
- ORIG_VALUE=$(cat "${TARGET}")
+test_mount_point_error()
+{
+ local t="${SYSCTL}/mnt/mnt_error"
- if [ $ORIG_VALUE -ne 1 ]; then
- echo "FAIL" >&2
- rc=1
- test_rc
+ if [[ -d "$t" ]]; then
+ ktap_print_msg "$t: directory unexpectedly created"
+ RET=$KSFT_FAIL
fi
-
- echo "OK"
- return 0
}
-sysctl_test_0009()
+test_empty_dir_registration()
{
- TARGET="${SYSCTL}/$(get_test_target 0009)"
- echo -n "Testing if $TARGET unregistered correctly ... "
- if [ -d $TARGET ]; then
- echo "FAIL" >&2
- rc=1
- test_rc
- fi
+ local t="${SYSCTL}/empty_add"
- echo "OK"
- return 0
+ if [[ ! -d "$t" ]]; then
+ ktap_print_msg "$t: directory was not created"
+ RET=$KSFT_FAIL
+ return
+ fi
+ if [[ ! -d "$t/empty" ]]; then
+ ktap_print_msg "$t/empty: directory was not created"
+ RET=$KSFT_FAIL
+ fi
}
-sysctl_test_0010()
+test_u8_range_check()
{
- TARGET="${SYSCTL}/$(get_test_target 0010)"
- echo -n "Testing that $TARGET was not created ... "
- if [ -d $TARGET ]; then
- echo "FAIL" >&2
- rc=1
- test_rc
+ local t="${SYSCTL}/u8_valid"
+
+ if [[ ! -f "$t" ]]; then
+ ktap_print_msg "$t: file was not created"
+ RET=$KSFT_FAIL
+ return
fi
- echo "OK"
- return 0
+ assert_dmesg_count "u8_over range value" 1 || RET=$KSFT_FAIL
+ assert_dmesg_count "u8_under range value" 1 || RET=$KSFT_FAIL
}
-sysctl_test_0011()
+test_boot_param_int()
{
- TARGET="${SYSCTL}/$(get_test_target 0011)"
- echo -n "Testing empty dir handling in ${TARGET} ... "
- if [ ! -d ${TARGET} ]; then
- echo -e "FAIL\nCould not create ${TARGET}" >&2
- rc=1
- test_rc
- fi
+ local t="${SYSCTL}/boot_int"
+ local orig found
- TARGET2="${TARGET}/empty"
- if [ ! -d ${TARGET2} ]; then
- echo -e "FAIL\nCould not create ${TARGET2}" >&2
- rc=1
- test_rc
- fi
+ [[ -f "$t" ]] || { RET=$KSFT_SKIP; return; }
- echo "OK"
- return 0
-}
+ # Boot cmdline params only apply when test_sysctl is built in, not
+ # loaded as a module.
+ if [[ -d "$MODULE_DIR" ]]; then
+ ktap_print_msg "$t: test only possible if test_sysctl is built-in, not a module"
+ RET=$KSFT_SKIP
+ return
+ fi
-sysctl_test_0012()
-{
- TARGET="${SYSCTL}/$(get_test_target 0012)"
- echo -n "Testing u8 range check in sysctl table check in ${TARGET} ... "
- if [ ! -f ${TARGET} ]; then
- echo -e "FAIL\nCould not create ${TARGET}" >&2
- rc=1
- test_rc
+ orig=$(cat "$t")
+ if [[ "$orig" == "1" ]]; then
+ return
fi
- local u8over_msg=$(dmesg | grep "u8_over range value" | wc -l)
- if [ ! ${u8over_msg} -eq 1 ]; then
- echo -e "FAIL\nu8 overflow not detected" >&2
- rc=1
- test_rc
+ if [[ ! -f /proc/cmdline ]]; then
+ ktap_print_msg "$t: no /proc/cmdline to check for the boot parameter"
+ RET=$KSFT_SKIP
+ return
fi
- local u8under_msg=$(dmesg | grep "u8_under range value" | wc -l)
- if [ ! ${u8under_msg} -eq 1 ]; then
- echo -e "FAIL\nu8 underflow not detected" >&2
- rc=1
- test_rc
+ found=$(grep -c "sysctl[./]debug[./]test_sysctl[./]boot_int=1" /proc/cmdline)
+ if [[ "$found" == "1" ]]; then
+ ktap_print_msg "$t: kernel param found on cmdline but value is not 1"
+ RET=$KSFT_FAIL
+ return
fi
- echo "OK"
- return 0
+ ktap_print_msg "$t: kernel not booted with sysctl.debug.test_sysctl.boot_int=1"
+ RET=$KSFT_SKIP
}
-list_tests()
+test_sysctl_macro_match()
{
- echo "Test ID list:"
- echo
- echo "TEST_ID x NUM_TEST"
- echo "TEST_ID: Test ID"
- echo "NUM_TESTS: Recommended number of times to run the test"
- echo
- echo "0001 x $(get_test_count 0001) - tests proc_dointvec_minmax()"
- echo "0002 x $(get_test_count 0002) - tests proc_dostring()"
- echo "0003 x $(get_test_count 0003) - tests proc_dointvec()"
- echo "0004 x $(get_test_count 0004) - tests proc_douintvec()"
- echo "0005 x $(get_test_count 0005) - tests proc_douintvec() array"
- echo "0006 x $(get_test_count 0006) - tests proc_do_large_bitmap()"
- echo "0007 x $(get_test_count 0007) - tests setting sysctl from kernel boot param"
- echo "0008 x $(get_test_count 0008) - tests sysctl macro values match"
- echo "0009 x $(get_test_count 0009) - tests sysct unregister"
- echo "0010 x $(get_test_count 0010) - tests sysct mount point"
- echo "0011 x $(get_test_count 0011) - tests empty directories"
- echo "0012 x $(get_test_count 0012) - tests range check for u8 proc_handler"
-}
+ local t="${SYSCTL}/match_int"
-usage()
-{
- NUM_TESTS=$(grep -o ' ' <<<"$ALL_TESTS" | grep -c .)
- let NUM_TESTS=$NUM_TESTS+1
- MAX_TEST=$(printf "%04d\n" $NUM_TESTS)
- echo "Usage: $0 [ -t <4-number-digit> ] | [ -w <4-number-digit> ] |"
- echo " [ -s <4-number-digit> ] | [ -c <4-number-digit> <test- count>"
- echo " [ all ] [ -h | --help ] [ -l ]"
- echo ""
- echo "Valid tests: 0001-$MAX_TEST"
- echo ""
- echo " all Runs all tests (default)"
- echo " -t Run test ID the recommended number of times"
- echo " -w Watch test ID run until it runs into an error"
- echo " -c Run test ID once"
- echo " -s Run test ID x test-count number of times"
- echo " -l List all test ID list"
- echo " -h|--help Help"
- echo
- echo "If an error every occurs execution will immediately terminate."
- echo "If you are adding a new test try using -w <test-ID> first to"
- echo "make sure the test passes a series of tests."
- echo
- echo Example uses:
- echo
- echo "$TEST_NAME.sh -- executes all tests"
- echo "$TEST_NAME.sh -t 0002 -- Executes test ID 0002 the recommended number of times"
- echo "$TEST_NAME.sh -w 0002 -- Watch test ID 0002 run until an error occurs"
- echo "$TEST_NAME.sh -s 0002 -- Run test ID 0002 once"
- echo "$TEST_NAME.sh -c 0002 3 -- Run test ID 0002 three times"
- echo
- list_tests
- exit 1
-}
+ [[ -f "$t" ]] || { RET=$KSFT_SKIP; return; }
-function test_num()
-{
- re='^[0-9]+$'
- if ! [[ $1 =~ $re ]]; then
- usage
- fi
-}
-function remove_leading_zeros()
-{
- echo $1 | sed 's/^0*//'
+ assert_content "$t" "1" || RET=$KSFT_FAIL
}
-function get_test_count()
+run_one()
{
- test_num $1
- awk_field=$(remove_leading_zeros $1)
- TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$awk_field'}')
- echo ${TEST_DATA} | awk -F":" '{print $2}'
-}
+ local name=$1
-function get_test_enabled()
-{
- test_num $1
- awk_field=$(remove_leading_zeros $1)
- TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$awk_field'}')
- echo ${TEST_DATA} | awk -F":" '{print $3}'
+ # Subshell: a test cannot leak vars/cwd/traps into the next one, and an
+ # accidental `exit` inside a test kills only its own subshell.
+ ( RET=$KSFT_PASS; "$name"; exit $RET )
+
+ case $? in
+ "$KSFT_PASS") ktap_test_pass "$name" ;;
+ "$KSFT_SKIP") ktap_test_skip "$name" ;;
+ *) ktap_test_fail "$name" ;;
+ esac
}
-function get_test_target()
+usage()
{
- test_num $1
- awk_field=$(remove_leading_zeros $1)
- TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$awk_field'}')
- echo ${TEST_DATA} | awk -F":" '{print $4}'
+ cat <<-EOF
+ Usage: $0 [-r N] [-l] [-t T] [-s N] [-h|--help]
+
+ Runs every auto-discovered test_* function. Setting SYSCTL_TESTS will
+ override the default and passing -t will override both env var and the
+ default.
+
+ -r N repeat selected tests N times (default 1)
+ -l list discovered tests
+ -t T space separated tests list. For example "test_1 test_2"
+ -s N base seed for the randomized bitmap iterations.
+ -h, --help help
+
+ SYSCTL_TESTS='test_1 test_2' $0 run only the named tests
+ EOF
}
-function get_test_skip_no_target()
+list_tests()
{
- test_num $1
- awk_field=$(remove_leading_zeros $1)
- TEST_DATA=$(echo $ALL_TESTS | awk '{print $'$awk_field'}')
- echo ${TEST_DATA} | awk -F":" '{print $5}'
+ echo "Discovered tests:"
+ local t
+ for t in $ALL_TESTS; do
+ echo " $t"
+ done
}
-function skip_test()
+check_reqs()
{
- TEST_ID=$1
- TEST_TARGET=$2
- if target_exists $TEST_TARGET $TEST_ID; then
- TEST_SKIP=$(get_test_skip_no_target $TEST_ID)
- if [[ $TEST_SKIP -eq "1" ]]; then
- echo "Target $TEST_TARGET for test $TEST_ID does not exist ... SKIPPING"
- return 0
- fi
+ local uid
+ uid=$(id -u)
+
+ if [ "$uid" -ne 0 ]; then
+ ktap_skip_all "must be run as root"
+ exit "$KSFT_SKIP"
+ fi
+
+ if ! which getconf > /dev/null 2>&1; then
+ ktap_skip_all "$0: You need getconf installed"
+ exit "$KSFT_SKIP"
+ fi
+
+ if ! which diff > /dev/null 2>&1; then
+ ktap_skip_all "$0: You need diff installed"
+ exit "$KSFT_SKIP"
fi
- return 1
}
-function run_all_tests()
+check_args() # <repeat> <selected> <seed>
{
- for i in $ALL_TESTS ; do
- TEST_ID=${i%:*:*:*:*}
- ENABLED=$(get_test_enabled $TEST_ID)
- TEST_COUNT=$(get_test_count $TEST_ID)
- TEST_TARGET=$(get_test_target $TEST_ID)
-
- if [[ $ENABLED -eq "1" ]]; then
- test_case $TEST_ID $TEST_COUNT $TEST_TARGET
+ local repeat_arg="$1"
+ local selected_arg="$2"
+ local seed_arg="$3"
+ local t
+
+ if [ -z "$selected_arg" ]; then
+ ktap_exit_fail_msg "no test selected"
+ fi
+
+ if ! [[ "$repeat_arg" =~ ^[0-9]+$ ]]; then
+ ktap_exit_fail_msg "repeat argument '$repeat_arg' is not a number"
+ fi
+
+ if ! [[ "$seed_arg" =~ ^[0-9]+$ ]]; then
+ ktap_exit_fail_msg "seed argument '$seed_arg' is not a number"
+ fi
+
+ for t in $selected_arg; do
+ if ! declare -F "$t" > /dev/null; then
+ ktap_exit_fail_msg "unknown selected test '$t'"
fi
done
}
-function watch_log()
+allow_user_defaults()
{
- if [ $# -ne 3 ]; then
- clear
+ if [ -z "${MODULE_DIR}" ]; then
+ MODULE_DIR="/sys/module/test_sysctl/"
+ fi
+ if [ -z "${SYSCTL}" ]; then
+ SYSCTL="/proc/sys/debug/test_sysctl"
+ fi
+ if [ -z "${PROD_SYSCTL}" ]; then
+ PROD_SYSCTL="/proc/sys"
+ fi
+ if [ -z "${WRITES_STRICT}" ]; then
+ WRITES_STRICT="${PROD_SYSCTL}/kernel/sysctl_writes_strict"
+ fi
+ if [ -z "${BITMAP_ITERATIONS}" ]; then
+ BITMAP_ITERATIONS=50
+ fi
+ # Set seed for bitmap random tests
+ if [ -z "${BITMAP_SEED}" ]; then
+ BITMAP_SEED=$RANDOM
fi
- date
- echo "Running test: $2 - run #$1"
}
-function watch_case()
+check_production_sysctl_writes_strict()
{
- i=0
- while [ 1 ]; do
-
- if [ $# -eq 1 ]; then
- test_num $1
- watch_log $i ${TEST_NAME}_test_$1
- ${TEST_NAME}_test_$1
- else
- watch_log $i all
- run_all_tests
+ if [ ! -e "${WRITES_STRICT}" ]; then
+ ktap_print_msg "${WRITES_STRICT} missing, skipping strict write check (old kernel?)"
+ else
+ old_strict=$(cat "${WRITES_STRICT}")
+ if [ "$old_strict" != "1" ]; then
+ ktap_print_msg "forcing ${WRITES_STRICT} to 1 (was ${old_strict})"
+ echo "1" > "${WRITES_STRICT}"
fi
- let i=$i+1
- done
+ fi
+
+ if [ -z "${PAGE_SIZE}" ]; then
+ PAGE_SIZE=$(getconf PAGESIZE)
+ fi
+ if [ -z "${MAX_DIGITS}" ]; then
+ MAX_DIGITS=$((PAGE_SIZE / 8))
+ fi
+ if [ -z "${INT_MAX}" ]; then
+ INT_MAX=$(getconf INT_MAX)
+ fi
+ if [ -z "${UINT_MAX}" ]; then
+ UINT_MAX=$(getconf UINT_MAX)
+ fi
}
-function test_case()
+load_req_mod()
{
- TEST_ID=$1
- NUM_TESTS=$2
- TARGET=$3
-
- if skip_test $TEST_ID $TARGET; then
- return
+ if [ ! -d "${SYSCTL}" ]; then
+ if ! modprobe -q -n "${TEST_DRIVER}"; then
+ ktap_skip_all "module ${TEST_DRIVER} not found. \
+ You must set CONFIG_TEST_SYSCTL=m in your kernel"
+ exit "$KSFT_SKIP"
+ fi
+ if ! modprobe "${TEST_DRIVER}"; then
+ ktap_exit_fail_msg "modprobe ${TEST_DRIVER} failed"
+ fi
fi
-
- i=0
- while [ $i -lt $NUM_TESTS ]; do
- test_num $TEST_ID
- watch_log $i ${TEST_NAME}_test_${TEST_ID} noclear
- RUN_TEST=${TEST_NAME}_test_${TEST_ID}
- $RUN_TEST
- let i=$i+1
- done
}
-function parse_args()
+# Cleanup only! Do not call exit here or it will silently override KTAP exit status
+restore_sysctl_writes_strict()
{
- if [ $# -eq 0 ]; then
- run_all_tests
- else
- if [[ "$1" = "all" ]]; then
- run_all_tests
- elif [[ "$1" = "-w" ]]; then
- shift
- watch_case $@
- elif [[ "$1" = "-t" ]]; then
- shift
- test_num $1
- test_case $1 $(get_test_count $1) $(get_test_target $1)
- elif [[ "$1" = "-c" ]]; then
- shift
- test_num $1
- test_num $2
- test_case $1 $2 $(get_test_target $1)
- elif [[ "$1" = "-s" ]]; then
- shift
- test_case $1 1 $(get_test_target $1)
- elif [[ "$1" = "-l" ]]; then
- list_tests
- elif [[ "$1" = "-h" || "$1" = "--help" ]]; then
- usage
- else
- usage
- fi
+ if [ -n "${old_strict}" ]; then
+ echo "${old_strict}" > "${WRITES_STRICT}"
fi
}
-test_reqs
allow_user_defaults
+
+# Each bitmap iteration is one function to generate one KTAP report per iteration
+for i in $(seq 1 "$BITMAP_ITERATIONS"); do
+ eval "test_bitmap_range_check_$(printf '%02d' "$i")() \
+ { \
+ bitmap_range_check_once $i; \
+ }"
+done
+
+# Discover test_* functions above this line
+ALL_TESTS=$(declare -F | awk '{print $3}' | grep '^test_' | sort)
+
+REPEAT=1
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -r )
+ shift
+ REPEAT=$1
+ ;;
+ -l )
+ list_tests
+ exit 0
+ ;;
+ -h | --help )
+ usage
+ exit 0
+ ;;
+ -t )
+ shift
+ SYSCTL_TESTS="$1"
+ ;;
+ -s )
+ shift
+ BITMAP_SEED="$1"
+ ;;
+ * )
+ usage
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+SELECTED=${SYSCTL_TESTS-$ALL_TESTS}
+
+ktap_print_header
+trap restore_sysctl_writes_strict EXIT
+check_args "$REPEAT" "$SELECTED" "$BITMAP_SEED"
+check_reqs
check_production_sysctl_writes_strict
load_req_mod
-trap "test_finish" EXIT
+ktap_print_msg "bitmap base seed: $BITMAP_SEED (replay with -s $BITMAP_SEED)"
-parse_args $@
+ktap_set_plan "$(( $(wc -w <<< "$SELECTED") * REPEAT ))"
+
+for ((i = 0; i < REPEAT; i++)); do
+ for t in $SELECTED; do
+ run_one "$t"
+ done
+done
-exit 0
+ktap_finished