/* this loads the module and sets up some services */
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <net/checksum.h>
#include <linux/tqueue.h>
#include <linux/spinlock.h>

#include <linux/socket.h>
#include <linux/in.h>

int kserv_running = 0;
int kserv_shutdown = 0;

static char *hello_world = "hello world\n\n\0\0\0\0\0\0\0\0";

/* ----------------------------------------------------------------
 * get a socket for communication
 * ---------------------------------------------------------------- */
static int
kserv_sock(struct socket **psock, int port)
{
  int ret = 0;
  struct sockaddr_in sin;
  struct socket *sock;

  printk("-> kserv_sock\n");
  
  *psock = NULL;
  
 /* create a socket for local use */
  ret = sock_create(PF_INET,SOCK_STREAM,IPPROTO_TCP,&sock);
  if( ret < 0 ) {
    printk(KERN_ERR "kserv: failed to sock_create\n");
    goto no_sock;
  }
  
 /* bind it to a port */
  sock->sk->reuse      = 1;	/* multi-thread TCP */
  sin.sin_family       = AF_INET;
  sin.sin_addr.s_addr  = INADDR_ANY;
  sin.sin_port         = htons(port);
  ret = sock->ops->bind(sock, (struct sockaddr*)&sin, sizeof(sin));
  if( ret < 0 ) {
    printk(KERN_ERR "kserv: failed to bind\n");
    goto no_bind;
  }
  
 /* start the litening */
  ret = sock->ops->listen(sock,5);
  if( ret < 0 ) {
    printk(KERN_ERR "kserv: failed to listen\n");
    goto no_listen;
  }

 /* socket was successfuly started */
  *psock = sock;
  return 0;

 /* error handling */
 no_listen:  
 no_bind:
  sock_release(sock);
 no_sock:
  printk("<- kserv_sock\n");
  
  return ret;
}


/* ----------------------------------------------------------------
 * detach from world
 * ---------------------------------------------------------------- */
static int 
kserv_main(void *arg)
{
  int ret=0;
  sigset_t tmpsig;
  struct socket *sock;
  struct socket *newsock = NULL;
  
  printk("-> kserv_main\n");
  
 /* become a kernel thread called 'skerv_main' */
  sprintf(current->comm,"kserv_main");
  daemonize();
  
 /* Block all signals except SIGKILL and SIGSTOP */
  spin_lock_irq(&current->sigmask_lock);
  tmpsig = current->blocked;
  siginitsetinv(&current->blocked, sigmask(SIGKILL) | sigmask(SIGSTOP) );
  recalc_sigpending(current);
  spin_unlock_irq(&current->sigmask_lock);
  
#if 0
 /* close all opened files in user/space */
  for(i=255;i>=0;i--)
    if( current->files->fd[i] )
      close(i);
#endif
  
 /* get a socket */
  ret = kserv_sock( &sock, 8888 );
  if( ret ) goto bail;

  kserv_running = 1;
  kserv_shutdown = 0;
 /* main loop */
  printk("  main loop\n");
  while ( kserv_running ) {
    
    if( !newsock ) 
      newsock = sock_alloc();
    
    if( !newsock ) {
      printk(KERN_ERR "kserv: failed to sock_alloc\n");
      kserv_running = 0;
      
    } else {
     /* we have a new socket */
      newsock->type = sock->type;
      newsock->ops  = sock->ops;
     /* accept a connection */
      ret=sock->ops->accept(sock, newsock, O_NONBLOCK);
      if( ret == -EAGAIN ) {
       /* need to rest a while */
	DECLARE_WAIT_QUEUE_HEAD(wait);
	interruptible_sleep_on_timeout(&wait,HZ); 
	
      } else if( ret ) {
	printk(KERN_ERR "kserv: failed to accept %d\n",ret);
	sock_release(newsock);
	kserv_running = 0;
	
      } else if ( newsock->sk->state==TCP_CLOSE ) {
	printk(KERN_ERR "kserv: state of socket is invalid (TCP_CLOSE)\n");
	sock_release(newsock);
	kserv_running = 0;
      
      } else {
	int space, len = 13;
      
	DECLARE_WAIT_QUEUE_HEAD(wait);
	interruptible_sleep_on_timeout(&wait,HZ); 

	switch( newsock->state) {
	case TCP_CLOSE:       printk("  state=TCP_CLOSE\n"); break;
	case TCP_CLOSE_WAIT:  printk("  state=TCP_CLOSE_WAIT\n"); break;
	case TCP_CLOSING:     printk("  state=TCP_CLOSING\n"); break;
	case TCP_ESTABLISHED: printk("  state=TCP_ESTABLISHED\n"); break;
	case TCP_FIN_WAIT1:   printk("  state=TCP_FIN_WAIT1\n"); break;
	case TCP_FIN_WAIT2:   printk("  state=TCP_FIN_WAIT2\n"); break;
	case TCP_LAST_ACK:    printk("  state=TCP_LAST_ACK\n"); break;
	case TCP_LISTEN:      printk("  state=TCP_LISTEN\n"); break;
	case TCP_SYN_RECV:    printk("  state=TCP_SYN_RECV\n"); break;
	case TCP_SYN_SENT:    printk("  state=TCP_SYN_SENT\n"); break;
	case TCP_TIME_WAIT:   printk("  state=TCP_TIME_WAIT\n"); break;
	default:              printk("  state=%d\n",newsock->state);
	}
	
	space = sock_wspace(newsock->sk);
	if( space < len ) {
	  printk(KERN_ERR "kserv: not enough wspace for msg\n");
	  sock_release(newsock);
	  kserv_running = 0;
	  
	} else {
	  struct msghdr   msg;
	  mm_segment_t    oldfs;
	  struct iovec    iov;

	  iov.iov_len = len;
	  iov.iov_base = hello_world;
	  
	  msg.msg_name       = 0;
	  msg.msg_namelen    = 0;
	  msg.msg_iov        = &iov;
	  msg.msg_iovlen     = 1;
	  msg.msg_control    = NULL;
	  msg.msg_controllen = 0;
	  msg.msg_flags      = 0;//MSG_NOSIGNAL;
	  
	  printk("  -> sendmsg\n");
	  oldfs = get_fs(); set_fs(KERNEL_DS);
	  printk("     sock_sendmsg(%p,%p,%d);\n",newsock, msg, len );
	  len = sock_sendmsg(newsock,&msg,(size_t)(len));
	  set_fs(oldfs);
	  printk("  <- sendmsg\n");
	  
	}
       /* for now we will exit after one transaction... */
	kserv_running = 0;
      }
    }

  }
  
 /* start the shutdown */
  sock_release(sock);
  kserv_shutdown = 1;
  
 bail: /* about to exit */
  printk("kserv: module can be unloaded now\n");
  MOD_DEC_USE_COUNT;
  
  printk("<- kserv_main\n");
  return 0;
}


/* ----------------------------------------------------------------
 * called on insmod
 * ---------------------------------------------------------------- */
static int __init
init(void)
{
  printk("-> init\n");

  MOD_INC_USE_COUNT;
  
  kernel_thread(kserv_main,	/* main function */
		NULL		/* no arg */, 
		0		/* no clone flags */
		);
  
  printk("<- init\n");
  return 0;
}

/* ----------------------------------------------------------------
 * called on rmmod
 * ---------------------------------------------------------------- */
static void __exit 
fini(void)
{
  printk("-> fini\n");
  
  printk("<- fini\n");
}

/* ----------------------------------------------------------------
 * define module entry and exit
 * ---------------------------------------------------------------- */
module_init(init);
module_exit(fini);
