/* 
   Unix SMB/Netbios implementation.
   Version 1.9.
   VFS initialisation and support functions
   Copyright (C) Tim Potter 1999
   
   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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "includes.h"
#ifdef HAVE_LIBDL
#include <dlfcn.h>
#endif

extern int DEBUGLEVEL;

/* Some structures to help us initialise the vfs operations table */

struct vfs_syminfo {
    char *name;
    void *fptr;
};

/* Default vfs hooks.  WARNING: The order of these initialisers is
   very important.  They must be in the same order as defined in
   vfs.h.  Change at your own peril. */

struct vfs_ops default_vfs_ops = {

    /* Disk operations */        

    vfswrap_dummy_connect,
    vfswrap_dummy_disconnect,
    vfswrap_disk_free,

    /* Directory operations */

    vfswrap_opendir,
    vfswrap_readdir,
    vfswrap_mkdir,
    vfswrap_rmdir,
    vfswrap_closedir,

    /* File operations */

    vfswrap_open,
    vfswrap_close,
    vfswrap_read,
    vfswrap_write,
    vfswrap_lseek,
    vfswrap_rename,
    vfswrap_sync_file,
    vfswrap_stat,
    vfswrap_fstat,
    vfswrap_lstat,
    vfswrap_fcntl_lock,
    vfswrap_unlink,
    vfswrap_chmod,
    vfswrap_utime
};

/****************************************************************************
  initialise default vfs hooks
****************************************************************************/
int vfs_init_default(connection_struct *conn)
{
    DEBUG(3, ("Initialising default vfs hooks\n"));

    memcpy(&conn->vfs_ops, &default_vfs_ops, sizeof(conn->vfs_ops));
    return True;
}

/****************************************************************************
  initialise custom vfs hooks
****************************************************************************/
#ifdef HAVE_LIBDL
BOOL vfs_init_custom(connection_struct *conn)
{
    void *handle;
    struct vfs_ops *ops, *(*fptr)(struct vfs_options *options);

    DEBUG(3, ("Initialising custom vfs hooks from %s\n",
	      lp_vfsobj(SNUM(conn))));

    /* Open object file */

    handle = dlopen(lp_vfsobj(SNUM(conn)), RTLD_NOW);
    if (!handle) {
	DEBUG(0, ("Error opening %s: %s\n", lp_vfsobj(SNUM(conn)),
		  dlerror()));
	return False;
    }

    /* Get handle on vfs_init() symbol */

    fptr = dlsym(handle, "vfs_init");
    if (fptr == NULL) {
	DEBUG(0, ("No vfs_init() symbol found in %s\n", 
		  lp_vfsobj(SNUM(conn))));
	return False;
    }

    dlclose(handle);

    /* Initialise vfs_ops structure */

    if ((ops = fptr(lp_vfsoptions(SNUM(conn)))) == NULL) {
	return False;
    }

    /* Fill in unused operations with default (disk based) ones.
       There's probably a neater way to do this then a whole bunch of
       if statements. */ 

    memcpy(&conn->vfs_ops, ops, sizeof(conn->vfs_ops));
    
    if (conn->vfs_ops.connect == NULL) {
	conn->vfs_ops.connect = default_vfs_ops.connect;
    }

    if (conn->vfs_ops.disconnect == NULL) {
	conn->vfs_ops.disconnect = default_vfs_ops.disconnect;
    }

    if (conn->vfs_ops.disk_free == NULL) {
	conn->vfs_ops.disk_free = default_vfs_ops.disk_free;
    }

    if (conn->vfs_ops.opendir == NULL) {
	conn->vfs_ops.opendir = default_vfs_ops.opendir;
    }

    if (conn->vfs_ops.readdir == NULL) {
	conn->vfs_ops.readdir = default_vfs_ops.readdir;
    }

    if (conn->vfs_ops.mkdir == NULL) {
	conn->vfs_ops.mkdir = default_vfs_ops.mkdir;
    }

    if (conn->vfs_ops.rmdir == NULL) {
	conn->vfs_ops.rmdir = default_vfs_ops.rmdir;
    }

    if (conn->vfs_ops.closedir == NULL) {
	conn->vfs_ops.closedir = default_vfs_ops.closedir;
    }

    if (conn->vfs_ops.open == NULL) {
	conn->vfs_ops.open = default_vfs_ops.open;
    }

    if (conn->vfs_ops.close == NULL) {
	conn->vfs_ops.close = default_vfs_ops.close;
    }

    if (conn->vfs_ops.read == NULL) {
	conn->vfs_ops.read = default_vfs_ops.read;
    }
    
    if (conn->vfs_ops.write == NULL) {
	conn->vfs_ops.write = default_vfs_ops.write;
    }
    
    if (conn->vfs_ops.lseek == NULL) {
	conn->vfs_ops.lseek = default_vfs_ops.lseek;
    }
    
    if (conn->vfs_ops.rename == NULL) {
	conn->vfs_ops.rename = default_vfs_ops.rename;
    }
    
    if (conn->vfs_ops.sync == NULL) {
	conn->vfs_ops.sync = default_vfs_ops.sync;
    }
    
    if (conn->vfs_ops.stat == NULL) {
	conn->vfs_ops.stat = default_vfs_ops.stat;
    }
    
    if (conn->vfs_ops.fstat == NULL) {
	conn->vfs_ops.fstat = default_vfs_ops.fstat;
    }
    
    if (conn->vfs_ops.lstat == NULL) {
	conn->vfs_ops.lstat = default_vfs_ops.lstat;
    }
    
    if (conn->vfs_ops.lock == NULL) {
	conn->vfs_ops.lock = default_vfs_ops.lock;
    }
    
    if (conn->vfs_ops.unlink == NULL) {
	conn->vfs_ops.unlink = default_vfs_ops.unlink;
    }
    
    if (conn->vfs_ops.chmod == NULL) {
	conn->vfs_ops.chmod = default_vfs_ops.chmod;
    }
    
    if (conn->vfs_ops.utime == NULL) {
	conn->vfs_ops.utime = default_vfs_ops.utime;
    }
    
    return True;
}
#endif

