From 2bbeb7ee393d4de5dd4c810930a7a3b2c0c868aa Mon Sep 17 00:00:00 2001 From: Oz Date: Thu, 1 Nov 2018 12:30:51 +0100 Subject: [PATCH] Add module code and update README --- .gitignore | 2 + Makefile | 12 ++ README.md | 73 +++++++++- install.sh | 10 ++ src/mailslot.c | 213 ++++++++++++++++++++++++++++++ src/mailslot.h | 72 ++++++++++ src/mailslot_driver.c | 246 ++++++++++++++++++++++++++++++++++ src/mailslot_driver.h | 29 ++++ test/test_mailslot.c | 301 ++++++++++++++++++++++++++++++++++++++++++ uninstall.sh | 5 + 10 files changed, 961 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 install.sh create mode 100644 src/mailslot.c create mode 100644 src/mailslot.h create mode 100644 src/mailslot_driver.c create mode 100644 src/mailslot_driver.h create mode 100644 test/test_mailslot.c create mode 100644 uninstall.sh diff --git a/.gitignore b/.gitignore index c6127b3..ae0c38d 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ modules.order Module.symvers Mkfile.old dkms.conf + +*.pdf diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..baf8237 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +CONFIG_MODULE_SIG=n +WARN := -W -Wall -Wstrict-prototypes -Wmissing-prototypes +ccflags-y := -O2 + +obj-m += mailslot.o +mailslot-objs := ./src/mailslot_driver.o ./src/mailslot.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/README.md b/README.md index 08ece10..de3cce6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,71 @@ -# LinuxMailslotsModule -Windows mailslots for the Linux kernel +

+Linux Mailslots Module +

+ +

Windows mailslots for the Linux kernel

+ +

+ GitHub release + MSVC version + Architectures + License +

+

+ Features • + Building • + Usage • + License +

