/*
   Unix SMB/CIFS implementation.
   Make use of gpfs prefetch functionality
   Copyright (C) Volker Lendecke 2008
   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 3 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, see .
*/
#include "includes.h"
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_VFS
#include 
#include 
static int (*gpfs_fcntl_fn)(int fd, void *arg);
static int smbd_gpfs_fcntl(int fd, void *arg)
{
	static void *libgpfs_handle = NULL;
	DEBUG(10, ("smbd_gpfs_fcntl called for %d\n", fd));
	if (gpfs_fcntl_fn == NULL) {
		libgpfs_handle = sys_dlopen("libgpfs.so", RTLD_LAZY);
		if (libgpfs_handle == NULL) {
			DEBUG(10, ("sys_dlopen for libgpfs failed: %s\n",
				   strerror(errno)));
			return;
		}
		gpfs_fcntl_fn = sys_dlsym(libgpfs_handle, "gpfs_fcntl");
		if (gpfs_fcntl_fn == NULL) {
			DEBUG(3, ("libgpfs.so does not contain the symbol "
				  "'gpfs_fcntl'\n"));
			errno = ENOSYS;
			return -1;
		}
	}
	return gpfs_fcntl_fn(fd, arg);
}
struct gpfs_prefetch_config {
	name_compare_entry *namelist;
	size_t size;
};
struct gpfs_prefetch_hints {
	blksize_t st_blksize;
	/*
	 * The current center around which config->size bytes are
	 * prefetched
	 */
	SMB_OFF_T center;
};
static void gpfs_prefetch_recenter(vfs_handle_struct *handle,
				   files_struct *fsp,
				   SMB_OFF_T offset, size_t size,
				   struct gpfs_prefetch_hints *hints)
{
	int ret;
	SMB_OFF_T new_center;
	struct {
		gpfsFcntlHeader_t hdr;
		gpfsMultipleAccessRange_t acc;
	} arg;
	if (hints->st_blksize == 0) {
		SMB_STRUCT_STAT sbuf;
		if (SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf) == -1) {
			return;
		}
		DEBUG(10, ("gpfs_prefetch_recenter: st_blksize = %d\n",
			   (int)sbuf.st_blksize));
		hints->st_blksize = sbuf.st_blksize;
	}
	new_center = (offset > size) ? offset : 0;
	DEBUG(10, ("gpfs_prefetch_recenter: size=%d, offset=%d, "
		   "old_center=%d, new_center=%d\n", (int)size, (int)offset,
		   (int)hints->center, (int)new_center));
	ZERO_STRUCT(arg);
	arg.hdr.totalLength = sizeof(arg);
	arg.hdr.fcntlVersion = GPFS_FCNTL_CURRENT_VERSION;
	arg.hdr.fcntlReserved = 0;
	arg.acc.structLen = sizeof(arg.acc);
	arg.acc.structType = GPFS_MULTIPLE_ACCESS_RANGE;
	arg.acc.accRangeCnt = 1;
	arg.acc.relRangeCnt = 1;
	arg.acc.accRangeArray[0].blockNumber = new_center/hints->st_blksize;
	arg.acc.accRangeArray[0].start = 0;
	arg.acc.accRangeArray[0].length = size;
	arg.acc.accRangeArray[0].isWrite = 0;
	arg.acc.relRangeArray[0].blockNumber = hints->center/hints->st_blksize;
	arg.acc.relRangeArray[0].start = 0;
	arg.acc.relRangeArray[0].length = size;
	arg.acc.relRangeArray[0].isWrite = 0;
	ret = smbd_gpfs_fcntl(fsp->fh->fd, &arg);
	if (ret == -1) {
		DEBUG(5, ("gpfs_fcntl returned %s\n", strerror(errno)));
	}
	hints->center = new_center;
}
static ssize_t gpfs_prefetch_pread(vfs_handle_struct *handle,
				   files_struct *fsp, void *data,
				   size_t n, SMB_OFF_T offset)
{
	struct gpfs_prefetch_config *config =
		(struct gpfs_prefetch_config *)handle->data;
	struct gpfs_prefetch_hints *hints = (struct gpfs_prefetch_hints *)
		VFS_FETCH_FSP_EXTENSION(handle, fsp);
	SMB_OFF_T out_of_center;
	/*
	 * How far away from the center of the prefetch region is the
	 * request?
	 */
	out_of_center = (offset > hints->center)
		? (offset - hints->center) : (hints->center - offset);
	DEBUG(10, ("gpfs_prefetch_pread: n=%d, offset=%d, center=%d, "
		   "out_of_center=%d, size=%d\n", (int)n, (int)offset,
		   (int)hints->center, (int)out_of_center,
		   (int)config->size));
	/*
	 * Are we completely out of the prefetch range or less than
	 * 10% at its borders?
	 */
	if ((out_of_center > config->size)
	    || ((config->size - out_of_center) * 10 < config->size)) {
		/*
		 * Re-center the prefetch area
		 */
		gpfs_prefetch_recenter(handle, fsp, offset, config->size,
				       hints);
	}
	return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
}
static int gpfs_prefetch_open(vfs_handle_struct *handle,  const char *fname,
			      files_struct *fsp, int flags, mode_t mode)
{
	int fd, ret;
	struct gpfs_prefetch_hints *hints;
	struct gpfs_prefetch_config *config =
		(struct gpfs_prefetch_config *)handle->data;
	struct {
		gpfsFcntlHeader_t hdr;
		gpfsAccessRange_t acc;
	} arg;
	DEBUG(10, ("gpfs_prefetch_open called for %s, config=%p, "
		   "config->namelist = %p, config->size=%d\n", fname,
		   config, config->namelist, (int)config->size));
	if (!is_in_path(fname, config->namelist,
			handle->conn->case_sensitive)) {
		DEBUG(10, ("gpfs_prefetch_open not in list: %s\n", fname));
		return SMB_VFS_NEXT_OPEN(handle, fname, fsp, flags, mode);
	}
	hints = (struct gpfs_prefetch_hints *)VFS_ADD_FSP_EXTENSION(
		handle, fsp, struct gpfs_prefetch_hints);
	if (hints == NULL) {
		errno = ENOMEM;
		return -1;
	}
	fd = SMB_VFS_NEXT_OPEN(handle, fname, fsp, flags, mode);
	if (fd == -1) {
		VFS_REMOVE_FSP_EXTENSION(handle, fsp);
		return -1;
	}
	arg.hdr.totalLength = sizeof(arg);
	arg.hdr.fcntlVersion = GPFS_FCNTL_CURRENT_VERSION;
	arg.hdr.fcntlReserved = 0;
	arg.acc.structLen = sizeof(arg.acc);
	arg.acc.structType = GPFS_ACCESS_RANGE;
	arg.acc.start = 0;
	arg.acc.length = 1;
	arg.acc.isWrite = 0;
	ret = smbd_gpfs_fcntl(fd, &arg);
	if (ret == -1) {
		DEBUG(5, ("gpfs_fcntl returned %s\n", strerror(errno)));
	}
	hints->st_blksize = 0;
	hints->center = 0;
	return fd;
}
static void gpfs_prefetch_config_free(void **data)
{
	struct gpfs_prefetch_config **config =
		(struct gpfs_prefetch_config **)data;
	free_namearray((*config)->namelist);
	TALLOC_FREE(*config);
}
static int gpfs_prefetch_connect(struct vfs_handle_struct *handle,
				 const char *service,
				 const char *user)
{
	struct gpfs_prefetch_config *config;
	const char *mask;
	config = talloc(handle, struct gpfs_prefetch_config);
	if (config == NULL) {
		DEBUG(0, ("talloc failed\n"));
		errno = ENOMEM;
		return -1;
	}
	mask = lp_parm_const_string(SNUM(handle->conn), "gpfs_prefetch",
				    "mask", "");
	set_namearray(&config->namelist, mask);
	config->size = lp_parm_int(SNUM(handle->conn), "gpfs_prefetch",
				   "size", 1024);
	/*
	 * The size calculations in the core routines assume that
	 * config->size is the size from the center to the border of
	 * the prefetched area. So we need to multiply by 1024/2 here
	 * to get the whole prefetch area in kilobytes.
	 */
	config->size *= 1024/2;
	SMB_VFS_HANDLE_SET_DATA(handle, config, gpfs_prefetch_config_free,
				struct gpfs_prefetch_config, goto fail);
	return SMB_VFS_NEXT_CONNECT(handle, service, user);
fail:
	free_namearray(config->namelist);
	TALLOC_FREE(config);
	return -1;
}
/* VFS operations structure */
static vfs_op_tuple gpfs_prefetch_op_tuples[] = {
	{SMB_VFS_OP(gpfs_prefetch_open),	SMB_VFS_OP_OPEN,
	 SMB_VFS_LAYER_TRANSPARENT },
	{SMB_VFS_OP(gpfs_prefetch_pread),	SMB_VFS_OP_PREAD,
	 SMB_VFS_LAYER_TRANSPARENT },
	{SMB_VFS_OP(gpfs_prefetch_connect),	SMB_VFS_OP_CONNECT,
	 SMB_VFS_LAYER_TRANSPARENT },
        { SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP }
};
/*
 * When done properly upstream (GPL issue resolved), change this
 * routine name to vfs_gpfs_prefetch_init!!
 */
NTSTATUS init_samba_module(void);
NTSTATUS init_samba_module(void)
{
	NTSTATUS status;
	DEBUG(10, ("vfs_gpfs_prefetch_init called\n"));
	status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "gpfs_prefetch",
				  gpfs_prefetch_op_tuples);
	DEBUG(10, ("smb_register_vfs returned %s\n",
		   nt_errstr(status)));
	return status;
}