/*******************************************************************
  check if a vfs file exists
********************************************************************/
BOOL vfs_file_exist(connection_struct *conn,char *fname,SMB_STRUCT_STAT *sbuf)
{
  SMB_STRUCT_STAT st;
  if (!sbuf) sbuf = &st;
  
  if (conn->vfs_ops.stat(fname,sbuf) != 0) 
    return(False);

  return(S_ISREG(sbuf->st_mode));
}

/****************************************************************************
  write data to a fd on the vfs
****************************************************************************/
ssize_t vfs_write_data(files_struct *fsp,char *buffer,size_t N)
{
  size_t total=0;
  ssize_t ret;
  int fd = fsp->fd_ptr->fd;

  while (total < N)
  {
    ret = fsp->conn->vfs_ops.write(fd,buffer + total,N - total);

    if (ret == -1) return -1;
    if (ret == 0) return total;

    total += ret;
  }
  return (ssize_t)total;
}

/****************************************************************************
transfer some data between two file_struct's
****************************************************************************/
SMB_OFF_T vfs_transfer_file(int in_fd, files_struct *in_fsp, 
			    int out_fd, files_struct *out_fsp,
			    SMB_OFF_T n, char *header, int headlen, int align)
{
  static char *buf=NULL;  
  static int size=0;
  char *buf1,*abuf;
  SMB_OFF_T total = 0;

  DEBUG(4,("vfs_transfer_file n=%.0f  (head=%d) called\n",(double)n,headlen));

  /* Check we have at least somewhere to read from */

  SMB_ASSERT((in_fd != -1) || (in_fsp != NULL));

  if (size == 0) {
    size = lp_readsize();
    size = MAX(size,1024);
  }

  while (!buf && size>0) {
    buf = (char *)Realloc(buf,size+8);
    if (!buf) size /= 2;
  }

  if (!buf) {
    DEBUG(0,("Can't allocate transfer buffer!\n"));
    exit(1);
  }

  abuf = buf + (align%8);

  if (header)
    n += headlen;

  while (n > 0)
  {
    int s = (int)MIN(n,(SMB_OFF_T)size);
    int ret,ret2=0;

    ret = 0;

    if (header && (headlen >= MIN(s,1024))) {
      buf1 = header;
      s = headlen;
      ret = headlen;
      headlen = 0;
      header = NULL;
    } else {
      buf1 = abuf;
    }

    if (header && headlen > 0)
    {
      ret = MIN(headlen,size);
      memcpy(buf1,header,ret);
      headlen -= ret;
      header += ret;
      if (headlen <= 0) header = NULL;
    }

    if (s > ret) {
      ret += in_fsp ? 
	  in_fsp->conn->vfs_ops.read(in_fsp->fd_ptr->fd,buf1+ret,s-ret) : read(in_fd,buf1+ret,s-ret);
    }

    if (ret > 0)
    {
	if (out_fsp) {
	    ret2 = out_fsp->conn->vfs_ops.write(out_fsp->fd_ptr->fd,buf1,ret);
	} else {
	    ret2= (out_fd != -1) ? write_data(out_fd,buf1,ret) : ret;
	}
    }

      if (ret2 > 0) total += ret2;
      /* if we can't write then dump excess data */
      if (ret2 != ret)
        vfs_transfer_file(in_fd, in_fsp, -1,NULL,n-(ret+headlen),NULL,0,0);

    if (ret <= 0 || ret2 != ret)
      return(total);
    n -= ret;
  }
  return(total);
}

/*******************************************************************
a vfs_readdir wrapper which just returns the file name
********************************************************************/
char *vfs_readdirname(connection_struct *conn, void *p)
{
	struct dirent *ptr;
	char *dname;

	if (!p) return(NULL);
  
	ptr = (struct dirent *)conn->vfs_ops.readdir(p);
	if (!ptr) return(NULL);

	dname = ptr->d_name;

#ifdef NEXT2
	if (telldir(p) < 0) return(NULL);
#endif

#ifdef HAVE_BROKEN_READDIR
	/* using /usr/ucb/cc is BAD */
	dname = dname - 2;
#endif

	{
		static pstring buf;
		memcpy(buf, dname, NAMLEN(ptr)+1);
		unix_to_dos(buf, True);
		dname = buf;
	}

	unix_to_dos(dname, True);
	return(dname);
}