/*
 * Name: fo_nfs.c
 * Description: File operation functions that can be called from the
 *     NFS server stubs.
 * Author: Christian Starkjohann <cs@hal.kph.tuwien.ac.at>
 * Date: 1996-12-06
 * Copyright: GNU-GPL
 * Tabsize: 4
 */

#include "syshdr.h"
#include "psinode.h"
#include "smb_abstraction.h"
#include "my_defines.h"

#define	TRANSFER_BUFFER_SIZE	8192	/* NFS uses this size... */
#define	MAX_OPEN_FILES			20	/* keep at maximum this much files open */
#define	KEEP_OPEN_TIMEOUT		5	/* keep files max 5s long open */

#define	DPRINTF(arg)	if(debug_mode & DEBUG_FOPS)	debprintf arg
#define	DFPRINTF(arg)	if(debug_mode & DEBUG_FILE)	debprintf arg

/* ------------------------------------------------------------------------- */

#define	MODE_NONE	0
#define	MODE_READ	1
#define	MODE_WRITE	2
#define	MODE_ANY	2

/* ------------------------------------------------------------------------- */

typedef struct file_info{
	struct file_info	*next;
	struct file_info	**prevnext;	/* doubly linked list for LRU */
	long long			ino;		/* pseudo inode number */
	int					index;		/* index in hashtable */
	time_t				touched;	/* for automatic close timeout */
	int					mode;		/* opened for read or write */
	smba_file_t			*file;		/* information managed by smb-abstraction*/
}file_info_t;

/* ------------------------------------------------------------------------- */

file_info_t		file_buffer[MAX_OPEN_FILES];
list_t			file_buffer_lru;		/* LRU list for file buffers */
smba_server_t	*the_server;

/* ------------------------------------------------------------------------- */

static inline void	file_touch(file_info_t *p)
{
	p->touched = time(NULL);
	list_move_to_end(&file_buffer_lru, p);
}

/* ------------------------------------------------------------------------- */

static void	file_close(file_info_t *finfo)
{
	if(finfo->file != NULL){
		DFPRINTF(("closing inode %d\n", (int)finfo->ino));
		smba_close(finfo->file);
		finfo->file = NULL;
	}
}

/* ------------------------------------------------------------------------- */

file_info_t		*file_get_open(long long ino, int mode, int *errnum)
{
int			i, free_i = -1, importance;
file_info_t	*p, *least_important;
char		*path;

	*errnum = 0;
	for(i=0;i<MAX_OPEN_FILES;i++){
		if(file_buffer[i].file != NULL){
			if(file_buffer[i].ino == ino){
				if(rw_is_safe || file_buffer[i].mode == mode
					|| mode == MODE_NONE || file_buffer[i].mode == MODE_NONE){
					file_touch(&file_buffer[i]);
					if(mode != MODE_NONE)
						file_buffer[i].mode = mode;
					return &file_buffer[i];
				}else{
					file_close(&file_buffer[i]);
					free_i = i;
					break;
				}
			}
		}else{
			free_i = i;
		}
	}
	if(free_i < 0){
		/* no free entry, search for least important least recently used */
		least_important = p = file_buffer_lru.head;
		importance = 10;
		while(p != NULL){
			i = smba_file_importance(p->file);
			if(i < importance){
				least_important = p;
				if((importance = i) == 0)
					break;
			}
			p = p->next;
		}
		p = least_important;
		file_close(p);
	}else{
		p = &file_buffer[free_i];
	}
	if((i = psi_index(ino)) < 0){
		eprintf("file_get_open: requested inode not found\n");
		*errnum = -MY_NFSERR_STALE;
		return NULL;
	}
	path = psi_to_path(i, DOS_PATHSEP, NULL);
	if((*errnum = smba_open(the_server, path, &p->file)) == 0){
		DFPRINTF(("opening inode %d\n", (int)ino));
		p->ino = ino;
		p->index = i;
		file_touch(p);
		return p;
	}else{
		p->file = NULL;
		return NULL;
	}
}

/* ------------------------------------------------------------------------- */

static void	set_attr(smba_stat_t *smbattr, my_attr_t *attr)
{
	memset(smbattr, 0, sizeof(*smbattr));
	if(attr->mode != -1){
		if((attr->mode & S_IFMT) == S_IFDIR)
			smbattr->is_dir = 1;
		if((attr->mode & 0200) == 0 && allow_readonly)
			smbattr->is_wp = 1;
	}
	smbattr->atime = attr->atime;
	smbattr->mtime = attr->mtime;
	smbattr->ctime = attr->ctime;
	smbattr->size  = attr->size;
}

