Writing a file in /proc continuouly floods the dmesg - c

I am trying to read/Write a variable in the linux kernel module using /proc file entry facility.
Kernel module compiles successfully but when tried writing it via
echo 1 > My_file
This operation didn't finish.
Also, dmesg console is continuously flooded with some random value.
[ 1171.481231] proc_write_flag New_Flag 1124646486
[ 1171.481245] proc_write_flag New_Flag 1124646486
[ 1171.481259] proc_write_flag New_Flag 1124646486
[ 1171.481271] proc_write_flag New_Flag 1124646486
[ 1171.481473] ^C
I am new to linux device drivers and trying to use /proc facility provided by the linux kernel. I tried removing the this kernel module, but again, the operation didn't finish.
what is causing this behaviour and how can i rectify it?
Here is the code:
int my_flag;
static struct proc_dir_entry *pdir = NULL;
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GPL");
static ssize_t proc_read_flag(struct file* page,char __user * data, size_t count, loff_t *offset);
static ssize_t proc_write_flag(struct file *file, const char __user* ubuf, size_t count, loff_t* offset);
static struct file_operations myops =
{
.owner = THIS_MODULE,
.read = proc_read_flag,
.write= proc_write_flag,
};
//ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
//Updated Read function after the reply.
static ssize_t proc_read_flag(struct file* page,char __user * data, size_t count,loff_t *offset)
{
int ret;
if( count >my_flag) //my_flag holds the count of chars received by write function.
count = my_flag;
ret = copy_to_user(data, my_buf, my_flag );
printk("%s: ret = %d ,my_flag %d\n",__FUNCTION__, ret, my_flag);
return ( my_flag - ret );
}
//ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
//Updated Write function After the reply.
static ssize_t proc_write_flag(struct file *file, const char __user* ubuf, size_t count, loff_t* offset)
{
if( copy_from_user(my_buf,ubuf,count) ){ //Returns No. of bytes could not copy
return -EFAULT;
}
my_flag = count;
printk("%s New_Flag %d Data: %s\n",__FUNCTION__,my_flag,my_buf);
return count;
}
int init_module(void)
{
struct proc_dir_entry *pfile = NULL;
pdir = proc_mkdir("My_dir",NULL);
if(!pdir){
return -ENOMEM;
}
pfile = proc_create("My_file", 0666, pdir, &myops);
if(!pfile)
return -ENOMEM;
printk("Proc_entry Created Successfully, Module initialized\n");
return 0;
}
void cleanup_function(void)
{
remove_proc_entry("My_file", pdir);
remove_proc_entry("My_dir", NULL);
printk("Removing Proc_entry!!!");
}

The write function should return the number of bytes you processed.
In your case, 'proc_write_flag' function is returning 'ret' which is 0.
which means it will be invoked repeatedly until you process 'count' number of bytes from 'ubuf'
Similarly, 'proc_read_flag' should return the number of bytes you wrote into 'data'. In your case it is returning 0 (len)
There are other problems in 'proc_write_flag' function.
'buf' array isn't initialize and casting 'buf'(address) to int will not give you the expected answer.
Start with this http://tuxthink.blogspot.com/2013/12/creating-directory-under-proc-in-kernel.html
Then look into kstrtol() function.

Related

Linux kernel module making a proc entry does not print on reading the proc entry to user

