root/lib/test_sysctl.c
// SPDX-License-Identifier: GPL-2.0-or-later OR copyleft-next-0.3.1
/*
 * proc sysctl test driver
 *
 * Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
 */

/*
 * This module provides an interface to the proc sysctl interfaces.  This
 * driver requires CONFIG_PROC_SYSCTL. It will not normally be loaded by the
 * system unless explicitly requested by name. You can also build this driver
 * into your kernel.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/async.h>
#include <linux/delay.h>
#include <linux/vmalloc.h>

static int i_zero;
static int i_one_hundred = 100;
static int match_int_ok = 1;

enum {
        TEST_H_SETUP_NODE,
        TEST_H_MNT,
        TEST_H_MNTERROR,
        TEST_H_EMPTY_ADD,
        TEST_H_EMPTY,
        TEST_H_U8,
        TEST_H_SIZE /* Always at the end */
};

static struct ctl_table_header *ctl_headers[TEST_H_SIZE] = {};
struct test_sysctl_data {
        int int_0001;
        int int_0002;
        int int_0003[4];

        int boot_int;

        unsigned int uint_0001;

        char string_0001[65];

#define SYSCTL_TEST_BITMAP_SIZE 65536
        unsigned long *bitmap_0001;
};

static struct test_sysctl_data test_data = {
        .int_0001 = 60,
        .int_0002 = 1,

        .int_0003[0] = 0,
        .int_0003[1] = 1,
        .int_0003[2] = 2,
        .int_0003[3] = 3,

        .boot_int = 0,

        .uint_0001 = 314,

        .string_0001 = "(none)",
};

/* These are all under /proc/sys/debug/test_sysctl/ */
static const struct ctl_table test_table[] = {
        {
                .procname       = "int_0001",
                .data           = &test_data.int_0001,
                .maxlen         = sizeof(int),
                .mode           = 0644,
                .proc_handler   = proc_dointvec_minmax,
                .extra1         = &i_zero,
                .extra2         = &i_one_hundred,
        },
        {
                .procname       = "int_0002",
                .data           = &test_data.int_0002,
                .maxlen         = sizeof(int),
                .mode           = 0644,
                .proc_handler   = proc_dointvec,
        },
        {
                .procname       = "int_0003",
                .data           = &test_data.int_0003,
                .maxlen         = sizeof(test_data.int_0003),
                .mode           = 0644,
                .proc_handler   = proc_dointvec,
        },
        {
                .procname       = "match_int",
                .data           = &match_int_ok,
                .maxlen         = sizeof(match_int_ok),
                .mode           = 0444,
                .proc_handler   = proc_dointvec,
        },
        {
                .procname       = "boot_int",
                .data           = &test_data.boot_int,
                .maxlen         = sizeof(test_data.boot_int),
                .mode           = 0644,
                .proc_handler   = proc_dointvec,
                .extra1         = SYSCTL_ZERO,
                .extra2         = SYSCTL_ONE,
        },
        {
                .procname       = "uint_0001",
                .data           = &test_data.uint_0001,
                .maxlen         = sizeof(unsigned int),
                .mode           = 0644,
                .proc_handler   = proc_douintvec,
        },
        {
                .procname       = "string_0001",
                .data           = &test_data.string_0001,
                .maxlen         = sizeof(test_data.string_0001),
                .mode           = 0644,
                .proc_handler   = proc_dostring,
        },
        {
                .procname       = "bitmap_0001",
                .data           = &test_data.bitmap_0001,
                .maxlen         = SYSCTL_TEST_BITMAP_SIZE,
                .mode           = 0644,
                .proc_handler   = proc_do_large_bitmap,
        },
};

static void test_sysctl_calc_match_int_ok(void)
{
        int i;

        struct {
                int defined;
                int wanted;
        } match_int[] = {
                {.defined = *(int *)SYSCTL_ZERO,        .wanted = 0},
                {.defined = *(int *)SYSCTL_ONE,         .wanted = 1},
                {.defined = *(int *)SYSCTL_TWO,         .wanted = 2},
                {.defined = *(int *)SYSCTL_THREE,       .wanted = 3},
                {.defined = *(int *)SYSCTL_FOUR,        .wanted = 4},
                {.defined = *(int *)SYSCTL_ONE_HUNDRED, .wanted = 100},
                {.defined = *(int *)SYSCTL_TWO_HUNDRED, .wanted = 200},
                {.defined = *(int *)SYSCTL_ONE_THOUSAND, .wanted = 1000},
                {.defined = *(int *)SYSCTL_THREE_THOUSAND, .wanted = 3000},
                {.defined = *(int *)SYSCTL_INT_MAX,     .wanted = INT_MAX},
                {.defined = *(int *)SYSCTL_MAXOLDUID,   .wanted = 65535},
                {.defined = *(int *)SYSCTL_NEG_ONE,     .wanted = -1},
        };

        for (i = 0; i < ARRAY_SIZE(match_int); i++)
                if (match_int[i].defined != match_int[i].wanted)
                        match_int_ok = 0;
}