/* ------------------------------------------------------------------------- */

static void	get_attr(smba_stat_t *smbattr, my_attr_t *attr, int inum)
{
	attr->mode = smbattr->is_dir ? conf_dirmode : conf_filemode;
	if(smbattr->is_wp){
		attr->mode &= ~0222;
	}
	attr->mode |= smbattr->is_dir ? S_IFDIR : S_IFREG;
	attr->nlink = 1;
	attr->uid = conf_uid;
	attr->gid = conf_gid;
	attr->size = smbattr->is_dir && smbattr->size == 0 ?
					100000 : smbattr->size;		/* fake a directory size */
#ifdef BUFSIZ
	attr->blocksize = BUFSIZ;
#else
	attr->blocksize = 1024;
#endif
	attr->blocks = (attr->size + attr->blocksize - 1) / attr->blocksize;
	attr->fileid = inum;
	attr->atime = smbattr->atime;
	attr->mtime = smbattr->mtime;
	attr->ctime = smbattr->ctime;
	attr->rdev = 0;
}

/* ------------------------------------------------------------------------- */

static file_info_t		*file_info_lookup(long long ino)
{
int		i;
   
	for(i=0;i<MAX_OPEN_FILES;i++){
		if(file_buffer[i].file != NULL){
			if(file_buffer[i].ino == ino){
			   return &file_buffer[i];
			}
		}
	}
	return NULL;
}

/* ------------------------------------------------------------------------- */

static int	fo_new(int is_dir, fh_t *fh, my_attr_t *fa, fh_t dir,
												char *name, my_attr_t *sa)
{
int			errnum = 0;
file_info_t	*p, *p2;
int			dir_i;
long long	ino;
smba_stat_t	smbattr;

	if((p = file_get_open(dir, MODE_NONE, &errnum)) == NULL)
		return errnum;
	set_attr(&smbattr, sa);
	if(is_dir){
		errnum = smba_mkdir(p->file, name, &smbattr);
	}else{
		if((dir_i = psi_index(dir)) < 0)
			return MY_NFSERR_STALE;
	   	psi_lookup(dir_i, name, &ino);
		if((p2 = file_info_lookup(ino)) != NULL)
		   file_close(p2);
		errnum = smba_create(p->file, name, &smbattr);
	}
	if(errnum)
		return errnum;
	errnum = fo_lookup(fh, fa, dir, name);
	return errnum;
}

/* ------------------------------------------------------------------------- */

int	fo_create(fh_t *fh, my_attr_t *fa, fh_t dir, char *name, my_attr_t *sa)
{
	DPRINTF(("fo_create(ino=%d, name=%s)\n", (int)dir, name));
	return fo_new(0, fh, fa, dir, name, sa);
}

/* ------------------------------------------------------------------------- */

int	fo_mkdir(fh_t *fh, my_attr_t *fa, fh_t dir, char *name, my_attr_t *sa)
{
	DPRINTF(("fo_mkdir(ino=%d, name=%s)\n", (int)dir, name));
	return fo_new(1, fh, fa, dir, name, sa);
}

/* ------------------------------------------------------------------------- */

int	fo_getattr(my_attr_t *fa, fh_t fh)
{
int			errnum = 0;
file_info_t	*file;
smba_stat_t	smbattr;

	DPRINTF(("fo_getattr(ino=%d)\n", (int)fh));
	if((file = file_get_open(fh, MODE_NONE, &errnum)) == NULL)
		return errnum;
	if((errnum = smba_getattr(file->file, &smbattr)) != 0)
		return errnum;
	get_attr(&smbattr, fa, (int)fh);
	return errnum;
}

/* ------------------------------------------------------------------------- */

int	fo_setattr(my_attr_t *fa, fh_t fh, my_attr_t *sa)
{
int			errnum = 0;
file_info_t	*file;
smba_stat_t	smbattr;

	DPRINTF(("fo_setattr(ino=%d)\n", (int)fh));
	if((file = file_get_open(fh, MODE_NONE, &errnum)) == NULL)
		return errnum;
	if((errnum = smba_getattr(file->file, &smbattr)) != 0)
		return errnum;
	set_attr(&smbattr, sa);
	errnum = smba_setattr(file->file, &smbattr);
	get_attr(&smbattr, fa, (int)fh);
	return errnum;
}

/* ------------------------------------------------------------------------- */

