[PATCH 2/4] debugfs: Provide min/max checking for simple u8, u16, u32, u64 debugfs files.

From: chris hyser
Date: Tue May 30 2023 - 15:41:03 EST


This patch extends the simple file interface to include min/max checking
files. Writes to a file are checked such that the written value must
satisfy the specified check (as well as fit into the specified storage):

minmax: 'min' <= value <= 'max'
min: 'min' <= value
max: value <= 'max'

Failure of the check returns EINVAL.

While the same checks could be done by providing a custom "set(void *data,
u64 val) function" in DEFINE_DEBUGFS_ATTRIBUTE() for each file needing said
check, each instance would require a private struct file_operations.

Using the same trick as the unsigned simple files (u8/u16/u32/u64), this
patch supports "unlimited" users with only two struct file_operations per
unsigned type. As min/max checking only applies to set/writing, read-only
files make no sense.

Various macros are provided to simplify setting up the params struct.

Signed-off-by: Chris Hyser <chris.hyser@xxxxxxxxxx>
---
Documentation/filesystems/debugfs.rst | 39 ++++++
fs/debugfs/file.c | 189 ++++++++++++++++++++++++++
include/linux/debugfs.h | 60 ++++++++
3 files changed, 288 insertions(+)

diff --git a/Documentation/filesystems/debugfs.rst b/Documentation/filesystems/debugfs.rst
index 6f1ac8d7f108..31d186e952eb 100644
--- a/Documentation/filesystems/debugfs.rst
+++ b/Documentation/filesystems/debugfs.rst
@@ -99,6 +99,45 @@ functions can be used instead::
void debugfs_create_x64(const char *name, umode_t mode,
struct dentry *parent, u64 *value);

+Some use cases require min/max checking of the written values preserving the
+initial value on failure and returning EINVAL for the write.
+
+ void debugfs_create_u8_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp);
+ void debugfs_create_u16_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp);
+ void debugfs_create_u32_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp);
+ void debugfs_create_u64_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp);
+
+These functions are called with the following parameter structure, flags and
+helper macros. The parameter structure must be available for the duration of
+the file, thus requiring global or malloced memory. Failure of the specified
+check(s) return EINVAL.
+
+struct debugfs_minmax_params {
+ void *value;
+ u64 min;
+ u64 max;
+ u8 flags;
+};
+
+Flags are defined as such:
+ DEBUGFS_ATTR_MIN 1
+ DEBUGFS_ATTR_MAX 2
+ DEBUGFS_ATTR_MINMAX (DEBUGFS_ATTR_MIN | DEBUGFS_ATTR_MAX)
+
+Additional helper macros provide for
+ DEBUGFS_MINMAX_ATTRIBUTES_BASE(name_parm_ptr, value, min, max, flags);
+ DEBUGFS_MIN_ATTRIBUTES(name_parm_ptr, value, min);
+ DEBUGFS_MAX_ATTRIBUTES(name_parm_ptr, value, max);
+ DEBUGFS_MINMAX_ATTRIBUTES(name_parm_ptr, value, min, max);
+
These functions are useful as long as the developer knows the size of the
value to be exported. Some types can have different widths on different
architectures, though, complicating the situation somewhat. There are
diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index 743ddd04f8d8..bd655b286da6 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -578,6 +578,195 @@ void debugfs_create_u64(const char *name, umode_t mode, struct dentry *parent,
}
EXPORT_SYMBOL_GPL(debugfs_create_u64);

