/*
 * Copyright (c) 1998-2000 Caucho Technology -- all rights reserved
 *
 * Caucho Technology permits redistribution, modification and use
 * of this file in source and binary form ("the Software") under the
 * Caucho Developer Source License ("the License").  The following
 * conditions must be met:
 *
 * 1. Each copy or derived work of the Software must preserve the copyright
 *    notice and this notice unmodified.
 *
 * 2. Redistributions of the Software in source or binary form must include 
 *    an unmodified copy of the License, normally in a plain ASCII text
 *
 * 3. The names "Resin" or "Caucho" are trademarks of Caucho Technology and
 *    may not be used to endorse products derived from this software.
 *    "Resin" or "Caucho" may not appear in the names of products derived
 *    from this software.
 *
 * 4. Caucho Technology requests that attribution be given to Resin
 *    in any manner possible.  We suggest using the "Resin Powered"
 *    button or creating a "powered by Resin(tm)" link to
 *    http://www.caucho.com for each page served by Resin.
 *
 * This Software is provided "AS IS," without a warranty of any kind. 
 * ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.

 * CAUCHO TECHNOLOGY AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR
 * DISTRIBUTING SOFTWARE. IN NO EVENT WILL CAUCHO OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE SOFTWARE, EVEN IF HE HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.      
 *
 * @author Scott Ferguson
 *
 * $Id: proc.c,v 1.6 2001/08/02 00:29:40 ferg Exp $
 */
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/in.h>

#include "hardcore.h"

static struct proc_dir_entry *proc_net_resin = 0;

// lock for all proc modifications
static spinlock_t g_proc_lock = SPIN_LOCK_UNLOCKED;

/**
 * Sets the read parameters
 *
 * @param buffer      the read buffer (any guarantees on size?)
 * @param start       output start pointer into the buffer
 * @param read_offset user read offset into the buffer
 * @param buf_len     size of the buffer
 * @param eof         output set 1 if the user read everything
 * @param len         the written buffer length
 */
static int
read_result(char *buffer, char **start, off_t read_offset, int buf_len,
            int *eof, int write_len)
{
  if (read_offset >= write_len) {
    *start = buffer;
    *eof = 1;

    return 0;
  }

  *start = buffer + read_offset;

  write_len -= read_offset;
  if (write_len > buf_len)
    return buf_len;

  *eof = 1;
  return write_len;
}

/**
 * Returns the port number that we're listening to.
 */
static int
resin_port_read(char *buffer, char **start, off_t read_offset, int buf_len,
                int *eof, void *data)
{
  int write_len;
  resin_t *resin = data;

  if (! resin || resin->is_dead) {
    *start = buffer;
    *eof = 1;
    
    return 0;
  }

  write_len = sprintf(buffer, "%d\n", resin->accept_port);

  return read_result(buffer, start, read_offset, buf_len, eof, write_len);
}

/**
 * Returns the srun configuration
 *
 * @param buffer      the read buffer (any guarantees on size?)
 * @param start       output start pointer into the buffer
 * @param read_offset user read offset into the buffer
 * @param buf_len     size of the buffer
 * @param eof         output set 1 if the user read everything
 * @param data        system data (resin_t)
 */
static int
resin_srun_read(char *buffer, char **start, off_t read_offset, int buf_len,
                int *eof, void *data)
{
  int write_len = 0;
  srun_t *srun = data;

  if (! srun || ! srun->is_live || srun->resin->is_dead) {
    *start = buffer;
    *eof = 1;
    
    return 0;
  }

  if (srun && srun->is_live) {
    write_len = sprintf(buffer, "%u.%u.%u.%u %d%s\n",
                        NIPQUAD(srun->addr.s_addr),
                        srun->port,
                        srun->is_backup ? " backup" : "");
  }

  return read_result(buffer, start, read_offset, buf_len, eof, write_len);
}

/**
 * Writes the srun configuration.  The srun file is a list of lines
 * like "ip port backup?"
 *
 * 127.0.0.1 6802
 * 127.0.0.1 6803 backup
 *
 * Currently, the write is only accepted if it's a complete block.  Partial
 * writes are ignored.
 *
 * @param file        named file
 * @param buffer      ascii buffer
 * @param count       how many bytes to write to the buffer
 * @param data        system data (resin_t)
 */
static int
resin_srun_write(struct file *file, const char *buffer,
                 unsigned long count, void *data)
{
  srun_t *srun = data;
  resin_t *resin = srun ? srun->resin : 0;
  int is_live = 0;
  struct in_addr addr;
  int port;
  int is_backup;
  
  int i, j;

  if (! resin || resin->is_dead || srun->session_index >= resin->srun_count)
    return 0;

  spin_lock_irq(&g_proc_lock);

  i = 0;
  
  is_live = 0;
  addr.s_addr = 0;
  port = 0;
  is_backup = 0;
    
  for (; i < count && (buffer[i] == ' ' || buffer[i] == '\t'); i++) {
  }

  /* blank line is a disabled srun */
  if (i >= count)
    goto done;

  if (buffer[i] == '\n') {
    if (srun->is_live)
      srun_close(srun);

    goto done;
  }

  addr.s_addr = 0;
  for (j = 0; j < 4; j++) {
    int segment = 0;
      
    if (i < count && buffer[i] == '.')
      i++;

    for (; i < count && buffer[i] >= '0' && buffer[i] <= '9'; i++)
      segment = 10 * segment + buffer[i] - '0';

    if (segment > 255)
      goto done;
      
    addr.s_addr = 256 * addr.s_addr + segment;
  }

  if ((addr.s_addr & 0xff000000) == 0 || (addr.s_addr & 0x000000ff) == 0)
    goto done;

  for (; i < count && (buffer[i] == ' ' || buffer[i] == '\t'); i++) {
  }

  port = 0;
  for (; i < count && buffer[i] >= '0' && buffer[i] <= '9'; i++)
    port = 10 * port + buffer[i] - '0';
    
  for (; i < count && (buffer[i] == ' ' || buffer[i] == '\t'); i++) {
  }

  if (i < count && buffer[i] == 'b')
    is_backup = 1;

  if (srun->is_live)
    srun_close(srun);
  srun_create(resin, srun->session_index, addr.s_addr, port);

  done:
  spin_unlock_irq(&g_proc_lock);
  
  return count;
}

