/* SPDX-License-Identifier: (BSD-2-Clause OR GPL-2.0-or-later)
 * (C) 2019-2021 AVM GmbH <info@avm.de>
 *
 * vim:set noexpandtab shiftwidth=8 softtabstop=8 fileencoding=utf-8:
 *
 * Simple handling of linux kernel proc fs trees
 */

#ifndef SHAREDLIBS_CPROCFS_H
#define SHAREDLIBS_CPROCFS_H

#ifdef CONFIG_PROC_FS

#include <linux/version.h>
#include <linux/proc_fs.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)
/* The wrappers are public for anyone who needs to use the procfs API directly (like
 * cprocfs internally). So just include cprocfs.h and use either cprocfs or the
 * new-style procfs kernel API (even on old kernels).
 *
 * See mainline commits:
 * commit 97a32539b956 proc: convert everything to "struct proc_ops"
 * commit d56c0d45f0e2 proc: decouple proc from VFS with "struct proc_ops"
 *
 * proc_ops replaces file_operations and must be passed to proc_* interfaces.
 *
 * Conversion rule (see commit 97a32539b956):
 * llseek          => proc_lseek
 * unlocked_ioctl  => proc_ioctl
 * *               => proc_*
 * owner           => delete assignment
 */
#ifndef CPROCFS_NO_PROC_OPS
/* Define if you don't want CPROCFS_NO_PROC_OPS, e.g. if the proc_read
 * definition conflicts with members in unrelated types.
 */
struct proc_ops {
#define proc_open    _proc_fops.open
#define proc_read    _proc_fops.read
#define proc_write   _proc_fops.write
#define proc_lseek   _proc_fops.llseek
#define proc_ioctl   _proc_fops.unlocked_ioctl
#define proc_release _proc_fops.release
	struct file_operations _proc_fops;
};

#define proc_create_data(name, mode, proc_dir, proc_ops, data) \
	proc_create_data(name, mode, proc_dir, &(proc_ops)->_proc_fops, data)

#define proc_create(name, mode, proc_dir, proc_ops) \
	proc_create(name, mode, proc_dir, &(proc_ops)->_proc_fops)

#endif /* CPROCFS_NO_PROC_OPS */
#endif

/* --------------------------------------------------------------------- */

typedef int (*cprocfs_walk_cb_t)(void *, const char *, ...) __attribute__((format(printf, 2, 3)));

struct cprocfs_file;

struct cprocfs_dir {
	struct cprocfs_dir *next;
	const char *name;
	bool is_root;
	struct cprocfs_dir *parent; /* NULL if is_root or not attached */
	struct proc_dir_entry *proc_dir;
	struct cprocfs_file *cfiles;
	struct cprocfs_dir *cdirs;
};

struct cprocfs_file {
	struct cprocfs_file *next;
	struct cprocfs_dir *cdir;
	const char *name;
	struct proc_dir_entry *procfs_file;
	void (*showfunc)(void *userdata, cprocfs_walk_cb_t cb, void *arg);
	void (*control)(void *userdata, int argc, char *argv[]);
	void *userdata;
};

/* --------------------------------------------------------------------- */

/**
 * cprocfs_create_root_dir: set root proc_dir_entry of a cprocfs_root.
 *
 *  @param name           name of directory
 *
 */
struct cprocfs_dir *cprocfs_create_root_dir(const char *name);

/**
 * cprocfs_destroy: remove all files and directories under 'root'
 *
 *  @param root           tree to destroy (NOT NULL).
 *
 */
void cprocfs_destroy(struct cprocfs_dir *root);

/* --------------------------------------------------------------------- */

/**
 * cprocfs_add_dir: add a directory to a directory.
 *
 *  @param cdir  parent directory (NOT NULL).
 *
 *  @returns     cprocfs_dir or NULL (kmalloc() failed).
 */
struct cprocfs_dir *cprocfs_add_dir(struct cprocfs_dir *cdir, const char *name);

/**
 * cprocfs_delete_dir: remove a directory tree
 *
 *  @param cdir directory to remove (NOT NULL).
 */
void cprocfs_delete_dir(struct cprocfs_dir *cdir);

/**
 * cprocfs_register_file: add a readonly file
 *
 *  @param cdir           directory NOT NULL
 *  @param name           name of file
 *  @param userdata       used as first argument for 'showfunc'
 *  @param showfunc       'read' function.
 *
 *  @retval  0            file registered
 *  @retval -1            kmalloc failed
 */
int cprocfs_register_file(struct cprocfs_dir *cdir,
                          const char *name,
                          void (*showfunc)(void *userdata, cprocfs_walk_cb_t cb, void *arg),
                          void *userdata);

/**
 * cprocfs_register_control: add a readwrite file
 *
 *  @param cdir           directory NOT NULL
 *  @param name           name of file
 *  @param showfunc       'read' function.
 *  @param control        'write' function.
 *  @param userdata       used as first argument for 'showfunc' and 'control'
 *
 *  @retval  0            file registered
 *  @retval -1            kmalloc failed
 */
int cprocfs_register_control(struct cprocfs_dir *cdir,
                             const char *name,
                             void (*showfunc)(void *userdata, cprocfs_walk_cb_t cb, void *arg),
                             void (*control)(void *userdata, int argc, char *argv[]),
                             void *userdata);

/* --------------------------------------------------------------------- */

/**
 * cprocfs_create_detached_dir: create a cprocfs_dir without parent
 *
 * can be attached with cprocfs_attach_dir().
 *
 *  @param name           name of directory
 *
 *  @returns              cprocfs_dir or NULL
 */
struct cprocfs_dir *cprocfs_create_detached_dir(const char *name);

/**
 * cprocfs_attach_dir: add a directory to a directory.
 *
 *  @param parent directory where 'child' will be attached (NOT NULL).
 *  @param child  directory to add to 'parent' (NOT NULL).
 */
void cprocfs_attach_dir(struct cprocfs_dir *parent, struct cprocfs_dir *child);

/**
 * cprocfs_rename_dir: rename a detached directory.
 *
 *  @param cdir  directory to rename (NOT NULL).
 *
 *  @retval  0            directory renamed
 *  @retval -1            directory already detached
 */
int cprocfs_rename_dir(struct cprocfs_dir *cdir, const char *name);

/**
 * cprocfs_walk: walk over directory tree and output information
 *
 *  @param cdir  directory to start (NOT NULL).
 *  @param cb function (if NULL output will be done via printk)
 *  @param arg         arg to function
 *
 *  @returns number of objects visited
 */
unsigned int cprocfs_walk(struct cprocfs_dir *cdir, cprocfs_walk_cb_t cb, void *arg);

/* --------------------------------------------------------------------- */

#endif /* CONFIG_PROCFS */
#endif /* SHAREDLIBS_CPROCFS_H */