From nobody Thu Apr 2 23:53:27 2026 Received: from out-188.mta1.migadu.com (out-188.mta1.migadu.com [95.215.58.188]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ED66A33B6F1 for ; Wed, 25 Mar 2026 18:39:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.188 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774464002; cv=none; b=GAY91Hab0CjsIEVAr2ZBpuqBuEt7rgwMaxCJeL7X2pRq6PKDrA/G3khA7xsQGXf0ZIdi+hX8AAzjujTrGEYSH+crfqaacccAoJlDIFIcGrb5XLt9UtYZ8Sl4LiYuaH54Kml3q8J6CmAGVphf78XmXVMHIirVLW67DTa6Xz49pTk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774464002; c=relaxed/simple; bh=IbUCOPop+VeAxf7Go8Qb9skjaDYOA09om7m98Up1aEQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=If626vVdzaPmkqR+BPpSs0XPqnbkXt8CzbVfU3FMnJTfXLtKKCCAkgdjt7uIykcT9qYryKqrc9VSAP+zQdL7z1Uz8LGwsM11QMxNUTNMUru+sr9ASwgSX5x4CpudSUcNT6oszzHkWYSmP1RtTVF5JAK87s5V6i3rrn3qDp2JtDw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=jTVuqrl8; arc=none smtp.client-ip=95.215.58.188 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="jTVuqrl8" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1774463996; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=9jJ24xyMLz5nvybLbU2CjERbvdu2Z++lvJfI0T3inaw=; b=jTVuqrl8u5gtpdCdTuH64HvfdQz182lfW9HNW8NUwnRTxfCkGRzNL/eXFoInhnQ/Zm9jpC khmG0NEWuok55XaeH1my2qfBiz3TuV6n0uBbbl8OvoD9zLJZDHbLpPcvNQmXH98isaUo1n EPFmQsL65GRp2d3ml9ugTHxWhA7oGPI= From: wen.yang@linux.dev To: Joel Granados , Kees Cook Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Wen Yang Subject: [RFC PATCH v5 1/2] sysctl: introduce CTLTBL_ENTRY_XXX() helper macros Date: Thu, 26 Mar 2026 02:39:15 +0800 Message-Id: <3c559c50d15c55f1f2f81f77b4f8c65c05858de3.1774463505.git.wen.yang@linux.dev> In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" From: Wen Yang Historically, changes to how struct ctl_table entries are initialized (e.g. removing the child field, const-qualifying ctl_table) required touching hundreds of files across all subsystems. Such series require the attention of many maintainers, sometimes need to be pulled into mainline in a special way, and create lots of unnecessary churn. This will all go away with the CTLTBL_ENTRY_XXX helper macros. Add a family of CTLTBL_ENTRY_XXX() macros that reduce the boilerplate of initialising struct ctl_table. The suffix letters encode the accepted arguments: V derives .procname from the variable name, N takes an explicit name, M an explicit mode, H an explicit handler, R enables range checking via extra1/extra2, and L overrides .maxlen. Unspecified fields default to: .procname =3D #var, .data =3D &var, .maxlen =3D sizeof(v= ar), and .proc_handler selected by _Generic() from the variable type. Auto-dispatch maps int/unsigned int to proc_dointvec/proc_douintvec, long/unsigned long to proc_doulongvec_minmax, bool to proc_dobool, and u8 to proc_dou8vec_minmax. Range variants (R suffix) substitute the corresponding _minmax handler. char[] cannot be dispatched via _Generic(); use CTLTBL_ENTRY_VNMH() with proc_dostring explicitly. Pass SYSCTL_NULL as the variable argument for entries where .data must be NULL. No functional change. Suggested-by: Joel Granados Signed-off-by: Wen Yang --- include/linux/sysctl.h | 307 +++++++++++++++++++++++++++++++++++++++++ kernel/sysctl.c | 2 + 2 files changed, 309 insertions(+) diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 2886fbceb5d6..f5aa7bc319da 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -175,6 +175,313 @@ struct ctl_table { void *extra2; } __randomize_layout; =20 +/** + * struct _sysctl_null_type - sentinel type for variable-less entries + */ +struct _sysctl_null_type { char __dummy; }; + +/** + * _sysctl_null_marker - sentinel instance used by SYSCTL_NULL + * + * Passed via SYSCTL_NULL to mark entries with no associated variable. + * Detected by _Generic() dispatch in CTLTBL_ENTRY_XXX(). + */ +extern const struct _sysctl_null_type _sysctl_null_marker; + +/** + * SYSCTL_NULL - pass as __var for entries with no associated variable + */ +#define SYSCTL_NULL (_sysctl_null_marker) + +/** + * __CTL_AUTO_HANDLER - select proc_handler based on the variable type + * + * Supported types: int, unsigned int, long, unsigned long, bool, u8. + * char arrays (strings) are not supported; use CTLTBL_ENTRY_VNMH() with + * proc_dostring instead. + * + * @__var: kernel variable (value, not address), or SYSCTL_NULL + */ +#define __CTL_AUTO_HANDLER(__var) \ + _Generic((__var), \ + int : proc_dointvec, \ + unsigned int : proc_douintvec, \ + long : proc_doulongvec_minmax, \ + unsigned long: proc_doulongvec_minmax, \ + bool : proc_dobool, \ + u8 : proc_dou8vec_minmax, \ + default : \ + 0xdeadbeaf) + +/** + * __CTL_AUTO_HANDLER_RANGE - select proc_handler for a range-constrained = entry + * + * Supported types: int, unsigned int, long, unsigned long, u8. + * bool is also accepted but proc_dobool ignores extra1/extra2 range bound= s. + * + * @__var: kernel variable (value, not address), or SYSCTL_NULL + */ +#define __CTL_AUTO_HANDLER_RANGE(__var) \ + _Generic((__var), \ + int : proc_dointvec_minmax, \ + unsigned int : proc_douintvec_minmax, \ + long : proc_doulongvec_minmax, \ + unsigned long: proc_doulongvec_minmax, \ + bool : proc_dobool, \ + u8 : proc_dou8vec_minmax, \ + default : \ + 0xdeadbeaf) + +/** + * __CTL_PROCNAME - validate and return the procname string + * + * "" __name "" enforces a string-literal argument at compile time. + * + * @__name: procname string literal + */ +#define __CTL_PROCNAME(__name) ("" __name "") + +/** + * __CTL_DATA - assert __var is addressable; return its address + * + * SYSCTL_NULL -> (void *)NULL + * lvalue __var -> (void *)&(__var) + * + * @__var: kernel variable (without &), or SYSCTL_NULL + */ +#define __CTL_DATA(__var) \ + _Generic((__var), \ + struct _sysctl_null_type : (void *)NULL, \ + default : \ + (void *)&(__var)) + +/* Compute maxlen for NULL entries: use explicit MAXLEN if >0, else 0 */ +#define __SYSCTL_MAXLEN_NULL(__maxlen) \ + ((__maxlen) > 0 ? (size_t)(__maxlen) : (size_t)0) + +/* Compute maxlen: use explicit MAXLEN if >0, else sizeof(VAR) */ +#define __SYSCTL_MAXLEN_VAR(__var, __maxlen) \ + ((__maxlen) >=3D 0 ? (size_t)(__maxlen) : (size_t)sizeof(__var)) + +/** + * __CTL_MAXLEN - compute the .maxlen value + * + * @__var: kernel variable (without &), or SYSCTL_NULL + * @__maxlen: explicit override, or -1 for auto-sizing + */ +#define __CTL_MAXLEN(__var, __maxlen) \ + _Generic((__var), \ + struct _sysctl_null_type : \ + __SYSCTL_MAXLEN_NULL(__maxlen), \ + default : \ + __SYSCTL_MAXLEN_VAR(__var, __maxlen) \ + ) + +/** + * __CTL_MODE - validate and return file permission bits + * + * @__mode: file permission bits (e.g. 0644) + */ +#define __CTL_MODE(__mode) (0 ? (umode_t)0 : (__mode)) + +/** + * __CTL_HANDLER - validate and return proc_handler + * + * @__handler: proc_handler + */ +#define __CTL_HANDLER(__handler) \ + (0 ? (typeof(proc_handler) *)0 : (__handler)) + +/* Validate PTR is pointer-compatible and return it as void* */ +#define __CTL_EXTRA(PTR) \ + (0 ? (void *)0 : (PTR)) + +/** + * Internal primitive (single source of truth) + * + * All public macros delegate here. + * + * @_var: kernel variable (without &), or SYSCTL_NULL + * @_name: procname string literal + * @_mode: file permission bits + * @_handler: proc_handler-compatible function pointer + * @_min: lower bound pointer, or NULL + * @_max: upper bound pointer, or NULL + * @_maxlen: explicit .maxlen, or -1 for auto-sizing + */ +#define __CTLTBL_ENTRY(_var, _name, _mode, _handler, _min, _max, _maxlen) \ +{ \ + .procname =3D __CTL_PROCNAME(_name), \ + .data =3D __CTL_DATA(_var), \ + .maxlen =3D __CTL_MAXLEN(_var, _maxlen), \ + .mode =3D __CTL_MODE(_mode), \ + .proc_handler =3D __CTL_HANDLER(_handler), \ + .poll =3D NULL, \ + .extra1 =3D __CTL_EXTRA(_min), \ + .extra2 =3D __CTL_EXTRA(_max), \ +} + +/** + * Public API + * + * Naming convention: + * V - Variable is auto-named via #__var + * N - explicit Name string + * M - explicit Mode + * H - explicit Handler + * R - Range checking (min/max, selects _minmax handler) + * L - explicit Length (.maxlen override) + */ + +/** + * CTLTBL_ENTRY_V - read-only entry; procname =3D=3D stringified variable = name + * + * Proto: (T __var) + * + * .procname =3D #__var, .mode =3D 0444, .extra1 =3D .extra2 =3D NULL. + * proc_handler and .maxlen inferred from typeof(__var). + * + * @__var: kernel variable (without &); must be an addressable lvalue + */ +#define CTLTBL_ENTRY_V(__var) \ + __CTLTBL_ENTRY(__var, #__var, 0444, \ + __CTL_AUTO_HANDLER(__var), NULL, NULL, -1) + +/** + * CTLTBL_ENTRY_VM - entry with custom mode; procname =3D=3D #__var + * + * Proto: (T __var, umode_t __mode) + * + * @__var: kernel variable (without &) + * @__mode: file permission bits + */ +#define CTLTBL_ENTRY_VM(__var, __mode) \ + __CTLTBL_ENTRY(__var, #__var, __mode, \ + __CTL_AUTO_HANDLER(__var), NULL, NULL, -1) + +/** + * CTLTBL_ENTRY_VMR - custom mode with range checking; procname =3D=3D #__= var + * + * Proto: (T __var, umode_t __mode, T *__min, T *__max) + * + * @__var: kernel variable (without &) + * @__mode: file permission bits + * @__min: pointer to minimum value; typeof(*__min) must =3D=3D typeof(__= var) + * @__max: pointer to maximum value; typeof(*__max) must =3D=3D typeof(__= var) + */ +#define CTLTBL_ENTRY_VMR(__var, __mode, __min, __max) \ + __CTLTBL_ENTRY(__var, #__var, __mode, \ + __CTL_AUTO_HANDLER_RANGE(__var), __min, __max, -1) + +/** + * CTLTBL_ENTRY_VN - read-only entry with explicit procname + * + * Proto: (T __var, const char *__name) + * + * @__var: kernel variable (without &) + * @__name: procname string literal + */ +#define CTLTBL_ENTRY_VN(__var, __name) \ + __CTLTBL_ENTRY(__var, __name, 0444, \ + __CTL_AUTO_HANDLER(__var), NULL, NULL, -1) + +/** + * CTLTBL_ENTRY_VNM - entry with explicit procname and mode + * + * Proto: (T __var, const char *__name, umode_t __mode) + * + * @__var: kernel variable (without &) + * @__name: procname string literal + * @__mode: file permission bits + */ +#define CTLTBL_ENTRY_VNM(__var, __name, __mode) \ + __CTLTBL_ENTRY(__var, __name, __mode, \ + __CTL_AUTO_HANDLER(__var), NULL, NULL, -1) + +/** + * CTLTBL_ENTRY_VNMH - entry with explicit procname, mode and handler + * + * Proto: (T __var, const char *__name, umode_t __mode, + * proc_handler *__handler) + * + * @__var: kernel variable (without &), or SYSCTL_NULL + * @__name: procname string literal + * @__mode: file permission bits + * @__handler: proc_handler-compatible function pointer + */ +#define CTLTBL_ENTRY_VNMH(__var, __name, __mode, __handler) \ + __CTLTBL_ENTRY(__var, __name, __mode, __handler, NULL, NULL, -1) + +/** + * CTLTBL_ENTRY_VNMR - entry with procname, mode and range checking + * + * Proto: (T __var, const char *__name, umode_t __mode, + * T *__min, T *__max) + * + * @__var: kernel variable (without &) + * @__name: procname string literal + * @__mode: file permission bits + * @__min: pointer to minimum value; typeof(*__min) must =3D=3D typeof(__= var) + * @__max: pointer to maximum value; typeof(*__max) must =3D=3D typeof(__= var) + */ +#define CTLTBL_ENTRY_VNMR(__var, __name, __mode, __min, __max) \ + __CTLTBL_ENTRY(__var, __name, __mode, \ + __CTL_AUTO_HANDLER_RANGE(__var), __min, __max, -1) + +/** + * CTLTBL_ENTRY_VNMHR - entry with explicit handler and range + * + * Proto: (T __var, const char *__name, umode_t __mode, + * proc_handler *__handler, T *__min, T *__max) + * + * @__var: kernel variable (without &), or SYSCTL_NULL + * @__name: procname string literal + * @__mode: file permission bits + * @__handler: proc_handler-compatible function pointer + * @__min: pointer to minimum value for .extra1 + * @__max: pointer to maximum value for .extra2 + */ +#define CTLTBL_ENTRY_VNMHR(__var, __name, __mode, __handler, __min, __max)= \ + __CTLTBL_ENTRY(__var, __name, __mode, __handler, __min, __max, -1) + +/** + * CTLTBL_ENTRY_VNMHRL - fully explicit entry + * + * Proto: (T __var, const char *__name, umode_t __mode, + * proc_handler *__handler, T *__min, T *__max, + * size_t __maxlen) + * + * Pass -1 for __maxlen to use sizeof(__var) / 0 (auto). + * Pass 0 to set .maxlen to zero explicitly. + * + * @__var: kernel variable (without &), or SYSCTL_NULL + * @__name: procname string literal + * @__mode: file permission bits + * @__handler: proc_handler-compatible function pointer + * @__min: pointer to minimum value for .extra1 + * @__max: pointer to maximum value for .extra2 + * @__maxlen: explicit value for .maxlen + */ +#define CTLTBL_ENTRY_VNMHRL(__var, __name, __mode, __handler, \ + __min, __max, __maxlen) \ + __CTLTBL_ENTRY(__var, __name, __mode, __handler, __min, __max, __maxlen) + +/** + * CTLTBL_ENTRY_NMH - custom-handler entry with no associated variable + * + * Proto: (const char *__name, umode_t __mode, + * proc_handler *__handler) + * + * Shorthand for CTLTBL_ENTRY_VNMH(SYSCTL_NULL, ...). + * .data =3D NULL, .maxlen =3D 0, .extra1 =3D NULL, .extra2 =3D NULL. + * + * @__name: procname string literal + * @__mode: file permission bits + * @__handler: proc_handler-compatible function pointer + */ +#define CTLTBL_ENTRY_NMH(__name, __mode, __handler) \ + CTLTBL_ENTRY_VNMH(SYSCTL_NULL, __name, __mode, __handler) + struct ctl_node { struct rb_node node; struct ctl_table_header *header; diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 9d3a666ffde1..121e743e7709 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -29,6 +29,8 @@ EXPORT_SYMBOL(sysctl_vals); const unsigned long sysctl_long_vals[] =3D { 0, 1, LONG_MAX }; EXPORT_SYMBOL_GPL(sysctl_long_vals); =20 +const struct _sysctl_null_type _sysctl_null_marker; + #if defined(CONFIG_SYSCTL) =20 /* Constants used for minimum and maximum */ --=20 2.25.1 From nobody Thu Apr 2 23:53:27 2026 Received: from out-171.mta1.migadu.com (out-171.mta1.migadu.com [95.215.58.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 49172334C28 for ; Wed, 25 Mar 2026 18:40:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774464006; cv=none; b=D5yZL/mzS7eqRMQyy1KVSGVqIO2n8wzniVUa76LKse4iaAe7oDfxhFTou9qUizcg1TDt9wqrhAEaACCbutIZvElx74TK6oQsDUDpGbxWubSpdpWW7VU3xid1GYQzWKFx/9q1wNaX0wUGDGwm/gFN1uFC1UiBU+uMh4sc4g58EXI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774464006; c=relaxed/simple; bh=fixBGJo6M7fHUae/bxhYOGyJ0GgE18cVwn2C/iuzmIE=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=A3IDR6UJgobqgMJeLtXOZw17SAyOIJu40PDw04d17d1zAe9hdT0zr3zM08e+Cr5SD/8O6++B/QbdyCXEK3uY8O2oiI9LsHoWFVvce6zfbPGWm8APTOYFrPDRoKfBmoiMbl3/6JoxB6+52X2hjqmr3feAWs3zUyxnfp4k0qG+dWY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=WbsUkHLP; arc=none smtp.client-ip=95.215.58.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="WbsUkHLP" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1774464000; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=PaTNz46zEkUTNzfpveGw6ogtHadEbA20yWavHlcngfY=; b=WbsUkHLPqKIq7mNPNUCupDI5R3OERO3dOlxv2dTKYfo1X/r4bffu3YkOKELVqrGiUrWhQw hYAnsfqF+zMBpL4rURC4GysvZ+HoY9wY1T8vX10Lji4yvyZ59E4LrtUUOi6nFSStF4pW7h YehBT/NnXR/5cGEuz8cyJFyG7nYZulg= From: wen.yang@linux.dev To: Joel Granados , Kees Cook Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Wen Yang Subject: [RFC PATCH v5 2/2] sysctl: convert kernel/sysctl-test.c to use CTLTBL_ENTRY_XXX() Date: Thu, 26 Mar 2026 02:39:16 +0800 Message-Id: In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT From: Wen Yang Replace open-coded struct ctl_table initialisers with the CTLTBL_ENTRY_VNMR(), CTLTBL_ENTRY_VNMHR(), and CTLTBL_ENTRY_VNMHRL() macros introduced in the previous patch. Add a parameterized KUnit test case exercising all CTLTBL_ENTRY_XXX() variants (V, VM, VMR, VN, VNM, VNMH) for int, u8, and bool types. char[] (proc_dostring) requires CTLTBL_ENTRY_VNMH() with an explicit handler since _Generic cannot dispatch on array types. Suggested-by: Joel Granados Signed-off-by: Wen Yang --- kernel/sysctl-test.c | 237 +++++++++++++++++++++++++------------------ 1 file changed, 138 insertions(+), 99 deletions(-) diff --git a/kernel/sysctl-test.c b/kernel/sysctl-test.c index 92f94ea28957..3b37bb0516e3 100644 --- a/kernel/sysctl-test.c +++ b/kernel/sysctl-test.c @@ -15,20 +15,14 @@ */ static void sysctl_test_api_dointvec_null_tbl_data(struct kunit *test) { - struct ctl_table null_data_table =3D { - .procname =3D "foo", - /* - * Here we are testing that proc_dointvec behaves correctly when - * we give it a NULL .data field. Normally this would point to a - * piece of memory where the value would be stored. - */ - .data =3D NULL, - .maxlen =3D sizeof(int), - .mode =3D 0644, - .proc_handler =3D proc_dointvec, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE_HUNDRED, - }; + /* + * Here we are testing that proc_dointvec behaves correctly when + * we give it a NULL .data field. Normally this would point to a + * piece of memory where the value would be stored. + */ + struct ctl_table null_data_table =3D CTLTBL_ENTRY_VNMHR( + SYSCTL_NULL, "foo", 0644, proc_dointvec, + SYSCTL_ZERO, SYSCTL_ONE_HUNDRED); /* * proc_dointvec expects a buffer in user space, so we allocate one. We * also need to cast it to __user so sparse doesn't get mad. @@ -66,19 +60,14 @@ static void sysctl_test_api_dointvec_null_tbl_data(stru= ct kunit *test) static void sysctl_test_api_dointvec_table_maxlen_unset(struct kunit *test) { int data =3D 0; - struct ctl_table data_maxlen_unset_table =3D { - .procname =3D "foo", - .data =3D &data, - /* - * So .data is no longer NULL, but we tell proc_dointvec its - * length is 0, so it still shouldn't try to use it. - */ - .maxlen =3D 0, - .mode =3D 0644, - .proc_handler =3D proc_dointvec, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE_HUNDRED, - }; + /* + * So .data is no longer NULL, but we tell proc_dointvec its + * length is 0, so it still shouldn't try to use it. + */ + struct ctl_table data_maxlen_unset_table =3D CTLTBL_ENTRY_VNMHRL( + data, "foo", 0644, proc_dointvec, + SYSCTL_ZERO, SYSCTL_ONE_HUNDRED, 0); + void __user *buffer =3D (void __user *)kunit_kzalloc(test, sizeof(int), GFP_USER); size_t len; @@ -113,15 +102,8 @@ static void sysctl_test_api_dointvec_table_len_is_zero= (struct kunit *test) { int data =3D 0; /* Good table. */ - struct ctl_table table =3D { - .procname =3D "foo", - .data =3D &data, - .maxlen =3D sizeof(int), - .mode =3D 0644, - .proc_handler =3D proc_dointvec, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE_HUNDRED, - }; + struct ctl_table table =3D CTLTBL_ENTRY_VNMR(data, "foo", + 0644, SYSCTL_ZERO, SYSCTL_ONE_HUNDRED); void __user *buffer =3D (void __user *)kunit_kzalloc(test, sizeof(int), GFP_USER); /* @@ -147,15 +129,8 @@ static void sysctl_test_api_dointvec_table_read_but_po= sition_set( { int data =3D 0; /* Good table. */ - struct ctl_table table =3D { - .procname =3D "foo", - .data =3D &data, - .maxlen =3D sizeof(int), - .mode =3D 0644, - .proc_handler =3D proc_dointvec, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE_HUNDRED, - }; + struct ctl_table table =3D CTLTBL_ENTRY_VNMR(data, "foo", 0644, + SYSCTL_ZERO, SYSCTL_ONE_HUNDRED); void __user *buffer =3D (void __user *)kunit_kzalloc(test, sizeof(int), GFP_USER); /* @@ -182,15 +157,8 @@ static void sysctl_test_dointvec_read_happy_single_pos= itive(struct kunit *test) { int data =3D 0; /* Good table. */ - struct ctl_table table =3D { - .procname =3D "foo", - .data =3D &data, - .maxlen =3D sizeof(int), - .mode =3D 0644, - .proc_handler =3D proc_dointvec, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE_HUNDRED, - }; + struct ctl_table table =3D CTLTBL_ENTRY_VNMR(data, "foo", 0644, + SYSCTL_ZERO, SYSCTL_ONE_HUNDRED); size_t len =3D 4; loff_t pos =3D 0; char *buffer =3D kunit_kzalloc(test, len, GFP_USER); @@ -213,15 +181,8 @@ static void sysctl_test_dointvec_read_happy_single_neg= ative(struct kunit *test) { int data =3D 0; /* Good table. */ - struct ctl_table table =3D { - .procname =3D "foo", - .data =3D &data, - .maxlen =3D sizeof(int), - .mode =3D 0644, - .proc_handler =3D proc_dointvec, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE_HUNDRED, - }; + struct ctl_table table =3D CTLTBL_ENTRY_VNMR(data, "foo", 0644, + SYSCTL_ZERO, SYSCTL_ONE_HUNDRED); size_t len =3D 5; loff_t pos =3D 0; char *buffer =3D kunit_kzalloc(test, len, GFP_USER); @@ -242,15 +203,8 @@ static void sysctl_test_dointvec_write_happy_single_po= sitive(struct kunit *test) { int data =3D 0; /* Good table. */ - struct ctl_table table =3D { - .procname =3D "foo", - .data =3D &data, - .maxlen =3D sizeof(int), - .mode =3D 0644, - .proc_handler =3D proc_dointvec, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE_HUNDRED, - }; + struct ctl_table table =3D CTLTBL_ENTRY_VNMR(data, "foo", 0644, + SYSCTL_ZERO, SYSCTL_ONE_HUNDRED); char input[] =3D "9"; size_t len =3D sizeof(input) - 1; loff_t pos =3D 0; @@ -272,15 +226,8 @@ static void sysctl_test_dointvec_write_happy_single_po= sitive(struct kunit *test) static void sysctl_test_dointvec_write_happy_single_negative(struct kunit = *test) { int data =3D 0; - struct ctl_table table =3D { - .procname =3D "foo", - .data =3D &data, - .maxlen =3D sizeof(int), - .mode =3D 0644, - .proc_handler =3D proc_dointvec, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE_HUNDRED, - }; + struct ctl_table table =3D CTLTBL_ENTRY_VNMR(data, "foo", 0644, + SYSCTL_ZERO, SYSCTL_ONE_HUNDRED); char input[] =3D "-9"; size_t len =3D sizeof(input) - 1; loff_t pos =3D 0; @@ -304,15 +251,8 @@ static void sysctl_test_api_dointvec_write_single_less= _int_min( struct kunit *test) { int data =3D 0; - struct ctl_table table =3D { - .procname =3D "foo", - .data =3D &data, - .maxlen =3D sizeof(int), - .mode =3D 0644, - .proc_handler =3D proc_dointvec, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE_HUNDRED, - }; + struct ctl_table table =3D CTLTBL_ENTRY_VNMR(data, "foo", 0644, + SYSCTL_ZERO, SYSCTL_ONE_HUNDRED); size_t max_len =3D 32, len =3D max_len; loff_t pos =3D 0; char *buffer =3D kunit_kzalloc(test, max_len, GFP_USER); @@ -342,15 +282,8 @@ static void sysctl_test_api_dointvec_write_single_grea= ter_int_max( struct kunit *test) { int data =3D 0; - struct ctl_table table =3D { - .procname =3D "foo", - .data =3D &data, - .maxlen =3D sizeof(int), - .mode =3D 0644, - .proc_handler =3D proc_dointvec, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE_HUNDRED, - }; + struct ctl_table table =3D CTLTBL_ENTRY_VNMR(data, "foo", 0644, + SYSCTL_ZERO, SYSCTL_ONE_HUNDRED); size_t max_len =3D 32, len =3D max_len; loff_t pos =3D 0; char *buffer =3D kunit_kzalloc(test, max_len, GFP_USER); @@ -367,6 +300,111 @@ static void sysctl_test_api_dointvec_write_single_gre= ater_int_max( KUNIT_EXPECT_EQ(test, 0, *((int *)table.data)); } =20 +/* + * Test each CTLTBL_ENTRY_XXX() variant. File-scope variables are required + * so that &var is a valid constant expression in a static initializer. + */ +static int ctltbl_int; +static u8 ctltbl_u8; +static u8 ctltbl_u8_min, ctltbl_u8_max =3D 200; +static bool ctltbl_bool; +static char ctltbl_str[64]; + +struct ctltbl_param { + const char *desc; + struct ctl_table table; /* built by CTLTBL_ENTRY_XXX() */ + struct ctl_table expected; /* expected field values */ +}; + +static const struct ctltbl_param ctltbl_cases[] =3D { + /* auto handler =E2=80=94 int */ + { + .desc =3D "sysctl_test_api_ctltbl_entry_V_int", + .table =3D CTLTBL_ENTRY_V(ctltbl_int), + .expected =3D { .procname =3D "ctltbl_int", .mode =3D 0444, + .data =3D &ctltbl_int, .maxlen =3D sizeof(int), + .proc_handler =3D proc_dointvec }, + }, + { + .desc =3D "sysctl_test_api_ctltbl_entry_VM_int", + .table =3D CTLTBL_ENTRY_VM(ctltbl_int, 0644), + .expected =3D { .procname =3D "ctltbl_int", .mode =3D 0644, + .data =3D &ctltbl_int, .maxlen =3D sizeof(int), + .proc_handler =3D proc_dointvec }, + }, + { + .desc =3D "sysctl_test_api_ctltbl_entry_VMR_int", + .table =3D CTLTBL_ENTRY_VMR(ctltbl_int, 0644, + SYSCTL_ZERO, SYSCTL_ONE_HUNDRED), + .expected =3D { .procname =3D "ctltbl_int", .mode =3D 0644, + .data =3D &ctltbl_int, .maxlen =3D sizeof(int), + .proc_handler =3D proc_dointvec_minmax }, + }, + { + .desc =3D "sysctl_test_api_ctltbl_entry_VN_int", + .table =3D CTLTBL_ENTRY_VN(ctltbl_int, "my-sysctl"), + .expected =3D { .procname =3D "my-sysctl", .mode =3D 0444, + .data =3D &ctltbl_int, .maxlen =3D sizeof(int), + .proc_handler =3D proc_dointvec }, + }, + { + .desc =3D "sysctl_test_api_ctltbl_entry_VNM_int", + .table =3D CTLTBL_ENTRY_VNM(ctltbl_int, "my-sysctl", 0644), + .expected =3D { .procname =3D "my-sysctl", .mode =3D 0644, + .data =3D &ctltbl_int, .maxlen =3D sizeof(int), + .proc_handler =3D proc_dointvec }, + }, + /* auto handler =E2=80=94 u8 */ + { + .desc =3D "sysctl_test_api_ctltbl_entry_V_u8", + .table =3D CTLTBL_ENTRY_V(ctltbl_u8), + .expected =3D { .procname =3D "ctltbl_u8", .mode =3D 0444, + .data =3D &ctltbl_u8, .maxlen =3D sizeof(u8), + .proc_handler =3D proc_dou8vec_minmax }, + }, + { + .desc =3D "sysctl_test_api_ctltbl_entry_VMR_u8", + .table =3D CTLTBL_ENTRY_VMR(ctltbl_u8, 0644, + &ctltbl_u8_min, &ctltbl_u8_max), + .expected =3D { .procname =3D "ctltbl_u8", .mode =3D 0644, + .data =3D &ctltbl_u8, .maxlen =3D sizeof(u8), + .proc_handler =3D proc_dou8vec_minmax }, + }, + /* auto handler =E2=80=94 bool */ + { + .desc =3D "sysctl_test_api_ctltbl_entry_V_bool", + .table =3D CTLTBL_ENTRY_V(ctltbl_bool), + .expected =3D { .procname =3D "ctltbl_bool", .mode =3D 0444, + .data =3D &ctltbl_bool, .maxlen =3D sizeof(bool), + .proc_handler =3D proc_dobool }, + }, + /* VNMH =E2=80=94 char[] cannot be auto-dispatched by _Generic (each char= [N] + * is a distinct type); explicit handler is required for strings. + * .maxlen must be sizeof(array), not sizeof(char *). */ + { + .desc =3D "sysctl_test_api_ctltbl_entry_VNMH_string", + .table =3D CTLTBL_ENTRY_VNMH(ctltbl_str, "foo", 0644, + proc_dostring), + .expected =3D { .procname =3D "foo", .mode =3D 0644, + .data =3D ctltbl_str, .maxlen =3D sizeof(ctltbl_str), + .proc_handler =3D proc_dostring }, + }, +}; + +KUNIT_ARRAY_PARAM_DESC(ctltbl, ctltbl_cases, desc); + +static void sysctl_test_api_ctltbl_entry(struct kunit *test) +{ + const struct ctltbl_param *p =3D test->param_value; + + KUNIT_EXPECT_STREQ(test, p->expected.procname, p->table.procname); + KUNIT_EXPECT_EQ(test, p->expected.mode, p->table.mode); + KUNIT_EXPECT_PTR_EQ(test, p->expected.data, p->table.data); + KUNIT_EXPECT_EQ(test, p->expected.maxlen, p->table.maxlen); + KUNIT_EXPECT_PTR_EQ(test, p->expected.proc_handler, + p->table.proc_handler); +} + static struct kunit_case sysctl_test_cases[] =3D { KUNIT_CASE(sysctl_test_api_dointvec_null_tbl_data), KUNIT_CASE(sysctl_test_api_dointvec_table_maxlen_unset), @@ -378,6 +416,7 @@ static struct kunit_case sysctl_test_cases[] =3D { KUNIT_CASE(sysctl_test_dointvec_write_happy_single_negative), KUNIT_CASE(sysctl_test_api_dointvec_write_single_less_int_min), KUNIT_CASE(sysctl_test_api_dointvec_write_single_greater_int_max), + KUNIT_CASE_PARAM(sysctl_test_api_ctltbl_entry, ctltbl_gen_params), {} }; =20 --=20 2.25.1