Skip to content

Instantly share code, notes, and snippets.

@ktemkin
Last active April 17, 2024 04:16
Show Gist options
  • Save ktemkin/3b54128622dec1172c50 to your computer and use it in GitHub Desktop.
Save ktemkin/3b54128622dec1172c50 to your computer and use it in GitHub Desktop.
As a snark: fizzbuzz as a kernel module. (for extra snark credit: mknod /dev/fizzbuzz1 c <major> 0)
/**
* This is all your fault, Baljem.
*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <asm/uaccess.h>
static int __init fizzbuzz_init_module(void);
static void fizzbuzz_exit_module(void);
static int set_up_character_device(void);
//Event handlers.
static ssize_t fizzbuzz_read(struct file *, char __user *, size_t, loff_t *);
module_init(fizzbuzz_init_module);
module_exit(fizzbuzz_exit_module);
//Information about the test module.
MODULE_AUTHOR("Someone Else (TM)");
MODULE_LICENSE("Dual BSD/GPL");
static const char * fizzbuzz_name = "FizzBuzz";
static const char * fizzbuzz_device_name = "fizzbuzz";
static const size_t buzz_offset = 4;
//Information about the active module.
static dev_t fizzbuzz_device_id;
static struct class * fizzbuzz_device_class;
static struct cdev core_device;
static struct device * fizzbuzz_device;
//Information about the current index in the fizzbuzz sequence.
static int fizzbuzz_index = 1;
DEFINE_MUTEX(fizzbuzz_index_mutex);
/**
* Define the file_operations supported by the test module.
*/
struct file_operations fizzbuzz_file_operations = {
.owner = THIS_MODULE,
.read = fizzbuzz_read,
};
/**
* Attempts to set up a device class for fizzbuzz devices.
*/
static int set_up_device_class(void) {
fizzbuzz_device_class = class_create(THIS_MODULE, fizzbuzz_device_name);
return IS_ERR(fizzbuzz_device_class);
}
/**
* Attempts to register a given device, resulting in a /dev/ file.
*/
static int set_up_dev_entry(void) {
fizzbuzz_device = device_create(fizzbuzz_device_class, NULL, fizzbuzz_device_id, NULL, "fizzbuzz0");
return IS_ERR(fizzbuzz_device);
}
/**
* Initialzation code for the test module.
*/
static int __init fizzbuzz_init_module(void) {
int last_error;
//Allocate a new major device number for our fizzbuzz module.
if((last_error = alloc_chrdev_region(&fizzbuzz_device_id, 0, 1, fizzbuzz_device_name))) {
printk(KERN_NOTICE "Couldn't allocate a character device number for fizzbuzz.\n");
return last_error;
}
if((last_error = set_up_device_class())) {
printk(KERN_NOTICE "Couldn't allocate a character class fizzbuzz.\n");
fizzbuzz_exit_module();
return last_error;
}
//Try to set up the character device for this module.
if((last_error = set_up_character_device())) {
printk(KERN_NOTICE "We couldn't allocate a character device for fizzbuzz. Are you out of memory?\n");
fizzbuzz_exit_module();
return last_error;
}
if((last_error = set_up_dev_entry())) {
printk(KERN_NOTICE "We couldn't allocate adevice target fizzbuzz. Are you out of memory?\n");
fizzbuzz_exit_module();
return last_error;
}
return 0;
}
/**
* Creates the main character device for the test module.
*
* returns: Zero on success, or a kernel error code on failure.
*/
static int set_up_character_device(void) {
//Initialize the space for this character device.
cdev_init(&core_device, &fizzbuzz_file_operations);
core_device.owner = THIS_MODULE;
core_device.ops = &fizzbuzz_file_operations;
//... and create the device itself.
return cdev_add(&core_device, fizzbuzz_device_id, 1);
}
/**
* File operations.
*/
/**
* Return the number of bytes that would be required for the given module.
*/
static uint8_t character_length_for(uint8_t index) {
if(index % 15 == 0) {
return 8; // Fizzbuzz\n
} else if((index % 3 == 0) || (index % 5 == 0)) {
return 4; // Fizz\n or Buzz\n
} else if(index == 100) {
return 3; // 100
} else if(index > 10) {
return 2; // 10
} else {
return 1; //1
}
}
/**
* Return the number of bytes required to store the given term, with newline.
*/
static inline uint8_t bytes_required_for(uint8_t index) {
return character_length_for(index) + 1;
}
/**
* Copy the next fizzbuzz term into the user buffer.
*
* CAUTION: Assumes there's sufficient space in the buffer. Be sure to check this first!
*/
static ssize_t __add_fizzbuzz_term(char __user * buffer) {
//Determine the total length of the data to be written.
size_t data_length = character_length_for(fizzbuzz_index);
//Create a simple buffer on the stack for our numeric writes.
char number[4];
//If this is a Fizz, or a Fizzbuzz, copy from the start of the string.
if((fizzbuzz_index % 15 == 0) || (fizzbuzz_index % 3 == 0)) {
copy_to_user(buffer, fizzbuzz_name, data_length);
}
//Otherwise, if we have a Buzz, offset the string by Buzz.
else if(fizzbuzz_index % 5 == 0) {
copy_to_user(buffer, fizzbuzz_name + buzz_offset, data_length);
}
//Otherwise, copy in the number itself.
else {
//Dump the number into our kernel-space stack buffer, formatted.
snprintf(number, sizeof(number), "%d", fizzbuzz_index);
//... and then copy it to the userspace.
copy_to_user(buffer, number, data_length);
}
//Return the actual data length written.
return data_length;
}
/**
* Appends a newline character to the given buffer,
* and returns the length of the characters added.
*/
static ssize_t __add_newline(char __user * buffer) {
copy_to_user(buffer, "\n", 1);
return 1;
}
/**
* Handle the read() syscall when delivered to our fizzbuzz device.
*/
static ssize_t fizzbuzz_read(struct file * file_pointer, char __user * buffer, size_t length, loff_t * offset_pointer) {
ssize_t length_written = 0;
mutex_lock(&fizzbuzz_index_mutex);
//If we've just completed a fizzbuzz read, we need to inform the reader that the file has ended.
//Return a zero-length read, indicating end-of-file.
if(fizzbuzz_index > 100) {
//Reset from the beginning of the sequence.
fizzbuzz_index = 1;
mutex_unlock(&fizzbuzz_index_mutex);
return 0;
}
//While we've yet to complete the fizzbuzz sequence
while(fizzbuzz_index <= 100) {
//Determine the amount of bytes remaining in the buffer.
size_t bytes_remaining = length - length_written;
//If it's not enough to fit the next element in the sequence,
//we're done for the current read(). Abort!
if(bytes_remaining < bytes_required_for(fizzbuzz_index)) {
break;
}
//Add the given fizzbuzz term to the output buffer...
length_written += __add_fizzbuzz_term(buffer + length_written);
//... and add a newline.
length_written += __add_newline(buffer + length_written);
//... finally, increment the index.
++fizzbuzz_index;
}
mutex_unlock(&fizzbuzz_index_mutex);
//If we didn't successfully write anything, this implies that the user provided a read()
//length that was impossibly short for our position in the sequence. Return an invalid length!
if(length_written == 0) {
return -EINVAL;
}
//Otherwise, indicate the actual length written.
else {
return length_written;
}
}
/**
* Handle module unloading.
*/
static void fizzbuzz_exit_module(void) {
//... remove the relevant character device.
cdev_del(&core_device);
//Destroy the fizzbuzz device registration.
if(fizzbuzz_device)
device_destroy(fizzbuzz_device_class, fizzbuzz_device_id);
//Delete the device class.
if(fizzbuzz_device_class)
class_destroy(fizzbuzz_device_class);
//Unregister the device ID.
if(fizzbuzz_device_id)
unregister_chrdev_region(fizzbuzz_device_id, 1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment