xref: /linux/lib/test_sysctl.c (revision a2c375789aa6c83fc85b8f528ee6d7d5f5666941)
1 // SPDX-License-Identifier: GPL-2.0-or-later OR copyleft-next-0.3.1
2 /*
3  * proc sysctl test driver
4  *
5  * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
6  */
7 
8 /*
9  * This module provides an interface to the proc sysctl interfaces.  This
10  * driver requires CONFIG_SYSCTL. It will not normally be loaded by the
11  * system unless explicitly requested by name. You can also build this driver
12  * into your kernel.
13  */
14 
15 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
16 
17 #include <linux/init.h>
18 #include <linux/jiffies.h>
19 #include <linux/list.h>
20 #include <linux/module.h>
21 #include <linux/printk.h>
22 #include <linux/fs.h>
23 #include <linux/miscdevice.h>
24 #include <linux/slab.h>
25 #include <linux/uaccess.h>
26 #include <linux/async.h>
27 #include <linux/delay.h>
28 #include <linux/vmalloc.h>
29 
30 static int i_zero;
31 static int i_one_hundred = 100;
32 static int match_int_ok = 1;
33 
34 enum {
35 	TEST_H_SETUP_NODE,
36 	TEST_H_MNT,
37 	TEST_H_MNTERROR,
38 	TEST_H_EMPTY_ADD,
39 	TEST_H_EMPTY,
40 	TEST_H_U8,
41 	TEST_H_SIZE /* Always at the end */
42 };
43 
44 static struct ctl_table_header *ctl_headers[TEST_H_SIZE] = {};
45 struct test_sysctl_data {
46 	int int_0001;
47 	int int_0002;
48 	int int_0003[4];
49 
50 	int boot_int;
51 
52 	int int_jiffies;
53 	int int_ms_jiffies;
54 	int int_userhz_jiffies;
55 
56 	unsigned int uint_0001;
57 
58 	char string_0001[65];
59 
60 #define SYSCTL_TEST_BITMAP_SIZE	65536
61 	unsigned long *bitmap_0001;
62 };
63 
64 static struct test_sysctl_data test_data = {
65 	.int_0001 = 60,
66 	.int_0002 = 1,
67 
68 	.int_0003[0] = 0,
69 	.int_0003[1] = 1,
70 	.int_0003[2] = 2,
71 	.int_0003[3] = 3,
72 
73 	.boot_int = 0,
74 
75 	/* One second in each converter's unit; all three store HZ jiffies. */
76 	.int_jiffies = HZ,
77 	.int_ms_jiffies = HZ,
78 	.int_userhz_jiffies = HZ,
79 
80 	.uint_0001 = 314,
81 
82 	.string_0001 = "(none)",
83 };
84 
85 /* These are all under /proc/sys/debug/test_sysctl/ */
86 static const struct ctl_table test_table[] = {
87 	{
88 		.procname	= "int_0001",
89 		.data		= &test_data.int_0001,
90 		.maxlen		= sizeof(int),
91 		.mode		= 0644,
92 		.proc_handler	= proc_dointvec_minmax,
93 		.extra1		= &i_zero,
94 		.extra2         = &i_one_hundred,
95 	},
96 	{
97 		.procname	= "int_0002",
98 		.data		= &test_data.int_0002,
99 		.maxlen		= sizeof(int),
100 		.mode		= 0644,
101 		.proc_handler	= proc_dointvec,
102 	},
103 	{
104 		.procname	= "int_0003",
105 		.data		= &test_data.int_0003,
106 		.maxlen		= sizeof(test_data.int_0003),
107 		.mode		= 0644,
108 		.proc_handler	= proc_dointvec,
109 	},
110 	{
111 		.procname	= "match_int",
112 		.data		= &match_int_ok,
113 		.maxlen		= sizeof(match_int_ok),
114 		.mode		= 0444,
115 		.proc_handler	= proc_dointvec,
116 	},
117 	{
118 		.procname	= "boot_int",
119 		.data		= &test_data.boot_int,
120 		.maxlen		= sizeof(test_data.boot_int),
121 		.mode		= 0644,
122 		.proc_handler	= proc_dointvec,
123 		.extra1		= SYSCTL_ZERO,
124 		.extra2         = SYSCTL_ONE,
125 	},
126 	{
127 		.procname	= "int_jiffies",
128 		.data		= &test_data.int_jiffies,
129 		.maxlen		= sizeof(int),
130 		.mode		= 0644,
131 		.proc_handler	= proc_dointvec_jiffies,
132 	},
133 	{
134 		.procname	= "int_ms_jiffies",
135 		.data		= &test_data.int_ms_jiffies,
136 		.maxlen		= sizeof(int),
137 		.mode		= 0644,
138 		.proc_handler	= proc_dointvec_ms_jiffies,
139 	},
140 	{
141 		.procname	= "int_userhz_jiffies",
142 		.data		= &test_data.int_userhz_jiffies,
143 		.maxlen		= sizeof(int),
144 		.mode		= 0644,
145 		.proc_handler	= proc_dointvec_userhz_jiffies,
146 	},
147 	{
148 		.procname	= "uint_0001",
149 		.data		= &test_data.uint_0001,
150 		.maxlen		= sizeof(unsigned int),
151 		.mode		= 0644,
152 		.proc_handler	= proc_douintvec,
153 	},
154 	{
155 		.procname	= "string_0001",
156 		.data		= &test_data.string_0001,
157 		.maxlen		= sizeof(test_data.string_0001),
158 		.mode		= 0644,
159 		.proc_handler	= proc_dostring,
160 	},
161 	{
162 		.procname	= "bitmap_0001",
163 		.data		= &test_data.bitmap_0001,
164 		.maxlen		= SYSCTL_TEST_BITMAP_SIZE,
165 		.mode		= 0644,
166 		.proc_handler	= proc_do_large_bitmap,
167 	},
168 };
169 
170 static void test_sysctl_calc_match_int_ok(void)
171 {
172 	int i;
173 
174 	struct {
175 		int defined;
176 		int wanted;
177 	} match_int[] = {
178 		{.defined = *(int *)SYSCTL_ZERO,	.wanted = 0},
179 		{.defined = *(int *)SYSCTL_ONE,		.wanted = 1},
180 		{.defined = *(int *)SYSCTL_TWO,		.wanted = 2},
181 		{.defined = *(int *)SYSCTL_THREE,	.wanted = 3},
182 		{.defined = *(int *)SYSCTL_FOUR,	.wanted = 4},
183 		{.defined = *(int *)SYSCTL_ONE_HUNDRED, .wanted = 100},
184 		{.defined = *(int *)SYSCTL_TWO_HUNDRED,	.wanted = 200},
185 		{.defined = *(int *)SYSCTL_ONE_THOUSAND, .wanted = 1000},
186 		{.defined = *(int *)SYSCTL_THREE_THOUSAND, .wanted = 3000},
187 		{.defined = *(int *)SYSCTL_INT_MAX,	.wanted = INT_MAX},
188 		{.defined = *(int *)SYSCTL_MAXOLDUID,	.wanted = 65535},
189 		{.defined = *(int *)SYSCTL_NEG_ONE,	.wanted = -1},
190 	};
191 
192 	for (i = 0; i < ARRAY_SIZE(match_int); i++)
193 		if (match_int[i].defined != match_int[i].wanted)
194 			match_int_ok = 0;
195 }
196 
197 static int test_sysctl_setup_node_tests(void)
198 {
199 	test_sysctl_calc_match_int_ok();
200 	test_data.bitmap_0001 = kzalloc(SYSCTL_TEST_BITMAP_SIZE/8, GFP_KERNEL);
201 	if (!test_data.bitmap_0001)
202 		return -ENOMEM;
203 	ctl_headers[TEST_H_SETUP_NODE] = register_sysctl("debug/test_sysctl", test_table);
204 	if (!ctl_headers[TEST_H_SETUP_NODE]) {
205 		kfree(test_data.bitmap_0001);
206 		return -ENOMEM;
207 	}
208 
209 	return 0;
210 }
211 
212 /* Used to test that unregister actually removes the directory */
213 static const struct ctl_table test_table_unregister[] = {
214 	{
215 		.procname	= "unregister_error",
216 		.data		= &test_data.int_0001,
217 		.maxlen		= sizeof(int),
218 		.mode		= 0644,
219 		.proc_handler	= proc_dointvec_minmax,
220 	},
221 };
222 
223 static int test_sysctl_run_unregister_nested(void)
224 {
225 	struct ctl_table_header *unregister;
226 
227 	unregister = register_sysctl("debug/test_sysctl/unregister_error",
228 				   test_table_unregister);
229 	if (!unregister)
230 		return -ENOMEM;
231 
232 	unregister_sysctl_table(unregister);
233 	return 0;
234 }
235 
236 static int test_sysctl_run_register_mount_point(void)
237 {
238 	ctl_headers[TEST_H_MNT]
239 		= register_sysctl_mount_point("debug/test_sysctl/mnt");
240 	if (!ctl_headers[TEST_H_MNT])
241 		return -ENOMEM;
242 
243 	ctl_headers[TEST_H_MNTERROR]
244 		= register_sysctl("debug/test_sysctl/mnt/mnt_error",
245 				  test_table_unregister);
246 	/*
247 	 * Don't check the result.:
248 	 * If it fails (expected behavior), return 0.
249 	 * If successful (missbehavior of register mount point), we want to see
250 	 * mnt_error when we run the sysctl test script
251 	 */
252 
253 	return 0;
254 }
255 
256 static const struct ctl_table test_table_empty[] = { };
257 
258 static int test_sysctl_run_register_empty(void)
259 {
260 	/* Tets that an empty dir can be created */
261 	ctl_headers[TEST_H_EMPTY_ADD]
262 		= register_sysctl("debug/test_sysctl/empty_add", test_table_empty);
263 	if (!ctl_headers[TEST_H_EMPTY_ADD])
264 		return -ENOMEM;
265 
266 	/* Test that register on top of an empty dir works */
267 	ctl_headers[TEST_H_EMPTY]
268 		= register_sysctl("debug/test_sysctl/empty_add/empty", test_table_empty);
269 	if (!ctl_headers[TEST_H_EMPTY])
270 		return -ENOMEM;
271 
272 	return 0;
273 }
274 
275 static const struct ctl_table table_u8_over[] = {
276 	{
277 		.procname	= "u8_over",
278 		.data		= &test_data.uint_0001,
279 		.maxlen		= sizeof(u8),
280 		.mode		= 0644,
281 		.proc_handler	= proc_dou8vec_minmax,
282 		.extra1		= SYSCTL_FOUR,
283 		.extra2		= SYSCTL_ONE_THOUSAND,
284 	},
285 };
286 
287 static const struct ctl_table table_u8_under[] = {
288 	{
289 		.procname	= "u8_under",
290 		.data		= &test_data.uint_0001,
291 		.maxlen		= sizeof(u8),
292 		.mode		= 0644,
293 		.proc_handler	= proc_dou8vec_minmax,
294 		.extra1		= SYSCTL_NEG_ONE,
295 		.extra2		= SYSCTL_ONE_HUNDRED,
296 	},
297 };
298 
299 static const struct ctl_table table_u8_valid[] = {
300 	{
301 		.procname	= "u8_valid",
302 		.data		= &test_data.uint_0001,
303 		.maxlen		= sizeof(u8),
304 		.mode		= 0644,
305 		.proc_handler	= proc_dou8vec_minmax,
306 		.extra1		= SYSCTL_ZERO,
307 		.extra2		= SYSCTL_TWO_HUNDRED,
308 	},
309 };
310 
311 static int test_sysctl_register_u8_extra(void)
312 {
313 	/* should fail because it's over */
314 	ctl_headers[TEST_H_U8]
315 		= register_sysctl("debug/test_sysctl", table_u8_over);
316 	if (ctl_headers[TEST_H_U8])
317 		return -ENOMEM;
318 
319 	/* should fail because it's under */
320 	ctl_headers[TEST_H_U8]
321 		= register_sysctl("debug/test_sysctl", table_u8_under);
322 	if (ctl_headers[TEST_H_U8])
323 		return -ENOMEM;
324 
325 	/* should not fail because it's valid */
326 	ctl_headers[TEST_H_U8]
327 		= register_sysctl("debug/test_sysctl", table_u8_valid);
328 	if (!ctl_headers[TEST_H_U8])
329 		return -ENOMEM;
330 
331 	return 0;
332 }
333 
334 static int __init test_sysctl_init(void)
335 {
336 	int err = 0;
337 
338 	int (*func_array[])(void) = {
339 		test_sysctl_setup_node_tests,
340 		test_sysctl_run_unregister_nested,
341 		test_sysctl_run_register_mount_point,
342 		test_sysctl_run_register_empty,
343 		test_sysctl_register_u8_extra
344 	};
345 
346 	for (int i = 0; !err && i < ARRAY_SIZE(func_array); i++)
347 		err = func_array[i]();
348 
349 	return err;
350 }
351 module_init(test_sysctl_init);
352 
353 static void __exit test_sysctl_exit(void)
354 {
355 	for (int i = 0; i < TEST_H_SIZE; i++) {
356 		if (ctl_headers[i])
357 			unregister_sysctl_table(ctl_headers[i]);
358 	}
359 	kfree(test_data.bitmap_0001);
360 }
361 
362 module_exit(test_sysctl_exit);
363 
364 MODULE_AUTHOR("Luis R. Rodriguez <mcgrof@kernel.org>");
365 MODULE_DESCRIPTION("proc sysctl test driver");
366 MODULE_LICENSE("GPL");
367