/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include	<limits.h>
#include	<pthread.h>

#include	"machine/include.h"
#include	"utils.h"
#include	"u_file.h"
#include	"task.h"
#include	"lock_level.h"
#include	"s_buf.h"
#include	"machine/fork_lock.h"
#include	"pri_level.h"

#define UFR_LA_INTERVAL		1800
#define UFR_LA_LIFE_TIME	3600
//#define UFR_LOG

int _check_own(int * uid,char * path);
int check_own(int * uid,char * path);
UFR_BLK * _ufr_search_blk_by_uid(int uid);
UFR_BLK * _ufr_search_blk_by_fd(int fd);
UFR_ERR _ufr_create_blk(UFR_BLK **ret,int uid);
int ufr_do(ssize_t (*func)(),int fd,void * data,int len);
int _ufr_recv(	UFR_BLK * blk,
		UFR_REQ * rq);
void ufr_last_access_task();
int create_proc;
void __u_cp(char * pos,int line);
int ufr_point;
int ufr_point2;

SEM	ufr_lock;
UFR_BLK *	ufr_blk_list;

UFR_TBL ufr_tbl_sv = {
	{0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,

	 check_own,
	 __ufr_call,
	__u_cp}
};


void
init_u_fileroot()
{
	ufr_lock = new_lock(LL_UFR);

	if ( geteuid() == 0 ) {
		ufr_dispatch = &ufr_tbl_sv;
		create_task(ufr_last_access_task,0,PRI_NETWORK);
	}

/*
printf("DISPATCH = %x (%i)\n",(int)ufr_dispatch,(int)getpid());
*/
}

int ufr_do(ssize_t (*func)(),int fd,void * d,int len)
{
int er;
char * data;
int ret;

	if( len < 0 )
		er_panic("ufr_do\n");

	data = (char*)d;
	ret = 0;
	for ( ; len ; ) {
	retry:
		er = (*func)(fd,data,len);
/*
printf("UFR_DO %i(%x:r-%x:-w%x) %i - %i\n",
(int)getpid(),
(int)func,(int)read,(int)write,
len,er);
*/
		if ( er == 0 )
			break; 
		if ( er < 0 ) {
			switch ( errno ) {
			case EINTR:
				goto retry;
			case EAGAIN:
				sleep_sec(2);
				goto retry;
			}
			break;
		}
		len -= er;
		data += er;
		ret += er;
	}
	if ( len == 0 )
		return ret;
	return -1;
}

void
free_log_list(UFR_LOG * lg)
{
UFR_LOG * lg2;
	for ( ; lg ; ) {
		lg2 = lg->next;
		free(lg);
		lg = lg2;
	}
}

void
ufr_last_access_task()
{
UFR_BLK * blk,** bp;
int t;
int er;
	for ( ; ; ) {
		sleep_sec(UFR_LA_INTERVAL);

		t = get_xltime();
		lock_task(ufr_lock);
		for ( bp = &ufr_blk_list ; *bp ; ) {
			blk = *bp;
			if ( blk->send < 0 )
				goto gc;
			if ( t - blk->create_time
					< UFR_LA_LIFE_TIME )
				goto gc1;
			if ( t - blk->last_time 
					< UFR_LA_INTERVAL )
				goto next;
		gc1:
			if ( blk->transaction )
				goto next;
		gc:
			if ( blk->req_queue )
				goto next;
			*bp = blk->next;
		r1:
			er = close(blk->send);
			if ( er < 0 && errno == EINTR )
				goto r1;
		r2:
			er = close(blk->recv);
			if ( er < 0 && errno == EINTR )
				goto r2;
			free_log_list(blk->send_log);
			free_log_list(blk->recv_log);
			free(blk);
			continue;
		next:
			bp = &(*bp)->next;
		}
		unlock_task(ufr_lock,"ufr_last_access_task");
	}
}

void
check_blk_req_nos(UFR_BLK * blk)
{
UFR_REQ * r;
	printf("BLK[%i](",(int)getpid());
	for ( r = blk->req_queue ; r ; r = r->next )
		printf("%x,",(int)r);
	printf(")\n");
}


UFR_BLK *
_ufr_search_blk_by_uid(int uid)
{
UFR_BLK * ret;
	for ( ret = ufr_blk_list ; ret ; ret = ret->next )
		if ( ret->uid == uid )
			return ret;
	return 0;
}

