Saner seq_file example adapted from kernel doc

This commit is contained in:
Ciro Santilli
2017-07-14 10:31:18 +01:00
parent 8058ee9f2b
commit f82c058eb6
4 changed files with 104 additions and 35 deletions

View File

@@ -1,12 +1,27 @@
/* /*
Adapted from: http://allskyee.blogspot.co.uk/2011/12/proc-seqfile-write.html Adapted from: Documentation/filesystems/seq_file.txt
but we limit the count to the max module parameter.
Writting trivial read fops is repetitive and error prone. Writting trivial read fops is repetitive and error prone.
The seq_file API makes the process much easier for those trivial cases. The seq_file API makes the process much easier for those trivial cases.
There is not write version however, as writes are more complex: 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.
There is not write version, as writes are more complex:
https://stackoverflow.com/questions/30710517/how-to-implement-a-writable-proc-file-by-using-seq-file-in-a-driver-module https://stackoverflow.com/questions/30710517/how-to-implement-a-writable-proc-file-by-using-seq-file-in-a-driver-module
Bibliography:
- Documentation/filesystems/seq_file.txt
- https://stackoverflow.com/questions/25399112/how-to-use-a-seq-file-in-linux-modules
*/ */
#include <asm/uaccess.h> /* copy_from_user, copy_to_user */ #include <asm/uaccess.h> /* copy_from_user, copy_to_user */
@@ -16,43 +31,70 @@ https://stackoverflow.com/questions/30710517/how-to-implement-a-writable-proc-fi
#include <linux/module.h> #include <linux/module.h>
#include <linux/printk.h> /* pr_info */ #include <linux/printk.h> /* pr_info */
#include <linux/seq_file.h> /* seq_read, seq_lseek, single_release */ #include <linux/seq_file.h> /* seq_read, seq_lseek, single_release */
#include <linux/slab.h>
#include <uapi/linux/stat.h> /* S_IRUSR */ #include <uapi/linux/stat.h> /* S_IRUSR */
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
static int max = 2;
module_param(max, int, S_IRUSR | S_IWUSR);
static struct dentry *debugfs_file; static struct dentry *debugfs_file;
static void * next(struct seq_file *s, void *v, loff_t *pos) /* 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)
{ {
pr_info("next\n"); loff_t *spos;
(*(unsigned long *)v)++;
(*pos)++;
return *pos < 3 ? v : NULL;
}
static void * start(struct seq_file *s, loff_t *pos)
{
static unsigned long counter = 0;
pr_info("start pos = %llx\n", (unsigned long long)*pos); pr_info("start pos = %llx\n", (unsigned long long)*pos);
if (*pos == 0) { spos = kmalloc(sizeof(loff_t), GFP_KERNEL);
return &counter; if (!spos || *pos >= max)
} else { return NULL;
*pos = 0; *spos = *pos;
return NULL; return spos;
}
} }
static int show(struct seq_file *s, void *v) /* 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)
{ {
pr_info("show\n"); loff_t *spos;
seq_printf(s, "%lx\n", *(unsigned long *)v);
return 0; 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) static void stop(struct seq_file *s, void *v)
{ {
pr_info("stop\n"); 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 = { static struct seq_operations my_seq_ops = {
@@ -64,6 +106,7 @@ static struct seq_operations my_seq_ops = {
static int open(struct inode *inode, struct file *file) static int open(struct inode *inode, struct file *file)
{ {
pr_info("open\n");
return seq_open(file, &my_seq_ops); return seq_open(file, &my_seq_ops);
} }
@@ -77,8 +120,13 @@ static struct file_operations fops = {
static int myinit(void) static int myinit(void)
{ {
debugfs_file = debugfs_create_file("lkmc_seq_file", S_IRUSR | S_IWUSR, NULL, NULL, &fops); debugfs_file = debugfs_create_file(
return 0; "lkmc_seq_file", S_IRUSR | S_IWUSR, NULL, NULL, &fops);
if (debugfs_file) {
return 0;
} else {
return -EINVAL;
}
} }
static void myexit(void) static void myexit(void)

View File

@@ -1,5 +1,11 @@
/* /*
For single reads, single_open is an even more convenient version of seq_file. 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
*/ */
#include <asm/uaccess.h> /* copy_from_user, copy_to_user */ #include <asm/uaccess.h> /* copy_from_user, copy_to_user */
@@ -17,7 +23,7 @@ static struct dentry *debugfs_file;
static int show(struct seq_file *m, void *v) static int show(struct seq_file *m, void *v)
{ {
seq_printf(m, "abcd\n"); seq_printf(m, "ab\ncd\n");
return 0; return 0;
} }
@@ -36,8 +42,13 @@ static const struct file_operations fops = {
static int myinit(void) static int myinit(void)
{ {
debugfs_file = debugfs_create_file("lkmc_seq_file", S_IRUSR | S_IWUSR, NULL, NULL, &fops); debugfs_file = debugfs_create_file(
return 0; "lkmc_seq_file_single", S_IRUSR | S_IWUSR, NULL, NULL, &fops);
if (debugfs_file) {
return 0;
} else {
return -EINVAL;
}
} }
static void myexit(void) static void myexit(void)

View File

@@ -11,9 +11,17 @@ cat 'lkmc_seq_file'
# => 2 # => 2
cat 'lkmc_seq_file' cat 'lkmc_seq_file'
# => 3 # => 0
# => 4 # => 1
# => 5 # => 2
# TODO understand, why does this print nothing? dd if='lkmc_seq_file' bs=1 count=2 skip=0 status=none
dd if='lkmc_seq_file' bs=1 count=2 skip=2 # => 0
dd if='lkmc_seq_file' bs=1 count=4 skip=0 status=none
# => 0
# => 1
dd if='lkmc_seq_file' bs=1 count=2 skip=2 status=none
# => 1
dd if='lkmc_seq_file' bs=4 count=1 skip=0 status=none
# => 0
# => 1

View File

@@ -3,6 +3,8 @@ set -ex
insmod /seq_file_single.ko insmod /seq_file_single.ko
cd /sys/kernel/debug cd /sys/kernel/debug
cat 'lkmc_seq_file_single' cat 'lkmc_seq_file_single'
# => abcd # => ab
dd if='lkmc_seq_file_single' bs=1 skip=2
# => cd # => cd
dd if='lkmc_seq_file_single' bs=1 count=3 skip=1
# => b
# => c