In the book Operating System Concepts: 10th edition, there's a programming project to log some information about a process by providing its pid echo'd to the /proc entry made while initializing the module, namely here, /proc/pid.
/**
* This function is called each time the /proc/pid is read.
*
* This function is called repeatedly until it returns 0, so
* there must be logic that ensures it ultimately returns 0
* once it has collected the data that is to go into the
* corresponding /proc file.
*/
static ssize_t proc_read(struct file *file, char __user *usr_buf, size_t count, loff_t *pos)
{
int rv = 0;
char buffer[BUFFER_SIZE];
static int completed = 0;
struct task_struct *tsk = NULL;
if (completed) {
completed = 0;
return 0;
}
/* l_pid is a global that gets set properly when the proc entry is read. */
tsk = pid_task(find_vpid(l_pid), PIDTYPE_PID);
if (tsk == NULL) {
rv = -1;
return rv;
}
completed = 1;
rv = sprintf(buffer, "pid = [%d], command = [%s], state = [%ld]\n",
tsk->pid, tsk->comm, tsk->state);
if (copy_to_user(usr_buf, buffer, rv)) {
rv = -1;
}
return rv;
}
My problem is with the proc_read function. The way I understand it (as explained in the book) is that rv has to be 0 eventually because the function specified as the .read field in the file_operations struct keeps getting called till returning 0.
The problem is that I do not understand the necessity of having all this "complete" logic. If I change the code to be like this:
static ssize_t proc_read(struct file *file, char __user *usr_buf, size_t count, loff_t *pos)
{
int rv = 0;
char buffer[BUFFER_SIZE];
struct task_struct *tsk = NULL;
/* l_pid is a global that gets set properly when the proc entry is read. */
tsk = pid_task(find_vpid(l_pid), PIDTYPE_PID);
if (tsk == NULL) {
rv = -1;
return rv;
}
int num_bytes = sprintf(buffer, "pid = [%d], command = [%s], state = [%ld]\n",
tsk->pid, tsk->comm, tsk->state);
if (copy_to_user(usr_buf, buffer, num_bytes)) {
rv = -1;
}
return rv;
}
To my understanding, when the proc entry is read, this function will get called, either returning -1 (if tsk == NULL or could not copy from buffer to usr_buf) and thus get called again. Or it will return 0 on success.
The problem with this change (of course) is that it does not work. It does not print anything to the console when issuing cat /proc/pid. However, by printking the string inside buffer, it appears to be correct (at least in the kernel log buffer).

Reading specific registers with seq_file : Device Driver

