From 0ec31e033f020dc83728b2c1dc1de9b3a4902eaa Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 25 Jun 2026 22:38:16 +0200 Subject: sysctl: Add negp parameter to douintvec converter functions Updates all douintvec converter function signatures to include a bool *negp parameter. This is a preparation commit required to eventually run all converters under the same function. The negp argument will be ignored as it is not relevant for the uint type. Note that do_proc_uint_conv_pipe_maxsz in pipe.c is also modified. Signed-off-by: Joel Granados --- include/linux/sysctl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux/sysctl.h') diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 2886fbceb5d6..0d406b64968a 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -102,7 +102,7 @@ int proc_douintvec_minmax(const struct ctl_table *table, int write, void *buffer size_t *lenp, loff_t *ppos); int proc_douintvec_conv(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(unsigned long *lvalp, unsigned int *valp, + int (*conv)(bool *negp, ulong *lvalp, uint *valp, int write, const struct ctl_table *table)); int proc_uint_k2u_conv(ulong *u_ptr, const uint *k_ptr); int proc_uint_u2k_conv_uop(const ulong *u_ptr, uint *k_ptr, -- cgit v1.2.3 From b96b5c6708eae2720053b50af4e9ef3d83a40a94 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 25 Jun 2026 22:38:14 +0200 Subject: sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec Make do_proc_vec static and parametrize by proc_vec_type enum which defines the type being processed and selects which converter is "live". Signed-ness and size are calculated based on proc_vec_type and table->data is now walked as raw bytes and advanced by the element size; the converter still performs the actual typed load/store. Pass converter as a union to avoid a cast from void*. The public proc_do{int,uint,ulong}vec_conv() prototypes and all converter signatures in kernel/, fs/ and the header are therefore unchanged. Remove do_proc_doulongvec_minmax. proc_doulongvec_minmax_conv uses a converter callback passed by the caller instead of conversions based on conv{mul,div}. Create uni and bi-direction converters for milliseconds to jiffies in proc_doulongvec_ms_jiffies_minmax; which is the only user of proc_doulongvec_minmax_conv. Replace do_proc_douintvec{,_w,_r} functions with a call to do_proc_vec. Disallow vectors for uint by returning -EINVAL when more than one element is detected. Signed-off-by: Joel Granados --- include/linux/sysctl.h | 11 +- kernel/sysctl.c | 422 +++++++++++++++++++++++++------------------------ kernel/time/jiffies.c | 26 ++- 3 files changed, 249 insertions(+), 210 deletions(-) (limited to 'include/linux/sysctl.h') diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 0d406b64968a..8f8e357b1f4d 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -117,11 +117,20 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int write, void *buffer, int proc_doulongvec_minmax(const struct ctl_table *, int, void *, size_t *, loff_t *); int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, - unsigned long convmul, unsigned long convdiv); + int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *table)); int proc_do_large_bitmap(const struct ctl_table *, int, void *, size_t *, loff_t *); int proc_do_static_key(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos); +int proc_ulong_u2k_conv_uop(const ulong *u_ptr, ulong *k_ptr, + ulong (*u_ptr_op)(const ulong)); +int proc_ulong_k2u_conv_kop(ulong *u_ptr, const ulong *k_ptr, + ulong (*k_ptr_op)(const ulong)); +int proc_ulong_conv(ulong *u_ptr, ulong *k_ptr, int dir, + const struct ctl_table *tbl, bool k_ptr_range_check, + int (*user_to_kern)(const ulong *u_ptr, ulong *k_ptr), + int (*kern_to_user)(ulong *u_ptr, const ulong *k_ptr)); /* * Register a set of sysctl names by calling register_sysctl * with an initialised array of struct ctl_table's. diff --git a/kernel/sysctl.c b/kernel/sysctl.c index ef5a9c5b1d10..97fdeeee9ef2 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -572,14 +572,80 @@ static int do_proc_int_conv_minmax(bool *negp, unsigned long *u_ptr, int *k_ptr, static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; -static int do_proc_dointvec(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, unsigned long *u_ptr, int *k_ptr, - int dir, const struct ctl_table *table)) +/* + * Element type processed by do_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 { + PROC_VEC_INT, + PROC_VEC_UINT, + PROC_VEC_ULONG, +}; + +/* + * Converter passed to do_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. + */ +union proc_vec_conv { + int (*int_conv)(bool *negp, ulong *u_ptr, int *k_ptr, + int dir, const struct ctl_table *table); + int (*uint_conv)(bool *negp, ulong *u_ptr, uint *k_ptr, + int dir, const struct ctl_table *table); + int (*ulong_conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *table); +}; + +/* + * Dispatch to the converter member selected by @type. @k_ptr walks + * table->data as raw bytes and is cast back to the element type here. + */ +static int proc_vec_conv(enum proc_vec_type type, union proc_vec_conv conv, + bool *negp, ulong *u_ptr, char *k_ptr, int dir, + const struct ctl_table *table) { - int *i, vleft, first = 1, err = 0; - size_t left; - char *p; + switch (type) { + case PROC_VEC_INT: + return conv.int_conv(negp, u_ptr, (int *)k_ptr, dir, table); + case PROC_VEC_UINT: + return conv.uint_conv(negp, u_ptr, (uint *)k_ptr, dir, table); + case PROC_VEC_ULONG: + return conv.ulong_conv(negp, u_ptr, (ulong *)k_ptr, dir, table); + } + 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) +{ + int vleft, first = 1, err = 0; + size_t left, size; + bool is_unsigned; + char *i, *p; + + switch (type) { + case PROC_VEC_INT: + size = sizeof(int); + is_unsigned = false; + break; + case PROC_VEC_UINT: + size = sizeof(uint); + is_unsigned = true; + break; + case PROC_VEC_ULONG: + size = sizeof(ulong); + is_unsigned = true; + break; + default: + return -EINVAL; + } if (!table->data || !table->maxlen || !*lenp || (*ppos && SYSCTL_KERN_TO_USER(dir))) { @@ -587,10 +653,14 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir, return 0; } - i = (int *) table->data; - vleft = table->maxlen / sizeof(*i); + 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; @@ -600,9 +670,9 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir, p = buffer; } - for (; left && vleft--; i++, first=0) { + for (; left && vleft--; i += size, first = 0) { unsigned long lval; - bool neg; + bool neg = false; if (SYSCTL_USER_TO_KERN(dir)) { proc_skip_spaces(&p, &left); @@ -610,16 +680,18 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir, if (!left) break; err = proc_get_long(&p, &left, &lval, &neg, - proc_wspace_sep, - sizeof(proc_wspace_sep), NULL); + proc_wspace_sep, + sizeof(proc_wspace_sep), NULL); + if (!err && neg && is_unsigned) + err = -EINVAL; if (err) break; - if (conv(&neg, &lval, i, 1, table)) { + if (proc_vec_conv(type, conv, &neg, &lval, i, dir, table)) { err = -EINVAL; break; } } else { - if (conv(&neg, &lval, i, 0, table)) { + if (proc_vec_conv(type, conv, &neg, &lval, i, dir, table)) { err = -EINVAL; break; } @@ -641,119 +713,6 @@ out: return err; } -static int do_proc_douintvec_w(const struct ctl_table *table, void *buffer, - size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, unsigned long *u_ptr, - unsigned int *k_ptr, int dir, - const struct ctl_table *table)) -{ - unsigned long lval; - int err = 0; - size_t left; - bool neg; - char *p = buffer; - - left = *lenp; - - if (proc_first_pos_non_zero_ignore(ppos, table)) - goto bail_early; - - if (left > PAGE_SIZE - 1) - left = PAGE_SIZE - 1; - - proc_skip_spaces(&p, &left); - if (!left) { - err = -EINVAL; - goto out_free; - } - - err = proc_get_long(&p, &left, &lval, &neg, - proc_wspace_sep, - sizeof(proc_wspace_sep), NULL); - if (err || neg) { - err = -EINVAL; - goto out_free; - } - - if (conv(&neg, &lval, (unsigned int *) table->data, 1, table)) { - err = -EINVAL; - goto out_free; - } - - if (!err && left) - proc_skip_spaces(&p, &left); - -out_free: - if (err) - return -EINVAL; - - return 0; - -bail_early: - *ppos += *lenp; - return err; -} - -static int do_proc_douintvec_r(const struct ctl_table *table, void *buffer, - size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, unsigned long *u_ptr, - unsigned int *k_ptr, int dir, - const struct ctl_table *table)) -{ - unsigned long lval; - int err = 0; - size_t left; - bool negp; - - left = *lenp; - - if (conv(&negp, &lval, (unsigned int *) table->data, 0, table)) { - err = -EINVAL; - goto out; - } - - proc_put_long(&buffer, &left, lval, false); - if (!left) - goto out; - - proc_put_char(&buffer, &left, '\n'); - -out: - *lenp -= left; - *ppos += *lenp; - - return err; -} - -static int do_proc_douintvec(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, ulong *u_ptr, uint *k_ptr, - int dir, const struct ctl_table *table)) -{ - unsigned int vleft; - - if (!table->data || !table->maxlen || !*lenp || - (*ppos && SYSCTL_KERN_TO_USER(dir))) { - *lenp = 0; - return 0; - } - - vleft = table->maxlen / sizeof(unsigned int); - - /* - * Arrays are not supported, keep this simple. *Do not* add - * support for them. - */ - if (vleft != 1) { - *lenp = 0; - return -EINVAL; - } - - if (SYSCTL_USER_TO_KERN(dir)) - return do_proc_douintvec_w(table, buffer, lenp, ppos, conv); - return do_proc_douintvec_r(table, buffer, lenp, ppos, conv); -} - /** * proc_douintvec_conv - read a vector of unsigned ints with a custom converter * @@ -779,7 +738,8 @@ int proc_douintvec_conv(const struct ctl_table *table, int dir, void *buffer, if (!conv) conv = do_proc_uint_conv; - return do_proc_douintvec(table, dir, buffer, lenp, ppos, conv); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT, + (union proc_vec_conv){ .uint_conv = conv }); } /** @@ -838,7 +798,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_dointvec(table, dir, buffer, lenp, ppos, do_proc_int_conv); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT, + (union proc_vec_conv){ .int_conv = do_proc_int_conv }); } /** @@ -857,8 +818,8 @@ 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_douintvec(table, dir, buffer, lenp, ppos, - do_proc_uint_conv); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT, + (union proc_vec_conv){ .uint_conv = do_proc_uint_conv }); } /** @@ -881,8 +842,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_dointvec(table, dir, buffer, lenp, ppos, - do_proc_int_conv_minmax); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT, + (union proc_vec_conv){ .int_conv = do_proc_int_conv_minmax }); } /** @@ -908,8 +869,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_douintvec(table, dir, buffer, lenp, ppos, - do_proc_uint_conv_minmax); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT, + (union proc_vec_conv){ .uint_conv = do_proc_uint_conv_minmax }); } /** @@ -952,8 +913,8 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int dir, tmp.extra2 = (unsigned int *) &max; val = READ_ONCE(*data); - res = do_proc_douintvec(&tmp, dir, buffer, lenp, ppos, - do_proc_uint_conv_minmax); + res = do_proc_vec(&tmp, dir, buffer, lenp, ppos, PROC_VEC_UINT, + (union proc_vec_conv){ .uint_conv = do_proc_uint_conv_minmax }); if (res) return res; if (SYSCTL_USER_TO_KERN(dir)) @@ -962,87 +923,129 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int dir, } EXPORT_SYMBOL_GPL(proc_dou8vec_minmax); -static int do_proc_doulongvec_minmax(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos, - unsigned long convmul, - unsigned long convdiv) +/** + * proc_ulong_conv - Change user or kernel pointer based on direction + * + * @u_ptr: pointer to user variable + * @k_ptr: pointer to kernel variable + * @dir: %TRUE if this is a write to the sysctl file + * @tbl: the sysctl table + * @k_ptr_range_check: Check range for k_ptr when %TRUE + * @user_to_kern: Callback used to assign value from user to kernel var + * @kern_to_user: Callback used to assign value from kernel to user var + * + * When direction is kernel to user, then the u_ptr is modified. + * When direction is user to kernel, then the k_ptr is modified. + * + * Returns: 0 on success + */ +int proc_ulong_conv(ulong *u_ptr, ulong *k_ptr, int dir, + const struct ctl_table *tbl, bool k_ptr_range_check, + int (*user_to_kern)(const ulong *u_ptr, ulong *k_ptr), + int (*kern_to_user)(ulong *u_ptr, const ulong *k_ptr)) { - unsigned long *i, *min, *max; - int vleft, first = 1, err = 0; - size_t left; - char *p; - - if (!table->data || !table->maxlen || !*lenp || - (*ppos && SYSCTL_KERN_TO_USER(dir))) { - *lenp = 0; - return 0; - } - - i = table->data; - min = table->extra1; - max = table->extra2; - vleft = table->maxlen / sizeof(unsigned long); - left = *lenp; + if (SYSCTL_KERN_TO_USER(dir)) + return kern_to_user(u_ptr, k_ptr); - if (SYSCTL_USER_TO_KERN(dir)) { - if (proc_first_pos_non_zero_ignore(ppos, table)) - goto out; + if (k_ptr_range_check) { + ulong tmp_k; + int ret; - if (left > PAGE_SIZE - 1) - left = PAGE_SIZE - 1; - p = buffer; - } + if (!tbl) + return -EINVAL; + ret = user_to_kern(u_ptr, &tmp_k); + if (ret) + return ret; + if ((tbl->extra1 && *(ulong *)tbl->extra1 > tmp_k) || + (tbl->extra2 && *(ulong *)tbl->extra2 < tmp_k)) + return -ERANGE; + WRITE_ONCE(*k_ptr, tmp_k); + } else + return user_to_kern(u_ptr, k_ptr); + return 0; +} - for (; left && vleft--; i++, first = 0) { - unsigned long val; +/** + * proc_ulong_u2k_conv_uop - Assign user value to a kernel pointer + * + * @u_ptr: pointer to user space variable + * @k_ptr: pointer to kernel variable + * @u_ptr_op: execute this function before assigning to k_ptr + * + * Uses WRITE_ONCE to assign value to k_ptr. Executes u_ptr_op if + * not NULL. + * + * returns: 0 on success. + */ +int proc_ulong_u2k_conv_uop(const ulong *u_ptr, ulong *k_ptr, + ulong (*u_ptr_op)(const ulong)) +{ + ulong u = u_ptr_op ? u_ptr_op(*u_ptr) : *u_ptr; - if (SYSCTL_USER_TO_KERN(dir)) { - bool neg; + WRITE_ONCE(*k_ptr, u); + return 0; +} - proc_skip_spaces(&p, &left); - if (!left) - break; +static int proc_ulong_u2k_conv(const ulong *u_ptr, ulong *k_ptr) +{ + return proc_ulong_u2k_conv_uop(u_ptr, k_ptr, NULL); +} - err = proc_get_long(&p, &left, &val, &neg, - proc_wspace_sep, - sizeof(proc_wspace_sep), NULL); - if (err || neg) { - err = -EINVAL; - break; - } +/** + * proc_ulong_k2u_conv_kop - Assign kernel value to a user space pointer + * + * @u_ptr: pointer to user space variable + * @k_ptr: pointer to kernel variable + * @k_ptr_op: Operation applied to k_ptr before assignment + * + * Uses READ_ONCE to assign value to u_ptr. Executes k_ptr_op if + * not NULL. + * + * returns: 0 on success. + */ +int proc_ulong_k2u_conv_kop(ulong *u_ptr, const ulong *k_ptr, + ulong (*k_ptr_op)(const ulong)) +{ + ulong val = k_ptr_op ? k_ptr_op(READ_ONCE(*k_ptr)) : READ_ONCE(*k_ptr); + *u_ptr = (ulong)val; + return 0; +} - val = convmul * val / convdiv; - if ((min && val < *min) || (max && val > *max)) { - err = -EINVAL; - break; - } - WRITE_ONCE(*i, val); - } else { - val = convdiv * READ_ONCE(*i) / convmul; - if (!first) - proc_put_char(&buffer, &left, '\t'); - proc_put_long(&buffer, &left, val, false); - } - } +static int proc_ulong_k2u_conv(ulong *u_ptr, const ulong *k_ptr) +{ + return proc_ulong_k2u_conv_kop(u_ptr, k_ptr, NULL); +} - if (SYSCTL_KERN_TO_USER(dir) && !first && left && !err) - proc_put_char(&buffer, &left, '\n'); - if (SYSCTL_USER_TO_KERN(dir) && !err) - proc_skip_spaces(&p, &left); - if (SYSCTL_USER_TO_KERN(dir) && first) - return err ? : -EINVAL; - *lenp -= left; -out: - *ppos += *lenp; - return err; +static int do_proc_ulong_conv(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir, + const struct ctl_table *tbl) +{ + return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, true, + proc_ulong_u2k_conv, proc_ulong_k2u_conv); } +/** + * proc_doulongvec_minmax_conv - read a vector of unsigned longs with a custom converter + * + * @table: the sysctl table + * @dir: %TRUE if this is a write to the sysctl file + * @buffer: the user buffer + * @lenp: the size of the user buffer + * @ppos: file position + * @conv: Custom converter call back + * + * Reads/writes up to table->maxlen/sizeof(unsigned long) unsigned long + * values from/to the user buffer, treated as an ASCII string. Negative + * strings are not allowed. + * + * Returns: 0 on success + */ int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, - unsigned long convmul, unsigned long convdiv) + int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *table)) { - return do_proc_doulongvec_minmax(table, dir, buffer, lenp, ppos, - convmul, convdiv); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_ULONG, + (union proc_vec_conv){ .ulong_conv = conv }); } /** @@ -1064,7 +1067,8 @@ int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, int proc_doulongvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos, 1l, 1l); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_ULONG, + (union proc_vec_conv){ .ulong_conv = do_proc_ulong_conv }); } /** @@ -1090,7 +1094,8 @@ int proc_dointvec_conv(const struct ctl_table *table, int dir, void *buffer, { if (!conv) conv = do_proc_int_conv; - return do_proc_dointvec(table, dir, buffer, lenp, ppos, conv); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT, + (union proc_vec_conv){ .int_conv = conv }); } /** @@ -1319,7 +1324,8 @@ int proc_doulongvec_minmax(const struct ctl_table *table, int dir, int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, - unsigned long convmul, unsigned long convdiv) + int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *table)) { return -ENOSYS; } diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index d51428867a33..01e634fdcfd0 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -185,6 +185,24 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, sysctl_u2k_int_conv_ms, sysctl_k2u_int_conv_ms); } +static int sysctl_u2k_ulong_conv_ms(const ulong *u_ptr, ulong *k_ptr) +{ + return proc_ulong_u2k_conv_uop(u_ptr, k_ptr, sysctl_msecs_to_jiffies); +} + +static int sysctl_k2u_ulong_conv_ms(ulong *u_ptr, const ulong *k_ptr) +{ + return proc_ulong_k2u_conv_kop(u_ptr, k_ptr, sysctl_jiffies_to_msecs); +} + +static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) +{ + return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, false, + sysctl_u2k_ulong_conv_ms, sysctl_k2u_ulong_conv_ms); +} + + #else // CONFIG_PROC_SYSCTL static int do_proc_int_conv_jiffies(bool *negp, ulong *u_ptr, int *k_ptr, int dir, const struct ctl_table *tbl) @@ -211,6 +229,12 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, { return -ENOSYS; } + +static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) +{ + return -ENOSYS; +} #endif /** @@ -310,7 +334,7 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos, - HZ, 1000l); + do_proc_ulong_conv_ms_jiffies); } EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); -- cgit v1.2.3 From 75fe29b9aa81970c2b00e95a2ac8a2879876ccaa Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 25 Jun 2026 22:38:19 +0200 Subject: sysctl: Group proc_handler declarations and document Make four groups in the sysctl header and document each group with an example of how to use them. 1. proc_handler : All functions that can be passed to the proc_handler pointer in ctl_table 2. proc handler aggregators: Functions to create proc handlers with custom converters 3. bi-directional converters: Functions to create read/write custom converters. Can be passed to proc handler aggregators 4. uni-directional converters: Functions to create read or write custom converters. Can be passed as args to bi-directional converters Use just one naming convention in the declarations: 'write' becomes 'dir' and 'buffer' becomes 'buf'. Signed-off-by: Joel Granados --- include/linux/sysctl.h | 126 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 40 deletions(-) (limited to 'include/linux/sysctl.h') diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 8f8e357b1f4d..ad268dfd9e79 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -74,63 +74,109 @@ extern const int sysctl_vals[]; extern const unsigned long sysctl_long_vals[]; -typedef int proc_handler(const struct ctl_table *ctl, int write, void *buffer, - size_t *lenp, loff_t *ppos); - -int proc_dostring(const struct ctl_table *, int, void *, size_t *, loff_t *); -int proc_dobool(const struct ctl_table *table, int write, void *buffer, - size_t *lenp, loff_t *ppos); +typedef int proc_handler(const struct ctl_table *ctl, int dir, void *buf, + size_t *lenp, loff_t *ppos); -int proc_dointvec(const struct ctl_table *, int, void *, size_t *, loff_t *); -int proc_dointvec_minmax(const struct ctl_table *table, int dir, void *buffer, +/* proc_handler functions */ +int proc_dostring(const struct ctl_table *ctl, int dir, void *buf, size_t *lenp, + loff_t *ppos); +int proc_dobool(const struct ctl_table *ctl, int dir, void *buf, size_t *lenp, + loff_t *ppos); +int proc_dointvec(const struct ctl_table *ctl, int dir, void *buf, size_t *lenp, + loff_t *ppos); +int proc_dointvec_minmax(const struct ctl_table *ctl, int dir, void *buf, + size_t *lenp, loff_t *ppos); +int proc_douintvec(const struct ctl_table *ctl, int dir, void *buf, size_t *lenp, + loff_t *ppos); +int proc_douintvec_minmax(const struct ctl_table *ctl, int dir, void *buf, + size_t *lenp, loff_t *ppos); +int proc_dou8vec_minmax(const struct ctl_table *ctl, int dir, void *buf, + size_t *lenp, loff_t *ppos); +int proc_doulongvec_minmax(const struct ctl_table *ctl, int dir, void *buf, + size_t *lenp, loff_t *ppos); +int proc_do_large_bitmap(const struct ctl_table *ctl, int dir, void *buf, size_t *lenp, loff_t *ppos); -int proc_dointvec_conv(const struct ctl_table *table, int dir, void *buffer, +int proc_do_static_key(const struct ctl_table *ctl, int dir, void *buf, + size_t *lenp, loff_t *ppos); + +/* + * proc_handler aggregators + * + * Create a proc_handler with a custom converter. Use when the user space + * value is a transformation of the kernel value. Cannot be passed as + * proc_handlers. + * + * Example of creating your custom proc handler: + * int custom_converter(bool *negp, ulong *u_ptr, uint *k_ptr, + * int dir, const struct ctl_table *ctl) {...} + * int custom_proc_handler(const struct ctl_table *ctl, int dir, + * void *buf, size_t *lenp, loff_t *ppos + * { return proc_dointvec_conv(ctl, dir, buf, lenp, ppos, custom_converter); } + */ +int proc_dointvec_conv(const struct ctl_table *ctl, int dir, void *buf, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, unsigned long *u_ptr, int *k_ptr, - int dir, const struct ctl_table *table)); -int proc_int_k2u_conv_kop(ulong *u_ptr, const int *k_ptr, bool *negp, - ulong (*k_ptr_op)(const ulong)); -int proc_int_u2k_conv_uop(const ulong *u_ptr, int *k_ptr, const bool *negp, - ulong (*u_ptr_op)(const ulong)); + int dir, const struct ctl_table *ctl)); +int proc_douintvec_conv(const struct ctl_table *ctl, int dir, void *buf, + size_t *lenp, loff_t *ppos, + int (*conv)(bool *negp, ulong *u_ptr, uint *k_ptr, + int dir, const struct ctl_table *ctl)); +int proc_doulongvec_minmax_conv(const struct ctl_table *ctl, int dir, void *buf, + size_t *lenp, loff_t *ppos, + int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *ctl)); + +/* + * bi-directional converter functions + * + * Specify the converter function for both directions (user to kernel & kernel + * to user). Use when you want to change the value of the variable before + * assignment. Used to create custom proc_handler aggregators. + * + * Example of creating your custom bi-directional converter: + * int custom_u2k(ulong *u_ptr, const uint *k_ptr) { ... } + * int custom_converter(bool *negp, ulong *u_ptr, uint *k_ptr, + * int dir, const struct ctl_table *ctl) + * { return proc_uint_conv(u_ptr, k_ptr, dir, ctl, true, + * custom_u2k, proc_uint_k2u_conv} + */ int proc_int_conv(bool *negp, ulong *u_ptr, int *k_ptr, int dir, const struct ctl_table *tbl, bool k_ptr_range_check, int (*user_to_kern)(const bool *negp, const ulong *u_ptr, int *k_ptr), int (*kern_to_user)(bool *negp, ulong *u_ptr, const int *k_ptr)); - -int proc_douintvec(const struct ctl_table *, int, void *, size_t *, loff_t *); -int proc_douintvec_minmax(const struct ctl_table *table, int write, void *buffer, - size_t *lenp, loff_t *ppos); -int proc_douintvec_conv(const struct ctl_table *table, int write, void *buffer, - size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, ulong *lvalp, uint *valp, - int write, const struct ctl_table *table)); -int proc_uint_k2u_conv(ulong *u_ptr, const uint *k_ptr); -int proc_uint_u2k_conv_uop(const ulong *u_ptr, uint *k_ptr, - ulong (*u_ptr_op)(const ulong)); int proc_uint_conv(ulong *u_ptr, uint *k_ptr, int dir, const struct ctl_table *tbl, bool k_ptr_range_check, int (*user_to_kern)(const ulong *u_ptr, uint *k_ptr), int (*kern_to_user)(ulong *u_ptr, const uint *k_ptr)); +int proc_ulong_conv(ulong *u_ptr, ulong *k_ptr, int dir, + const struct ctl_table *tbl, bool k_ptr_range_check, + int (*user_to_kern)(const ulong *u_ptr, ulong *k_ptr), + int (*kern_to_user)(ulong *u_ptr, const ulong *k_ptr)); -int proc_dou8vec_minmax(const struct ctl_table *table, int write, void *buffer, - size_t *lenp, loff_t *ppos); -int proc_doulongvec_minmax(const struct ctl_table *, int, void *, size_t *, loff_t *); -int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *table)); -int proc_do_large_bitmap(const struct ctl_table *, int, void *, size_t *, loff_t *); -int proc_do_static_key(const struct ctl_table *table, int write, void *buffer, - size_t *lenp, loff_t *ppos); - +/* + * uni-directional converter functions + * + * Specify the converter function for one directions (user to kernel or + * kernel to user). Use to call the actual value conversion. Used to Create + * bi-directional converters. + * + * Example of creating a uni-directional converter: + * ulong op(const ulong val) { ... } + * int custom_unidir_conv(ulong *u_ptr, const uint *k_ptr) + * { return proc_uint_k2u_conv_kop(u_ptr, k_ptr, op); } + */ +int proc_int_k2u_conv_kop(ulong *u_ptr, const int *k_ptr, bool *negp, + ulong (*k_ptr_op)(const ulong)); +int proc_int_u2k_conv_uop(const ulong *u_ptr, int *k_ptr, const bool *negp, + ulong (*u_ptr_op)(const ulong)); +int proc_uint_k2u_conv(ulong *u_ptr, const uint *k_ptr); +int proc_uint_u2k_conv_uop(const ulong *u_ptr, uint *k_ptr, + ulong (*u_ptr_op)(const ulong)); int proc_ulong_u2k_conv_uop(const ulong *u_ptr, ulong *k_ptr, ulong (*u_ptr_op)(const ulong)); int proc_ulong_k2u_conv_kop(ulong *u_ptr, const ulong *k_ptr, ulong (*k_ptr_op)(const ulong)); -int proc_ulong_conv(ulong *u_ptr, ulong *k_ptr, int dir, - const struct ctl_table *tbl, bool k_ptr_range_check, - int (*user_to_kern)(const ulong *u_ptr, ulong *k_ptr), - int (*kern_to_user)(ulong *u_ptr, const ulong *k_ptr)); + /* * Register a set of sysctl names by calling register_sysctl * with an initialised array of struct ctl_table's. -- cgit v1.2.3 From b1ca9dae826b99d09e898c7567b2e3bb4bdc866a Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 25 Jun 2026 22:38:20 +0200 Subject: sysctl: Rename proc_doulongvec_minmax_conv to proc_doulongvec_conv Remove "_minmax" from proc_doulongvec_minmax_conv as it does not enforce min/max limits but serves as a generic converter for unsigned long vectors. Update function declaration in sysctl.h, definition in sysctl.c, and caller in jiffies.c accordingly. Signed-off-by: Joel Granados --- include/linux/sysctl.h | 8 ++++---- kernel/sysctl.c | 6 +++--- kernel/time/jiffies.c | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'include/linux/sysctl.h') diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index ad268dfd9e79..39cf1bf9703f 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -121,10 +121,10 @@ int proc_douintvec_conv(const struct ctl_table *ctl, int dir, void *buf, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, ulong *u_ptr, uint *k_ptr, int dir, const struct ctl_table *ctl)); -int proc_doulongvec_minmax_conv(const struct ctl_table *ctl, int dir, void *buf, - size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *ctl)); +int proc_doulongvec_conv(const struct ctl_table *ctl, int dir, void *buf, + size_t *lenp, loff_t *ppos, + int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *ctl)); /* * bi-directional converter functions diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 97fdeeee9ef2..d30058d32340 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -1024,7 +1024,7 @@ static int do_proc_ulong_conv(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir, } /** - * proc_doulongvec_minmax_conv - read a vector of unsigned longs with a custom converter + * proc_doulongvec_conv - read a vector of unsigned longs with a custom converter * * @table: the sysctl table * @dir: %TRUE if this is a write to the sysctl file @@ -1039,7 +1039,7 @@ static int do_proc_ulong_conv(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir, * * Returns: 0 on success */ -int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, +int proc_doulongvec_conv(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir, const struct ctl_table *table)) @@ -1322,7 +1322,7 @@ int proc_doulongvec_minmax(const struct ctl_table *table, int dir, return -ENOSYS; } -int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, +int proc_doulongvec_conv(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir, const struct ctl_table *table)) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 01e634fdcfd0..af529d558fa9 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -333,8 +333,8 @@ int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos, - do_proc_ulong_conv_ms_jiffies); + return proc_doulongvec_conv(table, dir, buffer, lenp, ppos, + do_proc_ulong_conv_ms_jiffies); } EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); -- cgit v1.2.3 From 4280dd6da352c0544672102de09df15eb326a145 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 18 Jul 2026 12:03:56 -0700 Subject: sysctl: repair some kernel-doc comments Convert a non-kernel-comment to use "/*" instead. Don't use kernel-doc for the nested @type enum values since they aren't part of the struct. Warning: ./include/linux/sysctl.h:62 Cannot find identifier on line: * Warning: ./include/linux/sysctl.h:63 Cannot find identifier on line: * "dir" originates from read_iter (dir = 0) or write_iter (dir = 1) Warning: ./include/linux/sysctl.h:64 This comment starts with '/**', but isn't a kernel-doc comment. * in the file_operations struct at proc/proc_sysctl.c. Its value means Warning: ./include/linux/sysctl.h:274 Excess struct member 'type.SYSCTL_TABLE_TYPE_DEFAULT' description in 'ctl_table_header' Warning: ./include/linux/sysctl.h:274 Excess struct member 'type.SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY' description in 'ctl_table_header' Note: This still leaves 7 struct members in ctl_table_header that are not described. E.g.: Warning: include/linux/sysctl.h:274 struct member 'unregistering' not described in 'ctl_table_header' Warning: include/linux/sysctl.h:274 struct member 'ctl_table_arg' not described in 'ctl_table_header' Signed-off-by: Randy Dunlap Signed-off-by: Joel Granados --- include/linux/sysctl.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'include/linux/sysctl.h') diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 39cf1bf9703f..e5d7226ab6f5 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -59,7 +59,7 @@ extern const int sysctl_vals[]; #define SYSCTL_LONG_ONE ((void *)&sysctl_long_vals[1]) #define SYSCTL_LONG_MAX ((void *)&sysctl_long_vals[2]) -/** +/* * * "dir" originates from read_iter (dir = 0) or write_iter (dir = 1) * in the file_operations struct at proc/proc_sysctl.c. Its value means @@ -245,9 +245,9 @@ struct ctl_node { * @nreg: When nreg drops to 0 the ctl_table_header will be unregistered. * @rcu: Delays the freeing of the inode. Introduced with "unfuck proc_sysctl ->d_compare()" * - * @type: Enumeration to differentiate between ctl target types - * @type.SYSCTL_TABLE_TYPE_DEFAULT: ctl target with no special considerations - * @type.SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY: Identifies a permanently empty dir + * @type: Enumeration to differentiate between ctl target types: + * type.SYSCTL_TABLE_TYPE_DEFAULT: ctl target with no special considerations + * type.SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY: Identifies a permanently empty dir * target to serve as a mount point */ struct ctl_table_header { -- cgit v1.2.3