/*
    (C) 1995-96 AROS - The Amiga Replacement OS
    $Id: emul_handler.c,v 1.2 1996/08/01 17:41:22 digulla Exp $
    $Log: emul_handler.c,v $
    Revision 1.2  1996/08/01 17:41:22  digulla
    Added standard header for all files

    Desc:
    Lang:
*/
#include <exec/resident.h>
#include <exec/memory.h>
#include <clib/exec_protos.h>
#include <utility/tagitem.h>
#include <dos/dosextens.h>
#include <dos/filesystem.h>
#include <dos/exall.h>
#include <dos/dosasl.h>
#include <clib/dos_protos.h>
#include <aros/libcall.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#ifdef __GNUC__
    #include "emul_handler_gcc.h"
#endif

static const char name[];
static const char version[];
static const APTR inittabl[4];
static void *const functable[];
static const UBYTE datatable;
struct emulbase *emul_handler_init();
void emul_handler_open();
BPTR emul_handler_close();
BPTR emul_handler_expunge();
int emul_handler_null();
void emul_handler_beginio();
LONG emul_handler_abortio();
static const char end;

struct filehandle
{
    char *name;
    int type;
    long fd;
};
#define FHD_FILE	0
#define FHD_DIRECTORY	1

int emul_handler_entry(void)
{
    /* If the device was executed by accident return error code. */
    return -1;
}

const struct Resident emul_handler_resident=
{
    RTC_MATCHWORD,
    (struct Resident *)&emul_handler_resident,
    (APTR)&end,
    RTF_AUTOINIT,
    1,
    NT_LIBRARY,
    0,
    (char *)name,
    (char *)&version[6],
    (ULONG *)inittabl
};

static const char name[]="emul.handler";

static const char version[]="$VER: emul_handler 1.0 (28.3.96)\n\015";

static const APTR inittabl[4]=
{
    (APTR)sizeof(struct emulbase),
    (APTR)functable,
    (APTR)&datatable,
    &emul_handler_init
};

static void *const functable[]=
{
    &emul_handler_open,
    &emul_handler_close,
    &emul_handler_expunge,
    &emul_handler_null,
    &emul_handler_beginio,
    &emul_handler_abortio,
    (void *)-1
};

static const UBYTE datatable=0;

LONG u2a[][2]=
{
  { ENOMEM, ERROR_NO_FREE_STORE },
  { ENOENT, ERROR_OBJECT_NOT_FOUND },
  { 0, 0 }
};

LONG err_u2a(void)
{
    ULONG i;
    for(i=0;i<sizeof(u2a)/sizeof(u2a[0]);i++)
	if(u2a[i][0]==errno)
	    break;
    return u2a[i][1];
}

static void shrink(char *filename)
{
    char *s1,*s2;
    unsigned long len;
    for(;;)
    {
	while(*filename=='/')
	    memmove(filename,filename+1,strlen(filename));
	s1=strstr(filename,"//");
	if(s1==NULL)
	    break;
	s2=s1;
	while(s2>filename&&*--s2!='/')
	    ;
	memmove(s2,s1+2,strlen(s1+1));
    }
    len=strlen(filename);
    if(len&&filename[len-1]=='/')
	filename[len-1]=0;
}

