/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 2002-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * omf archive format method
 */

#include <ardirlib.h>
#include <cdt.h>
#include <omf.h>

#ifndef EILSEQ
#define EILSEQ		EINVAL
#endif

#define FILENAME    "U_w_i_n"

typedef struct _arfile_
{
	Ardirent_t	st;
	char		*alias;
	Dtlink_t	link;
	int		flags;
	size_t		toffset;
	char		suffix[4];
} Arfile_t;

typedef struct State_s			/* method state			*/
{
        Dt_t		*dict;
	void		*addr;
	Arfile_t	lib;
	Arfile_t	*next;
	int		state;
} State_t;

struct Table
{
	struct Table *next;
	size_t	offset;
	size_t	disp;
};

static int namcomp(Dt_t *dp, Void_t *left, Void_t *right, Dtdisc_t *dsp)
{
	char *l= (char*)left;
	char *r= (char*)right;
	char *suffix;
	if(suffix=strrchr(l,'.'))
		return(memcmp(l,r,suffix+1-l));
	return(strcmp(l,r));
}

static int offcomp(Dt_t *dp, Void_t *left, Void_t *right, Dtdisc_t *dsp)
{
	off_t l= *((off_t*)left);
	off_t r= *((off_t*)right);
	if (l < r)
		return -1;
	if (l > r)
		return 1;
	return 0;
}

static Dtdisc_t namdisc =
{
	offsetof(Arfile_t,st.name),-1, offsetof(Arfile_t,link), 0, 0, namcomp
};

static Dtdisc_t offdisc =
{
	offsetof(Arfile_t,st.offset), sizeof(off_t), offsetof(Arfile_t,link), 0, 0, offcomp
};

static unsigned char *readint(unsigned char *cp,int *i, int big)
{
	if(big)
	{
		*i = (*cp) | (cp[1]<<8) | (cp[2]<<16) | (cp[3]<<24);
		return(cp+4);
	}
	*i = (*cp) | (cp[1]<<8);
	return(cp+2);
}

static unsigned char *readindex(unsigned char *cp, int *size)
{
	if(*cp&0x80)
	{
		*size = (*cp&0xf7)<<8 | cp[1];
		return(cp+2);
	}
	*size = *cp++;
	return(cp);
}

#define round(a,b)	(((a)+(b)-1) &~ ((b)-1))

static unsigned int is_omf(int fd)
{
	unsigned char buff[4];
	if(read(fd,buff,4) != 4)
		return(0);
	if(*buff==OMF_THEADR)
		return(buff[3]);
	lseek(fd,(off_t)-4,SEEK_CUR);
	errno =  EILSEQ;
	return(0);
}

static Arfile_t *ar_getnode(Ardir_t *ar, const char *name)
{
	const char *cp;
	Arfile_t *fp;
	Dt_t *dp = ((State_t*)ar->data)->dict;
	if(cp = strrchr(name,'/'))
		name = cp+1;
	fp = (Arfile_t*)dtmatch(dp,(void*)name);
	if(!fp)
	{
		size_t len = strlen(name)+5;
		if(!(fp = newof(0,Arfile_t,1,len)))
			return(0);
		fp->st.name = (char*)(fp+1);
		memcpy((char*)fp->st.name,name,len);
		if(!strrchr(fp->st.name,'.'))
		{
			fp->st.name[len-5] = '.';
			fp->st.name[len-4] = 0;
		}
		fp->suffix[0] = 'o';
		fp->suffix[1] = 0;
		fp->st.mtime = ar->st.st_mtime;
		fp->st.mode = ar->st.st_mode&(S_IRWXU|S_IRWXG|S_IRWXO);
		fp->st.uid = ar->st.st_uid;
		fp->st.gid = ar->st.st_gid;
		dtinsert(dp,fp);
	}
	return(fp);
}

/*
 * closef
 */

static int
omfclose(Ardir_t* ar)
{
	State_t	*sp = (State_t*)ar->data;
	Dt_t	*dp = sp?sp->dict:0;
	if(sp && dp && sp->state)
	{
		/* update modification times */
		Arfile_t *fp;
		for(fp=(Arfile_t*)dtfirst(dp); fp; fp = (Arfile_t*)dtnext(dp,fp))
		{
			if(fp->flags && fp->toffset>0 && lseek(ar->fd,(off_t)fp->toffset,SEEK_SET)>0)
			{
				write(ar->fd,(void*)&fp->st.mtime,sizeof(time_t));
			}
		}
	}
	if (sp && sp->addr)
		free(sp->addr);
	if (dp)
		dtclose(dp);
	if(sp)
		free(sp);
	return 0;
}

