/***********************************************************************
  This file is part of HA, a general purpose file archiver.
  Copyright (C) 1995 Harri Hirvola

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  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.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
************************************************************************
	HA archive handling
***********************************************************************/

/* -----------------
    This file contains small changes by Johannes Schwabe to fix handling
    of long filenames. date: January 1995.
    Also, delold() was hacked to ignore case (strcmp -> stricmp). Will only
    occur if compiled with -DOS2.
   ------------------ */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "error.h"
#include "ha.h"
#include "archive.h"
#include "haio.h"

#define STRING	300

int arcfile=-1;
char *arcname=NULL;
struct stat arcstat;
static unsigned arccnt=0;
static int dirty=0,addtries;
static U32B nextheader=4,thisheader,arcsize,bestpos,trypos;
static Fheader newhdr;

static U32B getvalue(int len) {

    unsigned char buf[4];
    U32B val;
    int i;
    
    if (read(arcfile,buf,len)!=len) error(1,ERR_READ,arcname);
    for (val=i=0;i<len;++i) val|=(U32B)buf[i]<<(i<<3);
    return val;
}

static void putvalue(U32B val, int len) {

    unsigned char buf[4];
    int i;
    
    for (i=0;i<len;++i,val>>=8) buf[i]=(unsigned char) val&0xff;
    if (write(arcfile,&buf,len)!=len) error(1,ERR_WRITE,arcname);
}

static char *getstring(void) {
    
    char *sptr;
    int offset;
    
    if ((sptr=malloc(STRING))==NULL) error(1,ERR_MEM,"getstring()");
    for (offset=0;;offset++) {
	if (read(arcfile,sptr+offset,1)!=1) error(1,ERR_READ,arcname);
	if (sptr[offset]==0) break;
/*	if ((offset&(STRING-1))==0) {
	    if ((sptr=realloc(sptr,STRING))==NULL) 
	      error(1,ERR_MEM,"getstring()");}*/
/* BUG FIX: This is ERRORNEOUS */
    }
    return sptr;
}

static void putstring(char *string) {
    
    int len;

    len=strlen(string)+1;
    if (write(arcfile,string,len)!=len) error(1,ERR_WRITE,arcname);
}

static Fheader *getheader(void) {

    static Fheader hd={0,0,0,0,0,0,NULL,NULL,0};

    if ((hd.ver=getvalue(1))!=0xff) {
	hd.type=hd.ver&0xf;
	hd.ver>>=4;
	if (hd.ver>MYVER) error(1,ERR_TOONEW);
	if (hd.ver<LOWVER) error(1,ERR_TOOOLD);
	if (hd.type!=M_SPECIAL && hd.type!=M_DIR && hd.type>=M_UNK) 
	  error(1,ERR_UNKMET,hd.type);
    }
    hd.clen=getvalue(4);
    hd.olen=getvalue(4);
    hd.crc=getvalue(4);
    hd.time=getvalue(4);
    if (hd.path!=NULL) free(hd.path);
    hd.path=getstring();
    if (hd.name!=NULL) free(hd.name);
    hd.name=getstring();
    hd.mdilen=(unsigned)getvalue(1);
    hd.mylen=hd.mdilen+20+strlen(hd.path)+strlen(hd.name);
    md_gethdr(hd.mdilen,hd.type);
    return &hd;
}

static void putheader(Fheader *hd) {

    putvalue((hd->ver<<4)|hd->type,1);
    putvalue(hd->clen,4); 
    putvalue(hd->olen,4); 
    putvalue(hd->crc,4); 
    putvalue(hd->time,4); 
    putstring(hd->path);
    putstring(hd->name);
    putvalue(hd->mdilen,1);
    md_puthdr();
}	

static void arc_clean(void) {

    U32B ipos,opos,cpylen;
    int len;
    unsigned cnt;
    Fheader *hd;
    
    ipos=opos=4;
    for (cnt=arccnt;cnt;--cnt) {
	if (lseek(arcfile,ipos,SEEK_SET)<0) error(1,ERR_SEEK,"arc_clean()");
	for (;;) {
	    hd=getheader();
	    if (hd->ver!=0xff) break;
	    ipos+=hd->clen+hd->mylen;
	    if (lseek(arcfile,ipos,SEEK_SET)<0) 
	      error(1,ERR_SEEK,"arc_clean()");
	} 
	if (ipos==opos) ipos=opos+=hd->clen+hd->mylen;
	else {
	    cpylen=hd->clen+hd->mylen;
	    while (cpylen) {
		if (lseek(arcfile,ipos,SEEK_SET)<0) 
		  error(1,ERR_SEEK,"arc_clean()");
		len=read(arcfile,ib,BLOCKLEN>cpylen?(unsigned)cpylen:BLOCKLEN);
		if (len<=0) error(1,ERR_READ,arcname);
		cpylen-=len;
		ipos+=len;
		if (lseek(arcfile,opos,SEEK_SET)<0) 
		  error(1,ERR_SEEK,"arc_clean()");
		if (write(arcfile,ib,len)!=len) error(1,ERR_WRITE,arcname);
		opos+=len;
	    }
	}
    }
    md_truncfile(arcfile,opos);
}