I have a very simple modified driver to write to specific registers. I pass the value and the register I intend to write to. IE: 0x00000008 2, read as write 8 to register (base_addr + 2*4).
This works and was relatively straight forward to implement because the driver automatically receives an input buffer.
Now since in order to read I have to use the seq_file I'm not sure how to pass the register that I would like to read from... The augmented device driver I'm using is posted below.
Is there a way to pass the register value and then using ioread32(base_addr + register)? How can I use copy_from_user in this function where my only inputs are the seq_file?
Usage Example (altered ioread32 to be base_addr+2):
$ sudo echo "0x00001233 2" > /proc/accelerator
$ cat /proc/accelerator
0x1233
$
Dev Driver Code:
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h> /* Needed for copy_from_user */
#include <asm/io.h> /* Needed for IO Read/Write Functions */
#include <linux/proc_fs.h> /* Needed for Proc File System Functions */
#include <linux/seq_file.h> /* Needed for Sequence File Operations */
#include <linux/platform_device.h> /* Needed for Platform Driver Functions */
#include <linux/slab.h> /*for kmalloc and kfree */
#include <linux/vmalloc.h>
/* Define Driver Name */
#define DRIVER_NAME "accelerator"
unsigned long *base_addr; /* Vitual Base Address */
struct resource *res; /* Device Resource Structure */
unsigned long remap_size; /* Device Memory Size */
/* Write operation for /proc/accelerator
* -----------------------------------
* When user cat a string to /proc/accelerator file, the string will be stored in
* const char __user *buf. This function will copy the string from user
* space into kernel space, and change it to an unsigned long value.
* It will then write the value to the register of accelerator controller,
* and turn on the corresponding LEDs eventually.
*/
static ssize_t proc_accelerator_write(struct file *file, const char __user * buf,
size_t count, loff_t * ppos)
{
//Allocate
char * myaddr_phrase;
char * pEnd;
char *buffer = vzalloc(count);
myaddr_phrase = buffer;
u32 myaddr_value;
u32 myreg_value;
//Copy Data
if (count < 22) {
if (copy_from_user(myaddr_phrase, buf, count))
return -EFAULT;
//myaddr_phrase[count] = '\0';
printk("count = %d\n", count);
printk("%s\n",myaddr_phrase);
}
// Use strtol to parse input
/* http://www.cplusplus.com/reference/cstdlib/strtol/ */
myaddr_value = simple_strtoul(myaddr_phrase, &pEnd, 0);
printk("myaddr_value = %08x\n", myaddr_value);
pEnd = strsep(&myaddr_phrase," ");
myreg_value = simple_strtoul(myaddr_phrase,&pEnd ,0);
printk("myreg_value = %08x\n", myreg_value);
printk("final_value = %08x\n", (base_addr + (myreg_value)));
printk("mult_val = %08x\n", (myreg_value));
wmb();
iowrite32(myaddr_value, (base_addr + (myreg_value)));
return count;
}
/* Callback function when opening file /proc/accelerator
* ------------------------------------------------------
* Read the register value of accelerator controller, print the value to
* the sequence file struct seq_file *p. In file open operation for /proc/accelerator
* this callback function will be called first to fill up the seq_file,
* and seq_read function will print whatever in seq_file to the terminal.
*/
static int proc_accelerator_show(struct seq_file *p, void *v)
{
u32 accelerator_value;
accelerator_value = ioread32(base_addr+2);
seq_printf(p, "0x%x\n", accelerator_value);
return 0;
}
/* Open function for /proc/accelerator
* ------------------------------------
* When user want to read /proc/accelerator (i.e. cat /proc/accelerator), the open function
* will be called first. In the open function, a seq_file will be prepared and the
* status of accelerator will be filled into the seq_file by proc_accelerator_show function.
*
*p 69
int (*open) (struct inode *, struct file *);
Though this is always the first operation performed on the device file, the driver
is not required to declare a corresponding method. If this entry is NULL, opening
the device always succeeds, but your driver isn’t notified.
Open described on p76
*/
static int proc_accelerator_open(struct inode *inode, struct file *file)
{
unsigned int size = 16;
char *buf;
struct seq_file *m;
int res;
buf = (char *)kmalloc(size * sizeof(char), GFP_KERNEL);
if (!buf)
return -ENOMEM;
res = single_open(file, proc_accelerator_show, NULL);
if (!res) {
m = file->private_data;
m->buf = buf;
m->size = size;
} else {
kfree(buf);
}
return res;
}
/* File Operations for /proc/accelerator */
static const struct file_operations proc_accelerator_operations = {
.open = proc_accelerator_open,
.read = seq_read,
.write = proc_accelerator_write,
.llseek = seq_lseek,
.release = single_release
};
/*
int (*open) (struct inode *, struct file *);
Though this is always the first operation performed on the device file, the driver
is not required to declare a corresponding method. If this entry is NULL, opening
the device always succeeds, but your driver isn’t notified.
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
Used to retrieve data from the device. A null pointer in this position causes the
readsystem call to fail with-EINVAL(“Invalid argument”). A nonnegative return
value represents the number of bytes successfully read (the return value is a
“signed size” type, usually the native integer type for the target platform)
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
Sends data to the device. IfNULL, -EINVALis returned to the program calling the
writesystem call. The return value, if nonnegative, represents the number of
bytes successfully written.
loff_t (*llseek) (struct file *, loff_t, int);
Thellseek method is used to change the current read/write position in a file, and
the new position is returned as a (positive) return value. Theloff_tparameter is
a “long offset” and is at least 64 bits wide even on 32-bit platforms. Errors are
signaled by a negative return value. If this function pointer isNULL, seek calls will
modify the position counter in thefilestructure (described in the section “The
file Structure”) in potentially unpredictable ways.
int (*release) (struct inode *, struct file *);
This operation is invoked when thefilestructure is being released. Likeopen,
releasecan beNULL.
*
*/
/* Shutdown function for accelerator
* -----------------------------------
* Before accelerator shutdown, turn-off all the leds
*/
static void accelerator_shutdown(struct platform_device *pdev)
{
iowrite32(0, base_addr);
}
/* Remove function for accelerator
* ----------------------------------
* When accelerator module is removed, turn off all the leds first,
* release virtual address and the memory region requested.
*/
static int accelerator_remove(struct platform_device *pdev)
{
accelerator_shutdown(pdev);
/* Remove /proc/accelerator entry */
remove_proc_entry(DRIVER_NAME, NULL);
/* Release mapped virtual address */
iounmap(base_addr);
/* Release the region */
release_mem_region(res->start, remap_size);
return 0;
}
/* Device Probe function for accelerator
* ------------------------------------
* Get the resource structure from the information in device tree.
* request the memory region needed for the controller, and map it into
* kernel virtual memory space. Create an entry under /proc file system
* and register file operations for that entry.
*/
static int accelerator_probe(struct platform_device *pdev)
{
struct proc_dir_entry *accelerator_proc_entry;
int ret = 0;
printk(KERN_ALERT "Probing\n");
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "No memory resource\n");
return -ENODEV;
}
remap_size = res->end - res->start + 1;
if (!request_mem_region(res->start, remap_size, pdev->name)) {
dev_err(&pdev->dev, "Cannot request IO\n");
return -ENXIO;
}
base_addr = ioremap(res->start, remap_size);
if (base_addr == NULL) {
dev_err(&pdev->dev, "Couldn't ioremap memory at 0x%08lx\n",
(unsigned long)res->start);
ret = -ENOMEM;
goto err_release_region;
}
accelerator_proc_entry = proc_create(DRIVER_NAME, 0, NULL,
&proc_accelerator_operations);
if (accelerator_proc_entry == NULL) {
dev_err(&pdev->dev, "Couldn't create proc entry\n");
ret = -ENOMEM;
goto err_create_proc_entry;
}
printk(KERN_INFO DRIVER_NAME " probed at VA 0x%08lx\n",
(unsigned long) base_addr);
printk(KERN_ALERT "Goodbye, probe\n");
return 0;
err_create_proc_entry:
iounmap(base_addr);
err_release_region:
release_mem_region(res->start, remap_size);
return ret;
}
/* device match table to match with device node in device tree */
/*
https://lwn.net/Articles/448502/
*/
static const struct of_device_id accelerator_of_match[] = {
{.compatible = "PCA,bitSplitter"},
{},
};
MODULE_DEVICE_TABLE(of, accelerator_of_match);
/* platform driver structure for accelerator driver */
/*
Platform devices are represented by the struct, and is found in <linux/platform_device.h>
at minimum probe() and remove() must be supplied, the others have to do with power management
https://lwn.net/Articles/448499/
*/
static struct platform_driver accelerator_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = accelerator_of_match},
.probe = accelerator_probe,
.remove = accelerator_remove,
.shutdown = accelerator_shutdown
};
/* Register accelerator platform driver */
/*
Helper macro for drivers that don't do
* anything special in module init/exit. This eliminates a lot of
* boilerplate. Each module may only use this macro once, and
* calling it replaces module_init() and module_exit()
*
* Platform drivers are for HW that will not dynamically come and go into a Linux system,
* such as the video and audio controllers in a tablet. In makes sense to statically pull
* in those code necessary through the __initcall magic discussed above.
*
* http://henryomd.blogspot.com/2014/11/linux-kernel-startup.html
*/
module_platform_driver(accelerator_driver);
/* Module Informations */
/*
Discussed in 2.6 Preliminaries
*/
MODULE_AUTHOR("Digilent, Inc.");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION(DRIVER_NAME ": accelerator driver (Simple Version)");
MODULE_ALIAS(DRIVER_NAME);