static size_t displacement(struct Table *tp,size_t addr)
{
	size_t disp = 0;
	while(tp)
	{
		disp = tp->disp;
		if(addr>tp->offset)
			break;
		tp = tp->next;
	}
	return(disp);
}

static void omfload(Ardir_t *ar,unsigned char *base, unsigned char *last, struct Table *tp, int dmars)
{
	State_t *sp = (State_t*)ar->data;
	unsigned char *cp=base, *end=last;
	int *ip;
	Arfile_t *fp;
	int len;
	struct Table *tpnext;
	if(dmars)
	{
		/* string table at the top */
		while(*cp)
			cp += strlen((char*)cp)+1;
		while(cp[4]==0)
			cp++;
	}
	else
	{
		readint(cp,&len,1);
		end = base + len;
	}
	for(ip=(int*)cp; (unsigned char*)(ip+6) < end; ip+=7)
	{
		if(dmars)
			len = *ip;
		else
			readint((unsigned char*)ip,&len,1);
		if(base+len >= last)
			break;
		if(!(fp = ar_getnode(ar,(char*)base+len)))
			return;
		fp->st.mtime = (time_t)ip[1];
		fp->st.mode = (mode_t)ip[2];
		fp->st.uid = (uid_t)ip[3];
		fp->st.gid = (gid_t)ip[4];
		memcpy(fp->suffix,&ip[5],sizeof(fp->suffix));
		fp->toffset = ((char*)&ip[1])-(char*)(sp->addr); 
		fp->toffset += displacement(tp,((unsigned char*)&ip[1])-base);
		if(ip[6])
			fp->alias = (char*)base+ip[6];
			
	}
	while(tp)
	{
		tpnext = tp->next;
		free((void*)tp);
		tp = tpnext;
	}
}
/*
 * openf
 */

static int
omfopen(Ardir_t* ar, char* buf, size_t size)
{
	State_t*	state;
	int pagesize;
	unsigned char *addr, *addrstart, *addrmax, *begin,*cp, *base=0, *last;
	char name[PATH_MAX];
	Arfile_t *fp;
	struct Table *tp=0, *tpnew;
	int dmars=0,n,type,special=0,len,flen=strlen(FILENAME ".");

	if (ar->fd>=0 && (size<=0 || *((unsigned char*)buf) != OMF_LIBHDR))
		return -1;
	if(!(state = newof(0,State_t,1,0)))
		return -1;
	ar->data = (void*)state;
	if(!(state->dict = dtopen(&namdisc,Dtoset)))
		goto nope;
	if(ar->fd<0)
		return 0;
	size = (size_t)ar->st.st_size;
	if(!(addr = (unsigned char*)malloc(size)))
		goto nope;
	addrstart = addr;
	state->addr = (void*)addr;
	if(lseek(ar->fd,(off_t)0, SEEK_SET)<0)
		goto nope;
	if(read(ar->fd,(void*)addr,size) < 0)
		goto nope;
	addrmax=addr+size;
	addr = readint(addr+1,&n,0);
	pagesize = n+3;
	while((addr+=n) < addrmax)
	{
		if(*addr==OMF_LIBDHD)
			return(0);
		cp = addr;
		type = *addr;
		addr = readint(addr+1,&n,0);
		if((type&~1)==OMF_MODEND)
		{
			if(special)
			{
				if(base)
					omfload(ar,base,last,tp,dmars);
				special = 0;
			}
			else
			{
				if(!(fp = ar_getnode(ar,name)))
					return(0);
				fp->st.offset = begin-addrstart;
				fp->st.size = addr+n-begin;
			}
			addr +=n;
			addr = addrstart + round(addr-addrstart,pagesize);
			n = 0;
		}
		else if(type==OMF_THEADR)
		{
			if(memcmp(addr+1,FILENAME".",flen))
			{
				begin = addr+1;
				len = *addr;
				if(addr[1]=='.' && addr[2]=='\\')
				{
					len -=2;
					begin +=2;
				}
				memcpy(name,begin,len);
				name[len] =  0;
				begin = cp;
			}
			else
				special = 1;
		}
		else if((type&~1)==OMF_LEDATA && special)
		{
			int offset;
			cp = readindex(addr,&len);
			cp= readint(cp,&offset,type&1);
			dmars = (type&1);
			len = (n-1) - (cp-addr);
			if(!(tpnew = newof(0,struct Table,1,0)))
				return(0);
			tpnew->next = tp;
			tp = tpnew;
			if(!base)
				base = last = cp;
			else
			{
				tp->disp = cp - (base+offset);
				memcpy(base+offset,cp,len);
			}
			tp->offset = offset;
			last += len;
		}
	}
 nope:
	omfclose(ar);
	return -1;
}

/*
 * nextf
 */

static Ardirent_t*
omfnext(Ardir_t* ar)
{
	State_t *ap = (State_t*)ar->data;
	Arfile_t *fp;
	if(ap->next)
		ap->next = (Arfile_t*)dtnext(ap->dict,ap->next);
	else
	{
		dtdisc(ap->dict, &offdisc, 0);
		ap->next = (Arfile_t*)dtfirst(ap->dict);
	}
	if(!(fp = ap->next))
		return(0);
	if(*fp->suffix)
	{
		char *last = strrchr(fp->st.name,'.');
		if(last)
			last++;
		else
			last = fp->st.name+strlen(fp->st.name);
		memcpy((void*)last,(void*)fp->suffix,sizeof(fp->suffix));
	}
	if(!(ar->flags&ARDIR_FORCE) && fp->alias)
		fp->st.name = fp->alias;
	return(&fp->st);
}


/*
 * returns -1 for error
 * returns 0 for ignore
 * returns ARDIR_CREATE add
 * returns ARDIR_REPLACE replace
 * returns ARDIR_DELETE delete
 */
int omfinsert(Ardir_t *ar, const char *name, int op)
{
	char *suffix,fname[256];
	Dt_t *dp = ((State_t*)ar->data)->dict;
	int m,n,fd;
	Arfile_t *fp;
	struct stat statb;
	int ret = -1;
	if(suffix=strrchr(name,'.'))
		n = (const char*)++suffix - name;
	else
		n = strlen(name);
	if((fd=open(name,O_RDONLY))>=0)
	{
		if(fstat(fd,&statb)>=0 && (m=is_omf(fd)))
		{
			read(fd,(void*)fname,m);
			fname[m] = 0;
			m = memcmp((void*)fname,(void*)name,n);
			ret = ARDIR_CREATE;
		}
		close(fd);
	}
	fp = (Arfile_t*)dtmatch(dp,(void*)fname);
	if(ret<0 && !((op&ARDIR_DELETE) && fp))
		return -1;
	if(fp)
	{
		if(op&ARDIR_DELETE)
		{
			dtdelete(dp,(Void_t*)fp);
			return ARDIR_DELETE;
		}
		if(fp->st.mtime>= statb.st_mtime && (op&ARDIR_NEWER))
			return 0 ;
		ret = ARDIR_REPLACE;
	}
	else
	{
		if(op&ARDIR_DELETE)
			return -1 ;
		if(!(fp = (Arfile_t*)ar_getnode(ar,fname)))
			return -1 ;
	}
	fp->st.mode = statb.st_mode&(S_IRWXU|S_IRWXG|S_IRWXO);
	fp->st.uid = statb.st_uid;
	fp->st.gid = statb.st_gid;
	fp->st.mtime = statb.st_mtime;
	fp->st.size = (size_t)statb.st_size;
	fp->st.offset = ++ar->st.st_size;
	if(m)
		fp->alias = (char*)name;
	if(suffix)
		memcpy(fp->suffix,suffix,sizeof(fp->suffix));
	return(ret);
}

const char *omfspecial(Ardir_t *ar)
{
	return(FILENAME ".o");
}

/*
 * changef
 */

static int
omfchange(Ardir_t* ar, Ardirent_t* ent)
{
	State_t *sp = (State_t*)ar->data;
	Arfile_t *fp = (Arfile_t*)ent;
	fp->flags = 1;
	sp->state = 1;
	return 0;
}

Ardirmeth_t ar_omf =
{
	"omf",
	"omf archive",
	omfopen,
	omfnext,
	omfchange,
	omfinsert,
	omfspecial,
	omfclose,
	ar_omf_next
};