void arc_close(void) {
    
    if (arcfile>=0) {
	if (dirty) arc_clean();
	close(arcfile);
	if (!arccnt) {
	    if (remove(arcname)) error(1,ERR_REMOVE,arcname);
	}
    }
}

static U32B arc_scan(void) {
    
    U32B pos;
    unsigned i;
    Fheader *hd;
    
    pos=4;
    for (i=0;i<arccnt;++i) {
	if (pos>=arcsize) {
	    error(0,ERR_CORRUPTED);
	    arccnt=i;
	    return pos;
	}
	if (lseek(arcfile,pos,SEEK_SET)<0) error(1,ERR_SEEK,"arc_seek()");
	hd=getheader();
	pos+=hd->clen+hd->mylen;
	if (hd->ver==0xff) {
	    dirty=1;
	    --i;
	}
    }
    if (pos!=arcsize) dirty=1;
    return pos;
}

void arc_open(char *aname,int mode) {
    
    char id[2];
    
    dirty=0;
    arcname=md_arcname(aname);
    if ((arcfile=open(arcname,(mode&ARC_RDO)?AO_RDOFLAGS:AO_FLAGS))>=0) {
	if (fstat(arcfile,&arcstat)!=0) error(1,ERR_STAT,arcname);
	arcsize=arcstat.st_size;
	if (read(arcfile,id,2)!=2 || id[0]!='H' || id[1]!='A') {
	    error(1,ERR_NOHA,arcname);
	}
	arccnt=(unsigned)getvalue(2);
	arcsize=arc_scan();
	if (!quiet) printf("\nArchive : %s (%d files)\n",arcname,arccnt);
    }
    else if ((mode&ARC_NEW) && (arcfile=open(arcname,AC_FLAGS))>=0) {
	if (fstat(arcfile,&arcstat)!=0) error(1,ERR_STAT,arcname);
	if (!quiet) printf("\nNew archive : %s\n",arcname);
	if (write(arcfile,"HA\000",4)!=4) error(1,ERR_WRITE,arcname);
	arccnt=0;
	arcsize=4;
    }
    else error(1,ERR_ARCOPEN,arcname);
    cu_add(CU_FUNC,arc_close);
}

void arc_reset(void) {			

    nextheader=4;
}

Fheader *arc_seek(void) {

    static Fheader *hd;
    
    for (;;) {
	if (nextheader>=arcsize) return NULL;
	if (lseek(arcfile,nextheader,SEEK_SET)<0) 
	  error(1,ERR_SEEK,"arc_seek()");
	hd=getheader();
	thisheader=nextheader;
	nextheader+=hd->clen+hd->mylen;
	if (hd->ver==0xff) dirty=1;
	else if (match(hd->path,hd->name)) return hd;
    }
}

void arc_delete(void) {
    
    if (lseek(arcfile,thisheader,SEEK_SET)<0) 
      error(1,ERR_SEEK,"arc_delete()");	
    if (write(arcfile,"\xff",1)!=1) error(1,ERR_WRITE,arcname);
    if (lseek(arcfile,2,SEEK_SET)<0) error(1,ERR_SEEK,"arc_delete()");	
    putvalue(--arccnt,2);	
    dirty=1;
}

void arc_newfile(char *mdpath, char *name) {
    
    newhdr.ver=MYVER;
    newhdr.olen=md_curfilesize();
    newhdr.time=md_curfiletime();
    newhdr.path=md_tohapath(mdpath);
    newhdr.name=name;
    newhdr.mdilen=md_newfile();
    newhdr.mylen=newhdr.mdilen+20+strlen(newhdr.path)+strlen(newhdr.name);
    bestpos=trypos=arcsize+newhdr.mylen;
    addtries=0;
    dirty|=2;
}

void arc_accept(int method) {

    bestpos=trypos;
    newhdr.type=method;
    trypos+=newhdr.clen=ocnt;
    newhdr.crc=getcrc();
}