Print /proc/slabinfo by creating a kernel routine

+
trying to learn/tinker with the Linux kernel by following some PDFs and online resources.
I wanted to print cache allocations using kmalloc_caches[], print somewhat similar info present by cating /proc/slabinfo
So far, I have come to understand I have implement s_show (on line 5421 in /mm/slub.c) on kernel version 3.3
I understand most of the function and any function calls made inside it. But what puzzles me are the arguments passed to it. Cause, when I search where s_show() is called, this is what I find:
static const struct seq_operations slabinfo_op = {
.start = s_start,
.next = s_next,
.stop = s_stop,
.show = s_show,
};
Now, I understand somewhat of what is going on here, but I still don't get where the arguments are coming from.
s_show() function :
static int s_show(struct seq_file *m, void *p)
{
unsigned long nr_partials = 0;
unsigned long nr_slabs = 0;
unsigned long nr_inuse = 0;
unsigned long nr_objs = 0;
unsigned long nr_free = 0;
struct kmem_cache *s;
int node;
s = list_entry(p, struct kmem_cache, list);
for_each_online_node(node) {
struct kmem_cache_node *n = get_node(s, node);
if (!n)
continue;
nr_partials += n->nr_partial;
nr_slabs += atomic_long_read(&n->nr_slabs);
nr_objs += atomic_long_read(&n->total_objects);
nr_free += count_partial(n, count_free);
}
nr_inuse = nr_objs - nr_free;
seq_printf(m, "%-17s %6lu %6lu %6u %4u %4d", s->name, nr_inuse,
nr_objs, s->size, oo_objects(s->oo),
(1 << oo_order(s->oo)));
seq_printf(m, " : tunables %4u %4u %4u", 0, 0, 0);
seq_printf(m, " : slabdata %6lu %6lu %6lu", nr_slabs, nr_slabs,
0UL);
seq_putc(m, '\n');
return 0;
}
The seq_file argument is really the file into which you're outputting the data you want to print. It is automatically constructed.
However, the interesting thing is the p argument. To understand where it comes from, see this code:
static void *s_start(struct seq_file *m, loff_t *pos)
{
loff_t n = *pos;
mutex_lock(&cache_chain_mutex);
if (!n)
print_slabinfo_header(m);
return seq_list_start(&cache_chain, *pos);
}
static void *s_next(struct seq_file *m, void *p, loff_t *pos)
{
return seq_list_next(p, &cache_chain, pos);
}
static void s_stop(struct seq_file *m, void *p)
{
mutex_unlock(&cache_chain_mutex);
}
The s_start and s_next functions return what is given as the p argument. You may want to read the code of seq_list_start and seq_list_next.