int	fo_lookup(fh_t *fh, my_attr_t *fa, fh_t dir, char *name)
{
int			dir_i;

	DPRINTF(("fo_lookup(ino=%d, name=%s)\n", (int)dir, name));
	if((dir_i = psi_index(dir)) < 0)
		return MY_NFSERR_STALE;
	psi_lookup(dir_i, name, fh);
	return fo_getattr(fa, *fh);
}

/* ------------------------------------------------------------------------- */

struct readdir_stat{
	fh_t			dir_ino;
	my_direntry_t	**tail;
	int				maxlen;
	int				*eof;
};

/* ------------------------------------------------------------------------- */

static int	callback(void *d, int fpos, int nextpos, char *name, int eof)
{
struct readdir_stat	*rstat = d;
my_direntry_t		*dirent;
char				*n;
int					len, namelen;

	n = strrchr(name, DOS_PATHSEP);
	if(n == NULL)	/* root path! */
		n = name;
	else
		n++;
	namelen = strlen(n) + 1;
	len = namelen + sizeof(my_direntry_t);
	if(len > rstat->maxlen)
		return -1;
	dirent = calloc(1, sizeof(my_direntry_t));
	dirent->fh = (int)psi_inum(rstat->dir_ino, n);
	dirent->name = malloc(namelen);
	memcpy(dirent->name, n, namelen);
	dirent->cookie = nextpos;
	dirent->next = NULL;
	DPRINTF(("fo_readdir(): appended entry:\n"));
	DPRINTF(("\tfh=%d\n", dirent->fh));
	DPRINTF(("\tname=%s\n", dirent->name));
	DPRINTF(("\tcookie=%d\n", dirent->cookie));
	*rstat->tail = dirent;
	rstat->tail = &dirent->next;
	rstat->maxlen -= len;
	*rstat->eof = eof;
	return 0;
}

/* ------------------------------------------------------------------------- */

int	fo_readdir(my_direntry_t **result, int *eof, int max_bytes,
														fh_t dir, int cookie)
{
struct readdir_stat	rstat;
int					errnum = 0;
file_info_t			*f;

	if((f = file_get_open(dir, MODE_NONE, &errnum)) == NULL)
		return errnum;
	rstat.dir_ino = dir;
	rstat.tail = result;
	rstat.maxlen = max_bytes;
	rstat.eof = eof;
	*eof = 0;
	if(dir == root_inum && cookie == 0){	/* fake "." and ".." in root */
		if(fake_dot_in_root){
			callback(&rstat, -3, -2, ".", 0);
		}
		if(fake_dotdot_in_root){
			callback(&rstat, -2, -1, "..", 0);
		}
	}
	errnum = smba_readdir(f->file, cookie, &rstat, callback);
	if(errnum == 0)
		*eof = 1;
	if(errnum < 0)
		return errnum;
	return 0;
}

/* ------------------------------------------------------------------------- */

static int	fo_rm(int is_dir, fh_t dir, char *name)
{
int		dir_i, errnum = 0;
char	*path;

	DPRINTF(("fo_rm(ino=%d, name=%s)\n", (int)dir, name));
	if((dir_i = psi_index(dir)) < 0)
		return MY_NFSERR_STALE;
	path = psi_to_path(dir_i, DOS_PATHSEP, name);
	if(is_dir)
		errnum = smba_rmdir(the_server, path);
	else
		errnum = smba_remove(the_server, path);
	return errnum;
}

/* ------------------------------------------------------------------------- */

int	fo_remove(fh_t dir, char *name)
{
	return fo_rm(0, dir, name);
}

/* ------------------------------------------------------------------------- */

int	fo_rmdir(fh_t dir, char *name)
{
	return fo_rm(1, dir, name);
}

/* ------------------------------------------------------------------------- */

int	fo_rename(fh_t fromdir, char *fromname, fh_t todir, char *toname)
{
int		i, lold, errnum = 0;
char	*p, *old, *new;

	DPRINTF(("fo_rename(ino=%d, name=%s, ino=%d, name=%s)\n", (int)fromdir,
												fromname, (int)todir, toname));
	if((i = psi_index(fromdir)) < 0)
		return MY_NFSERR_STALE;
	p = psi_to_path(i, DOS_PATHSEP, fromname);
	if((i = psi_index(todir)) < 0)
		return MY_NFSERR_STALE;
	lold = strlen(p);
	old = malloc(lold + 1);
	memcpy(old, p, lold + 1);
	new = psi_to_path(i, DOS_PATHSEP, toname);
	errnum = smba_rename(the_server, old, new);
	free(old);
	return errnum;
}