UFR_BLK *
_ufr_search_blk_by_fd(int fd)
{
UFR_BLK * ret;
	fd = (fd>>16)&0xffff;
	for ( ret = ufr_blk_list ; ret ; ret = ret->next )
		if ( ret->send == fd )
			return ret;
	return 0;
}

UFR_ERR 
_ufr_create_blk(UFR_BLK **blkp,int uid)
{
int send[2];
int recv[2];
UFR_BLK * blk;
UFR_ERR ret;
int id;
UFR_REQ rq;
int i;
UFR_REQ ** rqp;
char * ex_argv[3];
char buf1[10];
char buf2[10];
int seq;
int er;

	for ( ; pipe(send) < 0 ; )
		sleep_sec(1);
	for ( ; pipe(recv) < 0 ; )
		sleep_sec(1);
	wlock_fork();
	id = fork();
	if ( id == 0 ) {
		for ( i = 3 ; i < sys_param.max_fd ; i ++ ) {
			if ( send[0] == i )
				continue;
			if ( recv[1] == i )
				continue;
		r1:
			er = close(i);
			if ( er < 0 && errno == EINTR )
				goto r1;
		}
		if ( seteuid(uid) < 0 ) {
			ret.ufr_code = UFR_E_PERM;
			ret.r.sys_code = errno;
			seq = 0;
			ufr_do(write,recv[1],&seq,sizeof(seq));
			ufr_do(write,recv[1],&ret,sizeof(ret));
			ufr_do(write,recv[1],&seq,sizeof(seq));
		r2:
			er = close(recv[1]);
			if ( er < 0 && errno == EINTR )
				goto r2;
		r3:
			er = close(send[0]);
			if ( er < 0 && errno == EINTR )
				goto r3;
			exit(1);
		}

		sprintf(buf1,"%i",send[0]);
		sprintf(buf2,"%i",recv[1]);

		ex_argv[0] = "xlufr";
		ex_argv[1] = &buf1[0];
		ex_argv[2] = &buf2[0];
		ex_argv[3] = 0;
		pthread_detach((pthread_t)_get_tid().tid);

		execvp(ex_argv[0],ex_argv);
		exit(1);
	}
	wunlock_fork();
	if ( id < 0 ) {
	r4:
		er = close(send[0]);
		if ( er < 0 && errno == EINTR )
			goto r4;
	r5:
		er = close(recv[0]);
		if ( er < 0 && errno == EINTR )
			goto r5;
	r6:
		er = close(send[1]);
		if ( er < 0 && errno == EINTR )
			goto r6;
	r7:
		er = close(recv[1]);
		if ( er < 0 && errno == EINTR )
			goto r7;
		ret.ufr_code = UFR_E_FORK;
		ret.r.sys_code = errno;
	}
	else {
		blk = malloc(sizeof(*blk));
		memset(blk,0,sizeof(*blk));
		blk->send = send[1];
		blk->recv = recv[0];
		blk->uid = uid;
		blk->create_time = get_xltime();

		ret.ufr_code = 0;

	r8:
		er = close(send[0]);
		if ( er < 0 && errno == EINTR )
			goto r8;
	r9:
		er = close(recv[1]);
		if ( er < 0 && errno == EINTR )
			goto r9;

		memset(&rq,0,sizeof(rq));
		rq.seq = 0;
		blk->seq = 1;
		rq.argc = 0;
		rq.argv = 0;
		blk->req_queue = &rq;
		blk->read_op = 1;
		_ufr_recv(blk,&rq);
		blk->read_op = 0;
		for ( rqp = &blk->req_queue ; 
				*rqp && (*rqp) != &rq ;
				rqp = &(*rqp)->next );
		if ( *rqp == 0 )
			er_panic("create_blk");
		*rqp = rq.next;


		if ( rq.ret.ufr_code ) {
		r10:
			er = close(send[1]);
			if ( er < 0 && errno == EINTR )
				goto r10;
		r11:
			er = close(recv[0]);
			if ( er < 0 && errno == EINTR )
				goto r11;
			free(blk);
			return rq.ret;
		}

/*
{
int test;
ufr_do(read,blk->recv,&test,sizeof(test));
printf("TEST %i\n",test);
}
*/

		blk->next = ufr_blk_list;
		ufr_blk_list = blk;

		*blkp = blk;
		ret.ufr_code = UFR_E_OK;
		ret.r.sys_code = 0;
	}
	return ret;
}

