#include "fat12prj.h"
#include "fat12_script_wrapper.h"
#include "reader/support.h"
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>

#define put_str(name) \
    if (ctx->name) \
    {\
	int len=strlen(ctx->name)+strlen(#name)+2;\
	ptr[0]=(TCHAR*)malloc(len);\
	if (ptr[0] == NULL)\
	    goto err;\
	snprintf(ptr++[0],len,"%s=%s",#name,ctx->name);\
    }
#define put_hex(name) \
    {\
	int len=8+strlen(#name)+2;\
	ptr[0]=(TCHAR *)malloc(len);\
	if (ptr[0] == NULL)\
	    goto err;\
	snprintf(ptr++[0],len,"%s=%08lx",#name,ctx->name);\
    }
#define put_val_noctx(name) \
    {\
	int len=8+strlen(#name)+2;\
	ptr[0]=(TCHAR *)malloc(len);\
	if (ptr[0] == NULL)\
	    goto err;\
	snprintf(ptr++[0],len,"%s=%d",#name,name);\
    }

//Используются для блокировки кода, вызывающего скрипт. 
static TSupportCriticalSection g_script_lock = PTHREAD_MUTEX_INITIALIZER;
static TSupportOnce g_script_lock_control = SUPPORT_ONCE_INIT;

static void init_g_script_lock() {
    support_section_init(&g_script_lock, NULL);
}

//Реализация всей логики
static int threadunsafe_fat12_default_lexec(const TCHAR *prog, const TCHAR* arg1, const TCHAR* arg2, TCHAR** env, time_t tmout_sec, TCHAR * out, size_t* out_size);

//обертка над thread-unsafe функцией
int fat12_default_lexec(const TCHAR *prog, const TCHAR* arg1, const TCHAR* arg2, TCHAR** env, time_t tmout_sec, TCHAR * out, size_t* out_size){
    support_once(&g_script_lock_control, init_g_script_lock);
    support_section_enter(g_script_lock);
    int res = threadunsafe_fat12_default_lexec(prog, arg1, arg2, env, tmout_sec, out, out_size);
    support_section_leave(g_script_lock);
    return res;
}

TCHAR ** fat12_default_makeenv( TFat12Context * ctx )
{
    TCHAR ** ret = NULL, ** ptr =NULL;
    uid_t euid,uid;
    gid_t egid,gid;
    ret = (TCHAR **)calloc(11,sizeof(TCHAR *));
    if (ret == NULL )
	return NULL;
    uid = getuid();
    gid = getgid();
    support_user_id_ex(NULL,NULL,&euid,&egid);
    ptr = ret;
    put_str(path);
    put_hex(volume_serial);
    put_str(volume_label);
    put_str(path_to_item);
    put_str(nickname);
    put_str(connect);
    put_val_noctx(uid);
    put_val_noctx(gid);
    put_val_noctx(euid);
    put_val_noctx(egid);
    ptr[0]=NULL;
    return ret;
err:
    fat12_default_freeenv(ret);
    return NULL;
}

#undef put_str
#undef put_hex
#undef put_val_noctx

int threadunsafe_fat12_default_lexec(const TCHAR *prog, const TCHAR* arg1, const TCHAR* arg2, TCHAR** env, time_t tmout_sec, TCHAR * out, size_t* out_size)
{
    pid_t pid;
    int status,wres,ret=-1;
    int fl[2];
    if (out)
	out[0]=0;
if(*out_size < PATH_MAX){
	*out_size = PATH_MAX;
	return (DWORD)ERROR_SUCCESS;;
}
    if (pipe (fl) == -1)
	return -1;
    fcntl(fl[0], F_SETFL, fcntl(fl[0],F_GETFL) | O_NONBLOCK);
    pid = fork();
    if ( pid == -1 )
    {
	close(fl[1]);
	goto done;
    }
    if ( pid == 0 )
    {
	int fld=-1;
	dup2(fl[1],1);
	close(fl[0]);
	if (fl[1] != 1)
	    close(fl[1]);
	close(0);
	close(2);
	fld=open("/dev/null",O_RDWR);
	dup2(fld,0);
	dup2(fld,2);
	//использовать exit(1) здесь нельзя - обработчики atexit родительского процесса могут скрэшиться (CPCSP-11195)
	if(seteuid(getuid()))
	    _exit(1);
	if(setegid(getgid()))
	    _exit(1);
	if (execle(prog,prog,arg1, arg2, NULL,env) == -1 )
	    _exit(1);
    }
    else
    {
	struct timeval tv,tv0;
	size_t nread = 0;
	ssize_t read_cur = 0;
	close(fl[1]);
	tv.tv_sec = tmout_sec;
	tv.tv_usec = 500000; /* to be sure */
	gettimeofday(&tv0,NULL);
	do 	{
	    int rsel;
	    struct timeval tv1;
	    int millisec_tmout; //poll работает с миллисекундами.
	    struct pollfd read_fds;
	    read_fds.fd = fl[0];//в этот файловый дескриптор будет писать дочерний процесс-скрипт.
	    read_fds.events = POLLIN; //poll оповестит, когда из fl[0] можно будет читать
	    read_fds.revents = 0; //выходное значение poll - статус отслеживаемого дескриптора
	    millisec_tmout = tv.tv_sec * 1000 + tv.tv_usec / 1000; //конвертируем (секунды + микросекунды) в миллисекунды

	    rsel = poll(&read_fds, 1, millisec_tmout); //при успехе вернет число > 0

	    gettimeofday(&tv1,NULL);
	    if (rsel < 0 && errno != EINTR && errno != EAGAIN)//при некритических ошибках пробуем еще в цикле
	    {
		wres = waitpid(pid,&status,WNOHANG);
		break;
	    }
	    if (rsel > 0 && out && (POLLIN & read_fds.revents))
	    {
		read_cur=read(fl[0],out+nread, PATH_MAX-nread);
		if (read_cur > 0)
		    nread+=read_cur;
		else if (read_cur == -1 && errno != EINTR)
		{
			wres = waitpid(pid,&status,WNOHANG);
			break;
		}
	    }
	    if (tv1.tv_sec > tv0.tv_sec+tmout_sec || 
		    (tv1.tv_sec == tv0.tv_sec+tmout_sec && tv1.tv_usec > tv0.tv_usec))
	    {	
		kill(pid,SIGKILL);
		if (out)
		    out[0] = 0;
		wres = 0;
		break;
	    }
	    if (tv1.tv_usec <= tv0.tv_usec)
	    {
		tv.tv_sec = tmout_sec + tv0.tv_sec - tv1.tv_sec;
		tv.tv_usec = tv0.tv_usec - tv1.tv_usec;
	    }
	    else
	    {
		tv.tv_sec = tmout_sec-1 + tv0.tv_sec - tv1.tv_sec;
		tv.tv_usec = 1000000 + tv0.tv_usec - tv1.tv_usec;
	    }
	} while ((wres = waitpid(pid,&status,WNOHANG))==0 );

	if (wres != pid)
	    goto done;
	if (!WIFEXITED(status))
	    goto done;
	ret=WEXITSTATUS(status);
	if (ret!=0)
	    goto done;
	if (out)
	{
	    read_cur=read(fl[0],out+nread, PATH_MAX-nread);
	    if (read_cur > 0)
		nread+=read_cur;
	    out[nread]=0;
	    *out_size = nread;
	}
    }
done:
    close(fl[0]);
    return ret;
}

void fat12_default_freeenv( TCHAR ** env )
{
    TCHAR ** ptr;
    if ( env == NULL )
	return;
    for (ptr=env;ptr[0];ptr++)
	free(ptr[0]);
    free(env);
}

