/**
* Function: Module to handle read/write calls to the /proc
* file, /proc/myprocfile. The reads are responded to
* only if there are changes
* Author: Asanga Udugama (adu@comnets.uni-bremen.de)
* License: GPL
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>
#include <linux/sched.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My Proc File Module");
MODULE_AUTHOR("Asanga Udugama");

#define MAX_MSG_LENGTH	PAGE_SIZE
static struct proc_dir_entry *proc_entry;

static char *curr_msg;
static int new_msg = 0;
static wait_queue_head_t wait_queue, wait_queue2;
static spinlock_t control_lock;
static int wait_count = 0;

int myprocfile_read( char *page, char **start, off_t off,
                   int count, int *eof, void *data )
{
  int len;

  if (off > 0) {
    *eof = 1;
    return 0;
  }

  // wait queue to hold new readers comming in
  if(new_msg) {
    int is_sig = 0, i;
    
    wait_event_interruptible(wait_queue2, (!new_msg));
    for (i = 0; i < _NSIG_WORDS && !is_sig; i++) {
      is_sig = current->pending.signal.sig[i] & ~current->blocked.sig[i];
    }
    if(is_sig) {
       return -EFAULT;
    }
  }
  
  // wait queue to let old readers wait for something to be written
  if(!new_msg) {
    int is_sig = 0, i;
    
    wait_count++;
    wait_event_interruptible(wait_queue, (new_msg));
    for (i = 0; i < _NSIG_WORDS && !is_sig; i++) {
      is_sig = current->pending.signal.sig[i] & ~current->blocked.sig[i];
    }
    wait_count--;
    if(is_sig) {
       return -EFAULT;
    }
  }
   
  spin_lock(&control_lock);
  len = sprintf(page, "%s\n", curr_msg);
  if(wait_count > 0) {
    wake_up(&wait_queue);
  } else {
    new_msg = 0;
    wake_up(&wait_queue2);
  }
  spin_unlock(&control_lock);
  
  return len;
}


ssize_t myprocfile_write( struct file *filp, const char __user *buff,
                        unsigned long len, void *data )
{

  if (len > MAX_MSG_LENGTH) {
    printk(KERN_INFO "myprocfile: msg too large!\n");
    return -ENOSPC;
  }

  spin_lock(&control_lock);
  if (copy_from_user(curr_msg, buff, len )) {
    spin_unlock(&control_lock);
    return -EFAULT;
  }
  *(curr_msg+len) = 0;
  new_msg = 1;
  wake_up(&wait_queue);
  spin_unlock(&control_lock);

  return len;
}


int init_myprocfile_module( void )
{
  int ret = 0;

  curr_msg = (char *)vmalloc(MAX_MSG_LENGTH);

  if (!curr_msg) {
    ret = -ENOMEM;
  } else {
    memset(curr_msg, 0, MAX_MSG_LENGTH );
    
    init_waitqueue_head(&wait_queue);
    init_waitqueue_head(&wait_queue2);
    spin_lock_init(&control_lock);
   
    proc_entry = create_proc_entry( "myprocfile", 0644, NULL );
    //proc_entry = create_proc_entry( "myprocfile", 0644, proc_net );

    if (proc_entry == NULL) {
      ret = -ENOMEM;
      vfree(curr_msg);
      printk(KERN_INFO "myprocfile: Couldn't create proc entry\n");
    } else {
      proc_entry->read_proc = myprocfile_read;
      proc_entry->write_proc = myprocfile_write;
      proc_entry->owner = THIS_MODULE;
      printk(KERN_INFO "myprocfile: Module loaded.\n");
    }
  }
  return ret;
}


void cleanup_myprocfile_module( void )
{
  remove_proc_entry("myprocfile", &proc_root);
  vfree(curr_msg);
  printk(KERN_INFO "myprocfile: Module unloaded.\n");
}


module_init(init_myprocfile_module);
module_exit(cleanup_myprocfile_module);