int
_ufr_recv(	UFR_BLK * blk,
		UFR_REQ * rq)
{
int seq;
UFR_REQ * r;
int ret_argc;
int i;
int argc;
int len;

#ifdef UFR_LOG
UFR_LOG * lg;
#endif

	unlock_task(ufr_lock,"_ufr_recv");

	if ( ufr_do(read,blk->recv,&seq,sizeof(seq)) < 0 ) {
		lock_task(ufr_lock);
		return -2;
	}

	lock_task(ufr_lock);

#ifdef UFR_LOG
	lg = malloc(sizeof(*lg));
	lg->seq = seq;
	lg->code = -1;
	lg->next = blk->recv_log;
	blk->recv_log = lg;
#endif


	for ( r = blk->req_queue ; r ; r = r->next )
		if ( r->seq == seq )
			break;

	if ( r == 0 ) {
		printf("SEQ %i\n",seq);
		er_panic("_ufr_recv(1)");
	}

	unlock_task(ufr_lock,"_ufr_recv");

	if ( ufr_do(read,blk->recv,&r->ret,sizeof(r->ret)) < 0 ) {
		lock_task(ufr_lock);
		return -2;
	}
	if ( ufr_do(read,blk->recv,&ret_argc,sizeof(ret_argc)) < 0 ) {
		lock_task(ufr_lock);
		return -2;
	}
	for ( i = 0 ; i < ret_argc ; i ++ ) {
		if ( ufr_do(read,blk->recv,&argc,sizeof(argc)) < 0 ) {
			lock_task(ufr_lock);
			return -2;
		}
		if ( argc < 0 || argc >= r->argc )
			er_panic("_ufr_recv(2)");
		if ( r->argv[argc].type != UFR_T_REP_CHAR_PTR ) {
			printf("ARGC seq=%i,%i %i\n",argc,
				r->seq,
				r->argv[argc].type);
			er_panic("_ufr_recv(3)");
		}
		if ( ufr_do(read,blk->recv,&len,sizeof(len)) < 0 ) {
			lock_task(ufr_lock);
			return -2;
		}
		if ( r->argv[argc].d_rep_char_ptr.len < len )
			er_panic("_ufr_recv(4)");
		if ( ufr_do(read,blk->recv,
				r->argv[argc].d_rep_char_ptr.d,
				len) < 0 ) {
			lock_task(ufr_lock);
			return -2;
		}
	}

	lock_task(ufr_lock);
	blk->last_time = get_xltime();
	r->ok = 1;
	wakeup_task((int)r);
	if ( r == rq )
		return 0;
	return -1;
}


void
ufr_recv(	UFR_BLK * blk,
		UFR_REQ * rq)
{
UFR_REQ ** rqp;
UFR_REQ * p;
int er;

retry:
	lock_task(ufr_lock);
	if ( rq->ok == 0 && blk->send >= 0 ) {
		if ( blk->read_op == 0 ) {
			blk->read_op = 1;
			for ( ; ; ) {
				switch ( _ufr_recv(blk,rq) ) {
				case 0:
					break;
				case -1:
					continue;
				case -2:
					for ( p = blk->req_queue ; p ;
							p = p->next ) {
						p->ret.ufr_code = UFR_E_ARGV;
						p->ok = 1;
						wakeup_task((int)p);
					}
				r12:
					er = close(blk->send);
					if ( er < 0 && errno == EINTR )
						goto r12;
				r13:
					er = close(blk->recv);
					if ( er < 0 && errno == EINTR )
						goto r13;
					blk->send = blk->recv = -1;
					blk->uid = -1;
					break;
				}
				break;
			}
			blk->read_op = 0;
		}
		else {
			sleep_task((int)rq,ufr_lock);
			goto retry;
		}
	}
	for ( rqp = &blk->req_queue ; 
			*rqp && (*rqp) != rq ;
			rqp = &(*rqp)->next );
	if ( *rqp == 0 )
		er_panic("ufr_recv");
	*rqp = rq->next;

	if ( blk->req_queue )
		wakeup_task((int)blk->req_queue);


	if ( rq->ret.ufr_code >= 0 )
		blk->transaction += rq->transaction;
	unlock_task(ufr_lock,"ufr_recv");
}