static LONG locate(struct filehandle **handle,STRPTR name,LONG mode,LONG protect)
{
    LONG ret=ERROR_NO_FREE_STORE;
    struct filehandle *fh;
    struct stat st;
    long flags;
    int ste;
    fh=(struct filehandle *)malloc(sizeof(struct filehandle));
    if(fh!=NULL)
    {
        if(!*name&&(*handle)->type==FHD_FILE&&((*handle)->fd==STDIN_FILENO||
           (*handle)->fd==STDOUT_FILENO||(*handle)->fd==STDERR_FILENO))
        {
            fh->type=FHD_FILE;
            fh->fd=(*handle)->fd;
            fh->name="";
            *handle=fh;
            return 0;
        }
	fh->name=(char *)malloc(strlen((*handle)->name)+strlen(name)+2);
	if(fh->name!=NULL)
	{
	    strcpy(fh->name,(*handle)->name);
	    strcat(fh->name,"/");
	    strcat(fh->name,name);
	    shrink(fh->name);
	    ste=stat(*fh->name?fh->name:".",&st);
	    if(!ste||(errno==ENOENT&&mode&LMF_CREATE))
	    {
		if(ste||S_ISREG(st.st_mode))
		{
		    fh->type=FHD_FILE;
		    flags=(mode&LMF_CREATE?O_CREAT:0)|
			  (mode&LMF_CLEAR?O_TRUNC:0);
		    if(mode&LMF_WRITE)
			flags|=mode&LMF_READ?O_RDWR:O_WRONLY;
		    else
			flags|=O_RDONLY;
		    fh->fd=open(fh->name,flags,0770);
		    if(fh->fd>=0)
		    {
			*handle=fh;
			return 0;
		    }
		}
		else if(S_ISDIR(st.st_mode))
		{
		    fh->type=FHD_DIRECTORY;
		    fh->fd=(long)opendir(*fh->name?fh->name:".");
		    if(fh->fd)
		    {
			*handle=fh;
			return 0;
		    }
		}
		errno=ENOENT;
	    }
	    ret=err_u2a();
	    free(fh->name);
	}
	free(fh);
    }
    return ret;
}

static LONG free_lock(struct filehandle *current)
{
    switch(current->type)
    {
	case FHD_FILE:
	    if(current->fd!=STDIN_FILENO&&current->fd!=STDOUT_FILENO&&
	       current->fd!=STDERR_FILENO)
	    {
	        close(current->fd);
	        free(current->name);
	    }
	    break;
	case FHD_DIRECTORY:
	    closedir((DIR *)current->fd);
	    free(current->name);
	    break;
    }
    free(current);
    return 0;
}

static LONG startup(struct emulbase *emulbase)
{
    struct filehandle *fhi, *fho, *fhe, *fhv, *fhc, *fhs;
    struct DosList *dlv, *dlc;
    static struct filehandle sys={ "", FHD_DIRECTORY, 0 };
    LONG ret=ERROR_NO_FREE_STORE;

    fhi=(struct filehandle *)malloc(sizeof(struct filehandle));
    if(fhi!=NULL)
    {
	fho=(struct filehandle *)malloc(sizeof(struct filehandle));
	if(fho!=NULL)
	{
	    fhe=(struct filehandle *)malloc(sizeof(struct filehandle));
	    if(fhe!=NULL)
	    {
		fhv=&sys;
		ret=locate(&fhv,"",0,0);
		if(!ret)
		{
		    fhc=&sys;
		    ret=locate(&fhc,"",0,0);
		    if(!ret)
		    {
			fhs=&sys;
			ret=locate(&fhs,"",0,0);
			if(!ret)
			{
			    ret=ERROR_NO_FREE_STORE;
			    dlv=MakeDosEntry("Workbench",DLT_VOLUME);
			    if(dlv!=NULL)
			    {
				dlc=MakeDosEntry("SYS",DLT_DEVICE);
				if(dlc!=NULL)
				{
				    ret=ERROR_OBJECT_EXISTS;
				    dlv->dol_Unit  =(struct Unit *)fhv;
				    dlv->dol_Device=&emulbase->device;
				    dlc->dol_Unit  =(struct Unit *)fhc;
				    dlc->dol_Device=&emulbase->device;
				    fhi->type=FHD_FILE;
				    fhi->fd=STDIN_FILENO;
				    fhi->name="";
				    emulbase->stdin=(struct Unit *)fhi;
				    fho->type=FHD_FILE;
				    fho->fd=STDOUT_FILENO;
				    fho->name="";
				    emulbase->stdout=(struct Unit *)fho;
				    fhe->type=FHD_FILE;
				    fhe->fd=STDERR_FILENO;
				    fhe->name="";
				    emulbase->stderr=(struct Unit *)fhe;
				    if(AddDosEntry(dlv))
				    {
					if(AddDosEntry(dlc))
					{
					    DOSBase->dl_NulHandler=&emulbase->device;
					    DOSBase->dl_NulLock   =(struct Unit *)fhs;
					    return 0;
					}
					RemDosEntry(dlv);
				    }
				    FreeDosEntry(dlc);
				}
				FreeDosEntry(dlv);
			    }
			    free_lock(fhs);
			}
			free_lock(fhc);
		    }
		    free_lock(fhv);
		}
		free(fhe);
	    }
	    free(fho);
	}
	free(fhi);
    }
    return ret;
}

static const ULONG sizes[]=
{ 0, offsetof(struct ExAllData,ed_Type), offsetof(struct ExAllData,ed_Size),
  offsetof(struct ExAllData,ed_Prot), offsetof(struct ExAllData,ed_Days),
  offsetof(struct ExAllData,ed_Comment), offsetof(struct ExAllData,ed_OwnerUID),
  sizeof(struct ExAllData) };

static LONG examine(struct filehandle *fh,struct ExAllData *ead,ULONG size,ULONG type)
{
    STRPTR next, end, last, name;
    struct stat st;
    if(type>ED_OWNER)
	return ERROR_BAD_NUMBER;
    next=(STRPTR)ead+sizes[type];
    end =(STRPTR)ead+size;
    if(next>=end)
	return ERROR_BUFFER_OVERFLOW;
    if(lstat(*fh->name?fh->name:".",&st))
	return err_u2a();
    switch(type)
    {
	default:
	case ED_OWNER:
	    ead->ed_OwnerUID=st.st_uid;
	    ead->ed_OwnerGID=st.st_gid;
	case ED_COMMENT:
	    ead->ed_Comment=NULL;
	case ED_DATE:
	    ead->ed_Days=st.st_ctime/(60*60*24)-(6*365+2*366);
	    ead->ed_Mins=(st.st_ctime/60)%(60*24);
	    ead->ed_Ticks=(st.st_ctime%60)*TICKS_PER_SECOND;
	case ED_PROTECTION:
	    ead->ed_Prot=(st.st_mode&S_IRUSR?FIBF_READ:0)|
	    		 (st.st_mode&S_IWUSR?FIBF_WRITE:0)|
	    		 (st.st_mode&S_IXUSR?FIBF_EXECUTE:0)|
	    		 FIBF_SCRIPT|FIBF_DELETE;
	case ED_SIZE:
	    ead->ed_Size=st.st_size;
	case ED_TYPE:
	    ead->ed_Type=S_ISREG(st.st_mode)?ST_FILE:
			 S_ISDIR(st.st_mode)?(*fh->name?ST_USERDIR:ST_ROOT):0;
	case ED_NAME:
	    ead->ed_Name=next;
	    last=name=*fh->name?fh->name:"Workbench";
	    while(*name)
		if(*name++=='/')
		    last=name;
	    for(;;)
	    {
		if(next>=end)
		    return ERROR_BUFFER_OVERFLOW;
		if(!(*next++=*last++))
		    break;
	    }
	case 0:
	    ead->ed_Next=(struct ExAllData *)(((ULONG)next+PTRALIGN-1)&~(PTRALIGN-1));
	    return 0;
    }
}

static LONG examine_all(struct filehandle *fh,struct ExAllData *ead,ULONG size,ULONG type)
{
    struct ExAllData *last=NULL;
    STRPTR end=(STRPTR)ead+size, name, old;
    off_t oldpos;
    struct dirent *dir;
    LONG error;
    if(fh->type!=FHD_DIRECTORY)
	return ERROR_OBJECT_WRONG_TYPE;
    for(;;)
    {
	oldpos=telldir((DIR *)fh->fd);
	errno=0;
	dir=readdir((DIR *)fh->fd);
	if(dir==NULL)
	{
	    error=err_u2a();
	    break;
	}
	if(dir->d_name[0]=='.'&&(!dir->d_name[1]||(dir->d_name[1]=='.'&&!dir->d_name[2])))
	    continue;
	name=(STRPTR)malloc(strlen(fh->name)+strlen(dir->d_name)+2);
	if(name==NULL)
	{
	    error=ERROR_NO_FREE_STORE;
	    break;
	}
	strcpy(name,fh->name);
	if(*name)
  	    strcat(name,"/");
	strcat(name,dir->d_name);
	old=fh->name;
	fh->name=name;
	error=examine(fh,ead,end-(STRPTR)ead,type);
	fh->name=old;
	free(name);
	if(error)
	    break;
	last=ead;
	ead=ead->ed_Next;
    }
    if((!error||error==ERROR_BUFFER_OVERFLOW)&&last!=NULL)
    {
        last->ed_Next=NULL;
	seekdir((DIR *)fh->fd,oldpos);
	return 0;
    }
    if(!error)
        error=ERROR_NO_MORE_ENTRIES;
    rewinddir((DIR *)fh->fd);
    return error;
}