/**
 * Returns the number of configured sruns configuration
 *
 * @param buffer      the read buffer (any guarantees on size?)
 * @param start       output start pointer into the buffer
 * @param read_offset user read offset into the buffer
 * @param buf_len     size of the buffer
 * @param eof         output set 1 if the user read everything
 * @param data        system data (resin_t)
 */
static int
resin_srun_count_read(char *buffer, char **start, off_t read_offset,
                      int buf_len, int *eof, void *data)
{
  int write_len;
  resin_t *resin = data;

  if (! resin || resin->is_dead) {
    *start = buffer;
    *eof = 1;
    
    return 0;
  }

  write_len = sprintf(buffer, "%d\n", resin->srun_count);

  return read_result(buffer, start, read_offset, buf_len, eof, write_len);
}

/**
 * Sets the number of sruns.
 *
 * @param file        named file
 * @param buffer      ascii buffer
 * @param count       how many bytes to write to the buffer
 * @param data        system data (resin_t)
 */
static int
resin_srun_count_write(struct file *file, const char *buffer,
                       unsigned long count, void *data)
{
  srun_t *srun = data;
  resin_t *resin = srun ? srun->resin : 0;
  
  int i;
  int srun_count;
  int has_digit = 0;
  int old_count;

  if (! srun || ! resin || resin->is_dead)
    return 0;

  spin_lock_irq(&g_proc_lock);

  old_count = resin->srun_count;

  i = 0;
  for (; i < count && (buffer[i] == ' ' || buffer[i] == '\t'); i++) {
  }

  srun_count = 0;
  has_digit = 0;
  for (; i < count && buffer[i] >= '0' && buffer[i] <= '9'; i++) {
    srun_count = 10 * srun_count + buffer[i] - '0';
    has_digit = 1;
  }

  for (;
       (i < count &&
        (buffer[i] == ' ' || buffer[i] == '\t' || buffer[i] == '\n'));
       i++) {
  }

  if (has_digit && i >= count && srun_count <= 64) {
    char buf[80];
    
    for (i = srun_count; i < old_count; i++) {
      sprintf(buf, "srun-%d", i);
      remove_proc_entry(buf, proc_net_resin);

      srun_close(&resin->srun_list[i]);      
    }

    srun_set_count(resin, srun_count);
    
    for (i = old_count; i < srun_count; i++) {
      struct proc_dir_entry *entry;
      
      sprintf(buf, "srun-%d", i);
      entry = create_proc_entry(buf, 0, proc_net_resin);
      
      if (entry) {
        entry->read_proc = resin_srun_read;
        entry->write_proc = resin_srun_write;
        entry->data = &resin->srun_list[i];
      }
    }
  }

  spin_unlock_irq(&g_proc_lock);
  
  return count;

}

/**
 * Initialize the /proc/net/resin directory
 */
void
resin_proc_init(resin_t *resin)
{
  LOG(("resind: registering /proc/net/resin\n"));
  
  if (! proc_net_resin) {
    struct proc_dir_entry *entry;
    char buf[80];
    int i;
    
    entry = create_proc_entry("net/resin", S_IFDIR, 0);
    if (entry) {
#ifdef MODULE
      // ent->fill_inode = rpc_modcount;
#endif
      proc_net_resin = entry;
    }
    
    entry = create_proc_entry("server_port", 0, proc_net_resin);
    if (entry) {
      entry->read_proc = resin_port_read;
      entry->data = resin;
    }
    
    entry = create_proc_entry("srun-count", 0, proc_net_resin);
    if (entry) {
      entry->data = resin;
      
      entry->read_proc = resin_srun_count_read;
      entry->write_proc = resin_srun_count_write;
    }

    for (i = 0; i < resin->srun_count; i++) {
      sprintf(buf, "srun-%d", i);
      
      entry = create_proc_entry(buf, 0, proc_net_resin);
      if (entry) {
        entry->data = &resin->srun_list[i];
        
        entry->read_proc = resin_srun_read;
        entry->write_proc = resin_srun_write;
      }
    }
  }
}

void
resin_proc_exit(resin_t *resin)
{
  LOG(("resind: unregistering /proc/net/resin\n"));
  
  if (proc_net_resin) {
    int i;
    
    remove_proc_entry("server_port", proc_net_resin);
    
    for (i = 0; i < resin->srun_count; i++) {
      char buf[80];
      
      sprintf(buf, "srun-%d", i);
      remove_proc_entry(buf, proc_net_resin);
    }
    
    proc_net_resin = 0;
    remove_proc_entry("net/resin", 0);
  }
}
