Last active
April 17, 2024 04:16
-
-
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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