+ +This project provides a simple and easy to use Linux kernel module implementing a service similar to Windows mailslots, i.e. a driver for special device files accessible according to FIFO policy. + +Developed for the course of *Advanced Operating Systems and Virtualization* (AOSV) taken in the A.Y. 2016/2017 for the *Master Degree in Engineering in Computer Science* (MSE-CS) at *Sapienza University of Rome*. +The original project specification can be found [here](https://www.dis.uniroma1.it/~quaglia/DIDATTICA/AOSV/examination.html). + +## Features +The module is a Linux driver for special device files supporting the following features: + ++ **FIFO** (**F**irst **I**n **F**irst **O**ut) access policy semantic (via *open/close/read/write* services). ++ **Atomic** message read/write, i.e. any segment read from or written to the file stream is seen as an independent data unit, a message, and it is posted/delivered atomically (all or nothing). ++ Support to **multiple instances** accessible concurrently by active processes/threads. ++ **Blocking/Non-Blocking** runtime behaviour of I/O sessions (tunable via *open* or *ioctl* commands) ++ Runtime configuration (via ioctl) of the following parameters: + + *Maximum message size* (configurable up to an absolute upper limit). + + *Maximum mailslot storage size* which is dynamically reserved to any individual mailslot. ++ Compile-time configuration of the following parameters: + + *Range of device file minor numbers* supported by the driver (default: [0-255]). + + *Number of mailslot instances* (default: 256). + +## Building + +The project provides a Makefile and can be compiled using the `make` command line utility. + +## Usage + +Once built, the module can be manually installed using the `insmod mailslot.ko` command (or via the `install.sh` utility shell script, which installs the module and creates also 3 mailslots files in the `/dev/` directory). +Mailslot files can be created using the `mknod` command (for example usages, please see the `install.sh` script), + +In order to uninstall the module, the `rmmod mailslot` command must be used, as well as mailslot files can removed using the `rm` command (if the installation script was used, the module can also be uninstalled using the provided `uninstall.sh` shell script, which removes also the 3 mailslots files created during the installation). + +## License (GPL v2) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +
+
+ +Copyright © 2017 - 2018 Riccardo Ostani ([@rikyoz](https://github.com/rikyoz)) + +
\ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..fc7f9fd --- /dev/null +++ b/install.sh @@ -0,0 +1,10 @@ +#!/bin/bash +insmod mailslot.ko + +#reading major number of the mailslot driver +mailslot_major=$( cat /proc/devices | grep mailslot | cut -d " " -f 1 ) + +#inserting device file in /dev/ +mknod -m 666 /dev/mailslot0 c $mailslot_major 0 +mknod -m 666 /dev/mailslot1 c $mailslot_major 1 +mknod -m 666 /dev/test_mailslot c $mailslot_major 2 \ No newline at end of file diff --git a/src/mailslot.c b/src/mailslot.c new file mode 100644 index 0000000..996b73a --- /dev/null +++ b/src/mailslot.c @@ -0,0 +1,213 @@ +/* +Copyright (C) 2017-2018 Riccardo Ostani. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "mailslot.h" + +#include /* for kzalloc */ +#include /* for mutex */ +#include /* for copy_to_user and copy_from_user functions */ +#include /* for wait_queue */ + +typedef struct message { + char* content; + size_t size; + struct message* next; +} message_t; + +struct mailslot { + struct mutex mutex; + wait_queue_head_t rd_queue, wr_queue; + message_t* head; + message_t* tail; + size_t max_msg_size; + int msg_count; + int id; /* needed only to help debugging! */ +}; + +mailslot_t* mailslot_alloc( void ) { + return kzalloc( sizeof( mailslot_t ), GFP_KERNEL ); +} + +void mailslot_init( mailslot_t* slot, int id ) { + mutex_init( &( slot->mutex ) ); + init_waitqueue_head( &( slot->rd_queue ) ); + init_waitqueue_head( &( slot->wr_queue ) ); + slot->head = NULL; + slot->tail = NULL; + slot->max_msg_size = DEFAULT_MAX_MSG_SIZE; + slot->id = id; +} + +ssize_t mailslot_enqueue( mailslot_t* slot, const char* content, size_t size, int non_blocking ) { + int error; + int count = slot->msg_count; + message_t* msg = NULL; + + if ( count == MAX_SLOT_SIZE ) { + printk( KERN_ERR "mailslot (id %d): cannot enqueue msg, slot is full\n", slot->id ); + return -ENOSPC; + } + + if ( size > slot->max_msg_size ) { /* all or nothing */ + printk( KERN_ERR "mailslot (id %d): cannot write msg, size (%lu) greater than max allowed by the slot (%lu)\n", slot->id, size, slot->max_msg_size ); + return -EPERM; + } + + msg = kzalloc( sizeof( message_t ), non_blocking ? GFP_ATOMIC : GFP_KERNEL ); + if ( msg == NULL ) { + printk( KERN_ERR "mailslot (id %d): failed to allocate space for the new msg\n", slot->id ); + return non_blocking ? -EAGAIN : -ENOMEM; + } + + msg->content = kzalloc( size, non_blocking ? GFP_ATOMIC : GFP_KERNEL ); + if ( msg->content == NULL ) { + printk( KERN_ERR "mailslot (id %d): failed to allocate space for the new msg's content\n", slot->id ); + kfree( msg ); + return non_blocking ? -EAGAIN : -ENOMEM; + } + + if ( non_blocking ) { /* disabling the pagefault handler, so that copy_from_user won't sleep */ + pagefault_disable(); + } + error = copy_from_user( msg->content, content, size ); + if ( non_blocking ) { + pagefault_enable(); + } + if ( error ) { + printk( KERN_ERR "mailslot (id %d): failed to copy msg from user space\n", slot->id ); + kfree( msg->content ); + kfree( msg ); + return -EFAULT; + } + msg->size = size; + msg->next = NULL; + + if ( count == 0 ) { + slot->head = msg; + } else { + slot->tail->next = msg; + } + slot->tail = msg; + slot->msg_count++; + mailslot_printqueue( slot ); /* debug help */ + + return size; +} + +ssize_t mailslot_dequeue( mailslot_t* slot, char* buffer, size_t size, int non_blocking ) { + int res, error; + int count = slot->msg_count; + message_t* msg = NULL; + + if ( count == 0 ) { /* not an error */ + printk( KERN_INFO "mailslot (id %d): no msg to read, empty slot\n", slot->id ); + return 0; + } + + msg = slot->head; + if ( msg->size > size ) { /* all or nothing */ + printk( KERN_ERR "mailslot (id %d): user buffer too small for the msg\n", slot->id ); + return -EMSGSIZE; + } + + if ( non_blocking ) { + pagefault_disable(); + } + error = copy_to_user( buffer, msg->content, msg->size ); + if ( non_blocking ) { + pagefault_enable(); + } + if ( error ) { + printk( KERN_ERR "mailslot (id %d): failed to copy msg to user space\n", slot->id ); + return -EFAULT; + } + + res = msg->size; + slot->head = slot->head->next; /* if count == 1, here head becomes NULL! */ + kfree( msg->content ); + kfree( msg ); + if ( count == 1 ) { + slot->tail = NULL; /* here head == NULL and tail == (deallocated) msg */ + } + slot->msg_count--; + mailslot_printqueue( slot ); /* debug help */ + + return res; +} + +int mailslot_lock( mailslot_t* slot, int non_blocking ) { + printk( KERN_INFO "mailslot (id %d): pid %d wants to lock the slot", slot->id, current->pid ); + if ( non_blocking ) { + printk( KERN_CONT " without blocking\n" ); + return mutex_trylock( &(slot->mutex) ); + } else { + printk( KERN_CONT "\n" ); + return mutex_lock_interruptible( &(slot->mutex) ) == 0; + } +} + +void mailslot_unlock( mailslot_t* slot ) { + mutex_unlock( &(slot->mutex) ); +} + +int mailslot_wait_msg( mailslot_t* slot ) { + return wait_event_interruptible_exclusive( slot->rd_queue, slot->msg_count > 0 ); +} + +int mailslot_wait_space( mailslot_t* slot ) { + return wait_event_interruptible_exclusive( slot->wr_queue, slot->msg_count < MAX_SLOT_SIZE ); +} + +void mailslot_notify_msg( mailslot_t* slot ) { + wake_up_interruptible( &(slot->rd_queue) ); +} + +void mailslot_notify_space( mailslot_t* slot ) { + wake_up_interruptible( &(slot->wr_queue) ); +} + +void mailslot_set_max_msg_size( mailslot_t* slot, size_t size ) { + slot->max_msg_size = size; +} + +void mailslot_free( mailslot_t* slot ) { + int i; + message_t* msg = NULL; + for ( i = 0; i < slot->msg_count; i++ ) { + msg = slot->head->next; + kfree( slot->head->content ); + kfree( slot->head ); + slot->head = msg; + } + kfree( slot ); +} + +void mailslot_printqueue( mailslot_t* slot ) { + message_t* msg = slot->head; + printk( KERN_INFO "mailslot (id %d): (slot content) head = ", slot->id ); + while ( msg != NULL ) { + printk( KERN_CONT "\"%s\"", msg->content ); + if ( msg->next != NULL ) { + printk( KERN_CONT ", " ); + } + msg = msg->next; + } + if ( msg == NULL ){ + printk( KERN_CONT " = tail\n" ); + } +} \ No newline at end of file diff --git a/src/mailslot.h b/src/mailslot.h new file mode 100644 index 0000000..957555a --- /dev/null +++ b/src/mailslot.h @@ -0,0 +1,72 @@ +/* +Copyright (C) 2017-2018 Riccardo Ostani. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef MAILSLOT_H +#define MAILSLOT_H + +#include + +#define DEFAULT_MAX_MSG_SIZE 256 /* default max size of a message data-unit */ +#define LIMIT_MAX_MSG_SIZE 512 /* upper limit to the max size of a message data-unit */ +#define MAX_SLOT_SIZE 64 /* max number of messages storable in a mailslot */ + +typedef struct mailslot mailslot_t; + +/* Allocates a mailslot struct. */ +mailslot_t* mailslot_alloc( void ); + +/* Initilizes the fields of a mailslot. */ +void mailslot_init( mailslot_t* slot, int id ); + +/* Enqueues a message in a slot. + * Note: it must be called in a critical section (e.g. after a mailslot_lock) */ +ssize_t mailslot_enqueue( mailslot_t* slot, const char* content, size_t size, int non_blocking ); + +/* Dequeues the oldest message in the slot. + * Note: it must be called in a critical section (e.g. after a mailslot_lock) */ +ssize_t mailslot_dequeue( mailslot_t* slot, char* buffer, size_t size, int non_blocking ); + +/* Locks the access to the slot. + * It returns 1 if lock was acquired, 0 otherwise. */ +int mailslot_lock( mailslot_t* slot, int non_blocking ); + +/* Unlocks the access to the slot. */ +void mailslot_unlock( mailslot_t* slot ); + +/* Makes the caller sleep and wait for a message to be written in the slot. */ +int mailslot_wait_msg( mailslot_t* slot ); + +/* Makes the caller sleep and wait for space availability in the slot. */ +int mailslot_wait_space( mailslot_t* slot ); + +/* Wakes up all processes waiting for new messages in the slot. */ +void mailslot_notify_msg( mailslot_t* slot ); + +/* Wakes up all processes waiting for space in the slot. */ +void mailslot_notify_space( mailslot_t* slot ); + +/* Sets the max message size allowed in the slot. */ +void mailslot_set_max_msg_size( mailslot_t* slot, size_t size ); + +/* Frees the mailslot memory. */ +void mailslot_free( mailslot_t* slot ); + +/* Prints the content of the slot fifo queue. */ +void mailslot_printqueue( mailslot_t* slot ); + +#endif \ No newline at end of file diff --git a/src/mailslot_driver.c b/src/mailslot_driver.c new file mode 100644 index 0000000..98576bc --- /dev/null +++ b/src/mailslot_driver.c @@ -0,0 +1,246 @@ +/* +Copyright (C) 2017-2018 Riccardo Ostani. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "mailslot_driver.h" +#include "mailslot.h" + +#include +#include +#include /* for char device functions */ +#include /* for cdev handling functions */ +#include /* for current pointer */ + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Riccardo Ostani"); +MODULE_DESCRIPTION("Mail slots for Linux"); +MODULE_VERSION("1.0"); + +#define DEV_NAME "mailslot" /* name of the device driver */ +#define BASE_MINOR 0 /* base minor number */ +#define INSTANCES 256 /* number of mailslot instances supported */ + +static int major; /* major number assigned to the mailslot device driver */ +static dev_t dev; +static struct cdev* ms_cdev; + +static mailslot_t* mailslot[ INSTANCES ] = { NULL }; + +static ssize_t ms_write( struct file* filp, const char __user* buffer, size_t size, loff_t* ofst ) { + int result, has_lock; + int non_blocking = filp->f_flags & O_NONBLOCK; + int slot_id = iminor( filp->f_path.dentry->d_inode ) ; + mailslot_t* slot = mailslot[ slot_id - BASE_MINOR ]; + + if ( size == 0 ) { + printk( KERN_INFO "mailslot (id %d): [write] pid %d tried to write a 0-size msg\n", slot_id, current->pid ); + return 0; + } + + if ( buffer == NULL ) { + printk( KERN_INFO "mailslot (id %d): [write] pid %d tried to write a NULL msg\n", slot_id, current->pid ); + return -EFAULT; + } + +write: + has_lock = mailslot_lock( slot, non_blocking ); + if ( !has_lock ) { + printk( KERN_ERR "mailslot (id %d): [write] pid %d couldn't acquire lock\n", slot_id, current->pid ); + return non_blocking ? -EAGAIN : -EINTR; + } + printk( KERN_INFO "mailslot (id %d): [write] lock acquired (pid %d)\n", slot_id, current->pid ); + + result = mailslot_enqueue( slot, buffer, size, non_blocking ); + + mailslot_unlock( slot ); + printk( KERN_INFO "mailslot (id %d): [write] slot unlocked (pid %d)\n", slot_id, current->pid ); + + if ( result > 0 ) { /* the message was correctly enqueued! */ + mailslot_notify_msg( slot ); + } else if ( result == -ENOSPC ) { /* slot is full! */ + if ( non_blocking ) { /* the write would block but we must not! */ + result = -EAGAIN; + } else { + result = mailslot_wait_space( slot ); + if ( result == 0 ) { /* now there's space for the message */ + goto write; /* try again to write the message */ + } else { /* sleep was interrupted by a signal! */ + result = -EINTR; + } + } + } + return result; +} + +static ssize_t ms_read( struct file* filp, char __user* buffer, size_t size, loff_t* ofst ) { + int result, has_lock; + int non_blocking = filp->f_flags & O_NONBLOCK; + int slot_id = iminor( filp->f_path.dentry->d_inode ); + mailslot_t* slot = mailslot[ slot_id - BASE_MINOR ]; + + if ( size == 0 ) { + printk( KERN_INFO "mailslot (id %d): [read] pid %d tried to read to 0-size buffer\n", slot_id, current->pid ); + return 0; + } + + if ( buffer == NULL ) { + printk( KERN_INFO "mailslot (id %d): [read] pid %d tried to read to a NULL buffer\n", slot_id, current->pid ); + return 0; + } + +read: + has_lock = mailslot_lock( slot, non_blocking ); + if ( !has_lock ) { + printk( KERN_ERR "mailslot (id %d): [read] pid %d couldn't acquire lock\n", slot_id, current->pid ); + return non_blocking ? -EAGAIN : -EINTR; + } + printk( KERN_INFO "mailslot (id %d): [read] lock acquired (pid %d)\n", slot_id, current->pid ); + + result = mailslot_dequeue( slot, buffer, size, non_blocking ); + + mailslot_unlock( slot ); + printk( KERN_INFO "mailslot (id %d): [read] slot unlocked (pid %d)\n", slot_id, current->pid ); + + if ( result > 0 ) { /* a message was correctly dequeued! */ + mailslot_notify_space( slot ); + } else if ( result == 0 ) { /* slot is empty! */ + if ( non_blocking ) { /* the read would block but we must not! */ + result = -EAGAIN; + } else { + result = mailslot_wait_msg( slot ); + if ( result == 0 ) { /* now there's a message to read! */ + goto read; /* try again to read a message */ + } else { + result = -EINTR; + } + } + } + return result; +} + +static long ms_unlocked_ioctl( struct file* filp, unsigned cmd, unsigned long arg ) { + int slot_id = iminor( filp->f_path.dentry->d_inode ); + mailslot_t* slot = NULL; + + switch ( cmd ) { + case MAILSLOT_SET_NONBLOCKING: /* per session setting */ + printk( KERN_INFO "mailslot (id %d): [ioctl] IO is now ", slot_id ); + if ( arg ) { + filp->f_flags |= O_NONBLOCK; + printk( KERN_CONT "non-" ); + } else { + filp->f_flags &= ~O_NONBLOCK; + } + printk( KERN_CONT "blocking for pid %d\n", current->pid ); + break; + + case MAILSLOT_SET_MAX_MSG_SIZE: /* per slot setting */ + if ( arg == 0 || arg > LIMIT_MAX_MSG_SIZE ) { + printk( KERN_ERR "mailslot (id %d): [ioctl] invalid max message size\n", slot_id ); + return -EINVAL; + } else { + slot = mailslot[ slot_id - BASE_MINOR ]; + mailslot_lock( slot, filp->f_flags & O_NONBLOCK ); + mailslot_set_max_msg_size( slot, arg ); + mailslot_unlock( slot ); + printk( KERN_INFO "mailslot (id %d): [ioctl] max msg size set to %lu chars\n", slot_id, arg ); + } + break; + + default: + printk( KERN_INFO "mailslot (id %d): [ioctl] invalid command code %u\n", slot_id, cmd ); + return -ENOTTY; + } + return 0; +} + +static int ms_open( struct inode* inode, struct file* filp ) { return 0; } + +static int ms_release( struct inode* inode, struct file* filp ) { return 0; } + +static struct file_operations ms_fops = { + .read = ms_read, + .write = ms_write, + .unlocked_ioctl = ms_unlocked_ioctl, + .open = ms_open, + .release = ms_release, + .owner = THIS_MODULE +}; + +void delete_slots( void ) { + int i; + for ( i = 0; i < INSTANCES; i++ ) { + if ( mailslot[i] == NULL ) { + return; + } + mailslot_free( mailslot[i] ); + } +} + +int init_module( void ) { + int i, error; + + /* allocating and initializing mailslots */ + for ( i = 0; i < INSTANCES; ++i ) { + mailslot[i] = mailslot_alloc(); + if ( mailslot[i] == NULL ) { + printk( KERN_ERR "mailslot: failed to allocate memory for slot %d\n", i + BASE_MINOR ); + error = -ENOMEM; + goto fail_alloc; + } + /* the id of the slot is the minor number, not the index in the array of the instances! */ + mailslot_init( mailslot[i], i + BASE_MINOR ); + } + + /* allocating char device minor numbers in range [BASE_MINOR, BASE_MINOR + INSTANCES - 1] */ + error = alloc_chrdev_region( &dev, BASE_MINOR, INSTANCES, DEV_NAME ); + if ( error ) { + printk( KERN_ERR "mailslot: failed to register char device numbers\n" ); + goto fail_alloc; + } + + major = MAJOR( dev ); + + /* registering the char device */ + ms_cdev = cdev_alloc(); + if ( !ms_cdev ) { + printk( KERN_ERR "mailslot: failed to allocate cdev\n" ); + error = -ENOMEM; + goto fail_cdev_alloc; + } + cdev_init( ms_cdev, &ms_fops ); + error = cdev_add( ms_cdev, dev, INSTANCES ); + if ( error ) { + printk( KERN_ERR "mailslot: failed to add cdev to the system\n" ); + goto fail_cdev_add; + } + + printk( KERN_INFO "mailslot: device registered successfully (major number: %d)\n", major ); + return 0; + +fail_cdev_add: cdev_del( ms_cdev ); +fail_cdev_alloc: unregister_chrdev_region( dev, INSTANCES ); +fail_alloc: delete_slots(); + return error; +} + +void cleanup_module( void ) { + cdev_del( ms_cdev ); + unregister_chrdev_region( dev, INSTANCES ); + delete_slots(); + printk( KERN_INFO "mailslot: device unregistered successfully (major number: %d)\n", major ); +} \ No newline at end of file diff --git a/src/mailslot_driver.h b/src/mailslot_driver.h new file mode 100644 index 0000000..59ff8ef --- /dev/null +++ b/src/mailslot_driver.h @@ -0,0 +1,29 @@ +/* +Copyright (C) 2017-2018 Riccardo Ostani. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef MAILSLOT_DRIVER_H +#define MAILSLOT_DRIVER_H + +#include + +#define MAILSLOT_IOCTL_MAGIC 'x' /* unused 8-bit number in ioctl-number.txt */ + +#define MAILSLOT_SET_NONBLOCKING _IOW( MAILSLOT_IOCTL_MAGIC, 0, unsigned int ) +#define MAILSLOT_SET_MAX_MSG_SIZE _IOW( MAILSLOT_IOCTL_MAGIC, 1, unsigned int ) + +#endif \ No newline at end of file diff --git a/test/test_mailslot.c b/test/test_mailslot.c new file mode 100644 index 0000000..e1e3d4f --- /dev/null +++ b/test/test_mailslot.c @@ -0,0 +1,301 @@ +#include +#include +#include +#include +#include + +#include "../src/mailslot.h" +#include "../src/mailslot_driver.h" + +#define DEVICE_FILE "/dev/test_mailslot" +#define NEW_MAX_MSG_SIZE (DEFAULT_MAX_MSG_SIZE / 2) + +/* terminal colors */ +#define GREEN_STR( str ) "\x1B[92m"str"\x1B[0m" +#define RED_STR( str ) "\x1B[91m"str"\x1B[0m" + +#define REQUIRE( expr, error_str ) \ +do { \ + if ( !( expr ) ) { \ + printf( RED_STR( "[ERROR] (%s)\n" ), error_str ); \ + return; \ + } \ +} while ( 0 ) + +int is_nonblocking( int fd ); + +void set_nonblocking( int fd, int non_blocking ); + +int fill_device( int fd, char* buffer, size_t size ); + +void cleanup_device( int fd ); + +void test_mailslot( int fd ) { + int cres; /* results of calls */ + int pid; + char buffer[ 4096 ]; + + {/* ioctl test */ + printf("Testing ioctl... "); + + cres = ioctl( fd, MAILSLOT_SET_NONBLOCKING, 1 ); + REQUIRE( cres == 0, "failed to set non-blocking IO!" ); + REQUIRE( is_nonblocking(fd), "ioctl succeeded but IO is still blocking!" ); + + cres = ioctl( fd, MAILSLOT_SET_NONBLOCKING, 0 ); + REQUIRE( cres == 0, "failed to set blocking IO!" ); + REQUIRE( !is_nonblocking(fd), "ioctl succeeded but IO is still blocking!" ); + + cres = ioctl( fd, MAILSLOT_SET_MAX_MSG_SIZE, NEW_MAX_MSG_SIZE ); + REQUIRE( cres == 0, "failed to set max data unit size!" ); + + cres = ioctl( fd, MAILSLOT_SET_MAX_MSG_SIZE, LIMIT_MAX_MSG_SIZE ); + REQUIRE( cres == 0, "failed to set max data unit size to upper limit!" ); + + cres = ioctl( fd, MAILSLOT_SET_MAX_MSG_SIZE, -10 ); + REQUIRE( cres == -1, "succeeded in setting a negative max data unit size (-10)!" ); + + cres = ioctl( fd, MAILSLOT_SET_MAX_MSG_SIZE, 0 ); + REQUIRE( cres == -1, "succeeded in setting an invalid max data unit size (0)!" ); + + cres = ioctl( fd, MAILSLOT_SET_MAX_MSG_SIZE, LIMIT_MAX_MSG_SIZE + 1 ); + REQUIRE( cres == -1, "succeeded in setting an invalid max data unit size (upper limit)!" ); + + cres = ioctl( fd, MAILSLOT_SET_MAX_MSG_SIZE, DEFAULT_MAX_MSG_SIZE ); + REQUIRE( cres == 0, "failed to reset max data unit size to default value!" ); + + cres = ioctl( fd, 42 ); + REQUIRE( cres == -1, "succeeded in sending a non valid ioctl command!" ); + + printf( GREEN_STR( "[OK]\n" ) ); + } + + {/* basic read/write test */ + printf("Testing basic read/write... "); /* expecting empty slot! */ + + cres = write( fd, "ciao mondo!", 12 ); + REQUIRE( cres == 12, "failed in writing a message!" ); + + cres = read( fd, buffer, 4096 ); + REQUIRE( cres == 12, "failed in reading a message!" ); + + printf( GREEN_STR( "[OK]\n" ) ); + } + + {/* queue order test */ + printf("Testing queue order... "); /* expecting empty slot! */ + + cres = write( fd, "abc", 4 ); + REQUIRE( cres == 4, "failed in writing a message!" ); + + cres = write( fd, "123", 4 ); + REQUIRE( cres == 4, "failed in writing a message!" ); + + cres = write( fd, "xyz", 4 ); + REQUIRE( cres == 4, "failed in writing a message!" ); + + cres = read( fd, buffer, 4096 ); + REQUIRE( cres == 4, "failed in reading a message!" ); + REQUIRE( strncmp( buffer, "abc", 3 ) == 0, "retrieved wrong message" ); + + cres = read( fd, buffer, 4096 ); + REQUIRE( cres == 4, "failed in reading a message!" ); + REQUIRE( strncmp( buffer, "123", 3 ) == 0, "retrieved wrong message" ); + + cres = read( fd, buffer, 4096 ); + REQUIRE( cres == 4, "failed in reading a message!" ); + REQUIRE( strncmp( buffer, "xyz", 3 ) == 0, "retrieved wrong message" ); + + printf( GREEN_STR( "[OK]\n" ) ); + } + + {/* write test */ + printf("Testing write... "); /* expecting empty slot! */ + + cres = write( fd, "ciao mondo!", 24 ); + REQUIRE( cres == 24, "failed in writing a msg (double of real size)!" ); + + cres = write( fd, NULL, 24 ); + REQUIRE( cres == -1, "succeeded in writing a NULL msg!" ); + + /* BVA (size must be in range [1, DEFAULT_MAX_MSG_SIZE]) */ + cres = write( fd, "ciao mondo!", 1 ); + REQUIRE( cres == 1, "failed in writing a msg with size 1!" ); + + cres = write( fd, "ciao mondo!", DEFAULT_MAX_MSG_SIZE ); + REQUIRE( cres == DEFAULT_MAX_MSG_SIZE, "failed in writing a msg with size DEFAULT_MAX_MSG_SIZE!" ); + + cres = write( fd, "ciao mondo!", 0 ); + REQUIRE( cres == 0, "succeeded in writing a msg with size 0!" ); + + cres = write( fd, "ciao mondo!", DEFAULT_MAX_MSG_SIZE + 1 ); + REQUIRE( cres == -1, "succeeded in writing a msg with size greater than (default) max!" ); + + /* BVA (with new size range [1, DEFAULT_MAX_MSG_SIZE/2]) */ + cres = ioctl( fd, MAILSLOT_SET_MAX_MSG_SIZE, NEW_MAX_MSG_SIZE ); + REQUIRE( cres == 0, "failed to set max data unit size!" ); + + cres = write( fd, "ciao mondo!", NEW_MAX_MSG_SIZE ); + REQUIRE( cres == NEW_MAX_MSG_SIZE, "failed in writing a msg with size DEFAULT_MAX_MSG_SIZE/2!" ); + + cres = write( fd, "ciao mondo!", NEW_MAX_MSG_SIZE + 1 ); + REQUIRE( cres == -1, "succeeded in writing a msg with size greater than max!" ); + + cleanup_device( fd ); + + /* reset max msg size to default value */ + cres = ioctl( fd, MAILSLOT_SET_MAX_MSG_SIZE, DEFAULT_MAX_MSG_SIZE ); + REQUIRE( cres == 0, "failed to set max data unit size!" ); + + printf( GREEN_STR( "[OK]\n" ) ); + } + + {/* waiting write test */ + printf("Testing waiting write... "); /* expecting empty slot */ + + cres = fill_device( fd, "hello world!", 12 ); + REQUIRE( cres == 1, "failed to fill device!" ); + + pid = fork(); + REQUIRE( pid >= 0, "failed to fork!" ); + + if ( pid == 0 ) { /* child */ + sleep(2); + cres = read( fd, buffer, 12 ); + REQUIRE( cres == 12, "failed in reading a msg from child!" ); + return; + } else { /* parent */ + cres = write( fd, "ciao mondo!", 12 ); + REQUIRE( cres == 12, "failed in writing a message from waiting parent!" ); + cleanup_device( fd ); + } + + printf( GREEN_STR( "[OK]\n" ) ); + } + + {/* non-waiting write test */ + printf("Testing non-waiting write... "); /* expecting empty slot and non-blocking io! */ + + set_nonblocking( fd, 1 ); + + /* still blocking, but we do not exceed the max slot size, hence it should not fail! */ + cres = fill_device( fd, "abc", 4 ); + REQUIRE( cres == 1, "failed to fill device!" ); + + cres = write( fd, "ciao mondo!", 12 ); /* WRITING TO FULL FILLED SLOT! */ + REQUIRE( cres == -1, "succeeded in writing to a full filled slot!" ); + + cleanup_device( fd ); + + printf( GREEN_STR( "[OK]\n" ) ); + } + + {/* read test */ + printf("Testing read... "); /* expecting empty slot! */ + + cres = fill_device( fd, "hello world!", 12 ); + REQUIRE( cres == 1, "failed to fill device!" ); + + cres = read( fd, buffer, 12 ); + REQUIRE( cres == 12, "failed in reading a msg!" ); + + cres = read( fd, buffer, 11 ); + REQUIRE( cres == -1, "succeeded in reading a msg with size greater than the buffer size!" ); + + cres = read( fd, buffer, 0 ); + REQUIRE( cres == 0, "succeeded in reading a msg to a 0-size buffer!" ); + + cres = read( fd, NULL, 12 ); + REQUIRE( cres == 0, "succeeded in reading a msg to a NULL buffer!" ); + + cleanup_device( fd ); + + printf( GREEN_STR( "[OK]\n" ) ); + } + + {/* waiting read test */ + printf("Testing waiting read... "); /* expecting empty slot and blocking io! */ + + pid = fork(); + REQUIRE( pid >= 0, "failed to fork!" ); + + if ( pid == 0 ) { /* child */ + sleep(2); + cres = write( fd, "ciao mondo!", 12 ); + REQUIRE( cres == 12, "failed in writing a message from child!" ); + return; + } else { /* parent */ + cres = read( fd, buffer, 12 ); + REQUIRE( cres == 12, "failed in reading a msg from waiting parent!" ); + } + + printf( GREEN_STR( "[OK]\n" ) ); + } + + { /* non-waiting read test */ + printf("Testing non-waiting read... "); /* expecting empty slot and non-blocking io! */ + + cleanup_device( fd ); + + set_nonblocking( fd, 1 ); + + cres = read( fd, buffer, 12 ); + REQUIRE( cres == -1, "succeeded in reading a msg from an empty slot!" ); + + set_nonblocking( fd, 0 ); + + printf( GREEN_STR( "[OK]\n" ) ); + } + + printf( GREEN_STR( "All tests were successful! No error occured!\n" ) ); +} + +int main() { + int fd = open( DEVICE_FILE, O_RDWR ); + + printf( RED_STR( "###### Linux Mail Slots -- TEST SUITE ######\n" ) ); + + if ( fd < 0 ) { + printf( RED_STR("[ERROR] Couldn't open device file!\n") ); + return 0; + } + + setbuf(stdout, NULL); /* disable buffering */ + + test_mailslot( fd ); + + close( fd ); + return 0; +} + +int is_nonblocking( int fd ) { + int flags = fcntl( fd, F_GETFL, 0 ); + return flags & O_NONBLOCK; +} + +void set_nonblocking( int fd, int non_blocking ) { + int flags = fcntl( fd, F_GETFL, 0 ); + if ( non_blocking ) { + fcntl( fd, F_SETFL, flags | O_NONBLOCK ); + } else { + fcntl( fd, F_SETFL, flags & ~O_NONBLOCK ); + } +} + +int fill_device( int fd, char* buffer, size_t size ) { + int i, cres = 1; + for ( i = 0; i < MAX_SLOT_SIZE; ++i ) { + cres = cres && ( write( fd, buffer, size ) != -1 ); + } + return cres; +} + +void cleanup_device( int fd ) { + int n; + char buffer[ 4096 ]; + set_nonblocking( fd, 1 ); + do { + n = read( fd, buffer, 4096 ); + } while ( n >= 0 ); + set_nonblocking( fd, 0 ); +} \ No newline at end of file diff --git a/uninstall.sh b/uninstall.sh new file mode 100644 index 0000000..fe3006d --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,5 @@ +#!/bin/bash +rm /dev/mailslot0 +rm /dev/mailslot1 +rm /dev/test_mailslot +rmmod mailslot \ No newline at end of file