Tom Riddel
Tom Riddel

Reputation: 87

How to use a seq_file in Linux kernel modules?

Hello all I'm new to Linux and wondering how to use a Linux sequence file in a module to traverse kernel objects.

What I know is I can use the command:

 cat /proc/kallsyms

to view the available symbols and from what I've read on google, the symbols in the list that have a 'D' or 'd' are pointers to data structures.

Though I know the basics of how to create a module, the examples on the internet on how to use seq operations are not uniform and I'm getting a little confused.

If someone knows of any good doco that will help me understand how to create a seq file to traverse kernel objects and could post a link (or a quick example), I would be greatly appreciative.

Upvotes: 7

Views: 7780

Answers (1)

Minimal runnable example

The kernel docs contain an example under Documentation/filesystems/seq_file.txt, but here is a runnable version of that with loop termination.

This example is behaves just like a file that contains:

0
1
2

However, we only store a single integer in memory and calculate the file on the fly in an iterator fashion.

The file works for both read and lseek system calls, but there is no write system call equivalent: How to implement a writable proc file by using seq_file in a driver module

Play around with the file with cat and dd skip= for the seeks.

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/printk.h> /* pr_info */
#include <linux/seq_file.h> /* seq_read, seq_lseek, single_release */
#include <linux/slab.h>
#include <uapi/linux/stat.h> /* S_IRUSR */

MODULE_LICENSE("GPL");

static int max = 2;
module_param(max, int, S_IRUSR | S_IWUSR);

static struct dentry *debugfs_file;

/* Called at the beginning of every read.
 *
 * The return value is passsed to the first show.
 * It normally represents the current position of the iterator.
 * It could be any struct, but we use just a single integer here.
 *
 * NULL return means stop should be called next, and so the read will be empty..
 * This happens for example for an ftell that goes beyond the file size.
 */
static void *start(struct seq_file *s, loff_t *pos)
{
    loff_t *spos;

    pr_info("start pos = %llx\n", (unsigned long long)*pos);
    spos = kmalloc(sizeof(loff_t), GFP_KERNEL);
    if (!spos || *pos >= max)
        return NULL;
    *spos = *pos;
    return spos;
}

/* The return value is passed to next show.
 * If NULL, stop is called next instead of show, and read ends.
 *
 * Can get called multiple times, until enough data is returned for the read.
 */
static void *next(struct seq_file *s, void *v, loff_t *pos)
{
    loff_t *spos;

    spos = v;
    pr_info("next pos = %llx\n", (unsigned long long)*pos);
    if (*pos >= max)
        return NULL;
    *pos = ++*spos;
    return spos;
}

/* Called at the end of every read. */
static void stop(struct seq_file *s, void *v)
{
    pr_info("stop\n");
    kfree(v);
}

/* Return 0 means success, SEQ_SKIP ignores previous prints, negative for error. */
static int show(struct seq_file *s, void *v)
{
    loff_t *spos;

    spos = v;
    pr_info("show pos = %llx\n", (unsigned long long)*spos);
    seq_printf(s, "%llx\n", (long long unsigned)*spos);
    return 0;
}

static struct seq_operations my_seq_ops = {
    .next  = next,
    .show  = show,
    .start = start,
    .stop  = stop,
};

static int open(struct inode *inode, struct file *file)
{
    pr_info("open\n");
    return seq_open(file, &my_seq_ops);
}

static struct file_operations fops = {
    .owner   = THIS_MODULE,
    .llseek  = seq_lseek,
    .open    = open,
    .read    = seq_read,
    .release = seq_release
};

static int myinit(void)
{
    debugfs_file = debugfs_create_file(
        "lkmc_seq_file", S_IRUSR, NULL, NULL, &fops);
    if (debugfs_file) {
        return 0;
    } else {
        return -EINVAL;
    }
}

static void myexit(void)
{
    debugfs_remove(debugfs_file);
}

module_init(myinit)
module_exit(myexit)

GitHub upstream.

Note how the seq_file API makes it much easier to write the read file operation.

single_open

If you have the entire read output upfront, single_open is an even more convenient version of seq_file.

This example behaves like a file that contains:

ab
cd

Code:

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/printk.h> /* pr_info */
#include <linux/seq_file.h> /* seq_read, seq_lseek, single_release */
#include <uapi/linux/stat.h> /* S_IRUSR */

MODULE_LICENSE("GPL");

static struct dentry *debugfs_file;

static int show(struct seq_file *m, void *v)
{
    seq_printf(m, "ab\ncd\n");
    return 0;
}

static int open(struct inode *inode, struct  file *file)
{
    return single_open(file, show, NULL);
}

static const struct file_operations fops = {
    .llseek = seq_lseek,
    .open = open,
    .owner = THIS_MODULE,
    .read = seq_read,
    .release = single_release,
};

static int myinit(void)
{
    debugfs_file = debugfs_create_file(
        "lkmc_seq_file_single", S_IRUSR, NULL, NULL, &fops);
    if (debugfs_file) {
        return 0;
    } else {
        return -EINVAL;
    }
}

static void myexit(void)
{
    debugfs_remove(debugfs_file);
}

module_init(myinit)
module_exit(myexit)

GitHub upstream.

Tested on Linux 4.9.6.

It appears that starting from Linux 5, there was a backwards incompatible change that requires you to implement seq_file a bit differently, I think this talks about it: seq_file not working properly after next returns NULL and it appears that if you don't update this you get a warning:

seq_file: buggy .next function next [module-name] did not update position index

Upvotes: 5

Related Questions