diff --git a/README.md b/README.md index 73b7f75..04c6438 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ The Linux kernel version can be found with: grep BR2_LINUX_KERNEL_VERSION buildroot/.config +We use `printk` a lot, and it shows on the QEMU terminal by default. If that annoys you (e.g. you want to see stdout separately), do: + + dmesg -n 1 + +See also: + 1. [Introduction](introduction.md) 1. [Build](build.md) 1. [kmod](kmod.md) diff --git a/kernel_module/fops.c b/kernel_module/fops.c index 704f23e..24ec783 100644 --- a/kernel_module/fops.c +++ b/kernel_module/fops.c @@ -1,5 +1,5 @@ /* -dmesg stuff when fops happen. +Basic fops example, with a fixed size static data buffer. Usage: @@ -27,54 +27,107 @@ MODULE_LICENSE("GPL"); static struct dentry *dir = 0; -int fop_open(struct inode *inode, struct file *file) +static char data[] = {'a', 'b', 'c', 'd'}; + +static int fop_open(struct inode *inode, struct file *file) { printk(KERN_INFO "open\n"); return 0; } -/**/ -ssize_t fop_read(struct file *file, char __user *buf, size_t len, loff_t *off) +/* @param[in,out] off: gives the initial position into the buffer. + * We must increment this by the ammount of bytes read. + * Then when userland reads the same file descriptor again, + * we start from that point instead. + * */ +static ssize_t fop_read(struct file *file, char __user *buf, size_t len, loff_t *off) { ssize_t ret; - char s[] = "abcd"; printk(KERN_INFO "read\n"); printk(KERN_INFO "len = %zu\n", len); printk(KERN_INFO "off = %lld\n", (long long)*off); - if (sizeof(s) <= *off) { + if (sizeof(data) <= *off) { ret = 0; } else { - ret = min(len, sizeof(s) - (size_t)*off); - if (copy_to_user(buf, s, ret)) { + ret = min(len, sizeof(data) - (size_t)*off); + if (copy_to_user(buf, data + *off, ret)) { ret = -EFAULT; } else { *off += ret; } } + printk(KERN_INFO "buf = %.*s\n", (int)len, buf); + printk(KERN_INFO "ret = %lld\n", (long long)ret); + return ret; +} + +/* Similar to read, but with one notable difference: + * we must return ENOSPC if the user tries to write more + * than the size of our buffer. Otherwise, Bash > just + * keeps trying to write to it infinitely. */ +static ssize_t fop_write(struct file *file, const char __user *buf, size_t len, loff_t *off) +{ + ssize_t ret; + printk(KERN_INFO "write\n"); + printk(KERN_INFO "buf = %.*s\n", (int)len, buf); + printk(KERN_INFO "len = %zu\n", len); + printk(KERN_INFO "off = %lld\n", (long long)*off); + if (sizeof(data) <= *off) { + ret = 0; + } else { + if (sizeof(data) - (size_t)*off < len) { + ret = -ENOSPC; + } else { + if (copy_from_user(data + *off, buf, len)) { + ret = -EFAULT; + } else { + ret = len; + *off += ret; + } + } + } printk(KERN_INFO "ret = %lld\n", (long long)ret); return ret; } -ssize_t fop_write(struct file *file, const char __user *buf, size_t len, loff_t *off) -{ - printk(KERN_INFO "write\n"); - printk(KERN_INFO "buf = %.*s\n", (int)len, buf); - printk(KERN_INFO "len = %zu\n", len); - printk(KERN_INFO "off = %lld\n", (long long)*off); - return len; -} - /* Called on the last close: http://stackoverflow.com/questions/11393674/why-is-the-close-function-is-called-release-in-struct-file-operations-in-the-l */ -int fop_release (struct inode *inode, struct file *file) +static int fop_release (struct inode *inode, struct file *file) { printk(KERN_INFO "release\n"); return 0; } -const struct file_operations fops = { +static loff_t fop_llseek(struct file *filp, loff_t off, int whence) +{ + loff_t newpos; + printk(KERN_INFO "llseek\n"); + printk(KERN_INFO "off = %lld\n", (long long)off); + printk(KERN_INFO "whence = %lld\n", (long long)whence); + + switch(whence) { + case SEEK_SET: + newpos = off; + break; + case SEEK_CUR: + newpos = filp->f_pos + off; + break; + case SEEK_END: + newpos = sizeof(data) + off; + break; + default: + return -EINVAL; + } + if (newpos < 0) return -EINVAL; + filp->f_pos = newpos; + printk(KERN_INFO "newpos = %lld\n", (long long)newpos); + return newpos; +} + +static const struct file_operations fops = { + .llseek = fop_llseek, .open = fop_open, .read = fop_read, .release = fop_release, diff --git a/rootfs_overlay/etc/inittab b/rootfs_overlay/etc/inittab index b377665..58e8a70 100644 --- a/rootfs_overlay/etc/inittab +++ b/rootfs_overlay/etc/inittab @@ -8,6 +8,7 @@ ::sysinit:/etc/init.d/rcS # https://unix.stackexchange.com/questions/299408/how-to-login-automatically-without-typing-root-in-buildroot-x86-64-qemu console::respawn:/bin/sh +#console::respawn:/sbin/getty -n -L console 0 vt100 ::ctrlaltdel:/sbin/reboot ::shutdown:/etc/init.d/rcK ::shutdown:/sbin/swapoff -a diff --git a/rootfs_overlay/fops.sh b/rootfs_overlay/fops.sh index 552f93f..f64c3e5 100755 --- a/rootfs_overlay/fops.sh +++ b/rootfs_overlay/fops.sh @@ -1,10 +1,12 @@ #!/bin/sh -set -ex + +set -x insmod /fops.ko mkdir -p /fops mount -t debugfs none /fops cd /fops/kernel_module_cheat +## Basic read. cat fops # => abcd # dmesg => open @@ -12,14 +14,32 @@ cat fops # dmesg => len = [0-9]+ # dmesg => close -printf a >fops +## Basic write + +printf '01' >fops # dmesg => open # dmesg => write # dmesg => len = 1 # dmesg => buf = a # dmesg => close -cd / -umount /fops -rmdir /fops -rmmod fops +cat fops +# => 01cd +# dmesg => open +# dmesg => read +# dmesg => len = [0-9]+ +# dmesg => close + +## ENOSPC +printf '1234' >fops +printf '12345' >fops +echo "$?" +# => 8 +cat fops +# => 1234 + +## seek +printf '1234' >fops +printf 'z' | dd bs=1 of=fops seek=2 +cat fops +# => 12z4