UFR_ERR
_ufr_send(	UFR_BLK * blk,
		UFR_REQ * rq)
{
int i;
UFR_ARGS * v;
int seq;
UFR_ERR ret;
int er;

#ifdef UFR_LOG
UFR_LOG * lg;
#endif

	rq->seq = seq = blk->seq ++;

#ifdef UFR_LOG
	lg = malloc(sizeof(*lg));
	lg->seq = seq;
	lg->code = rq->ufr_code;
	lg->next = blk->send_log;
	blk->send_log = lg;
#endif

	rq->ok = 0;
	if ( ufr_do(write,blk->send,&seq,sizeof(seq)) < 0 )
		goto err;
	if ( ufr_do(write,blk->send,&rq->ufr_code,sizeof(rq->ufr_code)) < 0 )
		goto err;
	if ( ufr_do(write,blk->send,&rq->argc,sizeof(rq->argc)) < 0 )
		goto err;
	v = rq->argv;
	for ( i = 0 ; i < rq->argc ; i ++ , v ++ ) {
		if ( ufr_do(write,blk->send,&v->type,sizeof(v->type)) < 0 )
			goto err;
		switch ( v->type ) {
		case UFR_T_INT:
			if ( ufr_do(write,blk->send,
				&v->d_int.d,sizeof(int)) < 0 )
					goto err;
			break;
		case UFR_T_INT64:
			if ( ufr_do(write,blk->send,
				&v->d_int64.d,
				sizeof(INTEGER64)) < 0 )
					goto err;
			break;
		case UFR_T_CHAR_PTR:
			if ( ufr_do(write,
				blk->send,&v->d_char_ptr.len,
				sizeof(v->d_char_ptr.len)) < 0)
					goto err;
			if ( ufr_do(write,blk->send,v->d_char_ptr.d,
					v->d_char_ptr.len) < 0)
						goto err;
			break;
		case UFR_T_REP_CHAR_PTR:
			if ( ufr_do(write,blk->send,&v->d_rep_char_ptr.len,
					sizeof(v->d_rep_char_ptr.len)) < 0 )
						goto err;
			break;
		default:
			er_panic("_ufr_send");
		}
	}
	rq->next = blk->req_queue;
	blk->req_queue = rq;
	blk->last_time = get_xltime();

	ret.ufr_code = UFR_E_OK;
	ret.r.sys_code = 0;
	return ret;
err:
	er = close(blk->send);
	if ( er < 0 && errno == EINTR )
		goto err;
err2:
	er = close(blk->recv);
	if ( er < 0 && errno == EINTR )
		goto err2;
	blk->send = blk->recv = -1;
	blk->uid = -1;

	ret.ufr_code = UFR_E_CONN;
	ret.r.sys_code = 0;
	return ret;
}

typedef struct u_stat_t {
	char * 		path;
	U_STAT *	ust;
	int		err;
	int		ret;
	int		flag;
} U_STAT_T;

void
u_stat_task(TKEY d)
{
U_STAT_T * s;
	s = (U_STAT_T*)GET_TKEY(d);
	s->ret = u_stat(s->path,s->ust);
	s->err = errno;
	s->flag = 1;
}

unsigned int call_entry_time;
unsigned int polling_entry_time;

int
call_u_stat(char * path,U_STAT * ust)
{
U_STAT_T u;
unsigned int st_time;
	u.path = path;
	u.ust = ust;
	u.flag = 0;
call_entry_time = get_xltime();
	create_task(u_stat_task,(int)&u,3);
	st_time = get_xltime();
	for ( ; u.flag == 0 ; ) {
		if ( get_xltime() - st_time > 1 )
			sleep_sec(1);
polling_entry_time = get_xltime();
	}
	errno = u.err;
	return u.ret;
}


int
_check_own(int * uidp,char * path)
{
U_STAT ust;
int p;
int loop_flag;

	loop_flag = 0;

retry:

	if ( u_stat(path,&ust) >= 0 ) {
		*uidp = ust.us_uid;
		return 0;
	}
	switch ( errno ) {
	case ENOENT:
		break;
	case EINTR:
		goto retry;
	default:
		break;
	}
	if ( loop_flag )
		return -1;
	for ( p = strlen(path)-1 ; p >= 0 ; p -- )
		if ( path[p] == '/' ) {
			path[p] = 0;
			break;
		}
	if ( p >= 0 ) {
		loop_flag = 1;
		goto retry;
	}
	path = ".";
	loop_flag = 1;
	goto retry;
}