/* ------------------------------------------------------------------------- */

int	fo_statfs(my_statfs_t *fsstat)
{
int		errnum = 0;

	DPRINTF(("fo_statfs()\n"));
	errnum = smba_statfs(the_server, &fsstat->bsize, &fsstat->blocks,
															&fsstat->bfree);
	if(errnum)
		return errnum;
	fsstat->type = 0;
	fsstat->bavail = fsstat->bfree;
	fsstat->files = -1;
	fsstat->ffree = -1;
	return 0;
}

/* ------------------------------------------------------------------------- */

int	fo_read(my_attr_t *fa, int *len, char **data, fh_t fh, int offs,
															int totalcount)
{
static char	transfer_buf[TRANSFER_BUFFER_SIZE];
file_info_t	*p;
smba_stat_t	smbattr;
int			errnum = 0;

	DPRINTF(("fo_read(ino=%d, offs=%d, len=%d)\n", (int)fh, offs, totalcount));
	if(totalcount > TRANSFER_BUFFER_SIZE)
		totalcount = TRANSFER_BUFFER_SIZE;
	*data = transfer_buf;
	if((p = file_get_open(fh, MODE_READ, &errnum)) == NULL)
		return errnum;
	if((errnum = smba_getattr(p->file, &smbattr)) < 0)
		return errnum;
	get_attr(&smbattr, fa, (int)p->ino);
	errnum = smba_read(p->file, transfer_buf, totalcount, offs);
	*len = errnum;	/* normally returns bytes read */
	DPRINTF(("fo_read() returns %d\n", errnum));
	if(errnum < 0)
		return errnum;
	return 0;
}

/* ------------------------------------------------------------------------- */

int	fo_write(my_attr_t *fa, fh_t fh, int offset, int totalcount, char *data)
{
file_info_t	*p;
smba_stat_t	smbattr;
int			errnum = 0;

	DPRINTF(("fo_write(ino=%d, offs=%d, len=%d)\n",(int)fh,offset,totalcount));
	if((p = file_get_open(fh, MODE_WRITE, &errnum)) == NULL)
		return errnum;
	if((errnum = smba_write(p->file, data, totalcount, offset)) < 0)
		return errnum;
	smba_touch(p->file);
	if((errnum = smba_getattr(p->file, &smbattr)) < 0)
		return errnum;
	get_attr(&smbattr, fa, (int)p->ino);
	DPRINTF(("fo_write() returns %d\n", errnum));
	return errnum;
}

/* ------------------------------------------------------------------------- */

int	fo_link(fh_t from, fh_t dir, char *name)
{
	return -EFBIG;
}

/* ------------------------------------------------------------------------- */

int	fo_readlink(char **path, fh_t fh)
{
	return -EFBIG;
}

/* ------------------------------------------------------------------------- */

int	fo_symlink(fh_t fromdir, char *fromname, char *topath, my_attr_t *sa)
{
	return -EFBIG;
}

/* ------------------------------------------------------------------------- */

int	fo_mount(char *server_ipname, char *server_name, char *client_name,
			char *service, char *root_path, char *username,
			char *password, int max_xmit, int port)
{
smba_connect_parameters_t	par;

	par.server_ipname = server_ipname;
	par.server_name = server_name;
	par.client_name = client_name;
	par.service = service;
	par.root_path = root_path;
	par.username = username;
	par.password = password;
	par.max_xmit = max_xmit;
	par.port = port;
	return smba_connect(&par, use_extended, &the_server);
}

/* ------------------------------------------------------------------------- */

void	fo_unmount(void)
{
int		i;

	for(i=0;i<MAX_OPEN_FILES;i++){
		if(file_buffer[i].file != NULL){
			file_close(&file_buffer[i]);
		}
	}
	smba_disconnect(the_server);
}

/* ------------------------------------------------------------------------- */

void	fo_regular(void)
{
int		i;
time_t	now = time(NULL);

	for(i=0;i<MAX_OPEN_FILES;i++){
		if(file_buffer[i].file != NULL){
			if((now - file_buffer[i].touched) > KEEP_OPEN_TIMEOUT){
				file_close(&file_buffer[i]);
			}
		}
	}
	smba_regular();
}

/* ------------------------------------------------------------------------- */

void	fo_init(void)
{
int		i;

	smba_init();
	list_init(&file_buffer_lru);
	for(i=0;i<MAX_OPEN_FILES;i++){
		list_append(&file_buffer_lru, &file_buffer[i]);
	}
}

/* ------------------------------------------------------------------------- */

