#include "indian.hpp"
#include "netfile.hpp"
#include "../inc/netface.hpp"
#include "engine.hpp"


nfs_client *first_nfs_client=NULL;
remote_file *remote_file_list=NULL;
char default_fs_name[256];    // default file server name (set with parm -fs)

nfs_client::nfs_client(net_socket *sock, int file_fd, nfs_client *next) : 
  sock(sock),file_fd(file_fd),next(next),size_to_read(0)
{ 
  sock->read_selectable();
}  


nfs_client::~nfs_client() 
{ 
  delete sock;
  if (file_fd>=0)
    close(file_fd);
}

void secure_filename(char *filename, char *mode)
{
  if (!no_security)
  {    
    if (filename[0]=='/') { filename[0]=0; return ; }
    int level=0;
    char *f=filename;
    while (*f)
    {
      if (*f=='/') { f++; level++; }
      else if (*f=='.' && f[1]=='.')
      {
	if (f[3]=='.') while (*f!='.') f++;
	else
	{
	  f+=2;
	  level--;
	}
      } else f++; 
      
    }
    if (level<0)
      filename[0]=0;
  }
}


int local_address(char *server_name)    // returns 1 if server name is ourself
{
  struct hostent *hn=gethostbyname(server_name);    // first check to see if this address is 127.0.0.1
  if (!hn) return 0;                                // if bad server_name, return false
  char **ip_address=hn->h_addr_list;
  while (*ip_address) 
  {
    char *a=*ip_address;
    if (a[0]==127 && a[1]==0 && a[2]==0 && a[3]==1)
      return 1;
    ip_address++;
  }
  char server_ip[4];
  memcpy(server_ip,hn->h_addr_list,4);

  char my_name[100];                              // now check to see if this address is 'hostname'
  gethostname(my_name,100);
  struct hostent *l_hn=gethostbyname(my_name);  
  char **l_ip_address=l_hn->h_addr_list;

  while (*l_ip_address)  // local ip_address
  {
    char *a=*l_ip_address;         // scan through all local ip's
    ip_address=hn->h_addr_list;
    while (*ip_address)            // scan through all ip's for server_name
    {
      char *b=server_ip;
      if (a[0]==b[0] && a[1]==b[1] && a[2]==b[2] && a[3]==b[3])    // check for match
        return 1;
      ip_address++;
    }
    l_ip_address++;
  }
  return 0;       // didn't match localhost nor hostname, must be somewhere else
}

int nfs_client::send_read()   // return 0 if failure on socket, not failure to read
{
  if (file_fd>=0 && socket_fd>=0)
  {
    // first make sure the socket isn't 'full'

    struct timeval tv={0,0};     // don't wait
    fd_set write_check;  
    FD_ZERO(&write_check);  
    FD_SET(socket_fd,&write_check);     
    select(FD_SETSIZE,NULL,&write_check,NULL,&tv);

    if (FD_ISSET(socket_fd,&write_check))            // ready to write?
    {
      char buf[READ_PACKET_SIZE];   
      short read_total;
      short actual;

      do
      {      
	read_total=size_to_read>(READ_PACKET_SIZE-2) ? (READ_PACKET_SIZE-2) : size_to_read;
	actual=read(file_fd,buf,read_total);
	actual=lstl(actual);
	if (write(socket_fd,&actual,sizeof(actual))!=sizeof(actual))
	{
	  fprintf(stderr,"write failed\n");
	  return 0;
	}
	actual=lstl(actual);

	int write_amount=write(socket_fd,buf,actual);
	if (write_amount!=actual) 
	{
	  fprintf(stderr,"write failed\n");
	  return 0;
	}

	size_to_read-=actual;

	FD_ZERO(&write_check);  
	FD_SET(socket_fd,&write_check);     
	select(FD_SETSIZE,NULL,&write_check,NULL,&tv);

	if (!FD_ISSET(socket_fd,&write_check))
	{
	  FD_SET(socket_fd,&master_write_set);      // socket is full, wait for it empty
	  FD_CLR(socket_fd,&master_set);            // don't check for reading or process commands till read is done
	  return 1;    // not ok to write anymore, try again latter
	}

      } while (size_to_read && actual==read_total);
      size_to_read=0;
      FD_CLR(socket_fd,&master_write_set);       // don't check this socket for write ok
      FD_SET(socket_fd,&master_set);             // check it for reading
      return 1;
    } else 
    {
      FD_SET(socket_fd,&master_write_set);      // socket is full, wait for it empty
      FD_CLR(socket_fd,&master_set);            // don't check for reading or process commands till read is done
      return 1;
    }
  }
  return 0;
}