static int test_sysctl_setup_node_tests(void)
{
        test_sysctl_calc_match_int_ok();
        test_data.bitmap_0001 = kzalloc(SYSCTL_TEST_BITMAP_SIZE/8, GFP_KERNEL);
        if (!test_data.bitmap_0001)
                return -ENOMEM;
        ctl_headers[TEST_H_SETUP_NODE] = register_sysctl("debug/test_sysctl", test_table);
        if (!ctl_headers[TEST_H_SETUP_NODE]) {
                kfree(test_data.bitmap_0001);
                return -ENOMEM;
        }

        return 0;
}

/* Used to test that unregister actually removes the directory */
static const struct ctl_table test_table_unregister[] = {
        {
                .procname       = "unregister_error",
                .data           = &test_data.int_0001,
                .maxlen         = sizeof(int),
                .mode           = 0644,
                .proc_handler   = proc_dointvec_minmax,
        },
};

static int test_sysctl_run_unregister_nested(void)
{
        struct ctl_table_header *unregister;

        unregister = register_sysctl("debug/test_sysctl/unregister_error",
                                   test_table_unregister);
        if (!unregister)
                return -ENOMEM;

        unregister_sysctl_table(unregister);
        return 0;
}

static int test_sysctl_run_register_mount_point(void)
{
        ctl_headers[TEST_H_MNT]
                = register_sysctl_mount_point("debug/test_sysctl/mnt");
        if (!ctl_headers[TEST_H_MNT])
                return -ENOMEM;

        ctl_headers[TEST_H_MNTERROR]
                = register_sysctl("debug/test_sysctl/mnt/mnt_error",
                                  test_table_unregister);
        /*
         * Don't check the result.:
         * If it fails (expected behavior), return 0.
         * If successful (missbehavior of register mount point), we want to see
         * mnt_error when we run the sysctl test script
         */

        return 0;
}

static const struct ctl_table test_table_empty[] = { };

static int test_sysctl_run_register_empty(void)
{
        /* Tets that an empty dir can be created */
        ctl_headers[TEST_H_EMPTY_ADD]
                = register_sysctl("debug/test_sysctl/empty_add", test_table_empty);
        if (!ctl_headers[TEST_H_EMPTY_ADD])
                return -ENOMEM;

        /* Test that register on top of an empty dir works */
        ctl_headers[TEST_H_EMPTY]
                = register_sysctl("debug/test_sysctl/empty_add/empty", test_table_empty);
        if (!ctl_headers[TEST_H_EMPTY])
                return -ENOMEM;

        return 0;
}

static const struct ctl_table table_u8_over[] = {
        {
                .procname       = "u8_over",
                .data           = &test_data.uint_0001,
                .maxlen         = sizeof(u8),
                .mode           = 0644,
                .proc_handler   = proc_dou8vec_minmax,
                .extra1         = SYSCTL_FOUR,
                .extra2         = SYSCTL_ONE_THOUSAND,
        },
};

static const struct ctl_table table_u8_under[] = {
        {
                .procname       = "u8_under",
                .data           = &test_data.uint_0001,
                .maxlen         = sizeof(u8),
                .mode           = 0644,
                .proc_handler   = proc_dou8vec_minmax,
                .extra1         = SYSCTL_NEG_ONE,
                .extra2         = SYSCTL_ONE_HUNDRED,
        },
};

static const struct ctl_table table_u8_valid[] = {
        {
                .procname       = "u8_valid",
                .data           = &test_data.uint_0001,
                .maxlen         = sizeof(u8),
                .mode           = 0644,
                .proc_handler   = proc_dou8vec_minmax,
                .extra1         = SYSCTL_ZERO,
                .extra2         = SYSCTL_TWO_HUNDRED,
        },
};

static int test_sysctl_register_u8_extra(void)
{
        /* should fail because it's over */
        ctl_headers[TEST_H_U8]
                = register_sysctl("debug/test_sysctl", table_u8_over);
        if (ctl_headers[TEST_H_U8])
                return -ENOMEM;

        /* should fail because it's under */
        ctl_headers[TEST_H_U8]
                = register_sysctl("debug/test_sysctl", table_u8_under);
        if (ctl_headers[TEST_H_U8])
                return -ENOMEM;

        /* should not fail because it's valid */
        ctl_headers[TEST_H_U8]
                = register_sysctl("debug/test_sysctl", table_u8_valid);
        if (!ctl_headers[TEST_H_U8])
                return -ENOMEM;

        return 0;
}

static int __init test_sysctl_init(void)
{
        int err = 0;

        int (*func_array[])(void) = {
                test_sysctl_setup_node_tests,
                test_sysctl_run_unregister_nested,
                test_sysctl_run_register_mount_point,
                test_sysctl_run_register_empty,
                test_sysctl_register_u8_extra
        };

        for (int i = 0; !err && i < ARRAY_SIZE(func_array); i++)
                err = func_array[i]();

        return err;
}
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]);
        }
}

module_exit(test_sysctl_exit);

MODULE_AUTHOR("Luis R. Rodriguez <mcgrof@kernel.org>");
MODULE_DESCRIPTION("proc sysctl test driver");
MODULE_LICENSE("GPL");