System Call write

The system call write it's defined as follow:
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count)
{
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
ret = vfs_write(file, buf, count, &pos);
file_pos_write(file, pos);
fput_light(file, fput_needed);
}
return ret;
}
I'd like to copy the variable buf to modify your content and
then use this new variable at:
vfs_write(file, new_buf, count, &pos);
I've tried to allocate memory to a char pointer variable with kmalloc and then I've used copy_from_user() to do the copy. Finally I've used the new variable at vfs_write(). After recompile the kernel and reboot the system I've got kernel panic error message.
Here is my implementation that generates a kernel panic error message:
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count){
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
char *data;
data = kmalloc(count, GFP_KERNEL);
if(!data)
return ret;
copy_from_user(data, buf, count);
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
ret = vfs_write(file, data, count, &pos);
file_pos_write(file, pos);
fput_light(file, fput_needed);
}
return ret;
}
How can I do this copy in kernel mode?
I'm using Linux Mint 12 - Kernel version: 3.0.30
You should probably also post your code. I.e. the changes you made to the write system call to be certain where the error is.
That said, there are checks in place that don't allow you to use kernel memory for system calls. You either need to allocate your buffer in user address space for the process (bad) or disable the checks (not as bad).
I'm not as familiar with the 3.0 kernel but this answer looks promising:
mm_segment_t old_fs;
old_fs = get_fs();
set_fs(KERNEL_DS);
/* Your syscall here */
set_fs(old_fs);

Code for writing and reading on a device file from a kernel module?