int process_nfs_command(nfs_client *c)
{
  char cmd;
  if (read(c->socket_fd,&cmd,1)!=1) return 0;
  switch (cmd)
  {
    case NFCMD_READ :
    {
      long size;
      if (read(c->socket_fd,&size,sizeof(size))!=sizeof(size)) return 0;
      size=lltl(size);

      c->size_to_read=size;
      return c->send_read();
    } break;
    case NFCMD_CLOSE :
    {
      return 0;
    } break;
    case NFCMD_SEEK :
    {
      long offset;
      if (read(c->socket_fd,&offset,sizeof(offset))!=sizeof(offset)) return 0;
      offset=lltl(offset);
      offset=lseek(c->file_fd,offset,0);
      offset=lltl(offset);
      if (write(c->socket_fd,&offset,sizeof(offset))!=sizeof(offset)) return 0;
      return 1;
    } break;
    case NFCMD_TELL :
    {
      long offset=lseek(c->file_fd,0,SEEK_CUR);
      offset=lltl(offset);
      if (write(c->socket_fd,&offset,sizeof(offset))!=sizeof(offset)) return 0;
      return 1;
    } break;
    
    default :
    { fprintf(stderr,"net driver : bad command from nfs client\n");
      return 0;
    }
  } 
}



void add_nfs_client(int fd)
{
  uchar size[2];
  char filename[300],mode[20],*mp;
  if (read(fd,size,2)!=2) { close(fd); return ; }
  if (read(fd,filename,size[0])!=size[0]) { close(fd); return ; }
  if (read(fd,mode,size[1])!=size[1]) { close(fd); return ; }
 
  fprintf(stderr,"remote request for %s ",filename);

  secure_filename(filename,mode);  // make sure this filename isn't a security risk
  if (filename[0]==0) { fprintf(stderr,"(denied)\n"); close(fd); return ; }

  mp=mode;
  int flags=0;

  while (*mp)
  {
    if (*mp=='w') flags|=O_CREAT|O_RDWR;
    else if (*mp=='r') flags|=O_RDONLY;
    mp++;
  }
      
  int f=open(filename,flags,S_IRWXU | S_IRWXG | S_IRWXO);
  
  if (f<0) 
  {
    fprintf(stderr,"(not found)\n");
    f=-1;  // make sure this is -1
  }

  long ret=lltl(f);
  if (write(fd,&ret,sizeof(ret))!=sizeof(ret)) { close(fd); return ; }

  if (f<0)    // no file, sorry
    close(fd);
  else
  {
    long cur_pos=lseek(f,0,SEEK_CUR);
    long size=lseek(f,0,SEEK_END);
    lseek(f,cur_pos,SEEK_SET);
    size=lltl(size);
    if (write(fd,&size,sizeof(size))!=sizeof(size)) {  close(f); close(fd); return ; }

    first_nfs_client=new nfs_client(fd,f,first_nfs_client);
    first_nfs_client->size=size;
  }
}

void remote_file::r_close(char *reason) 
{ 
  if (reason)
    fprintf(stderr,"remote_file : %s\n",reason);
  if (socket_fd>=0) 
  {
    uchar cmd=NFCMD_CLOSE;
    write(socket_fd,&cmd,1);
    close(socket_fd); 
  }
  socket_fd=-1; 
}

remote_file::remote_file(char *filename, char *mode, remote_file *Next)
{
  next=Next;
  open_local=0;

  socket_fd=connect_to_server(filename);
  if (socket_fd==-1)
  { 
    fprintf(stderr,"unable to connect\n");
    return ;
  }

  uchar sizes[3]={CLIENT_NFS,strlen(filename)+1,strlen(mode)+1};
  if (write(socket_fd,sizes,3)!=3) { r_close("could not send open info"); return ; }
  if (write(socket_fd,filename,sizes[1])!=sizes[1]) { r_close("could not send filename"); return ; }
  if (write(socket_fd,mode,sizes[2])!=sizes[2]) { r_close("could not send mode"); return ; }

  long remote_file_fd;
  if (read(socket_fd,&remote_file_fd,sizeof(remote_file_fd))!=sizeof(remote_file_fd)) 
  { r_close("could not read remote fd"); return ; }   
  remote_file_fd=lltl(remote_file_fd);
  if (remote_file_fd<0) { r_close("remote fd is bad"); return ; }

  if (read(socket_fd,&size,sizeof(size))!=sizeof(size)) { r_close("could not read remote filesize"); return ; } 
//  ulong remote_crc;
//  if (read(socket_fd,&remote_crc,sizeof(remote_crc))!=sizeof(remote_crc)) { r_close("could not read remote checksum"); return ; }
//  ulong local_crc=

  size=lltl(size);
}