int
check_own(int * uidp,char * path)
{
char * _path;
int ret;
	_path = malloc(strlen(path)+1);
	strcpy(_path,path);
	ret = _check_own(uidp,_path);
	free(_path);
	return ret;
}


int create_cnt;

UFR_ERR
open_path(UFR_REQ * rq)
{
UFR_ERR err;
UFR_BLK * blk;

	lock_task(ufr_lock);

	for ( ; create_proc ; ) {
		sleep_task((int)&create_proc,ufr_lock);
		lock_task(ufr_lock);
	}
	create_proc = 1;

	blk = _ufr_search_blk_by_uid(rq->uid);
	err.ufr_code = 0;
	if ( blk == 0 )
		err = _ufr_create_blk(&blk,rq->uid);

	create_proc = 0;
	wakeup_task((int)&create_proc);

	if ( err.ufr_code ) {
ufr_point = 10;
		unlock_task(ufr_lock,"open_path");
		return err;
	}
	err = _ufr_send(blk,rq);
	if ( err.ufr_code ) {
ufr_point = 11;
		unlock_task(ufr_lock,"open_path");
		return err;
	}
	unlock_task(ufr_lock,"open_path");
	if ( err.ufr_code ) {
ufr_point = 12;
		return err;
	}
	ufr_recv(blk,rq);
	if ( rq->ret.ufr_code == UFR_E_FD ) {
ufr_point = 13;
		rq->ret.r.d_int |= (blk->send<<16);
		rq->ret.ufr_code = UFR_E_OK;
	}
	else {
ufr_point = 14;
	}
	return rq->ret;
}


UFR_ERR
ufr_send(UFR_REQ * rq)
{
UFR_ERR err;
UFR_BLK * blk;
	lock_task(ufr_lock);
	blk = _ufr_search_blk_by_fd(rq->argv[0].d_int.d);
	if ( blk == 0 ) {
		err.ufr_code = UFR_E_ERRNO;
		err.r.sys_code = EBADF;
		unlock_task(ufr_lock,"ufr_send");
		return err;
	}
	err = _ufr_send(blk,rq);
	if ( err.ufr_code ) {
		unlock_task(ufr_lock,"open_path");
		return err;
	}
	unlock_task(ufr_lock,"ufr_send");
	ufr_recv(blk,rq);
	return rq->ret;
}

UFR_ERR
check_args(UFR_REQ * rq)
{
int i;
UFR_ARGS * v;
UFR_ERR err;
	v = rq->argv;
	for ( i = 0 ; i < rq->argc ; i ++ , v ++ ) {
		switch( v->type ) {
		case UFR_T_INT:
		case UFR_T_INT64:
			break;
		case UFR_T_CHAR_PTR:
			if ( v->d_char_ptr.len < 0 )
				goto err;
			break;
		case UFR_T_REP_CHAR_PTR:
			if ( v->d_rep_char_ptr.len < 0 )
				goto err;
			break;
		}
	}
	err.ufr_code = UFR_E_OK;
	err.r.sys_code = 0;
	return err;
err:
	err.ufr_code = UFR_E_ERRNO;
	err.r.sys_code = EINVAL;
	return err;
}

UFR_ERR
__ufr_call(UFR_REQ * rq)
{
UFR_ERR ret;

//	throw_signal_disable();

	if ( rq->argc < 1 ) {
		ret.ufr_code = UFR_E_ARGC;
ufr_point = 1;
		goto end;
	}

	ret = check_args(rq);
	if ( ret.ufr_code < 0 ) {
ufr_point = 2;
		goto end;
	}
	switch ( rq->argv[0].type ) {
	case UFR_T_CHAR_PTR:
		ret =  open_path(rq);
		goto end;
	case UFR_T_INT:
ufr_point = 3;
		ret =  ufr_send(rq);
		goto end;
	}
ufr_point = 4;
	ret.ufr_code = UFR_E_ARGV;
end:
//	throw_signal_enable();

	return ret;
}