I have tried the following code many times .
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<asm/uaccess.h>
#include<linux/semaphore.h>
MODULE_LICENSE("DUAL BSD/GPL");
static int dev_open(struct inode *,struct file *);
static int dev_release(struct inode *,struct file *);
ssize_t dev_read(struct file *,char *, size_t ,loff_t *);
ssize_t dev_write(struct file *,const char *,size_t ,loff_t *);
static int major;
int dev_major = 0;
int dev_minor = 0;
struct cdev *cdev;
struct device {
char array[100];
struct semaphore sem;
}chr_arr;
struct file_operations dev_ops = {
.owner = THIS_MODULE,
.read = dev_read,
.write = dev_write,
.open = dev_open,
.release = dev_release
};
ssize_t dev_read(struct file *filp,char *buf,size_t count,loff_t *offset)
{
int i;
i=copy_to_user(buf,chr_arr.array,count);
printk(KERN_ALERT"buff:%s",buf);
return i;
}
ssize_t dev_write(struct file *filp,const char *buf,size_t count,loff_t *offset)
{
//printk(KERN_ALERT"\nsorry,byebye");
int j;
//msg_ptr = kmalloc(count,GFP_KERNEL);
//for(j=0;j<count;j++)
if(count>100)
return -1;
j = copy_from_user(chr_arr.array,buf,count);
//printk(KERN_ALERT"msg_ptr:%s",msg_ptr);
return j;
}
static int dev_open(struct inode *inode,struct file *filp)
{
filp->private_data = inode->i_cdev;
if(down_interruptible(&chr_arr.sem))
{
printk(KERN_INFO " could not hold semaphore");
return -1;
}
//printk(KERN_ALERT"ah ha the device is open !now we can go further");
return 0;
}
static int dev_release(struct inode *inode,struct file *filp)
{
up(&chr_arr.sem);
//module_put(THIS_MODULE);
return 0;
}
static int init_device(void)
{
int result;
dev_t dev_no,dev;
result = alloc_chrdev_region(&dev_no,0,1,"chr_dev");
if(result < 0)
{
printk("sorry no major number left");
return result;
}
major = MAJOR(dev_no);
dev = MKDEV(major,0);
cdev = cdev_alloc();
cdev->ops = &dev_ops;
sema_init(&chr_arr.sem,1);
printk("the major number allocated is %d\n",major);
result = cdev_add(cdev,dev,1);
if(result < 0 )
{
printk(KERN_INFO "Unable to allocate cdev");
return result;
}
return 0;
}
static void clean_device(void)
{
cdev_del(cdev);
unregister_chrdev_region(major,1);
}
module_init(init_device);
module_exit(clean_device);
but its giving me the following warning.
CC [M] /home/karan/practice/scrw/scrw1.o
In file included from /usr/src/linux-2.6.34.10-0.6/arch/x86/include/asm/uaccess.h:571:0,
from /home/karan/practice/scrw/scrw1.c:4:
In function ‘copy_from_user’,inlined from ‘write’ at /home/karan/practice/scrw/scrw1.c:43:6:
/usr/src/linux-2.6.34.10-0.6/arch/x86/include/asm/uaccess_32.h:212:26: warning: call to ‘copy_from_user_overflow’ declared with attribute warning: copy_from_user() buffer size is not provably correct
Building modules, stage 2.
MODPOST 1 modules
CC /home/karan/practice/scrw/scrw1.mod.o
LD [M] /home/karan/practice/scrw/scrw1.ko
and then when I try to write echo hi > /dev/my_dev the screen freezes after 30 secs or so.
The problem is that you should return the number of bytes read/written in your read/write methods, the return value of copy_{from,to}_user() is 0 if everything goes well. Return e.g. count in your write method if copying succeeds:
unsigned long ret;
printk(KERN_INFO "Inside write \n");
if (count > sizeof(char_arr.array) - 1)
return -EINVAL;
ret = copy_from_user(char_arr.array, buff, count);
if (ret)
return -EFAULT;
char_arr.array[count] = '\0';
return count;
You should also make sure that a terminating '\0' character is appended when you copy to your buffer (if you'd like to deal only with strings). If it is binary data you deal with store its length in your struct as well.
An example read method:
ssize_t dev_read(struct file *filp,char *buf,size_t count,loff_t *offset)
{
int len = count >= strlen(chr_arr.array) ? strlen(chr_arr.array) : count;
if (*offset >= strlen(chr_arr.array))
return 0;
if (copy_to_user(buf,chr_arr.array,len))
return -EFAULT;
return len;
}
Edit: messed up example code, fixing it.
Edit2: example read method.

Resources