+static int debugfs_minmax_chk(struct debugfs_minmax_params *mnxp, u64 val)
+{
+ if ((mnxp->flags & 0x1) && val < mnxp->min)
+ return -EINVAL;
+ if ((mnxp->flags & 0x2) && val > mnxp->max)
+ return -EINVAL;
+ return 0;
+}
+
+static int debugfs_u8_minmax_set(void *data, u64 val)
+{
+ struct debugfs_minmax_params *mnxp = data;
+ int err = debugfs_minmax_chk(mnxp, val);
+
+ if (err)
+ return err;
+ return debugfs_u8_set(mnxp->value, val);
+}
+
+static int debugfs_u8_minmax_get(void *data, u64 *val)
+{
+ struct debugfs_minmax_params *mnxp = data;
+
+ return debugfs_u16_get(mnxp->value, val);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u8_minmax, debugfs_u8_minmax_get, debugfs_u8_minmax_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u8_minmax_wo, NULL, debugfs_u8_minmax_set, "%llu\n");
+
+/**
+ * debugfs_create_u8_minmax - create a debugfs file that is used to both read
+ * and write an unsigned 8-bit value if it satisfies a min check, max check
+ * or both.
+ * @name: a pointer to a string containing the name of the file to create.
+ * @mode: the permission that the file should have
+ * @parent: a pointer to the parent dentry for this file. This should be a
+ * directory dentry if set. If this parameter is %NULL, then the
+ * file will be created in the root of the debugfs filesystem.
+ * @mnxp: a pointer to the parameter struct which holds the test limits, the
+ * test to perform and a pointer to the variable to display and modify.
+ *
+ * This function creates a file in debugfs with the given name that contains
+ * the value of the specified variable. If the @mode variable is so
+ * set, it can be read from, and written to.
+ */
+void debugfs_create_u8_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp)
+{
+ debugfs_create_mode_unsafe(name, mode, parent, mnxp, &fops_u8_minmax,
+ NULL, &fops_u8_minmax_wo);
+}
+EXPORT_SYMBOL_GPL(debugfs_create_u8_minmax);
+
+static int debugfs_u16_minmax_set(void *data, u64 val)
+{
+ struct debugfs_minmax_params *mnxp = data;
+ int err = debugfs_minmax_chk(mnxp, val);
+
+ if (err)
+ return err;
+ return debugfs_u16_set(mnxp->value, val);
+}
+
+static int debugfs_u16_minmax_get(void *data, u64 *val)
+{
+ struct debugfs_minmax_params *mnxp = data;
+
+ return debugfs_u16_get(mnxp->value, val);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u16_minmax, debugfs_u16_minmax_get, debugfs_u16_minmax_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u16_minmax_wo, NULL, debugfs_u16_minmax_set, "%llu\n");
+
+/**
+ * debugfs_create_u16_minmax - create a debugfs file that is used to both read
+ * and write an unsigned 16-bit value if it satisfies a min check, max check
+ * or both.
+ * @name: a pointer to a string containing the name of the file to create.
+ * @mode: the permission that the file should have
+ * @parent: a pointer to the parent dentry for this file. This should be a
+ * directory dentry if set. If this parameter is %NULL, then the
+ * file will be created in the root of the debugfs filesystem.
+ * @mnxp: a pointer to the parameter struct which holds the test limits, the
+ * test to perform and a pointer to the variable to display and modify.
+ *
+ * This function creates a file in debugfs with the given name that contains
+ * the value of the specified variable. If the @mode variable is so
+ * set, it can be read from, and written to.
+ */
+void debugfs_create_u16_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp)
+{
+ debugfs_create_mode_unsafe(name, mode, parent, mnxp, &fops_u16_minmax,
+ NULL, &fops_u16_minmax_wo);
+}
+EXPORT_SYMBOL_GPL(debugfs_create_u16_minmax);
+
+static int debugfs_u32_minmax_set(void *data, u64 val)
+{
+ struct debugfs_minmax_params *mnxp = data;
+ int err = debugfs_minmax_chk(mnxp, val);
+
+ if (err)
+ return err;
+ return debugfs_u32_set(mnxp->value, val);
+}
+
+static int debugfs_u32_minmax_get(void *data, u64 *val)
+{
+ struct debugfs_minmax_params *mnxp = data;
+
+ return debugfs_u32_get(mnxp->value, val);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u32_minmax, debugfs_u32_minmax_get, debugfs_u32_minmax_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u32_minmax_wo, NULL, debugfs_u32_minmax_set, "%llu\n");
+
+/**
+ * debugfs_create_u32_minmax - create a debugfs file that is used to both read
+ * and write an unsigned 32-bit value if it satisfies a min check, max check
+ * or both.
+ * @name: a pointer to a string containing the name of the file to create.
+ * @mode: the permission that the file should have
+ * @parent: a pointer to the parent dentry for this file. This should be a
+ * directory dentry if set. If this parameter is %NULL, then the
+ * file will be created in the root of the debugfs filesystem.
+ * @mnxp: a pointer to the parameter struct which holds the test limits, the
+ * test to perform and a pointer to the variable to display and modify.
+ *
+ * This function creates a file in debugfs with the given name that contains
+ * the value of the specified variable. If the @mode variable is so
+ * set, it can be read from, and written to.
+ */
+void debugfs_create_u32_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp)
+{
+ debugfs_create_mode_unsafe(name, mode, parent, mnxp, &fops_u32_minmax,
+ NULL, &fops_u32_minmax_wo);
+}
+EXPORT_SYMBOL_GPL(debugfs_create_u32_minmax);
+
+static int debugfs_u64_minmax_set(void *data, u64 val)
+{
+ struct debugfs_minmax_params *mnxp = data;
+ int err = debugfs_minmax_chk(mnxp, val);
+
+ if (err)
+ return err;
+ return debugfs_u64_set(mnxp->value, val);
+}
+
+static int debugfs_u64_minmax_get(void *data, u64 *val)
+{
+ struct debugfs_minmax_params *mnxp = data;
+
+ return debugfs_u64_get(mnxp->value, val);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u64_minmax, debugfs_u64_minmax_get, debugfs_u64_minmax_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u64_minmax_wo, NULL, debugfs_u64_minmax_set, "%llu\n");
+
+/**
+ * debugfs_create_u64_minmax - create a debugfs file that is used both to read
+ * and write an unsigned 64-bit value if it satisfies a min check, max check
+ * or both.
+ * @name: a pointer to a string containing the name of the file to create.
+ * @mode: the permission that the file should have
+ * @parent: a pointer to the parent dentry for this file. This should be a
+ * directory dentry if set. If this parameter is %NULL, then the
+ * file will be created in the root of the debugfs filesystem.
+ * @mnxp: a pointer to the parameter struct which holds the test limits, the
+ * test to perform and a pointer to the variable to display and modify.
+ *
+ * This function creates a file in debugfs with the given name that contains
+ * the value of the specified variable. If the @mode variable is so
+ * set, it can be read from, and written to.
+ */
+void debugfs_create_u64_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp)
+{
+ debugfs_create_mode_unsafe(name, mode, parent, mnxp, &fops_u64_minmax,
+ NULL, &fops_u64_minmax_wo);
+}
+EXPORT_SYMBOL_GPL(debugfs_create_u64_minmax);
+
static int debugfs_ulong_set(void *data, u64 val)
{
*(unsigned long *)data = val;
diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h
index ea2d919fd9c7..322fecb22a0a 100644
--- a/include/linux/debugfs.h
+++ b/include/linux/debugfs.h
@@ -43,6 +43,36 @@ struct debugfs_u32_array {
u32 n_elements;
};

+struct debugfs_minmax_params {
+ void *value;
+ u64 min;
+ u64 max;
+ u8 flags;
+};
+
+/* debugfs_minmax_params "flag" values
+ */
+#define DEBUGFS_ATTR_MIN 1
+#define DEBUGFS_ATTR_MAX 2
+#define DEBUGFS_ATTR_MINMAX (DEBUGFS_ATTR_MIN | DEBUGFS_ATTR_MAX)
+
+#define DEBUGFS_MINMAX_ATTRIBUTES_BASE(__name, __value, __min, __max, __flags) \
+static struct debugfs_minmax_params __name = { \
+ .value = (__value), \
+ .min = (__min), \
+ .max = (__max), \
+ .flags = (__flags), \
+}
+
+#define DEBUGFS_MIN_ATTRIBUTES(__name, __value, __min) \
+ DEBUGFS_MINMAX_ATTRIBUTES_BASE(__name, __value, __min, 0, DEBUGFS_ATTR_MIN)
+
+#define DEBUGFS_MAX_ATTRIBUTES(__name, __value, __max) \
+ DEBUGFS_MINMAX_ATTRIBUTES_BASE(__name, __value, 0, __max, DEBUGFS_ATTR_MAX)
+
+#define DEBUGFS_MINMAX_ATTRIBUTES(__name, __value, __min, __max) \
+ DEBUGFS_MINMAX_ATTRIBUTES_BASE(__name, __value, __min, __max, DEBUGFS_ATTR_MINMAX)
+
extern struct dentry *arch_debugfs_dir;

#define DEFINE_DEBUGFS_ATTRIBUTE_XSIGNED(__fops, __get, __set, __fmt, __is_signed) \
@@ -122,6 +152,20 @@ void debugfs_create_u32(const char *name, umode_t mode, struct dentry *parent,
u32 *value);
void debugfs_create_u64(const char *name, umode_t mode, struct dentry *parent,
u64 *value);
+
+void debugfs_create_u8_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp);
+void debugfs_create_u16_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp);
+void debugfs_create_u32_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp);
+void debugfs_create_u64_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp);
+
void debugfs_create_ulong(const char *name, umode_t mode, struct dentry *parent,
unsigned long *value);
void debugfs_create_x8(const char *name, umode_t mode, struct dentry *parent,
@@ -287,6 +331,22 @@ static inline void debugfs_create_u32(const char *name, umode_t mode,
static inline void debugfs_create_u64(const char *name, umode_t mode,
struct dentry *parent, u64 *value) { }

+static inline void debugfs_create_u8_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp) { }
+
+static inline void debugfs_create_u16_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp) { }
+
+static inline void debugfs_create_u32_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp) { }
+
+static inline void debugfs_create_u64_minmax(const char *name, umode_t mode,
+ struct dentry *parent,
+ struct debugfs_minmax_params *mnxp) { }
+
static inline void debugfs_create_ulong(const char *name, umode_t mode,
struct dentry *parent,
unsigned long *value) { }
--
2.31.1