__AROS_LH2(struct emulbase *, init,
 __AROS_LA(struct emulbase *, emulbase, D0),
 __AROS_LA(BPTR,              segList,   A0),
	   struct ExecBase *, sysBase, 0, emul_handler)
{
    __AROS_FUNC_INIT

    /* Store arguments */
    emulbase->sysbase=sysBase;
    emulbase->seglist=segList;
    emulbase->device.dd_Library.lib_OpenCnt=1;
    emulbase->dosbase=(struct DosLibrary *)OpenLibrary("dos.library",39);
    if(emulbase->dosbase!=NULL)
    {
	if(AttemptLockDosList(LDF_ALL|LDF_WRITE))
	{
	    if(!startup(emulbase))
	    {
		UnLockDosList(LDF_ALL|LDF_WRITE);
		return emulbase;
	    }
	    UnLockDosList(LDF_ALL|LDF_WRITE);
	}
	CloseLibrary((struct Library *)emulbase->dosbase);
    }

    return NULL;
    __AROS_FUNC_EXIT
}

__AROS_LH3(void, open,
 __AROS_LA(struct IOFileSys *, iofs, A1),
 __AROS_LA(ULONG,              unitnum, D0),
 __AROS_LA(ULONG,              flags, D0),
	   struct emulbase *, emulbase, 1, emul_handler)
{
    __AROS_FUNC_INIT

    /* Keep compiler happy */
    unitnum=0;
    flags=0;

    /* I have one more opener. */
    emulbase->device.dd_Library.lib_Flags&=~LIBF_DELEXP;

    /* Set returncode */
    iofs->IOFS.io_Error=0;

    /* Mark Message as recently used. */
    iofs->IOFS.io_Message.mn_Node.ln_Type=NT_REPLYMSG;
    __AROS_FUNC_EXIT
}

__AROS_LH1(BPTR, close,
 __AROS_LA(struct IOFileSys *, iofs, A1),
	   struct emulbase *, emulbase, 2, emul_handler)
{
    __AROS_FUNC_INIT

    /* Let any following attemps to use the device crash hard. */
    iofs->IOFS.io_Device=(struct Device *)-1;
    return 0;
    __AROS_FUNC_EXIT
}

__AROS_LH0(BPTR, expunge, struct emulbase *, emulbase, 3, emul_handler)
{
    __AROS_FUNC_INIT

    /* Do not expunge the device. Set the delayed expunge flag and return. */
    emulbase->device.dd_Library.lib_Flags|=LIBF_DELEXP;
    return 0;
    __AROS_FUNC_EXIT
}

__AROS_LH0I(int, null, struct emulbase *, emulbase, 4, emul_handler)
{
    __AROS_FUNC_INIT
    return 0;
    __AROS_FUNC_EXIT
}

__AROS_LH1(void, beginio,
 __AROS_LA(struct IOFileSys *, iofs, A1),
	   struct emulbase *, emulbase, 5, emul_handler)
{
    __AROS_FUNC_INIT
    LONG error=0;

    /* WaitIO will look into this */
    iofs->IOFS.io_Message.mn_Node.ln_Type=NT_MESSAGE;