int remote_file::unbuffered_read(int out_fd, size_t count)
{
  if (socket_fd>=0 && count)
  {
    uchar cmd=NFCMD_READ;
    if (write(socket_fd,&cmd,sizeof(cmd))!=sizeof(cmd)) { r_close("read : could not send command"); return 0; }

    long rsize=lltl(count);
    if (write(socket_fd,&rsize,sizeof(rsize))!=sizeof(rsize)) { r_close("read : could not send size"); return 0; }

    long total_read=0,total;
    char buf[READ_PACKET_SIZE];
    ushort size;

    ushort packet_size;    
    do
    {
      if (read(socket_fd,&packet_size,sizeof(packet_size))!=sizeof(packet_size)) 
      {
	fprintf(stderr,"could not read packet size\n");
	return 0;
      }
      packet_size=lstl(packet_size);

      ushort size_read=read(socket_fd,buf+2,packet_size);   

      if (size_read!=packet_size) 
      { 
	if (read(socket_fd,buf+2+size_read,packet_size-size_read)!=packet_size-size_read)
	{
	  fprintf(stderr,"incomplete packet\n"); 
	  return 0; 
	}
      }

      *((short *)buf)=packet_size;
      if (write(out_fd,buf,packet_size+2)!=packet_size+2) comm_failed();

      total_read+=packet_size;
      count-=packet_size;
    } while (packet_size==READ_PACKET_SIZE-2 && count);     
    return total_read;
  }
  return 0;
}

int remote_file::unbuffered_tell()   // ask server where the offset of the file pointer is
{
  if (socket_fd>=0)
  {
    uchar cmd=NFCMD_TELL;
    if (write(socket_fd,&cmd,sizeof(cmd))!=sizeof(cmd)) { r_close("tell : could not send command"); return 0; }

    long offset;
    if (read(socket_fd,&offset,sizeof(offset))!=sizeof(offset)) { r_close("tell : could not read offset"); return 0; }    
    return lltl(offset);
  }    
  return 0;
}

int remote_file::unbuffered_seek(long offset)  // tell server to seek to a spot in a file
{
  if (socket_fd>=0)
  {
    uchar cmd=NFCMD_SEEK;
    if (write(socket_fd,&cmd,sizeof(cmd))!=sizeof(cmd)) { r_close("seek : could not send command"); return 0; }

    long off=lltl(offset);
    if (write(socket_fd,&off,sizeof(off))!=sizeof(off)) { r_close("seek : could not send offset"); return 0; }

    if (read(socket_fd,&offset,sizeof(offset))!=sizeof(offset)) { r_close("seek : could not read offset"); return 0; }    
    return lltl(offset);
  }    
  return 0;
}

int open_file(char *&filename, char *mode)
{
  if (filename[0]!='/' && filename[1]!='/' && default_fs_name[0])   // default file server?
  {
    char tmp_fn[500];  
    sprintf(tmp_fn,"//%s/%s",default_fs_name,filename);
    strcpy(filename,tmp_fn);
  }

  if (filename[0]=='/' && filename[1]=='/')   // passive server file reference?
  {
    filename+=2;
    remote_file *rf=new remote_file(filename,mode,remote_file_list);
    if (rf->open_failure())
    {
      delete rf;
      return -1;
    }
    else 
    {
      remote_file_list=rf;
      return rf->socket_fd;
    }      
  }

  secure_filename(filename,mode);
  if (filename[0]==0) return -1;

  int flags=0;
  while (*mode)
  {
    if (*mode=='w') flags|=O_CREAT|O_RDWR;
    else if (*mode=='r') flags|=O_RDONLY;
    mode++;
  }

  int f=open(filename,flags,S_IRWXU | S_IRWXG | S_IRWXO);
  if (f>=0)
  { close(f);
    return -2;
  }
  
  return -1;
}

remote_file *find_rfile(int fd)
{
  remote_file *r=remote_file_list;
  for (;r && r->socket_fd!=fd;r=r->next)
  {
    if (r->socket_fd==-1)
    {
      fprintf(stderr,"bad sock\n");
    }
  }
  return r;
}

void unlink_remote_file(remote_file *rf)
{
  if (rf==remote_file_list)
    remote_file_list=rf->next;
  else
  {
    remote_file *last=remote_file_list;
    while (last->next && last->next!=rf) last=last->next;
    last->next=rf->next;
  }
}

remote_file::~remote_file()
{ r_close(NULL); }


int fetch_crcs(char *server)
{
  int socket_fd=connect_to_server(server);
  if (socket_fd==-1)
  { 
    fprintf(stderr,"unable to connect\n");
    return 0;
  }

  uchar cmd=CLIENT_CRC_WAITER;
  if (write(socket_fd,&cmd,1)!=1)  { close(socket_fd); return 0; }
  if (read(socket_fd,&cmd,1)!=1)  { close(socket_fd); return 0; }
  close(socket_fd);
  return cmd;
  
}