void arc_trynext(void) {

    if (lseek(arcfile,trypos,SEEK_SET)<0) error(1,ERR_SEEK,"arc_trynext()");
    if (addtries++) dirty=1;
}

static void delold(void) {

    U32B pos,oldpos;
    unsigned i;
    Fheader *hd;
    
    pos=4;
    for (i=arccnt;i>0;--i) {
	if (pos>=arcsize) break;
	if (lseek(arcfile,pos,SEEK_SET)<0) error(1,ERR_SEEK,"delold()");
	hd=getheader();
	oldpos=pos;
	pos+=hd->clen+hd->mylen;
	if (hd->ver==0xff) {
	    dirty=1;
	    ++i;
	}
	else {
#ifdef OS2
/* MODIFICATION: strcmp replaced by stricmp for OS/2 */
	    if (!stricmp(md_strcase(hd->path),newhdr.path) && 
		!stricmp(md_strcase(hd->name),newhdr.name)) {
#else
            if (!strcmp(md_strcase(hd->path), newhdr.path) &&
                !stricmp(md_strcase(hd->name),newhdr.name)) {
#endif
		if (lseek(arcfile,oldpos,SEEK_SET)<0) 
		  error(1,ERR_SEEK,"delold()");
		if (write(arcfile,"\xff",1)!=1) error(1,ERR_WRITE,arcname);
		dirty=1;
		--arccnt;
	    }
	}
    }
}

int arc_addfile(void) {
    
    U32B basepos,len;
    int cplen;
    
    if ((basepos=arcsize+newhdr.mylen)!=bestpos) {
	if (lseek(arcfile,basepos,SEEK_SET)<0) 
	  error(1,ERR_SEEK,"arc_addfile()");
	len=newhdr.clen;
	while (len) {
	    if (lseek(arcfile,bestpos,SEEK_SET)<0) 
	      error(1,ERR_SEEK,"arc_addfile()");
	    cplen=BLOCKLEN>len?(int)len:BLOCKLEN;
	    if (read(arcfile,ib,cplen)!=cplen) error(1,ERR_READ,arcname);
	    len-=cplen;
	    bestpos+=cplen;
	    if (lseek(arcfile,basepos,SEEK_SET)<0) 
	      error(1,ERR_SEEK,"arc_addfile()");
	    if (write(arcfile,ib,cplen)!=cplen) error(1,ERR_WRITE,arcname);
	    basepos+=cplen;
	}
    }
    if (lseek(arcfile,arcsize,SEEK_SET)<0) error(1,ERR_SEEK,"arc_addfile()");
    putheader(&newhdr);
    dirty&=1;
    delold();
    ++arccnt;
    arcsize+=newhdr.mylen+newhdr.clen;
    if (lseek(arcfile,2,SEEK_SET)<0) error(1,ERR_SEEK,"arc_addfile()");
    putvalue(arccnt,2);
    return 1;
}

int arc_adddir(void) {
    
    newhdr.type=M_DIR;
    newhdr.olen=newhdr.clen=0;
    newhdr.crc=0;
    
    if (lseek(arcfile,arcsize,SEEK_SET)<0) error(1,ERR_SEEK,"arc_adddir()");
    putheader(&newhdr);
    dirty&=1;
    delold();
    ++arccnt;
    arcsize+=newhdr.mylen;
    if (lseek(arcfile,2,SEEK_SET)<0) error(1,ERR_SEEK,"arc_adddir()");
    putvalue(arccnt,2);
    return 1;
}

int arc_addspecial(char *fullname) {

    unsigned char *sdata;
    
    newhdr.type=M_SPECIAL;
    newhdr.olen=newhdr.clen=md_special(fullname, &sdata);
    newhdr.crc=0;
    
    if (lseek(arcfile,arcsize,SEEK_SET)<0) 
      error(1,ERR_SEEK,"arc_addspecial()");
    putheader(&newhdr);
    if (newhdr.clen!=0) {
	if (lseek(arcfile,arcsize+newhdr.mylen,SEEK_SET)<0) 
	  error(1,ERR_SEEK,"arc_addspecial()");
	if (write(arcfile,sdata,newhdr.clen)!=newhdr.clen) 
	  error(1,ERR_WRITE,arcname);
    }
    dirty&=1;
    delold();
    ++arccnt;
    arcsize+=newhdr.mylen+newhdr.clen;
    if (lseek(arcfile,2,SEEK_SET)<0) error(1,ERR_SEEK,"arc_addspecial()");
    putvalue(arccnt,2);
    return 1;
}
