    /*
	Do everything quick no matter what. This is possible
	because I never need to Wait().
    */
    switch(iofs->IOFS.io_Command)
    {
	case FSA_OPEN:
	    error=locate((struct filehandle **)&iofs->IOFS.io_Unit,
			 (char *)iofs->io_Args[0],
			 iofs->io_Args[1],iofs->io_Args[2]);
	    break;
		    
	case FSA_CLOSE:
	    error=free_lock((struct filehandle *)iofs->IOFS.io_Unit);
	    break;
	    
	case FSA_IS_INTERACTIVE:
	{
	    struct filehandle *fh=(struct filehandle *)iofs->IOFS.io_Unit;
	    if(fh->type==FHD_FILE)
	        iofs->io_Args[0]=isatty(fh->fd);
	    else
	        iofs->io_Args[0]=0;
	    break;
	}

	case FSA_READ:
	{
	    struct filehandle *fh=(struct filehandle *)iofs->IOFS.io_Unit;
	    if(fh->type==FHD_FILE)
	    {
	        if(fh->fd==STDOUT_FILENO)
	            fh->fd=STDIN_FILENO;
		iofs->io_Args[1]=read(fh->fd,(APTR)iofs->io_Args[0],iofs->io_Args[1]);
		if(iofs->io_Args[1]<0)
		    error=err_u2a();
	    }else
		error=ERROR_OBJECT_WRONG_TYPE;
	    break;
	}

	case FSA_WRITE:
	{
	    struct filehandle *fh=(struct filehandle *)iofs->IOFS.io_Unit;
	    if(fh->type==FHD_FILE)
	    {
	        if(fh->fd==STDIN_FILENO)
	            fh->fd=STDOUT_FILENO;
		iofs->io_Args[1]=write(fh->fd,(APTR)iofs->io_Args[0],iofs->io_Args[1]);
		if(iofs->io_Args[1]<0)
		    error=err_u2a();
	    }else
		error=ERROR_OBJECT_WRONG_TYPE;
	    break;
	}
		    
	case FSA_SEEK:
	{
	    struct filehandle *fh=(struct filehandle *)iofs->IOFS.io_Unit;
	    LONG mode=iofs->io_Args[2];
	    LONG oldpos;
	    if(fh->type==FHD_FILE)
	    {
		oldpos=lseek(fh->fd,0,SEEK_CUR);
		if(lseek(fh->fd,iofs->io_Args[1],mode==OFFSET_BEGINNING?SEEK_SET:mode==OFFSET_CURRENT?SEEK_CUR:SEEK_END)<0)
		    error=err_u2a();
		iofs->io_Args[0]=0;
		iofs->io_Args[1]=oldpos;
	    }else
		error=ERROR_OBJECT_WRONG_TYPE;
	    break;
	}

	case FSA_EXAMINE:
	    error=examine((struct filehandle *)iofs->IOFS.io_Unit,
			  (struct ExAllData *)iofs->io_Args[0],
			  iofs->io_Args[1],iofs->io_Args[2]);
	    break;

	case FSA_EXAMINE_ALL:
	    error=examine_all((struct filehandle *)iofs->IOFS.io_Unit,	    
			  (struct ExAllData *)iofs->io_Args[0],
			  iofs->io_Args[1],iofs->io_Args[2]);
	    break;

	default:
	    error=ERROR_NOT_IMPLEMENTED;
	    break;
    }
    
    /* Set error code */
    iofs->io_DosError=error;

    /* If the quick bit is not set send the message to the port */
    if(!(iofs->IOFS.io_Flags&IOF_QUICK))
	ReplyMsg(&iofs->IOFS.io_Message);

    /* Trigger a rescedule every now and then */
    if(SysBase->TaskReady.lh_Head->ln_Pri==SysBase->ThisTask->tc_Node.ln_Pri&&
       SysBase->TDNestCnt<0&&SysBase->IDNestCnt<0)
    {
        SysBase->ThisTask->tc_State=TS_READY;
        Enqueue(&SysBase->TaskReady,&SysBase->ThisTask->tc_Node);
        Switch();
    }
    
    __AROS_FUNC_EXIT
}

__AROS_LH1(LONG, abortio,
 __AROS_LA(struct IOFileSys *, iofs, A1),
	   struct emulbase *, emulbase, 6, emul_handler)
{
    __AROS_FUNC_INIT
    /* Everything already done. */
    return 0;
    __AROS_FUNC_EXIT
}

static const char end=0;
